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

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

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

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

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

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

@ -26,13 +26,12 @@ import {
} from 'nft/types' } from 'nft/types'
import { getMarketplaceIcon } from 'nft/utils' import { getMarketplaceIcon } from 'nft/utils'
import { buildActivityAsset } from 'nft/utils/buildActivityAsset' import { buildActivityAsset } from 'nft/utils/buildActivityAsset'
import { formatEth } from 'nft/utils/currency'
import { getTimeDifference } from 'nft/utils/date' import { getTimeDifference } from 'nft/utils/date'
import { putCommas } from 'nft/utils/putCommas'
import { MouseEvent, ReactNode, useMemo, useState } from 'react' import { MouseEvent, ReactNode, useMemo, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { ExternalLink } from 'theme/components' import { ExternalLink } from 'theme/components'
import { shortenAddress } from 'utils' import { shortenAddress } from 'utils'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink' import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
import * as styles from './Activity.css' 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 }) => { 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 ( return (
<Row display={{ sm: 'none', md: 'flex' }} gap="8"> <Row display={{ sm: 'none', md: 'flex' }} gap="8">
@ -257,7 +260,11 @@ export const EventCell = ({
price, price,
isMobile, isMobile,
}: EventCellProps) => { }: 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 ( return (
<Column height="full" justifyContent="center" gap="4"> <Column height="full" justifyContent="center" gap="4">
<Row className={styles.eventDetail} color={eventColors(eventType)}> <Row className={styles.eventDetail} color={eventColors(eventType)}>
@ -318,6 +325,7 @@ interface RankingProps {
} }
const Ranking = ({ rarity, collectionName, rarityVerified }: RankingProps) => { const Ranking = ({ rarity, collectionName, rarityVerified }: RankingProps) => {
const { formatNumber } = useFormatter()
const rank = (rarity as TokenRarity).rank || (rarity as Rarity).providers?.[0].rank const rank = (rarity as TokenRarity).rank || (rarity as Rarity).providers?.[0].rank
if (!rank) return null if (!rank) return null
@ -339,7 +347,7 @@ const Ranking = ({ rarity, collectionName, rarityVerified }: RankingProps) => {
> >
<Box className={styles.rarityInfo}> <Box className={styles.rarityInfo}>
<Box paddingTop="2" paddingBottom="2" display="flex"> <Box paddingTop="2" paddingBottom="2" display="flex">
{putCommas(rank)} {formatNumber({ input: rank, type: NumberType.WholeNumber })}
</Box> </Box>
<Box display="flex" height="16"> <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 { Ranking as RankingContainer, Suspicious as SuspiciousContainer } from 'nft/components/card/icons'
import { useBag } from 'nft/hooks' import { useBag } from 'nft/hooks'
import { GenieAsset, UniformAspectRatio } from 'nft/types' import { GenieAsset, UniformAspectRatio } from 'nft/types'
import { formatWeiToDecimal } from 'nft/utils'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { NumberType, useFormatter } from 'utils/formatNumbers'
interface CollectionAssetProps { interface CollectionAssetProps {
asset: GenieAsset asset: GenieAsset
@ -31,6 +31,7 @@ export const CollectionAsset = ({
renderedHeight, renderedHeight,
setRenderedHeight, setRenderedHeight,
}: CollectionAssetProps) => { }: CollectionAssetProps) => {
const { formatEther } = useFormatter()
const bagManuallyClosed = useBag((state) => state.bagManuallyClosed) const bagManuallyClosed = useBag((state) => state.bagManuallyClosed)
const addAssetsToBag = useBag((state) => state.addAssetsToBag) const addAssetsToBag = useBag((state) => state.addAssetsToBag)
const removeAssetsFromBag = useBag((state) => state.removeAssetsFromBag) const removeAssetsFromBag = useBag((state) => state.removeAssetsFromBag)
@ -76,12 +77,23 @@ export const CollectionAsset = ({
primaryInfo: asset.name ? asset.name : `#${asset.tokenId}`, primaryInfo: asset.name ? asset.name : `#${asset.tokenId}`,
primaryInfoIcon: asset.susFlag ? <SuspiciousContainer /> : null, primaryInfoIcon: asset.susFlag ? <SuspiciousContainer /> : null,
primaryInfoRight: asset.rarity && provider ? <RankingContainer provider={provider} /> : 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>, selectedInfo: <Trans>Remove from bag</Trans>,
notSelectedInfo: <Trans>Add to bag</Trans>, notSelectedInfo: <Trans>Add to bag</Trans>,
disabledInfo: <Trans>Not listed</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 ( return (
<NftCard <NftCard

@ -8,11 +8,12 @@ import { themeVars } from 'nft/css/sprinkles.css'
import { useBag, useIsMobile } from 'nft/hooks' import { useBag, useIsMobile } from 'nft/hooks'
import { useIsCollectionLoading } from 'nft/hooks/useIsCollectionLoading' import { useIsCollectionLoading } from 'nft/hooks/useIsCollectionLoading'
import { GenieCollection, TokenType } from 'nft/types' 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 { ReactNode, useEffect, useReducer, useRef, useState } from 'react'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import styled, { css } from 'styled-components' import styled, { css } from 'styled-components'
import { ThemedText } from 'theme/components' import { ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import { DiscordIcon, EllipsisIcon, ExternalIcon, InstagramIcon, TwitterIcon, VerifiedIcon, XMarkIcon } from '../icons' import { DiscordIcon, EllipsisIcon, ExternalIcon, InstagramIcon, TwitterIcon, VerifiedIcon, XMarkIcon } from '../icons'
import * as styles from './CollectionStats.css' 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 StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMobile?: boolean } & BoxProps) => {
const { formatNumberOrString, formatDelta } = useFormatter()
const uniqueOwnersPercentage = stats?.stats?.total_supply const uniqueOwnersPercentage = stats?.stats?.total_supply
? roundWholePercentage(((stats.stats.num_owners ?? 0) / stats.stats.total_supply) * 100) ? roundWholePercentage(((stats.stats.num_owners ?? 0) / stats.stats.total_supply) * 100)
: 0 : 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 const listedPercentageStr = stats?.stats?.total_supply
? roundWholePercentage(((stats.stats.total_listings ?? 0) / stats.stats.total_supply) * 100) ? roundWholePercentage(((stats.stats.total_listings ?? 0) / stats.stats.total_supply) * 100)
: 0 : 0
const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading) const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
// round daily volume & floorPrice to 3 decimals or less // round daily volume & floorPrice to 3 decimals or less
const totalVolumeStr = volumeFormatter(Number(stats.stats?.total_volume) ?? 0) const totalVolumeStr = formatNumberOrString({
const floorPriceStr = floorFormatter(stats.stats?.floor_price ?? 0) 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 // 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 isBagExpanded = useBag((state) => state.bagExpanded)
const isScreenSize = useScreenSize() const isScreenSize = useScreenSize()
@ -372,7 +383,7 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob
<StatsItem label="Floor 24H" shouldHide={false}> <StatsItem label="Floor 24H" shouldHide={false}>
<PercentChange isNegative={stats.stats.one_day_floor_change < 0}> <PercentChange isNegative={stats.stats.one_day_floor_change < 0}>
<DeltaArrow delta={stats?.stats?.one_day_floor_change} /> <DeltaArrow delta={stats?.stats?.one_day_floor_change} />
{floorChangeStr}% {floorChangeStr}
</PercentChange> </PercentChange>
</StatsItem> </StatsItem>
) : null} ) : null}

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

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

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

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

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

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

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

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

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

@ -1,8 +1,12 @@
import { isNumber } from 'nft/utils/numbers'
import { FormEvent, forwardRef } from 'react' import { FormEvent, forwardRef } from 'react'
import { Box, BoxProps } from '../Box' 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) => ( export const Input = forwardRef<HTMLInputElement, BoxProps>((props, ref) => (
<Box <Box
ref={ref} 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 { LIST_PAGE_MARGIN, LIST_PAGE_MARGIN_MOBILE } from 'nft/pages/profile/shared'
import { looksRareNonceFetcher } from 'nft/queries/looksRare' import { looksRareNonceFetcher } from 'nft/queries/looksRare'
import { ProfilePageStateType } from 'nft/types' import { ProfilePageStateType } from 'nft/types'
import { formatEth } from 'nft/utils'
import { ListingMarkets } from 'nft/utils/listNfts' import { ListingMarkets } from 'nft/utils/listNfts'
import { useEffect, useMemo, useReducer, useState } from 'react' import { useEffect, useMemo, useReducer, useState } from 'react'
import { ArrowLeft } from 'react-feather' import { ArrowLeft } from 'react-feather'
@ -183,6 +182,7 @@ const EthValueWrapper = styled.span<{ totalEthListingValue: boolean }>`
` `
export const ListPage = () => { export const ListPage = () => {
const { formatNumberOrString } = useFormatter()
const { setProfilePageState: setSellPageState } = useProfilePageState() const { setProfilePageState: setSellPageState } = useProfilePageState()
const { provider, chainId } = useWeb3React() const { provider, chainId } = useWeb3React()
const isMobile = useIsMobile() const isMobile = useIsMobile()
@ -295,7 +295,10 @@ export const ListPage = () => {
<ProceedsAndButtonWrapper> <ProceedsAndButtonWrapper>
<ProceedsWrapper> <ProceedsWrapper>
<EthValueWrapper totalEthListingValue={!!totalEthListingValue}> <EthValueWrapper totalEthListingValue={!!totalEthListingValue}>
{totalEthListingValue > 0 ? formatEth(totalEthListingValue) : '-'} ETH {totalEthListingValue > 0
? formatNumberOrString({ input: totalEthListingValue, type: NumberType.NFTToken })
: '-'}{' '}
ETH
</EthValueWrapper> </EthValueWrapper>
{!!usdcValue && <UsdValue>{usdcAmount}</UsdValue>} {!!usdcValue && <UsdValue>{usdcAmount}</UsdValue>}
</ProceedsWrapper> </ProceedsWrapper>

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

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

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

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

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

@ -1,7 +1,7 @@
import { Percent } from '@uniswap/sdk-core'
import { useMemo } from 'react' import { useMemo } from 'react'
import { ClassicTrade } from 'state/routing/types' import { ClassicTrade } from 'state/routing/types'
import { useTheme } from 'styled-components' import { useTheme } from 'styled-components'
import { useFormatter } from 'utils/formatNumbers'
import { computeRealizedPriceImpact, getPriceImpactWarning } from 'utils/prices' import { computeRealizedPriceImpact, getPriceImpactWarning } from 'utils/prices'
export interface PriceImpact { export interface PriceImpact {
@ -16,6 +16,7 @@ interface PriceImpactSeverity {
export function usePriceImpact(trade?: ClassicTrade): PriceImpact | undefined { export function usePriceImpact(trade?: ClassicTrade): PriceImpact | undefined {
const theme = useTheme() const theme = useTheme()
const { formatPercent } = useFormatter()
return useMemo(() => { return useMemo(() => {
const marketPriceImpact = trade ? computeRealizedPriceImpact(trade) : undefined const marketPriceImpact = trade ? computeRealizedPriceImpact(trade) : undefined
@ -33,18 +34,8 @@ export function usePriceImpact(trade?: ClassicTrade): PriceImpact | undefined {
type: priceImpactWarning, type: priceImpactWarning,
color: warningColor, color: warningColor,
}, },
displayPercentage: () => toHumanReadablePercent(marketPriceImpact), displayPercentage: () => formatPercent(marketPriceImpact),
} }
: undefined : undefined
}, [theme.critical, theme.deprecated_accentWarning, trade]) }, [formatPercent, 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}%`
} }

@ -1,10 +1,8 @@
import { parseEther } from 'ethers/lib/utils' import { parseEther } from 'ethers/lib/utils'
import { ActivityEvent, GenieAsset } from 'nft/types' import { ActivityEvent, GenieAsset } from 'nft/types'
import { formatEth } from './currency'
export const buildActivityAsset = (event: ActivityEvent, collectionName: string, ethPriceInUSD: number): GenieAsset => { 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) : '' 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 // prevent BigNumber overflow by properly handling scientific notation and comma delimited values
export function wrapScientificNotation(value: string | number): string { export function wrapScientificNotation(value: string | number): string {
return parseFloat(value.toString()) return parseFloat(value.toString())

@ -10,21 +10,11 @@ export { blocklistedCollections } from './blocklist'
export { buildNftTradeInputFromBagItems } from './buildSellObject' export { buildNftTradeInputFromBagItems } from './buildSellObject'
export { calculateCardIndex, calculateFirstCardIndex, calculateRank } from './carousel' export { calculateCardIndex, calculateFirstCardIndex, calculateRank } from './carousel'
export { isInSameMarketplaceCollection, isInSameSudoSwapPool } from './collection' export { isInSameMarketplaceCollection, isInSameSudoSwapPool } from './collection'
export { export { wrapScientificNotation } from './currency'
ethNumberStandardFormatter,
formatEth,
formatEthPrice,
formatUsdPrice,
formatUSDPriceWithCommas,
formatWeiToDecimal,
wrapScientificNotation,
} from './currency'
export { formatAssetEventProperties } from './formatEventProperties' export { formatAssetEventProperties } from './formatEventProperties'
export { isAudio } from './isAudio' export { isAudio } from './isAudio'
export { isVideo } from './isVideo' export { isVideo } from './isVideo'
export { floorFormatter, volumeFormatter } from './numbers'
export { calcAvgGroupPoolPrice, calcPoolPrice, recalculateBagUsingPooledAssets } from './pooledAssets' export { calcAvgGroupPoolPrice, calcPoolPrice, recalculateBagUsingPooledAssets } from './pooledAssets'
export { putCommas } from './putCommas'
export { pluralize, roundAndPluralize } from './roundAndPluralize' export { pluralize, roundAndPluralize } from './roundAndPluralize'
export { timeLeft } from './time' export { timeLeft } from './time'
export { getSuccessfulImageSize, parseTransactionResponse } from './transactionResponse' 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 => { export const roundWholePercentage = (n: number): string => {
if (n === 0) return '0' if (n === 0) return '0'
if (!n) return '' 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 { BuyItem, GenieAsset, isPooledMarket, Markets, PriceInfo, RoutingItem, UpdatedGenieAsset } from 'nft/types'
import { import { calcAvgGroupPoolPrice, isInSameMarketplaceCollection, isInSameSudoSwapPool } from 'nft/utils'
calcAvgGroupPoolPrice,
formatWeiToDecimal,
isInSameMarketplaceCollection,
isInSameSudoSwapPool,
} from 'nft/utils'
const isTheSame = (item: GenieAsset, routeAsset: BuyItem | PriceInfo) => { const isTheSame = (item: GenieAsset, routeAsset: BuyItem | PriceInfo) => {
// if route asset has id, match by id // 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 getPriceDiff = (oldPrice: string, newPrice: string): { hasPriceDiff: boolean; hasVisiblePriceDiff: boolean } => {
const hasPriceDiff = oldPrice !== newPrice const hasPriceDiff = oldPrice !== newPrice
const hasVisiblePriceDiff = formatWeiToDecimal(oldPrice) !== formatWeiToDecimal(newPrice) const hasVisiblePriceDiff = formatEther(oldPrice) !== formatEther(newPrice)
return { hasPriceDiff, hasVisiblePriceDiff } 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 { Currency, CurrencyAmount, Percent, Price, Token } from '@uniswap/sdk-core'
import { import {
DEFAULT_LOCAL_CURRENCY, DEFAULT_LOCAL_CURRENCY,
@ -67,11 +68,23 @@ const THREE_DECIMALS_CURRENCY: NumberFormatOptions = {
style: 'currency', 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 = { const TWO_DECIMALS_NO_TRAILING_ZEROS: NumberFormatOptions = {
notation: 'standard', notation: 'standard',
maximumFractionDigits: 2, maximumFractionDigits: 2,
} }
const TWO_DECIMALS_NO_TRAILING_ZEROS_CURRENCY: NumberFormatOptions = {
...TWO_DECIMALS_NO_TRAILING_ZEROS,
currency: 'USD',
style: 'currency',
}
const TWO_DECIMALS: NumberFormatOptions = { const TWO_DECIMALS: NumberFormatOptions = {
notation: 'standard', notation: 'standard',
maximumFractionDigits: 2, maximumFractionDigits: 2,
@ -97,6 +110,12 @@ const SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS: NumberFormatOptions = {
maximumFractionDigits: 2, 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 = { const SHORTHAND_ONE_DECIMAL: NumberFormatOptions = {
notation: 'compact', notation: 'compact',
minimumFractionDigits: 1, minimumFractionDigits: 1,
@ -317,6 +336,42 @@ const ntfCollectionStatsFormatter: FormatterRule[] = [
{ upperBound: Infinity, formatterOptions: SHORTHAND_ONE_DECIMAL }, { 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 { export enum NumberType {
// used for token quantities in non-transaction contexts (e.g. portfolio balances) // used for token quantities in non-transaction contexts (e.g. portfolio balances)
TokenNonTx = 'token-non-tx', TokenNonTx = 'token-non-tx',
@ -360,6 +415,15 @@ export enum NumberType {
// nft floor price with trailing zeros // nft floor price with trailing zeros
NFTTokenFloorPriceTrailingZeros = 'nft-token-floor-price-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[] type FormatterType = NumberType | FormatterRule[]
@ -378,6 +442,9 @@ const TYPE_TO_FORMATTER_RULES = {
[NumberType.NFTTokenFloorPrice]: ntfTokenFloorPriceFormatter, [NumberType.NFTTokenFloorPrice]: ntfTokenFloorPriceFormatter,
[NumberType.NFTTokenFloorPriceTrailingZeros]: ntfTokenFloorPriceFormatterTrailingZeros, [NumberType.NFTTokenFloorPriceTrailingZeros]: ntfTokenFloorPriceFormatterTrailingZeros,
[NumberType.NFTCollectionStats]: ntfCollectionStatsFormatter, [NumberType.NFTCollectionStats]: ntfCollectionStatsFormatter,
[NumberType.NFTToken]: nftTokenFormatter,
[NumberType.FiatNFTToken]: fiatNftTokenFormatter,
[NumberType.WholeNumber]: wholeNumberFormatter,
} }
function getFormatterRule(input: number, type: FormatterType, conversionRate?: number): FormatterRule { function getFormatterRule(input: number, type: FormatterType, conversionRate?: number): FormatterRule {
@ -563,6 +630,18 @@ function formatNumberOrString({
return formatNumber({ input, type, locale, localCurrency, conversionRate }) 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 { interface FormatFiatPriceOptions {
price: Nullish<number | string> price: Nullish<number | string>
type?: FormatterType type?: FormatterType
@ -733,9 +812,20 @@ export function useFormatter() {
[formatterLocale] [formatterLocale]
) )
const formatEtherwithLocales = useCallback(
(options: Omit<FormatEtherOptions, LocalesType>) =>
formatEther({
...options,
locale: formatterLocale,
localCurrency: currencyToFormatWith,
}),
[currencyToFormatWith, formatterLocale]
)
return useMemo( return useMemo(
() => ({ () => ({
formatCurrencyAmount: formatCurrencyAmountWithLocales, formatCurrencyAmount: formatCurrencyAmountWithLocales,
formatEther: formatEtherwithLocales,
formatFiatPrice: formatFiatPriceWithLocales, formatFiatPrice: formatFiatPriceWithLocales,
formatNumber: formatNumberWithLocales, formatNumber: formatNumberWithLocales,
formatNumberOrString: formatNumberOrStringWithLocales, formatNumberOrString: formatNumberOrStringWithLocales,
@ -747,6 +837,7 @@ export function useFormatter() {
}), }),
[ [
formatCurrencyAmountWithLocales, formatCurrencyAmountWithLocales,
formatEtherwithLocales,
formatFiatPriceWithLocales, formatFiatPriceWithLocales,
formatNumberOrStringWithLocales, formatNumberOrStringWithLocales,
formatNumberWithLocales, formatNumberWithLocales,