Commit 03ab5c80 authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

feat: prefetch eth price for swap currencies (#7187)

* feat: prefetch eth price for swap currencies

* chore: clarify namings
parent 4faaa60a
...@@ -3,6 +3,7 @@ import { ChainId, Currency, CurrencyAmount, Price, TradeType } from '@uniswap/sd ...@@ -3,6 +3,7 @@ import { ChainId, Currency, CurrencyAmount, Price, TradeType } from '@uniswap/sd
import { nativeOnChain } from 'constants/tokens' import { nativeOnChain } from 'constants/tokens'
import { Chain, useTokenSpotPriceQuery } from 'graphql/data/__generated__/types-and-hooks' import { Chain, useTokenSpotPriceQuery } from 'graphql/data/__generated__/types-and-hooks'
import { chainIdToBackendName, isGqlSupportedChain, PollingInterval } from 'graphql/data/util' import { chainIdToBackendName, isGqlSupportedChain, PollingInterval } from 'graphql/data/util'
import { useMemo } from 'react'
import { INTERNAL_ROUTER_PREFERENCE_PRICE, TradeState } from 'state/routing/types' import { INTERNAL_ROUTER_PREFERENCE_PRICE, TradeState } from 'state/routing/types'
import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade' import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade'
import { getNativeTokenDBAddress } from 'utils/nativeTokens' import { getNativeTokenDBAddress } from 'utils/nativeTokens'
...@@ -19,66 +20,89 @@ const ETH_AMOUNT_OUT: { [chainId: number]: CurrencyAmount<Currency> } = { ...@@ -19,66 +20,89 @@ const ETH_AMOUNT_OUT: { [chainId: number]: CurrencyAmount<Currency> } = {
[ChainId.CELO]: CurrencyAmount.fromRawAmount(nativeOnChain(ChainId.CELO), 10e18), [ChainId.CELO]: CurrencyAmount.fromRawAmount(nativeOnChain(ChainId.CELO), 10e18),
} }
function useETHValue(currencyAmount?: CurrencyAmount<Currency>): { function useETHPrice(currency?: Currency): {
data?: CurrencyAmount<Currency> data?: Price<Currency, Currency>
isLoading: boolean isLoading: boolean
} { } {
const chainId = currencyAmount?.currency?.chainId const chainId = currency?.chainId
const amountOut = isGqlSupportedChain(chainId) ? ETH_AMOUNT_OUT[chainId] : undefined const isSupported = currency && isGqlSupportedChain(chainId)
const amountOut = isSupported ? ETH_AMOUNT_OUT[chainId] : undefined
const { trade, state } = useRoutingAPITrade( const { trade, state } = useRoutingAPITrade(
TradeType.EXACT_OUTPUT, TradeType.EXACT_OUTPUT,
amountOut, amountOut,
currencyAmount?.currency, currency,
INTERNAL_ROUTER_PREFERENCE_PRICE INTERNAL_ROUTER_PREFERENCE_PRICE,
!isSupported
) )
// Get ETH value of ETH or WETH return useMemo(() => {
if (chainId && currencyAmount && currencyAmount.currency.wrapped.equals(nativeOnChain(chainId).wrapped)) { if (!isSupported) {
return { return { data: undefined, isLoading: false }
data: new Price(currencyAmount.currency, currencyAmount.currency, '1', '1').quote(currencyAmount), }
isLoading: false,
if (currency?.wrapped.equals(nativeOnChain(chainId).wrapped)) {
return {
data: new Price(currency, currency, '1', '1'),
isLoading: false,
}
} }
}
if (!trade || state === TradeState.LOADING || !currencyAmount?.currency || !isGqlSupportedChain(chainId)) { if (!trade || state === TradeState.LOADING) {
return { data: undefined, isLoading: state === TradeState.LOADING } return { data: undefined, isLoading: state === TradeState.LOADING }
} }
const { numerator, denominator } = trade.routes[0].midPrice const { numerator, denominator } = trade.routes[0].midPrice
const price = new Price(currencyAmount?.currency, nativeOnChain(chainId), denominator, numerator) const price = new Price(currency, nativeOnChain(chainId), denominator, numerator)
return { data: price.quote(currencyAmount), isLoading: false } return { data: price, isLoading: false }
}, [chainId, currency, isSupported, state, trade])
} }
// TODO(WEB-2095): This hook should early return `null` when `currencyAmount` is undefined. Otherwise, export function useUSDPrice(
// it is not possible to differentiate between a loading state and a state where `currencyAmount` currencyAmount?: CurrencyAmount<Currency>,
// is undefined prefetchCurrency?: Currency
export function useUSDPrice(currencyAmount?: CurrencyAmount<Currency>): { ): {
data?: number data?: number
isLoading: boolean isLoading: boolean
} { } {
const chain = currencyAmount?.currency.chainId ? chainIdToBackendName(currencyAmount?.currency.chainId) : undefined const currency = currencyAmount?.currency ?? prefetchCurrency
const currency = currencyAmount?.currency const chainId = currency?.chainId
const { data: ethValue, isLoading: isEthValueLoading } = useETHValue(currencyAmount) const chain = chainId ? chainIdToBackendName(chainId) : undefined
// Use ETH-based pricing if available.
const { data: tokenEthPrice, isLoading: isTokenEthPriceLoading } = useETHPrice(currency)
const isTokenEthPriced = Boolean(tokenEthPrice || isTokenEthPriceLoading)
const { data, networkStatus } = useTokenSpotPriceQuery({ const { data, networkStatus } = useTokenSpotPriceQuery({
variables: { chain: chain ?? Chain.Ethereum, address: getNativeTokenDBAddress(chain ?? Chain.Ethereum) }, variables: { chain: chain ?? Chain.Ethereum, address: getNativeTokenDBAddress(chain ?? Chain.Ethereum) },
skip: !chain || !isGqlSupportedChain(currency?.chainId), skip: !isTokenEthPriced,
pollInterval: PollingInterval.Normal, pollInterval: PollingInterval.Normal,
notifyOnNetworkStatusChange: true, notifyOnNetworkStatusChange: true,
fetchPolicy: 'cache-first', fetchPolicy: 'cache-first',
}) })
// Use USDC price for chains not supported by backend yet // Use USDC-based pricing for chains not yet supported by backend (for ETH-based pricing).
const stablecoinPrice = useStablecoinPrice(!isGqlSupportedChain(currency?.chainId) ? currency : undefined) const stablecoinPrice = useStablecoinPrice(isTokenEthPriced ? undefined : currency)
if (!isGqlSupportedChain(currency?.chainId) && currencyAmount && stablecoinPrice) {
return { data: parseFloat(stablecoinPrice.quote(currencyAmount).toSignificant()), isLoading: false }
}
const isFirstLoad = networkStatus === NetworkStatus.loading return useMemo(() => {
if (!currencyAmount) {
// Otherwise, get the price of the token in ETH, and then multiple by the price of ETH return { data: undefined, isLoading: false }
const ethUSDPrice = data?.token?.project?.markets?.[0]?.price?.value } else if (stablecoinPrice) {
if (!ethUSDPrice || !ethValue) return { data: undefined, isLoading: isEthValueLoading || isFirstLoad } return { data: parseFloat(stablecoinPrice.quote(currencyAmount).toSignificant()), isLoading: false }
} else {
return { data: parseFloat(ethValue.toExact()) * ethUSDPrice, isLoading: false } // Otherwise, get the price of the token in ETH, and then multiply by the price of ETH.
const ethUSDPrice = data?.token?.project?.markets?.[0]?.price?.value
if (ethUSDPrice && tokenEthPrice) {
return { data: parseFloat(tokenEthPrice.quote(currencyAmount).toExact()) * ethUSDPrice, isLoading: false }
} else {
return { data: undefined, isLoading: isTokenEthPriceLoading || networkStatus === NetworkStatus.loading }
}
}
}, [
currencyAmount,
data?.token?.project?.markets,
tokenEthPrice,
isTokenEthPriceLoading,
networkStatus,
stablecoinPrice,
])
} }
...@@ -299,8 +299,8 @@ export function Swap({ ...@@ -299,8 +299,8 @@ export function Swap({
[independentField, parsedAmount, showWrap, trade] [independentField, parsedAmount, showWrap, trade]
) )
const fiatValueInput = useUSDPrice(parsedAmounts[Field.INPUT]) const fiatValueInput = useUSDPrice(parsedAmounts[Field.INPUT], currencies[Field.INPUT] ?? undefined)
const fiatValueOutput = useUSDPrice(parsedAmounts[Field.OUTPUT]) const fiatValueOutput = useUSDPrice(parsedAmounts[Field.OUTPUT], currencies[Field.OUTPUT] ?? undefined)
const showFiatValueInput = Boolean(parsedAmounts[Field.INPUT]) const showFiatValueInput = Boolean(parsedAmounts[Field.INPUT])
const showFiatValueOutput = Boolean(parsedAmounts[Field.OUTPUT]) const showFiatValueOutput = Boolean(parsedAmounts[Field.OUTPUT])
......
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