diff --git a/src/components/swap/SwapModalHeader.tsx b/src/components/swap/SwapModalHeader.tsx index df4bef453d..a37b22588f 100644 --- a/src/components/swap/SwapModalHeader.tsx +++ b/src/components/swap/SwapModalHeader.tsx @@ -1,7 +1,7 @@ import { Trans } from '@lingui/macro' import { Currency, Percent, TradeType } from '@uniswap/sdk-core' import Column, { AutoColumn } from 'components/Column' -import { useUSDPrice } from 'hooks/useUSDPrice' +import { useLocalCurrencyPrice } from 'hooks/useLocalCurrencyPrice' import { InterfaceTrade } from 'state/routing/types' import { Field } from 'state/swap/actions' import styled from 'styled-components' @@ -26,8 +26,8 @@ export default function SwapModalHeader({ inputCurrency?: Currency allowedSlippage: Percent }) { - const fiatValueInput = useUSDPrice(trade.inputAmount) - const fiatValueOutput = useUSDPrice(trade.postTaxOutputAmount) + const fiatValueInput = useLocalCurrencyPrice(trade.inputAmount) + const fiatValueOutput = useLocalCurrencyPrice(trade.postTaxOutputAmount) return ( diff --git a/src/components/swap/TradePrice.tsx b/src/components/swap/TradePrice.tsx index e52eb20d38..2e5078e2ef 100644 --- a/src/components/swap/TradePrice.tsx +++ b/src/components/swap/TradePrice.tsx @@ -1,6 +1,6 @@ import { Trans } from '@lingui/macro' import { Currency, Price } from '@uniswap/sdk-core' -import { useUSDPrice } from 'hooks/useUSDPrice' +import { useLocalCurrencyPrice } from 'hooks/useLocalCurrencyPrice' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import { useCallback, useMemo, useState } from 'react' import styled from 'styled-components' @@ -31,7 +31,9 @@ export default function TradePrice({ price }: TradePriceProps) { const [showInverted, setShowInverted] = useState(false) 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(() => { try { diff --git a/src/constants/localCurrencies.tsx b/src/constants/localCurrencies.tsx index 616656252f..4d49bec6ba 100644 --- a/src/constants/localCurrencies.tsx +++ b/src/constants/localCurrencies.tsx @@ -1,3 +1,4 @@ +import { Currency } from 'graphql/data/__generated__/types-and-hooks' import { ReactNode } from 'react' import { @@ -22,29 +23,29 @@ import { } from './localCurrencyIcons' export const SUPPORTED_LOCAL_CURRENCIES = [ - 'USD', - 'AUD', - 'BRL', - 'CAD', - 'EUR', - 'GBP', - 'HKD', - 'IDR', - 'INR', - 'JPY', - 'NGN', - 'PKR', - 'RUB', - 'SGD', - 'THB', - 'TRY', - 'UAH', - 'VND', -] + Currency.Usd, + Currency.Aud, + Currency.Brl, + Currency.Cad, + Currency.Eur, + Currency.Gbp, + Currency.Hkd, + Currency.Idr, + Currency.Inr, + Currency.Jpy, + Currency.Ngn, + Currency.Pkr, + Currency.Rub, + Currency.Sgd, + Currency.Thb, + Currency.Try, + Currency.Uah, + Currency.Vnd, +] as const 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 // 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 - case 'EUR': + case Currency.Eur: return - case 'RUB': + case Currency.Rub: return - case 'INR': + case Currency.Inr: return - case 'GBP': + case Currency.Gbp: return - case 'JPY': + case Currency.Jpy: return - case 'VND': + case Currency.Vnd: return - case 'SGD': + case Currency.Sgd: return - case 'BRL': + case Currency.Brl: return - case 'HKD': + case Currency.Hkd: return - case 'CAD': + case Currency.Cad: return - case 'IDR': + case Currency.Idr: return - case 'TRY': + case Currency.Try: return - case 'NGN': + case Currency.Ngn: return - case 'AUD': + case Currency.Aud: return - case 'PKR': + case Currency.Pkr: return - case 'UAH': + case Currency.Uah: return - case 'THB': + case Currency.Thb: return default: return null diff --git a/src/graphql/data/ConversionRate.ts b/src/graphql/data/ConversionRate.ts new file mode 100644 index 0000000000..a18e135c5d --- /dev/null +++ b/src/graphql/data/ConversionRate.ts @@ -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, + } +} diff --git a/src/hooks/useLocalCurrencyPrice.ts b/src/hooks/useLocalCurrencyPrice.ts new file mode 100644 index 0000000000..8cd6cf4f01 --- /dev/null +++ b/src/hooks/useLocalCurrencyPrice.ts @@ -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 + +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 } +} diff --git a/src/nft/hooks/useUsdPrice.ts b/src/nft/hooks/useUsdPrice.ts index 9cdbf04288..af947e9d27 100644 --- a/src/nft/hooks/useUsdPrice.ts +++ b/src/nft/hooks/useUsdPrice.ts @@ -1,6 +1,6 @@ import { formatEther } from '@ethersproject/units' import { ChainId } from '@uniswap/sdk-core' -import { useUSDPrice } from 'hooks/useUSDPrice' +import { useLocalCurrencyPrice } from 'hooks/useLocalCurrencyPrice' import useNativeCurrency from 'lib/hooks/useNativeCurrency' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import { GenieAsset } from 'nft/types' @@ -8,7 +8,7 @@ import { GenieAsset } from 'nft/types' export const useNativeUsdPrice = (chainId: number = ChainId.MAINNET): number => { const nativeCurrency = useNativeCurrency(chainId) const parsedAmount = tryParseCurrencyAmount('1', nativeCurrency) - const usdcValue = useUSDPrice(parsedAmount)?.data ?? 0 + const usdcValue = useLocalCurrencyPrice(parsedAmount)?.data ?? 0 return usdcValue } diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index a37e80a688..31259d4d11 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -34,12 +34,12 @@ import { asSupportedChain, isSupportedChain } from 'constants/chains' import { getSwapCurrencyId, TOKEN_SHORTHANDS } from 'constants/tokens' import { useCurrency, useDefaultActiveTokens } from 'hooks/Tokens' import { useIsSwapUnsupported } from 'hooks/useIsSwapUnsupported' +import { useLocalCurrencyPrice } from 'hooks/useLocalCurrencyPrice' import { useMaxAmountIn } from 'hooks/useMaxAmountIn' import usePermit2Allowance, { AllowanceState } from 'hooks/usePermit2Allowance' import usePrevious from 'hooks/usePrevious' import { SwapResult, useSwapCallback } from 'hooks/useSwapCallback' import { useSwitchChain } from 'hooks/useSwitchChain' -import { useUSDPrice } from 'hooks/useUSDPrice' import useWrapCallback, { WrapErrorText, WrapType } from 'hooks/useWrapCallback' import JSBI from 'jsbi' import { formatSwapQuoteReceivedEventProperties } from 'lib/utils/analytics' @@ -313,8 +313,8 @@ export function Swap({ [independentField, parsedAmount, showWrap, trade] ) - const fiatValueInput = useUSDPrice(parsedAmounts[Field.INPUT], currencies[Field.INPUT] ?? undefined) - const fiatValueOutput = useUSDPrice(parsedAmounts[Field.OUTPUT], currencies[Field.OUTPUT] ?? undefined) + const fiatValueInput = useLocalCurrencyPrice(parsedAmounts[Field.INPUT], currencies[Field.INPUT] ?? undefined) + const fiatValueOutput = useLocalCurrencyPrice(parsedAmounts[Field.OUTPUT], currencies[Field.OUTPUT] ?? undefined) const showFiatValueInput = Boolean(parsedAmounts[Field.INPUT]) const showFiatValueOutput = Boolean(parsedAmounts[Field.OUTPUT]) @@ -327,9 +327,9 @@ export function Swap({ [trade, tradeState] ) - const fiatValueTradeInput = useUSDPrice(trade?.inputAmount) - const fiatValueTradeOutput = useUSDPrice(trade?.postTaxOutputAmount) - const preTaxFiatValueTradeOutput = useUSDPrice(trade?.outputAmount) + const fiatValueTradeInput = useLocalCurrencyPrice(trade?.inputAmount) + const fiatValueTradeOutput = useLocalCurrencyPrice(trade?.postTaxOutputAmount) + const preTaxFiatValueTradeOutput = useLocalCurrencyPrice(trade?.outputAmount) const [stablecoinPriceImpact, preTaxStablecoinPriceImpact] = useMemo( () => routeIsSyncing || !isClassicTrade(trade) diff --git a/src/utils/getFetchPolicyForKey.ts b/src/utils/getFetchPolicyForKey.ts new file mode 100644 index 0000000000..ca6b68bc55 --- /dev/null +++ b/src/utils/getFetchPolicyForKey.ts @@ -0,0 +1,16 @@ +import { WatchQueryFetchPolicy } from '@apollo/client' + +const keys = new Map() + +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 +}