Skip to Content
Advanced Mini CoursesUniswap IntroductionAdding Liquidity to a Uniswap v3 Pool

Adding Liquidity to a Uniswap v3 Pool

Once a pool is initialized, the next step is to add liquidity. In Uniswap v3, this process is more complex than in v2 because you need to specify a price range.

Understanding Liquidity Provision in v3

In Uniswap v3, when you provide liquidity:

  1. You specify a price range (lower and upper ticks)
  2. You provide both tokens in the correct ratio for the current price
  3. Your liquidity is only active when the price is within your specified range
  4. You receive an NFT representing your position

The NonfungiblePositionManager

Uniswap v3 uses a contract called NonfungiblePositionManager to handle liquidity positions. This contract:

  1. Creates and manages liquidity positions as NFTs
  2. Handles minting, collecting fees, and burning positions
  3. Simplifies the complex interactions with the core pool contracts

Code Example: Adding Liquidity

Here’s how to add liquidity to a Uniswap v3 pool:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; contract LiquidityProvider { INonfungiblePositionManager public positionManager; constructor(address _positionManager) { positionManager = INonfungiblePositionManager(_positionManager); } function addLiquidity( address tokenA, address tokenB, uint24 fee, int24 tickLower, int24 tickUpper, uint256 amountA, uint256 amountB, uint256 amountAMin, uint256 amountBMin ) external returns (uint256 tokenId) { // Approve tokens IERC20(tokenA).approve(address(positionManager), amountA); IERC20(tokenB).approve(address(positionManager), amountB); // Prepare parameters INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({ token0: tokenA < tokenB ? tokenA : tokenB, token1: tokenA < tokenB ? tokenB : tokenA, fee: fee, tickLower: tickLower, tickUpper: tickUpper, amount0Desired: tokenA < tokenB ? amountA : amountB, amount1Desired: tokenA < tokenB ? amountB : amountA, amount0Min: tokenA < tokenB ? amountAMin : amountBMin, amount1Min: tokenA < tokenB ? amountBMin : amountAMin, recipient: address(this), deadline: block.timestamp + 15 minutes }); // Mint position (tokenId, , , ) = positionManager.mint(params); return tokenId; } }

Choosing Tick Ranges

Selecting the right tick range is crucial. Here are some strategies:

1. Narrow Range (Active Liquidity Management)

Provide liquidity in a narrow range around the current price for maximum capital efficiency, but monitor closely to adjust as price moves.

2. Wide Range (Passive Liquidity)

Provide liquidity across a wide price range for less active management, but with lower capital efficiency.

3. One-Sided Liquidity

Provide liquidity only above or below the current price if you have a directional view on the market.

Calculating Ticks from Prices

To convert a price to a tick index:

function getPriceFromTick(int24 tick) public pure returns (uint256) { return uint256(1.0001 ** uint256(tick)); } function getTickFromPrice(uint256 price) public pure returns (int24) { return int24(log(price) / log(1.0001)); }

However, in practice, you’ll want to use Uniswap’s libraries for accurate calculations:

import "@uniswap/v3-core/contracts/libraries/TickMath.sol"; // Get the tick for a specific price int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96); // Get the price (as sqrtPriceX96) for a specific tick uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(tick);

Example: Adding Liquidity to ETH/USDC Pool

Let’s say we want to add liquidity to an ETH/USDC pool with a price range of $1,800 to $2,200:

// Addresses for Ethereum mainnet address WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; address USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; uint24 fee = 3000; // 0.3% // Calculate ticks for $1,800 and $2,200 // Note: These are approximate, you should use TickMath for exact values int24 tickLower = -74959; // Approximately $1,800 int24 tickUpper = -73791; // Approximately $2,200 // Amount of tokens to provide uint256 ethAmount = 1 ether; uint256 usdcAmount = 2000 * 10**6; // 2000 USDC // Minimum amounts (accounting for slippage) uint256 ethMinAmount = ethAmount * 99 / 100; // 1% slippage uint256 usdcMinAmount = usdcAmount * 99 / 100; // 1% slippage // Add liquidity uint256 tokenId = addLiquidity( WETH, USDC, fee, tickLower, tickUpper, ethAmount, usdcAmount, ethMinAmount, usdcMinAmount );
Tick Spacing

Different fee tiers have different tick spacing:

  • 0.05% fee: 10 tick spacing
  • 0.30% fee: 60 tick spacing
  • 1.00% fee: 200 tick spacing

This means you can only use ticks that are multiples of the spacing.

What Happens After Adding Liquidity?

After adding liquidity:

  1. You receive an NFT representing your position
  2. Your liquidity is active if the current price is within your range
  3. You start earning fees when trades occur through your liquidity
  4. You can collect these fees or adjust your position later

In the next section, we’ll learn how to perform swaps in Uniswap v3.

Last updated on