Deployment
Deploying Uniswap V4 Hooks involves several steps:
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.
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 at0x4e59b44847b379578588920cA78FbF26c0B4956C
, is employed for this purpose and is already available in most environments.Deploying Test Tokens: These tokens are essential for creating the pool. They need to be deployed before initializing the pool.
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.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 likePoolModifyPositionTest
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");
console.logBytes32(bytes32(idBytes));
vm.broadcast();
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(
Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG
| Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG
);
// 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
vm.broadcast();
Counter counter = new Counter{salt: salt}(IPoolManager(address(GOERLI_POOLMANAGER)));
require(address(counter) == hookAddress, "CounterScript: hook address mismatch");
}
}