Skip to main content
Helpful?

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:

  1. Create a pool only
  2. 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_ZERO as currency0

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:

FeeFee ValueTick Spacing
0.01%1001
0.05%50010
0.30%300060
1.00%10_000200

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. 79228162514264337593543950336 is the starting price for a 1:1 pool

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 encode initializePool parameters
  • The second call, params[1], will encode a mint operation for modifyLiquidities

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_ZERO as currency0

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)
    • 79228162514264337593543950336 is 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_POSITION creates a new liquidity position
  • The second command SETTLE_PAIR indicates 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 PoolKey defined 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 LiquidityAmounts for 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 SWEEP in the actions parameter

For a full end-to-end script, developers should see v4-template's scripts

Helpful?