This page describes the layout and role of each account. Seeds are canonical and listed in
reference/program-addresses. A CLMM pool is more account-heavy than a CPMM pool because liquidity is stored sparsely across the tick range; understanding that sparsity is the bulk of this page.Account inventory
A live CLMM pool is described by the following account families. All are owned by the CLMM program except the two mints and their vaults.PoolState
The pool’s live state, read on every swap and every position change.
sqrt_price_x64andtick_currentare the pool’s price state. They are updated together on every swap.tick_currentis the floor oflog_{1.0001}(price).liquidityis the active liquidity — the sum ofLvalues for all positions whose range containstick_current. It changes every time a swap crosses a tick and every time a position is opened/closed/resized.fee_growth_global_{0,1}_x64are the cumulative fees earned per unit of liquidity across the entire pool history. Positions read this to compute what’s owed to them.tick_spacingis locked to theAmmConfigat initialization and never changes. It determines which tick indices are even allowed to be position endpoints.tick_array_bitmapis an inline bitmap covering the commonly used tick range around spot price. For pools whose positions reach far out, overflow tracking lives in the separateTickArrayBitmapExtension.fee_onis fixed at pool creation.0(FromInput) reproduces classic Uniswap-V3 behavior.1and2route the swap fee to a single side of the book — seeproducts/clmm/feesfor trade-offs.dynamic_fee_infocarries volatility state for the dynamic-fee surcharge. When enabled, every swap recomputes adynamic_fee_componenton top ofAmmConfig.trade_fee_rate. Layout is documented underDynamicFeeInfobelow; pools without dynamic fee leave the entire struct zero.
AmmConfig
GET https://api-v3.raydium.io/main/clmm-config):
protocol_fee_rate and fund_fee_rate are fractions of the trade fee; same convention as CPMM. See products/clmm/fees.
TickArrayState
CLMM does not store a single record per tick. That would be billions of accounts. Instead it groups TICK_ARRAY_SIZE adjacent initialized-or-not ticks (typically 60 or 88 depending on program version) into a TickArrayState that is lazily created on first use.
order_phaseis the cohort id. It increments every time a cohort transitions from “all unfilled” to “partially filled.”orders_amountis the input-token total of the current (newest) cohort.part_filled_orders_remainingtracks the previous cohort that is currently being filled by ongoing swaps.unfilled_ratio_x64is a Q64.64 multiplier carried on the cohort: when a swap fills X% of the cohort, the ratio is multiplied by(1 − X). Each open order stores its own(order_phase, unfilled_ratio_x64)snapshot at open time, so settle math reduces to comparing snapshots.
- A position endpoint tick t must satisfy
t % tick_spacing == 0. The program rejects off-spacing positions. - The tick’s array is located at
floor(t / (TICK_ARRAY_SIZE * tick_spacing)) * (TICK_ARRAY_SIZE * tick_spacing). - A tick array is initialized lazily: the first position or swap that touches an uninitialized array creates it, paying the rent.
- A tick array is never closed by the program. Once allocated it persists for the life of the pool, even after every tick inside it returns to
liquidity_gross == 0. Subsequent positions and swaps reuse the existing account at no extra rent. There is noClosePosition-driven cleanup path for tick arrays.
TickArrayBitmapExtension
PoolState.tick_array_bitmap (inline) covers the “close to spot” range — ±1,024 tick arrays. Outside that range (for extreme tick values), the program maintains an extension account:
(MIN_TICK, MAX_TICK)) require it; the SDK resolves it for you.
Positions
A CLMM position is a bundle of three accounts plus a mint:Position NFT mint
An SPL Token mint with supply 1. The mint’s address is a deterministic PDA; the position NFT in the owner’s wallet is just an ATA holding that single token. Transferring the NFT is how a position changes hands — the program keys authorization to the current holder of the NFT’s ATA balance, not to a Pubkey stored in state.PersonalPositionState
One per open position. Keyed off the NFT mint.
ProtocolPositionState (deprecated)
Older CLMM releases stored aggregate per-
(pool, tick_lower, tick_upper) bookkeeping in a ProtocolPositionState PDA. Newer releases no longer create or read this account. The slot still appears on the OpenPosition / IncreaseLiquidity / DecreaseLiquidity account lists as an UncheckedAccount for ABI compatibility, but the program does not write to it. Existing accounts on-chain are vestigial; the admin can call CloseProtocolPosition to reclaim rent for them.Aggregate range bookkeeping is now derived directly from the two endpoint ticks (liquidity_gross, liquidity_net, and the per-tick fee_growth_outside_* / reward_growths_outside_x64) in TickArrayState. The fee-growth-inside formula fee_growth_inside = global − outside_lower − outside_upper continues to work without an aggregate position account.Observation
(tick_cumulative[t1] − tick_cumulative[t0]) / (t1 − t0) and then price = 1.0001 ** tick. See algorithms/clmm-math.
DynamicFeeConfig and DynamicFeeInfo
Dynamic fee parameters live in two places. The reusable template — DynamicFeeConfig — is admin-managed and shared across pools that opt in. The per-pool runtime state — DynamicFeeInfo — is embedded in PoolState and updated by every swap.
DynamicFeeConfig
["dynamic_fee_config", index.to_be_bytes()]. Created via create_dynamic_fee_config (admin-gated) and modified via update_dynamic_fee_config. A pool created with enable_dynamic_fee = true snapshots the config’s five calibration parameters (filter_period, decay_period, reduction_factor, dynamic_fee_control, max_volatility_accumulator) into its own DynamicFeeInfo at creation time; later edits to the DynamicFeeConfig do not retroactively affect existing pools.
DynamicFeeInfo (embedded in PoolState)
DynamicFeeConfig. The fee math and the decay rules are documented under products/clmm/math and products/clmm/fees.
Constants used by the formula:
LimitOrderState
One account per open limit order.
- Open — user calls
open_limit_order, depositstotal_amountof the input token, the order is bound to aTickStatecohort. - (optional) Increase / Decrease —
increase_limit_orderadds tototal_amount;decrease_limit_orderreturns unfilled tokens (and any settled output up to that point). - Settle — when the cohort is fully or partially filled, the owner or the operational keeper calls
settle_limit_orderto push output tokens to the owner’s ATA. - Close — once
unfilled_amount == 0, the account is closeable. Rent always returns toowner.
[owner.as_ref(), limit_order_nonce.key().as_ref(), limit_order_nonce.order_nonce.to_be_bytes().as_ref()]. The order PDA is therefore unique per (owner, nonce_index, order_nonce).
LimitOrderNonce
Per-(wallet, nonce_index) counter that lets a single user run multiple parallel pipelines of limit orders without colliding on PDAs.
[user_wallet.as_ref(), &[nonce_index]]. Most clients use nonce_index = 0 and let order_nonce carry the cardinality.
Deriving the key accounts
reference/program-addresses.
Lifecycle quick reference
TickArrayState accounts are never closed by the program — they persist for the life of the pool. Once a tick array has been initialised it remains on-chain even when every tick inside it returns to liquidity_gross == 0. Re-using an existing tick array is free; only the first position to touch a never-initialised array pays its rent.
What to read where
- Tick math and range mechanics:
products/clmm/ticks-and-positions. - Swap walk and fee-growth math:
products/clmm/math. - Instruction account lists:
products/clmm/instructions. - Fees and reward accrual:
products/clmm/fees. - Canonical program IDs and seeds:
reference/program-addresses.

