BTC/USD $68,420 +2.8%
ETH/USD $3,540 +1.4%
SOL/USD $142.80 -0.6%
BNB/USD $605.20 +0.9%
XRP/USD $0.62 -1.2%
DOGE/USD $0.18 +5.4%
BTC/USD $68,420 +2.8%
ETH/USD $3,540 +1.4%
SOL/USD $142.80 -0.6%
BNB/USD $605.20 +0.9%
XRP/USD $0.62 -1.2%
DOGE/USD $0.18 +5.4%
DeFi

Router Contracts, Pool Contracts, and Why Their Upgrade Models Differ

Router Contracts, Pool Contracts, and Why Their Upgrade Models Differ In most DeFi architectures, "which contracts are upgradeable" is a governance question. On STON.fi it's an architectural

AnonymousCryptoCompass newsroom
September 20, 2026
7 min read
NEWS
Router Contracts, Pool Contracts, and Why Their Upgrade Models Differ
CryptoCompass editorial visual for defi coverage.
Router Contracts, Pool Contracts, and Why Their Upgrade Models Differ

In most DeFi architectures, "which contracts are upgradeable" is a governance question. On STON.fi it's an architectural one β€” and the answer is unusually clean: the Router can be upgraded, and pools structurally cannot. Understanding why that asymmetry exists, rather than just noting it, explains a surprising amount about how the entire DEX is designed to behave under change.

πŸ—¨οΈ "The router is the only contract that can be upgraded. Each Jetton that goes through the DEX is owned by the router. The router does not store anything about pairs." β€” STON.fi, Architecture documentation

β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”

🧩 Two Contracts, Two Completely Different Jobs

The asymmetry starts with what each contract is actually responsible for:

  • β—† Router β€” the single entrypoint for every DEX call. It owns the Jetton wallets, receives transfer_notification messages, decodes the custom payload, and forwards to the correct pool. It holds no pair-specific state at all.

  • β—‡ Pool β€” holds the AMM state for one specific pair: reserves, fee configuration, the actual pricing math. One pool per pair, each with its own address derived from its own state.

That split is the whole story. The Router is stateless with respect to pairs, which makes it safe to replace. A Pool is nothing but pair state, which makes replacing it a fundamentally different and more dangerous operation.

β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”

βš™οΈ The Detail That Explains Everything: Pool Code Lives Inside Router State

This is the architectural fact most people miss, and it reframes the entire question.

Calling getRouterData() returns the Router's state, and among its fields are poolCode, jettonLpWalletCode, and lpAccountCode:

async getRouterData(provider: ContractProvider): Promise<{ isLocked: boolean; adminAddress: Address; tempUpgrade: Cell; // pending code/admin upgrade, zeros = none pending poolCode: Cell; // ← the Pool contract's code, stored in the Router jettonLpWalletCode: Cell; lpAccountCode: Cell; }>

Pools don't carry their own upgrade mechanism because they don't need one in the usual sense. The Router holds the code template from which pools are deployed. On TON, a contract's address is derived from its StateInit β€” its code plus initial data β€” which means changing pool code doesn't mutate existing pools. It produces pools at different addresses entirely.

πŸ—¨οΈ "Changing pool code doesn't upgrade a pool. It defines what the next pool will be."

Existing pools keep running the code they were deployed with, holding the liquidity that was deposited under those exact rules. That's not a limitation β€” it's a guarantee.

β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”

πŸ” The Router's Upgrade Path Is Deliberately Slow

Being upgradeable doesn't mean being instantly mutable. STON.fi's Router upgrade flow is a two-phase commit with enforced timelocks:

  1. Initiate β€” admin submits the new Router code. Nothing changes yet.

  2. Wait β€” a minimum seven-day delay before a code upgrade can be finalized. Admin changes carry a separate minimum two-day delay.

  3. Finalize β€” only once finalize_upgrades is received does the new code take effect.

The pending state is publicly readable the entire time, through the tempUpgrade field β€” zero values mean nothing is pending, non-zero means an upgrade is in flight and anyone can see it coming.

πŸ—¨οΈ "Seven days isn't a delay for the team's benefit. It's a window for everyone else."

There's also cancel_admin_upgrade, which exists specifically so a pending admin change can be revoked before it lands β€” an explicit escape hatch rather than a one-way door.

β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”

πŸ›‘ What the Router Can Do Without an Upgrade

Not every change requires replacing code, and the distinction matters for understanding the actual trust surface:

  • β—† set_fees (0x58274069) β€” adjust a specific pool's liquidity, protocol, and referral fees. Fee ratios are expressed against a divider of 10000, so 1% is the value 100. Note the direction here: the Router sends this message to the Pool. Pools don't govern themselves.

  • β—‡ Lock / unlock trading β€” flipping is_locked blocks transfer_notification messages from being processed, which effectively halts all swaps and liquidity provision through that Router. A circuit breaker, no code change required.

This is a meaningful design choice. Routine parameter changes and emergency response both happen without touching the timelocked upgrade path β€” which is exactly why that path can afford to be slow.

β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”

πŸ“¨ Why TON's Message Model Makes This Design Natural

On a synchronous chain, a "router" is often just a helper library β€” a convenience wrapper around direct pool calls. On TON, asynchronous message passing makes it something structurally heavier.

A jetton-to-jetton swap moves through a real chain of messages:

User β†’ their Jetton wallet β†’ Router's Jetton wallet (transfer_notification#7362d09c) β†’ Router (decodes payload, op: swap 0x6664de2a) β†’ Pool (executes AMM math against its own reserves) β†’ LpAccount / payout (settlement)

Because the Router genuinely owns the Jetton wallets for everything flowing through the DEX, it isn't optional infrastructure you could route around. Every swap and every liquidity provision physically passes through it. That's precisely why it's the contract worth making upgradeable β€” and precisely why that upgrade capability needs a seven-day window attached to it.

There's also cross_swap (0x69cf1a5b), a payload op for chaining swaps on the same Router β€” multi-hop routing implemented as message chaining rather than as a nested synchronous call, which is simply what multi-hop has to look like in an async execution model.

β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”

πŸ”€ What This Means for Anyone Building On Top

The practical consequence for integrators is direct, and STON.fi's own SDK documentation states it plainly: don't hardcode a router address.

// ❌ Hardcoded β€” a Router upgrade silently invalidates this const router = dexFactory('EQB3ncy...'); // βœ… Let the API resolve the current router, every time const simulationResult = await api.simulateSwap(params); const router = dexFactory(simulationResult.router);

There's a second consequence worth naming: because pool code is a Router-held template and pool addresses derive from StateInit, multiple pool types can coexist simultaneously. v2 introduced CPI pools alongside the existing types, and the SDK migration notes are explicit that base Router and Pool method calls are deprecated in favor of specifying pool type explicitly. That's not an inconvenience β€” it's the visible surface of an architecture where new pool designs ship as additions, never as forced migrations of existing liquidity.

β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”β–”

βœ… Why This Asymmetry Is the Right Call

  1. The stateless contract is the upgradeable one. Routing logic can evolve without ever touching deposited liquidity, because the Router holds no pair state to corrupt.

  2. Immutable pools mean LPs face fixed rules. Someone who deposits into a pool is trading against the exact code they deposited under β€” nobody can rewrite that pool's math underneath them.

  3. Parameter changes and emergency stops bypass the timelock entirely. set_fees and is_locked handle routine and urgent needs, which frees the upgrade path to be genuinely, usefully slow.

⚠️ Worth Understanding Correctly

  • "Only the Router is upgradeable" isn't a security weakness β€” it's a narrowed trust surface. One contract with a seven-day public timelock is a far smaller thing to monitor than every pool being independently mutable.

  • is_locked is real, immediate admin power. No timelock applies. That's appropriate for a circuit breaker, but it's an honest part of the trust model and shouldn't be glossed over.

  • New pool code means new pool addresses, not upgraded pools. Liquidity doesn't migrate itself; LPs move deliberately, or stay exactly where they are under unchanged rules.

🏁 Bottom Line

Router and Pool contracts have different upgrade models because they hold fundamentally different things. The Router holds routing logic and custody of Jetton wallets but no pair state, making it both worth upgrading and safe to upgrade β€” behind a seven-day timelock with publicly visible pending state. Pools hold nothing but pair state, so they're deployed from a Router-held code template and left immutable, with new designs arriving as new addresses rather than rewrites of existing liquidity. On an async chain where the Router is a mandatory message hop rather than an optional convenience, that split isn't a compromise. It's the shape the architecture was always going to take.

This article reflects independent research based on STON.fi's public developer documentation as of mid-2026. Contract behavior, upgrade delays, and op codes evolve as the protocol ships updates β€” always verify current details directly on docs.ston.fi before shipping a production integration.