Saltar para o conteúdo 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 foi traduzida automaticamente por IA. A versão em inglês é a fonte oficial.Ver versão em inglês →
Esta página descreve o layout e a função de cada conta. As seeds canônicas estão listadas em reference/program-addresses. Um pool CLMM utiliza mais contas do que um pool CPMM porque a liquidez é armazenada de forma esparsa ao longo do intervalo de ticks; entender essa esparsidade é o tema principal desta página.

Inventário de contas

Um pool CLMM ativo é descrito pelas seguintes famílias de contas. Todas pertencem ao programa CLMM, exceto as duas mints e seus vaults.
ContaFinalidadeQuantidade por pool
AmmConfigFee tier: taxa de negociação, participação do protocolo, participação do fundo, tick-spacing padrão. Compartilhada entre todos os pools desse tier.1 (compartilhada)
PoolStatesqrt_price_x64 atual, tick atual, liquidez total, crescimento global de taxas, informações de recompensa, ponteiro de observação.1
TickArrayStateUm bloco de TICK_ARRAY_SIZE ticks adjacentes. Inicializado apenas sob demanda.0 ≤ N ≤ intervalo
TickArrayBitmapExtensionBitmap de overflow que rastreia quais arrays de tick existem além do bitmap inline em PoolState.0 ou 1
PersonalPositionStateUm por posição de LP. Armazena o intervalo, a liquidez e o crescimento de taxas/recompensas visto pela última vez. Autoridade = dono do NFT.1 por posição
NFT mint da posiçãoMint com supply 1, associada ao PersonalPositionState. Transferir o NFT transfere a posição.1 por posição
ObservationStateBuffer circular de observações de preço para o TWAP.1
token_0_vault, token_1_vaultContas de token que guardam os saldos do pool. Pertencentes à autoridade do pool.2
DynamicFeeConfigConjunto de parâmetros reutilizável para o mecanismo de taxa dinâmica. Pools criados via create_customizable_pool podem aderir. Gerenciado pelo admin.compartilhada (por índice)
LimitOrderStateUm por ordem limitada aberta. Registra o dono, tick, direção, valor total e snapshot da saída liquidada.1 por ordem
LimitOrderNonceContador por (wallet, nonce_index) que deriva PDAs únicas para cada ordem.1 por (wallet, índice)

PoolState

O estado ativo do pool, lido a cada swap e a cada alteração de posição.
// 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 que você realmente utilizará:
  • sqrt_price_x64 e tick_current representam o estado de preço do pool. São atualizados juntos a cada swap. tick_current é o piso de log_{1.0001}(price).
  • liquidity é a liquidez ativa — a soma dos valores L de todas as posições cujo intervalo contém tick_current. Ela muda sempre que um swap cruza um tick e sempre que uma posição é aberta, fechada ou redimensionada.
  • fee_growth_global_{0,1}_x64 são as taxas acumuladas por unidade de liquidez ao longo de todo o histórico do pool. As posições consultam esses valores para calcular o que lhes é devido.
  • tick_spacing é fixado no AmmConfig na inicialização e nunca muda. Ele determina quais índices de tick podem ser endpoints de posição.
  • tick_array_bitmap é um bitmap inline que cobre o intervalo de tick mais utilizado em torno do preço spot. Para pools cujas posições se estendem muito além desse intervalo, o rastreamento de overflow fica na conta separada TickArrayBitmapExtension.
  • fee_on é definido na criação do pool. 0 (FromInput) reproduz o comportamento clássico do Uniswap-V3. 1 e 2 direcionam a taxa de swap para um único lado do livro — veja products/clmm/fees para as implicações.
  • dynamic_fee_info carrega o estado de volatilidade para o acréscimo de taxa dinâmica. Quando habilitado, cada swap recalcula um dynamic_fee_component sobre o AmmConfig.trade_fee_rate. O layout está documentado em DynamicFeeInfo abaixo; pools sem taxa dinâmica mantêm toda a struct zerada.

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],
}
Um conjunto típico de fee tiers CLMM publicados (confirme em GET https://api-v3.raydium.io/main/clmm-config):
Índicetrade_fee_rateTick spacingUso típico
0100 (0,01%)1Pares estáveis, USDC/USDT
1500 (0,05%)10Blue-chips correlacionadas
22_500 (0,25%)60Pares padrão
310_000 (1,00%)120Ativos voláteis ou de cauda longa
protocol_fee_rate e fund_fee_rate são frações da taxa de negociação; mesma convenção do CPMM. Veja products/clmm/fees.

TickArrayState

O CLMM não armazena um registro por tick — isso resultaria em bilhões de contas. Em vez disso, ele agrupa TICK_ARRAY_SIZE ticks adjacentes (tipicamente 60 ou 88, dependendo da versão do programa), sejam eles inicializados ou não, em um TickArrayState criado de forma lazy no primeiro 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],
}
Os quatro campos de ordem limitada são zero em qualquer tick que nunca foi utilizado para uma ordem limitada. Quando ordens são abertas em um tick, o programa as rastreia como uma sequência de coortes:
  • order_phase é o id da coorte. Ele incrementa toda vez que uma coorte transita de “totalmente não preenchida” para “parcialmente preenchida.”
  • orders_amount é o total do token de entrada da coorte atual (a mais recente).
  • part_filled_orders_remaining rastreia a coorte anterior que está sendo preenchida pelos swaps em andamento.
  • unfilled_ratio_x64 é um multiplicador Q64.64 mantido na coorte: quando um swap preenche X% da coorte, a razão é multiplicada por (1 − X). Cada ordem aberta armazena seu próprio snapshot de (order_phase, unfilled_ratio_x64) no momento da abertura, de modo que o cálculo de liquidação se reduz a comparar snapshots.
Regras:
  • Um tick de endpoint de posição t deve satisfazer t % tick_spacing == 0. O programa rejeita posições fora do espaçamento.
  • O array do tick está localizado em floor(t / (TICK_ARRAY_SIZE * tick_spacing)) * (TICK_ARRAY_SIZE * tick_spacing).
  • Um tick array é inicializado de forma lazy: a primeira posição ou swap que toca um array não inicializado o cria, pagando o rent.
  • Um tick array nunca é fechado pelo programa. Uma vez alocado, ele persiste durante toda a vida do pool, mesmo após todos os ticks dentro dele retornarem a liquidity_gross == 0. Posições e swaps subsequentes reutilizam a conta existente sem custo adicional de rent. Não há caminho de limpeza via ClosePosition para tick arrays.

TickArrayBitmapExtension

PoolState.tick_array_bitmap (inline) cobre o intervalo “próximo ao spot” — ±1.024 tick arrays. Além desse intervalo (para valores extremos de tick), o programa mantém uma conta de extensão:
pub struct TickArrayBitmapExtension {
    pub pool_id: Pubkey,
    pub positive_tick_array_bitmap: [[u64; 8]; 14],
    pub negative_tick_array_bitmap: [[u64; 8]; 14],
}
Se o intervalo da sua posição for “normal”, você nunca precisará pensar na conta de extensão. Posições de intervalo completo (p. ex., (MIN_TICK, MAX_TICK)) exigem ela; o SDK resolve isso por você.

Posições

Uma posição CLMM é um conjunto de três contas mais uma mint:

NFT mint da posição

Uma mint SPL Token com supply 1. O endereço da mint é um PDA determinístico; o NFT da posição na carteira do dono é simplesmente uma ATA que guarda esse único token. Transferir o NFT é como uma posição muda de titular — o programa vincula a autorização ao portador atual do saldo da ATA do NFT, não a uma Pubkey armazenada no estado.

PersonalPositionState

Um por posição aberta. Indexado pela mint do 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)

Versões anteriores do CLMM armazenavam bookkeeping agregado por (pool, tick_lower, tick_upper) em um PDA ProtocolPositionState. As versões mais recentes não criam nem leem mais essa conta. O slot ainda aparece nas listas de contas de OpenPosition / IncreaseLiquidity / DecreaseLiquidity como UncheckedAccount por compatibilidade de ABI, mas o programa não escreve nela. As contas existentes on-chain são vestigiais; o admin pode chamar CloseProtocolPosition para recuperar o rent delas.O bookkeeping agregado de intervalos agora é derivado diretamente dos dois ticks de endpoint (liquidity_gross, liquidity_net e os fee_growth_outside_* / reward_growths_outside_x64 por tick) no TickArrayState. A fórmula de crescimento de taxa interna fee_growth_inside = global − outside_lower − outside_upper continua funcionando sem uma conta de posição 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],
}
O buffer de observação do CLMM armazena um tick cumulativo, não um preço cumulativo. Consumidores externos calculam o preço médio geométrico em um intervalo a partir de (tick_cumulative[t1] − tick_cumulative[t0]) / (t1 − t0) e depois price = 1.0001 ** tick. Veja algorithms/clmm-math.

DynamicFeeConfig e DynamicFeeInfo

Os parâmetros de taxa dinâmica residem em dois lugares. O template reutilizável — DynamicFeeConfig — é gerenciado pelo admin e compartilhado entre os pools que aderem. O estado de execução por pool — DynamicFeeInfo — está embutido em PoolState e atualizado a 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],
}
Seed do PDA: ["dynamic_fee_config", index.to_be_bytes()]. Criado via create_dynamic_fee_config (restrito ao admin) e modificado via update_dynamic_fee_config. Um pool criado com enable_dynamic_fee = true copia os cinco parâmetros de calibração da config (filter_period, decay_period, reduction_factor, dynamic_fee_control, max_volatility_accumulator) para seu próprio DynamicFeeInfo no momento da criação; edições posteriores ao DynamicFeeConfig não afetam retroativamente pools já existentes.

DynamicFeeInfo (embutido em 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],
}
Os quatro campos inferiores são estado; os cinco superiores são parâmetros de calibração copiados do DynamicFeeConfig. O cálculo da taxa e as regras de decaimento estão documentados em products/clmm/math e products/clmm/fees. Constantes utilizadas pela fórmula:
ConstanteValorSignificado
VOLATILITY_ACCUMULATOR_SCALE10_000Granularidade do acumulador de volatilidade
REDUCTION_FACTOR_DENOMINATOR10_000Denominador de reduction_factor
DYNAMIC_FEE_CONTROL_DENOMINATOR100_000Denominador de dynamic_fee_control
MAX_FEE_RATE_NUMERATOR100_000Limite máximo de 10% sobre a taxa resultante

LimitOrderState

Uma conta por ordem limitada aberta.
// 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. Abrir — o usuário chama open_limit_order, deposita total_amount do token de entrada; a ordem é vinculada a uma coorte do TickState.
  2. (opcional) Aumentar / Diminuirincrease_limit_order adiciona a total_amount; decrease_limit_order devolve os tokens não preenchidos (e qualquer saída liquidada até aquele ponto).
  3. Liquidar — quando a coorte é total ou parcialmente preenchida, o dono ou o keeper operacional chama settle_limit_order para enviar os tokens de saída para a ATA do dono.
  4. Fechar — assim que unfilled_amount == 0, a conta pode ser fechada. O rent sempre retorna ao owner.
Seed do PDA: [owner.as_ref(), limit_order_nonce.key().as_ref(), limit_order_nonce.order_nonce.to_be_bytes().as_ref()]. O PDA da ordem é, portanto, único por (owner, nonce_index, order_nonce).

LimitOrderNonce

Contador por (wallet, nonce_index) que permite a um único usuário executar múltiplos pipelines paralelos de ordens limitadas sem colisão de 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],
}
Seed do PDA: [user_wallet.as_ref(), &[nonce_index]]. A maioria dos clientes usa nonce_index = 0 e deixa o order_nonce controlar a cardinalidade.

Derivando as contas principais

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;
}
As strings de seed exatas devem sempre ser verificadas contra o IDL on-chain e reference/program-addresses.

Referência rápida do ciclo de vida

EventoContas criadasContas destruídas
CreatePoolpoolState, observation, token_0_vault, token_1_vault
OpenPosition[WithToken22Nft]NFT mint + ATA, personalPosition, possivelmente novos tickArrayState(s), tickArrayBitmapExtension se ainda não existir
IncreaseLiquidityPossivelmente novos tickArrayState(s)
DecreaseLiquidityPossivelmente limpa entradas de tick (mas o tickArrayState em si não é fechado)
ClosePositionNFT mint, personalPosition
SwapV2Possivelmente novo tickArrayState
OpenLimitOrderlimitOrderState, possivelmente limitOrderNonce (init-if-needed), possivelmente novo tickArrayState
IncreaseLimitOrder
DecreaseLimitOrderFecha limitOrderState se a ordem for totalmente consumida
SettleLimitOrder
CloseLimitOrderlimitOrderState (rent → owner)
CreateDynamicFeeConfigdynamicFeeConfig
CreateCustomizablePoolpoolState, observation, vaults — igual a CreatePool. Copia dynamicFeeConfig se enable_dynamic_fee = true.
CollectRewards
UpdateRewardInfos
CloseProtocolPosition (admin)protocolPositionState vestigial (rent → admin)
As contas TickArrayState nunca são fechadas pelo programa — elas persistem durante toda a vida do pool. Uma vez que um tick array tenha sido inicializado, ele permanece on-chain mesmo quando todos os ticks dentro dele retornam a liquidity_gross == 0. Reutilizar um tick array existente é gratuito; apenas a primeira posição a tocar um array nunca inicializado paga seu rent.

O que ler e onde

Fontes: