zenith-camm — Constant Product + Idle-Reserve Yield
zenith-camm is a full-range constant-product AMM (x·y=k) with one distinguishing feature: its idle reserves earn yield. A share of the pool's reserves is marked as "deployed", and yield accrues on that principal over time, flowing back into the reserves and raising every LP's share value — without the LP doing anything.
Program ID (devnet): CjjcK3rnskHswBpTgZquLGgS7P2QyzeaNwwe98FUUdy7
Core ideas
Constant product with LP shares
Reserves x and y trade against the invariant x·y=k. Liquidity providers receive fungible LP shares:
- The first deposit bootstraps shares from the geometric mean of the two amounts, and a small
MINIMUM_LIQUIDITYis permanently locked to prevent the share price from being manipulated to zero. - Later deposits mint shares proportional to the pool they add.
- Redeeming shares returns a proportional slice of the reserves.
Swaps use the standard out = f(in) / in = f(out) constant-product formulas (exact, in fixed point), with a base fee split into an LP share (left in the pool, so it accrues to all holders) and a protocol share.
The yield vault
This is what sets zenith-camm apart. Reserves that aren't needed as an immediate swap buffer can be marked deployed, and yield accrues on them:
accrued_yield(deployed, rate, elapsed_slots) = deployed · rate · elapsed / SCALEThe share price is therefore (reserves + pending yield) / lp_supply, and it only ever rises as yield lands.
The devnet implementation is an honest mock — there is no external lending market on devnet to integrate against, so Zenith does not pretend to. Instead:
- Deployed principal physically stays in the reserve vault (it is an accounting marker only), so the pool is always solvent for swaps and there is no withdraw-on-demand risk.
- Yield is paid from a pre-funded yield-source vault into the reserves when harvested, which is exactly what raises the share price.
A buffer (in basis points) keeps a slice of reserves undeployed so swaps always have working liquidity.
No just-in-time yield capture
Depositing right before a harvest to skim un-accrued yield is blocked: add_liquidity prices any pending, un-harvested yield into the effective reserve used for share minting (computed on-chain from the deployed amount, rate, and elapsed slots). A late depositor buys in at the already-grown share price.
State
The Pool account holds the reserves, LP mint, fee parameters, and the yield fields: deployed_a / deployed_b, last_accrual_slot, yield_rate, and the buffer_bps.
Instructions
| Instruction | Purpose |
|---|---|
initialize_pool | Open the pool, LP mint, and reserve vaults; set fee parameters. |
add_liquidity | Deposit both tokens, mint LP shares (pending yield priced in). |
remove_liquidity | Burn LP shares, withdraw a proportional slice of reserves. |
swap | Constant-product trade with fee split. |
yield_vault | Yield operations: initialize_yield (set rate + buffer, fund sources), harvest_yield (pay accrued yield into reserves — permissionless), rebalance_to_vault (harvest, then re-mark the deployable amount). |
What proves it works
- Constant-product and LP-share golden vectors in the math crate.
- Devnet e2e checks in
sdk/scripts: a swap whose realized output equals the SDK quote exactly, an LP add/remove round-trip that conserves value (within a bounded pending-yield charge), and a harvest that grows the reserve by exactly the amount transferred from the yield source.