Commit 33c24a3f authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

fix: trade displays (#3594)

* fix: show syncing over insufficient balance

* fix: mv anti-janking up a level

* feat: add error caption for completeness

* chore: clarify naming
parent 9ef2b3a1
...@@ -48,6 +48,10 @@ export function InsufficientLiquidity() { ...@@ -48,6 +48,10 @@ export function InsufficientLiquidity() {
return <Caption caption={<Trans>Insufficient liquidity in the pool for your trade</Trans>} /> return <Caption caption={<Trans>Insufficient liquidity in the pool for your trade</Trans>} />
} }
export function Error() {
return <Caption caption={<Trans>Error fetching trade</Trans>} />
}
export function Empty() { export function Empty() {
return <Caption icon={Info} caption={<Trans>Enter an amount</Trans>} /> return <Caption icon={Info} caption={<Trans>Enter an amount</Trans>} />
} }
......
...@@ -25,7 +25,6 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) { ...@@ -25,7 +25,6 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) {
trade: { trade, state }, trade: { trade, state },
impact, impact,
} = useSwapInfo() } = useSwapInfo()
const isRouteLoading = state === TradeState.SYNCING || state === TradeState.LOADING
const isAmountPopulated = useIsAmountPopulated() const isAmountPopulated = useIsAmountPopulated()
const { type: wrapType } = useWrapCallback() const { type: wrapType } = useWrapCallback()
const caption = useMemo(() => { const caption = useMemo(() => {
...@@ -38,22 +37,23 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) { ...@@ -38,22 +37,23 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) {
} }
if (inputCurrency && outputCurrency && isAmountPopulated) { if (inputCurrency && outputCurrency && isAmountPopulated) {
if (state === TradeState.SYNCING || state === TradeState.LOADING) {
return <Caption.LoadingTrade />
}
if (inputBalance && inputAmount?.greaterThan(inputBalance)) { if (inputBalance && inputAmount?.greaterThan(inputBalance)) {
return <Caption.InsufficientBalance currency={inputCurrency} /> return <Caption.InsufficientBalance currency={inputCurrency} />
} }
if (wrapType !== WrapType.NONE) { if (wrapType !== WrapType.NONE) {
return <Caption.WrapCurrency inputCurrency={inputCurrency} outputCurrency={outputCurrency} /> return <Caption.WrapCurrency inputCurrency={inputCurrency} outputCurrency={outputCurrency} />
} }
if (isRouteLoading) { if (state === TradeState.NO_ROUTE_FOUND || (trade && !trade.swaps)) {
return <Caption.LoadingTrade /> return <Caption.InsufficientLiquidity />
}
if (trade?.inputAmount && trade.outputAmount) {
return <Caption.Trade trade={trade} outputUSDC={outputUSDC} impact={impact} />
} }
if (trade) { if (state === TradeState.INVALID) {
if (!trade.swaps) { return <Caption.Error />
return <Caption.InsufficientLiquidity />
}
if (trade.inputAmount && trade.outputAmount) {
return <Caption.Trade trade={trade} outputUSDC={outputUSDC} impact={impact} />
}
} }
} }
...@@ -66,9 +66,9 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) { ...@@ -66,9 +66,9 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) {
inputBalance, inputBalance,
inputCurrency, inputCurrency,
isAmountPopulated, isAmountPopulated,
isRouteLoading,
outputCurrency, outputCurrency,
outputUSDC, outputUSDC,
state,
trade, trade,
wrapType, wrapType,
]) ])
......
...@@ -4,7 +4,6 @@ import { Protocol } from '@uniswap/router-sdk' ...@@ -4,7 +4,6 @@ import { Protocol } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { ChainId } from '@uniswap/smart-order-router' import { ChainId } from '@uniswap/smart-order-router'
import useDebounce from 'hooks/useDebounce' import useDebounce from 'hooks/useDebounce'
import useLast from 'hooks/useLast'
import { useStablecoinAmountFromFiatValue } from 'hooks/useUSDCPrice' import { useStablecoinAmountFromFiatValue } from 'hooks/useUSDCPrice'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { GetQuoteResult, InterfaceTrade, TradeState } from 'state/routing/types' import { GetQuoteResult, InterfaceTrade, TradeState } from 'state/routing/types'
...@@ -111,29 +110,19 @@ export default function useClientSideSmartOrderRouterTrade<TTradeType extends Tr ...@@ -111,29 +110,19 @@ export default function useClientSideSmartOrderRouterTrade<TTradeType extends Tr
} }
return return
}, [gasUseEstimateUSD, route, tradeType]) }, [gasUseEstimateUSD, route, tradeType])
const lastTrade = useLast(trade, Boolean) ?? undefined
return useMemo(() => { return useMemo(() => {
if (!currencyIn || !currencyOut) { if (!currencyIn || !currencyOut) {
return { state: TradeState.INVALID, trade: undefined } return { state: TradeState.INVALID, trade: undefined }
} }
// Returns the last trade state while syncing/loading to avoid jank from clearing the last trade while loading.
if (!trade && !error) { if (!trade && !error) {
// Dont return last trade if currencies dont match. if (isDebouncing) {
const isStale = return { state: TradeState.SYNCING, trade: undefined }
(currencyIn && !lastTrade?.inputAmount?.currency.equals(currencyIn)) || } else if (!isValid) {
(currencyOut && !lastTrade?.outputAmount?.currency.equals(currencyOut))
if (isStale) {
return { state: TradeState.LOADING, trade: undefined } return { state: TradeState.LOADING, trade: undefined }
} else if (isDebouncing) {
return { state: TradeState.SYNCING, trade: lastTrade }
} }
} }
if (!isValid && !error) {
return { state: TradeState.LOADING, trade: lastTrade }
}
let otherAmount = undefined let otherAmount = undefined
if (quoteResult) { if (quoteResult) {
...@@ -155,5 +144,5 @@ export default function useClientSideSmartOrderRouterTrade<TTradeType extends Tr ...@@ -155,5 +144,5 @@ export default function useClientSideSmartOrderRouterTrade<TTradeType extends Tr
return { state: TradeState.VALID, trade } return { state: TradeState.VALID, trade }
} }
return { state: TradeState.INVALID, trade: undefined } return { state: TradeState.INVALID, trade: undefined }
}, [currencyIn, currencyOut, trade, error, isValid, quoteResult, route, isDebouncing, lastTrade, tradeType]) }, [currencyIn, currencyOut, trade, error, isValid, quoteResult, route, isDebouncing, tradeType])
} }
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { useClientSideV3Trade } from 'hooks/useClientSideV3Trade' import { useClientSideV3Trade } from 'hooks/useClientSideV3Trade'
import useLast from 'hooks/useLast'
import { useMemo } from 'react'
import { InterfaceTrade, TradeState } from 'state/routing/types' import { InterfaceTrade, TradeState } from 'state/routing/types'
import useClientSideSmartOrderRouterTrade from '../routing/useClientSideSmartOrderRouterTrade' import useClientSideSmartOrderRouterTrade from '../routing/useClientSideSmartOrderRouterTrade'
...@@ -18,15 +20,38 @@ export function useBestTrade( ...@@ -18,15 +20,38 @@ export function useBestTrade(
state: TradeState state: TradeState
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
} { } {
const clientSORTrade = useClientSideSmartOrderRouterTrade(tradeType, amountSpecified, otherCurrency) const clientSORTradeObject = useClientSideSmartOrderRouterTrade(tradeType, amountSpecified, otherCurrency)
// Use a simple client side logic as backup if SOR is not available. // Use a simple client side logic as backup if SOR is not available.
const useFallback = clientSORTrade.state === TradeState.NO_ROUTE_FOUND || clientSORTrade.state === TradeState.INVALID const useFallback =
const fallbackTrade = useClientSideV3Trade( clientSORTradeObject.state === TradeState.NO_ROUTE_FOUND || clientSORTradeObject.state === TradeState.INVALID
const fallbackTradeObject = useClientSideV3Trade(
tradeType, tradeType,
useFallback ? amountSpecified : undefined, useFallback ? amountSpecified : undefined,
useFallback ? otherCurrency : undefined useFallback ? otherCurrency : undefined
) )
return useFallback ? fallbackTrade : clientSORTrade const tradeObject = useFallback ? fallbackTradeObject : clientSORTradeObject
const lastTrade = useLast(tradeObject.trade, Boolean) ?? undefined
// Return the last trade while syncing/loading to avoid jank from clearing the last trade while loading.
// If the trade is unsettled and not stale, return the last trade as a placeholder during settling.
return useMemo(() => {
const { state, trade } = tradeObject
// If the trade is in a settled state, return it.
if ((state !== TradeState.LOADING && state !== TradeState.SYNCING) || trade) return tradeObject
const [currencyIn, currencyOut] =
tradeType === TradeType.EXACT_INPUT
? [amountSpecified?.currency, otherCurrency]
: [otherCurrency, amountSpecified?.currency]
// If the trade currencies have switched, consider it stale - do not return the last trade.
const isStale =
(currencyIn && !lastTrade?.inputAmount?.currency.equals(currencyIn)) ||
(currencyOut && !lastTrade?.outputAmount?.currency.equals(currencyOut))
if (isStale) return tradeObject
return { state, trade: lastTrade }
}, [amountSpecified?.currency, lastTrade, otherCurrency, tradeObject, tradeType])
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment