diff --git a/package-lock.json b/package-lock.json index aa477857d..35db55c79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27 +1,27 @@ { "name": "@uniswap/smart-order-router", - "version": "4.17.6", + "version": "4.17.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@uniswap/smart-order-router", - "version": "4.17.6", + "version": "4.17.7", "license": "GPL", "dependencies": { "@eth-optimism/sdk": "^3.2.2", "@types/brotli": "^1.3.4", "@uniswap/default-token-list": "^11.13.0", "@uniswap/permit2-sdk": "^1.3.0", - "@uniswap/router-sdk": "^1.21.0", + "@uniswap/router-sdk": "^1.22.1", "@uniswap/sdk-core": "^7.5.0", "@uniswap/swap-router-contracts": "^1.3.1", "@uniswap/token-lists": "^1.0.0-beta.31", "@uniswap/universal-router": "^1.6.0", - "@uniswap/universal-router-sdk": "^4.14.0", + "@uniswap/universal-router-sdk": "^4.17.0", "@uniswap/v2-sdk": "^4.13.0", "@uniswap/v3-sdk": "^3.24.0", - "@uniswap/v4-sdk": "^1.18.0", + "@uniswap/v4-sdk": "^1.18.1", "async-retry": "^1.3.1", "await-timeout": "^1.1.1", "axios": "^0.21.1", @@ -3262,16 +3262,16 @@ } }, "node_modules/@uniswap/router-sdk": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@uniswap/router-sdk/-/router-sdk-1.21.0.tgz", - "integrity": "sha512-L7NixcJvcjkkX4WOa9mSyep1M2LMRp6gI0DKZGNuYsY+mjSJfxdlZyAKPXWHmVZGfVSE25IcbKZhA/i0KgVhKg==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/@uniswap/router-sdk/-/router-sdk-1.22.1.tgz", + "integrity": "sha512-j7LzPaxdlA/6Rf3V9iHzi7GnLW6huJSpLTq+6KZRcD1jbmV7RRKcSq0tgqGv/fbJprM8LqgW/NetfPIAqxzdLw==", "dependencies": { "@ethersproject/abi": "^5.5.0", "@uniswap/sdk-core": "^7.5.0", "@uniswap/swap-router-contracts": "^1.3.0", "@uniswap/v2-sdk": "^4.13.0", "@uniswap/v3-sdk": "^3.24.0", - "@uniswap/v4-sdk": "^1.18.0" + "@uniswap/v4-sdk": "^1.18.1" } }, "node_modules/@uniswap/sdk-core": { @@ -3344,20 +3344,20 @@ } }, "node_modules/@uniswap/universal-router-sdk": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@uniswap/universal-router-sdk/-/universal-router-sdk-4.14.0.tgz", - "integrity": "sha512-lM8o5olNxJSgaAXUotltSuobfjiDQhkS46XkRbJEDrFjmUDCqggUgegZjU4TFDC87d5LBF47TWYF9dAVQENjxg==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@uniswap/universal-router-sdk/-/universal-router-sdk-4.17.0.tgz", + "integrity": "sha512-LjdiBq1ab1Z2l8DQ7OuOmpJ+BbhlTU8xxIrTvRppVf4RtHCHaNNrwwXMbZF3bBDWD4h0WrMDJqwZ5LSfm2plbg==", "dependencies": { "@openzeppelin/contracts": "4.7.0", "@uniswap/permit2-sdk": "^1.3.0", - "@uniswap/router-sdk": "^1.21.0", + "@uniswap/router-sdk": "^1.22.1", "@uniswap/sdk-core": "^7.5.0", "@uniswap/universal-router": "2.0.0-beta.2", "@uniswap/v2-core": "^1.0.1", "@uniswap/v2-sdk": "^4.13.0", "@uniswap/v3-core": "1.0.0", "@uniswap/v3-sdk": "^3.24.0", - "@uniswap/v4-sdk": "^1.18.0", + "@uniswap/v4-sdk": "^1.19.2", "bignumber.js": "^9.0.2", "ethers": "^5.7.0" }, @@ -3531,9 +3531,9 @@ } }, "node_modules/@uniswap/v4-sdk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@uniswap/v4-sdk/-/v4-sdk-1.18.0.tgz", - "integrity": "sha512-ZcVSwgozrggfotH6D5g63W1T84bndsU1VZ+x67KM0Z8csBNzyV4GkKsMQPE4jnPEf3O0JSnGyHL2qPlpTHvGOQ==", + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@uniswap/v4-sdk/-/v4-sdk-1.19.2.tgz", + "integrity": "sha512-WwebI+2rlCO44UIoqVIVrcDg6rD/GRY57rfZ43TEX/DYQIG7v/EvYxQ/TIDCeAw1b1Jy0bLuAIFis9fbdP7VRg==", "dependencies": { "@ethersproject/solidity": "^5.0.9", "@uniswap/sdk-core": "^7.5.0", @@ -14328,16 +14328,16 @@ } }, "@uniswap/router-sdk": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@uniswap/router-sdk/-/router-sdk-1.21.0.tgz", - "integrity": "sha512-L7NixcJvcjkkX4WOa9mSyep1M2LMRp6gI0DKZGNuYsY+mjSJfxdlZyAKPXWHmVZGfVSE25IcbKZhA/i0KgVhKg==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/@uniswap/router-sdk/-/router-sdk-1.22.1.tgz", + "integrity": "sha512-j7LzPaxdlA/6Rf3V9iHzi7GnLW6huJSpLTq+6KZRcD1jbmV7RRKcSq0tgqGv/fbJprM8LqgW/NetfPIAqxzdLw==", "requires": { "@ethersproject/abi": "^5.5.0", "@uniswap/sdk-core": "^7.5.0", "@uniswap/swap-router-contracts": "^1.3.0", "@uniswap/v2-sdk": "^4.13.0", "@uniswap/v3-sdk": "^3.24.0", - "@uniswap/v4-sdk": "^1.18.0" + "@uniswap/v4-sdk": "^1.18.1" } }, "@uniswap/sdk-core": { @@ -14409,20 +14409,20 @@ } }, "@uniswap/universal-router-sdk": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@uniswap/universal-router-sdk/-/universal-router-sdk-4.14.0.tgz", - "integrity": "sha512-lM8o5olNxJSgaAXUotltSuobfjiDQhkS46XkRbJEDrFjmUDCqggUgegZjU4TFDC87d5LBF47TWYF9dAVQENjxg==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@uniswap/universal-router-sdk/-/universal-router-sdk-4.17.0.tgz", + "integrity": "sha512-LjdiBq1ab1Z2l8DQ7OuOmpJ+BbhlTU8xxIrTvRppVf4RtHCHaNNrwwXMbZF3bBDWD4h0WrMDJqwZ5LSfm2plbg==", "requires": { "@openzeppelin/contracts": "4.7.0", "@uniswap/permit2-sdk": "^1.3.0", - "@uniswap/router-sdk": "^1.21.0", + "@uniswap/router-sdk": "^1.22.1", "@uniswap/sdk-core": "^7.5.0", "@uniswap/universal-router": "2.0.0-beta.2", "@uniswap/v2-core": "^1.0.1", "@uniswap/v2-sdk": "^4.13.0", "@uniswap/v3-core": "1.0.0", "@uniswap/v3-sdk": "^3.24.0", - "@uniswap/v4-sdk": "^1.18.0", + "@uniswap/v4-sdk": "^1.19.2", "bignumber.js": "^9.0.2", "ethers": "^5.7.0" }, @@ -14553,9 +14553,9 @@ } }, "@uniswap/v4-sdk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@uniswap/v4-sdk/-/v4-sdk-1.18.0.tgz", - "integrity": "sha512-ZcVSwgozrggfotH6D5g63W1T84bndsU1VZ+x67KM0Z8csBNzyV4GkKsMQPE4jnPEf3O0JSnGyHL2qPlpTHvGOQ==", + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@uniswap/v4-sdk/-/v4-sdk-1.19.2.tgz", + "integrity": "sha512-WwebI+2rlCO44UIoqVIVrcDg6rD/GRY57rfZ43TEX/DYQIG7v/EvYxQ/TIDCeAw1b1Jy0bLuAIFis9fbdP7VRg==", "requires": { "@ethersproject/solidity": "^5.0.9", "@uniswap/sdk-core": "^7.5.0", diff --git a/package.json b/package.json index ec25f4b3f..060dbfb70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@uniswap/smart-order-router", - "version": "4.17.6", + "version": "4.17.7", "description": "Uniswap Smart Order Router", "main": "build/main/index.js", "typings": "build/main/index.d.ts", @@ -37,15 +37,15 @@ "@types/brotli": "^1.3.4", "@uniswap/default-token-list": "^11.13.0", "@uniswap/permit2-sdk": "^1.3.0", - "@uniswap/router-sdk": "^1.21.0", + "@uniswap/router-sdk": "^1.22.1", "@uniswap/sdk-core": "^7.5.0", "@uniswap/swap-router-contracts": "^1.3.1", "@uniswap/token-lists": "^1.0.0-beta.31", "@uniswap/universal-router": "^1.6.0", - "@uniswap/universal-router-sdk": "^4.14.0", + "@uniswap/universal-router-sdk": "^4.17.0", "@uniswap/v2-sdk": "^4.13.0", "@uniswap/v3-sdk": "^3.24.0", - "@uniswap/v4-sdk": "^1.18.0", + "@uniswap/v4-sdk": "^1.18.1", "async-retry": "^1.3.1", "await-timeout": "^1.1.1", "axios": "^0.21.1", diff --git a/src/providers/on-chain-quote-provider.ts b/src/providers/on-chain-quote-provider.ts index fecf87017..7803aea41 100644 --- a/src/providers/on-chain-quote-provider.ts +++ b/src/providers/on-chain-quote-provider.ts @@ -483,9 +483,10 @@ export class OnChainQuoteProvider implements IOnChainQuoteProvider { // Hence in case of V2 or mixed, we explicitly encode into mixed routes. case Protocol.V2: case Protocol.MIXED: + // we need to retain the fake pool data for the mixed route return encodeMixedRouteToPath( route instanceof V2Route - ? new MixedRouteSDK(route.pairs, route.input, route.output) + ? new MixedRouteSDK(route.pairs, route.input, route.output, true) : route ) as TPath; default: diff --git a/src/routers/alpha-router/functions/compute-all-routes.ts b/src/routers/alpha-router/functions/compute-all-routes.ts index 0e9ad0cb4..27f27c806 100644 --- a/src/routers/alpha-router/functions/compute-all-routes.ts +++ b/src/routers/alpha-router/functions/compute-all-routes.ts @@ -1,11 +1,12 @@ import { TPool } from '@uniswap/router-sdk/dist/utils/TPool'; -import { Currency, Token } from '@uniswap/sdk-core'; +import { ChainId, Currency, Token } from '@uniswap/sdk-core'; import { Pair } from '@uniswap/v2-sdk'; import { Pool as V3Pool } from '@uniswap/v3-sdk'; import { Pool as V4Pool } from '@uniswap/v4-sdk'; -import { getAddressLowerCase } from '../../../util'; +import { getAddressLowerCase, nativeOnChain } from '../../../util'; import { log } from '../../../util/log'; +import { V4_ETH_WETH_FAKE_POOL } from '../../../util/pool'; import { poolToString, routeToString } from '../../../util/routes'; import { MixedRoute, @@ -75,17 +76,35 @@ export function computeAllMixedRoutes( parts: TPool[], maxHops: number ): MixedRoute[] { + // 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 + ? parts.concat(V4_ETH_WETH_FAKE_POOL[currencyIn.chainId as 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: TPool[], currencyIn: Currency, currencyOut: Currency) => { - return new MixedRoute(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: TPool, currency: Currency) => - currency.isNative + (pool: TPool, currency: Currency) => { + return currency.isNative ? (pool as V4Pool).involvesToken(currency) - : pool.involvesToken(currency), - parts, + : pool.involvesToken(currency); + }, + amendedPools, maxHops ); /// filter out pure v4 and v3 and v2 routes @@ -125,7 +144,24 @@ export function computeAllRoutes< tokensVisited: Set, _previousTokenOut?: TCurrency ) => { - if (currentRoute.length > maxHops) { + const currentRouteContainsFakeV4Pool = + currentRoute.filter( + (pool) => + pool instanceof V4Pool && + pool.tickSpacing === + V4_ETH_WETH_FAKE_POOL[tokenIn.chainId as 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; } diff --git a/src/routers/alpha-router/functions/get-candidate-pools.ts b/src/routers/alpha-router/functions/get-candidate-pools.ts index 948b14508..8df285ff0 100644 --- a/src/routers/alpha-router/functions/get-candidate-pools.ts +++ b/src/routers/alpha-router/functions/get-candidate-pools.ts @@ -1926,6 +1926,7 @@ export async function getMixedRouteCandidatePools({ v4PoolProvider, v3poolProvider, v2poolProvider, + chainId, }: MixedRouteGetCandidatePoolsParams): Promise { const beforeSubgraphPools = Date.now(); const [ @@ -2076,10 +2077,15 @@ export async function getMixedRouteCandidatePools({ const V4tokenPairsRaw = _.map< V4SubgraphPool, - [Token, Token, number, number, string] | undefined + [Currency, Currency, number, number, string] | undefined >(V4sortedPools, (subgraphPool) => { - const tokenA = tokenAccessor.getTokenByAddress(subgraphPool.token0.id); - const tokenB = tokenAccessor.getTokenByAddress(subgraphPool.token1.id); + // native currency is not erc20 token, therefore there's no way to retrieve native currency metadata as the erc20 token. + const tokenA = isNativeCurrency(subgraphPool.token0.id) + ? nativeOnChain(chainId) + : tokenAccessor.getTokenByAddress(subgraphPool.token0.id); + const tokenB = isNativeCurrency(subgraphPool.token1.id) + ? nativeOnChain(chainId) + : tokenAccessor.getTokenByAddress(subgraphPool.token1.id); let fee: FeeAmount; try { fee = Number(subgraphPool.feeTier); @@ -2090,7 +2096,6 @@ export async function getMixedRouteCandidatePools({ ); return undefined; } - if (!tokenA || !tokenB) { log.info( `Dropping candidate pool for ${subgraphPool.token0.id}/${ diff --git a/src/util/addresses.ts b/src/util/addresses.ts index 0a161ee0c..fc619dc58 100644 --- a/src/util/addresses.ts +++ b/src/util/addresses.ts @@ -141,12 +141,14 @@ export const MIXED_ROUTE_QUOTER_V1_ADDRESSES: AddressMap = { CHAIN_TO_ADDRESSES_MAP[ChainId.MAINNET].mixedRouteQuoterV1Address, [ChainId.GOERLI]: CHAIN_TO_ADDRESSES_MAP[ChainId.GOERLI].mixedRouteQuoterV1Address, + [ChainId.BASE]: '0xc7A3b85D43fF66AD98A895dE0EaE4b9e24C932D7', }; export const MIXED_ROUTE_QUOTER_V2_ADDRESSES: AddressMap = { [ChainId.SEPOLIA]: CHAIN_TO_ADDRESSES_MAP[ChainId.SEPOLIA].mixedRouteQuoterV2Address, [ChainId.MAINNET]: '0xE63C5F5005909E96b5aA9CE10744CCE70eC16CC3', + [ChainId.BASE]: '0xc7A3b85D43fF66AD98A895dE0EaE4b9e24C932D7', }; export const UNISWAP_MULTICALL_ADDRESSES: AddressMap = { diff --git a/src/util/chains.ts b/src/util/chains.ts index 916bf5666..ad89d5f16 100644 --- a/src/util/chains.ts +++ b/src/util/chains.ts @@ -52,6 +52,7 @@ export const MIXED_SUPPORTED = [ ChainId.MAINNET, ChainId.SEPOLIA, ChainId.GOERLI, + ChainId.BASE, ]; export const HAS_L1_FEE = [ diff --git a/src/util/index.ts b/src/util/index.ts index 414cafc06..5a4f3df40 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -4,5 +4,6 @@ export * from './chains'; export * from './intent'; export * from './log'; export * from './metric'; +export * from './pool'; export * from './protocols'; export * from './routes'; diff --git a/src/util/methodParameters.ts b/src/util/methodParameters.ts index 5d597fe87..1f1e1647b 100644 --- a/src/util/methodParameters.ts +++ b/src/util/methodParameters.ts @@ -282,6 +282,8 @@ export function buildTrade( quote.denominator ); + // we cannot retain fake pools for mixed routes, + // when we generate the ur swap calldata const routeRaw = new MixedRouteSDK( route.pools, amountCurrency.currency, diff --git a/src/util/pool.ts b/src/util/pool.ts new file mode 100644 index 000000000..ea9612621 --- /dev/null +++ b/src/util/pool.ts @@ -0,0 +1,300 @@ +import { ADDRESS_ZERO } from '@uniswap/router-sdk'; +import { ChainId } from '@uniswap/sdk-core'; +import { Pool as V4Pool } from '@uniswap/v4-sdk'; + +import { nativeOnChain } from './chains'; + +const FAKE_TICK_SPACING = 0; + +export const V4_ETH_WETH_FAKE_POOL: { [chainId in ChainId]: V4Pool } = { + [ChainId.MAINNET]: new V4Pool( + nativeOnChain(ChainId.MAINNET), + nativeOnChain(ChainId.MAINNET).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.GOERLI]: new V4Pool( + nativeOnChain(ChainId.GOERLI), + nativeOnChain(ChainId.GOERLI).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.SEPOLIA]: new V4Pool( + nativeOnChain(ChainId.SEPOLIA), + nativeOnChain(ChainId.SEPOLIA).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.OPTIMISM]: new V4Pool( + nativeOnChain(ChainId.OPTIMISM), + nativeOnChain(ChainId.OPTIMISM).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.OPTIMISM_GOERLI]: new V4Pool( + nativeOnChain(ChainId.OPTIMISM_GOERLI), + nativeOnChain(ChainId.OPTIMISM_GOERLI).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.OPTIMISM_SEPOLIA]: new V4Pool( + nativeOnChain(ChainId.OPTIMISM_SEPOLIA), + nativeOnChain(ChainId.OPTIMISM_SEPOLIA).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.ARBITRUM_ONE]: new V4Pool( + nativeOnChain(ChainId.ARBITRUM_ONE), + nativeOnChain(ChainId.ARBITRUM_ONE).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.ARBITRUM_GOERLI]: new V4Pool( + nativeOnChain(ChainId.ARBITRUM_GOERLI), + nativeOnChain(ChainId.ARBITRUM_GOERLI).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.ARBITRUM_SEPOLIA]: new V4Pool( + nativeOnChain(ChainId.ARBITRUM_SEPOLIA), + nativeOnChain(ChainId.ARBITRUM_SEPOLIA).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.POLYGON]: new V4Pool( + nativeOnChain(ChainId.POLYGON), + nativeOnChain(ChainId.POLYGON).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.POLYGON_MUMBAI]: new V4Pool( + nativeOnChain(ChainId.POLYGON_MUMBAI), + nativeOnChain(ChainId.POLYGON_MUMBAI).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.CELO]: new V4Pool( + nativeOnChain(ChainId.CELO), + nativeOnChain(ChainId.CELO).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.CELO_ALFAJORES]: new V4Pool( + nativeOnChain(ChainId.CELO_ALFAJORES), + nativeOnChain(ChainId.CELO_ALFAJORES).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.GNOSIS]: new V4Pool( + nativeOnChain(ChainId.GNOSIS), + nativeOnChain(ChainId.GNOSIS).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.MOONBEAM]: new V4Pool( + nativeOnChain(ChainId.MOONBEAM), + nativeOnChain(ChainId.MOONBEAM).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.BNB]: new V4Pool( + nativeOnChain(ChainId.BNB), + nativeOnChain(ChainId.BNB).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.AVALANCHE]: new V4Pool( + nativeOnChain(ChainId.AVALANCHE), + nativeOnChain(ChainId.AVALANCHE).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.BASE_GOERLI]: new V4Pool( + nativeOnChain(ChainId.BASE_GOERLI), + nativeOnChain(ChainId.BASE_GOERLI).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.BASE_SEPOLIA]: new V4Pool( + nativeOnChain(ChainId.BASE_SEPOLIA), + nativeOnChain(ChainId.BASE_SEPOLIA).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.BASE]: new V4Pool( + nativeOnChain(ChainId.BASE), + nativeOnChain(ChainId.BASE).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.ZORA]: new V4Pool( + nativeOnChain(ChainId.ZORA), + nativeOnChain(ChainId.ZORA).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.ZORA_SEPOLIA]: new V4Pool( + nativeOnChain(ChainId.ZORA_SEPOLIA), + nativeOnChain(ChainId.ZORA_SEPOLIA).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.ROOTSTOCK]: new V4Pool( + nativeOnChain(ChainId.ROOTSTOCK), + nativeOnChain(ChainId.ROOTSTOCK).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.BLAST]: new V4Pool( + nativeOnChain(ChainId.BLAST), + nativeOnChain(ChainId.BLAST).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.ZKSYNC]: new V4Pool( + nativeOnChain(ChainId.ZKSYNC), + nativeOnChain(ChainId.ZKSYNC).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.WORLDCHAIN]: new V4Pool( + nativeOnChain(ChainId.WORLDCHAIN), + nativeOnChain(ChainId.WORLDCHAIN).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.UNICHAIN_SEPOLIA]: new V4Pool( + nativeOnChain(ChainId.UNICHAIN_SEPOLIA), + nativeOnChain(ChainId.UNICHAIN_SEPOLIA).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.UNICHAIN]: new V4Pool( + nativeOnChain(ChainId.UNICHAIN), + nativeOnChain(ChainId.UNICHAIN).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), + [ChainId.MONAD_TESTNET]: new V4Pool( + nativeOnChain(ChainId.MONAD_TESTNET), + nativeOnChain(ChainId.MONAD_TESTNET).wrapped, + 0, + FAKE_TICK_SPACING, + ADDRESS_ZERO, + 79228162514264337593543950336, + 0, + 0 + ), +}; diff --git a/src/util/routes.ts b/src/util/routes.ts index 6884230b0..70af52ab3 100644 --- a/src/util/routes.ts +++ b/src/util/routes.ts @@ -1,5 +1,5 @@ import { Protocol } from '@uniswap/router-sdk'; -import { Currency, Percent } from '@uniswap/sdk-core'; +import { ChainId, Currency, Percent } from '@uniswap/sdk-core'; import { Pair } from '@uniswap/v2-sdk'; import { Pool as V3Pool } from '@uniswap/v3-sdk'; import { Pool as V4Pool } from '@uniswap/v4-sdk'; @@ -16,6 +16,7 @@ import { V3_CORE_FACTORY_ADDRESSES } from './addresses'; import { TPool } from '@uniswap/router-sdk/dist/utils/TPool'; import { CurrencyAmount } from '.'; import { CachedRoutes } from '../providers'; +import { V4_ETH_WETH_FAKE_POOL } from './pool'; export const routeToTokens = (route: SupportedRoutes): Currency[] => { switch (route.protocol) { @@ -91,6 +92,15 @@ export const routeToString = (route: SupportedRoutes): string => { V3_CORE_FACTORY_ADDRESSES[pool.chainId] )}]`; } else if (pool instanceof V4Pool) { + // Special case in the case of ETH/WETH fake pool + // where we do not want to return the fake pool in the route string as it is not a real pool + if ( + pool.tickSpacing === + V4_ETH_WETH_FAKE_POOL[pool.chainId as ChainId].tickSpacing + ) { + return ' -- '; + } + return ` -- ${pool.fee / 10000}% [${V4Pool.getPoolId( pool.token0, pool.token1, diff --git a/test/unit/routers/alpha-router/functions/compute-all-routes.test.ts b/test/unit/routers/alpha-router/functions/compute-all-routes.test.ts index d255d6c18..377f6f922 100644 --- a/test/unit/routers/alpha-router/functions/compute-all-routes.test.ts +++ b/test/unit/routers/alpha-router/functions/compute-all-routes.test.ts @@ -4,6 +4,7 @@ import { Pool as V4Pool } from '@uniswap/v4-sdk'; import { CurrencyAmount, DAI_MAINNET as DAI, + nativeOnChain, USDC_MAINNET as USDC, USDT_MAINNET as USDT, WBTC_MAINNET as WBTC, @@ -39,6 +40,7 @@ import { } from '../../../../test-util/mock-data'; import { ADDRESS_ZERO } from '@uniswap/router-sdk'; import { ChainId, WETH9 } from '@uniswap/sdk-core'; +import { V4_ETH_WETH_FAKE_POOL } from '../../../../../src/util/pool'; describe('compute all v4 routes', () => { test('succeeds to compute all routes', async () => { @@ -291,6 +293,44 @@ describe('compute all mixed routes', () => { expect(routes).toHaveLength(1); }); + + test('handles ETH/WETH wrapping in mixed routes', async () => { + const pools = [ + USDC_WETH_LOW, // V3 pool + ETH_USDT_V4_LOW + ]; + const routes = computeAllMixedRoutes(USDC, USDT, pools, 2); + expect(routes.length).toBeGreaterThan(0); + // Routes should not include both ETH and WETH fake pools + routes.forEach(route => { + expect(route.pools).toEqual([USDC_WETH_LOW, V4_ETH_WETH_FAKE_POOL[ChainId.MAINNET], ETH_USDT_V4_LOW]) + expect(route.path).toEqual([USDC, nativeOnChain(ChainId.MAINNET).wrapped, nativeOnChain(ChainId.MAINNET), USDT]) + expect(route.input).toEqual(USDC) + expect(route.output).toEqual(USDT) + expect(route.pathInput).toEqual(USDC) + expect(route.pathOutput).toEqual(USDT) + expect(route.chainId).toEqual(1) + }); + }); + + test('handles WETH/ETH unwrapping in mixed routes', async () => { + const pools = [ + ETH_USDT_V4_LOW, + USDC_WETH_LOW + ]; + const routes = computeAllMixedRoutes(USDT, USDC, pools, 2); + expect(routes.length).toBeGreaterThan(0); + // Routes should not include both ETH and WETH fake pools + routes.forEach(route => { + expect(route.pools).toEqual([ETH_USDT_V4_LOW, V4_ETH_WETH_FAKE_POOL[ChainId.MAINNET], USDC_WETH_LOW]) + expect(route.path).toEqual([USDT, nativeOnChain(ChainId.MAINNET), nativeOnChain(ChainId.MAINNET).wrapped, USDC]) + expect(route.input).toEqual(USDT) + expect(route.output).toEqual(USDC) + expect(route.pathInput).toEqual(USDT) + expect(route.pathOutput).toEqual(USDC) + expect(route.chainId).toEqual(1) + }); + }); }); describe('compute all v2 routes', () => {