Saltar al contenido principal

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.

Esta página fue traducida automáticamente por IA. La versión en inglés es la fuente autorizada.Ver versión en inglés →
Esta página describe el esquema y rol de cada cuenta. Las semillas canónicas están listadas en reference/program-addresses. Un pool CLMM utiliza más cuentas que un pool CPMM porque la liquidez se almacena de forma dispersa a lo largo del rango de ticks; comprender esa dispersión es el tema central de esta página.

Inventario de cuentas

Un pool CLMM activo se describe mediante las siguientes familias de cuentas. Todas pertenecen al programa CLMM, excepto los dos mints y sus vaults.
CuentaPropósitoCantidad por pool
AmmConfigNivel de comisión: tasa de comisión de trading, porcentaje de protocolo, porcentaje de fondo, tick-spacing por defecto. Compartida entre todos los pools de este nivel.1 (compartida)
PoolStatesqrt_price_x64 actual, tick actual, liquidez total, acumuladores globales de comisiones, información de recompensas, puntero de observación.1
TickArrayStateUn bloque de TICK_ARRAY_SIZE ticks adyacentes. Solo se inicializa bajo demanda.0 ≤ N ≤ rango
TickArrayBitmapExtensionBitmap de desbordamiento que registra qué tick arrays existen más allá del bitmap inline de PoolState.0 o 1
PersonalPositionStateUna por posición de LP. Almacena el rango, la liquidez y el último crecimiento de comisiones/recompensas visto. Authority = propietario del NFT.1 por posición
NFT mint de posiciónMint con supply 1, asociada a PersonalPositionState. Transferir el NFT transfiere la posición.1 por posición
ObservationStateBuffer circular de observaciones de precio para el TWAP.1
token_0_vault, token_1_vaultCuentas de token que contienen los saldos del pool. Propiedad de la authority del pool.2
DynamicFeeConfigConjunto de parámetros reutilizable para el mecanismo de comisión dinámica. Los pools creados con create_customizable_pool pueden activarlo. Administrado por el admin.compartida (por índice)
LimitOrderStateUna por orden límite abierta. Registra el propietario, el tick, la dirección, el importe total y el snapshot del output liquidado.1 por orden
LimitOrderNonceContador por (wallet, nonce_index) que genera PDAs de orden únicas.1 por (wallet, índice)

PoolState

El estado en vivo del pool, leído en cada swap y en cada cambio de posición.
// 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],
}
Campos con los que interactuarás directamente:
  • sqrt_price_x64 y tick_current representan el estado de precio del pool. Se actualizan juntos en cada swap. tick_current es el entero inferior de log_{1.0001}(price).
  • liquidity es la liquidez activa: la suma de los valores L de todas las posiciones cuyo rango contiene a tick_current. Cambia cada vez que un swap cruza un tick y cada vez que se abre, cierra o redimensiona una posición.
  • fee_growth_global_{0,1}_x64 son las comisiones acumuladas por unidad de liquidez a lo largo de toda la historia del pool. Las posiciones los leen para calcular lo que se les debe.
  • tick_spacing queda fijado al AmmConfig durante la inicialización y nunca cambia. Determina qué índices de tick pueden ser puntos extremos de una posición.
  • tick_array_bitmap es un bitmap inline que cubre el rango de ticks más habitual alrededor del precio spot. Para pools con posiciones muy alejadas del centro, el seguimiento del desbordamiento vive en la cuenta separada TickArrayBitmapExtension.
  • fee_on se fija en el momento de la creación del pool. El valor 0 (FromInput) reproduce el comportamiento clásico de Uniswap V3. Los valores 1 y 2 dirigen la comisión de swap a un solo lado del libro — consulta products/clmm/fees para ver los compromisos de diseño.
  • dynamic_fee_info almacena el estado de volatilidad para el recargo de comisión dinámica. Cuando está habilitado, cada swap recalcula un dynamic_fee_component sobre AmmConfig.trade_fee_rate. El esquema se documenta en DynamicFeeInfo más abajo; los pools sin comisión dinámica dejan toda la estructura en cero.

AmmConfig

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],
}
Conjunto típico de niveles de comisión CLMM publicados (verifica contra GET https://api-v3.raydium.io/main/clmm-config):
Índicetrade_fee_rateTick spacingUso típico
0100 (0,01%)1Pares estables, USDC/USDT
1500 (0,05%)10Blue-chips correlacionados
22_500 (0,25%)60Pares estándar
310_000 (1,00%)120Activos volátiles o de cola larga
protocol_fee_rate y fund_fee_rate son fracciones de la comisión de trading; misma convención que en CPMM. Consulta products/clmm/fees.

TickArrayState

El CLMM no almacena un registro por tick individual, ya que eso implicaría miles de millones de cuentas. En cambio, agrupa TICK_ARRAY_SIZE ticks adyacentes (inicializados o no, típicamente 60 u 88 según la versión del programa) en un TickArrayState que se crea de forma diferida en el primer uso.
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],
}
Los cuatro campos de orden límite son cero en cualquier tick que nunca haya sido usado para una orden límite. Cuando se abren órdenes en un tick, el programa las gestiona como una secuencia de cohortes:
  • order_phase es el identificador de cohorte. Se incrementa cada vez que una cohorte pasa de «totalmente sin ejecutar» a «parcialmente ejecutada».
  • orders_amount es el total en tokens de entrada de la cohorte actual (la más reciente).
  • part_filled_orders_remaining lleva el seguimiento de la cohorte anterior que está siendo ejecutada en curso por los swaps activos.
  • unfilled_ratio_x64 es un multiplicador Q64.64 asociado a la cohorte: cuando un swap ejecuta el X% de la cohorte, el ratio se multiplica por (1 − X). Cada orden abierta almacena su propio snapshot de (order_phase, unfilled_ratio_x64) en el momento de apertura, por lo que la liquidación se reduce a comparar snapshots.
Reglas:
  • Un tick t que sea extremo de posición debe satisfacer t % tick_spacing == 0. El programa rechaza posiciones que no respeten el espaciado.
  • El array de un tick se ubica en floor(t / (TICK_ARRAY_SIZE * tick_spacing)) * (TICK_ARRAY_SIZE * tick_spacing).
  • Un tick array se inicializa de forma diferida: la primera posición o swap que toca un array no inicializado lo crea y paga el alquiler.
  • El programa nunca cierra un tick array. Una vez asignado, persiste durante toda la vida del pool, incluso después de que todos los ticks dentro de él vuelvan a liquidity_gross == 0. Las posiciones y swaps posteriores reutilizan la cuenta existente sin coste adicional de alquiler. No existe una ruta de limpieza de tick arrays derivada de ClosePosition.

TickArrayBitmapExtension

PoolState.tick_array_bitmap (inline) cubre el rango «cercano al spot» — ±1.024 tick arrays. Fuera de ese rango (para valores de tick extremos), el programa mantiene una cuenta de extensión:
pub struct TickArrayBitmapExtension {
    pub pool_id: Pubkey,
    pub positive_tick_array_bitmap: [[u64; 8]; 14],
    pub negative_tick_array_bitmap: [[u64; 8]; 14],
}
Si el rango de tu posición es «normal», nunca tendrás que pensar en la cuenta de extensión. Las posiciones de rango completo (p. ej., (MIN_TICK, MAX_TICK)) sí la requieren; el SDK la resuelve por ti.

Posiciones

Una posición CLMM es un conjunto de tres cuentas más un mint:

NFT mint de posición

Un mint de token SPL con supply 1. La dirección del mint es un PDA determinista; el NFT de posición en la wallet del propietario es simplemente una ATA que contiene ese único token. Transferir el NFT es el mecanismo por el que cambia de manos una posición — el programa vincula la autorización al titular actual del saldo en la ATA del NFT, no a una Pubkey almacenada en el estado.

PersonalPositionState

Una por posición abierta. Su clave se deriva del mint del NFT.
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 (obsoleto)

Las versiones anteriores del CLMM almacenaban la contabilidad agregada por (pool, tick_lower, tick_upper) en un PDA llamado ProtocolPositionState. Las versiones más recientes ya no crean ni leen esta cuenta. El slot sigue apareciendo en las listas de cuentas de OpenPosition / IncreaseLiquidity / DecreaseLiquidity como UncheckedAccount por compatibilidad con el ABI, pero el programa no escribe en él. Las cuentas existentes en la cadena son vestigiales; el admin puede llamar a CloseProtocolPosition para recuperar el alquiler.La contabilidad agregada de rango ahora se deriva directamente de los dos ticks extremos (liquidity_gross, liquidity_net y los campos fee_growth_outside_* / reward_growths_outside_x64 por tick) en TickArrayState. La fórmula de crecimiento de comisiones dentro del rango fee_growth_inside = global − outside_lower − outside_upper sigue funcionando sin necesidad de una cuenta de posición agregada.

Observation

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],
}
El buffer de observaciones del CLMM almacena un tick acumulado, no un precio acumulado. Los consumidores externos calculan el precio de media geométrica sobre un intervalo a partir de (tick_cumulative[t1] − tick_cumulative[t0]) / (t1 − t0) y luego price = 1.0001 ** tick. Consulta algorithms/clmm-math.

DynamicFeeConfig y DynamicFeeInfo

Los parámetros de comisión dinámica viven en dos lugares. La plantilla reutilizable — DynamicFeeConfig — es administrada por el admin y compartida entre los pools que la activan. El estado de ejecución por pool — DynamicFeeInfo — está embebido en PoolState y se actualiza en cada swap.

DynamicFeeConfig

// 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],
}
Semilla PDA: ["dynamic_fee_config", index.to_be_bytes()]. Se crea mediante create_dynamic_fee_config (solo admin) y se modifica con update_dynamic_fee_config. Un pool creado con enable_dynamic_fee = true copia los cinco parámetros de calibración de la config (filter_period, decay_period, reduction_factor, dynamic_fee_control, max_volatility_accumulator) en su propio DynamicFeeInfo en el momento de la creación; las ediciones posteriores al DynamicFeeConfig no afectan retroactivamente a los pools existentes.

DynamicFeeInfo (embebido en PoolState)

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],
}
Los cuatro campos inferiores son estado; los cinco superiores son la calibración copiada de DynamicFeeConfig. La matemática de la comisión y las reglas de decaimiento están documentadas en products/clmm/math y products/clmm/fees. Constantes usadas por la fórmula:
ConstanteValorSignificado
VOLATILITY_ACCUMULATOR_SCALE10_000Granularidad del acumulador de volatilidad
REDUCTION_FACTOR_DENOMINATOR10_000Denominador de reduction_factor
DYNAMIC_FEE_CONTROL_DENOMINATOR100_000Denominador de dynamic_fee_control
MAX_FEE_RATE_NUMERATOR100_000Límite estricto del 10% sobre la tasa de comisión resultante

LimitOrderState

Una cuenta por orden límite abierta.
// 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],
}
Ciclo de vida:
  1. Apertura — el usuario llama a open_limit_order, deposita total_amount del token de entrada y la orden queda vinculada a una cohorte de TickState.
  2. (Opcional) Aumento / Reducciónincrease_limit_order añade al total_amount; decrease_limit_order devuelve los tokens sin ejecutar (y cualquier output liquidado hasta ese momento).
  3. Liquidación — cuando la cohorte está total o parcialmente ejecutada, el propietario o el keeper operacional llama a settle_limit_order para enviar los tokens de salida a la ATA del propietario.
  4. Cierre — una vez que unfilled_amount == 0, la cuenta puede cerrarse. El alquiler siempre retorna al owner.
Semilla PDA: [owner.as_ref(), limit_order_nonce.key().as_ref(), limit_order_nonce.order_nonce.to_be_bytes().as_ref()]. El PDA de la orden es por tanto único por (owner, nonce_index, order_nonce).

LimitOrderNonce

Contador por (wallet, nonce_index) que permite a un mismo usuario ejecutar múltiples pipelines paralelas de órdenes límite sin colisionar en los PDAs.
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],
}
Semilla PDA: [user_wallet.as_ref(), &[nonce_index]]. La mayoría de los clientes usan nonce_index = 0 y dejan que order_nonce gestione la cardinalidad.

Derivar las cuentas clave

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;
}
Las cadenas de semilla exactas siempre deben verificarse contra el IDL on-chain y reference/program-addresses.

Referencia rápida del ciclo de vida

EventoCuentas creadasCuentas destruidas
CreatePoolpoolState, observation, token_0_vault, token_1_vault
OpenPosition[WithToken22Nft]NFT mint + ATA, personalPosition, posiblemente nuevos tickArrayState(s), tickArrayBitmapExtension si no existe aún
IncreaseLiquidityPosiblemente nuevos tickArrayState(s)
DecreaseLiquidityPosiblemente limpia entradas de tick (pero el tickArrayState en sí no se cierra)
ClosePositionNFT mint, personalPosition
SwapV2Posiblemente nuevo tickArrayState
OpenLimitOrderlimitOrderState, posiblemente limitOrderNonce (init-if-needed), posiblemente nuevo tickArrayState
IncreaseLimitOrder
DecreaseLimitOrderCierra limitOrderState si la orden está totalmente consumida
SettleLimitOrder
CloseLimitOrderlimitOrderState (alquiler → owner)
CreateDynamicFeeConfigdynamicFeeConfig
CreateCustomizablePoolpoolState, observation, vaults — igual que CreatePool. Copia dynamicFeeConfig si enable_dynamic_fee = true.
CollectRewards
UpdateRewardInfos
CloseProtocolPosition (admin)protocolPositionState vestigial (alquiler → admin)
Las cuentas TickArrayState nunca son cerradas por el programa — persisten durante toda la vida del pool. Una vez inicializado un tick array, permanece en la cadena aunque todos los ticks dentro de él vuelvan a liquidity_gross == 0. Reutilizar un tick array existente es gratuito; solo la primera posición que toca un array nunca inicializado paga su alquiler.

Dónde leer cada tema

Fuentes: