Skip to main content


Deploying Uniswap V4 Hooks involves several steps:

  1. Deploying the PoolManager Contract: This contract is typically pre-deployed on many test environments. However, you have the option to deploy it locally on your machine if required.

  2. Deploying the Hook Contract: The hook contract needs to be deployed at a predetermined address. You can use CREATE2 for deterministic deployment. A Deterministic Deployment Proxy, usually found at 0x4e59b44847b379578588920cA78FbF26c0B4956C, is employed for this purpose and is already available in most environments.

  3. Deploying Test Tokens: These tokens are essential for creating the pool. They need to be deployed before initializing the pool.

  4. Initializing the Pool with the Hook Contract Address: This is achieved by invoking the initialize(PoolKey memory key, uint160 sqrtPriceX96, bytes calldata hookData) function on the PoolManager contract.

  5. Adding Liquidity or Modifying Position: If you wish to add liquidity to the pool or alter its position, a utility contract that implements the ILockCallback interface is necessary. You may consider deploying a utility contract like PoolModifyPositionTest for these operations.

Deployment Scripts

The template includes a few scripts that help with deploying hooks. These scripts are located in the scripts folder.

Lets look at these scripts one by one:

1. Deploying Your Own Tokens

The template includes Mock UNI and Mock USDC contracts for testing. Deploy them using:

 forge create script/mocks/mUNI.sol:MockUNI \
--rpc-url [your_rpc_url_here] \
--private-key [your_private_key_on_goerli_here] \

forge create script/mocks/mUSDC.sol:MockUSDC \
--rpc-url [your_rpc_url_here] \
--private-key [your_private_key_on_goerli_here] \

2. script/01_CreatePool.s.sol

This script contains the steps for initializing the pool with an existing hook. It uses the pre-deployed PoolManager contract and token contracts

contract CreatePoolScript is Script {
using CurrencyLibrary for Currency;

//addresses with contracts deployed
address constant GOERLI_POOLMANAGER = address(0x3A9D48AB9751398BbFa63ad67599Bb04e4BdF98b); //pool manager deployed to GOERLI
address constant MUNI_ADDRESS = address(0xbD97BF168FA913607b996fab823F88610DCF7737); //mUNI deployed to GOERLI -- insert your own contract address here
address constant MUSDC_ADDRESS = address(0xa468864e673a807572598AB6208E49323484c6bF); //mUSDC deployed to GOERLI -- insert your own contract address here
address constant HOOK_ADDRESS = address(0x3CA2cD9f71104a6e1b67822454c725FcaeE35fF6); //address of the hook contract deployed to goerli -- you can use this hook address or deploy your own!

IPoolManager manager = IPoolManager(GOERLI_POOLMANAGER);

function run() external {
// sort the tokens!
address token0 = uint160(MUSDC_ADDRESS) < uint160(MUNI_ADDRESS) ? MUSDC_ADDRESS : MUNI_ADDRESS;
address token1 = uint160(MUSDC_ADDRESS) < uint160(MUNI_ADDRESS) ? MUNI_ADDRESS : MUSDC_ADDRESS;
uint24 swapFee = 4000;
int24 tickSpacing = 10;

// floor(sqrt(1) * 2^96)
uint160 startingPrice = 79228162514264337593543950336;

bytes memory hookData = abi.encode(block.timestamp);

PoolKey memory pool = PoolKey({
currency0: Currency.wrap(token0),
currency1: Currency.wrap(token1),
fee: swapFee,
tickSpacing: tickSpacing,
hooks: IHooks(HOOK_ADDRESS)

// Turn the Pool into an ID so you can use it for modifying positions, swapping, etc.
PoolId id = PoolIdLibrary.toId(pool);
bytes32 idBytes = PoolId.unwrap(id);

console.log("Pool ID Below");

manager.initialize(pool, startingPrice, hookData);

3. script/00_Counter.s.sol

This script deploys the Counter hook using Deterministic Deployment Proxy. It uses the pre-deployed PoolManager contract and proxy

contract CounterScript is Script {
address constant CREATE2_DEPLOYER = address(0x4e59b44847b379578588920cA78FbF26c0B4956C);
address constant GOERLI_POOLMANAGER = address(0x3A9D48AB9751398BbFa63ad67599Bb04e4BdF98b);

function setUp() public {}

function run() public {
// hook contracts must have specific flags encoded in the address
uint160 flags = uint160(

// Mine a salt that will produce a hook address with the correct flags
(address hookAddress, bytes32 salt) =
HookMiner.find(CREATE2_DEPLOYER, flags, type(Counter).creationCode, abi.encode(address(GOERLI_POOLMANAGER)));

// Deploy the hook using CREATE2
Counter counter = new Counter{salt: salt}(IPoolManager(address(GOERLI_POOLMANAGER)));
require(address(counter) == hookAddress, "CounterScript: hook address mismatch");