Skip to main content
Helpful?

Swap Hooks

Swaps are the most common interaction with the Uniswap protocol. When it comes to swap there are two hook functions available to customize and extend its behavior:

  • beforeSwap
  • afterSwap

As the names suggest beforeSwap/afterSwap are functions called before or after a swap is executed on a pool.

This guide will explain the mechanism of encoded flags for hooks, and go through the parameters and examples for beforeSwap and afterSwap.

Note: The swap 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 SwapHook, use PoolIdLibrary to attach functions of computing ID of a pool to PoolKey. Declare two mappings to act as counters when calling beforeSwap and afterSwap.

contract SwapHook 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 beforeSwapCount;
mapping(PoolId => uint256 count) public afterSwapCount;

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: false,
afterAddLiquidity: false,
beforeRemoveLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: true,
afterSwap: true,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}

beforeSwap

Here the example shows that every time before a swap is executed in a pool, beforeSwapCount for that pool will be incremented by one.

function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata)
external
override
returns (bytes4, BeforeSwapDelta, uint24)
{
beforeSwapCount[key.toId()]++;
return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
}

beforeSwap Parameters

When triggering the beforeSwap hook function, there are some parameters we can make use of to customize or extend the behavior of swap. These parameters are described in beforeSwap from IHooks.sol.

A brief overview of the parameters:

  • sender The initial msg.sender for the PoolManager.swap call. Typically a swap router
  • key The key for the pool
  • params The parameters for the swap i.e. SwapParams from IPoolManager.sol
  • hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook

afterSwap

Similiar as above, every time after a swap is executed in a pool, afterSwapCount for that pool will be incremented by one.

function afterSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata)
external
override
returns (bytes4, int128)
{
afterSwapCount[key.toId()]++;
return (BaseHook.afterSwap.selector, 0);
}

afterSwap Parameters

When triggering the afterSwap hook function, there are some parameters we can make use of to customize or extend the behavior of swap. These parameters are described in afterSwap from IHooks.sol.

A brief overview of the parameters:

  • sender The initial msg.sender for the PoolManager.swap call. Typically a swap router
  • key The key for the pool
  • params The parameters for the swap i.e. SwapParams from IPoolManager.sol
  • delta The amount owed to the caller (positive) or owed to the pool (negative)
  • hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook

A Complete Swap 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 SwapHook 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 beforeSwapCount;
mapping(PoolId => uint256 count) public afterSwapCount;

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: true,
afterSwap: true,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}

// -----------------------------------------------
// NOTE: see IHooks.sol for function documentation
// -----------------------------------------------

function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata)
external
override
returns (bytes4, BeforeSwapDelta, uint24)
{
beforeSwapCount[key.toId()]++;
return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
}

function afterSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata)
external
override
returns (bytes4, int128)
{
afterSwapCount[key.toId()]++;
return (BaseHook.afterSwap.selector, 0);
}
}
Helpful?