The rule arrived as one sentence: nobody buys more than 1% of supply in the first 20 blocks after launch. Here is how it became a hook. This is an example build, written to show the process; it is not deployed.
The spec
- The limit applies to buys only. Sells are never blocked.
- The window is counted in blocks from pool initialisation, not in time.
- The cap is per receiving wallet, summed across every buy inside the window.
- After block 20 the hook does nothing. It cannot be switched back on.
The hook
Simplified for reading. The real build also handles exact-output swaps and routers that buy on behalf of a user.
function beforeSwap(address, PoolKey calldata key, SwapParams calldata p, bytes calldata hookData)
external override onlyPoolManager
returns (bytes4, BeforeSwapDelta, uint24)
{
PoolId id = key.toId();
if (block.number < launchBlock[id] + WINDOW && isBuy(key, p)) {
address buyer = abi.decode(hookData, (address));
bought[id][buyer] += amountOut(p);
require(bought[id][buyer] <= maxBuy[id], "anti-snipe: cap");
}
return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
}
Tests
Unit tests cover the edges: a buy exactly at the cap, one wei over, the last block of the window and the first block after it. Fuzzing throws 10,000 random sequences of buys from up to 50 wallets at a forked pool, and checks one invariant: no wallet ends the window above the cap.
What it will never do
Block a sell, change the cap after launch, or restart the window. All three are written into the spec, and all three have tests.