fix: loading states for USD prices (#6141)

* set loading state in usdc price fetch

* remove console log

* use 100 for polygon

* use 10k for polygon
This commit is contained in:
Tina 2023-03-13 15:18:20 -04:00 committed by GitHub
parent 1d2d1259e5
commit 2a04f0faca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 73 additions and 61 deletions

@ -5,7 +5,7 @@ import { formatNumber, formatPriceImpact, NumberType } from '@uniswap/conedison/
import { Percent } from '@uniswap/sdk-core'
import { LoadingBubble } from 'components/Tokens/loading'
import { MouseoverTooltip } from 'components/Tooltip'
import { useEffect, useMemo, useState } from 'react'
import { useMemo } from 'react'
import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from '../../theme'
@ -20,14 +20,11 @@ const FiatLoadingBubble = styled(LoadingBubble)`
export function FiatValue({
fiatValue,
priceImpact,
isLoading = false,
}: {
fiatValue: number | null | undefined
fiatValue?: { data?: number; isLoading: boolean }
priceImpact?: Percent
isLoading?: boolean
}) {
const theme = useTheme()
const [showLoadingPlaceholder, setShowLoadingPlaceholder] = useState(false)
const priceImpactColor = useMemo(() => {
if (!priceImpact) return undefined
if (priceImpact.lessThan('0')) return theme.accentSuccess
@ -37,26 +34,13 @@ export function FiatValue({
return theme.accentFailure
}, [priceImpact, theme.accentSuccess, theme.accentFailure, theme.textTertiary, theme.deprecated_yellow1])
useEffect(() => {
const stale = false
let timeoutId = 0
if (isLoading && !fiatValue) {
timeoutId = setTimeout(() => {
if (!stale) setShowLoadingPlaceholder(true)
}, 200) as unknown as number
} else {
setShowLoadingPlaceholder(false)
}
return () => clearTimeout(timeoutId)
}, [isLoading, fiatValue])
return (
<ThemedText.DeprecatedBody fontSize={14} color={theme.textSecondary}>
{showLoadingPlaceholder ? (
{fiatValue?.isLoading ? (
<FiatLoadingBubble />
) : (
<div>
{fiatValue ? formatNumber(fiatValue, NumberType.FiatTokenPrice) : undefined}
{fiatValue?.data ? formatNumber(fiatValue.data, NumberType.FiatTokenPrice) : undefined}
{priceImpact && (
<span style={{ color: priceImpactColor }}>
{' '}

@ -9,7 +9,7 @@ import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/
import CurrencyLogo from 'components/Logo/CurrencyLogo'
import { isSupportedChain } from 'constants/chains'
import { darken } from 'polished'
import { ReactNode, useCallback, useEffect, useState } from 'react'
import { ReactNode, useCallback, useState } from 'react'
import { Lock } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
@ -195,7 +195,7 @@ interface SwapCurrencyInputPanelProps {
pair?: Pair | null
hideInput?: boolean
otherCurrency?: Currency | null
fiatValue?: number | null
fiatValue: { data?: number; isLoading: boolean }
priceImpact?: Percent
id: string
showCommonBases?: boolean
@ -229,7 +229,6 @@ export default function SwapCurrencyInputPanel({
...rest
}: SwapCurrencyInputPanelProps) {
const [modalOpen, setModalOpen] = useState(false)
const [fiatValueIsLoading, setFiatValueIsLoading] = useState(false)
const { account, chainId } = useWeb3React()
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
const theme = useTheme()
@ -240,10 +239,6 @@ export default function SwapCurrencyInputPanel({
const chainAllowed = isSupportedChain(chainId)
useEffect(() => {
!!value && !fiatValue ? setFiatValueIsLoading(true) : setFiatValueIsLoading(false)
}, [fiatValueIsLoading, value, fiatValue])
return (
<InputPanel id={id} hideInput={hideInput} {...rest}>
{locked && (
@ -311,7 +306,7 @@ export default function SwapCurrencyInputPanel({
<FiatRow>
<RowBetween>
<LoadingOpacityContainer $loading={loading}>
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} isLoading={fiatValueIsLoading} />
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
</LoadingOpacityContainer>
{account ? (
<RowFixed style={{ height: '17px' }}>

@ -182,7 +182,7 @@ interface CurrencyInputPanelProps {
pair?: Pair | null
hideInput?: boolean
otherCurrency?: Currency | null
fiatValue?: number | null
fiatValue?: { data?: number; isLoading: boolean }
priceImpact?: Percent
id: string
showCommonBases?: boolean

@ -42,8 +42,8 @@ export default function ConfirmSwapModal({
swapErrorMessage: ReactNode | undefined
onDismiss: () => void
swapQuoteReceivedDate: Date | undefined
fiatValueInput?: number
fiatValueOutput?: number
fiatValueInput: { data?: number; isLoading: boolean }
fiatValueOutput: { data?: number; isLoading: boolean }
}) {
// shouldLogModalCloseEvent lets the child SwapModalHeader component know when modal has been closed
// and an event triggered by modal closing should be logged.

@ -118,8 +118,8 @@ export default function SwapModalFooter({
swapErrorMessage: ReactNode | undefined
disabledConfirm: boolean
swapQuoteReceivedDate: Date | undefined
fiatValueInput?: number
fiatValueOutput?: number
fiatValueInput: { data?: number; isLoading: boolean }
fiatValueOutput: { data?: number; isLoading: boolean }
}) {
const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch
const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto'
@ -142,8 +142,8 @@ export default function SwapModalFooter({
isAutoRouterApi: !clientSideRouter,
swapQuoteReceivedDate,
routes,
fiatValueInput,
fiatValueOutput,
fiatValueInput: fiatValueInput.data,
fiatValueOutput: fiatValueOutput.data,
})}
>
<ButtonError

@ -145,7 +145,7 @@ export default function SwapModalHeader({
<ThemedText.DeprecatedBody fontSize={14} color={theme.textTertiary}>
<FiatValue
fiatValue={fiatValueOutput}
priceImpact={computeFiatValuePriceImpact(fiatValueInput, fiatValueOutput)}
priceImpact={computeFiatValuePriceImpact(fiatValueInput.data, fiatValueOutput.data)}
/>
</ThemedText.DeprecatedBody>
</RowBetween>

@ -33,7 +33,7 @@ export default function TradePrice({ price }: TradePriceProps) {
const [showInverted, setShowInverted] = useState<boolean>(false)
const { baseCurrency, quoteCurrency } = price
const usdPrice = useUSDPrice(tryParseCurrencyAmount('1', showInverted ? baseCurrency : quoteCurrency))
const { data: usdPrice } = useUSDPrice(tryParseCurrencyAmount('1', showInverted ? baseCurrency : quoteCurrency))
let formattedPrice: string
try {

@ -1,8 +1,10 @@
import { NetworkStatus } from '@apollo/client'
import { Currency, CurrencyAmount, Price, SupportedChainId, TradeType } from '@uniswap/sdk-core'
import { nativeOnChain } from 'constants/tokens'
import { Chain, useTokenSpotPriceQuery } from 'graphql/data/__generated__/types-and-hooks'
import { chainIdToBackendName, isGqlSupportedChain, PollingInterval } from 'graphql/data/util'
import { RouterPreference } from 'state/routing/slice'
import { TradeState } from 'state/routing/types'
import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade'
import { getNativeTokenDBAddress } from 'utils/nativeTokens'
@ -11,16 +13,20 @@ import useStablecoinPrice from './useStablecoinPrice'
// ETH amounts used when calculating spot price for a given currency.
// The amount is large enough to filter low liquidity pairs.
const ETH_AMOUNT_OUT: { [chainId: number]: CurrencyAmount<Currency> } = {
[SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.MAINNET), 100),
[SupportedChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.ARBITRUM_ONE), 100),
[SupportedChainId.OPTIMISM]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.OPTIMISM), 100),
[SupportedChainId.POLYGON]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.POLYGON), 10_000e6),
[SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.MAINNET), 100e18),
[SupportedChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.ARBITRUM_ONE), 10e18),
[SupportedChainId.OPTIMISM]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.OPTIMISM), 10e18),
[SupportedChainId.POLYGON]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.POLYGON), 10_000e18),
[SupportedChainId.CELO]: CurrencyAmount.fromRawAmount(nativeOnChain(SupportedChainId.CELO), 10e18),
}
function useETHValue(currencyAmount?: CurrencyAmount<Currency>): CurrencyAmount<Currency> | undefined {
function useETHValue(currencyAmount?: CurrencyAmount<Currency>): {
data: CurrencyAmount<Currency> | undefined
isLoading: boolean
} {
const chainId = currencyAmount?.currency?.chainId
const amountOut = isGqlSupportedChain(chainId) ? ETH_AMOUNT_OUT[chainId] : undefined
const { trade } = useRoutingAPITrade(
const { trade, state } = useRoutingAPITrade(
TradeType.EXACT_OUTPUT,
amountOut,
currencyAmount?.currency,
@ -29,36 +35,47 @@ function useETHValue(currencyAmount?: CurrencyAmount<Currency>): CurrencyAmount<
// Get ETH value of ETH or WETH
if (chainId && currencyAmount && currencyAmount.currency.wrapped.equals(nativeOnChain(chainId).wrapped)) {
return new Price(currencyAmount.currency, currencyAmount.currency, '1', '1').quote(currencyAmount)
return {
data: new Price(currencyAmount.currency, currencyAmount.currency, '1', '1').quote(currencyAmount),
isLoading: false,
}
}
if (!trade || !currencyAmount?.currency || !isGqlSupportedChain(chainId)) return
if (!trade || !currencyAmount?.currency || !isGqlSupportedChain(chainId)) {
return { data: undefined, isLoading: state === TradeState.LOADING || state === TradeState.SYNCING }
}
const { numerator, denominator } = trade.routes[0].midPrice
const price = new Price(currencyAmount?.currency, nativeOnChain(chainId), denominator, numerator)
return price.quote(currencyAmount)
return { data: price.quote(currencyAmount), isLoading: false }
}
export function useUSDPrice(currencyAmount?: CurrencyAmount<Currency>): number | undefined {
export function useUSDPrice(currencyAmount?: CurrencyAmount<Currency>): {
data: number | undefined
isLoading: boolean
} {
const chain = currencyAmount?.currency.chainId ? chainIdToBackendName(currencyAmount?.currency.chainId) : undefined
const currency = currencyAmount?.currency
const ethValue = useETHValue(currencyAmount)
const { data: ethValue, isLoading: isEthValueLoading } = useETHValue(currencyAmount)
const { data } = useTokenSpotPriceQuery({
const { data, networkStatus } = useTokenSpotPriceQuery({
variables: { chain: chain ?? Chain.Ethereum, address: getNativeTokenDBAddress(chain ?? Chain.Ethereum) },
skip: !chain || !isGqlSupportedChain(currency?.chainId),
pollInterval: PollingInterval.Normal,
notifyOnNetworkStatusChange: true,
})
// Use USDC price for chains not supported by backend yet
const stablecoinPrice = useStablecoinPrice(!isGqlSupportedChain(currency?.chainId) ? currency : undefined)
if (!isGqlSupportedChain(currency?.chainId) && currencyAmount && stablecoinPrice) {
return parseFloat(stablecoinPrice.quote(currencyAmount).toSignificant())
return { data: parseFloat(stablecoinPrice.quote(currencyAmount).toSignificant()), isLoading: false }
}
const isFirstLoad = networkStatus === NetworkStatus.loading
// Otherwise, get the price of the token in ETH, and then multiple by the price of ETH
const ethUSDPrice = data?.token?.project?.markets?.[0]?.price?.value
if (!ethUSDPrice || !ethValue) return undefined
if (!ethUSDPrice || !ethValue) return { data: undefined, isLoading: isEthValueLoading || isFirstLoad }
return parseFloat(ethValue.toExact()) * ethUSDPrice
return { data: parseFloat(ethValue.toExact()) * ethUSDPrice, isLoading: false }
}

@ -9,7 +9,7 @@ import { useWeb3React } from '@web3-react/core'
import { sendEvent } from 'components/analytics'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import usePrevious from 'hooks/usePrevious'
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { AlertTriangle } from 'react-feather'
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
import { Text } from 'rebass'
@ -532,8 +532,22 @@ export default function AddLiquidity() {
</AutoColumn>
)
const currencyAFiat = usdcValues[Field.CURRENCY_A]
const currencyBFiat = usdcValues[Field.CURRENCY_B]
const usdcValueCurrencyA = usdcValues[Field.CURRENCY_A]
const usdcValueCurrencyB = usdcValues[Field.CURRENCY_B]
const currencyAFiat = useMemo(
() => ({
data: usdcValueCurrencyA ? parseFloat(usdcValueCurrencyA.toSignificant()) : undefined,
isLoading: false,
}),
[usdcValueCurrencyA]
)
const currencyBFiat = useMemo(
() => ({
data: usdcValueCurrencyB ? parseFloat(usdcValueCurrencyB.toSignificant()) : undefined,
isLoading: false,
}),
[usdcValueCurrencyB]
)
return (
<>
<ScrollablePage>
@ -684,7 +698,7 @@ export default function AddLiquidity() {
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A] ?? null}
id="add-liquidity-input-tokena"
fiatValue={currencyAFiat && parseFloat(currencyAFiat.toSignificant())}
fiatValue={currencyAFiat}
showCommonBases
locked={depositADisabled}
/>
@ -696,7 +710,7 @@ export default function AddLiquidity() {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
fiatValue={currencyBFiat && parseFloat(currencyBFiat.toSignificant())}
fiatValue={currencyBFiat}
currency={currencies[Field.CURRENCY_B] ?? null}
id="add-liquidity-input-tokenb"
showCommonBases

@ -242,7 +242,9 @@ export default function Swap({ className }: { className?: string }) {
const fiatValueTradeOutput = useUSDPrice(trade?.outputAmount)
const stablecoinPriceImpact = useMemo(
() =>
routeIsSyncing || !trade ? undefined : computeFiatValuePriceImpact(fiatValueTradeInput, fiatValueTradeOutput),
routeIsSyncing || !trade
? undefined
: computeFiatValuePriceImpact(fiatValueTradeInput.data, fiatValueTradeOutput.data),
[fiatValueTradeInput, fiatValueTradeOutput, routeIsSyncing, trade]
)
@ -334,7 +336,7 @@ export default function Swap({ className }: { className?: string }) {
)
const showMaxButton = Boolean(maxInputAmount?.greaterThan(0) && !parsedAmounts[Field.INPUT]?.equalTo(maxInputAmount))
const swapFiatValues = useMemo(() => {
return { amountIn: fiatValueTradeInput, amountOut: fiatValueTradeOutput }
return { amountIn: fiatValueTradeInput.data, amountOut: fiatValueTradeOutput.data }
}, [fiatValueTradeInput, fiatValueTradeOutput])
// the callback to execute the swap
@ -534,7 +536,7 @@ export default function Swap({ className }: { className?: string }) {
currency={currencies[Field.INPUT] ?? null}
onUserInput={handleTypeInput}
onMax={handleMaxInput}
fiatValue={fiatValueInput ?? undefined}
fiatValue={fiatValueInput}
onCurrencySelect={handleInputSelect}
otherCurrency={currencies[Field.OUTPUT]}
showCommonBases={true}
@ -581,7 +583,7 @@ export default function Swap({ className }: { className?: string }) {
}
showMaxButton={false}
hideBalance={false}
fiatValue={fiatValueOutput ?? undefined}
fiatValue={fiatValueOutput}
priceImpact={stablecoinPriceImpact}
currency={currencies[Field.OUTPUT] ?? null}
onCurrencySelect={handleOutputSelect}