Skip to main content
本页内容由 AI 自动翻译,所有内容以英文版本为准。查看英文版 →

环境准备

本页的每个示例都对应 raydium-sdk-V2-demo/src/clmm 中的一个文件,每个小节旁边都附有对应的 GitHub 链接。初始化方式参考 demo 仓库中的 config.ts.template(源文件)。对于任何非简单的集成,建议设置 disableFeatureCheck: true:

创建 CLMM 池

源文件:src/clmm/createPool.ts
SDK 会执行以下操作:
  • 在推导之前,按字节顺序对 mint1/mint2 排序。
  • 计算 sqrt_price_x64 = floor(sqrt(initialPrice × 10^(dB−dA)) × 2^64)。
  • 创建 observation 和 tick_array_bitmap_extension 账户。
  • 支付由 ammConfig 定义的池创建费用。

在指定区间开仓

源文件:src/clmm/createPosition.ts
SDK 会计算该区间涉及哪些 tick array,并将其作为账户传入。它不需要打包任何初始化指令:不存在 init-tick-array 指令,OpenPosition* 会自行分配缺失的 tick array,费用由付款方承担。

增加现有仓位的流动性

源文件:src/clmm/increaseLiquidity.ts

减少流动性(同时领取手续费)

源文件:src/clmm/decreaseLiquidity.ts 和 src/clmm/closePosition.ts
若要仅领取手续费和奖励,请以 liquidity = new BN(0) 调用 decreaseLiquidity。该指令的副作用是结算 token_fees_owed_{0,1} 和 reward_amount_owed 并将其转出,这是领取两者的唯一方式。 若要在将流动性和手续费清零后完全关闭仓位,请在最后一次 decreaseLiquidity 调用中传入 ownerInfo: { closePosition: true }。SDK 会追加 ClosePosition 并销毁 NFT。
受限发行方的仓位需要兼容的关闭构建器。 这类仓位的 NFT 代币账户处于冻结状态。ClosePosition 必须将仓位所在池的 ID 作为第一个剩余账户追加,以便 CLMM 在销毁前解冻该账户。程序源码分支不包含 SDK 的改动。在启用受限发行方仓位的创建之前,请确认你的 SDK 版本明确支持冻结状态下的关闭路径。
对于直接使用的 Anchor 客户端,请保持声明的账户不变,并追加池账户:
每次关闭时都可以传入 poolId。CLMM 仅在 positionNftAccount 处于冻结状态时才会读取它,这样同一套客户端代码即可同时兼容旧仓位和新仓位。

领取奖励

源文件:src/clmm/harvestAllRewards.ts
harvestAllRewards 会遍历所传入的每个池中的每一个仓位,将用于结算手续费和奖励的零流动性 DecreaseLiquidity 调用(以及任何 UpdateRewardInfos)打包在一起,必要时拆分到多笔交易中。

Swap

来源:src/clmm/swap.ts 本示例与 src/clmm/swap.ts 保持一致,它使用 swapInternal 进行模拟,而不是 PoolUtils.computeAmountOutFormat。
模拟过程会在链下按照与链上程序相同的逻辑遍历 tick map,并返回输出数量(amountCalculated),以及兑换将涉及的精确账户列表(accounts)。 务必传入模拟返回的 remainingAccounts:数量不足时,兑换会在遍历过程中以 NotEnoughTickArrayAccount 回滚;多余的账户则只会浪费计算资源。
PoolUtils.computeAmountOutFormat 仍然存在,但它需要一个 ComputeClmmPoolInfo(即由 getPoolInfoFromRpc 返回的 computePoolInfo,而非 API 池对象),此外还需要另外两个必填参数 —— tickarrayBitmapExtension 和 blockTimestamp。而且 raydium.clmm.fetchTickArrays 方法并不存在(fetchTickArrays 是一个独立函数;模块级辅助方法是 PoolUtils.fetchMultiplePoolTickArrays,以及 getPoolInfoFromRpc 返回的 tickData / tickArrays)。

创建可自定义的 CLMM 池

createCustomizablePool 是在创建池时开放动态手续费和单边手续费开关的入口。它的参数与 createPool 相同,但额外增加了两个参数:
不存在 enableDynamicFee、dynamicFeeConfigId 或 startTime 参数。只要传入 dynamicFeeConfig,就会启用动态手续费 —— 如果省略它,你会得到一个固定手续费的池,且不会报错。另外注意,SDK 的枚举成员名为 TokenOnlyA / TokenOnlyB,而链上 Rust 枚举的拼写是 Token0Only / Token1Only;两者的数值一致(FromInput = 0)。
createPool 在默认手续费、无动态手续费的场景下仍然可用。只要需要上述任一功能,就改用 createCustomizablePool。链上账户列表请参阅 products/clmm/instructions。

限价单

限价单会将用户的输入资产挂在单一 tick 上,当兑换跨过该 tick 时按先进先出(FIFO)的顺序成交。产出资金会在结算时转入所有者的 ATA;所有者无需在线即可完成成交。

开启限价单

该池不存在按池配置的限价单账户,也没有 getClmmLimitOrderConfigs() 辅助方法 —— 订单以 tick 作为索引。
SDK 会根据 (owner, nonce PDA, order nonce) 推导出 LimitOrderState PDA,递增每个钱包的 LimitOrderNonce,并将订单插入该 tick 对应的 FIFO 队列中。

增加 / 减少已开启的订单

decreaseLimitOrder 只能从订单中未成交的部分移除资产;已成交的部分会被锁定,直到结算完成。如果订单已全部成交,这两条指令都会以 InvalidOrderPhase 回滚。

结算已成交的订单

settleLimitOrder 会根据跟踪器读取订单的 unfilled_ratio_x64,计算已成交的产出,并将其转入所有者的 ATA。所有者可以自行调用;limit_order_admin(链下运维 keeper)也可以代为调用,但产出仍然归所有者。 如需关闭已完全结算的订单以回收租金,可使用 closeLimitOrder(单个)或 closeAllLimitOrder(批量)。如需一次结算多个订单,settleAllLimitOrder 会将尽可能多的 SettleLimitOrder 调用打包进一笔 v0 交易。

列出钱包中挂单的订单(链下)

有效订单接口会在同一个响应中同时返回未成交和部分成交的订单(通过 totalAmount / filledAmount / pendingSettle 区分各个阶段)。如需查询已关闭订单的历史,请使用 /limit-order/history/order/list-by-user?wallet=…(按钱包查询,通过 nextPageId 分页);如需查看某个订单的完整事件日志,请使用 /limit-order/history/event/list-by-pda?pda=…。

Rust CPI 骨架

SwapV2 的剩余账户顺序如下:
如果兑换从不需要扩展账户,则省略它;否则它就是第一个剩余账户。

常见陷阱

  • tick 端点未对齐间距 → TickAndSpacingNotMatch。请始终通过 TickUtil.getPriceAndTick(注意是单数的 TickUtil)进行对齐。
  • SwapV2 中提供的 tick array 数量不足 → NotEnoughTickArrayAccount。请从 swapInternal(...).accounts 中获取该列表。
  • 全范围仓位未使用 bitmap 扩展 → 扩展 PDA 必须可写。SDK 会自动处理这一点。
  • 把 sqrt_price_x64 误当作 price → 这里的 2 倍系数错误尤其难排查。如有疑问,请让 SDK 根据可读的价格进行计算。
  • 过于频繁地领取奖励 → 每次领取都是一笔零流动性的 DecreaseLiquidity,且各自占用一笔交易。请通过 harvestAllRewards 批量处理多个仓位,并注意其 execute 需要传入 { sequentially: true }。
  • 自行关闭 NFT 账户 → ClosePosition 会销毁 NFT 并关闭其 ATA。对于 Token-2022 NFT mint,它同样会将其关闭;而经典 SPL Token mint 则会保留,供应量为零,因为该程序无法关闭 mint。请勿单独关闭受支持的账户,否则指令会回滚。
  • 在未对齐间距的 tick 上开立限价单 → TickAndSpacingNotMatch。请始终通过导出的 getOrderTick 辅助函数进行量化。
  • 对已完全成交的订单调用 decreaseLimitOrder → InvalidOrderPhase。请改用 settleLimitOrder,然后再调用 closeLimitOrder。
  • 以为存在 enableDynamicFee 标志 → 实际上并不存在。省略 dynamicFeeConfig 只会创建一个静态费率池,不会有任何提示或报错。如果你需要动态费率,请传入配置账户的 PublicKey,可从 /main/clmm-dynamic-config 中选取。

下一步

来源: