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.