UNPKG

@xspswap/smart-order-router

Version:
714 lines 68.4 kB
import { Protocol } from '@x-swap-protocol/router-sdk'; import { TradeType } from '@x-swap-protocol/sdk-core'; import { FeeAmount } from '@x-swap-protocol/v3-sdk'; import _ from 'lodash'; import { DAI_XDC_APOTHEM, USDC_XDC_APOTHEM, USDT_XDC, USDT_XDC_APOTHEM, WBTC_XDC_APOTHEM, WXDC_XDC, } from '../../../providers/token-provider'; import { ChainId, WRAPPED_NATIVE_CURRENCY } from '../../../util'; import { parseFeeAmount, unparseFeeAmount } from '../../../util/amounts'; import { log } from '../../../util/log'; import { metric, MetricLoggerUnit } from '../../../util/metric'; const baseTokensByChain = { [ChainId.XDC]: [WXDC_XDC, USDT_XDC], [ChainId.XDC_APOTHEM]: [ DAI_XDC_APOTHEM, USDC_XDC_APOTHEM, USDT_XDC_APOTHEM, WBTC_XDC_APOTHEM, ], }; export async function getV3CandidatePools({ tokenIn, tokenOut, routeType, routingConfig, subgraphProvider, tokenProvider, poolProvider, blockedTokenListProvider, chainId, }) { var _a, _b, _c, _d; const { blockNumber, v3PoolSelection: { topN, topNDirectSwaps, topNTokenInOut, topNSecondHop, topNSecondHopForTokenAddress, 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, }); log.info({ samplePools: allPoolsRaw.slice(0, 3) }, 'Got all pools from V3 subgraph provider'); const allPools = _.map(allPoolsRaw, (pool) => { return { ...pool, token0: { ...pool.token0, id: pool.token0.id.toLowerCase(), }, token1: { ...pool.token1, id: pool.token1.id.toLowerCase(), }, }; }); metric.putMetric('V3SubgraphPoolsLoad', Date.now() - beforeSubgraphPools, MetricLoggerUnit.Milliseconds); // 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.getTokenByAddress(pool.token0.id); const token1InBlocklist = await blockedTokenListProvider.getTokenByAddress(pool.token1.id); if (token0InBlocklist || token1InBlocklist) { continue; } filteredPools.push(pool); } } const subgraphPoolsSorted = _(filteredPools) .sortBy((tokenListPool) => -tokenListPool.tvlUSD) .value(); 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) { // 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([FeeAmount.HIGH, FeeAmount.MEDIUM, FeeAmount.LOW, FeeAmount.LOWEST], (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; // 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.XDC]) === null || _d === void 0 ? void 0 : _d.symbol) && tokenOut.symbol != 'WXDC' && tokenOut.symbol != 'XDC') { 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) && (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) && (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); const beforePoolsLoad = Date.now(); const poolAccessor = await poolProvider.getPools(tokenPairs); 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, 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, }); const allPools = _.map(allPoolsRaw, (pool) => { return { ...pool, token0: { ...pool.token0, id: pool.token0.id.toLowerCase(), }, token1: { ...pool.token1, id: pool.token1.id.toLowerCase(), }, }; }); metric.putMetric('V2SubgraphPoolsLoad', Date.now() - beforeSubgraphPools, MetricLoggerUnit.Milliseconds); // 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.getTokenByAddress(pool.token0.id); const token1InBlocklist = await blockedTokenListProvider.getTokenByAddress(pool.token1.id); if (token0InBlocklist || token1InBlocklist) { continue; } filteredPools.push(pool); } } const subgraphPoolsSorted = _(filteredPools) .sortBy((tokenListPool) => -tokenListPool.reserve) .value(); 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.reserve) .slice(0, topNWithEachBaseToken) .value(); }) .sortBy((tokenListPool) => -tokenListPool.reserve) .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.reserve) .slice(0, topNWithEachBaseToken) .value(); }) .sortBy((tokenListPool) => -tokenListPool.reserve) .slice(0, topNWithBaseToken) .value(); // 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); topByDirectSwapPool = [ { id: poolAddress, token0: { id: token0.address, }, token1: { id: token1.address, }, supply: 10000, reserve: 10000, reserveUSD: 10000, // Not used. Set to arbitrary number. }, ]; } addToAddressSet(topByDirectSwapPool); const wethAddress = WRAPPED_NATIVE_CURRENCY[chainId].address; // Main reason we need this is for gas estimates, only needed if token out is not ETH. // We don't check the seen address set because if we've already added pools for getting ETH quotes // theres no need to add more. // Note: we do not need to check other native currencies for the V2 Protocol let topByEthQuoteTokenPool = []; if (tokenOut.symbol != 'WETH' && tokenOut.symbol != 'WETH9' && tokenOut.symbol != 'ETH') { topByEthQuoteTokenPool = _(subgraphPoolsSorted) .filter((subgraphPool) => { if (routeType == TradeType.EXACT_INPUT) { return ((subgraphPool.token0.id == wethAddress && subgraphPool.token1.id == tokenOutAddress) || (subgraphPool.token1.id == wethAddress && subgraphPool.token0.id == tokenOutAddress)); } else { return ((subgraphPool.token0.id == wethAddress && subgraphPool.token1.id == tokenInAddress) || (subgraphPool.token1.id == wethAddress && subgraphPool.token0.id == tokenInAddress)); } }) .slice(0, 1) .value(); } addToAddressSet(topByEthQuoteTokenPool); 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) => { return _(subgraphPoolsSorted) .filter((subgraphPool) => { return (!poolAddressesSoFar.has(subgraphPool.id) && (subgraphPool.token0.id == secondHopId || subgraphPool.token1.id == secondHopId)); }) .slice(0, 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) => { return _(subgraphPoolsSorted) .filter((subgraphPool) => { return (!poolAddressesSoFar.has(subgraphPool.id) && (subgraphPool.token0.id == secondHopId || subgraphPool.token1.id == secondHopId)); }) .slice(0, topNSecondHop) .value(); }) .uniqBy((pool) => pool.id) .value(); addToAddressSet(topByTVLUsingTokenOutSecondHops); const subgraphPools = _([ ...topByBaseWithTokenIn, ...topByBaseWithTokenOut, ...topByDirectSwapPool, ...topByEthQuoteTokenPool, ...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} V2 pools we are considering`); const tokenAccessor = await tokenProvider.getTokens(tokenAddresses, { blockNumber, }); const printV2SubgraphPool = (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}`; }; log.info({ topByBaseWithTokenIn: topByBaseWithTokenIn.map(printV2SubgraphPool), topByBaseWithTokenOut: topByBaseWithTokenOut.map(printV2SubgraphPool), topByTVL: topByTVL.map(printV2SubgraphPool), topByTVLUsingTokenIn: topByTVLUsingTokenIn.map(printV2SubgraphPool), topByTVLUsingTokenOut: topByTVLUsingTokenOut.map(printV2SubgraphPool), topByTVLUsingTokenInSecondHops: topByTVLUsingTokenInSecondHops.map(printV2SubgraphPool), topByTVLUsingTokenOutSecondHops: topByTVLUsingTokenOutSecondHops.map(printV2SubgraphPool), top2DirectSwap: topByDirectSwapPool.map(printV2SubgraphPool), top2EthQuotePool: topByEthQuoteTokenPool.map(printV2SubgraphPool), }, `V2 Candidate pools`); const tokenPairsRaw = _.map(subgraphPools, (subgraphPool) => { const tokenA = tokenAccessor.getTokenByAddress(subgraphPool.token0.id); const tokenB = tokenAccessor.getTokenByAddress(subgraphPool.token1.id); if (!tokenA || !tokenB) { log.info(`Dropping candidate pool for ${subgraphPool.token0.id}/${subgraphPool.token1.id}`); return undefined; } return [tokenA, tokenB]; }); const tokenPairs = _.compact(tokenPairsRaw); const beforePoolsLoad = Date.now(); const poolAccessor = await poolProvider.getPools(tokenPairs, { blockNumber }); metric.putMetric('V2PoolsLoad', Date.now() - beforePoolsLoad, MetricLoggerUnit.Milliseconds); const poolsBySelection = { protocol: Protocol.V2, selections: { topByBaseWithTokenIn, topByBaseWithTokenOut, topByDirectSwapPool, topByEthQuoteTokenPool: topByEthQuoteTokenPool, topByTVL, topByTVLUsingTokenIn, topByTVLUsingTokenOut, topByTVLUsingTokenInSecondHops, topByTVLUsingTokenOutSecondHops, }, }; return { poolAccessor, candidatePools: poolsBySelection, subgraphPools }; } export async function getMixedRouteCandidatePools({ tokenIn, tokenOut, routeType, routingConfig, v3subgraphProvider, v2subgraphProvider, tokenProvider, v3poolProvider, v2poolProvider, blockedTokenListProvider, chainId, }) { const { blockNumber } = routingConfig; const { subgraphPools: V3subgraphPools, candidatePools: V3candidatePools } = await getV3CandidatePools({ tokenIn, tokenOut, tokenProvider, blockedTokenListProvider, poolProvider: v3poolProvider, routeType, subgraphProvider: v3subgraphProvider, routingConfig, chainId, }); const { subgraphPools: V2subgraphPools, candidatePools: V2candidatePools } = await getV2CandidatePools({ tokenIn, tokenOut, tokenProvider, blockedTokenListProvider, poolProvider: v2poolProvider, routeType, subgraphProvider: v2subgraphProvider, routingConfig, chainId, }); /** * Main heuristic for pruning mixedRoutes: * - we pick V2 pools with higher liq than respective V3 pools, or if the v3 pool doesn't exist * * This way we can reduce calls to our provider since it's possible to generate a lot of mixed routes */ /// We only really care about pools involving the tokenIn or tokenOut explictly, /// since there's no way a long tail token in V2 would be routed through as an intermediary const V2topByTVLPoolIds = new Set([ ...V2candidatePools.selections.topByTVLUsingTokenIn, ...V2candidatePools.selections.topByBaseWithTokenIn, /// tokenOut: ...V2candidatePools.selections.topByTVLUsingTokenOut, ...V2candidatePools.selections.topByBaseWithTokenOut, /// Direct swap: ...V2candidatePools.selections.topByDirectSwapPool, ].map((poolId) => poolId.id)); const V2topByTVLSortedPools = _(V2subgraphPools) .filter((pool) => V2topByTVLPoolIds.has(pool.id)) .sortBy((pool) => -pool.reserveUSD) .value(); /// we consider all returned V3 pools for this heuristic to "fill in the gaps" const V3sortedPools = _(V3subgraphPools) .sortBy((pool) => -pool.tvlUSD) .value(); /// Finding pools with greater reserveUSD on v2 than tvlUSD on v3, or if there is no v3 liquidity const buildV2Pools = []; V2topByTVLSortedPools.forEach((V2subgraphPool) => { const V3subgraphPool = V3sortedPools.find((pool) => (pool.token0.id == V2subgraphPool.token0.id && pool.token1.id == V2subgraphPool.token1.id) || (pool.token0.id == V2subgraphPool.token1.id && pool.token1.id == V2subgraphPool.token0.id)); if (V3subgraphPool) { if (V2subgraphPool.reserveUSD > V3subgraphPool.tvlUSD) { log.info({ token0: V2subgraphPool.token0.id, token1: V2subgraphPool.token1.id, v2reserveUSD: V2subgraphPool.reserveUSD, v3tvlUSD: V3subgraphPool.tvlUSD, }, `MixedRoute heuristic, found a V2 pool with higher liquidity than its V3 counterpart`); buildV2Pools.push(V2subgraphPool); } } else { log.info({ token0: V2subgraphPool.token0.id, token1: V2subgraphPool.token1.id, v2reserveUSD: V2subgraphPool.reserveUSD, }, `MixedRoute heuristic, found a V2 pool with no V3 counterpart`); buildV2Pools.push(V2subgraphPool); } }); log.info(buildV2Pools.length, `Number of V2 candidate pools that fit first heuristic`); const subgraphPools = [...buildV2Pools, ...V3sortedPools]; 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} pools we are considering`); const tokenAccessor = await tokenProvider.getTokens(tokenAddresses, { blockNumber, }); const V3tokenPairsRaw = _.map(V3sortedPools, (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 V3tokenPairs = _.compact(V3tokenPairsRaw); const V2tokenPairsRaw = _.map(buildV2Pools, (subgraphPool) => { const tokenA = tokenAccessor.getTokenByAddress(subgraphPool.token0.id); const tokenB = tokenAccessor.getTokenByAddress(subgraphPool.token1.id); if (!tokenA || !tokenB) { log.info(`Dropping candidate pool for ${subgraphPool.token0.id}/${subgraphPool.token1.id}`); return undefined; } return [tokenA, tokenB]; }); const V2tokenPairs = _.compact(V2tokenPairsRaw); const [V2poolAccessor, V3poolAccessor] = await Promise.all([ v2poolProvider.getPools(V2tokenPairs, { blockNumber, }), v3poolProvider.getPools(V3tokenPairs, { blockNumber, }), ]); /// @dev a bit tricky here since the original V2CandidateSelections object included pools that we may have dropped /// as part of the heuristic. We need to reconstruct a new object with the v3 pools too. const buildPoolsBySelection = (key) => { return [ ...buildV2Pools.filter((pool) => V2candidatePools.selections[key].map((p) => p.id).includes(pool.id)), ...V3candidatePools.selections[key], ]; }; const poolsBySelection = { protocol: Protocol.MIXED, selections: { topByBaseWithTokenIn: buildPoolsBySelection('topByBaseWithTokenIn'), topByBaseWithTokenOut: buildPoolsBySelection('topByBaseWithTokenOut'), topByDirectSwapPool: buildPoolsBySelection('topByDirectSwapPool'), topByEthQuoteTokenPool: buildPoolsBySelection('topByEthQuoteTokenPool'), topByTVL: buildPoolsBySelection('topByTVL'), topByTVLUsingTokenIn: buildPoolsBySelection('topByTVLUsingTokenIn'), topByTVLUsingTokenOut: buildPoolsBySelection('topByTVLUsingTokenOut'), topByTVLUsingTokenInSecondHops: buildPoolsBySelection('topByTVLUsingTokenInSecondHops'), topByTVLUsingTokenOutSecondHops: buildPoolsBySelection('topByTVLUsingTokenOutSecondHops'), }, }; return { V2poolAccessor, V3poolAccessor, candidatePools: poolsBySelection, subgraphPools, }; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2V0LWNhbmRpZGF0ZS1wb29scy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9yb3V0ZXJzL2FscGhhLXJvdXRlci9mdW5jdGlvbnMvZ2V0LWNhbmRpZGF0ZS1wb29scy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDdkQsT0FBTyxFQUFTLFNBQVMsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQzdELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNwRCxPQUFPLENBQUMsTUFBTSxRQUFRLENBQUM7QUFPdkIsT0FBTyxFQUNMLGVBQWUsRUFFZixnQkFBZ0IsRUFDaEIsUUFBUSxFQUNSLGdCQUFnQixFQUNoQixnQkFBZ0IsRUFDaEIsUUFBUSxHQUNULE1BQU0sbUNBQW1DLENBQUM7QUFhM0MsT0FBTyxFQUFFLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUNqRSxPQUFPLEVBQUUsY0FBYyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDekUsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQ3hDLE9BQU8sRUFBRSxNQUFNLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQTREaEUsTUFBTSxpQkFBaUIsR0FBdUM7SUFDNUQsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxRQUFRLEVBQUUsUUFBUSxDQUFDO0lBQ25DLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxFQUFFO1FBQ3JCLGVBQWU7UUFDZixnQkFBZ0I7UUFDaEIsZ0JBQWdCO1FBQ2hCLGdCQUFnQjtLQUNqQjtDQUNGLENBQUM7QUFFRixNQUFNLENBQUMsS0FBSyxVQUFVLG1CQUFtQixDQUFDLEVBQ3hDLE9BQU8sRUFDUCxRQUFRLEVBQ1IsU0FBUyxFQUNULGFBQWEsRUFDYixnQkFBZ0IsRUFDaEIsYUFBYSxFQUNiLFlBQVksRUFDWix3QkFBd0IsRUFDeEIsT0FBTyxHQUNtQjs7SUFLMUIsTUFBTSxFQUNKLFdBQVcsRUFDWCxlQUFlLEVBQUUsRUFDZixJQUFJLEVBQ0osZUFBZSxFQUNmLGNBQWMsRUFDZCxhQUFhLEVBQ2IsNEJBQTRCLEVBQzVCLHFCQUFxQixFQUNyQixpQkFBaUIsR0FDbEIsR0FDRixHQUFHLGFBQWEsQ0FBQztJQUNsQixNQUFNLGNBQWMsR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQ3JELE1BQU0sZUFBZSxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsV0FBVyxFQUFFLENBQUM7SUFFdkQsTUFBTSxtQkFBbUIsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7SUFFdkMsTUFBTSxXQUFXLEdBQUcsTUFBTSxnQkFBZ0IsQ0FBQyxRQUFRLENBQUMsT0FBTyxFQUFFLFFBQVEsRUFBRTtRQUNyRSxXQUFXO0tBQ1osQ0FBQyxDQUFDO0lBRUgsR0FBRyxDQUFDLElBQUksQ0FDTixFQUFFLFdBQVcsRUFBRSxXQUFXLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxFQUN4Qyx5Q0FBeUMsQ0FDMUMsQ0FBQztJQUVGLE1BQU0sUUFBUSxHQUFHLENBQUMsQ0FBQyxHQUFHLENBQUMsV0FBVyxFQUFFLENBQUMsSUFBSSxFQUFFLEVBQUU7UUFDM0MsT0FBTztZQUNMLEdBQUcsSUFBSTtZQUNQLE1BQU0sRUFBRTtnQkFDTixHQUFHLElBQUksQ0FBQyxNQUFNO2dCQUNkLEVBQUUsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxXQUFXLEVBQUU7YUFDakM7WUFDRCxNQUFNLEVBQUU7Z0JBQ04sR0FBRyxJQUFJLENBQUMsTUFBTTtnQkFDZCxFQUFFLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsV0FBVyxFQUFFO2FBQ2pDO1NBQ0YsQ0FBQztJQUNKLENBQUMsQ0FBQyxDQUFDO0lBRUgsTUFBTSxDQUFDLFNBQVMsQ0FDZCxxQkFBcUIsRUFDckIsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLG1CQUFtQixFQUNoQyxnQkFBZ0IsQ0FBQyxZQUFZLENBQzlCLENBQUM7SUFFRiwwRUFBMEU7SUFDMUUsSUFBSSxhQUFhLEdBQXFCLFFBQVEsQ0FBQztJQUMvQyxJQUFJLHdCQUF3QixFQUFFO1FBQzVCLGFBQWEsR0FBRyxFQUFFLENBQUM7UUFDbkIsS0FBSyxNQUFNLElBQUksSUFBSSxRQUFRLEVBQUU7WUFDM0IsTUFBTSxpQkFBaUIsR0FDckIsTUFBTSx3QkFBd0IsQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQ25FLE1BQU0saUJBQWlCLEdBQ3JCLE1BQU0sd0JBQXdCLENBQUMsaUJBQWlCLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUVuRSxJQUFJLGlCQUFpQixJQUFJLGlCQUFpQixFQUFFO2dCQUMxQyxTQUFTO2FBQ1Y7WUFFRCxhQUFhLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1NBQzFCO0tBQ0Y7SUFFRCxNQUFNLG1CQUFtQixHQUFHLENBQUMsQ0FBQyxhQUFhLENBQUM7U0FDekMsTUFBTSxDQUFDLENBQUMsYUFBYSxFQUFFLEVBQUUsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUM7U0FDaEQsS0FBSyxFQUFFLENBQUM7SUFFWCxHQUFHLENBQUMsSUFBSSxDQUNOLDRDQUE0QyxRQUFRLENBQUMsTUFBTSxPQUFPLG1CQUFtQixDQUFDLE1BQU0sR0FBRyxDQUNoRyxDQUFDO0lBRUYsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLEdBQUcsRUFBVSxDQUFDO0lBQzdDLE1BQU0sZUFBZSxHQUFHLENBQUMsS0FBdUIsRUFBRSxFQUFFO1FBQ2xELENBQUMsQ0FBQyxLQUFLLENBQUM7YUFDTCxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7YUFDdEIsT0FBTyxDQUFDLENBQUMsV0FBVyxFQUFFLEVBQUUsQ0FBQyxrQkFBa0IsQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQztJQUNuRSxDQUFDLENBQUM7SUFFRixNQUFNLFVBQVUsR0FBRyxNQUFBLGlCQUFpQixDQUFDLE9BQU8sQ0FBQyxtQ0FBSSxFQUFFLENBQUM7SUFFcEQsTUFBTSxvQkFBb0IsR0FBRyxDQUFDLENBQUMsVUFBVSxDQUFDO1NBQ3ZDLE9BQU8sQ0FBQyxDQUFDLEtBQVksRUFBRSxFQUFFO1FBQ3hCLE9BQU8sQ0FBQyxDQUFDLG1CQUFtQixDQUFDO2FBQzFCLE1BQU0sQ0FBQyxDQUFDLFlBQVksRUFBRSxFQUFFO1lBQ3ZCLE1BQU0sWUFBWSxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsV0FBVyxFQUFFLENBQUM7WUFDakQsT0FBTyxDQUNMLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksWUFBWTtnQkFDckMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksY0FBYyxDQUFDO2dCQUMzQyxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxJQUFJLFlBQVk7b0JBQ3JDLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxJQUFJLGNBQWMsQ0FBQyxDQUM1QyxDQUFDO1FBQ0osQ0FBQyxDQUFDO2FBQ0QsTUFBTSxDQUFDLENBQUMsYUFBYSxFQUFFLEVBQUUsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUM7YUFDaEQsS0FBSyxDQUFDLENBQUMsRUFBRSxxQkFBcUIsQ0FBQzthQUMvQixLQUFLLEVBQUUsQ0FBQztJQUNiLENBQUMsQ0FBQztTQUNELE1BQU0sQ0FBQyxDQUFDLGFBQWEsRUFBRSxFQUFFLENBQUMsQ0FBQyxhQUFhLENBQUMsTUFBTSxDQUFDO1NBQ2hELEtBQUssQ0FBQyxDQUFDLEVBQUUsaUJBQWlCLENBQUM7U0FDM0IsS0FBSyxFQUFFLENBQUM7SUFFWCxNQUFNLHFCQUFxQixHQUFHLENBQUMsQ0FBQyxVQUFVLENBQUM7U0FDeEMsT0FBTyxDQUFDLENBQUMsS0FBWSxFQUFFLEVBQUU7UUFDeEIsT0FBTyxDQUFDLENBQUMsbUJBQW1CLENBQUM7YUFDMUIsTUFBTSxDQUFDLENBQUMsWUFBWSxFQUFFLEVBQUU7WUFDdkIsTUFBTSxZQUFZLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUNqRCxPQUFPLENBQ0wsQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLEVBQUUsSUFBSSxZQUFZO2dCQUNyQyxZQUFZLENBQUMsTUFBTSxDQUFDLEVBQUUsSUFBSSxlQUFlLENBQUM7Z0JBQzVDLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksWUFBWTtvQkFDckMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksZUFBZSxDQUFDLENBQzdDLENBQUM7UUFDSixDQUFDLENBQUM7YUFDRCxNQUFNLENBQUMsQ0FBQyxhQUFhLEVBQUUsRUFBRSxDQUFDLENBQUMsYUFBYSxDQUFDLE1BQU0sQ0FBQzthQUNoRCxLQUFLLENBQUMsQ0FBQyxFQUFFLHFCQUFxQixDQUFDO2FBQy9CLEtBQUssRUFBRSxDQUFDO0lBQ2IsQ0FBQyxDQUFDO1NBQ0QsTUFBTSxDQUFDLENBQUMsYUFBYSxFQUFFLEVBQUUsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUM7U0FDaEQsS0FBSyxDQUFDLENBQUMsRUFBRSxpQkFBaUIsQ0FBQztTQUMzQixLQUFLLEVBQUUsQ0FBQztJQUVYLElBQUksa0JBQWtCLEdBQUcsQ0FBQyxDQUFDLG1CQUFtQixDQUFDO1NBQzVDLE1BQU0sQ0FBQyxDQUFDLFlBQVksRUFBRSxFQUFFO1FBQ3ZCLE9BQU8sQ0FDTCxDQUFDLGtCQUFrQixDQUFDLEdBQUcsQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDO1lBQ3hDLENBQUMsQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLEVBQUUsSUFBSSxjQUFjO2dCQUN4QyxZQUFZLENBQUMsTUFBTSxDQUFDLEVBQUUsSUFBSSxlQUFlLENBQUM7Z0JBQzFDLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksY0FBYztvQkFDdkMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksZUFBZSxDQUFDLENBQUMsQ0FDaEQsQ0FBQztJQUNKLENBQUMsQ0FBQztTQUNELEtBQUssQ0FBQyxDQUFDLEVBQUUsZUFBZSxDQUFDO1NBQ3pCLEtBQUssRUFBRSxDQUFDO0lBRVgsSUFBSSxrQkFBa0IsQ0FBQyxNQUFNLElBQUksQ0FBQyxJQUFJLGVBQWUsR0FBRyxDQUFDLEVBQUU7UUFDekQsZ0ZBQWdGO1FBQ2hGLCtGQUErRjtRQUMvRix1R0FBdUc7UUFDdkcsa0JBQWtCLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FDeEIsQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLFNBQVMsQ0FBQyxNQUFNLEVBQUUsU0FBUyxDQUFDLEdBQUcsRUFBRSxTQUFTLENBQUMsTUFBTSxDQUFDLEVBQ25FLENBQUMsU0FBUyxFQUFFLEVBQUU7WUFDWixNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsR0FBRyxZQUFZLENBQUMsY0FBYyxDQUNqRSxPQUFPLEVBQ1AsUUFBUSxFQUNSLFNBQVMsQ0FDVixDQUFDO1lBQ0YsT0FBTztnQkFDTCxFQUFFLEVBQUUsV0FBVztnQkFDZixPQUFPLEVBQUUsZ0JBQWdCLENBQUMsU0FBUyxDQUFDO2dCQUNwQyxTQUFTLEVBQUUsT0FBTztnQkFDbEIsTUFBTSxFQUFFO29CQUNOLEVBQUUsRUFBRSxNQUFNLENBQUMsT0FBTztpQkFDbkI7Z0JBQ0QsTUFBTSxFQUFFO29CQUNOLEVBQUUsRUFBRSxNQUFNLENBQUMsT0FBTztpQkFDbkI7Z0JBQ0QsTUFBTSxFQUFFLEtBQUs7Z0JBQ2IsTUFBTSxFQUFFLEtBQUs7YUFDZCxDQUFDO1FBQ0osQ0FBQyxDQUNGLENBQUM7S0FDSDtJQUVELGVBQWUsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDO0lBRXBDLE1BQU0sb0JBQW9CLEdBQUcsTUFBQSx1QkFBdUIsQ0FBQyxPQUFPLENBQUMsMENBQUUsT0FBTyxDQUFDO0lBRXZFLHlGQUF5RjtJQUN6RixxR0FBcUc7SUFDckcsOEJBQThCO0lBQzlCLElBQUkscUJBQXFCLEdBQXFCLEVBQUUsQ0FBQztJQUNqRCxJQUNFLENBQUEsTUFBQSx1QkFBdUIsQ0FBQyxPQUFPLENBQUMsMENBQUUsTUFBTTtTQUN0QyxNQUFBLHVCQUF1QixDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsMENBQUUsTUFBTSxDQUFBO1FBQzlDLFFBQVEsQ0FBQyxNQUFNLElBQUksTUFBTTtRQUN6QixRQUFRLENBQUMsTUFBTSxJQUFJLEtBQUssRUFDeEI7UUFDQSxxQkFBcUIsR0FBRyxDQUFDLENBQUMsbUJBQW1CLENBQUM7YUFDM0MsTUFBTSxDQUFDLENBQUMsWUFBWSxFQUFFLEVBQUU7WUFDdkIsSUFBSSxTQUFTLElBQUksU0FBUyxDQUFDLFdBQVcsRUFBRTtnQkFDdEMsT0FBTyxDQUNMLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksb0JBQW9CO29CQUM3QyxZQUFZLENBQUMsTUFBTSxDQUFDLEVBQUUsSUFBSSxlQUFlLENBQUM7b0JBQzVDLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksb0JBQW9CO3dCQUM3QyxZQUFZLENBQUMsTUFBTSxDQUFDLEVBQUUsSUFBSSxlQUFlLENBQUMsQ0FDN0MsQ0FBQzthQUNIO2lCQUFNO2dCQUNMLE9BQU8sQ0FDTCxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxJQUFJLG9CQUFvQjtvQkFDN0MsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksY0FBYyxDQUFDO29CQUMzQyxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxJQUFJLG9CQUFvQjt3QkFDN0MsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksY0FBYyxDQUFDLENBQzVDLENBQUM7YUFDSDtRQUNILENBQUMsQ0FBQzthQUNELEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDO2FBQ1gsS0FBSyxFQUFFLENBQUM7S0FDWjtJQUVELGVBQWUsQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO0lBRXZDLE1BQU0sUUFBUSxHQUFHLENBQUMsQ0FBQyxtQkFBbUIsQ0FBQztTQUNwQyxNQUFNLENBQUMsQ0FBQyxZQUFZLEVBQUUsRUFBRTtRQUN2QixPQUFPLENBQUMsa0JBQWtCLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUNsRCxDQUFDLENBQUM7U0FDRCxLQUFLLENBQUMsQ0FBQyxFQUFFLElBQUksQ0FBQztTQUNkLEtBQUssRUFBRSxDQUFDO0lBRVgsZUFBZSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBRTFCLE1BQU0sb0JBQW9CLEdBQUcsQ0FBQyxDQUFDLG1CQUFtQixDQUFDO1NBQ2hELE1BQU0sQ0FBQyxDQUFDLFlBQVksRUFBRSxFQUFFO1FBQ3ZCLE9BQU8sQ0FDTCxDQUFDLGtCQUFrQixDQUFDLEdBQUcsQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDO1lBQ3hDLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksY0FBYztnQkFDdkMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksY0FBYyxDQUFDLENBQzVDLENBQUM7SUFDSixDQUFDLENBQUM7U0FDRCxLQUFLLENBQUMsQ0FBQyxFQUFFLGNBQWMsQ0FBQztTQUN4QixLQUFLLEVBQUUsQ0FBQztJQUVYLGVBQWUsQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO0lBRXRDLE1BQU0scUJBQXFCLEdBQUcsQ0FBQyxDQUFDLG1CQUFtQixDQUFDO1NBQ2pELE1BQU0sQ0FBQyxDQUFDLFlBQVksRUFBRSxFQUFFO1FBQ3ZCLE9BQU8sQ0FDTCxDQUFDLGtCQUFrQixDQUFDLEdBQUcsQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDO1lBQ3hDLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksZUFBZTtnQkFDeEMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksZUFBZSxDQUFDLENBQzdDLENBQUM7SUFDSixDQUFDLENBQUM7U0FDRCxLQUFLLENBQUMsQ0FBQyxFQUFFLGNBQWMsQ0FBQztTQUN4QixLQUFLLEVBQUUsQ0FBQztJQUVYLGVBQWUsQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO0lBRXZDLE1BQU0sOEJBQThCLEdBQUcsQ0FBQyxDQUFDLG9CQUFvQixDQUFDO1NBQzNELEdBQUcsQ0FBQyxDQUFDLFlBQVksRUFBRSxFQUFFO1FBQ3BCLE9BQU8sY0FBYyxJQUFJLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRTtZQUM3QyxDQUFDLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFO1lBQ3hCLENBQUMsQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztJQUM3QixDQUFDLENBQUM7U0FDRCxPQUFPLENBQUMsQ0FBQyxXQUFtQixFQUFFLEVBQUU7O1FBQy9CLE9BQU8sQ0FBQyxDQUFDLG1CQUFtQixDQUFDO2FBQzFCLE1BQU0sQ0FBQyxDQUFDLFlBQVksRUFBRSxFQUFFO1lBQ3ZCLE9BQU8sQ0FDTCxDQUFDLGtCQUFrQixDQUFDLEdBQUcsQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDO2dCQUN4QyxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxJQUFJLFdBQVc7b0JBQ3BDLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxJQUFJLFdBQVcsQ0FBQyxDQUN6QyxDQUFDO1FBQ0osQ0FBQyxDQUFDO2FBQ0QsS0FBSyxDQUNKLENBQUMsRUFDRCxNQUFBLDRCQUE0QixhQUE1Qiw0QkFBNEIsdUJBQTVCLDRCQUE0QixDQUFFLEdBQUcsQ0FBQyxXQUFXLENBQUMsbUNBQUksYUFBYSxDQUNoRTthQUNBLEtBQUssRUFBRSxDQUFDO0lBQ2IsQ0FBQyxDQUFDO1NBQ0QsTUFBTSxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1NBQ3pCLEtBQUssRUFBRSxDQUFDO0lBRVgsZUFBZSxDQUFDLDhCQUE4QixDQUFDLENBQUM7SUFFaEQsTUFBTSwrQkFBK0IsR0FBRyxDQUFDLENBQUMscUJBQXFCLENBQUM7U0FDN0QsR0FBRyxDQUFDLENBQUMsWUFBWSxFQUFFLEVBQUU7UUFDcEIsT0FBTyxlQUFlLElBQUksWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFO1lBQzlDLENBQUMsQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLEVBQUU7WUFDeEIsQ0FBQyxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDO0lBQzdCLENBQUMsQ0FBQztTQUNELE9BQU8sQ0FBQyxDQUFDLFdBQW1CLEVBQUUsRUFBRTs7UUFDL0IsT0FBTyxDQUFDLENBQUMsbUJBQW1CLENBQUM7YUFDMUIsTUFBTSxDQUFDLENBQUMsWUFBWSxFQUFFLEVBQUU7WUFDdkIsT0FBTyxDQUNMLENBQUMsa0JBQWtCLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUM7Z0JBQ3hDLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksV0FBVztvQkFDcEMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksV0FBVyxDQUFDLENBQ3pDLENBQUM7UUFDSixDQUFDLENBQUM7YUFDRCxLQUFLLENBQ0osQ0FBQyxFQUNELE1BQUEsNEJBQTRCLGFBQTVCLDRCQUE0Qix1QkFBNUIsNEJBQTRCLENBQUUsR0FBRyxDQUFDLFdBQVcsQ0FBQyxtQ0FBSSxhQUFhLENBQ2hFO2FBQ0EsS0FBSyxFQUFFLENBQUM7SUFDYixDQUFDLENBQUM7U0FDRCxNQUFNLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7U0FDekIsS0FBSyxFQUFFLENBQUM7SUFFWCxlQUFlLENBQUMsK0JBQStCLENBQUMsQ0FBQztJQUVqRCxNQUFNLGFBQWEsR0FBRyxDQUFDLENBQUM7UUFDdEIsR0FBRyxvQkFBb0I7UUFDdkIsR0FBRyxxQkFBcUI7UUFDeEIsR0FBRyxrQkFBa0I7UUFDckIsR0FBRyxxQkFBcUI7UUFDeEIsR0FBRyxRQUFRO1FBQ1gsR0FBRyxvQkFBb0I7UUFDdkIsR0FBRyxxQkFBcUI7UUFDeEIsR0FBRyw4QkFBOEI7UUFDakMsR0FBRywrQkFBK0I7S0FDbkMsQ0FBQztTQUNDLE9BQU8sRUFBRTtTQUNULE1BQU0sQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztTQUN6QixLQUFLLEVBQUUsQ0FBQztJQUVYLE1BQU0sY0FBYyxHQUFHLENBQUMsQ0FBQyxhQUFhLENBQUM7U0FDcEMsT0FBTyxDQUFDLENBQUMsWUFBWSxFQUFFLEVBQUUsQ0FBQyxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxFQUFFLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLENBQUM7U0FDM0UsT0FBTyxFQUFFO1NBQ1QsSUFBSSxFQUFFO1NBQ04sS0FBSyxFQUFFLENBQUM7SUFFWCxHQUFHLENBQUMsSUFBSSxDQUNOLGVBQWUsY0FBYyxDQUFDLE1BQU0sc0JBQXNCLGFBQWEsQ0FBQyxNQUFNLDhCQUE4QixDQUM3RyxDQUFDO0lBRUYsTUFBTSxhQUFhLEdBQUcsTUFBTSxhQUFhLENBQUMsU0FBUyxDQUFDLGNBQWMsRUFBRTtRQUNsRSxXQUFXO0tBQ1osQ0FBQyxDQUFDO0lBRUgsTUFBTSxtQkFBbUIsR0FBRyxDQUFDLENBQWlCLEVBQUUsRUFBRTs7UUFDaEQsT0FBQSxHQUFHLE1BQUEsTUFBQSxhQUFhLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsMENBQUUsTUFBTSxtQ0FBSSxDQUFDLENBQUMsTUFBTSxDQUFDLEVBQUUsSUFDcEUsTUFBQSxNQUFBLGFBQWEsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQywwQ0FBRSxNQUFNLG1DQUFJLENBQUMsQ0FBQyxNQUFNLENBQUMsRUFDbkUsSUFBSSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUE7S0FBQSxDQUFDO0lBRWxCLEdBQUcsQ0FBQyxJQUFJLENBQ047UUFDRSxvQkFBb0IsRUFBRSxvQkFBb0IsQ0FBQyxHQUFHLENBQUMsbUJBQW1CLENBQUM7UUFDbkUscUJBQXFCLEVBQUUscUJBQXFCLENBQUMsR0FBRyxDQUFDLG1CQUFtQixDQUFDO1FBQ3JFLFFBQVEsRUFBRSxRQUFRLENBQUMsR0FBRyxDQUFDLG1CQUFtQixDQUFDO1FBQzNDLG9CQUFvQixFQUFFLG9CQUFvQixDQUFDLEdBQUcsQ0FBQyxtQkFBbUIsQ0FBQztRQUNuRSxxQkFBcUIsRUFBRSxxQkFBcUIsQ0FBQyxHQUFHLENBQUMsbUJBQW1CLENBQUM7UUFDckUsOEJBQThCLEVBQzVCLDhCQUE4QixDQUFDLEdBQUcsQ0FBQyxtQkFBbUIsQ0FBQztRQUN6RCwrQkFBK0IsRUFDN0IsK0JBQStCLENBQUMsR0FBRyxDQUFDLG1CQUFtQixDQUFDO1FBQzFELGNBQWMsRUFBRSxrQkFBa0IsQ0FBQyxHQUFHLENBQUMsbUJBQW1CLENBQUM7UUFDM0QsZ0JBQWdCLEVBQUUscUJBQXFCLENBQUMsR0FBRyxDQUFDLG1CQUFtQixDQUFDO0tBQ2pFLEVBQ0Qsb0JBQW9CLENBQ3JCLENBQUM7SUFFRixNQUFNLGFBQWEsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUd6QixhQUFhLEVBQUUsQ0FBQyxZQUFZLEVBQUUsRUFBRTtRQUNoQyxNQUFNLE1BQU0sR0FBRyxhQUFhLENBQUMsaUJBQWlCLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUN2RSxNQUFNLE1BQU0sR0FBRyxhQUFhLENBQUMsaUJBQWlCLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUN2RSxJQUFJLEdBQWMsQ0FBQztRQUNuQixJQUFJO1lBQ0YsR0FBRyxHQUFHLGNBQWMsQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLENBQUM7U0FDNUM7UUFBQyxPQUFPLEdBQUcsRUFBRTtZQUNaLEdBQUcsQ0FBQyxJQUFJLENBQ04sRUFBRSxZQUFZLEVBQUUsRUFDaEIsK0JBQStCLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxJQUFJLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxJQUFJLFlBQVksQ0FBQyxPQUFPLGlDQUFpQyxDQUN6SSxDQUFDO1lBQ0YsT0FBTyxTQUFTLENBQUM7U0FDbEI7UUFFRCxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsTUFBTSxFQUFFO1lBQ3RCLEdBQUcsQ0FBQyxJQUFJLENBQ04sK0JBQStCLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxJQUNuRCxZQUFZLENBQUMsTUFBTSxDQUFDLEVBQ3RCLElBQUksR0FBRyxZQUNMLE1BQU0sQ0FBQyxDQUFDLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFDeEQsOEJBQThCLENBQy9CLENBQUM7WUFDRixPQUFPLFNBQVMsQ0FBQztTQUNsQjtRQUVELE9BQU8sQ0FBQyxNQUFNLEVBQUUsTUFBTSxFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBQy9CLENBQUMsQ0FBQyxDQUFDO0lBRUgsTUFBTSxVQUFVLEdBQUcsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxhQUFhLENBQUMsQ0FBQztJQUU1QyxNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7SUFFbkMsTUFBTSxZQUFZLEdBQUcsTUFBTSxZQUFZLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBRTdELE1BQU0sQ0FBQyxTQUFTLENBQ2QsYUFBYSxFQUNiLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxlQUFlLEVBQzVCLGdCQUFnQixDQUFDLFlBQVksQ0FDOUIsQ0FBQztJQUVGLE1BQU0sZ0JBQWdCLEdBQXNDO1FBQzFELFFBQVEsRUFBRSxRQUFRLENBQUMsRUFBRTtRQUNyQixVQUFVLEVBQUU7WUFDVixvQkFBb0I7WUFDcEIscUJBQXFCO1lBQ3JCLG1CQUFtQixFQUFFLGtCQUFrQjtZQUN2QyxzQkFBc0IsRUFBRSxxQkFBcUI7WUFDN0MsUUFBUTtZQUNSLG9CQUFvQjtZQUNwQixxQkFBcUI7WUFDckIsOEJBQThCO1lBQzlCLCtCQUErQjtTQUNoQztLQUNGLENBQUM7SUFFRixPQUFPLEVBQUUsWUFBWSxFQUFFLGNBQWMsRUFBRSxnQkFBZ0IsRUFBRSxhQUFhLEVBQUUsQ0FBQztBQUMzRSxDQUFDO0FBRUQsTUFBTSxDQUFDLEtBQUssVUFBVSxtQkFBbUIsQ0FBQyxFQUN4QyxPQUFPLEVBQ1AsUUFBUSxFQUNSLFNBQVMsRUFDVCxhQUFhLEVBQ2IsZ0JBQWdCLEVBQ2hCLGFBQWEsRUFDYixZQUFZLEVBQ1osd0JBQXdCLEVBQ3hCLE9BQU8sR0FDbUI7O0lBSzFCLE1BQU0sRUFDSixXQUFXLEVBQ1gsZUFBZSxFQUFFLEVBQ2YsSUFBSSxFQUNKLGVBQWUsRUFDZixjQUFjLEVBQ2QsYUFBYSxFQUNiLHFCQUFxQixFQUNyQixpQkFBaUIsR0FDbEIsR0FDRixHQUFHLGFBQWEsQ0FBQztJQUNsQixNQUFNLGNBQWMsR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQ3JELE1BQU0sZUFBZSxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsV0FBVyxFQUFFLENBQUM7SUFFdkQsTUFBTSxtQkFBbUIsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7SUFFdkMsTUFBTSxXQUFXLEdBQUcsTUFBTSxnQkFBZ0IsQ0FBQyxRQUFRLENBQUMsT0FBTyxFQUFFLFFBQVEsRUFBRTtRQUNyRSxXQUFXO0tBQ1osQ0FBQyxDQUFDO0lBRUgsTUFBTSxRQUFRLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxJQUFJLEVBQUUsRUFBRTtRQUMzQyxPQUFPO1lBQ0wsR0FBRyxJQUFJO1lBQ1AsTUFBTSxFQUFFO2dCQUNOLEdBQUcsSUFBSSxDQUFDLE1BQU07Z0JBQ2QsRUFBRSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLFdBQVcsRUFBRTthQUNqQztZQUNELE1BQU0sRUFBRTtnQkFDTixHQUFHLElBQUksQ0FBQyxNQUFNO2dCQUNkLEVBQUUsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxXQUFXLEVBQUU7YUFDakM7U0FDRixDQUFDO0lBQ0osQ0FBQyxDQUFDLENBQUM7SUFFSCxNQUFNLENBQUMsU0FBUyxDQUNkLHFCQUFxQixFQUNyQixJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsbUJBQW1CLEVBQ2hDLGdCQUFnQixDQUFDLFlBQVksQ0FDOUIsQ0FBQztJQUVGLDBFQUEwRTtJQUMxRSxJQUFJLGFBQWEsR0FBcUIsUUFBUSxDQUFDO0lBQy9DLElBQUksd0JBQXdCLEVBQUU7UUFDNUIsYUFBYSxHQUFHLEVBQUUsQ0FBQztRQUNuQixLQUFLLE1BQU0sSU