Passer au contenu 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.

Cette page est traduite automatiquement par IA. La version anglaise fait foi.Voir la version anglaise →
Les ID de programme et les seeds PDA pour CPMM sont listés canoniquement dans reference/program-addresses. Cette page se concentre sur à quoi sert chaque compte et les invariants qu’il maintient, pas sur les adresses codées en dur.

Les six comptes d’un pool CPMM

Chaque pool CPMM est entièrement décrit par six adresses dérivées du programme (PDA) sous le programme CPMM, plus un compte partagé AmmConfig auquel il fait référence. Une fois que vous avez les deux mints, vous pouvez dériver tout de manière déterministe sans toucher le réseau.
CompteSeed(s)PropriétaireObjectif
authority"vault_and_lp_mint_auth_seed"CPMMLe signataire pour chaque mouvement de coffre et chaque mint/burn LP. Partagé entre tous les pools CPMM.
poolState"pool", ammConfig, token0Mint, token1Mint ou une paire de clés aléatoire fournie par un signataireCPMMLa structure d’état du pool — paire de mints, soldes des coffres, approvisionnement en LP, accumulation de frais, pointeur d’observation. L’instruction CPMM Initialize accepte soit le PDA canonique dérivé des quatre seeds soit une paire de clés arbitraire signée par le créateur. Le chemin des paires de clés aléatoires existe pour contrer une attaque de front-running où un adversaire surveille le mempool et fait la course pour occuper le PDA canonique avant le créateur légitime.
lpMint"pool_lp_mint", poolStateSPL TokenLe jeton LP du pool. Approvisionnement = total LP en circulation. Autorité de mint = le PDA d’autorité CPMM.
vault0"pool_vault", poolState, token0MintSPL Token / Token-2022Détient le solde du pool en token0. Possédé par le PDA d’autorité.
vault1"pool_vault", poolState, token1MintSPL Token / Token-2022Détient le solde du pool en token1. Possédé par le PDA d’autorité.
observation"observation", poolStateCPMMRing buffer d’échantillons de prix utilisés pour le TWAP. Écrit à chaque swap.
Et la configuration partagée :
CompteSeed(s)PropriétaireObjectif
ammConfig"amm_config", index: u16CPMMDétient les taux de frais commerciaux/protocole/fonds/créateurs et les clés d’admin. Un par « niveau de frais ». PoolState se lie à un à la création et ne peut pas changer plus tard.

Dériver un pool à partir de rien d’autre que deux 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,
  };
}
Triez toujours les mints avant de dériver le PDA du pool. Le seed hache les deux mints dans l’ordre des octets, pas dans l’ordre de l’utilisateur. Deux pools avec (A, B) et (B, A) entreraient en collision on-chain — le tri est comment le programme rend le mappage canonique.
L’ID du pool n’est pas toujours le PDA canonique. Initialize accepte une paire de clés signataire arbitraire comme pool_state en plus du PDA ci-dessus. Si le compte transmis ne correspond pas au PDA canonique, le programme exige qu’il soit un signataire — c’est-à-dire que le créateur transmet une nouvelle paire de clés qu’il signe. C’est la défense contre le front-running : tout tiers qui fait la course pour saisir le PDA canonique peut être contourné par le créateur légitime qui utilise à la place une paire de clés aléatoire. Les PDA en aval (lpMint, vault0, vault1, observation) sont toujours dérivés de poolState.key(), ils restent donc uniques à l’adresse utilisée. Quand vous indexez les pools, découvrez toujours l’ID du pool à partir de l’état on-chain (par exemple, les comptes PoolState sous le programme CPMM), pas en dérivant le PDA canonique — ce dernier manquera les pools avec des paires de clés aléatoires.

Dispositions des comptes

Les définitions Rust complètes vivent dans la source raydium-cp-swap. Les champs ci-dessous sont ceux que vous lirez lors d’une intégration.

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],
}
Ce qu’il faut vraiment lire :
  • lp_supply — le miroir interne du pool de l’approvisionnement total du mint LP. Utilisez-le pour les calculs de part LP ; la valeur doit correspondre à l’approvisionnement on-chain du mint, mais la lire à partir de PoolState évite une récupération supplémentaire de compte.
  • protocol_fees_token{0,1}, fund_fees_token{0,1} — frais accumulés non encore balayés. Ceux-ci n’affectent pas la tarification des swaps ; ils restent dans les coffres jusqu’à ce que CollectProtocolFee / CollectFundFee soit appelé.
  • status — un masque de bits contrôlant si Swap, Deposit, Withdraw sont autorisés. Mis à jour par l’admin via UpdatePoolStatus. Le SDK le vérifie avant de construire une transaction ; si vous faites du CPI directement, vérifiez-le vous-même.
  • token0_program / token1_program — le programme de jetons vers lequel faire du CPI pour chaque coffre. L’un peut être SPL Token classique et l’autre Token-2022 ; ils sont indépendants.
  • open_time — un timestamp Unix. Les swaps avant cette heure échouent. Les dépôts sont autorisés avant open_time pour que le pool puisse être amorcé.
  • creator_fee_on / enable_creator_fee — ensemble contrôlent si le frais de créateur facultatif est actif pour ce pool et de quel côté du swap il est collecté. enable_creator_fee == false annule le chemin du frais de créateur. Quand activé, creator_fee_on sélectionne : 0 = prendre un frais sur quel que soit le jeton qui est l’entrée du swap (BothToken) ; 1 = prendre un frais sur token_0 uniquement (sauter sur les swaps token_1 → token_0) ; 2 = prendre un frais sur token_1 uniquement. Défini à la création du pool via InitializeWithPermission ; ne peut pas changer plus tard.
  • creator_fees_token_{0,1} — frais de créateur accumulés, balayés par 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],
}
Trois choses à faire attention :
  1. trade_fee_rate et creator_fee_rate sont des fractions du volume, tous deux exprimés en unités de 1/1_000_000. 2500 signifie 0,25 % du volume commercial. protocol_fee_rate et fund_fee_rate sont des fractions du frais commercial (pas du volume), avec le même dénominateur 1/1_000_000. Le frais de créateur est pas une fraction du frais commercial — c’est son propre taux indépendant. L’arithmétique complète se trouve dans products/cpmm/fees.
  2. index est un u16, donc le seed hash utilise 2 octets big-endian. Un décalage d’ordre d’octets est un bug d’intégration courant.
  3. AmmConfig est immuable au niveau du pool. Un pool pointe vers un AmmConfig à la création et ne change jamais. Les changements de frais se propagent parce que le pool lit la config à chaque swap — mais le pool ne peut pas être déplacé entre les niveaux de frais.
Une note sur les frais de créateur : le taux lui-même (creator_fee_rate) vit sur AmmConfig et est partagé dans le niveau de frais. Si un pool particulier le facture réellement (enable_creator_fee) et de quel côté du swap il arrive (creator_fee_on) vivent sur PoolState. Le frais de créateur est indépendant du frais commercial — c’est son propre taux, accumulé dans ses propres compteurs (creator_fees_token_{0,1}), et ne réduit jamais les parts LP / protocole / fonds du frais commercial. Le balayage se fait via CollectCreatorFee. Voir products/cpmm/fees pour la mécanique complète.

Permission

Un petit compte de contrôle d’accès utilisé par InitializeWithPermission. Le programme CPMM supporte un chemin de création de pool avec permission pour que d’autres programmes (par exemple LaunchLab lors de la promotion d’un jeton vers CPMM) puissent prouver qu’ils sont autorisés à créer un pool contre un AmmConfig donné.
pub struct Permission {
    pub authority: Pubkey,    // who is allowed to call InitializeWithPermission
    pub padding: [u64; 8],
}
Le PDA Permission est créé par l’admin CPMM via CreatePermissionPda et révoqué via ClosePermissionPda. Les utilisateurs finaux n’interagissent pas directement avec ce compte — c’est de la tuyauterie pour les flux cross-program.

Coffres et Token-2022

vault0 et vault1 sont possédés par le PDA d’autorité CPMM, et leur propriétaire du programme de jetons (token_program) est soit SPL Token soit Token-2022, déterminé à la création du pool par le programme du mint. Le pool gère les deux cas de manière transparente — vous passez le bon ID de programme de jetons pour chaque côté dans les comptes d’instruction Swap / Deposit / Withdraw. CPMM applique une stricte liste de permission des extensions à la création du pool (is_supported_mint dans utils/token.rs). Un mint Token-2022 ne peut être utilisé dans un pool CPMM que si chaque extension qu’il porte se trouve sur cette liste :
  • TransferFeeConfig. Appliquée par le mint à chaque transfert. Le pool est du côté récepteur pour les dépôts SwapBaseInput et du côté expéditeur pour les retraits. Le programme calcule la quantité nette arrivant dans le coffre et définit la courbe en conséquence. Voir algorithms/token-2022-transfer-fees.
  • MetadataPointer et TokenMetadata. Métadonnées standard on-mint. Aucun effet sur les calculs de swap.
  • InterestBearingConfig. La quantité d’affichage du mint accumule les intérêts. Le coffre stocke les quantités brutes ; la courbe opère uniquement sur les quantités brutes. Les interfaces qui affichent l’APR doivent appeler les aides Token-2022 pour afficher la quantité d’affichage.
  • ScaledUiAmount. Extension de mise à l’échelle d’affichage d’interface. Même traitement que InterestBearingConfig — la courbe utilise les quantités brutes.
Toute autre extension — PermanentDelegate, TransferHook, DefaultAccountState, NonTransferable, ConfidentialTransfer, Group/GroupMember, MintCloseAuthority, etc. — provoque le rejet de Initialize avec NotSupportMint. L’exception est une petite liste de mints codée en dur dans le programme (quelques pubkeys spécifiques) qui contourne la vérification des extensions ; elle est utilisée pour intégrer des mints spécifiques au cas par cas. La liste des extensions vérifiées et la liste blanche des mints vivent dans la source CP-Swap sous programs/cp-swap/src/utils/token.rs et peuvent changer avec les futures mises à niveau du programme.

Observation

Le compte observation est un ring buffer d’entrées ObservationState, chacune stockant un block_timestamp et un prix cumulatif. À chaque swap, le programme ajoute une nouvelle observation si suffisamment de temps s’est écoulé depuis la dernière. Les TWAP sont calculés en lisant deux observations et en divisant Δ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],
}
Le ring buffer est dimensionné pour 100 observations. Chaque observation fait 40 octets, donc le tableau seul fait 4 000 octets ; le ObservationState PDA complet fait environ 4 100 octets après les champs entourant et le discriminateur. Deux règles pour les consommateurs :
  • N’utilisez pas une observation unique comme prix. C’est un cumulatif, pas un prix spot. Utilisez deux d’entre elles pour calculer un TWAP.
  • Choisissez les observations au moins un bloc à part. Les swaps au cours du même bloc peuvent ne pas produire une nouvelle observation ; relire en arrière peut retourner le même enregistrement.
Plus de calculs dans products/clmm/accounts.

Cycle de vie des comptes

ÉvénementComptes créésComptes détruits
InitializepoolState, lpMint, vault0, vault1, observation
Deposit— (peut créer un ATA LP utilisateur)
Withdraw
Swap— (peut créer un ATA de destination utilisateur)
CollectProtocolFee
CollectFundFee
UpdatePoolStatus
Les pools CPMM et leurs PDA ne sont jamais fermés. Même avec zéro liquidité, le poolState reste. C’est volontaire : réensemencer le même pool plus tard préserve son tampon d’observation historique et sa dérivation PDA reste stable.

Où lire quoi

Sources :