@uniswap/smart-order-router
Version:
Uniswap Smart Order Router
923 lines • 156 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMixedRouteCandidatePools = exports.getV2CandidatePools = exports.getV3CandidatePools = exports.getV4CandidatePools = exports.getMixedCrossLiquidityCandidatePools = void 0;
const router_sdk_1 = require("@uniswap/router-sdk");
const sdk_core_1 = require("@uniswap/sdk-core");
const universal_router_sdk_1 = require("@uniswap/universal-router-sdk");
const v4_sdk_1 = require("@uniswap/v4-sdk");
const lodash_1 = __importDefault(require("lodash"));
const providers_1 = require("../../../providers");
const token_provider_1 = require("../../../providers/token-provider");
const util_1 = require("../../../util");
const amounts_1 = require("../../../util/amounts");
const log_1 = require("../../../util/log");
const metric_1 = require("../../../util/metric");
const baseTokensByChain = {
[sdk_core_1.ChainId.MAINNET]: [
token_provider_1.USDC_MAINNET,
token_provider_1.USDT_MAINNET,
token_provider_1.WBTC_MAINNET,
token_provider_1.DAI_MAINNET,
util_1.WRAPPED_NATIVE_CURRENCY[1],
token_provider_1.FEI_MAINNET,
token_provider_1.WSTETH_MAINNET,
],
[sdk_core_1.ChainId.OPTIMISM]: [
token_provider_1.DAI_OPTIMISM,
token_provider_1.USDC_OPTIMISM,
token_provider_1.USDT_OPTIMISM,
token_provider_1.WBTC_OPTIMISM,
],
[sdk_core_1.ChainId.SEPOLIA]: [token_provider_1.DAI_SEPOLIA, token_provider_1.USDC_SEPOLIA],
[sdk_core_1.ChainId.OPTIMISM_GOERLI]: [
token_provider_1.DAI_OPTIMISM_GOERLI,
token_provider_1.USDC_OPTIMISM_GOERLI,
token_provider_1.USDT_OPTIMISM_GOERLI,
token_provider_1.WBTC_OPTIMISM_GOERLI,
],
[sdk_core_1.ChainId.OPTIMISM_SEPOLIA]: [
providers_1.DAI_OPTIMISM_SEPOLIA,
providers_1.USDC_OPTIMISM_SEPOLIA,
providers_1.USDT_OPTIMISM_SEPOLIA,
providers_1.WBTC_OPTIMISM_SEPOLIA,
],
[sdk_core_1.ChainId.ARBITRUM_ONE]: [
token_provider_1.DAI_ARBITRUM,
token_provider_1.USDC_ARBITRUM,
token_provider_1.WBTC_ARBITRUM,
token_provider_1.USDT_ARBITRUM,
],
[sdk_core_1.ChainId.ARBITRUM_GOERLI]: [token_provider_1.USDC_ARBITRUM_GOERLI],
[sdk_core_1.ChainId.ARBITRUM_SEPOLIA]: [providers_1.USDC_ARBITRUM_SEPOLIA],
[sdk_core_1.ChainId.POLYGON]: [token_provider_1.USDC_POLYGON, token_provider_1.WMATIC_POLYGON],
[sdk_core_1.ChainId.POLYGON_MUMBAI]: [token_provider_1.DAI_POLYGON_MUMBAI, token_provider_1.WMATIC_POLYGON_MUMBAI],
[sdk_core_1.ChainId.CELO]: [token_provider_1.CUSD_CELO, token_provider_1.CEUR_CELO, token_provider_1.CELO],
[sdk_core_1.ChainId.CELO_ALFAJORES]: [
token_provider_1.CUSD_CELO_ALFAJORES,
token_provider_1.CEUR_CELO_ALFAJORES,
token_provider_1.CELO_ALFAJORES,
],
[sdk_core_1.ChainId.GNOSIS]: [token_provider_1.WBTC_GNOSIS, token_provider_1.WXDAI_GNOSIS, token_provider_1.USDC_ETHEREUM_GNOSIS],
[sdk_core_1.ChainId.MOONBEAM]: [
token_provider_1.DAI_MOONBEAM,
token_provider_1.USDC_MOONBEAM,
token_provider_1.WBTC_MOONBEAM,
token_provider_1.WGLMR_MOONBEAM,
],
[sdk_core_1.ChainId.BNB]: [token_provider_1.DAI_BNB, token_provider_1.USDC_BNB, token_provider_1.USDT_BNB],
[sdk_core_1.ChainId.AVALANCHE]: [token_provider_1.DAI_AVAX, token_provider_1.USDC_AVAX],
[sdk_core_1.ChainId.BASE]: [token_provider_1.USDC_BASE],
[sdk_core_1.ChainId.BLAST]: [util_1.WRAPPED_NATIVE_CURRENCY[sdk_core_1.ChainId.BLAST], token_provider_1.USDB_BLAST],
[sdk_core_1.ChainId.ZORA]: [util_1.WRAPPED_NATIVE_CURRENCY[sdk_core_1.ChainId.ZORA]],
[sdk_core_1.ChainId.ZKSYNC]: [util_1.WRAPPED_NATIVE_CURRENCY[sdk_core_1.ChainId.ZKSYNC]],
[sdk_core_1.ChainId.WORLDCHAIN]: [util_1.WRAPPED_NATIVE_CURRENCY[sdk_core_1.ChainId.WORLDCHAIN]],
[sdk_core_1.ChainId.UNICHAIN_SEPOLIA]: [
util_1.WRAPPED_NATIVE_CURRENCY[sdk_core_1.ChainId.UNICHAIN_SEPOLIA],
],
[sdk_core_1.ChainId.MONAD_TESTNET]: [
util_1.WRAPPED_NATIVE_CURRENCY[sdk_core_1.ChainId.MONAD_TESTNET],
token_provider_1.USDT_MONAD_TESTNET,
],
[sdk_core_1.ChainId.MONAD]: [util_1.WRAPPED_NATIVE_CURRENCY[sdk_core_1.ChainId.MONAD], token_provider_1.USDC_MONAD],
[sdk_core_1.ChainId.BASE_SEPOLIA]: [
util_1.WRAPPED_NATIVE_CURRENCY[sdk_core_1.ChainId.BASE_SEPOLIA],
token_provider_1.USDC_BASE_SEPOLIA,
],
[sdk_core_1.ChainId.UNICHAIN]: [
util_1.WRAPPED_NATIVE_CURRENCY[sdk_core_1.ChainId.UNICHAIN],
token_provider_1.DAI_UNICHAIN,
token_provider_1.USDC_UNICHAIN,
],
[sdk_core_1.ChainId.SONEIUM]: [token_provider_1.USDC_SONEIUM, util_1.WRAPPED_NATIVE_CURRENCY[sdk_core_1.ChainId.SONEIUM]],
[sdk_core_1.ChainId.XLAYER]: [token_provider_1.USDC_XLAYER, util_1.WRAPPED_NATIVE_CURRENCY[sdk_core_1.ChainId.XLAYER]],
};
const excludedV3PoolIds = new Set([
// https://linear.app/uniswap/issue/CX-1005
'0x0f681f10ab1aa1cde04232a199fe3c6f2652a80c'.toLowerCase(),
]);
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.
*
*/
async function getMixedCrossLiquidityCandidatePools({ tokenIn, tokenOut, blockNumber, v2SubgraphProvider, v3SubgraphProvider, v2Candidates, v3Candidates, v4Candidates, mixedCrossLiquidityV3AgainstV4Supported, chainId, }) {
var _a, _b, _c, _d, _e, _f, _g, _h;
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 v2AgainstV3SelectedPools = findCrossProtocolMissingPools(tokenInAddress, tokenOutAddress, v2Pools, v2Candidates, v3Candidates);
const v3AgainstV2SelectedPools = findCrossProtocolMissingPools(tokenInAddress, tokenOutAddress, v3Pools, v3Candidates, v2Candidates);
const v3AgainstV4SelectedPools = chainId && (mixedCrossLiquidityV3AgainstV4Supported === null || mixedCrossLiquidityV3AgainstV4Supported === void 0 ? void 0 : mixedCrossLiquidityV3AgainstV4Supported.includes(chainId))
? findCrossProtocolMissingPools(tokenInAddress, tokenOutAddress, v3Pools, v3Candidates, v4Candidates)
: { forTokenIn: undefined, forTokenOut: undefined };
// this is for deduplicate v3 pools, in case both v4 and v2 select the same v3 pools for tokenIn/tokenOut
if (((_a = v3AgainstV4SelectedPools.forTokenIn) === null || _a === void 0 ? void 0 : _a.id) ===
((_b = v3AgainstV2SelectedPools.forTokenIn) === null || _b === void 0 ? void 0 : _b.id) ||
((_c = v3AgainstV4SelectedPools.forTokenIn) === null || _c === void 0 ? void 0 : _c.id) ===
((_d = v3AgainstV2SelectedPools.forTokenOut) === null || _d === void 0 ? void 0 : _d.id)) {
v3AgainstV4SelectedPools.forTokenIn = undefined;
}
if (((_e = v3AgainstV4SelectedPools.forTokenOut) === null || _e === void 0 ? void 0 : _e.id) ===
((_f = v3AgainstV2SelectedPools.forTokenIn) === null || _f === void 0 ? void 0 : _f.id) ||
((_g = v3AgainstV4SelectedPools.forTokenOut) === null || _g === void 0 ? void 0 : _g.id) ===
((_h = v3AgainstV2SelectedPools.forTokenOut) === null || _h === void 0 ? void 0 : _h.id)) {
v3AgainstV4SelectedPools.forTokenOut = undefined;
}
const selectedV2Pools = [
v2AgainstV3SelectedPools.forTokenIn,
v2AgainstV3SelectedPools.forTokenOut,
].filter((pool) => pool !== undefined);
const selectedV3Pools = [
v3AgainstV2SelectedPools.forTokenIn,
v3AgainstV2SelectedPools.forTokenOut,
v3AgainstV4SelectedPools.forTokenIn,
v3AgainstV4SelectedPools.forTokenOut,
].filter((pool) => pool !== undefined);
return {
v2Pools: selectedV2Pools,
v3Pools: selectedV3Pools,
v4Pools: [],
};
}
exports.getMixedCrossLiquidityCandidatePools = getMixedCrossLiquidityCandidatePools;
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
async function getV4CandidatePools({ currencyIn, currencyOut, routeType, routingConfig, subgraphProvider, tokenProvider, poolProvider, blockedTokenListProvider, chainId, v4PoolParams = (0, util_1.getApplicableV4FeesTickspacingsHooks)(chainId), }) {
var _a, _b, _c, _d, _e;
const { blockNumber, v4PoolSelection: { topN, topNDirectSwaps, topNTokenInOut, topNSecondHop, topNSecondHopForTokenAddress, tokensToAvoidOnSecondHops, topNWithEachBaseToken, topNWithBaseToken, }, } = routingConfig;
const tokenInAddress = (0, util_1.getAddressLowerCase)(currencyIn);
const tokenOutAddress = (0, util_1.getAddressLowerCase)(currencyOut);
const beforeSubgraphPools = Date.now();
const allPools = await subgraphProvider.getPools(currencyIn, currencyOut, {
blockNumber,
});
log_1.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_1.metric.putMetric('V4SubgraphPoolsLoad', Date.now() - beforeSubgraphPools, metric_1.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_1.log.info(`After filtering blocked tokens went from ${allPools.length} to ${subgraphPoolsSorted.length}.`);
const poolAddressesSoFar = new Set();
const addToAddressSet = (pools) => {
(0, lodash_1.default)(pools)
.map((pool) => pool.id)
.forEach((poolAddress) => poolAddressesSoFar.add(poolAddress));
};
const baseTokens = (_a = baseTokensByChain[chainId]) !== null && _a !== void 0 ? _a : [];
const topByBaseWithTokenIn = (0, lodash_1.default)(baseTokens)
.flatMap((token) => {
return (0, lodash_1.default)(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 === util_1.HooksOptions.HOOKS_ONLY) {
return subgraphPool.hooks !== router_sdk_1.ADDRESS_ZERO;
}
// in case of no hooks, it means we want to filter out hook pools
if (routingConfig.hooksOptions === util_1.HooksOptions.NO_HOOKS) {
return subgraphPool.hooks === router_sdk_1.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 = (0, lodash_1.default)(baseTokens)
.flatMap((token) => {
return (0, lodash_1.default)(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 === util_1.HooksOptions.HOOKS_ONLY) {
return subgraphPool.hooks !== router_sdk_1.ADDRESS_ZERO;
}
// in case of no hooks, it means we want to filter out hook pools
if (routingConfig.hooksOptions === util_1.HooksOptions.NO_HOOKS) {
return subgraphPool.hooks === router_sdk_1.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 = (0, lodash_1.default)(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 === util_1.HooksOptions.HOOKS_ONLY) {
return subgraphPool.hooks !== router_sdk_1.ADDRESS_ZERO;
}
// in case of no hooks, it means we want to filter out hook pools
if (routingConfig.hooksOptions === util_1.HooksOptions.NO_HOOKS) {
return subgraphPool.hooks === router_sdk_1.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 !== util_1.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 = lodash_1.default.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: {
symbol: currency0.symbol,
id: (0, util_1.getAddress)(currency0),
name: currency0.name,
decimals: currency0.decimals.toString(),
},
token1: {
symbol: currency1.symbol,
id: (0, util_1.getAddress)(currency1),
name: currency1.name,
decimals: currency1.decimals.toString(),
},
tvlETH: 10000,
tvlUSD: 10000,
};
});
}
addToAddressSet(top2DirectSwapPool);
const wrappedNativeAddress = (_b = util_1.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 = util_1.WRAPPED_NATIVE_CURRENCY[chainId]) === null || _c === void 0 ? void 0 : _c.symbol) ==
((_d = util_1.WRAPPED_NATIVE_CURRENCY[sdk_core_1.ChainId.MAINNET]) === null || _d === void 0 ? void 0 : _d.symbol) &&
currencyOut.symbol != 'WETH' &&
currencyOut.symbol != 'WETH9' &&
currencyOut.symbol != 'ETH') ||
(((_e = util_1.WRAPPED_NATIVE_CURRENCY[chainId]) === null || _e === void 0 ? void 0 : _e.symbol) == token_provider_1.WMATIC_POLYGON.symbol &&
currencyOut.symbol != 'MATIC' &&
currencyOut.symbol != 'WMATIC')) {
top2EthQuoteTokenPool = (0, lodash_1.default)(subgraphPoolsSorted)
.filter((subgraphPool) => {
if (routeType == sdk_core_1.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 = (0, lodash_1.default)(subgraphPoolsSorted)
.filter((subgraphPool) => {
return !poolAddressesSoFar.has(subgraphPool.id);
})
.slice(0, topN)
.value();
addToAddressSet(topByTVL);
const topByTVLUsingTokenIn = (0, lodash_1.default)(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 === util_1.HooksOptions.HOOKS_ONLY) {
return subgraphPool.hooks !== router_sdk_1.ADDRESS_ZERO;
}
// in case of no hooks, it means we want to filter out hook pools
if (routingConfig.hooksOptions === util_1.HooksOptions.NO_HOOKS) {
return subgraphPool.hooks === router_sdk_1.ADDRESS_ZERO;
}
// otherwise it's the default case, so we just return true
return true;
})
.slice(0, topNTokenInOut)
.value();
addToAddressSet(topByTVLUsingTokenIn);
const topByTVLUsingTokenOut = (0, lodash_1.default)(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 === util_1.HooksOptions.HOOKS_ONLY) {
return subgraphPool.hooks !== router_sdk_1.ADDRESS_ZERO;
}
// in case of no hooks, it means we want to filter out hook pools
if (routingConfig.hooksOptions === util_1.HooksOptions.NO_HOOKS) {
return subgraphPool.hooks === router_sdk_1.ADDRESS_ZERO;
}
// otherwise it's the default case, so we just return true
return true;
})
.slice(0, topNTokenInOut)
.value();
addToAddressSet(topByTVLUsingTokenOut);
const topByTVLUsingTokenInSecondHops = (0, lodash_1.default)(topByTVLUsingTokenIn)
.map((subgraphPool) => {
return tokenInAddress == subgraphPool.token0.id
? subgraphPool.token1.id
: subgraphPool.token0.id;
})
.flatMap((secondHopId) => {
var _a;
return (0, lodash_1.default)(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 === util_1.HooksOptions.HOOKS_ONLY) {
return subgraphPool.hooks !== router_sdk_1.ADDRESS_ZERO;
}
// in case of no hooks, it means we want to filter out hook pools
if (routingConfig.hooksOptions === util_1.HooksOptions.NO_HOOKS) {
return subgraphPool.hooks === router_sdk_1.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 = (0, lodash_1.default)(topByTVLUsingTokenOut)
.map((subgraphPool) => {
return tokenOutAddress == subgraphPool.token0.id
? subgraphPool.token1.id
: subgraphPool.token0.id;
})
.flatMap((secondHopId) => {
var _a;
return (0, lodash_1.default)(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 === util_1.HooksOptions.HOOKS_ONLY) {
return subgraphPool.hooks !== router_sdk_1.ADDRESS_ZERO;
}
// in case of no hooks, it means we want to filter out hook pools
if (routingConfig.hooksOptions === util_1.HooksOptions.NO_HOOKS) {
return subgraphPool.hooks === router_sdk_1.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 = (0, lodash_1.default)([
...topByBaseWithTokenIn,
...topByBaseWithTokenOut,
...top2DirectSwapPool,
...top2EthQuoteTokenPool,
...topByTVL,
...topByTVLUsingTokenIn,
...topByTVLUsingTokenOut,
...topByTVLUsingTokenInSecondHops,
...topByTVLUsingTokenOutSecondHops,
])
.compact()
.uniqBy((pool) => pool.id)
.value();
const tokenAddresses = (0, lodash_1.default)(subgraphPools)
.flatMap((subgraphPool) => [subgraphPool.token0.id, subgraphPool.token1.id])
.compact()
.uniq()
.value();
log_1.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_1.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 = lodash_1.default.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 = (0, universal_router_sdk_1.isNativeCurrency)(subgraphPool.token0.id)
? (0, util_1.nativeOnChain)(chainId)
: tokenAccessor.getTokenByAddress(subgraphPool.token0.id);
const tokenB = (0, universal_router_sdk_1.isNativeCurrency)(subgraphPool.token1.id)
? (0, util_1.nativeOnChain)(chainId)
: tokenAccessor.getTokenByAddress(subgraphPool.token1.id);
let fee;
try {
fee = Number(subgraphPool.feeTier);
fee = (0, providers_1.isPoolFeeDynamic)(tokenA, tokenB, Number(subgraphPool.tickSpacing), subgraphPool.hooks, subgraphPool.id)
? v4_sdk_1.DYNAMIC_FEE_FLAG
: fee;
}
catch (err) {
log_1.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_1.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 = lodash_1.default.compact(tokenPairsRaw);
metric_1.metric.putMetric('V4PoolsFilterLoad', Date.now() - beforePoolsFiltered, metric_1.MetricLoggerUnit.Milliseconds);
const beforePoolsLoad = Date.now();
const poolAccessor = await poolProvider.getPools(tokenPairs, {
blockNumber,
});
metric_1.metric.putMetric('V4PoolsLoad', Date.now() - beforePoolsLoad, metric_1.MetricLoggerUnit.Milliseconds);
const poolsBySelection = {
protocol: router_sdk_1.Protocol.V4,
selections: {
topByBaseWithTokenIn,
topByBaseWithTokenOut,
topByDirectSwapPool: top2DirectSwapPool,
topByEthQuoteTokenPool: top2EthQuoteTokenPool,
topByTVL,
topByTVLUsingTokenIn,
topByTVLUsingTokenOut,
topByTVLUsingTokenInSecondHops,
topByTVLUsingTokenOutSecondHops,
},
};
return { poolAccessor, candidatePools: poolsBySelection, subgraphPools };
}
exports.getV4CandidatePools = getV4CandidatePools;
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_1.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_1.metric.putMetric('V3SubgraphPoolsLoad', Date.now() - beforeSubgraphPools, metric_1.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_1.log.info(`After filtering blocked tokens went from ${allPools.length} to ${subgraphPoolsSorted.length}.`);
const poolAddressesSoFar = new Set();
const addToAddressSet = (pools) => {
(0, lodash_1.default)(pools)
.map((pool) => pool.id)
.forEach((poolAddress) => poolAddressesSoFar.add(poolAddress));
};
const baseTokens = (_a = baseTokensByChain[chainId]) !== null && _a !== void 0 ? _a : [];
const topByBaseWithTokenIn = (0, lodash_1.default)(baseTokens)
.flatMap((token) => {
return (0, lodash_1.default)(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 = (0, lodash_1.default)(baseTokens)
.flatMap((token) => {
return (0, lodash_1.default)(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 = (0, lodash_1.default)(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 == sdk_core_1.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 = lodash_1.default.map((0, util_1.getApplicableV3FeeAmounts)(chainId), (feeAmount) => {
const { token0, token1, poolAddress } = poolProvider.getPoolAddress(tokenIn, tokenOut, feeAmount);
return {
id: poolAddress,
feeTier: (0, util_1.unparseFeeAmount)(feeAmount),
liquidity: '10000',
token0: {
id: token0.address,
},
token1: {
id: token1.address,
},
tvlETH: 10000,
tvlUSD: 10000,
};
});
top2DirectSwapPool = top2DirectSwapPool.filter((pool) => !excludedV3PoolIds.has(pool.id.toLowerCase()));
}
}
addToAddressSet(top2DirectSwapPool);
const wrappedNativeAddress = (_b = util_1.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 = util_1.WRAPPED_NATIVE_CURRENCY[chainId]) === null || _c === void 0 ? void 0 : _c.symbol) ==
((_d = util_1.WRAPPED_NATIVE_CURRENCY[sdk_core_1.ChainId.MAINNET]) === null || _d === void 0 ? void 0 : _d.symbol) &&
tokenOut.symbol != 'WETH' &&
tokenOut.symbol != 'WETH9' &&
tokenOut.symbol != 'ETH') ||
(((_e = util_1.WRAPPED_NATIVE_CURRENCY[chainId]) === null || _e === void 0 ? void 0 : _e.symbol) == token_provider_1.WMATIC_POLYGON.symbol &&
tokenOut.symbol != 'MATIC' &&
tokenOut.symbol != 'WMATIC')) {
top2EthQuoteTokenPool = (0, lodash_1.default)(subgraphPoolsSorted)
.filter((subgraphPool) => {
if (routeType == sdk_core_1.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 = (0, lodash_1.default)(subgraphPoolsSorted)
.filter((subgraphPool) => {
return !poolAddressesSoFar.has(subgraphPool.id);
})
.slice(0, topN)
.value();
addToAddressSet(topByTVL);
const topByTVLUsingTokenIn = (0, lodash_1.default)(subgraphPoolsSorted)
.filter((subgraphPool) => {
return (!poolAddressesSoFar.has(subgraphPool.id) &&
(subgraphPool.token0.id == tokenInAddress ||
subgraphPool.token1.id == tokenInAddress));
})
.slice(0, topNTokenInOut)
.value();
addToAddressSet(topByTVLUsingTokenIn);
const topByTVLUsingTokenOut = (0, lodash_1.default)(subgraphPoolsSorted)
.filter((subgraphPool) => {
return (!poolAddressesSoFar.has(subgraphPool.id) &&
(subgraphPool.token0.id == tokenOutAddress ||
subgraphPool.token1.id == tokenOutAddress));
})
.slice(0, topNTokenInOut)
.value();
addToAddressSet(topByTVLUsingTokenOut);
const topByTVLUsingTokenInSecondHops = (0, lodash_1.default)(topByTVLUsingTokenIn)
.map((subgraphPool) => {
return tokenInAddress == subgraphPool.token0.id
? subgraphPool.token1.id
: subgraphPool.token0.id;
})
.flatMap((secondHopId) => {
var _a;
return (0, lodash_1.default)(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 = (0, lodash_1.default)(topByTVLUsingTokenOut)
.map((subgraphPool) => {
return tokenOutAddress == subgraphPool.token0.id
? subgraphPool.token1.id
: subgraphPool.token0.id;
})
.flatMap((secondHopId) => {
var _a;
return (0, lodash_1.default)(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 = (0, lodash_1.default)([
...topByBaseWithTokenIn,
...topByBaseWithTokenOut,
...top2DirectSwapPool,
...top2EthQuoteTokenPool,
...topByTVL,
...topByTVLUsingTokenIn,
...topByTVLUsingTokenOut,
...topByTVLUsingTokenInSecondHops,
...topByTVLUsingTokenOutSecondHops,
])
.compact()
.uniqBy((pool) => pool.id)
.value();
const tokenAddresses = (0, lodash_1.default)(subgraphPools)
.flatMap((subgraphPool) => [subgraphPool.token0.id, subgraphPool.token1.id])
.compact()
.uniq()
.value();
log_1.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_1.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 = lodash_1.default.map(subgraphPools, (subgraphPool) => {
const tokenA = tokenAccessor.getTokenByAddress(subgraphPool.token0.id);
const tokenB = tokenAccessor.getTokenByAddress(subgraphPool.token1.id);
let fee;
try {
fee = (0, amounts_1.parseFeeAmount)(subgraphPool.feeTier);
}
catch (err) {
log_1.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_1.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 = lodash_1.default.compact(tokenPairsRaw);
metric_1.metric.putMetric('V3PoolsFilterLoad', Date.now() - beforePoolsFiltered, metric_1.MetricLoggerUnit.Milliseconds);
const beforePoolsLoad = Date.now();
const poolAccessor = await poolProvider.getPools(tokenPairs, {
blockNumber,
});
metric_1.metric.putMetric('V3PoolsLoad', Date.now() - beforePoolsLoad, metric_1.MetricLoggerUnit.Milliseconds);
const poolsBySelection = {
protocol: router_sdk_1.Protocol.V3,
selections: {
topByBaseWithTokenIn,
topByBaseWithTokenOut,
topByDirectSwapPool: top2DirectSwapPool,
topByEthQuoteTokenPool: top2EthQuoteTokenPool,
topByTVL,
topByTVLUsingTokenIn,
topByTVLUsingTokenOut,
topByTVLUsingTokenInSecondHops,
topByTVLUsingTokenOutSecondHops,
},
};
return { poolAccessor, candidatePools: poolsBySelection, subgraphPools };
}
exports.getV3CandidatePools = getV3CandidatePools;
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_1.metric.putMetric('V2SubgraphPoolsLoad', Date.now() - beforeSubgraphPools, metric_1.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(poolA