UNPKG

@syncswap/sdk

Version:

SyncSwap TypeScript SDK for building DeFi applications

369 lines 15.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SwapRouter = void 0; exports.getRoutePools = getRoutePools; exports.findAllPossiblePaths = findAllPossiblePaths; exports.filterPathsToUseExactIn = filterPathsToUseExactIn; exports.findBestAmountsForPathsExactIn = findBestAmountsForPathsExactIn; const fetchRoutePools_1 = __importDefault(require("./fetchRoutePools")); const types_1 = require("./types"); const sdkHelper_1 = require("../sdkHelper"); const address_1 = require("../utils/address"); const constants_1 = require("../utils/constants"); const tokenRegistry_1 = require("../tokens/tokenRegistry"); const numbers_1 = require("../utils/numbers"); const statestore_1 = require("../statestore/statestore"); function getRoutePools(userAccount, // using 0x1 by default tokenA, tokenB, routerCommonBasesTokens) { const route = (0, fetchRoutePools_1.default)(userAccount, tokenA, tokenB, routerCommonBasesTokens); return route; } class SwapRouter { static routeTimestamp() { return this.latestRouteTimestamp; } } exports.SwapRouter = SwapRouter; SwapRouter.latestRouteTimestamp = 0; const blacklistedPools = new Set(Array.from([], (item) => address_1.Address.normalizeAddress(item))); // find all possible paths from token in to token out with route pools. // this is the first step of processing route pools after fetched. function findAllPossiblePaths(tokenIn, ts, amountIn) { const paths = []; // all viable paths for swapping //console.log('findAllPossiblePaths routePools', routePools); const routePools = (0, statestore_1.stateStore)().currentRoutePools; if (!routePools) { throw Error('Route pools not found'); } // collect paths: direct for (const pool of routePools.pools.poolsDirect) { const allowed = (0, statestore_1.stateStore)().aquaPoolOnly ? (pool && ((0, statestore_1.stateStore)().allowUniswapV3Pools ? pool.poolType == types_1.PoolTypes.UNISWAP_V3 : pool.poolType == types_1.PoolTypes.AQUA)) : pool; if (allowed && (0, sdkHelper_1.hasLiquidity)(pool, tokenIn) && !blacklistedPools.has(pool.pool.toLowerCase())) { const path = (0, sdkHelper_1.createPath)([{ pool: pool, tokenIn: tokenIn, }]); paths.push(path); } } if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } // exit if hops are disabled if (!(0, statestore_1.stateStore)().enableHops) { //console.log('[findAllPossiblePaths] Multi-hop is disabled, paths', paths); return paths; } // put pools to map for search const poolByTokens = new Map(); const addPool = (pool) => { if (!(0, statestore_1.stateStore)().aquaPoolOnly || ((0, statestore_1.stateStore)().allowUniswapV3Pools ? pool.poolType == types_1.PoolTypes.UNISWAP_V3 : pool.poolType == types_1.PoolTypes.AQUA)) { if (!blacklistedPools.has(pool.pool.toLowerCase())) { const poolKey = (0, sdkHelper_1.getPoolKey)(pool.tokenA, pool.tokenB); let pools = poolByTokens.get(poolKey); if (!pools) { poolByTokens.set(poolKey, [pool]); } else { pools.push(pool); } } } }; for (const pool of routePools.pools.poolsA) { if ((0, sdkHelper_1.hasLiquidity)(pool, tokenIn, amountIn)) { addPool(pool); } } if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } for (const pool of routePools.pools.poolsB) { if ((0, sdkHelper_1.hasLiquidity)(pool)) { addPool(pool); } } if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } for (const pool of routePools?.pools.poolsBase) { if ((0, sdkHelper_1.hasLiquidity)(pool)) { addPool(pool); } } if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } //console.log('[findAllPossiblePaths] poolByTokens', poolByTokens); // multi-step paths with hops const length = routePools.routeTokens.length; const tokenOut = (tokenIn === routePools.tokenA ? routePools.tokenB : routePools.tokenA).toLowerCase(); // collect paths: 1 hop for (let i = 0; i < length; i++) { const baseToken = routePools.routeTokens[i].toLowerCase(); // skip invalid paths if (baseToken === tokenIn || baseToken === tokenOut) { continue; } if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } const poolsA = poolByTokens.get((0, sdkHelper_1.getPoolKey)(tokenIn, baseToken)); //console.log('[findAllPossiblePaths] hop 1 search poolA', [tokenIn, baseToken], poolA); // skip if pool A has no liquidity if (!poolsA || poolsA.length === 0) { continue; } const poolsB = poolByTokens.get((0, sdkHelper_1.getPoolKey)(baseToken, tokenOut)); //console.log('[findAllPossiblePaths] hop 1 search poolA', [baseToken, tokenOut], poolB); // skip if pool B has no liquidity if (!poolsB || poolsB.length === 0) { continue; } function createPaths(poolA, poolB) { if (poolA && poolB) { const path = (0, sdkHelper_1.createPath)([{ pool: poolA, tokenIn: tokenIn, }, { pool: poolB, tokenIn: baseToken, }]); paths.push(path); } } // A1 - B1, A1 - B2, A1 - B3, A2 - B1, A2 - B2, A2 - B3, A3 - B1, A3 - B2, A3 - B3 for (const poolA of poolsA) { for (const poolB of poolsB) { createPaths(poolA, poolB); } } } if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } // collect paths: 2 hop for (let i = 0; i < length; i++) { // base token 1 const baseToken1 = routePools.routeTokens[i].toLowerCase(); // skip invalid paths if (baseToken1 === tokenIn || baseToken1 === tokenOut) { continue; } if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } const poolsA = poolByTokens.get((0, sdkHelper_1.getPoolKey)(tokenIn, baseToken1)); // skip if pool A has no liquidity if (!poolsA || poolsA.length === 0) { continue; } for (let j = i + 1; j < length; j++) { // base token 2 // skip identical bases if (i === j) { continue; } const baseToken2 = routePools.routeTokens[j].toLowerCase(); // skip invalid paths if (baseToken2 === tokenIn || baseToken2 === tokenOut || baseToken2 === baseToken1) { continue; } const poolsBase = poolByTokens.get((0, sdkHelper_1.getPoolKey)(baseToken1, baseToken2)); // skip if pool Base has no liquidity if (!poolsBase || poolsBase.length === 0) { continue; } if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } const poolsB = poolByTokens.get((0, sdkHelper_1.getPoolKey)(baseToken2, tokenOut)); // skip if pool B has no liquidity if (!poolsB || poolsB.length === 0) { continue; } function createPaths(poolA, poolBase, poolB) { if (poolA && poolBase && poolB) { const path = (0, sdkHelper_1.createPath)([{ pool: poolA, tokenIn: tokenIn, }, { pool: poolBase, tokenIn: baseToken1, }, { pool: poolB, tokenIn: baseToken2, }]); paths.push(path); } } for (const poolA of poolsA) { for (const poolBase of poolsBase) { for (const poolB of poolsB) { createPaths(poolA, poolBase, poolB); } } } } } if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } return paths; } const MAX_PATHS = 2; // max path count to use async function filterPathsToUseExactIn(paths, tokenIn, amountIn, ts, enableSplits = true) { if (paths.length <= MAX_PATHS) { return paths; } const availablePaths = []; const availablePathAmounts = new Map(); // filter available paths with output amount const sessionId = Date.now(); for (const path of paths) { const amounts = await (0, sdkHelper_1.calculatePathAmountsByInput)(path, amountIn, sessionId); // skip failed paths if (amounts !== null) { availablePaths.push(path); availablePathAmounts.set(path, amounts); } } if (availablePaths.length <= MAX_PATHS) { return availablePaths; } if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } // sort available paths by output amount availablePaths.sort((pathA, pathB) => { const amountsA = availablePathAmounts.get(pathA); const amountsB = availablePathAmounts.get(pathB); //invariant(amountsA && amountsB, 'No amounts for available path'); return amountsA.amountOut.gt(amountsB.amountOut) ? -1 : 1; }); if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } const pathsToUse = availablePaths.slice(0, enableSplits ? MAX_PATHS : 1 // split count ); return pathsToUse; } // find the best amounts for given paths with all possible input amount groups. // this is the final step of processing route pools, after paths filtered. async function findBestAmountsForPathsExactIn(paths, amountIn, ts, tokenOut) { const pathAmounts = await (0, sdkHelper_1.splitAmount)(amountIn, paths.length); if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } const groups = []; const groupPromises = []; for (const amounts of pathAmounts) { const promise = new Promise((resolve, reject) => { (0, sdkHelper_1.calculateGroupAmounts)(paths, amounts).then(group => { if (group === null) { reject('expired'); } else { //groups.push(group); const { amountOut, quoteOut, pathsWithAmounts } = group; if (!amountOut.isZero() && !quoteOut.isZero()) { group.gas = (0, sdkHelper_1.computeGasFee)(pathsWithAmounts); //amountGranularity = amountGranularity.add(amountOut); //gasGranularity = gasGranularity.add(group.gas); groups.push(group); } resolve(true); } }); }); groupPromises.push(promise); } if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } await Promise.all(groupPromises); if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } let bestPathsWithAmounts = []; let bestAmountOut = constants_1.ZERO; let bestQuoteOut = constants_1.ZERO; let bestPriceImpact = null; let bestVal = null; const tokenOutData = tokenRegistry_1.TokenRegistry.getTokenByAddress(tokenOut); const testAmountOut = tokenOutData ? numbers_1.Numbers.pow(tokenOutData.decimals) : constants_1.ETHER; // one token const gasGranularity = constants_1.DECIMALS_6; // value for test gas price // const network = ContractRegistry.getNetworkName(); const [tokenOutTestPrice, pathGasPrice] = [constants_1.ZERO, constants_1.ZERO]; const computeWithoutGasPrice = pathGasPrice.isZero() || tokenOutTestPrice.isZero(); for (const group of groups) { const groupAmountOut = group.amountOut; if (!groupAmountOut.isZero()) { // Check and update price impact if (!group.quoteOut.isZero()) { const amountLoss = group.quoteOut.sub(groupAmountOut); const groupPriceImpact = amountLoss.mul(constants_1.ETHER).div(group.quoteOut); if (bestPriceImpact === null || groupPriceImpact.lt(bestPriceImpact)) { bestPriceImpact = groupPriceImpact; } } let bestTempVal = constants_1.ZERO; if (computeWithoutGasPrice || !group.gas) { bestTempVal = groupAmountOut; } else { const groupAmountOutPrice = groupAmountOut.mul(tokenOutTestPrice).div(testAmountOut); //console.log('groupAmountOutPrice', groupAmountOutPrice.toString(), 'amountGranularity', amountGranularity.toString(), 'tokenOutTestPrice', tokenOutTestPrice.toString()); const groupPathGasPrice = group.gas.mul(pathGasPrice).div(gasGranularity); const finallyPrice = groupAmountOutPrice.sub(groupPathGasPrice); bestTempVal = finallyPrice; } // Check and update amount out //if (groupAmountOut.gt(bestAmountOut)) { const hasBetterValue = !bestVal || bestTempVal.gt(bestVal); //const hasBetterAmountOut = groupAmountOut.gt(bestAmountOut); if (hasBetterValue) { // set as best if has more output, // there is not need to check path length as the case is rare bestPathsWithAmounts = group.pathsWithAmounts; bestAmountOut = groupAmountOut; bestQuoteOut = group.quoteOut; bestVal = bestTempVal; // Check and update price impact if (!group.quoteOut.isZero()) { const amountLoss = group.quoteOut.sub(groupAmountOut); const groupPriceImpact = amountLoss.mul(constants_1.ETHER).div(group.quoteOut); bestPriceImpact = groupPriceImpact; } else { bestPriceImpact = constants_1.ZERO; } } } } if (ts !== SwapRouter.routeTimestamp()) { throw Error('expired'); } const found = bestAmountOut !== null; // && !bestAmountOut.isZero() && bestPathsWithAmounts.length !== 0; // estimate gas //startTime = Date.now(); let gas; if (found) { gas = (0, sdkHelper_1.computeGasFee)(bestPathsWithAmounts); } else { gas = constants_1.ZERO; } return { found: found, direction: types_1.RouteDirection.EXACT_IN, pathsWithAmounts: bestPathsWithAmounts, amountIn: amountIn, amountOut: bestAmountOut, quote: bestQuoteOut, bestPriceImpact: bestPriceImpact, gas: gas, }; } //# sourceMappingURL=index.js.map