本页内容由 AI 自动翻译,所有内容以英文版本为准。查看英文版 →
CPI 何时是合适的工具
当交易需要与其他只有你的程序才能执行的链上状态变化原子性地发生时,自定义程序就很有意义。常见的案例包括:- 资金托管/限价单程序 — 用户向你的托管账户存入一个 mint,你的程序监视价格条件,当条件触发时,你的程序通过 Raydium 原子性地交换并将结果记入用户账户。
- 聚合器代理 — 单个指令将交换路由到 Raydium 和一个或多个其他 DEX,所有跳跃都在你的程序拥有的单个滑点检查下。
- 自动复合金库 — 将 LP 或农场质押存入你的金库,金库按计划收获奖励,重新提供流动性,发行份额代币。
- 策略金库 — 通过交换 CLMM 进行再平衡的杠杆 LP 头寸;在一笔交易中平仓头寸并交换抵押品的清算人。
- 带有自定义归属的代币启动平台 — 你的程序持有归属代币,并按计划释放到 Raydium 池中。
组合模式
模式 1:瘦代理
你的程序公开一条指令,该指令验证某个策略(例如白名单 mint 对、验证用户的费用折扣),然后转发到 Raydium。模式 2:资金托管
你的程序拥有一个持有用户输入 mint 的 PDA。触发时,PDA 签署向 Raydium 交换其自身余额的 CPI。CpiContext::new_with_signer 签署。参见签名者种子。
模式 3:组合多跳
你的程序在一条指令中发出多个 CPI,在所有 CPI 中执行单个滑点界限。Raydium 交换指令各自有自己的minimum_amount_out,但你将其设置为 0(或非常宽松的下限),在最后一跳后自己执行严格的最终最小值。
模式 4:金库/策略
你的程序在 PDA 中持有 LP 代币或农场质押。保管人(或用户)调用compound(),该函数:
- 从农场收获奖励。
- 交换奖励以获取池代币(CPI 进入 CPMM 或 CLMM)。
- 将所得重新存入 LP(另一个 CPI)。
- 质押新的 LP(另一个 CPI)。
账户列表构造
调用程序的Accounts 结构体镜像 Raydium 程序的账户顺序,但大多数 Raydium 侧的账户是 UncheckedAccount,因为 Raydium 本身验证它们。你只在你拥有的账户上添加约束:
UncheckedAccount — 不是懒惰。接收方验证自己的账户;在调用方重复验证只会消耗 CU,并且当 Raydium 发货新的结构体布局字段时有失步风险。
CPI 调用本身
PDA 签名者种子
仅当作为authority 传递的 PDA 与调用方声称的派生相匹配时,CPI 才成功。两者必须就以下内容达成一致:
- 种子字节序列(这里
[b"escrow", user.key().as_ref()])。 - bump。
- 调用程序 ID(你的程序,而非 Raydium 的)。
authority 签名覆盖交易,且输入 ATA 由该 authority 拥有。验证发生在 anchor_spl::token::transfer 中:ATA 的 authority 字段必须等于签名者。
常见错误:将 user 作为 authority 传递(并从由托管 PDA 拥有的 escrow_input_ata 转账)。SPL Token 程序以 owner mismatch 拒绝。始终让 authority 字段与 ATA 所有者匹配。
剩余账户
几个 Raydium 指令在固定账户之后采用可变长度的账户列表 — 剩余账户。- CLMM
SwapV2:1–8 个TickArrayState账户,用于交换可能穿过的 tick 数组,按交换方向。 - Farm v6
Deposit/Harvest/Withdraw:(reward_vault, user_reward_ata)对,每个活跃奖励槽一对。 - Token-2022 transfer-hook mints:transfer-hook 程序加上 hook 需要的任何账户。
组合调用的计算预算
一个 CPI 的调用框架本身耗费约 1,500 CU;被调用者自己的 CU 使用堆积在顶部。每个 Raydium CPI 的粗略预算:| 调用 | CU (SPL Token) | CU (Token-2022) |
|---|---|---|
| CPMM swap_base_input | ~150,000 | ~200,000 |
| CLMM swap_v2 (单个 tick 数组) | ~180,000 | ~230,000 |
| CLMM swap_v2 (穿过 2 个 ticks) | ~220,000 | ~270,000 |
| Farm v6 deposit | ~120,000 | ~150,000 |
| Farm v6 harvest (每个奖励槽) | +30,000 | +40,000 |
| AMM v4 swap_base_in | ~140,000 | n/a |
harvest → swap A → swap B → deposit LP → stake LP 的自动复合器轻易达到 700k CU。
总是设置显式的 ComputeBudgetProgram::set_compute_unit_limit:
错误传播
Raydium 程序返回带有稳定错误代码的 Anchor 错误。你的调用程序将其视为Err(ProgramError::Custom(code))。默认情况下冒泡:
sdk-api/anchor-idl)是稳定的;新代码在末尾附加,现有代码从不改变含义。
完整工作示例:限价单托管
流程:open_order— 用户向托管 PDA 存入amount_in的input_mint;记录目标min_amount_out和有效期。execute_order— 任何人(保管人)以当前池账户调用。程序检查当前报价 ≥min_amount_out,然后 CPI Raydium 交换并在托管中保留输出。claim— 用户从托管中提取输出 mint。
测试
将 Raydium 程序拉入本地验证器以进行集成测试(来自Anchor.toml):
anchor test 在启动时从主网获取它们。参见 sdk-api/rust-cpi。
组合特有的陷阱
重入
Solana 没有真正的重入 — 在同一调用中,CPI 不能回调到原始程序。但你仍然可以将自己陷入逻辑重入:CPI 读取你的状态,然后你的代码再次读取它,假设 CPI 没有改变它。对于 Raydium,CPI 不接触你的状态,所以这比例如闪贷上下文中的风险要小。但如果你将 Raydium 与借贷协议组合,请注意。账户可变性漂移
如果你的程序将账户作为mut 传递,但 Raydium 期望它是只读的(或反之),运行时以 InvalidAccountData 拒绝调用。总是检查 IDL 中 Raydium 指令的预期可变性;anchor_cp_swap::cpi::accounts::Swap 通过其字段类型执行它。
Token-2022 程序字段
输入和输出 mints 可能在不同的代币程序下 — 一个 SPL Token,一个 Token-2022。CPI 有单独的input_token_program 和 output_token_program 字段是有原因的。始终检查每个 mint 的 owner 字段,并将正确的程序路由到每个槽中。
版本化交易
执行 2+ Raydium CPI 加上 ATA 创建的组合 tx 很少适合遗留(v0 不带 LUT)交易。使用 V0 和地址查找表;通过raydium.getRaydiumLutAddresses() 拉取 Raydium 的公开 LUT。
指针
sdk-api/rust-cpi— 低级 CPI 机制。integration-guides/priority-fee-tuning— 调整计算预算的大小。products/cpmm/code-demos、products/clmm/code-demos、products/farm-staking/code-demos— 每个产品的 CPI 片段。

