TON's Async Architecture vs EVM's Synchronous Execution
TON async vs EVM synchronous execution: how actor-model message passing across shards forces different bot design than atomic EVM calls.
{"excerpt":"TON async vs EVM synchronous execution: how actor-model message passing across shards forces different bot design than atomic EVM calls.","tags":["TON","EVM","smart contracts","bot architecture","comparisons"],"cover":"nodes","content":"A Uniswap V2 arbitrage transaction on Ethereum touches four or five contracts, moves value through two pools, and either lands as one atomic state transition or reverts to zero effect. A comparable arbitrage on TON, hopping between two Jettons through a DEX router, doesn't work that way at all. Each hop is a separate message, sent to a separate contract, processed in a separate transaction, possibly in a separate shard, possibly several blocks apart. If the second hop fails, the first hop already happened. This is the single biggest thing that trips up EVM engineers building on TON for the first time, and it changes bot architecture from the ground up.\n\n## The core split: call vs message\n\nEVM contracts share a call stack. When contract A calls contract B via CALL or a Solidity function call, execution pauses in A, runs in B, and returns control to A with a return value, all inside the same transaction and the same state root. If anything downstream reverts, the EVM unwinds every state change back to the point where the transaction started. That's atomicity, and everything from flash loans to sandwich bots to simple multicall routers depends on it.\n\nTON contracts don't call each other. They send messages. A contract processes an incoming message in its own transaction, updates its own state, and can queue outbound messages to other contracts, but it has no way to block and wait for a reply. The "callee" transaction happens later, possibly in a different shard, with its own gas accounting and its own chance of failing independently. This is the actor model: every contract is an isolated actor with a mailbox, and composition happens through asynchronous message passing, not shared stack frames.\n\n## Why TON can't give you that back\n\nYou can simulate synchronous-looking flows on TON by chaining messages (A sends to B, B sends to C, C sends a result back to A), but you're building a state machine, not calling a function. Each contract in the chain needs to track what state it's in, what query it's answering, and what to do if the next hop never responds or bounces back. TON gives you a query_id field precisely for this correlation problem, and a bounce flag on messages so failed messages can return funds to the sender, but bounce only unwinds one hop. It doesn't cascade backward through the whole chain the way an EVM revert does.\n\n### A worked example: three-hop swap\n\nOn EVM, a three-hop swap router is one function:\n\nsolidity\nfunction swapExactTokensForTokens(\n uint amountIn,\n uint amountOutMin,\n address[] calldata path,\n address to\n) external returns (uint[] memory amounts) {\n // pulls tokens, swaps through path[0]->path[1]->path[2],\n // reverts the entire tx if amountOutMin isn't met\n}\n\n\nIf the final output is below amountOutMin, nothing happened. No approvals were burned, no intermediate tokens are stuck anywhere. We've covered how execution-path choice matters even within a single synchronous chain in our comparison of Jupiter's aggregation against direct Raydium routing; TON adds a whole extra dimension to that same problem because the "path" isn't executed in one breath.\n\nThe TON equivalent is three contracts passing messages, and the router has to explicitly design for partial failure:\n\n\n;; router receives swap request, forwards to pool A\n() recv_internal(...) impure {\n ;; op::swap_hop1\n send_raw_message(build_msg(pool_a_addr, op::do_swap, query_id, ...), 64);\n}\n\n;; pool A, on success, forwards output to pool B with the same query_id\n;; pool A, on failure, bounces to the original sender -- but any\n;; state pool A already committed (fee accrual, LP updates) stays committed\n\n\nIf pool B's swap fails after pool A already executed, you now hold an intermediate token you didn't want, at whatever rate pool A gave you, and you need explicit logic (a follow-up message, a refund path, a "hop failed, swap back" handler) to unwind it. Serious TON DEX routers, STON.fi and DeDust included, spend a large share of their contract logic on exactly this: what happens when hop N+1 fails after hop N already committed.\n\n## Gas, fees, and the cost of hops\n\nEVM gas is paid once, upfront, for the whole call graph, by the transaction sender. TON splits fees per message: a computation fee, a storage fee, and a forward fee for the network to route the message into its destination shard. Every hop in your chain needs enough TON attached to cover its own execution and to forward the next message, and you set this with send_raw_message mode flags before you actually know how much the recipient will need. Under-provision and a mid-chain contract runs out of gas, which triggers its own bounce with its own partial-state problem. Bots that get by fine on EVM with a flat gas estimate need real headroom on every hop on TON, or a fee-estimation pass against each contract in the path.\n\n## MEV and mempool visibility\n\nEVM bots read pending transactions from a public or semi-public mempool and race to land ahead of, behind, or around a target transaction. That's the basis of sandwich attacks and most textbook MEV. TON has no equivalent mempool for pending internal messages: once a message is emitted it's already committed to a transaction, and the "race" that exists is between which shard processes conflicting messages first and how validators order transactions within a block. Front-running tooling built for EVM, the kind that watches pending transactions over a WebSocket or routes through private RPC to dodge sandwiches, doesn't port over conceptually. We've written about the transport side of this on Solana, including how private bundle landing compares to standard RPC broadcast and how gRPC streaming compares to websocket subscriptions for latency-sensitive bots. The mechanics don't transfer to TON, but the underlying lesson does: transport and ordering guarantees shape strategy as much as the strategy logic itself.\n\n## Failure handling: revert vs bounce\n\nThis is worth stating plainly because it's where most bugs live: a revert on EVM is free correctness. A bounce on TON is a feature you have to build. If you're used to wrapping risky logic in a try/catch and trusting the VM to clean up after you, budget real design time for TON's compensation logic: idempotent handlers keyed by query_id, and reconciliation jobs that scan for stuck intermediate states. This exact gap is what shows up during code review and audit passes on TON bots ported from EVM codebases. The swap logic is usually fine; the failure paths were never designed, because the original author never had to think about them on EVM.\n\n## Comparison table\n\n| Dimension | EVM (Ethereum, BSC, Base, etc.) | TON |\n|---|---|---|\n| Execution model | Synchronous call stack | Async actor / message passing |\n| Cross-contract calls | In-tx function call, blocking | Outbound message, non-blocking |\n| Atomicity | Whole tx reverts together | Per-message; partial state possible |\n| Sharding | Single global state (L1) or L2 rollup | Native dynamic sharding, cross-shard messages |\n| Failure recovery | Automatic (revert) | Manual (bounce + compensation logic) |\n| MEV surface | Public/private mempool racing | Shard/validator ordering, no classic mempool |\n| Gas accounting | One estimate for the whole call graph | Per-hop, pre-funded per message |\n| Finality for a multi-hop flow | ~1 block, or instant on most L2s | Multiple blocks across shards |\n\n## Which to pick when\n\nIf your strategy needs atomicity, an arbitrage bot that must guarantee "both legs execute or none do," a liquidation bot racing a single oracle update, or anything that composes third-party contracts you don't control, build it on EVM or an EVM-equivalent chain. The revert guarantee isn't a nice-to-have there; it's the thing that makes the strategy safe to run unattended. The market-making infrastructure we build for venues like Hyperliquid leans on exactly this atomicity for order and hedge logic that has to be all-or-nothing.
Pick TON when the workload actually benefits from its architecture: high-throughput, consumer-facing flows (Telegram-native payments, ticketing, in-game economies) where near-linear horizontal sharding matters more than per-transaction atomicity, and where your team is willing to design explicit state machines for anything multi-step. Don't port an EVM arbitrage bot to TON expecting the same safety guarantees for free. It will look correct in testing and lose funds the first time a mid-chain message bounces after an earlier hop already committed.
If you're deciding between the two for a new bot, or inheriting a TON codebase that was clearly designed by someone thinking in Solidity, get the contract logic and message-flow design audited before you ship it to mainnet."}
Need a bot like this built?
We design, build and run trading bots on Solana, Hyperliquid and Polymarket.
Start a projectMore from the blog
Public vs Paid RPC: How to Run a Fair Benchmark
A fair comparison of public and paid RPC endpoints must account for quotas, workload, freshness and support guarantees.
Read articleRPC Benchmark Percentiles Explained: p50, p95 and p99
Why averages hide the latency spikes that break trading bots, wallets and production blockchain applications.
Read articleHyperliquid REST vs WebSocket Benchmark: What to Measure
A benchmark plan for Hyperliquid market data that separates request latency from streaming freshness and recovery.
Read article