UNPKG

quickswap-smart-order-router

Version:
686 lines 75.4 kB
import { BigNumber } from '@ethersproject/bignumber'; import { JsonRpcProvider } from '@ethersproject/providers'; import DEFAULT_TOKEN_LIST from '@uniswap/default-token-list'; import { Protocol, SwapRouter } from '@uniswap/router-sdk'; import { Fraction, TradeType } from '@uniswap/sdk-core'; import { Pool, Position, SqrtPriceMath, TickMath, } from '@uniswap/v3-sdk'; import retry from 'async-retry'; import JSBI from 'jsbi'; import _ from 'lodash'; import NodeCache from 'node-cache'; import { V3HeuristicGasModelFactory } from '.'; import { CachingGasStationProvider, CachingTokenProviderWithFallback, CachingV2SubgraphProvider, CachingV3PoolProvider, CachingV3SubgraphProvider, EIP1559GasPriceProvider, ETHGasStationInfoProvider, LegacyGasPriceProvider, NodeJSCache, OnChainGasPriceProvider, StaticV2SubgraphProvider, StaticV3SubgraphProvider, SwapRouterProvider, UniswapMulticallProvider, URISubgraphProvider, V2QuoteProvider, V2SubgraphProviderWithFallBacks, V3SubgraphProviderWithFallBacks, } from '../../providers'; import { CachingTokenListProvider, } from '../../providers/caching-token-list-provider'; import { TokenProvider } from '../../providers/token-provider'; import { TokenValidationResult, TokenValidatorProvider, } from '../../providers/token-validator-provider'; import { V2PoolProvider, } from '../../providers/v2/pool-provider'; import { ArbitrumGasDataProvider, OptimismGasDataProvider, } from '../../providers/v3/gas-data-provider'; import { V3PoolProvider, } from '../../providers/v3/pool-provider'; import { V3QuoteProvider, } from '../../providers/v3/quote-provider'; import { ChainId, ID_TO_CHAIN_ID, ID_TO_NETWORK_NAME, V2_SUPPORTED, } from '../../util/chains'; import { log } from '../../util/log'; import { buildSwapMethodParameters, buildTrade, } from '../../util/methodParameters'; import { metric, MetricLoggerUnit } from '../../util/metric'; import { poolToString, routeToString } from '../../util/routes'; import { UNSUPPORTED_TOKENS } from '../../util/unsupported-tokens'; import { SwapToRatioStatus, } from '../router'; import { DEFAULT_ROUTING_CONFIG_BY_CHAIN, ETH_GAS_STATION_API_URL, } from './config'; import { V2RouteWithValidQuote, V3RouteWithValidQuote, } from './entities/route-with-valid-quote'; import { getBestSwapRoute } from './functions/best-swap-route'; import { calculateRatioAmountIn } from './functions/calculate-ratio-amount-in'; import { computeAllV2Routes, computeAllV3Routes, } from './functions/compute-all-routes'; import { getV2CandidatePools, getV3CandidatePools, } from './functions/get-candidate-pools'; import { V2HeuristicGasModelFactory } from './gas-models/v2/v2-heuristic-gas-model'; export class AlphaRouter { constructor({ chainId, provider, multicall2Provider, v3PoolProvider, v3QuoteProvider, v2PoolProvider, v2QuoteProvider, v2SubgraphProvider, tokenProvider, blockedTokenListProvider, v3SubgraphProvider, gasPriceProvider, v3GasModelFactory, v2GasModelFactory, swapRouterProvider, optimismGasDataProvider, tokenValidatorProvider, arbitrumGasDataProvider, }) { this.chainId = chainId; this.provider = provider; this.multicall2Provider = multicall2Provider !== null && multicall2Provider !== void 0 ? multicall2Provider : new UniswapMulticallProvider(chainId, provider, 375000); this.v3PoolProvider = v3PoolProvider !== null && v3PoolProvider !== void 0 ? v3PoolProvider : new CachingV3PoolProvider(this.chainId, new V3PoolProvider(ID_TO_CHAIN_ID(chainId), this.multicall2Provider), new NodeJSCache(new NodeCache({ stdTTL: 360, useClones: false }))); if (v3QuoteProvider) { this.v3QuoteProvider = v3QuoteProvider; } else { switch (chainId) { case ChainId.OPTIMISM: case ChainId.OPTIMISTIC_KOVAN: this.v3QuoteProvider = new V3QuoteProvider(chainId, provider, this.multicall2Provider, { retries: 2, minTimeout: 100, maxTimeout: 1000, }, { multicallChunk: 110, gasLimitPerCall: 1200000, quoteMinSuccessRate: 0.1, }, { gasLimitOverride: 3000000, multicallChunk: 45, }, { gasLimitOverride: 3000000, multicallChunk: 45, }, { baseBlockOffset: -10, rollback: { enabled: true, attemptsBeforeRollback: 1, rollbackBlockOffset: -10, }, }); break; case ChainId.ARBITRUM_ONE: case ChainId.ARBITRUM_RINKEBY: this.v3QuoteProvider = new V3QuoteProvider(chainId, provider, this.multicall2Provider, { retries: 2, minTimeout: 100, maxTimeout: 1000, }, { multicallChunk: 10, gasLimitPerCall: 12000000, quoteMinSuccessRate: 0.1, }, { gasLimitOverride: 30000000, multicallChunk: 6, }, { gasLimitOverride: 30000000, multicallChunk: 6, }); break; default: this.v3QuoteProvider = new V3QuoteProvider(chainId, provider, this.multicall2Provider, { retries: 2, minTimeout: 100, maxTimeout: 1000, }, { multicallChunk: 210, gasLimitPerCall: 705000, quoteMinSuccessRate: 0.15, }, { gasLimitOverride: 2000000, multicallChunk: 70, }); break; } } this.v2PoolProvider = v2PoolProvider !== null && v2PoolProvider !== void 0 ? v2PoolProvider : new V2PoolProvider(chainId, this.multicall2Provider); this.v2QuoteProvider = v2QuoteProvider !== null && v2QuoteProvider !== void 0 ? v2QuoteProvider : new V2QuoteProvider(); this.blockedTokenListProvider = blockedTokenListProvider !== null && blockedTokenListProvider !== void 0 ? blockedTokenListProvider : new CachingTokenListProvider(chainId, UNSUPPORTED_TOKENS, new NodeJSCache(new NodeCache({ stdTTL: 3600, useClones: false }))); this.tokenProvider = tokenProvider !== null && tokenProvider !== void 0 ? tokenProvider : new CachingTokenProviderWithFallback(chainId, new NodeJSCache(new NodeCache({ stdTTL: 3600, useClones: false })), new CachingTokenListProvider(chainId, DEFAULT_TOKEN_LIST, new NodeJSCache(new NodeCache({ stdTTL: 3600, useClones: false }))), new TokenProvider(chainId, this.multicall2Provider)); const chainName = ID_TO_NETWORK_NAME(chainId); // ipfs urls in the following format: `https://cloudflare-ipfs.com/ipns/api.uniswap.org/v1/pools/${protocol}/${chainName}.json`; if (v2SubgraphProvider) { this.v2SubgraphProvider = v2SubgraphProvider; } else { this.v2SubgraphProvider = new V2SubgraphProviderWithFallBacks([ new CachingV2SubgraphProvider(chainId, new URISubgraphProvider(chainId, `https://cloudflare-ipfs.com/ipns/api.uniswap.org/v1/pools/v2/${chainName}.json`, undefined, 0), new NodeJSCache(new NodeCache({ stdTTL: 300, useClones: false }))), new StaticV2SubgraphProvider(chainId), ]); } if (v3SubgraphProvider) { this.v3SubgraphProvider = v3SubgraphProvider; } else { this.v3SubgraphProvider = new V3SubgraphProviderWithFallBacks([ new CachingV3SubgraphProvider(chainId, new URISubgraphProvider(chainId, `https://cloudflare-ipfs.com/ipns/api.uniswap.org/v1/pools/v3/${chainName}.json`, undefined, 0), new NodeJSCache(new NodeCache({ stdTTL: 300, useClones: false }))), new StaticV3SubgraphProvider(chainId, this.v3PoolProvider), ]); } this.gasPriceProvider = gasPriceProvider !== null && gasPriceProvider !== void 0 ? gasPriceProvider : new CachingGasStationProvider(chainId, this.provider instanceof JsonRpcProvider ? new OnChainGasPriceProvider(chainId, new EIP1559GasPriceProvider(this.provider), new LegacyGasPriceProvider(this.provider)) : new ETHGasStationInfoProvider(ETH_GAS_STATION_API_URL), new NodeJSCache(new NodeCache({ stdTTL: 15, useClones: false }))); this.v3GasModelFactory = v3GasModelFactory !== null && v3GasModelFactory !== void 0 ? v3GasModelFactory : new V3HeuristicGasModelFactory(); this.v2GasModelFactory = v2GasModelFactory !== null && v2GasModelFactory !== void 0 ? v2GasModelFactory : new V2HeuristicGasModelFactory(); this.swapRouterProvider = swapRouterProvider !== null && swapRouterProvider !== void 0 ? swapRouterProvider : new SwapRouterProvider(this.multicall2Provider); if (chainId == ChainId.OPTIMISM || chainId == ChainId.OPTIMISTIC_KOVAN) { this.l2GasDataProvider = optimismGasDataProvider !== null && optimismGasDataProvider !== void 0 ? optimismGasDataProvider : new OptimismGasDataProvider(chainId, this.multicall2Provider); } if (chainId == ChainId.ARBITRUM_ONE || chainId == ChainId.ARBITRUM_RINKEBY) { this.l2GasDataProvider = arbitrumGasDataProvider !== null && arbitrumGasDataProvider !== void 0 ? arbitrumGasDataProvider : new ArbitrumGasDataProvider(chainId, this.provider); } if (tokenValidatorProvider) { this.tokenValidatorProvider = tokenValidatorProvider; } else if (this.chainId == ChainId.MAINNET) { this.tokenValidatorProvider = new TokenValidatorProvider(this.chainId, this.multicall2Provider, new NodeJSCache(new NodeCache({ stdTTL: 30000, useClones: false }))); } } async routeToRatio(token0Balance, token1Balance, position, swapAndAddConfig, swapAndAddOptions, routingConfig = DEFAULT_ROUTING_CONFIG_BY_CHAIN(this.chainId)) { if (token1Balance.currency.wrapped.sortsBefore(token0Balance.currency.wrapped)) { [token0Balance, token1Balance] = [token1Balance, token0Balance]; } let preSwapOptimalRatio = this.calculateOptimalRatio(position, position.pool.sqrtRatioX96, true); // set up parameters according to which token will be swapped let zeroForOne; if (position.pool.tickCurrent > position.tickUpper) { zeroForOne = true; } else if (position.pool.tickCurrent < position.tickLower) { zeroForOne = false; } else { zeroForOne = new Fraction(token0Balance.quotient, token1Balance.quotient).greaterThan(preSwapOptimalRatio); if (!zeroForOne) preSwapOptimalRatio = preSwapOptimalRatio.invert(); } const [inputBalance, outputBalance] = zeroForOne ? [token0Balance, token1Balance] : [token1Balance, token0Balance]; let optimalRatio = preSwapOptimalRatio; let postSwapTargetPool = position.pool; let exchangeRate = zeroForOne ? position.pool.token0Price : position.pool.token1Price; let swap = null; let ratioAchieved = false; let n = 0; // iterate until we find a swap with a sufficient ratio or return null while (!ratioAchieved) { n++; if (n > swapAndAddConfig.maxIterations) { log.info('max iterations exceeded'); return { status: SwapToRatioStatus.NO_ROUTE_FOUND, error: 'max iterations exceeded', }; } const amountToSwap = calculateRatioAmountIn(optimalRatio, exchangeRate, inputBalance, outputBalance); if (amountToSwap.equalTo(0)) { log.info(`no swap needed: amountToSwap = 0`); return { status: SwapToRatioStatus.NO_SWAP_NEEDED, }; } swap = await this.route(amountToSwap, outputBalance.currency, TradeType.EXACT_INPUT, undefined, { ...DEFAULT_ROUTING_CONFIG_BY_CHAIN(this.chainId), ...routingConfig, protocols: [Protocol.V3, Protocol.V2], }); if (!swap) { log.info('no route found from this.route()'); return { status: SwapToRatioStatus.NO_ROUTE_FOUND, error: 'no route found', }; } const inputBalanceUpdated = inputBalance.subtract(swap.trade.inputAmount); const outputBalanceUpdated = outputBalance.add(swap.trade.outputAmount); const newRatio = inputBalanceUpdated.divide(outputBalanceUpdated); let targetPoolPriceUpdate; swap.route.forEach((route) => { if (route.protocol == Protocol.V3) { const v3Route = route; v3Route.route.pools.forEach((pool, i) => { if (pool.token0.equals(position.pool.token0) && pool.token1.equals(position.pool.token1) && pool.fee == position.pool.fee) { targetPoolPriceUpdate = JSBI.BigInt(v3Route.sqrtPriceX96AfterList[i].toString()); optimalRatio = this.calculateOptimalRatio(position, JSBI.BigInt(targetPoolPriceUpdate.toString()), zeroForOne); } }); } }); if (!targetPoolPriceUpdate) { optimalRatio = preSwapOptimalRatio; } ratioAchieved = newRatio.equalTo(optimalRatio) || this.absoluteValue(newRatio.asFraction.divide(optimalRatio).subtract(1)).lessThan(swapAndAddConfig.ratioErrorTolerance); if (ratioAchieved && targetPoolPriceUpdate) { postSwapTargetPool = new Pool(position.pool.token0, position.pool.token1, position.pool.fee, targetPoolPriceUpdate, position.pool.liquidity, TickMath.getTickAtSqrtRatio(targetPoolPriceUpdate), position.pool.tickDataProvider); } exchangeRate = swap.trade.outputAmount.divide(swap.trade.inputAmount); log.info({ exchangeRate: exchangeRate.asFraction.toFixed(18), optimalRatio: optimalRatio.asFraction.toFixed(18), newRatio: newRatio.asFraction.toFixed(18), inputBalanceUpdated: inputBalanceUpdated.asFraction.toFixed(18), outputBalanceUpdated: outputBalanceUpdated.asFraction.toFixed(18), ratioErrorTolerance: swapAndAddConfig.ratioErrorTolerance.toFixed(18), iterationN: n.toString(), }, 'QuoteToRatio Iteration Parameters'); if (exchangeRate.equalTo(0)) { log.info('exchangeRate to 0'); return { status: SwapToRatioStatus.NO_ROUTE_FOUND, error: 'insufficient liquidity to swap to optimal ratio', }; } } if (!swap) { return { status: SwapToRatioStatus.NO_ROUTE_FOUND, error: 'no route found', }; } let methodParameters; if (swapAndAddOptions) { methodParameters = await this.buildSwapAndAddMethodParameters(swap.trade, swapAndAddOptions, { initialBalanceTokenIn: inputBalance, initialBalanceTokenOut: outputBalance, preLiquidityPosition: position, }); } return { status: SwapToRatioStatus.SUCCESS, result: { ...swap, methodParameters, optimalRatio, postSwapTargetPool }, }; } /** * @inheritdoc IRouter */ async route(amount, quoteCurrency, tradeType, swapConfig, partialRoutingConfig = {}) { var _a; metric.putMetric(`QuoteRequestedForChain${this.chainId}`, 1, MetricLoggerUnit.Count); // Get a block number to specify in all our calls. Ensures data we fetch from chain is // from the same block. const blockNumber = (_a = partialRoutingConfig.blockNumber) !== null && _a !== void 0 ? _a : this.getBlockNumberPromise(); const routingConfig = _.merge({}, DEFAULT_ROUTING_CONFIG_BY_CHAIN(this.chainId), partialRoutingConfig, { blockNumber }); const { protocols } = routingConfig; const currencyIn = tradeType == TradeType.EXACT_INPUT ? amount.currency : quoteCurrency; const currencyOut = tradeType == TradeType.EXACT_INPUT ? quoteCurrency : amount.currency; const tokenIn = currencyIn.wrapped; const tokenOut = currencyOut.wrapped; // Generate our distribution of amounts, i.e. fractions of the input amount. // We will get quotes for fractions of the input amount for different routes, then // combine to generate split routes. const [percents, amounts] = this.getAmountDistribution(amount, routingConfig); // Get an estimate of the gas price to use when estimating gas cost of different routes. const beforeGas = Date.now(); const { gasPriceWei } = await this.gasPriceProvider.getGasPrice(); metric.putMetric('GasPriceLoad', Date.now() - beforeGas, MetricLoggerUnit.Milliseconds); const quoteToken = quoteCurrency.wrapped; const quotePromises = []; const protocolsSet = new Set(protocols !== null && protocols !== void 0 ? protocols : []); const gasModel = await this.v3GasModelFactory.buildGasModel(this.chainId, gasPriceWei, this.v3PoolProvider, quoteToken, this.l2GasDataProvider); if ((protocolsSet.size == 0 || (protocolsSet.has(Protocol.V2) && protocolsSet.has(Protocol.V3))) && V2_SUPPORTED.includes(this.chainId)) { log.info({ protocols, tradeType }, 'Routing across all protocols'); quotePromises.push(this.getV3Quotes(tokenIn, tokenOut, amounts, percents, quoteToken, gasModel, tradeType, routingConfig)); quotePromises.push(this.getV2Quotes(tokenIn, tokenOut, amounts, percents, quoteToken, gasPriceWei, tradeType, routingConfig)); } else { if (protocolsSet.has(Protocol.V3) || (protocolsSet.size == 0 && !V2_SUPPORTED.includes(this.chainId))) { log.info({ protocols, swapType: tradeType }, 'Routing across V3'); quotePromises.push(this.getV3Quotes(tokenIn, tokenOut, amounts, percents, quoteToken, gasModel, tradeType, routingConfig)); } if (protocolsSet.has(Protocol.V2)) { log.info({ protocols, swapType: tradeType }, 'Routing across V2'); quotePromises.push(this.getV2Quotes(tokenIn, tokenOut, amounts, percents, quoteToken, gasPriceWei, tradeType, routingConfig)); } } const routesWithValidQuotesByProtocol = await Promise.all(quotePromises); let allRoutesWithValidQuotes = []; let allCandidatePools = []; for (const { routesWithValidQuotes, candidatePools, } of routesWithValidQuotesByProtocol) { allRoutesWithValidQuotes = [ ...allRoutesWithValidQuotes, ...routesWithValidQuotes, ]; allCandidatePools = [...allCandidatePools, candidatePools]; } if (allRoutesWithValidQuotes.length == 0) { log.info({ allRoutesWithValidQuotes }, 'Received no valid quotes'); return null; } // Given all the quotes for all the amounts for all the routes, find the best combination. const beforeBestSwap = Date.now(); const swapRouteRaw = await getBestSwapRoute(amount, percents, allRoutesWithValidQuotes, tradeType, this.chainId, routingConfig, gasModel); if (!swapRouteRaw) { return null; } const { quote, quoteGasAdjusted, estimatedGasUsed, routes: routeAmounts, estimatedGasUsedQuoteToken, estimatedGasUsedUSD, } = swapRouteRaw; // Build Trade object that represents the optimal swap. const trade = buildTrade(currencyIn, currencyOut, tradeType, routeAmounts); let methodParameters; // If user provided recipient, deadline etc. we also generate the calldata required to execute // the swap and return it too. if (swapConfig) { methodParameters = buildSwapMethodParameters(trade, swapConfig); } metric.putMetric('FindBestSwapRoute', Date.now() - beforeBestSwap, MetricLoggerUnit.Milliseconds); metric.putMetric(`QuoteFoundForChain${this.chainId}`, 1, MetricLoggerUnit.Count); this.emitPoolSelectionMetrics(swapRouteRaw, allCandidatePools); return { quote, quoteGasAdjusted, estimatedGasUsed, estimatedGasUsedQuoteToken, estimatedGasUsedUSD, gasPriceWei, route: routeAmounts, trade, methodParameters, blockNumber: BigNumber.from(await blockNumber), }; } async applyTokenValidatorToPools(pools, isInvalidFn) { if (!this.tokenValidatorProvider) { return pools; } log.info(`Running token validator on ${pools.length} pools`); const tokens = _.flatMap(pools, (pool) => [pool.token0, pool.token1]); const tokenValidationResults = await this.tokenValidatorProvider.validateTokens(tokens); const poolsFiltered = _.filter(pools, (pool) => { const token0Validation = tokenValidationResults.getValidationByToken(pool.token0); const token1Validation = tokenValidationResults.getValidationByToken(pool.token1); const token0Invalid = isInvalidFn(pool.token0, token0Validation); const token1Invalid = isInvalidFn(pool.token1, token1Validation); if (token0Invalid || token1Invalid) { log.info(`Dropping pool ${poolToString(pool)} because token is invalid. ${pool.token0.symbol}: ${token0Validation}, ${pool.token1.symbol}: ${token1Validation}`); } return !token0Invalid && !token1Invalid; }); return poolsFiltered; } async getV3Quotes(tokenIn, tokenOut, amounts, percents, quoteToken, gasModel, swapType, routingConfig) { log.info('Starting to get V3 quotes'); // Fetch all the pools that we will consider routing via. There are thousands // of pools, so we filter them to a set of candidate pools that we expect will // result in good prices. const { poolAccessor, candidatePools } = await getV3CandidatePools({ tokenIn, tokenOut, tokenProvider: this.tokenProvider, blockedTokenListProvider: this.blockedTokenListProvider, poolProvider: this.v3PoolProvider, routeType: swapType, subgraphProvider: this.v3SubgraphProvider, routingConfig, chainId: this.chainId, }); const poolsRaw = poolAccessor.getAllPools(); // Drop any pools that contain fee on transfer tokens (not supported by v3) or have issues with being transferred. const pools = await this.applyTokenValidatorToPools(poolsRaw, (token, tokenValidation) => { // If there is no available validation result we assume the token is fine. if (!tokenValidation) { return false; } // Only filters out *intermediate* pools that involve tokens that we detect // cant be transferred. This prevents us trying to route through tokens that may // not be transferrable, but allows users to still swap those tokens if they // specify. // if (tokenValidation == TokenValidationResult.STF && (token.equals(tokenIn) || token.equals(tokenOut))) { return false; } return (tokenValidation == TokenValidationResult.FOT || tokenValidation == TokenValidationResult.STF); }); // Given all our candidate pools, compute all the possible ways to route from tokenIn to tokenOut. const { maxSwapsPerPath } = routingConfig; const routes = computeAllV3Routes(tokenIn, tokenOut, pools, maxSwapsPerPath); if (routes.length == 0) { return { routesWithValidQuotes: [], candidatePools }; } // For all our routes, and all the fractional amounts, fetch quotes on-chain. const quoteFn = swapType == TradeType.EXACT_INPUT ? this.v3QuoteProvider.getQuotesManyExactIn.bind(this.v3QuoteProvider) : this.v3QuoteProvider.getQuotesManyExactOut.bind(this.v3QuoteProvider); const beforeQuotes = Date.now(); log.info(`Getting quotes for V3 for ${routes.length} routes with ${amounts.length} amounts per route.`); const { routesWithQuotes } = await quoteFn(amounts, routes, { blockNumber: routingConfig.blockNumber, }); metric.putMetric('V3QuotesLoad', Date.now() - beforeQuotes, MetricLoggerUnit.Milliseconds); metric.putMetric('V3QuotesFetched', _(routesWithQuotes) .map(([, quotes]) => quotes.length) .sum(), MetricLoggerUnit.Count); const routesWithValidQuotes = []; for (const routeWithQuote of routesWithQuotes) { const [route, quotes] = routeWithQuote; for (let i = 0; i < quotes.length; i++) { const percent = percents[i]; const amountQuote = quotes[i]; const { quote, amount, sqrtPriceX96AfterList, initializedTicksCrossedList, gasEstimate, } = amountQuote; if (!quote || !sqrtPriceX96AfterList || !initializedTicksCrossedList || !gasEstimate) { log.debug({ route: routeToString(route), amountQuote, }, 'Dropping a null V3 quote for route.'); continue; } const routeWithValidQuote = new V3RouteWithValidQuote({ route, rawQuote: quote, amount, percent, sqrtPriceX96AfterList, initializedTicksCrossedList, quoterGasEstimate: gasEstimate, gasModel, quoteToken, tradeType: swapType, v3PoolProvider: this.v3PoolProvider, }); routesWithValidQuotes.push(routeWithValidQuote); } } return { routesWithValidQuotes, candidatePools }; } async getV2Quotes(tokenIn, tokenOut, amounts, percents, quoteToken, gasPriceWei, swapType, routingConfig) { log.info('Starting to get V2 quotes'); // Fetch all the pools that we will consider routing via. There are thousands // of pools, so we filter them to a set of candidate pools that we expect will // result in good prices. const { poolAccessor, candidatePools } = await getV2CandidatePools({ tokenIn, tokenOut, tokenProvider: this.tokenProvider, blockedTokenListProvider: this.blockedTokenListProvider, poolProvider: this.v2PoolProvider, routeType: swapType, subgraphProvider: this.v2SubgraphProvider, routingConfig, chainId: this.chainId, }); const poolsRaw = poolAccessor.getAllPools(); // Drop any pools that contain tokens that can not be transferred according to the token validator. const pools = await this.applyTokenValidatorToPools(poolsRaw, (token, tokenValidation) => { // If there is no available validation result we assume the token is fine. if (!tokenValidation) { return false; } // Only filters out *intermediate* pools that involve tokens that we detect // cant be transferred. This prevents us trying to route through tokens that may // not be transferrable, but allows users to still swap those tokens if they // specify. if (tokenValidation == TokenValidationResult.STF && (token.equals(tokenIn) || token.equals(tokenOut))) { return false; } return tokenValidation == TokenValidationResult.STF; }); // Given all our candidate pools, compute all the possible ways to route from tokenIn to tokenOut. const { maxSwapsPerPath } = routingConfig; const routes = computeAllV2Routes(tokenIn, tokenOut, pools, maxSwapsPerPath); if (routes.length == 0) { return { routesWithValidQuotes: [], candidatePools }; } // For all our routes, and all the fractional amounts, fetch quotes on-chain. const quoteFn = swapType == TradeType.EXACT_INPUT ? this.v2QuoteProvider.getQuotesManyExactIn.bind(this.v2QuoteProvider) : this.v2QuoteProvider.getQuotesManyExactOut.bind(this.v2QuoteProvider); const beforeQuotes = Date.now(); log.info(`Getting quotes for V2 for ${routes.length} routes with ${amounts.length} amounts per route.`); const { routesWithQuotes } = await quoteFn(amounts, routes); const gasModel = await this.v2GasModelFactory.buildGasModel(this.chainId, gasPriceWei, this.v2PoolProvider, quoteToken); metric.putMetric('V2QuotesLoad', Date.now() - beforeQuotes, MetricLoggerUnit.Milliseconds); metric.putMetric('V2QuotesFetched', _(routesWithQuotes) .map(([, quotes]) => quotes.length) .sum(), MetricLoggerUnit.Count); const routesWithValidQuotes = []; for (const routeWithQuote of routesWithQuotes) { const [route, quotes] = routeWithQuote; for (let i = 0; i < quotes.length; i++) { const percent = percents[i]; const amountQuote = quotes[i]; const { quote, amount } = amountQuote; if (!quote) { log.debug({ route: routeToString(route), amountQuote, }, 'Dropping a null V2 quote for route.'); continue; } const routeWithValidQuote = new V2RouteWithValidQuote({ route, rawQuote: quote, amount, percent, gasModel, quoteToken, tradeType: swapType, v2PoolProvider: this.v2PoolProvider, }); routesWithValidQuotes.push(routeWithValidQuote); } } return { routesWithValidQuotes, candidatePools }; } // Note multiplications here can result in a loss of precision in the amounts (e.g. taking 50% of 101) // This is reconcilled at the end of the algorithm by adding any lost precision to one of // the splits in the route. getAmountDistribution(amount, routingConfig) { const { distributionPercent } = routingConfig; const percents = []; const amounts = []; for (let i = 1; i <= 100 / distributionPercent; i++) { percents.push(i * distributionPercent); amounts.push(amount.multiply(new Fraction(i * distributionPercent, 100))); } return [percents, amounts]; } async buildSwapAndAddMethodParameters(trade, swapAndAddOptions, swapAndAddParameters) { const { swapOptions: { recipient, slippageTolerance, deadline, inputTokenPermit }, addLiquidityOptions: addLiquidityConfig, } = swapAndAddOptions; const preLiquidityPosition = swapAndAddParameters.preLiquidityPosition; const finalBalanceTokenIn = swapAndAddParameters.initialBalanceTokenIn.subtract(trade.inputAmount); const finalBalanceTokenOut = swapAndAddParameters.initialBalanceTokenOut.add(trade.outputAmount); const approvalTypes = await this.swapRouterProvider.getApprovalType(finalBalanceTokenIn, finalBalanceTokenOut); const zeroForOne = finalBalanceTokenIn.currency.wrapped.sortsBefore(finalBalanceTokenOut.currency.wrapped); return SwapRouter.swapAndAddCallParameters(trade, { recipient, slippageTolerance, deadlineOrPreviousBlockhash: deadline, inputTokenPermit, }, Position.fromAmounts({ pool: preLiquidityPosition.pool, tickLower: preLiquidityPosition.tickLower, tickUpper: preLiquidityPosition.tickUpper, amount0: zeroForOne ? finalBalanceTokenIn.quotient.toString() : finalBalanceTokenOut.quotient.toString(), amount1: zeroForOne ? finalBalanceTokenOut.quotient.toString() : finalBalanceTokenIn.quotient.toString(), useFullPrecision: false, }), addLiquidityConfig, approvalTypes.approvalTokenIn, approvalTypes.approvalTokenOut); } emitPoolSelectionMetrics(swapRouteRaw, allPoolsBySelection) { const poolAddressesUsed = new Set(); const { routes: routeAmounts } = swapRouteRaw; _(routeAmounts) .flatMap((routeAmount) => { const { poolAddresses } = routeAmount; return poolAddresses; }) .forEach((address) => { poolAddressesUsed.add(address.toLowerCase()); }); for (const poolsBySelection of allPoolsBySelection) { const { protocol } = poolsBySelection; _.forIn(poolsBySelection.selections, (pools, topNSelection) => { const topNUsed = _.findLastIndex(pools, (pool) => poolAddressesUsed.has(pool.id.toLowerCase())) + 1; metric.putMetric(_.capitalize(`${protocol}${topNSelection}`), topNUsed, MetricLoggerUnit.Count); }); } let hasV3Route = false; let hasV2Route = false; for (const routeAmount of routeAmounts) { if (routeAmount.protocol == Protocol.V3) { hasV3Route = true; } if (routeAmount.protocol == Protocol.V2) { hasV2Route = true; } } if (hasV3Route && hasV2Route) { metric.putMetric(`V3AndV2SplitRoute`, 1, MetricLoggerUnit.Count); metric.putMetric(`V3AndV2SplitRouteForChain${this.chainId}`, 1, MetricLoggerUnit.Count); } else if (hasV3Route) { if (routeAmounts.length > 1) { metric.putMetric(`V3SplitRoute`, 1, MetricLoggerUnit.Count); metric.putMetric(`V3SplitRouteForChain${this.chainId}`, 1, MetricLoggerUnit.Count); } else { metric.putMetric(`V3Route`, 1, MetricLoggerUnit.Count); metric.putMetric(`V3RouteForChain${this.chainId}`, 1, MetricLoggerUnit.Count); } } else if (hasV2Route) { if (routeAmounts.length > 1) { metric.putMetric(`V2SplitRoute`, 1, MetricLoggerUnit.Count); metric.putMetric(`V2SplitRouteForChain${this.chainId}`, 1, MetricLoggerUnit.Count); } else { metric.putMetric(`V2Route`, 1, MetricLoggerUnit.Count); metric.putMetric(`V2RouteForChain${this.chainId}`, 1, MetricLoggerUnit.Count); } } } calculateOptimalRatio(position, sqrtRatioX96, zeroForOne) { const upperSqrtRatioX96 = TickMath.getSqrtRatioAtTick(position.tickUpper); const lowerSqrtRatioX96 = TickMath.getSqrtRatioAtTick(position.tickLower); // returns Fraction(0, 1) for any out of range position regardless of zeroForOne. Implication: function // cannot be used to determine the trading direction of out of range positions. if (JSBI.greaterThan(sqrtRatioX96, upperSqrtRatioX96) || JSBI.lessThan(sqrtRatioX96, lowerSqrtRatioX96)) { return new Fraction(0, 1); } const precision = JSBI.BigInt('1' + '0'.repeat(18)); let optimalRatio = new Fraction(SqrtPriceMath.getAmount0Delta(sqrtRatioX96, upperSqrtRatioX96, precision, true), SqrtPriceMath.getAmount1Delta(sqrtRatioX96, lowerSqrtRatioX96, precision, true)); if (!zeroForOne) optimalRatio = optimalRatio.invert(); return optimalRatio; } absoluteValue(fraction) { const numeratorAbs = JSBI.lessThan(fraction.numerator, JSBI.BigInt(0)) ? JSBI.unaryMinus(fraction.numerator) : fraction.numerator; const denominatorAbs = JSBI.lessThan(fraction.denominator, JSBI.BigInt(0)) ? JSBI.unaryMinus(fraction.denominator) : fraction.denominator; return new Fraction(numeratorAbs, denominatorAbs); } getBlockNumberPromise() { return retry(async (_b, attempt) => { if (attempt > 1) { log.info(`Get block number attempt ${attempt}`); } return this.provider.getBlockNumber(); }, { retries: 2, minTimeout: 100, maxTimeout: 1000, }); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWxwaGEtcm91dGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3JvdXRlcnMvYWxwaGEtcm91dGVyL2FscGhhLXJvdXRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDckQsT0FBTyxFQUFnQixlQUFlLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN6RSxPQUFPLGtCQUFrQixNQUFNLDZCQUE2QixDQUFDO0FBQzdELE9BQU8sRUFBRSxRQUFRLEVBQUUsVUFBVSxFQUFTLE1BQU0scUJBQXFCLENBQUM7QUFDbEUsT0FBTyxFQUFZLFFBQVEsRUFBUyxTQUFTLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUd6RSxPQUFPLEVBRUwsSUFBSSxFQUNKLFFBQVEsRUFDUixhQUFhLEVBQ2IsUUFBUSxHQUNULE1BQU0saUJBQWlCLENBQUM7QUFDekIsT0FBTyxLQUFLLE1BQU0sYUFBYSxDQUFDO0FBQ2hDLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUN4QixPQUFPLENBQUMsTUFBTSxRQUFRLENBQUM7QUFDdkIsT0FBTyxTQUFTLE1BQU0sWUFBWSxDQUFDO0FBQ25DLE9BQU8sRUFBRSwwQkFBMEIsRUFBRSxNQUFNLEdBQUcsQ0FBQztBQUMvQyxPQUFPLEVBQ0wseUJBQXlCLEVBQ3pCLGdDQUFnQyxFQUNoQyx5QkFBeUIsRUFDekIscUJBQXFCLEVBQ3JCLHlCQUF5QixFQUN6Qix1QkFBdUIsRUFDdkIseUJBQXlCLEVBSXpCLHNCQUFzQixFQUN0QixXQUFXLEVBQ1gsdUJBQXVCLEVBQ3ZCLHdCQUF3QixFQUN4Qix3QkFBd0IsRUFDeEIsa0JBQWtCLEVBQ2xCLHdCQUF3QixFQUN4QixtQkFBbUIsRUFDbkIsZUFBZSxFQUNmLCtCQUErQixFQUMvQiwrQkFBK0IsR0FDaEMsTUFBTSxpQkFBaUIsQ0FBQztBQUN6QixPQUFPLEVBQ0wsd0JBQXdCLEdBRXpC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