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:
parent
1d2d1259e5
commit
2a04f0faca
@ -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}
|
||||
|
Loading…
Reference in New Issue
Block a user