Skip to content

Commit

Permalink
fix: stricter typing for router query (#548)
Browse files Browse the repository at this point in the history
* fix: stricter typing for router query

* fix: rm unused bits

* fix: tighten types even more
  • Loading branch information
zzmp authored Mar 10, 2023
1 parent 7fd2f2e commit ee653ee
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 297 deletions.
14 changes: 7 additions & 7 deletions src/hooks/routing/clientSideSmartOrderRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -82,7 +82,7 @@ function getRouter(chainId: ChainId, provider: BaseProvider): AlphaRouter {
return router
}

async function getQuote(
async function getQuoteResult(
{
tradeType,
tokenIn,
Expand All @@ -96,7 +96,7 @@ async function getQuote(
},
router: AlphaRouter,
routerConfig: Partial<AlphaRouterConfig>
): Promise<QuoteResult | GetQuoteError> {
): Promise<QuoteResult> {
const tokenInIsNative = Object.values(SwapRouterNativeAssets).includes(tokenIn.address as SwapRouterNativeAssets)
const tokenOutIsNative = Object.values(SwapRouterNativeAssets).includes(tokenOut.address as SwapRouterNativeAssets)
const currencyIn = tokenInIsNative
Expand All @@ -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,
Expand All @@ -138,7 +138,7 @@ export async function getClientSideQuote(
}

const router = getRouter(tokenInChainId, provider)
return getQuote(
return getQuoteResult(
{
tradeType,
tokenIn: {
Expand Down
35 changes: 19 additions & 16 deletions src/hooks/routing/transformSwapRouteToGetQuoteResult.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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,
},
}
}
20 changes: 12 additions & 8 deletions src/hooks/routing/useRouterTrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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.
Expand All @@ -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])
}
30 changes: 15 additions & 15 deletions src/state/routing/slice.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
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'
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]
Expand All @@ -16,19 +16,18 @@ const DEFAULT_QUERY_PARAMS = {
protocols: protocols.map((p) => p.toLowerCase()).join(','),
}

const baseQuery: BaseQueryFn<GetQuoteArgs, TradeQuoteResult> = () => {
const baseQuery: BaseQueryFn<GetQuoteArgs, TradeResult> = () => {
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 (
Expand Down Expand Up @@ -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(
Expand All @@ -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 } }
Expand Down
43 changes: 30 additions & 13 deletions src/state/routing/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Currency, Currency, TradeType> {}
Loading

1 comment on commit ee653ee

@vercel
Copy link

@vercel vercel bot commented on ee653ee Mar 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

widgets – ./

widgets-git-main-uniswap.vercel.app
widgets-uniswap.vercel.app
widgets-seven-tau.vercel.app

Please sign in to comment.