Commit a663482d authored by lynn's avatar lynn Committed by GitHub

fix: second part of price fixes (#4852)

* init

* use named args

* transaction price format

* respond to comments

* 1.00 rounding edge case
parent e8d62355
......@@ -14,7 +14,7 @@ import { ethNumberStandardFormatter } from 'nft/utils/currency'
import { putCommas } from 'nft/utils/putCommas'
import { useCallback, useEffect, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { formatDollar } from 'utils/formatDollarAmt'
import { formatDollar } from 'utils/formatNumbers'
import * as styles from './SearchBar.css'
......@@ -190,7 +190,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, traceE
<Column className={styles.suggestionSecondaryContainer}>
{token.priceUsd && (
<Row gap="4">
<Box className={styles.primaryText}>{formatDollar(token.priceUsd, true)}</Box>
<Box className={styles.primaryText}>{formatDollar({ num: token.priceUsd, isPrice: true })}</Box>
</Row>
)}
{token.price24hChange && (
......
......@@ -7,7 +7,7 @@ import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import { useParams } from 'react-router-dom'
import styled from 'styled-components/macro'
import { StyledInternalLink } from 'theme'
import { currencyAmountToPreciseFloat, formatDollar } from 'utils/formatDollarAmt'
import { currencyAmountToPreciseFloat, formatDollar } from 'utils/formatNumbers'
const BalancesCard = styled.div`
box-shadow: ${({ theme }) => theme.shallowShadow};
......@@ -57,7 +57,7 @@ function BalanceRow({ currency, formattedBalance, usdValue, href }: BalanceRowDa
<CurrencyLogo currency={currency} />
&nbsp;{formattedBalance} {currency?.symbol}
</TotalBalanceItem>
<TotalBalanceItem>{formatDollar(usdValue === 0 ? undefined : usdValue, true)}</TotalBalanceItem>
<TotalBalanceItem>{formatDollar({ num: usdValue === 0 ? undefined : usdValue, isPrice: true })}</TotalBalanceItem>
</TotalBalance>
)
if (href) {
......
......@@ -3,7 +3,7 @@ import { formatToDecimal } from 'analytics/utils'
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import styled from 'styled-components/macro'
import { StyledInternalLink } from 'theme'
import { currencyAmountToPreciseFloat, formatDollar } from 'utils/formatDollarAmt'
import { currencyAmountToPreciseFloat, formatDollar } from 'utils/formatNumbers'
import { BalanceSummaryProps } from './BalanceSummary'
......@@ -108,7 +108,7 @@ export default function MobileBalanceSummaryFooter({
<BalanceValue>
{formattedBalance} {tokenAmount?.currency?.symbol}
</BalanceValue>
<FiatValue>{formatDollar(balanceUsd, true)}</FiatValue>
<FiatValue>{formatDollar({ num: balanceUsd, isPrice: true })}</FiatValue>
</BalanceTotal>
</BalanceInfo>
)}
......@@ -119,7 +119,7 @@ export default function MobileBalanceSummaryFooter({
<BalanceValue>
{formattedNativeBalance} {nativeCurrencyAmount?.currency?.symbol}
</BalanceValue>
<FiatValue>{formatDollar(nativeBalanceUsd, true)}</FiatValue>
<FiatValue>{formatDollar({ num: nativeBalanceUsd, isPrice: true })}</FiatValue>
</BalanceTotal>
</BalanceInfo>
)}
......
......@@ -22,7 +22,7 @@ import {
monthYearDayFormatter,
weekFormatter,
} from 'utils/formatChartTimes'
import { formatDollar } from 'utils/formatDollarAmt'
import { formatDollar } from 'utils/formatNumbers'
import { MEDIUM_MEDIA_BREAKPOINT } from '../constants'
import { DISPLAYS, ORDERED_TIMES } from '../TokenTable/TimeSelector'
......@@ -271,7 +271,7 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
return (
<>
<ChartHeader>
<TokenPrice>{formatDollar(displayPrice.value, true)}</TokenPrice>
<TokenPrice>{formatDollar({ num: displayPrice.value, isPrice: true })}</TokenPrice>
<DeltaContainer>
{formattedDelta}
<ArrowCell>{arrow}</ArrowCell>
......
......@@ -3,7 +3,7 @@ import { ReactNode } from 'react'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { textFadeIn } from 'theme/animations'
import { formatDollar } from 'utils/formatDollarAmt'
import { formatDollar } from 'utils/formatNumbers'
import { TokenSortMethod } from '../state'
import { HEADER_DESCRIPTIONS } from '../TokenTable/TokenRow'
......@@ -69,7 +69,7 @@ function Stat({
{description && <InfoTip text={description}></InfoTip>}
</StatTitle>
<StatPrice>{formatDollar(value, isPrice)}</StatPrice>
<StatPrice>{formatDollar({ num: value, isPrice })}</StatPrice>
</StatWrapper>
)
}
......
......@@ -16,7 +16,7 @@ import { Link, useParams } from 'react-router-dom'
import { Text } from 'rebass'
import styled, { css, useTheme } from 'styled-components/macro'
import { ClickableStyle } from 'theme'
import { formatDollar } from 'utils/formatDollarAmt'
import { formatDollar } from 'utils/formatNumbers'
import {
LARGE_MEDIA_BREAKPOINT,
......@@ -534,7 +534,9 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
price={
<ClickableContent>
<PriceInfoCell>
{token.market?.price?.value ? formatDollar(token.market.price.value, true) : '-'}
{token.market?.price?.value
? formatDollar({ num: token.market.price.value, isPrice: true, lessPreciseStablecoinValues: true })
: '-'}
<PercentChangeInfoCell>
{formattedDelta}
{arrow}
......@@ -550,12 +552,12 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
}
marketCap={
<ClickableContent>
{token.market?.totalValueLocked?.value ? formatDollar(token.market.totalValueLocked.value) : '-'}
{token.market?.totalValueLocked?.value ? formatDollar({ num: token.market.totalValueLocked.value }) : '-'}
</ClickableContent>
}
volume={
<ClickableContent>
{token.market?.volume?.value ? formatDollar(token.market.volume.value) : '-'}
{token.market?.volume?.value ? formatDollar({ num: token.market.volume.value }) : '-'}
</ClickableContent>
}
sparkLine={
......
......@@ -5,6 +5,7 @@ import { useCallback } from 'react'
import { Text } from 'rebass'
import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from 'theme'
import { formatDollar, formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers'
interface TradePriceProps {
price: Price<Currency, Currency>
......@@ -33,15 +34,12 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
const theme = useTheme()
const usdcPrice = useStablecoinPrice(showInverted ? price.baseCurrency : price.quoteCurrency)
/*
* calculate needed amount of decimal prices, for prices between 0.95-1.05 use 4 decimal places
*/
const p = Number(usdcPrice?.toFixed())
const visibleDecimalPlaces = p < 1.05 ? 4 : 2
let formattedPrice: string
try {
formattedPrice = showInverted ? price.toSignificant(4) : price.invert()?.toSignificant(4)
formattedPrice = showInverted
? formatTransactionAmount(priceToPreciseFloat(price))
: formatTransactionAmount(priceToPreciseFloat(price.invert()))
} catch (error) {
formattedPrice = '0'
}
......@@ -65,7 +63,7 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
</Text>{' '}
{usdcPrice && (
<ThemedText.DeprecatedDarkGray>
<Trans>(${usdcPrice.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })})</Trans>
<Trans>(${formatDollar({ num: priceToPreciseFloat(usdcPrice) })})</Trans>
</ThemedText.DeprecatedDarkGray>
)}
</StyledPriceContainer>
......
......@@ -34,6 +34,7 @@ import { useToggleWalletModal } from 'state/application/hooks'
import { InterfaceTrade } from 'state/routing/types'
import { TradeState } from 'state/routing/types'
import styled, { css, useTheme } from 'styled-components/macro'
import { currencyAmountToPreciseFloat, formatTransactionAmount } from 'utils/formatNumbers'
import AddressInputPanel from '../../components/AddressInputPanel'
import { ButtonConfirmed, ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
......@@ -319,7 +320,7 @@ export default function Swap() {
[independentField]: typedValue,
[dependentField]: showWrap
? parsedAmounts[independentField]?.toExact() ?? ''
: parsedAmounts[dependentField]?.toSignificant(6) ?? '',
: formatTransactionAmount(currencyAmountToPreciseFloat(parsedAmounts[dependentField])),
}),
[dependentField, independentField, parsedAmounts, showWrap, typedValue]
)
......@@ -482,7 +483,7 @@ export default function Swap() {
)
const handleMaxInput = useCallback(() => {
maxInputAmount && onUserInput(Field.INPUT, maxInputAmount.toExact())
maxInputAmount && onUserInput(Field.INPUT, formatTransactionAmount(currencyAmountToPreciseFloat(maxInputAmount)))
sendEvent({
category: 'Swap',
action: 'Max',
......
import { CurrencyAmount } from '@uniswap/sdk-core'
import { USDC_MAINNET } from 'constants/tokens'
import { currencyAmountToPreciseFloat, formatDollar } from './formatDollarAmt'
describe('currencyAmountToPreciseFloat', () => {
it('small number', () => {
const currencyAmount = CurrencyAmount.fromFractionalAmount(USDC_MAINNET, '20000', '7')
expect(currencyAmountToPreciseFloat(currencyAmount)).toEqual(0.00285)
})
it('tiny number', () => {
const currencyAmount = CurrencyAmount.fromFractionalAmount(USDC_MAINNET, '2', '7')
expect(currencyAmountToPreciseFloat(currencyAmount)).toEqual(0.000000285)
})
it('lots of decimals', () => {
const currencyAmount = CurrencyAmount.fromFractionalAmount(USDC_MAINNET, '200000000', '7')
expect(currencyAmountToPreciseFloat(currencyAmount)).toEqual(28.571)
})
it('integer', () => {
const currencyAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '20000000')
expect(currencyAmountToPreciseFloat(currencyAmount)).toEqual(20.0)
})
})
describe('formatDollar for a price', () => {
it('undefined or null', () => {
expect(formatDollar(undefined, true)).toEqual('-')
expect(formatDollar(null, true)).toEqual('-')
})
it('0', () => {
expect(formatDollar(0, true)).toEqual('$0.00')
})
it('< 0.000001', () => {
expect(formatDollar(0.00000000011231231432, true)).toEqual('$1.12e-10')
})
it('num >= 0.000001 && num < 0.1', () => {
expect(formatDollar(0.00123123124, true)).toEqual('$0.00123')
})
it('num >= 0.1 && num < 1.05', () => {
expect(formatDollar(0.812831, true)).toEqual('$0.813')
})
it('number is greater than 1 million', () => {
expect(formatDollar(11192312.408, true)).toEqual('$1.12e+7')
})
it('number in the thousands', () => {
expect(formatDollar(1234.408, true)).toEqual('$1,234.41')
})
it('number is greater than 1.05', () => {
expect(formatDollar(102312.408, true)).toEqual('$102,312.41')
})
})
describe('formatDollar for a non-price amount', () => {
it('undefined or null', () => {
expect(formatDollar(undefined)).toEqual('-')
expect(formatDollar(null)).toEqual('-')
})
it('0', () => {
expect(formatDollar(0)).toEqual('0')
})
it('< 0.000001', () => {
expect(formatDollar(0.0000000001)).toEqual('$<0.000001')
})
it('num >= 0.000001 && num < 0.1', () => {
expect(formatDollar(0.00123123124)).toEqual('$0.00123')
})
it('num >= 0.1 && num < 1.05', () => {
expect(formatDollar(0.812831)).toEqual('$0.813')
})
it('number is greater than 1.05', () => {
expect(formatDollar(102312.408)).toEqual('$102.31K')
})
})
import { CurrencyAmount, Price } from '@uniswap/sdk-core'
import { renBTC, USDC_MAINNET } from 'constants/tokens'
import {
currencyAmountToPreciseFloat,
formatDollar,
formatTransactionAmount,
priceToPreciseFloat,
} from './formatNumbers'
describe('currencyAmountToPreciseFloat', () => {
it('small number', () => {
const currencyAmount = CurrencyAmount.fromFractionalAmount(USDC_MAINNET, '20000', '7')
expect(currencyAmountToPreciseFloat(currencyAmount)).toEqual(0.00285714)
})
it('tiny number', () => {
const currencyAmount = CurrencyAmount.fromFractionalAmount(USDC_MAINNET, '2', '7')
expect(currencyAmountToPreciseFloat(currencyAmount)).toEqual(0.000000285714)
})
it('lots of decimals', () => {
const currencyAmount = CurrencyAmount.fromFractionalAmount(USDC_MAINNET, '200000000', '7')
expect(currencyAmountToPreciseFloat(currencyAmount)).toEqual(28.571428)
})
it('integer', () => {
const currencyAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, '20000000')
expect(currencyAmountToPreciseFloat(currencyAmount)).toEqual(20.0)
})
})
describe('priceToPreciseFloat', () => {
it('small number', () => {
const price = new Price(renBTC, USDC_MAINNET, 1234, 1)
expect(priceToPreciseFloat(price)).toEqual(0.0810373)
})
it('tiny number', () => {
const price = new Price(renBTC, USDC_MAINNET, 12345600, 1)
expect(priceToPreciseFloat(price)).toEqual(0.00000810005)
})
it('lots of decimals', () => {
const price = new Price(renBTC, USDC_MAINNET, 123, 7)
expect(priceToPreciseFloat(price)).toEqual(5.691056911)
})
it('integer', () => {
const price = new Price(renBTC, USDC_MAINNET, 1, 7)
expect(priceToPreciseFloat(price)).toEqual(700)
})
})
describe('formatTransactionAmount', () => {
it('undefined or null', () => {
expect(formatTransactionAmount(undefined)).toEqual('')
expect(formatTransactionAmount(null)).toEqual('')
})
it('0', () => {
expect(formatTransactionAmount(0)).toEqual('0.00')
})
it('< 0.00001', () => {
expect(formatTransactionAmount(0.000000001)).toEqual('<0.00001')
})
it('1 > number ≥ .00001 full precision', () => {
expect(formatTransactionAmount(0.987654321)).toEqual('0.98765')
})
it('1 > number ≥ .00001 minimum 2 decimals', () => {
expect(formatTransactionAmount(0.9)).toEqual('0.90')
})
it('1 > number ≥ .00001 no trailing zeros beyond 2nd decimal', () => {
expect(formatTransactionAmount(0.901000123)).toEqual('0.901')
})
it('10,000 > number ≥ 1 round to 6 sig figs', () => {
expect(formatTransactionAmount(7654.3210789)).toEqual('7,654.32')
})
it('10,000 > number ≥ 1 round to 6 sig figs 2nd case', () => {
expect(formatTransactionAmount(76.3210789)).toEqual('76.3211')
})
it('10,000 > number ≥ 1 no trailing zeros beyond 2nd decimal place', () => {
expect(formatTransactionAmount(7.60000054321)).toEqual('7.60')
})
it('10,000 > number ≥ 1 always show at least 2 decimal places', () => {
expect(formatTransactionAmount(7)).toEqual('7.00')
})
it('1M > number ≥ 10,000 few decimals', () => {
expect(formatTransactionAmount(765432.1)).toEqual('765,432.10')
})
it('1M > number ≥ 10,000 lots of decimals', () => {
expect(formatTransactionAmount(76543.2123424)).toEqual('76,543.21')
})
it('Number ≥ 1M', () => {
expect(formatTransactionAmount(1234567.8901)).toEqual('1,234,567.89')
})
it('Number ≥ 1M extra long', () => {
expect(formatTransactionAmount(1234567890123456.789)).toEqual('1.234568e+15')
})
})
describe('formatDollar for a price', () => {
const isPrice = true
it('undefined or null', () => {
expect(formatDollar({ num: undefined, isPrice })).toEqual('-')
expect(formatDollar({ num: null, isPrice })).toEqual('-')
})
it('0', () => {
expect(formatDollar({ num: 0, isPrice })).toEqual('$0.00')
})
it('< 0.000001', () => {
expect(formatDollar({ num: 0.00000000011231231432, isPrice })).toEqual('$1.12e-10')
})
it('num >= 0.000001 && num < 0.1', () => {
expect(formatDollar({ num: 0.00123123124, isPrice })).toEqual('$0.00123')
})
it('num >= 0.1 && num < 1.05', () => {
expect(formatDollar({ num: 0.812831, isPrice })).toEqual('$0.813')
})
it('lessPreciseStablecoinValues number less than 1, rounds to 0.999', () => {
expect(formatDollar({ num: 0.9994, isPrice, lessPreciseStablecoinValues: true })).toEqual('$0.999')
})
it('lessPreciseStablecoinValues number less than, rounds to 1.00', () => {
expect(formatDollar({ num: 0.9995, isPrice, lessPreciseStablecoinValues: true })).toEqual('$1.00')
})
it('lessPreciseStablecoinValues number greater than 1', () => {
expect(formatDollar({ num: 1.0000001, isPrice, lessPreciseStablecoinValues: true })).toEqual('$1.00')
})
it('number is greater than 1 million', () => {
expect(formatDollar({ num: 11192312.408, isPrice })).toEqual('$1.12e+7')
})
it('number in the thousands', () => {
expect(formatDollar({ num: 1234.408, isPrice })).toEqual('$1,234.41')
})
it('number is greater than 1.05', () => {
expect(formatDollar({ num: 102312.408, isPrice })).toEqual('$102,312.41')
})
})
describe('formatDollar for a non-price amount', () => {
it('undefined or null', () => {
expect(formatDollar({ num: undefined })).toEqual('-')
expect(formatDollar({ num: null })).toEqual('-')
})
it('0', () => {
expect(formatDollar({ num: 0 })).toEqual('0')
})
it('< 0.000001', () => {
expect(formatDollar({ num: 0.0000000001 })).toEqual('$<0.000001')
})
it('num >= 0.000001 && num < 0.1', () => {
expect(formatDollar({ num: 0.00123123124 })).toEqual('$0.00123')
})
it('num >= 0.1 && num < 1.05', () => {
expect(formatDollar({ num: 0.812831 })).toEqual('$0.813')
})
it('number is greater than 1.05', () => {
expect(formatDollar({ num: 102312.408 })).toEqual('$102.31K')
})
})
/* Copied from Uniswap/v-3: https://github.com/Uniswap/v3-info/blob/master/src/utils/numbers.ts */
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, Price } from '@uniswap/sdk-core'
import { DEFAULT_LOCALE } from 'constants/locales'
import numbro from 'numbro'
// Convert [CurrencyAmount] to number with necessary precision for price formatting.
export const currencyAmountToPreciseFloat = (currencyAmount: CurrencyAmount<Currency>) => {
const floatForLargerNumbers = parseFloat(currencyAmount.toFixed(3))
export const currencyAmountToPreciseFloat = (currencyAmount: CurrencyAmount<Currency> | undefined) => {
if (!currencyAmount) return undefined
const floatForLargerNumbers = parseFloat(currencyAmount.toExact())
if (floatForLargerNumbers < 0.1) {
return parseFloat(currencyAmount.toSignificant(3))
return parseFloat(currencyAmount.toSignificant(6))
}
return floatForLargerNumbers
}
// Convert [Price] to number with necessary precision for price formatting.
export const priceToPreciseFloat = (price: Price<Currency, Currency> | undefined) => {
if (!price) return undefined
const floatForLargerNumbers = parseFloat(price.toFixed(9))
if (floatForLargerNumbers < 0.1) {
return parseFloat(price.toSignificant(6))
}
return floatForLargerNumbers
}
interface FormatDollarArgs {
num: number | undefined | null
isPrice?: boolean
lessPreciseStablecoinValues?: boolean
digits?: number
round?: boolean
}
// Using a currency library here in case we want to add more in future.
export const formatDollar = (num: number | undefined | null, isPrice = false, digits = 2, round = true) => {
export const formatDollar = ({
num,
isPrice = false,
lessPreciseStablecoinValues = false,
digits = 2,
round = true,
}: FormatDollarArgs): string => {
// For USD dollar denominated prices.
if (isPrice) {
if (num === 0) return '$0.00'
if (!num) return '-'
......@@ -23,11 +49,12 @@ export const formatDollar = (num: number | undefined | null, isPrice = false, di
if ((num >= 0.000001 && num < 0.1) || num > 1000000) {
return `$${Number(num).toPrecision(3)}`
}
if (num >= 0.1 && num < 1.05) {
// We only show 2 decimal places in explore table for stablecoin value ranges
// for the sake of readability (as opposed to the usual 3 elsewhere).
if (num >= 0.1 && num < (lessPreciseStablecoinValues ? 0.9995 : 1.05)) {
return `$${num.toFixed(3)}`
}
// if number is greater than 1.05:
return `$${Number(num.toFixed(2)).toLocaleString(DEFAULT_LOCALE)}`
return `$${Number(num.toFixed(2)).toLocaleString(DEFAULT_LOCALE, { minimumFractionDigits: 2 })}`
}
// For volume dollar amounts, like market cap, total value locked, etc.
else {
......@@ -56,6 +83,37 @@ export const formatDollar = (num: number | undefined | null, isPrice = false, di
}
}
// For transaction review numbers, such as token quantities, NFT price (token-denominated),
// network fees, transaction history items.
export const formatTransactionAmount = (num: number | undefined | null, maxDigits = 9) => {
if (num === 0) return '0.00'
if (!num) return ''
if (num < 0.00001) {
return '<0.00001'
}
if (num >= 0.00001 && num < 1) {
return `${Number(num.toFixed(5)).toLocaleString(DEFAULT_LOCALE, {
minimumFractionDigits: 2,
maximumFractionDigits: 5,
})}`
}
if (num >= 1 && num < 10000) {
return `${Number(num.toPrecision(6)).toLocaleString(DEFAULT_LOCALE, {
minimumFractionDigits: 2,
maximumFractionDigits: 6,
})}`
}
if (num >= 10000 && num < 1000000) {
return `${Number(num.toFixed(2)).toLocaleString(DEFAULT_LOCALE, { minimumFractionDigits: 2 })}`
}
// For very large numbers, switch to scientific notation and show as much precision
// as permissible by maxDigits param.
if (num >= Math.pow(10, maxDigits - 1)) {
return `${num.toExponential(maxDigits - 3)}`
}
return `${Number(num.toFixed(2)).toLocaleString(DEFAULT_LOCALE, { minimumFractionDigits: 2 })}`
}
// using a currency library here in case we want to add more in future
export const formatAmount = (num: number | undefined, digits = 2) => {
if (num === 0) return '0'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment