跳转到主要内容
本页内容由 AI 自动翻译,所有内容以英文版本为准。查看英文版 →

手续费档位

CLMM 池在创建时会绑定一个 AmmConfig,该配置决定交易手续费率、协议份额和基金份额,以及价格刻度间距(参见 products/clmm/ticks-and-positions)。以下是常见的公开档位(以实时 GET https://api-v3.raydium.io/main/clmm-config 为准): 交易手续费率的单位是 1/FEE_RATE_DENOMINATOR = 1/1_000_000 的交易量。协议费率和基金费率使用相同的分母,但作用于交易手续费而非交易量——这与 CPMM 的约定一致。

每笔 swap 的手续费拆分

在 swap 的每个步骤中(参见 products/clmm/math):
  • step_lp 流入 fee_growth_global_{input_side}_x64,并按当前活跃流动性缩放:fee_growth_global += step_lp × 2^64 / pool.liquidity
  • step_protocol 计入 PoolState.protocol_fees_token_{input_side},通过 CollectProtocolFee 提取。
  • step_fund 计入 PoolState.fund_fees_token_{input_side},通过 CollectFundFee 提取。
与 CPMM 相同,协议费和基金费虽存放在金库中,但不计入曲线的流动性视图:swap 数学计算读取的是 pool.liquidity,该值不受待提取手续费的影响。

手续费按代币方向分配的原因

在 CPMM 中,swap 手续费始终从输入代币扣除,另一侧不会产生协议/基金应计费。CLMM 在每个步骤中遵循相同规则:手续费计入该步骤的输入代币。由于多个刻度的 swap 方向一致,所有步骤均从同一代币扣费——因此实际上,任意一笔 swap 的手续费只流向某一侧 若用户将 token0 换成 token1,则 fee_growth_global_0_x64 上升,fee_growth_global_1_x64 不变。本次 swap 中头寸以 token0 获得手续费。下一笔 swap 可能方向相反,转而增加 fee_growth_global_1_x64。随着时间推移,均衡的池子两侧都会积累手续费。

单侧手续费(CollectFeeOn

通过 CreateCustomizablePool 创建的池子可选择非默认的手续费收取模式。该模式在池子创建时固定,存储在 PoolState.fee_on 中。 为什么池子会选择 Token0OnlyToken1Only——目的是让 LP 获得单一、可预期的计价货币。对于 MEMECOIN / USDC 这类以美元计价的 LP,选择 Token1Only(手续费始终结算为 USDC)后,LP 的盈亏不再受交易方向占比的影响。代价是:当手续费从 swap 输出扣除时,用户收到的是 out − fee 而非从输入端扣除的近似原值,因此报价逻辑须从输出端减去手续费。SDK 的 computeAmountOut 已根据 fee_on 处理这一分支;直接读取 pool.fee_on 的客户端代码应参照 PoolState 上的辅助函数:
LP 层面的影响——手续费仍通过标准的 fee_growth_global_{0,1}_x64 累加器按 swap 步骤路由,因此头寸仍使用相同的 fee_growth_inside 公式结算手续费。不对称性仅体现在侧面应计方向,而非数学计算本身。 fee_on 在创建后不可修改。通过旧版 CreatePool 创建的池子永久为 FromInput 模式。

动态手续费

enable_dynamic_fee = true 创建的池子会在 AmmConfig.trade_fee_rate 基础上叠加一个基于波动率的附加费率。该机制是对 Trader Joe / Meteora 动态手续费设计的简化移植。

状态

PoolState.dynamic_fee_info 包含五个校准参数(池子创建时 DynamicFeeConfig 的快照)以及每次 swap 更新的四个状态字段。字节布局参见 products/clmm/accounts

每笔 swap 的更新

每个 swap 步骤程序依次执行三个子步骤:
  1. 衰减参考值。若 now - last_update_timestamp > filter_period,波动率参考值衰减:
  2. 更新累加器。新的累加器等于参考值加上已遍历的绝对距离(以 tick_spacing 为单位)乘以粒度缩放系数,并不超过配置的最大值:
  3. 计算附加费率。附加费率关于累加器呈抛物线形(因为 swap 的”刻度距离”在标准公式中被平方),并通过 dynamic_fee_control 进行增益缩放:
10% 上限(1e6 单位下 MAX_FEE_RATE_NUMERATOR = 100_000)为硬编码安全限制;在实践中,调优后的配置通常远低于此上限。

参数选择

以下是在试点池中有效的默认范围: 建议将历史 swap 数据代入公式进行离线回放,再调整 dynamic_fee_control,使平均手续费符合目标(例如,1σ 行情日为基准的 1.5 倍,3σ 行情日为 5 倍)。

LP 所见

动态手续费收入与基础手续费流经相同的累加器——fee_growth_global_{0,1}_x64,不存在单独的”动态手续费增长”字段。高波动池中的 LP 在波动期间自然获得更高手续费,无需额外的领取或结算指令。

集成方须知

  • 即使池子储备未发生变化,某笔报价在区块 N 与区块 N+1 之间也可能发生变化——每笔 swap 都会改变波动率累加器。Trade API 的报价在报价区块时有效;若在报价与执行之间有响应型池子被触发,实际费率可能偏差数个 bps。
  • volatility_accumulatorlast_update_timestamp 是链上公开数据,客户端可在离线模拟中直接复现该公式。

每个头寸的手续费记账

每个头寸在最后一次被触碰时存储:
  • fee_growth_inside_0_last_x64fee_growth_inside_1_last_x64——该快照时刻的区间特定手续费增长值。
此后每次被触碰(IncreaseLiquidityDecreaseLiquidity,以及任何更新刻度边界手续费增长的状态转换)时:
  1. 程序从全局手续费增长和两个端点刻度的 fee_growth_outside_* 重新计算 fee_growth_inside_{0,1}_x64
  2. Δ 按头寸流动性权重累加至 tokens_fees_owed_{0,1}
  3. fee_growth_inside_{0,1}_last_x64 更新。
代币实际转移仅发生在 DecreaseLiquidity 或专用的 CollectFees 路径(在 Raydium 当前的指令集中,手续费作为 DecreaseLiquidity 的一部分被提取)。在 DecreaseLiquidity 调用中将 liquidity = 0 是”仅收集手续费”的标准写法。

区间外头寸不获得手续费

若头寸区间不包含 tick_current,则为其计算的 fee_growth_inside 受到上界约束,价格在区间外时不会增长。该头寸停止累积手续费,直到价格重新回到其区间。这是设计特性而非缺陷——正是集中流动性将手续费收益与资本同步集中的方式。

奖励流

一个 CLMM 池最多可同时运行三个奖励流。每个奖励流是一个(奖励 mint、发放速率、开始时间、结束时间)的元组,存储在 PoolState.reward_infos[i] 中。

结算循环

每条涉及流动性的指令(以及作为独立指令的 UpdateRewardInfos)都会将所有活跃奖励流推进到 now
若某段时间内 pool.liquidity == 0,该区间内的奖励发放将被跳过(无区间内流动性可分配)。剩余预算留在奖励金库中。协议方可通过 SetRewardParams 追加资金或结束奖励流。

每个头寸的奖励应计

与手续费完全一致,额外增加每个奖励流的维度:
用户通过 CollectReward 领取奖励,该指令将 reward_amount_owed 从奖励流金库转至用户账户并将计数器清零。

只有区间内头寸获得奖励

reward_growth_insidefee_growth_inside 使用相同的公式——通过刻度外部累加器计算——因此当前价格区间外的头寸不会累积奖励。这与 Uniswap v3”激励归于活跃流动性”的设计理念一致,也将 LP 利益与现货价格覆盖范围对齐。

资金注入与结束奖励流

奖励流通过 InitializeReward 创建,该指令会预先将总预算(emissions_per_second × (end_time − open_time))存入奖励金库。若资金方余额不足,程序会拒绝执行 InitializeRewardSetRewardParams 可延长 end_time 或提高发放速率;缩减上述任一参数的操作会被阻止,以防止对已承诺给 LP 的奖励进行撤销。 now > end_time 时,奖励流进入 Ended 状态,但其 reward_growth_global_x64 仍可被读取——LP 在发放结束很久后仍可通过 CollectReward 提取历史已赚取的奖励。

管理员收取手续费

两者均不影响曲线——已应计金额本就在 pool.liquidity 之外。关于主网上这些签名方的持有者,参见 security/admin-and-multisig

Token-2022 交互

手续费和奖励均以池子或奖励流中某一代币计价。Token-2022 扩展的行为与 CPMM 中相同:
  • swap 输入 mint 的转账费。 池子收到 amount_in − mint_transfer_fee。CLMM 程序的步骤输入按净额计算,因此池子的手续费累加器反映的是实际入库代币。
  • swap 输出 mint 的转账费。 池子发出 amount_out,用户收到 amount_out − mint_transfer_fee。滑点检查应基于用户实际收到的金额。
  • 奖励 mint 的转账费。InitializeReward 时,奖励以”入库”单位计价(资金方支付 mint 转账费存入金库)。在 CollectReward 时还会再次产生 mint 转账费;LP 应预计转账费奖励代币会有一定折扣。
  • 不可转让 / 机密 / 群组成员 mint。CreatePool / InitializeReward 时会被拒绝。
对于多跳含转账费的 swap,综合影响可能相当显著。忽略此影响的报价器会高估输出;参见 algorithms/token-2022-transfer-fees 了解参考计算方法。

链下读取手续费与奖励

tokenFeesOwed*rewardAmountOwed 是头寸最后一次被触碰时的快照。若要查看当前值(包含此后的增长),可以在模拟中以零流动性调用 IncreaseLiquidity,或直接使用全局 fee_growth_* 及两个刻度外部快照重新计算。

延伸阅读

参考资料: