Simulation
How each step is built and simulated before you sign, what is checked on the result, and how conversions are priced.
The preview builds every step that carries its own transaction (hydratePlanTransactions in
src/lib/plan/hydration.ts) and then runs each node through simulateNode in
src/lib/plan/simulator.ts, in topological order. Soroban steps go to the RPC; classic steps are
built and validated in memory.
Building the steps
Hydration attaches an unsigned transaction to each Soroban step and to the two trustline steps. The source account is loaded once, and every build gets a copy pinned to that sequence, so each transaction carries exactly the on-chain sequence plus one.
Aquarius and Soroswap pool withdraws are built twice. The first build has zero minimums and is simulated to read the expected payout per token; the second build signs those amounts less the slippage tolerance. An exit whose expected amounts cannot be read is not built, so no exit is signed without a floor.
A step with no dependencies that cannot be built is marked failed with its reason. A step that runs
after other steps (a collateral withdraw behind its repay, a sweep behind the exits that pay it) is
built here against state those steps have not changed yet, so a failure only defers it: it stays
pending with deferred set and is built at execute time once they land. A refused conversion and
a backstop emissions claim that cannot run fail either way, because they need the account owner's answer.
Soroban steps
Each built transaction goes to the RPC's simulateTransaction. On success the outcome records:
| Field | Use |
|---|---|
retval | The simulated return value |
minResourceFee | The resource fee the step pays |
maxFee | The fee the built transaction authorizes |
transactionData | The ledger footprint |
latestLedger | The ledger the simulation ran against |
auth | The authorization entries |
restorePreambleRequired | Whether the RPC reported a restore preamble |
A failed simulation raises SimulationFailedError with the node id and the RPC's error text. A
transport failure is retried up to three times with a growing delay; a simulation that ran and
failed is an answer and is not retried. A deferred step raises NodeNotBuiltError with
deferred set and stays pending.
The trustline steps are classic change_trust transactions: the built envelope is the check, and
their fee is reported as a classic fee.
Checks on the simulated steps
refuseUnsafeSteps runs the sign-time checks on every simulated required step: the allow-listed
root contract, exactly the authorization tree its call needs, and the fee cap for its class. A step
that fails is marked failed with the reason. The token transfer and the allowance revoke are
checked by their own guards when they are signed. Every failed required step becomes a
confirmation blocker, so confirming is disabled until the plan is rebuilt.
See Contract allow-list for the three checks.
The classic batches
FinalClassicTx and ClassicUnwind are not sent to the RPC. Every batch is built in memory against
a synthetic source account with sequence zero, at the fee bid the executor will start from and
with time bounds from the latest ledger. buildClassicTransaction throws on an empty or malformed
operation set and on a total fee above the u32 ceiling, so a broken close surfaces in the review.
The outcome's operation count, transaction count and fee cover every batch.
The mediator forward
The forward is built in the browser at execute time, against the forwarding account's live
sequence once the merge into it has landed; the server only co-signs it. The preview checks that
the node carries the forwarding account, the destination and the flow token, and reports 2
operations priced at the classic starting bid per operation, the same fee_stats bid the classic
batches use, capped at 1,000,000 stroops per operation. The forward raises its bid on a refusal up
to MAX_FORWARD_FEE (2,000,000 stroops, 0.2 XLM), and the review shows that as its most.
Fees on the review
src/app/demolish/_lib/fees.ts gives each step two figures, and the review adds them up:
| Step | Usual fee | Most it can be signed for |
|---|---|---|
| A classic batch or a trustline step | The built bid | 1,000,000 stroops per operation, the executor's ceiling |
| The mediator forward | Two operations at the classic starting bid | MAX_FORWARD_FEE, 2,000,000 stroops |
| A simulated Soroban step | The fee its built transaction authorizes | The inclusion fee plus the resource fee cap of its class (sorobanFeeCeiling) |
| A Soroban step built at execute | None: it has no transaction yet | The inclusion fee plus the resource fee cap of its class |
A Soroban step is rebuilt and simulated again right before it is signed, so the fee it is signed for can differ from the preview's, and the executor never signs it above its class's cap. That cap is why it is the step's most. A step built only at execute time adds nothing to the usual figure: the total reads about ... XLM plus N steps priced when they are built, at most ... XLM, and its row reads fee up to ... XLM.
Pricing conversions
resolveCreditPaths quotes each credit balance, plus what the close claims into it, with Horizon's
strict-send path search to XLM, keeping the best path of at most five hops. Alongside it, the same
search quotes a reference trade of one thousandth of the amount. The price impact is how far the
full-size rate sits below the reference rate, in basis points, rounded up.
- An impact over 200 basis points (2%) is refused unless the account owner accepted it for that asset.
- A balance with no reference quote is unpriced and refused unless the account owner accepted it.
- A path whose output after slippage nets at or below
NEXT_PUBLIC_MIN_MARKET_OUT_STROOPSis dropped, so the asset falls through to the issuer or destination choice. - At execute time, a fresh reference or full-size rate worse than the reviewed one by more than the slippage tolerance is refused, accepted or not.
The held-token swap through the Soroswap router uses the same cap, measured against a router quote for one thousandth of the amount, and the same refusal of a fresh quote worse than the reviewed one.
Re-simulation at execution time
The preview's transactions are not the ones that get signed. Before each Soroban step is signed, the executor rebuilds it from a fresh sequence number, a fresh simulation and a fresh time bound, runs the checks again, and only then signs. See Execution and recovery.