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.
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_gasfor 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
dexDirectPoolsEnabledis 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_chainIdmatches, the router's ownfactory()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.