Minting a Position

This section explains how to create (or mint) a liquidity position on the Storyhunt V3 protocol.

Introduction

This section explains how to create (or mint) a liquidity position on the Storyhunt V3 protocol. In Storyhunt V3, liquidity positions are represented using non-fungible tokens. This guide demonstrates minting a liquidity position for the USDC - DAI pair, including transferring token approvals, creating a pool instance, calculating the position, and executing the minting transaction.

Giving Approval to Transfer Tokens

The first step is to approve the protocol’s NonfungiblePositionManager contract to transfer your tokens. Since both USDC and DAI are ERC20 tokens, their contracts need approval to transfer funds on your behalf:

import { ethers, BigNumber } from 'ethers';

async function getTokenTransferApproval(address: string, amount: BigNumber) {
    const provider = new ethers.providers.JsonRpcProvider(rpcUrl);

    const tokenContract = new ethers.Contract(
        address,
        ERC20_ABI,
        provider
    );

    return tokenContract.approve(
        NONFUNGIBLE_POSITION_MANAGER_CONTRACT_ADDRESS,
        amount
    );
}

const token0Approval = await getTokenTransferApproval(token0Address, amount0);
const token1Approval = await getTokenTransferApproval(token1Address, amount1);

Creating a Pool Instance

After approving token transfers, gather the required pool data to instantiate the Pool class. Compute the pool address using the unique identifiers: the two tokens and the pool fee.

import { computePoolAddress, FeeAmount } from '@storyhunt/v3-sdk';
import { Token } from '@storyhunt/sdk-core';

const currentPoolAddress = computePoolAddress({
  factoryAddress: POOL_FACTORY_CONTRACT_ADDRESS,
  tokenA: token0,
  tokenB: token1,
  fee: poolFee,
});

Retrieve pool data by creating a reference to the pool’s smart contract:

import IPoolABI from '@storyhunt/v3-core/artifacts/contracts/interfaces/IPool.sol/IPool.json';

const poolContract = new ethers.Contract(
  currentPoolAddress,
  IPoolABI.abi,
  provider
);

const [liquidity, slot0] = await Promise.all([
  poolContract.liquidity(),
  poolContract.slot0(),
]);

Create the pool instance:

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

const configuredPool = new Pool(
  token0,
  token1,
  poolFee,
  slot0.sqrtPriceX96.toString(),
  liquidity.toString(),
  slot0.tick
);

Calculating a Position from Input Tokens

Using the pool instance, create a Position class to define the price range for providing liquidity:

import { Position } from '@storyhunt/v3-sdk';
import { BigIntish } from '@storyhunt/sdk-core';

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

Configuring and Executing the Minting Transaction

Pass the Position instance to the NonfungiblePositionManager class to generate the calldata for minting a new position. Use the MintOptions object to configure transaction parameters:

import { MintOptions, NonfungiblePositionManager } from '@storyhunt/v3-sdk';
import { Percent } from '@storyhunt/sdk-core';

const mintOptions: MintOptions = {
  recipient: address,
  deadline: Math.floor(Date.now() / 1000) + 60 * 20,
  slippageTolerance: new Percent(50, 10_000),
};

const { calldata, value } = NonfungiblePositionManager.addCallParameters(
  position,
  mintOptions
);

Create and send 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 wallet = new ethers.Wallet(privateKey, provider);
const txRes = await wallet.sendTransaction(transaction);

The transaction mints a new position NFT, representing your liquidity in the specified pool.

Next Steps

After minting a position, you can manage it by adding or removing liquidity as needed. Explore advanced features to optimize your liquidity management in the Storyhunt V3 protocol.