跳转到主要内容

Documentation Index

Fetch the complete documentation index at: https://docs.raydium.io/llms.txt

Use this file to discover all available pages before exploring further.

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

两笔独立手续费,四个目的地

CPMM 对每次交换征收两笔独立费率的手续费:
  1. 交易手续费 — 按 AmmConfig.trade_fee_rate 收取,拆分为三个目的地:
    • LP 份额 — 留在保管库中并增加 k。通过销毁 LP token 隐式领取。
    • 协议份额 — 累计到 PoolState.protocol_fees_token*;由 protocol_owner 通过 CollectProtocolFee 扫出。
    • 基金份额 — 累计到 PoolState.fund_fees_token*;由 fund_owner 通过 CollectFundFee 扫出。
  2. 创建者手续费(可选,按池配置)— 按 AmmConfig.creator_fee_rate 独立收取(不同于交易手续费),累计到 PoolState.creator_fees_token*,由 pool_state.pool_creator 通过 CollectCreatorFee 扫出。仅在使用 enable_creator_fee = true 创建池时启用。
创建者手续费不是交易手续费的一部分。两笔费率在交换输入端被扣除时会相加,但各自维护独立的桶 — 协议和基金份额总是仅从 trade_fee 推导,永远不从 creator_fee 推导。一个池配置为 creator_fee_rate = 1000(0.10%)和 trade_fee_rate = 2500(0.25%)时,在创建者手续费按输入计算的交换中,输入端的总费率为 0.35%,其中创建者保留 0.10%,交易手续费桶获得 0.25%。 交易手续费费率(trade_fee_rateprotocol_fee_ratefund_fee_rate)和 creator_fee_rate 都存储在 AmmConfig 上。按池的 enable_creator_fee 标志和 creator_fee_on 模式(创建者手续费取自交换哪一端)存储在 PoolState 上。见 products/cpmm/accounts

费率和单位

所有费率都是 u64 类型,以 1 / FEE_RATE_DENOMINATOR 为单位,其中 FEE_RATE_DENOMINATOR = 1_000_000
  • trade_fee_rate交换成交量的一部分。2500 ⇒ 相关端(输入或输出,取决于 creator_fee_on — 见下文”手续费取自交换的哪一端”)的 0.25%。
  • creator_fee_rate交换成交量的一部分,独立于交易手续费采集。1000 ⇒ 相关端的 0.10%。
  • protocol_fee_ratefund_fee_rate交易手续费的一部分,不是成交量的一部分。120_000 ⇒ 交易手续费的 12%。
主网上 AmmConfig[index=0](标准 0.25% 池)的默认参数供参考:
字段数值有效百分比
trade_fee_rate2500成交量的 0.25%(交易手续费桶)
protocol_fee_rate120000交易手续费的 12% ≈ 成交量的 0.030%
fund_fee_rate40000交易手续费的 4% ≈ 成交量的 0.010%
creator_fee_rate0(默认)0%(独立桶)
→ LP 份额有效成交量的 0.210%
因此,对于 AmmConfig[0]enable_creator_fee = false 的 $1,000 交换:总交易手续费 $2.50,其中 $2.10 留给 LP,$0.30 给协议,$0.10 给基金。创建者桶为 0,因为创建者手续费被禁用。 如果同一池配置为 enable_creator_fee = truecreator_fee_rate = 1000(0.10%),用户需额外支付 $1.00 到创建者桶 — 从由 creator_fee_on 配置的同一端采集 — 总手续费为 $3.50。交易手续费桶及其协议/基金拆分保持不变。 根据 GET https://api-v3.raydium.io/main/cpmm-config 确认当前主网数值 — 费率由管理员可变,应该是动态读取而不是硬编码的。

代码中的拆分

// 摘自 raydium-cp-swap/programs/cp-swap/src/curve/{calculator,fees}.rs。
// 实际代码会根据 `is_creator_fee_on_input` 分支;两个分支都维持
// creator_fee 是独立费率、永远不是 trade_fee 一部分的不变量。

const FEE_RATE_DENOMINATOR_VALUE: u64 = 1_000_000;

pub struct FeeBreakdown {
    pub amount_in_after_fees: u64,    // 减去在输入端采集的所有手续费后的输入
    pub amount_out_after_fees: u64,   // 减去在输出端采集的任何创建者手续费后的输出
    pub trade_fee:    u64,            // → 拆分为 LP / protocol / fund 桶(见下)
    pub protocol_fee: u64,            // 交易手续费的份额
    pub fund_fee:     u64,            // 交易手续费的份额
    pub creator_fee:  u64,            // 独立桶(输入或输出端)
}

fn take_fees_on_input(
    amount_in: u64,
    trade_rate: u64,
    creator_rate: u64,
    protocol_rate: u64,
    fund_rate: u64,
) -> (u64 /* trade_fee */, u64 /* creator_fee */, u64 /* amount_in_after_fees */) {
    // 两笔费率相加,这样我们只做一次舍入于组合手续费,然后
    // 按比例拆分 — 这纯粹是舍入/效率技巧。拆分遵守费率精确值:
    // creator_fee/trade_fee == creator_rate/trade_rate。
    let total_fee = ((amount_in as u128) * ((trade_rate + creator_rate) as u128))
        .div_ceil(FEE_RATE_DENOMINATOR_VALUE as u128) as u64;

    let creator_fee = ((total_fee as u128) * (creator_rate as u128)
                       / ((trade_rate + creator_rate) as u128)) as u64;
    let trade_fee   = total_fee - creator_fee;

    (trade_fee, creator_fee, amount_in - total_fee)
}

fn take_creator_fee_on_output(amount_out_swapped: u64, creator_rate: u64) -> (u64, u64) {
    // 当 creator_fee_on 将创建者手续费路由到输出端时,
    // trade_fee 作为单个费率在输入端采集,creator_fee
    // 按曲线输出计算。
    let creator_fee = ((amount_out_swapped as u128) * (creator_rate as u128))
        .div_ceil(FEE_RATE_DENOMINATOR_VALUE as u128) as u64;
    (amount_out_swapped - creator_fee, creator_fee)
}

fn split_trade_fee(
    trade_fee: u64,
    protocol_rate: u64,
    fund_rate: u64,
) -> (u64 /* protocol_fee */, u64 /* fund_fee */) {
    let protocol_fee = (trade_fee as u128 * protocol_rate as u128
                        / FEE_RATE_DENOMINATOR_VALUE as u128) as u64;
    let fund_fee     = (trade_fee as u128 * fund_rate as u128
                        / FEE_RATE_DENOMINATOR_VALUE as u128) as u64;
    (protocol_fee, fund_fee)
}
说明:
  • 输入端的总手续费向上舍入,所以池永远不会欠费。
  • trade_fee 的子拆分(protocol、fund)向下舍入,所以它们的和永远不超过 trade_fee;剩余部分是 LP 份额。
  • lp_share = trade_fee − protocol_fee − fund_fee(creator_fee 在此处扣除,因为它是独立桶)。
  • 创建者手续费从输入或输出端采集,取决于 PoolState.creator_fee_on(见下一节)。无论哪一端,费率都不变。

手续费从交换的哪一端采集

CPMM 有一个按池的 creator_fee_on 设置(BothToken / OnlyToken0 / OnlyToken1),决定创建者手续费是从给定交换的输入端还是输出端采集。运行时帮助函数 is_creator_fee_on_input(direction) 对每次交换将其化简为布尔值:
creator_fee_on交换 0 → 1交换 1 → 0
BothToken (0)输入端输入端
OnlyToken0 (1)输入端输出端
OnlyToken1 (2)输出端输入端
当创建者手续费在输入端时,交易手续费和创建者手续费都在曲线运行之前从 amount_in 扣除。报价数学:从输入端扣除组合 trade_rate + creator_rate 当创建者手续费在输出端时,只有交易手续费从 amount_in 扣除;曲线生成未扣费的输出,然后创建者手续费从该输出扣除。报价数学:从输入端扣除 trade_rate;从输出端扣除 creator_rate 交易手续费本身总是在输入端采集(标准 Uniswap-V2 模式)。只有创建者手续费可以在输出端。

“累计”手续费如何与曲线互动

一个重要的微妙之处:协议、基金和创建者手续费在被称为相应 Collect* 指令之前,物理上留在保管库中。但它们从曲线对保管库余额的看法中被排除。 一次交换后的具体图景:
raw_vault_0_balance = getTokenAccountBalance(vault_0).amount
                    = lp_entitled + protocol_fees_token0
                      + fund_fees_token0 + creator_fees_token0

curve_x = raw_vault_0_balance − (protocol_fees_token0
                                  + fund_fees_token0
                                  + creator_fees_token0)
程序在强制 k' ≥ k 时使用 curve_x(以及类似的 curve_y)。这就是非 LP 手续费到达其目的地而不膨胀池中 LP 份额的方式。 你应该在设计时考虑的后果:
  • 从原始余额报价是错误的。 如果你从 getTokenAccountBalance 构建报价程序,你会一致地高估池将遵守的价格。始终减去累计手续费,或通过 SwapBaseInput / API 模拟。
  • CollectProtocolFee 不移动价格。 它将 token 移出保管库清零 protocol_fees_token* 计数器,所以 curve_xcurve_y 保持不变。
  • LP 手续费不累计到计数器。 它们隐含在保管库余额中。LP 对累计 LP 手续费的权利通过销毁 LP token(即通过 Withdraw)行使 — 没有 CollectLpFee

与 Token-2022 转账手续费的交互

Token-2022 转账手续费由造币厂应用,不是由 CPMM。它们作用于每次 token 转账 — 交换、存款、提取以及 Collect* 扫出。CPMM 的交易手续费数学是针对实际到达保管库的金额计算的,即净扣除输入造币厂的转账手续费(如有)。 因此,在最坏情况下,用户在输入精确的交换中支付三笔不同的税:
  1. 输入造币厂在 amount_in 上的转账手续费(给造币厂的手续费权限账户)。
  2. 池的 trade_fee 在剩余部分上(按上述拆分)。
  3. 输出造币厂在 amount_out 上的转账手续费(给造币厂的手续费权限账户)。
SDK 的报价程序考虑所有三笔,所以 minimum_amount_out 以用户实际接收的额度计。如果你在写自己的报价程序,镜像该行为,否则你的滑点检查会系统性地过于宽松。 algorithms/token-2022-transfer-fees 了解详细推导。

创建者手续费

创建者手续费是可选的且按池配置。费率存储在 AmmConfig.creator_fee_rate 上;启用标志creator_fee_on)存储在 PoolState 上:
  • 在池创建时启用。 Initialize 默认设置 enable_creator_fee = false;通过 InitializeWithPermission 创建的池(由 LaunchLab 毕业和其他门控路径使用)可以传递 enable_creator_fee = true 并选择 creator_fee_on
  • 费率与费率层共享。 费率本身是 AmmConfig.creator_fee_rate,跨绑定到该配置的每个池都是同一个值。每个池随后决定是否收取它(enable_creator_fee)以及从交换的哪一端收取它(creator_fee_on)。当 enable_creator_fee = false 时,不管配置值如何,池的有效创建者手续费率为零(见源中的 PoolState::adjust_creator_fee_rate)。
  • 独立于交易手续费。 创建者手续费永远不会减少 LP / 协议 / 基金份额 — 它是独立费率,单独应用,在自己的计数器中累计。
  • 通过 CollectCreatorFee 扫出,由 PoolState.pool_creator 签名。
  • 创建后不能重新启用或重新路由。 使用 enable_creator_fee = false 初始化的池永远不会收取创建者手续费;使用特定 creator_fee_on 初始化的池无法切换端。
创建者手续费是 Raydium 的”销毁和赚取”模式的机制:LP token 被锁定在 LP Lock 程序下,所以创建者无法提取流动性,但可以无限期地领取 CollectCreatorFee

收集操作流程

签名者指令清零的源计数器典型节奏
amm_config.protocol_ownerCollectProtocolFeeprotocol_fees_token{0,1}每周或按程序
amm_config.fund_ownerCollectFundFeefund_fees_token{0,1}每周或按程序
pool_state.pool_creatorCollectCreatorFeecreator_fees_token{0,1}任何时间
协议和基金所有者是主网上的 Raydium 多签账户;见 security/admin-and-multisig。创建者签名者是运行 Initialize 的账户。

更改费率层

费率可以由管理员通过 UpdateAmmConfig 更改(见 products/cpmm/instructions)。更改对下次交换对绑定到该 AmmConfig 的每个池生效 — 没有迁移,因为池在每次交换时加载配置。 管理员不能做的:
  • 将池从一个 AmmConfig 移到另一个。
  • 追溯重新定价已累计的手续费。
  • 在没有 protocol_owner / fund_owner 签名者的情况下收取手续费。

从运行的池读取手续费

// 离链:每个桶中当前累计的手续费
const pool = await connection.getAccountInfo(poolStatePda);
const decoded = PoolState.decode(pool.data);
console.log(
  "Protocol accrued:",
  decoded.protocolFeesToken0.toString(),
  decoded.protocolFeesToken1.toString(),
);
console.log(
  "Fund accrued:",
  decoded.fundFeesToken0.toString(),
  decoded.fundFeesToken1.toString(),
);
console.log(
  "Creator accrued:",
  decoded.creatorFeesToken0.toString(),
  decoded.creatorFeesToken1.toString(),
);

// 离链:今天的有效费率。
// trade_fee_rate、creator_fee_rate 是成交量的分数(分母 1e6)。
// protocol_fee_rate、fund_fee_rate 是*交易手续费*的分数(相同分母)。
const config = await fetch("https://api-v3.raydium.io/main/cpmm-config")
  .then((r) => r.json());
const tier = config.data.find((t) => t.index === decoded.ammConfigIndex);

const tradeFeeOfVolume   = tier.tradeFeeRate / 1_000_000;
const protocolOfTradeFee = tier.protocolFeeRate / 1_000_000;
const fundOfTradeFee     = tier.fundFeeRate / 1_000_000;
const lpOfTradeFee       = 1 - protocolOfTradeFee - fundOfTradeFee;

console.log("LP fee effective:",       (tradeFeeOfVolume * lpOfTradeFee * 100).toFixed(4), "%");
console.log("Protocol fee effective:", (tradeFeeOfVolume * protocolOfTradeFee * 100).toFixed(4), "%");
console.log("Fund fee effective:",     (tradeFeeOfVolume * fundOfTradeFee * 100).toFixed(4), "%");
console.log(
  "Creator fee effective:",
  decoded.enableCreatorFee
    ? ((tier.creatorFeeRate / 1_000_000) * 100).toFixed(4) + " %"
    : "0 % (disabled on this pool)",
);

与 CLMM 和 AMM v4 的比较

reference/fee-comparison 了解并排矩阵。总结:
  • AMM v4 使用固定 0.25% 交易手续费,有不同的 LP/协议拆分,没有基金手续费。
  • CLMM 手续费按 tick-spacing 层级,按头寸累计(不是按池),通过 DecreaseLiquidityCollectFees 领取。

后续步骤

来源: