How the machine works
One hook, one pot per pool, two mechanics, one optional LP program. Everything below is the verified on-chain behaviour — no trust assumptions, no privileged actors.
MAIN, SECONDARY, and the pot
Every hooked pool declares two roles for its two currencies. MAIN is the asset being defended — it is what the pot buys and what the pot's recipient receives. SECONDARY is the buyback currency — the ONLY asset the pot holds and the only asset donate() accepts.
The pot is a per-pool, permissionless war chest. Anyone can fuel it — the token team, a protocol treasury, a community member, another contract. It spends itself automatically through two mechanics: the pump on buys and the shield on sells.
The pot's recipient decides where bought MAIN goes: address(0) means burn (the cascade below), anything else receives it directly.
native-main pools cannot set burn intent — you can't burn the network token. failed recipient deliveries are parked and retryable with flushDirect(poolId).
The pump — why it can't be sandwiched
On a SECONDARY → MAIN buy, afterSwap makes the pot buy more MAIN inside the buyer's own transaction. The spend is sized from the carrying buy:
A sandwich needs the attacker's round trip to profit from the pump's price push. But forcing a bigger pump requires a bigger real buy, which costs the attacker fee + impact twice — and the haircut means the pump moves the price by LESS than the attacker paid to trigger it. The attack is self-financing a donation to the pool.
note: zero-fee pools never pump by design — a fee-less pool makes the round trip free, which would break the sandwich arithmetic above.
The shield — sells that don't move the price
On a MAIN → SECONDARY sell, beforeSwap lets the pot absorb the sell at the pool's EXACT execution price — fee and tick impact included. The seller receives precisely what the pool would have paid; the pool's price simply does not move. The absorbed MAIN goes to the recipient instead of the pool.
Because the shield never pays ABOVE the pool's own price, selling into the pot is never better than selling into the pool. Moving spot first buys an attacker nothing, and every round trip still pays the pool's fee and impact twice.
When the pot can't cover the whole sell, it absorbs the slice it can afford and the remainder swaps through the pool in the same call — partial defense, zero seller friction.
Harvest split, compound and carry
The pool's LP program (one per pool, owned) accrues swap fees on both sides. A harvest — automatic once fees pass the minimums, or manual — splits each side FROM THE GROSS:
- ▸ compound share (both sides) → re-minted as liquidity
- ▸ buyback share (secondary side) → fuels the pot
- ▸ burn share (main side) → the burn cascade
- ▸ the residual of each side → one recipient per side
Constraints, enforced on-chain when the config is set: compound + buyback ≤ 100% and compound + burn ≤ 100%; burn must be 0 when MAIN is native; a recipient is required wherever a residual exists. Config changes only affect FUTURE harvests.
Compounding anchors on the secondary token: it places as much of the budget as the current price allows and CARRIES the unmatched remainder to the next harvest — nothing leaks, nothing is double-compounded (harvested fuel sits in the pot, not in the next compound).
the in-swap auto-harvest runs under a hard gas budget so a heavy config can never tax swappers: it reverts atomically (fees stay safe) and the public harvest(key) — full caller gas — picks it up.
Three roles, all surrenderable
- · initPot — declare MAIN + recipient
- · setRecipient (0x0 = burn)
- · create the pool's ONE LP program
- · add / remove liquidity
- · harvest (when not public)
- · transfer or surrender ownership
- · edit the fee split
- · edit minimums + recipients
- · toggle public harvest
the hook itself is ownerless: no role above has power over the hook, only over their own pool's pot or program. lockers, DAOs and vesting contracts compose on top by holding these roles.
Launch a hooked pool
address(0) is always currency0), pick a NON-ZERO fee tier, and set hooks = GlueHook. A zero-fee pool would never pump.launchPool(key, sqrtPriceX96, main, recipient, 0, 0, liquidity, owner, config): it initializes the pool (YOU become the pot admin), declares which currency is MAIN (defended) and where bought MAIN goes (0x0 = burn cascade), and seeds the LP program with its split rules — atomically. Prefer separate steps? PoolManager.initialize, initPot and addLiquidityAdvanced still work individually.donate(key, amount) in the SECONDARY currency — from your treasury, your launch contract, or your community. The machine runs from here.Contract-to-contract buybacks — no oracle
Traditional buyback machinery needs a price oracle and a keeper — two trust dependencies. Hook needs neither: your contract just donate()s, and the pot executes at REAL market prices, riding real user buys, only when there is real demand.
Two views quote the machine before you act: quotePump(key, buySize) returns what the pot would spend and buy alongside a buy of that size; quoteShield(key, sellSize) returns how much of a sell the pot would absorb and pay.
Everything is at the same address on all 18 chains, so a single integration ports everywhere unchanged.
LP program recipes
The pot admin creates the pool's single program with addLiquidity (everything off, tune later) or addLiquidityAdvanced (full rules at creation). Ticks 0/0 mean full range. Some proven configurations:
a normal position; harvest manually, keep everything.
- · compound 0%
- · buyback 0%
- · burn 0%
- · recipients = you
fees deepen liquidity and fuel the pot automatically.
- · compound 50%
- · buyback 50% (sec)
- · burn 0%
- · auto-harvest armed
the main side burns, the secondary side fuels defense.
- · compound 30%
- · buyback 40% (sec)
- · burn 70% (main)
- · recipient = treasury
owner surrendered at birth: LP locked forever, machine public.
- · owner = 0x0
- · harvest forced public
- · operator kept OR 0x0
- · config frozen optional
Addresses, ABI and audit
| network | hook | env |
|---|---|---|
| Ethereum | 0x89C5…a0C8 | mainnet |
| Base | 0x89C5…a0C8 | mainnet |
| Unichain | 0x89C5…a0C8 | mainnet |
| Arbitrum | 0x89C5…a0C8 | mainnet |
| Optimism | 0x89C5…a0C8 | mainnet |
| BNB Chain | 0x89C5…a0C8 | mainnet |
| Polygon | 0x89C5…a0C8 | mainnet |
| World Chain | 0x89C5…a0C8 | mainnet |
| Zora | 0x89C5…a0C8 | mainnet |
| Soneium | 0x89C5…a0C8 | mainnet |
| MegaETH | 0x89C5…a0C8 | mainnet |
| Robinhood | 0x89C5…a0C8 | mainnet |
| Tempo | 0x89C5…a0C8 | mainnet |
| Sepolia | 0x89C5…a0C8 | testnet |
| Base Sepolia | 0x89C5…a0C8 | testnet |
| Unichain Sepolia | 0x89C5…a0C8 | testnet |
| Arbitrum Sepolia | 0x89C5…a0C8 | testnet |
| Robinhood Testnet | 0x89C5…a0C8 | testnet |
- ▸the hook lives at the same canonical address on every network — Tempo included, no exceptions.
- ▸the hook address carries the V4 permission flags in its low bits (beforeInitialize · beforeSwap · afterSwap · beforeSwapReturnsDelta) — that's why the deployer key was mined.
- ▸failed payouts never brick anything: recipient deliveries park and retry (flushDirect), harvest payouts bank into owedOf and are pulled with claim(asset).
- ▸fee-on-transfer secondaries are measured on arrival — the pot books exactly what landed.