Skip to main content

Pool identity

A pool is (token0, token1, poolType) — nothing else. createPool(tokenA, tokenB, poolType) takes the tokens in either order and no fee argument: tick spacing and fee come from governance-set per-type defaults, so there is exactly one canonical pool per combination and nothing for a creator to misconfigure. Lookup is getPool(tokenA, tokenB, poolType), written both ways round. Creation can be restricted per type — check poolCreationRestricted(poolType) / canCreatePool(poolType, account) before offering pool creation in a UI.

Pool types are an open set

PoolType is a user-defined uint8, not an enum — deliberately, so the ABI never rejects a value newer than your build:
Numbers are forever and new types go at the end (the value is part of the CREATE2 salt). Write integrations that pass unknown types through rather than reject them.

Address derivation

The deployer, not the factory. Pools are CREATE2-deployed by a separate deployer contract — read its address from factory.poolDeployer(). Derivation:
Or simply call deployer.computePoolAddress(token0, token1, poolType).
Two classic V3 habits break here: hardcoding one init code hash (there is one per pool type — read and cache initCodeHashOf), and deriving from the factory address (it is the deployer). Blueprint registration is append-only, so each type’s hash is permanent once set.

Reading a pool

Uniform across every pool type:
The caveats that matter:
  • Uninitialized pools return zeros, never revert — a pool can exist with sqrtPriceX96() == 0 (createPool does not initialize).
  • tick on a tickless (STABLE) pool is a measurement of where the price sits, nothing more — the pool holds nothing at it. Never mint against a tick read from a STABLE pool.
  • feeInfo() is backward-looking on dynamic pools: it is the fee the last swap charged. For real quotes, use the Quoter.
  • tickSpacing is mutable on CL pools (governance), fixed on CP, 0 on STABLE.
  • liquidity() is in-range liquidity on CL — unrelated to the total across all ticks.

Positions and resolveTicks

Pool-level positions are keyed keccak256(owner, tickLower, tickUpper). But some pool types override the range: CP pins every position to the spacing-aligned full range; STABLE has exactly one range and ignores the arguments. Before computing a key, ask the pool:
Compute the key from what the pool settled on, or you will read an empty position and conclude — wrongly — that nothing is owed. The emitted Mint/Burn/Collect events always carry the resolved range.

Tick data for routing engines

The tick tree indexes by the tick itself, not tick / tickSpacing — that is what lets governance retune spacing on a live pool without moving a stored bit:
tickBitmap(int16 word) returns the leaf word; tickTreeRoot() the top. The TickLens walks it for you: getPopulatedTicksInWord(pool, word) returns exact-sized (tick, liquidityNet, liquidityGross) arrays, and wordOf(tick) computes the word — no spacing argument. CL and CP only; a STABLE pool’s whole depth sits at one price and is liquidity(). STABLE-only reads: curveReserve0() / curveReserve1() — the curve’s pricing reserves, deliberately not the pool’s balances (uncollected fees sit alongside), plus amplificationX100() and amplificationRamping().