V4HOOKSCUT SHEET DIRECTORY

Index / LiquidityPenaltyHook

pattern · MIT · last updated 2026-08-27

LiquidityPenaltyHook

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.

Permission bits

These bits must match the deployed address. Confirm on-chain before you route.

Initialize
ba
Liquidity
baba
Swap
ba
Donate
ba
Return delta
baaa

afterAddLiquidityafterRemoveLiquidityafterAddLiquidityReturnDeltaafterRemoveLiquidityReturnDelta

Solidity

Excerpt from src/general/LiquidityPenaltyHook.sol. Copy the full file to implement this. Not an audit.

// 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);
}

Spec

Kindpattern
Statusexperimental
LicenseMIT
Sourcehttps://github.com/OpenZeppelin/uniswap-hooks
CategoriesMEV protectionLP management
PropertiesVanilla swap
ChainsEthereum
Websitehttps://v4hooks.com/learn/openzeppelin-hooks
Docshttps://github.com/OpenZeppelin/uniswap-hooks

FAQ

Can I paste this into production? The snippet is an excerpt. Use the full file at the source URL, match flags to the address, and treat this page as a map, not a guarantee.

How do I build this safely? Start with secure v4 hooks and the OpenZeppelin hooks guide.