chore: only exposing useFormatter (#7308)

This commit is contained in:
Jack Short 2023-09-18 20:21:21 -04:00 committed by GitHub
parent cf09e80934
commit 91c2013522
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 679 additions and 622 deletions

@ -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 */

@ -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()
})

@ -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<typeof useFormatter>['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<Activity> {
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<Activity> {
function parseWrap(
wrap: WrapTransactionInfo,
chainId: ChainId,
status: TransactionStatus,
formatNumber: FormatNumberFunctionType
): Partial<Activity> {
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<Activity> {
function parseLP(
lp: GenericLPInfo,
chainId: ChainId,
tokens: ChainTokenMap,
formatNumber: FormatNumberFunctionType
): Partial<Activity> {
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<Activity> {
// 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<Activity> = {}
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])
}

@ -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<typeof useFormatter>['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<Activity>
type ActivityTypeParser = (
changes: TransactionChanges,
formatNumberOrString: FormatNumberOrStringFunctionType,
assetActivity: TransactionActivity
) => Partial<Activity>
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
}, {})

@ -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 && (
<>
<Row gap="4">
<Box className={styles.primaryText}>{formatUSDPrice(token.market.price.value)}</Box>
<Box className={styles.primaryText}>{formatFiatPrice({ price: token.market.price.value })}</Box>
</Row>
<PriceChangeContainer>
<DeltaArrow delta={token.market?.pricePercentChange?.value} />

@ -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

@ -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)

@ -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)

@ -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 }
<ChartHeader data-cy="chart-header">
{displayPrice.value ? (
<>
<TokenPrice>{formatUSDPrice(displayPrice.value)}</TokenPrice>
<TokenPrice>{formatFiatPrice({ price: displayPrice.value })}</TokenPrice>
<ChartDelta startingPrice={startingPrice} endingPrice={displayPrice} />
</>
) : lastPrice.value ? (
<OutdatedContainer>
<OutdatedPriceContainer>
<TokenPrice>{formatUSDPrice(lastPrice.value)}</TokenPrice>
<TokenPrice>{formatFiatPrice({ price: lastPrice.value })}</TokenPrice>
<MouseoverTooltip text={tooltipMessage}>
<Info size={16} />
</MouseoverTooltip>

@ -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<HTMLDivElement>) => {
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<HT
}
// A simple 0 price indicates the price is not currently available from the api
const price = token.market?.price?.value === 0 ? '-' : formatUSDPrice(token.market?.price?.value)
const price = token.market?.price?.value === 0 ? '-' : formatFiatPrice({ price: token.market?.price?.value })
// TODO: currency logo sizing mobile (32px) vs. desktop (24px)
return (

@ -17,7 +17,6 @@ import { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks'
import styled, { DefaultTheme, useTheme } from 'styled-components'
import { ExternalLink, ThemedText } from 'theme'
import { FormatterRule, NumberType, SIX_SIG_FIGS_NO_COMMAS, useFormatter } from 'utils/formatNumbers'
import { priceToPreciseFloat } from 'utils/formatNumbers'
import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries'
import { formatSwapButtonClickEventProperties } from 'utils/loggingFormatters'
import { getPriceImpactColor } from 'utils/prices'
@ -85,7 +84,7 @@ export default function SwapModalFooter({
const label = `${trade.executionPrice.baseCurrency?.symbol} `
const labelInverted = `${trade.executionPrice.quoteCurrency?.symbol}`
const formattedPrice = formatNumber({
input: priceToPreciseFloat(trade.executionPrice),
input: trade.executionPrice ? parseFloat(trade.executionPrice.toFixed(9)) : undefined,
type: NumberType.TokenTx,
})
const txCount = getTransactionCount(trade)

@ -6,7 +6,6 @@ import {
TEST_TRADE_EXACT_OUTPUT,
} from 'test-utils/constants'
import { render, screen } from 'test-utils/render'
import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers'
import SwapModalHeader from './SwapModalHeader'
@ -17,16 +16,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_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`)
})
})

@ -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 (
<>
<TableElement>
@ -212,7 +214,7 @@ const MarketplaceRow = ({ marketplace, floorInEth, listings }: MarketplaceRowPro
<TableElement>
<ThemedText.BodySmall color="neutral2">
{Number(floorInEth) > 0
? `${formatNumberOrString(floorInEth, NumberType.NFTTokenFloorPriceTrailingZeros)} ETH`
? `${formatNumberOrString({ input: floorInEth, type: NumberType.NFTTokenFloorPriceTrailingZeros })} ETH`
: '-'}
</ThemedText.BodySmall>
</TableElement>

@ -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)

@ -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'
jest.mock('hooks/useActiveLocale')
jest.mock('hooks/useActiveLocalCurrency')
jest.mock('graphql/data/ConversionRate')
jest.mock('featureFlags/flags/currencyConversion')
describe('formatNumber', () => {
beforeEach(() => {
mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false })
mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true)
})
it('formats token reference numbers correctly', () => {
const { formatNumber } = renderHook(() => useFormatter()).result.current
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', () => {
})
it('formats token reference numbers correctly with deutsch locale', () => {
mocked(useActiveLocale).mockReturnValue('de-DE')
const { formatNumber } = renderHook(() => useFormatter()).result.current
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 token transaction numbers correctly', () => {
const { formatNumber } = renderHook(() => useFormatter()).result.current
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')
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')
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 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€')
it('formats token transaction numbers correctly with russian locale', () => {
mocked(useActiveLocale).mockReturnValue('ru-RU')
const { formatNumber } = renderHook(() => useFormatter()).result.current
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')
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: 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')
})
it('formats fiat estimates on token details pages correctly', () => {
const { formatNumber } = renderHook(() => useFormatter()).result.current
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: 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: 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€')
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€')
})
it('formats fiat estimates for tokens correctly', () => {
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: 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¥')
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: 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¥')
})
it('formats fiat estimates for token stats correctly', () => {
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: 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('<CA$0.01')
expect(formatNumber({ input: 0, type: NumberType.FiatTokenStats })).toBe('-')
expect(
formatNumber({ input: 0, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: Currency.Cad })
).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('<CA$0.01')
expect(formatNumber({ input: 0, type: NumberType.FiatTokenStats })).toBe('-')
})
it('formats gas USD prices correctly', () => {
const { formatNumber } = renderHook(() => useFormatter()).result.current
it('formats gas USD prices correctly', () => {
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 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
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')
})
})
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
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.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.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 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
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: 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.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')
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')
})

@ -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> = 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<Price<Currency, Currency>>
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<number | string>, 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<Token, Token>
atLimit: { [bound in Bound]?: boolean | undefined }
direction: Bound
placeholder?: string
numberType?: NumberType
locale?: SupportedLocale
localCurrency?: SupportedLocalCurrency
conversionRate?: number
}
export function formatUSDPrice(price: Nullish<number | string>, type: NumberType = NumberType.FiatTokenPrice): string {
return formatNumberOrString(price, type)
}
/** Formats USD and non-USD prices */
export function formatFiatPrice(price: Nullish<number>, 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<Currency> | 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<Currency, Currency> | 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<number | string>
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<number | string>
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<Currency>,
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<FormatPriceProps, LocalesType>) =>
(options: Omit<FormatPriceOptions, LocalesType>) =>
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<FormatTickPriceOptions, LocalesType>) =>
formatTickPrice({
...options,
locale: formatterLocale,
localCurrency: currencyToFormatWith,
conversionRate: localCurrencyConversionRateToFormatWith,
}),
[currencyToFormatWith, formatterLocale, localCurrencyConversionRateToFormatWith]
)
const formatNumberOrStringWithLocales = useCallback(
(options: Omit<FormatNumberOrStringOptions, LocalesType>) =>
formatNumberOrString({
...options,
locale: formatterLocale,
localCurrency: currencyToFormatWith,
conversionRate: localCurrencyConversionRateToFormatWith,
}),
[currencyToFormatWith, formatterLocale, localCurrencyConversionRateToFormatWith]
)
const formatFiatPriceWithLocales = useCallback(
(options: Omit<FormatFiatPriceOptions, LocalesType>) =>
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,
]
)
}

@ -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<Token, Token>
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 })
}