Overview
How Socket works
A Uniswap v4 pool, the hook in its key, the permissions in the hook's address, and the order a swap runs in.
A pool#
Every pool Socket uses is a Uniswap v4 pool. All v4 pools live in one contract, PoolManager, which holds every pool's tokens. A pool is a concentrated-liquidity market for two tokens, sorted by address into currency0 and currency1. Native ETH is address(0), so a pool can trade it without wrapping. The pool's price is the amount of currency1 one currency0 buys, in base units, stored as a Q64.96 square root. Liquidity providers deposit into a price range; their liquidity only trades, and only earns fees, while the price is inside that range.
A pool is identified by its key. Every field of it is fixed when the pool is initialized:
| Field | What it is |
|---|---|
currency0, currency1 | The two tokens, sorted by address. |
fee | The LP fee in hundredths of a basis point (3,000 is 0.30%), or 0x800000, the dynamic-fee flag, which lets the hook set it. |
tickSpacing | The grid positions sit on, from 1 to 32,767 ticks. |
hooks | The hook contract, or address(0) for none. |
The starting price is set at initialization; liquidity added afterwards sets where it trades. A pool on one of Socket's hooks also fixes, in the transaction that initializes it:
| Setting | What it is |
|---|---|
| Buyback share | The share of collected LP fees the hook sends to the Buyback, read from HookRegistry: 20% today. |
| Fee settings | For the dynamic fee: the base fee, the max fee and the settings of the fee curve, chosen by the pool's creator. |
A hook#
A hook is a contract PoolManager calls at fixed points of a pool's life:
- Initialize, before and after the pool is created.
- Add liquidity and remove liquidity, before and after every position change.
- Swap, before and after every swap.
- Donate, before and after a donation to the pool's liquidity.
The hook receives the pool's key, the call's parameters and any hookData the caller passes. The after-calls also see the result: afterSwap gets the swap's balance change, and afterAddLiquidity and afterRemoveLiquidity get the position's balance change and feesAccrued, the fees the position collects in that call.
A hook changes amounts in two ways. In a pool whose fee is the dynamic-fee flag, the hook sets the LP fee, and beforeSwap can return an override for this swap, marked with 0x400000. With a returns-delta flag, a call can return a delta that changes what the caller settles: a swap's amounts, or what a position change pays in or out.
When the hook itself calls PoolManager for its pool, its own callbacks aren't called.
Permissions#
The lowest 14 bits of a hook's address are its permissions. PoolManager makes only the calls whose bits are set:
| Bit | Value | Flag |
|---|---|---|
| 13 | 8192 | beforeInitialize |
| 12 | 4096 | afterInitialize |
| 11 | 2048 | beforeAddLiquidity |
| 10 | 1024 | afterAddLiquidity |
| 9 | 512 | beforeRemoveLiquidity |
| 8 | 256 | afterRemoveLiquidity |
| 7 | 128 | beforeSwap |
| 6 | 64 | afterSwap |
| 5 | 32 | beforeDonate |
| 4 | 16 | afterDonate |
| 3 | 8 | beforeSwapReturnDelta |
| 2 | 4 | afterSwapReturnDelta |
| 1 | 2 | afterAddLiquidityReturnDelta |
| 0 | 1 | afterRemoveLiquidityReturnDelta |
- A returns-delta flag needs its call's flag. PoolManager won't initialize a pool whose hook breaks that rule.
- A hook gets its bits by being deployed with CREATE2 at a mined salt. Different permissions mean a different address, so a different contract.
- The address is part of the pool key, so a pool's permissions never change.
Socket's built-in hooks set these flags:
| Hook | Flags |
|---|---|
| Dynamic fee | afterInitialize, beforeSwap, afterSwap, and the four buyback flags |
| TWAP oracle | afterInitialize, beforeSwap, afterSwap, and the four buyback flags |
| Range order | afterInitialize, afterSwap, and the four buyback flags |
The four buyback flags are afterAddLiquidity, afterRemoveLiquidity, afterAddLiquidityReturnDelta and afterRemoveLiquidityReturnDelta. None of the built-in hooks has a swap returns-delta flag. A swap through them pays the pool's LP fee, which only the dynamic-fee hook sets.
The order a swap runs in#
A swap runs inside one unlock of PoolManager. The router calls unlock, PoolManager calls back into the router, and the router calls swap. PoolManager calls beforeSwap if the hook's address sets it, runs the pool math, then calls afterSwap. Each step records what each party owes or is owed. The router pays in what it owes and takes out what it is owed. If anything is left unsettled when unlock ends, or any hook call reverts, the whole transaction reverts and nothing moves. Only the gas is spent.
Socket's app quotes every route through v4's Quoter and executes through the Universal Router with Permit2. The router checks the minimum output you sign after the swap, whatever the pool's hook did. The app routes only through pools with no hook or a hook on the allowlist.
Liquidity#
Positions are ERC-721 tokens from PositionManager. A position holds one range and its liquidity, and earns its share of the LP fee in both tokens. Any change of a position collects its fees, including a removal of zero liquidity, which is how fees are collected. Adding and removing liquidity run the liquidity hooks the hook's address sets, which is how a hook can enforce a lockup, admit only certain ranges or track incentives.
In a pool on one of Socket's hooks, afterAddLiquidity and afterRemoveLiquidity receive the position's feesAccrued. The hook takes the pool's buyback share of it and sends that to the Buyback contract, which can only swap toward SOCKET and burn it. The position collects the rest. Swaps and quotes are untouched. Pools on External hooks, and pools with no hook, keep no buyback share. See The buyback.
Deposits round up and withdrawals round down, so rounding never takes value from the pool.
Socket's contracts#
| Contract | Does |
|---|---|
DynamicFeeHook | The dynamic fee. |
TwapHook | The TWAP oracle. |
RangeOrderHook | Range orders. |
HookRegistry | The allowlist, the listing stakes, and the buyback share new pools on Socket's hooks take. Its authority is the governance timelock. |
SocketGovernor + timelock | Voting with deposited SOCKET. |
Buyback | Receives the buyback share. It can only swap toward SOCKET and burn it. |
See Socket on Uniswap v4 for how they connect.
What a hook can't do#
These bounds come from PoolManager, not from the hook's good behavior:
- It isn't called at points its address doesn't set.
- It can't change a swap's or a position's amounts without the matching returns-delta flag.
- It can't set a swap's fee unless the pool's fee is the dynamic-fee flag.
- It can't leave a balance unsettled: anything owed when
unlockends reverts the whole transaction. - It can't change its own permissions: they are its address.
- It can't make a swap pay less than the minimum output you sign: the router reverts it.
The app routes only through pools with no hook or a hook SOCKET holders listed. See Limits for each bound in detail.