Skip to main content
本頁內容由 AI 自動翻譯,所有內容以英文版本為準。查看英文版 →
CPI(「跨程式呼叫」)是一個 Solana 程式呼叫另一個程式的機制。Raydium 的大多數程式都提供 Anchor CPI 包裝 crate,使呼叫位置看起來像一個型別化的函式呼叫,具有經過驗證的欄位名稱的帳戶結構和 cpi::<ix>() 輔助程式。本頁記錄了一般模式,然後是各程式的差異。如需可執行的 TypeScript,請參閱每個產品章節的 code-demos 頁面。

哪個模式適用於哪個程式

如果你正在整合 CPMM、CLMM 或 LaunchLab,請先閱讀一般模式,然後跳到你的程式章節以了解帳戶列表和任何差異。Farm v6 和 AMM v4 的差異足夠大,值得獨立閱讀其章節。

Cargo 依賴

依賴金鑰必須與目標倉庫的 [package] name 完全相符,包括連字號。 在解析 git 依賴時,Cargo 不會將 raydium_cp_swap 視為等同於 raydium-cp-swap。
branch = "master" 追蹤最新發佈的原始碼;如果你需要可重現的構建,請固定到特定的 rev = "<commit>"。一旦你超過原型設計階段,這是推薦的做法,因為上游在 master 上的帳戶佈局變更將無警告地破壞你的構建。 cpi 功能標誌使 crate 編譯為僅 CPI 表面(帳戶結構 + 呼叫程式),而不是完整程式,因此你的二進位檔案保持較小。 anchor-lang / anchor-spl 必須與目標 crate 固定的相符:
從同一 Anchor 線取兩個 crate。 兩個 master 分支都固定 =1.0.2,所以一個程式可以從單個 crate 中 CPI 到 CPMM 和 CLMM。混合線會破壞構建:例如,raydium-cp-swap 在 master 上,而 raydium-clmm 固定到其 Anchor 1.0 升級前的 rev。Cargo 必須將兩個不相容的 Anchor 特徵副本連結到一個二進位檔案中。如果你被困在混合對中,將程式分成兩個,或為一側放棄型別化 CPI crate 並手動編碼該指令(為 AMM v4 顯示的模式適用於任何程式)。
Anchor 1.0 改變了每個 CPI 呼叫位置接觸的兩件事。 如果你正在將工作整合從 0.3x 移動:
  • CpiContext::new 接受 Pubkey,而不是 AccountInfo。 CpiContext::new(ctx.accounts.cpmm_program.to_account_info(), accts) 變成 CpiContext::new(*ctx.accounts.cpmm_program.key, accts)。new_with_signer 也是如此。結構欄位現在是 program_id: Pubkey。
  • Context 有一個生命週期,而不是四個。 Context<'_, '_, 'info, 'info, MyProxySwap<'info>> 變成 Context<'info, MyProxySwap<'info>>。
在用戶端,anchor-client 的 RequestBuilder::instructions() 現在返回 Vec<Instruction> 而不是 Result<Vec<Instruction>>(刪除 ?),而 CommitmentConfig 已移出 solana-sdk — 改為從 anchor_client 取得。spl-associated-token-account 8.0 從新的 spl-associated-token-account-interface crate 重新匯出其輔助程式。get_associated_token_address 和 ID 仍可在 crate 根(spl_associated_token_account::{get_associated_token_address, ID})中取得,但地址輔助程式在那裡已棄用 — 改為直接依賴 spl-associated-token-account-interface 並匯入 spl_associated_token_account_interface::address::get_associated_token_address 和 spl_associated_token_account_interface::program::ID。注意 ::address 和 ::program 是 interface crate 的模組;spl_associated_token_account::address::… 無法解析。
如需端到端連接帳戶結構的可執行 CPI 範例,請參閱 raydium-io/raydium-cpi-example(涵蓋 AMM v4、CPMM 和 CLMM)。其最新分支是 anchor-0.31.0 — 還沒有 Anchor 1.x 分支,所以將該倉庫視為帳戶結構連接的參考,而不是本頁規定的版本固定。

通用 Anchor CPI 模式

本節以 CPMM 為完整範例逐步說明:Accounts 結構、CpiContext、cpi::<ix>()。CLMM 遵循相同的模式,但帳戶清單不同且需要剩餘帳戶。LaunchLab 遵循相同的機制,但其帳戶清單包含多個 CPMM/CLMM 沒有的帳戶(global_config、platform_config、event_authority、program),因此應將其視為相同的模式而非相同的結構。請參閱各程式的專屬章節,而不要假設本逐步說明的帳戶清單可直接轉移。

帳戶清單構建

每個 Raydium CPI 都需要在呼叫程式中有一個 Accounts 結構。其欄位是你的指令所需的任何帳戶,並帶有欄位級驗證器;它們的宣告順序不必與 Raydium 自身的指令帳戶順序相符,因為你自己的 IDL 生成客戶端按名稱而非位置來尋址它們:
大多數 Raydium 端的帳戶都是 UncheckedAccount,因為被呼叫者(Raydium)負責驗證。你的呼叫程式只需嚴格驗證你擁有的帳戶,例如使用者 ATA 和你自己的 PDA。/// CHECK: 文件註解會抑制 Anchor 關於缺少檢查的警告。唯一的 Raydium 端例外是 cpmm_program 本身:它是被呼叫的程式而非 Raydium 內部驗證的資料帳戶,因此它被型別化為 Program<T> 並獲得 Anchor 的自動位址檢查,而不是手動的 /// CHECK:。這種大多為 UncheckedAccount 的形狀(Raydium 驗證自己的帳戶)對 CLMM 和 LaunchLab 也是相同的。本範例假設兩個 mint 都是經典 SPL Token;如果任一方可以是 Token-2022 mint,請新增 token_program_2022: Program<'info, anchor_spl::token_2022::Token2022> 欄位,並在下面的 CPI 呼叫中將其作為該方的 input_token_program/output_token_program 傳遞,而不是 token_program。

構建 CPI 呼叫

Anchor 為每個指令生成一個輔助函式,以及一個 CPI 帳戶結構(cpi::accounts::Swap,下面別名為 CpmmSwap)。與上面你自己的 MyProxySwap 結構不同,這個結構的欄位名稱和順序由 raydium-cp-swap 自身的 IDL 固定,必須完全相符:
cpi::swap_base_input 由 IDL 生成;其引數清單鏡像 Anchor 指令的引數清單。每個已確認的基於 Anchor 的 Raydium 程式(CPMM、CLMM、LaunchLab)都以相同方式生成其 cpi::<ix>() 輔助函式,函式名稱與指令名稱以蛇形命名法相符。這是否擴展到 Farm v6 尚未確認;請參閱其章節。

簽署者種子(PDA 簽署的 CPI)

當你的程式代表 PDA 簽署 CPI 時(對於保管庫、託管等很常見),使用 CpiContext::new_with_signer:
簽署者種子必須與 PDA 的推導相符。對於作為 authority(或類似簽署者角色)傳遞的任何帳戶,Solana 執行時會檢查 PDA 是否透過這些種子簽署。

剩餘帳戶

某些 Raydium 指令採用剩餘帳戶,這是附加在固定帳戶之後的可變長度清單。Anchor 的 CPI 輔助函式不對剩餘帳戶進行型別檢查;透過 .with_remaining_accounts(...) 傳遞它們:
順序始終很重要,因為接收程式按你傳遞它們的順序迭代剩餘帳戶。兩個已確認的順序:
  • CLMM SwapV2:tick 陣列,按方向排序。
  • Farm v6:(reward_vault, user_reward_ata) 對,但僅從第二個獎勵流開始;請參閱 Farm v6 以了解實際交易解碼顯示的內容。

應用模式:CLMM

SwapV2 遵循上面的通用模式,但帳戶清單不同,且需要 tick 陣列的剩餘帳戶。該 crate 的 #[program] 模組名為 raydium_clmm,這也是其 Rust use 路徑。
CPI 帳戶結構名為 SwapSingleV2,而非 SwapV2。 SwapV2 是鏈上指令名稱。
透過針對目前池狀態的報價計算 tick 陣列清單,採用與 SDK 相同的方式,而不是猜測固定計數;超出你傳遞的陣列的交換會以 TickArrayNotFound 回復(請參閱 /zh-Hant/products/clmm/instructions 以取得完整帳戶表和錯誤清單)。按價格走向的方向傳遞它們:交換方向的第一個陣列優先。

應用模式:LaunchLab

LaunchLab 基於 Anchor 並發佈了 IDL:公開 raydium-idl 儲存庫中的 raydium_launchpad/raydium_launchpad.json。該 IDL 的內部元資料識別符是 raydium_launchpad,這是底層程式的技術名稱,而非產品的替代名稱。不過,與 CPMM 和 CLMM 不同的是,該程式的原始碼並未公開(見 reference/program-addresses)。沒有 git = "..." 依賴項指向 Cargo,也沒有原始碼可確認真實 crate 的 Rust use 路徑會是什麼。 使用 Anchor 的 declare_program! 巨集從已發佈的 IDL 生成繫結。將 IDL JSON 儲存為 crate 中的 idls/raydium_launchpad.json(Cargo 會在相對於 CARGO_MANIFEST_DIR 的 idls/ 目錄中查找),然後 declare_program!(raydium_launchpad); 會直接從 IDL 生成 raydium_launchpad::cpi::accounts::<Ix> 結構體和 cpi::<ix>() 函式,無需程式原始碼。生成的帳戶結構體名稱始終是 PascalCase 格式的指令名稱(buy_exact_in → BuyExactIn),欄位名稱與 IDL 的帳戶名稱完全相符,與下面 MyProxyBuy 中已使用的帳戶清單相同。 CPI 形狀遵循通用模式。下面的帳戶清單和引數來自鏈上 IDL 的 buy_exact_in 指令,而非 products/launchlab/instructions.mdx:
畢業後,目標程式是 CPMM 或 AMM v4,取決於 pool_state.migrate_type,products/launchlab/accounts.mdx 說該值在 Initialize 時設定。你的 CPI 帳戶清單必須為兩者做好準備,或者你需要先從 PoolState 讀取 migrate_type 並進行分支。

錯誤傳播

每個基於 Anchor 的 Raydium 程式都會返回自己的錯誤列舉;Anchor 會包裝它們,所以你的呼叫程式會看到 Err(ProgramError::Custom(code))。要處理特定錯誤:
根據你呼叫的程式替換相關的錯誤類型(CLMM 為 raydium_clmm::error::ErrorCode,以此類推)。根據 IDL 政策(sdk-api/anchor-idl),錯誤代碼編號在每個程式中是穩定的,所以你可以通過與數值進行比較來測試特定代碼。完整錯誤表:CPMM、CLMM、AMM v4、Farm v6 和 LaunchLab。

組合 CPI 中的計算預算

每個 CPI 框架都有開銷,被呼叫者自己的 CU 消耗會疊加在你的之上,所以從你的程式內部呼叫到 Raydium 的交易需要明確的計算預算,而不是依賴 200k CU 的預設值。
實測,非估計。 主網上的 CPMM swap_base_input 在 CPMM 程式本身中消耗 約 23,000 CU — 2026-09-09 在高交易量池上的八次實時交換中採樣(22,721–23,052),從 Program CPMMoo8… consumed N of M compute units 日誌行讀取。相比之下:AMM v4 交換約 26,000;CLMM swap 約 41,000;CLMM swap_v2 約 48,000(43,838–52,887),隨著每次 tick 穿越而增加。本頁的早期版本報告代理交換 CPI 約 47,700 CU。該數字是整個交易(computeUnitsConsumed),包括呼叫者自己的程式、CPI 框架和任何 ATA 設定 — 而非被呼叫者的成本。兩者都有用,但它們不是同一個數字,所以要進行類似的比較。根據你自己的測量來衡量你的交易,而不是根據文件中複製的數字進行預算。
CLMM 和 LaunchLab CPI 成本更高(CLMM 特別是通過 remaining_accounts 走過額外的 tick 陣列,每個陣列增加 CU),但上面只有 CPMM 數字是實測值。始終根據你自己的測量設定明確的 ComputeBudgetProgram::set_compute_unit_limit(...) 指令,而不是從文件中複製的數字,因為預設 200k CU 限制會無聲地耗盡,且隨著程式升級,每條指令的成本會變化。

AMM v4:手動指令構造

AMM v4 早於 Anchor,沒有 CPI crate,使其成為本文件中唯一不遵循上述通用模式的程式。手動構建 Instruction:
完整帳戶清單見 products/amm-v4/code-demos。

Farm v6

如果這對你的整合是一個選項,請使用 TS SDK。 raydium.farm.deposit(...) (見 products/farm-staking/code-demos)由真實示例驗證,不依賴是否存在此程式的 Rust Anchor crate。
Farm v6 不提供 Anchor CPI 路徑。 crates.io 上沒有 raydium_farm_v6 crate,沒有公開原始碼儲存庫,也沒有鏈上 IDL — 該程式既沒有舊版 anchor:idl 帳戶,也沒有在程式元資料程式中的條目(見 sdk-api/anchor-idl)。將其視為非 Anchor 程式,並手動構建其指令,如下所示。
如果你無論如何都需要 Rust CPI,例如從另一個鏈上程式進行組合,手動構建 Instruction,方式與 AMM v4 相同:獨立推導真實帳戶清單和指令判別器,例如通過解碼 SDK 的 TypeScript 佈局(raydium-sdk-V2 的 farm 模組)、直接解碼真實交易(見下文),或轉儲和反彙編已部署的程式。 對於與收穫或領取呼叫一致的零引數指令形狀,真實帳戶順序是固定前綴(token_program、farm 的狀態帳戶、vault 授權 PDA、該 PDA 的第一個獎勵 vault、第二個 PDA、呼叫者和呼叫者的該第一個獎勵 mint 的 ATA),後面跟著 remaining_accounts 中的 (reward_vault_i, user_reward_ata_i) 對,用於第一個之後的每個獎勵流。配對慣例是真實的,但它只從第二個獎勵流開始:第一個流的 vault 和 ATA 是固定帳戶,彼此不相鄰,根本不是 remaining_accounts 的一部分。

測試 CPI 流程

本地開發需要 Raydium 程式在你的測試驗證器中可用。有三個選項:
  1. 使用程式複製的 anchor test。 將已部署的主網位元組碼拉入本地驗證器;請參閱下方的將程式複製到本地驗證器,了解 Anchor.toml 設定以及特別會影響池建立測試的兩個問題。
  2. Devnet。 Raydium 將大多數程式部署到 devnet,但使用的程式 ID 與主網不同(CPMM、CLMM、AMM v4、Stable AMM 和 LaunchLab 各有不同的 devnet 位址;請參閱 reference/program-addresses 中的 Devnet 表格)。Farm v3/v5/v6 在 devnet 上並未可靠地發佈;實時 API(https://api-v3-devnet.raydium.io/main/info)提供當前情況。如果你使用 raydium_clmm 的內附 DEVNET_PROGRAM_ID 常數(或其他 crate 的等效項),不要假設主網 ID 也能在 devnet 上運作。一旦你有了正確的位址,執行 anchor test --provider.cluster devnet 來測試實時程式碼。
  3. 本地部署。 複製 Raydium 儲存庫(CPMM、CLMM;LaunchLab 的原始碼不適用於此選項)並 anchor deploy 到本地驗證器。增加測試週期開銷,但讓你可以修改被呼叫程式以進行除錯。
使用 anchor test 執行,或如果你在迭代測試檔案而不改變程式,先執行 anchor build,然後執行 anchor test --skip-build。

將程式複製到本地驗證器

這個方法無論程式的原始碼是否公開都能透過程式 ID 運作,所以 LaunchLab 的複製方式與 CPMM 和 CLMM 相同,即使其原始碼不可用。reference/program-addresses 是所有位址的真實來源。
如果你的測試也會建立池(而不是針對已存在的池進行交換),僅複製程式是不夠的。 CPMM 的 initialize 指令會根據真實的鏈上資料驗證其 amm_config 和 create_pool_fee 帳戶,所以你也需要複製這些帳戶,否則 initialize 會直接失敗。對於 CPMM 特別地:複製你想要的費用級別 AmmConfig(從 GET https://api-v3.raydium.io/main/cpmm-config 取得其位址,索引 0 是 0.25% 級別)和費用接收者代幣帳戶,由精確位址驗證,不是動態建立的,所以它必須已經存在。
你的測試剛建立的池在同一時刻是不可交換的。 CPMM 的 initialize 會無聲地覆蓋不是嚴格在未來的請求 open_time(if open_time <= block_timestamp { open_time = block_timestamp + 1 }),所以即使 startTime: 0(根據 SDK 的「立即開放」)也會在池接受交換之前留下真實的 ≥1 秒間隙。建立池並立即針對它進行交換的測試將會觸發 NotApproved。在池建立和第一次交換之間稍作等待(1–2 秒)就足夠了。這是測試特有的;執行兩個單獨手動命令的人通常不會注意到,因為輸入和程式啟動已經消耗超過一秒。

參考資源

來源: