Transaction Lifecycle: What Actually Happens After You Click Swap A swap transaction isn't one event — it's a state machine, moving through distinct, named phases between the moment a user ta
Transaction Lifecycle: What Actually Happens After You Click Swap
A swap transaction isn't one event — it's a state machine, moving through distinct, named phases between the moment a user taps "confirm" and the moment funds actually settle. This piece traces that lifecycle stage by stage on STON.fi, with the actual code shape at each transition, because this is genuinely an engineering topic underneath the trading language, and it deserves to be read as one.
🗨️ "Simulate the swap to obtain routing metadata (expected amounts, vault info, and the full router object). Feed simulationResult.router directly into dexFactory() to build contracts dynamically." — STON.fi, SDK v2 Swap Documentation
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
🎞️ The Six States a Transaction Actually Passes Through

Before the code, the shape of the lifecycle itself:
→ Simulated — a live calculation against current reserves, no commitment yet → Parameterized — the simulation's output converted into concrete transaction fields → Handed off — passed to the wallet layer for review → Signed — the trader's actual commitment point → Broadcast — submitted to the network with a built-in expiration → Settled — atomically completed, or reverted, with no state in between
Every one of these is a distinct, inspectable moment. Treating "confirm swap" as one atomic user action hides that six separate things have to happen correctly, in order, for that action to actually succeed.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
State One: Simulated
Nothing here is final, and that's the entire point of this stage existing separately from what follows.
const simulationResult = await api.simulateSwap({ offerAddress: offerJetton, askAddress: askJetton, offerUnits: amount, slippageTolerance: '0.01', }); // simulationResult now carries: expected output, fee breakdown, and // simulationResult.router — the current router object, not a saved address
This step can run any number of times, freely, with zero cost — which is precisely why it's a poor idea to cache and reuse an old simulation result rather than re-running it right before the next stage.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
⚙️ State Two: Parameterized
The simulation's output gets converted into the actual fields a transaction needs — this is where "approximately what you'll get" becomes "exactly what will be enforced on-chain."
const router = dexFactory(simulationResult.router); // never hardcoded const txParams = await router.getSwapJettonToJettonTxParams({ userWalletAddress: userAddress, offerJettonAddress: offerJetton, askJettonAddress: askJetton, offerAmount: amount, minAskAmount: calculateMinAskAmount(simulationResult.askUnits, slippageBps), queryId: generateQueryId(), });
The minAskAmount field is the crux of this state: it's the moment a slippage percentage typed into a UI becomes a specific integer that a smart contract will actually check against.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
🖊️ State Three: Handed Off, Then Signed
These are technically two states, but they happen across a single user-facing moment — the wallet prompt — so it's worth treating them together.
const result = await tonConnectUI.sendTransaction({ validUntil: Math.floor(Date.now() / 1000) + 300, messages: [ { address: txParams.to.toString(), amount: txParams.value.toString(), payload: txParams.body.toBoc().toString('base64'), }, ], });
Everything before this call was recalculable and free. sendTransaction is where that stops being true — the user's wallet signature is the actual, irreversible commitment point in the entire lifecycle. Before it, the transaction is a proposal. After it, it's a request the network will actually act on.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
📤 State Four: Broadcast
Once signed, the payload — a base64-encoded BOC, TON's native cell serialization format — actually leaves the client and enters the network. The validUntil field set in the previous state now becomes active protection:
This is also the point where, for a same-chain swap, TON's transaction model takes over: the transaction will either execute completely or fail completely. There's no documented partial-execution state at this layer — a multi-chunk route either lands as calculated, chunk by chunk, or the whole thing reverts.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
🔁 State Five: Settlement — and Where Cross-Chain Genuinely Diverges
For a same-chain trade, settlement is close to instantaneous once the transaction is included in a block. For a cross-chain trade, this state gets meaningfully more complex, because TON's own atomicity guarantee doesn't extend to a second, separate blockchain automatically.
This is why a cross-chain swap through STON.fi's Omniston takes measurably longer than a same-chain one — it's not inefficiency, it's a deliberate trade of speed for a correctness guarantee that two independent blockchains don't provide each other on their own.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
🎞️ Watching the State Machine From the Outside
A well-built integration doesn't just fire a transaction and hope — it subscribes to the lifecycle's own status stream rather than polling a balance and guessing.
omniston.trackTrade({ rfqId }).subscribe((event) => { switch (event.state) { case 'filled': confirmSuccess(event); break; case 'partiallyFilled': // Reflects the quote-matching stage, not a broken on-chain transaction — // some connected liquidity didn't fully match before execution handlePartialFill(event); break; case 'aborted': showRetryOption(event); break; } });
Treating partiallyFilled as equivalent to a crashed transaction is a common misread — it describes what happened during quote-matching among resolvers and pools, before final on-chain execution, not a submitted transaction that stopped halfway.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
⚖️ Comparing on the Dimensions That Actually Matter
💧 Free states vs. committed states. Everything through "parameterized" costs nothing and binds nobody. Everything from "signed" onward is real. Conflating these two categories is the most common source of confusion about what a "failed" swap actually means at each point.
🧭 Same-chain settlement vs. cross-chain settlement. One relies on native blockchain atomicity and settles quickly. The other layers HTLC-based correctness on top, deliberately trading speed for a guarantee TON alone can't provide across a chain boundary.
⏱️ Polling vs. subscribing. Manually checking a wallet balance after broadcasting is a weak signal — the trade could be pending, reverted, or still routing. Subscribing to trackTrade gets an explicit state instead of an inference.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
✅ What Genuinely Makes This Lifecycle Safe to Build On
- Every state before signature is inspectable and repeatable. A developer — or a curious user — can trigger and re-trigger simulation and parameter-building without any real-world consequence.
- The transaction's own fields, not external assumptions, enforce the guarantees. minAskAmount and validUntil are baked into the payload itself, not just displayed in a UI.
- Status is streamed, not inferred. trackTrade reports real states instead of forcing an integration to guess from a balance check.
⚠️ What's Worth Understanding Correctly
- "Broadcast" and "settled" are not the same state. A transaction can be genuinely on the network and still fail atomically at settlement if conditions shifted enough.
- partiallyFilled belongs to quote-matching, not execution. Once a transaction actually broadcasts on TON, it's binary — complete or reverted — with this status reflecting an earlier stage.
- Cross-chain lifecycle length is a designed tradeoff. The extra time isn't latency to optimize away; it's the HTLC window that prevents funds from ever being stuck between two chains.
🏁 Bottom Line
"What happens after you click swap" is really six distinct, inspectable states — simulated, parameterized, handed off, signed, broadcast, and settled — each with its own guarantees and its own failure mode. Building against this lifecycle explicitly, rather than treating a swap as one opaque action, is what separates an integration that degrades gracefully under real-world conditions from one that just breaks silently the first time a pool's reserves shift at the wrong moment.
This article reflects independent research based on STON.fi's public developer documentation as of mid-2026. Code examples are simplified for clarity and are not a verbatim copy of the official SDK. Transaction flow and protocol mechanics evolve as the system ships updates — always verify current functionality directly on docs.ston.fi before shipping a production integration.