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:
// - 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 provider
const provider = new ethers.JsonRpcProvider(hederaJsonRelayUrl, '', {
batchMaxCount: 1, //workaround for V6
});
//load the ABI data containing liquidity() and slot0()
const abiInterfaces = new ethers.Interface(abi);
//construct the pool contract
const poolContract = new ethers.Contract(poolEvmAddress,
abiInterfaces.fragments, provider);
//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 current slot0 and liquidity data from JSON-RPC Relay
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 token0 price in terms of token1
const currentPrice = pool.token0Price;
//Get amount0 in token's smallest unit
const amount0 = BigNumber(input0).times(Math.pow(10, token0Decimals)).toFixed(0);
//get the upper price (+5% from current in this example)
const multiplier = new Fraction(105, 100); //1.05 (105%)
const priceFraction = currentPrice.asFraction.multiply(multiplier);
const upperPrice = new Price(
currentPrice.baseCurrency,
currentPrice.quoteCurrency,
priceFraction.denominator,
priceFraction.numerator
);
//get the upper tick based on the target upper price
const tickUpperApprox = priceToClosestTick(upperPrice);
//calculate the delta between the current tick and the upper
const tickDelta = tickUpperApprox - pool.tickCurrent;
//get the lower tick based on the delta from current tick
const tickLowerApprox = pool.tickCurrent - tickDelta;
//get the nearest valid tick values
const tickUpper = nearestUsableTick(tickUpperApprox, pool.tickSpacing);
const tickLower = 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 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();
//MintParams struct
const params = {
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 function
const mintEncoded = abiInterfaces.encodeFunctionData('mint', [params]);
const refundEthEncoded = abiInterfaces.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 paid 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('mint', results[0]);;
//Retrieve the NFT token SN, liquidity and amounts for informative purposes
const tokenSN = Number(mintResult.tokenSN);
const liquidity = BigNumber(mintResult.liquidity);
const amount0 = BigNumber(mintResult.amount0);
const amount1 = BigNumber(mintResult.amount1);