Increasing Liquidity

Adding more liquidity to an existing liquidity position.

See Liquidity position fee for an example of how to obtain the current fee, payable in HBAR, for minting a new liquidity position or adding more liquidity to an existing one.

See New liquidity position for creating a new liquidity position.

When working with HBAR, use the Wrapped HBAR token ID and include the HBAR amount in the setPayableAmount() method for the ContractExecuteTransaction call.


Function name: increaseLiquidity

Recommended gas: 330,000 gwei (~ $0.028 USD)

Struct Parameter Name
Description

uint256 tokenSN

The serial number of the token for which liquidity is being increased

uint256 amount0Desired

The maximum amount for the first token in its smallest unit

uint256 amount1Desired

The maximum amount for the second token in its smallest unit

uint256 amount0Min

The minimum amount for the first token in its smallest unit

uint256 amount1Min

The minimum amount for the second token in its smallest unit

uint deadline

Deadline in Unix seconds

Solidity Interface & Function Body
//INonfungiblePositionManager.sol

struct IncreaseLiquidityParams {
  uint256 tokenSN;
  uint256 amount0Desired;
  uint256 amount1Desired;
  uint256 amount0Min;
  uint256 amount1Min;
  uint256 deadline;
}

/// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender`
/// @param params tokenSN The serial number of the token for which liquidity is being increased,
/// amount0Desired The desired amount of token0 to be spent,
/// amount1Desired The desired amount of token1 to be spent,
/// amount0Min The minimum amount of token0 to spend, which serves as a slippage check,
/// amount1Min The minimum amount of token1 to spend, which serves as a slippage check,
/// deadline The time by which the transaction must be included to effect the change
/// @return liquidity The new liquidity amount as a result of the increase
/// @return amount0 The amount of token0 to acheive resulting liquidity
/// @return amount1 The amount of token1 to acheive resulting liquidity
function increaseLiquidity(IncreaseLiquidityParams calldata params)
  external
  payable
  returns (
      uint128 liquidity,
      uint256 amount0,
      uint256 amount1
);
//NonfungiblePositionManager.sol

/// @inheritdoc INonfungiblePositionManager
function increaseLiquidity(IncreaseLiquidityParams calldata params)
  external
  payable
  override
  checkDeadline(params.deadline)
  returns (
    uint128 liquidity,
    uint256 amount0,
    uint256 amount1
  )
{

  Position storage position = _positions[params.tokenSN];

  PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId];

  IUniswapV3Pool pool;
  (liquidity, amount0, amount1, pool) = addLiquidity(
    AddLiquidityParams({
      token0: poolKey.token0,
      token1: poolKey.token1,
      fee: poolKey.fee,
      tickLower: position.tickLower,
      tickUpper: position.tickUpper,
      amount0Desired: params.amount0Desired,
      amount1Desired: params.amount1Desired,
      amount0Min: params.amount0Min,
      amount1Min: params.amount1Min,
      recipient: address(this)
    })
  );

  bytes32 positionKey = PositionKey.compute(address(this), position.tickLower, position.tickUpper);

  // this is now updated to the current transaction
  (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);

  position.tokensOwed0 += uint128(
    FullMath.mulDiv(
      feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
      position.liquidity,
      FixedPoint128.Q128
    )
  );
  position.tokensOwed1 += uint128(
    FullMath.mulDiv(
      feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
      position.liquidity,
      FixedPoint128.Q128
    )
  );

  position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
  position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
  position.liquidity += liquidity;

  emit IncreaseLiquidity(params.tokenSN, liquidity, amount0, amount1);
}

Code Overview

The following code demonstrates how to add more liquidity to an existing position.

See Fetch pool token ratio for an example how to retrieve the latest data construct the Pool object using the Uniswap SDK library.

See Fetch all pools to retrieve the pool address, token pairs, fee tier, token IDs and decimal places for the target pool of interest.

The refundETH function uses HBAR, but its name is derived from Uniswap on Ethereum. The name was retained to simplify integration for developers familiar with Uniswap tools. It is used to refund any excess HBAR when setting up a new liquidity position.

Hedera's EVM chain ID can be retrieved from https://chainlist.org.

The following code is intended for guidance purposes and does not include checks and safeguards.

Resources:

import * as ethers from 'ethers'; //V6
import { Pool, Position, nearestUsableTick, priceToClosestTick } from '@uniswap/v3-sdk';
import { Fraction, Percent, Token, Price } from '@uniswap/sdk-core';
import { ContractExecuteTransaction, .. } from '@hashgraph/sdk';

//Client pre-checks:
// - Router contract has spender allowance for the input HTS tokens

//Set one of Hedera's JSON RPC Relay as the provider
const provider = new ethers.JsonRpcProvider(hederaJsonRelayUrl, '', {
  batchMaxCount: 1, //workaround for V6
});

//Load the ABI data for UniswapV3Pool
const poolInterfaces = new ethers.Interface(poolAbi);

//Load the ABI data for NonfungiblePositionManager
const nftManagerInterfaces = new ethers.Interface(nftManagerAbi);

//Construct the pool contract
const poolContract = new ethers.Contract(poolEvmAddress, 
  poolInterfaces.fragments, provider);

//Construct the NFT Manager contract
const nftManagerContract = new ethers.Contract(nftManagerEvmAddress, 
  nftManagerInterfaces.fragments, provider);

//Get current slot0 and liquidity data from the pool
const [slot0, poolLiquidity] = await Promise.all([
  poolContract.slot0(),
  poolContract.liquidity()
]);

//Construct the pool using the latest data
const pool = new Pool(
  token0, token1, 
  feeTier, slot0.sqrtPriceX96.toString(),
  poolLiquidity.toString(), Number(slot0.tick)
);

//Get current position data for the given NFT token serial number
const lp = await nftManagerContract.positions(tokenSN);
const feeTier = Number(lp.fee);
const tickLower = Number(lp.tickLower);
const tickUpper = Number(lp.tickUpper);

//Construct the tokens
//For Hedera chain id, see https://chainlist.org/?testnets=true&search=Hedera
const token0 = new Token(hederaChainId, token0Address, token0Decimals);
const token1 = new Token(hederaChainId, token1Address, token1Decimals);

//Get amount0 in token's smallest unit from user's input (input0)
const amount0 = BigNumber(input0).times(Math.pow(10, token0Decimals)).toFixed(0);

//Construct a position using the SDK
// - use fromAmount0() if amount0 needs to be exact
// - use fromAmount1() if amount1 needs to be exact
// - use fromAmounts() if amount0 and amount1 do not need to be exact
const position = Position.fromAmount0({
  pool: pool,
  tickUpper: tickUpper,
  tickLower: tickLower,
  amount0: amount0,
  useFullPrecision: true
});

//Get the mint amounts based on what the router will give us
const amount0Mint = position.mintAmounts.amount0.toString();
const amount1Mint = position.mintAmounts.amount1.toString();

//Calculate the minimum amounts factoring in the price slippage % and range
const priceSlippagePercent = new Percent(1, 100); //1% price slippage
const minAmounts = position.mintAmountsWithSlippage(priceSlippagePercent);
const amount0Min = minAmounts.amount0.toString();
const amount1Min = minAmounts.amount1.toString();

//IncreaseLiquidityParams struct
const params = {
  tokenSN: tokenSN,
  fee: feeTier, //500, 1500, 3000 or 10000
  tickLower: tickLower, //lower tick of the range
  tickUpper: tickUpper, //upper tick of the range
  amount0Desired: amount0Mint, //in smallest unit
  amount1Desired: amount1Mint, //in smallest unit
  amount0Min: amount0Min, //in smallest unit
  amount1Min: amount1Min, //in smallest unit
  deadline: deadline, //Unix seconds
};

//Construct encoded data for each function
const increaseLiquidityEncoded = nftManagerInterfaces.encodeFunctionData('increaseLiquidity', [params]);  
const refundEthEncoded = nftManagerInterfaces.encodeFunctionData('refundETH');

//Build encoded data for multicall
const encodedData = abiInterfaces.encodeFunctionData('multicall', 
  [[mintEncoded, refundEthEncoded]]);  
const encodedDataAsUint8Array = hexToUint8Array(encodedData.substring(2));

//Give spender allowance for both tokens to the NFT Manager contract if needed.
//To avoid having to ask for allowance each time, request max allowance.
//If the token is HBAR, no spender allowance is required.
//Use Hedera's REST API to get current allowances for an account.
await yourGrantSpenderAllowanceFunc(..);

//Execute the contract call
const response = await new ContractExecuteTransaction()
  .setPayableAmount(inputHbar) //mint fee + HBAR token amount if used
  .setContractId(nftManagerContractId)
  .setGas(gasGwei)
  .setFunctionParameters(encodedDataAsUint8Array)
  .execute(client);

//Fetch the result
const record = await response.getRecord(client);    
const result = record.contractFunctionResult!;
const results = abiInterfaces.decodeFunctionResult('multicall', result.bytes)[0];
const mintResult = abiInterfaces.decodeFunctionResult('increaseLiquidity', results[0]);

//Retrieve the newly minted liquidity and amounts for informative purposes
const liquidity = BigNumber(mintResult.liquidity);
const amount0 = BigNumber(mintResult.amount0);
const amount1 = BigNumber(mintResult.amount1);

Last updated