UNPKG

@maxosllc/smart-order-router

Version:
989 lines (988 loc) 148 kB
import { ADDRESS_ZERO, Protocol } from '@uniswap/router-sdk'; import { ChainId } from '../../../../src/util/chains'; import { TradeType } from '@uniswap/sdk-core'; import _ from 'lodash'; import { isNativeCurrency } from '@uniswap/universal-router-sdk'; import { DAI_OPTIMISM_SEPOLIA, USDC_ARBITRUM_SEPOLIA, USDC_OPTIMISM_SEPOLIA, USDT_OPTIMISM_SEPOLIA, WBTC_OPTIMISM_SEPOLIA, } from '../../../providers'; import { CELO, CELO_ALFAJORES, CEUR_CELO, CEUR_CELO_ALFAJORES, CUSD_CELO, CUSD_CELO_ALFAJORES, DAI_ARBITRUM, DAI_AVAX, DAI_BNB, DAI_MAINNET, DAI_MOONBEAM, DAI_OPTIMISM, DAI_OPTIMISM_GOERLI, DAI_POLYGON_MUMBAI, DAI_SEPOLIA, DAI_UNICHAIN, FEI_MAINNET, USDB_BLAST, USDC_ARBITRUM, USDC_ARBITRUM_GOERLI, USDC_AVAX, USDC_BASE, USDC_BASE_SEPOLIA, USDC_BNB, USDC_ETHEREUM_GNOSIS, USDC_MAINNET, USDC_MOONBEAM, USDC_OPTIMISM, USDC_OPTIMISM_GOERLI, USDC_POLYGON, USDC_SEPOLIA, USDC_SONEIUM, USDC_UNICHAIN, USDT_ARBITRUM, USDT_BNB, USDT_MAINNET, USDT_MONAD_TESTNET, USDT_OPTIMISM, USDT_OPTIMISM_GOERLI, WBTC_ARBITRUM, WBTC_GNOSIS, WBTC_MAINNET, WBTC_MOONBEAM, WBTC_OPTIMISM, WBTC_OPTIMISM_GOERLI, WGLMR_MOONBEAM, WMATIC_POLYGON, WMATIC_POLYGON_MUMBAI, WSTETH_MAINNET, WXDAI_GNOSIS, } from '../../../providers/token-provider'; import { getAddress, getAddressLowerCase, getApplicableV3FeeAmounts, getApplicableV4FeesTickspacingsHooks, HooksOptions, nativeOnChain, unparseFeeAmount, WRAPPED_NATIVE_CURRENCY, } from '../../../util'; import { parseFeeAmount } from '../../../util/amounts'; import { log } from '../../../util/log'; import { metric, MetricLoggerUnit } from '../../../util/metric'; const baseTokensByChain = { [ChainId.MAINNET]: [ USDC_MAINNET, USDT_MAINNET, WBTC_MAINNET, DAI_MAINNET, WRAPPED_NATIVE_CURRENCY[1], FEI_MAINNET, WSTETH_MAINNET, ], [ChainId.OPTIMISM]: [ DAI_OPTIMISM, USDC_OPTIMISM, USDT_OPTIMISM, WBTC_OPTIMISM, ], [ChainId.SEPOLIA]: [DAI_SEPOLIA, USDC_SEPOLIA], [ChainId.OPTIMISM_GOERLI]: [ DAI_OPTIMISM_GOERLI, USDC_OPTIMISM_GOERLI, USDT_OPTIMISM_GOERLI, WBTC_OPTIMISM_GOERLI, ], [ChainId.OPTIMISM_SEPOLIA]: [ DAI_OPTIMISM_SEPOLIA, USDC_OPTIMISM_SEPOLIA, USDT_OPTIMISM_SEPOLIA, WBTC_OPTIMISM_SEPOLIA, ], [ChainId.ARBITRUM_ONE]: [ DAI_ARBITRUM, USDC_ARBITRUM, WBTC_ARBITRUM, USDT_ARBITRUM, ], [ChainId.ARBITRUM_GOERLI]: [USDC_ARBITRUM_GOERLI], [ChainId.ARBITRUM_SEPOLIA]: [USDC_ARBITRUM_SEPOLIA], [ChainId.POLYGON]: [USDC_POLYGON, WMATIC_POLYGON], [ChainId.POLYGON_MUMBAI]: [DAI_POLYGON_MUMBAI, WMATIC_POLYGON_MUMBAI], [ChainId.CELO]: [CUSD_CELO, CEUR_CELO, CELO], [ChainId.CELO_ALFAJORES]: [ CUSD_CELO_ALFAJORES, CEUR_CELO_ALFAJORES, CELO_ALFAJORES, ], [ChainId.GNOSIS]: [WBTC_GNOSIS, WXDAI_GNOSIS, USDC_ETHEREUM_GNOSIS], [ChainId.MOONBEAM]: [ DAI_MOONBEAM, USDC_MOONBEAM, WBTC_MOONBEAM, WGLMR_MOONBEAM, ], [ChainId.BNB]: [DAI_BNB, USDC_BNB, USDT_BNB], [ChainId.AVALANCHE]: [DAI_AVAX, USDC_AVAX], [ChainId.BASE]: [USDC_BASE], [ChainId.BLAST]: [WRAPPED_NATIVE_CURRENCY[ChainId.BLAST], USDB_BLAST], [ChainId.ZORA]: [WRAPPED_NATIVE_CURRENCY[ChainId.ZORA]], [ChainId.ZKSYNC]: [WRAPPED_NATIVE_CURRENCY[ChainId.ZKSYNC]], [ChainId.WORLDCHAIN]: [WRAPPED_NATIVE_CURRENCY[ChainId.WORLDCHAIN]], [ChainId.UNICHAIN_SEPOLIA]: [ WRAPPED_NATIVE_CURRENCY[ChainId.UNICHAIN_SEPOLIA], ], [ChainId.MONAD_TESTNET]: [ WRAPPED_NATIVE_CURRENCY[ChainId.MONAD_TESTNET], USDT_MONAD_TESTNET, ], [ChainId.BASE_SEPOLIA]: [ WRAPPED_NATIVE_CURRENCY[ChainId.BASE_SEPOLIA], USDC_BASE_SEPOLIA, ], [ChainId.UNICHAIN]: [ WRAPPED_NATIVE_CURRENCY[ChainId.UNICHAIN], DAI_UNICHAIN, USDC_UNICHAIN, ], [ChainId.SONEIUM]: [USDC_SONEIUM, WRAPPED_NATIVE_CURRENCY[ChainId.SONEIUM]], }; class SubcategorySelectionPools { constructor(pools, poolsNeeded) { this.pools = pools; this.poolsNeeded = poolsNeeded; } hasEnoughPools() { return this.pools.length >= this.poolsNeeded; } } /** * Function that finds any missing pools that were not selected by the heuristic but that would * create a route with the topPool by TVL with either tokenIn or tokenOut across protocols. * * e.g. In V2CandidatePools we found that wstETH/DOG is the most liquid pool, * then in V3CandidatePools ETH/wstETH is *not* the most liquid pool, so it is not selected * This process will look for that pool in order to complete the route. * */ export async function getMixedCrossLiquidityCandidatePools({ tokenIn, tokenOut, blockNumber, v2SubgraphProvider, v3SubgraphProvider, v2Candidates, v3Candidates, }) { const v2Pools = (await v2SubgraphProvider.getPools(tokenIn, tokenOut, { blockNumber, })).sort((a, b) => b.reserve - a.reserve); const v3Pools = (await v3SubgraphProvider.getPools(tokenIn, tokenOut, { blockNumber, })).sort((a, b) => b.tvlUSD - a.tvlUSD); const tokenInAddress = tokenIn.address.toLowerCase(); const tokenOutAddress = tokenOut.address.toLowerCase(); const v2SelectedPools = findCrossProtocolMissingPools(tokenInAddress, tokenOutAddress, v2Pools, v2Candidates, v3Candidates); const v3SelectedPools = findCrossProtocolMissingPools(tokenInAddress, tokenOutAddress, v3Pools, v3Candidates, v2Candidates); const selectedV2Pools = [ v2SelectedPools.forTokenIn, v2SelectedPools.forTokenOut, ].filter((pool) => pool !== undefined); const selectedV3Pools = [ v3SelectedPools.forTokenIn, v3SelectedPools.forTokenOut, ].filter((pool) => pool !== undefined); return { v2Pools: selectedV2Pools, v3Pools: selectedV3Pools, }; } function findCrossProtocolMissingPools(tokenInAddress, tokenOutAddress, pools, candidatesInProtocolToSearch, candidatesInContextProtocol) { var _a; const selectedPools = {}; const previouslySelectedPools = new Set((_a = candidatesInProtocolToSearch === null || candidatesInProtocolToSearch === void 0 ? void 0 : candidatesInProtocolToSearch.subgraphPools.map((pool) => pool.id)) !== null && _a !== void 0 ? _a : []); const topPoolByTvlWithTokenOut = candidatesInContextProtocol === null || candidatesInContextProtocol === void 0 ? void 0 : candidatesInContextProtocol.candidatePools.selections.topByTVLUsingTokenOut[0]; const crossTokenAgainstTokenOut = (topPoolByTvlWithTokenOut === null || topPoolByTvlWithTokenOut === void 0 ? void 0 : topPoolByTvlWithTokenOut.token0.id.toLowerCase()) === tokenOutAddress ? topPoolByTvlWithTokenOut === null || topPoolByTvlWithTokenOut === void 0 ? void 0 : topPoolByTvlWithTokenOut.token1.id.toLowerCase() : topPoolByTvlWithTokenOut === null || topPoolByTvlWithTokenOut === void 0 ? void 0 : topPoolByTvlWithTokenOut.token0.id.toLowerCase(); const topPoolByTvlWithTokenIn = candidatesInContextProtocol === null || candidatesInContextProtocol === void 0 ? void 0 : candidatesInContextProtocol.candidatePools.selections.topByTVLUsingTokenIn[0]; const crossTokenAgainstTokenIn = (topPoolByTvlWithTokenIn === null || topPoolByTvlWithTokenIn === void 0 ? void 0 : topPoolByTvlWithTokenIn.token0.id.toLowerCase()) === tokenInAddress ? topPoolByTvlWithTokenIn === null || topPoolByTvlWithTokenIn === void 0 ? void 0 : topPoolByTvlWithTokenIn.token1.id.toLowerCase() : topPoolByTvlWithTokenIn === null || topPoolByTvlWithTokenIn === void 0 ? void 0 : topPoolByTvlWithTokenIn.token0.id.toLowerCase(); for (const pool of pools) { // If we already found both pools for tokenIn and tokenOut. break out of this for loop. if (selectedPools.forTokenIn !== undefined && selectedPools.forTokenOut !== undefined) { break; } // If the pool has already been selected. continue to the next pool. if (previouslySelectedPools.has(pool.id.toLowerCase())) { continue; } const poolToken0Address = pool.token0.id.toLowerCase(); const poolToken1Address = pool.token1.id.toLowerCase(); // If we haven't selected the pool for tokenIn, and we found a pool matching the tokenOut, and the intermediateToken, select this pool if (selectedPools.forTokenIn === undefined && ((poolToken0Address === tokenOutAddress && poolToken1Address === crossTokenAgainstTokenIn) || (poolToken1Address === tokenOutAddress && poolToken0Address === crossTokenAgainstTokenIn))) { selectedPools.forTokenIn = pool; } // If we haven't selected the pool for tokenOut, and we found a pool matching the tokenIn, and the intermediateToken, select this pool if (selectedPools.forTokenOut === undefined && ((poolToken0Address === tokenInAddress && poolToken1Address === crossTokenAgainstTokenOut) || (poolToken1Address === tokenInAddress && poolToken0Address === crossTokenAgainstTokenOut))) { selectedPools.forTokenOut = pool; } } return selectedPools; } // TODO: ROUTE-241 - refactor getV3CandidatePools against getV4CandidatePools export async function getV4CandidatePools({ currencyIn, currencyOut, routeType, routingConfig, subgraphProvider, tokenProvider, poolProvider, blockedTokenListProvider, chainId, v4PoolParams = getApplicableV4FeesTickspacingsHooks(chainId), }) { var _a, _b, _c, _d, _e; const { blockNumber, v4PoolSelection: { topN, topNDirectSwaps, topNTokenInOut, topNSecondHop, topNSecondHopForTokenAddress, tokensToAvoidOnSecondHops, topNWithEachBaseToken, topNWithBaseToken, }, } = routingConfig; const tokenInAddress = getAddressLowerCase(currencyIn); const tokenOutAddress = getAddressLowerCase(currencyOut); const beforeSubgraphPools = Date.now(); const allPools = await subgraphProvider.getPools(currencyIn, currencyOut, { blockNumber, }); log.info({ samplePools: allPools.slice(0, 3) }, 'Got all pools from V4 subgraph provider'); // Although this is less of an optimization than the V2 equivalent, // save some time copying objects by mutating the underlying pool directly. for (const pool of allPools) { pool.token0.id = pool.token0.id.toLowerCase(); pool.token1.id = pool.token1.id.toLowerCase(); } metric.putMetric('V4SubgraphPoolsLoad', Date.now() - beforeSubgraphPools, MetricLoggerUnit.Milliseconds); const beforePoolsFiltered = Date.now(); // Only consider pools where neither tokens are in the blocked token list. let filteredPools = allPools; if (blockedTokenListProvider) { filteredPools = []; for (const pool of allPools) { const token0InBlocklist = await blockedTokenListProvider.hasTokenByAddress(pool.token0.id); const token1InBlocklist = await blockedTokenListProvider.hasTokenByAddress(pool.token1.id); if (token0InBlocklist || token1InBlocklist) { continue; } filteredPools.push(pool); } } // Sort by tvlUSD in descending order const subgraphPoolsSorted = filteredPools.sort((a, b) => b.tvlUSD - a.tvlUSD); log.info(`After filtering blocked tokens went from ${allPools.length} to ${subgraphPoolsSorted.length}.`); const poolAddressesSoFar = new Set(); const addToAddressSet = (pools) => { _(pools) .map((pool) => pool.id) .forEach((poolAddress) => poolAddressesSoFar.add(poolAddress)); }; const baseTokens = (_a = baseTokensByChain[chainId]) !== null && _a !== void 0 ? _a : []; const topByBaseWithTokenIn = _(baseTokens) .flatMap((token) => { return _(subgraphPoolsSorted) .filter((subgraphPool) => { const tokenAddress = token.address.toLowerCase(); return ((subgraphPool.token0.id == tokenAddress && subgraphPool.token1.id == tokenInAddress) || (subgraphPool.token1.id == tokenAddress && subgraphPool.token0.id == tokenInAddress)); }) .filter((subgraphPool) => { // in case of hooks only, it means we want to filter out hookless pools if (routingConfig.hooksOptions === HooksOptions.HOOKS_ONLY) { return subgraphPool.hooks !== ADDRESS_ZERO; } // in case of no hooks, it means we want to filter out hook pools if (routingConfig.hooksOptions === HooksOptions.NO_HOOKS) { return subgraphPool.hooks === ADDRESS_ZERO; } // otherwise it's the default case, so we just return true return true; }) .sortBy((tokenListPool) => -tokenListPool.tvlUSD) .slice(0, topNWithEachBaseToken) .value(); }) .sortBy((tokenListPool) => -tokenListPool.tvlUSD) .slice(0, topNWithBaseToken) .value(); const topByBaseWithTokenOut = _(baseTokens) .flatMap((token) => { return _(subgraphPoolsSorted) .filter((subgraphPool) => { const tokenAddress = token.address.toLowerCase(); return ((subgraphPool.token0.id == tokenAddress && subgraphPool.token1.id == tokenOutAddress) || (subgraphPool.token1.id == tokenAddress && subgraphPool.token0.id == tokenOutAddress)); }) .filter((subgraphPool) => { // in case of hooks only, it means we want to filter out hookless pools if (routingConfig.hooksOptions === HooksOptions.HOOKS_ONLY) { return subgraphPool.hooks !== ADDRESS_ZERO; } // in case of no hooks, it means we want to filter out hook pools if (routingConfig.hooksOptions === HooksOptions.NO_HOOKS) { return subgraphPool.hooks === ADDRESS_ZERO; } // otherwise it's the default case, so we just return true return true; }) .sortBy((tokenListPool) => -tokenListPool.tvlUSD) .slice(0, topNWithEachBaseToken) .value(); }) .sortBy((tokenListPool) => -tokenListPool.tvlUSD) .slice(0, topNWithBaseToken) .value(); let top2DirectSwapPool = _(subgraphPoolsSorted) .filter((subgraphPool) => { return (!poolAddressesSoFar.has(subgraphPool.id) && ((subgraphPool.token0.id == tokenInAddress && subgraphPool.token1.id == tokenOutAddress) || (subgraphPool.token1.id == tokenInAddress && subgraphPool.token0.id == tokenOutAddress))); }) .filter((subgraphPool) => { // in case of hooks only, it means we want to filter out hookless pools if (routingConfig.hooksOptions === HooksOptions.HOOKS_ONLY) { return subgraphPool.hooks !== ADDRESS_ZERO; } // in case of no hooks, it means we want to filter out hook pools if (routingConfig.hooksOptions === HooksOptions.NO_HOOKS) { return subgraphPool.hooks === ADDRESS_ZERO; } // otherwise it's the default case, so we just return true return true; }) .slice(0, topNDirectSwaps) .value(); if (top2DirectSwapPool.length == 0 && topNDirectSwaps > 0 && routingConfig.hooksOptions !== HooksOptions.HOOKS_ONLY) { // If we requested direct swap pools but did not find any in the subgraph query. // Optimistically add them into the query regardless. Invalid pools ones will be dropped anyway // when we query the pool on-chain. Ensures that new pools for new pairs can be swapped on immediately. // Also we need to avoid adding hookless pools into the query, when upstream requested hooksOnly top2DirectSwapPool = _.map(v4PoolParams, (poolParams) => { const [fee, tickSpacing, hooks] = poolParams; const { currency0, currency1, poolId } = poolProvider.getPoolId(currencyIn, currencyOut, fee, tickSpacing, hooks); return { id: poolId, feeTier: fee.toString(), tickSpacing: tickSpacing.toString(), hooks: hooks, liquidity: '10000', token0: { id: getAddress(currency0), }, token1: { id: getAddress(currency1), }, tvlETH: 10000, tvlUSD: 10000, }; }); } addToAddressSet(top2DirectSwapPool); const wrappedNativeAddress = (_b = WRAPPED_NATIVE_CURRENCY[chainId]) === null || _b === void 0 ? void 0 : _b.address.toLowerCase(); // Main reason we need this is for gas estimates, only needed if token out is not native. // We don't check the seen address set because if we've already added pools for getting native quotes // theres no need to add more. let top2EthQuoteTokenPool = []; if ((((_c = WRAPPED_NATIVE_CURRENCY[chainId]) === null || _c === void 0 ? void 0 : _c.symbol) == ((_d = WRAPPED_NATIVE_CURRENCY[ChainId.MAINNET]) === null || _d === void 0 ? void 0 : _d.symbol) && currencyOut.symbol != 'WETH' && currencyOut.symbol != 'WETH9' && currencyOut.symbol != 'ETH') || (((_e = WRAPPED_NATIVE_CURRENCY[chainId]) === null || _e === void 0 ? void 0 : _e.symbol) == WMATIC_POLYGON.symbol && currencyOut.symbol != 'MATIC' && currencyOut.symbol != 'WMATIC')) { top2EthQuoteTokenPool = _(subgraphPoolsSorted) .filter((subgraphPool) => { if (routeType == TradeType.EXACT_INPUT) { return ((subgraphPool.token0.id == wrappedNativeAddress && subgraphPool.token1.id == tokenOutAddress) || (subgraphPool.token1.id == wrappedNativeAddress && subgraphPool.token0.id == tokenOutAddress)); } else { return ((subgraphPool.token0.id == wrappedNativeAddress && subgraphPool.token1.id == tokenInAddress) || (subgraphPool.token1.id == wrappedNativeAddress && subgraphPool.token0.id == tokenInAddress)); } }) .slice(0, 1) .value(); } addToAddressSet(top2EthQuoteTokenPool); const topByTVL = _(subgraphPoolsSorted) .filter((subgraphPool) => { return !poolAddressesSoFar.has(subgraphPool.id); }) .slice(0, topN) .value(); addToAddressSet(topByTVL); const topByTVLUsingTokenIn = _(subgraphPoolsSorted) .filter((subgraphPool) => { return (!poolAddressesSoFar.has(subgraphPool.id) && (subgraphPool.token0.id == tokenInAddress || subgraphPool.token1.id == tokenInAddress)); }) .filter((subgraphPool) => { // in case of hooks only, it means we want to filter out hookless pools if (routingConfig.hooksOptions === HooksOptions.HOOKS_ONLY) { return subgraphPool.hooks !== ADDRESS_ZERO; } // in case of no hooks, it means we want to filter out hook pools if (routingConfig.hooksOptions === HooksOptions.NO_HOOKS) { return subgraphPool.hooks === ADDRESS_ZERO; } // otherwise it's the default case, so we just return true return true; }) .slice(0, topNTokenInOut) .value(); addToAddressSet(topByTVLUsingTokenIn); const topByTVLUsingTokenOut = _(subgraphPoolsSorted) .filter((subgraphPool) => { return (!poolAddressesSoFar.has(subgraphPool.id) && (subgraphPool.token0.id == tokenOutAddress || subgraphPool.token1.id == tokenOutAddress)); }) .filter((subgraphPool) => { // in case of hooks only, it means we want to filter out hookless pools if (routingConfig.hooksOptions === HooksOptions.HOOKS_ONLY) { return subgraphPool.hooks !== ADDRESS_ZERO; } // in case of no hooks, it means we want to filter out hook pools if (routingConfig.hooksOptions === HooksOptions.NO_HOOKS) { return subgraphPool.hooks === ADDRESS_ZERO; } // otherwise it's the default case, so we just return true return true; }) .slice(0, topNTokenInOut) .value(); addToAddressSet(topByTVLUsingTokenOut); const topByTVLUsingTokenInSecondHops = _(topByTVLUsingTokenIn) .map((subgraphPool) => { return tokenInAddress == subgraphPool.token0.id ? subgraphPool.token1.id : subgraphPool.token0.id; }) .flatMap((secondHopId) => { var _a; return _(subgraphPoolsSorted) .filter((subgraphPool) => { return (!poolAddressesSoFar.has(subgraphPool.id) && !(tokensToAvoidOnSecondHops === null || tokensToAvoidOnSecondHops === void 0 ? void 0 : tokensToAvoidOnSecondHops.includes(secondHopId.toLowerCase())) && (subgraphPool.token0.id == secondHopId || subgraphPool.token1.id == secondHopId)); }) .filter((subgraphPool) => { // in case of hooks only, it means we want to filter out hookless pools if (routingConfig.hooksOptions === HooksOptions.HOOKS_ONLY) { return subgraphPool.hooks !== ADDRESS_ZERO; } // in case of no hooks, it means we want to filter out hook pools if (routingConfig.hooksOptions === HooksOptions.NO_HOOKS) { return subgraphPool.hooks === ADDRESS_ZERO; } // otherwise it's the default case, so we just return true return true; }) .slice(0, (_a = topNSecondHopForTokenAddress === null || topNSecondHopForTokenAddress === void 0 ? void 0 : topNSecondHopForTokenAddress.get(secondHopId)) !== null && _a !== void 0 ? _a : topNSecondHop) .value(); }) .uniqBy((pool) => pool.id) .value(); addToAddressSet(topByTVLUsingTokenInSecondHops); const topByTVLUsingTokenOutSecondHops = _(topByTVLUsingTokenOut) .map((subgraphPool) => { return tokenOutAddress == subgraphPool.token0.id ? subgraphPool.token1.id : subgraphPool.token0.id; }) .flatMap((secondHopId) => { var _a; return _(subgraphPoolsSorted) .filter((subgraphPool) => { return (!poolAddressesSoFar.has(subgraphPool.id) && !(tokensToAvoidOnSecondHops === null || tokensToAvoidOnSecondHops === void 0 ? void 0 : tokensToAvoidOnSecondHops.includes(secondHopId.toLowerCase())) && (subgraphPool.token0.id == secondHopId || subgraphPool.token1.id == secondHopId)); }) .filter((subgraphPool) => { // in case of hooks only, it means we want to filter out hookless pools if (routingConfig.hooksOptions === HooksOptions.HOOKS_ONLY) { return subgraphPool.hooks !== ADDRESS_ZERO; } // in case of no hooks, it means we want to filter out hook pools if (routingConfig.hooksOptions === HooksOptions.NO_HOOKS) { return subgraphPool.hooks === ADDRESS_ZERO; } // otherwise it's the default case, so we just return true return true; }) .slice(0, (_a = topNSecondHopForTokenAddress === null || topNSecondHopForTokenAddress === void 0 ? void 0 : topNSecondHopForTokenAddress.get(secondHopId)) !== null && _a !== void 0 ? _a : topNSecondHop) .value(); }) .uniqBy((pool) => pool.id) .value(); addToAddressSet(topByTVLUsingTokenOutSecondHops); const subgraphPools = _([ ...topByBaseWithTokenIn, ...topByBaseWithTokenOut, ...top2DirectSwapPool, ...top2EthQuoteTokenPool, ...topByTVL, ...topByTVLUsingTokenIn, ...topByTVLUsingTokenOut, ...topByTVLUsingTokenInSecondHops, ...topByTVLUsingTokenOutSecondHops, ]) .compact() .uniqBy((pool) => pool.id) .value(); const tokenAddresses = _(subgraphPools) .flatMap((subgraphPool) => [subgraphPool.token0.id, subgraphPool.token1.id]) .compact() .uniq() .value(); log.info(`Getting the ${tokenAddresses.length} tokens within the ${subgraphPools.length} V4 pools we are considering`); const tokenAccessor = await tokenProvider.getTokens(tokenAddresses, { blockNumber, }); const printV4SubgraphPool = (s) => { var _a, _b, _c, _d; return `${(_b = (_a = tokenAccessor.getTokenByAddress(s.token0.id)) === null || _a === void 0 ? void 0 : _a.symbol) !== null && _b !== void 0 ? _b : s.token0.id}/${(_d = (_c = tokenAccessor.getTokenByAddress(s.token1.id)) === null || _c === void 0 ? void 0 : _c.symbol) !== null && _d !== void 0 ? _d : s.token1.id}/${s.feeTier}/${s.tickSpacing}/${s.hooks}`; }; log.info({ topByBaseWithTokenIn: topByBaseWithTokenIn.map(printV4SubgraphPool), topByBaseWithTokenOut: topByBaseWithTokenOut.map(printV4SubgraphPool), topByTVL: topByTVL.map(printV4SubgraphPool), topByTVLUsingTokenIn: topByTVLUsingTokenIn.map(printV4SubgraphPool), topByTVLUsingTokenOut: topByTVLUsingTokenOut.map(printV4SubgraphPool), topByTVLUsingTokenInSecondHops: topByTVLUsingTokenInSecondHops.map(printV4SubgraphPool), topByTVLUsingTokenOutSecondHops: topByTVLUsingTokenOutSecondHops.map(printV4SubgraphPool), top2DirectSwap: top2DirectSwapPool.map(printV4SubgraphPool), top2EthQuotePool: top2EthQuoteTokenPool.map(printV4SubgraphPool), }, `V4 Candidate Pools`); const tokenPairsRaw = _.map(subgraphPools, (subgraphPool) => { // native currency is not erc20 token, therefore there's no way to retrieve native currency metadata as the erc20 token. const tokenA = isNativeCurrency(subgraphPool.token0.id) ? nativeOnChain(chainId) : tokenAccessor.getTokenByAddress(subgraphPool.token0.id); const tokenB = isNativeCurrency(subgraphPool.token1.id) ? nativeOnChain(chainId) : tokenAccessor.getTokenByAddress(subgraphPool.token1.id); let fee; try { fee = Number(subgraphPool.feeTier); } catch (err) { log.info({ subgraphPool }, `Dropping candidate pool for ${subgraphPool.token0.id}/${subgraphPool.token1.id}/${subgraphPool.feeTier} because fee tier not supported`); return undefined; } if (!tokenA || !tokenB) { log.info(`Dropping candidate pool for ${subgraphPool.token0.id}/${subgraphPool.token1.id}/${fee} because ${tokenA ? subgraphPool.token1.id : subgraphPool.token0.id} not found by token provider`); return undefined; } return [ tokenA, tokenB, fee, Number(subgraphPool.tickSpacing), subgraphPool.hooks, ]; }); const tokenPairs = _.compact(tokenPairsRaw); metric.putMetric('V4PoolsFilterLoad', Date.now() - beforePoolsFiltered, MetricLoggerUnit.Milliseconds); const beforePoolsLoad = Date.now(); const poolAccessor = await poolProvider.getPools(tokenPairs, { blockNumber, }); metric.putMetric('V4PoolsLoad', Date.now() - beforePoolsLoad, MetricLoggerUnit.Milliseconds); const poolsBySelection = { protocol: Protocol.V4, selections: { topByBaseWithTokenIn, topByBaseWithTokenOut, topByDirectSwapPool: top2DirectSwapPool, topByEthQuoteTokenPool: top2EthQuoteTokenPool, topByTVL, topByTVLUsingTokenIn, topByTVLUsingTokenOut, topByTVLUsingTokenInSecondHops, topByTVLUsingTokenOutSecondHops, }, }; return { poolAccessor, candidatePools: poolsBySelection, subgraphPools }; } export async function getV3CandidatePools({ tokenIn, tokenOut, routeType, routingConfig, subgraphProvider, tokenProvider, poolProvider, blockedTokenListProvider, chainId, }) { var _a, _b, _c, _d, _e; const { blockNumber, v3PoolSelection: { topN, topNDirectSwaps, topNTokenInOut, topNSecondHop, topNSecondHopForTokenAddress, tokensToAvoidOnSecondHops, topNWithEachBaseToken, topNWithBaseToken, }, } = routingConfig; const tokenInAddress = tokenIn.address.toLowerCase(); const tokenOutAddress = tokenOut.address.toLowerCase(); const beforeSubgraphPools = Date.now(); const allPools = await subgraphProvider.getPools(tokenIn, tokenOut, { blockNumber, }); log.info({ samplePools: allPools.slice(0, 3) }, 'Got all pools from V3 subgraph provider'); // Although this is less of an optimization than the V2 equivalent, // save some time copying objects by mutating the underlying pool directly. for (const pool of allPools) { pool.token0.id = pool.token0.id.toLowerCase(); pool.token1.id = pool.token1.id.toLowerCase(); } metric.putMetric('V3SubgraphPoolsLoad', Date.now() - beforeSubgraphPools, MetricLoggerUnit.Milliseconds); const beforePoolsFiltered = Date.now(); // Only consider pools where neither tokens are in the blocked token list. let filteredPools = allPools; if (blockedTokenListProvider) { filteredPools = []; for (const pool of allPools) { const token0InBlocklist = await blockedTokenListProvider.hasTokenByAddress(pool.token0.id); const token1InBlocklist = await blockedTokenListProvider.hasTokenByAddress(pool.token1.id); if (token0InBlocklist || token1InBlocklist) { continue; } filteredPools.push(pool); } } // Sort by tvlUSD in descending order const subgraphPoolsSorted = filteredPools.sort((a, b) => b.tvlUSD - a.tvlUSD); log.info(`After filtering blocked tokens went from ${allPools.length} to ${subgraphPoolsSorted.length}.`); const poolAddressesSoFar = new Set(); const addToAddressSet = (pools) => { _(pools) .map((pool) => pool.id) .forEach((poolAddress) => poolAddressesSoFar.add(poolAddress)); }; const baseTokens = (_a = baseTokensByChain[chainId]) !== null && _a !== void 0 ? _a : []; const topByBaseWithTokenIn = _(baseTokens) .flatMap((token) => { return _(subgraphPoolsSorted) .filter((subgraphPool) => { const tokenAddress = token.address.toLowerCase(); return ((subgraphPool.token0.id == tokenAddress && subgraphPool.token1.id == tokenInAddress) || (subgraphPool.token1.id == tokenAddress && subgraphPool.token0.id == tokenInAddress)); }) .sortBy((tokenListPool) => -tokenListPool.tvlUSD) .slice(0, topNWithEachBaseToken) .value(); }) .sortBy((tokenListPool) => -tokenListPool.tvlUSD) .slice(0, topNWithBaseToken) .value(); const topByBaseWithTokenOut = _(baseTokens) .flatMap((token) => { return _(subgraphPoolsSorted) .filter((subgraphPool) => { const tokenAddress = token.address.toLowerCase(); return ((subgraphPool.token0.id == tokenAddress && subgraphPool.token1.id == tokenOutAddress) || (subgraphPool.token1.id == tokenAddress && subgraphPool.token0.id == tokenOutAddress)); }) .sortBy((tokenListPool) => -tokenListPool.tvlUSD) .slice(0, topNWithEachBaseToken) .value(); }) .sortBy((tokenListPool) => -tokenListPool.tvlUSD) .slice(0, topNWithBaseToken) .value(); let top2DirectSwapPool = _(subgraphPoolsSorted) .filter((subgraphPool) => { return (!poolAddressesSoFar.has(subgraphPool.id) && ((subgraphPool.token0.id == tokenInAddress && subgraphPool.token1.id == tokenOutAddress) || (subgraphPool.token1.id == tokenInAddress && subgraphPool.token0.id == tokenOutAddress))); }) .slice(0, topNDirectSwaps) .value(); if (top2DirectSwapPool.length == 0 && topNDirectSwaps > 0) { // We don't want to re-add AMPL token pools for V3 in Mainnet. // TODO: ROUTE-347, Remove this check once we have a better way to sync filters from subgraph cronjob <> routing path. if (!(chainId == ChainId.MAINNET && (tokenIn.address.toLowerCase() === '0xd46ba6d942050d489dbd938a2c909a5d5039a161' || tokenOut.address.toLowerCase() === '0xd46ba6d942050d489dbd938a2c909a5d5039a161'))) { // If we requested direct swap pools but did not find any in the subgraph query. // Optimistically add them into the query regardless. Invalid pools ones will be dropped anyway // when we query the pool on-chain. Ensures that new pools for new pairs can be swapped on immediately. top2DirectSwapPool = _.map(getApplicableV3FeeAmounts(chainId), (feeAmount) => { const { token0, token1, poolAddress } = poolProvider.getPoolAddress(tokenIn, tokenOut, feeAmount); return { id: poolAddress, feeTier: unparseFeeAmount(feeAmount), liquidity: '10000', token0: { id: token0.address, }, token1: { id: token1.address, }, tvlETH: 10000, tvlUSD: 10000, }; }); } } addToAddressSet(top2DirectSwapPool); const wrappedNativeAddress = (_b = WRAPPED_NATIVE_CURRENCY[chainId]) === null || _b === void 0 ? void 0 : _b.address.toLowerCase(); // Main reason we need this is for gas estimates, only needed if token out is not native. // We don't check the seen address set because if we've already added pools for getting native quotes // theres no need to add more. let top2EthQuoteTokenPool = []; if ((((_c = WRAPPED_NATIVE_CURRENCY[chainId]) === null || _c === void 0 ? void 0 : _c.symbol) == ((_d = WRAPPED_NATIVE_CURRENCY[ChainId.MAINNET]) === null || _d === void 0 ? void 0 : _d.symbol) && tokenOut.symbol != 'WETH' && tokenOut.symbol != 'WETH9' && tokenOut.symbol != 'ETH') || (((_e = WRAPPED_NATIVE_CURRENCY[chainId]) === null || _e === void 0 ? void 0 : _e.symbol) == WMATIC_POLYGON.symbol && tokenOut.symbol != 'MATIC' && tokenOut.symbol != 'WMATIC')) { top2EthQuoteTokenPool = _(subgraphPoolsSorted) .filter((subgraphPool) => { if (routeType == TradeType.EXACT_INPUT) { return ((subgraphPool.token0.id == wrappedNativeAddress && subgraphPool.token1.id == tokenOutAddress) || (subgraphPool.token1.id == wrappedNativeAddress && subgraphPool.token0.id == tokenOutAddress)); } else { return ((subgraphPool.token0.id == wrappedNativeAddress && subgraphPool.token1.id == tokenInAddress) || (subgraphPool.token1.id == wrappedNativeAddress && subgraphPool.token0.id == tokenInAddress)); } }) .slice(0, 1) .value(); } addToAddressSet(top2EthQuoteTokenPool); const topByTVL = _(subgraphPoolsSorted) .filter((subgraphPool) => { return !poolAddressesSoFar.has(subgraphPool.id); }) .slice(0, topN) .value(); addToAddressSet(topByTVL); const topByTVLUsingTokenIn = _(subgraphPoolsSorted) .filter((subgraphPool) => { return (!poolAddressesSoFar.has(subgraphPool.id) && (subgraphPool.token0.id == tokenInAddress || subgraphPool.token1.id == tokenInAddress)); }) .slice(0, topNTokenInOut) .value(); addToAddressSet(topByTVLUsingTokenIn); const topByTVLUsingTokenOut = _(subgraphPoolsSorted) .filter((subgraphPool) => { return (!poolAddressesSoFar.has(subgraphPool.id) && (subgraphPool.token0.id == tokenOutAddress || subgraphPool.token1.id == tokenOutAddress)); }) .slice(0, topNTokenInOut) .value(); addToAddressSet(topByTVLUsingTokenOut); const topByTVLUsingTokenInSecondHops = _(topByTVLUsingTokenIn) .map((subgraphPool) => { return tokenInAddress == subgraphPool.token0.id ? subgraphPool.token1.id : subgraphPool.token0.id; }) .flatMap((secondHopId) => { var _a; return _(subgraphPoolsSorted) .filter((subgraphPool) => { return (!poolAddressesSoFar.has(subgraphPool.id) && !(tokensToAvoidOnSecondHops === null || tokensToAvoidOnSecondHops === void 0 ? void 0 : tokensToAvoidOnSecondHops.includes(secondHopId.toLowerCase())) && (subgraphPool.token0.id == secondHopId || subgraphPool.token1.id == secondHopId)); }) .slice(0, (_a = topNSecondHopForTokenAddress === null || topNSecondHopForTokenAddress === void 0 ? void 0 : topNSecondHopForTokenAddress.get(secondHopId)) !== null && _a !== void 0 ? _a : topNSecondHop) .value(); }) .uniqBy((pool) => pool.id) .value(); addToAddressSet(topByTVLUsingTokenInSecondHops); const topByTVLUsingTokenOutSecondHops = _(topByTVLUsingTokenOut) .map((subgraphPool) => { return tokenOutAddress == subgraphPool.token0.id ? subgraphPool.token1.id : subgraphPool.token0.id; }) .flatMap((secondHopId) => { var _a; return _(subgraphPoolsSorted) .filter((subgraphPool) => { return (!poolAddressesSoFar.has(subgraphPool.id) && !(tokensToAvoidOnSecondHops === null || tokensToAvoidOnSecondHops === void 0 ? void 0 : tokensToAvoidOnSecondHops.includes(secondHopId.toLowerCase())) && (subgraphPool.token0.id == secondHopId || subgraphPool.token1.id == secondHopId)); }) .slice(0, (_a = topNSecondHopForTokenAddress === null || topNSecondHopForTokenAddress === void 0 ? void 0 : topNSecondHopForTokenAddress.get(secondHopId)) !== null && _a !== void 0 ? _a : topNSecondHop) .value(); }) .uniqBy((pool) => pool.id) .value(); addToAddressSet(topByTVLUsingTokenOutSecondHops); const subgraphPools = _([ ...topByBaseWithTokenIn, ...topByBaseWithTokenOut, ...top2DirectSwapPool, ...top2EthQuoteTokenPool, ...topByTVL, ...topByTVLUsingTokenIn, ...topByTVLUsingTokenOut, ...topByTVLUsingTokenInSecondHops, ...topByTVLUsingTokenOutSecondHops, ]) .compact() .uniqBy((pool) => pool.id) .value(); const tokenAddresses = _(subgraphPools) .flatMap((subgraphPool) => [subgraphPool.token0.id, subgraphPool.token1.id]) .compact() .uniq() .value(); log.info(`Getting the ${tokenAddresses.length} tokens within the ${subgraphPools.length} V3 pools we are considering`); const tokenAccessor = await tokenProvider.getTokens(tokenAddresses, { blockNumber, }); const printV3SubgraphPool = (s) => { var _a, _b, _c, _d; return `${(_b = (_a = tokenAccessor.getTokenByAddress(s.token0.id)) === null || _a === void 0 ? void 0 : _a.symbol) !== null && _b !== void 0 ? _b : s.token0.id}/${(_d = (_c = tokenAccessor.getTokenByAddress(s.token1.id)) === null || _c === void 0 ? void 0 : _c.symbol) !== null && _d !== void 0 ? _d : s.token1.id}/${s.feeTier}`; }; log.info({ topByBaseWithTokenIn: topByBaseWithTokenIn.map(printV3SubgraphPool), topByBaseWithTokenOut: topByBaseWithTokenOut.map(printV3SubgraphPool), topByTVL: topByTVL.map(printV3SubgraphPool), topByTVLUsingTokenIn: topByTVLUsingTokenIn.map(printV3SubgraphPool), topByTVLUsingTokenOut: topByTVLUsingTokenOut.map(printV3SubgraphPool), topByTVLUsingTokenInSecondHops: topByTVLUsingTokenInSecondHops.map(printV3SubgraphPool), topByTVLUsingTokenOutSecondHops: topByTVLUsingTokenOutSecondHops.map(printV3SubgraphPool), top2DirectSwap: top2DirectSwapPool.map(printV3SubgraphPool), top2EthQuotePool: top2EthQuoteTokenPool.map(printV3SubgraphPool), }, `V3 Candidate Pools`); const tokenPairsRaw = _.map(subgraphPools, (subgraphPool) => { const tokenA = tokenAccessor.getTokenByAddress(subgraphPool.token0.id); const tokenB = tokenAccessor.getTokenByAddress(subgraphPool.token1.id); let fee; try { fee = parseFeeAmount(subgraphPool.feeTier); } catch (err) { log.info({ subgraphPool }, `Dropping candidate pool for ${subgraphPool.token0.id}/${subgraphPool.token1.id}/${subgraphPool.feeTier} because fee tier not supported`); return undefined; } if (!tokenA || !tokenB) { log.info(`Dropping candidate pool for ${subgraphPool.token0.id}/${subgraphPool.token1.id}/${fee} because ${tokenA ? subgraphPool.token1.id : subgraphPool.token0.id} not found by token provider`); return undefined; } return [tokenA, tokenB, fee]; }); const tokenPairs = _.compact(tokenPairsRaw); metric.putMetric('V3PoolsFilterLoad', Date.now() - beforePoolsFiltered, MetricLoggerUnit.Milliseconds); const beforePoolsLoad = Date.now(); const poolAccessor = await poolProvider.getPools(tokenPairs, { blockNumber, }); metric.putMetric('V3PoolsLoad', Date.now() - beforePoolsLoad, MetricLoggerUnit.Milliseconds); const poolsBySelection = { protocol: Protocol.V3, selections: { topByBaseWithTokenIn, topByBaseWithTokenOut, topByDirectSwapPool: top2DirectSwapPool, topByEthQuoteTokenPool: top2EthQuoteTokenPool, topByTVL, topByTVLUsingTokenIn, topByTVLUsingTokenOut, topByTVLUsingTokenInSecondHops, topByTVLUsingTokenOutSecondHops, }, }; return { poolAccessor, candidatePools: poolsBySelection, subgraphPools }; } export async function getV2CandidatePools({ tokenIn, tokenOut, routeType, routingConfig, subgraphProvider, tokenProvider, poolProvider, blockedTokenListProvider, chainId, }) { var _a; const { blockNumber, v2PoolSelection: { topN, topNDirectSwaps, topNTokenInOut, topNSecondHop, tokensToAvoidOnSecondHops, topNWithEachBaseToken, topNWithBaseToken, }, } = routingConfig; const tokenInAddress = tokenIn.address.toLowerCase(); const tokenOutAddress = tokenOut.address.toLowerCase(); const beforeSubgraphPools = Date.now(); const allPoolsRaw = await subgraphProvider.getPools(tokenIn, tokenOut, { blockNumber, }); // With tens of thousands of V2 pools, operations that copy pools become costly. // Mutate the pool directly rather than creating a new pool / token to optimmize for speed. for (const pool of allPoolsRaw) { pool.token0.id = pool.token0.id.toLowerCase(); pool.token1.id = pool.token1.id.toLowerCase(); } metric.putMetric('V2SubgraphPoolsLoad', Date.now() - beforeSubgraphPools, MetricLoggerUnit.Milliseconds); const beforePoolsFiltered = Date.now(); // Sort by pool reserve in descending order. const subgraphPoolsSorted = allPoolsRaw.sort((a, b) => b.reserve - a.reserve); const poolAddressesSoFar = new Set(); // Always add the direct swap pool into the mix regardless of if it exists in the subgraph pool list. // Ensures that new pools can be swapped on immediately, and that if a pool was filtered out of the // subgraph query for some reason (e.g. trackedReserveETH was 0), then we still consider it. let topByDirectSwapPool = []; if (topNDirectSwaps > 0) { const { token0, token1, poolAddress } = poolProvider.getPoolAddress(tokenIn, tokenOut); poolAddressesSoFar.add(poolAddress.toLowerCase()); topByDirectSwapPool = [ { id: poolAddress, token0: { id: token0.address, }, token1: { id: token1.address, }, supply: 10000, reserve: 10000, reserveUSD: 10000, // Not used. Set to arbitrary number. }, ]; } const wethAddress = WRAPPED_NATIVE_CURRENCY[chainId].address.toLowerCase(); const topByBaseWithTokenInMap = new Map(); const topByBaseWithTokenOutMap = new Map(); const baseTokens = (_a = baseTokensByChain[chainId]) !== null && _a !== void 0 ? _a : []; const baseTokensAddresses = new Set(); baseTokens.forEach((token) => { const baseTokenAddr = token.address.toLowerCase(); baseTokensAddresses.add(baseTokenAddr); topByBaseWithTokenInMap.set(baseTokenAddr, new SubcategorySelectionPools([], topNWithEachBaseToken)); topByBaseWithTokenOutMap.set(baseTokenAddr, new SubcategorySelectionPools([], topNWithEachBaseToken)); }); let topByBaseWithTokenInPoolsFound = 0; let topByBaseWithTokenOutPoolsFound = 0; // Main reason we need this is for gas estimates // There can ever only be 1 Token/ETH pool, so we will only look for 1 let topNEthQuoteToken = 1; // but, we only need it if token out is not ETH. if (tokenOut.symbol == 'WETH' || tokenOut.symbol == 'WETH9' || tokenOut.symbol == 'ETH') { // if it's eth we change the topN to 0, so we can break early from the loop. topNEthQuoteToken = 0; } const topByEthQuoteTokenPool = []; const topByTVLUsingTokenIn = []; const topByTVLUsingTokenOut = []; const topByTVL = []; // Used to track how many iterations we do in the first loop let loopsInFirstIteration = 0; // Filtering step for up to first hop // The pools are pre-sorted, so we can just iterate through them and fill our heuristics. for (const subgraphPool of subgraphPoolsSorted) { loopsInFirstIteration += 1; // Check if we have satisfied all the heuristics, if so, we can stop. if (topByBaseWithTokenInPoolsFound >= topNWithBaseToken && topByBaseWithTokenOutPoolsFound >= topNWithBaseToken && topByEthQuoteTokenPool.length >= topNEthQuoteToken && topByTVL.length >= topN && topByTVLUsingTokenIn.length >= topNTokenInOut && topByTVLUsingTokenOut.length >= topNTokenInOut) { // We have satisfied all the heuristics, so we can stop. break; } if (poolAddressesSoFar.has(subgraphPool.id)) { // We've already added this pool, so skip it. continue; } // Only consider pools where neither tokens are in the blocked token list. if (blockedTokenListProvider) { const [token0InBlocklist, token1InBlocklist] = await Promise.all([ blockedTokenListProvider.hasTokenByAddress(subgraphPool.token0.id), blockedTokenListProvider.hasTokenByAddress(subgraphPool.token1.id), ]); if (token0InBlocklist || token1InBlocklist) { continue; } } const tokenInToken0TopByBase = topByBaseWithTokenInMap.get(subgraphPool.token0.id); if (topByBaseWithTokenInPoolsFound < topNWithBaseToken && tokenInToken0TopByBase && subgraphPool.token0.id != tokenOutAddress && subgraphPool.token1.id == tokenInAddress) { topByBaseWithTokenInPoolsFound += 1; poolAddressesSoFar.add(subgraphPool.id); if (topByTVLUsingTokenIn.length < topNTokenInOut) { topByTVLUsingTokenIn.push(subgraphPool); } if (routeType === TradeType.EXACT_OUTPUT && subgraphPool.token0.id == wethAddress) { topByEthQuoteTokenPool.push(subgraphPool); } tokenInToken0TopByBase.pools.push(subgraphPool); continue; } const tokenInToken1TopByBase = topByBaseWithTokenInMap.get(subgraphPool.token1.id); if (topByBaseWithTokenInPoolsFound < topNWithBaseToken && tokenInToken1TopByBase && subgraphPool.token0.id == tokenInAddress && subgraphPool.token1.id != tokenOutAddress) { topByBaseWithTokenInPoolsFound += 1; poolAddressesSoFar.add(subgraphPool.id); if (topByTVLUsingTokenIn.length < topNTokenInOut) { topByTVLUsingTokenIn.push(subgraphPool); } if (routeType === TradeType.EXACT_OUTPUT && subgraphPool.token1.id == wethAddress) { topByEthQuoteTokenPool.push(subgraphPool); } tokenInToken1TopByBase.pools.push(subgraphPool); continue; } const tokenOutToken0TopByBase = topByBaseWithTokenOutMap.get(subgraphPool.token0.id); if (topByBaseWithTokenOutPoolsFound < topNWithBaseToken && tokenOutToken0TopByBase && subgraphPool.token0.id != tokenInAddress &&