Adding & Removing Liquidity

This section explains how to modify a liquidity position by adding or removing liquidity on the Storyhunt V3 protocol.

Introduction

This section explains how to modify a liquidity position by adding or removing liquidity on the Storyhunt V3 protocol. Liquidity positions are represented as non-fungible tokens. This guide demonstrates adding and removing liquidity for the USDC - DAI pair using the NonfungiblePositionManager class. The inputs include the tokens, pool fee, and fractions to adjust the position's liquidity.

Adding Liquidity to a Position

To add liquidity, construct a modified position by calculating the amount to increase based on the current position:

const fractionToAdd: number = ...;

const amount0Increased: JSBI = fromReadableAmount(
    readableAmount0 * fractionToAdd,
    token0.decimals
);
const amount1Increased: JSBI = fromReadableAmount(
    readableAmount1 * fractionToAdd,
    token1.decimals
);

const positionToIncreaseBy = constructPosition(
    amount0Increased,
    amount1Increased
);

Here, fromReadableAmount() converts the token amount to its smallest unit (e.g., 1 WIP = 1e18 Wei).

Create the constructPosition function to build a Position object:

import { Pool, Position } from '@storyhunt/v3-sdk';
import JSBI from 'jsbi';

function constructPosition(
    amount0: JSBI,
    amount1: JSBI
): Position {
    const pool = new Pool(...);

    return Position.fromAmounts({
        pool,
        tickLower:
            nearestUsableTick(pool.tickCurrent, pool.tickSpacing) -
            pool.tickSpacing * 2,
        tickUpper:
            nearestUsableTick(pool.tickCurrent, pool.tickSpacing) +
            pool.tickSpacing * 2,
        amount0,
        amount1,
        useFullPrecision: true,
    });
}

Next, create an options object of type AddLiquidityOptions:

import { AddLiquidityOptions } from '@storyhunt/v3-sdk';

const addLiquidityOptions: AddLiquidityOptions = {
  deadline: Math.floor(Date.now() / 1000) + 60 * 20,
  slippageTolerance: new Percent(50, 10_000),
  tokenId,
};

Use the NonfungiblePositionManager to get the calldata and value for the transaction:

import { NonfungiblePositionManager } from '@storyhunt/v3-sdk';

const { calldata, value } = NonfungiblePositionManager.addCallParameters(
  positionToIncreaseBy,
  addLiquidityOptions
);

Construct and execute the transaction:

import { ethers } from 'ethers';

const transaction = {
  data: calldata,
  to: NONFUNGIBLE_POSITION_MANAGER_CONTRACT_ADDRESS,
  value: value,
  from: address,
  maxFeePerGas: MAX_FEE_PER_GAS,
  maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS,
};

const wallet = new ethers.Wallet(privateKey, provider);
const txRes = await wallet.sendTransaction(transaction);

Removing Liquidity from a Position

To remove liquidity, start by constructing a position identical to the minted one:

const amount0: JSBI = fromReadableAmount(
    readableAmount0 * fractionToRemove,
    token0.decimals
);
const amount1: JSBI = fromReadableAmount(
    readableAmount1 * fractionToRemove,
    token1.decimals
);

const currentPosition = constructPosition(
  amount0,
  amount1
);

Create a RemoveLiquidityOptions object:

import { RemoveLiquidityOptions } from '@storyhunt/v3-sdk';
import { Percent } from '@storyhunt/sdk-core';

const removeLiquidityOptions: RemoveLiquidityOptions = {
  deadline: Math.floor(Date.now() / 1000) + 60 * 20,
  slippageTolerance: new Percent(50, 10_000),
  tokenId: positionId,
  liquidityPercentage: new Percent(0.5),
  collectOptions,
};

The collectOptions parameter allows optional fee collection:

import { CurrencyAmount } from '@storyhunt/sdk-core';
import { CollectOptions } from '@storyhunt/v3-sdk';

const collectOptions: Omit<CollectOptions, 'tokenId'> = {
  expectedCurrencyOwed0: CurrencyAmount.fromRawAmount(
    token0,
    0
  ),
  expectedCurrencyOwed1: CurrencyAmount.fromRawAmount(
    token1,
    0
  ),
  recipient: address,
};

Use removeCallParameters to get the calldata and value for the transaction:

const { calldata, value } = NonfungiblePositionManager.removeCallParameters(
  currentPosition,
  removeLiquidityOptions
);

Construct and execute the transaction:

const transaction = {
  data: calldata,
  to: NONFUNGIBLE_POSITION_MANAGER_CONTRACT_ADDRESS,
  value: value,
  from: address,
  maxFeePerGas: MAX_FEE_PER_GAS,
  maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS,
};

const txRes = await wallet.sendTransaction(transaction);

Next Steps

Now that you can mint and modify a position, check out how to collect fees from the position!