UNPKG

@syncswap/sdk

Version:

SyncSwap TypeScript SDK for building DeFi applications

483 lines 20.4 kB
"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