Unichain Docs

Create a Pool

Learn how to create a trading pool on Unichain

Summary

Create a pool and add initial liquidity to enable trading of the token

The guide prioritizes Uniswap v4, however developers can elect to create pools on Uniswap v2 or Uniswap v3

Requirements

ETH on Unichain is required, see Funding a Wallet

  1. The guide uses foundry

    for deployments. Install it by running:

    curl -L https://foundry.paradigm.xyz | bash
  2. Add Unichain to your foundry.toml

    [rpc_endpoints]
    unichain = "https://sepolia.unichain.org"
  3. 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:

  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

    • 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#script

Create a Pool Only

To initialize a Uniswap v4 Pool without initial liquidity, developers should call PoolManager.initialize()

  1. 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

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

Uniswap v3

Developers can opt for the battle-tested Uniswap v3 and create a pool with the UniswapV3Factory contract.

  1. Install Uniswap v3 dependencies
forge install uniswap/v3-core
forge install uniswap/v3-periphery
  1. Create a pool with UniswapV3Factory
address pair = IUniswapV2Factory(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
  • Starting price is determined by the initial liquidity added
  • The pair address return-value corresponds to a UniswapV3Pool contract

Valid fee values are:

FeeFee 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

  1. Install Uniswap v2 dependencies
forge install uniswap/v2-core
forge install uniswap/v2-periphery
  1. 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
  • Starting price is determined by the initial liquidity added
  • The pair address return-value corresponds to a UniswapV2Pair 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

Last updated on

On this page