From b1c99d4b3770af0c474142b6c48a9012d147a24d Mon Sep 17 00:00:00 2001 From: Jack Short Date: Wed, 9 Aug 2023 14:01:37 -0400 Subject: [PATCH] chore: moving coned formatting to interface (#7114) --- craco.config.cjs | 3 +- .../AccountDrawer/AuthenticatedHeader.tsx | 2 +- .../MiniPortfolio/Activity/parseLocal.ts | 2 +- .../MiniPortfolio/Activity/parseRemote.tsx | 2 +- .../MiniPortfolio/Pools/index.tsx | 2 +- .../MiniPortfolio/Tokens/index.tsx | 2 +- .../CurrencyInputPanel/FiatValue.tsx | 2 +- .../SwapCurrencyInputPanel.tsx | 2 +- src/components/NavBar/SuggestionRow.tsx | 2 +- .../Tokens/TokenDetails/BalanceSummary.tsx | 2 +- .../MobileBalanceSummaryFooter.tsx | 2 +- .../Tokens/TokenDetails/PriceChart.tsx | 2 +- .../Tokens/TokenDetails/StatsSection.tsx | 2 +- src/components/Tokens/TokenTable/TokenRow.tsx | 2 +- src/components/swap/AdvancedSwapDetails.tsx | 2 +- src/components/swap/ConfirmSwapModal.tsx | 2 +- src/components/swap/GasBreakdownTooltip.tsx | 2 +- src/components/swap/GasEstimateTooltip.tsx | 2 +- src/components/swap/PriceImpactModal.tsx | 2 +- src/components/swap/SwapModalFooter.tsx | 2 +- src/components/swap/SwapModalHeader.test.tsx | 2 +- src/components/swap/SwapModalHeaderAmount.tsx | 2 +- src/components/swap/TradePrice.tsx | 2 +- src/nft/components/explore/CarouselCard.tsx | 2 +- src/nft/components/profile/list/ListPage.tsx | 2 +- .../profile/list/Modal/ListModal.tsx | 2 +- .../profile/list/Modal/SuccessScreen.tsx | 2 +- src/pages/Pool/PositionPage.tsx | 2 +- src/pages/Swap/index.tsx | 2 +- src/utils/formatNumbers.test.ts | 190 ++++++++- src/utils/formatNumbers.ts | 378 +++++++++++++++++- src/utils/formatTickPrice.ts | 2 +- 32 files changed, 594 insertions(+), 35 deletions(-) diff --git a/craco.config.cjs b/craco.config.cjs index d87d328b5a..824149dedb 100644 --- a/craco.config.cjs +++ b/craco.config.cjs @@ -57,9 +57,8 @@ module.exports = { '\\.(t|j)sx?$': '@swc/jest', }, // Use @uniswap/conedison's build directly, as jest does not support its exports. - transformIgnorePatterns: ['@uniswap/conedison/format', '@uniswap/conedison/provider'], + transformIgnorePatterns: ['@uniswap/conedison/provider'], moduleNameMapper: { - '@uniswap/conedison/format': '@uniswap/conedison/dist/format', '@uniswap/conedison/provider': '@uniswap/conedison/dist/provider', }, }) diff --git a/src/components/AccountDrawer/AuthenticatedHeader.tsx b/src/components/AccountDrawer/AuthenticatedHeader.tsx index 539ea5b81a..1b5e14601c 100644 --- a/src/components/AccountDrawer/AuthenticatedHeader.tsx +++ b/src/components/AccountDrawer/AuthenticatedHeader.tsx @@ -1,6 +1,5 @@ import { Trans } from '@lingui/macro' import { BrowserEvent, InterfaceElementName, InterfaceEventName, SharedEventName } from '@uniswap/analytics-events' -import { formatNumber, NumberType } from '@uniswap/conedison/format' import { CurrencyAmount, Token } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import { sendAnalyticsEvent, TraceEvent } from 'analytics' @@ -24,6 +23,7 @@ import { updateSelectedWallet } from 'state/user/reducer' import styled, { useTheme } from 'styled-components' import { CopyHelper, ExternalLink, ThemedText } from 'theme' import { shortenAddress } from 'utils' +import { formatNumber, NumberType } from 'utils/formatNumbers' import { useCloseModal, useFiatOnrampAvailability, useOpenModal, useToggleModal } from '../../state/application/hooks' import { ApplicationModal } from '../../state/application/reducer' diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts b/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts index 850ab96304..9d0ea1e253 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts +++ b/src/components/AccountDrawer/MiniPortfolio/Activity/parseLocal.ts @@ -1,6 +1,5 @@ import { BigNumber } from '@ethersproject/bignumber' import { t } from '@lingui/macro' -import { formatCurrencyAmount } from '@uniswap/conedison/format' import { ChainId, Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { nativeOnChain } from '@uniswap/smart-order-router' import UniswapXBolt from 'assets/svg/bolt.svg' @@ -24,6 +23,7 @@ import { TransactionType, WrapTransactionInfo, } from 'state/transactions/types' +import { formatCurrencyAmount } from 'utils/formatNumbers' import { CancelledTransactionTitleTable, getActivityTitle, OrderTextTable } from '../constants' import { Activity, ActivityMap } from './types' diff --git a/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx b/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx index af74fbb46e..9f33dbedab 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx +++ b/src/components/AccountDrawer/MiniPortfolio/Activity/parseRemote.tsx @@ -1,5 +1,4 @@ import { t } from '@lingui/macro' -import { formatFiatPrice, formatNumberOrString, NumberType } from '@uniswap/conedison/format' import { ChainId, NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, UNI_ADDRESSES } from '@uniswap/sdk-core' import UniswapXBolt from 'assets/svg/bolt.svg' import moonpayLogoSrc from 'assets/svg/moonpay.svg' @@ -22,6 +21,7 @@ import { logSentryErrorForUnsupportedChain, supportedChainIdFromGQLChain } from import ms from 'ms' import { useEffect, useState } from 'react' import { isAddress } from 'utils' +import { formatFiatPrice, formatNumberOrString, NumberType } from 'utils/formatNumbers' import { MOONPAY_SENDER_ADDRESSES, OrderStatusTable, OrderTextTable } from '../constants' import { Activity } from './types' diff --git a/src/components/AccountDrawer/MiniPortfolio/Pools/index.tsx b/src/components/AccountDrawer/MiniPortfolio/Pools/index.tsx index a7ba5da4f0..7f4195dd2d 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Pools/index.tsx +++ b/src/components/AccountDrawer/MiniPortfolio/Pools/index.tsx @@ -1,6 +1,5 @@ import { t } from '@lingui/macro' import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events' -import { formatNumber, NumberType } from '@uniswap/conedison/format' import { Position } from '@uniswap/v3-sdk' import { useWeb3React } from '@web3-react/core' import { TraceEvent } from 'analytics' @@ -14,6 +13,7 @@ import { useCallback, useMemo, useReducer } from 'react' import { useNavigate } from 'react-router-dom' import styled from 'styled-components' import { ThemedText } from 'theme' +import { formatNumber, NumberType } from 'utils/formatNumbers' import { ExpandoRow } from '../ExpandoRow' import { PortfolioLogo } from '../PortfolioLogo' diff --git a/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx b/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx index 6b40b02e54..076e097ce3 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx +++ b/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx @@ -1,5 +1,4 @@ import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events' -import { formatNumber, NumberType } from '@uniswap/conedison/format' import { TraceEvent } from 'analytics' import { useCachedPortfolioBalancesQuery } from 'components/AccountDrawer/PrefetchBalancesWrapper' import Row from 'components/Row' @@ -12,6 +11,7 @@ import { useCallback, useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' import styled from 'styled-components' import { EllipsisStyle, ThemedText } from 'theme' +import { formatNumber, NumberType } from 'utils/formatNumbers' import { useToggleAccountDrawer } from '../..' import { PortfolioArrow } from '../../AuthenticatedHeader' diff --git a/src/components/CurrencyInputPanel/FiatValue.tsx b/src/components/CurrencyInputPanel/FiatValue.tsx index 3671ee4164..99c90f98b0 100644 --- a/src/components/CurrencyInputPanel/FiatValue.tsx +++ b/src/components/CurrencyInputPanel/FiatValue.tsx @@ -1,5 +1,4 @@ import { Trans } from '@lingui/macro' -import { formatNumber, formatPriceImpact, NumberType } from '@uniswap/conedison/format' import { Percent } from '@uniswap/sdk-core' import Row from 'components/Row' import { LoadingBubble } from 'components/Tokens/loading' @@ -7,6 +6,7 @@ import { MouseoverTooltip } from 'components/Tooltip' import { useMemo } from 'react' import styled from 'styled-components' import { ThemedText } from 'theme' +import { formatNumber, formatPriceImpact, NumberType } from 'utils/formatNumbers' import { warningSeverity } from 'utils/prices' const FiatLoadingBubble = styled(LoadingBubble)` diff --git a/src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx b/src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx index f66a00cdef..79074d810f 100644 --- a/src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx +++ b/src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx @@ -1,6 +1,5 @@ import { Trans } from '@lingui/macro' import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events' -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import { Pair } from '@uniswap/v2-sdk' import { useWeb3React } from '@web3-react/core' @@ -15,6 +14,7 @@ import { ReactNode, useCallback, useState } from 'react' import { Lock } from 'react-feather' import styled, { useTheme } from 'styled-components' import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles' +import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg' import { useCurrencyBalance } from '../../state/connection/hooks' diff --git a/src/components/NavBar/SuggestionRow.tsx b/src/components/NavBar/SuggestionRow.tsx index c3a188038a..c1db8cc83e 100644 --- a/src/components/NavBar/SuggestionRow.tsx +++ b/src/components/NavBar/SuggestionRow.tsx @@ -1,5 +1,4 @@ import { InterfaceEventName } from '@uniswap/analytics-events' -import { formatUSDPrice } from '@uniswap/conedison/format' import { sendAnalyticsEvent } from 'analytics' import clsx from 'clsx' import QueryTokenLogo from 'components/Logo/QueryTokenLogo' @@ -19,6 +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 { DeltaText, getDeltaArrow } from '../Tokens/TokenDetails/PriceChart' import { useAddRecentlySearchedAsset } from './RecentlySearchedAssets' diff --git a/src/components/Tokens/TokenDetails/BalanceSummary.tsx b/src/components/Tokens/TokenDetails/BalanceSummary.tsx index 5d1531563b..4a62f1cd86 100644 --- a/src/components/Tokens/TokenDetails/BalanceSummary.tsx +++ b/src/components/Tokens/TokenDetails/BalanceSummary.tsx @@ -1,5 +1,4 @@ import { Trans } from '@lingui/macro' -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' import { ChainId, Currency } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import CurrencyLogo from 'components/Logo/CurrencyLogo' @@ -9,6 +8,7 @@ import { useStablecoinValue } from 'hooks/useStablecoinPrice' import useCurrencyBalance from 'lib/hooks/useCurrencyBalance' import styled, { useTheme } from 'styled-components' import { ThemedText } from 'theme' +import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' const BalancesCard = styled.div` box-shadow: ${({ theme }) => theme.shallowShadow}; diff --git a/src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx b/src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx index ea5ec97c36..5d01cc981f 100644 --- a/src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx +++ b/src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx @@ -1,5 +1,4 @@ import { Trans } from '@lingui/macro' -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' import { Currency } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import { NATIVE_CHAIN_ID } from 'constants/tokens' @@ -8,6 +7,7 @@ import { useStablecoinValue } from 'hooks/useStablecoinPrice' import useCurrencyBalance from 'lib/hooks/useCurrencyBalance' import styled from 'styled-components' import { StyledInternalLink } from 'theme' +import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' const Wrapper = styled.div` align-content: center; diff --git a/src/components/Tokens/TokenDetails/PriceChart.tsx b/src/components/Tokens/TokenDetails/PriceChart.tsx index cf24624292..70d164a2bb 100644 --- a/src/components/Tokens/TokenDetails/PriceChart.tsx +++ b/src/components/Tokens/TokenDetails/PriceChart.tsx @@ -1,5 +1,4 @@ import { Trans } from '@lingui/macro' -import { formatUSDPrice } from '@uniswap/conedison/format' import { AxisBottom, TickFormatter } from '@visx/axis' import { localPoint } from '@visx/event' import { EventType } from '@visx/event/lib/types' @@ -24,6 +23,7 @@ import { monthYearDayFormatter, weekFormatter, } from 'utils/formatChartTimes' +import { formatUSDPrice } from 'utils/formatNumbers' const DATA_EMPTY = { value: 0, timestamp: 0 } diff --git a/src/components/Tokens/TokenDetails/StatsSection.tsx b/src/components/Tokens/TokenDetails/StatsSection.tsx index 370b6cb923..665f651151 100644 --- a/src/components/Tokens/TokenDetails/StatsSection.tsx +++ b/src/components/Tokens/TokenDetails/StatsSection.tsx @@ -1,5 +1,4 @@ import { Trans } from '@lingui/macro' -import { formatNumber, NumberType } from '@uniswap/conedison/format' import { ChainId } from '@uniswap/sdk-core' import { MouseoverTooltip } from 'components/Tooltip' import { getChainInfo } from 'constants/chainInfo' @@ -7,6 +6,7 @@ import { ReactNode } from 'react' import styled from 'styled-components' import { ExternalLink, ThemedText } from 'theme' import { textFadeIn } from 'theme/styles' +import { formatNumber, NumberType } from 'utils/formatNumbers' import { UNSUPPORTED_METADATA_CHAINS } from '../constants' import { TokenSortMethod } from '../state' diff --git a/src/components/Tokens/TokenTable/TokenRow.tsx b/src/components/Tokens/TokenTable/TokenRow.tsx index 66522e96ec..6f63979091 100644 --- a/src/components/Tokens/TokenTable/TokenRow.tsx +++ b/src/components/Tokens/TokenTable/TokenRow.tsx @@ -1,6 +1,5 @@ import { Trans } from '@lingui/macro' import { InterfaceEventName } from '@uniswap/analytics-events' -import { formatNumber, formatUSDPrice, NumberType } from '@uniswap/conedison/format' import { ParentSize } from '@visx/responsive' import { sendAnalyticsEvent } from 'analytics' import SparklineChart from 'components/Charts/SparklineChart' @@ -15,6 +14,7 @@ import { ArrowDown, ArrowUp, Info } from 'react-feather' import { Link, useParams } from 'react-router-dom' import styled, { css, useTheme } from 'styled-components' import { BREAKPOINTS, ClickableStyle } from 'theme' +import { formatNumber, formatUSDPrice, NumberType } from 'utils/formatNumbers' import { LARGE_MEDIA_BREAKPOINT, diff --git a/src/components/swap/AdvancedSwapDetails.tsx b/src/components/swap/AdvancedSwapDetails.tsx index 19aa6ad47d..d3fa196733 100644 --- a/src/components/swap/AdvancedSwapDetails.tsx +++ b/src/components/swap/AdvancedSwapDetails.tsx @@ -1,6 +1,5 @@ import { Plural, Trans } from '@lingui/macro' import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events' -import { formatCurrencyAmount, formatNumber, formatPriceImpact, NumberType } from '@uniswap/conedison/format' import { Percent, TradeType } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import { sendAnalyticsEvent } from 'analytics' @@ -9,6 +8,7 @@ import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import useNativeCurrency from 'lib/hooks/useNativeCurrency' import { InterfaceTrade } from 'state/routing/types' import { getTransactionCount, isClassicTrade } from 'state/routing/utils' +import { formatCurrencyAmount, formatNumber, formatPriceImpact, NumberType } from 'utils/formatNumbers' import { Separator, ThemedText } from '../../theme' import Column from '../Column' diff --git a/src/components/swap/ConfirmSwapModal.tsx b/src/components/swap/ConfirmSwapModal.tsx index 0a888b6ca6..dad6e8a69c 100644 --- a/src/components/swap/ConfirmSwapModal.tsx +++ b/src/components/swap/ConfirmSwapModal.tsx @@ -5,7 +5,6 @@ import { SwapEventName, SwapPriceUpdateUserResponse, } from '@uniswap/analytics-events' -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' import { Currency, Percent } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import { sendAnalyticsEvent, Trace, useTrace } from 'analytics' @@ -30,6 +29,7 @@ import styled from 'styled-components' import { ThemedText } from 'theme' import invariant from 'tiny-invariant' import { isL2ChainId } from 'utils/chains' +import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { formatSwapPriceUpdatedEventProperties } from 'utils/loggingFormatters' import { didUserReject } from 'utils/swapErrorToUserReadableMessage' import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer' diff --git a/src/components/swap/GasBreakdownTooltip.tsx b/src/components/swap/GasBreakdownTooltip.tsx index 4ff76be711..1934122de5 100644 --- a/src/components/swap/GasBreakdownTooltip.tsx +++ b/src/components/swap/GasBreakdownTooltip.tsx @@ -1,5 +1,4 @@ import { Trans } from '@lingui/macro' -import { formatNumber, NumberType } from '@uniswap/conedison/format' import { AutoColumn } from 'components/Column' import UniswapXRouterLabel, { UniswapXGradient } from 'components/RouterLabel/UniswapXRouterLabel' import Row from 'components/Row' @@ -8,6 +7,7 @@ import { InterfaceTrade } from 'state/routing/types' import { isClassicTrade, isUniswapXTrade } from 'state/routing/utils' import styled from 'styled-components' import { Divider, ExternalLink, ThemedText } from 'theme' +import { formatNumber, NumberType } from 'utils/formatNumbers' const Container = styled(AutoColumn)` padding: 4px; diff --git a/src/components/swap/GasEstimateTooltip.tsx b/src/components/swap/GasEstimateTooltip.tsx index 2a0be36417..8ef80752a5 100644 --- a/src/components/swap/GasEstimateTooltip.tsx +++ b/src/components/swap/GasEstimateTooltip.tsx @@ -1,5 +1,4 @@ import { InterfaceElementName, SwapEventName } from '@uniswap/analytics-events' -import { formatNumber, NumberType } from '@uniswap/conedison/format' import { useWeb3React } from '@web3-react/core' import { sendAnalyticsEvent } from 'analytics' import { LoadingOpacityContainer } from 'components/Loader/styled' @@ -11,6 +10,7 @@ import { InterfaceTrade } from 'state/routing/types' import { isUniswapXTrade } from 'state/routing/utils' import styled from 'styled-components' import { ThemedText } from 'theme' +import { formatNumber, NumberType } from 'utils/formatNumbers' import { ReactComponent as GasIcon } from '../../assets/images/gas-icon.svg' import { GasBreakdownTooltip } from './GasBreakdownTooltip' diff --git a/src/components/swap/PriceImpactModal.tsx b/src/components/swap/PriceImpactModal.tsx index ca965d33e5..4b3b8faecb 100644 --- a/src/components/swap/PriceImpactModal.tsx +++ b/src/components/swap/PriceImpactModal.tsx @@ -1,5 +1,4 @@ import { Trans } from '@lingui/macro' -import { formatPriceImpact } from '@uniswap/conedison/format' import { Percent } from '@uniswap/sdk-core' import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button' import { ColumnCenter } from 'components/Column' @@ -7,6 +6,7 @@ import Row from 'components/Row' import { AlertTriangle } from 'react-feather' import styled from 'styled-components' import { CloseIcon, ThemedText } from 'theme' +import { formatPriceImpact } from 'utils/formatNumbers' import Modal from '../Modal' diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 918fc728e5..2188f9d098 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -1,6 +1,5 @@ import { Plural, Trans } from '@lingui/macro' import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events' -import { formatNumber, formatPriceImpact, NumberType } from '@uniswap/conedison/format' import { Percent, TradeType } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import { TraceEvent } from 'analytics' @@ -16,6 +15,7 @@ import { getTransactionCount, isClassicTrade } from 'state/routing/utils' import { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks' import styled, { useTheme } from 'styled-components' import { ThemedText } from 'theme' +import { formatNumber, formatPriceImpact, NumberType } from 'utils/formatNumbers' import { formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers' import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries' import { formatSwapButtonClickEventProperties } from 'utils/loggingFormatters' diff --git a/src/components/swap/SwapModalHeader.test.tsx b/src/components/swap/SwapModalHeader.test.tsx index e2d09599a7..45b6f78e26 100644 --- a/src/components/swap/SwapModalHeader.test.tsx +++ b/src/components/swap/SwapModalHeader.test.tsx @@ -1,4 +1,3 @@ -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' import { ETH_MAINNET, TEST_ALLOWED_SLIPPAGE, @@ -7,6 +6,7 @@ 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' diff --git a/src/components/swap/SwapModalHeaderAmount.tsx b/src/components/swap/SwapModalHeaderAmount.tsx index 4fc38697b2..c063ae960d 100644 --- a/src/components/swap/SwapModalHeaderAmount.tsx +++ b/src/components/swap/SwapModalHeaderAmount.tsx @@ -1,4 +1,3 @@ -import { formatNumber, NumberType } from '@uniswap/conedison/format' import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import Column from 'components/Column' import CurrencyLogo from 'components/Logo/CurrencyLogo' @@ -10,6 +9,7 @@ import { TextProps } from 'rebass' import { Field } from 'state/swap/actions' import styled from 'styled-components' import { BREAKPOINTS, ThemedText } from 'theme' +import { formatNumber, NumberType } from 'utils/formatNumbers' import { formatReviewSwapCurrencyAmount } from 'utils/formatNumbers' export const Label = styled(ThemedText.BodySmall)<{ cursor?: string }>` diff --git a/src/components/swap/TradePrice.tsx b/src/components/swap/TradePrice.tsx index aeed0d2435..2913579f78 100644 --- a/src/components/swap/TradePrice.tsx +++ b/src/components/swap/TradePrice.tsx @@ -1,11 +1,11 @@ import { Trans } from '@lingui/macro' -import { formatNumber, formatPrice, NumberType } from '@uniswap/conedison/format' import { Currency, Price } from '@uniswap/sdk-core' import { useUSDPrice } from 'hooks/useUSDPrice' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import { useCallback, useMemo, useState } from 'react' import styled from 'styled-components' import { ThemedText } from 'theme' +import { formatNumber, formatPrice, NumberType } from 'utils/formatNumbers' interface TradePriceProps { price: Price diff --git a/src/nft/components/explore/CarouselCard.tsx b/src/nft/components/explore/CarouselCard.tsx index f10983b405..0ffafdce3f 100644 --- a/src/nft/components/explore/CarouselCard.tsx +++ b/src/nft/components/explore/CarouselCard.tsx @@ -1,4 +1,3 @@ -import { formatNumberOrString, NumberType } from '@uniswap/conedison/format' import { loadingAnimation } from 'components/Loader/styled' import { LoadingBubble } from 'components/Tokens/loading' import { useCollection } from 'graphql/data/nft/Collection' @@ -7,6 +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' const CarouselCardBorder = styled.div` width: 100%; diff --git a/src/nft/components/profile/list/ListPage.tsx b/src/nft/components/profile/list/ListPage.tsx index 78fe2c4565..fc9e15174e 100644 --- a/src/nft/components/profile/list/ListPage.tsx +++ b/src/nft/components/profile/list/ListPage.tsx @@ -1,6 +1,5 @@ import { Trans } from '@lingui/macro' import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events' -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' import { useWeb3React } from '@web3-react/core' import { sendAnalyticsEvent, useTrace } from 'analytics' import Column from 'components/Column' @@ -26,6 +25,7 @@ import { ArrowLeft } from 'react-feather' import styled from 'styled-components' import { BREAKPOINTS, ThemedText } from 'theme' import { Z_INDEX } from 'theme/zIndex' +import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { shallow } from 'zustand/shallow' import { ListModal } from './Modal/ListModal' diff --git a/src/nft/components/profile/list/Modal/ListModal.tsx b/src/nft/components/profile/list/Modal/ListModal.tsx index a18f0e0fab..a52f7f7bf1 100644 --- a/src/nft/components/profile/list/Modal/ListModal.tsx +++ b/src/nft/components/profile/list/Modal/ListModal.tsx @@ -1,6 +1,5 @@ import { Trans } from '@lingui/macro' import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events' -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' import { useWeb3React } from '@web3-react/core' import { sendAnalyticsEvent, Trace, useTrace } from 'analytics' import { useStablecoinValue } from 'hooks/useStablecoinPrice' @@ -16,6 +15,7 @@ import { X } from 'react-feather' import styled from 'styled-components' import { BREAKPOINTS, ThemedText } from 'theme' import { Z_INDEX } from 'theme/zIndex' +import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { shallow } from 'zustand/shallow' import { TitleRow } from '../shared' diff --git a/src/nft/components/profile/list/Modal/SuccessScreen.tsx b/src/nft/components/profile/list/Modal/SuccessScreen.tsx index b7e834d955..5d7fa22758 100644 --- a/src/nft/components/profile/list/Modal/SuccessScreen.tsx +++ b/src/nft/components/profile/list/Modal/SuccessScreen.tsx @@ -1,5 +1,4 @@ import { Trans } from '@lingui/macro' -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' import { useWeb3React } from '@web3-react/core' import Column from 'components/Column' import { ScrollBarStyles } from 'components/Common' @@ -14,6 +13,7 @@ import { useMemo } from 'react' import { Twitter, X } from 'react-feather' import styled, { css, useTheme } from 'styled-components' import { BREAKPOINTS, ThemedText } from 'theme' +import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { TitleRow } from '../shared' diff --git a/src/pages/Pool/PositionPage.tsx b/src/pages/Pool/PositionPage.tsx index 1b0378c402..7ef0ca503a 100644 --- a/src/pages/Pool/PositionPage.tsx +++ b/src/pages/Pool/PositionPage.tsx @@ -2,7 +2,6 @@ import { BigNumber } from '@ethersproject/bignumber' import type { TransactionResponse } from '@ethersproject/providers' import { Trans } from '@lingui/macro' import { InterfacePageName } from '@uniswap/analytics-events' -import { formatPrice, NumberType } from '@uniswap/conedison/format' import { Currency, CurrencyAmount, Fraction, Percent, Price, Token } from '@uniswap/sdk-core' import { NonfungiblePositionManager, Pool, Position } from '@uniswap/v3-sdk' import { useWeb3React } from '@web3-react/core' @@ -38,6 +37,7 @@ import styled, { useTheme } from 'styled-components' import { ExternalLink, HideExtraSmall, HideSmall, StyledRouterLink, ThemedText } from 'theme' import { currencyId } from 'utils/currencyId' import { formatCurrencyAmount } from 'utils/formatCurrencyAmount' +import { formatPrice, NumberType } from 'utils/formatNumbers' import { formatTickPrice } from 'utils/formatTickPrice' import { unwrappedToken } from 'utils/unwrappedToken' diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 76bd2f3e1a..34f0f25875 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -8,7 +8,6 @@ import { SharedEventName, SwapEventName, } from '@uniswap/analytics-events' -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' import { ChainId, Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core' import { UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk' import { useWeb3React } from '@web3-react/core' @@ -58,6 +57,7 @@ import swapReducer, { initialState as initialSwapState, SwapState } from 'state/ import styled, { useTheme } from 'styled-components' import { LinkStyledButton, ThemedText } from 'theme' import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact' +import { formatCurrencyAmount, NumberType } from 'utils/formatNumbers' import { maxAmountSpend } from 'utils/maxAmountSpend' import { computeRealizedPriceImpact, warningSeverity } from 'utils/prices' import { didUserReject } from 'utils/swapErrorToUserReadableMessage' diff --git a/src/utils/formatNumbers.test.ts b/src/utils/formatNumbers.test.ts index 4eb2562b84..96fca2a86a 100644 --- a/src/utils/formatNumbers.test.ts +++ b/src/utils/formatNumbers.test.ts @@ -1,13 +1,201 @@ -import { CurrencyAmount, Price } from '@uniswap/sdk-core' +import { CurrencyAmount, Percent, Price } from '@uniswap/sdk-core' import { renBTC, USDC_MAINNET } from 'constants/tokens' import { currencyAmountToPreciseFloat, + formatNumber, + formatPriceImpact, formatReviewSwapCurrencyAmount, + formatSlippage, formatTransactionAmount, + formatUSDPrice, + NumberType, priceToPreciseFloat, } from './formatNumbers' +it('formats token reference numbers correctly', () => { + expect(formatNumber(1234567000000000, NumberType.TokenNonTx)).toBe('>999T') + expect(formatNumber(1002345, NumberType.TokenNonTx)).toBe('1.00M') + expect(formatNumber(1234, NumberType.TokenNonTx)).toBe('1,234.00') + expect(formatNumber(0.00909, NumberType.TokenNonTx)).toBe('0.009') + expect(formatNumber(0.09001, NumberType.TokenNonTx)).toBe('0.090') + expect(formatNumber(0.00099, NumberType.TokenNonTx)).toBe('<0.001') + expect(formatNumber(0, NumberType.TokenNonTx)).toBe('0') +}) + +it('formats token transaction numbers correctly', () => { + expect(formatNumber(1234567.8901, NumberType.TokenTx)).toBe('1,234,567.89') + expect(formatNumber(765432.1, NumberType.TokenTx)).toBe('765,432.10') + + expect(formatNumber(7654.321, NumberType.TokenTx)).toBe('7,654.32') + expect(formatNumber(765.4321, NumberType.TokenTx)).toBe('765.432') + expect(formatNumber(76.54321, NumberType.TokenTx)).toBe('76.5432') + expect(formatNumber(7.654321, NumberType.TokenTx)).toBe('7.65432') + expect(formatNumber(7.60000054321, NumberType.TokenTx)).toBe('7.60') + expect(formatNumber(7.6, NumberType.TokenTx)).toBe('7.60') + expect(formatNumber(7, NumberType.TokenTx)).toBe('7.00') + + expect(formatNumber(0.987654321, NumberType.TokenTx)).toBe('0.98765') + expect(formatNumber(0.9, NumberType.TokenTx)).toBe('0.90') + expect(formatNumber(0.901000123, NumberType.TokenTx)).toBe('0.901') + expect(formatNumber(0.000000001, NumberType.TokenTx)).toBe('<0.00001') + expect(formatNumber(0, NumberType.TokenTx)).toBe('0') +}) + +it('formats fiat estimates on token details pages correctly', () => { + expect(formatNumber(1234567.891, NumberType.FiatTokenDetails)).toBe('$1.23M') + expect(formatNumber(1234.5678, NumberType.FiatTokenDetails)).toBe('$1,234.57') + expect(formatNumber(1.048942, NumberType.FiatTokenDetails)).toBe('$1.049') + + expect(formatNumber(0.001231, NumberType.FiatTokenDetails)).toBe('$0.00123') + expect(formatNumber(0.00001231, NumberType.FiatTokenDetails)).toBe('$0.0000123') + + expect(formatNumber(0.0000001234, NumberType.FiatTokenDetails)).toBe('$0.000000123') + expect(formatNumber(0.000000009876, NumberType.FiatTokenDetails)).toBe('<$0.00000001') +}) + +it('formats fiat estimates for tokens correctly', () => { + expect(formatNumber(1234567.891, NumberType.FiatTokenPrice)).toBe('$1.23M') + expect(formatNumber(1234.5678, NumberType.FiatTokenPrice)).toBe('$1,234.57') + + expect(formatNumber(0.010235, NumberType.FiatTokenPrice)).toBe('$0.0102') + expect(formatNumber(0.001231, NumberType.FiatTokenPrice)).toBe('$0.00123') + expect(formatNumber(0.00001231, NumberType.FiatTokenPrice)).toBe('$0.0000123') + + expect(formatNumber(0.0000001234, NumberType.FiatTokenPrice)).toBe('$0.000000123') + expect(formatNumber(0.000000009876, NumberType.FiatTokenPrice)).toBe('<$0.00000001') + expect(formatNumber(10000000000000000000000000000000, NumberType.FiatTokenPrice)).toBe('$1.000000E31') +}) + +it('formats fiat estimates for token stats correctly', () => { + expect(formatNumber(1234576, NumberType.FiatTokenStats)).toBe('$1.2M') + expect(formatNumber(234567, NumberType.FiatTokenStats)).toBe('$234.6K') + expect(formatNumber(123.456, NumberType.FiatTokenStats)).toBe('$123.46') + expect(formatNumber(1.23, NumberType.FiatTokenStats)).toBe('$1.23') + expect(formatNumber(0.123, NumberType.FiatTokenStats)).toBe('$0.12') + expect(formatNumber(0.00123, NumberType.FiatTokenStats)).toBe('<$0.01') + expect(formatNumber(0, NumberType.FiatTokenStats)).toBe('-') +}) + +it('formats gas USD prices correctly', () => { + expect(formatNumber(1234567.891, NumberType.FiatGasPrice)).toBe('$1.23M') + expect(formatNumber(18.448, NumberType.FiatGasPrice)).toBe('$18.45') + expect(formatNumber(0.0099, NumberType.FiatGasPrice)).toBe('<$0.01') + expect(formatNumber(0, NumberType.FiatGasPrice)).toBe('$0.00') +}) + +it('formats USD token quantities prices correctly', () => { + expect(formatNumber(1234567.891, NumberType.FiatTokenQuantity)).toBe('$1.23M') + expect(formatNumber(18.448, NumberType.FiatTokenQuantity)).toBe('$18.45') + expect(formatNumber(0.0099, NumberType.FiatTokenQuantity)).toBe('<$0.01') + expect(formatNumber(0, NumberType.FiatTokenQuantity)).toBe('$0.00') +}) + +it('formats Swap text input/output numbers correctly', () => { + expect(formatNumber(1234567.8901, NumberType.SwapTradeAmount)).toBe('1234570') + expect(formatNumber(765432.1, NumberType.SwapTradeAmount)).toBe('765432') + + expect(formatNumber(7654.321, NumberType.SwapTradeAmount)).toBe('7654.32') + expect(formatNumber(765.4321, NumberType.SwapTradeAmount)).toBe('765.432') + expect(formatNumber(76.54321, NumberType.SwapTradeAmount)).toBe('76.5432') + expect(formatNumber(7.654321, NumberType.SwapTradeAmount)).toBe('7.65432') + expect(formatNumber(7.60000054321, NumberType.SwapTradeAmount)).toBe('7.60') + expect(formatNumber(7.6, NumberType.SwapTradeAmount)).toBe('7.60') + expect(formatNumber(7, NumberType.SwapTradeAmount)).toBe('7.00') + + expect(formatNumber(0.987654321, NumberType.SwapTradeAmount)).toBe('0.98765') + expect(formatNumber(0.9, NumberType.SwapTradeAmount)).toBe('0.90') + expect(formatNumber(0.901000123, NumberType.SwapTradeAmount)).toBe('0.901') + expect(formatNumber(0.000000001, NumberType.SwapTradeAmount)).toBe('0.000000001') + expect(formatNumber(0, NumberType.SwapTradeAmount)).toBe('0') +}) + +it('formats NFT numbers correctly', () => { + expect(formatNumber(1234567000000000, NumberType.NFTTokenFloorPrice)).toBe('>999T') + expect(formatNumber(1002345, NumberType.NFTTokenFloorPrice)).toBe('1M') + expect(formatNumber(1234, NumberType.NFTTokenFloorPrice)).toBe('1.23K') + expect(formatNumber(12.34467, NumberType.NFTTokenFloorPrice)).toBe('12.34') + expect(formatNumber(12.1, NumberType.NFTTokenFloorPrice)).toBe('12.1') + expect(formatNumber(0.00909, NumberType.NFTTokenFloorPrice)).toBe('0.009') + expect(formatNumber(0.09001, NumberType.NFTTokenFloorPrice)).toBe('0.09') + expect(formatNumber(0.00099, NumberType.NFTTokenFloorPrice)).toBe('<0.001') + expect(formatNumber(0, NumberType.NFTTokenFloorPrice)).toBe('0') + + expect(formatNumber(12.1, NumberType.NFTTokenFloorPriceTrailingZeros)).toBe('12.10') + expect(formatNumber(0.09001, NumberType.NFTTokenFloorPriceTrailingZeros)).toBe('0.090') + + expect(formatNumber(0.987654321, NumberType.NFTCollectionStats)).toBe('1') + expect(formatNumber(0.9, NumberType.NFTCollectionStats)).toBe('1') + expect(formatNumber(76543.21, NumberType.NFTCollectionStats)).toBe('76.5K') + expect(formatNumber(7.60000054321, NumberType.NFTCollectionStats)).toBe('8') + expect(formatNumber(1234567890, NumberType.NFTCollectionStats)).toBe('1.2B') + expect(formatNumber(1234567000000000, NumberType.NFTCollectionStats)).toBe('1234.6T') +}) + +describe('formatUSDPrice', () => { + it('should correctly format 0.000000009876', () => { + expect(formatUSDPrice(0.000000009876)).toBe('<$0.00000001') + }) + it('should correctly format 0.00001231', () => { + expect(formatUSDPrice(0.00001231)).toBe('$0.0000123') + }) + 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') + }) +}) + +describe('formatPriceImpact', () => { + it('should correctly format undefined', () => { + expect(formatPriceImpact(undefined)).toBe('-') + }) + + it('correctly formats a percent with 3 significant digits', () => { + 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', () => { + it('should correctly format undefined', () => { + expect(formatSlippage(undefined)).toBe('-') + }) + + it('correctly formats a percent with 3 significant digits', () => { + 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') diff --git a/src/utils/formatNumbers.ts b/src/utils/formatNumbers.ts index dfde82b360..b5226e9fce 100644 --- a/src/utils/formatNumbers.ts +++ b/src/utils/formatNumbers.ts @@ -1,8 +1,380 @@ -/* Copied from Uniswap/v-3: https://github.com/Uniswap/v3-info/blob/master/src/utils/numbers.ts */ -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' -import { Currency, CurrencyAmount, Price } from '@uniswap/sdk-core' +import { Currency, CurrencyAmount, Percent, Price } from '@uniswap/sdk-core' import { DEFAULT_LOCALE } from 'constants/locales' +type Nullish = T | null | undefined + +// Number formatting follows the standards laid out in this spec: +// https://www.notion.so/uniswaplabs/Number-standards-fbb9f533f10e4e22820722c2f66d23c0 + +const FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 5, + minimumFractionDigits: 2, +}) + +const FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN_NO_COMMAS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 5, + minimumFractionDigits: 2, + useGrouping: false, +}) + +const NO_DECIMALS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 0, + minimumFractionDigits: 0, +}) + +const THREE_DECIMALS_NO_TRAILING_ZEROS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 3, + minimumFractionDigits: 0, +}) + +const THREE_DECIMALS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 3, + minimumFractionDigits: 3, +}) + +const THREE_DECIMALS_USD = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 3, + minimumFractionDigits: 3, + currency: 'USD', + style: 'currency', +}) + +const TWO_DECIMALS_NO_TRAILING_ZEROS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 2, +}) + +const TWO_DECIMALS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 2, + minimumFractionDigits: 2, +}) + +const TWO_DECIMALS_USD = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 2, + minimumFractionDigits: 2, + currency: 'USD', + style: 'currency', +}) + +const SHORTHAND_TWO_DECIMALS = new Intl.NumberFormat('en-US', { + notation: 'compact', + minimumFractionDigits: 2, + maximumFractionDigits: 2, +}) + +const SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS = new Intl.NumberFormat('en-US', { + notation: 'compact', + maximumFractionDigits: 2, +}) + +const SHORTHAND_ONE_DECIMAL = new Intl.NumberFormat('en-US', { + notation: 'compact', + minimumFractionDigits: 1, + maximumFractionDigits: 1, +}) + +const SHORTHAND_USD_TWO_DECIMALS = new Intl.NumberFormat('en-US', { + notation: 'compact', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + currency: 'USD', + style: 'currency', +}) + +const SHORTHAND_USD_ONE_DECIMAL = new Intl.NumberFormat('en-US', { + notation: 'compact', + minimumFractionDigits: 1, + maximumFractionDigits: 1, + currency: 'USD', + style: 'currency', +}) + +const SIX_SIG_FIGS_TWO_DECIMALS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumSignificantDigits: 6, + minimumSignificantDigits: 3, + maximumFractionDigits: 2, + minimumFractionDigits: 2, +}) + +const SIX_SIG_FIGS_NO_COMMAS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumSignificantDigits: 6, + useGrouping: false, +}) + +const SIX_SIG_FIGS_TWO_DECIMALS_NO_COMMAS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumSignificantDigits: 6, + minimumSignificantDigits: 3, + maximumFractionDigits: 2, + minimumFractionDigits: 2, + useGrouping: false, +}) + +const THREE_SIG_FIGS_USD = new Intl.NumberFormat('en-US', { + notation: 'standard', + minimumSignificantDigits: 3, + maximumSignificantDigits: 3, + currency: 'USD', + style: 'currency', +}) + +const SEVEN_SIG_FIGS__SCI_NOTATION_USD = new Intl.NumberFormat('en-US', { + notation: 'scientific', + minimumSignificantDigits: 7, + maximumSignificantDigits: 7, + currency: 'USD', + style: 'currency', +}) + +type Format = Intl.NumberFormat | string + +// each rule must contain either an `upperBound` or an `exact` value. +// upperBound => number will use that formatter as long as it is < upperBound +// exact => number will use that formatter if it is === exact +type FormatterRule = + | { upperBound?: undefined; exact: number; formatter: Format } + | { upperBound: number; exact?: undefined; formatter: Format } + +// these formatter objects dictate which formatter rule to use based on the interval that +// the number falls into. for example, based on the rule set below, if your number +// falls between 1 and 1e6, you'd use TWO_DECIMALS as the formatter. +const tokenNonTxFormatter: FormatterRule[] = [ + { exact: 0, formatter: '0' }, + { upperBound: 0.001, formatter: '<0.001' }, + { upperBound: 1, formatter: THREE_DECIMALS }, + { upperBound: 1e6, formatter: TWO_DECIMALS }, + { upperBound: 1e15, formatter: SHORTHAND_TWO_DECIMALS }, + { upperBound: Infinity, formatter: '>999T' }, +] + +const tokenTxFormatter: FormatterRule[] = [ + { exact: 0, formatter: '0' }, + { upperBound: 0.00001, formatter: '<0.00001' }, + { upperBound: 1, formatter: FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN }, + { upperBound: 10000, formatter: SIX_SIG_FIGS_TWO_DECIMALS }, + { upperBound: Infinity, formatter: TWO_DECIMALS }, +] + +const swapTradeAmountFormatter: FormatterRule[] = [ + { exact: 0, formatter: '0' }, + { upperBound: 0.1, formatter: SIX_SIG_FIGS_NO_COMMAS }, + { upperBound: 1, formatter: FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN_NO_COMMAS }, + { upperBound: Infinity, formatter: SIX_SIG_FIGS_TWO_DECIMALS_NO_COMMAS }, +] + +const swapPriceFormatter: FormatterRule[] = [ + { exact: 0, formatter: '0' }, + { upperBound: 0.00001, formatter: '<0.00001' }, + ...swapTradeAmountFormatter, +] + +const fiatTokenDetailsFormatter: FormatterRule[] = [ + { exact: 0, formatter: '$0.00' }, + { upperBound: 0.00000001, formatter: '<$0.00000001' }, + { upperBound: 0.1, formatter: THREE_SIG_FIGS_USD }, + { upperBound: 1.05, formatter: THREE_DECIMALS_USD }, + { upperBound: 1e6, formatter: TWO_DECIMALS_USD }, + { upperBound: Infinity, formatter: SHORTHAND_USD_TWO_DECIMALS }, +] + +const fiatTokenPricesFormatter: FormatterRule[] = [ + { exact: 0, formatter: '$0.00' }, + { upperBound: 0.00000001, formatter: '<$0.00000001' }, + { upperBound: 1, formatter: THREE_SIG_FIGS_USD }, + { upperBound: 1e6, formatter: TWO_DECIMALS_USD }, + { upperBound: 1e16, formatter: SHORTHAND_USD_TWO_DECIMALS }, + { upperBound: Infinity, formatter: SEVEN_SIG_FIGS__SCI_NOTATION_USD }, +] + +const fiatTokenStatsFormatter: FormatterRule[] = [ + // if token stat value is 0, we probably don't have the data for it, so show '-' as a placeholder + { exact: 0, formatter: '-' }, + { upperBound: 0.01, formatter: '<$0.01' }, + { upperBound: 1000, formatter: TWO_DECIMALS_USD }, + { upperBound: Infinity, formatter: SHORTHAND_USD_ONE_DECIMAL }, +] + +const fiatGasPriceFormatter: FormatterRule[] = [ + { exact: 0, formatter: '$0.00' }, + { upperBound: 0.01, formatter: '<$0.01' }, + { upperBound: 1e6, formatter: TWO_DECIMALS_USD }, + { upperBound: Infinity, formatter: SHORTHAND_USD_TWO_DECIMALS }, +] + +const fiatTokenQuantityFormatter = [{ exact: 0, formatter: '$0.00' }, ...fiatGasPriceFormatter] + +const portfolioBalanceFormatter: FormatterRule[] = [ + { exact: 0, formatter: '$0.00' }, + { upperBound: Infinity, formatter: TWO_DECIMALS_USD }, +] + +const ntfTokenFloorPriceFormatterTrailingZeros: FormatterRule[] = [ + { exact: 0, formatter: '0' }, + { upperBound: 0.001, formatter: '<0.001' }, + { upperBound: 1, formatter: THREE_DECIMALS }, + { upperBound: 1000, formatter: TWO_DECIMALS }, + { upperBound: 1e15, formatter: SHORTHAND_TWO_DECIMALS }, + { upperBound: Infinity, formatter: '>999T' }, +] + +const ntfTokenFloorPriceFormatter: FormatterRule[] = [ + { exact: 0, formatter: '0' }, + { upperBound: 0.001, formatter: '<0.001' }, + { upperBound: 1, formatter: THREE_DECIMALS_NO_TRAILING_ZEROS }, + { upperBound: 1000, formatter: TWO_DECIMALS_NO_TRAILING_ZEROS }, + { upperBound: 1e15, formatter: SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS }, + { upperBound: Infinity, formatter: '>999T' }, +] + +const ntfCollectionStatsFormatter: FormatterRule[] = [ + { upperBound: 1000, formatter: NO_DECIMALS }, + { upperBound: Infinity, formatter: SHORTHAND_ONE_DECIMAL }, +] + +export enum NumberType { + // used for token quantities in non-transaction contexts (e.g. portfolio balances) + TokenNonTx = 'token-non-tx', + + // used for token quantities in transaction contexts (e.g. swap, send) + TokenTx = 'token-tx', + + // this formatter is used for displaying swap price conversions + // below the input/output amounts + SwapPrice = 'swap-price', + + // this formatter is only used for displaying the swap trade output amount + // in the text input boxes. Output amounts on review screen should use the above TokenTx formatter + SwapTradeAmount = 'swap-trade-amount', + + // fiat prices in any component that belongs in the Token Details flow (except for token stats) + FiatTokenDetails = 'fiat-token-details', + + // fiat prices everywhere except Token Details flow + FiatTokenPrice = 'fiat-token-price', + + // fiat values for market cap, TVL, volume in the Token Details screen + FiatTokenStats = 'fiat-token-stats', + + // fiat price of token balances + FiatTokenQuantity = 'fiat-token-quantity', + + // fiat gas prices + FiatGasPrice = 'fiat-gas-price', + + // portfolio balance + PortfolioBalance = 'portfolio-balance', + + // nft floor price denominated in a token (e.g, ETH) + NFTTokenFloorPrice = 'nft-token-floor-price', + + // nft collection stats like number of items, holder, and sales + NFTCollectionStats = 'nft-collection-stats', + + // nft floor price with trailing zeros + NFTTokenFloorPriceTrailingZeros = 'nft-token-floor-price-trailing-zeros', +} + +const TYPE_TO_FORMATTER_RULES = { + [NumberType.TokenNonTx]: tokenNonTxFormatter, + [NumberType.TokenTx]: tokenTxFormatter, + [NumberType.SwapPrice]: swapPriceFormatter, + [NumberType.SwapTradeAmount]: swapTradeAmountFormatter, + [NumberType.FiatTokenQuantity]: fiatTokenQuantityFormatter, + [NumberType.FiatTokenDetails]: fiatTokenDetailsFormatter, + [NumberType.FiatTokenPrice]: fiatTokenPricesFormatter, + [NumberType.FiatTokenStats]: fiatTokenStatsFormatter, + [NumberType.FiatGasPrice]: fiatGasPriceFormatter, + [NumberType.PortfolioBalance]: portfolioBalanceFormatter, + [NumberType.NFTTokenFloorPrice]: ntfTokenFloorPriceFormatter, + [NumberType.NFTTokenFloorPriceTrailingZeros]: ntfTokenFloorPriceFormatterTrailingZeros, + [NumberType.NFTCollectionStats]: ntfCollectionStatsFormatter, +} + +function getFormatterRule(input: number, type: NumberType): Format { + const rules = TYPE_TO_FORMATTER_RULES[type] + for (const rule of rules) { + if ( + (rule.exact !== undefined && input === rule.exact) || + (rule.upperBound !== undefined && input < rule.upperBound) + ) { + return rule.formatter + } + } + + throw new Error(`formatter for type ${type} not configured correctly`) +} + +export function formatNumber( + input: Nullish, + type: NumberType = NumberType.TokenNonTx, + placeholder = '-' +): string { + if (input === null || input === undefined) { + return placeholder + } + + const formatter = getFormatterRule(input, type) + if (typeof formatter === 'string') return formatter + return formatter.format(input) +} + +export function formatCurrencyAmount( + amount: Nullish>, + type: NumberType = NumberType.TokenNonTx, + placeholder?: string +): string { + return formatNumber(amount ? parseFloat(amount.toSignificant()) : undefined, type, placeholder) +} + +export function formatPriceImpact(priceImpact: Percent | undefined): string { + if (!priceImpact) return '-' + + return `${priceImpact.multiply(-1).toFixed(3)}%` +} + +export function formatSlippage(slippage: Percent | undefined) { + if (!slippage) return '-' + + return `${slippage.toFixed(3)}%` +} + +export function formatPrice( + price: Nullish>, + type: NumberType = NumberType.FiatTokenPrice +): string { + if (price === null || price === undefined) { + return '-' + } + + return formatNumber(parseFloat(price.toSignificant()), type) +} + +export function formatNumberOrString(price: Nullish, type: NumberType): string { + if (price === null || price === undefined) return '-' + if (typeof price === 'string') return formatNumber(parseFloat(price), type) + return formatNumber(price, type) +} + +export function formatUSDPrice(price: Nullish, type: NumberType = NumberType.FiatTokenPrice): string { + return formatNumberOrString(price, type) +} + +/** Formats USD and non-USD prices */ +export function formatFiatPrice(price: Nullish, currency = 'USD'): string { + if (price === null || price === undefined) return '-' + return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(price) +} + // Convert [CurrencyAmount] to number with necessary precision for price formatting. export const currencyAmountToPreciseFloat = (currencyAmount: CurrencyAmount | undefined) => { if (!currencyAmount) return undefined diff --git a/src/utils/formatTickPrice.ts b/src/utils/formatTickPrice.ts index b71ab71d31..9b6e37a595 100644 --- a/src/utils/formatTickPrice.ts +++ b/src/utils/formatTickPrice.ts @@ -1,5 +1,5 @@ -import { formatPrice, NumberType } from '@uniswap/conedison/format' import { Price, Token } from '@uniswap/sdk-core' +import { formatPrice, NumberType } from 'utils/formatNumbers' import { Bound } from '../state/mint/v3/actions'