Uniswap V4 introduces a powerful new feature called Liquidity Hooks, enabling developers to customize and extend core pool behaviors during liquidity operations. These hooks unlock unprecedented flexibility for DeFi builders, allowing them to implement custom logic before or after liquidity is added or removed from a pool.
This comprehensive guide walks through the implementation of beforeAddLiquidity and beforeRemoveLiquidity hooks using Solidity, explains key parameters, and demonstrates how to build a functional hook contract that interacts seamlessly with Uniswap’s advanced architecture.
Whether you're building yield-enhancing strategies, compliance layers, or automated rebalancing tools, understanding liquidity hooks is essential for leveraging Uniswap V4’s full potential.
👉 Discover how liquidity hooks can supercharge your DeFi strategy with real-time execution tools.
Understanding Liquidity Hooks in Uniswap V4
Liquidity hooks are callback functions executed at specific points during a pool’s lifecycle. In Uniswap V4, these hooks allow developers to inject custom logic into standard operations such as adding or removing liquidity.
There are four primary hook functions available:
beforeAddLiquidityafterAddLiquiditybeforeRemoveLiquidityafterRemoveLiquidity
As their names suggest:
beforeAddLiquidityandafterAddLiquidityexecute around liquidity addition.beforeRemoveLiquidityandafterRemoveLiquidityrun when liquidity is withdrawn.
This guide focuses on the pre-operation hooks — beforeAddLiquidity and beforeRemoveLiquidity — which are ideal for validating conditions, tracking activity, or triggering external actions prior to state changes.
These examples are simplified for educational purposes and not intended for direct production use without further security audits and optimization.
Setting Up the Hook Contract
To begin, declare the Solidity version. Since Uniswap V4 leverages transient storage (introduced in Solidity 0.8.24), your contract must use version ^0.8.24 or higher.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;Next, import necessary modules from the Uniswap v4-core and v4-periphery libraries:
import { BaseHook } from "v4-periphery/src/utils/BaseHook.sol";
import { Hooks } from "v4-core/src/libraries/Hooks.sol";
import { IPoolManager } from "v4-core/src/interfaces/IPoolManager.sol";
import { PoolKey } from "v4-core/src/types/PoolKey.sol";
import { PoolId, PoolIdLibrary } from "v4-core/src/types/PoolId.sol";
import { BalanceDelta } from "v4-core/src/types/BalanceDelta.sol";
import { BeforeSwapDelta, BeforeSwapDeltaLibrary } from "v4-core/src/types/BeforeSwapDelta.sol";Now, define the contract by extending BaseHook. This base class provides essential integration with the Uniswap pool manager.
contract LiquidityHook is BaseHook {
using PoolIdLibrary for PoolKey;
mapping(PoolId => uint256) public beforeAddLiquidityCount;
mapping(PoolId => uint256) public beforeRemoveLiquidityCount;
constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}
}Two state variables track how many times each hook has been triggered per pool. Note that state variables should be scoped to individual pools to ensure one hook contract can serve multiple pools securely.
Configuring Hook Permissions
Every hook must explicitly declare which callbacks it implements via the getHookPermissions function. This serves both as documentation and a runtime validation mechanism.
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: false,
afterInitialize: false,
beforeAddLiquidity: true,
afterAddLiquidity: false,
beforeRemoveLiquidity: true,
afterRemoveLiquidity: false,
beforeSwap: false,
afterSwap: false,
beforeDonate: false,
afterDonate: false,
beforeAddLiquidityReturnDelta: false,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}In this configuration, only beforeAddLiquidity and beforeRemoveLiquidity are enabled — all others are disabled for simplicity.
Implementing beforeAddLiquidity
The beforeAddLiquidity hook runs just before liquidity is added to a pool. It allows developers to inspect or modify behavior based on context.
Here’s an example implementation that increments a counter each time the hook is called:
function _beforeAddLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) internal override returns (bytes4) {
beforeAddLiquidityCount[key.toId()]++;
return BaseHook.beforeAddLiquidity.selector;
}Each time someone adds liquidity to a pool, this function increases the associated counter by one.
Parameters Explained
The hook receives four parameters:
sender: The original caller (msg.sender) initiating the liquidity addition.key: A unique identifier for the pool (token pair, fee tier, etc.).params: Struct containing details like desired liquidity amount, tick ranges, and slippage tolerances.hookData: Arbitrary data passed by the user, useful for signaling intent or passing configuration.
These inputs enable conditional logic — for instance, restricting deposits during certain market conditions or logging metadata off-chain.
👉 Learn how integrating smart hooks can improve your trading efficiency with advanced analytics.
Implementing beforeRemoveLiquidity
Similarly, beforeRemoveLiquidity executes prior to withdrawing liquidity.
function _beforeRemoveLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) internal override returns (bytes4) {
beforeRemoveLiquidityCount[key.toId()]++;
return BaseHook.beforeRemoveLiquidity.selector;
}Like its counterpart, this version also increments a per-pool counter, providing visibility into withdrawal frequency.
Key Use Cases
Such tracking enables several practical applications:
- Detecting sudden outflows that may signal market shifts.
- Enforcing cooldown periods between withdrawals.
- Triggering alerts or rebalancing mechanisms in yield strategies.
Developers can expand these hooks to interact with external protocols, emit events, or enforce access control — limited only by creativity and gas efficiency.
Complete Liquidity Hook Contract
Below is the full working example combining all components:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import { BaseHook } from "v4-periphery/src/utils/BaseHook.sol";
import { Hooks } from "v4-core/src/libraries/Hooks.sol";
import { IPoolManager } from "v4-core/src/interfaces/IPoolManager.sol";
import { PoolKey } from "v4-core/src/types/PoolKey.sol";
import { PoolId, PoolIdLibrary } from "v4-core/src/types/PoolId.sol";
import { BalanceDelta } from "v4-core/src/types/BalanceDelta.sol";
import { BeforeSwapDelta, BeforeSwapDeltaLibrary } from "v4-core/src/types/BeforeSwapDelta.sol";
contract LiquidityHook is BaseHook {
using PoolIdLibrary for PoolKey;
mapping(PoolId => uint256) public beforeAddLiquidityCount;
mapping(PoolId => uint256) public beforeRemoveLiquidityCount;
constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: false,
afterInitialize: false,
beforeAddLiquidity: true,
afterAddLiquidity: false,
beforeRemoveLiquidity: true,
afterRemoveLiquidity: false,
beforeSwap: false,
afterSwap: false,
beforeDonate: false,
afterDonate: false,
beforeAddLiquidityReturnDelta: false,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}
function _beforeAddLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) internal override returns (bytes4) {
beforeAddLiquidityCount[key.toId()]++;
return BaseHook.beforeAddLiquidity.selector;
}
function _beforeRemoveLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) internal override returns (bytes4) {
beforeRemoveLiquidityCount[key.toId()]++;
return BaseHook.beforeRemoveLiquidity.selector;
}
}Frequently Asked Questions
Q: What are liquidity hooks used for in Uniswap V4?
A: Liquidity hooks allow developers to insert custom logic before or after liquidity operations like adding or removing funds from a pool. They enable advanced features such as automated monitoring, conditional access, and integration with other DeFi protocols.
Q: Can one hook contract manage multiple pools?
A: Yes. By scoping state variables to PoolId, a single hook contract can safely service multiple pools without interference, reducing deployment costs and improving maintainability.
Q: Are there gas implications when using hooks?
A: Yes. Every hook execution consumes additional gas due to extra computations and storage updates. Developers should optimize logic carefully to avoid excessive fees for end users.
Q: How do I pass data into a hook?
A: Use the hookData parameter when calling modifyLiquidity. This arbitrary data is forwarded to the hook, allowing dynamic behavior based on user input.
Q: Can I modify liquidity parameters inside a hook?
A: Not directly in beforeAddLiquidity or beforeRemoveLiquidity. However, you can validate inputs and revert if conditions aren’t met. For parameter modification, consider delta-returning hooks (e.g., afterAddLiquidityReturnDelta).
👉 Explore how real-time data tools can help optimize your liquidity management decisions.
Core Keywords
- Uniswap V4
- liquidity hooks
- Solidity smart contracts
- DeFi development
- pool management
- beforeAddLiquidity
- beforeRemoveLiquidity
- custom hooks
By mastering these concepts, developers gain fine-grained control over liquidity interactions — paving the way for next-generation decentralized finance applications built on Uniswap’s cutting-edge infrastructure.