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
| Account | Holds |
|---|---|
Config | Fee schedule, price band, tick spacing — a reusable pool template. |
Pool | sqrt_price, current_tick, active liquidity, fee-growth globals, protocol/partner fee accumulators, tick spacing. |
Position | Owner (via NFT), tick_lower / tick_upper, liquidity, fee checkpoints (inside). |
TickArray | 88 Ticks + the owning pool + the array's start tick index. |
Instructions
| Instruction | Purpose |
|---|---|
create_config | Stamp a reusable template (fee schedule, band, tick spacing). |
initialize_pool | Open an empty pool at a starting price with a full-range first position. Liquidity enters only via add_liquidity. |
init_tick_array | Permissionlessly allocate the tick array covering a tick index. |
create_position | Open an empty position over [tick_lower, tick_upper]. |
add_liquidity / remove_liquidity / remove_all_liquidity | Deposit / withdraw over a position's range (threads the two boundary tick arrays). |
swap | Trade ExactIn, ExactOut, or PartialFill, walking the tick curve. |
claim_position_fee | Settle a position's fee-growth-inside and pay out (or compound). |
set_position_compounding | Toggle auto-compounding for a position. |
claim_protocol_fee / claim_partner_fee | Withdraw the protocol / partner fee share. |
close_position | Close 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
quoteSwapwalks the same tick curve and is checked against the compiled program (see Math).