Account Demolisher
How it works

The plan graph

The 20 node kinds, the dependency edges between them, how the graph is validated, and how it is ordered.

generatePlan(audit, positions, allowances, destination, options) in src/lib/plan/generator.ts emits a directed acyclic graph of nodes. It makes no network calls; its only impurity is reading the clock to estimate a Blend backstop unlock date, which tests pass in as options.now.

A node is emitted only when the account needs it.

Node kinds

Twenty kinds, declared in src/lib/plan/tree.ts.

KindWhat it doesOwn transaction
RevokeAllowanceSets a SEP-41 approval to zeroSoroban
ClassicUnwindCancels offers and withdraws classic pool shares that hold a repay assetClassic batches
RepayBlendRepays a Blend liabilitySoroban
WithdrawBlendWithdraws Blend collateral or supplySoroban
WithdrawAquariusWithdraws an Aquarius standard pool shareSoroban
WithdrawAquariusConcentratedWithdraws one tick range of an Aquarius concentrated positionSoroban
WithdrawSoroswapLpWithdraws a Soroswap LP positionSoroban
AddBlndTrustlineOpens a BLND trustline for the emissions claimsClassic
AddExitTrustlineOpens a trustline for a classic asset a pool exit pays outClassic
ClaimBlendEmissionsClaims BLND emissions for a poolSoroban
ClaimAquariusRewardsClaims Aquarius pool rewards through the routerSoroban
ClaimAquariusConcentratedFeesCollects the swap fees of a concentrated positionSoroban
ClaimAquariusGaugeRewardsClaims what a pool's reward gauges owe with gauges_claimSoroban
ConvertSorobanToXLMSwaps a standalone token to XLM through the Soroswap routerSoroban
TransferAsIsSends a standalone token to the destination unchangedSoroban
ClaimBackstopEmissionsClaims accrued backstop BLND into backstop sharesSoroban
BackstopQueueQueues a Blend backstop withdrawalSoroban
BackstopWithdrawWithdraws an unlocked backstop queueSoroban
FinalClassicTxThe batched classic close, ending in the account mergeClassic batches
MediatorForwardThe exchange delivery out of the forwarding accountMediator

isSorobanNode is true for every kind that carries its own prebuilt transaction, the two trustline kinds included, and false for ClassicUnwind, FinalClassicTx and MediatorForward, whose transactions the executor builds itself.

Node status

Seven values: pending, simulated, signed, submitted, confirmed, failed, skipped. A node can also carry deferred (built at execute time, see Simulation), simulated (the preview's outcome) and executed (its own receipt).

Dependency edges

A node runs once every dependency is confirmed. A dependency that was skipped counts as done only when it is a best-effort kind (TransferAsIs, ConvertSorobanToXLM, RevokeAllowance) skipped on purpose, not because of its own dependency. MediatorForward runs only after a confirmed merge.

KindWaits on
AddBlndTrustline, AddExitTrustline, RevokeAllowance, ClassicUnwindNothing
ClaimAquariusGaugeRewardsThe trustline of every token the pool keeps a gauge for, including gauges that owe nothing
WithdrawAquariusThe pool's gauge claim and the trustlines its tokens need
ClaimAquariusRewardsThe pool's withdraw (standard) and the reward token's trustline
ClaimAquariusConcentratedFeesThe trustlines its tokens need
WithdrawAquariusConcentratedThe pool's gauge claim, reward and fee claims, and its token trustlines
WithdrawSoroswapLpThe trustlines its tokens need
WithdrawBlend (pool without debt)The trustline its asset needs
RepayBlendEvery exit that pays tokens in without a repay (they can fund it), and ClassicUnwind
WithdrawBlend (pool with debt)That pool's repays and the trustline its asset needs
ClaimBlendEmissionsThat pool's withdraws and the BLND trustline
ClaimBackstopEmissions, BackstopWithdrawNothing
BackstopQueueThe pool's backstop emissions claim
ConvertSorobanToXLM, TransferAsIsEvery exit, withdraw, reward claim and backstop payout, since those can pay more of the token
FinalClassicTxEvery other node except MediatorForward
MediatorForwardfinal-classic-tx

Plans that only prepare

preparePlanFor(positions) returns a PreparePlan when a Blend backstop withdrawal stays locked after the run: shares this run queues, or a queued part that has not unlocked. Such a plan carries prepareOnly, sweeps no standalone tokens, has no FinalClassicTx and no MediatorForward, and buildPlanTree refuses a prepare-only plan that carries a merge.

Exchange closes

A plan with useMediator: true that merges needs both mediatorPublicKey and flowToken, or the generator throws. assertExchangeCloseCanRun refuses a plan whose DeFi exits pay out a token that is not a classic balance the close can sell, because an exchange close forwards only XLM. An exchange close sweeps no standalone tokens.

Node ids

FinalClassicTx and MediatorForward use the literal ids final-classic-tx and mediator-forward, the classic unwind classic-unwind, and the BLND trustline blnd-trustline. Every other id joins its lowercased parts with :, for example blend-repay:<pool>:<asset>, aquarius-withdraw:<tokens...>:<index>, aquarius-cl-withdraw:<pool>:<lower>:<upper>, exit-trustline:<token>, backstop-queue:<pool> and drain-token:<contract>.

Validation

buildPlanTree(nodes, extras) runs these checks, each throwing a plain Error:

  1. Duplicate ids: buildPlanTree: duplicate node id "<id>"
  2. Missing dependency targets: buildPlanTree: node "<id>" depends on missing node id "<depId>"
  3. Cycles: buildPlanTree: cycle detected in plan dependencies: <a -> b -> a>
  4. A prepare-only plan with a merge: buildPlanTree: a prepare-only plan must not carry the final merge

Cycle detection is an iterative depth-first search with three-colour marking and an explicit stack. The reported path starts at the repeated node, so the message reads as the cycle itself.

Ordering

topologicalOrder(tree) is Kahn's algorithm with a FIFO queue. The seed scan and the children lists follow Map insertion order, which is the order generatePlan pushed the nodes, so the order is deterministic. A short result means a cycle and throws topologicalOrder: cycle detected at runtime: produced N of M nodes.

The tree shape

PlanTree holds rootNodes (no dependencies), allNodes (the id-to-node index), and optionally prepareOnly and forfeitedBackstopEmissions (pools whose backstop BLND the account owner agreed to leave unclaimed).

Metadata worth knowing

  • FinalClassicTxMetadata records the reviewed claimable-balance list, the disposal choices, the accepted price-impact keys, the reviewed reference and full-size quotes, the deposit memo and whether the batches carry the close marker. The executor re-batches from a fresh audit with exactly these, so it can never claim a balance the account owner did not review or sell below the reviewed price beyond the slippage tolerance.
  • MediatorForwardMetadata.memo is carried verbatim, because the forward is the only hop that reaches the exchange.
  • ClaimAquariusGaugeRewardsMetadata.routerPool records the pool the pinned router listed when the transaction was last built, which the allow-list check requires.

Next

On this page