Skip to content

zenith-amm — Concentrated Liquidity AMM

zenith-amm is a concentrated-liquidity automated market maker. Liquidity providers supply capital to a chosen price range rather than the whole curve, so their capital is only active — and only earning fees — while the price sits inside that range. Swaps walk a tick curve, crossing initialized ticks as the price moves and switching which liquidity is active at each boundary.

Program ID (devnet): AA8cKcHQj63GEHRaLrrT87W1efRZ44U147JTCXC2Rmkq

Core ideas

Prices as sqrt-price (Q64.64)

The pool stores price as a square-root price in Q64.64 fixed point (sqrt_price). Working in sqrt-price makes the swap step math exact and cheap: the amount of each token to move between two prices is linear in the sqrt-price delta. The tradable band is bounded by the config's sqrt_min_price / sqrt_max_price.

Ticks

A tick is a discrete price point, price = 1.0001^tick. Only ticks that are multiples of the config's tick spacing (64) may be used as position bounds. Each tick records:

  • liquidity_net (i128) — how the active liquidity changes when the price crosses this tick (added going up, removed going down),
  • liquidity_gross (u128) — total liquidity referencing the tick (so it is cleared only when truly empty),
  • fee_growth_outside_{a,b} — the fee bookkeeping used for per-range fees.

Ticks are stored in fixed-size TickArray accounts (88 ticks each), created on demand with init_tick_array and passed to swaps and liquidity changes as remaining accounts. A swap is handed a contiguous run of arrays starting from the one holding the current price; it may cross up to three arrays per transaction and otherwise stops (a PartialFill) rather than gliding past liquidity it wasn't shown.

current_tick

The pool persists a current_tick pointer alongside sqrt_price. Price alone cannot say which side of a tick the active liquidity is on when a swap ends exactly on a tick boundary, so the pointer disambiguates it. Adding or removing liquidity gates on this pointer (tick_lower ≤ current_tick < tick_upper), which keeps the active-liquidity total consistent with the price-based deposit amounts.

Fee-growth-inside

Each position earns only the fees that accrued while the price was inside its range. This is the Uniswap-v3 identity inside = global − below − above, computed in wrapping u128 arithmetic so a claim never spuriously reverts. A position's two fee checkpoints hold its last-seen inside value; the difference since then is what it is owed. Fees can optionally be compounded back into the position's own range.

State

AccountHolds
ConfigFee schedule, price band, tick spacing — a reusable pool template.
Poolsqrt_price, current_tick, active liquidity, fee-growth globals, protocol/partner fee accumulators, tick spacing.
PositionOwner (via NFT), tick_lower / tick_upper, liquidity, fee checkpoints (inside).
TickArray88 Ticks + the owning pool + the array's start tick index.

Instructions

InstructionPurpose
create_configStamp a reusable template (fee schedule, band, tick spacing).
initialize_poolOpen an empty pool at a starting price with a full-range first position. Liquidity enters only via add_liquidity.
init_tick_arrayPermissionlessly allocate the tick array covering a tick index.
create_positionOpen an empty position over [tick_lower, tick_upper].
add_liquidity / remove_liquidity / remove_all_liquidityDeposit / withdraw over a position's range (threads the two boundary tick arrays).
swapTrade ExactIn, ExactOut, or PartialFill, walking the tick curve.
claim_position_feeSettle a position's fee-growth-inside and pay out (or compound).
set_position_compoundingToggle auto-compounding for a position.
claim_protocol_fee / claim_partner_feeWithdraw the protocol / partner fee share.
close_positionClose an empty position and reclaim rent.

Fees

Each swap charges a single fee derived from the config's fee scheduler on the pre-swap price (a constant base fee, optionally a volatility-driven dynamic fee). The fee is split three ways: the LP share raises the pool's per-liquidity fee-growth accumulator (divided by that segment's active liquidity, so multi-tick swaps attribute fees correctly), while the protocol and any partner share accrue on the pool for later claim.

What proves it works

  • Host unit tests and golden math vectors in the crate and program.
  • On-chain integration tests (tests/amm-integration) that drive real swaps across ticks and assert the liquidity gate and fee settlement — including a swap that crosses a concentrated position's boundary, leaves it out of range, and confirms it can still be removed.
  • The SDK's quoteSwap walks the same tick curve and is checked against the compiled program (see Math).

Built on Solana devnet. Integer-exact math, no floats.