Create Pool
Context
Creating a pool on Uniswap v4 is permissionless and enables the trading of an asset. Uniswap v4 is a popular destination for creating markets due to its:
- Proven track record and battle-tested codebase
- Concentrated liquidity, unlocking capital efficiency
- Flexibile pool design through dynamic fees and hooks
- Gas-efficient architecture
- Integrations with alternative trading venues
For more information, developers should see Uniswap v4 Overview
The guide covers two approaches to creating a pool:
- Create a pool only
- Create a pool and add initial liquidity, with one transaction
Setup
Developing with Uniswap v4 requires foundry
Install the dependencies:
forge install uniswap/v4-core
forge install uniswap/v4-periphery
Guide: Create a Pool Only
To initialize a Uniswap v4 Pool without initial liquidity, developers should call PoolManager.initialize()
Creating a pool without liquidity may be useful for "reserving" a pool for future use, when initial liquidity is not available, or when external market makers would provide the starting liquidity
1. Configure the Pool
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
PoolKey memory pool = PoolKey({
    currency0: currency0,
    currency1: currency1,
    fee: lpFee,
    tickSpacing: tickSpacing,
    hooks: hookContract
});
For native token pairs (Ether), use
CurrencyLibrary.ADDRESS_ZEROascurrency0
PoolKey uniquely identifies a pool
- Currencies should be sorted, uint160(currency0) < uint160(currency1)
- lpFee is the fee expressed in pips, i.e. 3000 = 0.30%
- tickSpacing is the granularity of the pool. Lower values are more precise but may be more expensive to trade on
- hookContract is the address of the hook contract
A note on tickSpacing:
Lower tick spacing provides improved price precision; however, smaller tick spaces will cause swaps to cross ticks more often, incurring higher gas costs
As a reference, Uniswap v3 pools are configured with:
| Fee | Fee Value | Tick Spacing | 
|---|---|---|
| 0.01% | 100 | 1 | 
| 0.05% | 500 | 10 | 
| 0.30% | 3000 | 60 | 
| 1.00% | 10_000 | 200 | 
2. Call initialize
Pools are initialized with a starting price
IPoolManager(manager).initialize(pool, startingPrice);
- the startingPrice is expressed as sqrtPriceX96: floor(sqrt(token1 / token0) * 2^96)- i.e. 79228162514264337593543950336is the starting price for a 1:1 pool
 
- i.e. 
Guide: Create a Pool & Add Liquidity
Uniswap v4's PositionManager supports atomic creation of a pool and initial liquidity using multicall. Developers can create a trading pool, with liquidity, in a single transaction:
1. Initialize the parameters provided to multicall()
bytes[] memory params = new bytes[](2);
- The first call, params[0], will encodeinitializePoolparameters
- The second call, params[1], will encode a mint operation formodifyLiquidities
2. Configure the pool
PoolKey memory pool = PoolKey({
    currency0: currency0,
    currency1: currency1,
    fee: lpFee,
    tickSpacing: tickSpacing,
    hooks: hookContract
});
For native token pairs (Ether), use
CurrencyLibrary.ADDRESS_ZEROascurrency0
PoolKey uniquely identifies a pool
- Currencies should be sorted, uint160(currency0) < uint160(currency1)
- lpFee is the fee expressed in pips, i.e. 3000 = 0.30%
- tickSpacing is the granularity of the pool. Lower values are more precise but more expensive to trade
- hookContract is the address of the hook contract
3. Encode the initializePool parameters
Pools are initialized with a starting price
params[0] = abi.encodeWithSelector(
    PositionManager.initializePool.selector,
    pool,
    startingPrice
);
- the startingPrice is expressed as sqrtPriceX96: floor(sqrt(token1 / token0) * 2^96)- 79228162514264337593543950336is the starting price for a 1:1 pool
 
4. Initialize the mint-liquidity parameters
PositionManager's modifyLiquidities uses an encoded command system
bytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR));
- The first command MINT_POSITIONcreates a new liquidity position
- The second command SETTLE_PAIRindicates that tokens are to be paid by the caller, to create the position
5. Encode the MINT_POSITION parameters
bytes[] memory mintParams = new bytes[](2);
mintParams[0] = abi.encode(pool, tickLower, tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData);
- pool the same PoolKeydefined above, in pool-creation
- tickLower and tickUpper are the range of the position, must be a multiple of pool.tickSpacing
- liquidity is the amount of liquidity units to add, see LiquidityAmountsfor converting token amounts to liquidity units
- amount0Max and amount1Max are the maximum amounts of token0 and token1 the caller is willing to transfer
- recipient is the address that will receive the liquidity position (ERC-721)
- hookData is the optional hook data
6. Encode the SETTLE_PAIR parameters
Creating a position on a pool requires the caller to transfer `currency0` and `currency1` tokens
mintParams[1] = abi.encode(pool.currency0, pool.currency1);
7. Encode the modifyLiquidites call
uint256 deadline = block.timestamp + 60;
params[1] = abi.encodeWithSelector(
    posm.modifyLiquidities.selector, abi.encode(actions, mintParams), deadline
);
8. Approve the tokens
PositionManager uses Permit2 for token transfers
- Repeat for both tokens
// approve permit2 as a spender
IERC20(token).approve(address(permit2), type(uint256).max);
// approve `PositionManager` as a spender
IAllowanceTransfer(address(permit2)).approve(token, address(positionManager), type(uint160).max, type(uint48).max);
9. Execute the multicall
The multicall is used to execute multiple calls in a single transaction
PositionManager(posm).multicall(params);
For pools paired with native tokens (Ether), provide value in the contract call
PositionManager(posm).multicall{value: ethToSend}(params);
Excess Ether is NOT refunded unless developers encoded
SWEEPin theactionsparameter
For a full end-to-end script, developers should see v4-template's scripts