Skip to main content

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.

The invariant

The pool maintains coin_reserve × pc_reserve = k, where:
coin_reserve = coin_vault_balance
             + orders_posted_on_openbook.base
             + pending_coin_fill_not_yet_settled
pc_reserve   = pc_vault_balance
             + orders_posted_on_openbook.quote
             + pending_pc_fill_not_yet_settled
             - accrued_pnl_pc
Two things to notice:
  1. Reserves include committed-on-OpenBook amounts. The AMM’s limit orders remain part of its liquidity — they are not “lost” to the order book, just escrowed there. Computing k off only the on-chain vault balances underestimates reserves.
  2. The PnL accrual (need_take_pnl_*) is subtracted so the curve is conserved when the admin sweeps fees. Same principle as CPMM’s protocol_fees_* exclusion.
Every Swap* operation enforces k' ≥ k after adding the LP’s fee share back into the reserves.

Fee convention

AMM v4 uses ratio fees (numerator/denominator pairs) rather than the 1/1_000_000 convention of CPMM / CLMM. The on-chain Fees struct (see Fees::initialize in the program source) defaults to:
Fees {
  min_separate_numerator:    5,
  min_separate_denominator:  10_000,   //  5/10_000 = 0.05%

  trade_fee_numerator:      25,
  trade_fee_denominator:    10_000,    // 25/10_000 = 0.25% — used for OpenBook limit-order pricing

  pnl_numerator:            12,
  pnl_denominator:          100,       // 12/100   = 12%   — protocol's share OF the swap fee

  swap_fee_numerator:       25,
  swap_fee_denominator:     10_000,    // 25/10_000 = 0.25% — gross fee on AMM-path swaps
}
Interpretation (published mainnet defaults):
  • Total swap fee: swap_fee = amount_in × 25 / 10_000 = 0.25% of the gross input.
  • Protocol share: pnl_numerator / pnl_denominator = 12 / 100 = 12% of the swap fee, which works out to 0.25% × 12% = 0.03% of volume. This share accrues to the PnL counters and is swept by WithdrawPnl.
  • LP share: the remaining 88% of the swap fee, which works out to 0.25% × 88% = 0.22% of volume. Stays in the pool and inflates k.
  • No fund share. AMM v4 does not have the CPMM/CLMM fund-fee split.
Note that pnl_numerator / pnl_denominator is a fraction of the fee, not of trade volume — a common misreading of these field names. trade_fee_numerator / trade_fee_denominator (also 25 / 10_000) is a separate field used by the OpenBook integration when computing fee-inclusive prices for the AMM’s grid of limit orders; it equals swap_fee by default but is read from a different code path. Deviations from these defaults are rare but do exist on a handful of legacy pools; always read the fees from AmmInfo.fees before quoting.

Direct swap math (AMM path)

The simplest case: user swaps against the pool’s vaults without interacting with OpenBook. The pool’s internal reserves (including on-book allocations) are the denominator. SwapBaseIn (exact input):
amount_after_fee = amount_in − ceil(amount_in × swap_fee_numerator / swap_fee_denominator)
amount_out = amount_after_fee × out_reserve
           / (in_reserve + amount_after_fee)
require(amount_out >= minimum_amount_out)
The reserves used here are the effective reserves. Historically this was coin_vault_balance + coin_posted_on_openbook + ... (the AMM’s vault plus the tokens it had locked into OpenBook orders). As of the OpenBook deactivation, the on-book balance is zero, so the effective reserves equal the raw vault balances. The MonitorStep / implicit-settle path that used to refresh the OpenBook side is no longer needed in practice. SwapBaseOut (exact output):
amount_in_after_fee = ceil(in_reserve × amount_out / (out_reserve − amount_out))
amount_in_gross     = ceil(amount_in_after_fee × swap_fee_denominator
                            / (swap_fee_denominator − swap_fee_numerator))
require(amount_in_gross <= maximum_amount_in)

Order-book interaction (historical)

No longer active. The grid construction described in this section reflects how AMM v4 originally mirrored the curve onto an OpenBook market. The OpenBook integration has been deactivated; pools no longer post or maintain orders on OpenBook. The math below is preserved for context — it explains what the on-chain target_orders / amm_open_orders accounts were sized for and why the program still validates MonitorStep-related parameters even though the keeper no longer cranks them.
Separately from user swaps, AMM v4 historically placed a grid of limit orders on the OpenBook market. The grid was computed from AmmInfo parameters:
  • depth — number of price levels per side.
  • amount_wave — base unit of size per level.
  • min_size, coin_lot_size, pc_lot_size — OpenBook market constraints.
  • state_data.swap_acc_coin_fee, swap_acc_pc_fee — cumulative fee counters since last TakePnl.
The program derives per-level prices by walking out from the current curve price in constant-ratio steps:
price_level(k) = curve_price × (1.0001 ^ k)       # conceptually
size_level(k)  = amount_wave × f(depth, k)        # tapered by depth
The exact prices and sizes are determined by target_orders computed in build_orders and compared with amm_open_orders each MonitorStep. Any divergence results in cancellations + new posts. Freshly filled orders on OpenBook settle into the pool vaults on the next operation that refreshes the OpenBook side. Integrators rarely need to compute the grid — the Raydium keeper maintains it — but it is useful to know that:
  • A pool with significant on-book liquidity has that liquidity contributing to k, not sitting idle.
  • A stale OpenBook market (event queue full, cranks blocked) prevents grid updates; the AMM can then quote prices that diverge from the visible order book until the next crank.

Settlement step (PnL)

The 0.03% protocol share accrues into state_data.need_take_pnl_coin and state_data.need_take_pnl_pc. TakePnl moves these amounts out of the vaults to the admin-specified destination, then zeroes the counters. Crucial property: reserves in the invariant are always computed minus accrued PnL, so TakePnl does not move the curve. This matches the CPMM convention.

Worked example

Pool state:
  • coin_reserve = 1_000_000_000_000 (1,000,000 coin-side; 6 decimals)
  • pc_reserve = 2_000_000_000_000 (2,000,000 pc-side; 6 decimals)
  • Fees: default swap = 25/10_000, pnl = 3/10_000.
User: SwapBaseIn exact-input 1_000_000_000 coin (1,000 coin).
swap_fee        = ceil(1_000_000_000 * 25 / 10_000)    = 2_500_000
amount_after_fee =                                      997_500_000

amount_out = amount_after_fee * pc_reserve
           / (coin_reserve + amount_after_fee)
           = 997_500_000 * 2_000_000_000_000
           / (1_000_000_000_000 + 997_500_000)
           ≈ 1_995_015_009  (1,995.015 pc)

// Of the 2_500_000 swap fee:
pnl_share = 2_500_000 * 3 / 25  = 300_000    (goes to protocol via need_take_pnl_coin)
lp_share  = 2_500_000 * 22 / 25 = 2_200_000  (stays in coin_reserve)

new coin_reserve = 1_000_000_000_000 + 1_000_000_000                 = 1_001_000_000_000
                   (of which 300_000 is accrued PnL)
  curve coin_reserve = 1_001_000_000_000 − 300_000 = 1_000_999_700_000
new pc_reserve   = 2_000_000_000_000 − 1_995_015_009                 ≈ 1_998_004_984_991

k' = curve_coin_reserve * new_pc_reserve
   ≈ 2.000_002_701E24
k  = 1_000_000_000_000 * 2_000_000_000_000
   = 2.0E24
k' > k   ✓
The LP share (2_200_000) is not broken out anywhere — it is simply the residual that raises k'.

Precision rules

  • Reserve multiplications use u128; final divisions round toward zero.
  • swap_fee rounds up (so the pool does not undercharge).
  • amount_in for SwapBaseOut rounds up (so the user does not underpay).
  • Pools with extreme reserve ratios can hit ZeroTradingTokens on very small inputs; same convention as CPMM.

Limitations vs CPMM

  • AMM v4’s reserves include the OpenBook-escrowed portion, so an integrator cannot quote correctly from getTokenAccountBalance alone. Always fetch the full state (vaults + open_orders.free + open_orders.locked), or use the SDK / API quote.
  • AMM v4 does not expose a structured on-chain TWAP. External consumers that want an AMM-v4-backed price must compute it themselves from trade logs.
  • Token-2022 is not supported.

Where to go next

Sources: