voidcashdocs

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

ConstantValueMeaning
N_OBS15Observations in the ring
OBS_INTERVAL30 slots (about 12 s)At most one observation per interval
MAX_OBS_AGE90 slots (3 intervals)A longer gap starts the ring over; a buy needs an observation this recent
BUY_INTERVAL150 slots (about a minute)At most one buy per interval
CHUNK_MAX1 SOLThe largest chunk
CHUNK_RESERVE_BPS50 (0.5%)The largest chunk as a share of the pool's SOL reserve
CHUNK_MIN0.01 SOLA smaller chunk is not bought (ChunkTooSmall)
SQRT_BAND_BPS50 (0.5% on the square root, about 1% on the price)The band above the reference
ROOM_USE_BPS9,000 (90%)The share of the room left in the band one chunk may use
MAX_LOSS_BPS200 (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. observe reads 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:

BoundValue
Constant capCHUNK_MAX, 1 SOL
Pool depth0.5% of the pool's SOL reserve
FundsWhat the buyback holds above its rent-exempt minimum
Room in the band90% 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:

MarketWhat the buyback does
FlatAbout 0.45 SOL a minute
Just under the top of the bandA smaller chunk: about 0.09 SOL at 0.4% above the median
A rally of about 1% a minuteStops while the price runs ahead of the median; buys again about 24 s after it stops
A sell-offGoes on, about one buy every five intervals
±0.8% chopBuys 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.

On this page