> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lunya.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Pools & Discovery

> Finding pools, deriving addresses, and reading their state

## 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](/exchange/pools-and-fees#who-can-create-pools) — 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:

```solidity theme={null}
CL = 0       // concentrated liquidity, the Uniswap V3 model
CP = 1       // constant product — CL constrained to full range
STABLE = 2   // amplified stableswap
```

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:

```
salt         = keccak256(abi.encode(token0, token1, poolType))   // abi.encode, NOT encodePacked
initCodeHash = deployer.initCodeHashOf(poolType)                  // ONE HASH PER TYPE
pool         = keccak256(0xff ++ deployer ++ salt ++ initCodeHash)[12:]
```

Or simply call `deployer.computePoolAddress(token0, token1, poolType)`.

<Warning>
  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.
</Warning>

## Reading a pool

Uniform across every pool type:

```solidity theme={null}
function slot0() external view
    returns (uint160 sqrtPriceX96, int24 tick, uint24 fee, uint16 feeProtocol0, uint16 feeProtocol1);
function sqrtPriceX96() external view returns (uint160);   // one SLOAD — prefer it when the price is all you want
function feeInfo() external view returns (uint24 fee, bool isDynamic);
function liquidity() external view returns (uint128);
function tickSpacing() external view returns (int24);      // 0 on tickless pools — a capability, not a name
function poolType() external view returns (PoolType);
```

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](/developers/dex/swaps-and-routing#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:

```solidity theme={null}
function resolveTicks(int24 tickLower, int24 tickUpper) external view returns (int24, int24);
```

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:

```
word = tick >> 8        // arithmetic shift: floors correctly for negative ticks
```

`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()`.
