本页内容由 AI 自动翻译,所有内容以英文版本为准。查看英文版 →
手续费档位
CLMM 池在创建时会绑定一个AmmConfig,该配置决定交易手续费率、协议份额和基金份额,以及价格刻度间距(参见 products/clmm/ticks-and-positions)。以下是常见的公开档位(以实时 GET https://api-v3.raydium.io/main/clmm-config 为准):
AmmConfig 索引 | trade_fee_rate | 刻度间距 | 典型用途 |
|---|---|---|---|
| 0 | 100(0.01%) | 1 | 稳定币对 |
| 1 | 500(0.05%) | 10 | 相关性强的蓝筹币对 |
| 2 | 2_500(0.25%) | 60 | 标准币对 |
| 3 | 10_000(1.00%) | 120 | 高波动或长尾币对 |
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提取。
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 中。
CollectFeeOn 值 | fee_on 字节 | 行为 |
|---|---|---|
FromInput(默认) | 0 | 经典 Uniswap-V3 模式——手续费始终从每个 swap 步骤的输入代币扣除,输入代币随 swap 方向交替。 |
Token0Only | 1 | 手续费始终以 token0 计价。对于 0→1 的 swap,手续费来自输入代币(与 FromInput 相同);对于 1→0 的 swap,手续费从 swap 输出(token0)扣除。 |
Token1Only | 2 | 与 Token0Only 对称——手续费始终以 token1 计价。 |
Token0Only 或 Token1Only——目的是让 LP 获得单一、可预期的计价货币。对于 MEMECOIN / USDC 这类以美元计价的 LP,选择 Token1Only(手续费始终结算为 USDC)后,LP 的盈亏不再受交易方向占比的影响。代价是:当手续费从 swap 输出扣除时,用户收到的是 out − fee 而非从输入端扣除的近似原值,因此报价逻辑须从输出端减去手续费。SDK 的 computeAmountOut 已根据 fee_on 处理这一分支;直接读取 pool.fee_on 的客户端代码应参照 PoolState 上的辅助函数:
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 步骤程序依次执行三个子步骤:-
衰减参考值。若
now - last_update_timestamp > filter_period,波动率参考值衰减: -
更新累加器。新的累加器等于参考值加上已遍历的绝对距离(以
tick_spacing为单位)乘以粒度缩放系数,并不超过配置的最大值: -
计算附加费率。附加费率关于累加器呈抛物线形(因为 swap 的”刻度距离”在标准公式中被平方),并通过
dynamic_fee_control进行增益缩放:
1e6 单位下 MAX_FEE_RATE_NUMERATOR = 100_000)为硬编码安全限制;在实践中,调优后的配置通常远低于此上限。
参数选择
以下是在试点池中有效的默认范围:| 参数 | 典型范围 | 说明 |
|---|---|---|
filter_period | 30 – 60 秒 | 在微波动期间保持参考值;值越小响应越灵敏 |
decay_period | 300 – 1800 秒 | 平静窗口结束后,手续费回归基准 |
reduction_factor | 4_000 – 8_000 | 基于 10_000,值越高则高位手续费越难回落 |
dynamic_fee_control | 1_000 – 50_000 | 基于 100_000,曲线增益系数 |
max_volatility_accumulator | 100_000 – 10_000_000 | 附加费率的饱和上限 |
dynamic_fee_control,使平均手续费符合目标(例如,1σ 行情日为基准的 1.5 倍,3σ 行情日为 5 倍)。
LP 所见
动态手续费收入与基础手续费流经相同的累加器——fee_growth_global_{0,1}_x64,不存在单独的”动态手续费增长”字段。高波动池中的 LP 在波动期间自然获得更高手续费,无需额外的领取或结算指令。
集成方须知
- 即使池子储备未发生变化,某笔报价在区块 N 与区块 N+1 之间也可能发生变化——每笔 swap 都会改变波动率累加器。Trade API 的报价在报价区块时有效;若在报价与执行之间有响应型池子被触发,实际费率可能偏差数个 bps。
volatility_accumulator和last_update_timestamp是链上公开数据,客户端可在离线模拟中直接复现该公式。
每个头寸的手续费记账
每个头寸在最后一次被触碰时存储:fee_growth_inside_0_last_x64和fee_growth_inside_1_last_x64——该快照时刻的区间特定手续费增长值。
IncreaseLiquidity、DecreaseLiquidity,以及任何更新刻度边界手续费增长的状态转换)时:
-
程序从全局手续费增长和两个端点刻度的
fee_growth_outside_*重新计算fee_growth_inside_{0,1}_x64。 -
Δ 按头寸流动性权重累加至
tokens_fees_owed_{0,1}: -
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_inside 与 fee_growth_inside 使用相同的公式——通过刻度外部累加器计算——因此当前价格区间外的头寸不会累积奖励。这与 Uniswap v3”激励归于活跃流动性”的设计理念一致,也将 LP 利益与现货价格覆盖范围对齐。
资金注入与结束奖励流
奖励流通过InitializeReward 创建,该指令会预先将总预算(emissions_per_second × (end_time − open_time))存入奖励金库。若资金方余额不足,程序会拒绝执行 InitializeReward。SetRewardParams 可延长 end_time 或提高发放速率;缩减上述任一参数的操作会被阻止,以防止对已承诺给 LP 的奖励进行撤销。
当 now > end_time 时,奖励流进入 Ended 状态,但其 reward_growth_global_x64 仍可被读取——LP 在发放结束很久后仍可通过 CollectReward 提取历史已赚取的奖励。
管理员收取手续费
| 签名方 | 指令 | 效果 |
|---|---|---|
amm_config.owner | CollectProtocolFee | 将 protocol_fees_token_{0,1} 转至指定接收方。 |
amm_config.fund_owner | CollectFundFee | 将 fund_fees_token_{0,1} 转至指定接收方。 |
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时会被拒绝。
algorithms/token-2022-transfer-fees 了解参考计算方法。
链下读取手续费与奖励
tokenFeesOwed* 和 rewardAmountOwed 是头寸最后一次被触碰时的快照。若要查看当前值(包含此后的增长),可以在模拟中以零流动性调用 IncreaseLiquidity,或直接使用全局 fee_growth_* 及两个刻度外部快照重新计算。
延伸阅读
products/clmm/math—fee_growth_inside的完整推导。products/clmm/instructions—CollectReward、InitializeReward、SetRewardParams的账户列表。algorithms/token-2022-transfer-fees— 含转账费 mint 的报价方法。reference/fee-comparison— CLMM/CPMM/AMM-v4 手续费矩阵对比。
raydium-io/raydium-clmm—states/pool.rs,libraries/fixed_point_64.rs- “Uniswap v3 Core” 白皮书,§7(手续费增长)

