V4 Hooks Architecture

Uniswap V4 introduces hook contracts — permissionless plugins that execute custom logic at every step of a swap lifecycle. Hooks can modify swap behavior, implement limit orders, charge dynamic fees, or create entirely new AMM primitives without changing the core pool contract.

🔄 Hook Lifecycle — Execution Order

Every V4 swap passes through a deterministic sequence of hook callbacks. The diagram below shows which hooks fire and in what order for a complete swap cycle.

V4 Hook Execution Order PoolManager initializePool() Hook: beforeInitialize can modify sqrtPriceX96 Pool Created at CREATE2 address addLiquidityLock() Caller → PoolManager beforeAddLiquidity validate / modify params afterAddLiquidity hook finalization swap() exactInput / exactOutput beforeSwap can revert / modify getFee dynamic fee lookup afterSwap hook settlement removeLiquidity() burn position tokens beforeRemoveLiquidity hook pre-checks afterRemoveLiquidity hook callback Hook callback PoolManager action

🎣 Hook Type 1: Limit Order Hook

A limit order hook listens for beforeSwap and only allows the swap if the price is at or beyond a specified boundary. The afterSwap callback is where the actual fill is recorded and the order is updated.

function beforeSwap(address sender, PoolKey calldata key) external view {
require(poolState.sqrtPriceX96 >= LIMIT_PRICE, "Not yet");
// price hasn't crossed limit — skip this swap
}
💡 Result: Users can post a "buy ETH at $1,800" limit order. The order sits dormant until the pool's spot price reaches $1,800 — then it fills at exactly that price, like a traditional limit order on a centralized exchange.

🌊 Hook Type 2: TWAMM (Time-Weighted AMM)

TWAMM amortizes large orders over time as a series of micro-swaps. The hook intercepts beforeSwap to record the order, then afterSwap to execute the time-fraction and accumulate the result.

struct TWAPOrder { uint256 amount; uint256 startTime; uint256 duration; }
mapping(address ⇒ TWAPOrder) orders;
function beforeSwap(...) {
uint256 elapsed = block.timestamp − order.startTime;
uint256 microSwaps = elapsed / INTERVAL;
uint256 toSwap = order.amount × microSwaps / order.duration;
}
💡 Key advantage: TWAMM prevents MEV sandwiching of large orders by spreading the order over hours or days, making it economically infeasible to attack each micro-swap individually.

🌐 Hook Type 3: Dutch Auction Hook

A Dutch auction hook starts with a high price and decreases it linearly over time. Used for ICOs, token sales, or protocol treasury liquidation. The getFee callback dynamically returns the current auction price.

function getFee(...) view returns(uint256 fee) {
uint256 elapsed = block.timestamp − auctionStart;
uint256 decay = startPrice × elapsed / duration;
return startPrice − decay; // Dutch decay
}

🔗 Hook Type 4: Dynamic Fee Hook

Rather than a static fee tier, the getFee hook can implement any fee-setting logic: volatility-based fees, momentum fees, cross-asset fees, or governance-controlled fees. The fee is computed per-swap, not per-pool.

function getFee(...) view returns(uint256) {
uint256 vol = oracle.getVolatility(key.token0, key.token1);
if (vol > HIGH_VOL_THRESHOLD)
return FEE_10BP; // 1.0% in high vol
return FEE_3BP; // 0.30% normal
}

🧩 Interactive: Build a Custom Hook

Toggle different hook capabilities to see how the pool behavior changes. Each toggle enables or disables a specific callback behavior:

How V4 singleton architecture works

V3 uses a separate contract for each pool (deploy per pair, deploy per fee tier). V4 uses a single PoolManager contract that holds all pools, with pool identity determined by the PoolKey tuple (token0, token1, fee, tickSpacing, hooks). The hook address is part of the PoolKey and is baked into the pool's CREATE2 address — once deployed, the hook for a pool can never be changed, which protects LPs from rug-pull fee changes.

Because all pools share one contract, cross-pool operations like multi-hop swaps in a single transaction become gas-cheaper and simpler to coordinate. The native ETH token is used for gas instead of separate Wrapped ETH deposits, further reducing overhead. Anyone can deploy a new hook contract and create a pool referencing that hook — V4 permissionlessly extends the AMM without Uniswap Labs' involvement.

Key concepts

PoolKey
A struct encoding (token0, token1, fee, tickSpacing, hooks). The hook address is a parameter in the PoolKey, so the pool's CREATE2 address is a function of the hook — once deployed, the hook cannot be swapped out, protecting LPs from post-deployment hook changes.
Hook permissions bitmap
A 248-bit bitmap in the hook contract that declares which callback functions the hook implements. The PoolManager checks this bitmap before calling any hook to avoid unnecessary calls. A hook can implement any subset of the 7 callbacks.
beforeSwap / afterSwap
Called immediately before and after the core swap math (x·y=k with dynamic fee from getFee). beforeSwap can revert the swap or modify swap parameters; afterSwap can record the result, update state, or emit custom events.
getFee
The most powerful hook primitive. Called inside the swap math and expected to return a fee numerator — not a basis-point amount but a fraction of the input. A hook returning 300 = 0.03% (matching the 0.30% tier) is equivalent to the standard pool fee, but arbitrary values like 150 (0.015%) or 500 (0.05%) can be returned based on any on-chain logic.
CREATE2 and hook immutability
V4 pools are deployed using CREATE2 with a salt derived from the PoolKey, so anyone can compute a pool's address before it exists. The hook address in the PoolKey means that a malicious operator cannot deploy a pool with a legitimate-looking hook address — LPs can verify pool authenticity by checking the CREATE2 address independently.
Flash accounting
V4 nets all token transfers in a transaction using internal balance accounting instead of actual ERC-20 transfers per pool. This means a multi-hop swap through ETH/USDC → USDC/DAI routes efficiently without multiple token transfer events, dramatically reducing gas for complex routing.