> ## 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.

# Rust CPI

> 通过 CPI 从另一个 Solana 程序调用 Raydium 程序：账户列表构造、签名者种子、剩余账户和错误传播，涵盖 CPMM、CLMM 和 LaunchLab。AMM v4（无 Anchor crate）和 Farm v6 在各自的章节中介绍。

<Info>
  **本页内容由 AI 自动翻译，所有内容以英文版本为准。**

  [查看英文版 →](/sdk-api/rust-cpi)
</Info>

<Info>
  CPI（"跨程序调用"）是一个 Solana 程序调用另一个程序的机制。Raydium 的大多数程序都提供 Anchor CPI 包装 crate，使调用点看起来像一个类型化的函数调用，其中账户结构具有经过验证的字段名称和 `cpi::<ix>()` 辅助函数。本页记录了通用模式一次，然后是各个程序的差异。有关可运行的 TypeScript，请参阅每个产品章节的 `code-demos` 页面。
</Info>

## 哪个模式适用于哪个程序

| 程序 | 基于 Anchor | Crate | 位置 |
| - | - | - | - |
| CPMM | 是 | `raydium-cp-swap`（公开仓库；Rust `use` 路径为 `raydium_cp_swap`） | [通用模式](#the-general-anchor-cpi-pattern)、工作示例 |
| CLMM | 是 | `raydium-clmm`（公开仓库；Rust `use` 路径为 `raydium_clmm`） | [应用模式：CLMM](#applying-the-pattern-clmm) |
| LaunchLab | 是 | 无确认的 crate 名称；程序源代码已关闭，但链上 IDL 的内部元数据标识符是 `raydium_launchpad`，这是技术名称而非产品名称（参见 [应用模式：LaunchLab](#applying-the-pattern-launchlab) 中的警告） | [应用模式：LaunchLab](#applying-the-pattern-launchlab) |
| Farm v6 | 未确认；未找到公开 IDL 或源代码（参见 [Farm v6](#farm-v6) 中的警告） | n/a | [Farm v6](#farm-v6) |
| AMM v4 | **否**，早于 Anchor，无 CPI crate | 无 | [AMM v4](#amm-v4-manual-instruction-construction) |

如果你正在集成 CPMM、CLMM 或 LaunchLab，请先阅读[通用模式](#the-general-anchor-cpi-pattern)，然后跳转到你的程序部分了解账户列表和任何差异。Farm v6 和 AMM v4 的差异足够大，值得单独阅读它们的部分。

## Cargo 依赖

```toml icon="file-code" theme={null}
# 两个公开的 Raydium CPI crate 在其升级分支上都固定 Anchor =1.0.2，
# 所以一个程序可以依赖其中任何一个或两个 — 参见下表。
[dependencies]
anchor-lang     = "=1.0.2"
anchor-spl      = "=1.0.2"
raydium-cp-swap = { git = "https://github.com/raydium-io/raydium-cp-swap", branch = "chore/upgrade-anchor", features = ["cpi"] }
raydium-clmm    = { git = "https://github.com/raydium-io/raydium-clmm",    branch = "chore/upgrade-anchor", features = ["cpi"] }
# 注意：两个仓库的 `master` 分支仍然固定 anchor-lang 0.32.1。上面的 `=1.0.2` 固定
# 仅针对升级分支解析；如果你针对 `master`，使用 =0.32.1
# 和 0.3x CpiContext API — 然后一次只能使用两个 crate 中的一个。
# LaunchLab：程序源代码不公开（参见 reference/program-addresses）。
# 没有 git 依赖可以指向。改为从已发布的 IDL 生成 CPI 绑定。
# 参见下面的"应用模式：LaunchLab"。
# AMM v4 没有已发布的 Anchor CPI crate；参见其自己的部分。
```

<Warning>
  **依赖键必须与目标仓库的 `[package] name` 完全匹配，包括连字符。** 在解析 *git* 依赖时，Cargo 不会将 `raydium_cp_swap` 视为等同于 `raydium-cp-swap`。
</Warning>

`branch = "master"` 跟踪最新发布的源代码；如果你需要可重现的构建，请固定到特定的 `rev = "<commit>"`。一旦你完成原型设计，这是推荐的做法，因为上游在 `master` 上的账户布局更改将无警告地破坏你的构建。

`cpi` 功能标志使 crate 仅编译为 CPI 表面（账户结构 + 调用器），而不是完整程序，因此你的二进制文件保持较小。

**`anchor-lang` / `anchor-spl` 必须与目标 crate 固定的版本匹配**：

| Crate | 固定 `anchor-lang` / `anchor-spl` |
| - | - |
| `raydium-cp-swap`（`chore/upgrade-anchor`） | `=1.0.2`，来自 [2026-09-09 升级](/zh/reference/changelog/2026-09-09-cpmm-anchor-1-and-excess-lamports) |
| `raydium-clmm`（`chore/upgrade-anchor`） | `=1.0.2`，来自 [2026-09-30 升级](/zh/reference/changelog/2026-09-30-clmm-anchor-1-and-excess-lamports) |
| `raydium-cp-swap`（`master`） | `0.32.1`，插入符号固定。升级尚未登陆 `master` |
| `raydium-clmm`（`master`） | `=0.32.1`。升级尚未登陆 `master` |

<Warning>
  **从同一 Anchor 线获取两个 crate。** 两个升级分支都固定 `=1.0.2`，所以一个程序可以从单个 crate 中 CPI 到 CPMM 和 CLMM。混合线会破坏构建：例如，`raydium-cp-swap` 在 `chore/upgrade-anchor` 上与 `raydium-clmm` 在 `master` 上。Cargo 必须将两个不兼容的 Anchor 特性副本链接到一个二进制文件中。如果你被困在混合对中，将程序分成两个，或为一侧放弃类型化 CPI crate 并手动编码该指令（为 [AMM v4](#amm-v4-manual-instruction-construction) 显示的模式适用于任何程序）。在开始之前重新检查两个 `Cargo.toml` 文件，因为分支最终会移动到 `master`。
</Warning>

<Warning>
  **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::…` 不解析。
</Warning>

有关端到端连接账户结构的工作 CPI 示例，请参见 [`raydium-io/raydium-cpi-example`](https://github.com/raydium-io/raydium-cpi-example)（涵盖 AMM v4、CPMM 和 CLMM）。其最新分支是 [`anchor-0.31.0`](https://github.com/raydium-io/raydium-cpi-example/tree/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 生成的客户端按名称而不是位置寻址它们：

```rust icon="rust" theme={null}
use anchor_lang::prelude::*;
use anchor_spl::token::{Token, TokenAccount, Mint};

#[derive(Accounts)]
pub struct MyProxySwap<'info> {
    #[account(mut)]
    pub user: Signer<'info>,

    /// CHECK: validated by CPMM
    #[account(mut)]
    pub pool_state: UncheckedAccount<'info>,

    /// CHECK: ditto
    pub amm_config: UncheckedAccount<'info>,

    /// CHECK: ditto
    pub pool_authority: UncheckedAccount<'info>,

    #[account(mut)]
    pub input_vault: Account<'info, TokenAccount>,
    #[account(mut)]
    pub output_vault: Account<'info, TokenAccount>,

    pub input_mint: Account<'info, Mint>,
    pub output_mint: Account<'info, Mint>,

    #[account(mut)]
    pub user_input_ata:  Account<'info, TokenAccount>,
    #[account(mut)]
    pub user_output_ata: Account<'info, TokenAccount>,

    /// 类型为 `Program<RaydiumCpSwap>` 而不是 `UncheckedAccount`：此账户
    /// 是被调用的程序，而不是 Raydium 拥有的数据，所以 Anchor 的 `Program<T>`
    /// 包装器自动检查其地址，而不是将该检查留给被调用方。
    pub cpmm_program: Program<'info, raydium_cp_swap::program::RaydiumCpSwap>,
    pub token_program: Program<'info, Token>,
    /// CHECK: observation PDA
    #[account(mut)]
    pub observation_state: UncheckedAccount<'info>,
}
```

大多数 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 固定，必须完全匹配：

```rust icon="rust" theme={null}
use raydium_cp_swap::cpi::{self, accounts::Swap as CpmmSwap};

pub fn my_proxy_swap(
    ctx: Context<MyProxySwap>,
    amount_in: u64,
    minimum_amount_out: u64,
) -> Result<()> {
    let cpi_accounts = CpmmSwap {
        // `payer` 是你的签名者 — 它必须拥有 `input_token_account`。
        payer:                ctx.accounts.user.to_account_info(),
        // `authority` 是 CPMM 自己的保险库 PDA，种子为 [b"vault_and_lp_mint_auth_seed"]，
        // CPMM 用自己签署。在这里传递你的签名者会在任何转账运行之前
        // 失败种子约束（`ConstraintSeeds`）。
        authority:            ctx.accounts.pool_authority.to_account_info(),
        amm_config:           ctx.accounts.amm_config.to_account_info(),
        pool_state:           ctx.accounts.pool_state.to_account_info(),
        input_token_account:  ctx.accounts.user_input_ata.to_account_info(),
        output_token_account: ctx.accounts.user_output_ata.to_account_info(),
        input_vault:          ctx.accounts.input_vault.to_account_info(),
        output_vault:         ctx.accounts.output_vault.to_account_info(),
        input_token_program:  ctx.accounts.token_program.to_account_info(),
        output_token_program: ctx.accounts.token_program.to_account_info(),
        input_token_mint:     ctx.accounts.input_mint.to_account_info(),
        output_token_mint:    ctx.accounts.output_mint.to_account_info(),
        observation_state:    ctx.accounts.observation_state.to_account_info(),
    };
    // Anchor 1.0：第一个参数是程序的 Pubkey，而不是其 AccountInfo。
    let cpi_ctx = CpiContext::new(
        *ctx.accounts.cpmm_program.key,
        cpi_accounts,
    );

    cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out)?;
    Ok(())
}
```

`cpi::swap_base_input` 是从 IDL 生成的；其参数列表镜像 Anchor 指令的参数列表。每个确认的基于 Anchor 的 Raydium 程序（CPMM、CLMM、LaunchLab）以相同的方式生成其 `cpi::<ix>()` 辅助函数，函数名称与蛇形大小写的指令名称匹配。这是否扩展到 Farm v6 是未确认的；参见其部分。

### 签名者种子（PDA 签署的 CPI）

当你的程序代表 PDA 签署 CPI 时（对于保险库、托管等很常见），使用 `CpiContext::new_with_signer`：

```rust icon="rust" theme={null}
let bump         = ctx.accounts.my_authority_bump;
let signer_seeds: &[&[&[u8]]] = &[&[b"my_authority", &[bump]]];

let cpi_ctx = CpiContext::new_with_signer(
    *ctx.accounts.cpmm_program.key,
    cpi_accounts,
    signer_seeds,
);

cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out)?;
```

签名者种子必须与 PDA 的推导相匹配。对于作为 `authority`（或类似签名者角色）传递的任何账户，Solana 运行时检查 PDA 通过这些种子签署。

### 剩余账户

某些 Raydium 指令接受 **剩余账户**，一个在固定账户之后附加的可变长度列表。Anchor 的 CPI 辅助函数不对剩余账户进行类型检查；通过 `.with_remaining_accounts(...)` 传递它们：

```rust icon="rust" theme={null}
let cpi_ctx = CpiContext::new(program_id, accounts)
    .with_remaining_accounts(ctx.remaining_accounts.to_vec());
```

顺序总是重要的，因为接收程序按你传递它们的顺序迭代剩余账户。两个确认的顺序：

* **CLMM `SwapV2`**：[tick 数组](#applying-the-pattern-clmm)，按方向顺序。
* **Farm v6**：`(reward_vault, user_reward_ata)` 对，但仅从第二个奖励流开始；参见 [Farm v6](#farm-v6) 了解解码真实交易显示的内容。

## 应用模式：CLMM

`SwapV2` 遵循上面的[通用模式](#the-general-anchor-cpi-pattern)，具有不同的账户列表和 tick 数组的剩余账户要求。crate 的 `#[program]` 模块名为 `raydium_clmm`，这也是其 Rust `use` 路径。

<Warning>
  **CPI 账户结构名为 `SwapSingleV2`，而不是 `SwapV2`。** `SwapV2` 是链上 *指令* 名称。
</Warning>

```rust icon="rust" theme={null}
use raydium_clmm::cpi::{self, accounts::SwapSingleV2 as ClmmSwap};

#[derive(Accounts)]
pub struct MyProxyClmmSwap<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    /// CHECK: validated by CLMM
    pub amm_config: UncheckedAccount<'info>,
    /// CHECK: ditto
    #[account(mut)]
    pub pool_state: UncheckedAccount<'info>,

    #[account(mut)]
    pub input_token_account: Account<'info, TokenAccount>,
    #[account(mut)]
    pub output_token_account: Account<'info, TokenAccount>,
    #[account(mut)]
    pub input_vault: Account<'info, TokenAccount>,
    #[account(mut)]
    pub output_vault: Account<'info, TokenAccount>,

    /// CHECK: observation PDA
    #[account(mut)]
    pub observation_state: UncheckedAccount<'info>,

    pub token_program: Program<'info, Token>,
    pub token_program_2022: Program<'info, anchor_spl::token_2022::Token2022>,
    /// CHECK: SPL memo program，某些 Token-2022 路径需要
    pub memo_program: UncheckedAccount<'info>,

    pub input_vault_mint: Account<'info, Mint>,
    pub output_vault_mint: Account<'info, Mint>,

    /// 类型为 `Program<T>` 而不是 `UncheckedAccount`，原因与上面 CPMM 的
    /// `cpmm_program` 在通用模式中相同：它是被调用的程序，
    /// `Program<T>` 自动检查其地址。
    pub clmm_program: Program<'info, raydium_clmm::program::RaydiumClmm>,
    // remaining_accounts: tick_array_bitmap_extension（如果遍历需要），然后
    // 尽可能多的 tick_array 账户，因为交换的预期价格范围跨越。
}
```

以与 SDK 相同的方式计算 tick 数组列表，通过对当前池状态的报价，而不是猜测固定计数；超出你传递的数组的交换会以 `TickArrayNotFound` 恢复（参见 [`products/clmm/instructions`](/zh/products/clmm/instructions) 了解完整账户表和错误列表）。按价格遍历方向传递它们：交换方向的第一个数组优先。

## 应用模式：LaunchLab

LaunchLab 是基于 Anchor 的并发布了 IDL：[`raydium_launchpad/raydium_launchpad.json`](https://github.com/raydium-io/raydium-idl/blob/master/raydium_launchpad/raydium_launchpad.json) 在公开的 `raydium-idl` 仓库中。该 IDL 的内部元数据标识符是 `raydium_launchpad`，这是底层程序的技术名称，而不是产品名称的替代名称。与 CPMM 和 CLMM 不同，**程序自己的源代码不公开**（参见 [`reference/program-addresses`](/zh/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 形状遵循[通用模式](#the-general-anchor-cpi-pattern)。下面的账户列表和参数来自链上 IDL 的 `buy_exact_in` 指令，而不是来自 `products/launchlab/instructions.mdx`：

```rust icon="rust" theme={null}
use anchor_lang::prelude::*;
use anchor_spl::token::{Token, TokenAccount, Mint};
use raydium_launchpad::cpi::{self, accounts::BuyExactIn};

#[derive(Accounts)]
pub struct MyProxyBuy<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    /// CHECK: PDA，种子 = [b"vault_auth_seed"]，由程序验证
    pub authority: UncheckedAccount<'info>,

    /// CHECK: 由程序验证
    pub global_config: UncheckedAccount<'info>,
    /// CHECK: 由程序验证
    pub platform_config: UncheckedAccount<'info>,

    /// CHECK: 由程序验证
    #[account(mut)]
    pub pool_state: UncheckedAccount<'info>,

    #[account(mut)]
    pub user_base_token: Account<'info, TokenAccount>,
    #[account(mut)]
    pub user_quote_token: Account<'info, TokenAccount>,
    #[account(mut)]
    pub base_vault: Account<'info, TokenAccount>,
    #[account(mut)]
    pub quote_vault: Account<'info, TokenAccount>,

    pub base_token_mint: Account<'info, Mint>,
    pub quote_token_mint: Account<'info, Mint>,

    /// CHECK: base mint 的 token 程序（SPL Token 或 Token-2022，取决于 mint）
    pub base_token_program: UncheckedAccount<'info>,
    /// 类型为 `Program<Token>` 而不是 `UncheckedAccount`：IDL 将其固定为
    /// 经典 SPL Token 特别是（与上面的 base_token_program 不同），所以 Anchor 的
    /// 内置地址检查在 `Program<Token>` 上本身就足够了。
    pub quote_token_program: Program<'info, Token>,

    /// CHECK: PDA，种子 = [b"__event_authority"]，程序的 emit_cpi! 模式需要
    pub event_authority: UncheckedAccount<'info>,
    /// CHECK: 程序自己的账户，与 event_authority 一起需要
    pub program: UncheckedAccount<'info>,
}

pub fn my_proxy_buy(
    ctx: Context<MyProxyBuy>,
    amount_in: u64,
    minimum_amount_out: u64,
    share_fee_rate: u64,
) -> Result<()> {
    let cpi_accounts = BuyExactIn {
        payer:               ctx.accounts.payer.to_account_info(),
        authority:           ctx.accounts.authority.to_account_info(),
        global_config:       ctx.accounts.global_config.to_account_info(),
        platform_config:     ctx.accounts.platform_config.to_account_info(),
        pool_state:          ctx.accounts.pool_state.to_account_info(),
        user_base_token:     ctx.accounts.user_base_token.to_account_info(),
        user_quote_token:    ctx.accounts.user_quote_token.to_account_info(),
        base_vault:          ctx.accounts.base_vault.to_account_info(),
        quote_vault:         ctx.accounts.quote_vault.to_account_info(),
        base_token_mint:     ctx.accounts.base_token_mint.to_account_info(),
        quote_token_mint:    ctx.accounts.quote_token_mint.to_account_info(),
        base_token_program:  ctx.accounts.base_token_program.to_account_info(),
        quote_token_program: ctx.accounts.quote_token_program.to_account_info(),
        event_authority:     ctx.accounts.event_authority.to_account_info(),
        program:             ctx.accounts.program.to_account_info(),
    };
    // `program` 在这里兼作 CPI 目标：LaunchLab 自己的账户既是其指令的
    // 必需账户之一（用于其 emit_cpi! 模式），也是程序
    // CpiContext::new 调用的。
    let cpi_ctx = CpiContext::new(
        *ctx.accounts.program.key,
        cpi_accounts,
    );

    cpi::buy_exact_in(cpi_ctx, amount_in, minimum_amount_out, share_fee_rate)?;
    Ok(())
}
```

毕业后，目标程序是 CPMM 或 AMM v4，取决于 `pool_state.migrate_type`，`products/launchlab/accounts.mdx` 说这是在 `Initialize` 时设置的。你的 CPI 账户列表必须为任何一个做好准备，或者你需要先从 `PoolState` 读取 `migrate_type` 并分支。

## 错误传播

每个基于 Anchor 的 Raydium 程序都返回自己的错误枚举；Anchor 包装它们，所以你的调用程序将它们视为 `Err(ProgramError::Custom(code))`。要处理特定错误：

```rust icon="rust" theme={null}
use raydium_cp_swap::error::ErrorCode as CpmmErr;

match cpi::swap_base_input(cpi_ctx, amount_in, minimum_amount_out) {
    Ok(_) => Ok(()),
    Err(e) => {
        msg!("CPMM swap failed: {:?}", e);
        // 重新抛出，或转换为你自己的错误类型。
        Err(e)
    }
}
```

为你调用的程序交换相关的错误类型（CLMM 为 `raydium_clmm::error::ErrorCode`，等等）。错误代码数字根据 IDL 策略是稳定的（[`sdk-api/anchor-idl`](/zh/sdk-api/anchor-idl#idl-change-policy)），所以你可以通过与数值进行比较来测试特定代码。完整错误表：[CPMM](/zh/reference/error-codes#cpmm-standard-amm-errors)、[CLMM](/zh/reference/error-codes#clmm-errors)、[AMM v4、Farm v6 和 LaunchLab](/zh/reference/error-codes#amm-v4-farm-v3-/-v5-/-v6-launchlab-errors)。

## 组合 CPI 中的计算预算

每个 CPI 帧都有开销，被调用方自己的 CU 消耗堆叠在你的之上，所以从你的程序内部调用 Raydium 的交易需要显式计算预算，而不是依赖 200k CU 默认值。

<Note>
  **测量，而不是估计。** 主网上的 CPMM `swap_base_input` 消耗 **约 23,000 CU** 在 CPMM 程序本身中 — 在 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 限制会无声地耗尽，每指令成本随着程序升级而移动。
</Note>

CLMM 和 LaunchLab CPI 成本更高（CLMM 特别是通过 `remaining_accounts` 遍历额外的 tick 数组，每个数组添加 CU），但只有上面的 CPMM 数字是测量值。始终设置一个显式的 `ComputeBudgetProgram::set_compute_unit_limit(...)` 指令，大小来自你自己的测量，而不是从文档复制的数字，因为默认 200k CU 限制会无声地耗尽，每指令成本随着程序升级而移动。

## AMM v4：手动指令构造

AMM v4 早于 Anchor，没有 CPI crate，使其成为本文档中不遵循上面通用模式的唯一程序。手动构建 `Instruction`：

```rust icon="rust" theme={null}
use anchor_lang::prelude::*;
use anchor_lang::solana_program::program::invoke_signed;
use anchor_lang::solana_program::instruction::{Instruction, AccountMeta};

const AMM_V4_PROGRAM_ID: Pubkey = pubkey!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8");

// SwapBaseIn 判别器是 9。
let mut data = vec![9u8];
data.extend_from_slice(&amount_in.to_le_bytes());
data.extend_from_slice(&minimum_amount_out.to_le_bytes());

let ix = Instruction {
    program_id: AMM_V4_PROGRAM_ID,
    accounts: vec![
        AccountMeta::new_readonly(token_program_id, false),
        AccountMeta::new(amm_id, false),
        AccountMeta::new_readonly(amm_authority, false),
        // ... 根据 products/amm-v4/instructions 的剩余账户 ...
    ],
    data,
};
invoke_signed(&ix, &account_infos, signer_seeds)?;
```

参见 [`products/amm-v4/code-demos`](/zh/products/amm-v4/code-demos) 了解完整账户列表。

## Farm v6

**如果这是你的集成的一个选项，使用 TS SDK。** `raydium.farm.deposit(...)`（参见 [`products/farm-staking/code-demos`](/zh/products/farm-staking/code-demos)）由真实演示执行，不依赖于此程序是否存在 Rust Anchor crate。

<Warning>
  **Farm v6 不提供 Anchor CPI 路径。** crates.io 上没有 `raydium_farm_v6` crate，没有公开源仓库，也没有链上 IDL — 程序既没有遗留 `anchor:idl` 账户，也没有在程序元数据程序中的条目（参见 [`sdk-api/anchor-idl`](/zh/sdk-api/anchor-idl)）。将其视为非 Anchor 程序，手动构建其指令，如下所示。
</Warning>

**如果你无论如何都需要 Rust CPI**，例如从另一个链上程序组合，手动构建 `Instruction`，与 [AMM v4](#amm-v4-manual-instruction-construction) 相同的方式：独立推导真实账户列表和指令判别器，例如通过解码 SDK 的 TypeScript 布局（`raydium-sdk-V2` 的 farm 模块）、直接解码真实交易（参见下面）或转储和反汇编已部署的程序。

对于与收获或声明调用一致的零参数指令形状，真实账户顺序是固定前缀（`token_program`、farm 的状态账户、保险库权限 PDA、该 PDA 的第一个奖励保险库、第二个 PDA、调用方和调用方的该第一个奖励 mint 的 ATA），然后是 `(reward_vault_i, user_reward_ata_i)` 对在 `remaining_accounts` 中，用于第一个之后的每个奖励流。配对约定是真实的，但它仅从第二个奖励流开始：第一个流的保险库和 ATA 是固定账户，彼此不相邻，根本不是 `remaining_accounts` 的一部分。

## 测试 CPI 流

本地开发需要 Raydium 程序在你的测试验证器中可用。三个选项：

1. **`anchor test` 与程序克隆。** 将已部署的主网字节码拉入你的本地验证器；参见下面的 [将程序克隆到本地验证器](#cloning-programs-into-a-local-validator) 了解 `Anchor.toml` 配置和两个特别困扰池创建测试的事项。
2. **Devnet。** Raydium 将大多数程序部署到 devnet，但在 **不同的程序 ID 上，与主网不同** 对于每个程序（CPMM、CLMM、AMM v4、Stable AMM 和 LaunchLab 各有不同的 devnet 地址；参见 [`reference/program-addresses`](/zh/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`。

### 将程序克隆到本地验证器

```toml icon="file-code" theme={null}
[test]
# 默认值（5s）一旦你克隆多个账户就不够了，因为每个都是
# 验证器启动期间的顺序主网 RPC 往返。
startup_wait = 60000

[test.validator]
url = "https://api.mainnet-beta.solana.com"

[[test.validator.clone]]
address = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK"  # CLMM

[[test.validator.clone]]
address = "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"  # CPMM

[[test.validator.clone]]
address = "LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj"   # LaunchLab

[[test.validator.clone]]
address = "FarmqiPv5eAj3j1GMdMCMUGXqPUvmquZtMy86QH6rzhG"  # Farm v6
```

这通过程序 ID 工作，无论程序的源是否公开，所以 LaunchLab 以与 CPMM 和 CLMM 相同的方式克隆，即使其源不可用。`reference/program-addresses` 是这里每个地址的真实来源。

<Warning>
  **如果你的测试也 *创建* 一个池**（而不是针对已存在的池进行交换），克隆程序是不够的。CPMM 的 `initialize` 指令根据真实链上数据验证其 `amm_config` 和 `create_pool_fee` 账户，所以你也需要克隆那些，或者 `initialize` 直接失败。对于 CPMM 特别是：克隆你想要的费用等级 `AmmConfig`（从 `GET https://api-v3.raydium.io/main/cpmm-config` 获取其地址，索引 0 是 0.25% 等级）和[费用接收者 token 账户](/zh/reference/program-addresses#shared-config-/-pda-conventions)，由精确地址验证，而不是动态创建，所以它必须已经存在。
</Warning>

<Warning>
  **你的测试刚创建的池在同一时刻不可交换。** CPMM 的 `initialize` 无声地覆盖不严格在未来的请求 `open_time`（`if open_time <= block_timestamp { open_time = block_timestamp + 1 }`），所以即使 `startTime: 0`（"立即打开"，根据 SDK）也留下真实的 ≥1 秒间隙，然后池接受交换。一个创建池并立即与其交换的测试将命中 [`NotApproved`](/zh/reference/error-codes#cpmm-standard-amm-errors)。池创建和第一次交换之间的短 `await`（1–2s）就足够了。这特定于测试；一个人运行两个单独的手动命令通常不会注意到，因为输入和进程启动已经吃掉超过一秒。
</Warning>

## 指针

* [`products/cpmm/code-demos`](/zh/products/cpmm/code-demos)、[`products/clmm/code-demos`](/zh/products/clmm/code-demos)、[`products/amm-v4/code-demos`](/zh/products/amm-v4/code-demos)、[`products/farm-staking/code-demos`](/zh/products/farm-staking/code-demos)、[`products/launchlab/code-demos`](/zh/products/launchlab/code-demos)：产品特定的 CPI 和 TypeScript 示例。
* [`sdk-api/anchor-idl`](/zh/sdk-api/anchor-idl)：IDL 检索和客户端重新生成，包括 LaunchLab 的 IDL 代码生成路径。
* [`integration-guides/cpi-integration`](/zh/integration-guides/cpi-integration)：更高级的集成模式，如托管、保险库和聚合器组合。

来源：

* [raydium-cp-swap](https://github.com/raydium-io/raydium-cp-swap)
* [raydium-clmm](https://github.com/raydium-io/raydium-clmm)
* [raydium-idl](https://github.com/raydium-io/raydium-idl)：LaunchLab IDL（程序源本身已关闭）
* [Anchor CPI 文档](https://www.anchor-lang.com/docs/basics/cpi)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.