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.
This page is operational: it gives the formulas, fixed-point conventions, and step-through used by the CLMM program. For the reasoning behind the concentrated-liquidity curve itself — why
L = sqrt(x · y) matters — see algorithms/clmm-math. This page assumes you have read that.Sqrt-price representation
CLMM stores price assqrt_price_x64 — the square root of the token1-per-token0 price, as a Q64.64 fixed-point number:
where p = token1_amount / token0_amount. Working in sqrt instead of p linearizes the swap math (token-amount deltas become linear in Δsqrt_price), and the x64 fixed-point keeps precision through many-tick swaps.
Tick ↔ sqrt-price conversion is precomputed via a bit-by-bit log-approximation:
implemented as a lookup-based exponentiation in tick_math::get_sqrt_price_at_tick.
Liquidity as a canonical unit
Inside a range[sqrt_a, sqrt_b] (with sqrt_a < sqrt_b) a position of liquidity L maps to token amounts as follows. Let sqrt_c = sqrt_price_x64 be the pool’s current price.
| Case | amount0 | amount1 |
|---|---|---|
sqrt_c <= sqrt_a (pool price below range) | L · (sqrt_b - sqrt_a) / (sqrt_a · sqrt_b) | 0 |
sqrt_a < sqrt_c < sqrt_b (in range) | L · (sqrt_b - sqrt_c) / (sqrt_c · sqrt_b) | L · (sqrt_c - sqrt_a) |
sqrt_c >= sqrt_b (pool price above range) | 0 | L · (sqrt_b - sqrt_a) |
x = L / sqrt_p, y = L · sqrt_p that concentrated liquidity satisfies within a range.
Integrators typically want the inverse: given a deposit of amount0 / amount1, compute the maximum L that fits in the range. The SDK’s LiquidityMath.getLiquidityFromTokenAmounts does this. The formula for the in-range case:
Whichever side binds determines the ratio actually consumed; the other side may have leftover.
Single-tick swap step
A swap proceeds in steps. Each step either (a) consumes all available input within the current tick range without crossing a tick, or (b) moves the price exactly to the next initialized tick. Given current state(sqrt_c, L) and a swap up (token0 in, token1 out, sqrt_price increases), the distance to the next initialized tick is sqrt_t. Inside this micro-interval the relationship between input and price is:
and
The program does one of two things:
-
Does the entire input fit? If the input remaining (after fee) is less than
Δamount0to reachsqrt_t, solve for the newsqrt_c'exactly: (for an exact-inputtoken0 → token1swap). The swap completes in this step without crossing a tick. -
Input exceeds
Δamount0? Setsqrt_c' = sqrt_t, cross the tick (applyliquidity_net), decrement remaining input byΔamount0, increment output byΔamount1, and repeat.
token1 → token0, price going down), the formulas have sqrt_c and sqrt_t swapped and the inversion in the other slot.
The full Rust implementation lives in raydium-clmm/programs/amm/src/libraries/swap_math.rs. The logic there matches Uniswap v3’s SwapMath.computeSwapStep one-for-one.
Fees on each step
Trade fees are taken off the input amount in each step, same convention as CPMM:L_i that stayed in range across this swap will later read back L_i · Δfee_growth_global / 2^{64} owed tokens.
The protocol and fund portions accrue to PoolState.protocol_fees_token_{0,1} and PoolState.fund_fees_token_{0,1} respectively, identical to CPMM. They are swept by CollectProtocolFee / CollectFundFee.
Fee growth outside and inside
The tricky part of CLMM fee accounting: a position earns fees only while the pool’s price is inside its range. The pool tracks cumulative fees globally; the position needs to know the cumulative fees while inside its specific range. The solution is a tick-based accumulator. Each tick stores:- If the pool’s price is above this tick (
tick_current >= this_tick),fee_growth_outside = fee_growth_global. (Everything earned so far is “outside” — i.e., below — this tick, relative to the current price.) - Else
fee_growth_outside = 0.
fee_growth_outside:
The invariant this preserves: for any tick t, fee_growth_outside(t) equals the fees that accrued while tick_current was on the opposite side of t.
Fee growth inside a range [tick_lower, tick_upper] is then derived:
What a position stores and what it reads
APersonalPositionState stores fee_growth_inside_0_last_x64 and fee_growth_inside_1_last_x64: the fee_growth_inside values at the last time the position was touched.
At any subsequent touch (increase, decrease, collect), the program:
- Computes the current
fee_growth_inside_{0,1}_x64using the formula above. - Computes
Δ = fee_growth_inside_now − fee_growth_inside_last(modular-subtraction on u128). - Adds
Δ × position.liquidity / 2^{64}totokens_fees_owed_{0,1}. - Updates
fee_growth_inside_lastto the new value.
CollectFees / DecreaseLiquidity, against tokens_fees_owed.
Rewards
Each of the pool’s up to 3 reward streams uses the same growth-inside machinery, in its ownreward_growth_global_x64 accumulator. At emission time:
— emissions scale inversely with active liquidity, so a denser pool pays each position proportionally less per second, but over more positions total. The per-position reward owed is
and is claimed via CollectReward. See products/clmm/fees.
Worked example: exact-input swap
Suppose:tick_spacing = 60sqrt_price_x64 = 1 × 2^{64}— price = 1.0, sotick_current = 0.- Active liquidity
L = 1_000_000 × 2^{64}. - Next initialized tick above:
t = 60(sqrt_price_b ≈1.003004 × 2^{64}). - Trade fee rate: 500 (0.05%).
SwapBaseInput exact-input 1,000 token0.
Step 1 — fees:
999 < 2995.5, so the entire input fits without crossing the tick.
Step 3 — new price:
sqrt_c' slightly below sqrt_c. Note that the formula above is for a token1 → token0 swap. The example here is token0 → token1, which drives the price up, not down — so we use the corresponding form for token0 in:
token0 → token1: sqrt_c rises along with the price.)
Step 4 — amount out:
trade_fee_rate × protocol_fee_rate / 1e6 (and similar for fund); the LP portion flows into fee_growth_global_0_x64.
Limit-order matching during swap
When a swap step crosses a tick that holds open limit orders, those orders consume swap input before the LP curve does, at the tick’s exact price. The matching is FIFO within the tick byorder_phase cohort.
Per-cohort state on TickState
orders_amount and inherit the next order_phase; they cannot fill until the previous cohort is fully consumed.
Matching step
Pseudo-code for the matching that happens at each tick crossing during a swap:SettleLimitOrder (or DecreaseLimitOrder). The pool simply tracks how much of the cohort is now filled via unfilled_ratio_x64. Each LimitOrderState stores its own (order_phase, unfilled_ratio_x64) snapshot at open time, so settlement reduces to:
Interaction with the LP curve
In a swap step, limit-order matching happens at the tick (zeroΔsqrt_price); LP curve consumption happens between ticks. The order is therefore:
- Cross tick
t_cross(apply LPliquidity_netchange first, since this is how Uniswap-V3 does it). - Fill any limit orders sitting at
t_cross. - Continue along the LP curve to the next initialized tick or to
swap_inputexhaustion.
Dynamic fee derivation
PoolState.dynamic_fee_info carries the volatility state. Each swap step computes the per-step fee rate as:
where:
- —
DYNAMIC_FEE_CONTROL_DENOMINATOR - —
VOLATILITY_ACCUMULATOR_SCALE vol_accis the per-swap accumulator after the update rule belowtick_spacingis fromPoolState.tick_spacing
Accumulator update
Two rules are applied each swap, in order: Decay. The reference floor decays based on time since last update: Accumulate. The new accumulator is the reference plus tick-distance traversed since the previous reference index:tick_spacing_index_reference () is in tick-spacing-units, not raw ticks: .
Why parabolic in tick distance
Squaring the accumulator means the fee rises as the square of how far price has walked away from its reference point. Empirically this matches the variance scaling of price under random-walk pressure: a 2× tick excursion implies 4× the implied volatility, so charges 4× the surcharge. Thedynamic_fee_control parameter calibrates the absolute level.
The filter_period window prevents tiny sub-second oscillations (e.g., MEV bots sandwiching) from inflating the accumulator. The decay_period window prevents a single past spike from charging fees indefinitely after the market has calmed.
Numerical robustness
- All intermediate products go through
u128oru256-shaped arithmetic. CLMM usesU128Sqrthelpers andFullMath::mulDivpatterns directly ported from Uniswap v3. - Division rounding is chosen per-step to enforce the invariant
k' ≥ klocally.SwapBaseInputrounds output down;SwapBaseOutputrounds input up. - Tick crossings that drop
PoolState.liquidityto zero are allowed (the price can traverse a “liquidity hole”) but the swap simply advances to the next initialized tick without consuming input, charging no fee. - Overflow guard:
sqrt_price_x64is kept in the inclusive range[MIN_SQRT_PRICE_X64, MAX_SQRT_PRICE_X64]corresponding to[MIN_TICK, MAX_TICK]. A swap that would push past either bound reverts withSqrtPriceLimitOverflow.
Where to go next
products/clmm/ticks-and-positionsfor how the tick map participates in the walk.products/clmm/feesfor the fee/reward side of the math in detail.algorithms/clmm-mathfor the derivations behindL = sqrt(x · y)and the range-vs-liquidity formulas.
raydium-io/raydium-clmm—libraries/swap_math.rs,libraries/tick_math.rs- “Uniswap v3 Core” whitepaper, §6–7


