chore: updating formatCurrencyAmount to handle multiple langs and cur… (#7263)

* chore: updating formatCurrencyAmount to handle multiple langs and currencies

* missed advanced swap details

* missed confirmed swap modal

* removing updating visual effects

* removing it from parseLocale

* chore: displaying local currency and language formatting

* making effects visible

* moving to hook

* useFormatCurrencyAmount

* moving it to useformatter hook

* exporting formatting locales

* missed one parsed remote

* moving hook to bottom of file

* moving to bottom
This commit is contained in:
Jack Short 2023-09-11 13:51:11 -04:00 committed by GitHub
parent e4d103b015
commit 86e4dd5153
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 137 additions and 55 deletions

@ -3,6 +3,7 @@ import { t } from '@lingui/macro'
import { ChainId, Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { ChainId, Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { nativeOnChain } from '@uniswap/smart-order-router' import { nativeOnChain } from '@uniswap/smart-order-router'
import UniswapXBolt from 'assets/svg/bolt.svg' import UniswapXBolt from 'assets/svg/bolt.svg'
import { SupportedLocale } from 'constants/locales'
import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks' import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
import { ChainTokenMap, useAllTokensMultichain } from 'hooks/Tokens' import { ChainTokenMap, useAllTokensMultichain } from 'hooks/Tokens'
import { useMemo } from 'react' import { useMemo } from 'react'
@ -23,7 +24,7 @@ import {
TransactionType, TransactionType,
WrapTransactionInfo, WrapTransactionInfo,
} from 'state/transactions/types' } from 'state/transactions/types'
import { formatCurrencyAmount } from 'utils/formatNumbers' import { formatCurrencyAmount, useFormatterLocales } from 'utils/formatNumbers'
import { CancelledTransactionTitleTable, getActivityTitle, OrderTextTable } from '../constants' import { CancelledTransactionTitleTable, getActivityTitle, OrderTextTable } from '../constants'
import { Activity, ActivityMap } from './types' import { Activity, ActivityMap } from './types'
@ -37,11 +38,16 @@ function buildCurrencyDescriptor(
amtA: string, amtA: string,
currencyB: Currency | undefined, currencyB: Currency | undefined,
amtB: string, amtB: string,
delimiter = t`for` delimiter = t`for`,
locale?: SupportedLocale
) { ) {
const formattedA = currencyA ? formatCurrencyAmount(CurrencyAmount.fromRawAmount(currencyA, amtA)) : t`Unknown` const formattedA = currencyA
? formatCurrencyAmount({ amount: CurrencyAmount.fromRawAmount(currencyA, amtA), locale })
: t`Unknown`
const symbolA = currencyA?.symbol ?? '' const symbolA = currencyA?.symbol ?? ''
const formattedB = currencyB ? formatCurrencyAmount(CurrencyAmount.fromRawAmount(currencyB, amtB)) : t`Unknown` const formattedB = currencyB
? formatCurrencyAmount({ amount: CurrencyAmount.fromRawAmount(currencyB, amtB), locale })
: t`Unknown`
const symbolB = currencyB?.symbol ?? '' const symbolB = currencyB?.symbol ?? ''
return [formattedA, symbolA, delimiter, formattedB, symbolB].filter(Boolean).join(' ') return [formattedA, symbolA, delimiter, formattedB, symbolB].filter(Boolean).join(' ')
} }
@ -49,7 +55,8 @@ function buildCurrencyDescriptor(
function parseSwap( function parseSwap(
swap: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo, swap: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo,
chainId: ChainId, chainId: ChainId,
tokens: ChainTokenMap tokens: ChainTokenMap,
locale?: SupportedLocale
): Partial<Activity> { ): Partial<Activity> {
const tokenIn = getCurrency(swap.inputCurrencyId, chainId, tokens) const tokenIn = getCurrency(swap.inputCurrencyId, chainId, tokens)
const tokenOut = getCurrency(swap.outputCurrencyId, chainId, tokens) const tokenOut = getCurrency(swap.outputCurrencyId, chainId, tokens)
@ -59,7 +66,7 @@ function parseSwap(
: [swap.expectedInputCurrencyAmountRaw, swap.outputCurrencyAmountRaw] : [swap.expectedInputCurrencyAmountRaw, swap.outputCurrencyAmountRaw]
return { return {
descriptor: buildCurrencyDescriptor(tokenIn, inputRaw, tokenOut, outputRaw), descriptor: buildCurrencyDescriptor(tokenIn, inputRaw, tokenOut, outputRaw, undefined, locale),
currencies: [tokenIn, tokenOut], currencies: [tokenIn, tokenOut],
prefixIconSrc: swap.isUniswapXOrder ? UniswapXBolt : undefined, prefixIconSrc: swap.isUniswapXOrder ? UniswapXBolt : undefined,
} }
@ -149,7 +156,8 @@ export function getTransactionStatus(details: TransactionDetails): TransactionSt
export function transactionToActivity( export function transactionToActivity(
details: TransactionDetails, details: TransactionDetails,
chainId: ChainId, chainId: ChainId,
tokens: ChainTokenMap tokens: ChainTokenMap,
locale?: SupportedLocale
): Activity | undefined { ): Activity | undefined {
try { try {
const status = getTransactionStatus(details) const status = getTransactionStatus(details)
@ -168,7 +176,7 @@ export function transactionToActivity(
let additionalFields: Partial<Activity> = {} let additionalFields: Partial<Activity> = {}
const info = details.info const info = details.info
if (info.type === TransactionType.SWAP) { if (info.type === TransactionType.SWAP) {
additionalFields = parseSwap(info, chainId, tokens) additionalFields = parseSwap(info, chainId, tokens, locale)
} else if (info.type === TransactionType.APPROVAL) { } else if (info.type === TransactionType.APPROVAL) {
additionalFields = parseApproval(info, chainId, tokens, status) additionalFields = parseApproval(info, chainId, tokens, status)
} else if (info.type === TransactionType.WRAP) { } else if (info.type === TransactionType.WRAP) {
@ -199,7 +207,11 @@ export function transactionToActivity(
} }
} }
export function signatureToActivity(signature: SignatureDetails, tokens: ChainTokenMap): Activity | undefined { export function signatureToActivity(
signature: SignatureDetails,
tokens: ChainTokenMap,
locale?: SupportedLocale
): Activity | undefined {
switch (signature.type) { switch (signature.type) {
case SignatureType.SIGN_UNISWAPX_ORDER: { case SignatureType.SIGN_UNISWAPX_ORDER: {
// Only returns Activity items for orders that don't have an on-chain counterpart // Only returns Activity items for orders that don't have an on-chain counterpart
@ -217,7 +229,7 @@ export function signatureToActivity(signature: SignatureDetails, tokens: ChainTo
from: signature.offerer, from: signature.offerer,
statusMessage, statusMessage,
prefixIconSrc: UniswapXBolt, prefixIconSrc: UniswapXBolt,
...parseSwap(signature.swapInfo, signature.chainId, tokens), ...parseSwap(signature.swapInfo, signature.chainId, tokens, locale),
} }
} }
default: default:
@ -229,23 +241,24 @@ export function useLocalActivities(account: string): ActivityMap {
const allTransactions = useMultichainTransactions() const allTransactions = useMultichainTransactions()
const allSignatures = useAllSignatures() const allSignatures = useAllSignatures()
const tokens = useAllTokensMultichain() const tokens = useAllTokensMultichain()
const { formatterLocale } = useFormatterLocales()
return useMemo(() => { return useMemo(() => {
const activityMap: ActivityMap = {} const activityMap: ActivityMap = {}
for (const [transaction, chainId] of allTransactions) { for (const [transaction, chainId] of allTransactions) {
if (transaction.from !== account) continue if (transaction.from !== account) continue
const activity = transactionToActivity(transaction, chainId, tokens) const activity = transactionToActivity(transaction, chainId, tokens, formatterLocale)
if (activity) activityMap[transaction.hash] = activity if (activity) activityMap[transaction.hash] = activity
} }
for (const signature of Object.values(allSignatures)) { for (const signature of Object.values(allSignatures)) {
if (signature.offerer !== account) continue if (signature.offerer !== account) continue
const activity = signatureToActivity(signature, tokens) const activity = signatureToActivity(signature, tokens, formatterLocale)
if (activity) activityMap[signature.id] = activity if (activity) activityMap[signature.id] = activity
} }
return activityMap return activityMap
}, [account, allSignatures, allTransactions, tokens]) }, [account, allSignatures, allTransactions, formatterLocale, tokens])
} }

@ -16,7 +16,7 @@ import { forwardRef, ReactNode, useCallback, useEffect, useState } from 'react'
import { Lock } from 'react-feather' import { Lock } from 'react-feather'
import styled, { useTheme } from 'styled-components' import styled, { useTheme } from 'styled-components'
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles' import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg' import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import { useCurrencyBalance } from '../../state/connection/hooks' import { useCurrencyBalance } from '../../state/connection/hooks'
@ -277,6 +277,7 @@ const SwapCurrencyInputPanel = forwardRef<HTMLInputElement, SwapCurrencyInputPan
const { account, chainId } = useWeb3React() const { account, chainId } = useWeb3React()
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined) const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
const theme = useTheme() const theme = useTheme()
const { formatCurrencyAmount } = useFormatter()
const handleDismissSearch = useCallback(() => { const handleDismissSearch = useCallback(() => {
setModalOpen(false) setModalOpen(false)
@ -396,7 +397,13 @@ const SwapCurrencyInputPanel = forwardRef<HTMLInputElement, SwapCurrencyInputPan
renderBalance ? ( renderBalance ? (
renderBalance(selectedCurrencyBalance) renderBalance(selectedCurrencyBalance)
) : ( ) : (
<Trans>Balance: {formatCurrencyAmount(selectedCurrencyBalance, NumberType.TokenNonTx)}</Trans> <Trans>
Balance:{' '}
{formatCurrencyAmount({
amount: selectedCurrencyBalance,
type: NumberType.TokenNonTx,
})}
</Trans>
) )
) : null} ) : null}
</ThemedText.DeprecatedBody> </ThemedText.DeprecatedBody>

@ -8,7 +8,7 @@ import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance' import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
import styled, { useTheme } from 'styled-components' import styled, { useTheme } from 'styled-components'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
const BalancesCard = styled.div` const BalancesCard = styled.div`
background-color: ${({ theme }) => theme.surface1}; background-color: ${({ theme }) => theme.surface1};
@ -67,8 +67,15 @@ export default function BalanceSummary({ token }: { token: Currency }) {
const theme = useTheme() const theme = useTheme()
const { label, color } = getChainInfo(asSupportedChain(chainId) ?? ChainId.MAINNET) const { label, color } = getChainInfo(asSupportedChain(chainId) ?? ChainId.MAINNET)
const balance = useCurrencyBalance(account, token) const balance = useCurrencyBalance(account, token)
const formattedBalance = formatCurrencyAmount(balance, NumberType.TokenNonTx) const { formatCurrencyAmount } = useFormatter()
const formattedUsdValue = formatCurrencyAmount(useStablecoinValue(balance), NumberType.FiatTokenStats) const formattedBalance = formatCurrencyAmount({
amount: balance,
type: NumberType.TokenNonTx,
})
const formattedUsdValue = formatCurrencyAmount({
amount: useStablecoinValue(balance),
type: NumberType.FiatTokenStats,
})
if (!account || !balance) { if (!account || !balance) {
return null return null

@ -7,7 +7,7 @@ import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance' import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
import styled from 'styled-components' import styled from 'styled-components'
import { StyledInternalLink } from 'theme' import { StyledInternalLink } from 'theme'
import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
const Wrapper = styled.div` const Wrapper = styled.div`
align-content: center; align-content: center;
@ -84,8 +84,15 @@ const SwapButton = styled(StyledInternalLink)`
export default function MobileBalanceSummaryFooter({ token }: { token: Currency }) { export default function MobileBalanceSummaryFooter({ token }: { token: Currency }) {
const { account } = useWeb3React() const { account } = useWeb3React()
const balance = useCurrencyBalance(account, token) const balance = useCurrencyBalance(account, token)
const formattedBalance = formatCurrencyAmount(balance, NumberType.TokenNonTx) const { formatCurrencyAmount } = useFormatter()
const formattedUsdValue = formatCurrencyAmount(useStablecoinValue(balance), NumberType.FiatTokenStats) const formattedBalance = formatCurrencyAmount({
amount: balance,
type: NumberType.TokenNonTx,
})
const formattedUsdValue = formatCurrencyAmount({
amount: useStablecoinValue(balance),
type: NumberType.FiatTokenStats,
})
const chain = CHAIN_ID_TO_BACKEND_NAME[token.chainId].toLowerCase() const chain = CHAIN_ID_TO_BACKEND_NAME[token.chainId].toLowerCase()
return ( return (

@ -9,7 +9,7 @@ import { ZERO_PERCENT } from 'constants/misc'
import useNativeCurrency from 'lib/hooks/useNativeCurrency' import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { ClassicTrade, InterfaceTrade } from 'state/routing/types' import { ClassicTrade, InterfaceTrade } from 'state/routing/types'
import { getTransactionCount, isClassicTrade } from 'state/routing/utils' import { getTransactionCount, isClassicTrade } from 'state/routing/utils'
import { formatCurrencyAmount, formatPriceImpact, NumberType, useFormatter } from 'utils/formatNumbers' import { formatPriceImpact, NumberType, useFormatter } from 'utils/formatNumbers'
import { ExternalLink, Separator, ThemedText } from '../../theme' import { ExternalLink, Separator, ThemedText } from '../../theme'
import Column from '../Column' import Column from '../Column'
@ -47,7 +47,7 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const nativeCurrency = useNativeCurrency(chainId) const nativeCurrency = useNativeCurrency(chainId)
const txCount = getTransactionCount(trade) const txCount = getTransactionCount(trade)
const { formatNumber } = useFormatter() const { formatNumber, formatCurrencyAmount } = useFormatter()
const supportsGasEstimate = chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) const supportsGasEstimate = chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId)
@ -117,9 +117,10 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
<TextWithLoadingPlaceholder syncing={syncing} width={70}> <TextWithLoadingPlaceholder syncing={syncing} width={70}>
<ThemedText.BodySmall> <ThemedText.BodySmall>
{trade.tradeType === TradeType.EXACT_INPUT {trade.tradeType === TradeType.EXACT_INPUT
? `${formatCurrencyAmount(trade.minimumAmountOut(allowedSlippage), NumberType.SwapTradeAmount)} ${ ? `${formatCurrencyAmount({
trade.outputAmount.currency.symbol amount: trade.minimumAmountOut(allowedSlippage),
}` type: NumberType.SwapTradeAmount,
})} ${trade.outputAmount.currency.symbol}`
: `${trade.maximumAmountIn(allowedSlippage).toSignificant(6)} ${trade.inputAmount.currency.symbol}`} : `${trade.maximumAmountIn(allowedSlippage).toSignificant(6)} ${trade.inputAmount.currency.symbol}`}
</ThemedText.BodySmall> </ThemedText.BodySmall>
</TextWithLoadingPlaceholder> </TextWithLoadingPlaceholder>
@ -141,9 +142,10 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
</RowFixed> </RowFixed>
<TextWithLoadingPlaceholder syncing={syncing} width={65}> <TextWithLoadingPlaceholder syncing={syncing} width={65}>
<ThemedText.BodySmall> <ThemedText.BodySmall>
{`${formatCurrencyAmount(trade.postTaxOutputAmount, NumberType.SwapTradeAmount)} ${ {`${formatCurrencyAmount({
trade.outputAmount.currency.symbol amount: trade.postTaxOutputAmount,
}`} type: NumberType.SwapTradeAmount,
})} ${trade.outputAmount.currency.symbol}`}
</ThemedText.BodySmall> </ThemedText.BodySmall>
</TextWithLoadingPlaceholder> </TextWithLoadingPlaceholder>
</RowBetween> </RowBetween>

@ -29,7 +29,7 @@ import styled from 'styled-components'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import invariant from 'tiny-invariant' import invariant from 'tiny-invariant'
import { isL2ChainId } from 'utils/chains' import { isL2ChainId } from 'utils/chains'
import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
import { formatSwapPriceUpdatedEventProperties } from 'utils/loggingFormatters' import { formatSwapPriceUpdatedEventProperties } from 'utils/loggingFormatters'
import { didUserReject } from 'utils/swapErrorToUserReadableMessage' import { didUserReject } from 'utils/swapErrorToUserReadableMessage'
import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer' import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer'
@ -84,6 +84,7 @@ function useConfirmModalState({
const [confirmModalState, setConfirmModalState] = useState<ConfirmModalState>(ConfirmModalState.REVIEWING) const [confirmModalState, setConfirmModalState] = useState<ConfirmModalState>(ConfirmModalState.REVIEWING)
const [approvalError, setApprovalError] = useState<PendingModalError>() const [approvalError, setApprovalError] = useState<PendingModalError>()
const [pendingModalSteps, setPendingModalSteps] = useState<PendingConfirmModalState[]>([]) const [pendingModalSteps, setPendingModalSteps] = useState<PendingConfirmModalState[]>([])
const { formatCurrencyAmount } = useFormatter()
// This is a function instead of a memoized value because we do _not_ want it to update as the allowance changes. // This is a function instead of a memoized value because we do _not_ want it to update as the allowance changes.
// For example, if the user needs to complete 3 steps initially, we should always show 3 step indicators // For example, if the user needs to complete 3 steps initially, we should always show 3 step indicators
@ -123,7 +124,10 @@ function useConfirmModalState({
const { execute: onWrap } = useWrapCallback( const { execute: onWrap } = useWrapCallback(
nativeCurrency, nativeCurrency,
trade.inputAmount.currency, trade.inputAmount.currency,
formatCurrencyAmount(trade.inputAmount, NumberType.SwapTradeAmount) formatCurrencyAmount({
amount: trade.inputAmount,
type: NumberType.SwapTradeAmount,
})
) )
const wrapConfirmed = useIsTransactionConfirmed(wrapTxHash) const wrapConfirmed = useIsTransactionConfirmed(wrapTxHash)
const prevWrapConfirmed = usePrevious(wrapConfirmed) const prevWrapConfirmed = usePrevious(wrapConfirmed)

@ -18,12 +18,12 @@ describe('SwapModalHeader.tsx', () => {
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
expect(screen.getByText(/Output is estimated. You will receive at least /i)).toBeInTheDocument() expect(screen.getByText(/Output is estimated. You will receive at least /i)).toBeInTheDocument()
expect(screen.getByTestId('INPUT-amount')).toHaveTextContent( expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(
`${formatCurrencyAmount(TEST_TRADE_EXACT_INPUT.inputAmount, NumberType.TokenTx)} ${ `${formatCurrencyAmount({ amount: TEST_TRADE_EXACT_INPUT.inputAmount, type: NumberType.TokenTx })} ${
TEST_TRADE_EXACT_INPUT.inputAmount.currency.symbol ?? '' TEST_TRADE_EXACT_INPUT.inputAmount.currency.symbol ?? ''
}` }`
) )
expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent( expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(
`${formatCurrencyAmount(TEST_TRADE_EXACT_INPUT.outputAmount, NumberType.TokenTx)} ${ `${formatCurrencyAmount({ amount: TEST_TRADE_EXACT_INPUT.outputAmount, type: NumberType.TokenTx })} ${
TEST_TRADE_EXACT_INPUT.outputAmount.currency.symbol ?? '' TEST_TRADE_EXACT_INPUT.outputAmount.currency.symbol ?? ''
}` }`
) )
@ -40,10 +40,12 @@ describe('SwapModalHeader.tsx', () => {
expect(asFragment()).toMatchSnapshot() expect(asFragment()).toMatchSnapshot()
expect(screen.getByText(/Output is estimated. You will receive at least /i)).toBeInTheDocument() expect(screen.getByText(/Output is estimated. You will receive at least /i)).toBeInTheDocument()
expect(screen.getByTestId('INPUT-amount')).toHaveTextContent( expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(
`${formatCurrencyAmount(TEST_DUTCH_TRADE_ETH_INPUT.inputAmount, NumberType.TokenTx)} ${ETH_MAINNET.symbol}` `${formatCurrencyAmount({ amount: TEST_DUTCH_TRADE_ETH_INPUT.inputAmount, type: NumberType.TokenTx })} ${
ETH_MAINNET.symbol
}`
) )
expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent( expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(
`${formatCurrencyAmount(TEST_DUTCH_TRADE_ETH_INPUT.outputAmount, NumberType.TokenTx)} ${ `${formatCurrencyAmount({ amount: TEST_DUTCH_TRADE_ETH_INPUT.outputAmount, type: NumberType.TokenTx })} ${
TEST_DUTCH_TRADE_ETH_INPUT.outputAmount.currency.symbol ?? '' TEST_DUTCH_TRADE_ETH_INPUT.outputAmount.currency.symbol ?? ''
}` }`
) )
@ -57,12 +59,12 @@ describe('SwapModalHeader.tsx', () => {
expect(screen.getByText(/Input is estimated. You will sell at most/i)).toBeInTheDocument() expect(screen.getByText(/Input is estimated. You will sell at most/i)).toBeInTheDocument()
expect(screen.getByTestId('INPUT-amount')).toHaveTextContent( expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(
`${formatCurrencyAmount(TEST_TRADE_EXACT_OUTPUT.inputAmount, NumberType.TokenTx)} ${ `${formatCurrencyAmount({ amount: TEST_TRADE_EXACT_OUTPUT.inputAmount, type: NumberType.TokenTx })} ${
TEST_TRADE_EXACT_OUTPUT.inputAmount.currency.symbol ?? '' TEST_TRADE_EXACT_OUTPUT.inputAmount.currency.symbol ?? ''
}` }`
) )
expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent( expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(
`${formatCurrencyAmount(TEST_TRADE_EXACT_OUTPUT.outputAmount, NumberType.TokenTx)} ${ `${formatCurrencyAmount({ amount: TEST_TRADE_EXACT_OUTPUT.outputAmount, type: NumberType.TokenTx })} ${
TEST_TRADE_EXACT_OUTPUT.outputAmount.currency.symbol ?? '' TEST_TRADE_EXACT_OUTPUT.outputAmount.currency.symbol ?? ''
}` }`
) )

@ -25,7 +25,7 @@ import { ArrowLeft } from 'react-feather'
import styled from 'styled-components' import styled from 'styled-components'
import { BREAKPOINTS, ThemedText } from 'theme' import { BREAKPOINTS, ThemedText } from 'theme'
import { Z_INDEX } from 'theme/zIndex' import { Z_INDEX } from 'theme/zIndex'
import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
import { shallow } from 'zustand/shallow' import { shallow } from 'zustand/shallow'
import { ListModal } from './Modal/ListModal' import { ListModal } from './Modal/ListModal'
@ -186,6 +186,7 @@ export const ListPage = () => {
const { provider, chainId } = useWeb3React() const { provider, chainId } = useWeb3React()
const isMobile = useIsMobile() const isMobile = useIsMobile()
const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING }) const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING })
const { formatCurrencyAmount } = useFormatter()
const { setGlobalMarketplaces, sellAssets, issues } = useSellAsset( const { setGlobalMarketplaces, sellAssets, issues } = useSellAsset(
({ setGlobalMarketplaces, sellAssets, issues }) => ({ ({ setGlobalMarketplaces, sellAssets, issues }) => ({
setGlobalMarketplaces, setGlobalMarketplaces,
@ -208,7 +209,10 @@ export const ListPage = () => {
const nativeCurrency = useNativeCurrency(chainId) const nativeCurrency = useNativeCurrency(chainId)
const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency) const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency)
const usdcValue = useStablecoinValue(parsedAmount) const usdcValue = useStablecoinValue(parsedAmount)
const usdcAmount = formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice) const usdcAmount = formatCurrencyAmount({
amount: usdcValue,
type: NumberType.FiatTokenPrice,
})
const [showListModal, toggleShowListModal] = useReducer((s) => !s, false) const [showListModal, toggleShowListModal] = useReducer((s) => !s, false)
const [selectedMarkets, setSelectedMarkets] = useState([ListingMarkets[0]]) // default marketplace: x2y2 const [selectedMarkets, setSelectedMarkets] = useState([ListingMarkets[0]]) // default marketplace: x2y2
const signer = provider?.getSigner() const signer = provider?.getSigner()

@ -15,7 +15,7 @@ import { X } from 'react-feather'
import styled from 'styled-components' import styled from 'styled-components'
import { BREAKPOINTS, ThemedText } from 'theme' import { BREAKPOINTS, ThemedText } from 'theme'
import { Z_INDEX } from 'theme/zIndex' import { Z_INDEX } from 'theme/zIndex'
import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
import { shallow } from 'zustand/shallow' import { shallow } from 'zustand/shallow'
import { TitleRow } from '../shared' import { TitleRow } from '../shared'
@ -48,6 +48,7 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
const { provider, chainId } = useWeb3React() const { provider, chainId } = useWeb3React()
const signer = provider?.getSigner() const signer = provider?.getSigner()
const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING }) const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING })
const { formatCurrencyAmount } = useFormatter()
const sellAssets = useSellAsset((state) => state.sellAssets) const sellAssets = useSellAsset((state) => state.sellAssets)
const { setListingStatusAndCallback, setLooksRareNonce, getLooksRareNonce, collectionsRequiringApproval, listings } = const { setListingStatusAndCallback, setLooksRareNonce, getLooksRareNonce, collectionsRequiringApproval, listings } =
useNFTList( useNFTList(
@ -75,7 +76,10 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
const nativeCurrency = useNativeCurrency(chainId) const nativeCurrency = useNativeCurrency(chainId)
const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency) const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency)
const usdcValue = useStablecoinValue(parsedAmount) const usdcValue = useStablecoinValue(parsedAmount)
const usdcAmount = formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice) const usdcAmount = formatCurrencyAmount({
amount: usdcValue,
type: NumberType.FiatTokenPrice,
})
const allCollectionsApproved = useMemo( const allCollectionsApproved = useMemo(
() => collectionsRequiringApproval.every((collection) => collection.status === ListingStatus.APPROVED), () => collectionsRequiringApproval.every((collection) => collection.status === ListingStatus.APPROVED),

@ -13,7 +13,7 @@ import { useMemo } from 'react'
import { Twitter, X } from 'react-feather' import { Twitter, X } from 'react-feather'
import styled, { css, useTheme } from 'styled-components' import styled, { css, useTheme } from 'styled-components'
import { BREAKPOINTS, ThemedText } from 'theme' import { BREAKPOINTS, ThemedText } from 'theme'
import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
import { TitleRow } from '../shared' import { TitleRow } from '../shared'
@ -79,6 +79,7 @@ export const SuccessScreen = ({ overlayClick }: { overlayClick: () => void }) =>
const sellAssets = useSellAsset((state) => state.sellAssets) const sellAssets = useSellAsset((state) => state.sellAssets)
const { chainId } = useWeb3React() const { chainId } = useWeb3React()
const nativeCurrency = useNativeCurrency(chainId) const nativeCurrency = useNativeCurrency(chainId)
const { formatCurrencyAmount } = useFormatter()
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets]) const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency) const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency)
@ -110,7 +111,10 @@ export const SuccessScreen = ({ overlayClick }: { overlayClick: () => void }) =>
<ThemedText.SubHeader>{formatEth(totalEthListingValue)} ETH</ThemedText.SubHeader> <ThemedText.SubHeader>{formatEth(totalEthListingValue)} ETH</ThemedText.SubHeader>
{usdcValue && ( {usdcValue && (
<ThemedText.BodySmall lineHeight="20px" color="neutral2"> <ThemedText.BodySmall lineHeight="20px" color="neutral2">
{formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice)} {formatCurrencyAmount({
amount: usdcValue,
type: NumberType.FiatTokenPrice,
})}
</ThemedText.BodySmall> </ThemedText.BodySmall>
)} )}
</ProceedsColumn> </ProceedsColumn>

@ -58,7 +58,7 @@ import styled, { useTheme } from 'styled-components'
import { LinkStyledButton, ThemedText } from 'theme' import { LinkStyledButton, ThemedText } from 'theme'
import { maybeLogFirstSwapAction } from 'tracing/swapFlowLoggers' import { maybeLogFirstSwapAction } from 'tracing/swapFlowLoggers'
import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact' import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact'
import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { NumberType, useFormatter } from 'utils/formatNumbers'
import { maxAmountSpend } from 'utils/maxAmountSpend' import { maxAmountSpend } from 'utils/maxAmountSpend'
import { computeRealizedPriceImpact, warningSeverity } from 'utils/prices' import { computeRealizedPriceImpact, warningSeverity } from 'utils/prices'
import { didUserReject } from 'utils/swapErrorToUserReadableMessage' import { didUserReject } from 'utils/swapErrorToUserReadableMessage'
@ -382,14 +382,19 @@ export function Swap({
swapResult: undefined, swapResult: undefined,
}) })
const { formatCurrencyAmount } = useFormatter()
const formattedAmounts = useMemo( const formattedAmounts = useMemo(
() => ({ () => ({
[independentField]: typedValue, [independentField]: typedValue,
[dependentField]: showWrap [dependentField]: showWrap
? parsedAmounts[independentField]?.toExact() ?? '' ? parsedAmounts[independentField]?.toExact() ?? ''
: formatCurrencyAmount(parsedAmounts[dependentField], NumberType.SwapTradeAmount, ''), : formatCurrencyAmount({
amount: parsedAmounts[dependentField],
type: NumberType.SwapTradeAmount,
placeholder: '',
}),
}), }),
[dependentField, independentField, parsedAmounts, showWrap, typedValue] [dependentField, formatCurrencyAmount, independentField, parsedAmounts, showWrap, typedValue]
) )
const userHasSpecifiedInputOutput = Boolean( const userHasSpecifiedInputOutput = Boolean(

@ -415,12 +415,28 @@ export function formatNumber({
return (prefix ?? '') + new Intl.NumberFormat(locale, formatterOptions).format(hardCodedInputValue) return (prefix ?? '') + new Intl.NumberFormat(locale, formatterOptions).format(hardCodedInputValue)
} }
export function formatCurrencyAmount( interface FormatCurrencyAmountOptions {
amount: Nullish<CurrencyAmount<Currency>>, amount: Nullish<CurrencyAmount<Currency>>
type: NumberType = NumberType.TokenNonTx, type?: NumberType
placeholder?: string placeholder?: string
): string { locale?: SupportedLocale
return formatNumber({ input: amount ? parseFloat(amount.toSignificant()) : undefined, type, placeholder }) localCurrency?: SupportedLocalCurrency
}
export function formatCurrencyAmount({
amount,
type = NumberType.TokenNonTx,
placeholder,
locale = DEFAULT_LOCALE,
localCurrency = DEFAULT_LOCAL_CURRENCY,
}: FormatCurrencyAmountOptions): string {
return formatNumber({
input: amount ? parseFloat(amount.toSignificant()) : undefined,
type,
placeholder,
locale,
localCurrency,
})
} }
export function formatPriceImpact(priceImpact: Percent | undefined): string { export function formatPriceImpact(priceImpact: Percent | undefined): string {
@ -523,14 +539,14 @@ export const formatTransactionAmount = (num: number | undefined | null, maxDigit
const MAX_AMOUNT_STR_LENGTH = 9 const MAX_AMOUNT_STR_LENGTH = 9
export function formatReviewSwapCurrencyAmount(amount: CurrencyAmount<Currency>): string { export function formatReviewSwapCurrencyAmount(amount: CurrencyAmount<Currency>): string {
let formattedAmount = formatCurrencyAmount(amount, NumberType.TokenTx) let formattedAmount = formatCurrencyAmount({ amount, type: NumberType.TokenTx })
if (formattedAmount.length > MAX_AMOUNT_STR_LENGTH) { if (formattedAmount.length > MAX_AMOUNT_STR_LENGTH) {
formattedAmount = formatCurrencyAmount(amount, NumberType.SwapTradeAmount) formattedAmount = formatCurrencyAmount({ amount, type: NumberType.SwapTradeAmount })
} }
return formattedAmount return formattedAmount
} }
function useFormatterLocales(): { export function useFormatterLocales(): {
formatterLocale: SupportedLocale formatterLocale: SupportedLocale
formatterLocalCurrency: SupportedLocalCurrency formatterLocalCurrency: SupportedLocalCurrency
} { } {
@ -561,10 +577,17 @@ export function useFormatter() {
[formatterLocalCurrency, formatterLocale] [formatterLocalCurrency, formatterLocale]
) )
const formatCurrencyAmountWithLocales = useCallback(
(options: Omit<FormatCurrencyAmountOptions, 'locale' | 'localCurrency'>) =>
formatCurrencyAmount({ ...options, locale: formatterLocale, localCurrency: formatterLocalCurrency }),
[formatterLocalCurrency, formatterLocale]
)
return useMemo( return useMemo(
() => ({ () => ({
formatNumber: formatNumberWithLocales, formatNumber: formatNumberWithLocales,
formatCurrencyAmount: formatCurrencyAmountWithLocales,
}), }),
[formatNumberWithLocales] [formatCurrencyAmountWithLocales, formatNumberWithLocales]
) )
} }