Zum Hauptinhalt springen

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.

Diese Seite wurde mit KI automatisch übersetzt. Maßgeblich ist stets die englische Version.Englische Version ansehen →
Programm-ID und PDA-Seeds für CPMM sind kanonisch in reference/program-addresses aufgelistet. Diese Seite konzentriert sich auf wofür jedes Konto ist und welche Invarianten es aufrechterhält, nicht auf die hartcodierten Adressen.

Die sechs Konten eines CPMM-Pools

Jeder CPMM-Pool wird vollständig durch sechs programmabgeleitete Adressen (PDAs) unter dem CPMM-Programm beschrieben, plus ein gemeinsames AmmConfig-Konto, auf das er verweist. Sobald Sie die beiden Mints haben, können Sie deterministisch alles ableiten, ohne das Netzwerk zu berühren.
KontoSeed(s)BesitzerZweck
authority"vault_and_lp_mint_auth_seed"CPMMDer Unterzeichner für jede Vault-Bewegung und jede LP-Mint/Burn. Gemeinsam über alle CPMM-Pools.
poolState"pool", ammConfig, token0Mint, token1Mint oder beliebiger von Unterzeichner bereitgestellter Zufalls-KeypairCPMMDie Pool-Status-Struktur — Mintpaar, Vault-Salden, LP-Angebot, Gebührenakkretion, Observation-Pointer. Die CPMM-Initialize-Anweisung akzeptiert entweder die kanonische PDA, die aus den vier Seeds abgeleitet wird, oder einen beliebigen Keypair, der vom Creator unterzeichnet wird. Der Zufalls-Keypair-Pfad existiert, um einen Front-Running-Angriff zu vereiteln, bei dem ein Gegner den Mempool beobachtet und sich beeilt, die kanonische PDA vor dem legitimen Creator zu belegen.
lpMint"pool_lp_mint", poolStateSPL TokenDer LP-Token des Pools. Angebot = ausstehende LP-Gesamtmenge. Mint-Autorität = die CPMM-Authority-PDA.
vault0"pool_vault", poolState, token0MintSPL Token / Token-2022Hält den Pool-Saldo von Token0. Im Besitz der Authority-PDA.
vault1"pool_vault", poolState, token1MintSPL Token / Token-2022Hält den Pool-Saldo von Token1. Im Besitz der Authority-PDA.
observation"observation", poolStateCPMMRingpuffer von Preisproben, die für die TWAP verwendet werden. Wird bei jedem Swap geschrieben.
Und die gemeinsame Konfiguration:
KontoSeed(s)BesitzerZweck
ammConfig"amm_config", index: u16CPMMHält die Handels-/Protokoll-/Fonds-/Creator-Gebührensätze und Admin-Schlüssel. Ein pro „Gebührentiers”. Poolstate bindet sich bei der Erstellung an einen und kann sich später nicht ändern.

Ableitung eines Pools aus nichts als zwei 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,
  };
}
Sortieren Sie Mints immer, bevor Sie die Pool-PDA ableiten. Der Seed hasht die zwei Mints in Byte-Reihenfolge, nicht in Benutzerreihenfolge. Zwei Pools mit (A, B) und (B, A) würden sich on-chain kollidieren — Sortieren ist, wie das Programm die Zuordnung kanonisch macht.
Pool-ID ist nicht immer die kanonische PDA. Initialize akzeptiert einen beliebigen Signer-Keypair als pool_state zusätzlich zur obigen PDA. Wenn das übergebene Konto nicht mit der kanonischen PDA übereinstimmt, erfordert das Programm, dass es ein Signer ist — d.h., der Creator übergibt einen frischen Keypair, mit dem sie signieren. Dies ist die Front-Run-Verteidigung: Jede dritte Partei, die sich beeilt, die kanonische PDA zu greifen, kann vom legitimen Creator durch die Verwendung eines Zufalls-Keypairs stattdessen umgangen werden. Die nachgelagerten PDAs (lpMint, vault0, vault1, observation) werden immer noch von poolState.key() abgeleitet, sodass sie eindeutig für jede verwendete Adresse bleiben. Wenn Sie Pools indexieren, entdecken Sie die Pool-ID immer aus dem on-chain-Status (z.B. PoolState-Konten unter dem CPMM-Programm), nicht durch Ableitung der kanonischen PDA — letztere wird Zufalls-Keypair-Pools vermissen.

Konto-Layouts

Die vollständigen Rust-Definitionen befinden sich in der raydium-cp-swap-Quelle. Die Felder unten sind diejenigen, die Sie aus einer Integration lesen werden.

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],
}
Was Sie tatsächlich lesen sollten:
  • lp_supply — der interne Spiegel des Pools des LP-Mints. Verwenden Sie ihn für LP-Anteil-Mathematik; der Wert sollte mit dem Mint’s on-chain-Angebot übereinstimmen, aber ihn aus PoolState zu lesen, vermeidet einen zusätzlichen Account-Abruf.
  • protocol_fees_token{0,1}, fund_fees_token{0,1}aufgelaufene Gebühren, die noch nicht eingezogen wurden. Diese beeinflussen nicht die Swap-Preisgestaltung; sie sitzen in den Vaults, bis CollectProtocolFee / CollectFundFee aufgerufen wird.
  • status — eine Bitmaske, die kontrolliert, ob Swap, Deposit, Withdraw erlaubt sind. Aktualisiert vom Admin über UpdatePoolStatus. Das SDK prüft dies, bevor es eine Transaktion erstellt; wenn Sie direkt CPI verwenden, prüfen Sie es selbst.
  • token0_program / token1_program — das Token-Programm, in das für jede Vault CPI durchgeführt wird. Das eine kann klassisches SPL Token und das andere Token-2022 sein; sie sind unabhängig.
  • open_time — ein Unix-Zeitstempel. Swaps vor dieser Zeit schlagen fehl. Ablagerungen sind vor open_time zulässig, damit der Pool aufgefüllt werden kann.
  • creator_fee_on / enable_creator_fee — kontrollieren zusammen, ob die optionale Creator-Gebühr für diesen Pool aktiv ist und auf welcher Seite des Swaps sie eingezogen wird. enable_creator_fee == false nullifiziert den Creator-Gebühren-Pfad vollständig. Wenn aktiviert, wählt creator_fee_on: 0 = Gebühr vom Token, das der Swap-Input ist (BothToken); 1 = Gebühr von token_0 nur (überspringen bei token_1 → token_0-Swaps); 2 = Gebühr von token_1 nur. Wird bei Pool-Erstellung über InitializeWithPermission festgelegt; kann sich später nicht ändern.
  • creator_fees_token_{0,1} — aufgelaufene Creator-Gebühren, eingezogen durch 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],
}
Drei Dinge, auf die Sie achten sollten:
  1. trade_fee_rate und creator_fee_rate sind Bruchteile des Volumens, beide in Einheiten von 1/1_000_000 ausgedrückt. 2500 bedeutet 0,25% des Swap-Volumens. protocol_fee_rate und fund_fee_rate sind Bruchteile der Handelsgebühr (nicht des Volumens), mit dem gleichen Nenner von 1/1_000_000. Die Creator-Gebühr ist nicht ein Bruchteil der Handelsgebühr — sie ist ihre eigene unabhängige Rate. Vollständige Arithmetik in products/cpmm/fees.
  2. index ist ein u16, also verwendet der Seed-Hash 2 Bytes Big-Endian. Ein Byte-Reihenfolge-Fehler um 1 ist ein häufiger Integrationsfehler.
  3. AmmConfig ist unveränderlich auf Pool-Ebene. Ein Pool verweist bei der Erstellung auf ein AmmConfig und wechselt nie. Gebührenänderungen werden verteilt, da der Pool die Konfiguration bei jedem Swap liest — aber der Pool kann nicht zwischen Gebührentiers verschoben werden.
Eine Anmerkung zu Creator-Gebühren: die Rate selbst (creator_fee_rate) lebt auf AmmConfig und wird über das Gebührentier geteilt. Ob ein bestimmter Pool sie tatsächlich berechnet (enable_creator_fee) und auf welcher Seite des Swaps sie landet (creator_fee_on) leben auf PoolState. Die Creator-Gebühr ist unabhängig von der Handelsgebühr — sie ist ihre eigene Rate, aufgelaufen auf ihren eigenen Zählern (creator_fees_token_{0,1}), und reduziert niemals die LP-/Protokoll-/Fonds-Anteile der Handelsgebühr. Eintreibung ist über CollectCreatorFee. Siehe products/cpmm/fees für die vollständige Mechanik.

Permission

Ein kleines Zugriffskontrollkonto, das von InitializeWithPermission verwendet wird. Das CPMM-Programm unterstützt einen Pool-Erstellungs-Pfad mit Genehmigung, damit andere Programme (z.B. LaunchLab beim Hochfahren eines Tokens zu CPMM) beweisen können, dass sie berechtigt sind, einen Pool gegen ein gegebenes AmmConfig zu erstellen.
pub struct Permission {
    pub authority: Pubkey,    // who is allowed to call InitializeWithPermission
    pub padding: [u64; 8],
}
Die Permission-PDA wird vom CPMM-Admin über CreatePermissionPda erstellt und über ClosePermissionPda widerrufen. Endbenutzer interagieren nicht direkt mit diesem Konto — es ist Installationen für Cross-Program-Flows.

Vaults und Token-2022

vault0 und vault1 werden von der CPMM-Authority-PDA besessen, und ihr Token-Programm-Besitzer (token_program) ist entweder SPL Token oder Token-2022, bestimmt bei Pool-Erstellung durch das Mint’s-Programm. Der Pool handhabt beide Fälle transparent — Sie übergeben die richtige Token-Programm-ID für jede Seite in den Swap / Deposit / Withdraw-Anweisungs-Konten. CPMM erzwingt eine strikte Erweiterungs-Whitelist bei Pool-Erstellung (is_supported_mint in utils/token.rs). Ein Token-2022-Mint kann in einem CPMM-Pool nur verwendet werden, wenn jede Erweiterung, die er trägt, auf dieser Liste steht:
  • TransferFeeConfig. Angewendet vom Mint bei jeder Übertragung. Der Pool ist auf der Empfängerseite für SwapBaseInput-Ablagerungen und auf der Sender-Seite für Abhebungen. Das Programm berechnet den Netto-Betrag, der in der Vault landet, und stellt die Kurve entsprechend ein. Siehe algorithms/token-2022-transfer-fees.
  • MetadataPointer und TokenMetadata. Standard-on-Mint-Metadaten. Keine Auswirkung auf Swap-Mathematik.
  • InterestBearingConfig. Der UI-Betrag des Mints sammelt Zinsen. Die Vault speichert Rohbeträge; die Kurve operiert nur auf Rohbeträgen. Benutzeroberflächen, die APR anzeigen, sollten die Token-2022-Helfer aufrufen, um den UI-Betrag zu rendern.
  • ScaledUiAmount. Erweiterung zur UI-Display-Skalierung. Gleiche Behandlung wie InterestBearingConfig — die Kurve verwendet Rohbeträge.
Jede andere Erweiterung — PermanentDelegate, TransferHook, DefaultAccountState, NonTransferable, ConfidentialTransfer, Group/GroupMember, MintCloseAuthority, usw. — verursacht, dass Initialize mit NotSupportMint ablehnt. Die Ausnahme ist eine kleine hartcodierte Mint-Whitelist im Programm (eine Handvoll spezifischer Pubkeys), die die Erweiterungs-Prüfung umgeht; sie wird verwendet, um spezifische Mints Fall für Fall zu integrieren. Die geprüfte Erweiterungs-Liste und die Mint-Whitelist befinden sich in der CP-Swap-Quelle unter programs/cp-swap/src/utils/token.rs und können sich bei zukünftigen Programm-Upgrades ändern.

Observation

Das Observation-Konto ist ein Ringpuffer von ObservationState-Einträgen, die jeweils einen block_timestamp und einen kumulativen Preis speichern. Bei jedem Swap fügt das Programm eine neue Observation an, wenn genügend Zeit seit der letzten vergangen ist. TWAPs werden berechnet, indem zwei Observations gelesen und Δcumulative / Δtime dividiert werden.
// 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],
}
Der Ringpuffer ist für 100 Observations dimensioniert. Jede Observation ist 40 Bytes, das Array allein also 4.000 Bytes; die vollständige ObservationState-PDA ist nach den umgebenden Feldern und dem Diskriminator etwa 4.100 Bytes. Zwei Konsumentenregeln:
  • Verwenden Sie nicht eine einzelne Observation als Preis. Es ist eine kumulative, keine Spotpreis. Verwenden Sie zwei davon, um eine TWAP zu berechnen.
  • Wählen Sie Observations mindestens einen Block auseinander. Swaps innerhalb des gleichen Blocks können keine neue Observation produzieren; das Zurücklesen nacheinander kann denselben Datensatz zurückgeben.
Weitere Mathematik in products/clmm/accounts.

Konto-Lebenszyklus

EreignisKonten erstelltKonten zerstört
InitializepoolState, lpMint, vault0, vault1, observation
Deposit— (kann Benutzer-LP-ATA erstellen)
Withdraw
Swap— (kann Benutzer-Ziel-ATA erstellen)
CollectProtocolFee
CollectFundFee
UpdatePoolStatus
CPMM-Pools und ihre PDAs werden niemals geschlossen. Auch bei Null-Liquidität bleibt der poolState erhalten. Dies ist absichtlich: Das erneute Aufseeden desselben Pools später bewahrt seinen historischen Observation-Puffer und seine PDA-Ableitung bleibt stabil.

Was wo lesen

Quellen: