From ee653ee58d23d158d0bafd6de3aec551471d95d4 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Fri, 10 Mar 2023 15:50:18 -0800 Subject: [PATCH] fix: stricter typing for router query (#548) * fix: stricter typing for router query * fix: rm unused bits * fix: tighten types even more --- .../routing/clientSideSmartOrderRouter.ts | 14 +- .../transformSwapRouteToGetQuoteResult.ts | 35 +- src/hooks/routing/useRouterTrade.ts | 20 +- src/state/routing/slice.ts | 30 +- src/state/routing/types.ts | 43 +- src/state/routing/utils.test.ts | 426 +++++++++--------- src/state/routing/utils.ts | 24 +- 7 files changed, 295 insertions(+), 297 deletions(-) diff --git a/src/hooks/routing/clientSideSmartOrderRouter.ts b/src/hooks/routing/clientSideSmartOrderRouter.ts index 8b3a578df..a9f4e629b 100644 --- a/src/hooks/routing/clientSideSmartOrderRouter.ts +++ b/src/hooks/routing/clientSideSmartOrderRouter.ts @@ -13,7 +13,7 @@ import { } from '@uniswap/smart-order-router' import { nativeOnChain } from 'constants/tokens' import JSBI from 'jsbi' -import { GetQuoteArgs, GetQuoteError, INITIALIZED, NO_ROUTE, QuoteResult } from 'state/routing/types' +import { GetQuoteArgs, QuoteResult, QuoteState } from 'state/routing/types' import { isExactInput } from 'utils/tradeType' import { transformSwapRouteToGetQuoteResult } from './transformSwapRouteToGetQuoteResult' @@ -82,7 +82,7 @@ function getRouter(chainId: ChainId, provider: BaseProvider): AlphaRouter { return router } -async function getQuote( +async function getQuoteResult( { tradeType, tokenIn, @@ -96,7 +96,7 @@ async function getQuote( }, router: AlphaRouter, routerConfig: Partial -): Promise { +): Promise { const tokenInIsNative = Object.values(SwapRouterNativeAssets).includes(tokenIn.address as SwapRouterNativeAssets) const tokenOutIsNative = Object.values(SwapRouterNativeAssets).includes(tokenOut.address as SwapRouterNativeAssets) const currencyIn = tokenInIsNative @@ -111,13 +111,13 @@ async function getQuote( const amount = CurrencyAmount.fromRawAmount(baseCurrency, JSBI.BigInt(amountRaw ?? '1')) // a null amountRaw should initialize the route const route = await router.route(amount, quoteCurrency, tradeType, /*swapConfig=*/ undefined, routerConfig) - if (!amountRaw) return INITIALIZED - if (!route) return NO_ROUTE + if (!amountRaw) return { state: QuoteState.INITIALIZED } + if (!route) return { state: QuoteState.NOT_FOUND } return transformSwapRouteToGetQuoteResult({ ...route, routeString: routeAmountsToString(route.route) }) } -export async function getClientSideQuote( +export async function getClientSideQuoteResult( { tokenInAddress, tokenInChainId, @@ -138,7 +138,7 @@ export async function getClientSideQuote( } const router = getRouter(tokenInChainId, provider) - return getQuote( + return getQuoteResult( { tradeType, tokenIn: { diff --git a/src/hooks/routing/transformSwapRouteToGetQuoteResult.ts b/src/hooks/routing/transformSwapRouteToGetQuoteResult.ts index a3f65fc44..897c941a5 100644 --- a/src/hooks/routing/transformSwapRouteToGetQuoteResult.ts +++ b/src/hooks/routing/transformSwapRouteToGetQuoteResult.ts @@ -1,6 +1,6 @@ import { Protocol } from '@uniswap/router-sdk' import type { SwapRoute } from '@uniswap/smart-order-router' -import { QuoteResult, V2PoolInRoute, V3PoolInRoute } from 'state/routing/types' +import { QuoteResult, QuoteState, V2PoolInRoute, V3PoolInRoute } from 'state/routing/types' import { isExactInput } from 'utils/tradeType' // from routing-api (https://github.com/Uniswap/routing-api/blob/main/lib/handlers/quote/quote.ts#L243-L311) @@ -128,20 +128,23 @@ export function transformSwapRouteToGetQuoteResult({ const amount = isExactInput(tradeType) ? inputAmount : outputAmount return { - methodParameters, - blockNumber: blockNumber.toString(), - amount: amount.quotient.toString(), - amountDecimals: amount.toExact(), - quote: quote.quotient.toString(), - quoteDecimals: quote.toExact(), - quoteGasAdjusted: quoteGasAdjusted.quotient.toString(), - quoteGasAdjustedDecimals: quoteGasAdjusted.toExact(), - gasUseEstimateQuote: estimatedGasUsedQuoteToken.quotient.toString(), - gasUseEstimateQuoteDecimals: estimatedGasUsedQuoteToken.toExact(), - gasUseEstimate: estimatedGasUsed.toString(), - gasUseEstimateUSD: estimatedGasUsedUSD.toExact(), - gasPriceWei: gasPriceWei.toString(), - route: routeResponse, - routeString, + state: QuoteState.SUCCESS, + data: { + methodParameters, + blockNumber: blockNumber.toString(), + amount: amount.quotient.toString(), + amountDecimals: amount.toExact(), + quote: quote.quotient.toString(), + quoteDecimals: quote.toExact(), + quoteGasAdjusted: quoteGasAdjusted.quotient.toString(), + quoteGasAdjustedDecimals: quoteGasAdjusted.toExact(), + gasUseEstimateQuote: estimatedGasUsedQuoteToken.quotient.toString(), + gasUseEstimateQuoteDecimals: estimatedGasUsedQuoteToken.toExact(), + gasUseEstimate: estimatedGasUsed.toString(), + gasUseEstimateUSD: estimatedGasUsedUSD.toExact(), + gasPriceWei: gasPriceWei.toString(), + route: routeResponse, + routeString, + }, } } diff --git a/src/hooks/routing/useRouterTrade.ts b/src/hooks/routing/useRouterTrade.ts index 3e2fa389b..f9934fe3b 100644 --- a/src/hooks/routing/useRouterTrade.ts +++ b/src/hooks/routing/useRouterTrade.ts @@ -8,7 +8,7 @@ import ms from 'ms.macro' import { useCallback, useMemo } from 'react' import { useGetQuoteArgs } from 'state/routing/args' import { useGetTradeQuoteQueryState, useLazyGetTradeQuoteQuery } from 'state/routing/slice' -import { InterfaceTrade, NO_ROUTE, TradeResult, TradeState } from 'state/routing/types' +import { InterfaceTrade, QuoteState, TradeState } from 'state/routing/types' import { QuoteConfig, QuoteType } from './types' @@ -61,7 +61,12 @@ export function useRouterTrade( // Get the cached state *immediately* to update the UI without sending a request - using useGetTradeQuoteQueryState - // but debounce the actual request - using useLazyGetTradeQuoteQuery - to avoid flooding the router / JSON-RPC endpoints. - const { isError, data, currentData, fulfilledTimeStamp } = useGetTradeQuoteQueryState(queryArgs) + const { + data: tradeResult, + currentData: currentTradeResult, + fulfilledTimeStamp, + isError, + } = useGetTradeQuoteQueryState(queryArgs) // An already-fetched value should be refetched if it is older than the pollingInterval. // Without explicit refetch, it would not be refetched until another pollingInterval has elapsed. @@ -74,21 +79,20 @@ export function useRouterTrade( }, [fulfilledTimeStamp, pollingInterval, queryArgs, trigger]) useTimeout(request, 200) - const tradeResult: TradeResult | undefined = typeof data === 'object' ? data : undefined + const isCurrent = currentTradeResult === tradeResult const isValidBlock = useIsValidBlock(Number(tradeResult?.blockNumber)) - const isValid = currentData === data && isValidBlock const gasUseEstimateUSD = useStablecoinAmountFromFiatValue(tradeResult?.gasUseEstimateUSD) return useMemo(() => { if (!amountSpecified || isError || queryArgs === skipToken) { return TRADE_INVALID - } else if (data === NO_ROUTE && isValid) { + } else if (tradeResult?.state === QuoteState.NOT_FOUND && isCurrent) { return TRADE_NOT_FOUND } else if (!tradeResult?.trade) { return TRADE_LOADING } else { - const state = isValid ? TradeState.VALID : TradeState.LOADING - return { state, trade: tradeResult?.trade, gasUseEstimateUSD } + const state = isCurrent && isValidBlock ? TradeState.VALID : TradeState.LOADING + return { state, trade: tradeResult.trade, gasUseEstimateUSD } } - }, [amountSpecified, isError, queryArgs, data, tradeResult?.trade, isValid, gasUseEstimateUSD]) + }, [amountSpecified, gasUseEstimateUSD, isCurrent, isError, isValidBlock, queryArgs, tradeResult]) } diff --git a/src/state/routing/slice.ts b/src/state/routing/slice.ts index c53d62c3b..8e1804f22 100644 --- a/src/state/routing/slice.ts +++ b/src/state/routing/slice.ts @@ -1,4 +1,4 @@ -import { BaseQueryFn, createApi, SkipToken, skipToken } from '@reduxjs/toolkit/query/react' +import { BaseQueryFn, createApi, FetchBaseQueryError, SkipToken, skipToken } from '@reduxjs/toolkit/query/react' import { Protocol } from '@uniswap/router-sdk' import { RouterPreference } from 'hooks/routing/types' import ms from 'ms.macro' @@ -6,7 +6,7 @@ import qs from 'qs' import { isExactInput } from 'utils/tradeType' import { serializeGetQuoteArgs } from './args' -import { GetQuoteArgs, GetQuoteError, NO_ROUTE, QuoteResult, TradeResult } from './types' +import { GetQuoteArgs, QuoteData, QuoteState, TradeResult } from './types' import { transformQuoteToTradeResult } from './utils' const protocols: Protocol[] = [Protocol.V2, Protocol.V3, Protocol.MIXED] @@ -16,19 +16,18 @@ const DEFAULT_QUERY_PARAMS = { protocols: protocols.map((p) => p.toLowerCase()).join(','), } -const baseQuery: BaseQueryFn = () => { +const baseQuery: BaseQueryFn = () => { return { error: { reason: 'Unimplemented baseQuery' } } } -type TradeQuoteResult = TradeResult | GetQuoteError - export const routing = createApi({ reducerPath: 'routing', baseQuery, serializeQueryArgs: serializeGetQuoteArgs, endpoints: (build) => ({ getTradeQuote: build.query({ - async queryFn(args: GetQuoteArgs | SkipToken) { + // Explicitly typing the return type enables typechecking of return values. + async queryFn(args: GetQuoteArgs | SkipToken): Promise<{ data: TradeResult } | { error: FetchBaseQueryError }> { if (args === skipToken) return { error: { status: 'CUSTOM_ERROR', error: 'Skipped' } } if ( @@ -59,14 +58,14 @@ export const routing = createApi({ // NO_ROUTE should be treated as a valid response to prevent retries. if (typeof data === 'object' && data.errorCode === 'NO_ROUTE') { - return { data: NO_ROUTE as TradeQuoteResult } + return { data: { state: QuoteState.NOT_FOUND } } } throw data } - const quote: QuoteResult = await response.json() - const tradeResult = transformQuoteToTradeResult(args, quote) + const quoteData: QuoteData = await response.json() + const tradeResult = transformQuoteToTradeResult(args, quoteData) return { data: tradeResult } } catch (error: any) { console.warn( @@ -78,12 +77,13 @@ export const routing = createApi({ // Lazy-load the client-side router to improve initial pageload times. const clientSideSmartOrderRouter = await import('../../hooks/routing/clientSideSmartOrderRouter') try { - const quote: QuoteResult | GetQuoteError = await clientSideSmartOrderRouter.getClientSideQuote(args, { - protocols, - }) - if (typeof quote === 'string') return { data: quote as TradeQuoteResult } - const tradeResult = transformQuoteToTradeResult(args, quote) - return { data: tradeResult } + const quoteResult = await clientSideSmartOrderRouter.getClientSideQuoteResult(args, { protocols }) + if (quoteResult.state === QuoteState.SUCCESS) { + const tradeResult = transformQuoteToTradeResult(args, quoteResult.data) + return { data: tradeResult } + } else { + return { data: quoteResult } + } } catch (error: any) { console.warn(`GetQuote failed on client: ${error}`) return { error: { status: 'CUSTOM_ERROR', error: error?.message ?? error?.detail ?? error } } diff --git a/src/state/routing/types.ts b/src/state/routing/types.ts index 21988d0ab..b9722a546 100644 --- a/src/state/routing/types.ts +++ b/src/state/routing/types.ts @@ -59,13 +59,19 @@ export interface GetQuoteArgs { tokenOutDecimals: number tokenOutSymbol?: string amount: string | null // passing null will initialize the client-side SOR - routerPreference?: RouterPreference + routerPreference: RouterPreference routerUrl?: string tradeType: TradeType provider: BaseProvider } -export interface QuoteResult { +export enum QuoteState { + SUCCESS = 'Success', + INITIALIZED = 'Initialized', + NOT_FOUND = 'Not found', +} + +export interface QuoteData { quoteId?: string blockNumber: string amount: string @@ -84,17 +90,28 @@ export interface QuoteResult { routeString: string } -export const INITIALIZED = 'Initialized' -export const NO_ROUTE = 'No Route' - -export type GetQuoteError = typeof INITIALIZED | typeof NO_ROUTE - -export type TradeResult = { - trade?: InterfaceTrade - gasUseEstimateUSD?: string - blockNumber: string -} +export type QuoteResult = + | { + state: QuoteState.INITIALIZED | QuoteState.NOT_FOUND + data?: undefined + } + | { + state: QuoteState.SUCCESS + data: QuoteData + } -export type TradeQuoteResult = TradeResult | GetQuoteError +export type TradeResult = + | { + state: QuoteState.INITIALIZED | QuoteState.NOT_FOUND + trade?: undefined + gasUseEstimateUSD?: undefined + blockNumber?: undefined + } + | { + state: QuoteState.SUCCESS + trade: InterfaceTrade + gasUseEstimateUSD: string + blockNumber: string + } export class InterfaceTrade extends Trade {} diff --git a/src/state/routing/utils.test.ts b/src/state/routing/utils.test.ts index 029368cba..7b22a13da 100644 --- a/src/state/routing/utils.test.ts +++ b/src/state/routing/utils.test.ts @@ -8,36 +8,28 @@ import { computeRoutes } from './utils' const ETH = nativeOnChain(1) describe('#useRoute', () => { - it('handles an undefined payload', () => { - const result = computeRoutes(false, false, undefined) - - expect(result).toBeUndefined() - }) - it('handles empty edges and nodes', () => { - const result = computeRoutes(false, false, { route: [] }) + const result = computeRoutes(false, false, []) expect(result).toEqual([]) }) it('handles a single route trade from DAI to USDC from v3', () => { - const result = computeRoutes(false, false, { - route: [ - [ - { - type: 'v3-pool', - address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', - amountIn: amount`1`, - amountOut: amount`5`, - fee: '500', - sqrtRatioX96: '2437312313659959819381354528', - liquidity: '10272714736694327408', - tickCurrent: '-69633', - tokenIn: DAI, - tokenOut: USDC, - }, - ], + const result = computeRoutes(false, false, [ + [ + { + type: 'v3-pool', + address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', + amountIn: amount`1`, + amountOut: amount`5`, + fee: '500', + sqrtRatioX96: '2437312313659959819381354528', + liquidity: '10272714736694327408', + tickCurrent: '-69633', + tokenIn: DAI, + tokenOut: USDC, + }, ], - }) + ]) const r = result?.[0] @@ -52,28 +44,26 @@ describe('#useRoute', () => { }) it('handles a single route trade from DAI to USDC from v2', () => { - const result = computeRoutes(false, false, { - route: [ - [ - { - type: 'v2-pool', - address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', - amountIn: amount`1`, - amountOut: amount`5`, - tokenIn: DAI, - tokenOut: USDC, - reserve0: { - token: DAI, - quotient: amount`100`, - }, - reserve1: { - token: USDC, - quotient: amount`200`, - }, + const result = computeRoutes(false, false, [ + [ + { + type: 'v2-pool', + address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', + amountIn: amount`1`, + amountOut: amount`5`, + tokenIn: DAI, + tokenOut: USDC, + reserve0: { + token: DAI, + quotient: amount`100`, }, - ], + reserve1: { + token: USDC, + quotient: amount`200`, + }, + }, ], - }) + ]) const r = result?.[0] @@ -88,54 +78,52 @@ describe('#useRoute', () => { }) it('handles a multi-route trade from DAI to USDC', () => { - const result = computeRoutes(false, false, { - route: [ - [ - { - type: 'v2-pool', - address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', - amountIn: amount`5`, - amountOut: amount`6`, - tokenIn: DAI, - tokenOut: USDC, - reserve0: { - token: DAI, - quotient: amount`1000`, - }, - reserve1: { - token: USDC, - quotient: amount`500`, - }, + const result = computeRoutes(false, false, [ + [ + { + type: 'v2-pool', + address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', + amountIn: amount`5`, + amountOut: amount`6`, + tokenIn: DAI, + tokenOut: USDC, + reserve0: { + token: DAI, + quotient: amount`1000`, }, - ], - [ - { - type: 'v3-pool', - address: '0x2f8F72aA9304c8B593d555F12eF6589cC3A579A2', - amountIn: amount`10`, - amountOut: amount`1`, - fee: '3000', - tokenIn: DAI, - tokenOut: MKR, - sqrtRatioX96: '2437312313659959819381354528', - liquidity: '10272714736694327408', - tickCurrent: '-69633', + reserve1: { + token: USDC, + quotient: amount`500`, }, - { - type: 'v3-pool', - address: '0x3f8F72aA9304c8B593d555F12eF6589cC3A579A2', - amountIn: amount`1`, - amountOut: amount`200`, - fee: '10000', - tokenIn: MKR, - tokenOut: USDC, - sqrtRatioX96: '2437312313659959819381354528', - liquidity: '10272714736694327408', - tickCurrent: '-69633', - }, - ], + }, ], - }) + [ + { + type: 'v3-pool', + address: '0x2f8F72aA9304c8B593d555F12eF6589cC3A579A2', + amountIn: amount`10`, + amountOut: amount`1`, + fee: '3000', + tokenIn: DAI, + tokenOut: MKR, + sqrtRatioX96: '2437312313659959819381354528', + liquidity: '10272714736694327408', + tickCurrent: '-69633', + }, + { + type: 'v3-pool', + address: '0x3f8F72aA9304c8B593d555F12eF6589cC3A579A2', + amountIn: amount`1`, + amountOut: amount`200`, + fee: '10000', + tokenIn: MKR, + tokenOut: USDC, + sqrtRatioX96: '2437312313659959819381354528', + liquidity: '10272714736694327408', + tickCurrent: '-69633', + }, + ], + ]) expect(result).toBeDefined() expect(result?.length).toBe(2) @@ -157,38 +145,36 @@ describe('#useRoute', () => { }) it('handles a single route trade with same token pair, different fee tiers', () => { - const result = computeRoutes(false, false, { - route: [ - [ - { - type: 'v3-pool', - address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', - amountIn: amount`1`, - amountOut: amount`5`, - fee: '500', - tokenIn: DAI, - tokenOut: USDC, - sqrtRatioX96: '2437312313659959819381354528', - liquidity: '10272714736694327408', - tickCurrent: '-69633', - }, - ], - [ - { - type: 'v3-pool', - address: '0x2f8F72aA9304c8B593d555F12eF6589cC3A579A2', - amountIn: amount`10`, - amountOut: amount`50`, - fee: '3000', - tokenIn: DAI, - tokenOut: USDC, - sqrtRatioX96: '2437312313659959819381354528', - liquidity: '10272714736694327408', - tickCurrent: '-69633', - }, - ], + const result = computeRoutes(false, false, [ + [ + { + type: 'v3-pool', + address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', + amountIn: amount`1`, + amountOut: amount`5`, + fee: '500', + tokenIn: DAI, + tokenOut: USDC, + sqrtRatioX96: '2437312313659959819381354528', + liquidity: '10272714736694327408', + tickCurrent: '-69633', + }, ], - }) + [ + { + type: 'v3-pool', + address: '0x2f8F72aA9304c8B593d555F12eF6589cC3A579A2', + amountIn: amount`10`, + amountOut: amount`50`, + fee: '3000', + tokenIn: DAI, + tokenOut: USDC, + sqrtRatioX96: '2437312313659959819381354528', + liquidity: '10272714736694327408', + tickCurrent: '-69633', + }, + ], + ]) expect(result).toBeDefined() expect(result?.length).toBe(2) @@ -199,40 +185,38 @@ describe('#useRoute', () => { }) it('computes mixed routes correctly', () => { - const result = computeRoutes(false, false, { - route: [ - [ - { - type: PoolType.V3Pool, - address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', - amountIn: amount`1`, - amountOut: amount`5`, - fee: '500', - tokenIn: DAI, - tokenOut: USDC, - sqrtRatioX96: '2437312313659959819381354528', - liquidity: '10272714736694327408', - tickCurrent: '-69633', + const result = computeRoutes(false, false, [ + [ + { + type: PoolType.V3Pool, + address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', + amountIn: amount`1`, + amountOut: amount`5`, + fee: '500', + tokenIn: DAI, + tokenOut: USDC, + sqrtRatioX96: '2437312313659959819381354528', + liquidity: '10272714736694327408', + tickCurrent: '-69633', + }, + { + type: PoolType.V2Pool, + address: 'x2f8F72aA9304c8B593d555F12eF6589cC3A579A2', + amountIn: amount`10`, + amountOut: amount`50`, + tokenIn: USDC, + tokenOut: MKR, + reserve0: { + token: USDC, + quotient: amount`100`, }, - { - type: PoolType.V2Pool, - address: 'x2f8F72aA9304c8B593d555F12eF6589cC3A579A2', - amountIn: amount`10`, - amountOut: amount`50`, - tokenIn: USDC, - tokenOut: MKR, - reserve0: { - token: USDC, - quotient: amount`100`, - }, - reserve1: { - token: MKR, - quotient: amount`200`, - }, + reserve1: { + token: MKR, + quotient: amount`200`, }, - ], + }, ], - }) + ]) expect(result).toBeDefined() expect(result?.length).toBe(1) @@ -246,24 +230,22 @@ describe('#useRoute', () => { it('outputs native ETH as input currency', () => { const WETH = ETH.wrapped - const result = computeRoutes(true, false, { - route: [ - [ - { - type: 'v3-pool', - address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', - amountIn: (1e18).toString(), - amountOut: amount`5`, - fee: '500', - sqrtRatioX96: '2437312313659959819381354528', - liquidity: '10272714736694327408', - tickCurrent: '-69633', - tokenIn: WETH, - tokenOut: USDC, - }, - ], + const result = computeRoutes(true, false, [ + [ + { + type: 'v3-pool', + address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', + amountIn: (1e18).toString(), + amountOut: amount`5`, + fee: '500', + sqrtRatioX96: '2437312313659959819381354528', + liquidity: '10272714736694327408', + tickCurrent: '-69633', + tokenIn: WETH, + tokenOut: USDC, + }, ], - }) + ]) expect(result).toBeDefined() expect(result?.length).toBe(1) @@ -275,24 +257,22 @@ describe('#useRoute', () => { it('outputs native ETH as output currency', () => { const WETH = new Token(1, ETH.wrapped.address, 18, 'WETH') - const result = computeRoutes(false, true, { - route: [ - [ - { - type: 'v3-pool', - address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', - amountIn: amount`5`, - amountOut: (1e18).toString(), - fee: '500', - sqrtRatioX96: '2437312313659959819381354528', - liquidity: '10272714736694327408', - tickCurrent: '-69633', - tokenIn: USDC, - tokenOut: WETH, - }, - ], + const result = computeRoutes(false, true, [ + [ + { + type: 'v3-pool', + address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', + amountIn: amount`5`, + amountOut: (1e18).toString(), + fee: '500', + sqrtRatioX96: '2437312313659959819381354528', + liquidity: '10272714736694327408', + tickCurrent: '-69633', + tokenIn: USDC, + tokenOut: WETH, + }, ], - }) + ]) expect(result?.length).toBe(1) expect(result?.[0].routev3?.input).toStrictEqual(USDC) @@ -304,28 +284,26 @@ describe('#useRoute', () => { it('outputs native ETH as input currency for v2 routes', () => { const WETH = ETH.wrapped - const result = computeRoutes(true, false, { - route: [ - [ - { - type: 'v2-pool', - address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', - amountIn: (1e18).toString(), - amountOut: amount`5`, - tokenIn: WETH, - tokenOut: USDC, - reserve0: { - token: WETH, - quotient: amount`100`, - }, - reserve1: { - token: USDC, - quotient: amount`200`, - }, + const result = computeRoutes(true, false, [ + [ + { + type: 'v2-pool', + address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', + amountIn: (1e18).toString(), + amountOut: amount`5`, + tokenIn: WETH, + tokenOut: USDC, + reserve0: { + token: WETH, + quotient: amount`100`, + }, + reserve1: { + token: USDC, + quotient: amount`200`, }, - ], + }, ], - }) + ]) expect(result).toBeDefined() expect(result?.length).toBe(1) @@ -337,28 +315,26 @@ describe('#useRoute', () => { it('outputs native ETH as output currency for v2 routes', () => { const WETH = new Token(1, ETH.wrapped.address, 18, 'WETH') - const result = computeRoutes(false, true, { - route: [ - [ - { - type: 'v2-pool', - address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', - amountIn: amount`5`, - amountOut: (1e18).toString(), - tokenIn: USDC, - tokenOut: WETH, - reserve0: { - token: WETH, - quotient: amount`100`, - }, - reserve1: { - token: USDC, - quotient: amount`200`, - }, + const result = computeRoutes(false, true, [ + [ + { + type: 'v2-pool', + address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2', + amountIn: amount`5`, + amountOut: (1e18).toString(), + tokenIn: USDC, + tokenOut: WETH, + reserve0: { + token: WETH, + quotient: amount`100`, }, - ], + reserve1: { + token: USDC, + quotient: amount`200`, + }, + }, ], - }) + ]) expect(result?.length).toBe(1) expect(result?.[0].routev2?.input).toStrictEqual(USDC) diff --git a/src/state/routing/utils.ts b/src/state/routing/utils.ts index 2f38cbddf..619efadee 100644 --- a/src/state/routing/utils.ts +++ b/src/state/routing/utils.ts @@ -6,8 +6,8 @@ import { isPolygonChain } from 'constants/chains' import { nativeOnChain } from 'constants/tokens' import { PoolType, SwapRouterNativeAssets } from 'hooks/routing/types' -import { TradeResult } from './types' -import { GetQuoteArgs, InterfaceTrade, QuoteResult, V2PoolInRoute, V3PoolInRoute } from './types' +import { QuoteData, QuoteState, TradeResult } from './types' +import { GetQuoteArgs, InterfaceTrade, V2PoolInRoute, V3PoolInRoute } from './types' /** * Transforms a Routing API quote into an array of routes that can be used to @@ -16,7 +16,7 @@ import { GetQuoteArgs, InterfaceTrade, QuoteResult, V2PoolInRoute, V3PoolInRoute export function computeRoutes( tokenInIsNative: boolean, tokenOutIsNative: boolean, - quoteResult?: Pick + routes: QuoteData['route'] ): | { routev3: V3Route | null @@ -26,19 +26,17 @@ export function computeRoutes( outputAmount: CurrencyAmount }[] | undefined { - if (!quoteResult || !quoteResult.route) return + if (routes.length === 0) return [] - if (quoteResult.route.length === 0) return [] - - const tokenIn = quoteResult.route[0]?.[0]?.tokenIn - const tokenOut = quoteResult.route[0]?.[quoteResult.route[0]?.length - 1]?.tokenOut + const tokenIn = routes[0]?.[0]?.tokenIn + const tokenOut = routes[0]?.[routes[0]?.length - 1]?.tokenOut if (!tokenIn || !tokenOut) throw new Error('Expected both tokenIn and tokenOut to be present') const parsedCurrencyIn = tokenInIsNative ? nativeOnChain(tokenIn.chainId) : parseToken(tokenIn) const parsedCurrencyOut = tokenOutIsNative ? nativeOnChain(tokenOut.chainId) : parseToken(tokenOut) try { - return quoteResult.route.map((route) => { + return routes.map((route) => { if (route.length === 0) { throw new Error('Expected route to have at least one pair or pool') } @@ -69,11 +67,11 @@ export function computeRoutes( } } -export function transformQuoteToTradeResult(args: GetQuoteArgs, quoteResult: QuoteResult): TradeResult { +export function transformQuoteToTradeResult(args: GetQuoteArgs, data: QuoteData): TradeResult { const { tokenInAddress, tokenOutAddress, tradeType } = args const tokenInIsNative = Object.values(SwapRouterNativeAssets).includes(tokenInAddress as SwapRouterNativeAssets) const tokenOutIsNative = Object.values(SwapRouterNativeAssets).includes(tokenOutAddress as SwapRouterNativeAssets) - const routes = computeRoutes(tokenInIsNative, tokenOutIsNative, quoteResult) + const routes = computeRoutes(tokenInIsNative, tokenOutIsNative, data.route) const trade = new InterfaceTrade({ v2Routes: @@ -110,10 +108,10 @@ export function transformQuoteToTradeResult(args: GetQuoteArgs, quoteResult: Quo tradeType, }) - return { trade, gasUseEstimateUSD: quoteResult.gasUseEstimateUSD, blockNumber: quoteResult.blockNumber } + return { state: QuoteState.SUCCESS, trade, gasUseEstimateUSD: data.gasUseEstimateUSD, blockNumber: data.blockNumber } } -const parseToken = ({ address, chainId, decimals, symbol }: QuoteResult['route'][0][0]['tokenIn']): Token => { +const parseToken = ({ address, chainId, decimals, symbol }: QuoteData['route'][0][0]['tokenIn']): Token => { return new Token(chainId, address, parseInt(decimals.toString()), symbol) }