Hooks / Risk
DepegShield
Makes leaving a peg expensive and returning to it cheap, in proportion to how far the pool has already strayed.
DepegShield implements 2 of the fourteen Uniswap v4 callbacks:
afterInitialize, beforeSwap.
How it works
A pegged pool fails in a specific way. Something spooks the market, the first sellers cross, the price slips, and the slip is itself the signal that brings the next sellers. Liquidity providers are filled the whole way down at a fee that was set for a pool sitting at par.
By the time anyone reacts, the pool is one-sided and the providers own the asset that broke. This hook makes the fee a function of two things: how far the pool is from the peg, and which way the swap pushes it. A swap that widens the gap pays `baseFee` plus a surcharge that grows with the existing deviation.
A swap that closes the gap pays less than `baseFee`, down to a floor, with the discount growing the same way. The result is a spread that opens as the pool strays and pays anyone willing to push it back. widening: fee = baseFee + maxSurcharge * deviation / (deviation + halfDeviation) restoring: fee = baseFee - (baseFee - minFee) * deviation / (deviation + halfDeviation) Both are LP fees, so the surcharge is paid to liquidity and the discount is given up by liquidity.
That is the right trade for a provider in a pegged pool: paying for the flow that repairs the pool is cheaper than being filled on the way out. The peg is a tick, not an oracle. `pegTick = 0` is a one-to-one pool; a pair whose par is not one-to-one sets the tick that corresponds to par.
Since it is fixed at initialization, there is nothing to manipulate and no feed to go stale, and a pool whose peg genuinely re-bases has to be re-created, which for a pegged pair is the honest outcome. Deviation is measured in ticks. One tick is one basis point to within rounding, so `halfDeviationTicks = 50` means half the surcharge applies once the pool is fifty basis points off par.
Prior art: stable-swap curves flatten the price impact near par, and dynamic-fee hooks keyed on volatility exist. Neither is directional. A curve treats a swap toward the peg and a swap away from it identically, and a volatility fee charges the repairing flow exactly as much as the flow that broke the pool.
Charging asymmetrically by direction of travel is what is new here.
Prior art
Stable-swap curves flatten price impact near par, and dynamic-fee hooks keyed on volatility exist. Neither is directional: a curve prices a swap toward the peg and one away from it identically, and a volatility fee charges the repairing flow exactly as much as the flow that broke the pool. Charging asymmetrically by direction of travel is what is new.
Where it does not help
The peg is fixed at initialization, so a pair whose par genuinely re-bases has to be re-created. For a pegged pair that is the honest outcome, but it does mean this is the wrong hook for a drifting reference such as a yield-bearing wrapper.
Configuring a pool
Uniswap v4 removed hookData from initialize, so a hook that needs per-pool parameters has to
receive them out of band. DepegShield takes them through configure, which anyone may call for a pool
key whose pool does not exist yet, and which nobody may call afterwards. The parameters are part of what the pool
is, so they are fixed for its lifetime.
// 1. Fix the terms, before the pool exists.
hook.configure(
key,
DepegShieldHook.Config({
pegTick: /* int24 */ 0,
baseFee: /* uint24 */ 0,
minFee: /* uint24 */ 0,
maxSurcharge: /* uint24 */ 0,
halfDeviationTicks: /* uint24 */ 0
})
);
// 2. Initialize the pool. The hook rejects a pool it was never configured for.
poolManager.initialize(key, startingSqrtPriceX96);
| Parameter | Type | Units |
|---|---|---|
pegTick | int24 | tick |
baseFee | uint24 | hundredths of a bip (3000 = 0.30%) |
minFee | uint24 | hundredths of a bip (3000 = 0.30%) |
maxSurcharge | uint24 | hundredths of a bip (3000 = 0.30%) |
halfDeviationTicks | uint24 | tick |
From TypeScript
The SDK ships the catalogue, the address book and the pool-key helpers, so a client never hardcodes an address or recomputes a pool id by hand.
npm i @hookforge/sdk
import {getHook, hookAddress, poolKeyFor, poolId} from "@hookforge/sdk";
const hook = getHook("depeg-shield");
const address = hookAddress("depeg-shield", 1);
const key = poolKeyFor({hook: address, currencyA: USDC, currencyB: WETH, tickSpacing: 60, dynamicFee: true});
console.log(poolId(key));
What it reverts with
| Error | Meaning |
|---|---|
FeeTooLarge(uint24) | A fee was configured above the protocol maximum of 100%. |
InvalidConfig() | `halfDeviationTicks` was zero, or `minFee` exceeded `baseFee`. |
NotDynamicFee() | The hook was attempted to be initialized with a non-dynamic fee. |
PoolAlreadyInitialized() | The pool already exists, so its configuration is final. |
PoolNotConfigured() | The pool was initialized without a configuration for this hook. |
SurchargeTooLarge() | `baseFee + maxSurcharge` must leave room under the 100% protocol maximum. |
Addresses
| Chain | Chain ID | Address |
|---|---|---|
| unichain | 130 | 0x6536608422113CFF4cbDb9c413E447EEd1fF1080 |
| robinhood | 4663 | 0x6D7B49F497e6Dec999147dF461038AE312609080 |
| base | 8453 | 0xFe88b1eECB9AeFF393f4E7a7fFBB1f80392D9080 |
| arbitrum | 42161 | 0xb888b2e1886d4C703c53C2a1650ced1262bCd080 |
These addresses are deterministic, not live. They are the CREATE2 addresses the deploy script mines so that the low fourteen bits encode this hook's permissions. Until the deploy runs on a chain, there is no code at them. Check before you send anything anywhere.
Source and verification
The contract is contracts/src/hooks/DepegShieldHook.sol,
and everything on this page is generated from it: the prose is its NatSpec, the parameters are its
configure ABI, the callbacks above are the flags it declares, and the tags are the strings its own
hookTags() returns. A hook cannot be documented here as something it is not.
Ask the deployed contract what it is and it answers directly, with no registry in the loop:
cast call $HOOK "hookName()(string)" # DepegShield
cast call $HOOK "specURI()(string)" # https://hookforge.pages.dev/schema/hooks/depeg-shield.json
cast call $HOOK "hookTags()(string[])" # risk, stablecoin, dynamic-fee, oracle-free