Skip to content

Commit

Permalink
Support displaying fiat values in non-USD currencies
Browse files Browse the repository at this point in the history
The preferred fiat currency can now be configured
per paratime / network.
  • Loading branch information
csillag committed Feb 21, 2024
1 parent c3bc5eb commit 62409af
Show file tree
Hide file tree
Showing 12 changed files with 70 additions and 26 deletions.
4 changes: 2 additions & 2 deletions src/app/components/Account/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { AccountAvatar } from '../AccountAvatar'
import { RuntimeBalanceDisplay } from '../Balance/RuntimeBalanceDisplay'
import { calculateFiatValue } from '../Balance/hooks'
import { FiatMoneyAmount } from '../Balance/FiatMoneyAmount'
import { getTokensForScope } from '../../../config'
import { getFiatCurrencyForScope, getTokensForScope } from '../../../config'

type AccountProps = {
account?: RuntimeAccount
Expand All @@ -48,7 +48,7 @@ export const Account: FC<AccountProps> = ({ account, token, isLoading, tokenPric
const nativeTokens = getTokensForScope(account || { network: 'mainnet', layer: 'sapphire' })
const nativeTickerNames = nativeTokens.map(token => getNameForTicker(t, token.ticker))
const contract = account?.evm_contract
const fiatValueInfo = calculateFiatValue(account?.balances, tokenPrices)
const fiatValueInfo = calculateFiatValue(account?.balances, tokenPrices, getFiatCurrencyForScope(account))

return (
<>
Expand Down
10 changes: 8 additions & 2 deletions src/app/components/Balance/FiatMoneyAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ export const FiatMoneyAmountBox = styled(Box)(() => ({
flex: 1,
}))

export const FiatMoneyAmount: FC<FiatValueInfo> = ({ value, hasUsedCoinGecko, unknownTickers, loading }) => {
export const FiatMoneyAmount: FC<FiatValueInfo> = ({
value,
fiatCurrency,
hasUsedCoinGecko,
unknownTickers,
loading,
}) => {
const { t } = useTranslation()
return (
<FiatMoneyAmountBox>
Expand All @@ -25,7 +31,7 @@ export const FiatMoneyAmount: FC<FiatValueInfo> = ({ value, hasUsedCoinGecko, un
value,
formatParams: {
value: {
currency: 'USD', // TODO: why are we fixated on USD?
currency: fiatCurrency,
} satisfies Intl.NumberFormatOptions,
},
})}
Expand Down
7 changes: 7 additions & 0 deletions src/app/components/Balance/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export type FiatValueInfo = {
*/
value?: string

/**
* The fiat currency used to express the value
*/
fiatCurrency: string

/**
* Have we used CoinGecko for calculating this value?
*/
Expand All @@ -36,6 +41,7 @@ export type FiatValueInfo = {
export const calculateFiatValue = (
balances: RuntimeSdkBalance[] = [],
tokenPrices: AllTokenPrices,
fiatCurrency: string,
): FiatValueInfo => {
let hasValue = false
let value = new BigNumber(0)
Expand Down Expand Up @@ -72,5 +78,6 @@ export const calculateFiatValue = (
loading,
unknownTickers: unknown,
value: value.toFixed(),
fiatCurrency,
}
}
3 changes: 2 additions & 1 deletion src/app/pages/AccountDetailsPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { AccountDetailsCard } from './AccountDetailsCard'
import { AccountEventsCard } from './AccountEventsCard'
import { DappBanner } from '../../components/DappBanner'
import { AddressLoaderData } from '../../utils/route-utils'
import { getFiatCurrencyForScope } from '../../../config'

export type AccountDetailsContext = {
scope: SearchScope
Expand All @@ -36,7 +37,7 @@ export const AccountDetailsPage: FC = () => {
const isContract = !!account?.evm_contract
const { token, isLoading: isTokenLoading } = useTokenInfo(scope, address, isContract)

const tokenPrices = useAllTokenPrices()
const tokenPrices = useAllTokenPrices(getFiatCurrencyForScope(scope))

const { isLoading: areEventsLoading, isError: isEventsError, events } = useAccountEvents(scope, address)

Expand Down
21 changes: 13 additions & 8 deletions src/app/pages/ParatimeDashboardPage/TokenPriceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,28 @@ import { COLORS } from '../../../styles/theme/colors'
import Typography from '@mui/material/Typography'
import { NativeTokenInfo } from '../../../types/ticker'
import { SmallTokenLogo } from '../../components/logo/SmallTokenLogo'
import { getFiatCurrencyForScope } from '../../../config'
import { useScopeParam } from '../../hooks/useScopeParam'

const StyledBox = styled(Box)(({ theme }) => ({
position: 'absolute',
top: `-${theme.spacing(2)}`,
left: theme.spacing(4),
}))

const formatFiatParams = {
value: {
currency: 'USD', // TODO: why are we fixated on USD
maximumFractionDigits: 5,
} satisfies Intl.NumberFormatOptions,
}

export const TokenPriceCard: FC<{ token: NativeTokenInfo }> = ({ token }) => {
const { t } = useTranslation()
const priceQuery = useTokenPrice(token.ticker)
const scope = useScopeParam()
const fiatCurrency = getFiatCurrencyForScope(scope)
const priceQuery = useTokenPrice(token.ticker, fiatCurrency)

const formatFiatParams = {
value: {
currency: fiatCurrency,
maximumFractionDigits: 5,
} satisfies Intl.NumberFormatOptions,
}

const priceString = priceQuery.price
? t('common.fiatValueInUSD', {
value: priceQuery.price,
Expand Down
11 changes: 8 additions & 3 deletions src/app/pages/RuntimeTransactionDetailPage/CurrentFiatValue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ import HelpIcon from '@mui/icons-material/Help'
import { TokenPriceInfo } from '../../../coin-gecko/api'
import BigNumber from 'bignumber.js'

type CurrentFiatValueProps = Pick<TokenPriceInfo, 'price' | 'hasUsedCoinGecko'> & {
type CurrentFiatValueProps = Pick<TokenPriceInfo, 'price' | 'fiatCurrency' | 'hasUsedCoinGecko'> & {
amount: string
}

export const CurrentFiatValue: FC<CurrentFiatValueProps> = ({ amount, price, hasUsedCoinGecko }) => {
export const CurrentFiatValue: FC<CurrentFiatValueProps> = ({
amount,
price,
fiatCurrency,
hasUsedCoinGecko,
}) => {
const { t } = useTranslation()
return price === undefined ? null : (
<FiatMoneyAmountBox>
Expand All @@ -21,7 +26,7 @@ export const CurrentFiatValue: FC<CurrentFiatValueProps> = ({ amount, price, has
value: new BigNumber(amount).multipliedBy(price).toFixed(),
formatParams: {
value: {
currency: 'USD', // TODO: why are we fixated on USD
currency: fiatCurrency,
} satisfies Intl.NumberFormatOptions,
},
})}
Expand Down
3 changes: 2 additions & 1 deletion src/app/pages/RuntimeTransactionDetailPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { LongDataDisplay } from '../../components/LongDataDisplay'
import { getPreciseNumberFormat } from '../../../locales/getPreciseNumberFormat'
import { base64ToHex } from '../../utils/helpers'
import { DappBanner } from '../../components/DappBanner'
import { getFiatCurrencyForScope } from '../../../config'

type TransactionSelectionResult = {
wantedTransaction?: RuntimeTransaction
Expand Down Expand Up @@ -102,7 +103,7 @@ export const RuntimeTransactionDetailPage: FC = () => {
data?.data,
)

const tokenPrices = useAllTokenPrices()
const tokenPrices = useAllTokenPrices(getFiatCurrencyForScope(scope))

if (!transaction && !isLoading) {
throw AppErrors.NotFoundTxHash
Expand Down
3 changes: 2 additions & 1 deletion src/app/pages/RuntimeTransactionsPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { RuntimeTransactionDetailView } from '../RuntimeTransactionDetailPage'
import { useRequiredScopeParam } from '../../hooks/useScopeParam'
import { useAllTokenPrices } from '../../../coin-gecko/api'
import { VerticalList } from '../../components/VerticalList'
import { getFiatCurrencyForScope } from '../../../config'

const limit = NUMBER_OF_ITEMS_ON_SEPARATE_PAGE

Expand All @@ -34,7 +35,7 @@ export const RuntimeTransactionsPage: FC = () => {
// we should call useGetConsensusTransactions()
}

const tokenPrices = useAllTokenPrices()
const tokenPrices = useAllTokenPrices(getFiatCurrencyForScope(scope))

useEffect(() => {
if (!isMobile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe('SearchResultsView', () => {
isLoading: false,
isFree: false,
price: 1,
fiatCurrency: 'usd',
hasUsedCoinGecko: true,
},
[Ticker.TEST]: {
Expand All @@ -40,6 +41,7 @@ describe('SearchResultsView', () => {
isLoading: false,
isFree: false,
price: 1,
fiatCurrency: 'usd',
hasUsedCoinGecko: true,
},
}}
Expand Down Expand Up @@ -73,6 +75,7 @@ describe('SearchResultsView', () => {
isLoading: false,
isFree: false,
price: 1,
fiatCurrency: 'usd',
hasUsedCoinGecko: true,
},
[Ticker.TEST]: {
Expand All @@ -84,6 +87,7 @@ describe('SearchResultsView', () => {
isLoading: false,
isFree: false,
price: 1,
fiatCurrency: 'usd',
hasUsedCoinGecko: true,
},
}}
Expand Down
3 changes: 2 additions & 1 deletion src/app/pages/SearchResultsPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { useScopeParam } from '../../hooks/useScopeParam'
import { useSearch } from './hooks'
import { SearchResultsView } from './SearchResultsView'
import { useAllTokenPrices } from '../../../coin-gecko/api'
import { getFiatCurrencyForScope } from '../../../config'

export const SearchResultsPage: FC = () => {
const searchParams = useParamSearch()
const scope = useScopeParam()
const { results, isLoading } = useSearch(searchParams)

const tokenPrices = useAllTokenPrices()
const tokenPrices = useAllTokenPrices(getFiatCurrencyForScope(scope))

return (
<SearchResultsView
Expand Down
18 changes: 11 additions & 7 deletions src/coin-gecko/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,20 @@ export const getTokenPricesFromGecko = (

const staleTime = 1000 * 60 * 3 // 3 minutes

export function useGetTokenPricesFromGecko(tokenIds: string[]) {
export function useGetTokenPricesFromGecko(tokenIds: string[], fiatCurrency: string) {
return useQuery<AxiosResponse<TokenPriceMap>, AxiosError<unknown>>(
['tokenFiatPrices'],
() =>
getTokenPricesFromGecko({
ids: tokenIds.join(','),
vs_currencies: 'usd',
vs_currencies: fiatCurrency ?? 'usd',
}),
{
select: ({ data }) => {
const result: TokenPriceMap = {}
Object.keys(data).forEach(key => (result[key] = (data as any)[key].usd)) // TODO why are we fixated on USD
Object.keys(data).forEach(key => {
result[key] = (data as any)[key][fiatCurrency]
})
return result as any
},
staleTime,
Expand All @@ -52,13 +54,14 @@ export function useGetTokenPricesFromGecko(tokenIds: string[]) {

export type TokenPriceInfo = {
price?: number
fiatCurrency?: string
isLoading: boolean
isFree: boolean
hasUsedCoinGecko: boolean
}

export const useTokenPrice = (ticker: Ticker): TokenPriceInfo => {
const tokenPrices = useAllTokenPrices()
export const useTokenPrice = (ticker: Ticker, fiatCurrency: string): TokenPriceInfo => {
const tokenPrices = useAllTokenPrices(fiatCurrency)
const price = tokenPrices[ticker]
if (!price) {
exhaustedTypeWarning('Checking price of unknown token ticker', ticker as any)
Expand All @@ -68,17 +71,18 @@ export const useTokenPrice = (ticker: Ticker): TokenPriceInfo => {

export type AllTokenPrices = Partial<Record<Ticker, TokenPriceInfo>>

export const useAllTokenPrices = (): AllTokenPrices => {
export const useAllTokenPrices = (fiatCurrency: string): AllTokenPrices => {
const tokens = uniq(RouteUtils.getEnabledScopes().map(getTokensForScope).flat())
const geckoIds = tokens.map(token => token.geckoId).filter((id): id is string => !!id)
const { isLoading: geckoIsLoading, data: geckoPrices } = useGetTokenPricesFromGecko(geckoIds)
const { isLoading: geckoIsLoading, data: geckoPrices } = useGetTokenPricesFromGecko(geckoIds, fiatCurrency)
const results: AllTokenPrices = {}
tokens.forEach(token => {
results[token.ticker] = {
isLoading: geckoIsLoading,
isFree: !!token.free,
hasUsedCoinGecko: !!token.geckoId,
price: token.geckoId && geckoPrices ? (geckoPrices as any)[token.geckoId] : undefined,
fiatCurrency: token.geckoId && geckoPrices ? fiatCurrency : 'xx',
}
})
return results
Expand Down
9 changes: 9 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ type LayerNetwork = {
* (If not given, the network's default token will be used.)
*/
tokens?: NativeTokenInfo[]

/**
* What fiat currency should we use for displaying value?
*/
fiatCurrency?: string
}

type LayerConfig = {
Expand Down Expand Up @@ -132,6 +137,7 @@ const pontusxConfig: LayerConfig = {
blockGasLimit: 15_000_000,
runtimeId: '000000000000000000000000000000000000000000000000a6d1e3ebf60dff6c',
tokens: [NativeToken.EUROe, NativeToken.TEST],
fiatCurrency: 'eur',
},
local: {
activeNodes: undefined,
Expand Down Expand Up @@ -188,3 +194,6 @@ export const getTokensForScope = (scope: SearchScope | undefined): NativeTokenIn
}
return [networkDefault]
}

export const getFiatCurrencyForScope = (scope: SearchScope | undefined) =>
(scope ? paraTimesConfig[scope.layer]?.[scope.network]?.fiatCurrency : undefined) ?? 'usd'

0 comments on commit 62409af

Please sign in to comment.