UNPKG

@uniswap/smart-order-router

Version:
125 lines 14.9 kB
import { ADDRESS_ZERO } from '@uniswap/router-sdk'; import { Pair } from '@uniswap/v2-sdk'; import { Pool as V3Pool } from '@uniswap/v3-sdk'; import { Pool as V4Pool } from '@uniswap/v4-sdk'; import { getAddressLowerCase, nativeOnChain, V4_ETH_WETH_FAKE_POOL, } from '../../../util'; import { HooksOptions } from '../../../util/hooksOptions'; import { log } from '../../../util/log'; import { poolToString, routeToString } from '../../../util/routes'; import { MixedRoute, V2Route, V3Route, V4Route, } from '../../router'; export function computeAllV4Routes(currencyIn, currencyOut, pools, maxHops, hooksOptions) { let filteredPools = pools; if (hooksOptions === HooksOptions.HOOKS_ONLY) { filteredPools = pools.filter((pool) => pool.hooks !== ADDRESS_ZERO); } if (hooksOptions === HooksOptions.NO_HOOKS) { filteredPools = pools.filter((pool) => pool.hooks === ADDRESS_ZERO); } return computeAllRoutes(currencyIn, currencyOut, (route, currencyIn, currencyOut) => { return new V4Route(route, currencyIn, currencyOut); }, (pool, currency) => pool.involvesToken(currency), filteredPools, maxHops); } export function computeAllV3Routes(tokenIn, tokenOut, pools, maxHops) { return computeAllRoutes(tokenIn, tokenOut, (route, tokenIn, tokenOut) => { return new V3Route(route, tokenIn, tokenOut); }, (pool, token) => pool.involvesToken(token), pools, maxHops); } export function computeAllV2Routes(tokenIn, tokenOut, pools, maxHops) { return computeAllRoutes(tokenIn, tokenOut, (route, tokenIn, tokenOut) => { return new V2Route(route, tokenIn, tokenOut); }, (pool, token) => pool.involvesToken(token), pools, maxHops); } export function computeAllMixedRoutes(currencyIn, currencyOut, parts, maxHops, shouldEnableMixedRouteEthWeth, hooksOptions) { // first we need to filter non v4-pools const filteredPools = !hooksOptions || hooksOptions === HooksOptions.HOOKS_INCLUSIVE ? parts : parts.filter((pool) => !(pool instanceof V4Pool)); if (hooksOptions === HooksOptions.HOOKS_ONLY) { // we need to filter out v4-pools with hooks // then concat the v4-pools with hooks const v4HookslessPools = parts.filter((pool) => pool instanceof V4Pool && pool.hooks !== ADDRESS_ZERO); parts = filteredPools.concat(v4HookslessPools); } if (hooksOptions === HooksOptions.NO_HOOKS) { // we need to filter out v4-pools without hooks // then concat the v4-pools without hooks const v4HookfulPools = parts.filter((pool) => pool instanceof V4Pool && pool.hooks === ADDRESS_ZERO); parts = filteredPools.concat(v4HookfulPools); } // only add fake v4 pool, if we see there's a native v4 pool in the candidate pool const containsV4NativePools = parts.filter((pool) => pool instanceof V4Pool && pool.v4InvolvesToken(nativeOnChain(currencyIn.chainId))).length > 0; const amendedPools = containsV4NativePools && shouldEnableMixedRouteEthWeth ? parts.concat(V4_ETH_WETH_FAKE_POOL[currencyIn.chainId]) : parts; // NOTE: we added a fake v4 pool, in order for mixed route to connect the v3 weth pool with v4 eth pool const routesRaw = computeAllRoutes(currencyIn, currencyOut, (route, currencyIn, currencyOut) => { // we only retake the fake v4 pool if the route contains a native v4 pool return new MixedRoute(route, currencyIn, currencyOut, containsV4NativePools); }, (pool, currency) => currency.isNative ? pool.involvesToken(currency) : pool.involvesToken(currency), amendedPools, maxHops); /// filter out pure v4 and v3 and v2 routes return routesRaw.filter((route) => { return (!route.pools.every((pool) => pool instanceof V4Pool) && !route.pools.every((pool) => pool instanceof V3Pool) && !route.pools.every((pool) => pool instanceof Pair)); }); } export function computeAllRoutes(tokenIn, tokenOut, buildRoute, involvesToken, pools, maxHops) { var _a; const poolsUsed = Array(pools.length).fill(false); const routes = []; const computeRoutes = (tokenIn, tokenOut, currentRoute, poolsUsed, tokensVisited, _previousTokenOut) => { const currentRouteContainsFakeV4Pool = currentRoute.filter((pool) => pool instanceof V4Pool && pool.tickSpacing === V4_ETH_WETH_FAKE_POOL[tokenIn.chainId].tickSpacing).length > 0; const amendedMaxHops = currentRouteContainsFakeV4Pool ? maxHops + 1 : maxHops; // amendedMaxHops is the maxHops + 1 if the current route contains a fake v4 pool // b/c we want to allow the route to go through the fake v4 pool // also gas wise, if a route goes through the fake v4 pool, mixed quoter will add the wrap/unwrap gas cost: // https://github.com/Uniswap/mixed-quoter/pull/41/files#diff-a4d1289f264d1da22aac20cc55a9d01c8ba9cccd76ce1af8f952ec9034e7e1aaR189 // and SOR will use the gas cost from the mixed quoter: // https://github.com/Uniswap/smart-order-router/blob/17da523f1af050e6430afb866d96681346c8fb8b/src/routers/alpha-router/gas-models/mixedRoute/mixed-route-heuristic-gas-model.ts#L222 if (currentRoute.length > amendedMaxHops) { return; } if (currentRoute.length > 0 && involvesToken(currentRoute[currentRoute.length - 1], tokenOut)) { routes.push(buildRoute([...currentRoute], tokenIn, tokenOut)); return; } for (let i = 0; i < pools.length; i++) { if (poolsUsed[i]) { continue; } const curPool = pools[i]; const previousTokenOut = _previousTokenOut ? _previousTokenOut : tokenIn; if (!involvesToken(curPool, previousTokenOut)) { continue; } const currentTokenOut = curPool.token0.equals(previousTokenOut) ? curPool.token1 : curPool.token0; if (tokensVisited.has(getAddressLowerCase(currentTokenOut))) { continue; } tokensVisited.add(getAddressLowerCase(currentTokenOut)); currentRoute.push(curPool); poolsUsed[i] = true; computeRoutes(tokenIn, tokenOut, currentRoute, poolsUsed, tokensVisited, currentTokenOut); poolsUsed[i] = false; currentRoute.pop(); tokensVisited.delete(getAddressLowerCase(currentTokenOut)); } }; computeRoutes(tokenIn, tokenOut, [], poolsUsed, new Set([getAddressLowerCase(tokenIn)])); log.info({ routes: routes.map(routeToString), pools: pools.map(poolToString), }, `Computed ${routes.length} possible routes for type ${(_a = routes[0]) === null || _a === void 0 ? void 0 : _a.protocol}.`); return routes; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcHV0ZS1hbGwtcm91dGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL3JvdXRlcnMvYWxwaGEtcm91dGVyL2Z1bmN0aW9ucy9jb21wdXRlLWFsbC1yb3V0ZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFlBQVksRUFBUyxNQUFNLHFCQUFxQixDQUFDO0FBRTFELE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN2QyxPQUFPLEVBQUUsSUFBSSxJQUFJLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ2pELE9BQU8sRUFBRSxJQUFJLElBQUksTUFBTSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFFakQsT0FBTyxFQUNMLG1CQUFtQixFQUNuQixhQUFhLEVBQ2IscUJBQXFCLEdBQ3RCLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUMxRCxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDeEMsT0FBTyxFQUFFLFlBQVksRUFBRSxhQUFhLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNuRSxPQUFPLEVBQ0wsVUFBVSxFQUVWLE9BQU8sRUFDUCxPQUFPLEVBQ1AsT0FBTyxHQUNSLE1BQU0sY0FBYyxDQUFDO0FBRXRCLE1BQU0sVUFBVSxrQkFBa0IsQ0FDaEMsVUFBb0IsRUFDcEIsV0FBcUIsRUFDckIsS0FBZSxFQUNmLE9BQWUsRUFDZixZQUEyQjtJQUUzQixJQUFJLGFBQWEsR0FBYSxLQUFLLENBQUM7SUFFcEMsSUFBSSxZQUFZLEtBQUssWUFBWSxDQUFDLFVBQVUsRUFBRTtRQUM1QyxhQUFhLEdBQUcsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLEtBQUssS0FBSyxZQUFZLENBQUMsQ0FBQztLQUNyRTtJQUVELElBQUks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