Building a v4 hook? Pick what it does, set the numbers, and deploy it as your own hook or stacked on v4 Marketplace. Open the builder ↗
v4 Builder docs
Build a v4 hook from modules

Own hook or stacked hook

One recipe, two contracts. Pick a hook of its own when you open the pool yourself and want it to run alone, or a stacked hook when the pool lives on v4 Marketplace and shares its swaps with other hooks.

At a glance

Own v4 hookStacked on v4 Marketplace
ContractOpenZeppelin BaseHookILog
Called byThe PoolManager, on the pools opened with itThe hook stack (Dam), on the pools that stack it
AddressMined: the low 14 bits are its callbacksAny address
ModulesAll ten8: no swap tax, no liquidity lock
A refused swapReverts with a named errorAnswers with a veto
The swappertx.originThe origin the hook stack passes in
FeesOverrides the LP fee of a dynamic fee poolProposes a fee; the stack takes the highest
Goes liveWhen you open a pool with itWhen listed with 10,000 $V4M staked
EarnsWhatever its swap tax sendsA royalty of 0 to 10 bps per swap

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.
Anti-snipe, cooldown and liquidity lock: the permissions it declares
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.

Fair Launch declares afterInitialize, beforeRemoveLiquidity, beforeSwap: flags 0x1280. Its live address is 0xFA85FfB4445faF97210E14aAE4d3eFB8dc121280, ending in 1280.

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.

ILog
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.
  • afterSwap is 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

beforeSwap
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.