chore: updating nft numbers (#7351)

* format price impact

* format price

* adding confirm swap modal

* removing export

* adding export back

* half of test functions done

* format numbers tests

* formatting

* making numberFormat local

* making formatCurrencyAmount internal

* price impact

* formatSlippage

* formatTickPrice

* formatNumberOrString

* formatFiatPrice

* removing formatPreciseFloat

* formatReviewSwapCurrencyAmount

* correct active currency

* explore table

* deleting formatTickPrice

* fixing explore table

* nft assset details

* removing all instancees of ethnumberstandardformatter

* removing format wei impls

* explore table

* collectino stats

* removing almost everything from nft/numbers

* filter button

* final nft fixes

* removing put commas

* explore page

* listing page

* extraneous functions

* responding to comments

* formatEhter

* updating formatter names

* dep array

* comments

---------

Co-authored-by: John Short <john.short@CORN-Jack-899.local>
This commit is contained in:
Jack Short 2023-11-01 12:44:45 -04:00 committed by GitHub
parent 802d56231a
commit bd30721989
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 370 additions and 345 deletions

@ -8,10 +8,10 @@ import { NftCard } from 'nft/components/card'
import { detailsHref } from 'nft/components/card/utils'
import { VerifiedIcon } from 'nft/components/icons'
import { WalletAsset } from 'nft/types'
import { floorFormatter } from 'nft/utils'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components'
import { ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
const FloorPrice = styled(Row)`
opacity: 0;
@ -83,6 +83,8 @@ export function NFT({
}
function NFTDetails({ asset }: { asset: WalletAsset }) {
const { formatNumberOrString } = useFormatter()
return (
<Box overflow="hidden" width="full" flexWrap="nowrap">
<Row gap="4px">
@ -91,7 +93,9 @@ function NFTDetails({ asset }: { asset: WalletAsset }) {
</Row>
<FloorPrice>
<ThemedText.BodySmall color="neutral2">
{asset.floorPrice ? `${floorFormatter(asset.floorPrice)} ETH` : ' '}
{asset.floorPrice
? `${formatNumberOrString({ input: asset.floorPrice, type: NumberType.NFTTokenFloorPrice })} ETH`
: ' '}
</ThemedText.BodySmall>
</FloorPrice>
</Box>

@ -13,13 +13,11 @@ import { Column, Row } from 'nft/components/Flex'
import { VerifiedIcon } from 'nft/components/icons'
import { vars } from 'nft/css/sprinkles.css'
import { GenieCollection } from 'nft/types'
import { ethNumberStandardFormatter } from 'nft/utils/currency'
import { putCommas } from 'nft/utils/putCommas'
import { useCallback, useEffect, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import styled from 'styled-components'
import { ThemedText } from 'theme/components'
import { useFormatter } from 'utils/formatNumbers'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { DeltaArrow, DeltaText } from '../Tokens/TokenDetails/Delta'
import { useAddRecentlySearchedAsset } from './RecentlySearchedAssets'
@ -49,6 +47,8 @@ export const CollectionRow = ({
index,
eventProperties,
}: CollectionRowProps) => {
const { formatNumberOrString } = useFormatter()
const [brokenImage, setBrokenImage] = useState(false)
const [loaded, setLoaded] = useState(false)
@ -102,13 +102,17 @@ export const CollectionRow = ({
<Box className={styles.primaryText}>{collection.name}</Box>
{collection.isVerified && <VerifiedIcon className={styles.suggestionIcon} />}
</Row>
<Box className={styles.secondaryText}>{putCommas(collection?.stats?.total_supply ?? 0)} items</Box>
<Box className={styles.secondaryText}>
{formatNumberOrString({ input: collection?.stats?.total_supply, type: NumberType.WholeNumber })} items
</Box>
</Column>
</Row>
{collection.stats?.floor_price ? (
<Column className={styles.suggestionSecondaryContainer}>
<Row gap="4">
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.stats?.floor_price)} ETH</Box>
<Box className={styles.primaryText}>
{formatNumberOrString({ input: collection.stats?.floor_price, type: NumberType.NFTToken })} ETH
</Box>
</Row>
<Box className={styles.secondaryText}>Floor</Box>
</Column>

@ -31,12 +31,12 @@ import { useSubscribeTransactionState } from 'nft/hooks/useSubscribeTransactionS
import { useTokenInput } from 'nft/hooks/useTokenInput'
import { useWalletBalance } from 'nft/hooks/useWalletBalance'
import { BagStatus } from 'nft/types'
import { ethNumberStandardFormatter, formatWeiToDecimal } from 'nft/utils'
import { PropsWithChildren, useEffect, useMemo, useState } from 'react'
import { AlertTriangle, ChevronDown } from 'react-feather'
import { InterfaceTrade, TradeFillType, TradeState } from 'state/routing/types'
import styled, { useTheme } from 'styled-components'
import { ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { BuyButtonStateData, BuyButtonStates, getBuyButtonStateData } from './ButtonStates'
@ -189,10 +189,12 @@ const InputCurrencyValue = ({
tradeState: TradeState
trade?: InterfaceTrade
}) => {
const { formatEther, formatNumberOrString } = useFormatter()
if (!usingPayWithAnyToken) {
return (
<ThemedText.BodyPrimary lineHeight="20px" fontWeight="535">
{formatWeiToDecimal(totalEthPrice.toString())}
{formatEther({ input: totalEthPrice.toString(), type: NumberType.NFTToken })}
&nbsp;{activeCurrency?.symbol ?? 'ETH'}
</ThemedText.BodyPrimary>
)
@ -208,7 +210,7 @@ const InputCurrencyValue = ({
return (
<ValueText color={tradeState === TradeState.LOADING ? 'neutral3' : 'neutral1'}>
{ethNumberStandardFormatter(trade?.inputAmount.toExact())}
{formatNumberOrString({ input: trade?.inputAmount.toExact(), type: NumberType.NFTToken })}
</ValueText>
)
}
@ -224,6 +226,8 @@ const FiatValue = ({
tradeState: TradeState
usingPayWithAnyToken: boolean
}) => {
const { formatNumberOrString } = useFormatter()
if (!usdcValue) {
if (usingPayWithAnyToken && (tradeState === TradeState.INVALID || tradeState === TradeState.NO_ROUTE_FOUND)) {
return null
@ -247,7 +251,7 @@ const FiatValue = ({
</>
)}
<ThemedText.BodySmall color="neutral3" lineHeight="20px">
{`${ethNumberStandardFormatter(usdcValue?.toExact(), true)}`}
{`${formatNumberOrString({ input: usdcValue?.toExact(), type: NumberType.FiatNFTToken })}`}
</ThemedText.BodySmall>
</PriceImpactContainer>
)

@ -1,5 +1,5 @@
import { BigNumber } from '@ethersproject/bignumber'
import { formatEther } from '@ethersproject/units'
import { formatEther as ethersFormatEther } from '@ethersproject/units'
import clsx from 'clsx'
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
import { TimedLoader } from 'nft/components/bag/TimedLoader'
@ -18,10 +18,11 @@ import {
import { bodySmall } from 'nft/css/common.css'
import { loadingBlock } from 'nft/css/loading.css'
import { GenieAsset, UpdatedGenieAsset } from 'nft/types'
import { ethNumberStandardFormatter, formatWeiToDecimal, getAssetHref } from 'nft/utils'
import { getAssetHref } from 'nft/utils'
import { MouseEvent, useCallback, useEffect, useReducer, useState } from 'react'
import { Link } from 'react-router-dom'
import styled from 'styled-components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import * as styles from './BagRow.css'
@ -90,6 +91,7 @@ interface BagRowProps {
}
export const BagRow = ({ asset, usdPrice, removeAsset, showRemove, grayscale, isMobile }: BagRowProps) => {
const { formatEther, formatNumberOrString } = useFormatter()
const [loadedImage, setImageLoaded] = useState(false)
const [noImageAvailable, setNoImageAvailable] = useState(!asset.smallImageUrl)
@ -99,11 +101,11 @@ export const BagRow = ({ asset, usdPrice, removeAsset, showRemove, grayscale, is
const showRemoveButton = Boolean(showRemove && cardHovered && !isMobile)
const assetEthPrice = asset.updatedPriceInfo ? asset.updatedPriceInfo.ETHPrice : asset.priceInfo.ETHPrice
const assetEthPriceFormatted = formatWeiToDecimal(assetEthPrice)
const assetUSDPriceFormatted = ethNumberStandardFormatter(
usdPrice ? parseFloat(formatEther(assetEthPrice)) * usdPrice : usdPrice,
true
)
const assetEthPriceFormatted = formatEther({ input: assetEthPrice, type: NumberType.NFTToken })
const assetUSDPriceFormatted = formatNumberOrString({
input: usdPrice ? parseFloat(ethersFormatEther(assetEthPrice)) * usdPrice : usdPrice,
type: NumberType.FiatNFTToken,
})
const handleRemoveClick = useCallback(
(e: MouseEvent<HTMLElement>) => {
@ -175,6 +177,7 @@ interface PriceChangeBagRowProps {
}
export const PriceChangeBagRow = ({ asset, usdPrice, markAssetAsReviewed, top, isMobile }: PriceChangeBagRowProps) => {
const { formatEther } = useFormatter()
const isPriceIncrease = BigNumber.from(asset.updatedPriceInfo?.ETHPrice).gt(BigNumber.from(asset.priceInfo.ETHPrice))
const handleRemove = useCallback(
(e: MouseEvent<HTMLButtonElement>) => {
@ -198,9 +201,10 @@ export const PriceChangeBagRow = ({ asset, usdPrice, markAssetAsReviewed, top, i
<Column className={styles.priceChangeColumn} borderTopColor={top ? 'surface3' : 'transparent'}>
<Row className={styles.priceChangeRow}>
{isPriceIncrease ? <SquareArrowUpIcon /> : <SquareArrowDownIcon />}
<Box>{`Price ${isPriceIncrease ? 'increased' : 'decreased'} from ${formatWeiToDecimal(
asset.priceInfo.ETHPrice
)} ETH`}</Box>
<Box>{`Price ${isPriceIncrease ? 'increased' : 'decreased'} from ${formatEther({
input: asset.priceInfo.ETHPrice,
type: NumberType.NFTToken,
})} ETH`}</Box>
</Row>
<Box style={{ marginLeft: '-8px', marginRight: '-8px' }}>
<BagRow asset={asset} usdPrice={usdPrice} removeAsset={() => undefined} isMobile={isMobile} />

@ -3,7 +3,8 @@ import { Column, Row } from 'nft/components/Flex'
import { body, bodySmall } from 'nft/css/common.css'
import { useBag } from 'nft/hooks'
import { useBagTotalEthPrice, useBagTotalUsdPrice } from 'nft/hooks/useBagTotalEthPrice'
import { ethNumberStandardFormatter, formatWeiToDecimal, roundAndPluralize } from 'nft/utils'
import { roundAndPluralize } from 'nft/utils'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import * as styles from './MobileHoverBag.css'
export const MobileHoverBag = () => {
@ -11,6 +12,7 @@ export const MobileHoverBag = () => {
const toggleBag = useBag((state) => state.toggleBag)
const totalEthPrice = useBagTotalEthPrice()
const totalUsdPrice = useBagTotalUsdPrice()
const { formatEther, formatNumberOrString } = useFormatter()
const shouldShowBag = itemsInBag.length > 0
@ -48,9 +50,11 @@ export const MobileHoverBag = () => {
{roundAndPluralize(itemsInBag.length, 'NFT')}
</Box>
<Row gap="8">
<Box className={body}>{`${formatWeiToDecimal(totalEthPrice.toString())}`} ETH</Box>
<Box className={body}>
{`${formatEther({ input: totalEthPrice.toString(), type: NumberType.NFTToken })}`} ETH
</Box>
<Box color="neutral2" className={bodySmall}>
{ethNumberStandardFormatter(totalUsdPrice, true)}
{formatNumberOrString({ input: totalUsdPrice, type: NumberType.FiatNFTToken })}
</Box>
</Row>
</Column>

@ -5,10 +5,10 @@ import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { getMarketplaceIcon } from 'nft/components/card/utils'
import { CollectionSelectedAssetIcon } from 'nft/components/icons'
import { Markets } from 'nft/types'
import { putCommas } from 'nft/utils'
import { AlertTriangle, Check, Tag } from 'react-feather'
import styled from 'styled-components'
import { ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
const StyledMarketplaceContainer = styled.div<{ isText?: boolean }>`
position: absolute;
@ -114,6 +114,8 @@ const RarityInfo = styled(ThemedText.BodySmall)`
`
export const Ranking = ({ provider }: RankingProps) => {
const { formatNumber } = useFormatter()
if (!provider.rank) {
return null
}
@ -131,7 +133,7 @@ export const Ranking = ({ provider }: RankingProps) => {
}
placement="top"
>
# {putCommas(provider.rank)}
# {formatNumber({ input: provider.rank, type: NumberType.WholeNumber })}
</MouseoverTooltip>
</RarityInfo>
)

@ -4,8 +4,8 @@ import { MediaContainer } from 'nft/components/card/media'
import { detailsHref, getNftDisplayComponent, useSelectAsset } from 'nft/components/card/utils'
import { useBag } from 'nft/hooks'
import { GenieAsset, UniformAspectRatio, UniformAspectRatios, WalletAsset } from 'nft/types'
import { floorFormatter } from 'nft/utils'
import { ReactNode } from 'react'
import { NumberType, useFormatter } from 'utils/formatNumbers'
interface NftCardProps {
asset: GenieAsset | WalletAsset
@ -74,12 +74,15 @@ export const NftCard = ({
setBagExpanded: state.setBagExpanded,
}))
const { formatNumberOrString } = useFormatter()
const collectionNft = 'marketplace' in asset
const profileNft = 'asset_contract' in asset
const tokenType = collectionNft ? asset.tokenType : profileNft ? asset.asset_contract.tokenType : undefined
const marketplace = collectionNft ? asset.marketplace : undefined
const listedPrice =
profileNft && !isDisabled && asset.floor_sell_order_price ? floorFormatter(asset.floor_sell_order_price) : undefined
profileNft && !isDisabled && asset.floor_sell_order_price
? formatNumberOrString({ input: asset.floor_sell_order_price, type: NumberType.NFTTokenFloorPrice })
: undefined
return (
<Card.Container

@ -26,13 +26,12 @@ import {
} from 'nft/types'
import { getMarketplaceIcon } from 'nft/utils'
import { buildActivityAsset } from 'nft/utils/buildActivityAsset'
import { formatEth } from 'nft/utils/currency'
import { getTimeDifference } from 'nft/utils/date'
import { putCommas } from 'nft/utils/putCommas'
import { MouseEvent, ReactNode, useMemo, useState } from 'react'
import styled from 'styled-components'
import { ExternalLink } from 'theme/components'
import { shortenAddress } from 'utils'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
import * as styles from './Activity.css'
@ -185,7 +184,11 @@ const PriceTooltip = ({ price }: { price: string }) => (
)
export const PriceCell = ({ marketplace, price }: { marketplace?: Markets | string; price?: string | number }) => {
const formattedPrice = useMemo(() => (price ? formatEth(parseFloat(price?.toString())) : null), [price])
const { formatNumberOrString } = useFormatter()
const formattedPrice = useMemo(
() => (price ? formatNumberOrString({ input: parseFloat(price?.toString()), type: NumberType.NFTToken }) : null),
[formatNumberOrString, price]
)
return (
<Row display={{ sm: 'none', md: 'flex' }} gap="8">
@ -257,7 +260,11 @@ export const EventCell = ({
price,
isMobile,
}: EventCellProps) => {
const formattedPrice = useMemo(() => (price ? formatEth(parseFloat(price?.toString())) : null), [price])
const { formatNumberOrString } = useFormatter()
const formattedPrice = useMemo(
() => (price ? formatNumberOrString({ input: parseFloat(price?.toString()), type: NumberType.NFTToken }) : null),
[formatNumberOrString, price]
)
return (
<Column height="full" justifyContent="center" gap="4">
<Row className={styles.eventDetail} color={eventColors(eventType)}>
@ -318,6 +325,7 @@ interface RankingProps {
}
const Ranking = ({ rarity, collectionName, rarityVerified }: RankingProps) => {
const { formatNumber } = useFormatter()
const rank = (rarity as TokenRarity).rank || (rarity as Rarity).providers?.[0].rank
if (!rank) return null
@ -339,7 +347,7 @@ const Ranking = ({ rarity, collectionName, rarityVerified }: RankingProps) => {
>
<Box className={styles.rarityInfo}>
<Box paddingTop="2" paddingBottom="2" display="flex">
{putCommas(rank)}
{formatNumber({ input: rank, type: NumberType.WholeNumber })}
</Box>
<Box display="flex" height="16">

@ -6,8 +6,8 @@ import { NftCard, NftCardDisplayProps } from 'nft/components/card'
import { Ranking as RankingContainer, Suspicious as SuspiciousContainer } from 'nft/components/card/icons'
import { useBag } from 'nft/hooks'
import { GenieAsset, UniformAspectRatio } from 'nft/types'
import { formatWeiToDecimal } from 'nft/utils'
import { useCallback, useMemo } from 'react'
import { NumberType, useFormatter } from 'utils/formatNumbers'
interface CollectionAssetProps {
asset: GenieAsset
@ -31,6 +31,7 @@ export const CollectionAsset = ({
renderedHeight,
setRenderedHeight,
}: CollectionAssetProps) => {
const { formatEther } = useFormatter()
const bagManuallyClosed = useBag((state) => state.bagManuallyClosed)
const addAssetsToBag = useBag((state) => state.addAssetsToBag)
const removeAssetsFromBag = useBag((state) => state.removeAssetsFromBag)
@ -76,12 +77,23 @@ export const CollectionAsset = ({
primaryInfo: asset.name ? asset.name : `#${asset.tokenId}`,
primaryInfoIcon: asset.susFlag ? <SuspiciousContainer /> : null,
primaryInfoRight: asset.rarity && provider ? <RankingContainer provider={provider} /> : null,
secondaryInfo: notForSale ? '' : `${formatWeiToDecimal(asset.priceInfo.ETHPrice, true)} ETH`,
secondaryInfo: notForSale
? ''
: `${formatEther({ input: asset.priceInfo.ETHPrice, type: NumberType.NFTToken })} ETH`,
selectedInfo: <Trans>Remove from bag</Trans>,
notSelectedInfo: <Trans>Add to bag</Trans>,
disabledInfo: <Trans>Not listed</Trans>,
}
}, [asset.name, asset.priceInfo.ETHPrice, asset.rarity, asset.susFlag, asset.tokenId, notForSale, provider])
}, [
asset.name,
asset.priceInfo.ETHPrice,
asset.rarity,
asset.susFlag,
asset.tokenId,
formatEther,
notForSale,
provider,
])
return (
<NftCard

@ -8,11 +8,12 @@ import { themeVars } from 'nft/css/sprinkles.css'
import { useBag, useIsMobile } from 'nft/hooks'
import { useIsCollectionLoading } from 'nft/hooks/useIsCollectionLoading'
import { GenieCollection, TokenType } from 'nft/types'
import { floorFormatter, quantityFormatter, roundWholePercentage, volumeFormatter } from 'nft/utils/numbers'
import { roundWholePercentage } from 'nft/utils/numbers'
import { ReactNode, useEffect, useReducer, useRef, useState } from 'react'
import ReactMarkdown from 'react-markdown'
import styled, { css } from 'styled-components'
import { ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { DiscordIcon, EllipsisIcon, ExternalIcon, InstagramIcon, TwitterIcon, VerifiedIcon, XMarkIcon } from '../icons'
import * as styles from './CollectionStats.css'
@ -338,20 +339,30 @@ const statsLoadingSkeleton = (isMobile: boolean) =>
))
const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMobile?: boolean } & BoxProps) => {
const { formatNumberOrString, formatDelta } = useFormatter()
const uniqueOwnersPercentage = stats?.stats?.total_supply
? roundWholePercentage(((stats.stats.num_owners ?? 0) / stats.stats.total_supply) * 100)
: 0
const totalSupplyStr = stats.stats ? quantityFormatter(stats.stats.total_supply ?? 0) : 0
const totalSupplyStr = stats.stats
? formatNumberOrString({ input: stats.stats.total_supply ?? 0, type: NumberType.NFTCollectionStats })
: 0
const listedPercentageStr = stats?.stats?.total_supply
? roundWholePercentage(((stats.stats.total_listings ?? 0) / stats.stats.total_supply) * 100)
: 0
const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
// round daily volume & floorPrice to 3 decimals or less
const totalVolumeStr = volumeFormatter(Number(stats.stats?.total_volume) ?? 0)
const floorPriceStr = floorFormatter(stats.stats?.floor_price ?? 0)
const totalVolumeStr = formatNumberOrString({
input: Number(stats.stats?.total_volume) ?? 0,
type: NumberType.NFTCollectionStats,
})
const floorPriceStr = formatNumberOrString({
input: stats.stats?.floor_price ?? 0,
type: NumberType.NFTTokenFloorPrice,
})
// graphQL formatted %age values out of 100, whereas v3 endpoint did a decimal between 0 & 1
const floorChangeStr = Math.round(Math.abs(stats?.stats?.one_day_floor_change ?? 0))
const floorChangeStr = formatDelta(Math.round(Math.abs(stats?.stats?.one_day_floor_change ?? 0)))
const isBagExpanded = useBag((state) => state.bagExpanded)
const isScreenSize = useScreenSize()
@ -372,7 +383,7 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob
<StatsItem label="Floor 24H" shouldHide={false}>
<PercentChange isNegative={stats.stats.one_day_floor_change < 0}>
<DeltaArrow delta={stats?.stats?.one_day_floor_change} />
{floorChangeStr}%
{floorChangeStr}
</PercentChange>
</StatsItem>
) : null}

@ -4,7 +4,8 @@ import * as styles from 'nft/components/collection/FilterButton.css'
import { FilterIcon } from 'nft/components/icons'
import { buttonTextMedium } from 'nft/css/common.css'
import { breakpoints } from 'nft/css/sprinkles.css'
import { pluralize, putCommas } from 'nft/utils'
import { pluralize } from 'nft/utils'
import { NumberType, useFormatter } from 'utils/formatNumbers'
export const FilterButton = ({
onClick,
@ -17,6 +18,7 @@ export const FilterButton = ({
onClick: () => void
collectionCount?: number
}) => {
const { formatNumberOrString } = useFormatter()
const hideResultsCount = window.innerWidth >= breakpoints.sm && window.innerWidth < breakpoints.md
return (
@ -41,7 +43,10 @@ export const FilterButton = ({
{' '}
{!collectionCount || hideResultsCount
? 'Filter'
: `Filter • ${putCommas(collectionCount)} result${pluralize(collectionCount)}`}
: `Filter • ${formatNumberOrString({
input: collectionCount,
type: NumberType.WholeNumber,
})} result${pluralize(collectionCount)}`}
</Box>
) : null}
</Box>

@ -1,16 +1,17 @@
import 'rc-slider/assets/index.css'
import { BigNumber } from '@ethersproject/bignumber'
import { formatEther, parseEther } from '@ethersproject/units'
import { formatEther as ethersFormatEther, parseEther } from '@ethersproject/units'
import { Trans } from '@lingui/macro'
import { SweepFetcherParams, useSweepNftAssets } from 'graphql/data/nft/Asset'
import { useBag, useCollectionFilters } from 'nft/hooks'
import { GenieAsset, isPooledMarket, Markets } from 'nft/types'
import { calcPoolPrice, formatWeiToDecimal, isInSameSudoSwapPool } from 'nft/utils'
import { calcPoolPrice, isInSameSudoSwapPool } from 'nft/utils'
import { default as Slider } from 'rc-slider'
import { useEffect, useMemo, useReducer, useState } from 'react'
import styled, { useTheme } from 'styled-components'
import { ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
const SweepContainer = styled.div`
display: flex;
@ -160,6 +161,7 @@ interface SweepProps {
export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
const theme = useTheme()
const { formatEther } = useFormatter()
const [isItemsToggled, toggleSweep] = useReducer((state) => !state, true)
const [sweepAmount, setSweepAmount] = useState<string>('')
@ -374,8 +376,12 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
<StyledSlider
defaultValue={0}
min={0}
max={isItemsToggled ? sortedAssets?.length ?? 0 : parseFloat(formatEther(sortedAssetsTotalEth).toString())}
value={isItemsToggled ? sweepItemsInBag.length : parseFloat(formatWeiToDecimal(sweepEthPrice.toString()))}
max={
isItemsToggled
? sortedAssets?.length ?? 0
: parseFloat(ethersFormatEther(sortedAssetsTotalEth).toString())
}
value={isItemsToggled ? sweepItemsInBag.length : parseFloat(ethersFormatEther(sweepEthPrice.toString()))}
step={isItemsToggled ? 1 : 0.01}
trackStyle={{
top: '3px',
@ -424,9 +430,10 @@ export const Sweep = ({ contractAddress, minPrice, maxPrice }: SweepProps) => {
</SweepSubContainer>
</SweepLeftmostContainer>
<SweepRightmostContainer>
<ThemedText.SubHeader font-size="14px">{`${formatWeiToDecimal(
sweepEthPrice.toString()
)} ETH`}</ThemedText.SubHeader>
<ThemedText.SubHeader font-size="14px">{`${formatEther({
input: sweepEthPrice.toString(),
type: NumberType.NFTToken,
})} ETH`}</ThemedText.SubHeader>
<NftDisplay nfts={sweepItemsInBag} />
</SweepRightmostContainer>
</SweepContainer>

@ -1,4 +1,4 @@
import { formatEther } from '@ethersproject/units'
import { formatEther as ethersFormatEther } from '@ethersproject/units'
import { Trans } from '@lingui/macro'
import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events'
import { Trace, useTrace } from 'analytics'
@ -12,17 +12,11 @@ import { Overlay, stopPropagation } from 'nft/components/modals/Overlay'
import { themeVars, vars } from 'nft/css/sprinkles.css'
import { useIsMobile, useNativeUsdPrice, useSendTransaction, useTransactionResponse } from 'nft/hooks'
import { TxResponse, TxStateType } from 'nft/types'
import {
formatEthPrice,
formatUsdPrice,
formatUSDPriceWithCommas,
generateTweetForPurchase,
getSuccessfulImageSize,
parseTransactionResponse,
} from 'nft/utils'
import { generateTweetForPurchase, getSuccessfulImageSize, parseTransactionResponse } from 'nft/utils'
import { formatAssetEventProperties } from 'nft/utils/formatEventProperties'
import { useEffect, useMemo, useRef, useState } from 'react'
import styled from 'styled-components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
import * as styles from './TransactionCompleteModal.css'
@ -47,6 +41,7 @@ const UploadLink = styled.a`
const TxCompleteModal = () => {
const ethUsdPrice = useNativeUsdPrice()
const { formatEther, formatNumberOrString } = useFormatter()
const [showUnavailable, setShowUnavailable] = useState(false)
const txHash = useSendTransaction((state) => state.txHash)
const purchasedWithErc20 = useSendTransaction((state) => state.purchasedWithErc20)
@ -107,7 +102,7 @@ const TxCompleteModal = () => {
name={NFTEventName.NFT_BUY_BAG_SUCCEEDED}
properties={{
buy_quantity: nftsPurchased.length,
usd_value: parseFloat(formatEther(totalPurchaseValue)) * ethUsdPrice,
usd_value: parseFloat(ethersFormatEther(totalPurchaseValue)) * ethUsdPrice,
transaction_hash: txHash,
using_erc20: purchasedWithErc20,
...formatAssetEventProperties(nftsPurchased),
@ -168,7 +163,13 @@ const TxCompleteModal = () => {
<Box marginRight="16">
{nftsPurchased.length} NFT{nftsPurchased.length === 1 ? '' : 's'}
</Box>
<Box>{formatEthPrice(totalPurchaseValue.toString())} ETH</Box>
<Box>
{formatEther({
input: totalPurchaseValue.toString(),
type: NumberType.NFTToken,
})}{' '}
ETH
</Box>
</Row>
<a href={txHashUrl} target="_blank" rel="noreferrer" style={{ textDecoration: 'none' }}>
<Box color="neutral2" fontWeight="book">
@ -205,7 +206,13 @@ const TxCompleteModal = () => {
<p className={styles.subtitle}>Instant Refund</p>
<p className={styles.interStd}>
Uniswap returned{' '}
<span style={{ fontWeight: '535' }}>{formatEthPrice(totalRefundValue.toString())} ETH</span>{' '}
<span style={{ fontWeight: '535' }}>
{formatEther({
input: totalRefundValue.toString(),
type: NumberType.NFTToken,
})}{' '}
ETH
</span>{' '}
back to your wallet for unavailable items.
</p>
<Box
@ -217,9 +224,15 @@ const TxCompleteModal = () => {
position={{ sm: 'absolute', md: 'static' }}
>
<p className={styles.totalEthCost} style={{ marginBottom: '2px' }}>
{formatEthPrice(totalRefundValue.toString())} ETH
{formatEther({
input: totalRefundValue.toString(),
type: NumberType.NFTToken,
})}{' '}
ETH
</p>
<p className={styles.totalUsdRefund}>
{formatNumberOrString({ input: totalUSDRefund, type: NumberType.FiatNFTToken })}
</p>
<p className={styles.totalUsdRefund}>{formatUSDPriceWithCommas(totalUSDRefund)}</p>
<p className={styles.totalEthCost} style={{ width: '100%' }}>
for {nftsNotPurchased.length} unavailable item
{nftsNotPurchased.length === 1 ? '' : 's'}.
@ -280,8 +293,9 @@ const TxCompleteModal = () => {
`Selected item${
nftsPurchased.length === 1 ? ' is' : 's are'
} no longer available. Uniswap instantly refunded you for this incomplete transaction. `}
{formatUsdPrice(txFeeFiat)} was used for gas in attempt to complete this transaction. For support,
please visit our <a href="https://discord.gg/FCfyBSbCU5">Discord</a>
{formatNumberOrString({ input: txFeeFiat, type: NumberType.FiatNFTToken })} was used for gas in
attempt to complete this transaction. For support, please visit our{' '}
<a href="https://discord.gg/FCfyBSbCU5">Discord</a>
</p>
<Box className={styles.allUnavailableAssets}>
{nftsNotPurchased.length >= 3 && (
@ -324,9 +338,12 @@ const TxCompleteModal = () => {
<Box flexWrap="wrap" marginTop="4">
<Box marginLeft="4" width="full" display="flex">
<p className={styles.totalEthCost} style={{ marginBottom: '2px' }}>
{formatEthPrice(
asset.updatedPriceInfo ? asset.updatedPriceInfo.ETHPrice : asset.priceInfo.ETHPrice
)}{' '}
{formatEther({
input: asset.updatedPriceInfo
? asset.updatedPriceInfo.ETHPrice
: asset.priceInfo.ETHPrice,
type: NumberType.NFTToken,
})}{' '}
ETH
</p>
</Box>
@ -339,9 +356,15 @@ const TxCompleteModal = () => {
</Box>
{showUnavailable && <Box className={styles.fullRefundOverflowFade} />}
<p className={styles.totalEthCost} style={{ marginBottom: '2px' }}>
{formatEthPrice(totalRefundValue.toString())} ETH
{formatEther({
input: totalRefundValue.toString(),
type: NumberType.NFTToken,
})}{' '}
ETH
</p>
<p className={styles.totalUsdRefund}>
{formatNumberOrString({ input: totalUSDRefund, type: NumberType.FiatNFTToken })}
</p>
<p className={styles.totalUsdRefund}>{formatUSDPriceWithCommas(totalUSDRefund)}</p>
<Box className={styles.walletAddress} marginLeft="auto" marginRight="0">
<a href={txHashUrl} target="_blank" rel="noreferrer">
<Box className={styles.addressHash}>View on Etherscan</Box>

@ -4,11 +4,11 @@ import { LoadingBubble } from 'components/Tokens/loading'
import { EventCell } from 'nft/components/collection/ActivityCells'
import { ActivityEvent } from 'nft/types'
import { getMarketplaceIcon } from 'nft/utils'
import { formatEth } from 'nft/utils/currency'
import { getTimeDifference } from 'nft/utils/date'
import { ReactNode } from 'react'
import styled from 'styled-components'
import { shortenAddress } from 'utils'
import { NumberType, useFormatter } from 'utils/formatNumbers'
const TR = styled.tr`
border-bottom: ${({ theme }) => `1px solid ${theme.surface3}`};
@ -148,12 +148,15 @@ export const LoadingAssetActivity = ({ rowCount }: { rowCount: number }) => {
}
const AssetActivity = ({ events }: { events?: ActivityEvent[] }) => {
const { formatNumberOrString } = useFormatter()
return (
<ActivityTable>
{events &&
events.map((event, index) => {
const { eventTimestamp, eventType, fromAddress, marketplace, price, toAddress, transactionHash } = event
const formattedPrice = price ? formatEth(parseFloat(price ?? '')) : null
const formattedPrice = price
? formatNumberOrString({ input: parseFloat(price), type: NumberType.NFTToken })
: null
if (!eventType) return null
return (
<TR key={index}>

@ -10,15 +10,14 @@ import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails'
import { Center } from 'nft/components/Flex'
import { themeVars, vars } from 'nft/css/sprinkles.css'
import { ActivityEventType, CollectionInfoForAsset, GenieAsset } from 'nft/types'
import { formatEth } from 'nft/utils/currency'
import { isAudio } from 'nft/utils/isAudio'
import { isVideo } from 'nft/utils/isVideo'
import { putCommas } from 'nft/utils/putCommas'
import { useCallback, useMemo, useReducer, useState } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component'
import { Link as RouterLink } from 'react-router-dom'
import styled from 'styled-components'
import { shortenAddress } from 'utils/addresses'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import AssetActivity, { LoadingAssetActivity } from './AssetActivity'
import * as styles from './AssetDetails.css'
@ -243,6 +242,7 @@ interface AssetDetailsProps {
}
export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
const { formatNumberOrString } = useFormatter()
const [dominantColor] = useState<[number, number, number]>([0, 0, 0])
const { rarityProvider } = useMemo(
@ -281,7 +281,9 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
)
const weiPrice = gqlPriceData?.[0]?.price
const formattedPrice = weiPrice ? formatEth(parseFloat(weiPrice)) : undefined
const formattedPrice = weiPrice
? formatNumberOrString({ input: parseFloat(weiPrice), type: NumberType.NFTToken })
: undefined
const [activeFilters, filtersDispatch] = useReducer(reduceFilters, initialFilterState)
const Filter = useCallback(
@ -359,7 +361,9 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
}
placement="top"
>
<RarityWrap>Rarity: {putCommas(rarity.score)}</RarityWrap>
<RarityWrap>
Rarity: {formatNumberOrString({ input: rarity.score, type: NumberType.WholeNumber })}
</RarityWrap>
</MouseoverTooltip>
) : null
}

@ -7,18 +7,13 @@ import { useNftBalance } from 'graphql/data/nft/NftBalance'
import { CancelListingIcon, VerifiedIcon } from 'nft/components/icons'
import { useBag, useNativeUsdPrice, useProfilePageState, useSellAsset, useUsdPriceofNftAsset } from 'nft/hooks'
import { CollectionInfoForAsset, GenieAsset, ProfilePageStateType, WalletAsset } from 'nft/types'
import {
ethNumberStandardFormatter,
formatEthPrice,
generateTweetForAsset,
getMarketplaceIcon,
timeLeft,
} from 'nft/utils'
import { generateTweetForAsset, getMarketplaceIcon, timeLeft } from 'nft/utils'
import { useMemo } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import styled, { css, useTheme } from 'styled-components'
import { ExternalLink, ThemedText } from 'theme/components'
import { shortenAddress } from 'utils/addresses'
import { NumberType, useFormatter } from 'utils/formatNumbers'
const TWITTER_WIDTH = 560
const TWITTER_HEIGHT = 480
@ -213,6 +208,7 @@ const OwnerContainer = ({ asset }: { asset: WalletAsset }) => {
const setSellPageState = useProfilePageState((state) => state.setProfilePageState)
const selectSellAsset = useSellAsset((state) => state.selectSellAsset)
const resetSellAssets = useSellAsset((state) => state.reset)
const { formatEther, formatNumberOrString } = useFormatter()
const listing = asset.sellOrders && asset.sellOrders.length > 0 ? asset.sellOrders[0] : undefined
const expirationDate = listing?.endAt ? new Date(listing.endAt) : undefined
@ -249,11 +245,15 @@ const OwnerContainer = ({ asset }: { asset: WalletAsset }) => {
{listing ? (
<>
<ThemedText.MediumHeader fontSize="28px" lineHeight="36px">
{formatEthPrice(asset.priceInfo?.ETHPrice)} ETH
{formatEther({
input: asset.priceInfo?.ETHPrice,
type: NumberType.NFTToken,
})}{' '}
ETH
</ThemedText.MediumHeader>
{USDPrice && (
<ThemedText.BodySecondary lineHeight="24px">
{ethNumberStandardFormatter(USDPrice, true, true)}
{formatNumberOrString({ input: USDPrice, type: NumberType.FiatNFTToken })}
</ThemedText.BodySecondary>
)}
</>
@ -311,6 +311,7 @@ const NotForSale = ({ collectionName, collectionUrl }: { collectionName: string;
export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps) => {
const { account } = useWeb3React()
const { formatEther, formatNumberOrString } = useFormatter()
const cheapestOrder = asset.sellorders && asset.sellorders.length > 0 ? asset.sellorders[0] : undefined
const expirationDate = cheapestOrder?.endAt ? new Date(cheapestOrder.endAt) : undefined
@ -376,11 +377,11 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
</HeaderRow>
<PriceRow>
<ThemedText.MediumHeader fontSize="28px" lineHeight="36px">
{formatEthPrice(asset.priceInfo.ETHPrice)} ETH
{formatEther({ input: asset.priceInfo.ETHPrice, type: NumberType.NFTToken })} ETH
</ThemedText.MediumHeader>
{USDPrice && (
<ThemedText.BodySecondary lineHeight="24px">
{ethNumberStandardFormatter(USDPrice, true, true)}
{formatNumberOrString({ input: USDPrice, type: NumberType.FiatNFTToken })}
</ThemedText.BodySecondary>
)}
</PriceRow>

@ -1,11 +1,11 @@
import { OpacityHoverState } from 'components/Common'
import useCopyClipboard from 'hooks/useCopyClipboard'
import { CollectionInfoForAsset, GenieAsset } from 'nft/types'
import { putCommas } from 'nft/utils'
import { useCallback } from 'react'
import { Copy } from 'react-feather'
import styled from 'styled-components'
import { shortenAddress } from 'utils'
import { NumberType, useFormatter } from 'utils/formatNumbers'
const Details = styled.div`
display: grid;
@ -66,6 +66,7 @@ const GridItem = ({ header, body }: { header: string; body: React.ReactNode }) =
const stringShortener = (text: string) => `${text.substring(0, 4)}...${text.substring(text.length - 4, text.length)}`
const DetailsContainer = ({ asset, collection }: { asset: GenieAsset; collection: CollectionInfoForAsset }) => {
const { formatNumber } = useFormatter()
const { address, tokenId, tokenType, creator } = asset
const { totalSupply } = collection
@ -87,7 +88,10 @@ const DetailsContainer = ({ asset, collection }: { asset: GenieAsset; collection
<GridItem header="Token ID" body={tokenId.length > 9 ? stringShortener(tokenId) : tokenId} />
<GridItem header="Token standard" body={tokenType} />
<GridItem header="Blockchain" body="Ethereum" />
<GridItem header="Total supply" body={`${putCommas(totalSupply ?? 0)}`} />
<GridItem
header="Total supply"
body={`${formatNumber({ input: totalSupply ?? 0, type: NumberType.WholeNumber })}`}
/>
<GridItem
header="Creator"
body={

@ -3,7 +3,6 @@ import { LoadingBubble } from 'components/Tokens/loading'
import { useCollection } from 'graphql/data/nft/Collection'
import { UniswapMagentaIcon, VerifiedIcon } from 'nft/components/icons'
import { Markets, TrendingCollection } from 'nft/types'
import { ethNumberStandardFormatter } from 'nft/utils'
import styled from 'styled-components'
import { ThemedText } from 'theme/components/text'
import { NumberType, useFormatter } from 'utils/formatNumbers'
@ -239,6 +238,7 @@ const MARKETS_ENUM_TO_NAME = {
export const CarouselCard = ({ collection, onClick }: CarouselCardProps) => {
const { data: gqlCollection, loading } = useCollection(collection.address ?? '')
const { formatNumber } = useFormatter()
if (loading) return <LoadingCarouselCard />
@ -257,7 +257,7 @@ export const CarouselCard = ({ collection, onClick }: CarouselCardProps) => {
<TableElement>
{collection.floor && (
<ThemedText.SubHeaderSmall color="userThemeColor">
{ethNumberStandardFormatter(collection.floor)} ETH Floor
{formatNumber({ input: collection.floor, type: NumberType.NFTToken })} ETH Floor
</ThemedText.SubHeaderSmall>
)}
</TableElement>

@ -3,10 +3,10 @@ import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta'
import { VerifiedIcon } from 'nft/components/icons'
import { useIsMobile } from 'nft/hooks'
import { Denomination } from 'nft/types'
import { ethNumberStandardFormatter, volumeFormatter } from 'nft/utils'
import { ReactNode } from 'react'
import styled from 'styled-components'
import { ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import * as styles from './Cells.css'
@ -93,9 +93,12 @@ export const CollectionTitleCell = ({ value }: CellProps) => {
)
}
export const DiscreteNumberCell = ({ value }: CellProps) => (
<span>{value.value ? volumeFormatter(value.value) : '-'}</span>
export const DiscreteNumberCell = ({ value }: CellProps) => {
const { formatNumberOrString } = useFormatter()
return (
<span>{value.value ? formatNumberOrString({ input: value.value, type: NumberType.NFTCollectionStats }) : '-'}</span>
)
}
const getDenominatedValue = (denomination: Denomination, inWei: boolean, value?: number, usdPrice?: number) => {
if (denomination === Denomination.ETH) return value
@ -113,12 +116,14 @@ export const EthCell = ({
denomination: Denomination
usdPrice?: number
}) => {
const { formatNumberOrString } = useFormatter()
const denominatedValue = getDenominatedValue(denomination, false, value, usdPrice)
const formattedValue = denominatedValue
? denomination === Denomination.ETH
? ethNumberStandardFormatter(denominatedValue.toString(), false, true, false) + ' ETH'
: ethNumberStandardFormatter(denominatedValue, true, false, true)
: '-'
const ethDenomination = denomination === Denomination.ETH
const formattedValue =
formatNumberOrString({
input: denominatedValue,
type: ethDenomination ? NumberType.NFTToken : NumberType.FiatTokenStats,
}) + (ethDenomination ? ' ETH' : '')
const isMobile = useIsMobile()
const TextComponent = isMobile ? ThemedText.BodySmall : ThemedText.BodyPrimary
@ -141,17 +146,19 @@ export const VolumeCell = ({
denomination: Denomination
usdPrice?: number
}) => {
const { formatNumberOrString } = useFormatter()
const denominatedValue = getDenominatedValue(denomination, false, value, usdPrice)
const ethDenomination = denomination === Denomination.ETH
const formattedValue = denominatedValue
? denomination === Denomination.ETH
? ethNumberStandardFormatter(denominatedValue.toString(), false, false, true) + ' ETH'
: ethNumberStandardFormatter(denominatedValue, true, false, true)
: '-'
const formattedValue =
formatNumberOrString({
input: denominatedValue,
type: ethDenomination ? NumberType.WholeNumber : NumberType.FiatTokenStats,
}) + (ethDenomination ? ' ETH' : '')
return (
<EthContainer>
<ThemedText.BodyPrimary>{value ? formattedValue : '-'}</ThemedText.BodyPrimary>
<ThemedText.BodyPrimary>{formattedValue}</ThemedText.BodyPrimary>
</EthContainer>
)
}

@ -6,6 +6,7 @@ import { CollectionTableColumn, Denomination, TimePeriod, VolumeType } from 'nft
import { useMemo, useState } from 'react'
import styled from 'styled-components'
import { ThemedText } from 'theme/components'
import { useFormatterLocales } from 'utils/formatNumbers'
import CollectionTable from './CollectionTable'
@ -84,6 +85,7 @@ function convertTimePeriodToHistoryDuration(timePeriod: TimePeriod): HistoryDura
}
const TrendingCollections = () => {
const { formatterLocalCurrency } = useFormatterLocales()
const [timePeriod, setTimePeriod] = useState<TimePeriod>(TimePeriod.OneDay)
const [isEthToggled, setEthToggled] = useState(true)
@ -151,7 +153,7 @@ const TrendingCollections = () => {
</Selector>
<Selector active={!isEthToggled}>
<StyledSelectorText lineHeight="20px" active={!isEthToggled}>
USD
{formatterLocalCurrency}
</StyledSelectorText>
</Selector>
</Filter>

@ -1,8 +1,12 @@
import { isNumber } from 'nft/utils/numbers'
import { FormEvent, forwardRef } from 'react'
import { Box, BoxProps } from '../Box'
const isNumber = (s: string): boolean => {
const reg = /^-?\d+\.?\d*$/
return reg.test(s) && !isNaN(parseFloat(s)) && isFinite(parseFloat(s))
}
export const Input = forwardRef<HTMLInputElement, BoxProps>((props, ref) => (
<Box
ref={ref}

@ -18,7 +18,6 @@ import { useIsMobile, useNFTList, useProfilePageState, useSellAsset } from 'nft/
import { LIST_PAGE_MARGIN, LIST_PAGE_MARGIN_MOBILE } from 'nft/pages/profile/shared'
import { looksRareNonceFetcher } from 'nft/queries/looksRare'
import { ProfilePageStateType } from 'nft/types'
import { formatEth } from 'nft/utils'
import { ListingMarkets } from 'nft/utils/listNfts'
import { useEffect, useMemo, useReducer, useState } from 'react'
import { ArrowLeft } from 'react-feather'
@ -183,6 +182,7 @@ const EthValueWrapper = styled.span<{ totalEthListingValue: boolean }>`
`
export const ListPage = () => {
const { formatNumberOrString } = useFormatter()
const { setProfilePageState: setSellPageState } = useProfilePageState()
const { provider, chainId } = useWeb3React()
const isMobile = useIsMobile()
@ -295,7 +295,10 @@ export const ListPage = () => {
<ProceedsAndButtonWrapper>
<ProceedsWrapper>
<EthValueWrapper totalEthListingValue={!!totalEthListingValue}>
{totalEthListingValue > 0 ? formatEth(totalEthListingValue) : '-'} ETH
{totalEthListingValue > 0
? formatNumberOrString({ input: totalEthListingValue, type: NumberType.NFTToken })
: '-'}{' '}
ETH
</EthValueWrapper>
{!!usdcValue && <UsdValue>{usdcAmount}</UsdValue>}
</ProceedsWrapper>

@ -8,11 +8,11 @@ import { useSellAsset } from 'nft/hooks'
import { useNativeUsdPrice } from 'nft/hooks/useUsdPrice'
import { ListingMarket, WalletAsset } from 'nft/types'
import { getMarketplaceIcon } from 'nft/utils'
import { formatEth, formatUsdPrice } from 'nft/utils/currency'
import { Dispatch, DispatchWithoutAction, useCallback, useEffect, useMemo, useReducer, useState } from 'react'
import styled from 'styled-components'
import { BREAKPOINTS } from 'theme'
import { ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { PriceTextInput } from './PriceTextInput'
import { RoyaltyTooltip } from './RoyaltyTooltip'
@ -126,6 +126,7 @@ export const MarketplaceRow = ({
toggleExpandMarketplaceRows,
rowHovered,
}: MarketplaceRowProps) => {
const { formatNumberOrString, formatDelta } = useFormatter()
const setAssetListPrice = useSellAsset((state) => state.setAssetListPrice)
const removeAssetMarketplace = useSellAsset((state) => state.removeAssetMarketplace)
const [marketIconHovered, toggleMarketIconHovered] = useReducer((s) => !s, false)
@ -184,12 +185,17 @@ export const MarketplaceRow = ({
<Row onMouseEnter={toggleMarketRowHovered} onMouseLeave={toggleMarketRowHovered}>
<FloorPriceInfo>
<ThemedText.BodyPrimary color="neutral2" lineHeight="24px">
{asset.floorPrice ? `${asset.floorPrice.toFixed(3)} ETH` : '-'}
{formatNumberOrString({
input: asset.floorPrice,
type: NumberType.NFTToken,
}) + asset.floorPrice
? ' ETH'
: ''}
</ThemedText.BodyPrimary>
</FloorPriceInfo>
<LastPriceInfo>
<ThemedText.BodyPrimary color="neutral2" lineHeight="24px">
{asset.lastPrice ? `${asset.lastPrice.toFixed(3)} ETH` : '-'}
{asset.lastPrice ? `${formatNumberOrString({ input: asset.lastPrice, type: NumberType.NFTToken })} ETH` : '-'}
</ThemedText.BodyPrimary>
</LastPriceInfo>
@ -235,7 +241,7 @@ export const MarketplaceRow = ({
>
<FeeWrapper>
<ThemedText.BodyPrimary color="neutral2">
{fees > 0 ? `${fees.toFixed(2)}${selectedMarkets.length > 1 ? t`% max` : '%'}` : '--%'}
{fees > 0 ? `${formatDelta(fees)}${selectedMarkets.length > 1 ? t`max` : ''}` : '--%'}
</ThemedText.BodyPrimary>
</FeeWrapper>
</MouseoverTooltip>
@ -249,6 +255,7 @@ export const MarketplaceRow = ({
}
const EthPriceDisplay = ({ ethPrice = 0 }: { ethPrice?: number }) => {
const { formatNumberOrString } = useFormatter()
const ethUsdPrice = useNativeUsdPrice()
return (
@ -256,8 +263,10 @@ const EthPriceDisplay = ({ ethPrice = 0 }: { ethPrice?: number }) => {
<ThemedText.BodyPrimary lineHeight="24px" color={ethPrice ? 'neutral1' : 'neutral2'} textAlign="right">
{ethPrice !== 0 ? (
<Column>
<span>{formatEth(ethPrice)} ETH</span>
<ThemedText.BodyPrimary color="neutral2">{formatUsdPrice(ethPrice * ethUsdPrice)}</ThemedText.BodyPrimary>
<span>{formatNumberOrString({ input: ethPrice, type: NumberType.NFTToken })} ETH</span>
<ThemedText.BodyPrimary color="neutral2">
{formatNumberOrString({ input: ethPrice * ethUsdPrice, type: NumberType.FiatNFTToken })}
</ThemedText.BodyPrimary>
</Column>
) : (
'- ETH'

@ -10,6 +10,7 @@ import styled, { useTheme } from 'styled-components'
import { BREAKPOINTS } from 'theme'
import { ThemedText } from 'theme/components'
import { Z_INDEX } from 'theme/zIndex'
import { useFormatter } from 'utils/formatNumbers'
const ModalWrapper = styled(Column)`
position: fixed;
@ -82,6 +83,7 @@ export const BelowFloorWarningModal = ({
startListing: () => void
}) => {
const theme = useTheme()
const { formatDelta } = useFormatter()
const clickContinue = (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
@ -103,10 +105,9 @@ export const BelowFloorWarningModal = ({
<ThemedText.BodyPrimary textAlign="center">
<Plural
value={listingsBelowFloor.length !== 1 ? 2 : 1}
_1={t`One NFT is listed ${(
(1 - (listingsBelowFloor[0][1].price ?? 0) / (listingsBelowFloor[0][0].floorPrice ?? 0)) *
100
).toFixed(0)}% `}
_1={t`One NFT is listed ${formatDelta(
(1 - (listingsBelowFloor[0][1].price ?? 0) / (listingsBelowFloor[0][0].floorPrice ?? 0)) * 100
)} `}
other={t`${listingsBelowFloor.length} NFTs are listed significantly `}
/>
&nbsp;

@ -8,7 +8,7 @@ import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { getTotalEthValue } from 'nft/components/profile/list/utils'
import { useSellAsset } from 'nft/hooks'
import { formatEth, generateTweetForList, pluralize } from 'nft/utils'
import { generateTweetForList, pluralize } from 'nft/utils'
import { useMemo } from 'react'
import { Twitter, X } from 'react-feather'
import styled, { css, useTheme } from 'styled-components'
@ -77,6 +77,7 @@ const TweetRow = styled(Row)`
export const SuccessScreen = ({ overlayClick }: { overlayClick: () => void }) => {
const theme = useTheme()
const { formatNumberOrString } = useFormatter()
const sellAssets = useSellAsset((state) => state.sellAssets)
const { chainId } = useWeb3React()
const nativeCurrency = useNativeCurrency(chainId)
@ -109,7 +110,9 @@ export const SuccessScreen = ({ overlayClick }: { overlayClick: () => void }) =>
<Trans>Proceeds if sold</Trans>
</ThemedText.SubHeader>
<ProceedsColumn>
<ThemedText.SubHeader>{formatEth(totalEthListingValue)} ETH</ThemedText.SubHeader>
<ThemedText.SubHeader>
{formatNumberOrString({ input: totalEthListingValue, type: NumberType.NFTToken })} ETH
</ThemedText.SubHeader>
{usdcValue && (
<ThemedText.BodySmall lineHeight="20px" color="neutral2">
{formatCurrencyAmount({

@ -7,12 +7,12 @@ import { useUpdateInputAndWarnings } from 'nft/components/profile/list/utils'
import { body } from 'nft/css/common.css'
import { useSellAsset } from 'nft/hooks'
import { WalletAsset } from 'nft/types'
import { formatEth } from 'nft/utils/currency'
import { Dispatch, useRef, useState } from 'react'
import { AlertTriangle, Link } from 'react-feather'
import styled, { useTheme } from 'styled-components'
import { BREAKPOINTS } from 'theme'
import { colors } from 'theme/colors'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { WarningType } from './shared'
@ -104,6 +104,7 @@ export const PriceTextInput = ({
globalOverride,
asset,
}: PriceTextInputProps) => {
const { formatNumberOrString, formatDelta } = useFormatter()
const [warningType, setWarningType] = useState(WarningType.NONE)
const removeSellAsset = useSellAsset((state) => state.removeSellAsset)
const showResolveIssues = useSellAsset((state) => state.showResolveIssues)
@ -159,10 +160,14 @@ export const PriceTextInput = ({
<WarningRow>
<AlertTriangle height={16} width={16} color={warningColor} />
<span>
{warningType === WarningType.BELOW_FLOOR && `${percentBelowFloor.toFixed(0)}% `}
{warningType === WarningType.BELOW_FLOOR && `${formatDelta(percentBelowFloor)} `}
{getWarningMessage(warningType)}
&nbsp;
{warningType === WarningType.ALREADY_LISTED && `${formatEth(asset?.floor_sell_order_price ?? 0)} ETH`}
{warningType === WarningType.ALREADY_LISTED &&
`${formatNumberOrString({
input: asset?.floor_sell_order_price ?? 0,
type: NumberType.NFTToken,
})} ETH`}
</span>
<WarningAction
onClick={() => {

@ -3,9 +3,10 @@ import Column from 'components/Column'
import Row from 'components/Row'
import { getRoyalty } from 'nft/components/profile/list/utils'
import { ListingMarket, WalletAsset } from 'nft/types'
import { formatEth, getMarketplaceIcon } from 'nft/utils'
import { getMarketplaceIcon } from 'nft/utils'
import styled, { css } from 'styled-components'
import { ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
const FeeWrap = styled(Row)`
margin-bottom: 4px;
@ -56,7 +57,8 @@ export const RoyaltyTooltip = ({
asset: WalletAsset
fees?: number
}) => {
const maxRoyalty = Math.max(...selectedMarkets.map((market) => getRoyalty(market, asset) ?? 0)).toFixed(2)
const { formatNumberOrString, formatDelta } = useFormatter()
const maxRoyalty = Math.max(...selectedMarkets.map((market) => getRoyalty(market, asset) ?? 0))
return (
<RoyaltyContainer>
{selectedMarkets.map((market) => (
@ -68,7 +70,7 @@ export const RoyaltyTooltip = ({
<Trans>fee</Trans>
</ThemedText.BodySmall>
</Row>
<FeePercent>{market.fee}%</FeePercent>
<FeePercent>{formatDelta(market.fee)}</FeePercent>
</FeeWrap>
))}
<FeeWrap>
@ -85,7 +87,7 @@ export const RoyaltyTooltip = ({
<Trans>Max fees</Trans>
</ThemedText.BodySmall>
<ThemedText.BodySmall lineHeight="16px" color={fees ? 'neutral1' : 'neutral2'}>
{fees ? formatEth(fees) : '-'} ETH
{fees ? formatNumberOrString({ input: fees, type: NumberType.NFTToken }) : '-'} ETH
</ThemedText.BodySmall>
</MaxFeeContainer>
</RoyaltyContainer>

@ -1,7 +1,7 @@
import { Percent } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { ClassicTrade } from 'state/routing/types'
import { useTheme } from 'styled-components'
import { useFormatter } from 'utils/formatNumbers'
import { computeRealizedPriceImpact, getPriceImpactWarning } from 'utils/prices'
export interface PriceImpact {
@ -16,6 +16,7 @@ interface PriceImpactSeverity {
export function usePriceImpact(trade?: ClassicTrade): PriceImpact | undefined {
const theme = useTheme()
const { formatPercent } = useFormatter()
return useMemo(() => {
const marketPriceImpact = trade ? computeRealizedPriceImpact(trade) : undefined
@ -33,18 +34,8 @@ export function usePriceImpact(trade?: ClassicTrade): PriceImpact | undefined {
type: priceImpactWarning,
color: warningColor,
},
displayPercentage: () => toHumanReadablePercent(marketPriceImpact),
displayPercentage: () => formatPercent(marketPriceImpact),
}
: undefined
}, [theme.critical, theme.deprecated_accentWarning, trade])
}
function toHumanReadablePercent(priceImpact: Percent): string {
const sign = priceImpact.lessThan(0) ? '+' : ''
const exactFloat = (Number(priceImpact.numerator) / Number(priceImpact.denominator)) * 100
if (exactFloat < 0.005) {
return '0.00%'
}
const number = parseFloat(priceImpact.multiply(-1)?.toFixed(2))
return `${sign}${number}%`
}, [formatPercent, theme.critical, theme.deprecated_accentWarning, trade])
}

@ -1,10 +1,8 @@
import { parseEther } from 'ethers/lib/utils'
import { ActivityEvent, GenieAsset } from 'nft/types'
import { formatEth } from './currency'
export const buildActivityAsset = (event: ActivityEvent, collectionName: string, ethPriceInUSD: number): GenieAsset => {
const assetUsdPrice = event.price ? formatEth(parseFloat(event.price) * ethPriceInUSD) : '0'
const assetUsdPrice = event.price ? parseFloat(event.price) * ethPriceInUSD : '0'
const weiPrice = event.price ? parseEther(event.price) : ''

@ -1,74 +1,3 @@
import { formatEther } from '@ethersproject/units'
export const formatUsdPrice = (price: number) => {
if (price > 1000000) {
return `$${(price / 1000000).toFixed(1)}M`
} else if (price > 1000) {
return `$${(price / 1000).toFixed(1)}K`
} else {
return `$${price.toFixed(2)}`
}
}
export const formatEth = (price: number) => {
if (price > 1000000) {
return `${Math.round(price / 1000000)}M`
} else if (price > 1000) {
return `${Math.round(price / 1000)}K`
} else if (price < 0.001) {
return '<0.001'
} else {
return `${Math.round(price * 1000 + Number.EPSILON) / 1000}`
}
}
export const formatUSDPriceWithCommas = (price: number) => {
return `$${Math.round(price)
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`
}
export const formatEthPrice = (price: string | undefined) => {
if (!price) return 0
const formattedPrice = parseFloat(formatEther(String(price)))
return (
Math.round(formattedPrice * (formattedPrice >= 1 ? 100 : 1000) + Number.EPSILON) /
(formattedPrice >= 1 ? 100 : 1000)
)
}
export const ethNumberStandardFormatter = (
amount: string | number | undefined,
includeDollarSign = false,
removeZeroes = false,
roundToNearestWholeNumber = false
): string => {
if (!amount) return '-'
const amountInDecimals = parseFloat(amount.toString())
const conditionalDollarSign = includeDollarSign ? '$' : ''
if (amountInDecimals <= 0) return '-'
if (amountInDecimals < 0.0001) return `< ${conditionalDollarSign}0.00001`
if (amountInDecimals < 1) return `${conditionalDollarSign}${parseFloat(amountInDecimals.toFixed(3))}`
const formattedPrice = (
removeZeroes
? parseFloat(amountInDecimals.toFixed(2))
: roundToNearestWholeNumber
? Math.round(amountInDecimals)
: amountInDecimals.toFixed(2)
)
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
return conditionalDollarSign + formattedPrice
}
export const formatWeiToDecimal = (amount: string, removeZeroes = false) => {
if (!amount) return '-'
return ethNumberStandardFormatter(formatEther(amount), false, removeZeroes, false)
}
// prevent BigNumber overflow by properly handling scientific notation and comma delimited values
export function wrapScientificNotation(value: string | number): string {
return parseFloat(value.toString())

@ -10,21 +10,11 @@ export { blocklistedCollections } from './blocklist'
export { buildNftTradeInputFromBagItems } from './buildSellObject'
export { calculateCardIndex, calculateFirstCardIndex, calculateRank } from './carousel'
export { isInSameMarketplaceCollection, isInSameSudoSwapPool } from './collection'
export {
ethNumberStandardFormatter,
formatEth,
formatEthPrice,
formatUsdPrice,
formatUSDPriceWithCommas,
formatWeiToDecimal,
wrapScientificNotation,
} from './currency'
export { wrapScientificNotation } from './currency'
export { formatAssetEventProperties } from './formatEventProperties'
export { isAudio } from './isAudio'
export { isVideo } from './isVideo'
export { floorFormatter, volumeFormatter } from './numbers'
export { calcAvgGroupPoolPrice, calcPoolPrice, recalculateBagUsingPooledAssets } from './pooledAssets'
export { putCommas } from './putCommas'
export { pluralize, roundAndPluralize } from './roundAndPluralize'
export { timeLeft } from './time'
export { getSuccessfulImageSize, parseTransactionResponse } from './transactionResponse'

@ -1,104 +1,3 @@
import { DEFAULT_LOCALE } from 'constants/locales'
import numbro from 'numbro'
export const isNumber = (s: string): boolean => {
const reg = /^-?\d+\.?\d*$/
return reg.test(s) && !isNaN(parseFloat(s)) && isFinite(parseFloat(s))
}
export const floorFormatter = (n: number): string => {
if (n === 0) return '0.00'
if (!n) return ''
if (n < 0.001) {
return '<0.001'
}
if (n >= 0.001 && n < 1) {
return `${parseFloat(n.toFixed(3)).toLocaleString(DEFAULT_LOCALE, {
minimumFractionDigits: 1,
maximumFractionDigits: 3,
})}`
}
if (n >= 1 && n < 1e6) {
return `${parseFloat(n.toPrecision(6)).toLocaleString(DEFAULT_LOCALE, {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
})}`
}
if (n >= 1e6 && n < 1e15) {
return numbro(n)
.format({
average: true,
mantissa: 2,
optionalMantissa: true,
abbreviations: {
million: 'M',
billion: 'B',
trillion: 'T',
},
})
.toUpperCase()
}
if (n >= 1e15) {
return `${n.toExponential(3).replace(/(\.[0-9]*[1-9])0*|(\.0*)/, '$1')}`
}
return `${Number(n.toFixed(2)).toLocaleString(DEFAULT_LOCALE, { minimumFractionDigits: 2 })}`
}
export const volumeFormatter = (n: number): string => {
if (n === 0) return '0.00'
if (!n) return ''
if (n < 0.01) {
return '<0.01'
}
if (n >= 0.01 && n < 1) {
return `${parseFloat(n.toFixed(2)).toLocaleString(DEFAULT_LOCALE)}`
}
if (n >= 1 && n < 1000) {
return `${Number(Math.round(n).toLocaleString(DEFAULT_LOCALE))}`
}
if (n >= 1000) {
return numbro(n)
.format({
average: true,
mantissa: 1,
optionalMantissa: true,
abbreviations: {
thousand: 'K',
million: 'M',
billion: 'B',
trillion: 'T',
},
})
.toUpperCase()
}
return `${Number(n.toFixed(1)).toLocaleString(DEFAULT_LOCALE, { minimumFractionDigits: 1 })}`
}
export const quantityFormatter = (n: number): string => {
if (n === 0) return '0.00'
if (!n) return ''
if (n >= 1 && n < 1000) {
return `${Number(Math.round(n).toLocaleString(DEFAULT_LOCALE))}`
}
if (n >= 1000) {
return numbro(n)
.format({
average: true,
mantissa: 1,
thousandSeparated: true,
optionalMantissa: true,
abbreviations: {
thousand: 'K',
million: 'M',
billion: 'B',
trillion: 'T',
},
})
.toUpperCase()
}
return `${Number(n.toFixed(2)).toLocaleString(DEFAULT_LOCALE, { minimumFractionDigits: 2 })}`
}
export const roundWholePercentage = (n: number): string => {
if (n === 0) return '0'
if (!n) return ''

@ -1,8 +0,0 @@
export const putCommas = (value: number) => {
try {
if (!value) return value
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
} catch (err) {
return value
}
}

@ -1,10 +1,6 @@
import { formatEther } from '@ethersproject/units'
import { BuyItem, GenieAsset, isPooledMarket, Markets, PriceInfo, RoutingItem, UpdatedGenieAsset } from 'nft/types'
import {
calcAvgGroupPoolPrice,
formatWeiToDecimal,
isInSameMarketplaceCollection,
isInSameSudoSwapPool,
} from 'nft/utils'
import { calcAvgGroupPoolPrice, isInSameMarketplaceCollection, isInSameSudoSwapPool } from 'nft/utils'
const isTheSame = (item: GenieAsset, routeAsset: BuyItem | PriceInfo) => {
// if route asset has id, match by id
@ -21,7 +17,7 @@ const isTheSame = (item: GenieAsset, routeAsset: BuyItem | PriceInfo) => {
const getPriceDiff = (oldPrice: string, newPrice: string): { hasPriceDiff: boolean; hasVisiblePriceDiff: boolean } => {
const hasPriceDiff = oldPrice !== newPrice
const hasVisiblePriceDiff = formatWeiToDecimal(oldPrice) !== formatWeiToDecimal(newPrice)
const hasVisiblePriceDiff = formatEther(oldPrice) !== formatEther(newPrice)
return { hasPriceDiff, hasVisiblePriceDiff }
}

@ -1,3 +1,4 @@
import { formatEther as ethersFormatEther } from '@ethersproject/units'
import { Currency, CurrencyAmount, Percent, Price, Token } from '@uniswap/sdk-core'
import {
DEFAULT_LOCAL_CURRENCY,
@ -67,11 +68,23 @@ const THREE_DECIMALS_CURRENCY: NumberFormatOptions = {
style: 'currency',
}
const THREE_DECIMALS_NO_TRAILING_ZEROS_CURRENCY: NumberFormatOptions = {
...THREE_DECIMALS_NO_TRAILING_ZEROS,
currency: 'USD',
style: 'currency',
}
const TWO_DECIMALS_NO_TRAILING_ZEROS: NumberFormatOptions = {
notation: 'standard',
maximumFractionDigits: 2,
}
const TWO_DECIMALS_NO_TRAILING_ZEROS_CURRENCY: NumberFormatOptions = {
...TWO_DECIMALS_NO_TRAILING_ZEROS,
currency: 'USD',
style: 'currency',
}
const TWO_DECIMALS: NumberFormatOptions = {
notation: 'standard',
maximumFractionDigits: 2,
@ -97,6 +110,12 @@ const SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS: NumberFormatOptions = {
maximumFractionDigits: 2,
}
const SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS_CURRENCY: NumberFormatOptions = {
...SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS,
currency: 'USD',
style: 'currency',
}
const SHORTHAND_ONE_DECIMAL: NumberFormatOptions = {
notation: 'compact',
minimumFractionDigits: 1,
@ -317,6 +336,42 @@ const ntfCollectionStatsFormatter: FormatterRule[] = [
{ upperBound: Infinity, formatterOptions: SHORTHAND_ONE_DECIMAL },
]
const nftTokenFormatter: FormatterRule[] = [
{ exact: 0, hardCodedInput: { hardcodedOutput: '-' }, formatterOptions: FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN },
{
upperBound: 0.0001,
hardCodedInput: { input: 0.0001, prefix: '<' },
formatterOptions: FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN,
},
{ upperBound: 1.0, formatterOptions: THREE_DECIMALS },
{ upperBound: 1000, formatterOptions: TWO_DECIMALS_NO_TRAILING_ZEROS },
{ upperBound: 1e15, formatterOptions: SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS },
{
upperBound: Infinity,
hardCodedInput: { input: 999_000_000_000_000, prefix: '>' },
formatterOptions: SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS,
},
]
const fiatNftTokenFormatter: FormatterRule[] = [
{ exact: 0, hardCodedInput: { hardcodedOutput: '-' }, formatterOptions: NO_DECIMALS },
{
upperBound: 0.0001,
hardCodedInput: { input: 0.0001, prefix: '<' },
formatterOptions: ONE_SIG_FIG_CURRENCY,
},
{ upperBound: 1.0, formatterOptions: THREE_DECIMALS_NO_TRAILING_ZEROS_CURRENCY },
{ upperBound: 1000, formatterOptions: TWO_DECIMALS_NO_TRAILING_ZEROS_CURRENCY },
{ upperBound: 1e15, formatterOptions: SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS_CURRENCY },
{
upperBound: Infinity,
hardCodedInput: { input: 999_000_000_000_000, prefix: '>' },
formatterOptions: SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS_CURRENCY,
},
]
const wholeNumberFormatter: FormatterRule[] = [{ upperBound: Infinity, formatterOptions: NO_DECIMALS }]
export enum NumberType {
// used for token quantities in non-transaction contexts (e.g. portfolio balances)
TokenNonTx = 'token-non-tx',
@ -360,6 +415,15 @@ export enum NumberType {
// nft floor price with trailing zeros
NFTTokenFloorPriceTrailingZeros = 'nft-token-floor-price-trailing-zeros',
// nft token price in currency
NFTToken = 'nft-token',
// nft token price in local fiat currency
FiatNFTToken = 'fiat-nft-token',
// whole number formatting
WholeNumber = 'whole-number',
}
type FormatterType = NumberType | FormatterRule[]
@ -378,6 +442,9 @@ const TYPE_TO_FORMATTER_RULES = {
[NumberType.NFTTokenFloorPrice]: ntfTokenFloorPriceFormatter,
[NumberType.NFTTokenFloorPriceTrailingZeros]: ntfTokenFloorPriceFormatterTrailingZeros,
[NumberType.NFTCollectionStats]: ntfCollectionStatsFormatter,
[NumberType.NFTToken]: nftTokenFormatter,
[NumberType.FiatNFTToken]: fiatNftTokenFormatter,
[NumberType.WholeNumber]: wholeNumberFormatter,
}
function getFormatterRule(input: number, type: FormatterType, conversionRate?: number): FormatterRule {
@ -563,6 +630,18 @@ function formatNumberOrString({
return formatNumber({ input, type, locale, localCurrency, conversionRate })
}
interface FormatEtherOptions {
input: Nullish<number | string>
type: FormatterType
locale?: SupportedLocale
localCurrency?: SupportedLocalCurrency
}
function formatEther({ input, type, locale, localCurrency }: FormatEtherOptions) {
if (input === null || input === undefined) return '-'
return formatNumber({ input: parseFloat(ethersFormatEther(input.toString())), type, locale, localCurrency })
}
interface FormatFiatPriceOptions {
price: Nullish<number | string>
type?: FormatterType
@ -733,9 +812,20 @@ export function useFormatter() {
[formatterLocale]
)
const formatEtherwithLocales = useCallback(
(options: Omit<FormatEtherOptions, LocalesType>) =>
formatEther({
...options,
locale: formatterLocale,
localCurrency: currencyToFormatWith,
}),
[currencyToFormatWith, formatterLocale]
)
return useMemo(
() => ({
formatCurrencyAmount: formatCurrencyAmountWithLocales,
formatEther: formatEtherwithLocales,
formatFiatPrice: formatFiatPriceWithLocales,
formatNumber: formatNumberWithLocales,
formatNumberOrString: formatNumberOrStringWithLocales,
@ -747,6 +837,7 @@ export function useFormatter() {
}),
[
formatCurrencyAmountWithLocales,
formatEtherwithLocales,
formatFiatPriceWithLocales,
formatNumberOrStringWithLocales,
formatNumberWithLocales,