# v4hooks full catalog ## AllowlistSwap (allowlist-swap) Kind: pattern Gate-controlled per-pool swap allowlist; beforeSwap reverts for unlisted traders. First-party teaching hook inspired by Uniswap Permissioned Pools. An immutable gate address sets allowed[poolId][trader]. beforeSwap resolves the trader from hookData (abi.encode address) or the swap sender and reverts Unauthorized if not listed. No permissions-adapter stack — minimal allowlist gate only. Experimental. Forge tests in test/AllowlistSwap.t.sol. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata, bytes calldata hookData) internal view override returns (bytes4, BeforeSwapDelta, uint24) { address trader = traderOf(sender, hookData); if (!allowed[key.toId()][trader]) revert Unauthorized(); return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/AllowlistSwap.sol Status: experimental License: MIT Categories: compliance Properties: vanilla-swap Chains: ethereum Flags: beforeSwap Page: https://v4hooks.com/hooks/allowlist-swap ## AntiSandwichHook (anti-sandwich) Kind: pattern Checkpoint top-of-block pool state and cap one swap direction to that price. OpenZeppelin’s AntiSandwichHook (MIT) saves Slot0 and tick liquidity at the first swap of a block. Later swaps in the protected direction cannot fill better than that beginning-of-block price; the other direction still follows xy=k. Permissions need beforeSwap, afterSwap, and afterSwapReturnDelta. Inherit it and implement _handleCollectedFees. Large tick walks can OOG on small tickSpacing — read the file warnings. Experimental library code, not an audit. Prefer this over inventing MEV protection from scratch. ### Solidity ```solidity // SPDX-License-Identifier: MIT // OpenZeppelin Uniswap Hooks — excerpt. Full file: source.url function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: false, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: true, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } // First swap in a block: checkpoint Slot0 + ticks. // Later !zeroForOne swaps: _getTargetUnspecified simulates against that checkpoint // and afterSwap enforces the target via afterSwapReturnDelta. ``` Source: https://github.com/OpenZeppelin/uniswap-hooks/blob/master/src/general/AntiSandwichHook.sol Status: experimental License: MIT Categories: mev-protection Properties: vanilla-swap Chains: ethereum Flags: beforeSwap, afterSwap, afterSwapReturnDelta Website: https://v4hooks.com/learn/openzeppelin-hooks Docs: https://github.com/OpenZeppelin/uniswap-hooks Page: https://v4hooks.com/hooks/anti-sandwich ## AntiSnipe (anti-snipe) Kind: pattern Launch guard — max buy (% supply), snipe tax on exact-input buys for N blocks after init. First-party teaching hook inspired by Spark's Anti-Snipe launchpad block (usespark.fun/builder). afterInitialize records graduationBlock and requires native currency0 + dynamic fee. beforeSwap applies snipeTax via OVERRIDE_FEE_FLAG on exact-input buys while inGuardWindow; after guard, baseLpFee. afterSwap caps token output to maxBuyBps of totalSupply using afterSwapReturnDelta (FullMath.mulDiv avoids supply overflow). Per-swap cap only — not per-wallet. Sells skip tax and cap. Experimental. Forge tests in test/AntiSnipe.t.sol. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT // v4hooks first-party — excerpt. Full file: source.url function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: true, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function _beforeSwap(address, PoolKey calldata key, SwapParams calldata params, bytes calldata) internal view override returns (bytes4, BeforeSwapDelta, uint24) { bool isExactInputBuy = params.zeroForOne && params.amountSpecified < 0; uint24 fee = feeFor(key.toId(), isExactInputBuy); return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee | LPFeeLibrary.OVERRIDE_FEE_FLAG); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/AntiSnipe.sol Status: experimental License: MIT Categories: launchpads, mev-protection, dynamic-fees Properties: dynamic-fee, vanilla-swap Chains: ethereum Flags: afterInitialize, beforeSwap, afterSwap, afterSwapReturnDelta Page: https://v4hooks.com/hooks/anti-snipe ## AutoBurn (auto-burn) Kind: pattern Burns burnBps of token output on exact-input buys via afterSwapReturnDelta. First-party teaching hook inspired by Spark Auto Burn. afterInitialize requires native currency0. afterSwap on exact-input buys (zeroForOne, amountSpecified < 0) takes burnBps of token1 output from the swapper via poolManager.take, transfers to DEAD, and returns the same amount as afterSwap return delta. Sells and exact-output swaps skip. Teaches output-side hook accounting without composing extra LP fee. Experimental. Forge tests in test/AutoBurn.t.sol. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT // v4hooks first-party — excerpt. Full file: source.url function _afterSwap(address, PoolKey calldata key, SwapParams calldata params, BalanceDelta delta, bytes calldata) internal override returns (bytes4, int128) { if (!params.zeroForOne || params.amountSpecified >= 0) return (this.afterSwap.selector, 0); int128 tokenOut = delta.amount1(); if (tokenOut <= 0) return (this.afterSwap.selector, 0); uint256 burnAmount = (uint256(int256(tokenOut)) * burnBps) / 10_000; if (burnAmount == 0) return (this.afterSwap.selector, 0); key.currency1.take(poolManager, address(this), burnAmount, false); IERC20(Currency.unwrap(key.currency1)).transfer(DEAD, burnAmount); return (this.afterSwap.selector, burnAmount.toInt128()); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/AutoBurn.sol Status: experimental License: MIT Categories: launchpads Properties: vanilla-swap Chains: ethereum Flags: afterInitialize, afterSwap, afterSwapReturnDelta Page: https://v4hooks.com/hooks/auto-burn ## BackGeoOracle (back-geo-oracle) Kind: pattern Geomean oracle that backruns its own pool so the recorded price is not stale. A geometric-mean TWAP oracle that solves the usual problem with in-pool oracles: the price you record is the price after someone traded against you, which is exactly the price an attacker chose. BackGeoOracle answers that by backrunning the swap itself in afterSwap and recording the corrected price, so the observation reflects a rebalanced pool rather than the post-trade extreme. Permissions are wide but each one is doing something specific. beforeInitialize is a configuration gate: it reverts unless fee is 0 and tickSpacing is MAX_TICK_SPACING, so only one oracle pool can exist per pair and liquidity cannot fragment. afterInitialize seeds the observation array. beforeAddLiquidity rejects anything that is not full range — minUsableTick to maxUsableTick — and updates the pool first; beforeRemoveLiquidity updates too, so observations stay current across liquidity moves. beforeSwap reverts on exact-output swaps (only exactIn is supported) and refreshes the pool. The interesting one is afterSwap, which calls _backrun to compute a corrective delta, then settles it with afterSwapReturnDelta — inverting zeroForOne and the sign of amountSpecified while keeping the specified currency the same. The fee for that correction is charged in the unspecified currency, the one the user is buying. Every callback is onlyPoolManager. The full-range and zero-fee constraints mean this is a purpose-built oracle pool, not a hook you bolt onto a trading pool. RigoBlock has an audit for this one — linked below — which makes it unusual among research-grade oracle hooks. Read the report, not just the excerpt. ### Solidity ```solidity // SPDX-License-Identifier: MIT // RigoBlock/back-geo-oracle — excerpt. Full file: source.url function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: true, beforeAddLiquidity: true, afterAddLiquidity: false, beforeRemoveLiquidity: true, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: true, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function _beforeInitialize(address, PoolKey calldata key, uint160) internal view override onlyPoolManager returns (bytes4) { // This is to limit the fragmentation of pools using this oracle hook. In other words, // there may only be one pool per pair of tokens that use this hook. The tick spacing is set to the maximum // because we only allow max range liquidity in this pool. if (key.fee != 0 || key.tickSpacing != TickMath.MAX_TICK_SPACING) { revert OnlyOneOraclePoolAllowed(); } return BaseHook.beforeInitialize.selector; } function _beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) internal override onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) { // only exactIn swaps are supported if (params.amountSpecified >= 0) { revert NotExactIn(); } _updatePool(key); return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } function _afterSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, BalanceDelta swapDelta, bytes calldata) internal override onlyPoolManager returns (bytes4, int128) { // the unspecified currency is always the one user is buying, so we charge a fee to settle the backrun (BalanceDelta hookDelta, bool isBackrun) = _backrun(key, params, swapDelta); // ... settles the corrective delta via afterSwapReturnDelta ``` Source: https://github.com/RigoBlock/back-geo-oracle/blob/548e6a643ebf8c1671d2aaa85c979e86bcac979e/src/BackGeoOracle.sol Status: experimental License: MIT Categories: oracles, mev-protection Properties: vanilla-swap Chains: ethereum Flags: beforeInitialize, afterInitialize, beforeAddLiquidity, beforeRemoveLiquidity, beforeSwap, afterSwap, afterSwapReturnDelta Docs: https://mirror.xyz/rigoblock.eth/yKAD5uYyH0KwfdsOxzt0MyppkFJZzXkxAFeufPGVA2M Audit: https://github.com/RigoBlock/back-geo-oracle/blob/main/audits/33Audits_audit_back_geo_oracle.pdf Page: https://v4hooks.com/hooks/back-geo-oracle ## BaseDynamicFee (base-dynamic-fee) Kind: pattern Set the pool LP fee afterInitialize, then poke updateDynamicLPFee when conditions change. OpenZeppelin’s BaseDynamicFee (MIT) is the smallest dynamic-fee building block. afterInitialize reverts unless key.fee is the dynamic-fee flag, then calls poolManager.updateDynamicLPFee with _getFee(key). Inherit it, implement _getFee, and call _poke from a guarded keeper path when you want to refresh. Permissions are afterInitialize only — this does not override a per-swap fee in beforeSwap. Experimental library code. Copy the full file including the NotDynamicFee revert. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT // OpenZeppelin Uniswap Hooks — excerpt. Full file: source.url function _getFee(PoolKey calldata key) internal virtual returns (uint24); function _afterInitialize(address, PoolKey calldata key, uint160, int24) internal virtual override returns (bytes4) { if (!key.fee.isDynamicFee()) revert NotDynamicFee(); poolManager.updateDynamicLPFee(key, _getFee(key)); return this.afterInitialize.selector; } function _poke(PoolKey calldata key) internal virtual { poolManager.updateDynamicLPFee(key, _getFee(key)); } function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: false, afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/OpenZeppelin/uniswap-hooks/blob/master/src/fee/BaseDynamicFee.sol Status: experimental License: MIT Categories: dynamic-fees Properties: dynamic-fee, vanilla-swap Chains: ethereum Flags: afterInitialize Website: https://v4hooks.com/learn/openzeppelin-hooks Docs: https://github.com/OpenZeppelin/uniswap-hooks Page: https://v4hooks.com/hooks/base-dynamic-fee ## BaseHook (base-hook) Kind: pattern Inherit this, declare permissions, gate callbacks with onlyPoolManager. Start every custom Uniswap v4 hook from OpenZeppelin’s BaseHook (MIT). PoolManager only calls functions whose bits are set in the hook address. BaseHook stores the PoolManager, reverts NotPoolManager unless msg.sender is that singleton, and routes each IHooks entrypoint to an internal _callback that reverts HookNotImplemented until you override it. getHookPermissions must match those overrides or constructor validation fails. Flip the bits you need, implement the matching _beforeSwap (or other) function, and keep return-delta flags in sync. Listing is not an audit. The library is experimental — still run the secure-hooks checklist before you ship. ### Solidity ```solidity // SPDX-License-Identifier: MIT // OpenZeppelin Uniswap Hooks — excerpt. Full file: source.url abstract contract BaseHook is IHooks { IPoolManager public immutable poolManager; error HookNotImplemented(); error NotPoolManager(); constructor(IPoolManager _poolManager) { poolManager = _poolManager; _validateHookAddress(this); } modifier onlyPoolManager() { if (msg.sender != address(poolManager)) revert NotPoolManager(); _; } function getHookPermissions() public pure virtual returns (Hooks.Permissions memory permissions); function beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData) external onlyPoolManager returns (bytes4, BeforeSwapDelta, uint24) { return _beforeSwap(sender, key, params, hookData); } function _beforeSwap(address, PoolKey calldata, SwapParams calldata, bytes calldata) internal virtual returns (bytes4, BeforeSwapDelta, uint24) { revert HookNotImplemented(); } } ``` Source: https://github.com/OpenZeppelin/uniswap-hooks/blob/master/src/base/BaseHook.sol Status: experimental License: MIT Categories: wrappers Properties: vanilla-swap Chains: ethereum Flags: beforeSwap Website: https://v4hooks.com/learn/openzeppelin-hooks Docs: https://github.com/OpenZeppelin/uniswap-hooks Page: https://v4hooks.com/hooks/base-hook ## BaseV4Hook (base-v4-hook) Kind: pattern Abstract base that re-implements v4 pool internals inside the hook for custom curves. Most custom-curve hooks bolt a NoOp beforeSwap onto a normal hook and manage reserves by hand. BaseV4Hook takes the other route: it inherits the PoolManager's own machinery — ProtocolFees, NoDelegateCall, ERC6909Claims, Extsload and Exttload — and runs v4-like liquidity logic inside the hook, using Pool, Position, CurrencyDelta and CurrencyReserves directly. If you want a pool that behaves like v4 but on a different curve, this is the scaffold that gives you the accounting for free. It is abstract on purpose. The external beforeSwap is implemented, gated by onlyByPoolManager, and its job is to repackage IPoolManager.SwapParams into a Pool.SwapParams struct — adding tickSpacing from the key and an lpFeeOverride of 0 — before delegating to an internal _beforeSwap that you must implement. Your curve goes there and returns the BeforeSwapDelta. Permissions are the three a custom curve needs, and the file annotates each one: beforeAddLiquidity because liquidity must be deposited into the hook directly rather than the PoolManager, beforeSwap as the custom curve handler, and beforeSwapReturnDelta to skip the PoolManager swap. Nothing else is claimed. Read the NatSpec on beforeSwap before you use it. It states the delta sign convention — positive means the hook is owed or took currency, negative means it owes or sent — and the three conditions an lp fee override must satisfy: dynamic fee pool, the 23rd bit (0x400000) set, and a value at or below 1,000,000. Those are the rules people get wrong. Unaudited. Inheriting core internals means you inherit their invariants too. ### Solidity ```solidity // SPDX-License-Identifier: MIT // cairoeth/base-v4-hook — excerpt. Full file: source.url abstract contract BaseV4Hook is BaseHook, ProtocolFees, NoDelegateCall, ERC6909Claims, Extsload, Exttload { using PoolIdLibrary for PoolKey; using Pool for *; using Position for mapping(bytes32 => Position.Info); using CurrencyDelta for Currency; using CurrencyReserves for Currency; using LPFeeLibrary for uint24; /// @return BeforeSwapDelta The hook's delta in specified and unspecified currencies. /// Positive: the hook is owed/took currency, negative: the hook owes/sent currency /// @return uint24 Optionally override the lp fee, only used if three conditions are met: /// 1. the Pool has a dynamic fee, 2. the value's 2nd highest bit is set /// (23rd bit, 0x400000), and 3. the value is less than or equal to the maximum fee (1 million) function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) external virtual override onlyByPoolManager returns (bytes4, BeforeSwapDelta, uint24) { return _beforeSwap( sender, key, Pool.SwapParams({ tickSpacing: key.tickSpacing, zeroForOne: params.zeroForOne, amountSpecified: params.amountSpecified, sqrtPriceLimitX96: params.sqrtPriceLimitX96, lpFeeOverride: 0 }) ); } /// @dev Execute swap with custom logic — implement this in your subclass function _beforeSwap(address sender, PoolKey calldata key, Pool.SwapParams memory params) internal virtual returns (bytes4, BeforeSwapDelta, uint24); function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: false, beforeAddLiquidity: true, // -- liquidity must be deposited here directly -- // afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, // -- custom curve handler -- // afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: true, // -- enable custom curve by skipping poolmanager swap -- // afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/cairoeth/base-v4-hook/blob/a784e215a4fc1ff1d0f3e4697b764ab035c121b8/src/BaseV4Hook.sol Status: experimental License: MIT Categories: wrappers, lp-management Properties: vanilla-swap Chains: ethereum Flags: beforeAddLiquidity, beforeSwap, beforeSwapReturnDelta Page: https://v4hooks.com/hooks/base-v4-hook ## Buyback (buyback) Kind: pattern Treasury-funded market buy when pool tick is at or below thresholdTick. First-party rewrite inspired by atj3097/buyback-hook. Native-pair pool; fund the hook with ETH. setThreshold stores a tick floor; buyback() unlocks a zeroForOne swap when current tick <= threshold. Fixed ungated-callback issues from the legacy repo. Experimental. Listing is not an audit. ### Solidity ```solidity function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: false, afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/Buyback.sol Status: experimental License: MIT Categories: launchpads Properties: vanilla-swap Chains: ethereum Flags: afterInitialize Docs: https://github.com/atj3097/buyback-hook Page: https://v4hooks.com/hooks/buyback ## Clanker (clanker) Kind: product Dynamic-fee launch hook: beforeSwap sets fee, claims, runs an MEV module, then takes protocol delta. ClankerHook is MIT production code. getHookPermissions enables initialize, add-liquidity (blocked while the MEV module is hot), beforeSwap/afterSwap, and both swap return-delta bits so the hook can mint protocol fee into its PoolManager account. _beforeSwap sets the LP fee, claims, optionally runs mevModule.beforeSwap, then uses BeforeSwapDelta on paired token. Do not reimplement Clanker to “launch a coin” — use their factory. Excerpt is how a launchpad hook takes fee-on-delta. Full file at the permalink. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return 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 }); } function _beforeSwap( address, PoolKey calldata poolKey, IPoolManager.SwapParams calldata swapParams, bytes calldata mevModuleSwapData ) internal virtual override returns (bytes4, BeforeSwapDelta delta, uint24) { _setFee(poolKey, swapParams); _hookFeeClaim(poolKey); _lpLockerFeeClaim(poolKey); _runMevModule(poolKey, swapParams, mevModuleSwapData); // mint protocol fee as BeforeSwapDelta when swapping for/against the clanker return (BaseHook.beforeSwap.selector, delta, 0); } ``` Source: https://github.com/clanker-devco/v4-contracts/blob/main/src/hooks/ClankerHook.sol Status: production License: MIT Categories: launchpads, dynamic-fees, mev-protection Properties: dynamic-fee, vanilla-swap Chains: base Flags: beforeInitialize, beforeAddLiquidity, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta Website: https://www.clanker.world/ Docs: https://clanker.gitbook.io/documentation/references/core-contracts/v4 Page: https://v4hooks.com/hooks/clanker ## Constant sum (constant-sum) Kind: pattern Replace xy=k with x+y=k so every swap fills exactly 1:1, using a NoOp beforeSwap. The cleanest example of a v4 custom curve. The pool's own math is never used — the hook intercepts the swap in beforeSwap, returns a BeforeSwapDelta that fully accounts for both sides, and the PoolManager skips its concentrated-liquidity swap entirely. That is what beforeSwapReturnDelta buys you, and this is the smallest readable demonstration of it. The swap body is short. It picks input and output currency from zeroForOne, treats a negative amountSpecified as exact-input, and — because the curve is x+y=k — uses the same amount for both sides. It then calls poolManager.mint to take the input currency as ERC-6909 into the hook's reserves and poolManager.burn to pay the output currency out of them. The returned delta is the mirror of that: for exact input the specified delta is positive and the unspecified negative, and for exact output the signs invert. The comments in the file spell the sign convention out line by line, which is the part worth reading twice — getting it backwards is the classic custom-curve bug. beforeAddLiquidity reverts with "No v4 Liquidity allowed". That is deliberate, not an oversight: reserves live in the hook as ERC-6909 claims, so ordinary v4 liquidity would be stranded and unusable by the curve. Any custom-curve hook has to make the same choice. Note the file is named Counter.sol — leftover from the v4-template scaffold — even though the contract does constant-sum swaps. Unaudited example code. ### Solidity ```solidity // SPDX-License-Identifier: MIT // saucepoint/v4-constant-sum — excerpt. Full file: source.url function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: false, beforeAddLiquidity: true, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: true, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function _beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) internal override returns (bytes4, BeforeSwapDelta, uint24) { // determine inbound/outbound token based on 0->1 or 1->0 swap (Currency inputCurrency, Currency outputCurrency) = params.zeroForOne ? (key.currency0, key.currency1) : (key.currency1, key.currency0); bool isExactInput = params.amountSpecified < 0; // tokens are always swapped 1:1, so use amountSpecified to determine both input and output amounts uint256 amount = isExactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified); // take the input token, as ERC6909, from the PoolManager poolManager.mint(address(this), inputCurrency.toId(), amount); // pay the output token, as ERC6909, to the PoolManager poolManager.burn(address(this), outputCurrency.toId(), amount); int128 tokenAmount = amount.toInt128(); // exact input: specifiedDelta positive, unspecifiedDelta negative // exact output: specifiedDelta negative, unspecifiedDelta positive BeforeSwapDelta returnDelta = isExactInput ? toBeforeSwapDelta(tokenAmount, -tokenAmount) : toBeforeSwapDelta(-tokenAmount, tokenAmount); return (BaseHook.beforeSwap.selector, returnDelta, 0); } /// @notice No liquidity will be managed by v4 PoolManager function _beforeAddLiquidity(address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata) internal pure override returns (bytes4) { revert("No v4 Liquidity allowed"); } ``` Source: https://github.com/saucepoint/v4-constant-sum/blob/cce8f6cfb614e19144b1858348b23f56c241f50d/src/Counter.sol Status: experimental License: MIT Categories: wrappers Properties: vanilla-swap Chains: ethereum Flags: beforeAddLiquidity, beforeSwap, beforeSwapReturnDelta Page: https://v4hooks.com/hooks/constant-sum ## Doppler (doppler) Kind: product Whetstone bonding-curve hook: rebalance slugs before swap, migrate when proceeds cap hit. Doppler is Whetstone Research production code (BUSL-1.1). The hook runs a time-epoch bonding curve: beforeSwap rebalances concentrated liquidity slugs when a new epoch starts, tracks totalTokensSold and totalProceeds, and blocks swaps before startTime or after migration. beforeAddLiquidity reverts (CannotAddLiquidity); LPs are the hook's slug positions, not external adds. Study for launchpad / LBP-style v4 hooks. Do not deploy without a Whetstone license. Excerpt shows permissions and epoch gate in beforeSwap. Full file at the permalink. Not an audit. ### Solidity ```solidity // SPDX-License-Identifier: BUSL-1.1 function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: true, beforeAddLiquidity: true, beforeRemoveLiquidity: false, afterAddLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: true, beforeDonate: true, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function _beforeSwap(...) internal override returns (bytes4, BeforeSwapDelta, uint24) { if (block.timestamp < startingTime) revert CannotSwapBeforeStartTime(); // rebalance slug liquidity when epoch advances, then pool executes swap } ``` Source: https://github.com/whetstoneresearch/doppler/blob/main/src/initializers/Doppler.sol Status: production License: BUSL-1.1 Categories: launchpads, lp-management Properties: dynamic-fee, vanilla-swap Chains: base Flags: beforeInitialize, afterInitialize, beforeAddLiquidity, beforeSwap, afterSwap, beforeDonate Website: https://whetstone.cc/ Page: https://v4hooks.com/hooks/doppler ## DualPool (dual-pool) Kind: product Uniswap ALF JIT hook: deploy multi-range LP around each swap, vault via ERC4626. DualPoolHook is Uniswap Labs production code from v4-hooks-public. On each swap it runs a JIT cycle: beforeSwap locks LP ops, redeploys liquidity across weighted tick buckets, then afterSwap tears down positions and re-deposits surplus to ERC4626 vaults. Pool fee is static from PoolKey; the hook does not override fees. Study this for JIT + vault rehypothecation, not as a drop-in ALF deployment. Full file at the permalink. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeAddLiquidity: true, beforeRemoveLiquidity: true, afterAddLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } // beforeSwap: set JIT lock → deploy bucket LP → [swap] → afterSwap: remove LP, settle, vault ``` Source: https://github.com/Uniswap/v4-hooks-public/blob/main/src/alf/DualPoolHook.sol Status: production License: MIT Categories: lp-management, wrappers Properties: vanilla-swap Chains: ethereum Flags: beforeInitialize, beforeAddLiquidity, beforeRemoveLiquidity, beforeSwap, afterSwap Docs: https://github.com/Uniswap/v4-hooks-public Page: https://v4hooks.com/hooks/dual-pool ## EulerSwap (eulerswap) Kind: product Custom curve via beforeSwapReturnDelta: take input to Euler vaults, pay output, skip CLAMM math. Euler’s UniswapHook (BUSL-1.1) quotes amountIn/amountOut off the v3 tick curve, returns a BeforeSwapDelta so PoolManager’s concentrated liquidity is unused, takes the input to vaults and settles the output from vaults. beforeInitialize / beforeAddLiquidity / beforeDonate stay enabled so BaseHook’s default revert blocks stray inits, CLAMM LP, and donations. Study this for “hook is the AMM.” Do not paste BUSL into a new MIT repo without reading the license. Full UniswapHook.sol at the permalink. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: BUSL-1.1 function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeAddLiquidity: true, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: false, beforeDonate: true, afterDonate: false, beforeSwapReturnDelta: true, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } // take input to Euler vaults, pay output, offset the CLAMM swap function _beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) internal override nonReentrant returns (bytes4, BeforeSwapDelta, uint24) { // QuoteLib.computeQuote → poolManager.take(input) → vaults → poolManager.settle(output) BeforeSwapDelta returnDelta = isExactInput ? toBeforeSwapDelta(amountIn.toInt128(), -(amountOut.toInt128())) : toBeforeSwapDelta(-(amountOut.toInt128()), amountIn.toInt128()); return (BaseHook.beforeSwap.selector, returnDelta, 0); } ``` Source: https://github.com/euler-xyz/euler-swap/blob/master/src/UniswapHook.sol Status: production License: BUSL-1.1 Categories: lending, lp-management Properties: vanilla-swap Chains: ethereum Flags: beforeInitialize, beforeAddLiquidity, beforeSwap, beforeDonate, beforeSwapReturnDelta Website: https://app.euler.finance/swap Page: https://v4hooks.com/hooks/eulerswap ## FeeRouter (fee-router) Kind: pattern afterSwap skims protocolBps of output to an immutable treasury address. First-party teaching hook for fee collect-and-route on every swap. afterSwap takes protocolBps of the swap output token via take + ERC20 transfer to treasury. Similar shape to referral-fee.yml but fixed recipient instead of hookData referrer. Experimental. Forge tests in test/FeeRouter.t.sol. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT function _afterSwap(...) internal override returns (bytes4, int128) { uint256 pay = (uint256(int256(out)) * protocolBps) / 10_000; outCur.take(poolManager, address(this), pay, false); IERC20(Currency.unwrap(outCur)).transfer(treasury, pay); return (this.afterSwap.selector, pay.toInt128()); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/FeeRouter.sol Status: experimental License: MIT Categories: creator-economy, dynamic-fees Properties: vanilla-swap Chains: ethereum Flags: afterSwap, afterSwapReturnDelta Page: https://v4hooks.com/hooks/fee-router ## Flaunch (flaunch) Kind: product PositionManager hook: beforeSwap runs the internal swap pool; afterSwap distributes creator fees. Flaunch’s PositionManager is a Uniswap v4 hook (MIT). Permissions: block external initialize, InternalSwapPool on beforeSwap + beforeSwapReturnDelta, FeeDistributor and BidWall on afterSwap + afterSwapReturnDelta, liquidity/donate event tracking. You do not copy this to “build Flaunch” — you read how a launchpad declares a wide permission mask and splits work across callbacks. Full PositionManager.sol is large; start at getHookPermissions then follow the comments in square brackets. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, // Prevent initialize afterInitialize: false, beforeAddLiquidity: false, afterAddLiquidity: true, // [EventTracking] beforeRemoveLiquidity: false, afterRemoveLiquidity: true, // [EventTracking] beforeSwap: true, // [InternalSwapPool] afterSwap: true, // [FeeDistributor], [InternalSwapPool], [BidWall], [EventTracking] beforeDonate: false, afterDonate: true, // [EventTracking] beforeSwapReturnDelta: true, // [InternalSwapPool] afterSwapReturnDelta: true, // [FeeDistributor] afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/flayerlabs/flaunchgg-contracts/blob/main/src/contracts/PositionManager.sol Status: production License: MIT Categories: launchpads, creator-economy Properties: custom-swap-data Chains: base Flags: beforeInitialize, afterAddLiquidity, afterRemoveLiquidity, beforeSwap, afterSwap, afterDonate, beforeSwapReturnDelta, afterSwapReturnDelta Website: https://flaunch.gg/ Page: https://v4hooks.com/hooks/flaunch ## Full range (full-range) Kind: pattern Force v2-style full-range LP: only the hook can modify liquidity, users mint a pool ERC20 instead. FullRange’s beforeInitialize requires tickSpacing 60 and deploys a per-pool ERC20 receipt. beforeAddLiquidity reverts unless sender is the hook, so LPs must call addLiquidity on the hook (min to max tick). beforeSwap marks that fees have accrued. This is how you wrap a v4 pool as constant-product inventory. UNLICENSED Uniswap example — take the full file, including rebalance/donate dust. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: UNLICENSED function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeAddLiquidity: true, beforeRemoveLiquidity: false, afterAddLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function beforeAddLiquidity( address sender, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata ) external view override returns (bytes4) { if (sender != address(this)) revert SenderMustBeHook(); return FullRange.beforeAddLiquidity.selector; } ``` Source: https://github.com/Uniswap/v4-periphery/blob/example-contracts/contracts/hooks/examples/FullRange.sol Status: experimental License: UNLICENSED Categories: lp-management, wrappers Properties: vanilla-swap Chains: ethereum Flags: beforeInitialize, beforeAddLiquidity, beforeSwap Page: https://v4hooks.com/hooks/full-range ## Geomean oracle (geomean-oracle) Kind: pattern Write a geometric-mean observation before any action that can move price or liquidity. The Uniswap GeomeanOracle example allows one oracle pool per pair (fee 0, max tick spacing). afterInitialize seeds the observation cardinality. beforeAddLiquidity forces full-range positions and writes a sample. beforeRemoveLiquidity always reverts so liquidity stays locked. beforeSwap updates the oracle then lets the swap proceed. Read observe() offchain or from another contract. UNLICENSED example — copy the full file including the Oracle library. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: UNLICENSED function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: true, beforeAddLiquidity: true, beforeRemoveLiquidity: true, afterAddLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) external override onlyByManager returns (bytes4, BeforeSwapDelta, uint24) { _updatePool(key); return (GeomeanOracle.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } function beforeRemoveLiquidity(address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata) external view override onlyByManager returns (bytes4) { revert OraclePoolMustLockLiquidity(); } ``` Source: https://github.com/Uniswap/v4-periphery/blob/example-contracts/contracts/hooks/examples/GeomeanOracle.sol Status: experimental License: UNLICENSED Categories: oracles Properties: vanilla-swap Chains: ethereum Flags: beforeInitialize, afterInitialize, beforeAddLiquidity, beforeRemoveLiquidity, beforeSwap Docs: https://docs.uniswap.org/contracts/v4/concepts/hooks Page: https://v4hooks.com/hooks/geomean-oracle ## Iceberg (iceberg-order) Kind: pattern Encrypted limit orders — size and direction stay hidden until the tick is crossed. Iceberg is the familiar v4 tick-crossing limit order, with the order book held in Fhenix CoFHE ciphertext instead of plaintext storage. Permissions are afterInitialize, beforeSwap and afterSwap; every callback is gated by onlyByManager. afterInitialize records the starting lower tick for the pool. afterSwap recomputes the ticks crossed since the last swap and walks them, filling resting orders in the direction opposite the swap — the file notes that a zeroForOne swap makes the pool gain token0, so limit fills invert the swap direction. That part is the standard LimitOrderHook shape. The FHE part lives in beforeSwap. Order amounts are euint128 handles rather than uint128, so a fill cannot settle until the coprocessor has decrypted the amount. beforeSwap drains a per-pool decryption Queue, peeking each pending handle and executing whatever has become available before the swap proceeds. Balances are held as IFHERC20, an encrypted ERC-20, so a resting order's size is never readable on-chain. Two consequences worth understanding before copying it. Fills are asynchronous: an order becomes claimable a decryption round after the tick is crossed, not in the same transaction. And beforeSwap does unbounded work proportional to queue depth, so a congested pool pays for other people's pending decryptions — the same tick-walk gas exposure the plaintext limit order hooks have, plus the queue. Depends on Fhenix CoFHE, so it only runs where that coprocessor is deployed. Experimental, unaudited. ### Solidity ```solidity // SPDX-License-Identifier: MIT // marronjo/iceberg-cofhe — excerpt. Full file: source.url function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: false, beforeRemoveLiquidity: false, afterAddLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function _beforeSwap( address, PoolKey calldata key, SwapParams calldata, bytes calldata ) internal override onlyByManager returns (bytes4, BeforeSwapDelta, uint24) { Queue queue = getPoolQueue(key); //if nothing in decryption queue, continue //otherwise try execute trades while(!queue.isEmpty()){ euint128 liquidityHandle = queue.peek(); // ... drains handles that CoFHE has finished decrypting function _afterSwap( address, PoolKey calldata key, SwapParams calldata params, BalanceDelta, bytes calldata ) internal override onlyByManager returns (bytes4, int128) { (int24 tickLower, int24 lower, int24 upper) = _getCrossedTicks(key.toId(), key.tickSpacing); if (lower > upper) return (BaseHook.afterSwap.selector, 0); // note that a zeroForOne swap means that the pool is actually gaining token0, so limit // order fills are the opposite of swap fills, hence the inversion below bool zeroForOne = !params.zeroForOne; for (; lower <= upper; lower += key.tickSpacing) { // ... fills resting encrypted orders at each crossed tick ``` Source: https://github.com/marronjo/iceberg-cofhe/blob/3dab6b2e1f12897d3b8c8edd93d369c9c46dfcd2/src/Iceberg.sol Status: experimental License: MIT Categories: limit-orders Properties: vanilla-swap Chains: ethereum Flags: afterInitialize, beforeSwap, afterSwap Website: https://www.fhenix.io/ Page: https://v4hooks.com/hooks/iceberg-order ## JitVault (jit-vault) Kind: pattern JIT lock blocks LP ops during swap; optional output skim accrues to vault balances. First-party teaching hook inspired by Uniswap DualPoolHook. beforeSwap sets jitActive; beforeAddLiquidity and beforeRemoveLiquidity revert while active. afterSwap clears the lock and optionally skims skimBps of swap output into per-pool vault0/vault1 balances held by the hook. No ERC4626 or bucket deployment — study the lifecycle gate only. Experimental. Forge tests in test/JitVault.t.sol. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT function _beforeSwap(...) internal override returns (bytes4, BeforeSwapDelta, uint24) { jitActive[key.toId()] = true; return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } function _afterSwap(...) internal override returns (bytes4, int128) { jitActive[id] = false; // skim skimBps of output into vault0/vault1 } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/JitVault.sol Status: experimental License: MIT Categories: lp-management, mev-protection Properties: vanilla-swap Chains: ethereum Flags: beforeAddLiquidity, beforeRemoveLiquidity, beforeSwap, afterSwap, afterSwapReturnDelta Page: https://v4hooks.com/hooks/jit-vault ## Limit order (limit-order) Kind: pattern Rest liquidity at a tick and fill it in afterSwap when the pool trades through that price. Uniswap’s LimitOrder example records the last tick on afterInitialize. On afterSwap it walks ticks the swap just crossed and fills any epoch resting at those ticks by removing that one-tick-wide liquidity. Users call place() to add an order; withdraw() after Fill. Flags must be afterInitialize + afterSwap. This is the example-contracts file, UNLICENSED, not a production CLOB. Production forks add cancel, partial fill, and gas accounting. Copy the full Solidity from the permalink. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: UNLICENSED function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: false, beforeRemoveLiquidity: false, afterAddLiquidity: false, afterRemoveLiquidity: false, beforeSwap: false, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function afterSwap( address, PoolKey calldata key, IPoolManager.SwapParams calldata params, BalanceDelta, bytes calldata ) external override onlyByManager returns (bytes4, int128) { (int24 tickLower, int24 lower, int24 upper) = _getCrossedTicks(key.toId(), key.tickSpacing); if (lower > upper) return (LimitOrder.afterSwap.selector, 0); bool zeroForOne = !params.zeroForOne; for (; lower <= upper; lower += key.tickSpacing) { _fillEpoch(key, lower, zeroForOne); } setTickLowerLast(key.toId(), tickLower); return (LimitOrder.afterSwap.selector, 0); } ``` Source: https://github.com/Uniswap/v4-periphery/blob/example-contracts/contracts/hooks/examples/LimitOrder.sol Status: experimental License: UNLICENSED Categories: limit-orders Properties: vanilla-swap Chains: ethereum Flags: afterInitialize, afterSwap Docs: https://docs.uniswap.org/contracts/v4/concepts/hooks Page: https://v4hooks.com/hooks/limit-order ## LimitOrderHook (limit-order-hook) Kind: pattern Place out-of-range liquidity, fill on afterSwap tick cross, cancel or withdraw via unlock. OpenZeppelin’s LimitOrderHook (MIT) is the library-shaped fill-on-cross. afterInitialize stores last tick; afterSwap walks crossed ticks and _fillOrder. Users call placeOrder, cancelOrder, and withdraw through unlockCallback. Fees accrue per liquidity unit so late joiners do not steal earlier fees. Compare with Uniswap’s UNLICENSED LimitOrder example — this file adds cancel and fee accounting. Experimental library code. Copy the full file from the permalink. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT // OpenZeppelin Uniswap Hooks — excerpt. Full file: source.url function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: false, beforeRemoveLiquidity: false, afterAddLiquidity: false, afterRemoveLiquidity: false, beforeSwap: false, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function _afterSwap(address, PoolKey calldata key, SwapParams calldata params, BalanceDelta, bytes calldata) internal virtual override returns (bytes4, int128) { (int24 tickLower, int24 lower, int24 upper) = _getCrossedTicks(key.toId(), key.tickSpacing); if (lower > upper) return (this.afterSwap.selector, 0); _tickLowerLasts[key.toId()] = tickLower; bool zeroForOne = !params.zeroForOne; for (; lower <= upper; lower += key.tickSpacing) { _fillOrder(key, lower, zeroForOne); } return (this.afterSwap.selector, 0); } ``` Source: https://github.com/OpenZeppelin/uniswap-hooks/blob/master/src/general/LimitOrderHook.sol Status: experimental License: MIT Categories: limit-orders Properties: vanilla-swap Chains: ethereum Flags: afterInitialize, afterSwap Website: https://v4hooks.com/learn/openzeppelin-hooks Docs: https://github.com/OpenZeppelin/uniswap-hooks Page: https://v4hooks.com/hooks/limit-order-hook ## Liquidity bootstrapping (liquidity-bootstrapping) Kind: pattern Linear-decay token sale via beforeSwapReturnDelta (LBP-style custom curve). First-party rewrite inspired by kadenzipfel uni-lbp (AGPL). configureSale sets start/end time and price bounds. beforeSwapReturnDelta mints/burns ERC-6909 for ETH→token sales at currentPrice. beforeAddLiquidity reverts — no v4 LP during the sale phase. Teaching cut of an LBP launch curve. Listing is not an audit. Forge tests under test/LiquidityBootstrapping.t.sol. ### Solidity ```solidity function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: false, beforeAddLiquidity: true, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: true, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/LiquidityBootstrapping.sol Status: experimental License: MIT Categories: launchpads Properties: vanilla-swap Chains: ethereum Flags: beforeAddLiquidity, beforeSwap, beforeSwapReturnDelta Docs: https://github.com/kadenzipfel/uni-lbp Page: https://v4hooks.com/hooks/liquidity-bootstrapping ## Liquidity lock (liquidity-lock) Kind: pattern afterAddLiquidity sets unlock time; beforeRemoveLiquidity reverts until lock expires. First-party rewrite inspired by Hookathon C1 LiquidityLock / timelock LP. lockDuration is added to block.timestamp on each net add keyed by the liquidity sender (router in tests). Removes revert with StillLocked until unlock. Launchpad LP commitment pattern. Listing is not an audit. ### Solidity ```solidity function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: false, beforeAddLiquidity: false, afterAddLiquidity: true, beforeRemoveLiquidity: true, afterRemoveLiquidity: false, beforeSwap: false, afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/LiquidityLock.sol Status: experimental License: MIT Categories: launchpads, lp-management Properties: vanilla-swap Chains: ethereum Flags: afterAddLiquidity, beforeRemoveLiquidity Page: https://v4hooks.com/hooks/liquidity-lock ## LiquidityPenaltyHook (liquidity-penalty) Kind: pattern Withhold JIT LP fees in afterAddLiquidity and donate a linear penalty on early remove. OpenZeppelin’s LiquidityPenaltyHook (MIT) deters just-in-time liquidity. afterAddLiquidity records lastAddedLiquidityBlock and, if the position was topped up inside blockNumberOffset, takes feeDelta into the hook via afterAddLiquidityReturnDelta. afterRemoveLiquidity settles withheld fees, then if the offset has not elapsed, donates a linear remaining-blocks penalty to in-range LPs and returns the rest through afterRemoveLiquidityReturnDelta. Long-term LPs who add continuously must wait the offset before collecting fees. Experimental library code; low-liquidity pools can still be gamed. Copy the full file. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT // OpenZeppelin Uniswap Hooks — excerpt. Full file: source.url function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: false, beforeAddLiquidity: false, afterAddLiquidity: true, beforeRemoveLiquidity: false, afterRemoveLiquidity: true, beforeSwap: false, afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: true, afterRemoveLiquidityReturnDelta: true }); } function _afterAddLiquidity( address sender, PoolKey calldata key, ModifyLiquidityParams calldata params, BalanceDelta, /* delta */ BalanceDelta feeDelta, bytes calldata ) internal virtual override returns (bytes4, BalanceDelta) { PoolId poolId = key.toId(); bytes32 positionKey = Position.calculatePositionKey(sender, params.tickLower, params.tickUpper, params.salt); if (_getBlockNumber() - getLastAddedLiquidityBlock(poolId, positionKey) < blockNumberOffset) { _updateLastAddedLiquidityBlock(poolId, positionKey); _takeFeesToHook(key, positionKey, feeDelta); return (this.afterAddLiquidity.selector, feeDelta); } _updateLastAddedLiquidityBlock(poolId, positionKey); return (this.afterAddLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); } ``` Source: https://github.com/OpenZeppelin/uniswap-hooks/blob/master/src/general/LiquidityPenaltyHook.sol Status: experimental License: MIT Categories: mev-protection, lp-management Properties: vanilla-swap Chains: ethereum Flags: afterAddLiquidity, afterRemoveLiquidity, afterAddLiquidityReturnDelta, afterRemoveLiquidityReturnDelta Website: https://v4hooks.com/learn/openzeppelin-hooks Docs: https://github.com/OpenZeppelin/uniswap-hooks Page: https://v4hooks.com/hooks/liquidity-penalty ## LpRewards (lp-rewards) Kind: pattern Pull buy-side token rewards, then flush() donates pending balance to in-range LPs. First-party teaching hook inspired by Spark LP Rewards. Spark routes an ETH-side buy cut; this version takes rewardBps of token output on exact-input buys (same afterSwapReturnDelta path as AutoBurn) into pending[poolId], then flush() unlocks and calls poolManager.donate + settle so feeGrowthGlobal rises for LPs. Deferred donate avoids NoLiquidityToReceiveFees when a large buy walks price out of range mid-callback. Contrast liquidity-penalty.yml (donate on JIT remove). Experimental. Forge tests in test/LpRewards.t.sol. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT // v4hooks first-party — excerpt. Full file: source.url function _afterSwap(address, PoolKey calldata key, SwapParams calldata params, BalanceDelta delta, bytes calldata) internal override returns (bytes4, int128) { if (!params.zeroForOne || params.amountSpecified >= 0) return (this.afterSwap.selector, 0); uint256 reward = (uint256(int256(delta.amount1())) * rewardBps) / 10_000; if (reward == 0) return (this.afterSwap.selector, 0); key.currency1.take(poolManager, address(this), reward, false); pending[key.toId()] += reward; return (this.afterSwap.selector, reward.toInt128()); } function flush(PoolKey calldata key) external { poolManager.unlock(abi.encode(key)); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/LpRewards.sol Status: experimental License: MIT Categories: launchpads, lp-management Properties: vanilla-swap Chains: ethereum Flags: afterInitialize, afterSwap, afterSwapReturnDelta Page: https://v4hooks.com/hooks/lp-rewards ## Median oracle (median-oracle) Kind: pattern Ring buffer of post-swap ticks with onchain medianTick view. First-party rewrite inspired by saucepoint median-oracles (MIT). afterSwap pushes floored tick into a 16-sample ring per pool. medianTick sorts the active window on read — teaching cut of a manipulation-resistant oracle vs TWAP geomean examples. Experimental. Listing is not an audit. ### Solidity ```solidity function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: false, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: false, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/MedianOracle.sol Status: experimental License: MIT Categories: oracles Properties: vanilla-swap Chains: ethereum Flags: afterSwap Docs: https://github.com/saucepoint/median-oracles Page: https://v4hooks.com/hooks/median-oracle ## MEV donate (mev-donate) Kind: pattern Skim swap output to pending, then flush() donates to in-range LPs. First-party rewrite inspired by FairArbooors (EthLondon) and Detox. afterSwapReturnDelta takes skimBps of swap output into pending balances per pool; flush() unlocks a donate + settle path so in-range LPs receive the skim (same unlock pattern as LpRewards). Skims both swap directions. Teaching cut of MEV-to-LP redistribution — not a production MEV auction. Experimental. Listing is not an audit. Forge tests under test/MevDonate.t.sol. ### Solidity ```solidity function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: false, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: false, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: true, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/MevDonate.sol Status: experimental License: MIT Categories: mev-protection, lp-management Properties: vanilla-swap Chains: ethereum Flags: afterSwap, afterSwapReturnDelta Docs: https://github.com/LVooooors/ETHGlobalLondon Page: https://v4hooks.com/hooks/mev-donate ## MevWindow (mev-window) Kind: pattern Elevated swap fee and blocked add-liquidity for mevBlocks after pool init. First-party teaching hook inspired by Clanker MEV module window. afterInitialize records launchBlock; while block.number < launchBlock + mevBlocks, beforeSwap returns mevFee and beforeAddLiquidity reverts MevWindowActive. After the window, baseFee applies and LP adds resume. Requires dynamic fee pool. Experimental. Forge tests in test/MevWindow.t.sol. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT function feeFor(PoolId poolId) public view returns (uint24) { return inMevWindow(poolId) ? mevFee : baseFee; } function _beforeAddLiquidity(...) internal view override returns (bytes4) { if (inMevWindow(key.toId())) revert MevWindowActive(); return this.beforeAddLiquidity.selector; } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/MevWindow.sol Status: experimental License: MIT Categories: launchpads, mev-protection, dynamic-fees Properties: dynamic-fee, vanilla-swap Chains: base Flags: afterInitialize, beforeAddLiquidity, beforeSwap Page: https://v4hooks.com/hooks/mev-window ## NthBuyPot (nth-buy-pot) Kind: pattern Deterministic Nth-buy jackpot — potBps per buy, counter advances once per block, claim-backed. First-party teaching hook inspired by Spark Nth-buy Pot. afterInitialize requires native currency0. On exact-input buys, afterSwap pulls potBps of token output into the hook (afterSwapReturnDelta) and increments buyCount at most once per block; when buyCount reaches nthBuy, winner is set from abi.encode(trader) in hookData (else swap sender). claim() sends accumulated token1 to the winner. No random draw — public counter, no permissionless flush. Exact-input buys only. Experimental. Forge tests in test/NthBuyPot.t.sol. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT // v4hooks first-party — excerpt. Full file: source.url function _afterSwap( address sender, PoolKey calldata key, SwapParams calldata params, BalanceDelta delta, bytes calldata hookData ) internal override returns (bytes4, int128) { if (!params.zeroForOne || params.amountSpecified >= 0) return (this.afterSwap.selector, 0); PotState storage state = pots[key.toId()]; if (block.number != state.lastCountBlock) { state.lastCountBlock = block.number; state.buyCount++; if (state.buyCount == nthBuy) state.winner = traderOf(sender, hookData); } uint256 contribution = (uint256(int256(delta.amount1())) * potBps) / 10_000; key.currency1.take(poolManager, address(this), contribution, false); return (this.afterSwap.selector, contribution.toInt128()); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/NthBuyPot.sol Status: experimental License: MIT Categories: launchpads Properties: custom-swap-data, vanilla-swap Chains: ethereum Flags: afterInitialize, afterSwap, afterSwapReturnDelta Page: https://v4hooks.com/hooks/nth-buy-pot ## Permissioned Pools (permissioned-pools) Kind: product Per-currency allowlist enforced on swap and add-liquidity via permissions adapters. PermissionedHooks is Uniswap v4-periphery production code. Pools must include at least one verified IPermissionsAdapter currency; beforeInitialize rejects unverified adapters. beforeSwap and beforeAddLiquidity call the adapter to authorize msgSender() reported by registered wrapper contracts. afterSwap emits a router-compatible Swap event for indexers. Use Uniswap's permissioned pool stack — do not reimplement KYC here. Excerpt shows permissions and initialize gate. Full file at the permalink. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT function getHookPermissions() public pure override returns (Hooks.Permissions memory permissions) { permissions.beforeInitialize = true; permissions.beforeSwap = true; permissions.afterSwap = true; permissions.beforeAddLiquidity = true; } function _beforeInitialize(address, PoolKey calldata key, uint160) internal view override returns (bytes4) { address currency0 = Currency.unwrap(key.currency0); address currency1 = Currency.unwrap(key.currency1); bool currency0IsAdapter = PERMISSIONS_ADAPTER_FACTORY.permissionsAdapterOf(currency0) != address(0); bool currency1IsAdapter = PERMISSIONS_ADAPTER_FACTORY.permissionsAdapterOf(currency1) != address(0); // requires at least one verified adapter; rejects unverified adapter currencies } ``` Source: https://github.com/Uniswap/v4-hooks-public/blob/main/src/permissioned-pools/PermissionedHooks.sol Status: production License: MIT Categories: compliance, wrappers Properties: vanilla-swap Chains: ethereum Flags: beforeInitialize, beforeAddLiquidity, beforeSwap, afterSwap Docs: https://docs.uniswap.org/contracts/v4/overview Page: https://v4hooks.com/hooks/permissioned-pools ## Referral fee (referral-fee) Kind: pattern abi.encode(referrer) in hookData pays refBps of swap output to the referrer. First-party rewrite inspired by mergd/ref-fee-hook (MIT). afterSwapReturnDelta takes refBps of output token and transfers to the referrer decoded from hookData. Zero referrer skips the skim. Experimental referral split pattern. Listing is not an audit. Forge tests under test/ReferralFee.t.sol. ### Solidity ```solidity function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: false, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: false, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: true, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/ReferralFee.sol Status: experimental License: MIT Categories: creator-economy Properties: custom-swap-data, vanilla-swap Chains: ethereum Flags: afterSwap, afterSwapReturnDelta Docs: https://github.com/mergd/ref-fee-hook Page: https://v4hooks.com/hooks/referral-fee ## ReHypothecationHook (rehypothecation) Kind: pattern Park idle LP in yield sources, JIT a hook-owned position on beforeSwap, unwind afterSwap. OpenZeppelin’s ReHypothecationHook (MIT) parks idle LP in external yield sources (ERC-4626 or lending) and JIT-injects a hook-owned full-range position on beforeSwap, then unwinds it on afterSwap and settles PoolManager deltas back to the vaults. Users mint and redeem the hook’s ERC20 via addReHypothecatedLiquidity; they do not add liquidity through PoolManager. beforeInitialize binds a single PoolKey. You implement getCurrencyYieldSource and the deposit or withdraw adapters. Experimental: yield-source risk, PoolManager reserve timing, and leveraged liquidity notes are in the file. Copy the full file. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT // OpenZeppelin Uniswap Hooks — excerpt. Full file: source.url function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory permissions) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function _beforeSwap(address, PoolKey calldata, SwapParams calldata, bytes calldata) internal virtual override returns (bytes4, BeforeSwapDelta, uint24) { uint256 liquidityToUse = _getLiquidityToUse(); if (liquidityToUse > 0) _modifyLiquidity(liquidityToUse.toInt256()); return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } function _afterSwap(address, PoolKey calldata key, SwapParams calldata, BalanceDelta, bytes calldata) internal virtual override returns (bytes4, int128) { uint128 liquidity = _getHookPositionLiquidity(); if (liquidity > 0) { _modifyLiquidity(-liquidity.toInt256()); _resolveHookDelta(key.currency0); _resolveHookDelta(key.currency1); } return (this.afterSwap.selector, 0); } ``` Source: https://github.com/OpenZeppelin/uniswap-hooks/blob/master/src/general/ReHypothecationHook.sol Status: experimental License: MIT Categories: lending, lp-management Properties: vanilla-swap Chains: ethereum Flags: beforeInitialize, beforeSwap, afterSwap Website: https://v4hooks.com/learn/openzeppelin-hooks Docs: https://github.com/OpenZeppelin/uniswap-hooks Page: https://v4hooks.com/hooks/rehypothecation ## Sell guard (sell-guard) Kind: pattern Sell cooldown blocks rapid dumps; repeat sells in a window pay ramp fee. First-party rewrite inspired by FairTrade (ETHNYC) and SafeSwap. Native-pair dynamic fee pool. beforeSwap enforces cooldownBlocks between sells and ramps fee after repeat sells in windowBlocks. Trader keyed via abi.encode(trader) in hookData. Complements buy-side AntiSnipe. Experimental. Listing is not an audit. Forge tests under test/SellGuard.t.sol. ### Solidity ```solidity function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/SellGuard.sol Status: experimental License: MIT Categories: launchpads, mev-protection Properties: dynamic-fee, custom-swap-data, vanilla-swap Chains: ethereum Flags: afterInitialize, beforeSwap, afterSwap Page: https://v4hooks.com/hooks/sell-guard ## SlippageFeeHook (slippage-fee) Kind: pattern Simulate the swap, measure the tick it would move, then price the fee off that slippage. A dynamic fee that reacts to the size of the swap in front of it rather than to historical volatility. Before the real swap runs, the hook simulates it, reads how far the tick would move, and charges accordingly — so a swap that walks the pool pays more than one that barely moves the price. The mechanism is the part worth studying. Solidity has no way to ask "what would this swap do" without doing it, so _getExactSingleFee reads the current tick from poolManager.getSlot0, then calls this._getTickAfterExactSingle in a try/catch. That inner call performs the swap and deliberately reverts with SlippageFeeHook__RevertWithTick, packing the resulting tick into the revert reason. The catch block pulls the selector and the tick straight out of the returndata with assembly, and anything other than the expected selector is rethrown as SlippageFeeHook__UnexpectedRevert. Reverting unwinds the simulated swap; the tick delta survives in memory. That is the whole trick. Re-entrancy is handled by identity, not a lock: _beforeSwap checks whether sender is the hook itself and charges zero fee if so, which is what keeps the simulation from recursing into another simulation. Worth understanding before adapting it — the guard is that one comparison. afterInitialize refuses to attach to a pool whose key.fee is not dynamic, which is the correct place for that check. The fee returned from beforeSwap is OR'd with LPFeeLibrary.OVERRIDE_FEE_FLAG, the 23rd-bit flag v4 requires for a per-swap override. Note the simulate-then-revert pattern doubles the swap work, so gas is roughly twice a plain swap. Single-file, unaudited example. ### Solidity ```solidity // SPDX-License-Identifier: MIT // dennnis0204/slippage-fee-hook — excerpt. Full file: source.url function getHookPermissions() public pure override returns (Hooks.Permissions memory permissions) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function _getExactSingleFee(ExactSingleParams memory params) internal returns (uint24 fee) { (, int24 currentTick,,) = poolManager.getSlot0(params.poolKey.toId()); bytes4 selector; int24 tickAfter; // simulate the swap; it reverts with the resulting tick packed into the reason try this._getTickAfterExactSingle(params) {} catch (bytes memory reason) { assembly ("memory-safe") { selector := mload(add(reason, 0x20)) tickAfter := mload(add(reason, 0x24)) } } if (selector != SlippageFeeHook__RevertWithTick.selector) { revert SlippageFeeHook__UnexpectedRevert(); } fee = _calculateFee(currentTick, tickAfter, params.zeroForOne); } function _beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) internal override returns (bytes4, BeforeSwapDelta, uint24) { uint24 fee; /// @dev Sets the fee for the swap simulation via this hook if (sender == address(this)) { fee = 0; } else { ExactSingleParams memory exactSingleParams = ExactSingleParams({ poolKey: key, zeroForOne: params.zeroForOne, amountSpecified: params.amountSpecified }); fee = _getExactSingleFee(exactSingleParams); } return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee | LPFeeLibrary.OVERRIDE_FEE_FLAG); } /** * @dev Check that the pool key has a dynamic fee. */ function _afterInitialize(address, PoolKey calldata key, uint160, int24) internal pure override returns (bytes4) { if (!key.fee.isDynamicFee()) revert SlippageFeeHook__NotDynamicFee(); return this.afterInitialize.selector; } ``` Source: https://github.com/dennnis0204/slippage-fee-hook/blob/a6764f52a0b92b1f17c40fd918d3caf86ba1dded/src/SlippageFeeHook.sol Status: experimental License: MIT Categories: dynamic-fees, mev-protection Properties: dynamic-fee, vanilla-swap Chains: ethereum Flags: afterInitialize, beforeSwap Page: https://v4hooks.com/hooks/slippage-fee ## SniperTax (sniper-tax) Kind: pattern Launch LP fee decays linearly from startFee to baseFee over duration seconds. First-party teaching hook inspired by Zora ZoraV4CoinHook launch fee decay. afterInitialize records launchTime; beforeSwap returns a fee that linearly decays from startFee (e.g. 99%) to baseFee (e.g. 1%) over duration seconds. Requires a dynamic-fee pool. Contrast anti-snipe.yml which caps buy size in blocks. Experimental. Forge tests in test/SniperTax.t.sol. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT function feeFor(PoolId poolId) public view returns (uint24) { uint256 elapsed = block.timestamp - launchTime[poolId]; if (elapsed >= duration) return baseFee; uint256 delta = uint256(startFee) - uint256(baseFee); return uint24(uint256(baseFee) + (delta * (duration - elapsed)) / duration); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/SniperTax.sol Status: experimental License: MIT Categories: launchpads, dynamic-fees, mev-protection Properties: dynamic-fee, vanilla-swap Chains: base Flags: afterInitialize, beforeSwap Page: https://v4hooks.com/hooks/sniper-tax ## Stop-loss (stop-loss) Kind: pattern Tick-walk fill-on-cross stop loss via unlock-wrapped market sells. First-party rewrite of saucepoint v4-stoploss (MIT). afterInitialize snapshots tickLowerLast. registerStop records size at a trigger tick. afterSwap walks downward crosses and fillStopLoss runs an unlock-wrapped market sell. Uses 14 permission fields and OpenZeppelin BaseHook. Simplified vs original: no ERC1155 receipts. Experimental teaching hook. Listing is not an audit. Forge tests under test/StopLoss.t.sol. ### Solidity ```solidity function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: false, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/StopLoss.sol Status: experimental License: MIT Categories: limit-orders Properties: vanilla-swap Chains: ethereum Flags: afterInitialize, afterSwap Docs: https://github.com/saucepoint/v4-stoploss Page: https://v4hooks.com/hooks/stop-loss ## SuckerPunch (sucker-punch) Kind: pattern Free ETH-pair buys, hold-time sell fee decay, same-block sell taxed as MEV. First-party rewrite of PopFendi’s EthLondon 2024 SuckerPunch (MIT). afterInitialize requires a native currency0 pair and a dynamic fee pool. beforeSwap returns a per-swap fee with OVERRIDE_FEE_FLAG: buys are free, same-block sells pay MEV_FEE, never-bought sells pay BASE_FEE, and holders decay linearly to MIN_FEE over 90 days. afterSwap records buy inventory keyed by abi.encode(trader) in hookData (else the swap sender). Fixes vs the original: 14 permission fields, OpenZeppelin BaseHook onlyPoolManager, no tx.origin, no updateDynamicSwapFee, and hold-fee math that does not underflow. Experimental. Listing is not an audit. Forge tests live under test/SuckerPunch.t.sol. ### Solidity ```solidity // SPDX-License-Identifier: MIT // v4hooks first-party rewrite — excerpt. Full file: source.url function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData) internal view override returns (bytes4, BeforeSwapDelta, uint24) { if (!key.currency0.isAddressZero()) revert NotNativePair(); address trader = traderOf(sender, hookData); bool isBuy = params.zeroForOne; uint24 fee = feeFor(key.toId(), trader, isBuy); return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee | LPFeeLibrary.OVERRIDE_FEE_FLAG); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/SuckerPunch.sol Status: experimental License: MIT Categories: dynamic-fees, mev-protection Properties: dynamic-fee, custom-swap-data, vanilla-swap Chains: ethereum Flags: afterInitialize, beforeSwap, afterSwap Docs: https://github.com/popfendi/suckerpunch Page: https://v4hooks.com/hooks/sucker-punch ## Super DCA (super-dca) Kind: product Dynamic LP fee in beforeSwap from who is swapping; pool must include the DCA token. SuperDCAGauge (Apache-2.0) requires the Super DCA token in the pair and tickSpacing 60 in _beforeInitialize, then dynamic fees after init. _beforeSwap only accepts verified routers, reads msgSender, and returns fee | OVERRIDE_FEE_FLAG (0 for internal, keeper vs external). Liquidity callbacks also exist for gauge accounting. This is how to do allowlisted fee tiers, not a generic TWAMM you should copy blindly. Full SuperDCAGauge.sol at the permalink. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: Apache-2.0 function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: true, beforeAddLiquidity: true, afterAddLiquidity: false, beforeRemoveLiquidity: true, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function _beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata hookData) internal override returns (bytes4, BeforeSwapDelta, uint24) { if (!verifiedRouters[sender]) revert SuperDCAGauge__UnauthorizedRouter(); address swapper = IMsgSender(sender).msgSender(); uint24 fee = isInternalAddress[swapper] ? internalFee : swapper == keeper ? keeperFee : externalFee; if (!isInternalAddress[swapper] && swapper != keeper) _handleDistributionAndSettlement(key, hookData); return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee | LPFeeLibrary.OVERRIDE_FEE_FLAG); } ``` Source: https://github.com/Super-DCA-Tech/super-dca-gauge/blob/master/src/SuperDCAGauge.sol Status: production License: Apache-2.0 Categories: twamm, dynamic-fees, ve Properties: dynamic-fee Chains: base, ethereum Flags: beforeInitialize, afterInitialize, beforeAddLiquidity, beforeRemoveLiquidity, beforeSwap Website: https://superdca.org/ Page: https://v4hooks.com/hooks/super-dca ## SurgeFee (surge-fee) Kind: pattern LP fee scales with swap size vs pool liquidity — no simulate-and-revert. First-party teaching hook inspired by Spark Surge Fees. Contrast with slippage-fee.yml: that hook simulates the swap and reverts with the post-trade tick to price the fee; SurgeFee reads pool liquidity from StateLibrary and adds surgeFactor * (tradeSize / liquidity) to baseFee in beforeSwap, capped at maxFee. No try/catch simulation — cheaper gas, coarser signal (size vs liquidity, not tick walk). Inherits OpenZeppelin BaseOverrideFee. Requires dynamic fee pool. Experimental. Forge tests in test/SurgeFee.t.sol. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT // v4hooks first-party — excerpt. Full file: source.url function feeFor(PoolKey calldata key, SwapParams calldata params) public view returns (uint24) { uint128 liquidity = poolManager.getLiquidity(key.toId()); if (liquidity == 0) return baseFee; uint256 tradeSize = params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified); uint256 surge = (tradeSize * uint256(surgeFactor)) / uint256(liquidity); uint256 total = uint256(baseFee) + surge; if (total > maxFee) return maxFee; return uint24(total); } function _getFee(address, PoolKey calldata key, SwapParams calldata params, bytes calldata) internal view override returns (uint24) { return feeFor(key, params); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/SurgeFee.sol Status: experimental License: MIT Categories: dynamic-fees, launchpads Properties: dynamic-fee, vanilla-swap Chains: ethereum Flags: afterInitialize, beforeSwap Page: https://v4hooks.com/hooks/surge-fee ## Take profits (take-profits) Kind: pattern Tick-walk fill-on-cross take profit on upward price crosses. First-party rewrite inspired by LearnWeb3 take-profits hook. Mirror of StopLoss: registerProfit at a tick, afterSwap fills on upward crosses with unlock-wrapped swaps. 14 permission fields, OpenZeppelin BaseHook, current v4 API. No ERC1155 layer in this teaching cut. Listing is not an audit. Forge tests under test/TakeProfit.t.sol. ### Solidity ```solidity function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: false, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/TakeProfit.sol Status: experimental License: MIT Categories: limit-orders Properties: vanilla-swap Chains: ethereum Flags: afterInitialize, afterSwap Docs: https://github.com/LearnWeb3DAO/uniswap-v4-take-profits-hook Page: https://v4hooks.com/hooks/take-profits ## Trading days (trading-days) Kind: pattern Revert beforeSwap outside NY weekday cash hours (9:30–16:00 ET, fixed UTC-5). First-party rewrite inspired by horsefacts trading-days (MIT). beforeSwap reverts on weekends or outside the cash session. First open swap of the day emits DingDingDing. Simplified vs the original: no holiday/DST libraries — weekday + hour gate only. 14 permission fields, OpenZeppelin BaseHook. Listing is not an audit. Forge tests under test/TradingDays.t.sol. ### Solidity ```solidity function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: false, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/TradingDays.sol Status: experimental License: MIT Categories: rwa, compliance Properties: vanilla-swap Chains: ethereum Flags: beforeSwap Docs: https://github.com/horsefacts/trading-days Page: https://v4hooks.com/hooks/trading-days ## Trailing stop (trailing-stop) Kind: pattern Peak-tick trailing stop with trail fee on sells after retrace. First-party rewrite inspired by Hookathon C1 Trailing Hook. armTrail sets peak tick per trader. afterSwap updates peak on buys; beforeSwap applies TRAIL_FEE when a sell retraces trailDistance ticks from peak. Dynamic fee pool with OVERRIDE_FEE_FLAG. Experimental. Listing is not an audit. ### Solidity ```solidity function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/TrailingStop.sol Status: experimental License: MIT Categories: limit-orders Properties: dynamic-fee, custom-swap-data, vanilla-swap Chains: ethereum Flags: afterInitialize, beforeSwap, afterSwap Page: https://v4hooks.com/hooks/trailing-stop ## TWAMM (twamm) Kind: pattern Execute large orders over time by settling virtual TWAMM flow before each swap and LP change. Uniswap’s example TWAMM hook runs executeTWAMMOrders in beforeInitialize, beforeAddLiquidity, and beforeSwap so long-lived orders accrue between blocks and settle against the pool instead of dumping in one swap. Permissions must enable those three callbacks. The rest of the file (order storage, expiration) lives in the linked Solidity — copy the full contract from v4-periphery example-contracts, do not ship this excerpt. The example is UNLICENSED research code, not production. Confirm bytecode on hooklist if you route size. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: UNLICENSED function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeAddLiquidity: true, beforeRemoveLiquidity: false, afterAddLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function beforeAddLiquidity(address, PoolKey calldata key, IPoolManager.ModifyLiquidityParams calldata, bytes calldata) external override onlyByManager returns (bytes4) { executeTWAMMOrders(key); return BaseHook.beforeAddLiquidity.selector; } function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) external override onlyByManager returns (bytes4, BeforeSwapDelta, uint24) { executeTWAMMOrders(key); return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } ``` Source: https://github.com/Uniswap/v4-periphery/blob/example-contracts/contracts/hooks/examples/TWAMM.sol Status: experimental License: UNLICENSED Categories: twamm Properties: vanilla-swap, custom-swap-data Chains: ethereum Flags: beforeInitialize, beforeAddLiquidity, beforeSwap Docs: https://blog.uniswap.org/v4-twamm-hook Page: https://v4hooks.com/hooks/twamm ## V2 pair hook (v2-pair) Kind: pattern Run xy=k inside the hook: mint LP on the ERC20, NoOp the CLAMM swap with return-delta. hensha256’s V2PairHook implements Uniswap v2 constant-product math inside a v4 hook instead of concentrating liquidity. beforeInitialize binds factory, fee 0, tickSpacing 1, and the pair currencies. beforeAddLiquidity always reverts so LPs mint() on the hook ERC20. beforeSwap mints and burns ERC-6909 claims, prices with _getAmountOut, and returns a specified delta that NoOps the CLAMM; afterSwap returns the unspecified delta from transient storage. Flags include both return-delta bits. UNLICENSED research code. Distinct from Uniswap FullRange, which still uses the concentrated AMM. Copy the full file. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: UNLICENSED function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return 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 }); } function beforeAddLiquidity(address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata) external pure override returns (bytes4) { revert AddLiquidityDirectToHook(); } function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) external override poolManagerOnly returns (bytes4, int128) { bool exactIn = (params.amountSpecified < 0); uint256 amountIn; uint256 amountOut; if (exactIn) { amountIn = uint256(-params.amountSpecified); amountOut = _getAmountOut(params.zeroForOne, amountIn); } else { amountOut = uint256(params.amountSpecified); amountIn = _getAmountIn(params.zeroForOne, amountOut); } (Currency inputCurrency, Currency outputCurrency) = _getInputOutput(key, params.zeroForOne); poolManager.mint(address(this), CurrencyLibrary.toId(inputCurrency), amountIn); poolManager.burn(address(this), CurrencyLibrary.toId(outputCurrency), amountOut); return (IHooks.beforeSwap.selector, int128(-params.amountSpecified)); } ``` Source: https://github.com/hensha256/v2-on-v4/blob/main/src/V2PairHook.sol Status: experimental License: UNLICENSED Categories: wrappers, lp-management Properties: vanilla-swap Chains: ethereum Flags: beforeInitialize, beforeAddLiquidity, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta Page: https://v4hooks.com/hooks/v2-pair ## veLP (velp) Kind: pattern Curve-style vote-escrow on a v4 position: lock ticks, block early withdraw in beforeModifyPosition. kadenzipfel ports VotingEscrow onto a Uniswap v4 hook. afterInitialize stores the PoolId. beforeModifyPosition lets unrelated ticks through, but if sender is modifying their locked range it requires liquidityDelta > 0 until lock end. afterModifyPosition checkpoints ve weight from the position’s liquidity. Early Hooks.Calls API (modifyPosition, not add/remove). AGPL-3.0 — copy the full VotingEscrow.sol. Map modifyPosition to today’s beforeAddLiquidity / beforeRemoveLiquidity if you port forward. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: AGPL-3.0-or-later function getHooksCalls() public pure override returns (Hooks.Calls memory) { return Hooks.Calls({ beforeInitialize: false, afterInitialize: true, beforeModifyPosition: true, afterModifyPosition: true, beforeSwap: false, afterSwap: false, beforeDonate: false, afterDonate: false }); } function beforeModifyPosition( address sender, PoolKey calldata, IPoolManager.ModifyPositionParams calldata modifyPositionParams ) external view override poolManagerOnly returns (bytes4) { LockTicks memory lockTicks_ = lockTicks[sender]; if ( lockTicks_.lowerTick != modifyPositionParams.tickLower || lockTicks_.upperTick != modifyPositionParams.tickUpper ) { return VotingEscrow.beforeModifyPosition.selector; } LockedBalance memory locked_ = locked[sender]; require( modifyPositionParams.liquidityDelta > 0 || locked_.end <= block.timestamp, "Can't withdraw before lock end" ); return VotingEscrow.beforeModifyPosition.selector; } ``` Source: https://github.com/kadenzipfel/veLP/blob/main/src/VotingEscrow.sol Status: experimental License: AGPL-3.0-or-later Categories: ve, lp-management Properties: vanilla-swap Chains: ethereum Flags: afterInitialize, beforeAddLiquidity, afterAddLiquidity, beforeRemoveLiquidity, afterRemoveLiquidity Page: https://v4hooks.com/hooks/velp ## VerifierHook (purefi-verifier) Kind: pattern Gate every pool action behind an off-chain AML proof checked in the callback. PureFi's VerifierHook is a compliance gate wired into almost every callback v4 offers: beforeAddLiquidity, afterAddLiquidity, beforeRemoveLiquidity, afterRemoveLiquidity, beforeSwap, afterSwap and beforeDonate. Seven bits is a lot of surface, and the reason is that a compliance rule which only covers swaps is trivially sidestepped by moving liquidity instead. The hook holds an immutable IPureFiVerifier and a PureFiHookWhitelist. Callers hand it a signed PureFi package — an off-chain AML/KYC attestation — which the callback decodes through PureFiDataLibrary and checks against a ruleId mapping before letting the action through. Roles are OpenZeppelin AccessControl: ISSUER, MARKET_MAKER, ROUTER and QUOTER, plus a routersWhitelist so only sanctioned routers can reach the pool at all. That last part is the important structural detail. The proof travels as hookData, and the repo ships its own PureFiSwapRouter and PureFiModifyLiquidityRouter to carry it, so this is not a pool you can trade against with a stock router — the swap path is part of the product. Read the routers alongside the hook; the hook alone will not tell you how the proof gets there. Copy the shape, not the dependency. Gating all four liquidity callbacks plus both swap callbacks is the transferable idea; swap IPureFiVerifier for whatever attestation source you actually trust and the rest of the structure still holds. Unaudited in this repository. ### Solidity ```solidity // SPDX-License-Identifier: MIT // purefiprotocol/purefi-verifier-hook — excerpt. Full file: source.url contract VerifierHook is BaseHook, AccessControl { IPureFiVerifier public immutable verifier; PureFiHookWhitelist public immutable whitelist; mapping(address => bool) private routersWhitelist; mapping(uint32 => bool) public ruleId; bytes32 public constant ISSUER = keccak256('ISSUER'); bytes32 public constant MARKET_MAKER = keccak256('MARKET_MAKER'); bytes32 public constant ROUTER = keccak256('ROUTER'); bytes32 public constant QUOTER = keccak256('QUOTER'); function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: false, beforeAddLiquidity: true, afterAddLiquidity: true, beforeRemoveLiquidity: true, afterRemoveLiquidity: true, beforeSwap: true, afterSwap: true, beforeDonate: true, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } // Every gated callback decodes a signed PureFi package out of hookData via // PureFiDataLibrary and checks it against ruleId before allowing the action. // The proof is delivered by the repo's own PureFiSwapRouter / // PureFiModifyLiquidityRouter — a stock router cannot reach this pool. ``` Source: https://github.com/purefiprotocol/purefi-verifier-hook/blob/1efdca6703a473942a02d4a166f0f5292bcecf5b/src/VerifierHook.sol Status: experimental License: MIT Categories: compliance Properties: custom-swap-data Chains: ethereum Flags: beforeAddLiquidity, afterAddLiquidity, beforeRemoveLiquidity, afterRemoveLiquidity, beforeSwap, afterSwap, beforeDonate Website: https://purefi.io/ Page: https://v4hooks.com/hooks/purefi-verifier ## Volatility oracle (volatility-oracle) Kind: pattern Require a dynamic fee on initialize, then ramp LP fee by elapsed time after deploy. Uniswap’s VolatilityOracle example is a teaching dynamic-fee hook, not a live volatility feed. beforeInitialize requires key.fee.isDynamicFee(). afterInitialize calls setFee, which starts at 3000 and adds 100 bps per elapsed minute via poolManager.updateDynamicLPFee. UNLICENSED example-contracts file. Use it to learn the initialize-plus-poke pattern; do not treat the contract name as an oracle. Copy the full file from the permalink. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: UNLICENSED function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: true, afterInitialize: true, beforeAddLiquidity: false, beforeRemoveLiquidity: false, afterAddLiquidity: false, afterRemoveLiquidity: false, beforeSwap: false, afterSwap: false, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } function beforeInitialize(address, PoolKey calldata key, uint160, bytes calldata) external pure override returns (bytes4) { if (!key.fee.isDynamicFee()) revert MustUseDynamicFee(); return VolatilityOracle.beforeInitialize.selector; } function setFee(PoolKey calldata key) public { uint24 startingFee = 3000; uint32 lapsed = _blockTimestamp() - deployTimestamp; uint24 fee = startingFee + (uint24(lapsed) * 100) / 60; // 100 bps a minute manager.updateDynamicLPFee(key, fee); // initial fee 0.30% } ``` Source: https://github.com/Uniswap/v4-periphery/blob/example-contracts/contracts/hooks/examples/VolatilityOracle.sol Status: experimental License: UNLICENSED Categories: dynamic-fees Properties: dynamic-fee, vanilla-swap Chains: ethereum Flags: beforeInitialize, afterInitialize Docs: https://docs.uniswap.org/contracts/v4/concepts/hooks Page: https://v4hooks.com/hooks/volatility-oracle ## VolatilityFee (volatility-fee) Kind: pattern LP fee rises with |currentTick − lastTick| since the previous swap. First-party teaching hook inspired by Clanker-style tick-accumulator dynamic fees. Stores lastTick per pool; beforeSwap reads current tick from StateLibrary and adds tickFactor × |Δtick| to baseFee, capped at maxFee. afterSwap updates lastTick. Contrast surge-fee.yml (size vs liquidity) and volatility-oracle.yml (time ramp). Requires dynamic fee pool. Experimental. Forge tests in test/VolatilityFee.t.sol. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT function feeFor(PoolKey calldata key) public view returns (uint24) { (, int24 tick,,) = poolManager.getSlot0(key.toId()); uint256 move = _absTickDelta(tick, lastTick[key.toId()]); uint256 total = uint256(baseFee) + move * uint256(tickFactor); if (total > maxFee) return maxFee; return uint24(total); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/VolatilityFee.sol Status: experimental License: MIT Categories: dynamic-fees Properties: dynamic-fee, vanilla-swap Chains: base Flags: afterInitialize, beforeSwap, afterSwap Page: https://v4hooks.com/hooks/volatility-fee ## Volume tier (volume-tier) Kind: pattern Cumulative swap notional unlocks lower dynamic fees for high-volume traders. First-party rewrite inspired by EthLondon Royalty Swap (Keinberger/royalty-swap). afterSwap accumulates amountSpecified notional per trader (hookData address). beforeSwap returns tiered fee with OVERRIDE_FEE_FLAG at tier1/tier2 breakpoints. Distinct from SuckerPunch hold-time fees and SurgeFee size/liquidity scaling. Experimental. Listing is not an audit. ### Solidity ```solidity function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: false, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } ``` Source: https://github.com/CryptoGnome/v4hooks/blob/main/contracts/VolumeTier.sol Status: experimental License: MIT Categories: dynamic-fees Properties: dynamic-fee, custom-swap-data, vanilla-swap Chains: ethereum Flags: afterInitialize, beforeSwap, afterSwap Docs: https://github.com/Keinberger/royalty-swap Page: https://v4hooks.com/hooks/volume-tier ## Zora Coin Hook (zora-coin-hook) Kind: product Creator coin hook: launch fee decay, LP positions on init, fee swap + reward distribution. ZoraV4CoinHook is Zora production code. On pool init it seeds multi-range LP from coin config. beforeSwap returns a dynamic launch fee that decays from ~99% to 1% over ~10 seconds after coin creation (LAUNCH_FEE_START → LP_FEE_V4). afterSwap collects LP fees, swaps to backing currency, and distributes rewards. Supports hook upgrades via IUpgradeableV4Hook migration paths. Use Zora's factory — do not reimplement to launch a coin. Excerpt shows permissions and launch fee calc. Full file at the permalink. Listing is not an audit. ### Solidity ```solidity // SPDX-License-Identifier: MIT function getHookPermissions() public pure override returns (Hooks.Permissions memory) { return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeAddLiquidity: true, afterAddLiquidity: false, beforeRemoveLiquidity: false, afterRemoveLiquidity: false, beforeSwap: true, afterSwap: true, beforeDonate: false, afterDonate: false, beforeSwapReturnDelta: false, afterSwapReturnDelta: false, afterAddLiquidityReturnDelta: false, afterRemoveLiquidityReturnDelta: false }); } /// Fee decays linearly from LAUNCH_FEE_START (99%) to LP_FEE_V4 (1%) over LAUNCH_FEE_DURATION. function _calculateLaunchFee(address coin) internal view returns (uint24 fee) { // elapsed since IHasCreationInfo.creationTime(); returns OVERRIDE_FEE_FLAG | fee } ``` Source: https://github.com/ourzora/zora-protocol/blob/main/packages/coins/src/hooks/ZoraV4CoinHook.sol Status: production License: MIT Categories: launchpads, creator-economy, dynamic-fees Properties: dynamic-fee, vanilla-swap Chains: base Flags: afterInitialize, beforeAddLiquidity, beforeSwap, afterSwap Website: https://zora.co/ Docs: https://docs.zora.co/ Page: https://v4hooks.com/hooks/zora-coin-hook