本页内容由 AI 自动翻译,所有内容以英文版本为准。查看英文版 →
本页描述了每个账户的布局和作用。规范的种子列在
reference/program-addresses 中。CLMM 池比 CPMM 池需要更多账户,因为流动性稀疏地分布在 tick 范围内;理解这种稀疏性是本页的重点。账户清单
一个活跃的 CLMM 池由以下账户族组成。除了两个 mint 及其金库外,所有账户都由 CLMM 程序拥有。PoolState
池的实时状态,在每次交换和每次头寸变化时读取。
sqrt_price_x64和tick_current是池的价格状态。它们在每次交换时一起更新。tick_current是log_{1.0001}(price)的下界。liquidity是活跃流动性——所有范围包含tick_current的头寸的L值之和。每次交换穿过 tick 时以及每次打开/关闭/调整头寸大小时都会改变。fee_growth_global_{0,1}_x64是整个池历史中每单位流动性累积的费用。头寸读取这个值来计算欠它们的费用。tick_spacing在初始化时锁定到AmmConfig,永远不会改变。它决定了哪些 tick 索引可以作为头寸端点。tick_array_bitmap是一个内联位图,覆盖现货价格周围的”接近”范围——±1,024 个 tick 数组。对于头寸范围很远的池,溢出跟踪位于单独的TickArrayBitmapExtension中。fee_on在池创建时固定。0(FromInput)复现经典 Uniswap-V3 行为。1和2将交换费路由到账簿的单一一侧——参见products/clmm/fees了解权衡。seed_index对于通过CreatePool/CreateCustomizablePool创建的每个池都是[0, 0](每对一个规范池)。非零值意味着池是通过CreatePermissionedPool创建的,索引是池 PDA 种子的一部分,允许多个池为同一(config, mint0, mint1)共存。要重新派生这样的池的地址,你必须知道它的seed_index。dynamic_fee_info携带动态费用加价的波动性状态。启用时,每次交换在AmmConfig.trade_fee_rate之上重新计算dynamic_fee_component。布局在下面的DynamicFeeInfo中记录;没有动态费用的池将整个结构保留为零。
AmmConfig
GET https://api-v3.raydium.io/main/clmm-config):
protocol_fee_rate 和 fund_fee_rate 是交易费的分数;与 CPMM 相同的约定。参见 products/clmm/fees。
TickArrayState
CLMM 不为每个 tick 存储单个记录。那会是数十亿个账户。相反,它将**TICK_ARRAY_SIZE 个相邻的已初始化或未初始化 tick**(通常 60 或 88,取决于程序版本)分组到一个 TickArrayState 中,该状态在首次使用时懒惰创建。
order_phase是队列 id。每次队列从”全部未填充”转换为”部分填充”时递增。orders_amount是当前(最新)队列的输入代币总额。part_filled_orders_remaining跟踪当前正在被进行中的交换填充的前一个队列。unfilled_ratio_x64是在队列上携带的 Q64.64 乘数:当交换填充队列的 X% 时,比率乘以(1 − X)。每个开放订单在打开时存储自己的(order_phase, unfilled_ratio_x64)快照,所以结算数学简化为比较快照。
- 头寸端点 tick t 必须满足
t % tick_spacing == 0。程序拒绝不符合间距的头寸。 - tick 的数组位于
floor(t / (TICK_ARRAY_SIZE * tick_spacing)) * (TICK_ARRAY_SIZE * tick_spacing)。 - tick 数组懒惰初始化:第一个接触未初始化数组的头寸或交换创建它,支付租金。
- tick 数组永远不会被程序关闭。一旦分配,它就会在池的整个生命周期内持续存在,即使其中的每个 tick 都返回到
liquidity_gross == 0。后续头寸和交换以零额外租金重用现有账户。没有ClosePosition驱动的 tick 数组清理路径。
TickArrayBitmapExtension
PoolState.tick_array_bitmap(内联)覆盖”接近现货”范围——±1,024 个 tick 数组。超出该范围(对于极端 tick 值),程序维护一个扩展账户:
(MIN_TICK, MAX_TICK))需要它;SDK 为你解决它。
头寸
CLMM 头寸是三个账户加一个 mint 的捆绑:Position NFT mint
一个供应量为 1 的 SPL Token 或 Token-2022 mint。所有者钱包中的头寸 NFT 是持有该单个代币的 ATA。程序将授权密钥设置为 NFT ATA 余额的当前持有者,而不是存储在状态中的 Pubkey。 新头寸 NFT mint 在铸造单个代币并移除 mint 权限之前将pool_state 设置为其冻结权限。设置冻结权限本身不会冻结 NFT 账户。 账户保持未冻结和可转移,除非两个条件都成立:调用者使用 OpenPositionV2 或 OpenPositionWithToken22Nft,并且至少一个底层金库 mint 的冻结权限出现在 CLMM 的受限发行者列表中。只有这样,CLMM 才会在铸造后冻结 NFT 账户。这不改变任何 PersonalPositionState 或 PoolState 字节。
PersonalPositionState
每个开放头寸一个。以 NFT mint 为键。
ProtocolPositionState(已弃用)
较早的 CLMM 版本在
ProtocolPositionState PDA 中存储每个 (pool, tick_lower, tick_upper) 的聚合簿记。较新的版本不再创建或读取此账户。 该槽仍然作为 UncheckedAccount 出现在 OpenPosition / IncreaseLiquidity / DecreaseLiquidity 账户列表中以保持 ABI 兼容性,但程序不写入它。链上现有账户是残留的;管理员可以调用 CloseProtocolPosition 来回收它们的租金。聚合范围簿记现在直接从两个端点 tick(liquidity_gross、liquidity_net 和 TickArrayState 中的每个 tick fee_growth_outside_* / reward_growths_outside_x64)派生。费用增长内部公式 fee_growth_inside = global − outside_lower − outside_upper 继续在没有聚合头寸账户的情况下工作。Observation
(tick_cumulative[t1] − tick_cumulative[t0]) / (t1 − t0) 计算区间上的几何平均价格,然后 price = 1.0001 ** tick。参见 algorithms/clmm-math。
DynamicFeeConfig 和 DynamicFeeInfo
动态费用参数位于两个地方。可重用模板——DynamicFeeConfig——由管理员管理并在选择加入的池之间共享。每个池的运行时状态——DynamicFeeInfo——嵌入在 PoolState 中并由每次交换更新。
DynamicFeeConfig
["dynamic_fee_config", index.to_be_bytes()]。通过 create_dynamic_fee_config(管理员门控)创建,通过 update_dynamic_fee_config 修改。通过 enable_dynamic_fee = true 创建的池在创建时将配置的五个校准参数(filter_period、decay_period、reduction_factor、dynamic_fee_control、max_volatility_accumulator)快照到自己的 DynamicFeeInfo 中;对 DynamicFeeConfig 的后续编辑不会追溯影响现有池。
DynamicFeeInfo(嵌入在 PoolState 中)
DynamicFeeConfig 复制的校准。费用数学和衰减规则在 products/clmm/math 和 products/clmm/fees 中记录。
公式使用的常数:
LimitOrderState
每个开放限价单一个账户。
- 打开 — 用户调用
open_limit_order,存入输入代币的total_amount,订单绑定到TickState队列。 - (可选)增加/减少 —
increase_limit_order添加到total_amount;decrease_limit_order返回未填充的代币(和到该点为止的任何已结算输出)。 - 结算 — 当队列完全或部分填充时,所有者或操作保管人调用
settle_limit_order将输出代币推送到所有者的 ATA。 - 关闭 — 一旦
unfilled_amount == 0,账户可关闭。租金总是返回给owner。
[owner.as_ref(), limit_order_nonce.key().as_ref(), limit_order_nonce.order_nonce.to_be_bytes().as_ref()]。订单 PDA 因此对每个 (owner, nonce_index, order_nonce) 唯一。
LimitOrderNonce
每个 (wallet, nonce_index) 的计数器,让单个用户运行多个并行的限价单管道而不在 PDA 上碰撞。
[user_wallet.as_ref(), &[nonce_index]]。大多数客户端使用 nonce_index = 0 并让 order_nonce 承载基数。
Permission
一个能力账户,其存在就是授权:如果为给定权限派生了 Permission PDA,该权限可以调用 CreatePermissionedPool。它除了创建它的权限外不存储任何东西。
["permission", authority.as_ref()]。由管理员通过 CreatePermissionPda 创建,通过 ClosePermissionPda 拆除(租金退款给调用者)。两个管理员指令都接受程序 admin 或专用 permission_pda_admin 密钥。关闭 PDA 撤销授权——权限不能再创建额外的池,但它已创建的池不受影响。
派生关键账户
reference/program-addresses 进行双重检查。
生命周期快速参考
TickArrayState 账户永远不会被程序关闭——它们在池的整个生命周期内持续存在。一旦 tick 数组被初始化,即使其中的每个 tick 都返回到 liquidity_gross == 0,它也会保留在链上。重用现有 tick 数组是免费的;只有第一个接触从未初始化数组的头寸支付其租金。
在哪里阅读什么
- Tick 数学和范围机制:
products/clmm/ticks-and-positions。 - 交换遍历和费用增长数学:
products/clmm/math。 - 指令账户列表:
products/clmm/instructions。 - 费用和奖励应计:
products/clmm/fees。 - 规范程序 ID 和种子:
reference/program-addresses。

