Skip to main content

The Uniswap v4 hook

CovenantHook is the brain of the protocol. The entire mechanism is defined by three callbacks; everything else is parameters.

The callbacks

CallbackPhaseWhat it does
beforeSwappre-settlementDetects buy/sell direction, levies the 3% ETH tax, absorbs the covenant queue on buys, and retains 20% of the net on sells — all via returned deltas.
afterSwappost-settlementOn sells: skims the creator's ETH share from the realized output and books the covenant (size, frozen discount, expiry) into the queue.
expireMany / expireOnepermissionlessConverts matured covenant debt plus its share of reserve ETH into the locked protocol-owned LP position.

The hook returns deltas, so it needs the v4 permissions beforeSwapReturnDelta and afterSwapReturnDelta (the latter is required to take ETH out of a sell's realized output).

Delta accounting

Covenant is exact-input only (ExactOutputNotSupported reverts exact-output swaps). The delta convention:

  • BUYbeforeSwap returns deltaSpecified = +(tax + ethUsed) and deltaUnspecified = -tokensFromCovenant. The tax is accrued as an ETH claim for the creator (flushed by flushTax, which the router calls after each swap); absorbed ETH becomes the vault's claim; absorbed CVN is delivered by burning the vault's claims.
  • SELLbeforeSwap returns deltaSpecified = +covenantTokens (the retention). The 3% ETH tax is skimmed from the realized output in afterSwap (hookDeltaUnspecified = +ethToCreator) and paid to the creator immediately.

Immutable configuration

The hook's configuration is fixed at deployment — there are no admin setters:

ImmutableMeaning
creatorThe 3% tax recipient and seed operator. Cannot be changed.
tokencurrency1 — the CVN token.
queueThe CovenantQueue address.
vaultThe CovenantVault address.
poolInitializerThe only account allowed to bind the canonical pool.

The poolInitializer front-run guard

The hook's address is a mined CREATE2 address known before the pool exists. Without a guard, an open afterInitialize could be front-run between deploy and initialize — binding the hook to a junk fee tier or price and bricking the deployment. So afterInitialize requires sender == poolInitializer. (The Queue and Vault use the same deployer-only initHook pattern.)

Constants

uint256 public constant RETENTION_BPS = 2_000; // 20% of net-of-tax sell retained
uint256 public constant BASE_DISCOUNT_BPS = 500; // 5% floor
uint256 public constant MAX_DISCOUNT_BPS = 2_000; // 20% cap
uint256 public constant TAX_BPS = 300; // 3% buy & sell, in ETH, to creator
uint256 public constant BPS = 10_000;

The contracts are immutable — no upgrades, no admin keys. See all parameters in Parameters, and note the documented implementation choices in Known risks.