Skip to main content
Helpful?

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 initial msg.sender for the add liquidity call
  • key The key for the pool
  • params The parameters for adding liquidity i.e. ModifyLiquidityParams from IPoolManager.sol
  • hookData Arbitrary data handed into the PoolManager 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 call
  • key The key for the pool
  • params The parameters for removing liquidity i.e. ModifyLiquidityParams from IPoolManager.sol
  • hookData Arbitrary data handed into the PoolManager 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;
}
}
Helpful?