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。 兩個升級分支都固定 =1.0.2,因此一個程式可以從單個 crate 中 CPI 進入 CPMM 和 CLMM。混合線會破壞構建:例如,raydium-cp-swap 在 chore/upgrade-anchor 上與 raydium-clmm 在 master 上。Cargo 必須將兩個不相容的 Anchor 特徵副本連結到一個二進位檔案中。如果您卡在混合對上,請將程式分成兩個,或為一側放棄型別化 CPI crate 並手動編碼該指令(為 AMM v4 顯示的模式適用於任何程式)。在開始之前重新檢查兩個 Cargo.toml 檔案,因為分支最終會移動到 master。
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 是鏈上 指令 名稱。
使用與 SDK 相同的方式計算 tick 陣列列表,透過針對當前池狀態的報價,而不是猜測固定計數;超出您傳遞的陣列的交換會以 TickArrayNotFound 回復(參見 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 設定 — 而不是被呼叫者的成本。兩者都有用,但它們不是相同的數字,因此請比較相同的內容。測量您自己的交易,而不是根據文件中複製的數字進行預算,因為預設 200k CU 限制會無聲地耗盡,每指令成本隨著程式升級而移動。
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 的狀態帳戶、保管庫授權 PDA、該 PDA 的第一個獎勵保管庫、第二個 PDA、呼叫者和呼叫者的該第一個獎勵 mint 的 ATA),然後是 remaining_accounts 中的 (reward_vault_i, user_reward_ata_i) 對,用於第一個之後的每個獎勵流。配對約定是真實的,但它只在第二個獎勵流開始:第一個流的保管庫和 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。池創建和第一次交換之間的短 await(1–2 秒)就足夠了。這是測試特定的;人類執行兩個單獨的手動命令通常不會注意到,因為輸入和程式啟動已經消耗超過一秒。

指標

來源: