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 →
Los ID del programa y las semillas PDA para CPMM se enumeran canónicamente en reference/program-addresses. Esta página se enfoca en para qué sirve cada cuenta y los invariantes que mantiene, no en las direcciones codificadas.

Las seis cuentas de un pool de CPMM

Cada pool de CPMM se describe completamente mediante seis direcciones derivadas del programa (PDA) bajo el programa CPMM, más una cuenta AmmConfig compartida a la que hace referencia. Una vez que tengas los dos mints, puedes derivar todo de forma determinística sin tocar la red.
CuentaSemilla(s)PropietarioPropósito
authority"vault_and_lp_mint_auth_seed"CPMMEl firmante para cada movimiento de bóveda y cada acuñación/quema de LP. Compartido entre todos los pools de CPMM.
poolState"pool", ammConfig, token0Mint, token1Mint o cualquier keypair aleatorio proporcionado por el firmanteCPMMLa estructura de estado del pool — par de mints, saldos de bóveda, suministro de LP, acumulación de comisiones, puntero de observación. La instrucción Initialize de CPMM acepta tanto el PDA canónico derivado de las cuatro semillas como un keypair arbitrario firmado por el creador. La ruta del keypair aleatorio existe para evitar un ataque de adelantamiento donde un adversario observa el mempool y se apresura a ocupar el PDA canónico antes que el creador legítimo.
lpMint"pool_lp_mint", poolStateSPL TokenEl token LP del pool. Suministro = total de LP pendiente. Autoridad de acuñación = el PDA de autoridad de CPMM.
vault0"pool_vault", poolState, token0MintSPL Token / Token-2022Contiene el saldo del token0 del pool. Propiedad del PDA de autoridad.
vault1"pool_vault", poolState, token1MintSPL Token / Token-2022Contiene el saldo del token1 del pool. Propiedad del PDA de autoridad.
observation"observation", poolStateCPMMBúfer de anillo de muestras de precios utilizadas para el TWAP. Se escribe en cada swap.
Y la configuración compartida:
CuentaSemilla(s)PropietarioPropósito
ammConfig"amm_config", index: u16CPMMContiene las tasas de comisiones comerciales/protocolo/fondo/creador y las claves de administrador. Una por “nivel de tarifa”. El estado del pool se vincula a uno en la creación y no puede cambiar después.

Derivar un pool a partir de solo dos 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,
  };
}
Siempre ordena los mints antes de derivar el PDA del pool. La semilla codifica los dos mints en orden de bytes, no en orden de usuario. Dos pools con (A, B) y (B, A) colisionarían en la cadena — la ordenación es cómo el programa hace que la asignación sea canónica.
El ID del pool no siempre es el PDA canónico. Initialize acepta un keypair firmante arbitrario como pool_state además del PDA anterior. Si la cuenta pasada no coincide con el PDA canónico, el programa requiere que sea un firmante — es decir, el creador pasa un keypair fresco que firma. Esta es la defensa contra adelantamiento: cualquier tercero que intente tomar el PDA canónico puede ser eludido por el creador legítimo usando un keypair aleatorio en su lugar. Los PDA posteriores (lpMint, vault0, vault1, observation) aún se derivan de poolState.key(), por lo que permanecen únicos para cualquier dirección que se haya utilizado. Cuando indexas pools, siempre descubre el ID del pool desde el estado en cadena (por ejemplo, cuentas PoolState bajo el programa CPMM), no derivando el PDA canónico — este último perderá los pools con keypair aleatorio.

Diseños de cuentas

Las definiciones completas en Rust viven en la fuente de raydium-cp-swap. Los campos a continuación son los que leerás desde una integración.

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],
}
Qué leer realmente:
  • lp_supply — el espejo interno del pool del suministro total del mint de LP. Úsalo para las matemáticas de participación de LP; el valor debe coincidir con el suministro en cadena del mint, pero leerlo desde PoolState evita una búsqueda de cuenta adicional.
  • protocol_fees_token{0,1}, fund_fees_token{0,1} — comisiones acumuladas aún no barridas. Estas no afectan el precio del swap; se mantienen en las bóvedas hasta que se llame a CollectProtocolFee / CollectFundFee.
  • status — una máscara de bits que controla si se permiten Swap, Deposit, Withdraw. Actualizado por el administrador mediante UpdatePoolStatus. El SDK verifica esto antes de construir una transacción; si estás haciendo CPI directamente, verifica por ti mismo.
  • token0_program / token1_program — el programa de token en el que hacer CPI para cada bóveda. Uno puede ser SPL Token clásico y el otro Token-2022; son independientes.
  • open_time — una marca de tiempo Unix. Los swaps antes de esta hora fallan. Los depósitos se permiten antes de open_time para que el pool pueda ser sembrado.
  • creator_fee_on / enable_creator_fee — juntos controlan si la comisión del creador opcional está activa para este pool y de qué lado del swap se cobra. enable_creator_fee == false anula completamente la ruta de comisión del creador. Cuando está habilitado, creator_fee_on selecciona: 0 = cobrar comisión del token que sea la entrada del swap (BothToken); 1 = cobrar comisión solo de token_0 (omitir en swaps token_1 → token_0); 2 = cobrar comisión solo de token_1. Se establece en la creación del pool mediante InitializeWithPermission; no puede cambiar después.
  • creator_fees_token_{0,1} — comisiones del creador acumuladas, barridas 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],
}
Tres cosas a tener cuidado:
  1. trade_fee_rate y creator_fee_rate son fracciones del volumen, ambas denominadas en unidades de 1/1_000_000. 2500 significa 0,25% del volumen comercial. protocol_fee_rate y fund_fee_rate son fracciones de la comisión comercial (no del volumen), con el mismo denominador 1/1_000_000. La comisión del creador no es una fracción de la comisión comercial — es su propia tasa independiente. La aritmética completa está en products/cpmm/fees.
  2. index es un u16, por lo que la semilla hash usa 2 bytes big-endian. Un error de uno en el orden de bytes es un error de integración común.
  3. AmmConfig es inmutable a nivel de pool. Un pool apunta a un AmmConfig en la creación y nunca cambia. Los cambios de comisión se propagan porque el pool lee la configuración en cada swap — pero el pool no puede moverse entre niveles de tarifa.
Una nota sobre las comisiones del creador: la tasa misma (creator_fee_rate) vive en AmmConfig y se comparte entre el nivel de tarifa. Si un pool en particular realmente la cobra (enable_creator_fee) y de qué lado del swap cae (creator_fee_on) viven en PoolState. La comisión del creador es independiente de la comisión comercial — es su propia tasa, acumulada en sus propios contadores (creator_fees_token_{0,1}), y nunca reduce las participaciones de LP / protocolo / fondo de la comisión comercial. El barrido se realiza mediante CollectCreatorFee. Consulta products/cpmm/fees para la mecánica completa.

Permission

Una pequeña cuenta de control de acceso utilizada por InitializeWithPermission. El programa CPMM admite una ruta de creación de pool con permisos para que otros programas (por ejemplo, LaunchLab cuando gradúa un token a CPMM) puedan demostrar que tienen derecho a crear un pool contra un AmmConfig dado.
pub struct Permission {
    pub authority: Pubkey,    // who is allowed to call InitializeWithPermission
    pub padding: [u64; 8],
}
El PDA de Permission se crea por el administrador de CPMM mediante CreatePermissionPda y se revoca mediante ClosePermissionPda. Los usuarios finales no interactúan directamente con esta cuenta — es tubería para flujos entre programas.

Bóvedas y Token-2022

vault0 y vault1 son propiedad del PDA de autoridad de CPMM, y su propietario del programa de token (token_program) es SPL Token o Token-2022, determinado en la creación del pool por el programa del mint. El pool maneja los dos casos de forma transparente — pasas el ID del programa de token correcto para cada lado en las cuentas de instrucción Swap / Deposit / Withdraw. CPMM impone una lista de extensiones permitidas estricta en la creación del pool (is_supported_mint en utils/token.rs). Un mint de Token-2022 solo puede usarse en un pool de CPMM si cada extensión que lleva está en esta lista:
  • TransferFeeConfig. Aplicado por el mint en cada transferencia. El pool está del lado receptor para depósitos SwapBaseInput y del lado de envío para retiros. El programa calcula la cantidad neta que llega a la bóveda y establece la curva en consecuencia. Consulta algorithms/token-2022-transfer-fees.
  • MetadataPointer y TokenMetadata. Metadatos estándar en mint. Sin efecto en las matemáticas del swap.
  • InterestBearingConfig. El monto UI del mint acumula interés. La bóveda almacena montos brutos; la curva opera solo en montos brutos. Las IU que muestran APR deben llamar a los helpers de Token-2022 para renderizar el monto UI.
  • ScaledUiAmount. Extensión de escalado de pantalla UI. El mismo tratamiento que InterestBearingConfig — la curva usa montos brutos.
Cualquier otra extensión — PermanentDelegate, TransferHook, DefaultAccountState, NonTransferable, ConfidentialTransfer, Group/GroupMember, MintCloseAuthority, etc. — causa que Initialize rechace con NotSupportMint. La excepción es una pequeña lista blanca de mints codificada en el programa (un puñado de pubkeys específicas) que omite la verificación de extensión; se utiliza para incorporar mints específicas caso por caso. La lista de extensiones verificadas y la lista blanca de mints viven en la fuente de CP-Swap bajo programs/cp-swap/src/utils/token.rs y pueden cambiar con futuras actualizaciones del programa.

Observation

La cuenta de observación es un búfer de anillo de entradas ObservationState, cada una almacenando un block_timestamp y un precio acumulativo. En cada swap, el programa añade una nueva observación si ha pasado suficiente tiempo desde la última. Los TWAP se calculan leyendo dos observaciones y dividiendo Δ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],
}
El búfer de anillo tiene un tamaño para 100 observaciones. Cada observación tiene 40 bytes, por lo que solo el array tiene 4,000 bytes; el PDA ObservationState completo tiene alrededor de 4,100 bytes después de los campos circundantes y el discriminador. Dos reglas para consumidores:
  • No uses una observación única como precio. Es un acumulativo, no un precio spot. Usa dos para calcular un TWAP.
  • Elige observaciones al menos un bloque separadas. Los swaps dentro del mismo bloque pueden no producir una nueva observación; leer de forma consecutiva puede devolver el mismo registro.
Más matemáticas en products/clmm/accounts.

Ciclo de vida de la cuenta

EventoCuentas creadasCuentas destruidas
InitializepoolState, lpMint, vault0, vault1, observation
Deposit— (puede crear ATA de LP de usuario)
Withdraw
Swap— (puede crear ATA de destino de usuario)
CollectProtocolFee
CollectFundFee
UpdatePoolStatus
Los pools de CPMM y sus PDA nunca se cierran. Incluso con liquidez cero, el poolState permanece. Esto es deliberado: resembrar el mismo pool más tarde preserva su búfer de observaciones histórico y su derivación PDA permanece estable.

Qué leer dónde

Fuentes: