Docs
Concepts
Native is a role. Every chain has a gas asset; on most of the 20 registry testnets it is ETH, on others MON, AVAX, POL, S, SEI, TCRO, XPL, OKB or INJ. On Arc Testnet the gas asset is USDC itself, with 18 decimals at the RPC level and a 6-decimal ERC-20 mirror; amounts are scaled at the boundary.
Asset identity is chain + address + representation. Representations: NATIVE, WRAPPED_NATIVE, CIRCLE_NATIVE (Circle-issued USDC), CANONICAL (issuer test tokens such as EURC and LINK), UNKNOWN (wallet-discovered or user-added tokens, hidden unless enabled in Settings).
Capability graph. Nodes are (chain, canonical asset, representation). Edges are what a provider can do right now, each carrying its provenance (official registry, on-chain probe or runtime API, with a timestamp), gas requirements, reliability class and output canonicality. Paths are searched with iterative deepening so a direct route is never hidden behind long detours.
Unified balance vs. bridge. CCTP burns and mints move USDC one chain at a time. Circle Gateway deposits USDC into a per-chain vault and lets a signed burn intent mint it anywhere Circle forwards to; RouteDust uses it as one more edge, with the fee taken from the deposited amount.
Providers
- Circle CCTP: burn on the source, Iris attestation, mint on the destination. Two edges per pair: manual mint (you submit
receiveMessage) and Forwarding Service (Circle mints, flat USDC fee deducted). Fast Transfer where the source supports it. - Circle Gateway: deposit → finality → EIP-712 burn intent → Circle forwards the mint. Slow from Sepolia-family chains (about 15 minutes of finality) and expensive towards Ethereum Sepolia; cheap between L2s and Arc.
- Uniswap v3 / v4 / v2 and v2-style AMMs (Pangolin, LFJ on Fuji): only pools with liquidity and a successful probe quote. v3 routes are single-hop, two-hop through WETH, or split across two fee tiers in one transaction. v4 goes through the Universal Router with exact Permit2 allowances.
- Hyperlane CCTP-backed USDC warp routes (Sepolia, Base, OP, Arbitrum Sepolia): relayer delivery, interchain gas paid as
msg.valueand reserved with gas. - Stargate V2 native ETH pools (Sepolia, Arbitrum Sepolia, OP Sepolia): capped by the path credit, delivery confirmed through LayerZero Scan.
- Across and LI.FI Intents testnet routes, always flagged best effort; OP Standard Bridge L1 → L2 ETH deposits; native wrap/unwrap where the wrapped contract was verified.
Live status per provider is on Protocols; what each external registry claims versus what discovery found is on Coverage.
Planner and modes
For every balance: reserve gas (units × max fee × safety, plus relayer fees) → enumerate structural paths → quote short paths, then detours, then relays → shrink to a provider cap or to the price-impact limit (PARTIAL) → score per mode. Modes: Best output (output-dominant), Fewest transactions, Fastest, Native only (no wrapped outputs), Max coverage. Changing the mode re-scores without re-quoting.
Pooled bridges (chain consolidation): when several balances on one chain leave through the same hub asset, the same-chain legs run first and one bridge is quoted for the pooled amount: fewer signatures and one destination claim. The bridge leg executes in balance mode, capped at the plan amount.
Gas needs are structured: when a shorter path was skipped because an intermediate or destination chain has no gas, the card shows the shortfall with that chain's faucets.
Execution and safety
- Approvals are exact amounts to spenders that come from the registry (router, TokenMessenger, Gateway wallet, position manager), never unlimited.
- Every transaction is simulated, gas-estimated (including the OP Stack L1 data fee) and checked against the native balance before the wallet prompt; the calldata is summarised on the timeline.
- Bytecode hashes of spenders are pinned (registry values plus first use in this browser); a change is reported before signing.
- The wallet nonce is snapshotted before each signature. On retry, if the nonce moved, the provider looks for the transaction on-chain (CCTP: DepositForBurn logs) and resumes from it; otherwise the route stops with POSSIBLE_DUPLICATE and you decide with the explorer open. A burn is never repeated.
- A wallet disconnect pauses the route (nothing failed on-chain); reconnect and resume. Sped-up or cancelled transactions are followed by hash.
- History is never deleted, only archived. Activity also reads the wallet's CCTP burns directly from every chain and lets you mint the unminted ones.
Adding a chain
- Run
pnpm exec tsx scripts/probe-chains.tswith the candidate in its list: it verifies the RPC chain id, Circle USDC, TokenMessengerV2 bytecode, Multicall3 and a WETH predeploy, and prints a registry seed. - Add the chain id to
packages/registry/src/faucets.ts(with its faucets), the chain tochains.ts, USDC toassets.ts, the CCTP domain (fast / forwarding flags) tocctp.ts, and mark the coverage entry verified. pnpm probe --writere-verifies everything and stamps the registry date;pnpm edgesshows the live edges the chain gained. No route-specific code is needed: CCTP, Gateway and wrap edges come from the registry data.
Writing an adapter
A provider implements RouteProvider: discover(ctx) returns edges that were confirmed live (pool with liquidity + probe quote, API route list, verified bytecode), quote(req) returns a live quote or null (throw QuoteLimitError with the cap when the provider can take a smaller amount), build(edge, ctx) returns the steps for the actual input amount (approval, transaction, wait, permit), status(exec) polls a cross-chain step and may return a claim transaction or persist data into the step, and the optional recover(step) finds an already-sent transaction so retries never duplicate it.
Rules: never emit an edge from “protocol supports chain X”; put non-user-specific data behind TtlCache; keep bigints out of quote.raw route objects; set nativeFeeWei when the source transaction carries a relayer fee; give every signed step a summary.
Troubleshooting
- Wallet shows an RPC error on a testnet (Rabby, MetaMask): the wallet's own endpoint is failing. Networks → “Add to wallet” registers the chain with the registry RPC; the executor also adds it automatically when the wallet does not know the chain.
- Need gas: the card names the chain and links its faucets; after topping up, Retry. Destination mints (manual CCTP) need gas on the destination; the Forwarding Service edge does not.
- Quote expired: cards re-quote themselves in the background; execution re-quotes before signing.
- POSSIBLE_DUPLICATE: a transaction left the wallet after a failed step. Paste its hash if it was this step, or mark it unrelated; nothing is sent until you choose.
- Burned but not minted: Activity → “Circle USDC burns on-chain” → Scan; unminted burns can be minted from there even if the route was archived.