Skip to main content
Helpful?

Set Up Local Environment

Before writing the hook let's first have a local environment properly configured e.g. deploying pool manager, utility routers and test tokens.

At the end of this guide a development environment will be set up to be used to build the rest of the examples in the Guides section of the docs.

To get started as quickly as possible for building Uniswap v4 hooks, there is a Quick Start section below to clone a boilerplate and get building. To start from scratch and learn the underlying concepts, jump to the Start from Scratch section.

Quick Start

The Uniswap v4-template repo provides a basic foundry environment with required imports already pre-loaded. Click on Use this template to create a new repository with it.

Or simply clone it and install the dependencies:

git clone https://github.com/uniswapfoundation/v4-template.git
cd v4-template
# requires foundry
forge install
forge test

Then hop to the Local Node via Anvil to complete the set up and start developing.


Start from Scratch

In the following sections, let's walk through the steps to create the same environment set up as the boilerplate from scratch and learn the underlying concepts.

Setting up Foundry​

First thing is to set up a new Foundry project.

If there is no Foundry installed - follow the Foundry Book for installation.

Once Foundry is setup, initialize a new project:

forge init counter-hook
cd counter-hook

Then install the Uniswap v4-core and v4-periphery contracts as dependencies:

forge install Uniswap/v4-core && forge install Uniswap/v4-periphery

Next, set up the remappings so that the shorthand syntax for importing contracts from the dependencies work nicely:

forge remappings > remappings.txt

If there is something wrong with the inferred remappings, please replace with the following in remappings.txt:

@uniswap/v4-core/=lib/v4-core/
forge-gas-snapshot/=lib/v4-core/lib/forge-gas-snapshot/src/
forge-std/=lib/v4-core/lib/forge-std/src/
permit2/=lib/v4-periphery/lib/permit2/
solmate/=lib/v4-core/lib/solmate/
v4-core/=lib/v4-core/
v4-periphery/=lib/v4-periphery/

After that, remove the default Counter contract and its associated test and script file that Foundry initially set up. To do that, either manually delete those files, or just run the following:

rm ./**/Counter*.sol

Finally, since v4 uses transient storage which is only available after Ethereum's cancun hard fork and on Solidity versions >= 0.8.24 - some config must be set in foundry.toml config file.

To do that, add the following lines to foundry.toml:

# foundry.toml

solc_version = "0.8.26"
evm_version = "cancun"
ffi = true

Awesome! Now it's all set to start building the hook! Let’s run a quick test to confirm everything is set up properly.

Compile a Basic Hook Contract​

To confirm that the environment is configured correctly let's write a basic Counter Hook contract. Create a new file, ./src/CounterHook.sol and paste the following code into it:

// 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 CounterHook 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;

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

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

To compile the Counter Hook contracts in the ./src folder, use the foundry build command:

forge build

If the environment is compiled correctly it will display a message:

Compiler run successful!

Local Node via Anvil​

Other than writing unit tests, Anvil can be used to deploy and test hooks.

With the local node up and running, use the --rpc-url 127.0.0.1:8545 flag in tests to point the Foundry testing suite to that local node:

# start anvil, a local EVM chain
anvil

# in a new terminal
# foundry script for deploying v4 & hooks to anvil
forge script script/Anvil.s.sol \
--rpc-url http://localhost:8545 \
--private-key <test_wallet_private_key> \
--broadcast

# test on the anvil local node
forge test --rpc-url 127.0.0.1:8545

Next Steps

With the environment set up ready to be built on. Jump over to the guides section to learn about the Uniswap functions that can be integrated with Hook. Remember to add all contracts (.sol files) to the ./src folder and their subsequent tests to the ./test folder. Then test them against the local anvil node by running:

forge test --rpc-url 127.0.0.1:8545
Helpful?