Pool legs
A token is a leg of a pool when it has an assets[token] slot. Every core designates one of its
legs as the base asset: its unit of account, and the root its
anchor tree resolves to. Which assets sit on which core, and which one
is the base, is per instance: see Pool Composition. Routing across
pools is in Swapping; per-asset addresses, feeds and ERC-20 quirks are in the
Asset Registry.
1. What a listing is
On chain, a listing is one assets[token] slot written by PoolConfig.initAsset:
- Decimals are read from the token, never passed in.
initAssetcalls the ERC-20 and stores what it returns. A token reporting 0 or more than 18 decimals cannot be listed. decimals != 0is the is-configured sentinel. Every reader gates on it, which is why 0 is unlistable: it collides with “not configured”.
Listing is admin-only, and one-way-latched. Before sealBootstrap(pool) the deployer lists through Admin.addAsset. After it, addAsset reverts permanently and the only path is the timelocked ADD_ASSET queue: Admin.requestOp(..., ADD_ASSET, ...) then Admin.executeAddAsset(pool, token). See Admin for the queue and Access Control for who may drive it.
Every listed asset, the base included, must carry kappaCovBps > 0 (Invariants §I-9).
2. Why every core has a base leg
Each pool stores one baseToken, fixed at initialize. A new listing defaults its
anchor to the base (initAsset sets
anchor = (token == baseToken) ? 0 : baseToken) and adminSetAnchor can afterwards re-parent it to
any correlated listed asset, up to MAX_DEPTH = 4 edges from the root. A core whose whole roster
still carries that default is a depth-1 hub-and-spoke star, and any cross
there is at most spoke → base → spoke in a single Pool.swap. Deeper trees are configuration, not a
different mechanism: a swap between two assets sharing a parent prices through that parent without
touching the base at all (Anchor Path Pricing).
The base is the numeraire, so it carries constraints no spoke does:
| Requirement on the base | Why |
|---|---|
| EXTERNAL-quoted | its own depeg halt must be able to bite (Flow Guards) |
QUOTE_UNIT_ANCHOR | it is the unit other legs are attested in |
| spokes carry an independent ref band | a spoke’s mark is checked against a second attestation, not against the base alone |
Changing the base is not a pointer swap. PoolConfig.setBaseToken(newBase, spokes), reachable only through Admin.executeBaseMigration, reverts unless every one of these holds:
- the incoming base is already listed;
- it is EXTERNAL-quoted;
- it is
QUOTE_UNIT_UOA; - it is trading within
BASE_DEPEG_HALT_BPSof parity; - the demoted base gains a ref band in the same call.
Off chain the same leg is the router’s hub: a pair with no shared pool is
fillable only as one hop through the hub symbol
(Swapping §2). pool.baseToken() is the authority for
which asset that is; no deployment artifact records it.
A cross-core route takes the hub out of the source pool and into the destination, so the hub has to be replenishable. On a testnet deployment that rules out an official issuer token nobody can mint, and the base is the mintable twin instead (Faucets §2).
3. What an LP holds against a leg
One ERC-20 receipt per leg, single-sided, with the leg’s own liquidity index: mint, cooldown and exit are in Providing Liquidity. Moving a claim from one leg to another without withdrawing is Liability Swaps. A leg’s coverage ratio prices both sides of a swap and caps a same-asset exit; it is derived in Inventory Management §5.
4. Reading a leg’s state
Everything a user sees as “this leg is not trading right now” is one of four on-chain reads, each documented where the mechanism lives:
| What you see | Read | Where it is specified |
|---|---|---|
| leg disabled for an action | the leg’s flag bits (swap, deposit, withdraw, liability swap) | Basic Operations §2 |
| leg or pool halted | the halt bits an admin or guardian sets | Access Control, Guardian |
| quotes rejected as stale or off-band | feed age, deviation, ref band, depeg band | Oracle Keeper, Flow Guards |
| a size that quoted refuses to fill | per-block and per-window flow caps | Flow Guards |
5. Decimals, and gas
The pool rescales per hop with a pure power-of-ten shift between the two legs’ stored decimals
(Pricing.sol), so decimals never touch pricing and show up only in raw amounts. Read decimals()
on the deployed token before scaling one: the decimals column of the
Asset Registry describes an asset’s mainnet reference, which is not
always the token in your wallet on a testnet instance.
Where a chain pays gas in a token that is also a listed leg, a “max” that spends the whole balance cannot afford its own transaction fee, so the app reserves a gas buffer on that input. Which chains those are, and what the gas token’s ERC-20 view looks like: Deployments §1.
6. Related documentation
- Swapping: how a swap is routed, within a pool and across pools
- Faucets: mintable twins and how testnet balances are obtained
- Asset Registry: per-asset addresses, feeds, ERC-20 oddities
- Pool Composition: which assets sit on which core
- Anchor Path Pricing: the anchor column and leg pricing
- Admin: the timelocked listing and base-migration queues
- Spread & Fees: what a listed asset’s fee floor and coverage wall cost