The chain registry

The fifteen chains hard-coded into Swap — id, virtual machine, slug, native and wrapped-native tokens, confirmation depth, gas reserve, explorer, and which venue covers each.

9 min readUpdated 6 August 2026chains, registry, confirmations, aggregators, vm

Fifteen chains across four virtual machines are compiled in, not configured. DEX_CHAIN_STATIC — a table in dex/utils/chains.ts keyed by numeric chain id — supplies the id, the VM, the slug, the ecosystem key, the native token, the wrapped native, the explorer, the default confirmation depth and the minimum gas reserve. The dexChain table holds only the operator's half of a row: the switch, the fee recipient, both RPC fields, the confirmation override and the zero-fee acknowledgement.

Every quote path iterates DEX_CHAIN_STATIC, not the dexChain table. A row you insert by hand for a chain the registry does not know is invisible to every user no matter what its status column says — and the chain console refuses to enable it by name: "Chain N is not in the DEX chain registry, so it cannot be enabled."

There is no admin screen that adds a chain, and there cannot be: the wallet adapter builds its network definition from the same table, so a chain the server knows and the browser does not is a chain a user's wallet cannot switch to.

Identity, and the two environment variables each chain derives

slug is what APP_DEX_RPC_<SLUG> is built from. key is the ecosystem ChainSymbol, which is what the <KEY>_<NETWORK>_RPC fallback and the boot chain-id cross-check both join on — null means neither applies.

Chain Chain id VM slug RPC variable key Native
Ethereum 1 EVM ethereum APP_DEX_RPC_ETHEREUM ETH ETH (18)
Optimism 10 EVM optimism APP_DEX_RPC_OPTIMISM OPTIMISM ETH (18)
Cronos 25 EVM cronos APP_DEX_RPC_CRONOS CRONOS CRO (18)
Rootstock 30 EVM rootstock APP_DEX_RPC_ROOTSTOCK RSK RBTC (18)
BNB Smart Chain 56 EVM bsc APP_DEX_RPC_BSC BSC BNB (18)
Polygon 137 EVM polygon APP_DEX_RPC_POLYGON POLYGON POL (18)
Fantom Opera 250 EVM fantom APP_DEX_RPC_FANTOM FTM FTM (18)
TON 607 TON ton APP_DEX_RPC_TON TON TON (9)
Base 8453 EVM base APP_DEX_RPC_BASE BASE ETH (18)
Arbitrum One 42161 EVM arbitrum APP_DEX_RPC_ARBITRUM ARBITRUM ETH (18)
Celo 42220 EVM celo APP_DEX_RPC_CELO CELO CELO (18)
Avalanche 43114 EVM avalanche APP_DEX_RPC_AVALANCHE null AVAX (18)
Linea 59144 EVM linea APP_DEX_RPC_LINEA null ETH (18)
TRON 728126428 TVM tron APP_DEX_RPC_TRON TRON TRX (6)
Solana 1399811149 SVM solana APP_DEX_RPC_SOLANA SOL SOL (9)

Avalanche and Linea carry key: null because the ecosystem addon has no entry for either. That is deliberate rather than an omission — a plausible-looking "AVAX" would make the boot cross-check look up a symbol that does not exist, find nothing and skip, which is a safety check that silently checks nothing. The cost is one required variable: on those two chains, APP_DEX_RPC_<SLUG> or a row override is the only way to give the chain an endpoint.

Solana has no EIP-155 id at all, only a genesis hash. 1399811149 is used because chainId is a signed MySQL INTEGER on eleven of the twelve DEX models: LI.FI's 1151111081099710 would not error, it would saturate to 2147483647 on every row and collide every Solana swap, quote, pool and accrual with each other.

TON does not number networks either — its own tooling reports -239, which cannot be a primary key in a column every other chain shares. 607 is this platform's key for TON mainnet.

TRON is the opposite case, and it is not one of the two: 728126428 (0x2b6653dc) is the id TronLink, every bridge and Tron's own tooling report, and it fits the column, so it is used verbatim. Inventing a synthetic id where a real one fits would have been the mistake; Solana needed one only because the id in circulation overflows.

Neither invented id is ever sent anywhere. No RPC sees 1399811149 or 607, no wallet is asked to switch to one, no signature commits to one. TRON's is a real id, so it is the same number TronLink reports.

Confirmations, gas reserve and explorer

Chain Confirmations Minimum gas reserve Explorer
Ethereum 3 0.001 ETH https://etherscan.io
Optimism 5 0.0002 ETH https://optimistic.etherscan.io
Cronos 5 0.5 CRO https://explorer.cronos.org
Rootstock 3 0.00002 RBTC https://explorer.rootstock.io
BNB Smart Chain 5 0.002 BNB https://bscscan.com
Polygon 20 0.05 POL https://polygonscan.com
Fantom Opera 5 1 FTM https://ftmscan.com
TON 1 (ignored) 1 TON https://tonviewer.com
Base 5 0.0002 ETH https://basescan.org
Arbitrum One 5 0.0002 ETH https://arbiscan.io
Celo 5 0.5 CELO https://celoscan.io
Avalanche 5 0.02 AVAX https://snowtrace.io
Linea 10 0.0002 ETH https://lineascan.build
TRON 20 100 TRX https://tronscan.org
Solana 1 (ignored) 0.01 SOL https://solscan.io

The seeded depth is the default. The chain console can override it per row, whole numbers 1–200, and the override is what the confirmation sweep uses.

Why the depths differ

Polygon is 20 because Polygon reorgs

It is the one chain in this registry with a history of multi-block reorgs. A swap marked CONFIRMED on a block that is later replaced is a settled trade the ledger has already believed, and the fee accrual behind it has already been written. Twenty blocks is the cost of not having to reconcile that by hand.

Ethereum is 3 because a deep reorg on mainnet is a different kind of event

Mainnet block times are long and single-block reorgs are the realistic case, so the depth buys wall-clock time cheaply. Three blocks on Ethereum is roughly thirty-six seconds.

The OP-stack L2s are 5, and Linea is 10

Optimism, Base and Arbitrum sit at 5. Linea is deliberately higher, not lower: its sequencer can reorder before finality is posted to L1, so an L2 with cheap reorgs needs more confirmations rather than fewer. The poller costs one extra block time per swap; the alternative costs a reconciliation.

Rootstock is 3, and that is a longer wait than Linea's 10

Rootstock is merge-mined with Bitcoin. Block time is about thirty seconds and each confirmation carries bitcoin's hashrate behind it, so three here is a longer wall-clock wait than ten on an L2 — fewer confirmations by count, far more security per confirmation.

TRON is 20 because that is one past its finality threshold

TRON finalises by supermajority vote after about nineteen blocks (roughly fifty-seven seconds). A block before that is genuinely reversible, so 20 is one past the threshold rather than a round number chosen for comfort.

Avalanche is 5 even though Snowman does not reorg

Snowman consensus finalises in one to two seconds. The depth is also what paces the confirmation poller, so 5 keeps Avalanche in step with the other chains rather than making it the one chain that reports differently.

Solana and TON hold a 1 that every code path ignores

On Solana a slot is finalized or it is not — the poller asks for the finalized commitment instead. TON finalises a masterchain block in seconds and does not reorg the way a longest-chain network does. The column is NOT NULL, so both rows carry a value; zero would have been more honest and would also have read as "no confirmations required" to any EVM code path that forgot to branch.

The gas reserve is in base units, and the units are not the same

minGasReserve is what Max must leave behind when a user is selling the chain's native token and the live gas estimate comes back implausibly low. The client takes max(2 × estimate, this). It is stored as a decimal string because JSON has no bigint and Number loses the low digits of an 18-decimal value.

Three of the four VMs mean something different by it:

  • EVM — wei. A plain gas reserve.
  • Solana — lamports, and it is gas plus rent. A fee is trivial (5,000 lamports per signature plus priority); what dominates is that buying a token the user has never held creates an associated token account, and an account must hold about 0.00204 SOL to exist at all. A Jupiter route can create more than one. Reserving only the fee lets a user spend to the last lamport and then watch the swap fail with the fee already paid.
  • TRON — SUN. TRON charges bandwidth and energy, which staked TRX provides for free; a user with no stake pays by burning TRX, and a contract call burns far more than a transfer. The 100 TRX floor covers the burn case.
  • TON — nanotons, and it is not a gas reserve at all. TON charges per message, a swap fans out into several, and each message carries an attached value that is largely refunded. What strands a user is having too little TON to attach; STON.fi's own forward_gas for a simple swap measured 0.185 TON.

Polygon is POL here and MATIC in the ecosystem

The native asset was renamed MATIC to POL on chain in 2024. This registry follows the network, because the symbol is a user-facing swap label. The ecosystem addon's chainConfigs still says currency: "MATIC", because its currency codes are wallet symbols — the code a custodial balance is held under, which cannot be renamed without rewriting every historic row.

Both are correct in their own table and they are never compared. What follows from it operationally is only this: a Polygon deposit in the ecosystem is labelled MATIC, the same asset in the swap terminal is labelled POL, and the ecosystem's mainnet network name is matic — so the fallback variable is POLYGON_MATIC_RPC, not POLYGON_MAINNET_RPC.

Wrapped native, per chain

On EVM the native token has no contract, so the addon uses a sentinel address and routes through the wrapped form. Three entries are exceptions worth knowing before somebody "corrects" one.

Chain Wrapped native
Ethereum 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2
Optimism 0x4200000000000000000000000000000000000006
Cronos 0x5c7f8a570d578ed84e63fdfa7b1ee72deae1ae23
Rootstock 0x542fda317318ebf1d3deaf76e0b632741a7e677d
BNB Smart Chain 0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c
Polygon 0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270
Fantom Opera 0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83
TON EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c (the zero address)
Base 0x4200000000000000000000000000000000000006
Arbitrum One 0x82af49447d8a07e3bd95bd0d56f35241523fbab1
Celo 0x471ece3750da237f93b8e339c536989b8978a438 (CELO itself)
Avalanche 0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7
Linea 0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f
TRON TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR
Solana So11111111111111111111111111111111111111112
  • Celo has no "wrapped CELO". Its native token is an ERC-20 — the same asset, at a real contract, is both the gas token and a transferable token. The native-to-wrapped translation every other EVM chain performs is an identity here, and a pool of CELO against anything is a pool of that address.
  • Native TON is the zero address. There is no wrapped TON to route through; the routers use a proxy (pTON) internally and the API takes the zero address. STON.fi's asset list contains a different entry symbolled "TON" that looks like the answer and returns "could not find pool address" on every router.
  • Wrapped SOL is a real mint that aggregators quote directly, so it is not a translation target — it is the address you ask for a price with.

Which venue covers which chain

Chain coverage is a property of the adapter, checked before a dexProvider row is even consulted. A provider switched on for a chain its adapter does not carry simply never enters the race.

Chain Aggregators Direct-pool venue
Ethereum 0x, 1inch, KyberSwap, Odos, LI.FI Uniswap V2 + V3
Optimism 0x, 1inch, KyberSwap, Odos, LI.FI
Cronos LI.FI only VVS Finance V2
Rootstock LI.FI only Uniswap V3
BNB Smart Chain 0x, 1inch, KyberSwap, Odos, LI.FI PancakeSwap V2
Polygon 0x, 1inch, KyberSwap, LI.FI
Fantom Opera none SpookySwap V2
TON STON.fi only
Base 0x, 1inch, KyberSwap, LI.FI Uniswap V2 + V3
Arbitrum One 0x, 1inch, KyberSwap, Odos, LI.FI Uniswap V2 + V3
Celo LI.FI only Uniswap V3
Avalanche 0x, 1inch, KyberSwap, LI.FI
Linea 0x, 1inch, KyberSwap
TRON SunSwap only
Solana Jupiter only

Read that table before you switch a chain on:

  • Cronos, Rootstock and Celo quote only through LI.FI. Switch LI.FI off on Admin → Swap → Providers and all three go dark with nothing else to fall back to.
  • Solana, TRON and TON have exactly one adapter each — Jupiter, SunSwap and STON.fi. Each is also the only adapter for its VM, so there is no second opinion on price and no fallback during a vendor outage.
  • Fantom Opera has no aggregator at all. It is quotable only through the direct-pool venue, which means it is quotable only while dexDirectPoolsEnabled is on and a verified pool is bound — see Direct pools.
  • Odos reaches four chains only: Ethereum, Optimism, BNB Smart Chain and Arbitrum One. Its integrator fee is an on-chain referral code registered per chain, so it also refuses to quote on any chain whose code is unverified while a platform fee is set — see The Chains console.
  • The four chains with a direct venue and thin aggregator coverage — Cronos, Rootstock, Fantom and Celo — carry one because of it. Every address in those four deployments was verified against the live chain: eth_chainId matches, the router's own factory() returns the factory claimed, and that factory returns a real pool for a major pair.

Optimism, Polygon, Avalanche and Linea have no direct-pool deployment in the registry, deliberately: the direct venue exists for pairs no aggregator will quote, and these four are the best-covered chains in the table — five aggregators on Optimism, four each on Polygon and Avalanche, three on Linea.

An operator who has verified a deployment adds it as an entry in dexChain.metadata.extraAmmDeployments[], which is a different key from extraRouters on the same blob — that one is the router allowlist, the list checked against a quote's to and approval spender. Neither has a field on the chain console and no admin route writes dexChain.metadata at all: the chain PUT accepts six columns and nothing else, so in this build both are a direct database edit. See The Chains console.

The list exists in three places, and a test holds them together

DEX_CHAIN_STATIC (backend) and DEX_WALLET_CHAIN_IDS in frontend/config/dex-chain-ids.ts carry all fifteen ids. The networks array in frontend/config/wallet.tsx carries thirteen: TON and TRON are deliberately absent, because AppKit has no TON or TRON namespace — TON is reached through TON Connect and TRON through the injected TronLink provider, neither of which is a WalletConnect session. chain-registry.test.ts encodes exactly that: every registry chain must be reachable by some wallet layer, every AppKit network must have a registry entry, and DEX_WALLET_CHAIN_IDS must equal networks plus those two named exceptions.

That matters to you when you upgrade: the server cannot answer "can a wallet reach this chain", because the answer lives in the frontend bundle that was built and shipped. An install whose backend is newer than its frontend bundle has exactly that disagreement, and the readiness console reports such a chain as unreachable rather than as ready. If a chain looks configured and the terminal will not switch to it, rebuild the frontend before changing anything else.

Where the operator's half lives

Everything above is a protocol fact. What you actually set is on The Chains console, and what a chain still needs before it can serve a swap is reported by Requirements. The endpoint resolution order and every variable name are in Environment variables.