@hikaru-fi/swap-router
Version:
Package for calculating optimal path for executing swaps
181 lines (171 loc) • 6.07 kB
JavaScript
const BN = require("bn.js");
const { toBN } = require("web3-utils");
const { WeightedPool } = require("../poolMath/weightedPool/weightedPool");
const { PoolTypes, SwapTypes } = require("../utils/types");
/**
* @param {import("./poolCache").PoolDictionary} pools
* @param {String} requiredPoolType
* @returns {import("./poolCache").PoolDictionary}
*/
function filterPools(
pools,
requiredPoolType
) {
requiredPoolType = requiredPoolType ? requiredPoolType : PoolTypes.WeightedPool;
const filteredPools = {};
for (const poolAddress in pools) {
if (
pools[poolAddress].type == PoolTypes.All ||
pools[poolAddress].type == requiredPoolType
) {
filteredPools[poolAddress] = pools[poolAddress];
}
}
return filteredPools;
}
/**
*
* @param {import("./poolCache").PoolDictionary} pools
* @param {String} tokenIn
* @param {String} tokenOut
* @returns {[Record<String, WeightedPool>, Record<String, WeightedPool[]>, Record<String, WeightedPool[]>]}
*/
function getPoolsOfInterest(
pools,
tokenIn,
tokenOut
) {
/**
* @type {Record<String,WeightedPool>}
*/
const directPools = {};
/**
* @type {Record<String,WeightedPool[]>}
*/
const hopsIn = {};
/**
* @type {Record<String,WeightedPool[]>}
*/
const hopsOut = {};
for (const poolAddress in pools) {
const pool = pools[poolAddress];
const tokenInExists = pool.containsToken(tokenIn);
const tokenOutExists = pool.containsToken(tokenOut);
if (tokenInExists && tokenOutExists) {
directPools[pool.address] = [pool];
continue;
}
if (tokenInExists) {
for (const token of pool.tokens) {
if (token == tokenIn) continue;
if (!hopsIn[token]) hopsIn[token] = [];
hopsIn[token].push(pool);
}
}
if (tokenOutExists) {
for (const token of pool.tokens) {
if (token == tokenOut) continue;
if (!hopsOut[token]) hopsOut[token] = [];
hopsOut[token].push(pool);
}
}
}
return [directPools, hopsIn, hopsOut]
}
/**
*
* @param {Record<String,WeightedPool>} pools
* @param {String} poolAddress
* @param {String} token
* @param {BN} amount
* @returns {Boolean}
*/
function checkSwapLimit(pools, poolAddress, token, amount) {
const pool = pools[poolAddress];
const swapLimit = pool.getSwapLimit(token);
return swapLimit.gt(amount);
}
/**
*
* @param {import("./pathCalculation").SwapRoute} swapPath
* @param {Record<String,WeightedPool>} pools
* @param {BN} swapAmount
* @param {String} swapType
*/
function checkSwapPath(swapPath, pools, swapAmount, swapType) {
let tokensInPools = true;
let liquidityAvailable = true;
switch(swapType) {
case SwapTypes.Sell: {
let amountIn = swapAmount.clone();
let amountOut = toBN(0);
for (const swap of swapPath.path) {
const pool = pools[swap.poolAddress];
tokensInPools = tokensInPools && pool.containsToken(swap.tokenIn) && pool.containsToken(swap.tokenOut);
liquidityAvailable = liquidityAvailable && checkSwapLimit(pools, swap.poolAddress, swap.tokenIn, amountIn);
if (!tokensInPools || !liquidityAvailable) break;
amountOut = pool.sellTokens(swap.tokenIn, swap.tokenOut, amountIn);
amountIn = amountOut;
}
break;
}
case SwapTypes.Buy: {
let amountIn = toBN(0);
let amountOut = swapAmount.clone();
for (let swapId = swapPath.path.length - 1; swapId >= 0; swapId--) {
const swap = swapPath.path[swapId];
const pool = pools[swap.poolAddress];
tokensInPools = tokensInPools && pool.containsToken(swap.tokenIn) && pool.containsToken(swap.tokenOut);
liquidityAvailable = liquidityAvailable && checkSwapLimit(pools, swap.poolAddress, swap.tokenOut, amountOut);
if (!tokensInPools || !liquidityAvailable) break;
amountIn = pool.buyTokens(swap.tokenIn, swap.tokenOut, amountOut);
amountOut = amountIn;
}
break;
}
}
return tokensInPools && liquidityAvailable;
}
/**
*
* @param {Record<String,WeightedPool>} pools
* @param {import("./pathCalculation").SwapRoute[]} directSwaps
* @param {import("./pathCalculation").SwapRoute[]} hopSwaps
* @param {BN} swapAmount
* @param {String} swapType
* @returns {import("./pathCalculation").SwapRoute[]}
*/
function filterPathsForSwapType(pools, directSwaps, hopSwaps, swapAmount, swapType) {
let suitableSwaps = [];
switch(swapType) {
case SwapTypes.Sell: {
suitableSwaps = directSwaps.filter(
(val) => checkSwapLimit(pools, val.path[0].poolAddress, val.path[0].tokenIn, swapAmount)
)
suitableSwaps = suitableSwaps.concat(
hopSwaps.filter(
(swapPath) => checkSwapPath(swapPath, pools, swapAmount, swapType)
)
)
break;
}
case SwapTypes.Buy: {
suitableSwaps = directSwaps.filter(
(val) => checkSwapLimit(pools, val.path[0].poolAddress, val.path[0].tokenOut, swapAmount)
);
suitableSwaps = suitableSwaps.concat(
hopSwaps.filter(
(swapPath) => checkSwapPath(swapPath, pools, swapAmount, swapType)
)
)
break;
}
}
return suitableSwaps;
}
module.exports = {
filterPools,
getPoolsOfInterest,
checkSwapLimit,
filterPathsForSwapType
}