@perk.money/perk-swap-core
Version:
This npm package contains core logic of Perk Aggregator build on top of NEAR blockchain
1,712 lines (1,554 loc) • 86.7 kB
JavaScript
import { TokenListProvider } from '@tonic-foundation/token-list';
import JSBI from 'jsbi';
import BN$1, { BN as BN$2 } from 'bn.js';
import { keyStores, KeyPair, connect, Account } from 'near-api-js';
export { Connection, KeyPair } from 'near-api-js';
import Decimal from 'decimal.js';
import { JsonRpcProvider as JsonRpcProvider$1 } from 'near-api-js/lib/providers/json-rpc-provider';
export { JsonRpcProvider as JSONRpc } from 'near-api-js/lib/providers/json-rpc-provider';
import { NEAR } from 'near-units';
import { stringifyJsonOrBytes } from 'near-api-js/lib/transaction';
export { AccessKey, Action, addKey, createAccount, deleteAccount, deleteKey, deployContract, fullAccessKey, functionCall, stake, transfer } from 'near-api-js/lib/transaction';
export { ServerError } from 'near-api-js/lib/utils/rpc_errors';
export { KeyPairEd25519, PublicKey } from 'near-api-js/lib/utils';
export { KeyStore } from 'near-api-js/lib/key_stores';
export * from 'near-api-js/lib/providers/provider';
export { DEFAULT_FUNCTION_CALL_GAS } from 'near-api-js/lib/constants';
const ZERO = /*#__PURE__*/JSBI.BigInt(0);
const STORAGE_TO_REGISTER_WITH_MFT = '100000000000000000000000';
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'; // TODO: change
const DEFAULT_GAS = '150000000000000';
var SwapMode;
(function (SwapMode) {
SwapMode["ExactIn"] = "ExactIn";
SwapMode["ExactOut"] = "ExactOut";
})(SwapMode || (SwapMode = {}));
const decimalsExponentiateBase = /*#__PURE__*/JSBI.BigInt(10);
const addDecimals = (number, decimals) => {
const decimalsJSBI = decimals instanceof JSBI ? decimals : JSBI.BigInt(decimals);
const multiplier = JSBI.exponentiate(decimalsExponentiateBase, decimalsJSBI);
return JSBI.multiply(number, multiplier);
};
const removeDecimals = (number, decimals) => {
const decimalsJSBI = decimals instanceof JSBI ? decimals : JSBI.BigInt(decimals);
const multiplier = JSBI.exponentiate(decimalsExponentiateBase, decimalsJSBI);
return JSBI.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.BigInt(a)));
const createCAmountReserves = pool => createCorrelation(pool.token_account_ids, pool.c_amounts.map(a => JSBI.BigInt(a)));
const getMostLiquidPools = 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.greaterThan(JSBI.BigInt(poolB.reserves.get(tokenA) || ZERO), JSBI.BigInt(poolA.reserves.get(tokenA) || ZERO)) && JSBI.greaterThan(JSBI.BigInt(poolB.reserves.get(tokenB) || ZERO), JSBI.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(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.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.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
}),
memo: MEMO
},
gas: DEFAULT_GAS,
deposit: '1'
}
}]
});
return transactions;
}
const FEE_DIVISOR$1 = /*#__PURE__*/JSBI.BigInt(10000); // From ref.finance/jumbo specs
function getOutputAmount({
reserves,
poolFee = 0,
slippage,
inputMint,
outputMint,
inputAmount
}) {
const inputPoolBalance = reserves.get(inputMint) || ZERO;
const outputPoolBalance = reserves.get(outputMint) || ZERO;
if (JSBI.equal(inputPoolBalance, ZERO) || JSBI.equal(outputPoolBalance, ZERO)) {
return {
amountIn: inputAmount,
amountOut: ZERO,
minAmountOut: ZERO,
feeAmount: ZERO,
notEnoughLiquidity: true,
priceImpact: 0
};
}
const feeAmount = JSBI.divide(JSBI.multiply(JSBI.BigInt(poolFee), inputAmount), FEE_DIVISOR$1);
const inputAmountLessFees = JSBI.subtract(inputAmount, feeAmount);
const numerator = JSBI.multiply(inputAmountLessFees, outputPoolBalance);
const denominator = JSBI.add(inputPoolBalance, inputAmountLessFees);
const amountOut = JSBI.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({
reserves,
poolFee = 0,
slippage,
inputMint,
outputMint,
outputAmount
}) {
const inputPoolBalance = reserves.get(inputMint) || ZERO;
const outputPoolBalance = reserves.get(outputMint) || ZERO;
if (JSBI.equal(inputPoolBalance, ZERO) || JSBI.equal(outputPoolBalance, ZERO)) {
return {
amountIn: ZERO,
amountOut: outputAmount,
minAmountOut: outputAmount,
feeAmount: ZERO,
notEnoughLiquidity: true,
priceImpact: 0
};
}
const numerator = JSBI.multiply(outputAmount, inputPoolBalance);
const denominator = JSBI.subtract(outputPoolBalance, outputAmount);
const amountIn = JSBI.divide(numerator, denominator);
const feeAmount = JSBI.divide(JSBI.multiply(JSBI.BigInt(poolFee), amountIn), FEE_DIVISOR$1);
const amountInWithFees = JSBI.add(amountIn, feeAmount);
const slippageAmount = JSBI.divide(JSBI.multiply(outputAmount, slippage.numerator), slippage.denominator);
const minAmountOut = JSBI.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 = 10000;
const tradeFee = (amount, trade_fee) => {
return JSBI.divide(JSBI.multiply(amount, trade_fee), JSBI.BigInt(FEE_DIVISOR));
};
function calc_swap({
pool,
tokenInId,
tokenOutId,
amountIn
}) {
const inputTokenBalance = pool.cAmountReserves.get(tokenInId) || '0';
const y = calc_y({
pool,
x_c_amount: +JSBI.add(amountIn, JSBI.BigInt(inputTokenBalance)).toString(),
tokenInId,
tokenOutId
});
const outputTokenBalance = pool.cAmountReserves.get(tokenOutId) || '0';
const amountOut = JSBI.BigInt(+outputTokenBalance.toString() - y);
const fee = tradeFee(amountOut, JSBI.BigInt(pool.total_fee));
const amountOutLessFees = JSBI.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.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.equal(inputPoolBalance, ZERO) || JSBI.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.divide(addDecimals(amount_swapped, STABLE_LP_TOKEN_DECIMALS), rateOut);
const feeWithRate = JSBI.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.divide(JSBI.subtract(maxAmount, minAmount), JSBI.BigInt(numberOfSteps));
const swapOptions = Array.from(Array(numberOfSteps).keys()).map(v => v + 1).map(iteration => {
const amountIn = JSBI.add(minAmount, JSBI.multiply(stepSize, JSBI.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.multiply(appproxInputAmount, JSBI.BigInt(minAmountMultiplier * 10 ** 3)), 3);
const maxAmount = removeDecimals(JSBI.multiply(appproxInputAmount, JSBI.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.subtract(amountOut, outputAmount);
const absDiff = JSBI.lessThan(swapAmountOutDiff, ZERO) ? JSBI.unaryMinus(swapAmountOutDiff) : swapAmountOutDiff;
return { ...ratios,
swapAmountOutDiff: absDiff
};
}).sort((ratioA, ratioB) => JSBI.equal(ratioA.swapAmountOutDiff, ratioB.swapAmountOutDiff) ? 0 : JSBI.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 === SwapMode.ExactIn) {
if (this.isSimplePool) {
const {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feeAmount,
priceImpact
} = getOutputAmount({
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 === SwapMode.ExactOut) {
if (this.isSimplePool) {
const {
amountIn,
amountOut,
minAmountOut,
feeAmount,
priceImpact,
notEnoughLiquidity
} = getInputAmount({
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;
}
}
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 === SwapMode.ExactIn) {
if (this.isSimplePool) {
const {
notEnoughLiquidity,
amountIn,
amountOut,
minAmountOut,
feeAmount,
priceImpact
} = getOutputAmount({
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 === SwapMode.ExactOut) {
if (this.isSimplePool) {
const {
amountIn,
amountOut,
minAmountOut,
feeAmount,
priceImpact,
notEnoughLiquidity
} = getInputAmount({
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.BigInt(num * SLIPPAGE_NUMERATOR), JSBI.BigInt(SLIPPAGE_DENOMINATOR));
}
static fromFeeBpsNumber(num) {
return new Percentage(JSBI.BigInt(num), JSBI.BigInt(FEE_BPS_DENOMINATOR));
}
getFor(amount) {
const numerator = JSBI.BigInt(this.numerator.toString());
const denominator = JSBI.BigInt(this.denominator.toString());
return JSBI.divide(JSBI.multiply(amount, JSBI.subtract(denominator, numerator)), denominator);
}
getFrom(amount) {
const numerator = JSBI.BigInt(this.numerator.toString());
const denominator = JSBI.BigInt(this.denominator.toString());
return JSBI.divide(JSBI.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', `tokenId: ${tokenId}`, `tokenOwnerId: ${tokenOwnerId}`);
return {
tokenId,
existing: false,
storageBalance: null
};
}
const res = await provider.query({
request_type: 'call_function',
account_id: tokenId,
method_name: 'storage_balance_of',
args_base64: Buffer.from(JSON.stringify({
account_id: tokenOwnerId
})).toString('base64'),
finality: 'optimistic'
}).then(res => JSON.parse(Buffer.from(res.result).toString()));
return {
tokenId,
existing: res !== null,
storageBalance: res
};
};
async function registerToken(provider, tokenId, user) {
const tokenOutActions = new Array();
const {
storageBalance: tokenRegistered
} = await loadAccountStorageBalance({
provider,
tokenId,
tokenOwnerId: user
});
if (tokenRegistered) {
return undefined;
}
tokenOutActions.push({
type: 'FunctionCall',
params: {
methodName: 'storage_deposit',
args: {
registration_only: true,
account_id: user
},
gas: DEFAULT_GAS,
deposit: STORAGE_TO_REGISTER_WITH_MFT
}
});
return {
receiverId: tokenId,
signerId: user,
actions: tokenOutActions
};
}
const builDepositNearActions = async (account, amount) => {
const actions = new Array();
const registerTokenTx = await registerToken(account.connection.provider, WRAPPED_NEAR_ID, account.accountId);
let deposit = amount;
if (registerTokenTx) {
actions.push(...registerTokenTx.actions);
} else {
const balance = await account.viewFunction(WRAPPED_NEAR_ID, 'ft_balance_of', {
account_id: account.accountId
});
if (JSBI.greaterThanOrEqual(JSBI.BigInt(balance), deposit)) {
return [];
}
console.log('NEAR account found, balance:', balance);
deposit = JSBI.subtract(amount, JSBI.BigInt(balance));
}
actions.push({
type: 'FunctionCall',
params: {
methodName: 'near_deposit',
args: {},
gas: DEFAULT_GAS,
deposit: deposit.toString()
}
});
return actions;
};
const buildDepositNearTransaction = async (account, amount) => {
const transaction = {
receiverId: WRAPPED_NEAR_ID,
signerId: account.accountId,
actions: await builDepositNearActions(account, amount)
};
return transaction;
};
const builWithdrawNearActions = async amount => {
const actions = new Array();
const withdrawAmount = amount.toString();
actions.push({
type: 'FunctionCall',
params: {
methodName: 'near_withdraw',
args: {
amount: withdrawAmount
},
gas: DEFAULT_GAS,
deposit: '1'
}
}); // TBD: close wrapped account/return deposit
return actions;
};
const buildWithdrawNearTransaction = async (signerId, amount) => {
const transaction = {
receiverId: WRAPPED_NEAR_ID,
signerId,
actions: await builWithdrawNearActions(amount)
};
return transaction;
};
const callFunction = async params => {
const {
provider,
accountId,
method,
args = {}
} = params;
const response = await provider.query({
request_type: 'call_function',
account_id: accountId,
method_name: method,
args_base64: Buffer.from(JSON.stringify(args)).toString('base64'),
finality: 'optimistic'
});
const parsedResponse = JSON.parse(Buffer.from(response.result).toString());
return parsedResponse;
};
const SPIN_CONTRACT_ADDRESS = 'spot.spin-fi.near';
const NATIVE_NEAR_ADDRESS = 'near.near';
class SpinFinanceMarket {
constructor(rawMarket) {
this.rawMarket = void 0;
this.label = 'Spin Finance';
this.id = void 0;
this.reserveTokenMints = void 0;
this.contractId = SPIN_CONTRACT_ADDRESS;
this.instanceId = void 0;
this.orderBook = undefined;
this.rawMarket = rawMarket;
this.id = `${rawMarket.id}`;
this.instanceId = rawMarket.id; // Pass wrapped near to the router
this.reserveTokenMints = [rawMarket.base.address, rawMarket.quote.address].map(_ => _ === NATIVE_NEAR_ADDRESS ? WRAPPED_NEAR_ID : _);
}
static async loadUserDeposits({
user,
provider
}) {
const deposits = await callFunction({
provider,
accountId: SPIN_CONTRACT_ADDRESS,
method: 'get_deposits',
args: {
account_id: user
}
});
return deposits;
}
static createWithdrawFromDepositsTransaction({
user,
token,
amount
}) {
const isNearToken = token === NATIVE_NEAR_ADDRESS;
const transaction = {
receiverId: SPIN_CONTRACT_ADDRESS,
signerId: user,
actions: [{
type: 'FunctionCall',
params: {
methodName: 'withdraw',
args: {
token: isNearToken ? NATIVE_NEAR_ADDRESS : token,
amount
},
gas: DEFAULT_GAS,
deposit: isNearToken ? '0' : '1'
}
}]
}; // @ts-ignore
return transaction;
}
async createSwapInstructions(swapParams) {
const {
user,
swapStep,
provider
} = swapParams;
const transactions = [];
const deposits = await SpinFinanceMarket.loadUserDeposits({
user,
provider
});
const inputDepositToken = swapStep.inputMint === WRAPPED_NEAR_ID ? NATIVE_NEAR_ADDRESS : swapStep.inputMint;
const existingAmount = JSBI.BigInt(deposits[inputDepositToken] || '0');
const {
address: baseAddress
} = this.rawMarket.base;
const {
address: quoteAddress
} = this.rawMarket.quote;
const isSellBase = baseAddress === swapStep.inputMint || baseAddress === NATIVE_NEAR_ADDRESS && swapStep.inputMint === WRAPPED_NEAR_ID;
const isNativeMarket = baseAddress === NATIVE_NEAR_ADDRESS || quoteAddress === NATIVE_NEAR_ADDRESS; // Deposit if not enough
if (JSBI.lessThan(existingAmount, swapStep.amountIn)) {
const amountToAdd = JSBI.subtract(swapStep.amountIn, existingAmount); // Deposit native NEAR
if (isNativeMarket && swapStep.inputMint === WRAPPED_NEAR_ID) {
// Unwrap wrapped NEAR
transactions.push(await buildWithdrawNearTransaction(user, amountToAdd)); // Deposit near
transactions.push({
receiverId: SPIN_CONTRACT_ADDRESS,
signerId: user,
actions: [{
type: 'FunctionCall',
params: {
methodName: 'deposit_near',
args: {},
gas: DEFAULT_GAS,
deposit: amountToAdd.toString()
}
}]
});
} else {
// Deposit FT
transactions.push({
receiverId: swapStep.inputMint,
signerId: user,
actions: [{
type: 'FunctionCall',
params: {
methodName: 'ft_transfer_call',
args: {
receiver_id: SPIN_CONTRACT_ADDRESS,
amount: amountToAdd.toString(),
msg: ''
},
gas: DEFAULT_GAS,
deposit: '1'
}
}]
});
}
}
const methodName = isSellBase ? 'place_ask' : 'place_bid';
const quoteNominator = 10 ** this.rawMarket.quote.decimal;
const orderStep = new Decimal(this.rawMarket.limits.step_size);
let quantity = new Decimal((isSellBase ? swapStep.amountIn : swapStep.minAmountOut).toString()).div(orderStep).floor().mul(orderStep); // if we buy base then quantity is what we'll receive, but spin fi
// charge fee from amount out, that's why we need to add it here
if (!isSellBase) {
quantity = quantity.add(swapStep.feeAmount.toString()).div(orderStep).ceil().mul(orderStep);
}
const quoteAmount = new Decimal((isSellBase ? swapStep.minAmountOut : swapStep.amountIn).toString()).div(quoteNominator);
const tickSize = new Decimal(this.rawMarket.limits.tick_size);
const price = quoteAmount.mul(quoteNominator).div(tickSize).div(quantity.div(10 ** this.rawMarket.base.decimal)).floor().mul(tickSize); // Place market order
transactions.push({
receiverId: SPIN_CONTRACT_ADDRESS,
signerId: user,
actions: [{
type: 'FunctionCall',
params: {
methodName,
args: {
market_id: this.rawMarket.id,
price: price.ceil().toFixed(0),
quantity: quantity.toFixed(0),
market_order: true,
memo: MEMO
},
gas: DEFAULT_GAS,
deposit: '0'
}
}]
});
const withdrawAmount = swapStep.minAmountOut.toString();
const withdrawToken = swapStep.outputMint === WRAPPED_NEAR_ID ? NATIVE_NEAR_ADDRESS : swapStep.outputMint; // Withdraw
transactions.push(SpinFinanceMarket.createWithdrawFromDepositsTransaction({
user,
token: withdrawToken,
amount: withdrawAmount
}));
if (isNativeMarket && swapStep.outputMint === WRAPPED_NEAR_ID) {
// Wrap NEAR back
transactions.push({
receiverId: WRAPPED_NEAR_ID,
signerId: user,
actions: [{
type: 'FunctionCall',
params: {
methodName: 'near_deposit',
args: {},
gas: DEFAULT_GAS,
deposit: withdrawAmount
}
}]
});
}
return transactions;
}
async createSwapRouteInstructions(swapParams) {
const {
provider,
user,
swapRoute
} = swapParams;
const transactions = [];
for (let routeStep of swapRoute.steps) {
const stepTransactions = await routeStep.amm.createSwapInstructions({
provider,
user,
swapStep: routeStep
});
transactions.push(...stepTransactions);
}
return transactions;
}
async getPromiseForUpdate({
provider
}) {
try {
const orderBook = await callFunction({
provider,
accountId: SPIN_CONTRACT_ADDRESS,
method: 'get_orderbook',
args: {
market_id: this.rawMarket.id,
limit: 100
}
});
this.orderBook = orderBook;
} catch (e) {
console.log('error getPromiseForUpdate spin for market', this.rawMarket.id);
}
}
getQuote(quoteParams) {
if (!this.orderBook) {
throw new Error(`Order book for market ${this.rawMarket.ticker} is not loaded`);
}
const isSellBase = quoteParams.inputMint === this.rawMarket.base.address || quoteParams.inputMint === WRAPPED_NEAR_ID && this.rawMarket.base.address === NATIVE_NEAR_ADDRESS;
const side = isSellBase ? this.orderBook.bid_orders : this.orderBook.ask_orders;
const isAmountInBase = isSellBase && quoteParams.swapMode === SwapMode.ExactIn || !isSellBase && quoteParams.swapMode === SwapMode.ExactOut;
let restUserAmount = new Decimal(quoteParams.amount.toString());
let amountIn = new Decimal(0);
let amountOut = new Decimal(0);
const baseMultiplier = new Decimal(10).pow(this.rawMarket.base.decimal);
const stepSize = new Decimal(this.rawMarket.limits.step_size);
const fee = new Decimal(this.rawMarket.fees.taker_fee).div(10 ** this.rawMarket.fees.decimals);
for (const order of side) {
const price = new Decimal(order.price);
const restAmountInBase = isAmountInBase ? restUserAmount.div(stepSize).floor().mul(stepSize) : restUserAmount.mul(baseMultiplier).div(price).div(stepSize).floor().mul(stepSize);
const orderAmount = new Decimal(order.quantity);
const takeFromLevelInBase = Decimal.min(restAmountInBase, orderAmount);
const takeFromLevelInQuote = takeFromLevelInBase.mul(price).div(baseMultiplier);
amountIn = isSellBase ? amountIn.add(takeFromLevelInBase) : amountIn.add(takeFromLevelInQuote);
amountOut = isSellBase ? amountOut.add(takeFromLevelInQuote) : amountOut.add(takeFromLevelInBase);
const feeAmount = amountOut.mul(fee);
const amountOutLessFee = amountOut.sub(feeAmount).floor();
let minAmountOut = new Decimal(quoteParams.slippage.getFor(JSBI.BigInt(amountOutLessFee.floor().toString())).toString()); // after removing slippage part requires to be stripped
if (!isSellBase) {
minAmountOut = minAmountOut.div(stepSize).floor().mul(stepSize);
}
restUserAmount = restUserAmount.sub(isAmountInBase ? takeFromLevelInBase : takeFromLevelInQuote);
if (takeFromLevelInBase.lessThanOrEqualTo(0)) {
const amountInBase = isSellBase ? amountIn : minAmountOut;
const amountInQuote = isSellBase ? minAmountOut : amountIn; // check market limits for base token
const isAmountInBaseGreaterMinLimit = amountInBase.greaterThanOrEqualTo(new Decimal(this.rawMarket.limits.min_base_quantity));
const isAmountInBaseLessMaxLimit = amountInBase.lessThanOrEqualTo(new Decimal(this.rawMarket.limits.max_base_quantity)); // check market limits for quote token
const isAmountInQuoteGreaterMinLimit = amountInQuote.greaterThanOrEqualTo(new Decimal(this.rawMarket.limits.min_quote_quantity));
const isAmountInQuoteLessMaxLimit = amountInQuote.lessThanOrEqualTo(new Decimal(this.rawMarket.limits.max_quote_quantity));
return {
amountIn: JSBI.BigInt(amountIn.floor().toString()),
amountOut: JSBI.BigInt(amountOutLessFee.floor().toString()),
minAmountOut: JSBI.BigInt(minAmountOut.floor().toString()),
feeAmount: JSBI.BigInt(feeAmount.floor().toString()),
feeMint: quoteParams.outputMint,
feePct: parseFloat(this.rawMarket.fees.taker_fee) / 10 ** this.rawMarket.base.decimal,
notEnoughLiquidity: !(isAmountInBaseGreaterMinLimit && isAmountInBaseLessMaxLimit && isAmountInQuoteGreaterMinLimit && isAmountInQuoteLessMaxLimit),
priceImpact: 0
};
}
}
const feeAmount = amountOut.mul(fee);
return {
amountIn: JSBI.BigInt(amountIn.floor().toString()),
amountOut: JSBI.BigInt(amountOut.sub(feeAmount).floor().toString()),
minAmountOut: quoteParams.slippage.getFor(JSBI.BigInt(amountOut.floor().toString())),
feeAmount: JSBI.BigInt(feeAmount.floor().toString()),
feeMint: quoteParams.inputMint,
feePct: parseFloat(this.rawMarket.fees.taker_fee) / 10 ** this.rawMarket.base.decimal,
notEnoughLiquidity: true,
priceImpact: 0
};
}
static async loadMarkets(params) {
const {
provider
} = params;
const markets = await callFunction({
accountId: SPIN_CONTRACT_ADDRESS,
method: 'get_markets',
provider
});
return markets.map(market => new SpinFinanceMarket(market));
}
}
const TONIC_FEE_DIVISOR = 10000; // By design
const roundForStep = (n, step, up = false) => {
JSBI.BigInt(step);
if (up) {
return n.div(step).ceil().mul(step);
}
return n.div(step).floor().mul(step);
};
class TonicFoundationMarket {
constructor(market) {
this.market = void 0;
this.label = 'Tonic';
this.id = void 0;
this.contractId = TONIC_ID;
this.instanceId = void 0;
this.reserveTokenMints = void 0;
this.orderBook = void 0;
this.market = market;
this.id = this.market.id;
this.instanceId = Math.round(Math.random() * 1000000); // Tonic doesn't have an instance id
this.orderBook = this.market.orderbook;
this.reserveTokenMints = [this.market.base_token.token_type.type === 'ft' ? this.market.base_token.token_type.account_id : WRAPPED_NEAR_ID, this.market.quote_token.token_type.type === 'ft' ? this.market.quote_token.token_type.account_id : WRAPPED_NEAR_ID];
}
async getPromiseForUpdate({
provider
}) {
const orderBook = await callFunction({
accountId: TONIC_ID,
method: 'get_orderbook',
args: {
market_id: this.id,
depth: 128
},
provider
});
this.orderBook = orderBook;
}
resolveQuote(params) {
const {
amountOut,
amountIn,
fee,
slippage,
stepSize,
outputMint,
notEnoughLiquidity,
swapMode,
isSellBase
} = params;
const feeAmount = amountOut.mul(fee);
const outputWithoutFees = amountOut.sub(feeAmount).floor().toString();
const outputWithoutFeesBI = JSBI.BigInt(outputWithoutFees);
const amountInRounded = swapMode === SwapMode.ExactIn ? amountIn.floor() : amountIn.ceil();
const inputResult = isSellBase ? roundForStep(amountInRounded, stepSize.toString(), swapMode === SwapMode.ExactOut) : amountInRounded;
const outputResult = isSellBase ? new Decimal(outputWithoutFeesBI.toString()) : roundForStep(new Decimal(outputWithoutFeesBI.toString()), stepSize.toString());
const minOutputResult = isSellBase ? new Decimal(slippage.getFor(outputWithoutFeesBI).toString()) : roundForStep(new Decimal(slippage.getFor(outputWithoutFeesBI).toString()), stepSize.toString());
return {
amountIn: JSBI.BigInt(inputResult.floor().toString()),
amountOut: JSBI.BigInt(outputResult.floor().toString()),
minAmountOut: JSBI.BigInt(minOutputResult.floor().toString()),
feeAmount: JSBI.BigInt(feeAmount.floor().toString()),
feeMint: outputMint,
feePct: this.market.taker_fee_base_rate / TONIC_FEE_DIVISOR,
notEnoughLiquidity,
priceImpact: 0