@xzwiex/perk-swap-core
Version:
This npm package contains core logic of Perk Aggregator build on top of NEAR blockchain
1,651 lines (1,495 loc) • 109 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var tokenList = require('@tonic-foundation/token-list');
var JSBI = require('jsbi');
var BN$1 = require('bn.js');
var nearApiJs = require('near-api-js');
var concentratedLiquidity = require('@perk/concentrated-liquidity');
var Decimal = require('decimal.js');
var jsonRpcProvider = require('near-api-js/lib/providers/json-rpc-provider');
var nearUnits = require('near-units');
var transaction = require('near-api-js/lib/transaction');
var rpc_errors = require('near-api-js/lib/utils/rpc_errors');
var utils = require('near-api-js/lib/utils');
var key_stores = require('near-api-js/lib/key_stores');
var provider = require('near-api-js/lib/providers/provider');
var constants = require('near-api-js/lib/constants');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var JSBI__default = /*#__PURE__*/_interopDefaultLegacy(JSBI);
var BN__default = /*#__PURE__*/_interopDefaultLegacy(BN$1);
var Decimal__default = /*#__PURE__*/_interopDefaultLegacy(Decimal);
const ZERO = /*#__PURE__*/JSBI__default["default"].BigInt(0);
const STORAGE_TO_REGISTER_WITH_MFT = '100000000000000000000000';
const PERK_LIQUIDITY_ID = 'dev-perk-conliq2.near';
const REF_FINANCE_ID = 'v2.ref-finance.near';
const JUMBO_ID = 'v1.jumbo_exchange.near';
const TONIC_ID = 'v1.orderbook.near';
const WRAPPED_NEAR_ID = 'wrap.near';
const MEMO = 'perk';
const REFERRAL_ID = '95c9554eab0003879a571bc4c24f1ab8e30e1f698929c1be61e6a66770358b0f';
const DEFAULT_GAS = '150000000000000';
exports.SwapMode = void 0;
(function (SwapMode) {
SwapMode["ExactIn"] = "ExactIn";
SwapMode["ExactOut"] = "ExactOut";
})(exports.SwapMode || (exports.SwapMode = {}));
const decimalsExponentiateBase = /*#__PURE__*/JSBI__default["default"].BigInt(10);
const addDecimals = (number, decimals) => {
const decimalsJSBI = decimals instanceof JSBI__default["default"] ? decimals : JSBI__default["default"].BigInt(decimals);
const multiplier = JSBI__default["default"].exponentiate(decimalsExponentiateBase, decimalsJSBI);
return JSBI__default["default"].multiply(number, multiplier);
};
const removeDecimals = (number, decimals) => {
const decimalsJSBI = decimals instanceof JSBI__default["default"] ? decimals : JSBI__default["default"].BigInt(decimals);
const multiplier = JSBI__default["default"].exponentiate(decimalsExponentiateBase, decimalsJSBI);
return JSBI__default["default"].divide(number, multiplier);
};
const changeDecimals = (number, fromDecimals, toDecimals) => {
const numbweWithNewDecimals = removeDecimals(addDecimals(number, toDecimals), fromDecimals);
return numbweWithNewDecimals;
};
const filterEmptyPools = pools => pools.filter(pool => pool.shares_total_supply !== '0');
const createCorrelation = (arr1, arr2) => arr1.reduce((acc, curr, index) => acc.set(curr, arr2[index]), new Map());
const createReserves = pool => createCorrelation(pool.token_account_ids, pool.amounts.map(a => JSBI__default["default"].BigInt(a)));
const createCAmountReserves = pool => createCorrelation(pool.token_account_ids, pool.c_amounts.map(a => JSBI__default["default"].BigInt(a)));
const getMostLiquidPools$1 = pools => {
const poolsMap = pools.reduce((acc, pool) => {
const tokenAccountsIds = pool.token_account_ids.sort();
const id = `${tokenAccountsIds[0]}_${tokenAccountsIds[1]}`;
if (acc.has(id)) {
const savedPools = acc.get(id) || [];
acc.set(id, [...savedPools, pool]);
} else {
acc.set(id, [pool]);
}
return acc;
}, new Map());
const uniqPoolsByAmounts = Array.from(poolsMap.values()).flatMap(poolsByTokens => {
return poolsByTokens.sort((poolA, poolB) => {
const [tokenA, tokenB] = poolA.token_account_ids;
return JSBI__default["default"].greaterThan(JSBI__default["default"].BigInt(poolB.reserves.get(tokenA) || ZERO), JSBI__default["default"].BigInt(poolA.reserves.get(tokenA) || ZERO)) && JSBI__default["default"].greaterThan(JSBI__default["default"].BigInt(poolB.reserves.get(tokenB) || ZERO), JSBI__default["default"].BigInt(poolA.reserves.get(tokenB) || ZERO)) ? 1 : -1;
})[0];
}); // console.log('uniqPoolsByAmounts', uniqPoolsByAmounts);
return uniqPoolsByAmounts;
};
const filterMostLiquidUniqPools = pools => {
const notEmptyPools = filterEmptyPools(pools);
const simplePools = notEmptyPools.filter(pool => pool.pool_kind === 'SIMPLE_POOL');
const mostLiquidPools = getMostLiquidPools$1(simplePools);
return mostLiquidPools;
};
const getNumberOfPools = async ({
provider,
ammId
}) => {
const poolsNumber = await provider.query({
request_type: 'call_function',
account_id: ammId,
method_name: 'get_number_of_pools',
args_base64: Buffer.from(JSON.stringify({})).toString('base64'),
finality: 'optimistic'
}).then(res => JSON.parse(Buffer.from(res.result).toString()));
return poolsNumber;
};
const parseSimplePool = (pool, id) => {
return { ...pool,
id,
reserves: createReserves(pool)
};
};
const loadSimplePool = async ({
provider,
ammId,
poolId
}) => {
const pool = await provider.query({
request_type: 'call_function',
account_id: ammId,
method_name: 'get_pool',
args_base64: Buffer.from(JSON.stringify({
pool_id: poolId
})).toString('base64'),
finality: 'optimistic'
}).then(res => JSON.parse(Buffer.from(res.result).toString()));
const parsedPool = parseSimplePool(pool, poolId);
return parsedPool;
};
const loadPool = async ({
provider,
ammId,
poolId,
poolKind
}) => {
if (poolKind) {
switch (poolKind) {
case 'STABLE_SWAP':
return await getStablePool(provider, ammId, poolId);
case 'RATED_SWAP':
return await getRatedPool(provider, ammId, poolId);
default:
// @ts-ignore
return await loadSimplePool({
provider,
ammId,
poolId
});
}
} else {
// @ts-ignore
return await loadSimplePool({
provider,
ammId,
poolId
});
}
};
const loadPools = async ({
provider,
ammId,
index = 0,
limit
}) => {
const pools = await provider.query({
request_type: 'call_function',
account_id: ammId,
method_name: 'get_pools',
args_base64: Buffer.from(JSON.stringify({
from_index: index,
limit
})).toString('base64'),
finality: 'optimistic'
}).then(res => JSON.parse(Buffer.from(res.result).toString()));
return pools;
};
const parseStableSwapPool = (pool, id) => {
const STABLE_SWAP_LP_DECIMALS = 18;
return { ...pool,
id,
reserves: createReserves(pool),
cAmountReserves: createCAmountReserves(pool),
decimals: createCorrelation(pool.token_account_ids, pool.decimals),
// @ts-ignore
rates: createCorrelation(pool.token_account_ids, pool.c_amounts.map(() => addDecimals(JSBI__default["default"].BigInt(1), STABLE_SWAP_LP_DECIMALS))),
pool_kind: 'STABLE_SWAP'
};
};
async function getStablePool(provider, exchange, poolId) {
const pool = await provider.query({
request_type: 'call_function',
account_id: exchange,
method_name: 'get_stable_pool',
args_base64: Buffer.from(JSON.stringify({
pool_id: poolId
})).toString('base64'),
finality: 'optimistic'
}).then(res => JSON.parse(Buffer.from(res.result).toString()));
return parseStableSwapPool(pool, poolId);
}
const parseRatedPool = (pool, poolId) => {
return { ...pool,
id: poolId,
reserves: createReserves(pool),
cAmountReserves: createCAmountReserves(pool),
decimals: createCorrelation(pool.token_account_ids, pool.decimals),
rates: createCorrelation(pool.token_account_ids, pool.rates.map(r => JSBI__default["default"].BigInt(r))),
pool_kind: 'RATED_SWAP'
};
};
/**
* Fetch a rated pool. Rated pools are an improved version of stable pools.
* @param provider
* @param poolId
* @returns
*/
async function getRatedPool(provider, exchange, poolId) {
const pool = await provider.query({
request_type: 'call_function',
account_id: exchange,
method_name: 'get_rated_pool',
args_base64: Buffer.from(JSON.stringify({
pool_id: poolId
})).toString('base64'),
finality: 'optimistic'
}).then(res => JSON.parse(Buffer.from(res.result).toString()));
return parseRatedPool(pool, poolId);
}
const loadAllPools = async ({
provider,
ammId
}) => {
const numberOfPools = await getNumberOfPools({
provider,
ammId
});
let poolsCounter = 0;
let poolsPromises = [];
const poolsBatchToLoad = 500;
while (numberOfPools > poolsCounter) {
const poolsBatchPromise = loadPools({
provider,
ammId,
index: poolsCounter,
limit: poolsBatchToLoad
});
poolsPromises.push(poolsBatchPromise);
poolsCounter += poolsBatchToLoad;
}
const pools = (await Promise.all(poolsPromises)).flat();
const poolsWithIndexes = pools.map((pool, index) => parseSimplePool(pool, index)); // @ts-ignore
const simplePools = poolsWithIndexes.filter(pool => pool.pool_kind === 'SIMPLE_POOL');
const rawStablePools = poolsWithIndexes.filter(pool => pool.pool_kind !== 'SIMPLE_POOL');
const stablePools = await Promise.all(rawStablePools.map(pool => {
if (pool.pool_kind === 'STABLE_SWAP') {
return getStablePool(provider, ammId, pool.id);
} else {
return getRatedPool(provider, ammId, pool.id);
}
}));
return {
simplePools,
stablePools
};
}; // will test later
async function createRefTransactions({
user,
swapSteps
}) {
const transactions = []; // works only for 1 and 2 steps route, if there will be 3 and more steps
// in future - we'll need to update it
const actionsList = swapSteps.map((swapStep, index, arr) => {
const {
amm,
inputMint,
outputMint,
amountIn,
minAmountOut
} = swapStep;
const commonActionInfo = {
pool_id: amm.instanceId,
token_in: inputMint,
token_out: outputMint
};
const isFirstSwapStep = index === 0;
const isMultiHop = arr.length > 1; // for direct route specify both amount in & out
if (!isMultiHop) {
return { ...commonActionInfo,
amount_in: amountIn.toString(),
min_amount_out: minAmountOut.toString()
};
} // for first step in multi hop we skip min amount out and specify iy only
// on last step of swap route
if (isFirstSwapStep) {
return { ...commonActionInfo,
amount_in: amountIn.toString(),
min_amount_out: '0'
};
} // for last step we specify only min amount out - so we take all amount out from
// prev step as amount in, and fail automatically if it's less then needed for min_amount_out
return {
pool_id: amm.instanceId,
token_in: inputMint,
token_out: outputMint,
min_amount_out: minAmountOut.toString()
};
});
const {
amm: commonAMM,
inputMint,
amountIn
} = swapSteps[0];
transactions.push({
receiverId: inputMint,
signerId: user,
actions: [{
type: 'FunctionCall',
params: {
methodName: 'ft_transfer_call',
args: {
receiver_id: commonAMM.contractId,
amount: amountIn.toString(),
msg: JSON.stringify({
force: 0,
actions: actionsList,
referral_id: REFERRAL_ID
}),
memo: MEMO
},
gas: DEFAULT_GAS,
deposit: '1'
}
}]
});
return transactions;
}
const FEE_DIVISOR$2 = /*#__PURE__*/JSBI__default["default"].BigInt(10000); // From ref.finance/jumbo specs
function getOutputAmount$1({
reserves,
poolFee = 0,
slippage,
inputMint,
outputMint,
inputAmount
}) {
const inputPoolBalance = reserves.get(inputMint) || ZERO;
const outputPoolBalance = reserves.get(outputMint) || ZERO;
if (JSBI__default["default"].equal(inputPoolBalance, ZERO) || JSBI__default["default"].equal(outputPoolBalance, ZERO)) {
return {
amountIn: inputAmount,
amountOut: ZERO,
minAmountOut: ZERO,
feeAmount: ZERO,
notEnoughLiquidity: true,
priceImpact: 0
};
}
const feeAmount = JSBI__default["default"].divide(JSBI__default["default"].multiply(JSBI__default["default"].BigInt(poolFee), inputAmount), FEE_DIVISOR$2);
const inputAmountLessFees = JSBI__default["default"].subtract(inputAmount, feeAmount);
const numerator = JSBI__default["default"].multiply(inputAmountLessFees, outputPoolBalance);
const denominator = JSBI__default["default"].add(inputPoolBalance, inputAmountLessFees);
const amountOut = JSBI__default["default"].divide(numerator, denominator);
const amountOutLessSlippage = slippage.getFor(amountOut);
const finalPrice = +inputPoolBalance.toString() / +outputPoolBalance.toString();
const newPrice = +inputAmount.toString() / +amountOut.toString();
const priceImpact = (newPrice - finalPrice) / newPrice;
return {
notEnoughLiquidity: false,
amountIn: inputAmount,
amountOut,
minAmountOut: amountOutLessSlippage,
feeAmount,
priceImpact
};
}
function getInputAmount$1({
reserves,
poolFee = 0,
slippage,
inputMint,
outputMint,
outputAmount
}) {
const inputPoolBalance = reserves.get(inputMint) || ZERO;
const outputPoolBalance = reserves.get(outputMint) || ZERO;
if (JSBI__default["default"].equal(inputPoolBalance, ZERO) || JSBI__default["default"].equal(outputPoolBalance, ZERO)) {
return {
amountIn: ZERO,
amountOut: outputAmount,
minAmountOut: outputAmount,
feeAmount: ZERO,
notEnoughLiquidity: true,
priceImpact: 0
};
}
const numerator = JSBI__default["default"].multiply(outputAmount, inputPoolBalance);
const denominator = JSBI__default["default"].subtract(outputPoolBalance, outputAmount);
const amountIn = JSBI__default["default"].divide(numerator, denominator);
const feeAmount = JSBI__default["default"].divide(JSBI__default["default"].multiply(JSBI__default["default"].BigInt(poolFee), amountIn), FEE_DIVISOR$2);
const amountInWithFees = JSBI__default["default"].add(amountIn, feeAmount);
const slippageAmount = JSBI__default["default"].divide(JSBI__default["default"].multiply(outputAmount, slippage.numerator), slippage.denominator);
const minAmountOut = JSBI__default["default"].subtract(outputAmount, slippageAmount);
const finalPrice = +inputPoolBalance.toString() / +outputPoolBalance.toString();
const newPrice = +amountInWithFees.toString() / +outputAmount.toString();
const priceImpact = (newPrice - finalPrice) / newPrice;
return {
notEnoughLiquidity: false,
amountIn: amountInWithFees,
amountOut: outputAmount,
minAmountOut,
feeAmount,
priceImpact
};
}
const calc_d = ({
pool
}) => {
const {
amp,
cAmountReserves
} = pool;
const token_num = cAmountReserves.size;
const numReserves = Array.from(cAmountReserves.values()).map(v => +v.toString());
const sum_amounts = numReserves.reduce((acc, amount) => acc + amount, 0);
let d_prev = 0;
let d = sum_amounts;
for (let i = 0; i < 256; i++) {
let d_prod = d;
for (const c_amount of numReserves) {
d_prod = d_prod * d / (c_amount * token_num);
}
d_prev = d;
const ann = amp * token_num ** token_num;
const numerator = d_prev * (d_prod * token_num + ann * sum_amounts);
const denominator = d_prev * (ann - 1) + d_prod * (token_num + 1);
d = numerator / denominator;
if (Math.abs(d - d_prev) <= 1) break;
}
return d;
};
const calc_y = ({
pool,
x_c_amount,
tokenInId,
tokenOutId
}) => {
const token_num = pool.cAmountReserves.size;
const ann = pool.amp * token_num ** token_num;
const d = calc_d({
pool
});
let s = x_c_amount;
let c = d * d / x_c_amount; // need for 3-token pools
for (const [token, amount] of pool.cAmountReserves) {
if (token !== tokenInId && token !== tokenOutId) {
const numAmount = +amount.toString();
s += numAmount;
c = c * d / numAmount;
}
}
c = c * d / (ann * token_num ** token_num);
const b = d / ann + s;
let y_prev = 0;
let y = d;
for (let i = 0; i < 256; i++) {
y_prev = y;
const y_numerator = y ** 2 + c;
const y_denominator = 2 * y + b - d;
y = y_numerator / y_denominator;
if (Math.abs(y - y_prev) <= 1) break;
}
return y;
};
const FEE_DIVISOR$1 = 10000;
const tradeFee = (amount, trade_fee) => {
return JSBI__default["default"].divide(JSBI__default["default"].multiply(amount, trade_fee), JSBI__default["default"].BigInt(FEE_DIVISOR$1));
};
function calc_swap({
pool,
tokenInId,
tokenOutId,
amountIn
}) {
const inputTokenBalance = pool.cAmountReserves.get(tokenInId) || '0';
const y = calc_y({
pool,
x_c_amount: +JSBI__default["default"].add(amountIn, JSBI__default["default"].BigInt(inputTokenBalance)).toString(),
tokenInId,
tokenOutId
});
const outputTokenBalance = pool.cAmountReserves.get(tokenOutId) || '0';
const amountOut = JSBI__default["default"].BigInt(+outputTokenBalance.toString() - y);
const fee = tradeFee(amountOut, JSBI__default["default"].BigInt(pool.total_fee));
const amountOutLessFees = JSBI__default["default"].subtract(amountOut, fee);
return [amountOutLessFees, fee];
} // TODO: add output types
function getStableOutputAmount({
tokenInId,
tokenOutId,
slippage,
amountIn,
stablePool
}) {
// hardcoded decimals for tokens in pool
const STABLE_LP_TOKEN_DECIMALS = stablePool.pool_kind === 'STABLE_SWAP' ? 18 : 24;
const inputTokenDecimals = stablePool.decimals.get(tokenInId) || null;
const outputTokenDecimals = stablePool.decimals.get(tokenOutId) || null;
if (inputTokenDecimals === null || outputTokenDecimals === null) {
console.error({
inputTokenDecimals,
outputTokenDecimals,
stablePool,
tokenInId,
tokenOutId
});
throw new Error('No info about decimals of tokens');
} // amount * rate / stable lp
const updatedCReservesNum = Array.from(stablePool.cAmountReserves.entries()).map(([tokenId, amount]) => {
const rate = stablePool.rates.get(tokenId) || null;
if (!rate) {
throw new Error('No rate for token');
}
return [tokenId, removeDecimals(JSBI__default["default"].multiply(amount, rate), STABLE_LP_TOKEN_DECIMALS)];
});
const updatedCReserves = updatedCReservesNum.reduce((acc, [tokenId, amount]) => acc.set(tokenId, amount), new Map());
const updatedStablePool = { ...stablePool,
cAmountReserves: updatedCReserves
}; // change decimals from one to another
const amountInWithHardcodedDecimals = changeDecimals(amountIn, inputTokenDecimals, STABLE_LP_TOKEN_DECIMALS);
const inputPoolBalance = updatedCReserves.get(tokenInId) || ZERO;
const outputPoolBalance = updatedCReserves.get(tokenOutId) || ZERO;
if (JSBI__default["default"].equal(inputPoolBalance, ZERO) || JSBI__default["default"].equal(outputPoolBalance, ZERO)) {
return {
amountIn,
amountOut: ZERO,
minAmountOut: ZERO,
feeAmount: ZERO,
notEnoughLiquidity: true,
priceImpact: 0
};
}
const [amount_swapped, fee] = calc_swap({
tokenInId,
amountIn: amountInWithHardcodedDecimals,
tokenOutId,
pool: updatedStablePool
});
const rateIn = stablePool.rates.get(tokenInId) || null;
const rateOut = stablePool.rates.get(tokenOutId) || null;
if (!rateIn || !rateOut) {
throw new Error('No rate for in or out token');
}
const amountOutWithRate = JSBI__default["default"].divide(addDecimals(amount_swapped, STABLE_LP_TOKEN_DECIMALS), rateOut);
const feeWithRate = JSBI__default["default"].divide(addDecimals(fee, STABLE_LP_TOKEN_DECIMALS), rateOut);
const amountOutWithTokenDecimals = changeDecimals(amountOutWithRate, STABLE_LP_TOKEN_DECIMALS, outputTokenDecimals);
const feeAmountWithTokenDecimals = changeDecimals(feeWithRate, STABLE_LP_TOKEN_DECIMALS, outputTokenDecimals);
const minAmountOut = slippage.getFor(amountOutWithTokenDecimals);
const marketPrice = +rateOut.toString() / +rateIn.toString();
const newMarketPrice = +amountInWithHardcodedDecimals.toString() / +amount_swapped.toString();
const priceImpact = (newMarketPrice - marketPrice) / newMarketPrice;
return {
notEnoughLiquidity: false,
amountIn,
amountOut: amountOutWithTokenDecimals,
minAmountOut,
feeAmount: feeAmountWithTokenDecimals,
priceImpact
};
} // TODO: separate & pass swap func as param
const createSwapOptions = params => {
const {
stablePool,
tokenInId,
tokenOutId,
slippage,
minAmount,
maxAmount,
numberOfSteps = 200
} = params;
const stepSize = JSBI__default["default"].divide(JSBI__default["default"].subtract(maxAmount, minAmount), JSBI__default["default"].BigInt(numberOfSteps));
const swapOptions = Array.from(Array(numberOfSteps).keys()).map(v => v + 1).map(iteration => {
const amountIn = JSBI__default["default"].add(minAmount, JSBI__default["default"].multiply(stepSize, JSBI__default["default"].BigInt(iteration)));
const {
notEnoughLiquidity,
amountOut,
minAmountOut,
feeAmount,
priceImpact
} = getStableOutputAmount({
amountIn,
tokenOutId,
tokenInId,
slippage,
stablePool
});
return {
priceImpact,
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feeAmount
};
});
return swapOptions;
};
const findBestOption = params => {
const {
stablePool,
tokenInId,
tokenOutId,
outputAmount,
slippage,
appproxInputAmount,
minAmountMultiplier = 0,
maxAmountMultiplier = 2,
numberOfSteps = 200
} = params;
const minAmount = removeDecimals(JSBI__default["default"].multiply(appproxInputAmount, JSBI__default["default"].BigInt(minAmountMultiplier * 10 ** 3)), 3);
const maxAmount = removeDecimals(JSBI__default["default"].multiply(appproxInputAmount, JSBI__default["default"].BigInt(maxAmountMultiplier * 10 ** 3)), 3); // create array of results swapping tokenA-tokenB and tokenB-tokenA
const swapResults = createSwapOptions({
stablePool,
tokenInId,
tokenOutId,
slippage,
minAmount,
maxAmount,
numberOfSteps
}); // take the closes one to pool amount ratio - user amount ratio
const swapAmounts = swapResults.map(ratios => {
const {
amountOut
} = ratios;
const swapAmountOutDiff = JSBI__default["default"].subtract(amountOut, outputAmount);
const absDiff = JSBI__default["default"].lessThan(swapAmountOutDiff, ZERO) ? JSBI__default["default"].unaryMinus(swapAmountOutDiff) : swapAmountOutDiff;
return { ...ratios,
swapAmountOutDiff: absDiff
};
}).sort((ratioA, ratioB) => JSBI__default["default"].equal(ratioA.swapAmountOutDiff, ratioB.swapAmountOutDiff) ? 0 : JSBI__default["default"].lessThan(ratioA.swapAmountOutDiff, ratioB.swapAmountOutDiff) ? -1 : 1);
return swapAmounts[0];
}; // TODO: update this method to do it by calculations, not searching the best match
const getStableInputAmount = ({
tokenInId,
tokenOutId,
amountOut,
slippage,
stablePool
}) => {
// we expect that it's almost the same
const appproxInputAmount = amountOut;
let bestOption = null;
const numberOfIterations = 2;
for (let i = 0; i < numberOfIterations; i += 1) {
if (!bestOption) {
bestOption = findBestOption({
stablePool,
tokenInId,
tokenOutId,
slippage,
outputAmount: amountOut,
appproxInputAmount,
numberOfSteps: 200
});
} else {
bestOption = findBestOption({
stablePool,
tokenInId,
tokenOutId,
slippage,
outputAmount: amountOut,
appproxInputAmount: bestOption.amountIn,
minAmountMultiplier: 0.95,
maxAmountMultiplier: 1.05,
numberOfSteps: 200
});
}
}
if (!bestOption) {
throw new Error('No best option for getInputAmount');
}
return bestOption;
};
class Jumbo {
constructor(pool) {
this.id = void 0;
this.contractId = void 0;
this.instanceId = void 0;
this.label = void 0;
this.pool = void 0;
this.isSimplePool = void 0;
this.reserves = void 0;
this.label = 'Jumbo';
this.id = `${pool.id}`;
this.contractId = JUMBO_ID;
this.instanceId = pool.id;
this.pool = pool;
this.isSimplePool = pool.pool_kind === 'SIMPLE_POOL';
this.reserves = pool.reserves;
}
static async loadPools({
provider
}) {
try {
const {
simplePools,
stablePools
} = await loadAllPools({
provider,
ammId: JUMBO_ID
});
const mostLiquidPools = filterMostLiquidUniqPools(simplePools);
return [...mostLiquidPools, ...stablePools].map(pool => new Jumbo(pool));
} catch (e) {
console.log('Error loading pools for Ref Finance', e);
}
return [];
}
getQuote(quoteParams) {
const {
inputMint,
outputMint,
amount,
slippage,
swapMode
} = quoteParams;
const feePct = this.pool.total_fee / 10000;
if (swapMode === exports.SwapMode.ExactIn) {
if (this.isSimplePool) {
const {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feeAmount,
priceImpact
} = getOutputAmount$1({
reserves: this.pool.reserves,
poolFee: this.pool.total_fee,
inputMint,
outputMint,
inputAmount: amount,
slippage
});
return {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feePct,
feeMint: inputMint,
feeAmount,
priceImpact
};
} else {
const {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feeAmount,
priceImpact
} = getStableOutputAmount({
tokenInId: inputMint,
tokenOutId: outputMint,
slippage,
// @ts-ignore
stablePool: this.pool,
amountIn: amount
});
return {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feePct,
feeMint: inputMint,
feeAmount,
priceImpact
};
}
} else if (swapMode === exports.SwapMode.ExactOut) {
if (this.isSimplePool) {
const {
amountIn,
amountOut,
minAmountOut,
feeAmount,
priceImpact,
notEnoughLiquidity
} = getInputAmount$1({
reserves: this.pool.reserves,
poolFee: this.pool.total_fee,
inputMint,
outputMint,
outputAmount: amount,
slippage
});
return {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feePct,
feeMint: inputMint,
feeAmount,
priceImpact
};
} else {
const {
notEnoughLiquidity,
feeAmount,
amountIn,
amountOut,
minAmountOut,
priceImpact
} = getStableInputAmount({
tokenInId: inputMint,
tokenOutId: outputMint,
slippage,
// @ts-ignore
stablePool: this.pool,
amountOut: amount
});
return {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feePct,
feeMint: inputMint,
feeAmount,
priceImpact
};
}
}
throw new Error('Unknown swap mode type');
}
async getPromiseForUpdate({
provider
}) {
return loadPool({
provider,
ammId: this.contractId,
poolId: this.instanceId,
poolKind: this.pool.pool_kind
}).then(pool => {
this.pool = pool;
});
}
async createSwapInstructions(params) {
const {
user,
swapStep
} = params;
const swapTransactions = await createRefTransactions({
user,
swapSteps: [swapStep]
});
console.log('swapTransactions', swapTransactions);
return swapTransactions;
}
async createSwapRouteInstructions(params) {
const {
user,
swapRoute
} = params;
const swapTransactions = await createRefTransactions({
user,
swapSteps: swapRoute.steps
});
console.log('swapTransactions', swapTransactions);
return swapTransactions;
}
get reserveTokenMints() {
return this.pool.token_account_ids;
}
}
const transformPositionsToTicksData = ({
perkLiquidity,
pool,
positions
}) => {
const {
spacing
} = pool;
const ticksMap = positions.reduce((acc, position) => {
const {
lower_index,
upper_index,
total_liquidity
} = position;
const numberOfTicks = Math.abs(upper_index - lower_index) / spacing;
const liquidityForTick = total_liquidity;
for (let i = 0; i <= numberOfTicks; i++) {
const tick = lower_index + spacing * i;
if (acc.has(tick)) {
acc.set(tick, (acc.get(tick) || 0) + liquidityForTick);
} else {
acc.set(tick, liquidityForTick);
}
}
return acc;
}, new Map());
const ticks = Array.from(ticksMap.entries()).map(tickData => {
const actualPrice = perkLiquidity.math.getPriceFromTick({
tick: tickData[0],
spacing
});
const liquidity = Math.floor(tickData[1]);
return {
tick: tickData[0],
actualPrice,
sqrtActualPrice: Math.sqrt(actualPrice),
liquidity
};
}).sort((a, b) => a.tick - b.tick);
return ticks;
};
const FEE_DIVISOR = 1000000;
const FEE_DIVISOR_BI = /*#__PURE__*/JSBI__default["default"].BigInt(FEE_DIVISOR);
function getOutputAmount({
positions,
perkLiquiditySDK,
pool,
slippage,
inputMint,
outputMint,
inputAmount
}) {
const ticks = transformPositionsToTicksData({
pool,
positions,
perkLiquidity: perkLiquiditySDK
});
if (ticks.length === 0) {
return {
amountIn: inputAmount,
amountOut: ZERO,
minAmountOut: ZERO,
feeAmount: ZERO,
notEnoughLiquidity: true,
priceImpact: 0
};
}
const tickDirectionToPass = inputMint === pool.alpha_id ? 'below' : 'above'; // prepare ticks so we can go one by one in any case
const filteredTicks = ticks.filter(tick => {
if (tickDirectionToPass === 'below') {
return tick.actualPrice < pool.current_price;
}
return tick.actualPrice > pool.current_price;
}).sort((a, b) => {
if (tickDirectionToPass === 'below') {
return b.actualPrice - a.actualPrice;
}
return a.actualPrice - b.actualPrice;
});
console.log('filteredTicks', filteredTicks);
const feeAmount = JSBI__default["default"].divide(JSBI__default["default"].multiply(JSBI__default["default"].BigInt(pool.pool_fee_tier * FEE_DIVISOR), inputAmount), FEE_DIVISOR_BI);
const inputAmountLessFees = JSBI__default["default"].subtract(inputAmount, feeAmount);
let remainingInputAmount = inputAmountLessFees;
let outputAmount = ZERO;
let currentPrice = pool.current_price;
for (const tickData of filteredTicks) {
if (JSBI__default["default"].equal(remainingInputAmount, ZERO)) continue;
if (inputMint === pool.alpha_id) {
const betaAmountInTick = perkLiquiditySDK.math.getBetaAmountFromLiquidity({
liquidity: tickData.liquidity,
currentPrice: currentPrice,
lowerPrice: tickData.actualPrice
});
const inputAmountToReduce = JSBI__default["default"].BigInt(new Decimal__default["default"](betaAmountInTick).div(tickData.actualPrice).floor());
console.log({
tickData,
currentPrice,
betaAmountInTick,
remainingInputAmount: remainingInputAmount.toString(),
inputAmountToReduce: inputAmountToReduce.toString()
});
if (JSBI__default["default"].greaterThanOrEqual(inputAmountToReduce, remainingInputAmount)) {
const outputAmountToAdd = JSBI__default["default"].BigInt(new Decimal__default["default"](remainingInputAmount.toString()).mul(tickData.actualPrice).floor());
remainingInputAmount = ZERO;
outputAmount = JSBI__default["default"].add(outputAmount, outputAmountToAdd);
} else {
remainingInputAmount = JSBI__default["default"].subtract(remainingInputAmount, JSBI__default["default"].BigInt(inputAmountToReduce));
outputAmount = JSBI__default["default"].add(outputAmount, JSBI__default["default"].BigInt(new Decimal__default["default"](betaAmountInTick).floor()));
}
} else {
const alphaAmountInTick = perkLiquiditySDK.math.getAlphaAmountFromLiquidity({
liquidity: tickData.liquidity,
currentPrice: currentPrice,
upperPrice: tickData.actualPrice
});
const inputAmountToReduce = JSBI__default["default"].BigInt(new Decimal__default["default"](alphaAmountInTick).mul(tickData.actualPrice).floor());
if (JSBI__default["default"].greaterThanOrEqual(inputAmountToReduce, remainingInputAmount)) {
const outputAmountToAdd = JSBI__default["default"].BigInt(new Decimal__default["default"](remainingInputAmount.toString()).div(tickData.actualPrice).floor());
remainingInputAmount = ZERO;
outputAmount = JSBI__default["default"].add(outputAmount, outputAmountToAdd);
} else {
remainingInputAmount = JSBI__default["default"].subtract(remainingInputAmount, JSBI__default["default"].BigInt(inputAmountToReduce));
outputAmount = JSBI__default["default"].add(outputAmount, JSBI__default["default"].BigInt(new Decimal__default["default"](alphaAmountInTick).floor()));
}
}
currentPrice = tickData.actualPrice;
}
const outputAmountLessSlippage = slippage.getFor(outputAmount);
const newPrice = inputMint === pool.alpha_id ? +outputAmount.toString() / +inputAmount.toString() : +inputAmount.toString() / +outputAmount.toString(); // check
const priceImpact = Math.abs(newPrice - pool.current_price) / newPrice;
console.log({
remainingInputAmount: remainingInputAmount.toString(),
inputAmount: inputAmount.toString(),
outputAmount: outputAmount.toString(),
newPrice,
currentPrice,
pool: pool.current_price,
priceImpact
});
return {
notEnoughLiquidity: JSBI__default["default"].greaterThan(remainingInputAmount, ZERO),
amountIn: inputAmount,
amountOut: outputAmount,
minAmountOut: outputAmountLessSlippage,
feeAmount,
priceImpact
};
}
function getInputAmount({
positions,
perkLiquiditySDK,
pool,
slippage,
inputMint,
outputMint,
outputAmount
}) {
const ticks = transformPositionsToTicksData({
pool,
positions,
perkLiquidity: perkLiquiditySDK
});
if (ticks.length === 0) {
return {
amountIn: ZERO,
amountOut: outputAmount,
minAmountOut: outputAmount,
feeAmount: ZERO,
notEnoughLiquidity: true,
priceImpact: 0
};
}
const tickDirectionToPass = inputMint === pool.alpha_id ? 'below' : 'above'; // prepare ticks so we can go one by one in any case
const filteredTicks = ticks.filter(tick => {
if (tickDirectionToPass === 'below') {
return tick.actualPrice < pool.current_price;
}
return tick.actualPrice > pool.current_price;
}).sort((a, b) => {
if (tickDirectionToPass === 'below') {
return b.actualPrice - a.actualPrice;
}
return a.actualPrice - b.actualPrice;
});
let inputAmount = ZERO;
let remainingOutputAmount = outputAmount;
let currentPrice = pool.current_price;
for (const tickData of filteredTicks) {
if (JSBI__default["default"].equal(remainingOutputAmount, ZERO)) continue;
if (inputMint === pool.alpha_id) {
const betaAmountInTick = perkLiquiditySDK.math.getBetaAmountFromLiquidity({
liquidity: tickData.liquidity,
currentPrice: currentPrice,
lowerPrice: tickData.actualPrice
});
const betaAmountInTickJSBI = JSBI__default["default"].BigInt(new Decimal__default["default"](betaAmountInTick).floor());
const alphaAmountFromTick = JSBI__default["default"].BigInt(new Decimal__default["default"](betaAmountInTick).div(tickData.actualPrice).ceil());
if (JSBI__default["default"].greaterThanOrEqual(betaAmountInTickJSBI, remainingOutputAmount)) {
const leftInputAmountToAdd = JSBI__default["default"].BigInt(new Decimal__default["default"](remainingOutputAmount.toString()).div(tickData.actualPrice).ceil());
remainingOutputAmount = ZERO;
inputAmount = JSBI__default["default"].add(inputAmount, leftInputAmountToAdd);
} else {
remainingOutputAmount = JSBI__default["default"].subtract(remainingOutputAmount, betaAmountInTickJSBI);
inputAmount = JSBI__default["default"].add(inputAmount, alphaAmountFromTick);
}
} else {
const alphaAmountInTick = perkLiquiditySDK.math.getAlphaAmountFromLiquidity({
liquidity: tickData.liquidity,
currentPrice: currentPrice,
upperPrice: tickData.actualPrice
});
const alphaAmountInTickJSBI = JSBI__default["default"].BigInt(new Decimal__default["default"](alphaAmountInTick).floor());
const betaAmountInTickJSBI = JSBI__default["default"].BigInt(new Decimal__default["default"](alphaAmountInTick).mul(tickData.actualPrice).ceil());
if (JSBI__default["default"].greaterThanOrEqual(alphaAmountInTickJSBI, remainingOutputAmount)) {
const inputAmountToAdd = JSBI__default["default"].BigInt(new Decimal__default["default"](remainingOutputAmount.toString()).mul(tickData.actualPrice).ceil());
remainingOutputAmount = ZERO;
inputAmount = JSBI__default["default"].add(inputAmount, inputAmountToAdd);
} else {
remainingOutputAmount = JSBI__default["default"].subtract(remainingOutputAmount, alphaAmountInTickJSBI);
inputAmount = JSBI__default["default"].add(inputAmount, betaAmountInTickJSBI);
}
}
currentPrice = tickData.actualPrice;
}
const feeAmount = JSBI__default["default"].divide(JSBI__default["default"].multiply(JSBI__default["default"].BigInt(pool.pool_fee_tier * FEE_DIVISOR), inputAmount), FEE_DIVISOR_BI);
const inputAmountwithFees = JSBI__default["default"].add(inputAmount, feeAmount);
const outputAmountLessSlippage = slippage.getFor(outputAmount);
const newPrice = inputMint === pool.alpha_id ? +outputAmount.toString() / +inputAmount.toString() : +inputAmount.toString() / +outputAmount.toString(); // check
const priceImpact = Math.abs(newPrice - pool.current_price) / newPrice;
return {
notEnoughLiquidity: JSBI__default["default"].greaterThan(remainingOutputAmount, ZERO),
amountIn: inputAmountwithFees,
amountOut: outputAmount,
minAmountOut: outputAmountLessSlippage,
feeAmount,
priceImpact
};
}
const getMostLiquidPools = pools => {
const poolsMap = pools.reduce((acc, pool) => {
const tokenAccountsIds = [pool.alpha_id, pool.beta_id].sort();
const id = `${tokenAccountsIds[0]}_${tokenAccountsIds[1]}-${pool.pool_fee_tier}`;
if (acc.has(id)) {
const savedPools = acc.get(id) || [];
acc.set(id, [...savedPools, pool]);
} else {
acc.set(id, [pool]);
}
return acc;
}, new Map());
const uniqPoolsByAmounts = Array.from(poolsMap.values()).flatMap(poolsByTokens => {
return poolsByTokens.sort((poolA, poolB) => {
return JSBI__default["default"].greaterThan(JSBI__default["default"].BigInt(poolB.liquidity || ZERO), JSBI__default["default"].BigInt(poolA.liquidity || ZERO)) ? 1 : -1;
})[0];
});
return uniqPoolsByAmounts;
};
class PerkLiquidity {
constructor(pool, perkLiquiditySDK) {
this.id = void 0;
this.contractId = void 0;
this.instanceId = void 0;
this.label = void 0;
this.pool = void 0;
this.positions = void 0;
this.perkLiquiditySDK = void 0;
this.label = 'Perk';
this.id = `${pool.pool_id}`;
this.contractId = PERK_LIQUIDITY_ID;
this.instanceId = pool.pool_id;
this.pool = pool;
this.positions = [];
this.perkLiquiditySDK = perkLiquiditySDK;
}
static async loadPools({
provider
}) {
try {
const perkLiquiditySDK = await concentratedLiquidity.PerkLiquidity.load({
provider
});
const pools = await perkLiquiditySDK.fetchers.loadPools(); // removing duplicates here
const mostLiquidPools = getMostLiquidPools(pools);
return mostLiquidPools.map(pool => new PerkLiquidity(pool, perkLiquiditySDK));
} catch (e) {
console.log('Error loading pools for Ref Finance', e);
}
return [];
}
getQuote(quoteParams) {
const {
inputMint,
outputMint,
amount,
slippage,
swapMode
} = quoteParams;
const feePct = this.pool.pool_fee_tier;
if (swapMode === exports.SwapMode.ExactIn) {
const {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feeAmount,
priceImpact
} = getOutputAmount({
positions: this.positions,
pool: this.pool,
perkLiquiditySDK: this.perkLiquiditySDK,
inputMint,
outputMint,
inputAmount: amount,
slippage
});
return {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feePct,
feeMint: inputMint,
feeAmount,
priceImpact
};
} else if (swapMode === exports.SwapMode.ExactOut) {
const {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feeAmount,
priceImpact
} = getInputAmount({
positions: this.positions,
pool: this.pool,
perkLiquiditySDK: this.perkLiquiditySDK,
inputMint,
outputMint,
outputAmount: amount,
slippage
});
return {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feePct,
feeMint: inputMint,
feeAmount,
priceImpact
};
}
throw new Error('Unknown swap mode type');
}
async getPromiseForUpdate() {
return Promise.all([this.perkLiquiditySDK.fetchers.loadPool(this.pool.pool_id).then(pool => {
this.pool = pool;
}), this.perkLiquiditySDK.fetchers.getPoolsPositions([this.pool.pool_id]).then(positions => {
this.positions = Array.from(positions.values()).flat();
})]).then(() => {});
}
async createSwapInstructions(params) {
const {
swapStep,
user
} = params;
const swapTransactions = await this.perkLiquiditySDK.instructions.getSwapTransactions({
user,
pool_id: swapStep.amm.instanceId,
input_token: swapStep.inputMint,
output_token: swapStep.outputMint,
input_amount: swapStep.amountIn.toString(),
output_amount: swapStep.amountOut.toString()
});
console.log('swapTransactions', swapTransactions);
return swapTransactions;
}
async createSwapRouteInstructions(params) {
const {
swapRoute,
user
} = params;
const formattedSteps = swapRoute.steps.map(swapStep => ({
pool_id: swapStep.amm.instanceId,
input_token: swapStep.inputMint,
output_token: swapStep.outputMint,
input_amount: swapStep.amountIn.toString(),
output_amount: swapStep.amountOut.toString()
}));
const swapTransactions = await this.perkLiquiditySDK.instructions.getMultiSwapTransactions({
user,
steps: formattedSteps
});
console.log('swapTransactions', swapTransactions);
return swapTransactions;
}
get reserveTokenMints() {
return [this.pool.alpha_id, this.pool.beta_id];
}
}
class RefFinance {
constructor(pool) {
this.id = void 0;
this.contractId = void 0;
this.instanceId = void 0;
this.pool = void 0;
this.isSimplePool = void 0;
this.label = void 0;
this.label = 'Ref.Finance';
this.id = `${pool.id}`;
this.contractId = REF_FINANCE_ID;
this.instanceId = pool.id;
this.pool = pool;
this.isSimplePool = pool.pool_kind === 'SIMPLE_POOL';
}
static async loadPools({
provider
}) {
try {
const {
simplePools,
stablePools
} = await loadAllPools({
provider,
ammId: REF_FINANCE_ID
});
const mostLiquidSimplePools = filterMostLiquidUniqPools(simplePools);
return [...mostLiquidSimplePools, ...stablePools].map(pool => new RefFinance(pool));
} catch (e) {
console.log('Error loading pools for Ref Finance', e);
}
return [];
}
getQuote(quoteParams) {
const {
inputMint,
outputMint,
amount,
slippage,
swapMode
} = quoteParams;
const feePct = this.pool.total_fee / 10000;
if (swapMode === exports.SwapMode.ExactIn) {
if (this.isSimplePool) {
const {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feeAmount,
priceImpact
} = getOutputAmount$1({
reserves: this.pool.reserves,
poolFee: this.pool.total_fee,
inputMint,
outputMint,
inputAmount: amount,
slippage
});
return {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feePct,
feeMint: inputMint,
feeAmount,
priceImpact
};
} else {
const {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feeAmount,
priceImpact
} = getStableOutputAmount({
tokenInId: inputMint,
tokenOutId: outputMint,
slippage,
// @ts-ignore
stablePool: this.pool,
amountIn: amount
});
return {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feePct,
feeMint: inputMint,
feeAmount,
priceImpact
};
}
} else if (swapMode === exports.SwapMode.ExactOut) {
if (this.isSimplePool) {
const {
amountIn,
amountOut,
minAmountOut,
feeAmount,
priceImpact,
notEnoughLiquidity
} = getInputAmount$1({
reserves: this.pool.reserves,
poolFee: this.pool.total_fee,
inputMint,
outputMint,
outputAmount: amount,
slippage
});
return {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feePct,
feeMint: inputMint,
feeAmount,
priceImpact
};
} else {
const {
notEnoughLiquidity,
feeAmount,
amountIn,
amountOut,
minAmountOut,
priceImpact
} = getStableInputAmount({
tokenInId: inputMint,
tokenOutId: outputMint,
slippage,
// @ts-ignore
stablePool: this.pool,
amountOut: amount
});
return {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feePct,
feeMint: inputMint,
feeAmount,
priceImpact
};
}
}
throw new Error('Unknown swap mode type');
}
async getPromiseForUpdate({
provider
}) {
return loadPool({
provider,
ammId: this.contractId,
poolId: this.instanceId,
poolKind: this.pool.pool_kind
}).then(pool => {
this.pool = pool;
});
}
async createSwapInstructions(params) {
const {
user,
swapStep
} = params;
const swapTransactions = await createRefTransactions({
user,
swapSteps: [swapStep]
});
console.log('swapTransactions', swapTransactions);
return swapTransactions;
}
async createSwapRouteInstructions(params) {
const {
user,
swapRoute
} = params;
const swapTransactions = await createRefTransactions({
user,
swapSteps: swapRoute.steps
});
console.log('swapTransactions', swapTransactions);
return swapTransactions;
}
get reserveTokenMints() {
return this.pool.token_account_ids;
}
}
const SLIPPAGE_NUMERATOR = 10000;
const SLIPPAGE_DENOMINATOR = 1000000;
const FEE_BPS_DENOMINATOR = 100000;
class Percentage {
constructor(numerator, denominator) {
this.numerator = void 0;
this.denominator = void 0;
this.toString = () => {
return `${this.numerator.toString()}/${this.denominator.toString()}`;
};
this.numerator = numerator;
this.denominator = denominator;
}
static fromSlippageNumber(num) {
return new Percentage(JSBI__default["default"].BigInt(num * SLIPPAGE_NUMERATOR), JSBI__default["default"].BigInt(SLIPPAGE_DENOMINATOR));
}
static fromFeeBpsNumber(num) {
return new Percentage(JSBI__default["default"].BigInt(num), JSBI__default["default"].BigInt(FEE_BPS_DENOMINATOR));
}
getFor(amount) {
const numerator = JSBI__default["default"].BigInt(this.numerator.toString());
const denominator = JSBI__default["default"].BigInt(this.denominator.toString());
return JSBI__default["default"].divide(JSBI__default["default"].multiply(amount, JSBI__default["default"].subtract(denominator, numerator)), denominator);
}
getFrom(amount) {
const numerator = JSBI__default["default"].BigInt(this.numerator.toString());
const denominator = JSBI__default["default"].BigInt(this.denominator.toString());
return JSBI__default["default"].divide(JSBI__default["default"].multiply(amount, numerator), denominator);
}
}
const loadAccountStorageBalance = async ({
provider,
tokenId,
tokenOwnerId
}) => {
if (!tokenId || !tokenOwnerId) {
console.error('No token id or token owner id for loading account storage balance', `to