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

# CLMM accounts

> Every account a CLMM pool touches: PoolState, AmmConfig, TickArrayState, TickArrayBitmapExtension, PersonalPositionState, and Observation.

<Info>
  This page describes the **layout and role** of each account. Seeds are canonical and listed in [`reference/program-addresses`](/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.
</Info>

## 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.

| Account                          | Purpose                                                                                                                       | Count per pool        |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| `AmmConfig`                      | Fee tier: trade-fee rate, protocol share, fund share, default tick-spacing. Shared across all pools in this tier.             | 1 (shared)            |
| `PoolState`                      | Current `sqrt_price_x64`, current tick, total liquidity, fee growth globals, reward info, observation pointer.                | 1                     |
| `TickArrayState`                 | A block of `TICK_ARRAY_SIZE` adjacent ticks. Only initialized on demand.                                                      | 0 ≤ N ≤ range         |
| `TickArrayBitmapExtension`       | Overflow bitmap tracking which tick arrays exist past the inline bitmap in `PoolState`.                                       | 0 or 1                |
| `PersonalPositionState`          | One per LP position. Stores the range, liquidity, and last-seen fee/reward growth. Authority = NFT owner.                     | 1 per position        |
| Position NFT mint                | Mint with supply 1, associated with `PersonalPositionState`. Transferring transfers the position.                             | 1 per position        |
| `ObservationState`               | Ring buffer of price observations for the TWAP.                                                                               | 1                     |
| `token_0_vault`, `token_1_vault` | Token accounts holding the pool's balances. Owned by pool authority.                                                          | 2                     |
| `DynamicFeeConfig`               | Reusable parameter set for the dynamic-fee mechanism. Pools created via `create_customizable_pool` can opt in. Admin-managed. | shared (per index)    |
| `LimitOrderState`                | One per open limit order. Records owner, tick, side, total amount, settled-output snapshot.                                   | 1 per order           |
| `LimitOrderNonce`                | Per-`(wallet, nonce_index)` counter that derives unique order PDAs.                                                           | 1 per (wallet, index) |

## `PoolState`

The pool's live state, read on every swap and every position change.

```rust theme={null}
// programs/amm/src/states/pool.rs
pub struct PoolState {
    pub bump:           [u8; 1],
    pub amm_config:     Pubkey,            // fee tier binding
    pub owner:          Pubkey,            // admin (multisig)
    pub token_mint_0:   Pubkey,
    pub token_mint_1:   Pubkey,
    pub token_vault_0:  Pubkey,
    pub token_vault_1:  Pubkey,
    pub observation_key: Pubkey,

    pub mint_decimals_0: u8,
    pub mint_decimals_1: u8,
    pub tick_spacing:   u16,               // inherited from amm_config at init

    pub liquidity:      u128,              // total active (in-range) liquidity
    pub sqrt_price_x64: u128,              // Q64.64 of sqrt(price)
    pub tick_current:   i32,               // current tick index

    pub padding3:       u16,
    pub padding4:       u16,

    // Global fee growth per unit of liquidity, Q64.64.
    pub fee_growth_global_0_x64: u128,
    pub fee_growth_global_1_x64: u128,

    // Accrued-but-not-swept protocol fees (per mint).
    pub protocol_fees_token_0: u64,
    pub protocol_fees_token_1: u64,

    // Reserved padding for future upgrades.
    pub padding5: [u128; 4],

    // Status bitmask. Bits 0-5: open-position, decrease-liquidity,
    // collect-fee, collect-reward, swap, limit-order. A set bit disables
    // the corresponding operation.
    pub status:  u8,

    // Fee-collection mode (CollectFeeOn).
    //   0 = FromInput (deduct fee from the swap input — Uniswap-V3 default)
    //   1 = Token0Only (always deduct fee from token0 vault)
    //   2 = Token1Only (always deduct fee from token1 vault)
    pub fee_on: u8,
    pub padding: [u8; 6],

    // Live reward streams (up to REWARD_NUM = 3).
    pub reward_infos: [RewardInfo; 3],

    // Inline bitmap tracking initialized tick-arrays in the primary range.
    pub tick_array_bitmap: [u64; 16],

    // Reserved padding for future upgrades.
    pub padding6: [u64; 4],

    pub fund_fees_token_0: u64,
    pub fund_fees_token_1: u64,

    pub open_time:    u64,                 // currently disabled by the program
    pub recent_epoch: u64,

    // Per-pool dynamic-fee state. Zero-valued unless the pool was
    // created with `enable_dynamic_fee = true` via create_customizable_pool.
    pub dynamic_fee_info: DynamicFeeInfo,

    // Reserved for future upgrades.
    pub padding1: [u64; 14],
    pub padding2: [u64; 32],
}
```

Fields you will actually touch:

* **`sqrt_price_x64`** and **`tick_current`** are the pool's price state. They are updated together on every swap. `tick_current` is the floor of `log_{1.0001}(price)`.
* **`liquidity`** is the **active** liquidity — the sum of `L` values for all positions whose range contains `tick_current`. It changes every time a swap crosses a tick and every time a position is opened/closed/resized.
* **`fee_growth_global_{0,1}_x64`** are the cumulative fees earned per unit of liquidity across the entire pool history. Positions read this to compute what's owed to them.
* **`tick_spacing`** is locked to the `AmmConfig` at initialization and never changes. It determines which tick indices are even allowed to be position endpoints.
* **`tick_array_bitmap`** is an **inline** bitmap covering the commonly used tick range around spot price. For pools whose positions reach far out, overflow tracking lives in the separate `TickArrayBitmapExtension`.
* **`fee_on`** is fixed at pool creation. `0` (FromInput) reproduces classic Uniswap-V3 behavior. `1` and `2` route the swap fee to a single side of the book — see [`products/clmm/fees`](/products/clmm/fees) for trade-offs.
* **`dynamic_fee_info`** carries volatility state for the dynamic-fee surcharge. When enabled, every swap recomputes a `dynamic_fee_component` on top of `AmmConfig.trade_fee_rate`. Layout is documented under `DynamicFeeInfo` below; pools without dynamic fee leave the entire struct zero.

## `AmmConfig`

```rust theme={null}
pub struct AmmConfig {
    pub bump: u8,
    pub index: u16,                       // uses "amm_config"+u16 seed

    pub owner:             Pubkey,        // admin
    pub protocol_fee_rate: u32,           // fraction of trade fee to protocol, denom 1e6
    pub trade_fee_rate:    u32,           // trade fee in 1e6ths of volume
    pub tick_spacing:      u16,           // default spacing for pools using this config
    pub fund_fee_rate:     u32,           // fraction of trade fee to fund, denom 1e6
    pub padding_u32: u32,

    pub fund_owner: Pubkey,
    pub padding: [u64; 3],
}
```

A typical published set of CLMM fee tiers (confirm against `GET https://api-v3.raydium.io/main/clmm-config`):

| Index | `trade_fee_rate` | Tick spacing | Typical use             |
| ----- | ---------------- | ------------ | ----------------------- |
| 0     | `100` (0.01%)    | 1            | Stable pairs, USDC/USDT |
| 1     | `500` (0.05%)    | 10           | Correlated blue-chips   |
| 2     | `2_500` (0.25%)  | 60           | Standard pairs          |
| 3     | `10_000` (1.00%) | 120          | Volatile or long-tail   |

`protocol_fee_rate` and `fund_fee_rate` are fractions of the trade fee; same convention as CPMM. See [`products/clmm/fees`](/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.

```rust theme={null}
pub const TICK_ARRAY_SIZE: usize = 60;
pub const TICK_ARRAY_SIZE_USIZE: usize = 60;

pub struct TickArrayState {
    pub pool_id:                Pubkey,
    pub start_tick_index:       i32,                            // lowest tick in this array
    pub ticks:                  [TickState; TICK_ARRAY_SIZE],   // 60 entries
    pub initialized_tick_count: u8,
    pub recent_epoch:           u64,
    pub padding:                [u8; 107],
}

pub struct TickState {
    pub tick:                       i32,
    pub liquidity_net:              i128,                       // ΔL when crossing this tick upward
    pub liquidity_gross:            u128,                       // total L referencing this tick
    pub fee_growth_outside_0_x64:   u128,                       // see math.mdx
    pub fee_growth_outside_1_x64:   u128,
    pub reward_growths_outside_x64: [u128; 3],

    // Limit-order bookkeeping. All zero for ticks that have never carried
    // a limit order. See products/clmm/math for the matching algorithm.
    pub order_phase:                  u64,                      // monotonic FIFO cohort id
    pub orders_amount:                u64,                      // unfilled tokens in current cohort
    pub part_filled_orders_remaining: u64,                      // remaining tokens of partially-filled cohort
    pub unfilled_ratio_x64:           u128,                     // Q64.64; starts at 1.0 and shrinks as fills occur

    pub padding:                    [u32; 3],
}
```

The four limit-order fields are zero on any tick that has never been used for a limit order. When orders are opened on a tick, the program tracks them as a sequence of cohorts:

* `order_phase` is the cohort id. It increments every time a cohort transitions from "all unfilled" to "partially filled."
* `orders_amount` is the input-token total of the current (newest) cohort.
* `part_filled_orders_remaining` tracks the previous cohort that is currently being filled by ongoing swaps.
* `unfilled_ratio_x64` is 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.

Rules:

* 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 no `ClosePosition`-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:

```rust theme={null}
pub struct TickArrayBitmapExtension {
    pub pool_id: Pubkey,
    pub positive_tick_array_bitmap: [[u64; 8]; 14],
    pub negative_tick_array_bitmap: [[u64; 8]; 14],
}
```

If your position's range is "normal", you never think about the extension account. Full-range positions (e.g., `(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.

```rust theme={null}
pub struct PersonalPositionState {
    pub bump: [u8; 1],
    pub nft_mint: Pubkey,                 // this position's NFT mint
    pub pool_id:  Pubkey,

    pub tick_lower_index: i32,
    pub tick_upper_index: i32,

    pub liquidity: u128,                  // this position's L

    // Fee-growth snapshots at last time the position was touched.
    pub fee_growth_inside_0_last_x64: u128,
    pub fee_growth_inside_1_last_x64: u128,
    pub token_fees_owed_0: u64,           // accrued since last collect
    pub token_fees_owed_1: u64,

    pub reward_infos: [PositionRewardInfo; 3],
    pub recent_epoch: u64,
    pub padding: [u64; 7],
}

pub struct PositionRewardInfo {
    pub growth_inside_last_x64: u128,
    pub reward_amount_owed: u64,
}
```

### `ProtocolPositionState` (deprecated)

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

## Observation

```rust theme={null}
pub const OBSERVATION_NUM: usize = 100;

pub struct Observation {
    pub block_timestamp: u32,
    pub tick_cumulative: i64,                            // Σ tick_current × Δt
    pub padding:         [u64; 4],
}

pub struct ObservationState {
    pub initialized:       bool,
    pub recent_epoch:      u64,
    pub observation_index: u16,
    pub pool_id:           Pubkey,
    pub observations:      [Observation; OBSERVATION_NUM], // 100 entries
    pub padding:           [u64; 4],
}
```

CLMM's observation buffer stores a **cumulative tick**, not a cumulative price. External consumers compute the geometric-mean price over an interval from `(tick_cumulative[t1] − tick_cumulative[t0]) / (t1 − t0)` and then `price = 1.0001 ** tick`. See [`algorithms/clmm-math`](/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`

```rust theme={null}
// programs/amm/src/states/pool_fee.rs
pub struct DynamicFeeConfig {
    pub index:                       u16,    // identifier; PDA seed component
    pub filter_period:               u16,    // seconds — within this window the volatility reference is held
    pub decay_period:                u16,    // seconds — beyond this window the reference fully decays
    pub reduction_factor:            u16,    // fixed-point in [1, 10_000); applied at decay
    pub dynamic_fee_control:         u32,    // fixed-point in (0, 100_000); fee-rate gain
    pub max_volatility_accumulator:  u32,    // ceiling on the volatility accumulator
    pub padding:                     [u64; 8],
}
```

PDA seed: `["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`)

```rust theme={null}
pub struct DynamicFeeInfo {
    pub filter_period:                u16,
    pub decay_period:                 u16,
    pub reduction_factor:             u16,
    pub dynamic_fee_control:          u32,
    pub max_volatility_accumulator:   u32,
    pub tick_spacing_index_reference: i32,    // tick-spacing-units; reference for next swap
    pub volatility_reference:         u32,    // running floor for the accumulator
    pub volatility_accumulator:       u32,    // current cumulative volatility (capped)
    pub last_update_timestamp:        u64,
    pub padding:                      [u8; 46],
}
```

The bottom four fields are state; the top five are calibration copied from `DynamicFeeConfig`. The fee math and the decay rules are documented under [`products/clmm/math`](/products/clmm/math) and [`products/clmm/fees`](/products/clmm/fees).

Constants used by the formula:

| Constant                          | Value     | Meaning                                   |
| --------------------------------- | --------- | ----------------------------------------- |
| `VOLATILITY_ACCUMULATOR_SCALE`    | `10_000`  | Granularity of the volatility accumulator |
| `REDUCTION_FACTOR_DENOMINATOR`    | `10_000`  | Denominator for `reduction_factor`        |
| `DYNAMIC_FEE_CONTROL_DENOMINATOR` | `100_000` | Denominator for `dynamic_fee_control`     |
| `MAX_FEE_RATE_NUMERATOR`          | `100_000` | Hard cap of 10% on the resulting fee rate |

## `LimitOrderState`

One account per open limit order.

```rust theme={null}
// programs/amm/src/states/limit_order.rs
pub struct LimitOrderState {
    pub pool_id:            Pubkey,
    pub owner:              Pubkey,
    pub tick_index:         i32,
    pub zero_for_one:       bool,    // direction: true sells token0 for token1
    pub order_phase:        u64,     // snapshot of TickState.order_phase at open time
    pub total_amount:       u64,     // input-token amount placed
    pub filled_amount:      u64,     // informational; computed precisely on settle
    pub settle_base:        u64,     // unfilled remainder at last settle/decrease
    pub settled_output:     u64,     // cumulative output-token paid to owner
    pub open_time:          u64,
    pub unfilled_ratio_x64: u128,    // Q64.64 snapshot of TickState.unfilled_ratio_x64 at open
    pub padding:            [u64; 4],
}
```

Lifecycle:

1. **Open** — user calls `open_limit_order`, deposits `total_amount` of the input token, the order is bound to a `TickState` cohort.
2. **(optional) Increase / Decrease** — `increase_limit_order` adds to `total_amount`; `decrease_limit_order` returns unfilled tokens (and any settled output up to that point).
3. **Settle** — when the cohort is fully or partially filled, the owner *or* the operational keeper calls `settle_limit_order` to push output tokens to the owner's ATA.
4. **Close** — once `unfilled_amount == 0`, the account is closeable. Rent always returns to `owner`.

PDA seed: `[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.

```rust theme={null}
pub struct LimitOrderNonce {
    pub user_wallet: Pubkey,
    pub nonce_index: u8,             // user-chosen, 0..255
    pub order_nonce: u64,            // monotonic, incremented every time a new order is opened
    pub padding:     [u64; 4],
}
```

PDA seed: `[user_wallet.as_ref(), &[nonce_index]]`. Most clients use `nonce_index = 0` and let `order_nonce` carry the cardinality.

## Deriving the key accounts

```ts theme={null}
import { PublicKey } from "@solana/web3.js";

const CLMM_PROGRAM_ID = new PublicKey(
  "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK"
); // see reference/program-addresses

function i32ToBytes(n: number): Buffer {
  const b = Buffer.alloc(4);
  b.writeInt32BE(n);
  return b;
}

export function deriveClmmAccounts(
  ammConfig: PublicKey,
  token0Mint: PublicKey,           // must already be sorted
  token1Mint: PublicKey,
) {
  const [poolState] = PublicKey.findProgramAddressSync(
    [Buffer.from("pool"), ammConfig.toBuffer(),
     token0Mint.toBuffer(), token1Mint.toBuffer()],
    CLMM_PROGRAM_ID,
  );
  const [observation] = PublicKey.findProgramAddressSync(
    [Buffer.from("observation"), poolState.toBuffer()],
    CLMM_PROGRAM_ID,
  );
  const [tickArrayBitmapExtension] = PublicKey.findProgramAddressSync(
    [Buffer.from("pool_tick_array_bitmap_extension"), poolState.toBuffer()],
    CLMM_PROGRAM_ID,
  );
  return { poolState, observation, tickArrayBitmapExtension };
}

export function deriveTickArray(
  pool: PublicKey,
  startTickIndex: number,
) {
  const [tickArray] = PublicKey.findProgramAddressSync(
    [Buffer.from("tick_array"), pool.toBuffer(), i32ToBytes(startTickIndex)],
    CLMM_PROGRAM_ID,
  );
  return tickArray;
}

export function deriveDynamicFeeConfig(index: number) {
  const idx = Buffer.alloc(2);
  idx.writeUInt16BE(index);
  const [pda] = PublicKey.findProgramAddressSync(
    [Buffer.from("dynamic_fee_config"), idx],
    CLMM_PROGRAM_ID,
  );
  return pda;
}

export function deriveLimitOrderNonce(
  wallet: PublicKey,
  nonceIndex: number,
) {
  const [pda] = PublicKey.findProgramAddressSync(
    [wallet.toBuffer(), Buffer.from([nonceIndex & 0xff])],
    CLMM_PROGRAM_ID,
  );
  return pda;
}

export function deriveLimitOrder(
  wallet: PublicKey,
  nonceAccount: PublicKey,
  orderNonce: bigint,
) {
  const nonceBytes = Buffer.alloc(8);
  nonceBytes.writeBigUInt64BE(orderNonce);
  const [pda] = PublicKey.findProgramAddressSync(
    [wallet.toBuffer(), nonceAccount.toBuffer(), nonceBytes],
    CLMM_PROGRAM_ID,
  );
  return pda;
}

export function derivePersonalPosition(nftMint: PublicKey) {
  const [personalPosition] = PublicKey.findProgramAddressSync(
    [Buffer.from("position"), nftMint.toBuffer()],
    CLMM_PROGRAM_ID,
  );
  return personalPosition;
}
```

The exact seed strings should always be double-checked against the on-chain IDL and [`reference/program-addresses`](/reference/program-addresses).

## Lifecycle quick reference

| Event                           | Accounts created                                                                                                        | Accounts destroyed                                                           |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `CreatePool`                    | `poolState`, `observation`, `token_0_vault`, `token_1_vault`                                                            | —                                                                            |
| `OpenPosition[WithToken22Nft]`  | NFT mint + ATA, `personalPosition`, possibly new `tickArrayState`(s), `tickArrayBitmapExtension` if not yet existing    | —                                                                            |
| `IncreaseLiquidity`             | Possibly new `tickArrayState`(s)                                                                                        | —                                                                            |
| `DecreaseLiquidity`             | —                                                                                                                       | Possibly clears tick entries (but the `tickArrayState` itself is not closed) |
| `ClosePosition`                 | —                                                                                                                       | NFT mint, `personalPosition`                                                 |
| `SwapV2`                        | Possibly new `tickArrayState`                                                                                           | —                                                                            |
| `OpenLimitOrder`                | `limitOrderState`, possibly `limitOrderNonce` (init-if-needed), possibly new `tickArrayState`                           | —                                                                            |
| `IncreaseLimitOrder`            | —                                                                                                                       | —                                                                            |
| `DecreaseLimitOrder`            | —                                                                                                                       | Closes `limitOrderState` if order is fully consumed                          |
| `SettleLimitOrder`              | —                                                                                                                       | —                                                                            |
| `CloseLimitOrder`               | —                                                                                                                       | `limitOrderState` (rent → `owner`)                                           |
| `CreateDynamicFeeConfig`        | `dynamicFeeConfig`                                                                                                      | —                                                                            |
| `CreateCustomizablePool`        | `poolState`, `observation`, vaults — same as `CreatePool`. Snapshots `dynamicFeeConfig` if `enable_dynamic_fee = true`. | —                                                                            |
| `CollectRewards`                | —                                                                                                                       | —                                                                            |
| `UpdateRewardInfos`             | —                                                                                                                       | —                                                                            |
| `CloseProtocolPosition` (admin) | —                                                                                                                       | Vestigial `protocolPositionState` (rent → admin)                             |

`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`](/products/clmm/ticks-and-positions).
* **Swap walk and fee-growth math**: [`products/clmm/math`](/products/clmm/math).
* **Instruction account lists**: [`products/clmm/instructions`](/products/clmm/instructions).
* **Fees and reward accrual**: [`products/clmm/fees`](/products/clmm/fees).
* **Canonical program IDs and seeds**: [`reference/program-addresses`](/reference/program-addresses).

Sources:

* [`raydium-io/raydium-clmm` — `programs/amm/src/states`](https://github.com/raydium-io/raydium-clmm)
