@syncswap/sdk
Version:
SyncSwap TypeScript SDK for building DeFi applications
483 lines • 20.4 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.lastRoutePoolArgs = void 0;
exports.default = fetchRoutePools;
const contractRegistry_1 = __importDefault(require("../contracts/contractRegistry"));
const types_1 = require("./types");
const constants_1 = require("../utils/constants");
const helper_1 = require("./helper");
const tokenRegistry_1 = require("../tokens/tokenRegistry");
async function fetchRoutePools(userAccount, // using 0x1 by default
tokenA, tokenB, routerCommonBasesTokens, isTokenInHighValue = false, isTokenOutHighValue = false) {
// Handles the ETH.
const wETHAddress = (0, tokenRegistry_1.getWETHAddress)();
tokenA = tokenA.toLowerCase() === tokenRegistry_1.Token.ETHER.address ? wETHAddress : tokenA?.toLowerCase();
tokenB = tokenB.toLowerCase() === tokenRegistry_1.Token.ETHER.address ? wETHAddress : tokenB?.toLowerCase();
if (tokenA === tokenB || !tokenA || !tokenB) {
// This can happens on wrapping ETH.
//console.error('Cannot fetch route pools for two identical tokens', tokenA);
return null;
}
const routeHelper = contractRegistry_1.default.getContractByName("route_helper");
if (!routeHelper) {
console.error("fetchRoutePools: no route helper contract found, skipped.");
return null;
}
// Update args.
const currentArgs = tokenA + tokenB;
exports.lastRoutePoolArgs = currentArgs;
//const tasks: Promise<boolean>[] = [];
const allArgsData = [];
const allRoutePools = [];
if (exports.lastRoutePoolArgs !== currentArgs) {
return null;
}
// V2
const classicFactoryV2 = contractRegistry_1.default.getAddressByName("classic_factory2");
if (classicFactoryV2) {
const factories = [classicFactoryV2];
const stableFactoryV2 = contractRegistry_1.default.getAddressByName("stable_factory2");
if (stableFactoryV2) {
factories.push(stableFactoryV2);
}
const cryptoFactoryV2 = contractRegistry_1.default.getAddressByName("crypto_factory2");
if (cryptoFactoryV2) {
factories.push(cryptoFactoryV2);
}
const masterAddressV2 = contractRegistry_1.default.getAddressByName("pool_master2");
//console.log('factories', factories, 'masterAddressV2', masterAddressV2);
allArgsData.push({
arg: [
tokenA,
tokenB,
factories,
routerCommonBasesTokens,
masterAddressV2,
userAccount,
false,
],
isV2: true,
isV3: false,
isUniswapV3: false,
});
}
// V2.1
const classicFactoryV3 = contractRegistry_1.default.getAddressByName("classic_factory3");
if (classicFactoryV3) {
const factories = [classicFactoryV3];
const stableFactoryV3 = contractRegistry_1.default.getAddressByName("stable_factory3");
if (stableFactoryV3) {
factories.push(stableFactoryV3);
}
const cryptoFactoryV3 = contractRegistry_1.default.getAddressByName("crypto_factory3");
if (cryptoFactoryV3) {
factories.push(cryptoFactoryV3);
}
const masterAddressV3 = contractRegistry_1.default.getAddressByName("pool_master3");
allArgsData.push({
arg: [
tokenA,
tokenB,
factories,
routerCommonBasesTokens,
masterAddressV3,
userAccount,
true,
],
isV2: true,
isV3: true,
isUniswapV3: false,
});
}
// V3
if (true) {
const uniswapV3Factory = contractRegistry_1.default.getAddressByName("ranged_factory");
if (uniswapV3Factory) {
const factories = [uniswapV3Factory];
// Note `master` is only used to get fee manager here, use the v2.1 master
const masterAddress = contractRegistry_1.default.getAddressByName("pool_master_range");
//console.log('fetchRoutePools: fetching range route pools', factories, masterAddress);
allArgsData.push({
arg: [
tokenA,
tokenB,
factories,
routerCommonBasesTokens,
masterAddress,
userAccount,
true,
],
isV2: true,
isV3: true,
isUniswapV3: true,
});
}
}
if (exports.lastRoutePoolArgs !== currentArgs) {
return null;
}
const results = await getRoutePoolsWithTimeout(routeHelper, allArgsData.map((data) => data.arg));
if (!results) {
return null;
}
results.forEach((payload, index) => {
if (payload) {
const { arg, isV2, isV3, isUniswapV3 } = allArgsData[index];
const [, , factories, , masterAddress] = arg;
const pool = _fetchRoutePools(payload, tokenA, tokenB, userAccount, routeHelper, factories, routerCommonBasesTokens, masterAddress, isV2, isV3, isUniswapV3);
if (pool) {
allRoutePools.push(pool);
}
}
});
// await Promise.all(tasks);
if (allRoutePools.length === 0) {
console.log("fetchRoutePools: no route pools found.");
return null;
}
//console.log('allRoutePools', allRoutePools);
if (exports.lastRoutePoolArgs !== currentArgs) {
return null;
}
const result = {
routeTokens: routerCommonBasesTokens,
tokenA: tokenA,
tokenB: tokenB,
//timestamp: Date.now(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
directPoolOptimal: null,
pools: {
poolsA: [],
poolsB: [],
poolsBase: [],
poolsDirect: [],
},
};
for (const routePools of allRoutePools) {
if (routePools) {
const pools = routePools.pools;
result.pools.poolsA = result.pools.poolsA.concat(pools.poolsA);
result.pools.poolsB = result.pools.poolsB.concat(pools.poolsB);
result.pools.poolsBase = result.pools.poolsBase.concat(pools.poolsBase);
result.pools.poolsDirect = result.pools.poolsDirect.concat(pools.poolsDirect);
//console.log('routePools.directPoolOptimal', routePools.directPoolOptimal, 'result.pools.poolsDirect', result.pools.poolsDirect);
if (routePools.directPoolOptimal &&
routePools.directPoolOptimal.reserveA) {
if (result.directPoolOptimal === null ||
routePools.directPoolOptimal.reserveA.gt(result.directPoolOptimal.reserveA)) {
result.directPoolOptimal = routePools.directPoolOptimal;
}
}
}
}
// filter possible pools
const commonMinReserve = constants_1.DECIMALS_4;
const canFilterByReserves = false;
// A -> base
{
let totalReserveA = constants_1.ZERO;
let avgPoolCountA = 0;
const filteredPoolsA = result.pools.poolsA.filter((pool) => {
if (pool.pool !== constants_1.ZERO_ADDRESS && pool.isUniswap) {
return (pool.reserveA.gt(commonMinReserve) ||
pool.reserveB.gt(commonMinReserve));
}
if (pool.pool !== constants_1.ZERO_ADDRESS &&
pool.reserveA.gt(commonMinReserve) &&
pool.reserveB.gt(commonMinReserve)) {
// filter by A average reserve
avgPoolCountA++;
totalReserveA = totalReserveA.add(pool.reserveA);
if (canFilterByReserves && avgPoolCountA > 1) {
const averageReserveA = totalReserveA.div(avgPoolCountA);
const minReserveA = averageReserveA.div(pool.isUniswap ? 50 : 4); // avg / 4
if (pool.reserveA.lt(minReserveA)) {
return false;
}
}
return true;
}
return false;
});
if (filteredPoolsA.length > 0) {
result.pools.poolsA = filteredPoolsA;
}
else {
result.pools.poolsA = result.pools.poolsA.filter((pool) => pool.pool !== constants_1.ZERO_ADDRESS && !pool.reserveA.isZero());
}
}
// base -> B
{
let totalReserveB = constants_1.ZERO;
let avgPoolCountB = 0;
const filteredPoolsB = result.pools.poolsB.filter((pool) => {
if (pool.pool !== constants_1.ZERO_ADDRESS && pool.isUniswap) {
return (pool.reserveA.gt(commonMinReserve) ||
pool.reserveB.gt(commonMinReserve));
}
if (pool.pool !== constants_1.ZERO_ADDRESS &&
pool.reserveA.gt(commonMinReserve) &&
pool.reserveB.gt(commonMinReserve)) {
// filter by B average reserve
avgPoolCountB++;
totalReserveB = totalReserveB.add(pool.reserveB);
if (canFilterByReserves && avgPoolCountB > 1) {
const averageReserveB = totalReserveB.div(avgPoolCountB);
const minReserveB = averageReserveB.div(pool.isUniswap ? 50 : 4); // avg / 4
if (pool.reserveB.lt(minReserveB)) {
return false;
}
}
return true;
}
return false;
});
if (filteredPoolsB.length > 0) {
result.pools.poolsB = filteredPoolsB;
}
else {
result.pools.poolsB = result.pools.poolsB.filter((pool) => pool.pool !== constants_1.ZERO_ADDRESS && !pool.reserveB.isZero());
}
}
// base -> base
{
const minReserveIn = isTokenInHighValue || isTokenOutHighValue ? constants_1.DECIMALS_7 : constants_1.DECIMALS_10;
const minReserveOut = isTokenInHighValue || isTokenOutHighValue ? constants_1.DECIMALS_7 : constants_1.DECIMALS_10;
// compare average reserve for A and B
let countA = 0;
let totalAmountA = constants_1.ZERO;
let countB = 0;
let totalAmountB = constants_1.ZERO;
result.pools.poolsBase = result.pools.poolsBase.filter((pool) => {
if (pool.pool !== constants_1.ZERO_ADDRESS) {
//if (pool.isUniswap) {
// return pool.reserveA.gt(commonMinReserve) || pool.reserveB.gt(commonMinReserve);
//}
const [minA, minB] = pool.tokenA === tokenA
? [minReserveIn, minReserveOut]
: [minReserveOut, minReserveIn];
// check min reserves
if (pool.reserveA.gte(minA) && pool.reserveB.gte(minB)) {
// check average reserve A
if (canFilterByReserves && countA >= 1) {
const averageReserveA = totalAmountA.div(countA);
if (pool.reserveA.lt(averageReserveA.div(pool.isUniswap ? 50 : 10))) {
// skip if : pool reserve < avg / 20
// check average reserve B
if (countB >= 1) {
const averageReserveB = totalAmountB.div(countB);
if (pool.reserveB.lt(averageReserveB.div(pool.isUniswap ? 50 : 10))) {
// skip if : pool reserve < avg / 20
return false;
}
}
}
}
countA++;
totalAmountA = totalAmountA.add(pool.reserveA);
countB++;
totalAmountB = totalAmountB.add(pool.reserveB);
return true;
}
}
return false;
});
}
// A -> B
{
let totalReserveA = constants_1.ZERO;
let avgPoolCountA = 0;
let totalReserveB = constants_1.ZERO;
let avgPoolCountB = 0;
const filteredPoolsDirect = result.pools.poolsDirect.filter((pool) => {
if (pool.pool !== constants_1.ZERO_ADDRESS && pool.isUniswap) {
return (pool.reserveA.gt(commonMinReserve) ||
pool.reserveB.gt(commonMinReserve));
}
if (pool.pool !== constants_1.ZERO_ADDRESS &&
pool.reserveA.gt(commonMinReserve) &&
pool.reserveB.gt(commonMinReserve)) {
// filter by A average reserve
if (avgPoolCountA > 1 && avgPoolCountB > 1) {
const averageReserveA = totalReserveA.div(avgPoolCountA);
const minReserveA = averageReserveA.div(pool.isUniswap ? 50 : 15); // avg / 15
if (canFilterByReserves && pool.reserveA.lt(minReserveA)) {
// filter by B average reserve
const averageReserveB = totalReserveB.div(avgPoolCountB);
const minReserveB = averageReserveB.div(pool.isUniswap ? 50 : 15); // avg / 15
if (pool.reserveB.lt(minReserveB)) {
return false;
}
}
}
avgPoolCountA++;
totalReserveA = totalReserveA.add(pool.reserveA);
avgPoolCountB++;
totalReserveB = totalReserveB.add(pool.reserveB);
return true;
}
});
if (filteredPoolsDirect.length > 0) {
result.pools.poolsDirect = filteredPoolsDirect;
}
else {
result.pools.poolsDirect = result.pools.poolsDirect.filter((pool) => pool.pool !== constants_1.ZERO_ADDRESS && !pool.reserveA.isZero());
}
}
//console.log('fetchRoutePools: result', result);
return result;
}
function _fetchRoutePools(payload, tokenA, tokenB, account, routeHelper,
//vault: string,
factories, routeTokens, masterAddress, isV2, isV3, isUniswapV3) {
try {
const currentArgs = tokenA + tokenB;
if (exports.lastRoutePoolArgs !== currentArgs) {
return null;
}
const poolsDirect = normalizePools(isV2, isV3, isUniswapV3, payload.poolsDirect);
// Finds the optimal pool.
const [directPoolClassic, directPoolStable, directPoolAqua] = poolsDirect.length >= 3 ? poolsDirect : [poolsDirect[0], poolsDirect[1]];
let directPoolOptimal;
let fairReserveOptimal = constants_1.ZERO;
if (directPoolAqua &&
directPoolAqua.reserveA.gt(directPoolClassic.reserveA.div(constants_1.TWO))) {
directPoolOptimal = directPoolAqua;
fairReserveOptimal = directPoolAqua.reserveA
.mul(constants_1.TWO)
.gt(directPoolClassic.reserveA)
? directPoolClassic.reserveA
: directPoolAqua.reserveA.mul(constants_1.TWO);
}
else if (directPoolStable &&
directPoolStable.reserveA.gt(fairReserveOptimal)) {
directPoolOptimal = directPoolStable;
}
else {
directPoolOptimal = directPoolClassic;
}
const routePools = {
directPoolOptimal: directPoolOptimal,
routeTokens: routeTokens,
tokenA: tokenA,
tokenB: tokenB,
timestamp: Date.now(),
pools: {
poolsDirect: poolsDirect,
poolsA: normalizePools(isV2, isV3, isUniswapV3, payload.poolsA),
poolsB: normalizePools(isV2, isV3, isUniswapV3, payload.poolsB),
poolsBase: normalizePools(isV2, isV3, isUniswapV3, payload.poolsBase),
},
};
//console.log('routePools', routePools);
return routePools;
}
catch (error) {
console.log("fetchRoutePools: Error on fetching route pools", error, tokenA, tokenB, masterAddress, "isV2", isV2);
throw error;
}
}
function normalizePool(isV2, isV3, isUniswapV3, pool) {
const cryptoPoolData = [];
if (pool.poolType === types_1.PoolTypes.AQUA &&
pool.cryptoPoolData &&
pool.cryptoPoolData.length !== 0) {
const data = pool.cryptoPoolData[0];
cryptoPoolData.push(data);
}
const rangePoolData = [];
if (pool.poolType === types_1.PoolTypes.UNISWAP_V3 &&
pool.rangePoolData &&
pool.rangePoolData.length !== 0) {
const data = pool.rangePoolData[0];
rangePoolData.push(data);
}
return {
isV2: isV2,
isV3: isV3,
isUniswap: isUniswapV3,
//vault: vault,
pool: pool.pool,
tokenA: pool.tokenA.toLowerCase(),
tokenB: pool.tokenB.toLowerCase(),
poolType: pool.poolType,
reserveA: pool.reserveA,
reserveB: pool.reserveB,
swapFeeAB: pool.swapFeeAB,
swapFeeBA: pool.swapFeeBA,
// aqua / stable params
a: pool.a,
// aqua data
cryptoPoolData: cryptoPoolData,
// range data
rangePoolData: rangePoolData,
};
}
function normalizePools(isV2, isV3, isUniswapV3, pools) {
const newPools = [];
for (const pool of pools) {
newPools.push(normalizePool(isV2, isV3, isUniswapV3, pool));
}
return newPools;
}
async function getRoutePoolsWithTimeout(routeHelper, allArgs, retryCount = 0, maxRetries = 3) {
const TIMEOUT = 4000;
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Operation timed out")), TIMEOUT);
});
const currentArgs = allArgs[0][0] + allArgs[0][1];
if (exports.lastRoutePoolArgs !== currentArgs) {
return null;
}
try {
const getRoutePools = async () => {
// console.log('getRoutePools: start', allArgs.length, 'calls');
try {
const startTime = Date.now();
// Use multicall to batch all getRoutePools calls
try {
const multicallContract = (0, helper_1.getCachedMulticall3Contract)();
if (multicallContract) {
const calls = allArgs.map((args) => ({
target: routeHelper.address,
callData: routeHelper.interface.encodeFunctionData("getRoutePools", args),
}));
const results = await multicallContract.callStatic.tryAggregate(false, calls);
// console.log('getRoutePools: done', (Date.now() - startTime), 'ms');
return results.map((result) => result.success
? routeHelper.interface.decodeFunctionResult("getRoutePools", result.returnData)[0]
: null);
}
}
catch (e) {
console.warn("getRoutePools: error on getting route pools with multicall3", e);
}
// Fallback to legacy function
console.log("getRoutePools: using individual calls");
const results = await Promise.all(allArgs.map((args) => routeHelper.getRoutePools(...args).catch(() => null)));
console.log("getRoutePools: done with individual calls", Date.now() - startTime, "ms");
return results;
}
catch (e) {
console.warn("Error getRoutePools", e);
throw e;
}
};
return await Promise.any([getRoutePools(), timeoutPromise]);
}
catch (error) {
if (exports.lastRoutePoolArgs !== currentArgs) {
return null;
}
if (retryCount < maxRetries) {
return getRoutePoolsWithTimeout(routeHelper, allArgs, retryCount + 1, maxRetries);
}
else {
throw error;
}
}
}
//# sourceMappingURL=fetchRoutePools.js.map