跳轉到主要內容

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.

本頁內容由 AI 自動翻譯,所有內容以英文版本為準。查看英文版 →
CPMM 的程式 ID 與 PDA 種子已在 reference/program-addresses 中規範列舉。本頁著重於各帳户的用途及其維持的不變性,而非硬編碼地址。

CPMM 流動性池的六個帳户

每個 CPMM 流動性池均由 CPMM 程式下的六個衍生地址(PDA)完整描述,加上其引用的一個共享 AmmConfig 帳户。一旦有了兩個 mint,你可以無需接觸網路而確定性地推導出所有內容。
帳户種子所有者用途
authority"vault_and_lp_mint_auth_seed"CPMM簽署每次庫移動與每次 LP 鑄造/銷毀的簽名者。跨所有 CPMM 流動性池共享。
poolState"pool"ammConfigtoken0Minttoken1Mint 任何由簽署人提供的隨機密鑰對CPMM流動性池的狀態結構 — mint 對、庫餘額、LP 供應量、費用累積、觀察指針。CPMM Initialize 指令接受從四個種子衍生的規範 PDA 由建立者簽署的任意密鑰對。隨機密鑰對路徑存在是為了防止前置執行攻擊,其中對手監視記憶池並競速搶佔規範 PDA,搶在合法建立者之前。
lpMint"pool_lp_mint"poolStateSPL Token流動性池的 LP 代幣。供應量 = 總 LP 未償還額。鑄造授權 = CPMM authority PDA。
vault0"pool_vault"poolStatetoken0MintSPL Token / Token-2022保有流動性池的 token0 餘額。由 authority PDA 擁有。
vault1"pool_vault"poolStatetoken1MintSPL Token / Token-2022保有流動性池的 token1 餘額。由 authority PDA 擁有。
observation"observation"poolStateCPMMTWAP 使用之價格樣本的環形緩衝區。每次交換時寫入。
以及共享配置:
帳户種子所有者用途
ammConfig"amm_config"index: u16CPMM保有交易/協議/基金/建立者費率與管理員密鑰。每個「費率級別」一個。PoolState 在建立時綁定到一個,之後無法更改。

從兩個 mint 推導流動性池

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,
  };
}
推導流動性池 PDA 前務必排序 mint。 種子依位元組順序而非使用者順序雜湊兩個 mint。包含 (A, B)(B, A) 的兩個流動性池在鏈上會衝突 — 排序是程式如何使映射規範化的方式。
流動性池 ID 不總是規範 PDA。 Initialize 除了上述 PDA 外,還接受任意簽署人密鑰對作為 pool_state。若傳遞的帳户與規範 PDA 不符,程式要求其為簽署人 — 即建立者傳遞他們簽署的新密鑰對。這是前置執行防衛:任何競速搶佔規範 PDA 的第三方可被合法建立者使用隨機密鑰對繞過。下游 PDA(lpMintvault0vault1observation)仍從 poolState.key() 衍生,因此它們對任何使用的地址保持唯一。當索引流動性池時,始終從鏈上狀態發現流動性池 ID(例如 CPMM 程式下的 PoolState 帳户),而非推導規範 PDA — 後者將遺漏隨機密鑰對流動性池。

帳户配置

完整的 Rust 定義存在於 raydium-cp-swap 原始碼。下面的欄位是你從整合中將讀取的欄位。

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],
}
實際要讀取的內容:
  • lp_supply — 流動性池對 LP mint 總供應量的內部鏡像。用於 LP 份額計算;該值應與 mint 的鏈上供應量相符,但從 PoolState 讀取可避免額外帳户擷取。
  • protocol_fees_token{0,1}fund_fees_token{0,1}累積尚未掃描的費用。這些不影響交換定價;它們停留在庫中直到調用 CollectProtocolFee / CollectFundFee
  • status — 控制是否允許 SwapDepositWithdraw 的位元遮罩。由管理員透過 UpdatePoolStatus 更新。SDK 在建構交易前檢查此項;若你直接 CPI,請自行檢查。
  • token0_program / token1_program — 為每個庫 CPI 進入的代幣程式。一個可以是經典 SPL Token,另一個是 Token-2022;它們是獨立的。
  • open_time — Unix 時戳。此時間前的交換失敗。open_time 前允許存款,以便流動性池可被種子化。
  • creator_fee_on / enable_creator_fee — 合作控制此流動性池的選擇性建立者費用是否活躍以及從交換的哪一側收取。enable_creator_fee == false 完全清零建立者費用路徑。啟用時,creator_fee_on 選擇:0 = 從交換輸入的任何代幣收取費用(BothToken);1 = 僅從 token_0 收取費用(在 token_1 → token_0 交換上跳過);2 = 僅從 token_1 收取費用。在流動性池建立時透過 InitializeWithPermission 設定;之後無法更改。
  • creator_fees_token_{0,1} — 累積建立者費用,由 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],
}
需特別留意三點:
  1. trade_fee_ratecreator_fee_rate 是交易量的分數,兩者以 1/1_000_000 單位計值。2500 意味著交易量的 0.25%。protocol_fee_ratefund_fee_rate交易費用的分數(非交易量的),採用相同 1/1_000_000 計值。建立者費用交易費用的分數 — 它是自身獨立費率。完整算術在 products/cpmm/fees
  2. indexu16,因此種子雜湊使用 2 位元組大端序。位元組順序的錯一則是常見的整合錯誤。
  3. AmmConfig 在流動性池級別是不可變的。 流動性池在建立時指向一個 AmmConfig,之後永不切換。費用變更傳播是因為流動性池在每次交換時讀取配置 — 但流動性池無法在費率級別之間移動。
關於建立者費用的註解:費率本身creator_fee_rate)位於 AmmConfig 並跨費率級別共享。特定流動性池實際是否收取費用(enable_creator_fee)以及它從交換的哪一側著陸(creator_fee_on)位於 PoolState。建立者費用獨立於交易費用 — 它是自身費率,累積到自身計數器(creator_fees_token_{0,1}),且永不減少交易費用的 LP / 協議 / 基金份額。掃描透過 CollectCreatorFee。完整機制見 products/cpmm/fees

Permission

InitializeWithPermission 使用的小型存取控制帳户。CPMM 程式支援授權流動性池建立路徑,以便其他程式(例如 LaunchLab 在將代幣升級為 CPMM 時)可證明其有權針對給定 AmmConfig 建立流動性池。
pub struct Permission {
    pub authority: Pubkey,    // who is allowed to call InitializeWithPermission
    pub padding: [u64; 8],
}
Permission PDA 由 CPMM 管理員透過 CreatePermissionPda 建立,並透過 ClosePermissionPda 撤銷。終端使用者不直接與此帳户互動 — 它是跨程式流的管道。

庫與 Token-2022

vault0vault1 由 CPMM authority PDA 擁有,其代幣程式所有者(token_program)要么是 SPL Token 要么是 Token-2022,在流動性池建立時由 mint 的程式決定。流動性池透明處理兩種情況 — 你為 Swap / Deposit / Withdraw 指令帳户中的每一側傳遞正確的代幣程式 ID。 CPMM 在流動性池建立時強制執行嚴格的擴充允許清單utils/token.rs 中的 is_supported_mint)。Token-2022 mint 只有在其攜帶的每一項擴充都在此清單上時,才能在 CPMM 流動性池中使用:
  • TransferFeeConfig 由 mint 在每次轉移時應用。流動性池對 SwapBaseInput 存款接收,對提款發送。程式計算著陸在庫中的金額並相應設定曲線。見 algorithms/token-2022-transfer-fees
  • MetadataPointerTokenMetadata 標準 mint 元資料。對交換數學無影響。
  • InterestBearingConfig mint 的 UI 金額累積利息。庫儲存原始金額;曲線僅在原始金額上運作。展示 APR 的 UI 應呼叫 Token-2022 幫助器來渲染 UI 金額。
  • ScaledUiAmount UI 顯示縮放擴充。與 InterestBearingConfig 相同處理 — 曲線使用原始金額。
任何其他擴充 — PermanentDelegateTransferHookDefaultAccountStateNonTransferableConfidentialTransferGroup/GroupMemberMintCloseAuthority 等 — 導致 InitializeNotSupportMint 拒絕。例外是程式中小型硬編碼的 mint 白名單(少數特定公鑰)繞過擴充檢查;它用於逐案例登記特定 mint。 已審核擴充清單和 mint 白名單位於 CP-Swap 原始碼的 programs/cp-swap/src/utils/token.rs,可隨未來程式升級變更。

觀察

觀察帳户是 ObservationState 項的環形緩衝區,每個項儲存 block_timestamp 與一個累積價格。每次交換時,若自最後一個以來足夠時間已過,程式追加一項新觀察。TWAP 透過讀取兩項觀察並除以 Δ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],
}
環形緩衝區針對100 項觀察調整大小。每項觀察是 40 位元組,因此陣列單獨是 4,000 位元組;完整 ObservationState PDA 在環繞欄位和鑑別器後約 4,100 位元組。 兩項消費者規則:
  • 不使用單一觀察作為價格。 它是累積,不是即期價格。使用兩項計算 TWAP。
  • 挑選至少相隔一區塊的觀察。 同一區塊內的交換可能不產生新觀察;讀取連續可能返回相同記錄。
更多數學在 products/clmm/accounts

帳户生命週期

事件建立的帳户銷毀的帳户
InitializepoolStatelpMintvault0vault1observation
Deposit— (可能建立使用者 LP ATA)
Withdraw
Swap— (可能建立使用者目標 ATA)
CollectProtocolFee
CollectFundFee
UpdatePoolStatus
CPMM 流動性池及其 PDA 永不關閉。即使流動性為零,poolState 仍存在。這是刻意的:稍後重新種子化相同流動性池保留其歷史觀察緩衝區,其 PDA 推導保持穩定。

在何處讀取什麼

來源: