Hooks

Not middleware. Not a bot.
Code the pool calls mid-swap.

A hook is not something watching the pool from outside — the PoolManager calls it during execution. If the rule reverts, the swap does not happen. That is the whole difference between a rule and a preference, and it is why this design is possible now and was not before.

The sequence

Where the rule actually runs

Literal call order inside one swap(). There is no window between the check and the trade for anything to slip through, because they are the same transaction.

swap() └ PoolManager.unlock() └ hook.beforeSwap() ← your rule runs here └ swap executes └ hook.afterSwap()
Why it holds

The permissions live in the
hook's own address.

The pool keeps a hook's permissions in the low 14 bits of its own contract address. The PoolManager reads the address to learn which callbacks it may invoke — it never asks the contract to describe itself, and a contract cannot lie about a number that was fixed when it was deployed.

So deploying a hook means mining a salt until the address ends in the right bits. A pool cannot quietly gain a capability it was not deployed with, and what you audit at launch is what runs for the life of the pool.

// THROTTLE · FEE · LOCK beforeSwap 1 << 7 0x0080 beforeRemoveLiquidity 1 << 9 0x0200 ────── required address suffix 0x0280
The terminal computes that suffix live as you toggle rules — the arithmetic on this page is the same code that runs there.
Six rules

Selected per launch, mined into the address

THROTTLE beforeSwap

First-block cap

For the first N blocks after the pool opens, per-wallet size is capped. Snipers can still buy — they cannot buy the whole book. The overflow fee goes to the pool, not to a bot.

cap(wallet, n_blocks)
Cuts both ways: it caps you as well.
FEE beforeSwap

Dynamic fee

The fee is priced per swap against realized volatility. Calm markets stay cheap. Violent ones pay liquidity providers for the risk they are actually carrying, instead of charging 3am prices during a 40% candle.

fee = f(realized_vol)
Cuts both ways: your exit can be the expensive one.
SEED beforeAddLiquidity

Single-sided liquidity

Deposit one token. The hook derives the range from current depth and places it. No counterpart, no manual tick math, no leftover dust to sweep up afterwards.

range = auto(depth)
The range is the hook's call, not yours.
BUYBACK afterSwap

Fee routing

A slice of every fee is routed into a standing bid below spot. Fees stop being a receipt and start being a floor that grows with volume — and it only grows while the pool actually trades.

route(fee_share) → bid
No volume, no bid. Nothing tops it up.
LOCK beforeRemoveLiquidity

Enforced lock

Liquidity cannot leave before the unlock block. Not a multisig, not a pledge, not a screenshot of a locker — a revert. The pool refuses the transaction.

require(block > unlock)
Cuts both ways: you cannot exit early either.
GUARD beforeSwap

TWAP guard

Swaps that shove price outside a band against the oracle in a single block are rejected. It cuts the sandwich without closing the market.

reject(|Δp| > band)
A legitimate large swap can be rejected too.
Execution path

All six, in the order the PoolManager runs them

swap() └ PoolManager.unlock() └ hook.beforeSwap() ├ THROTTLE gate on wallet size ├ FEE prices this swap └ GUARD band check against the oracle └ swap executes └ hook.afterSwap() └ BUYBACK routes a fee slice into the bid modifyLiquidity() └ hook.beforeAddLiquidity() → SEED derives the range └ hook.beforeRemoveLiquidity() → LOCK reverts before the unlock block