Skip to main content
本页内容由 AI 自动翻译,所有内容以英文版本为准。查看英文版 →
本页是权威的指令参考。如需实际编写这些指令的代码,请参阅 products/cpmm/code-demos。错误代码含义见 reference/error-codes。2026-09 程序升级在 Anchor 1.0.2 / Solana 3.1.10 上重建了 CPMM,添加了管理员指令 CollectExcessLamports,移除了硬编码的 Token-2022 mint 白名单,并改变了 CreateAmmConfig 写入 protocol_owner / fund_owner 的内容。没有用户面向的指令改变其账户、参数或数学逻辑。详见 2026-09-09 更新日志条目。
两条创建者费用收集指令在 2026-09-19 改变了账户列表。 CollectCreatorFee 增加了 creator_fee_share;CollectCreatorFeePermissionless 增加了 amm_config 和 creator_fee_share。两者都追加在 system_program 之后,因此现有客户端已经传递的每个账户都保持其索引不变——但新账户是强制性的,所以针对旧布局构建的交易传递的账户数量不足,会被 Anchor 的 AccountNotEnoughKeys(3005)拒绝。添加了两条管理员指令 — CreateCreatorFeeShare 和 CloseCreatorFeeShare — 并且 UpdateAmmConfig 接受新的 param = 8。详见 2026-09-19 更新日志条目。

指令概览

状态位掩码:每个池的 status 是一个 u8,其中位 0 = 禁用存款,位 1 = 禁用提取,位 2 = 禁用交换(程序中的 PoolStatusBitIndex { Deposit, Withdraw, Swap })。清除位表示允许操作;设置位表示暂停。UpdatePoolStatus 接受原始 u8 并覆盖现有值。 后续部分详细介绍每一条。账户顺序遵循 CPMM IDL;SDK 和 raydium-cp-swap/programs/cp-swap/src/instructions 中的 Rust 客户端匹配此顺序。

Initialize

创建新的 CPMM 池。 参数
账户(W = 可写,S = 签名者) * pool_state 仅在随机密钥对路径上签名;规范 PDA 路径运行时不需要 pool_state 签名。 前置条件
  • Mint 已排序(token_0_mint < token_1_mint 按字节顺序)。
  • 两个 mint 都不使用 CPMM 允许列表外的扩展(TransferFeeConfig、MetadataPointer、TokenMetadata、InterestBearingConfig、ScaledUiAmount)— 见 products/cpmm/accounts。其 SupportMintAssociated PDA(种子 [b"support_mint", mint])存在的 mint 跳过扩展检查 — 但你必须将该 PDA 附加到 remaining_accounts。程序仅扫描你传递的账户,从不自己加载 PDA,所以依赖注册表而不提供账户仍会失败,返回 NotSupportMint(6007)。顺序无关紧要(按密钥匹配);为每个需要绕过的 mint 传递一个条目。该注册表是唯一的绕过,因为 2026-09 升级移除了硬编码的四个 mint 白名单。
  • creator 在各自的 ATA 中至少有 init_amount_0 和 init_amount_1。
  • amm_config.disable_create_pool == false。
后置条件
  • pool_state.lp_supply = sqrt(init_amount_0 * init_amount_1) — 完整的平方根。创建者被铸造 lp_supply − 100;100 个锁定的基础单位被计入 lp_supply 但从不铸造。
  • 因此在池的生命周期内 lp_mint.supply == pool_state.lp_supply − 100。所有 LP 份额数学(存款、提取)除以 lp_supply,所以使用该字段,不要替换 mint 的链上供应。如果 sqrt(...) < 100 则回退为 InitLpAmountTooLess。
  • observation_state 被初始化;observation_index = 0 和 pool_id = pool_state.key()。
  • create_pool_fee lamport 从创建者转移到接收者并同步为原生 SOL(它是 wSOL ATA)。
  • 池的状态位掩码是 0(存款/提取/交换全部启用)。
  • enable_creator_fee = false 和 creator_fee_on = BothToken。Initialize 不支持启用创建者费用 — 该路径是 InitializeWithPermission。
  • 如果调用者传递的值 <= block_timestamp,open_time 被提升到 block_timestamp + 1。交换在 open_time 之前被拒绝;存款和提取立即工作。
常见错误(完整列表见 reference/error-codes)
  • InvalidInput — mint 未排序或相同。
  • NotSupportMint — 被阻止的 Token-2022 扩展。
  • ExceededSlippage — 罕见;如果 init_amount_0/1 由于小数位不匹配导致零 LP。

Deposit

按池的比例在两个代币中添加流动性。 参数
账户 数学
两个值得强调的细节:按比例基数是费用排除的金库总额(vault_amount_without_fee,即原始余额减去累积的协议、基金和创建者计数器),而不是原始金库余额;滑点上限针对支付者实际转移的内容检查,在 Token-2022 转账费用添加后,而不是针对总金库移动。 k 的比例性没有变化 — 两个总额和 lp_supply 按相同因子缩放。 后置条件
  • lp_supply += lp_token_amount。
  • vault_0 += needed_token_0(扣除输入上任何 Token-2022 转账费用)。
  • vault_1 += needed_token_1(扣除输入上任何 Token-2022 转账费用)。
常见错误 — ExceededSlippage、ZeroTradingTokens、如果存款被暂停则 InvalidStatus。

Withdraw

销毁 LP 代币并按比例接收两个基础代币。 参数
账户 前 13 个账户与 Deposit 相同,lp_mint 是可写的,因为 LP 代币被销毁。Withdraw 另外接受第 14 个账户 memo_program(受约束 address = memo::ID)— Deposit 不接受。13 账户的 Withdraw 失败 Anchor 反序列化,所以 LP 无法退出。 数学
后置条件
  • lp_supply -= lp_token_amount。
  • 金库发送 out_token_0 / out_token_1(总额;用户接收扣除任何 Token-2022 转账费用的净额)。

SwapBaseInput

精确输入交换。 参数
账户 顺序输入 → 输出是按用户的方向,而不是池的规范 token_0 / token_1。程序通过匹配 mint 来确定哪个金库是哪个。 数学 — 见 products/cpmm/math。 前置条件
  • open_time <= now。
  • pool_status 允许交换。
  • 对于此权限,两个 mint 都未暂停或冻结。
  • amount_in > 0。
常见错误
  • ExceededSlippage — amount_out < minimum_amount_out。
  • ZeroTradingTokens — 交易舍入为零。
  • NotApproved — 池通过 UpdatePoolStatus 暂停交换。
  • InvalidInput — mint 不匹配池的任一金库 mint。

SwapBaseOutput

精确输出交换。 参数
账户 — 与 SwapBaseInput 相同。 数学 — 反向曲线带上限,见 products/cpmm/math。 常见错误 — ExceededSlippage(gross_in > max_amount_in)、ZeroTradingTokens、InvalidInput、NotApproved。

CollectProtocolFee

从金库中清扫累积的协议费用到协议目标。 参数 — 无。 账户 效果
曲线有效余额没有变化(累积费用已被排除)。 常见错误 — 如果签名者既不是 amm_config.protocol_owner 也不是程序管理员,则 InvalidOwner(6001)。(此路径上没有 NotApproved。)

CollectFundFee

与 CollectProtocolFee 形状相同,但由 amm_config.fund_owner 签名 — 或再次由程序管理员 — 并将 fund_fees_* 计数器清零。错误签名者时返回相同的 InvalidOwner。

CollectCreatorFee

由 pool_state.pool_creator 签名。它结算累积的创建者费用并将创建者的部分转移到创建者的代币账户。 参数 — 无。 账户 效果
协议的份额从不在此处离开金库 — 它被重新标记为协议费用并等待 CollectProtocolFee。两个计数器已被排除在曲线对金库的视图之外,所以池的价格不会移动。完整推导见 products/cpmm/fees。 常见错误 — 当两个创建者计数器都为零时 NoFeeCollect(在分割前检查),如果解析的 share_rate 超过 1_000_000 则 InvalidInput(6003),如果预订份额会溢出 protocol_fees_token_* 则 MathOverflow(6011),以及如果 creator_fee_share 不是规范 PDA 则 Anchor 的 ConstraintSeeds 错误。

CollectCreatorFeePermissionless

任何人都可以触发创建者费用收集。指令始终将创建者的部分发送到由 pool_state.pool_creator 拥有的规范关联代币账户;调用者无法选择另一个创建者或目标。如果任一 ATA 缺失,支付者为其创建提供资金。 原始的 CollectCreatorFee 仍然可调用,所以想要为自己的收集签名的创建者仍然可以。 参数 — 无。 账户
两个新账户追加在 system_program 之后,而不是插入。从 payer 到 system_program 的每个账户都保持升级前的位置,因此这是一次干净的破坏性变更:针对升级前十四账户布局构建的交易不会误读金库为配置账户 — 它只是传递的账户过少,Anchor 会在任何约束运行之前以 AccountNotEnoughKeys(3005)拒绝它。这些账户仍然是强制性的,所以请追加两者并刷新 IDL;旧布局没有兼容路径。
效果 — 与上面的 CollectCreatorFee 相同:份额从 creator_fee_share 或 amm_config 解析,协议的部分被记入 protocol_fees_token_{0,1},创建者的部分被转移到创建者 ATA,两个创建者计数器被清零,recent_epoch 被更新。当两个计数器都为零时返回 NoFeeCollect。

UpdatePoolStatus

暂停或恢复池上的单个操作。status 字段是一个位掩码: 参数
账户 管理员密钥是编译到程序中的公钥(crate::admin::ID),而不是 BPF 升级权限 — 更改它需要程序升级。见 reference/program-addresses 了解值,以及 security/admin-and-multisig 了解谁持有它。

CreateAmmConfig

创建新的费用等级。 参数
账户 前置条件
  • 不存在具有相同 index 的 AmmConfig。
  • protocol_fee_rate + fund_fee_rate <= FEE_RATE_DENOMINATOR_VALUE。
2026-09 中更改:新配置的费用所有者不再来自签名者。 create_amm_config 现在将程序的硬编码 protocol_fee_owner::ID 写入 protocol_owner 和 fund_fee_owner::ID 写入 fund_owner,而不是将管理员签名者的密钥复制到两者。地址在 reference/program-addresses。后果:新创建的 AmmConfig 上的费用落入专用费用钱包,而不是管理员的。管理员确实仍然是收集的接受签名者 — CollectProtocolFee / CollectFundFee 接受 amm_config.protocol_owner / fund_owner 或 crate::admin::ID — 所以没有什么需要轮换来清扫;改变的只是收益默认去向的地方。现有的 AmmConfig 账户不被重写 — 存储在它们上的任何内容仍然管理,所以始终从账户读取 protocol_owner / fund_owner 而不是假设任一值。UpdateAmmConfig 参数 3 和 4 仍然轮换它们。

UpdateAmmConfig

更改现有 AmmConfig 上的费率或所有权。接受 param: u8(要更新的字段)和 value: u64。完整的分派表:
  • param = 0 → trade_fee_rate(断言 trade_fee_rate + creator_fee_rate < 1_000_000)
  • param = 1 → protocol_fee_rate(断言 ≤ 1_000_000 和 + fund_fee_rate ≤ 1_000_000)
  • param = 2 → fund_fee_rate(断言 ≤ 1_000_000 和 + protocol_fee_rate ≤ 1_000_000)
  • param = 3 → protocol_owner。新密钥不在 value 中:将其附加为 remaining_accounts[0](只读可以)。它不能是默认公钥,省略账户会在 unwrap() 上恐慌。
  • param = 4 → fund_owner。与 3 相同的机制。
  • param = 5 → create_pool_fee
  • param = 6 → disable_create_pool(任何非零 value 禁用)
  • param = 7 → creator_fee_rate(断言 creator_fee_rate + trade_fee_rate < 1_000_000)
  • param = 8 → creator_fee_share_rate(断言 ≤ 1_000_000)。添加于 2026-09-19。协议在此等级上的创建者费用的默认份额;见 products/cpmm/fees。它与 protocol_fee_rate 无关,后者分割交易费用。
任何其他 param 返回 InvalidInput。 更改由管理员签名并影响绑定到此 AmmConfig 的每个池在下一次交换时。无迁移;池简单地读取新值。

CreateCreatorFeeShare

为一个 (creator, amm_config) 对设置自定义协议份额的创建者费用,覆盖 AmmConfig.creator_fee_share_rate 用于该创建者在该费用等级上拥有的每个池。添加于 2026-09-19 创建者费用份额升级。 参数
账户 前置条件
  • share_rate <= 1_000_000,否则 InvalidInput(6003)。
  • PDA 不能已存在 — Anchor 的 init 在同一对的第二次调用时失败。要更改费率,关闭账户并重新创建。
后置条件
  • creator_fee_share 存储 bump、creator、amm_config 和 share_rate。
  • 每个后续的 CollectCreatorFee / CollectCreatorFeePermissionless 在由 creator 在 amm_config 下创建的池上从此账户解析份额,而不是配置。
池创建者不是此指令的一方,也不签名。费率在收集时读取,所以在费用已经累积后创建的覆盖适用于该累积余额。

CloseCreatorFeeShare

移除覆盖。该对回退到 AmmConfig.creator_fee_share_rate。 参数 — 无。 账户 后置条件
  • 账户被关闭,其 lamport 去到 owner。
  • 该对的收集再次从 amm_config.creator_fee_share_rate 解析份额 — 除非管理员已设置 UpdateAmmConfig 参数 8,否则为 0。

CollectExcessLamports

管理员清扫 CPMM 控制的账户上超过租金豁免最低值的 lamport。添加于 2026-09 升级,以便协议可以回收 SIMD-0437 租金减少 在每个步骤后在升级前创建的账户上留下的过度资金。 只有超额移动。代币余额、账户数据、所有者、池状态和曲线保持不变,指令对已在最低值的账户是无操作 — 所以在每个推出步骤后重新运行是安全的。 参数 — 无。 账户
顺序修复,2026-09-19。 程序现在对 remaining_accounts 进行两次传递 — 每个代币程序 CPI 首先,然后是 CPMM 拥有的 PDA 的直接借记。当 PDA 在 CPI 之前被借记时,它们会中止,返回运行时的 UnbalancedInstruction(“指令前后账户余额之和不匹配”),因为调用者的待处理 lamport 更改仅在 CPI 实际执行时刷新到账户。调用者不必自己对列表进行分组或排序。
每个源账户的处理方式 程序根据源账户的 owner 分派: 因为它接受无界的 remaining_accounts 列表,交易大小是真正的限制 — 与 solana-fundamentals/rent-and-reclaimable-rent 中描述的钱包侧清扫相同的约束。 常见错误 — InvalidOwner(6001,错误的签名者)、LamportsCalculateError(6015,wSOL 往返没有净为零)、以及当账户持有少于其自己的租金最低值时来自程序拥有路径的 InsufficientFunds。 无 SDK 构建器。 @raydium-io/raydium-sdk-v2 不提供此指令的构建器,raydium-sdk-V2-demo 仓库也不提供 — 它是管理员路径。手动编码,就像 solana-fundamentals/rent-and-reclaimable-rent 中的钱包侧清扫对代币程序指令所做的那样。

状态变化矩阵

后续步骤

来源: