feat: adding currency conversion hook to useUSDPrice (#7251)

* feat: adding currency conversion hook

* 5m cache policy

* conversion rate hook

* usd check in outer hook

* converting usd price to localcurrencyprice

* checking usd manually
This commit is contained in:
Jack Short 2023-09-05 13:38:33 -04:00 committed by GitHub
parent 60acc689ee
commit 5951d0c40c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 126 additions and 51 deletions

@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core' import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import Column, { AutoColumn } from 'components/Column' import Column, { AutoColumn } from 'components/Column'
import { useUSDPrice } from 'hooks/useUSDPrice' import { useLocalCurrencyPrice } from 'hooks/useLocalCurrencyPrice'
import { InterfaceTrade } from 'state/routing/types' import { InterfaceTrade } from 'state/routing/types'
import { Field } from 'state/swap/actions' import { Field } from 'state/swap/actions'
import styled from 'styled-components' import styled from 'styled-components'
@ -26,8 +26,8 @@ export default function SwapModalHeader({
inputCurrency?: Currency inputCurrency?: Currency
allowedSlippage: Percent allowedSlippage: Percent
}) { }) {
const fiatValueInput = useUSDPrice(trade.inputAmount) const fiatValueInput = useLocalCurrencyPrice(trade.inputAmount)
const fiatValueOutput = useUSDPrice(trade.postTaxOutputAmount) const fiatValueOutput = useLocalCurrencyPrice(trade.postTaxOutputAmount)
return ( return (
<HeaderContainer gap="sm"> <HeaderContainer gap="sm">

@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Currency, Price } from '@uniswap/sdk-core' import { Currency, Price } from '@uniswap/sdk-core'
import { useUSDPrice } from 'hooks/useUSDPrice' import { useLocalCurrencyPrice } from 'hooks/useLocalCurrencyPrice'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
@ -31,7 +31,9 @@ export default function TradePrice({ price }: TradePriceProps) {
const [showInverted, setShowInverted] = useState<boolean>(false) const [showInverted, setShowInverted] = useState<boolean>(false)
const { baseCurrency, quoteCurrency } = price const { baseCurrency, quoteCurrency } = price
const { data: usdPrice } = useUSDPrice(tryParseCurrencyAmount('1', showInverted ? baseCurrency : quoteCurrency)) const { data: usdPrice } = useLocalCurrencyPrice(
tryParseCurrencyAmount('1', showInverted ? baseCurrency : quoteCurrency)
)
const formattedPrice = useMemo(() => { const formattedPrice = useMemo(() => {
try { try {

@ -1,3 +1,4 @@
import { Currency } from 'graphql/data/__generated__/types-and-hooks'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import { import {
@ -22,29 +23,29 @@ import {
} from './localCurrencyIcons' } from './localCurrencyIcons'
export const SUPPORTED_LOCAL_CURRENCIES = [ export const SUPPORTED_LOCAL_CURRENCIES = [
'USD', Currency.Usd,
'AUD', Currency.Aud,
'BRL', Currency.Brl,
'CAD', Currency.Cad,
'EUR', Currency.Eur,
'GBP', Currency.Gbp,
'HKD', Currency.Hkd,
'IDR', Currency.Idr,
'INR', Currency.Inr,
'JPY', Currency.Jpy,
'NGN', Currency.Ngn,
'PKR', Currency.Pkr,
'RUB', Currency.Rub,
'SGD', Currency.Sgd,
'THB', Currency.Thb,
'TRY', Currency.Try,
'UAH', Currency.Uah,
'VND', Currency.Vnd,
] ] as const
export type SupportedLocalCurrency = (typeof SUPPORTED_LOCAL_CURRENCIES)[number] export type SupportedLocalCurrency = (typeof SUPPORTED_LOCAL_CURRENCIES)[number]
export const DEFAULT_LOCAL_CURRENCY: SupportedLocalCurrency = 'USD' export const DEFAULT_LOCAL_CURRENCY: SupportedLocalCurrency = Currency.Usd
// some currencies need to be forced to use the narrow symbol and others need to be forced to use symbol // some currencies need to be forced to use the narrow symbol and others need to be forced to use symbol
// for example: when CAD is set to narrowSymbol it is displayed as $ which offers no differentiation from USD // for example: when CAD is set to narrowSymbol it is displayed as $ which offers no differentiation from USD
@ -73,41 +74,41 @@ export const LOCAL_CURRENCY_SYMBOL_DISPLAY_TYPE: Record<SupportedLocalCurrency,
export function getLocalCurrencyIcon(localCurrency: SupportedLocalCurrency, size = 20): ReactNode { export function getLocalCurrencyIcon(localCurrency: SupportedLocalCurrency, size = 20): ReactNode {
switch (localCurrency) { switch (localCurrency) {
case 'USD': case Currency.Usd:
return <USD_ICON width={size} height={size} /> return <USD_ICON width={size} height={size} />
case 'EUR': case Currency.Eur:
return <EUR_ICON width={size} height={size} /> return <EUR_ICON width={size} height={size} />
case 'RUB': case Currency.Rub:
return <RUB_ICON width={size} height={size} /> return <RUB_ICON width={size} height={size} />
case 'INR': case Currency.Inr:
return <INR_ICON width={size} height={size} /> return <INR_ICON width={size} height={size} />
case 'GBP': case Currency.Gbp:
return <GBP_ICON width={size} height={size} /> return <GBP_ICON width={size} height={size} />
case 'JPY': case Currency.Jpy:
return <JPY_ICON width={size} height={size} /> return <JPY_ICON width={size} height={size} />
case 'VND': case Currency.Vnd:
return <VND_ICON width={size} height={size} /> return <VND_ICON width={size} height={size} />
case 'SGD': case Currency.Sgd:
return <SGD_ICON width={size} height={size} /> return <SGD_ICON width={size} height={size} />
case 'BRL': case Currency.Brl:
return <BRL_ICON width={size} height={size} /> return <BRL_ICON width={size} height={size} />
case 'HKD': case Currency.Hkd:
return <HKD_ICON width={size} height={size} /> return <HKD_ICON width={size} height={size} />
case 'CAD': case Currency.Cad:
return <CAD_ICON width={size} height={size} /> return <CAD_ICON width={size} height={size} />
case 'IDR': case Currency.Idr:
return <IDR_ICON width={size} height={size} /> return <IDR_ICON width={size} height={size} />
case 'TRY': case Currency.Try:
return <TRY_ICON width={size} height={size} /> return <TRY_ICON width={size} height={size} />
case 'NGN': case Currency.Ngn:
return <NGN_ICON width={size} height={size} /> return <NGN_ICON width={size} height={size} />
case 'AUD': case Currency.Aud:
return <AUD_ICON width={size} height={size} /> return <AUD_ICON width={size} height={size} />
case 'PKR': case Currency.Pkr:
return <PKR_ICON width={size} height={size} /> return <PKR_ICON width={size} height={size} />
case 'UAH': case Currency.Uah:
return <UAH_ICON width={size} height={size} /> return <UAH_ICON width={size} height={size} />
case 'THB': case Currency.Thb:
return <THB_ICON width={size} height={size} /> return <THB_ICON width={size} height={size} />
default: default:
return null return null

@ -0,0 +1,29 @@
import { SupportedLocalCurrency } from 'constants/localCurrencies'
import gql from 'graphql-tag'
import ms from 'ms'
import { getFetchPolicyForKey } from 'utils/getFetchPolicyForKey'
import { useConvertQuery } from './__generated__/types-and-hooks'
gql`
query Convert($toCurrency: Currency!) {
convert(fromAmount: { currency: USD, value: 1.0 }, toCurrency: $toCurrency) {
id
value
currency
}
}
`
export function useLocalCurrencyConversionRate(localCurrency: SupportedLocalCurrency, skip?: boolean) {
const { data, loading } = useConvertQuery({
variables: { toCurrency: localCurrency },
fetchPolicy: getFetchPolicyForKey(`convert-${localCurrency}`, ms('5m')),
skip,
})
return {
data: data?.convert?.value,
isLoading: loading,
}
}

@ -0,0 +1,27 @@
import { Currency } from 'graphql/data/__generated__/types-and-hooks'
import { useLocalCurrencyConversionRate } from 'graphql/data/ConversionRate'
import { useActiveLocalCurrency } from './useActiveLocalCurrency'
import { useUSDPrice } from './useUSDPrice'
type useUSDPriceParameters = Parameters<typeof useUSDPrice>
export function useLocalCurrencyPrice(...useUSDPriceParameters: useUSDPriceParameters) {
const activeLocalCurrency = useActiveLocalCurrency()
const activeLocalCurrencyIsUSD = activeLocalCurrency === Currency.Usd
const { data: usdPrice, isLoading: isUSDPriceLoading } = useUSDPrice(...useUSDPriceParameters)
const { data: localCurrencyConversionRate, isLoading: isLocalCurrencyConversionRateLoading } =
useLocalCurrencyConversionRate(activeLocalCurrency, activeLocalCurrencyIsUSD)
if (activeLocalCurrencyIsUSD) {
return { data: usdPrice, isLoading: isUSDPriceLoading }
}
const isLoading = isUSDPriceLoading || isLocalCurrencyConversionRateLoading
if (!usdPrice || !localCurrencyConversionRate) {
return { data: undefined, isLoading }
}
return { data: usdPrice * localCurrencyConversionRate, isLoading: false }
}

@ -1,6 +1,6 @@
import { formatEther } from '@ethersproject/units' import { formatEther } from '@ethersproject/units'
import { ChainId } from '@uniswap/sdk-core' import { ChainId } from '@uniswap/sdk-core'
import { useUSDPrice } from 'hooks/useUSDPrice' import { useLocalCurrencyPrice } from 'hooks/useLocalCurrencyPrice'
import useNativeCurrency from 'lib/hooks/useNativeCurrency' import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { GenieAsset } from 'nft/types' import { GenieAsset } from 'nft/types'
@ -8,7 +8,7 @@ import { GenieAsset } from 'nft/types'
export const useNativeUsdPrice = (chainId: number = ChainId.MAINNET): number => { export const useNativeUsdPrice = (chainId: number = ChainId.MAINNET): number => {
const nativeCurrency = useNativeCurrency(chainId) const nativeCurrency = useNativeCurrency(chainId)
const parsedAmount = tryParseCurrencyAmount('1', nativeCurrency) const parsedAmount = tryParseCurrencyAmount('1', nativeCurrency)
const usdcValue = useUSDPrice(parsedAmount)?.data ?? 0 const usdcValue = useLocalCurrencyPrice(parsedAmount)?.data ?? 0
return usdcValue return usdcValue
} }

@ -34,12 +34,12 @@ import { asSupportedChain, isSupportedChain } from 'constants/chains'
import { getSwapCurrencyId, TOKEN_SHORTHANDS } from 'constants/tokens' import { getSwapCurrencyId, TOKEN_SHORTHANDS } from 'constants/tokens'
import { useCurrency, useDefaultActiveTokens } from 'hooks/Tokens' import { useCurrency, useDefaultActiveTokens } from 'hooks/Tokens'
import { useIsSwapUnsupported } from 'hooks/useIsSwapUnsupported' import { useIsSwapUnsupported } from 'hooks/useIsSwapUnsupported'
import { useLocalCurrencyPrice } from 'hooks/useLocalCurrencyPrice'
import { useMaxAmountIn } from 'hooks/useMaxAmountIn' import { useMaxAmountIn } from 'hooks/useMaxAmountIn'
import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance' import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance'
import usePrevious from 'hooks/usePrevious' import usePrevious from 'hooks/usePrevious'
import { SwapResult, useSwapCallback } from 'hooks/useSwapCallback' import { SwapResult, useSwapCallback } from 'hooks/useSwapCallback'
import { useSwitchChain } from 'hooks/useSwitchChain' import { useSwitchChain } from 'hooks/useSwitchChain'
import { useUSDPrice } from 'hooks/useUSDPrice'
import useWrapCallback, { WrapErrorText, WrapType } from 'hooks/useWrapCallback' import useWrapCallback, { WrapErrorText, WrapType } from 'hooks/useWrapCallback'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { formatSwapQuoteReceivedEventProperties } from 'lib/utils/analytics' import { formatSwapQuoteReceivedEventProperties } from 'lib/utils/analytics'
@ -313,8 +313,8 @@ export function Swap({
[independentField, parsedAmount, showWrap, trade] [independentField, parsedAmount, showWrap, trade]
) )
const fiatValueInput = useUSDPrice(parsedAmounts[Field.INPUT], currencies[Field.INPUT] ?? undefined) const fiatValueInput = useLocalCurrencyPrice(parsedAmounts[Field.INPUT], currencies[Field.INPUT] ?? undefined)
const fiatValueOutput = useUSDPrice(parsedAmounts[Field.OUTPUT], currencies[Field.OUTPUT] ?? undefined) const fiatValueOutput = useLocalCurrencyPrice(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])
@ -327,9 +327,9 @@ export function Swap({
[trade, tradeState] [trade, tradeState]
) )
const fiatValueTradeInput = useUSDPrice(trade?.inputAmount) const fiatValueTradeInput = useLocalCurrencyPrice(trade?.inputAmount)
const fiatValueTradeOutput = useUSDPrice(trade?.postTaxOutputAmount) const fiatValueTradeOutput = useLocalCurrencyPrice(trade?.postTaxOutputAmount)
const preTaxFiatValueTradeOutput = useUSDPrice(trade?.outputAmount) const preTaxFiatValueTradeOutput = useLocalCurrencyPrice(trade?.outputAmount)
const [stablecoinPriceImpact, preTaxStablecoinPriceImpact] = useMemo( const [stablecoinPriceImpact, preTaxStablecoinPriceImpact] = useMemo(
() => () =>
routeIsSyncing || !isClassicTrade(trade) routeIsSyncing || !isClassicTrade(trade)

@ -0,0 +1,16 @@
import { WatchQueryFetchPolicy } from '@apollo/client'
const keys = new Map<string, number>()
export const getFetchPolicyForKey = (key: string, expirationMs: number): WatchQueryFetchPolicy => {
const lastFetchTimestamp = keys.get(key)
const diffFromNow = lastFetchTimestamp ? Date.now() - lastFetchTimestamp : Number.MAX_SAFE_INTEGER
let fetchPolicy: WatchQueryFetchPolicy = 'cache-first'
if (diffFromNow > expirationMs) {
keys.set(key, Date.now())
fetchPolicy = 'network-only'
}
return fetchPolicy
}