Buy and Burn
How supply gets destroyed, and how to observe it. There is no buyAndBurn() function — burning is a branch the contract selects from pool state, not an action a caller elects.
Earlier versions of the protocol exposed a deployer-only buyAndBurn(token) entry point. It no longer exists. Anything calling it will revert. Burning now happens inside distributeFees() and processProtocolFees(), chosen automatically.
Where burns come from
| Source | Trigger | What burns |
|---|---|---|
| Reinvest fallback | distributeFees(token) when the pool is token-poor | Reinvest budget buys tokens, sent to 0x…dEaD |
| Protocol processing | processProtocolFees() when the $LP24 pool holds ≥ 24M tokens | Protocol balance buys $LP24, burned |
| Secondary pool fees | Every swap on a token↔$LP24 pool | 60 bps (0.6%) of the token side, burned in the callback |
| Dust cleanup | After any liquidity add | Leftover tokens above 1,000,000 wei |
| Gnosis DEEP path | distributeFees(token) when pool ≥ 48,000,000 × 10⁹ (48M tokens) | All pending tokens, plus the reinvest budget as a buy-and-burn |
Which branch runs
The choice is made from the pool's token-side reserve, read from pool state rather than from a balance lookup on the pool manager — the latter would conflate this pool with any other pool holding the same token and could send the branch the wrong way.
V4, processProtocolFees:
pool_token_side >= 24_000_000 tokens → buy and burn
otherwise → reinvest to deepen
V4, distributeFees reinvest step:
acquired tokens > dust → add liquidity
otherwise → buy and burn the remainder
Gnosis, distributeFees:
pool >= POOL_DEPTH_THRESHOLD → DEEP: burn everything
otherwise → THIN: pair into liquidity
The logic is deliberately counter-cyclical. A pool that has been bought heavily is token-poor, so fees go to refilling it; a pool that is deep and healthy gets supply removed instead.
Triggering a burn
You cannot request one directly, but calling distribution when conditions favour the burn branch will produce one:
// Read the current state to predict the branch
const [liquidity, sqrtPriceX96] = await hook.getPoolLiquidity(LP24_TOKEN);
// token-side reserve ≈ L * sqrtP / 2^96
const Q96 = 2n ** 96n;
const tokenSide = (liquidity * sqrtPriceX96) / Q96;
if (tokenSide >= 24_000_000_000_000_000n) {
// processProtocolFees will buy and burn
await (await hook.processProtocolFees()).wait();
}# Read the current state to predict the branch
liquidity, sqrt_price_x96, _tick = hook.functions.getPoolLiquidity(LP24_TOKEN).call()
# token-side reserve ≈ L * sqrtP / 2^96
Q96 = 2 ** 96
token_side = (liquidity * sqrt_price_x96) // Q96
if token_side >= 24_000_000_000_000_000:
# processProtocolFees will buy and burn
send(hook.functions.processProtocolFees())Observing burns
const burns = await hook.queryFilter(
hook.filters.BuyAndBurn(token), fromBlock, "latest"
);
const totalNativeSpent = burns.reduce(
(acc, e) => acc + e.args.nativeSpent, 0n
);burns = hook.events.BuyAndBurn().get_logs(
from_block=from_block, argument_filters={"token": token}
)
total_native_spent = sum(e["args"]["nativeSpent"] for e in burns)Secondary-pool burns emit SecondaryTokenBurn(token, amount) on the SecondaryHook instead, one per taxed swap. For a complete picture of destroyed supply, watch both, plus Transfer events to 0x…dEaD which cover dust cleanup.
The curve is the only minter, and it is closed once the token graduates. From that point every supply change is a burn, so totalSupply() is monotonically non-increasing. Comparing it against 1,200,000,000 × 10⁹ (1.2B) gives cumulative destruction without reconstructing any event history.