feat: Remove local routing setting (#7462)

* remove client side router preference

* update e2e test

* fix comment
This commit is contained in:
Tina 2023-10-13 14:33:47 -04:00 committed by GitHub
parent e9fbf61375
commit cfaf5d79c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 225 additions and 180 deletions

@ -9,7 +9,6 @@ describe('Swap settings', () => {
cy.contains('Max. slippage').should('exist') cy.contains('Max. slippage').should('exist')
cy.contains('Transaction deadline').should('exist') cy.contains('Transaction deadline').should('exist')
cy.contains('UniswapX').should('exist') cy.contains('UniswapX').should('exist')
cy.contains('Local routing').should('exist')
cy.get(getTestSelector('open-settings-dialog-button')).click() cy.get(getTestSelector('open-settings-dialog-button')).click()
cy.contains('Settings').should('not.exist') cy.contains('Settings').should('not.exist')
}) })
@ -28,7 +27,6 @@ describe('Swap settings', () => {
.within(() => { .within(() => {
cy.contains('Max. slippage').should('exist') cy.contains('Max. slippage').should('exist')
cy.contains('UniswapX').should('exist') cy.contains('UniswapX').should('exist')
cy.contains('Local routing').should('exist')
cy.contains('Transaction deadline').should('exist') cy.contains('Transaction deadline').should('exist')
cy.get(getTestSelector('mobile-settings-close')).click() cy.get(getTestSelector('mobile-settings-close')).click()
}) })

@ -114,7 +114,7 @@ export function AddRemoveTabs({
)} )}
</AddRemoveTitleText> </AddRemoveTitleText>
{children && <Box style={{ marginRight: '.5rem' }}>{children}</Box>} {children && <Box style={{ marginRight: '.5rem' }}>{children}</Box>}
<SettingsTab autoSlippage={autoSlippage} chainId={chainId} showRoutingSettings={false} /> <SettingsTab autoSlippage={autoSlippage} chainId={chainId} hideRoutingSettings />
</RowBetween> </RowBetween>
</Tabs> </Tabs>
) )

@ -13,8 +13,10 @@ export default function RouterLabel({ trade, color }: { trade: SubmittableTrade;
</UniswapXRouterLabel> </UniswapXRouterLabel>
) )
} }
if (trade.quoteMethod === QuoteMethod.CLIENT_SIDE || trade.quoteMethod === QuoteMethod.CLIENT_SIDE_FALLBACK) {
if (trade.quoteMethod === QuoteMethod.CLIENT_SIDE_FALLBACK) {
return <ThemedText.BodySmall color={color}>Uniswap Client</ThemedText.BodySmall> return <ThemedText.BodySmall color={color}>Uniswap Client</ThemedText.BodySmall>
} }
return <ThemedText.BodySmall color={color}>Uniswap API</ThemedText.BodySmall> return <ThemedText.BodySmall color={color}>Uniswap API</ThemedText.BodySmall>
} }

@ -24,18 +24,4 @@ describe('RouterPreferenceSettings', () => {
expect(uniswapXToggle).toHaveAttribute('aria-selected', 'false') expect(uniswapXToggle).toHaveAttribute('aria-selected', 'false')
expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.API) expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.API)
}) })
it('toggles `Local Routing` router preference', () => {
render(<RouterPreferenceSettings />)
const localRoutingToggle = screen.getByTestId('toggle-local-routing-button')
fireEvent.click(localRoutingToggle)
expect(localRoutingToggle).toHaveAttribute('aria-selected', 'true')
expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.CLIENT)
fireEvent.click(localRoutingToggle)
expect(localRoutingToggle).toHaveAttribute('aria-selected', 'false')
expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.API)
})
}) })

@ -1,17 +1,15 @@
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import Column from 'components/Column' import Column from 'components/Column'
import UniswapXBrandMark from 'components/Logo/UniswapXBrandMark' import UniswapXBrandMark from 'components/Logo/UniswapXBrandMark'
import { RowBetween, RowFixed } from 'components/Row' import { RowBetween, RowFixed } from 'components/Row'
import Toggle from 'components/Toggle' import Toggle from 'components/Toggle'
import { isUniswapXSupportedChain } from 'constants/chains'
import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault'
import { useAppDispatch } from 'state/hooks' import { useAppDispatch } from 'state/hooks'
import { RouterPreference } from 'state/routing/types' import { RouterPreference } from 'state/routing/types'
import { useRouterPreference, useUserOptedOutOfUniswapX } from 'state/user/hooks' import { useRouterPreference, useUserOptedOutOfUniswapX } from 'state/user/hooks'
import { updateDisabledUniswapX, updateOptedOutOfUniswapX } from 'state/user/reducer' import { updateDisabledUniswapX, updateOptedOutOfUniswapX } from 'state/user/reducer'
import styled from 'styled-components' import styled from 'styled-components'
import { Divider, ExternalLink, ThemedText } from 'theme/components' import { ExternalLink, ThemedText } from 'theme/components'
const InlineLink = styled(ThemedText.BodySmall)` const InlineLink = styled(ThemedText.BodySmall)`
color: ${({ theme }) => theme.accent1}; color: ${({ theme }) => theme.accent1};
@ -23,81 +21,48 @@ const InlineLink = styled(ThemedText.BodySmall)`
` `
export default function RouterPreferenceSettings() { export default function RouterPreferenceSettings() {
const { chainId } = useWeb3React()
const [routerPreference, setRouterPreference] = useRouterPreference() const [routerPreference, setRouterPreference] = useRouterPreference()
const uniswapXEnabled = chainId && isUniswapXSupportedChain(chainId)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX() const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX()
const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled()
const isUniswapXOverrideEnabled = isUniswapXDefaultEnabled && !userOptedOutOfUniswapX const isUniswapXOverrideEnabled = isUniswapXDefaultEnabled && !userOptedOutOfUniswapX
const uniswapXInEffect = const uniswapXInEffect = routerPreference === RouterPreference.X || isUniswapXOverrideEnabled
routerPreference === RouterPreference.X ||
(routerPreference !== RouterPreference.CLIENT && isUniswapXOverrideEnabled)
return ( return (
<> <RowBetween gap="sm">
{uniswapXEnabled && ( <RowFixed>
<> <Column gap="xs">
<RowBetween gap="sm"> <ThemedText.BodySecondary>
<RowFixed> <UniswapXBrandMark />
<Column gap="xs"> </ThemedText.BodySecondary>
<ThemedText.BodySecondary> <ThemedText.BodySmall color="neutral2">
<UniswapXBrandMark /> <Trans>When available, aggregates liquidity sources for better prices and gas free swaps.</Trans>{' '}
</ThemedText.BodySecondary> <ExternalLink href="https://support.uniswap.org/hc/en-us/articles/17515415311501">
<ThemedText.BodySmall color="neutral2"> <InlineLink>Learn more</InlineLink>
<Trans>When available, aggregates liquidity sources for better prices and gas free swaps.</Trans>{' '} </ExternalLink>
<ExternalLink href="https://support.uniswap.org/hc/en-us/articles/17515415311501"> </ThemedText.BodySmall>
<InlineLink>Learn more</InlineLink> </Column>
</ExternalLink> </RowFixed>
</ThemedText.BodySmall> <Toggle
</Column> id="toggle-uniswap-x-button"
</RowFixed> // If UniswapX-by-default is enabled we need to render this as active even if routerPreference === RouterPreference.API
<Toggle // because we're going to default to the UniswapX quote.
id="toggle-uniswap-x-button" // If the user manually toggles it off, this doesn't apply.
// If UniswapX-by-default is enabled we need to render this as active even if routerPreference === RouterPreference.API isActive={uniswapXInEffect}
// because we're going to default to the UniswapX quote. toggle={() => {
// If the user manually toggles it off, this doesn't apply. if (uniswapXInEffect) {
isActive={uniswapXInEffect} if (isUniswapXDefaultEnabled) {
toggle={() => { // We need to remember if a opts out of UniswapX, so we don't request UniswapX quotes.
if (uniswapXInEffect) { dispatch(updateOptedOutOfUniswapX({ optedOutOfUniswapX: true }))
if (isUniswapXDefaultEnabled) { } else {
// We need to remember if a opts out of UniswapX, so we don't request UniswapX quotes. // We need to remember if a user disables Uniswap X, so we don't show the opt-in flow again.
dispatch(updateOptedOutOfUniswapX({ optedOutOfUniswapX: true })) dispatch(updateDisabledUniswapX({ disabledUniswapX: true }))
} else { }
// We need to remember if a user disables Uniswap X, so we don't show the opt-in flow again.
dispatch(updateDisabledUniswapX({ disabledUniswapX: true }))
}
}
setRouterPreference(uniswapXInEffect ? RouterPreference.API : RouterPreference.X)
}}
/>
</RowBetween>
<Divider />
</>
)}
<RowBetween gap="sm">
<RowFixed>
<Column gap="xs">
<ThemedText.BodySecondary>
<Trans>Local routing</Trans>
</ThemedText.BodySecondary>
</Column>
</RowFixed>
<Toggle
id="toggle-local-routing-button"
isActive={routerPreference === RouterPreference.CLIENT}
toggle={() =>
setRouterPreference(
routerPreference === RouterPreference.CLIENT
? isUniswapXDefaultEnabled
? RouterPreference.X
: RouterPreference.API
: RouterPreference.CLIENT
)
} }
/> setRouterPreference(uniswapXInEffect ? RouterPreference.API : RouterPreference.X)
</RowBetween> }}
</> />
</RowBetween>
) )
} }

@ -1,5 +1,5 @@
import { Percent } from '@uniswap/sdk-core' import { Percent } from '@uniswap/sdk-core'
import { isSupportedChain } from 'constants/chains' import { isSupportedChain, isUniswapXSupportedChain } from 'constants/chains'
import { mocked } from 'test-utils/mocked' import { mocked } from 'test-utils/mocked'
import { fireEvent, render, screen, waitFor } from 'test-utils/render' import { fireEvent, render, screen, waitFor } from 'test-utils/render'
@ -14,25 +14,38 @@ describe('Settings Tab', () => {
mocked(isSupportedChain).mockReturnValue(true) mocked(isSupportedChain).mockReturnValue(true)
}) })
it('renders routing settings when showRoutingSettings is true', async () => { it('renders routing settings when hideRoutingSettings is false', async () => {
render(<SettingsTab showRoutingSettings={true} chainId={1} autoSlippage={slippage} />) mocked(isUniswapXSupportedChain).mockReturnValue(true)
render(<SettingsTab hideRoutingSettings={false} chainId={1} autoSlippage={slippage} />)
const settingsButton = screen.getByTestId('open-settings-dialog-button') const settingsButton = screen.getByTestId('open-settings-dialog-button')
fireEvent.click(settingsButton) fireEvent.click(settingsButton)
await waitFor(() => { await waitFor(() => {
expect(screen.getByTestId('toggle-local-routing-button')).toBeInTheDocument() expect(screen.getByTestId('toggle-uniswap-x-button')).toBeInTheDocument()
}) })
}) })
it('does not render routing settings when showRoutingSettings is false', async () => { it('does not render routing settings when hideRoutingSettings is true', async () => {
render(<SettingsTab showRoutingSettings={false} chainId={1} autoSlippage={slippage} />) render(<SettingsTab hideRoutingSettings chainId={1} autoSlippage={slippage} />)
const settingsButton = screen.getByTestId('open-settings-dialog-button') const settingsButton = screen.getByTestId('open-settings-dialog-button')
fireEvent.click(settingsButton) fireEvent.click(settingsButton)
await waitFor(() => { await waitFor(() => {
expect(screen.queryByTestId('toggle-local-routing-button')).not.toBeInTheDocument() expect(screen.queryByTestId('toggle-uniswap-x-button')).not.toBeInTheDocument()
})
})
it('does not render routing settings when uniswapx is not enabled', async () => {
mocked(isUniswapXSupportedChain).mockReturnValue(false)
render(<SettingsTab hideRoutingSettings chainId={1} autoSlippage={slippage} />)
const settingsButton = screen.getByTestId('open-settings-dialog-button')
fireEvent.click(settingsButton)
await waitFor(() => {
expect(screen.queryByTestId('toggle-uniswap-x-button')).not.toBeInTheDocument()
}) })
}) })
}) })

@ -5,7 +5,7 @@ import { Scrim } from 'components/AccountDrawer'
import AnimatedDropdown from 'components/AnimatedDropdown' import AnimatedDropdown from 'components/AnimatedDropdown'
import Column, { AutoColumn } from 'components/Column' import Column, { AutoColumn } from 'components/Column'
import Row from 'components/Row' import Row from 'components/Row'
import { isSupportedChain, L2_CHAIN_IDS } from 'constants/chains' import { isSupportedChain, isUniswapXSupportedChain, L2_CHAIN_IDS } from 'constants/chains'
import useDisableScrolling from 'hooks/useDisableScrolling' import useDisableScrolling from 'hooks/useDisableScrolling'
import { useOnClickOutside } from 'hooks/useOnClickOutside' import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { Portal } from 'nft/components/common/Portal' import { Portal } from 'nft/components/common/Portal'
@ -101,12 +101,12 @@ export default function SettingsTab({
autoSlippage, autoSlippage,
chainId, chainId,
trade, trade,
showRoutingSettings = true, hideRoutingSettings = false,
}: { }: {
autoSlippage: Percent autoSlippage: Percent
chainId?: number chainId?: number
trade?: InterfaceTrade trade?: InterfaceTrade
showRoutingSettings?: boolean hideRoutingSettings?: boolean
}) { }) {
const { chainId: connectedChainId } = useWeb3React() const { chainId: connectedChainId } = useWeb3React()
const showDeadlineSettings = Boolean(chainId && !L2_CHAIN_IDS.includes(chainId)) const showDeadlineSettings = Boolean(chainId && !L2_CHAIN_IDS.includes(chainId))
@ -124,6 +124,9 @@ export default function SettingsTab({
useOnClickOutside(node, isOpenDesktop ? closeMenu : undefined) useOnClickOutside(node, isOpenDesktop ? closeMenu : undefined)
useDisableScrolling(isOpen) useDisableScrolling(isOpen)
const uniswapXEnabled = chainId && isUniswapXSupportedChain(chainId)
const showRoutingSettings = Boolean(uniswapXEnabled && !hideRoutingSettings)
const isChainSupported = isSupportedChain(chainId) const isChainSupported = isSupportedChain(chainId)
const Settings = useMemo( const Settings = useMemo(
() => ( () => (

@ -34,7 +34,7 @@ beforeEach(() => {
mocked(useIsWindowVisible).mockReturnValue(true) mocked(useIsWindowVisible).mockReturnValue(true)
mocked(useAutoRouterSupported).mockReturnValue(true) mocked(useAutoRouterSupported).mockReturnValue(true)
mocked(useRouterPreference).mockReturnValue([RouterPreference.CLIENT, () => undefined]) mocked(useRouterPreference).mockReturnValue([RouterPreference.API, () => undefined])
}) })
describe('#useBestV3Trade ExactIn', () => { describe('#useBestV3Trade ExactIn', () => {
@ -49,7 +49,7 @@ describe('#useBestV3Trade ExactIn', () => {
TradeType.EXACT_INPUT, TradeType.EXACT_INPUT,
USDCAmount, USDCAmount,
DAI, DAI,
RouterPreference.CLIENT, RouterPreference.API,
/* account = */ undefined, /* account = */ undefined,
/* inputTax = */ undefined, /* inputTax = */ undefined,
/* outputTax = */ undefined /* outputTax = */ undefined
@ -69,7 +69,7 @@ describe('#useDebouncedTrade ExactOut', () => {
TradeType.EXACT_OUTPUT, TradeType.EXACT_OUTPUT,
DAIAmount, DAIAmount,
USDC_MAINNET, USDC_MAINNET,
RouterPreference.CLIENT, RouterPreference.API,
/* account = */ undefined, /* account = */ undefined,
/* inputTax = */ undefined, /* inputTax = */ undefined,
/* outputTax = */ undefined /* outputTax = */ undefined

@ -37,7 +37,7 @@ export function useDebouncedTrade(
tradeType: TradeType, tradeType: TradeType,
amountSpecified?: CurrencyAmount<Currency>, amountSpecified?: CurrencyAmount<Currency>,
otherCurrency?: Currency, otherCurrency?: Currency,
routerPreferenceOverride?: RouterPreference.API | RouterPreference.CLIENT, routerPreferenceOverride?: RouterPreference.API,
account?: string, account?: string,
inputTax?: Percent, inputTax?: Percent,
outputTax?: Percent outputTax?: Percent
@ -97,8 +97,7 @@ export function useDebouncedTrade(
const skipBothFetches = !autoRouterSupported || !isWindowVisible || isWrap const skipBothFetches = !autoRouterSupported || !isWindowVisible || isWrap
const skipRoutingFetch = skipBothFetches || isDebouncing const skipRoutingFetch = skipBothFetches || isDebouncing
const skipPreviewTradeFetch = const skipPreviewTradeFetch = skipBothFetches || isPreviewTradeDebouncing
skipBothFetches || routerPreference === RouterPreference.CLIENT || isPreviewTradeDebouncing
const previewTradeResult = usePreviewTrade( const previewTradeResult = usePreviewTrade(
skipPreviewTradeFetch, skipPreviewTradeFetch,

@ -739,11 +739,7 @@ export default function MigrateV2Pair() {
<ThemedText.DeprecatedMediumHeader> <ThemedText.DeprecatedMediumHeader>
<Trans>Migrate V2 liquidity</Trans> <Trans>Migrate V2 liquidity</Trans>
</ThemedText.DeprecatedMediumHeader> </ThemedText.DeprecatedMediumHeader>
<SettingsTab <SettingsTab autoSlippage={DEFAULT_MIGRATE_SLIPPAGE_TOLERANCE} chainId={chainId} hideRoutingSettings />
autoSlippage={DEFAULT_MIGRATE_SLIPPAGE_TOLERANCE}
chainId={chainId}
showRoutingSettings={false}
/>
</AutoRow> </AutoRow>
{!account ? ( {!account ? (

@ -13,7 +13,7 @@ const defaultState = {
user: {}, user: {},
_persist: { _persist: {
rehydrated: true, rehydrated: true,
version: 1, version: 2,
}, },
application: { application: {
chainId: null, chainId: null,

@ -3,6 +3,7 @@ import { MigrationConfig } from 'redux-persist/es/createMigrate'
import { migration0 } from './migrations/0' import { migration0 } from './migrations/0'
import { migration1 } from './migrations/1' import { migration1 } from './migrations/1'
import { migration2 } from './migrations/2'
import { legacyLocalStorageMigration } from './migrations/legacy' import { legacyLocalStorageMigration } from './migrations/legacy'
/** /**
@ -17,6 +18,7 @@ import { legacyLocalStorageMigration } from './migrations/legacy'
export const migrations: MigrationManifest = { export const migrations: MigrationManifest = {
0: migration0, 0: migration0,
1: migration1, 1: migration1,
2: migration2,
} }
// We use a custom migration function for the initial state, because redux-persist // We use a custom migration function for the initial state, because redux-persist

@ -0,0 +1,62 @@
import { createMigrate } from 'redux-persist'
import { RouterPreference } from 'state/routing/types'
import { SlippageTolerance } from 'state/user/types'
import { migration1 } from './1'
import { migration2, PersistAppStateV2 } from './2'
const previousState: PersistAppStateV2 = {
user: {
userLocale: null,
// @ts-ignore this is intentionally a string and not the `RouterPreference` enum because `client` is a deprecated option
userRouterPreference: 'client',
userHideClosedPositions: false,
userSlippageTolerance: SlippageTolerance.Auto,
userSlippageToleranceHasBeenMigratedToAuto: true,
userDeadline: 1800,
tokens: {},
pairs: {},
timestamp: Date.now(),
hideBaseWalletBanner: false,
},
_persist: {
version: 1,
rehydrated: true,
},
}
describe('migration to v2', () => {
it('should migrate users who currently have `client` router preference', async () => {
const migrator = createMigrate(
{
1: migration1,
2: migration2,
},
{ debug: false }
)
const result: any = await migrator(previousState, 2)
expect(result?.user?.userRouterPreference).toEqual(RouterPreference.API)
expect(result?._persist.version).toEqual(2)
})
it('should not migrate non-client router preference', async () => {
const migrator = createMigrate(
{
1: migration1,
2: migration2,
},
{ debug: false }
)
const result: any = await migrator(
{
...previousState,
user: {
...previousState.user,
userRouterPreference: RouterPreference.X,
},
} as PersistAppStateV2,
2
)
expect(result?.user?.userRouterPreference).toEqual(RouterPreference.X)
})
})

29
src/state/migrations/2.ts Normal file

@ -0,0 +1,29 @@
import { PersistState } from 'redux-persist'
import { RouterPreference } from 'state/routing/types'
import { UserState } from 'state/user/reducer'
export type PersistAppStateV2 = {
_persist: PersistState
} & { user?: UserState }
/**
* Migration to move users who have local routing as their router preference to API
* since forced local routing is now deprecated
*/
export const migration2 = (state: PersistAppStateV2 | undefined) => {
// @ts-ignore this is intentionally a string and not the `RouterPreference` enum because `client` is a deprecated option
if (state?.user && state.user?.userRouterPreference === 'client') {
return {
...state,
user: {
...state.user,
userRouterPreference: RouterPreference.API,
},
_persist: {
...state._persist,
version: 2,
},
}
}
return state
}

@ -44,7 +44,7 @@ export type AppState = ReturnType<typeof appReducer>
const persistConfig: PersistConfig<AppState> = { const persistConfig: PersistConfig<AppState> = {
key: 'interface', key: 'interface',
version: 1, // see migrations.ts for more details about this version version: 2, // see migrations.ts for more details about this version
storage: localForage.createInstance({ storage: localForage.createInstance({
name: 'redux', name: 'redux',
}), }),

@ -19,7 +19,7 @@ import {
URAQuoteResponse, URAQuoteResponse,
URAQuoteType, URAQuoteType,
} from './types' } from './types'
import { isExactInput, shouldUseAPIRouter, transformRoutesToTrade } from './utils' import { isExactInput, transformRoutesToTrade } from './utils'
const UNISWAP_API_URL = process.env.REACT_APP_UNISWAP_API_URL const UNISWAP_API_URL = process.env.REACT_APP_UNISWAP_API_URL
if (UNISWAP_API_URL === undefined) { if (UNISWAP_API_URL === undefined) {
@ -121,73 +121,69 @@ export const routingApi = createApi({
) )
}, },
async queryFn(args, _api, _extraOptions, fetch) { async queryFn(args, _api, _extraOptions, fetch) {
let fellBack = false
logSwapQuoteRequest(args.tokenInChainId, args.routerPreference, false) logSwapQuoteRequest(args.tokenInChainId, args.routerPreference, false)
const quoteStartMark = performance.mark(`quote-fetch-start-${Date.now()}`) const quoteStartMark = performance.mark(`quote-fetch-start-${Date.now()}`)
if (shouldUseAPIRouter(args)) {
fellBack = true
try {
const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, tradeType } = args
const type = isExactInput(tradeType) ? 'EXACT_INPUT' : 'EXACT_OUTPUT'
const requestBody = {
tokenInChainId,
tokenIn: tokenInAddress,
tokenOutChainId,
tokenOut: tokenOutAddress,
amount,
type,
intent: args.routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? 'pricing' : undefined,
configs: getRoutingAPIConfig(args),
}
const response = await fetch({
method: 'POST',
url: '/quote',
body: JSON.stringify(requestBody),
})
if (response.error) {
try {
// cast as any here because we do a runtime check on it being an object before indexing into .errorCode
const errorData = response.error.data as { errorCode?: string; detail?: string }
// NO_ROUTE should be treated as a valid response to prevent retries.
if (
typeof errorData === 'object' &&
(errorData?.errorCode === 'NO_ROUTE' || errorData?.detail === 'No quotes available')
) {
sendAnalyticsEvent('No quote received from routing API', {
requestBody,
response,
routerPreference: args.routerPreference,
})
return {
data: { state: QuoteState.NOT_FOUND, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration },
}
}
} catch {
throw response.error
}
}
const uraQuoteResponse = response.data as URAQuoteResponse
const tradeResult = await transformRoutesToTrade(args, uraQuoteResponse, QuoteMethod.ROUTING_API)
return { data: { ...tradeResult, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration } }
} catch (error: any) {
console.warn(
`GetQuote failed on Unified Routing API, falling back to client: ${
error?.message ?? error?.detail ?? error
}`
)
}
}
try { try {
const method = fellBack ? QuoteMethod.CLIENT_SIDE_FALLBACK : QuoteMethod.CLIENT_SIDE const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, tradeType } = args
const type = isExactInput(tradeType) ? 'EXACT_INPUT' : 'EXACT_OUTPUT'
const requestBody = {
tokenInChainId,
tokenIn: tokenInAddress,
tokenOutChainId,
tokenOut: tokenOutAddress,
amount,
type,
intent: args.routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? 'pricing' : undefined,
configs: getRoutingAPIConfig(args),
}
const response = await fetch({
method: 'POST',
url: '/quote',
body: JSON.stringify(requestBody),
})
if (response.error) {
try {
// cast as any here because we do a runtime check on it being an object before indexing into .errorCode
const errorData = response.error.data as { errorCode?: string; detail?: string }
// NO_ROUTE should be treated as a valid response to prevent retries.
if (
typeof errorData === 'object' &&
(errorData?.errorCode === 'NO_ROUTE' || errorData?.detail === 'No quotes available')
) {
sendAnalyticsEvent('No quote received from routing API', {
requestBody,
response,
routerPreference: args.routerPreference,
})
return {
data: { state: QuoteState.NOT_FOUND, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration },
}
}
} catch {
throw response.error
}
}
const uraQuoteResponse = response.data as URAQuoteResponse
const tradeResult = await transformRoutesToTrade(args, uraQuoteResponse, QuoteMethod.ROUTING_API)
return { data: { ...tradeResult, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration } }
} catch (error: any) {
console.warn(
`GetQuote failed on Unified Routing API, falling back to client: ${
error?.message ?? error?.detail ?? error
}`
)
}
try {
const { getRouter, getClientSideQuote } = await import('lib/hooks/routing/clientSideSmartOrderRouter') const { getRouter, getClientSideQuote } = await import('lib/hooks/routing/clientSideSmartOrderRouter')
const router = getRouter(args.tokenInChainId) const router = getRouter(args.tokenInChainId)
const quoteResult = await getClientSideQuote(args, router, CLIENT_PARAMS) const quoteResult = await getClientSideQuote(args, router, CLIENT_PARAMS)
if (quoteResult.state === QuoteState.SUCCESS) { if (quoteResult.state === QuoteState.SUCCESS) {
const trade = await transformRoutesToTrade(args, quoteResult.data, method) const trade = await transformRoutesToTrade(args, quoteResult.data, QuoteMethod.CLIENT_SIDE_FALLBACK)
return { return {
data: { ...trade, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration }, data: { ...trade, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration },
} }

@ -16,7 +16,6 @@ export enum TradeState {
export enum QuoteMethod { export enum QuoteMethod {
ROUTING_API = 'ROUTING_API', ROUTING_API = 'ROUTING_API',
QUICK_ROUTE = 'QUICK_ROUTE', QUICK_ROUTE = 'QUICK_ROUTE',
CLIENT_SIDE = 'CLIENT_SIDE',
CLIENT_SIDE_FALLBACK = 'CLIENT_SIDE_FALLBACK', // If client-side was used after the routing-api call failed. CLIENT_SIDE_FALLBACK = 'CLIENT_SIDE_FALLBACK', // If client-side was used after the routing-api call failed.
} }
@ -27,7 +26,6 @@ export const INTERNAL_ROUTER_PREFERENCE_PRICE = 'price' as const
export enum RouterPreference { export enum RouterPreference {
X = 'uniswapx', X = 'uniswapx',
API = 'api', API = 'api',
CLIENT = 'client',
} }
export interface GetQuoteArgs { export interface GetQuoteArgs {

@ -339,7 +339,3 @@ export function isSubmittableTrade(trade?: InterfaceTrade): trade is Submittable
export function isUniswapXTrade(trade?: InterfaceTrade): trade is DutchOrderTrade { export function isUniswapXTrade(trade?: InterfaceTrade): trade is DutchOrderTrade {
return trade?.fillType === TradeFillType.UniswapX return trade?.fillType === TradeFillType.UniswapX
} }
export function shouldUseAPIRouter(args: GetQuoteArgs): boolean {
return args.routerPreference !== RouterPreference.CLIENT
}

@ -48,7 +48,7 @@ export const TEST_TRADE_EXACT_INPUT = new ClassicTrade({
tradeType: TradeType.EXACT_INPUT, tradeType: TradeType.EXACT_INPUT,
gasUseEstimateUSD: 1.0, gasUseEstimateUSD: 1.0,
approveInfo: { needsApprove: false }, approveInfo: { needsApprove: false },
quoteMethod: QuoteMethod.CLIENT_SIDE, quoteMethod: QuoteMethod.CLIENT_SIDE_FALLBACK,
inputTax: ZERO_PERCENT, inputTax: ZERO_PERCENT,
outputTax: ZERO_PERCENT, outputTax: ZERO_PERCENT,
}) })
@ -80,7 +80,7 @@ export const TEST_TRADE_EXACT_OUTPUT = new ClassicTrade({
], ],
v2Routes: [], v2Routes: [],
tradeType: TradeType.EXACT_OUTPUT, tradeType: TradeType.EXACT_OUTPUT,
quoteMethod: QuoteMethod.CLIENT_SIDE, quoteMethod: QuoteMethod.CLIENT_SIDE_FALLBACK,
approveInfo: { needsApprove: false }, approveInfo: { needsApprove: false },
inputTax: ZERO_PERCENT, inputTax: ZERO_PERCENT,
outputTax: ZERO_PERCENT, outputTax: ZERO_PERCENT,