Skip to main content

Two independent fees, four destinations

CPMM levies two separately-rated fees on every swap:
  1. Trade fee — charged at AmmConfig.trade_fee_rate and split between three destinations:
    • LP share — stays in the vault and grows k. Claimed implicitly by burning LP tokens.
    • Protocol share — accrued to PoolState.protocol_fees_token*; swept by the protocol_owner via CollectProtocolFee.
    • Fund share — accrued to PoolState.fund_fees_token*; swept by the fund_owner via CollectFundFee.
  2. Creator fee (optional, per-pool) — charged at AmmConfig.creator_fee_rate independently of the trade fee and accrued to PoolState.creator_fees_token*. The creator can sweep it through CollectCreatorFee, or any payer can trigger the destination-constrained CollectCreatorFeePermissionless path. Active only when the pool was created with enable_creator_fee = true. Since the 2026-09-19 upgrade the accrued creator fee is split once more at collection time: a configurable share is moved into the pool’s protocol bucket and only the remainder reaches the creator — see Protocol share of the creator fee.
The creator fee is not a slice of the trade fee. The two rates are added together when the fee is taken on the swap input, but each remains its own bucket — the protocol and fund shares taken on a swap are always derived from trade_fee only, never from creator_fee. A pool with creator_fee_rate = 1000 (0.10%) and trade_fee_rate = 2500 (0.25%) charges a combined 0.35% of the input on a creator-fee-on-input swap, of which the creator bucket gets the 0.10% and the trade-fee bucket gets the 0.25%. The protocol’s share of the creator fee runs the other way round and is easy to confuse with the above: it is carved out of the creator bucket, not out of the trade fee, and not on the swap — it is applied when CollectCreatorFee or CollectCreatorFeePermissionless settles the accrued balance. Swap math is unchanged by it. The trade-fee rates (trade_fee_rate, protocol_fee_rate, fund_fee_rate), the creator_fee_rate and the default creator_fee_share_rate all live on AmmConfig. The per-pool enable_creator_fee flag and the creator_fee_on mode (which side of the trade the creator fee is taken from) live on PoolState. A per-creator override of the share rate lives on its own CreatorFeeShare PDA. See products/cpmm/accounts.

Rates and units

All rates are u64s denominated in units of 1 / FEE_RATE_DENOMINATOR where FEE_RATE_DENOMINATOR = 1_000_000.
  • trade_fee_rate is a fraction of swap volume. 2500 ⇒ 0.25% of the relevant side (input or output, depending on creator_fee_on — see “Which side of the trade the fees are taken from” below).
  • creator_fee_rate is a fraction of swap volume, taken separately from the trade fee. 1000 ⇒ 0.10% of the relevant side.
  • protocol_fee_rate and fund_fee_rate are fractions of the trade fee, not of volume. 120_000 ⇒ 12% of the trade fee.
  • creator_fee_share_rate is a fraction of the accrued creator fee, not of volume and not of the trade fee. 200_000 ⇒ 20% of whatever sits in creator_fees_token* at the moment it is collected. 0 (the default) leaves the whole creator fee with the creator.
Default parameters for AmmConfig[index=0] (the “standard” 0.25% pool) on mainnet, for reference: So on a $1,000 swap against AmmConfig[0] with enable_creator_fee = false: $2.50 total trade fee, of which $2.10 stays with LPs, $0.30 goes to protocol, $0.10 to the fund. The creator bucket is 0 because creator fee is disabled. If the same pool had enable_creator_fee = true and creator_fee_rate = 1000 (0.10%), the user pays an additional $1.00 to the creator bucket — taken on the same side of the trade configured by creator_fee_on — for $3.50 of total fees. The trade-fee bucket and its protocol/fund splits are unchanged. Confirm the current mainnet values against GET https://api-v3.raydium.io/main/cpmm-config — rates are admin-mutable and should be read fresh rather than hardcoded.

The split, in code

Notes:
  • The total fee on input rounds up so the pool never undercharges.
  • The sub-splits of trade_fee (protocol, fund) round down so their sum never exceeds trade_fee; the remainder is the LP share.
  • lp_share = trade_fee − protocol_fee − fund_fee (creator_fee is not subtracted here because it is its own bucket).
  • The creator fee is taken from input or output depending on PoolState.creator_fee_on (see next section). The rate is unchanged either way.

Which side of the trade the fees are taken from

CPMM has a per-pool creator_fee_on setting (BothToken / OnlyToken0 / OnlyToken1) that determines whether the creator fee is taken from the input side or the output side of a given swap. The runtime helper is_creator_fee_on_input(direction) collapses that to a boolean per swap: When the creator fee is on the input side, both the trade fee and the creator fee are deducted from amount_in before the curve runs. Quote math: take the combined trade_rate + creator_rate off the input. When the creator fee is on the output side, only the trade fee is deducted from amount_in; the curve produces an unfee’d output, then the creator fee is deducted from that output. Quote math: take trade_rate off the input; take creator_rate off the output. Trade fee itself is always taken on the input side (the standard Uniswap-V2 pattern). Only the creator fee can land on output.

How “accrued” fees interact with the curve

An important subtlety: the protocol, fund, and creator fees stay physically in the vault until their respective Collect* instruction is called. But they are excluded from the curve’s view of the vault balance. A concrete picture after one swap:
The program uses curve_x (and the analogous curve_y) when enforcing k' ≥ k. This is how the non-LP fees reach their destinations without inflating the LP share of the pool. Consequences you should design around:
  • Quoting off raw balances is wrong. If you build a quoter off getTokenAccountBalance, you will consistently overstate the price the pool will honor. Always subtract accrued fees, or simulate via SwapBaseInput / the API.
  • CollectProtocolFee does not move the price. It moves tokens out of the vault and zeros the protocol_fees_token* counters, so curve_x and curve_y are unchanged.
  • LP fees do not accrue to a counter. They are implicit in the vault balance. LP’s entitlement to accumulated LP fees is exercised by burning LP tokens (i.e., via Withdraw) — there is no CollectLpFee.

Interaction with Token-2022 transfer fees

Token-2022 transfer fees are applied by the mint, not by CPMM. They act on every token transfer — swap, deposit, withdrawal, and the Collect* sweeps. CPMM’s trade-fee math is computed against the amount that actually landed in the vault, i.e., net of the input mint’s transfer fee (if any). So in the worst case a user pays three distinct taxes on an input-exact swap:
  1. The input mint’s transfer fee on amount_in (to the mint’s fee authority).
  2. The pool’s trade_fee on the remainder (split per above).
  3. The output mint’s transfer fee on amount_out (to the mint’s fee authority).
The SDK’s quoter accounts for all three so minimum_amount_out is denominated in what the user actually receives. If you are writing your own quoter, mirror that behavior, or your slippage checks will be systematically too generous. See algorithms/token-2022-transfer-fees for the detailed derivation.

Creator fee

The creator fee is optional and per-pool. The rate lives on AmmConfig.creator_fee_rate; the enable flag and the side (creator_fee_on) live on PoolState:
  • Enabled at pool creation. Initialize sets enable_creator_fee = false by default; pools created via InitializeWithPermission (used by LaunchLab graduations and other gated paths) can pass enable_creator_fee = true and choose creator_fee_on.
  • Rate is shared with the fee tier. The rate itself is AmmConfig.creator_fee_rate, the same value across every pool bound to that config. Each pool then decides whether to charge it (enable_creator_fee) and which side of the swap to charge it on (creator_fee_on). When enable_creator_fee = false, the pool’s effective creator-fee rate is zero regardless of the config value (see PoolState::adjust_creator_fee_rate in the source).
  • Independent of trade fee. The creator fee never reduces the LP / protocol / fund shares — it is its own rate, applied separately, accrued in its own counters.
  • Swept via CollectCreatorFee or CollectCreatorFeePermissionless. The original path requires PoolState.pool_creator to sign. The permissionless path lets any payer trigger collection but fixes both destinations to the creator’s canonical ATAs. Both paths settle the protocol’s share first — see the next section.
  • Cannot be re-enabled or re-routed after creation. A pool initialized with enable_creator_fee = false will never charge a creator fee; one initialized with a particular creator_fee_on cannot switch sides.
Creator fees are the mechanism behind Raydium’s “Burn & Earn” pattern: LP tokens are locked under the LP Lock program so the creator cannot withdraw liquidity, but the accrued creator fees can still be collected indefinitely.

Protocol share of the creator fee

Since the 2026-09-19 upgrade the protocol can retain a configurable share of the creator fee. Nothing about the swap changes: the creator fee is still charged at creator_fee_rate and still accrues in full to creator_fees_token{0,1}. The split happens once, at collection time, inside CollectCreatorFee and CollectCreatorFeePermissionless.

Where the rate comes from

Two sources, in priority order:
  1. CreatorFeeShare PDA — seeds ["creator_fee_share", creator, amm_config]. When this account exists and is owned by CPMM, its share_rate wins. It lets the protocol negotiate a per-creator rate on a given fee tier without touching the tier itself.
  2. AmmConfig.creator_fee_share_rate — the default for every creator on that fee tier. Used whenever the PDA does not exist.
Both are u64s over the same FEE_RATE_DENOMINATOR = 1_000_000, and both are capped at the denominator. The collection instructions always take the creator_fee_share account, even when it has never been created — the program checks whether it is empty and falls back to the config. Passing the wrong address fails the PDA constraint, not the fallback.

What the split does

Applied independently to creator_fees_token_0 and creator_fees_token_1, then:
  • creator_amount_{0,1} is transferred out of the vaults to the creator’s token accounts.
  • shared_amount_{0,1} is added to protocol_fees_token_{0,1} and stays in the vault until the protocol owner sweeps it with CollectProtocolFee. There is no separate instruction and no separate counter for it.
  • creator_fees_token_{0,1} are zeroed, exactly as before.
Three properties worth relying on:
  • Rounding favours the creator. The protocol share floors, so dust stays with the creator — the same direction as protocol_fee and fund_fee, which also carve a share out of an already-accrued fee.
  • Value is conserved. creator_amount + shared_amount == creator_fee for every rate and every fee, including u64::MAX.
  • share_rate = 0 is a no-op. Both the default config value and the absence of a CreatorFeeShare PDA leave the whole creator fee with the creator, which is the pre-upgrade behaviour.

What it means for integrators

  • LPs and quoting are unaffected. The shared amount moves between two counters that are both already excluded from the curve’s view of the vault (vault_amount_without_fee), so curve_x and curve_y do not move across a collection. k is untouched.
  • A creator-fee estimator that reads creator_fees_token* now overstates the payout. Multiply by (1 − share_rate / 1_000_000) using the rate that actually applies to that (creator, amm_config) pair, not the config default.
  • protocol_fees_token* grows outside of swaps. A monitor that reconciles protocol accrual against swap volume will see jumps at each creator-fee collection. Protocol accrual is no longer trade_fee × protocol_fee_rate alone.
  • The rate can change between accrual and collection. It is read at collection time, so fees that accrued under one rate settle at whatever rate is in force when someone calls Collect*.
The CreatorFeeShare account is created and closed by the admin or a dedicated creator-fee-share authority through CreateCreatorFeeShare / CloseCreatorFeeShare; closing it returns the pair to AmmConfig.creator_fee_share_rate. Account layout in products/cpmm/accounts, addresses in reference/program-addresses.

Collection operational flow

Protocol and fund owners are the Raydium multisig on mainnet; see security/admin-and-multisig. On the original creator-only path, the creator signer is the account recorded in PoolState. On the permissionless path, the caller pays to create either missing creator ATA. The program constrains creator to pool_state.pool_creator and derives each destination from that creator plus the corresponding vault mint and token program, so the caller cannot redirect funds.

Changing a fee tier

Fee rates can be changed by the admin via UpdateAmmConfig (see products/cpmm/instructions). Changes take effect on the next swap for every pool bound to that AmmConfig — there is no migration, because pools load the config each swap. What the admin cannot do:
  • Move a pool from one AmmConfig to another.
  • Retroactively reprice already-accrued fees.
  • Collect fees without the protocol_owner / fund_owner signer.

Reading fees from a running pool

Resolve the share rate on-chain, not from a cached config. creator_fee_share_rate is a newly added AmmConfig field, so read it off the account rather than assuming the REST config payload carries it, and check whether a CreatorFeeShare PDA exists at ["creator_fee_share", creator, ammConfig] before quoting a creator their payout. An absent PDA is the common case and means the config default applies.

Comparison with CLMM and AMM v4

See reference/fee-comparison for a side-by-side matrix. Summary:
  • AMM v4 uses a fixed 0.25% trade fee with a different LP/protocol split and no fund fee.
  • CLMM fees are per-tick-spacing tier, accrued per-position (not per-pool), and claimed via DecreaseLiquidity or CollectFees.

Where to go next

Sources: