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 →
A ID do programa e as sementes de PDA para CPMM estão listadas canonicamente em reference/program-addresses. Esta página se concentra em para que serve cada conta e os invariantes que ela mantém, não nos endereços hardcoded.

As seis contas de um pool CPMM

Cada pool CPMM é totalmente descrito por seis endereços derivados do programa (PDAs) sob o programa CPMM, além de uma conta AmmConfig compartilhada que ele referencia. Uma vez que você tenha os dois mints, pode derivar tudo de forma determinística sem tocar na rede.
ContaSeed(s)ProprietárioPropósito
authority"vault_and_lp_mint_auth_seed"CPMMO signatário para cada movimento de vault e cada mint/burn de LP. Compartilhado entre todos os pools CPMM.
poolState"pool", ammConfig, token0Mint, token1Mint ou qualquer keypair aleatória fornecida pelo signatárioCPMMA struct de estado do pool — par de mints, saldos de vault, fornecimento de LP, acúmulo de taxas, ponteiro de observação. A instrução Initialize do CPMM aceita tanto o PDA canônico derivado das quatro sementes quanto um keypair arbitrário assinado pelo criador. O caminho de keypair aleatória existe para derrotar um ataque de front-running onde um adversário observa o mempool e corre para ocupar o PDA canônico antes do criador legítimo.
lpMint"pool_lp_mint", poolStateSPL TokenO token LP do pool. Fornecimento = total de LP em aberto. Autoridade de mint = PDA de autoridade CPMM.
vault0"pool_vault", poolState, token0MintSPL Token / Token-2022Mantém o saldo token0 do pool. Possuído pelo PDA de autoridade.
vault1"pool_vault", poolState, token1MintSPL Token / Token-2022Mantém o saldo token1 do pool. Possuído pelo PDA de autoridade.
observation"observation", poolStateCPMMRing buffer de amostras de preço usadas para o TWAP. Escrito em cada swap.
E a configuração compartilhada:
ContaSeed(s)ProprietárioPropósito
ammConfig"amm_config", index: u16CPMMMantém as taxas de trade/protocolo/fundo/criador e chaves de admin. Uma por “faixa de taxa”. Poolstate vincula-se a uma na criação e não pode mudar depois.

Derivando um pool de nada além de dois mints

import { PublicKey } from "@solana/web3.js";
import BN from "bn.js";

const CPMM_PROGRAM_ID = new PublicKey(
  "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"
); // mainnet — see reference/program-addresses

function u16ToBytes(n: number): Buffer {
  const b = Buffer.alloc(2);
  b.writeUInt16BE(n);
  return b;
}

// token0 < token1 by byte order. Getting this wrong yields a valid PDA
// that points at a nonexistent pool.
function sortMints(a: PublicKey, b: PublicKey): [PublicKey, PublicKey] {
  return Buffer.compare(a.toBuffer(), b.toBuffer()) < 0 ? [a, b] : [b, a];
}

export function deriveCpmmAccounts(
  mintA: PublicKey,
  mintB: PublicKey,
  ammConfigIndex = 0,
) {
  const [token0Mint, token1Mint] = sortMints(mintA, mintB);

  const [ammConfig] = PublicKey.findProgramAddressSync(
    [Buffer.from("amm_config"), u16ToBytes(ammConfigIndex)],
    CPMM_PROGRAM_ID,
  );
  const [authority] = PublicKey.findProgramAddressSync(
    [Buffer.from("vault_and_lp_mint_auth_seed")],
    CPMM_PROGRAM_ID,
  );
  const [poolState] = PublicKey.findProgramAddressSync(
    [
      Buffer.from("pool"),
      ammConfig.toBuffer(),
      token0Mint.toBuffer(),
      token1Mint.toBuffer(),
    ],
    CPMM_PROGRAM_ID,
  );
  const [lpMint] = PublicKey.findProgramAddressSync(
    [Buffer.from("pool_lp_mint"), poolState.toBuffer()],
    CPMM_PROGRAM_ID,
  );
  const [vault0] = PublicKey.findProgramAddressSync(
    [Buffer.from("pool_vault"), poolState.toBuffer(), token0Mint.toBuffer()],
    CPMM_PROGRAM_ID,
  );
  const [vault1] = PublicKey.findProgramAddressSync(
    [Buffer.from("pool_vault"), poolState.toBuffer(), token1Mint.toBuffer()],
    CPMM_PROGRAM_ID,
  );
  const [observation] = PublicKey.findProgramAddressSync(
    [Buffer.from("observation"), poolState.toBuffer()],
    CPMM_PROGRAM_ID,
  );

  return {
    ammConfig,
    authority,
    poolState,
    lpMint,
    token0Mint,
    token1Mint,
    vault0,
    vault1,
    observation,
  };
}
Sempre ordene os mints antes de derivar o PDA do pool. A seed faz hash dos dois mints em ordem de bytes, não em ordem do usuário. Dois pools com (A, B) e (B, A) entrariam em colisão na cadeia — a ordenação é como o programa torna o mapeamento canônico.
Pool ID nem sempre é o PDA canônico. Initialize aceita um keypair signatário arbitrário como pool_state além do PDA acima. Se a conta passada não corresponder ao PDA canônico, o programa requer que seja um signatário — ou seja, o criador passa um keypair novo que assina. Esta é a defesa contra front-run: qualquer terceiro correndo para agarrar o PDA canônico pode ser contornado pelo criador legítimo usando um keypair aleatório. Os PDAs posteriores (lpMint, vault0, vault1, observation) ainda são derivados de poolState.key(), então permanecem únicos para qualquer endereço usado. Ao indexar pools, sempre descubra o pool ID a partir do estado na cadeia (por exemplo, contas PoolState sob o programa CPMM), não derivando o PDA canônico — o último perderá pools com keypair aleatório.

Layouts de contas

As definições Rust completas vivem na fonte raydium-cp-swap. Os campos abaixo são aqueles que você lerá de uma integração.

PoolState

// programs/cp-swap/src/states/pool.rs
pub struct PoolState {
    pub amm_config: Pubkey,               // binds this pool to an AmmConfig
    pub pool_creator: Pubkey,             // who ran initialize
    pub token_0_vault: Pubkey,            // == vault0 PDA
    pub token_1_vault: Pubkey,            // == vault1 PDA

    pub lp_mint: Pubkey,
    pub token_0_mint: Pubkey,
    pub token_1_mint: Pubkey,

    pub token_0_program: Pubkey,          // SPL Token or Token-2022 program
    pub token_1_program: Pubkey,

    pub observation_key: Pubkey,          // == observation PDA
    pub auth_bump: u8,
    pub status: u8,                       // bitmask: deposit | withdraw | swap
    pub lp_mint_decimals: u8,
    pub mint_0_decimals: u8,
    pub mint_1_decimals: u8,

    pub lp_supply: u64,                   // mirrors lp_mint supply
    pub protocol_fees_token_0: u64,
    pub protocol_fees_token_1: u64,
    pub fund_fees_token_0: u64,
    pub fund_fees_token_1: u64,

    pub open_time: u64,                   // unix; swaps rejected before this
    pub recent_epoch: u64,

    // Creator-fee state (added after the original layout):
    pub creator_fee_on: u8,               // 0=BothToken, 1=OnlyToken0, 2=OnlyToken1
    pub enable_creator_fee: bool,
    pub padding1: [u8; 6],
    pub creator_fees_token_0: u64,
    pub creator_fees_token_1: u64,

    pub padding: [u64; 28],
}
O que realmente ler:
  • lp_supply — o espelho interno do pool do fornecimento total do mint de LP. Use para matemática de compartilhamento de LP; o valor deve corresponder ao fornecimento na cadeia do mint, mas lê-lo de PoolState evita uma busca de conta extra.
  • protocol_fees_token{0,1}, fund_fees_token{0,1} — taxas acumuladas ainda não varridas. Estas não afetam o preço do swap; elas ficam nos vaults até que CollectProtocolFee / CollectFundFee seja chamado.
  • status — uma máscara de bits controlando se Swap, Deposit, Withdraw são permitidos. Atualizado pelo admin via UpdatePoolStatus. O SDK verifica isto antes de construir uma transação; se você estiver CPIando diretamente, verifique por si mesmo.
  • token0_program / token1_program — o programa de token para CPI em cada vault. Um pode ser SPL Token clássico e o outro Token-2022; eles são independentes.
  • open_time — um timestamp Unix. Swaps antes desta hora falham. Depósitos são permitidos antes de open_time para que o pool possa ser inicializado.
  • creator_fee_on / enable_creator_fee — juntos controlam se a taxa de criador opcional está ativa para este pool e de que lado do swap ela é coletada. enable_creator_fee == false zera o caminho de taxa do criador completamente. Quando ativado, creator_fee_on seleciona: 0 = cobrar taxa de qualquer token que seja a entrada do swap (BothToken); 1 = cobrar taxa apenas de token_0 (pular em swaps token_1 → token_0); 2 = cobrar taxa apenas de token_1. Definido na criação do pool via InitializeWithPermission; não pode mudar depois.
  • creator_fees_token_{0,1} — taxas de criador acumuladas, varridas por CollectCreatorFee.

AmmConfig

pub struct AmmConfig {
    pub bump: u8,
    pub disable_create_pool: bool,
    pub index: u16,                       // matches the seed
    pub trade_fee_rate: u64,              // e.g., 2500 = 0.25%
    pub protocol_fee_rate: u64,           // fraction of trade fee to protocol
    pub fund_fee_rate: u64,               // fraction of trade fee to fund
    pub create_pool_fee: u64,             // paid once at init (in SOL or token)
    pub protocol_owner: Pubkey,           // can call CollectProtocolFee
    pub fund_owner: Pubkey,               // can call CollectFundFee
    pub creator_fee_rate: u64,            // optional pool-creator fee rate (1/1_000_000 of volume)
    pub padding: [u64; 15],
}
Três coisas para ter cuidado:
  1. trade_fee_rate e creator_fee_rate são frações de volume, ambas denominadas em unidades de 1/1_000_000. 2500 significa 0,25% do volume de trade. protocol_fee_rate e fund_fee_rate são frações da taxa de trade (não de volume), no mesmo denominador 1/1_000_000. A taxa de criador não é uma fração da taxa de trade — é sua própria taxa independente. A aritmética completa está em products/cpmm/fees.
  2. index é um u16, então a seed hash usa 2 bytes big-endian. Um erro de um byte na ordem dos bytes é um bug de integração comum.
  3. AmmConfig é imutável no nível do pool. Um pool aponta para um AmmConfig na criação e nunca muda. As mudanças de taxa se propagam porque o pool lê a configuração a cada swap — mas o pool não pode ser movido entre faixas de taxa.
Uma observação sobre taxas de criador: a taxa em si (creator_fee_rate) vive em AmmConfig e é compartilhada entre a faixa de taxa. Se um pool específico realmente a cobra (enable_creator_fee) e de que lado do swap ela cai (creator_fee_on) vivem em PoolState. A taxa de criador é independente da taxa de trade — é sua própria taxa, acumulada em seus próprios contadores (creator_fees_token_{0,1}), e nunca reduz os compartilhamentos LP / protocolo / fundo da taxa de trade. Varrer é via CollectCreatorFee. Veja products/cpmm/fees para a mecânica completa.

Permission

Uma pequena conta de controle de acesso usada por InitializeWithPermission. O programa CPMM suporta um caminho de criação de pool com permissão para que outros programas (por exemplo, LaunchLab ao graduar um token para CPMM) possam provar que estão autorizados a criar um pool contra um determinado AmmConfig.
pub struct Permission {
    pub authority: Pubkey,    // who is allowed to call InitializeWithPermission
    pub padding: [u64; 8],
}
O PDA de Permission é criado pelo admin do CPMM via CreatePermissionPda e revogado via ClosePermissionPda. Usuários finais não interagem com esta conta diretamente — é encanamento para fluxos entre programas.

Vaults e Token-2022

vault0 e vault1 são possuídos pelo PDA de autoridade do CPMM, e seu proprietário de programa de token (token_program) é SPL Token ou Token-2022, determinado na criação do pool pelo programa do mint. O pool lida com os dois casos de forma transparente — você passa o ID correto do programa de token para cada lado nas contas de instrução Swap / Deposit / Withdraw. O CPMM impõe uma lista de permissões de extensão rigorosa na criação do pool (is_supported_mint em utils/token.rs). Um mint Token-2022 pode ser usado em um pool CPMM apenas se cada extensão que ele carrega estiver nesta lista:
  • TransferFeeConfig. Aplicado pelo mint em cada transferência. O pool fica no lado receptivo para depósitos SwapBaseInput e no lado emissor para saques. O programa calcula o valor líquido aterrando no vault e define a curva de acordo. Veja algorithms/token-2022-transfer-fees.
  • MetadataPointer e TokenMetadata. Metadados padrão de mint. Nenhum efeito na matemática de swap.
  • InterestBearingConfig. O valor de UI do mint acumula juros. O vault armazena quantidades brutas; a curva opera apenas em quantidades brutas. UIs que mostram APR devem chamar os helpers Token-2022 para renderizar o valor de UI.
  • ScaledUiAmount. Extensão de escala de exibição de UI. Mesmo tratamento que InterestBearingConfig — a curva usa quantidades brutas.
Qualquer outra extensão — PermanentDelegate, TransferHook, DefaultAccountState, NonTransferable, ConfidentialTransfer, Group/GroupMember, MintCloseAuthority, etc. — causa que Initialize rejeite com NotSupportMint. A exceção é uma pequena lista de mints hardcoded no programa (um punhado de pubkeys específicas) que contorna a verificação de extensão; é usada para integrar mints específicas caso a caso. A lista de extensões verificadas e a lista de mints whitelist vivem na fonte CP-Swap sob programs/cp-swap/src/utils/token.rs e podem mudar com futuras atualizações de programa.

Observation

A conta de observação é um ring buffer de entradas ObservationState, cada uma armazenando um block_timestamp e um preço cumulativo. Em cada swap o programa adiciona uma nova observação se tempo suficiente passou desde a última. TWAPs são calculados lendo duas observações e dividindo Δcumulative / Δtime.
// OBSERVATION_NUM is hardcoded in the program to 100.
pub const OBSERVATION_NUM: usize = 100;

pub struct Observation {
    pub block_timestamp:              u64,
    pub cumulative_token_0_price_x32: u128,   // Q32.32, top 64 bits left for overflow
    pub cumulative_token_1_price_x32: u128,
}

pub struct ObservationState {
    pub initialized:           bool,
    pub observation_index:     u16,                            // circular index
    pub pool_id:               Pubkey,
    pub observations:          [Observation; OBSERVATION_NUM], // 100 entries
    pub last_update_timestamp: u64,                            // timestamp of the most recent append
    pub padding:               [u64; 3],
}
O ring buffer é dimensionado para 100 observações. Cada observação tem 40 bytes, então o array sozinho tem 4.000 bytes; o PDA ObservationState completo tem cerca de 4.100 bytes após os campos circundantes e o discriminador. Duas regras de consumidor:
  • Não use uma única observação como preço. É um cumulativo, não um preço spot. Use duas delas para calcular um TWAP.
  • Escolha observações pelo menos um bloco separado. Swaps no mesmo bloco podem não produzir uma nova observação; leitura back-to-back pode retornar o mesmo registro.
Mais matemática em products/clmm/accounts.

Ciclo de vida da conta

EventoContas criadasContas destruídas
InitializepoolState, lpMint, vault0, vault1, observation
Deposit— (pode criar ATA de LP do usuário)
Withdraw
Swap— (pode criar ATA de destino do usuário)
CollectProtocolFee
CollectFundFee
UpdatePoolStatus
Pools CPMM e seus PDAs nunca são fechados. Mesmo com liquidez zero o poolState permanece. Isto é intencional: reinicializar o mesmo pool depois preserva seu buffer de observação histórico e sua derivação de PDA permanece estável.

O que ler onde

Fontes: