TON Jetton vs ERC-20: Security Pitfalls Compared
TON jetton security vs ERC-20: why fake jetton-wallet notifications and bounce-message draining are exploit classes EVM auditors never see.
Two Token Standards, Two Threat Models
ERC-20 and TON jettons both claim to do the same job — let a contract represent a fungible token — but they solve it with opposite architectures, and that difference is where most of the interesting bugs live. ERC-20 keeps one contract holding a mapping(address => uint256) for every balance. Every transfer is a function call into that single contract, and the EVM's synchronous execution model means you can trace a transfer start to finish in one transaction. TON jettons do the opposite: every holder gets their own jetton-wallet contract, deployed from the jetton-master, and a "transfer" is actually a chain of asynchronous internal messages bouncing between two or three separate contracts. That's the actor model, and it's why an auditor who's only ever reviewed EVM code walks into a TON codebase and misses an entire category of bugs.
I've reviewed both, and the honest answer is neither is "more secure" in the abstract. They just fail differently.
How ERC-20 Actually Breaks
The ERC-20 attack surface is well-mapped at this point, but teams still ship the same three mistakes:
- The approve/transferFrom race. Changing an allowance from N to M without zeroing it first lets a watching attacker front-run the new approval and spend both amounts.
increaseAllowance/decreaseAllowancepatch this, but plenty of 2024-2026 codebases still use rawapprove. - Missing return-value checks. Some tokens (
USDTfamously) don't returnboolontransfer, they just revert or silently do nothing. A contract that doestoken.transfer(to, amount)without usingSafeERC20can treat a failed transfer as successful. - Fee-on-transfer and rebasing tokens breaking invariants. If your vault assumes
amountIn == amountReceived, a deflationary token will quietly desync your accounting until someone drains the difference.
None of this requires understanding anything beyond "one contract, one storage slot per balance." That's the appeal of ERC-20 for auditors — the state is centralized and synchronous, so you can reason about a transfer as a single atomic operation. Reentrancy is possible if the token has hooks (ERC-777-style tokensReceived), but plain ERC-20 has no callback into the recipient, which closes off a whole class of reentrancy bugs by design.
How Jettons Actually Break
Jettons don't have a global balance mapping. TEP-74 splits state across contracts: the jetton-master tracks total supply and mints, and each owner's balance lives in their own jetton-wallet contract, deployed deterministically from (jetton_master_address, owner_address, jetton_wallet_code). A transfer is: owner's wallet debits itself, sends an internal message to the recipient's wallet, which credits itself and optionally forwards a transfer-notification message to the recipient's contract if it's a smart contract (like a DEX pool or vault).
This is where the two TON-specific exploit classes an EVM auditor won't have muscle memory for come in.
1. Fake jetton wallet notifications. A contract that accepts jetton deposits — say, a swap pool — typically listens for a transfer_notification message and credits the sender based on the jetton_amount field in that message. The catch: anyone can deploy any contract and send it a message shaped exactly like a transfer_notification. If the receiving contract doesn't verify that msg.sender is the specific jetton-wallet address that would be deployed for that jetton-master and that owner, an attacker just deploys a look-alike contract, sends a fake notification claiming a 10,000 USDT deposit, and the pool credits them for tokens that were never moved. This is the single most common finding I see in TON DeFi audits — it's the rough equivalent of an ERC-20 contract trusting msg.sender claims about balances instead of reading balanceOf.
The fix is always the same: recompute the expected jetton-wallet address from its state init on-chain and compare.
;; Verify the incoming message is really from OUR jetton wallet,
;; not an attacker-deployed lookalike.
cell expected_state_init = calculate_jetton_wallet_state_init(
my_address(), jetton_master_address, jetton_wallet_code
);
slice expected_wallet_addr = calculate_address(0, expected_state_init);
throw_unless(707, equal_slices(sender_address, expected_wallet_addr));
Skip this check and your "deposit" flow is a free-mint bug.
2. Bounce-message draining. Internal TON messages can bounce: if the receiving contract throws (out of gas, wrong opcode, whatever), and the message was sent with the bounce flag set, TON automatically returns the message body to the sender with the bounced bit flipped. A jetton-wallet transfer that debits the sender's balance before confirming the recipient actually accepted it has to handle that bounce correctly, restoring the debited amount. Contracts that don't implement bounced() handling — or implement it naively — leave a window where a transfer can be debited on one side, bounce, and either strand the tokens in limbo or, worse, let an attacker script repeated failed transfers that drain the gas reserve funding the bounce round-trips, a slow griefing attack that doesn't show up in a quick functional test. There's no equivalent primitive on EVM: a failed call just reverts the whole transaction, state and all, in one atomic step. TON's asynchronous, multi-message nature means partial-failure states are a first-class thing you must design for, not an edge case.
Side by Side
| ERC-20 (EVM) | Jetton (TON) | |
|---|---|---|
| Balance storage | Single contract, one mapping | Per-holder wallet contract |
| Transfer execution | Synchronous, atomic | Async, multi-message chain |
| Failure mode | Revert (all-or-nothing) | Bounce (partial state possible) |
| Trust assumption to verify | Return value / allowance state | Sender is the real jetton wallet (state-init check) |
| Common exploit | approve race, fee-token desync | fake wallet notification, bounce drain |
| Reentrancy surface | Only via hook-token callbacks | Inherent to message-passing model |
| Gas/fee risk | Gas estimation failure | Insufficient forward_ton_amount drops notifications |
Which to Pick When
If you're building on EVM and sticking to plain ERC-20 with SafeERC20 and OpenZeppelin's tested implementations, your main job is checking integration points — how other contracts consume your token, not the token contract itself. That's a comparatively contained audit scope.
If you're building on TON, budget more review time for every contract that receives jettons, not just the jetton-wallet code. The wallet-per-holder architecture is genuinely better for parallelism and avoids EVM's global-lock bottleneck, but it pushes verification responsibility onto every downstream contract in the message chain. A TON audit that only reads the jetton-wallet source and skips the receiving vault's sender-verification logic is an incomplete audit — and I'd argue that's the single biggest gap I see in solo or first-pass reviews of Tact and FunC jetton integrations, a point covered in more depth in our FunC vs Tact security comparison.
Neither standard is inherently riskier; they just require different mental models. Teams shipping on both chains should treat "read the whole message flow" as a TON-specific checklist item the way "check the approve pattern" is an EVM-specific one — and if you're weighing whether that check happens in-house or externally, it's worth reading our take on in-house review versus third-party audits for TON contracts before deciding. For teams still choosing tooling, our breakdown of manual audits versus automated scanners across Solana and EVM covers why static analyzers still miss most jetton-specific logic bugs.
None of this is theoretical — fake jetton wallet notifications alone have drained real TON pools, and it's the first thing we check in a smart contract audit engagement, alongside a full code review of every message handler that touches jettons. If you're mid-build and want a second pair of eyes before mainnet, a strategy consultation is a low-cost way to catch architecture-level issues before they become audit findings.
Get a smart contract audit before your jetton integration goes live — the fake-wallet check takes five minutes to add and an incident to regret skipping.
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