Contents

Docs · Referencehookr.fun/docs · 23 Sep 2026

Hook permissions

Read the six permission bits the root hook's own address encodes, what each one is for, and the separate dynamic-fee flag.

The default Hookr root hook is HookrModularHookV6. Its address is mined so the low fourteen bits equal 0x28cc, which is six permissions.

Every other Hookr root carries the same word. The live arbitrage-recapture root 0xb914f955294799de4b891bd2EA8AF628Fa1c68CC answers REQUIRED_FLAGS 10444, 0x28cc, and so do the three roots it supersedes, so the permission set on this page describes all of them. What differs between roots is the code behind the address and the profile sealed against it, not what the PoolManager is allowed to call. Allowlisting, by contrast, is per address and carries over to nothing.

The Permissions Struct

Hooks.Permissions({
    beforeInitialize: true,
    afterInitialize: false,
    beforeAddLiquidity: true,
    afterAddLiquidity: false,
    beforeRemoveLiquidity: false,
    afterRemoveLiquidity: false,
    beforeSwap: true,
    afterSwap: true,
    beforeDonate: false,
    afterDonate: false,
    beforeSwapReturnDelta: true,
    afterSwapReturnDelta: true,
    afterAddLiquidityReturnDelta: false,
    afterRemoveLiquidityReturnDelta: false
});

The root does not inherit BaseHook and does not expose getHookPermissions(). It pins the same set as a constant instead:

uint160 public constant REQUIRED_FLAGS =
    uint160((1 << 13) | (1 << 11) | (1 << 7) | (1 << 6) | (1 << 3) | (1 << 2));

That value is 10444 decimal, 0x28cc hex. The CREATE2 mining target is requiredFlags = 0x28cc under mask = 0x3fff, and the accounting kernel's own REQUIRED_FLAGS is checked against the root's at construction.

Bit Mapping

BitFlagPermissionSet
13BEFORE_INITIALIZE_FLAGbeforeInitializeyes
12AFTER_INITIALIZE_FLAGafterInitializeno
11BEFORE_ADD_LIQUIDITY_FLAGbeforeAddLiquidityyes
10AFTER_ADD_LIQUIDITY_FLAGafterAddLiquidityno
9BEFORE_REMOVE_LIQUIDITY_FLAGbeforeRemoveLiquidityno
8AFTER_REMOVE_LIQUIDITY_FLAGafterRemoveLiquidityno
7BEFORE_SWAP_FLAGbeforeSwapyes
6AFTER_SWAP_FLAGafterSwapyes
5BEFORE_DONATE_FLAGbeforeDonateno
4AFTER_DONATE_FLAGafterDonateno
3BEFORE_SWAP_RETURNS_DELTA_FLAGbeforeSwapReturnDeltayes
2AFTER_SWAP_RETURNS_DELTA_FLAGafterSwapReturnDeltayes
1AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAGafterAddLiquidityReturnDeltano
0AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAGafterRemoveLiquidityReturnDeltano

The authoritative bit list is Hooks.sol in v4-core.

Note the two spellings. Hooks.Permissions uses beforeSwapReturnDelta and afterSwapReturnDelta. Uniswap's hooklist schema uses beforeSwapReturnsDelta and afterSwapReturnsDelta. The hooklist entry uses the hooklist spelling; everything else uses the struct spelling.

What v4-core Actually Checks

Three facts from the pinned v4-core submodule, src/libraries/Hooks.sol at commit 46c6834, which is the copy these contracts were built against. They are worth stating exactly, because each one is easy to remember backwards.

The return-delta dependency checks always run.isValidHookAddress opens with four of them (Hooks.sol:111-120): a return-delta bit without its corresponding action bit returns false, before the fee is consulted at all. A hook address mined with beforeSwapReturnDelta and without beforeSwap is invalid for every pool, dynamic fee or not.

The dynamic fee waives one rule and only one. The final clause (Hooks.sol:124-126) is that a hook with a non-zero address must have at least one flag set, or the pool must use a dynamic fee. That is the whole of the exemption. Nothing short-circuits, and a dynamic-fee pool does not get to skip the dependency checks above it.

An undeclared return delta is discarded in silence.callHookWithReturnDelta is if (!parseReturn) return 0; (Hooks.sol:163). A hook that returns a delta it did not mine the flag for is not rejected: its delta is dropped, the callback appears to succeed, and the transaction then fails at settlement because the hook has already moved value. The failure surfaces nowhere near its cause.

Hookr does not rely on any of this. HookrStackRegistryV2 re-derives a root's flags from its own address and refuses a registration whose declared hookFlags disagree, reverting InvalidHookFlags(declared, actual), and it repeats the four return-delta dependency checks itself and reverts InvalidHookFlagDependencies (HookrStackRegistryV1.sol:1038-1039 and :1049-1054). A root whose address was mined wrong never reaches a pool, because it never gets registered as a kernel.

What Each Permission Is For

beforeInitialize

Accepts pool initialization only from HookrMarketCoordinatorV5, only once per PoolId, and only when the PoolKey matches the stack the registry already froze for that PoolId. This is what makes a Hookr pool impossible to create by any path other than the coordinator, and what guarantees every pool that names this hook has a frozen configuration behind it.

beforeAddLiquidity

Runs the module's liquidity check. Its only effect today is the guard-window lock: while a new-token pool's guard window is open, any sender other than the coordinator reverts ExternalLiquidityBlockedDuringGuard. Outside the window it returns true for everyone.

There is no corresponding removal permission. Removing liquidity is never hooked, so no Hookr rule can trap an LP position.

beforeSwap

Sets the effective LP fee for this swap and, on an exact-input buy, takes the quote-side cuts as a BeforeSwapDelta on the specified currency.

afterSwap

Takes the protocol share on the unspecified quote leg, applies the auto-burn to subject output, and updates the guard and pot counters. It returns an int128 delta on the unspecified currency.

beforeSwapReturnDelta and afterSwapReturnDelta

Both are required because the hook moves value in both phases. beforeSwapReturnDelta carries the LP-reward, pot and protocol takes off the specified quote input. afterSwapReturnDelta carries the burn and the unspecified-leg protocol take.

A hook with a return-delta permission can hold value outside the curve. Hookr's is bounded twice: the catalog registration fixes a structural ceiling on what any module may ever request, and the pool's own frozen StackLimits fix a lower per-pool ceiling that the accounting kernel enforces on every callback.

Arbitrage Recapture Needs Neither

The correction lane on a recapture root moves no value through the hook, so it adds no flag. The nested callbacks the correction makes return BeforeSwapDeltaLibrary.ZERO_DELTA from beforeSwap and 0 from afterSwap (HookrSwapKernelV3.sol:128-132 and :163-167), and the published correction library contains no donate, mint, take, sync or settle. The partner's executor opens its own lock on the PoolManager and settles its own trade under its own address.

That is why 0xb914f955294799de4b891bd2EA8AF628Fa1c68CC is mined to the same 0x28cc as the default root and not to something wider. The two return-delta bits in that word are there for the five native rules, which do move value in both phases. A reader who assumes a correcting hook must need extra permissions is reading the lane backwards: the recapture root asks the PoolManager for nothing the default root does not already ask for. See HookrModularHookV6WthV5.

The Dynamic Fee Flag

Every Hookr pool sets PoolKey.fee = 0x800000. The coordinator refuses to open a pool with any other value, and the router and quoter both refuse to serve one.

The hook sets the fee per swap by returning an override from beforeSwap. It also exposes syncBaseFee(PoolKey), to be called at the root hook's address (it runs in the accounting kernel by DELEGATECALL; the PoolManager accepts a dynamic-fee update only from the pool's hook), which pushes the pool's frozen baseLpFeePips into the PoolManager's dynamic-fee cache for any caller that reads it directly.

Fees are in pips: 3,000 pips is 0.30%, and MAX_TOTAL_FEE_PIPS = 500_000 is 50%.

Hooklist Entry

The hooklist entry carries this flag set in the shape Uniswap's hooklist expects, with the default root hook's live address filled in. A recapture root would need its own entry at its own address, carrying this same flag set. It names no audit report, because none exists. See Hooklist and routing.