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.
Ensure the recipient has associated the SaucerSwapV2 LP NFT ID representing the liquidity positions before minting a new 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: mint
⛽ Recommended gas: 900,000 gwei (~ $0.077 USD)
Struct Parameter Name
Description
address token0
EVM address of the first token
address token1
EVM address of the second token
int24 tickLower
The lower end of the tick range for the position
int24 tickUpper
The upper end of the tick range for the position
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
address recipient
EVM address to receive the new liquidity position.
uint deadline
Deadline in Unix seconds
Solidity Interface & Function Body
//INonfungiblePositionManager.solstructMintParams {address token0;address token1;uint24 fee;int24 tickLower;int24 tickUpper;uint256 amount0Desired;uint256 amount1Desired;uint256 amount0Min;uint256 amount1Min;address recipient;uint256 deadline;}/// @notice Creates a new position wrapped in a NFT/// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized/// a method does not exist, i.e. the pool is assumed to be initialized./// @param params The params necessary to mint a position, encoded as `MintParams` in calldata/// @return tokenSN The token serial number of the new position/// @return liquidity The amount of liquidity for this position/// @return amount0 The amount of token0/// @return amount1 The amount of token1functionmint(MintParamscalldata params)externalpayablereturns (uint256 tokenSN,uint128 liquidity,uint256 amount0,uint256 amount1);
The following code demonstrates how to create a new liquidity position with a price range being +/- 5% from the current token price, and receive a NFT representing the 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 operates in 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.
import*as ethers from'ethers'; //V6import { 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:// - NFT token id is associated// - Router contract has spender allowance for the input HTS tokens//Set one of Hedera's JSON RPC Relay as the providerconstprovider=newethers.JsonRpcProvider(hederaJsonRelayUrl,'', { batchMaxCount:1,//workaround for V6});//load the ABI data containing liquidity() and slot0()constabiInterfaces=newethers.Interface(abi);//construct the pool contractconstpoolContract=newethers.Contract(poolEvmAddress,abiInterfaces.fragments, provider);//construct the tokens//For Hedera chain id, see https://chainlist.org/?testnets=true&search=Hederaconsttoken0=newToken(hederaChainId, token0Address, token0Decimals);consttoken1=newToken(hederaChainId, token1Address, token1Decimals);//get current slot0 and liquidity data from JSON-RPC Relayconst [slot0,poolLiquidity] =awaitPromise.all([poolContract.slot0(),poolContract.liquidity()]);//construct the pool using the latest dataconstpool=newPool( token0, token1, feeTier,slot0.sqrtPriceX96.toString(),poolLiquidity.toString(),Number(slot0.tick));//Get current token0 price in terms of token1constcurrentPrice=pool.token0Price;//Get amount0 in token's smallest unitconstamount0=BigNumber(input0).times(Math.pow(10, token0Decimals)).toFixed(0);//get the upper price (+5% from current in this example)constmultiplier=newFraction(105,100); //1.05 (105%)constpriceFraction=currentPrice.asFraction.multiply(multiplier);constupperPrice=newPrice(currentPrice.baseCurrency,currentPrice.quoteCurrency,priceFraction.denominator,priceFraction.numerator);//get the upper tick based on the target upper priceconsttickUpperApprox=priceToClosestTick(upperPrice);//calculate the delta between the current tick and the upperconsttickDelta= tickUpperApprox -pool.tickCurrent;//get the lower tick based on the delta from current tickconsttickLowerApprox=pool.tickCurrent - tickDelta;//get the nearest valid tick valuesconsttickUpper=nearestUsableTick(tickUpperApprox,pool.tickSpacing);consttickLower=nearestUsableTick(tickLowerApprox,pool.tickSpacing);//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 exactconstposition=Position.fromAmount0({ pool: pool, tickUpper: tickUpper, tickLower: tickLower, amount0: amount0, useFullPrecision:true});//get the mint amounts based on what the router will give usconstamount0Mint=position.mintAmounts.amount0.toString();constamount1Mint=position.mintAmounts.amount1.toString();//calculate the minimum amounts factoring in the price slippage % and rangeconstpriceSlippagePercent=newPercent(1,100); //1% price slippageconstminAmounts=position.mintAmountsWithSlippage(priceSlippagePercent);constamount0Min=minAmounts.amount0.toString();constamount1Min=minAmounts.amount1.toString();//MintParams structconstparams= { token0: token0Address,//0x.. token1: token1Address,//0x.. 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 recipient: recipientAddress,//0x.. deadline: deadline,//Unix seconds};//construct encoded data for each functionconstmintEncoded=abiInterfaces.encodeFunctionData('mint', [params]); constrefundEthEncoded=abiInterfaces.encodeFunctionData('refundETH');//build encoded data for multicallconstencodedData=abiInterfaces.encodeFunctionData('multicall', [[mintEncoded, refundEthEncoded]]); constencodedDataAsUint8Array=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.awaityourGrantSpenderAllowanceFunc(..);//Execute the paid contract callconstresponse=awaitnewContractExecuteTransaction().setPayableAmount(inputHbar) //mint fee + HBAR token amount if used.setContractId(nftManagerContractId).setGas(gasGwei).setFunctionParameters(encodedDataAsUint8Array).execute(client);//Fetch the resultconstrecord=awaitresponse.getRecord(client); constresult=record.contractFunctionResult!;constresults=abiInterfaces.decodeFunctionResult('multicall',result.bytes)[0];constmintResult=abiInterfaces.decodeFunctionResult('mint', results[0]);;//Retrieve the NFT token SN, liquidity and amounts for informative purposesconsttokenSN=Number(mintResult.tokenSN);constliquidity=BigNumber(mintResult.liquidity);constamount0=BigNumber(mintResult.amount0);constamount1=BigNumber(mintResult.amount1);