Buyback and burn
How a share of every pool fee buys $VOID on Meteora DAMM v2 and burns it - the median price guard, chunk sizing, the instructions, and why callers are paid nothing.
A share of every fee the pool charges goes to the buyback PDA. A permissionless instruction spends it, one chunk at
a time, buying $VOID on its Meteora DAMM v2 pool and burning what it bought in the same instruction. The program sets
every amount and every price limit; the caller chooses nothing and is paid nothing. Two instructions do it: observe
and buyback.
Where the SOL comes from
Every shield fee and exit fee is split: buyback_share_bps of the fee to the buyback PDA
["buyback"], the rest to the treasury (see Fees). The launch parameters set the
share to 5,000 bps, half of every fee. The admin can change it with set_params, up to 10,000.
The buyback is fed only by its share of the pool's fees. Its SOL can leave in one way only: as a chunk, into the swap.
Constants
| Constant | Value | Meaning |
|---|---|---|
N_OBS | 15 | Observations in the ring |
OBS_INTERVAL | 30 slots (about 12 s) | At most one observation per interval |
MAX_OBS_AGE | 90 slots (3 intervals) | A longer gap starts the ring over; a buy needs an observation this recent |
BUY_INTERVAL | 150 slots (about a minute) | At most one buy per interval |
CHUNK_MAX | 1 SOL | The largest chunk |
CHUNK_RESERVE_BPS | 50 (0.5%) | The largest chunk as a share of the pool's SOL reserve |
CHUNK_MIN | 0.01 SOL | A smaller chunk is not bought (ChunkTooSmall) |
SQRT_BAND_BPS | 50 (0.5% on the square root, about 1% on the price) | The band above the reference |
ROOM_USE_BPS | 9,000 (90%) | The share of the room left in the band one chunk may use |
MAX_LOSS_BPS | 200 (2%) | The minimum output is the reference price's output less this |
Times assume 400 ms slots.
The price guard
Anyone can call observe, so a bot could pump the price, observe and dump within one transaction. The guard is built
so that this does not move the reference.
- The program's own observations.
observereads the pinned pool's square-root price and writes it into a ring of 15, at most once per 30 slots (TooSoon). It pays nothing and is not paused. - The median, not the mean. The reference is the median of the 15 observations. To move it a bot would have to pump the price for more than half of the observations in a row, paying the pool's fee on every round trip.
- The ring restarts after a gap. If more than 90 slots pass between two observations, the ring starts over. A buy
needs a full ring (
NotEnoughObservations) and an observation from the last 90 slots (StaleObservations). So the median is never made of stale prices: after a quiet hour and a fall in price, one fresh observation plus a pump back to the old median in the buy's own transaction finds no ring to buy against. - Within the band, before and after. A buy runs only if the token's price is not above the band over the reference,
both before the swap and after it (
PricePumped). The band guards only the top: a token cheaper than the reference is still bought. - A minimum output. The swap's minimum output is what the chunk would buy at the reference price, ignoring fees, less 2%. A pool fee plus slippage above that makes the swap fail, so nothing is bought.
- A live pool. A disabled pool is refused (
PoolDisabled).
The band check compares square roots. With SOL as the pool's token A, the pool's price is tokens per SOL, so a pumped token
shows as a lower square root, below reference × (1 − 0.5%); with SOL as token B, as a higher one, above
reference × (1 + 0.5%).
Chunk sizing
The program computes the chunk; the caller cannot choose it. It is the lowest of:
| Bound | Value |
|---|---|
| Constant cap | CHUNK_MAX, 1 SOL |
| Pool depth | 0.5% of the pool's SOL reserve |
| Funds | What the buyback holds above its rent-exempt minimum |
| Room in the band | 90% of the SOL that would move the price from where it is to the top of the band |
The room comes from the pool's liquidity L (cp-amm's, scaled by 2^64), the current square root √P and the band's top t:
SOL as token B: the square root rises by Δb / L, so room = (t − √P) × L
SOL as token A: 1/√P' = 1/√P + Δa / L, so room = L × (√P − t) / (t × √P)It is zero when the price is already at or past the top. Fees taken from the input only move the price less. So near
the top of the band the program buys less instead of failing, and a buy never pushes the price out of the band by
itself. If the result is below CHUNK_MIN, nothing is bought, and what does not fit waits for the next interval.
A buy
Check. The buyback is not paused (flag 8), 150 slots have passed since the last buy, the ring is full and fresh, the pool is enabled and the price is within the band.
Size. The program computes the chunk and the minimum output as above.
Fund the swap. The buyback PDA moves the chunk to its own wSOL account with a signed system transfer, then
SyncNative.
Swap. A CPI to cp-amm's swap, signed by the buyback PDA, with the chunk in and the minimum out, into the
buyback's $VOID account.
Check again. The price after the swap is still within the band, and the wSOL account is empty
(InputNotSpent).
Burn. The program burns exactly the $VOID the swap bought, records the slot and adds the amount to burned, and
logs buyback: <lamports> lamports -> <amount> burned (min <min>).
Burned amounts are public on-chain: in each buy's SPL Token Burn, in its log line, and as the running total
burned in the buyback state account, which the site shows.
No caller payments
Neither observe nor buyback pays its caller anything. The only SOL that leaves the buyback is the chunk, into the
swap, and what it buys is burned in the same instruction. Whoever calls them pays their own transaction fee; the
relayer runs a crank that does.
In tests, both instructions are called 300 times in a loop, on both sides of the pool, with the price, the pause flag and the observations moving in between. No call gains its caller a lamport, and the buyback loses SOL only into a burned buy.
How it behaves
From tests on a 100 SOL pool, on both sides of the pool:
| Market | What the buyback does |
|---|---|
| Flat | About 0.45 SOL a minute |
| Just under the top of the band | A smaller chunk: about 0.09 SOL at 0.4% above the median |
| A rally of about 1% a minute | Stops while the price runs ahead of the median; buys again about 24 s after it stops |
| A sell-off | Goes on, about one buy every five intervals |
| ±0.8% chop | Buys on the dips; no buy leaves the price above the band |
| Pool fee 1% / 3% | Buys / does not buy (the 2% loss limit) |
Setting it up
The buyback starts after $VOID trades on Meteora DAMM v2 (cp-amm, program cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG).
set_void_mint(admin only) sets the $VOID mint in the config, so the program can be initialized before $VOID exists. It accepts only an initialized SPL Token mint (BadMint): the buyback calls only the Token program, so $VOID cannot be a Token-2022 mint. It works only until a pool is pinned (BuybackPinned).init_buyback(admin only, once) pins the pool. The pool must be owned by cp-amm, 1,112 bytes long, and trade wSOL against the config's $VOID mint, on either side (WrongPool). The program records which side SOL is on, the pool's two token vaults, and the buyback PDA's wSOL and $VOID associated token accounts.- Once a pool is pinned, the mint and the pool are fixed.
The admin does all of it in one transaction: it sets the mint, creates the buyback's two token accounts and pins the pool. Before signing, the admin's setup step checks that the mint is an SPL Token mint and that the pool is a live cp-amm pool of $VOID and SOL, and shows the pool's fee and depth. The program checks a pool's owner, size and mints; which $VOID/SOL pool to pin is the admin's choice.