Create a Pool
Create a Uniswap v4 pool on Unichain and add initial liquidity for your token pair.
Summary
Create a pool and add initial liquidity to enable trading of the token
This guide prioritizes Uniswap v4, but you can also create pools on Uniswap v2 or Uniswap v3.
Requirements
ETH on Unichain is required. See Get Funds on Unichain.
-
The guide uses foundry for deployments. Install it by running:
curl -L https://foundry.paradigm.xyz |Â bash -
Add Unichain to yourÂ
foundry.toml[rpc_endpoints] unichain =Â "https://sepolia.unichain.org" -
Add Uniswap v4Â dependencies
For a fully setup repository, see v4-template
forge install uniswap/v4-core forge install uniswap/v4-periphery
For Uniswap contract addresses please see Contract Addresses
Uniswap v4
The guide serves as additional material to Uniswap v4Â docs
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:
-
Initialize the parameters provided toÂ
multicall()bytes[] memory params = new bytes[](2);- The first call,
params[0], will encodeinitializePool parameters - The second call,
params[1], will encode a mint operation forÂmodifyLiquidities
- The first call,
-
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_ZEROasÂcurrency0- 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
- Currencies should be sorted,
-
Encode the
initializePool parametersPools are initialized with a starting price
params[0] = abi.encodeWithSelector( IPoolInitializer_v4.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
- the startingPrice is expressed as sqrtPriceX96:
-
Initialize the mint-liquidity parameters
PositionManager's
modifyLiquiditiesuses an encoded command systembytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR));If providing ETH liquidity, a third action is required: sweep - to recover excess eth sent to the position manager
bytes memory actions = abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR), uint8(Actions.SWEEP));- 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 - The third command
SWEEPindicates that unused tokens should be paid back to the wallet address provided.
- The first command
-
Encode the
MINT_POSITIONÂ parametersbytes[] memory mintParams = new bytes[](2); // use new bytes[](3) for ETH liquidity positions 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
- pool the same
-
Encode the
SETTLE_PAIRÂ parametersCreating a position on a pool requires the caller to transfer
currency0andcurrency1 tokensmintParams[1] = abi.encode(pool.currency0, pool.currency1); -
Encode the
SWEEPparameters (For ETH liquidity positions) The SWEEP action requiresrecipientaddress to send excess tokens andcurrencytoken to sweep (most commonly native Ether:ÂCurrencyLibrary.ADDRESS_ZERO)mintParams[2] = abi.encode(currency, recipient); -
Encode the
modifyLiquidities calluint256 deadline = block.timestamp + 60; params[1] = abi.encodeWithSelector( posm.modifyLiquidities.selector, abi.encode(actions, mintParams), deadline ); -
Approve the tokens
PositionManagerusesPermit2for 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); -
Execute the multicall
- The
multicallis used to execute multiple calls in a single transaction
PositionManager(posm).multicall(params);For pools paired with native tokens (Ether), provide
valuein the contract callPositionManager(posm).multicall{value: ethToSend}(params);Excess Ether is NOT refunded unless developers encoded
SWEEPin theÂactions parameter - The
For a full end-to-end script, developers should see v4-template#script
Create a pool only
To initialize a Uniswap v4 Pool without initial liquidity, developers should call PoolManager.initialize()
-
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_ZEROasÂcurrency0- 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
- Currencies should be sorted,
-
Call
initializePools are initialized with a starting priceIPoolManager(manager).initialize(pool, startingPrice);- the startingPrice is expressed as sqrtPriceX96:
floor(sqrt(token1 / token0) * 2^96)79228162514264337593543950336is the starting price for a 1:1 pool
- the startingPrice is expressed as sqrtPriceX96:
Uniswap v3
Developers can opt for the battle-tested Uniswap v3 and create a pool with the UniswapV3Factory contract.
- Install Uniswap v3Â dependencies
forge install uniswap/v3-core
forge install uniswap/v3-periphery- Create a pool withÂ
UniswapV3Factory
address pair = IUniswapV3Factory(factory).createPool(tokenA, tokenB, 3000);Additional documentation
- tokenA and tokenB are the ERC20 token addresses
- the ordering does not matter, however the alphabetically-lower address is assigned toÂ
token0
- the ordering does not matter, however the alphabetically-lower address is assigned toÂ
- Starting price is determined by the initial liquidity added
- The
pairaddress return-value corresponds to aUniswapV3Pool contract
Valid fee values are:
| Fee | Fee Value |
|---|---|
| 0.01% | 100 |
| 0.05% | 500 |
| 0.30% | 3000 |
| 1.00% | 10_000 |
To create a pool with initial liquidity, developers should use multicall PoolInitializer and NonfungiblePositionManager.mint
Uniswap v2
Developers can opt for Uniswap v2's simplicity and create a pool with the UniswapV2Factory contract. Note that pools on Uniswap v2 trade with a fixed 0.30%Â fee
- Install Uniswap v2Â dependencies
forge install uniswap/v2-core
forge install uniswap/v2-periphery- Create a pool withÂ
UniswapV2Factory
address pair = IUniswapV2Factory(factory).createPair(tokenA, tokenB);Additional documentation
- tokenA and tokenB are the ERC20 token addresses
- the ordering does not matter, however the alphabetically-lower address is assigned toÂ
token0
- the ordering does not matter, however the alphabetically-lower address is assigned toÂ
- Starting price is determined by the initial liquidity added
- The
pairaddress return-value corresponds to aUniswapV2Pair contract - The pair trades on a fixed 0.30% fee, and is not configurable
To create a pool with initial liquidity, developers should reference addLiquidity. If a pool does not exist, one is automatically created.