diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/hooks.ts b/src/components/AccountDrawer/MiniPortfolio/Activity/hooks.ts index 4cd0469655..acb91b111e 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Activity/hooks.ts +++ b/src/components/AccountDrawer/MiniPortfolio/Activity/hooks.ts @@ -2,6 +2,7 @@ import { TransactionStatus, useActivityQuery } from 'graphql/data/__generated__/ import { useEffect, useMemo } from 'react' import { usePendingOrders } from 'state/signatures/hooks' import { usePendingTransactions, useTransactionCanceller } from 'state/transactions/hooks' +import { useFormatter } from 'utils/formatNumbers' import { useLocalActivities } from './parseLocal' import { parseRemoteActivities } from './parseRemote' @@ -55,6 +56,7 @@ function combineActivities(localMap: ActivityMap = {}, remoteMap: ActivityMap = } export function useAllActivities(account: string) { + const { formatNumberOrString } = useFormatter() const { data, loading, refetch } = useActivityQuery({ variables: { account }, errorPolicy: 'all', @@ -62,7 +64,10 @@ export function useAllActivities(account: string) { }) const localMap = useLocalActivities(account) - const remoteMap = useMemo(() => parseRemoteActivities(data?.portfolios?.[0].assetActivities), [data?.portfolios]) + const remoteMap = useMemo( + () => parseRemoteActivities(formatNumberOrString, data?.portfolios?.[0].assetActivities), + [data?.portfolios, formatNumberOrString] + ) const updateCancelledTx = useTransactionCanceller() /* Updates locally stored pendings tx's when remote data contains a conflicting cancellation tx */ diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.test.ts b/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.test.ts index 9667254993..5d5d8269d4 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.test.ts +++ b/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.test.ts @@ -11,6 +11,7 @@ import { TransactionType as MockTxType, } from 'state/transactions/types' import { renderHook } from 'test-utils/render' +import { useFormatter } from 'utils/formatNumbers' import { UniswapXOrderStatus } from '../../../../lib/hooks/orders/types' import { SignatureDetails, SignatureType } from '../../../../state/signatures/types' @@ -237,6 +238,8 @@ jest.mock('../../../../state/transactions/hooks', () => { describe('parseLocalActivity', () => { it('returns swap activity fields with known tokens, exact input', () => { + const { formatNumber } = renderHook(() => useFormatter()).result.current + const details = { info: mockSwapInfo( MockTradeType.EXACT_INPUT, @@ -251,7 +254,7 @@ describe('parseLocalActivity', () => { }, } as TransactionDetails const chainId = ChainId.MAINNET - expect(transactionToActivity(details, chainId, mockTokenAddressMap)).toEqual({ + expect(transactionToActivity(details, chainId, mockTokenAddressMap, formatNumber)).toEqual({ chainId: 1, currencies: [MockUSDC_MAINNET, MockDAI], descriptor: '1.00 USDC for 1.00 DAI', @@ -264,6 +267,8 @@ describe('parseLocalActivity', () => { }) it('returns swap activity fields with known tokens, exact output', () => { + const { formatNumber } = renderHook(() => useFormatter()).result.current + const details = { info: mockSwapInfo( MockTradeType.EXACT_OUTPUT, @@ -278,7 +283,7 @@ describe('parseLocalActivity', () => { }, } as TransactionDetails const chainId = ChainId.MAINNET - expect(transactionToActivity(details, chainId, mockTokenAddressMap)).toMatchObject({ + expect(transactionToActivity(details, chainId, mockTokenAddressMap, formatNumber)).toMatchObject({ chainId: 1, currencies: [MockUSDC_MAINNET, MockDAI], descriptor: '1.00 USDC for 1.00 DAI', @@ -288,6 +293,8 @@ describe('parseLocalActivity', () => { }) it('returns swap activity fields with unknown tokens', () => { + const { formatNumber } = renderHook(() => useFormatter()).result.current + const details = { info: mockSwapInfo( MockTradeType.EXACT_INPUT, @@ -303,7 +310,7 @@ describe('parseLocalActivity', () => { } as TransactionDetails const chainId = ChainId.MAINNET const tokens = {} as ChainTokenMap - expect(transactionToActivity(details, chainId, tokens)).toMatchObject({ + expect(transactionToActivity(details, chainId, tokens, formatNumber)).toMatchObject({ chainId: 1, currencies: [undefined, undefined], descriptor: 'Unknown for Unknown', @@ -496,13 +503,16 @@ describe('parseLocalActivity', () => { }) it('Signature to activity - returns undefined if is on chain order', () => { + const { formatNumber } = renderHook(() => useFormatter()).result.current + expect( signatureToActivity( { type: SignatureType.SIGN_UNISWAPX_ORDER, status: UniswapXOrderStatus.FILLED, } as SignatureDetails, - {} + {}, + formatNumber ) ).toBeUndefined() @@ -512,7 +522,8 @@ describe('parseLocalActivity', () => { type: SignatureType.SIGN_UNISWAPX_ORDER, status: UniswapXOrderStatus.CANCELLED, } as SignatureDetails, - {} + {}, + formatNumber ) ).toBeUndefined() }) diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts b/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts index 9071af0ff0..9d4e3d7d14 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts +++ b/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts @@ -2,7 +2,6 @@ import { BigNumber } from '@ethersproject/bignumber' import { t } from '@lingui/macro' import { ChainId, Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' import UniswapXBolt from 'assets/svg/bolt.svg' -import { SupportedLocale } from 'constants/locales' import { nativeOnChain } from 'constants/tokens' import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks' import { ChainTokenMap, useAllTokensMultichain } from 'hooks/Tokens' @@ -24,11 +23,13 @@ import { TransactionType, WrapTransactionInfo, } from 'state/transactions/types' -import { formatCurrencyAmount, useFormatterLocales } from 'utils/formatNumbers' +import { NumberType, useFormatter } from 'utils/formatNumbers' import { CancelledTransactionTitleTable, getActivityTitle, OrderTextTable } from '../constants' import { Activity, ActivityMap } from './types' +type FormatNumberFunctionType = ReturnType['formatNumber'] + function getCurrency(currencyId: string, chainId: ChainId, tokens: ChainTokenMap): Currency | undefined { return currencyId === 'ETH' ? nativeOnChain(chainId) : tokens[chainId]?.[currencyId] } @@ -38,15 +39,21 @@ function buildCurrencyDescriptor( amtA: string, currencyB: Currency | undefined, amtB: string, - delimiter = t`for`, - locale?: SupportedLocale + formatNumber: FormatNumberFunctionType, + delimiter = t`for` ) { const formattedA = currencyA - ? formatCurrencyAmount({ amount: CurrencyAmount.fromRawAmount(currencyA, amtA), locale }) + ? formatNumber({ + input: parseFloat(CurrencyAmount.fromRawAmount(currencyA, amtA).toSignificant()), + type: NumberType.TokenNonTx, + }) : t`Unknown` const symbolA = currencyA?.symbol ?? '' const formattedB = currencyB - ? formatCurrencyAmount({ amount: CurrencyAmount.fromRawAmount(currencyB, amtB), locale }) + ? formatNumber({ + input: parseFloat(CurrencyAmount.fromRawAmount(currencyB, amtB).toSignificant()), + type: NumberType.TokenNonTx, + }) : t`Unknown` const symbolB = currencyB?.symbol ?? '' return [formattedA, symbolA, delimiter, formattedB, symbolB].filter(Boolean).join(' ') @@ -56,7 +63,7 @@ function parseSwap( swap: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo, chainId: ChainId, tokens: ChainTokenMap, - locale?: SupportedLocale + formatNumber: FormatNumberFunctionType ): Partial { const tokenIn = getCurrency(swap.inputCurrencyId, chainId, tokens) const tokenOut = getCurrency(swap.outputCurrencyId, chainId, tokens) @@ -66,18 +73,29 @@ function parseSwap( : [swap.expectedInputCurrencyAmountRaw, swap.outputCurrencyAmountRaw] return { - descriptor: buildCurrencyDescriptor(tokenIn, inputRaw, tokenOut, outputRaw, undefined, locale), + descriptor: buildCurrencyDescriptor(tokenIn, inputRaw, tokenOut, outputRaw, formatNumber, undefined), currencies: [tokenIn, tokenOut], prefixIconSrc: swap.isUniswapXOrder ? UniswapXBolt : undefined, } } -function parseWrap(wrap: WrapTransactionInfo, chainId: ChainId, status: TransactionStatus): Partial { +function parseWrap( + wrap: WrapTransactionInfo, + chainId: ChainId, + status: TransactionStatus, + formatNumber: FormatNumberFunctionType +): Partial { const native = nativeOnChain(chainId) const wrapped = native.wrapped const [input, output] = wrap.unwrapped ? [wrapped, native] : [native, wrapped] - const descriptor = buildCurrencyDescriptor(input, wrap.currencyAmountRaw, output, wrap.currencyAmountRaw) + const descriptor = buildCurrencyDescriptor( + input, + wrap.currencyAmountRaw, + output, + wrap.currencyAmountRaw, + formatNumber + ) const title = getActivityTitle(TransactionType.WRAP, status, wrap.unwrapped) const currencies = wrap.unwrapped ? [wrapped, native] : [native, wrapped] @@ -107,11 +125,16 @@ type GenericLPInfo = Omit< AddLiquidityV3PoolTransactionInfo | RemoveLiquidityV3TransactionInfo | AddLiquidityV2PoolTransactionInfo, 'type' > -function parseLP(lp: GenericLPInfo, chainId: ChainId, tokens: ChainTokenMap): Partial { +function parseLP( + lp: GenericLPInfo, + chainId: ChainId, + tokens: ChainTokenMap, + formatNumber: FormatNumberFunctionType +): Partial { const baseCurrency = getCurrency(lp.baseCurrencyId, chainId, tokens) const quoteCurrency = getCurrency(lp.quoteCurrencyId, chainId, tokens) const [baseRaw, quoteRaw] = [lp.expectedAmountBaseRaw, lp.expectedAmountQuoteRaw] - const descriptor = buildCurrencyDescriptor(baseCurrency, baseRaw, quoteCurrency, quoteRaw, t`and`) + const descriptor = buildCurrencyDescriptor(baseCurrency, baseRaw, quoteCurrency, quoteRaw, formatNumber, t`and`) return { descriptor, currencies: [baseCurrency, quoteCurrency] } } @@ -119,7 +142,8 @@ function parseLP(lp: GenericLPInfo, chainId: ChainId, tokens: ChainTokenMap): Pa function parseCollectFees( collect: CollectFeesTransactionInfo, chainId: ChainId, - tokens: ChainTokenMap + tokens: ChainTokenMap, + formatNumber: FormatNumberFunctionType ): Partial { // Adapts CollectFeesTransactionInfo to generic LP type const { @@ -128,7 +152,12 @@ function parseCollectFees( expectedCurrencyOwed0: expectedAmountBaseRaw, expectedCurrencyOwed1: expectedAmountQuoteRaw, } = collect - return parseLP({ baseCurrencyId, quoteCurrencyId, expectedAmountBaseRaw, expectedAmountQuoteRaw }, chainId, tokens) + return parseLP( + { baseCurrencyId, quoteCurrencyId, expectedAmountBaseRaw, expectedAmountQuoteRaw }, + chainId, + tokens, + formatNumber + ) } function parseMigrateCreateV3( @@ -157,7 +186,7 @@ export function transactionToActivity( details: TransactionDetails, chainId: ChainId, tokens: ChainTokenMap, - locale?: SupportedLocale + formatNumber: FormatNumberFunctionType ): Activity | undefined { try { const status = getTransactionStatus(details) @@ -176,19 +205,19 @@ export function transactionToActivity( let additionalFields: Partial = {} const info = details.info if (info.type === TransactionType.SWAP) { - additionalFields = parseSwap(info, chainId, tokens, locale) + additionalFields = parseSwap(info, chainId, tokens, formatNumber) } else if (info.type === TransactionType.APPROVAL) { additionalFields = parseApproval(info, chainId, tokens, status) } else if (info.type === TransactionType.WRAP) { - additionalFields = parseWrap(info, chainId, status) + additionalFields = parseWrap(info, chainId, status, formatNumber) } else if ( info.type === TransactionType.ADD_LIQUIDITY_V3_POOL || info.type === TransactionType.REMOVE_LIQUIDITY_V3 || info.type === TransactionType.ADD_LIQUIDITY_V2_POOL ) { - additionalFields = parseLP(info, chainId, tokens) + additionalFields = parseLP(info, chainId, tokens, formatNumber) } else if (info.type === TransactionType.COLLECT_FEES) { - additionalFields = parseCollectFees(info, chainId, tokens) + additionalFields = parseCollectFees(info, chainId, tokens, formatNumber) } else if (info.type === TransactionType.MIGRATE_LIQUIDITY_V3 || info.type === TransactionType.CREATE_V3_POOL) { additionalFields = parseMigrateCreateV3(info, chainId, tokens) } @@ -210,7 +239,7 @@ export function transactionToActivity( export function signatureToActivity( signature: SignatureDetails, tokens: ChainTokenMap, - locale?: SupportedLocale + formatNumber: FormatNumberFunctionType ): Activity | undefined { switch (signature.type) { case SignatureType.SIGN_UNISWAPX_ORDER: { @@ -229,7 +258,7 @@ export function signatureToActivity( from: signature.offerer, statusMessage, prefixIconSrc: UniswapXBolt, - ...parseSwap(signature.swapInfo, signature.chainId, tokens, locale), + ...parseSwap(signature.swapInfo, signature.chainId, tokens, formatNumber), } } default: @@ -241,24 +270,24 @@ export function useLocalActivities(account: string): ActivityMap { const allTransactions = useMultichainTransactions() const allSignatures = useAllSignatures() const tokens = useAllTokensMultichain() - const { formatterLocale } = useFormatterLocales() + const { formatNumber } = useFormatter() return useMemo(() => { const activityMap: ActivityMap = {} for (const [transaction, chainId] of allTransactions) { if (transaction.from !== account) continue - const activity = transactionToActivity(transaction, chainId, tokens, formatterLocale) + const activity = transactionToActivity(transaction, chainId, tokens, formatNumber) if (activity) activityMap[transaction.hash] = activity } for (const signature of Object.values(allSignatures)) { if (signature.offerer !== account) continue - const activity = signatureToActivity(signature, tokens, formatterLocale) + const activity = signatureToActivity(signature, tokens, formatNumber) if (activity) activityMap[signature.id] = activity } return activityMap - }, [account, allSignatures, allTransactions, formatterLocale, tokens]) + }, [account, allSignatures, allTransactions, formatNumber, tokens]) } diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx b/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx index 6438aafc76..a1e4f8bed7 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx +++ b/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx @@ -21,7 +21,7 @@ import { gqlToCurrency, logSentryErrorForUnsupportedChain, supportedChainIdFromG import ms from 'ms' import { useEffect, useState } from 'react' import { isAddress } from 'utils' -import { formatFiatPrice, formatNumberOrString, NumberType } from 'utils/formatNumbers' +import { NumberType, useFormatter } from 'utils/formatNumbers' import { MOONPAY_SENDER_ADDRESSES, OrderStatusTable, OrderTextTable } from '../constants' import { Activity } from './types' @@ -34,6 +34,8 @@ type TransactionChanges = { NftApproveForAll: NftApproveForAllPartsFragment[] } +type FormatNumberOrStringFunctionType = ReturnType['formatNumberOrString'] + // TODO: Move common contract metadata to a backend service const UNI_IMG = 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/logo.png' @@ -140,13 +142,13 @@ function getSwapDescriptor({ * @param transactedValue Transacted value amount from TokenTransfer API response * @returns parsed & formatted USD value as a string if currency is of type USD */ -function formatTransactedValue(transactedValue: TokenTransferPartsFragment['transactedValue']): string { - if (!transactedValue) return '-' +function getTransactedValue(transactedValue: TokenTransferPartsFragment['transactedValue']): number | undefined { + if (!transactedValue) return undefined const price = transactedValue?.currency === GQLCurrency.Usd ? transactedValue.value ?? undefined : undefined - return formatFiatPrice(price) + return price } -function parseSwap(changes: TransactionChanges) { +function parseSwap(changes: TransactionChanges, formatNumberOrString: FormatNumberOrStringFunctionType) { if (changes.NftTransfer.length > 0 && changes.TokenTransfer.length === 1) { const collectionCounts = getCollectionCounts(changes.NftTransfer) @@ -168,8 +170,8 @@ function parseSwap(changes: TransactionChanges) { if (sent && received) { const adjustedInput = parseFloat(sent.quantity) - parseFloat(refund?.quantity ?? '0') - const inputAmount = formatNumberOrString(adjustedInput, NumberType.TokenNonTx) - const outputAmount = formatNumberOrString(received.quantity, NumberType.TokenNonTx) + const inputAmount = formatNumberOrString({ input: adjustedInput, type: NumberType.TokenNonTx }) + const outputAmount = formatNumberOrString({ input: received.quantity, type: NumberType.TokenNonTx }) return { title: getSwapTitle(sent, received), descriptor: getSwapDescriptor({ tokenIn: sent.asset, inputAmount, tokenOut: received.asset, outputAmount }), @@ -180,8 +182,8 @@ function parseSwap(changes: TransactionChanges) { return { title: t`Unknown Swap` } } -function parseSwapOrder(changes: TransactionChanges) { - return { ...parseSwap(changes), prefixIconSrc: UniswapXBolt } +function parseSwapOrder(changes: TransactionChanges, formatNumberOrString: FormatNumberOrStringFunctionType) { + return { ...parseSwap(changes, formatNumberOrString), prefixIconSrc: UniswapXBolt } } function parseApprove(changes: TransactionChanges) { @@ -194,12 +196,12 @@ function parseApprove(changes: TransactionChanges) { return { title: t`Unknown Approval` } } -function parseLPTransfers(changes: TransactionChanges) { +function parseLPTransfers(changes: TransactionChanges, formatNumberOrString: FormatNumberOrStringFunctionType) { const poolTokenA = changes.TokenTransfer[0] const poolTokenB = changes.TokenTransfer[1] - const tokenAQuanitity = formatNumberOrString(poolTokenA.quantity, NumberType.TokenNonTx) - const tokenBQuantity = formatNumberOrString(poolTokenB.quantity, NumberType.TokenNonTx) + const tokenAQuanitity = formatNumberOrString({ input: poolTokenA.quantity, type: NumberType.TokenNonTx }) + const tokenBQuantity = formatNumberOrString({ input: poolTokenB.quantity, type: NumberType.TokenNonTx }) return { descriptor: `${tokenAQuanitity} ${poolTokenA.asset.symbol} and ${tokenBQuantity} ${poolTokenB.asset.symbol}`, @@ -211,11 +213,15 @@ function parseLPTransfers(changes: TransactionChanges) { type TransactionActivity = AssetActivityPartsFragment & { details: TransactionDetailsPartsFragment } type OrderActivity = AssetActivityPartsFragment & { details: SwapOrderDetailsPartsFragment } -function parseSendReceive(changes: TransactionChanges, assetActivity: TransactionActivity) { +function parseSendReceive( + changes: TransactionChanges, + formatNumberOrString: FormatNumberOrStringFunctionType, + assetActivity: TransactionActivity +) { // TODO(cartcrom): remove edge cases after backend implements // Edge case: Receiving two token transfers in interaction w/ V3 manager === removing liquidity. These edge cases should potentially be moved to backend if (changes.TokenTransfer.length === 2 && callsPositionManagerContract(assetActivity)) { - return { title: t`Removed Liquidity`, ...parseLPTransfers(changes) } + return { title: t`Removed Liquidity`, ...parseLPTransfers(changes, formatNumberOrString) } } let transfer: NftTransferPartsFragment | TokenTransferPartsFragment | undefined @@ -230,7 +236,7 @@ function parseSendReceive(changes: TransactionChanges, assetActivity: Transactio } else if (changes.TokenTransfer.length === 1) { transfer = changes.TokenTransfer[0] assetName = transfer.asset.symbol - amount = formatNumberOrString(transfer.quantity, NumberType.TokenNonTx) + amount = formatNumberOrString({ input: transfer.quantity, type: NumberType.TokenNonTx }) currencies = [gqlToCurrency(transfer.asset)] } @@ -241,7 +247,10 @@ function parseSendReceive(changes: TransactionChanges, assetActivity: Transactio return isMoonpayPurchase && transfer.__typename === 'TokenTransfer' ? { title: t`Purchased`, - descriptor: `${amount} ${assetName} ${t`for`} ${formatTransactedValue(transfer.transactedValue)}`, + descriptor: `${amount} ${assetName} ${t`for`} ${formatNumberOrString({ + input: getTransactedValue(transfer.transactedValue), + type: NumberType.FiatTokenPrice, + })}`, logos: [moonpayLogoSrc], currencies, } @@ -263,25 +272,37 @@ function parseSendReceive(changes: TransactionChanges, assetActivity: Transactio return { title: t`Unknown Send` } } -function parseMint(changes: TransactionChanges, assetActivity: TransactionActivity) { +function parseMint( + changes: TransactionChanges, + formatNumberOrString: FormatNumberOrStringFunctionType, + assetActivity: TransactionActivity +) { const collectionMap = getCollectionCounts(changes.NftTransfer) if (Object.keys(collectionMap).length === 1) { const collectionName = Object.keys(collectionMap)[0] // Edge case: Minting a v3 positon represents adding liquidity if (changes.TokenTransfer.length === 2 && callsPositionManagerContract(assetActivity)) { - return { title: t`Added Liquidity`, ...parseLPTransfers(changes) } + return { title: t`Added Liquidity`, ...parseLPTransfers(changes, formatNumberOrString) } } return { title: t`Minted`, descriptor: `${collectionMap[collectionName]} ${collectionName}` } } return { title: t`Unknown Mint` } } -function parseUnknown(_changes: TransactionChanges, assetActivity: TransactionActivity) { +function parseUnknown( + _changes: TransactionChanges, + _formatNumberOrString: FormatNumberOrStringFunctionType, + assetActivity: TransactionActivity +) { return { title: t`Contract Interaction`, ...COMMON_CONTRACTS[assetActivity.details.to.toLowerCase()] } } -type ActivityTypeParser = (changes: TransactionChanges, assetActivity: TransactionActivity) => Partial +type ActivityTypeParser = ( + changes: TransactionChanges, + formatNumberOrString: FormatNumberOrStringFunctionType, + assetActivity: TransactionActivity +) => Partial const ActivityParserByType: { [key: string]: ActivityTypeParser | undefined } = { [ActivityType.Swap]: parseSwap, [ActivityType.SwapOrder]: parseSwapOrder, @@ -345,7 +366,10 @@ function parseUniswapXOrder({ details, chain, timestamp }: OrderActivity): Activ } } -function parseRemoteActivity(assetActivity: AssetActivityPartsFragment): Activity | undefined { +function parseRemoteActivity( + assetActivity: AssetActivityPartsFragment, + formatNumberOrString: FormatNumberOrStringFunctionType +): Activity | undefined { try { if (assetActivity.details.__typename === 'SwapOrderDetails') { return parseUniswapXOrder(assetActivity as OrderActivity) @@ -385,6 +409,7 @@ function parseRemoteActivity(assetActivity: AssetActivityPartsFragment): Activit const parsedFields = ActivityParserByType[assetActivity.details.type]?.( changes, + formatNumberOrString, assetActivity as TransactionActivity ) return { ...defaultFields, ...parsedFields } @@ -394,9 +419,12 @@ function parseRemoteActivity(assetActivity: AssetActivityPartsFragment): Activit } } -export function parseRemoteActivities(assetActivities?: readonly AssetActivityPartsFragment[]) { +export function parseRemoteActivities( + formatNumberOrString: FormatNumberOrStringFunctionType, + assetActivities?: readonly AssetActivityPartsFragment[] +) { return assetActivities?.reduce((acc: { [hash: string]: Activity }, assetActivity) => { - const activity = parseRemoteActivity(assetActivity) + const activity = parseRemoteActivity(assetActivity, formatNumberOrString) if (activity) acc[activity.hash] = activity return acc }, {}) diff --git a/src/components/NavBar/SuggestionRow.tsx b/src/components/NavBar/SuggestionRow.tsx index fb906388ad..f2ea60f0df 100644 --- a/src/components/NavBar/SuggestionRow.tsx +++ b/src/components/NavBar/SuggestionRow.tsx @@ -18,7 +18,7 @@ import { useCallback, useEffect, useState } from 'react' import { Link, useNavigate } from 'react-router-dom' import styled from 'styled-components' import { ThemedText } from 'theme' -import { formatUSDPrice } from 'utils/formatNumbers' +import { useFormatter } from 'utils/formatNumbers' import { DeltaArrow, DeltaText } from '../Tokens/TokenDetails/Delta' import { useAddRecentlySearchedAsset } from './RecentlySearchedAssets' @@ -128,6 +128,7 @@ interface TokenRowProps { export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, eventProperties }: TokenRowProps) => { const addRecentlySearchedAsset = useAddRecentlySearchedAsset() const navigate = useNavigate() + const { formatFiatPrice } = useFormatter() const handleClick = useCallback(() => { const address = !token.address && token.standard === TokenStandard.Native ? 'NATIVE' : token.address @@ -184,7 +185,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, {!!token.market?.price?.value && ( <> - {formatUSDPrice(token.market.price.value)} + {formatFiatPrice({ price: token.market.price.value })} diff --git a/src/components/Popups/PopupContent.tsx b/src/components/Popups/PopupContent.tsx index 96922b7988..8928eb44b4 100644 --- a/src/components/Popups/PopupContent.tsx +++ b/src/components/Popups/PopupContent.tsx @@ -17,6 +17,7 @@ import { useOrder } from 'state/signatures/hooks' import { useTransaction } from 'state/transactions/hooks' import styled from 'styled-components' import { EllipsisStyle, ThemedText } from 'theme' +import { useFormatter } from 'utils/formatNumbers' import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink' const StyledClose = styled(X)<{ $padding: number }>` @@ -137,9 +138,10 @@ export function TransactionPopupContent({ }) { const transaction = useTransaction(hash) const tokens = useAllTokensMultichain() + const { formatNumber } = useFormatter() if (!transaction) return null - const activity = transactionToActivity(transaction, chainId, tokens) + const activity = transactionToActivity(transaction, chainId, tokens, formatNumber) if (!activity) return null @@ -153,9 +155,10 @@ export function UniswapXOrderPopupContent({ orderHash, onClose }: { orderHash: s const order = useOrder(orderHash) const tokens = useAllTokensMultichain() const openOffchainActivityModal = useOpenOffchainActivityModal() + const { formatNumber } = useFormatter() if (!order) return null - const activity = signatureToActivity(order, tokens) + const activity = signatureToActivity(order, tokens, formatNumber) if (!activity) return null diff --git a/src/components/PositionListItem/index.tsx b/src/components/PositionListItem/index.tsx index d644196b76..f530182f92 100644 --- a/src/components/PositionListItem/index.tsx +++ b/src/components/PositionListItem/index.tsx @@ -15,7 +15,7 @@ import { Link } from 'react-router-dom' import { Bound } from 'state/mint/v3/actions' import styled from 'styled-components' import { HideSmall, MEDIA_WIDTHS, SmallOnly, ThemedText } from 'theme' -import { formatTickPrice } from 'utils/formatTickPrice' +import { useFormatter } from 'utils/formatNumbers' import { unwrappedToken } from 'utils/unwrappedToken' import { DAI, USDC_MAINNET, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens' @@ -172,6 +172,8 @@ export default function PositionListItem({ tickLower, tickUpper, }: PositionListItemProps) { + const { formatTickPrice } = useFormatter() + const token0 = useToken(token0Address) const token1 = useToken(token1Address) diff --git a/src/components/PositionPreview/index.tsx b/src/components/PositionPreview/index.tsx index e346f91856..0672741dab 100644 --- a/src/components/PositionPreview/index.tsx +++ b/src/components/PositionPreview/index.tsx @@ -14,7 +14,7 @@ import { ReactNode, useCallback, useState } from 'react' import { Bound } from 'state/mint/v3/actions' import { useTheme } from 'styled-components' import { ThemedText } from 'theme' -import { formatTickPrice } from 'utils/formatTickPrice' +import { useFormatter } from 'utils/formatNumbers' import { unwrappedToken } from 'utils/unwrappedToken' export const PositionPreview = ({ @@ -31,6 +31,7 @@ export const PositionPreview = ({ ticksAtLimit: { [bound: string]: boolean | undefined } }) => { const theme = useTheme() + const { formatTickPrice } = useFormatter() const currency0 = unwrappedToken(position.pool.token0) const currency1 = unwrappedToken(position.pool.token1) diff --git a/src/components/Tokens/TokenDetails/PriceChart.tsx b/src/components/Tokens/TokenDetails/PriceChart.tsx index 1a81a1cb16..1647c3f86d 100644 --- a/src/components/Tokens/TokenDetails/PriceChart.tsx +++ b/src/components/Tokens/TokenDetails/PriceChart.tsx @@ -17,7 +17,7 @@ import { Info, TrendingUp } from 'react-feather' import styled, { useTheme } from 'styled-components' import { ThemedText } from 'theme' import { textFadeIn } from 'theme/styles' -import { formatUSDPrice } from 'utils/formatNumbers' +import { useFormatter } from 'utils/formatNumbers' import { calculateDelta, DeltaArrow, formatDelta } from './Delta' @@ -87,6 +87,7 @@ interface PriceChartProps { export function PriceChart({ width, height, prices: originalPrices, timePeriod }: PriceChartProps) { const locale = useActiveLocale() const theme = useTheme() + const { formatFiatPrice } = useFormatter() const { prices, blanks } = useMemo( () => @@ -208,13 +209,13 @@ export function PriceChart({ width, height, prices: originalPrices, timePeriod } {displayPrice.value ? ( <> - {formatUSDPrice(displayPrice.value)} + {formatFiatPrice({ price: displayPrice.value })} ) : lastPrice.value ? ( - {formatUSDPrice(lastPrice.value)} + {formatFiatPrice({ price: lastPrice.value })} diff --git a/src/components/Tokens/TokenTable/TokenRow.tsx b/src/components/Tokens/TokenTable/TokenRow.tsx index f8e7a3ae60..a478fb0410 100644 --- a/src/components/Tokens/TokenTable/TokenRow.tsx +++ b/src/components/Tokens/TokenTable/TokenRow.tsx @@ -16,7 +16,7 @@ import { CSSProperties, ReactNode } from 'react' import { Link, useParams } from 'react-router-dom' import styled, { css, useTheme } from 'styled-components' import { BREAKPOINTS, ClickableStyle } from 'theme' -import { formatUSDPrice, NumberType, useFormatter } from 'utils/formatNumbers' +import { NumberType, useFormatter } from 'utils/formatNumbers' import { LARGE_MEDIA_BREAKPOINT, @@ -440,7 +440,7 @@ interface LoadedRowProps { /* Loaded State: row component with token information */ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef) => { - const { formatNumber } = useFormatter() + const { formatFiatPrice, formatNumber } = useFormatter() const { tokenListIndex, tokenListLength, token, sortRank } = props const filterString = useAtomValue(filterStringAtom) @@ -463,7 +463,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef { ) expect(asFragment()).toMatchSnapshot() expect(screen.getByText(/Output is estimated. You will receive at least /i)).toBeInTheDocument() - expect(screen.getByTestId('INPUT-amount')).toHaveTextContent( - `${formatCurrencyAmount({ amount: TEST_TRADE_EXACT_INPUT.inputAmount, type: NumberType.TokenTx })} ${ - TEST_TRADE_EXACT_INPUT.inputAmount.currency.symbol ?? '' - }` - ) - expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent( - `${formatCurrencyAmount({ amount: TEST_TRADE_EXACT_INPUT.outputAmount, type: NumberType.TokenTx })} ${ - TEST_TRADE_EXACT_INPUT.outputAmount.currency.symbol ?? '' - }` - ) + expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(`<0.00001 ABC`) + expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(`<0.00001 DEF`) }) it('renders ETH input token for an ETH input UniswapX swap', () => { @@ -39,16 +30,8 @@ describe('SwapModalHeader.tsx', () => { ) expect(asFragment()).toMatchSnapshot() expect(screen.getByText(/Output is estimated. You will receive at least /i)).toBeInTheDocument() - expect(screen.getByTestId('INPUT-amount')).toHaveTextContent( - `${formatCurrencyAmount({ amount: TEST_DUTCH_TRADE_ETH_INPUT.inputAmount, type: NumberType.TokenTx })} ${ - ETH_MAINNET.symbol - }` - ) - expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent( - `${formatCurrencyAmount({ amount: TEST_DUTCH_TRADE_ETH_INPUT.outputAmount, type: NumberType.TokenTx })} ${ - TEST_DUTCH_TRADE_ETH_INPUT.outputAmount.currency.symbol ?? '' - }` - ) + expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(`<0.00001 ETH`) + expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(`<0.00001 DEF`) }) it('test trade exact output, no recipient', () => { @@ -58,15 +41,7 @@ describe('SwapModalHeader.tsx', () => { expect(asFragment()).toMatchSnapshot() expect(screen.getByText(/Input is estimated. You will sell at most/i)).toBeInTheDocument() - expect(screen.getByTestId('INPUT-amount')).toHaveTextContent( - `${formatCurrencyAmount({ amount: TEST_TRADE_EXACT_OUTPUT.inputAmount, type: NumberType.TokenTx })} ${ - TEST_TRADE_EXACT_OUTPUT.inputAmount.currency.symbol ?? '' - }` - ) - expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent( - `${formatCurrencyAmount({ amount: TEST_TRADE_EXACT_OUTPUT.outputAmount, type: NumberType.TokenTx })} ${ - TEST_TRADE_EXACT_OUTPUT.outputAmount.currency.symbol ?? '' - }` - ) + expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(`<0.00001 ABC`) + expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(`<0.00001 GHI`) }) }) diff --git a/src/nft/components/explore/CarouselCard.tsx b/src/nft/components/explore/CarouselCard.tsx index ce2c508e25..5a7aaa36f9 100644 --- a/src/nft/components/explore/CarouselCard.tsx +++ b/src/nft/components/explore/CarouselCard.tsx @@ -6,7 +6,7 @@ import { Markets, TrendingCollection } from 'nft/types' import { ethNumberStandardFormatter } from 'nft/utils' import styled from 'styled-components' import { ThemedText } from 'theme/components/text' -import { formatNumberOrString, NumberType } from 'utils/formatNumbers' +import { NumberType, useFormatter } from 'utils/formatNumbers' const CarouselCardBorder = styled.div` width: 100%; @@ -198,6 +198,8 @@ interface MarketplaceRowProps { } const MarketplaceRow = ({ marketplace, floorInEth, listings }: MarketplaceRowProps) => { + const { formatNumberOrString } = useFormatter() + return ( <> @@ -212,7 +214,7 @@ const MarketplaceRow = ({ marketplace, floorInEth, listings }: MarketplaceRowPro {Number(floorInEth) > 0 - ? `${formatNumberOrString(floorInEth, NumberType.NFTTokenFloorPriceTrailingZeros)} ETH` + ? `${formatNumberOrString({ input: floorInEth, type: NumberType.NFTTokenFloorPriceTrailingZeros })} ETH` : '-'} diff --git a/src/pages/Pool/PositionPage.tsx b/src/pages/Pool/PositionPage.tsx index c09b1f30e7..f5bbc55353 100644 --- a/src/pages/Pool/PositionPage.tsx +++ b/src/pages/Pool/PositionPage.tsx @@ -38,7 +38,6 @@ import { currencyId } from 'utils/currencyId' import { WrongChainError } from 'utils/errors' import { formatCurrencyAmount } from 'utils/formatCurrencyAmount' import { NumberType, useFormatter } from 'utils/formatNumbers' -import { formatTickPrice } from 'utils/formatTickPrice' import { unwrappedToken } from 'utils/unwrappedToken' import RangeBadge from '../../components/Badge/RangeBadge' @@ -389,6 +388,7 @@ function PositionPageContent() { const { tokenId: tokenIdFromUrl } = useParams<{ tokenId?: string }>() const { chainId, account, provider } = useWeb3React() const theme = useTheme() + const { formatTickPrice } = useFormatter() const parsedTokenId = tokenIdFromUrl ? BigNumber.from(tokenIdFromUrl) : undefined const { loading, position: positionDetails } = useV3PositionFromTokenId(parsedTokenId) diff --git a/src/utils/formatNumbers.test.ts b/src/utils/formatNumbers.test.ts index e50a44b07e..1da13d0795 100644 --- a/src/utils/formatNumbers.test.ts +++ b/src/utils/formatNumbers.test.ts @@ -1,523 +1,461 @@ -import { CurrencyAmount, Percent, Price } from '@uniswap/sdk-core' -import { USDC_MAINNET, WBTC } from 'constants/tokens' +import { renderHook } from '@testing-library/react' +import { CurrencyAmount, Percent } from '@uniswap/sdk-core' +import { USDC_MAINNET } from 'constants/tokens' +import { useCurrencyConversionFlagEnabled } from 'featureFlags/flags/currencyConversion' import { Currency } from 'graphql/data/__generated__/types-and-hooks' +import { useLocalCurrencyConversionRate } from 'graphql/data/ConversionRate' +import { useActiveLocalCurrency } from 'hooks/useActiveLocalCurrency' +import { useActiveLocale } from 'hooks/useActiveLocale' +import { mocked } from 'test-utils/mocked' -import { - currencyAmountToPreciseFloat, - formatNumber, - formatPriceImpact, - formatReviewSwapCurrencyAmount, - formatSlippage, - formatUSDPrice, - NumberType, - priceToPreciseFloat, -} from './formatNumbers' +import { NumberType, useFormatter } from './formatNumbers' -it('formats token reference numbers correctly', () => { - expect(formatNumber({ input: 1234567000000000, type: NumberType.TokenNonTx })).toBe('>999T') - expect(formatNumber({ input: 1234567000000000, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('>999\xa0Bio.') - expect(formatNumber({ input: 1002345, type: NumberType.TokenNonTx })).toBe('1.00M') - expect(formatNumber({ input: 1002345, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('1,00\xa0Mio.') - expect(formatNumber({ input: 1234, type: NumberType.TokenNonTx })).toBe('1,234.00') - expect(formatNumber({ input: 1234, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('1.234,00') - expect(formatNumber({ input: 0.00909, type: NumberType.TokenNonTx })).toBe('0.009') - expect(formatNumber({ input: 0.00909, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('0,009') - expect(formatNumber({ input: 0.09001, type: NumberType.TokenNonTx })).toBe('0.090') - expect(formatNumber({ input: 0.09001, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('0,090') - expect(formatNumber({ input: 0.00099, type: NumberType.TokenNonTx })).toBe('<0.001') - expect(formatNumber({ input: 0.00099, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('<0,001') - expect(formatNumber({ input: 0, type: NumberType.TokenNonTx })).toBe('0') - expect(formatNumber({ input: 0, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('0') -}) -it('formats token transaction numbers correctly', () => { - expect(formatNumber({ input: 1234567.8901, type: NumberType.TokenTx })).toBe('1,234,567.89') - expect(formatNumber({ input: 1234567.8901, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('1\xa0234\xa0567,89') - expect(formatNumber({ input: 765432.1, type: NumberType.TokenTx })).toBe('765,432.10') - expect(formatNumber({ input: 765432.1, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('765\xa0432,10') +jest.mock('hooks/useActiveLocale') +jest.mock('hooks/useActiveLocalCurrency') +jest.mock('graphql/data/ConversionRate') +jest.mock('featureFlags/flags/currencyConversion') - expect(formatNumber({ input: 7654.321, type: NumberType.TokenTx })).toBe('7,654.32') - expect(formatNumber({ input: 7654.321, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('7\xa0654,32') - expect(formatNumber({ input: 765.4321, type: NumberType.TokenTx })).toBe('765.432') - expect(formatNumber({ input: 765.4321, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('765,432') - expect(formatNumber({ input: 76.54321, type: NumberType.TokenTx })).toBe('76.5432') - expect(formatNumber({ input: 76.54321, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('76,5432') - expect(formatNumber({ input: 7.654321, type: NumberType.TokenTx })).toBe('7.65432') - expect(formatNumber({ input: 7.654321, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('7,65432') - expect(formatNumber({ input: 7.60000054321, type: NumberType.TokenTx })).toBe('7.60') - expect(formatNumber({ input: 7.60000054321, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('7,60') - expect(formatNumber({ input: 7.6, type: NumberType.TokenTx })).toBe('7.60') - expect(formatNumber({ input: 7.6, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('7,60') - expect(formatNumber({ input: 7, type: NumberType.TokenTx })).toBe('7.00') - expect(formatNumber({ input: 7, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('7,00') +describe('formatNumber', () => { + beforeEach(() => { + mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) + mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true) + }) - expect(formatNumber({ input: 0.987654321, type: NumberType.TokenTx })).toBe('0.98765') - expect(formatNumber({ input: 0.987654321, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('0,98765') - expect(formatNumber({ input: 0.9, type: NumberType.TokenTx })).toBe('0.90') - expect(formatNumber({ input: 0.9, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('0,90') - expect(formatNumber({ input: 0.901000123, type: NumberType.TokenTx })).toBe('0.901') - expect(formatNumber({ input: 0.901000123, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('0,901') - expect(formatNumber({ input: 0.000000001, type: NumberType.TokenTx })).toBe('<0.00001') - expect(formatNumber({ input: 0.000000001, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('<0,00001') - expect(formatNumber({ input: 0, type: NumberType.TokenTx })).toBe('0') - expect(formatNumber({ input: 0, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('0') -}) + it('formats token reference numbers correctly', () => { + const { formatNumber } = renderHook(() => useFormatter()).result.current -it('formats fiat estimates on token details pages correctly', () => { - expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenDetails })).toBe('$1.23M') - expect( - formatNumber({ - input: 1234567.891, - type: NumberType.FiatTokenDetails, - locale: 'fr-FR', - localCurrency: Currency.Eur, - }) - ).toBe('1,23\xa0M\xa0€') - expect(formatNumber({ input: 1234.5678, type: NumberType.FiatTokenDetails })).toBe('$1,234.57') - expect( - formatNumber({ input: 1234.5678, type: NumberType.FiatTokenDetails, locale: 'fr-FR', localCurrency: Currency.Eur }) - ).toBe('1\u202f234,57\xa0€') - expect(formatNumber({ input: 1.048942, type: NumberType.FiatTokenDetails })).toBe('$1.049') - expect( - formatNumber({ input: 1.048942, type: NumberType.FiatTokenDetails, locale: 'fr-FR', localCurrency: Currency.Eur }) - ).toBe('1,049\xa0€') + expect(formatNumber({ input: 1234567000000000, type: NumberType.TokenNonTx })).toBe('>999T') + expect(formatNumber({ input: 1002345, type: NumberType.TokenNonTx })).toBe('1.00M') + expect(formatNumber({ input: 1234, type: NumberType.TokenNonTx })).toBe('1,234.00') + expect(formatNumber({ input: 0.00909, type: NumberType.TokenNonTx })).toBe('0.009') + expect(formatNumber({ input: 0.09001, type: NumberType.TokenNonTx })).toBe('0.090') + expect(formatNumber({ input: 0.00099, type: NumberType.TokenNonTx })).toBe('<0.001') + expect(formatNumber({ input: 0, type: NumberType.TokenNonTx })).toBe('0') + }) - expect(formatNumber({ input: 0.001231, type: NumberType.FiatTokenDetails })).toBe('$0.00123') - expect( - formatNumber({ input: 0.001231, type: NumberType.FiatTokenDetails, locale: 'fr-FR', localCurrency: Currency.Eur }) - ).toBe('0,00123\xa0€') - expect(formatNumber({ input: 0.00001231, type: NumberType.FiatTokenDetails })).toBe('$0.0000123') - expect( - formatNumber({ input: 0.00001231, type: NumberType.FiatTokenDetails, locale: 'fr-FR', localCurrency: Currency.Eur }) - ).toBe('0,0000123\xa0€') + it('formats token reference numbers correctly with deutsch locale', () => { + mocked(useActiveLocale).mockReturnValue('de-DE') + const { formatNumber } = renderHook(() => useFormatter()).result.current - expect(formatNumber({ input: 0.0000001234, type: NumberType.FiatTokenDetails })).toBe('$0.000000123') - expect( - formatNumber({ - input: 0.0000001234, - type: NumberType.FiatTokenDetails, - locale: 'fr-FR', - localCurrency: Currency.Eur, - }) - ).toBe('0,000000123\xa0€') - expect(formatNumber({ input: 0.000000009876, type: NumberType.FiatTokenDetails })).toBe('<$0.00000001') - expect( - formatNumber({ - input: 0.000000009876, - type: NumberType.FiatTokenDetails, - locale: 'fr-FR', - localCurrency: Currency.Eur, - }) - ).toBe('<0,00000001\xa0€') -}) + expect(formatNumber({ input: 1234567000000000, type: NumberType.TokenNonTx })).toBe('>999\xa0Bio.') + expect(formatNumber({ input: 1002345, type: NumberType.TokenNonTx })).toBe('1,00\xa0Mio.') + expect(formatNumber({ input: 1234, type: NumberType.TokenNonTx })).toBe('1.234,00') + expect(formatNumber({ input: 0.00909, type: NumberType.TokenNonTx })).toBe('0,009') + expect(formatNumber({ input: 0.09001, type: NumberType.TokenNonTx })).toBe('0,090') + expect(formatNumber({ input: 0.00099, type: NumberType.TokenNonTx })).toBe('<0,001') + expect(formatNumber({ input: 0, type: NumberType.TokenNonTx })).toBe('0') + }) -it('formats fiat estimates for tokens correctly', () => { - expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenPrice })).toBe('$1.23M') - expect( - formatNumber({ input: 1234567.891, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: Currency.Jpy }) - ).toBe('1,23\xa0M¥') - expect(formatNumber({ input: 1234.5678, type: NumberType.FiatTokenPrice })).toBe('$1,234.57') - expect( - formatNumber({ input: 1234.5678, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: Currency.Jpy }) - ).toBe('1234,57\xa0¥') - expect( - formatNumber({ input: 12345.678, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: Currency.Jpy }) - ).toBe('12.345,68\xa0¥') + it('formats token transaction numbers correctly', () => { + const { formatNumber } = renderHook(() => useFormatter()).result.current - expect(formatNumber({ input: 0.010235, type: NumberType.FiatTokenPrice })).toBe('$0.0102') - expect( - formatNumber({ input: 0.010235, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: Currency.Jpy }) - ).toBe('0,0102\xa0¥') - expect(formatNumber({ input: 0.001231, type: NumberType.FiatTokenPrice })).toBe('$0.00123') - expect( - formatNumber({ input: 0.001231, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: Currency.Jpy }) - ).toBe('0,00123\xa0¥') - expect(formatNumber({ input: 0.00001231, type: NumberType.FiatTokenPrice })).toBe('$0.0000123') - expect( - formatNumber({ input: 0.00001231, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: Currency.Jpy }) - ).toBe('0,0000123\xa0¥') + expect(formatNumber({ input: 1234567.8901, type: NumberType.TokenTx })).toBe('1,234,567.89') + expect(formatNumber({ input: 765432.1, type: NumberType.TokenTx })).toBe('765,432.10') - expect(formatNumber({ input: 0.0000001234, type: NumberType.FiatTokenPrice })).toBe('$0.000000123') - expect( - formatNumber({ input: 0.0000001234, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: Currency.Jpy }) - ).toBe('0,000000123\xa0¥') - expect(formatNumber({ input: 0.000000009876, type: NumberType.FiatTokenPrice })).toBe('<$0.00000001') - expect( - formatNumber({ - input: 0.000000009876, - type: NumberType.FiatTokenPrice, - locale: 'es-ES', - localCurrency: Currency.Jpy, - }) - ).toBe('<0,00000001\xa0¥') - expect(formatNumber({ input: 10000000000000000000000000000000, type: NumberType.FiatTokenPrice })).toBe( - '$1.000000E31' - ) - expect( - formatNumber({ - input: 10000000000000000000000000000000, - type: NumberType.FiatTokenPrice, - locale: 'es-ES', - localCurrency: Currency.Jpy, - }) - ).toBe('1,000000E31\xa0¥') -}) + expect(formatNumber({ input: 7654.321, type: NumberType.TokenTx })).toBe('7,654.32') + expect(formatNumber({ input: 765.4321, type: NumberType.TokenTx })).toBe('765.432') + expect(formatNumber({ input: 76.54321, type: NumberType.TokenTx })).toBe('76.5432') + expect(formatNumber({ input: 7.654321, type: NumberType.TokenTx })).toBe('7.65432') + expect(formatNumber({ input: 7.60000054321, type: NumberType.TokenTx })).toBe('7.60') + expect(formatNumber({ input: 7.6, type: NumberType.TokenTx })).toBe('7.60') + expect(formatNumber({ input: 7, type: NumberType.TokenTx })).toBe('7.00') -it('formats fiat estimates for token stats correctly', () => { - expect(formatNumber({ input: 1234576, type: NumberType.FiatTokenStats })).toBe('$1.2M') - expect( - formatNumber({ input: 1234576, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: Currency.Cad }) - ).toBe('CA$123.5万') - expect(formatNumber({ input: 234567, type: NumberType.FiatTokenStats })).toBe('$234.6K') - expect( - formatNumber({ input: 234567, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: Currency.Cad }) - ).toBe('CA$23.5万') - expect(formatNumber({ input: 123.456, type: NumberType.FiatTokenStats })).toBe('$123.46') - expect( - formatNumber({ input: 123.456, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: Currency.Cad }) - ).toBe('CA$123.46') - expect(formatNumber({ input: 1.23, type: NumberType.FiatTokenStats })).toBe('$1.23') - expect( - formatNumber({ input: 1.23, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: Currency.Cad }) - ).toBe('CA$1.23') - expect(formatNumber({ input: 0.123, type: NumberType.FiatTokenStats })).toBe('$0.12') - expect( - formatNumber({ input: 0.123, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: Currency.Cad }) - ).toBe('CA$0.12') - expect(formatNumber({ input: 0.00123, type: NumberType.FiatTokenStats })).toBe('<$0.01') - expect( - formatNumber({ input: 0.00123, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: Currency.Cad }) - ).toBe(' { - expect(formatNumber({ input: 1234567.891, type: NumberType.FiatGasPrice })).toBe('$1.23M') - expect( - formatNumber({ input: 1234567.891, type: NumberType.FiatGasPrice, locale: 'pt-PR', localCurrency: Currency.Thb }) - ).toBe('฿\xa01,23\xa0mi') - expect(formatNumber({ input: 18.448, type: NumberType.FiatGasPrice })).toBe('$18.45') - expect( - formatNumber({ input: 18.448, type: NumberType.FiatGasPrice, locale: 'pt-PR', localCurrency: Currency.Thb }) - ).toBe('฿\xa018,45') - expect(formatNumber({ input: 0.0099, type: NumberType.FiatGasPrice })).toBe('<$0.01') - expect( - formatNumber({ input: 0.0099, type: NumberType.FiatGasPrice, locale: 'pt-PR', localCurrency: Currency.Thb }) - ).toBe('<฿\xa00,01') - expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice })).toBe('$0.00') - expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice, locale: 'pt-PR', localCurrency: Currency.Thb })).toBe( - '฿\xa00,00' - ) -}) + it('formats token transaction numbers correctly with russian locale', () => { + mocked(useActiveLocale).mockReturnValue('ru-RU') + const { formatNumber } = renderHook(() => useFormatter()).result.current -it('formats USD token quantities prices correctly', () => { - expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenQuantity })).toBe('$1.23M') - expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenQuantity, localCurrency: Currency.Ngn })).toBe( - '₦1.23M' - ) - expect(formatNumber({ input: 18.448, type: NumberType.FiatTokenQuantity })).toBe('$18.45') - expect(formatNumber({ input: 18.448, type: NumberType.FiatTokenQuantity, localCurrency: Currency.Ngn })).toBe( - '₦18.45' - ) - expect(formatNumber({ input: 0.0099, type: NumberType.FiatTokenQuantity })).toBe('<$0.01') - expect(formatNumber({ input: 0.0099, type: NumberType.FiatTokenQuantity, localCurrency: Currency.Ngn })).toBe( - '<₦0.01' - ) - expect(formatNumber({ input: 0, type: NumberType.FiatTokenQuantity })).toBe('$0.00') - expect(formatNumber({ input: 0, type: NumberType.FiatTokenQuantity, localCurrency: Currency.Ngn })).toBe('₦0.00') -}) + expect(formatNumber({ input: 1234567.8901, type: NumberType.TokenTx })).toBe('1\xa0234\xa0567,89') + expect(formatNumber({ input: 765432.1, type: NumberType.TokenTx })).toBe('765\xa0432,10') -it('formats Swap text input/output numbers correctly', () => { - expect(formatNumber({ input: 1234567.8901, type: NumberType.SwapTradeAmount })).toBe('1234570') - expect(formatNumber({ input: 1234567.8901, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('1234570') - expect(formatNumber({ input: 765432.1, type: NumberType.SwapTradeAmount })).toBe('765432') - expect(formatNumber({ input: 765432.1, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('765432') + expect(formatNumber({ input: 7654.321, type: NumberType.TokenTx })).toBe('7\xa0654,32') + expect(formatNumber({ input: 765.4321, type: NumberType.TokenTx })).toBe('765,432') + expect(formatNumber({ input: 76.54321, type: NumberType.TokenTx })).toBe('76,5432') + expect(formatNumber({ input: 7.654321, type: NumberType.TokenTx })).toBe('7,65432') + expect(formatNumber({ input: 7.60000054321, type: NumberType.TokenTx })).toBe('7,60') + expect(formatNumber({ input: 7.6, type: NumberType.TokenTx })).toBe('7,60') + expect(formatNumber({ input: 7, type: NumberType.TokenTx })).toBe('7,00') - expect(formatNumber({ input: 7654.321, type: NumberType.SwapTradeAmount })).toBe('7654.32') - expect(formatNumber({ input: 7654.321, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('7654.32') - expect(formatNumber({ input: 765.4321, type: NumberType.SwapTradeAmount })).toBe('765.432') - expect(formatNumber({ input: 765.4321, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('765.432') - expect(formatNumber({ input: 76.54321, type: NumberType.SwapTradeAmount })).toBe('76.5432') - expect(formatNumber({ input: 76.54321, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('76.5432') - expect(formatNumber({ input: 7.654321, type: NumberType.SwapTradeAmount })).toBe('7.65432') - expect(formatNumber({ input: 7.654321, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('7.65432') - expect(formatNumber({ input: 7.60000054321, type: NumberType.SwapTradeAmount })).toBe('7.60') - expect(formatNumber({ input: 7.60000054321, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('7.60') - expect(formatNumber({ input: 7.6, type: NumberType.SwapTradeAmount })).toBe('7.60') - expect(formatNumber({ input: 7.6, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('7.60') - expect(formatNumber({ input: 7, type: NumberType.SwapTradeAmount })).toBe('7.00') - expect(formatNumber({ input: 7, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('7.00') + expect(formatNumber({ input: 0.987654321, type: NumberType.TokenTx })).toBe('0,98765') + expect(formatNumber({ input: 0.9, type: NumberType.TokenTx })).toBe('0,90') + expect(formatNumber({ input: 0.901000123, type: NumberType.TokenTx })).toBe('0,901') + expect(formatNumber({ input: 0.000000001, type: NumberType.TokenTx })).toBe('<0,00001') + expect(formatNumber({ input: 0, type: NumberType.TokenTx })).toBe('0') + }) - expect(formatNumber({ input: 0.987654321, type: NumberType.SwapTradeAmount })).toBe('0.98765') - expect(formatNumber({ input: 0.987654321, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('0.98765') - expect(formatNumber({ input: 0.9, type: NumberType.SwapTradeAmount })).toBe('0.90') - expect(formatNumber({ input: 0.9, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('0.90') - expect(formatNumber({ input: 0.901000123, type: NumberType.SwapTradeAmount })).toBe('0.901') - expect(formatNumber({ input: 0.901000123, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('0.901') - expect(formatNumber({ input: 0.000000001, type: NumberType.SwapTradeAmount })).toBe('0.000000001') - expect(formatNumber({ input: 0.000000001, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('0.000000001') - expect(formatNumber({ input: 0, type: NumberType.SwapTradeAmount })).toBe('0') - expect(formatNumber({ input: 0, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('0') -}) + it('formats fiat estimates on token details pages correctly', () => { + const { formatNumber } = renderHook(() => useFormatter()).result.current -it('formats NFT numbers correctly', () => { - expect(formatNumber({ input: 1234567000000000, type: NumberType.NFTTokenFloorPrice })).toBe('>999T') - expect( - formatNumber({ - input: 1234567000000000, - type: NumberType.NFTTokenFloorPrice, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('>999\xa0tri') - expect(formatNumber({ input: 1002345, type: NumberType.NFTTokenFloorPrice })).toBe('1M') - expect( - formatNumber({ - input: 1002345, - type: NumberType.NFTTokenFloorPrice, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('1\xa0mi') - expect(formatNumber({ input: 1234, type: NumberType.NFTTokenFloorPrice })).toBe('1.23K') - expect( - formatNumber({ - input: 1234, - type: NumberType.NFTTokenFloorPrice, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('1,23\xa0mil') - expect(formatNumber({ input: 12.34467, type: NumberType.NFTTokenFloorPrice })).toBe('12.34') - expect( - formatNumber({ - input: 12.34467, - type: NumberType.NFTTokenFloorPrice, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('12,34') - expect(formatNumber({ input: 12.1, type: NumberType.NFTTokenFloorPrice })).toBe('12.1') - expect( - formatNumber({ - input: 12.1, - type: NumberType.NFTTokenFloorPrice, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('12,1') - expect(formatNumber({ input: 0.00909, type: NumberType.NFTTokenFloorPrice })).toBe('0.009') - expect( - formatNumber({ - input: 0.00909, - type: NumberType.NFTTokenFloorPrice, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('0,009') - expect(formatNumber({ input: 0.09001, type: NumberType.NFTTokenFloorPrice })).toBe('0.09') - expect( - formatNumber({ - input: 0.09001, - type: NumberType.NFTTokenFloorPrice, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('0,09') - expect(formatNumber({ input: 0.00099, type: NumberType.NFTTokenFloorPrice })).toBe('<0.001') - expect( - formatNumber({ - input: 0.00099, - type: NumberType.NFTTokenFloorPrice, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('<0,001') - expect(formatNumber({ input: 0, type: NumberType.NFTTokenFloorPrice })).toBe('0') - expect( - formatNumber({ - input: 0, - type: NumberType.NFTTokenFloorPrice, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('0') + expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenDetails })).toBe('$1.23M') + expect(formatNumber({ input: 1234.5678, type: NumberType.FiatTokenDetails })).toBe('$1,234.57') + expect(formatNumber({ input: 1.048942, type: NumberType.FiatTokenDetails })).toBe('$1.049') - expect(formatNumber({ input: 12.1, type: NumberType.NFTTokenFloorPriceTrailingZeros })).toBe('12.10') - expect( - formatNumber({ - input: 12.1, - type: NumberType.NFTTokenFloorPriceTrailingZeros, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('12,10') - expect(formatNumber({ input: 0.09001, type: NumberType.NFTTokenFloorPriceTrailingZeros })).toBe('0.090') - expect( - formatNumber({ - input: 0.09001, - type: NumberType.NFTTokenFloorPriceTrailingZeros, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('0,090') + expect(formatNumber({ input: 0.001231, type: NumberType.FiatTokenDetails })).toBe('$0.00123') + expect(formatNumber({ input: 0.00001231, type: NumberType.FiatTokenDetails })).toBe('$0.0000123') - expect(formatNumber({ input: 0.987654321, type: NumberType.NFTCollectionStats })).toBe('1') - expect( - formatNumber({ - input: 0.987654321, - type: NumberType.NFTCollectionStats, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('1') - expect(formatNumber({ input: 0.9, type: NumberType.NFTCollectionStats })).toBe('1') - expect( - formatNumber({ input: 0.9, type: NumberType.NFTCollectionStats, locale: 'pt-BR', localCurrency: Currency.Brl }) - ).toBe('1') - expect(formatNumber({ input: 76543.21, type: NumberType.NFTCollectionStats })).toBe('76.5K') - expect( - formatNumber({ input: 76543.21, type: NumberType.NFTCollectionStats, locale: 'pt-BR', localCurrency: Currency.Brl }) - ).toBe('76,5\xa0mil') - expect(formatNumber({ input: 7.60000054321, type: NumberType.NFTCollectionStats })).toBe('8') - expect( - formatNumber({ - input: 7.60000054321, - type: NumberType.NFTCollectionStats, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('8') - expect(formatNumber({ input: 1234567890, type: NumberType.NFTCollectionStats })).toBe('1.2B') - expect( - formatNumber({ - input: 1234567890, - type: NumberType.NFTCollectionStats, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('1,2\xa0bi') - expect(formatNumber({ input: 1234567000000000, type: NumberType.NFTCollectionStats })).toBe('1234.6T') - expect( - formatNumber({ - input: 1234567000000000, - type: NumberType.NFTCollectionStats, - locale: 'pt-BR', - localCurrency: Currency.Brl, - }) - ).toBe('1234,6\xa0tri') + expect(formatNumber({ input: 0.0000001234, type: NumberType.FiatTokenDetails })).toBe('$0.000000123') + expect(formatNumber({ input: 0.000000009876, type: NumberType.FiatTokenDetails })).toBe('<$0.00000001') + }) + + it('formats fiat estimates on token details pages correctly with french locale and euro currency', () => { + mocked(useActiveLocale).mockReturnValue('fr-FR') + mocked(useActiveLocalCurrency).mockReturnValue(Currency.Eur) + const { formatNumber } = renderHook(() => useFormatter()).result.current + + expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenDetails })).toBe('1,23\xa0M\xa0€') + expect(formatNumber({ input: 1234.5678, type: NumberType.FiatTokenDetails })).toBe('1\u202f234,57\xa0€') + expect(formatNumber({ input: 1.048942, type: NumberType.FiatTokenDetails })).toBe('1,049\xa0€') + + expect(formatNumber({ input: 0.001231, type: NumberType.FiatTokenDetails })).toBe('0,00123\xa0€') + expect(formatNumber({ input: 0.00001231, type: NumberType.FiatTokenDetails })).toBe('0,0000123\xa0€') + + expect(formatNumber({ input: 0.0000001234, type: NumberType.FiatTokenDetails })).toBe('0,000000123\xa0€') + expect(formatNumber({ input: 0.000000009876, type: NumberType.FiatTokenDetails })).toBe('<0,00000001\xa0€') + }) + + it('formats fiat estimates for tokens correctly', () => { + const { formatNumber } = renderHook(() => useFormatter()).result.current + + expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenPrice })).toBe('$1.23M') + expect(formatNumber({ input: 1234.5678, type: NumberType.FiatTokenPrice })).toBe('$1,234.57') + + expect(formatNumber({ input: 0.010235, type: NumberType.FiatTokenPrice })).toBe('$0.0102') + expect(formatNumber({ input: 0.001231, type: NumberType.FiatTokenPrice })).toBe('$0.00123') + expect(formatNumber({ input: 0.00001231, type: NumberType.FiatTokenPrice })).toBe('$0.0000123') + + expect(formatNumber({ input: 0.0000001234, type: NumberType.FiatTokenPrice })).toBe('$0.000000123') + expect(formatNumber({ input: 0.000000009876, type: NumberType.FiatTokenPrice })).toBe('<$0.00000001') + expect(formatNumber({ input: 10000000000000000000000000000000, type: NumberType.FiatTokenPrice })).toBe( + '$1.000000E31' + ) + }) + + it('formats fiat estimates for tokens correctly with spanish locale and yen currency', () => { + mocked(useActiveLocale).mockReturnValue('es-ES') + mocked(useActiveLocalCurrency).mockReturnValue(Currency.Jpy) + const { formatNumber } = renderHook(() => useFormatter()).result.current + + expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenPrice })).toBe('1,23\xa0M¥') + expect(formatNumber({ input: 1234.5678, type: NumberType.FiatTokenPrice })).toBe('1234,57\xa0¥') + expect(formatNumber({ input: 12345.678, type: NumberType.FiatTokenPrice })).toBe('12.345,68\xa0¥') + + expect(formatNumber({ input: 0.010235, type: NumberType.FiatTokenPrice })).toBe('0,0102\xa0¥') + expect(formatNumber({ input: 0.001231, type: NumberType.FiatTokenPrice })).toBe('0,00123\xa0¥') + expect(formatNumber({ input: 0.00001231, type: NumberType.FiatTokenPrice })).toBe('0,0000123\xa0¥') + + expect(formatNumber({ input: 0.0000001234, type: NumberType.FiatTokenPrice })).toBe('0,000000123\xa0¥') + expect(formatNumber({ input: 0.000000009876, type: NumberType.FiatTokenPrice })).toBe('<0,00000001\xa0¥') + expect(formatNumber({ input: 10000000000000000000000000000000, type: NumberType.FiatTokenPrice })).toBe( + '1,000000E31\xa0¥' + ) + }) + + it('formats fiat estimates for token stats correctly', () => { + const { formatNumber } = renderHook(() => useFormatter()).result.current + + expect(formatNumber({ input: 1234576, type: NumberType.FiatTokenStats })).toBe('$1.2M') + expect(formatNumber({ input: 234567, type: NumberType.FiatTokenStats })).toBe('$234.6K') + expect(formatNumber({ input: 123.456, type: NumberType.FiatTokenStats })).toBe('$123.46') + expect(formatNumber({ input: 1.23, type: NumberType.FiatTokenStats })).toBe('$1.23') + expect(formatNumber({ input: 0.123, type: NumberType.FiatTokenStats })).toBe('$0.12') + expect(formatNumber({ input: 0.00123, type: NumberType.FiatTokenStats })).toBe('<$0.01') + expect(formatNumber({ input: 0, type: NumberType.FiatTokenStats })).toBe('-') + }) + + it('formats fiat estimates for token stats correctly with japenese locale and cad currency', () => { + mocked(useActiveLocale).mockReturnValue('ja-JP') + mocked(useActiveLocalCurrency).mockReturnValue(Currency.Cad) + const { formatNumber } = renderHook(() => useFormatter()).result.current + + expect(formatNumber({ input: 1234576, type: NumberType.FiatTokenStats })).toBe('CA$123.5万') + expect(formatNumber({ input: 234567, type: NumberType.FiatTokenStats })).toBe('CA$23.5万') + expect(formatNumber({ input: 123.456, type: NumberType.FiatTokenStats })).toBe('CA$123.46') + expect(formatNumber({ input: 1.23, type: NumberType.FiatTokenStats })).toBe('CA$1.23') + expect(formatNumber({ input: 0.123, type: NumberType.FiatTokenStats })).toBe('CA$0.12') + expect(formatNumber({ input: 0.00123, type: NumberType.FiatTokenStats })).toBe(' { + const { formatNumber } = renderHook(() => useFormatter()).result.current + + expect(formatNumber({ input: 1234567.891, type: NumberType.FiatGasPrice })).toBe('$1.23M') + expect(formatNumber({ input: 18.448, type: NumberType.FiatGasPrice })).toBe('$18.45') + expect(formatNumber({ input: 0.0099, type: NumberType.FiatGasPrice })).toBe('<$0.01') + expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice })).toBe('$0.00') + }) + + it('formats gas prices correctly with portugese locale and thai baht currency', () => { + mocked(useActiveLocale).mockReturnValue('pt-PR') + mocked(useActiveLocalCurrency).mockReturnValue(Currency.Thb) + const { formatNumber } = renderHook(() => useFormatter()).result.current + + expect(formatNumber({ input: 1234567.891, type: NumberType.FiatGasPrice })).toBe('฿\xa01,23\xa0mi') + expect(formatNumber({ input: 18.448, type: NumberType.FiatGasPrice })).toBe('฿\xa018,45') + expect(formatNumber({ input: 0.0099, type: NumberType.FiatGasPrice })).toBe('<฿\xa00,01') + expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice })).toBe('฿\xa00,00') + }) + + it('formats USD token quantities prices correctly', () => { + const { formatNumber } = renderHook(() => useFormatter()).result.current + + expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenQuantity })).toBe('$1.23M') + expect(formatNumber({ input: 18.448, type: NumberType.FiatTokenQuantity })).toBe('$18.45') + expect(formatNumber({ input: 0.0099, type: NumberType.FiatTokenQuantity })).toBe('<$0.01') + expect(formatNumber({ input: 0, type: NumberType.FiatTokenQuantity })).toBe('$0.00') + }) + + it('formats token quantities prices correctly with nigerian naira currency', () => { + mocked(useActiveLocalCurrency).mockReturnValue(Currency.Ngn) + const { formatNumber } = renderHook(() => useFormatter()).result.current + + expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenQuantity })).toBe('₦1.23M') + expect(formatNumber({ input: 18.448, type: NumberType.FiatTokenQuantity })).toBe('₦18.45') + expect(formatNumber({ input: 0.0099, type: NumberType.FiatTokenQuantity })).toBe('<₦0.01') + expect(formatNumber({ input: 0, type: NumberType.FiatTokenQuantity })).toBe('₦0.00') + }) + + it('formats Swap text input/output numbers correctly', () => { + const { formatNumber } = renderHook(() => useFormatter()).result.current + + expect(formatNumber({ input: 1234567.8901, type: NumberType.SwapTradeAmount })).toBe('1234570') + expect(formatNumber({ input: 765432.1, type: NumberType.SwapTradeAmount })).toBe('765432') + + expect(formatNumber({ input: 7654.321, type: NumberType.SwapTradeAmount })).toBe('7654.32') + expect(formatNumber({ input: 765.4321, type: NumberType.SwapTradeAmount })).toBe('765.432') + expect(formatNumber({ input: 76.54321, type: NumberType.SwapTradeAmount })).toBe('76.5432') + expect(formatNumber({ input: 7.654321, type: NumberType.SwapTradeAmount })).toBe('7.65432') + expect(formatNumber({ input: 7.60000054321, type: NumberType.SwapTradeAmount })).toBe('7.60') + expect(formatNumber({ input: 7.6, type: NumberType.SwapTradeAmount })).toBe('7.60') + expect(formatNumber({ input: 7, type: NumberType.SwapTradeAmount })).toBe('7.00') + + expect(formatNumber({ input: 0.987654321, type: NumberType.SwapTradeAmount })).toBe('0.98765') + expect(formatNumber({ input: 0.9, type: NumberType.SwapTradeAmount })).toBe('0.90') + expect(formatNumber({ input: 0.901000123, type: NumberType.SwapTradeAmount })).toBe('0.901') + expect(formatNumber({ input: 0.000000001, type: NumberType.SwapTradeAmount })).toBe('0.000000001') + expect(formatNumber({ input: 0, type: NumberType.SwapTradeAmount })).toBe('0') + }) + + it('formats Swap text input/output numbers correctly with Korean locale', () => { + mocked(useActiveLocale).mockReturnValue('ko-KR') + const { formatNumber } = renderHook(() => useFormatter()).result.current + + expect(formatNumber({ input: 1234567.8901, type: NumberType.SwapTradeAmount })).toBe('1234570') + expect(formatNumber({ input: 765432.1, type: NumberType.SwapTradeAmount })).toBe('765432') + + expect(formatNumber({ input: 7654.321, type: NumberType.SwapTradeAmount })).toBe('7654.32') + expect(formatNumber({ input: 765.4321, type: NumberType.SwapTradeAmount })).toBe('765.432') + expect(formatNumber({ input: 76.54321, type: NumberType.SwapTradeAmount })).toBe('76.5432') + expect(formatNumber({ input: 7.654321, type: NumberType.SwapTradeAmount })).toBe('7.65432') + expect(formatNumber({ input: 7.60000054321, type: NumberType.SwapTradeAmount })).toBe('7.60') + expect(formatNumber({ input: 7.6, type: NumberType.SwapTradeAmount })).toBe('7.60') + expect(formatNumber({ input: 7, type: NumberType.SwapTradeAmount })).toBe('7.00') + + expect(formatNumber({ input: 0.987654321, type: NumberType.SwapTradeAmount })).toBe('0.98765') + expect(formatNumber({ input: 0.9, type: NumberType.SwapTradeAmount })).toBe('0.90') + expect(formatNumber({ input: 0.901000123, type: NumberType.SwapTradeAmount })).toBe('0.901') + expect(formatNumber({ input: 0.000000001, type: NumberType.SwapTradeAmount })).toBe('0.000000001') + expect(formatNumber({ input: 0, type: NumberType.SwapTradeAmount })).toBe('0') + }) + + it('formats NFT numbers correctly', () => { + const { formatNumber } = renderHook(() => useFormatter()).result.current + + expect(formatNumber({ input: 1234567000000000, type: NumberType.NFTTokenFloorPrice })).toBe('>999T') + expect(formatNumber({ input: 1002345, type: NumberType.NFTTokenFloorPrice })).toBe('1M') + expect(formatNumber({ input: 1234, type: NumberType.NFTTokenFloorPrice })).toBe('1.23K') + expect(formatNumber({ input: 12.34467, type: NumberType.NFTTokenFloorPrice })).toBe('12.34') + expect(formatNumber({ input: 12.1, type: NumberType.NFTTokenFloorPrice })).toBe('12.1') + expect(formatNumber({ input: 0.00909, type: NumberType.NFTTokenFloorPrice })).toBe('0.009') + expect(formatNumber({ input: 0.09001, type: NumberType.NFTTokenFloorPrice })).toBe('0.09') + expect(formatNumber({ input: 0.00099, type: NumberType.NFTTokenFloorPrice })).toBe('<0.001') + expect(formatNumber({ input: 0, type: NumberType.NFTTokenFloorPrice })).toBe('0') + + expect(formatNumber({ input: 12.1, type: NumberType.NFTTokenFloorPriceTrailingZeros })).toBe('12.10') + expect(formatNumber({ input: 0.09001, type: NumberType.NFTTokenFloorPriceTrailingZeros })).toBe('0.090') + + expect(formatNumber({ input: 0.987654321, type: NumberType.NFTCollectionStats })).toBe('1') + expect(formatNumber({ input: 0.9, type: NumberType.NFTCollectionStats })).toBe('1') + expect(formatNumber({ input: 76543.21, type: NumberType.NFTCollectionStats })).toBe('76.5K') + expect(formatNumber({ input: 7.60000054321, type: NumberType.NFTCollectionStats })).toBe('8') + expect(formatNumber({ input: 1234567890, type: NumberType.NFTCollectionStats })).toBe('1.2B') + expect(formatNumber({ input: 1234567000000000, type: NumberType.NFTCollectionStats })).toBe('1234.6T') + }) + + it('formats NFT numbers correctly with brazilian portugese locale and braziliean real currency', () => { + mocked(useActiveLocale).mockReturnValue('pt-Br') + mocked(useActiveLocalCurrency).mockReturnValue(Currency.Brl) + const { formatNumber } = renderHook(() => useFormatter()).result.current + + expect(formatNumber({ input: 1234567000000000, type: NumberType.NFTTokenFloorPrice })).toBe('>999\xa0tri') + expect(formatNumber({ input: 1002345, type: NumberType.NFTTokenFloorPrice })).toBe('1\xa0mi') + expect(formatNumber({ input: 1234, type: NumberType.NFTTokenFloorPrice })).toBe('1,23\xa0mil') + expect(formatNumber({ input: 12.34467, type: NumberType.NFTTokenFloorPrice })).toBe('12,34') + expect(formatNumber({ input: 12.1, type: NumberType.NFTTokenFloorPrice })).toBe('12,1') + expect(formatNumber({ input: 0.00909, type: NumberType.NFTTokenFloorPrice })).toBe('0,009') + expect(formatNumber({ input: 0.09001, type: NumberType.NFTTokenFloorPrice })).toBe('0,09') + expect(formatNumber({ input: 0.00099, type: NumberType.NFTTokenFloorPrice })).toBe('<0,001') + expect(formatNumber({ input: 0, type: NumberType.NFTTokenFloorPrice })).toBe('0') + + expect(formatNumber({ input: 12.1, type: NumberType.NFTTokenFloorPriceTrailingZeros })).toBe('12,10') + expect(formatNumber({ input: 0.09001, type: NumberType.NFTTokenFloorPriceTrailingZeros })).toBe('0,090') + + expect(formatNumber({ input: 0.987654321, type: NumberType.NFTCollectionStats })).toBe('1') + expect(formatNumber({ input: 0.9, type: NumberType.NFTCollectionStats })).toBe('1') + expect(formatNumber({ input: 76543.21, type: NumberType.NFTCollectionStats })).toBe('76,5\xa0mil') + expect(formatNumber({ input: 7.60000054321, type: NumberType.NFTCollectionStats })).toBe('8') + expect(formatNumber({ input: 1234567890, type: NumberType.NFTCollectionStats })).toBe('1,2\xa0bi') + expect(formatNumber({ input: 1234567000000000, type: NumberType.NFTCollectionStats })).toBe('1234,6\xa0tri') + }) }) describe('formatUSDPrice', () => { - it('should correctly format 0.000000009876', () => { - expect(formatUSDPrice(0.000000009876)).toBe('<$0.00000001') + beforeEach(() => { + mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) + mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true) }) - it('should correctly format 0.00001231', () => { - expect(formatUSDPrice(0.00001231)).toBe('$0.0000123') + + it('format fiat price correctly', () => { + const { formatFiatPrice } = renderHook(() => useFormatter()).result.current + + expect(formatFiatPrice({ price: 0.000000009876 })).toBe('<$0.00000001') + expect(formatFiatPrice({ price: 0.00001231 })).toBe('$0.0000123') + expect(formatFiatPrice({ price: 0.001231 })).toBe('$0.00123') + expect(formatFiatPrice({ price: 0.0 })).toBe('$0.00') + expect(formatFiatPrice({ price: 0 })).toBe('$0.00') + expect(formatFiatPrice({ price: 1.048942 })).toBe('$1.05') + expect(formatFiatPrice({ price: 0.10235 })).toBe('$0.102') + expect(formatFiatPrice({ price: 1_234.5678 })).toBe('$1,234.57') + expect(formatFiatPrice({ price: 1_234_567.891 })).toBe('$1.23M') + expect(formatFiatPrice({ price: 1_000_000_000_000 })).toBe('$1.00T') + expect(formatFiatPrice({ price: 1_000_000_000_000_000 })).toBe('$1000.00T') }) - it('should correctly format 0.001231', () => { - expect(formatUSDPrice(0.001231)).toBe('$0.00123') - }) - it('should correctly format 0.0', () => { - expect(formatUSDPrice(0.0)).toBe('$0.00') - }) - it('should correctly format 0', () => { - expect(formatUSDPrice(0)).toBe('$0.00') - }) - it('should correctly format 1.048942', () => { - expect(formatUSDPrice(1.048942)).toBe('$1.05') - }) - it('should correctly format 0.10235', () => { - expect(formatUSDPrice(0.10235)).toBe('$0.102') - }) - it('should correctly format 1234.5678', () => { - expect(formatUSDPrice(1_234.5678)).toBe('$1,234.57') - }) - it('should correctly format 1234567.8910', () => { - expect(formatUSDPrice(1_234_567.891)).toBe('$1.23M') - }) - it('should correctly format 1000000000000', () => { - expect(formatUSDPrice(1_000_000_000_000)).toBe('$1.00T') - }) - it('should correctly format 1000000000000000', () => { - expect(formatUSDPrice(1_000_000_000_000_000)).toBe('$1000.00T') + + it('format fiat price correctly in euros with french locale', () => { + mocked(useActiveLocalCurrency).mockReturnValue(Currency.Eur) + mocked(useActiveLocale).mockReturnValue('fr-FR') + const { formatFiatPrice } = renderHook(() => useFormatter()).result.current + + expect(formatFiatPrice({ price: 0.000000009876 })).toBe('<0,00000001\xa0€') + expect(formatFiatPrice({ price: 0.00001231 })).toBe('0,0000123\xa0€') + expect(formatFiatPrice({ price: 0.001231 })).toBe('0,00123\xa0€') + expect(formatFiatPrice({ price: 0.0 })).toBe('0,00\xa0€') + expect(formatFiatPrice({ price: 0 })).toBe('0,00\xa0€') + expect(formatFiatPrice({ price: 1.048942 })).toBe('1,05\xa0€') + expect(formatFiatPrice({ price: 0.10235 })).toBe('0,102\xa0€') + expect(formatFiatPrice({ price: 1_234.5678 })).toBe('1\u202f234,57\xa0€') + expect(formatFiatPrice({ price: 1_234_567.891 })).toBe('1,23\xa0M\xa0€') + expect(formatFiatPrice({ price: 1_000_000_000_000 })).toBe('1,00\xa0Bn\xa0€') + expect(formatFiatPrice({ price: 1_000_000_000_000_000 })).toBe('1000,00\xa0Bn\xa0€') }) }) describe('formatPriceImpact', () => { + beforeEach(() => { + mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) + mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true) + }) + it('should correctly format undefined', () => { + const { formatPriceImpact } = renderHook(() => useFormatter()).result.current + expect(formatPriceImpact(undefined)).toBe('-') }) it('correctly formats a percent with 3 significant digits', () => { + const { formatPriceImpact } = renderHook(() => useFormatter()).result.current + expect(formatPriceImpact(new Percent(-1, 100000))).toBe('0.001%') expect(formatPriceImpact(new Percent(-1, 1000))).toBe('0.100%') expect(formatPriceImpact(new Percent(-1, 100))).toBe('1.000%') expect(formatPriceImpact(new Percent(-1, 10))).toBe('10.000%') expect(formatPriceImpact(new Percent(-1, 1))).toBe('100.000%') }) + + it('correctly formats a percent with 3 significant digits with french locale', () => { + mocked(useActiveLocale).mockReturnValue('fr-FR') + const { formatPriceImpact } = renderHook(() => useFormatter()).result.current + + expect(formatPriceImpact(new Percent(-1, 100000))).toBe('0,001%') + expect(formatPriceImpact(new Percent(-1, 1000))).toBe('0,100%') + expect(formatPriceImpact(new Percent(-1, 100))).toBe('1,000%') + expect(formatPriceImpact(new Percent(-1, 10))).toBe('10,000%') + expect(formatPriceImpact(new Percent(-1, 1))).toBe('100,000%') + }) }) describe('formatSlippage', () => { + beforeEach(() => { + mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) + mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true) + }) + it('should correctly format undefined', () => { + const { formatSlippage } = renderHook(() => useFormatter()).result.current + expect(formatSlippage(undefined)).toBe('-') }) it('correctly formats a percent with 3 significant digits', () => { + const { formatSlippage } = renderHook(() => useFormatter()).result.current + expect(formatSlippage(new Percent(1, 100000))).toBe('0.001%') expect(formatSlippage(new Percent(1, 1000))).toBe('0.100%') expect(formatSlippage(new Percent(1, 100))).toBe('1.000%') expect(formatSlippage(new Percent(1, 10))).toBe('10.000%') expect(formatSlippage(new Percent(1, 1))).toBe('100.000%') }) -}) -describe('currencyAmountToPreciseFloat', () => { - it('small number', () => { - const currencyAmount = CurrencyAmount.fromFractionalAmount(USDC_MAINNET, '20000', '7') - expect(currencyAmountToPreciseFloat(currencyAmount)).toEqual(0.00285714) - }) - it('tiny number', () => { - const currencyAmount = CurrencyAmount.fromFractionalAmount(USDC_MAINNET, '2', '7') - expect(currencyAmountToPreciseFloat(currencyAmount)).toEqual(0.000000285714) - }) - it('lots of decimals', () => { - const currencyAmount = CurrencyAmount.fromFractionalAmount(USDC_MAINNET, '200000000', '7') - expect(currencyAmountToPreciseFloat(currencyAmount)).toEqual(28.571428) - }) - it('integer', () => { - const currencyAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '20000000') - expect(currencyAmountToPreciseFloat(currencyAmount)).toEqual(20.0) - }) -}) + it('correctly formats a percent with 3 significant digits with french locale', () => { + mocked(useActiveLocale).mockReturnValue('fr-FR') + const { formatSlippage } = renderHook(() => useFormatter()).result.current -describe('priceToPreciseFloat', () => { - it('small number', () => { - const price = new Price(WBTC, USDC_MAINNET, 1234, 1) - expect(priceToPreciseFloat(price)).toEqual(0.0810373) - }) - it('tiny number', () => { - const price = new Price(WBTC, USDC_MAINNET, 12345600, 1) - expect(priceToPreciseFloat(price)).toEqual(0.00000810005) - }) - it('lots of decimals', () => { - const price = new Price(WBTC, USDC_MAINNET, 123, 7) - expect(priceToPreciseFloat(price)).toEqual(5.691056911) - }) - it('integer', () => { - const price = new Price(WBTC, USDC_MAINNET, 1, 7) - expect(priceToPreciseFloat(price)).toEqual(700) + expect(formatSlippage(new Percent(1, 100000))).toBe('0,001%') + expect(formatSlippage(new Percent(1, 1000))).toBe('0,100%') + expect(formatSlippage(new Percent(1, 100))).toBe('1,000%') + expect(formatSlippage(new Percent(1, 10))).toBe('10,000%') + expect(formatSlippage(new Percent(1, 1))).toBe('100,000%') }) }) describe('formatReviewSwapCurrencyAmount', () => { + beforeEach(() => { + mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) + mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true) + }) + it('should use TokenTx formatting under a default length', () => { + const { formatReviewSwapCurrencyAmount } = renderHook(() => useFormatter()).result.current + const currencyAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '2000000000') // 2,000 USDC expect(formatReviewSwapCurrencyAmount(currencyAmount)).toBe('2,000') }) + + it('should use TokenTx formatting under a default length with french locales', () => { + mocked(useActiveLocale).mockReturnValue('fr-FR') + const { formatReviewSwapCurrencyAmount } = renderHook(() => useFormatter()).result.current + + const currencyAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '2000000000') // 2,000 USDC + expect(formatReviewSwapCurrencyAmount(currencyAmount)).toBe('2\u202f000') + }) + it('should use SwapTradeAmount formatting over the default length', () => { + const { formatReviewSwapCurrencyAmount } = renderHook(() => useFormatter()).result.current + + const currencyAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '2000000000000') // 2,000,000 USDC + expect(formatReviewSwapCurrencyAmount(currencyAmount)).toBe('2000000') + }) + + it('should use SwapTradeAmount formatting over the default length with french locales', () => { + mocked(useActiveLocale).mockReturnValue('fr-FR') + const { formatReviewSwapCurrencyAmount } = renderHook(() => useFormatter()).result.current + const currencyAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '2000000000000') // 2,000,000 USDC expect(formatReviewSwapCurrencyAmount(currencyAmount)).toBe('2000000') }) diff --git a/src/utils/formatNumbers.ts b/src/utils/formatNumbers.ts index c21d4550bc..f2a48c515c 100644 --- a/src/utils/formatNumbers.ts +++ b/src/utils/formatNumbers.ts @@ -1,4 +1,4 @@ -import { Currency, CurrencyAmount, Percent, Price } from '@uniswap/sdk-core' +import { Currency, CurrencyAmount, Percent, Price, Token } from '@uniswap/sdk-core' import { DEFAULT_LOCAL_CURRENCY, LOCAL_CURRENCY_SYMBOL_DISPLAY_TYPE, @@ -12,6 +12,7 @@ import { useActiveLocalCurrency } from 'hooks/useActiveLocalCurrency' import { useActiveLocale } from 'hooks/useActiveLocale' import usePrevious from 'hooks/usePrevious' import { useCallback, useMemo } from 'react' +import { Bound } from 'state/mint/v3/actions' type Nullish = T | null | undefined type NumberFormatOptions = Intl.NumberFormatOptions @@ -392,7 +393,7 @@ interface FormatNumberOptions { conversionRate?: number } -export function formatNumber({ +function formatNumber({ input, type = NumberType.TokenNonTx, placeholder = '-', @@ -434,7 +435,7 @@ interface FormatCurrencyAmountOptions { conversionRate?: number } -export function formatCurrencyAmount({ +function formatCurrencyAmount({ amount, type = NumberType.TokenNonTx, placeholder, @@ -452,7 +453,7 @@ export function formatCurrencyAmount({ }) } -export function formatPriceImpact(priceImpact: Percent | undefined, locale: SupportedLocale = DEFAULT_LOCALE): string { +function formatPriceImpact(priceImpact: Percent | undefined, locale: SupportedLocale = DEFAULT_LOCALE): string { if (!priceImpact) return '-' return `${Number(priceImpact.multiply(-1).toFixed(3)).toLocaleString(locale, { @@ -462,13 +463,17 @@ export function formatPriceImpact(priceImpact: Percent | undefined, locale: Supp })}%` } -export function formatSlippage(slippage: Percent | undefined) { +function formatSlippage(slippage: Percent | undefined, locale: SupportedLocale = DEFAULT_LOCALE) { if (!slippage) return '-' - return `${slippage.toFixed(3)}%` + return `${Number(slippage.toFixed(3)).toLocaleString(locale, { + minimumFractionDigits: 3, + maximumFractionDigits: 3, + useGrouping: false, + })}%` } -interface FormatPriceProps { +interface FormatPriceOptions { price: Nullish> type: FormatterType locale?: SupportedLocale @@ -476,13 +481,13 @@ interface FormatPriceProps { conversionRate?: number } -export function formatPrice({ +function formatPrice({ price, type = NumberType.FiatTokenPrice, locale = DEFAULT_LOCALE, localCurrency = DEFAULT_LOCAL_CURRENCY, conversionRate, -}: FormatPriceProps): string { +}: FormatPriceOptions): string { if (price === null || price === undefined) { return '-' } @@ -490,45 +495,80 @@ export function formatPrice({ return formatNumber({ input: parseFloat(price.toSignificant()), type, locale, localCurrency, conversionRate }) } -export function formatNumberOrString(price: Nullish, type: FormatterType): string { - if (price === null || price === undefined) return '-' - if (typeof price === 'string') return formatNumber({ input: parseFloat(price), type }) - return formatNumber({ input: price, type }) +interface FormatTickPriceOptions { + price?: Price + atLimit: { [bound in Bound]?: boolean | undefined } + direction: Bound + placeholder?: string + numberType?: NumberType + locale?: SupportedLocale + localCurrency?: SupportedLocalCurrency + conversionRate?: number } -export function formatUSDPrice(price: Nullish, type: NumberType = NumberType.FiatTokenPrice): string { - return formatNumberOrString(price, type) -} - -/** Formats USD and non-USD prices */ -export function formatFiatPrice(price: Nullish, currency = 'USD'): string { - if (price === null || price === undefined) return '-' - return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(price) -} - -// Convert [CurrencyAmount] to number with necessary precision for price formatting. -export const currencyAmountToPreciseFloat = (currencyAmount: CurrencyAmount | undefined) => { - if (!currencyAmount) return undefined - const floatForLargerNumbers = parseFloat(currencyAmount.toExact()) - if (floatForLargerNumbers < 0.1) { - return parseFloat(currencyAmount.toSignificant(6)) +function formatTickPrice({ + price, + atLimit, + direction, + placeholder, + numberType, + locale, + localCurrency, + conversionRate, +}: FormatTickPriceOptions) { + if (atLimit[direction]) { + return direction === Bound.LOWER ? '0' : '∞' } - return floatForLargerNumbers + + if (!price && placeholder !== undefined) { + return placeholder + } + + return formatPrice({ price, type: numberType ?? NumberType.TokenNonTx, locale, localCurrency, conversionRate }) } -// Convert [Price] to number with necessary precision for price formatting. -export const priceToPreciseFloat = (price: Price | undefined) => { - if (!price) return undefined - const floatForLargerNumbers = parseFloat(price.toFixed(9)) - if (floatForLargerNumbers < 0.1) { - return parseFloat(price.toSignificant(6)) - } - return floatForLargerNumbers +interface FormatNumberOrStringOptions { + input: Nullish + type: FormatterType + locale?: SupportedLocale + localCurrency?: SupportedLocalCurrency + conversionRate?: number +} + +function formatNumberOrString({ + input, + type, + locale, + localCurrency, + conversionRate, +}: FormatNumberOrStringOptions): string { + if (input === null || input === undefined) return '-' + if (typeof input === 'string') + return formatNumber({ input: parseFloat(input), type, locale, localCurrency, conversionRate }) + return formatNumber({ input, type, locale, localCurrency, conversionRate }) +} + +interface FormatFiatPriceOptions { + price: Nullish + type?: FormatterType + locale?: SupportedLocale + localCurrency?: SupportedLocalCurrency + conversionRate?: number +} + +function formatFiatPrice({ + price, + type = NumberType.FiatTokenPrice, + locale, + localCurrency, + conversionRate, +}: FormatFiatPriceOptions): string { + return formatNumberOrString({ input: price, type, locale, localCurrency, conversionRate }) } const MAX_AMOUNT_STR_LENGTH = 9 -export function formatReviewSwapCurrencyAmount( +function formatReviewSwapCurrencyAmount( amount: CurrencyAmount, locale: SupportedLocale = DEFAULT_LOCALE ): string { @@ -539,7 +579,7 @@ export function formatReviewSwapCurrencyAmount( return formattedAmount } -export function useFormatterLocales(): { +function useFormatterLocales(): { formatterLocale: SupportedLocale formatterLocalCurrency: SupportedLocalCurrency } { @@ -620,7 +660,7 @@ export function useFormatter() { ) const formatPriceWithLocales = useCallback( - (options: Omit) => + (options: Omit) => formatPrice({ ...options, locale: formatterLocale, @@ -640,20 +680,66 @@ export function useFormatter() { [formatterLocale] ) + const formatSlippageWithLocales = useCallback( + (slippage: Percent | undefined) => formatSlippage(slippage, formatterLocale), + [formatterLocale] + ) + + const formatTickPriceWithLocales = useCallback( + (options: Omit) => + formatTickPrice({ + ...options, + locale: formatterLocale, + localCurrency: currencyToFormatWith, + conversionRate: localCurrencyConversionRateToFormatWith, + }), + [currencyToFormatWith, formatterLocale, localCurrencyConversionRateToFormatWith] + ) + + const formatNumberOrStringWithLocales = useCallback( + (options: Omit) => + formatNumberOrString({ + ...options, + locale: formatterLocale, + localCurrency: currencyToFormatWith, + conversionRate: localCurrencyConversionRateToFormatWith, + }), + [currencyToFormatWith, formatterLocale, localCurrencyConversionRateToFormatWith] + ) + + const formatFiatPriceWithLocales = useCallback( + (options: Omit) => + formatFiatPrice({ + ...options, + locale: formatterLocale, + localCurrency: currencyToFormatWith, + conversionRate: localCurrencyConversionRateToFormatWith, + }), + [currencyToFormatWith, formatterLocale, localCurrencyConversionRateToFormatWith] + ) + return useMemo( () => ({ formatCurrencyAmount: formatCurrencyAmountWithLocales, + formatFiatPrice: formatFiatPriceWithLocales, formatNumber: formatNumberWithLocales, + formatNumberOrString: formatNumberOrStringWithLocales, formatPrice: formatPriceWithLocales, formatPriceImpact: formatPriceImpactWithLocales, formatReviewSwapCurrencyAmount: formatReviewSwapCurrencyAmountWithLocales, + formatSlippage: formatSlippageWithLocales, + formatTickPrice: formatTickPriceWithLocales, }), [ formatCurrencyAmountWithLocales, + formatFiatPriceWithLocales, + formatNumberOrStringWithLocales, formatNumberWithLocales, formatPriceImpactWithLocales, formatPriceWithLocales, formatReviewSwapCurrencyAmountWithLocales, + formatSlippageWithLocales, + formatTickPriceWithLocales, ] ) } diff --git a/src/utils/formatTickPrice.ts b/src/utils/formatTickPrice.ts deleted file mode 100644 index 072594f9c2..0000000000 --- a/src/utils/formatTickPrice.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Price, Token } from '@uniswap/sdk-core' -import { formatPrice, NumberType } from 'utils/formatNumbers' - -import { Bound } from '../state/mint/v3/actions' - -interface FormatTickPriceArgs { - price?: Price - atLimit: { [bound in Bound]?: boolean | undefined } - direction: Bound - placeholder?: string - numberType?: NumberType -} - -export function formatTickPrice({ price, atLimit, direction, placeholder, numberType }: FormatTickPriceArgs) { - if (atLimit[direction]) { - return direction === Bound.LOWER ? '0' : '∞' - } - - if (!price && placeholder !== undefined) { - return placeholder - } - - return formatPrice({ price, type: numberType ?? NumberType.TokenNonTx }) -}