Hooks
Hooks and permissions
What a Uniswap v4 hook is, how the 14 flags in its address decide which calls it gets, and which flags Socket's hooks use.
A hook is a contract that Uniswap v4's PoolManager calls at fixed points of a pool's life: when it is initialized, when liquidity is added or removed, around each swap and around donations. A pool names its hook in its key, so the hook is fixed for the pool's life. A pool with hook address(0) has no hook.
Flags in the address#
PoolManager doesn't ask a hook what it wants to be called for. It reads the lowest 14 bits of the hook's address. Each set bit turns on one call:
| Bit | Value | Flag | Called |
|---|---|---|---|
| 13 | 8192 | beforeInitialize | Before the pool's state is set. |
| 12 | 4096 | afterInitialize | After the pool is initialized, with its starting price and tick. |
| 11 | 2048 | beforeAddLiquidity | Before liquidity is added. |
| 10 | 1024 | afterAddLiquidity | After liquidity is added, with the caller's delta and feesAccrued. |
| 9 | 512 | beforeRemoveLiquidity | Before liquidity is removed, including a removal of zero liquidity. |
| 8 | 256 | afterRemoveLiquidity | After liquidity is removed, with the caller's delta and feesAccrued. |
| 7 | 128 | beforeSwap | Before the swap runs against the pool. |
| 6 | 64 | afterSwap | After the swap, with its delta. |
| 5 | 32 | beforeDonate | Before a donation to in-range LPs. |
| 4 | 16 | afterDonate | After a donation. |
| 3 | 8 | beforeSwapReturnDelta | beforeSwap may return a delta. |
| 2 | 4 | afterSwapReturnDelta | afterSwap may return a delta. |
| 1 | 2 | afterAddLiquidityReturnDelta | afterAddLiquidity may return a delta. |
| 0 | 1 | afterRemoveLiquidityReturnDelta | afterRemoveLiquidity may return a delta. |
A hook gets its bits by being deployed with CREATE2 at a mined salt. Different permissions mean a different address, so a different contract. PoolManager checks the address when a pool is initialized and refuses it if:
- a returns-delta flag is set without its call's flag;
- the address is a hook with no flags at all, and the pool's fee isn't dynamic.
Every call must return the selector of the function PoolManager called. A wrong answer reverts with InvalidHookResponse. When a hook itself calls PoolManager for its pool, its own callbacks aren't called.
Try a combination:
Returns-delta#
Without a returns-delta flag, a hook can read an operation, keep its own state or revert, but it can't move amounts between itself and the caller. In a dynamic-fee pool it can still set the LP fee. With a returns-delta flag, the call returns a delta: positive when the hook is owed or took currency, negative when it owes or sent currency.
| Flag | The call returns | Effect |
|---|---|---|
| beforeSwapReturnDelta | BeforeSwapDelta, in the specified and unspecified currencies | Changes the amount that swaps against the pool. It can't turn an exact-input swap into an exact-output one, or back. |
| afterSwapReturnDelta | int128, in the unspecified currency | Adds to or takes from the side of the swap the swapper didn't fix. |
| afterAddLiquidityReturnDelta | BalanceDelta | Subtracted from the caller's delta for the addition, fees included. |
| afterRemoveLiquidityReturnDelta | BalanceDelta | Subtracted from the caller's delta for the removal, fees included. |
A hook's delta is recorded against the hook. It settles in the same transaction as everyone else's, or the transaction reverts.
Dynamic fees#
A pool's fee is either a static LP fee or the dynamic-fee flag, 0x800000. LP fees are in hundredths of a basis point: 1,000,000 is 100%, and 3,000 is 0.30%.
In a pool whose fee is 0x800000, the hook sets the LP fee:
- Per swap. beforeSwap returns a fee with the override flag
0x400000set. That fee applies to this swap only. - Stored. The hook calls
updateDynamicLPFee(key, fee)on PoolManager. A dynamic-fee pool starts with a stored fee of 0.
A fee above 1,000,000 reverts. A pool with no hook can't use the dynamic-fee flag.
Socket's hooks#
Each built-in hook is its own contract with its own flags. All three carry the four flags of the buyback share: afterAddLiquidity, afterRemoveLiquidity and both of their returns-delta flags.
| Hook | Flags | Low bits |
|---|---|---|
| Dynamic fee | afterInitialize, beforeSwap, afterSwap, plus the four buyback flags | 0x15C3 |
| TWAP oracle | afterInitialize, beforeSwap, afterSwap, plus the four buyback flags | 0x15C3 |
| Range order | afterInitialize, afterSwap, plus the four buyback flags | 0x1543 |
A pool on the dynamic-fee hook has the dynamic-fee flag as its fee. None of the three has a swap returns-delta flag. Swappers pay the pool's fee and get what the pool's curve gives. The liquidity flags take the buyback share from the fees a position collects.
External hooks and the allowlist#
Any pool can name any hook. PoolManager enforces v4's rules for all of them, and a router can't tell a reviewed hook from one that isn't.
Socket keeps that list on chain. HookRegistry holds the hooks Socket's app routes through, and SOCKET holders vote it:
- Socket's built-in hooks are on the list from the start.
- Any other hook is added by a vote, with a stake from its author. See Write a hook.
- Pools with no hook are always routable.
The app routes only through pools with no hook or a listed hook. Listed hooks outside the built-ins show as External hook in mustard.
Next: Limits lists what holds whatever a hook does.