Skip to main content

The invariant

The pool maintains coin_reserve × pc_reserve = k, where (after the 2026-07 OpenBook removal):
Two things to notice:
  1. Reserves are now vault-only. The historical open-order term (tokens the pool had escrowed as OpenBook limit orders) has been dropped — it was zero in practice long before the removal. You can compute k directly from the on-chain vault balances.
  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.
The program computes this via calc_total_without_take_pnl_no_orderbook (the old orderbook-aware variant is gone). 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:
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) was historically used by the OpenBook integration when computing fee-inclusive prices for the AMM’s limit-order grid. With the OpenBook code removed, this field is vestigial; the active swap fee is swap_fee_*. 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):
The reserves used here are the vault balances (minus accrued PnL). Historically the formula also added the tokens the AMM had locked into OpenBook orders; that term has been removed — the effective reserves now equal the raw vault balances less pending PnL. The MonitorStep / implicit-settle path that used to refresh the OpenBook side has been removed. SwapBaseOut (exact output):

Order-book interaction (historical)

Removed. The grid construction described in this section reflects how AMM v4 originally mirrored the curve onto an OpenBook market. The OpenBook integration — including the MonitorStep crank and the build_orders grid logic — has been removed from the program (2026-07 upgrade). The math below is preserved purely as historical context for what the on-chain target_orders / amm_open_orders accounts were once sized for.
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:
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).
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 are now vault-only, so you can quote directly from the vault balances (minus need_take_pnl_*) — the previous requirement to add the open_orders.free / open_orders.locked amounts no longer applies. The SDK / API quote remains the simplest option.
  • 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: