Own hook or stacked hook
At a glance
Your own v4 hook
The builder writes an OpenZeppelin BaseHook with exactly the callbacks its modules need: afterInitialize to note when the pool opened, beforeSwap for fees, guards and rebates, afterSwap with its return delta for the swap tax, beforeRemoveLiquidity for the lock. Its constructor takes the PoolManager, plus the owner of the allowlist when the recipe has one.
- A hook that sets fees reverts with
NeedsDynamicFee()if its pool is not opened with the dynamic fee flag (0x800000). A hook without fee modules runs in a pool with a fixed fee, the recipe's base fee. - Refusals are named errors:
NotAllowed(),CoolingDown(until),PriceGuard(),LiquidityLocked(until). - The swapper is
tx.origin, so a cooldown or a rebate follows the wallet that signed the swap, whatever router it went through. - The swap tax is taken in the swap's unspecified currency: the output of an exact-input swap, the input of an exact-output one. The pool itself is untouched.
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: false,
afterInitialize: true,
beforeAddLiquidity: false,
afterAddLiquidity: false,
beforeRemoveLiquidity: true,
afterRemoveLiquidity: false,
beforeSwap: true,
afterSwap: false,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}Mined flags
The PoolManager reads a hook's permissions from the last 14 bits of its address, so the address has to be mined: the builder tries salts until CREATE2 lands on an address whose low bits are exactly the declared callbacks, about 16,384 tries on average. The salt is mined for the wallet that sends the deploy, because the factory binds it to the sender. At deploy, the factory reads getHookPermissions() back from the new contract and refuses an address whose bits do not match.
Stacked on v4 Marketplace
A stacked hook implements the interface every hook listed on v4 Marketplace implements. The hook stack calls onStack once when a pool stacks it, then beforeSwap before every swap. It answers with a fee proposal in pips (0 for no opinion), a rebate in percent and a veto.
interface ILog {
function onStack(PoolKey calldata key, bytes calldata cfg) external;
function beforeSwap(address origin, PoolKey calldata key, SwapParams calldata params)
external
returns (uint24 feePips, uint8 rebatePct, bool veto);
function afterSwap(PoolKey calldata key, SwapParams calldata params, BalanceDelta delta) external;
}- Only the hook stack may call it: any other caller gets
NotHookStack(). - It never reverts on a rule. A refusal is a veto, so a cooldown or a price guard turns the swap down without making the hook a failed run.
- It never holds funds, which is why swap tax and liquidity lock are not offered here.
afterSwapis an empty function: the builder lists its hooks with afterSwap off. Modules that need the pool's price read it from the PoolManager directly.
Cooldown and AI agent rebate, stacked
function beforeSwap(address origin, PoolKey calldata key, SwapParams calldata params)
external
returns (uint24 feePips, uint8 rebatePct, bool veto);
function afterSwap(PoolKey calldata key, SwapParams calldata params, BalanceDelta delta) external;
}
contract Example is ILog {
using PoolIdLibrary for PoolKey;
/// The v4 Marketplace hook stack (the Dam): the only caller.
address public immutable hookStack;
uint256 public constant COOLDOWN = 30; // seconds between two swaps of one wallet
address public constant AGENT_REGISTRY = 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432; // ERC-8004 identity registry
uint8 public constant AGENT_REBATE = 25; // percent off the fee
uint8 internal constant COOLING_DOWN = 1;
mapping(PoolId => mapping(address => uint256)) public lastSwapAt;
error NotHookStack();
constructor(address hookStack_) {
hookStack = hookStack_;
}Which one
Launching a coin in a pool you open yourself, with a tax or a lock: a hook of its own. A rule you want many pools to use, paid on every swap that runs it: a stacked hook, listed from v4 Builder in the same flow. See listing on v4 Marketplace.
