Liquidity Hooks
This guide will walk through on an example of adding and removing liquidity. There are four hook functions available to customize and extend these behavior:
beforeAddLiquidity
afterAddLiquidity
beforeRemoveLiquidity
afterRemoveLiquidity
As the names suggest beforeAddLiquidity
/afterAddLiquidity
are functions called before or after liquidity is added to a pool.
Similarly beforeRemoveLiquidity
/afterRemoveLiquidity
are functions called before or after liquidity is removed from a pool.
This guide will go through the parameters and examples specifically for beforeAddLiquidity
and beforeRemoveLiquidity
.
Note: The liquidity examples are not production ready code, and are implemented in a simplistic manner for the purpose of learning.
Set Up the Contract
Declare the solidity version used to compile the contract, since transient storage is used the solidity version will be >=0.8.24
.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
Import the relevant dependencies from v4-core
and v4-periphery
:
import {BaseHook} from "v4-periphery/src/base/hooks/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";
Create a contract called LiquidityHook, use PoolIdLibrary
to attach functions of computing ID of a pool to PoolKey
. Declare two mappings to act as counters when calling beforeAddLiquidity
and beforeRemoveLiquidity
.
contract LiquidityHook is BaseHook {
using PoolIdLibrary for PoolKey;
// NOTE: ---------------------------------------------------------
// state variables should typically be unique to a pool
// a single hook contract should be able to service multiple pools
// ---------------------------------------------------------------
mapping(PoolId => uint256 count) public beforeAddLiquidityCount;
mapping(PoolId => uint256 count) public beforeRemoveLiquidityCount;
constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}
Override getHookPermissions
from BaseHook.sol
to return a struct of permissions to signal which hook functions are to be implemented.
It will also be used at deployment to validate the address correctly represents the expected permissions.
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
});
}
beforeAddLiquidity
Here the example shows that every time before liquidity is added to a pool, beforeAddLiquidityCount
for that pool will be incremented by one.
function beforeAddLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) external override returns (bytes4) {
beforeAddLiquidityCount[key.toId()]++;
return BaseHook.beforeAddLiquidity.selector;
}
beforeAddLiquidity
Parameters
When triggering the beforeAddLiquidity
hook function, there are some parameters we can make use of to customize or extend the behavior of modifyLiquidity
. These parameters are described in beforeAddLiquidity
from IHooks.sol
.
A brief overview of the parameters:
sender
The initialmsg.sender
for the add liquidity callkey
The key for the poolparams
The parameters for adding liquidity i.e.ModifyLiquidityParams
fromIPoolManager.sol
hookData
Arbitrary data handed into thePoolManager
by the liquidity provider to be be passed on to the hook
beforeRemoveLiquidity
Similiar as above, every time before liquidity is removed from a pool, beforeRemoveLiquidityCount
for that pool will be incremented by one.
function beforeRemoveLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) external override returns (bytes4) {
beforeRemoveLiquidityCount[key.toId()]++;
return BaseHook.beforeRemoveLiquidity.selector;
}
beforeRemoveLiquidity
Parameters
When triggering the beforeRemoveLiquidity
hook function, there are some parameters we can make use of to customize or extend the behavior of modifyLiquidity
. These parameters are described in beforeRemoveLiquidity
from IHooks.sol
.
A brief overview of the parameters:
sender
The initial msg.sender for the remove liquidity callkey
The key for the poolparams
The parameters for removing liquidity i.e.ModifyLiquidityParams
fromIPoolManager.sol
hookData
Arbitrary data handed into thePoolManager
by the liquidity provider to be be passed on to the hook
A Complete Liquidity Hook Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {BaseHook} from "v4-periphery/src/base/hooks/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;
// NOTE: ---------------------------------------------------------
// state variables should typically be unique to a pool
// a single hook contract should be able to service multiple pools
// ---------------------------------------------------------------
mapping(PoolId => uint256 count) public beforeAddLiquidityCount;
mapping(PoolId => uint256 count) 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
});
}
// -----------------------------------------------
// NOTE: see IHooks.sol for function documentation
// -----------------------------------------------
function beforeAddLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) external override returns (bytes4) {
beforeAddLiquidityCount[key.toId()]++;
return BaseHook.beforeAddLiquidity.selector;
}
function beforeRemoveLiquidity(
address,
PoolKey calldata key,
IPoolManager.ModifyLiquidityParams calldata,
bytes calldata
) external override returns (bytes4) {
beforeRemoveLiquidityCount[key.toId()]++;
return BaseHook.beforeRemoveLiquidity.selector;
}
}