Skip to main content
本页内容由 AI 自动翻译,所有内容以英文版本为准。查看英文版 →
sdk-api/rust-cpi 涵盖了调用每个 Raydium 程序的低级机制。本页是更高层次的补充:为什么要将 Raydium 组合到你的程序中、哪种模式适合你的用例,以及端到端所需的完整粘合代码。

CPI 何时是正确的工具

当交易需要与只有你的程序才能进行的其他链上状态变化原子性地发生时,自定义程序才有意义。常见情况包括:
  • 托管/限价单程序 — 用户将一个代币存入你的托管账户,你的程序监视价格条件,当触发时,你的程序原子性地通过 Raydium 交换并将结果记入用户账户。
  • 聚合器代理 — 单个指令通过 Raydium 和一个或多个其他 DEX 路由交换,所有跳跃都在由你的程序拥有的单个滑点检查下。
  • 自动复利金库 — 将 LP 或农场质押存入你的金库,金库按计划收获奖励、重新供应流动性、发行份额代币。
  • 策略金库 — 通过 CLMM 交换进行再平衡的杠杆 LP 头寸;清算人在一笔交易中关闭头寸并交换抵押品。
  • 带有自定义归属的代币启动平台 — 你的程序持有归属代币并按计划释放到 Raydium 池中。
如果你只想从链下代码发送交换,CPI 就太复杂了——使用 SDK。只有当与你自己的状态的原子性是要求时,CPI 才值得这种复杂性。

组合模式

模式 1:薄代理

你的程序公开单个指令,验证某些策略(例如白名单代币对、经过验证的用户的费用折扣),然后转发到 Raydium。
状态存在于用户的 ATA 中。你的程序不拥有任何代币。最小信任足迹。

模式 2:托管

你的程序拥有一个 PDA,持有用户的输入代币。触发时,PDA 签署一个 CPI 到 Raydium 来交换其自己的余额。
关键细节:PDA 通过 CpiContext::new_with_signer 签署。见 PDA 签名者种子。

模式 3:组合多跳

你的程序在一个指令中发出多个 CPI,在所有 CPI 中强制执行单个滑点界限。Raydium 交换指令各自有自己的 minimum_amount_out,但你将其设置为 0(或非常宽松的下限),并在最后一跳后自己强制执行严格的最终最小值。
这为整个路由提供了单个回滚门。仅当你信任每个跳跃都是滑点安全的时才使用此模式;否则,让每个跳跃强制执行其自己的最小值。

模式 4:金库/策略

你的程序在 PDA 中持有 LP 代币或农场质押。保管人(或用户)调用 compound(),其中:
  1. 从农场收获奖励。
  2. 将奖励交换为池代币(CPI 到 CPMM 或 CLMM)。
  3. 将收益存回 LP(另一个 CPI)。
  4. 质押新的 LP(另一个 CPI)。
全部在一笔交易中进行,以便金库的 NAV 原子性地移动。计算预算通常为 600k–1M CU;地址查找表是必需的。

账户列表构造

调用程序的 Accounts 结构镜像 Raydium 程序的账户顺序,但大多数 Raydium 端账户是 UncheckedAccount,因为 Raydium 自己验证它们。你只在你拥有的账户上添加约束:
不对称性——对你的账户进行严格验证,对 Raydium 的账户使用 UncheckedAccount——不是懒惰。接收方验证自己的;在调用方处双重验证只会浪费 CU,并且当 Raydium 发布新的结构布局字段时存在不同步的风险。

CPI 调用本身

PDA 签名者种子

CPI 仅在作为 authority 传递的 PDA 与调用方声称的派生相匹配时才成功。两者必须同意:
  1. 种子字节序列(这里是 [b"escrow", user.key().as_ref()])。
  2. bump。
  3. 调用程序 ID(你的程序,不是 Raydium 的)。
注意 PDA 必须与什么匹配。CPMM 的 authority 槽位是它自己的金库 PDA —— 一个固定的、程序范围内的账户,由 CPMM 自己派生并签名,你的程序既不控制它也不能替换它。你的 PDA 种子必须对得上的账户是 payer:检查发生在 CPMM 自己的 transfer_from_user_to_pool_vault 辅助函数内部,它要求作为 payer 传入的账户必须是 input_token_account 的所有者。 常见错误:在 escrow_input_ata 由托管 PDA 拥有时,却把 user 作为 payer 传递。SPL Token 程序会以 owner mismatch 拒绝。始终让 payer 是该 ATA 的所有者 —— 当该所有者是 PDA 时,用 new_with_signer 为它签名。

剩余账户

几个 Raydium 指令在固定账户之后采用可变长度的账户列表——剩余账户。
  • CLMM SwapV2:1–8 个 TickArrayState 账户,用于交换可能遍历的 tick 数组,按交换方向。
  • Farm v6 Deposit / Harvest / Withdraw:(reward_vault, user_reward_ata) 对,每个活跃奖励槽一对。
  • Token-2022 转账钩子代币:转账钩子程序加上钩子需要的任何账户。
Anchor CPI 助手不对剩余账户进行类型检查。直接传递它们:
顺序很重要。 对于 CLMM:
对于 farm v6 harvest:
你的调用程序必须将从客户端接收的剩余账户原封不动地传递。不要尝试过滤或重新排序它们。

组合调用的计算预算

CPI 本身的调用框架成本约 1,500 CU;被调用方自己的 CU 使用堆叠在其上。 下面的被调用方数据是 2026-09-09 在高交易量池上从主网实际交易中测得的,读取的是 Raydium 程序自身调用的 Program <id> consumed N of M compute units 日志行(因此已包含其内部的 token 程序 CPI): 在此之上,每个 CPI 框架再加约 1,500,以及你自己程序的开销。CLMM 交换的成本随穿越的 tick 数增长,因此把它的数值当作下限。Token-2022 mint 会增加转账本身处理扩展的成本;请针对你自己的 mint 实测,而不要套用一个固定倍数。
本页较早的修订版给出的估计高出 5–7 倍(CPMM 交换约 150,000 CU,CLMM 约 180,000)。那些数字从未 被实测过。请以你自己读到的 computeUnitsConsumed 为预算依据,而不是文档里的数字 —— 并注意一旦把 ATA 创建、wSOL 包装和 compute-budget 指令都算进去,整笔交易的成本会高于单条 Raydium 指令。
始终设置显式的 ComputeBudgetProgram::set_compute_unit_limit:
默认的 200k CU 上限将在组合调用完成之前很久就无声地耗尽。

错误传播

Raydium 的程序返回带有稳定错误代码的 Anchor 错误。你的调用程序将其视为 Err(ProgramError::Custom(code))。默认情况下冒泡:
或拦截特定代码:
注意 ERROR_CODE_OFFSET 这一项:#[error_code] 变体从 6000 开始生成,因此与裸枚举判别值比较永远不会匹配。(anchor-lang 和 raydium_cp_swap 中都没有 is_err 辅助函数——本页的早期版本用过一个并不存在的函数。) 错误代码到含义的映射根据 IDL 策略是稳定的(sdk-api/anchor-idl);新代码在末尾追加,现有代码的含义永不改变。

完整的实现示例:限价单托管

流程:
  1. open_order — 用户将 amount_in 的 input_mint 存入托管 PDA;记录目标 min_amount_out 和过期时间。
  2. execute_order — 任何人(保管人)使用当前池账户调用。程序检查当前报价 ≥ min_amount_out,然后 CPI Raydium 交换并将输出保留在托管中。
  3. claim — 用户从托管中提取输出代币。
保管人支付交易费(他们在其他地方获得保管人费——未显示)。order PDA 作为 payer 为 CPI 签名,因为它拥有托管的输入 ATA;因此 ExecuteOrder 还需要一个 pool_authority: UncheckedAccount<'info> 字段来传入 CPMM 自己的金库 PDA。Raydium 端滑点检查和托管自己的增量检查都强制执行下限——双重保险。

测试

将 Raydium 程序拉入本地验证器进行集成测试(来自 Anchor.toml):
也克隆池状态账户,以便你的测试可以实际执行交换;anchor test 在启动时从主网获取它们。见 sdk-api/rust-cpi。

特定于组合的陷阱

可重入性

Solana 没有真正的可重入性——CPI 不能在同一调用中回调到原始程序。但你仍然可以将自己构建成逻辑可重入性:CPI 读取你的状态,然后你的代码再次读取它,假设 CPI 没有改变它。对于 Raydium,CPI 不接触你的状态,所以这不如例如闪电贷上下文那样令人担忧。但如果你将 Raydium 与借贷协议组合,请注意。

账户可变性漂移

如果你的程序将账户作为 mut 传递,但 Raydium 期望它为只读(或反之),运行时以 InvalidAccountData 拒绝调用。始终在 IDL 中检查 Raydium 指令的预期可变性;raydium_cp_swap::cpi::accounts::Swap 会根据 CPMM 自己的 Swap 结构体上的 #[account(mut)] 标记为你设置每个账户的可变性 —— 生成的字段全都是普通的 AccountInfo<'info>,因此携带这些标志的是派生出的 ToAccountMetas 实现,而不是字段类型。

Token-2022 程序字段

输入和输出代币可能在不同的代币程序下——一个 SPL Token,一个 Token-2022。CPI 有单独的 input_token_program 和 output_token_program 字段正是出于这个原因。始终检查每个代币的 owner 字段,并将正确的程序路由到每个槽中。

版本化交易

执行 2+ 个 Raydium CPI 加上 ATA 创建的组合 tx 很少适合传统(v0-without-LUT)交易。使用 V0 和地址查找表;通过 raydium.getRaydiumLutAddresses() 拉取 Raydium 的公共 LUT。

指针

来源: