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.
| Kind | What it does | Own transaction |
|---|---|---|
RevokeAllowance | Sets a SEP-41 approval to zero | Soroban |
ClassicUnwind | Cancels offers and withdraws classic pool shares that hold a repay asset | Classic batches |
RepayBlend | Repays a Blend liability | Soroban |
WithdrawBlend | Withdraws Blend collateral or supply | Soroban |
WithdrawAquarius | Withdraws an Aquarius standard pool share | Soroban |
WithdrawAquariusConcentrated | Withdraws one tick range of an Aquarius concentrated position | Soroban |
WithdrawSoroswapLp | Withdraws a Soroswap LP position | Soroban |
AddBlndTrustline | Opens a BLND trustline for the emissions claims | Classic |
AddExitTrustline | Opens a trustline for a classic asset a pool exit pays out | Classic |
ClaimBlendEmissions | Claims BLND emissions for a pool | Soroban |
ClaimAquariusRewards | Claims Aquarius pool rewards through the router | Soroban |
ClaimAquariusConcentratedFees | Collects the swap fees of a concentrated position | Soroban |
ClaimAquariusGaugeRewards | Claims what a pool's reward gauges owe with gauges_claim | Soroban |
ConvertSorobanToXLM | Swaps a standalone token to XLM through the Soroswap router | Soroban |
TransferAsIs | Sends a standalone token to the destination unchanged | Soroban |
ClaimBackstopEmissions | Claims accrued backstop BLND into backstop shares | Soroban |
BackstopQueue | Queues a Blend backstop withdrawal | Soroban |
BackstopWithdraw | Withdraws an unlocked backstop queue | Soroban |
FinalClassicTx | The batched classic close, ending in the account merge | Classic batches |
MediatorForward | The exchange delivery out of the forwarding account | Mediator |
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.
| Kind | Waits on |
|---|---|
AddBlndTrustline, AddExitTrustline, RevokeAllowance, ClassicUnwind | Nothing |
ClaimAquariusGaugeRewards | The trustline of every token the pool keeps a gauge for, including gauges that owe nothing |
WithdrawAquarius | The pool's gauge claim and the trustlines its tokens need |
ClaimAquariusRewards | The pool's withdraw (standard) and the reward token's trustline |
ClaimAquariusConcentratedFees | The trustlines its tokens need |
WithdrawAquariusConcentrated | The pool's gauge claim, reward and fee claims, and its token trustlines |
WithdrawSoroswapLp | The trustlines its tokens need |
WithdrawBlend (pool without debt) | The trustline its asset needs |
RepayBlend | Every 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 |
ClaimBlendEmissions | That pool's withdraws and the BLND trustline |
ClaimBackstopEmissions, BackstopWithdraw | Nothing |
BackstopQueue | The pool's backstop emissions claim |
ConvertSorobanToXLM, TransferAsIs | Every exit, withdraw, reward claim and backstop payout, since those can pay more of the token |
FinalClassicTx | Every other node except MediatorForward |
MediatorForward | final-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:
- Duplicate ids:
buildPlanTree: duplicate node id "<id>" - Missing dependency targets:
buildPlanTree: node "<id>" depends on missing node id "<depId>" - Cycles:
buildPlanTree: cycle detected in plan dependencies: <a -> b -> a> - 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
FinalClassicTxMetadatarecords 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.memois carried verbatim, because the forward is the only hop that reaches the exchange.ClaimAquariusGaugeRewardsMetadata.routerPoolrecords the pool the pinned router listed when the transaction was last built, which the allow-list check requires.
Next
- The classic batch for what
FinalClassicTxcontains - Simulation for how nodes are built before signing
- Execution and recovery for how the order is walked