Skip to main content

Provide Liquidity (V3)

V3 allows LPs to concentrate liquidity within a specific price range for higher capital efficiency. Each position is an NFT with unique parameters.

Create a Position

1. Choose Pool Parameters

  • Token pair: The two tokens
  • Fee tier: 0.01%, 0.05%, 0.3%, or 1% (see Fee Tiers)
  • Price range: [tickLower, tickUpper] (see Ticks and Ranges)

2. Get Current Pool State

const V3_FACTORY = '0x0dfb1Bb755d872EA1fa4d95E4ad0c2E6317Ce9B9'; // Mainnet
const V3_POSITION_MANAGER = '0x343b244bEDF133D57C61b241557bF29AA32ea4F9';

const FACTORY_ABI = ['function getPool(address,address,uint24) view returns (address)'];
const POOL_ABI = [
  'function slot0() view returns (uint160, int24, uint16, uint16, uint16, uint8, bool)',
  'function tickSpacing() view returns (int24)'
];

const factory = new ethers.Contract(V3_FACTORY, FACTORY_ABI, provider);
const poolAddress = await factory.getPool(token0, token1, 3000);
const pool = new ethers.Contract(poolAddress, POOL_ABI, provider);

const [sqrtPriceX96, currentTick] = await pool.slot0();
const tickSpacing = await pool.tickSpacing();

3. Calculate Tick Range

Align your desired price range to the pool’s tick spacing:
function nearestUsableTick(tick, tickSpacing) {
  return Math.round(tick / tickSpacing) * tickSpacing;
}

// Example: ±10% around current price
const tickLower = nearestUsableTick(currentTick - 2000, tickSpacing);
const tickUpper = nearestUsableTick(currentTick + 2000, tickSpacing);

4. Approve Tokens

await token0Contract.approve(V3_POSITION_MANAGER, amount0Desired);
await token1Contract.approve(V3_POSITION_MANAGER, amount1Desired);

5. Mint Position

const PM_ABI = [
  'function mint((address,address,uint24,int24,int24,uint256,uint256,uint256,uint256,address,uint256)) payable returns (uint256,uint128,uint256,uint256)'
];

const pm = new ethers.Contract(V3_POSITION_MANAGER, PM_ABI, signer);
const deadline = Math.floor(Date.now() / 1000) + 1200;

const tx = await pm.mint({
  token0,
  token1,
  fee: 3000,
  tickLower,
  tickUpper,
  amount0Desired,
  amount1Desired,
  amount0Min: amount0Desired * 99n / 100n,
  amount1Min: amount1Desired * 99n / 100n,
  recipient: userAddress,
  deadline
});

const receipt = await tx.wait();
// Parse the tokenId from the receipt events
token0 must have a lower address than token1. Sort them before calling.

With Native KAS

If one token is KAS, send it as value instead of approving WKAS:
const tx = await pm.mint(params, { value: kasAmount });

Increase Liquidity

Add more tokens to an existing position (same tick range):
const tx = await pm.increaseLiquidity({
  tokenId,
  amount0Desired,
  amount1Desired,
  amount0Min: 0,
  amount1Min: 0,
  deadline
});

Collect Fees

Claim accumulated trading fees:
const tx = await pm.collect({
  tokenId,
  recipient: userAddress,
  amount0Max: ethers.MaxUint128,
  amount1Max: ethers.MaxUint128
});

Remove Liquidity

1. Decrease Liquidity

const tx = await pm.decreaseLiquidity({
  tokenId,
  liquidity: liquidityToRemove,  // Use full liquidity for complete removal
  amount0Min: 0,
  amount1Min: 0,
  deadline
});

2. Collect the Tokens

After decreasing, tokens are held by the Position Manager. Call collect() to withdraw:
const tx = await pm.collect({
  tokenId,
  recipient: userAddress,
  amount0Max: ethers.MaxUint128,
  amount1Max: ethers.MaxUint128
});

3. Burn the NFT (Optional)

After removing all liquidity and collecting all tokens/fees:
const tx = await pm.burn(tokenId);

Single-Sided Positions

You can create positions entirely above or below the current price:
RangeDepositBehavior
Above current price (tickLower > currentTick)Token0 onlyActs as a limit sell order
Below current price (tickUpper < currentTick)Token1 onlyActs as a limit buy order
// Limit sell: provide only token0, range above current price
const tx = await pm.mint({
  token0,
  token1,
  fee: 3000,
  tickLower: currentTick + 60,    // Above current
  tickUpper: currentTick + 600,
  amount0Desired: sellAmount,
  amount1Desired: 0,              // No token1 needed
  amount0Min: 0,
  amount1Min: 0,
  recipient: userAddress,
  deadline
});

Key Points

  • Each position is an NFT — track tokenId for future operations
  • Fees must be explicitly collected (they don’t auto-compound)
  • Out-of-range positions earn no fees until price re-enters the range
  • Narrower ranges = higher capital efficiency but more active management needed
  • Use multicall() on the Position Manager to batch decrease + collect in one transaction