diff --git a/test/e2e/tests/simulation-details/simulation-details.spec.ts b/test/e2e/tests/simulation-details/simulation-details.spec.ts index 0c3e514f88ad..65e21b5d9ab2 100644 --- a/test/e2e/tests/simulation-details/simulation-details.spec.ts +++ b/test/e2e/tests/simulation-details/simulation-details.spec.ts @@ -114,7 +114,7 @@ describe('Simulation Details', () => { await switchToNotificationWindow(driver); await expectBalanceChange(driver, true, 0, '- 0.002', 'ETH'); - await expectBalanceChange(driver, false, 0, '+ 6.756291', 'DAI'); + await expectBalanceChange(driver, false, 0, '+ 6.756', 'DAI'); }); }); diff --git a/ui/hooks/useCurrencyDisplay.js b/ui/hooks/useCurrencyDisplay.js index e6eded343fe6..192aebf37906 100644 --- a/ui/hooks/useCurrencyDisplay.js +++ b/ui/hooks/useCurrencyDisplay.js @@ -1,5 +1,6 @@ import { useMemo } from 'react'; import { useSelector } from 'react-redux'; +import BigNumber from 'bignumber.js'; import { formatCurrency } from '../helpers/utils/confirm-tx.util'; import { getCurrentCurrency } from '../selectors'; import { @@ -12,9 +13,16 @@ import { TEST_NETWORK_TICKER_MAP } from '../../shared/constants/network'; import { Numeric } from '../../shared/modules/Numeric'; import { EtherDenomination } from '../../shared/constants/common'; -export const MIN_DISPLAY_AMOUNT = '<0.000001'; +// The smallest non-zero amount that can be displayed. +export const MIN_AMOUNT = 0.000001; -export const DEFAULT_PRECISION_DECIMALS = 6; +// The string to display when 0 < amount < MIN_AMOUNT. +// TODO(dbrans): Localize this string using Intl.NumberFormatter. +const MIN_AMOUNT_DISPLAY = `<${MIN_AMOUNT}`; + +// The default precision for displaying currency values. +// It set to the number of decimal places in the minimum amount. +export const DEFAULT_PRECISION = new BigNumber(MIN_AMOUNT).decimalPlaces(); /** * Defines the shape of the options parameter for useCurrencyDisplay @@ -66,12 +74,12 @@ export function useCurrencyDisplay( ) { const ethDisplayValue = new Numeric(inputValue, 16, EtherDenomination.WEI) .toDenomination(denomination || EtherDenomination.ETH) - .round(numberOfDecimals || DEFAULT_PRECISION_DECIMALS) + .round(numberOfDecimals || DEFAULT_PRECISION) .toBase(10) .toString(); return ethDisplayValue === '0' && inputValue && Number(inputValue) !== 0 - ? MIN_DISPLAY_AMOUNT + ? MIN_AMOUNT_DISPLAY : ethDisplayValue; } else if (isUserPreferredCurrency && conversionRate) { return formatCurrency( diff --git a/ui/pages/confirmations/components/simulation-details/amount-pill.test.tsx b/ui/pages/confirmations/components/simulation-details/amount-pill.test.tsx index d3a458400326..c35b090fda84 100644 --- a/ui/pages/confirmations/components/simulation-details/amount-pill.test.tsx +++ b/ui/pages/confirmations/components/simulation-details/amount-pill.test.tsx @@ -1,16 +1,23 @@ import React from 'react'; import { render } from '@testing-library/react'; +import { BigNumber } from 'bignumber.js'; import { TokenStandard } from '../../../../../shared/constants/transaction'; -import { Numeric } from '../../../../../shared/modules/Numeric'; import Tooltip from '../../../../components/ui/tooltip'; import { AmountPill } from './amount-pill'; import { - Amount, AssetIdentifier, NATIVE_ASSET_IDENTIFIER, TokenAssetIdentifier, } from './types'; +jest.mock('react-redux', () => ({ + useSelector: jest.fn((selector) => selector()), +})); + +jest.mock('../../../../ducks/locale/locale', () => ({ + getIntlLocale: jest.fn(() => 'en-US'), +})); + jest.mock('../../../../components/ui/tooltip', () => ({ __esModule: true, default: jest.fn(({ children }) => children), @@ -35,7 +42,7 @@ const ERC1155_ASSET_MOCK: TokenAssetIdentifier = { const renderAndExpect = ( asset: AssetIdentifier, - amount: Amount, + amount: BigNumber, expected: { text: string; tooltip: string }, ): void => { const { getByText } = render(); @@ -47,50 +54,48 @@ const renderAndExpect = ( }; describe('AmountPill', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + const nativeAndErc20Cases = [ { - isNegative: true, - numeric: new Numeric(-123.1234567, 10), + amount: new BigNumber(-123.1234567), expected: { - text: '- 123.123457', + text: '- 123.1', tooltip: '123.1234567', }, }, { - isNegative: false, - numeric: new Numeric(789.012, 10), + amount: new BigNumber(789.412), expected: { - text: '+ 789.012', - tooltip: '789.012', + text: '+ 789.4', + tooltip: '789.412', }, }, { - isNegative: true, - numeric: new Numeric(-0.000000001, 10), + amount: new BigNumber(-0.000000001), expected: { text: '- <0.000001', tooltip: '0.000000001', }, }, { - isNegative: false, - numeric: new Numeric(0.000000001, 10), + amount: new BigNumber(0.000000001), expected: { text: '+ <0.000001', tooltip: '0.000000001', }, }, { - isNegative: true, - numeric: new Numeric(0, 10), + amount: new BigNumber(-0), expected: { text: '- 0', tooltip: '0', }, }, { - isNegative: false, - numeric: new Numeric(0, 10), + amount: new BigNumber(0), expected: { text: '+ 0', tooltip: '0', @@ -100,26 +105,18 @@ describe('AmountPill', () => { describe('Native', () => { it.each(nativeAndErc20Cases)( - 'renders the correct sign and amount for $numeric.value', - ({ isNegative, numeric, expected }) => { - renderAndExpect( - NATIVE_ASSET_IDENTIFIER, - { isNegative, numeric } as Amount, - expected, - ); + 'renders the correct sign and amount for $amount', + ({ amount, expected }) => { + renderAndExpect(NATIVE_ASSET_IDENTIFIER, amount, expected); }, ); }); describe('ERC20', () => { it.each(nativeAndErc20Cases)( - 'renders the correct sign and amount for $numeric.value', - ({ isNegative, numeric, expected }) => { - renderAndExpect( - ERC20_ASSET_MOCK, - { isNegative, numeric } as Amount, - expected, - ); + 'renders the correct sign and amount for $amount', + ({ amount, expected }) => { + renderAndExpect(ERC20_ASSET_MOCK, amount, expected); }, ); }); @@ -127,16 +124,14 @@ describe('AmountPill', () => { describe('ERC721', () => { const cases = [ { - isNegative: true, - numeric: new Numeric(-1, 10), + amount: new BigNumber(-1), expected: { text: '- #2748', tooltip: '#2748', }, }, { - isNegative: false, - numeric: new Numeric(1, 10), + amount: new BigNumber(1), expected: { text: '+ #2748', tooltip: '#2748', @@ -146,12 +141,8 @@ describe('AmountPill', () => { it.each(cases)( 'renders the token ID with just a plus or minus for $expected.text', - ({ isNegative, numeric, expected }) => { - renderAndExpect( - ERC721_ASSET_MOCK, - { isNegative, numeric } as Amount, - expected, - ); + ({ amount, expected }) => { + renderAndExpect(ERC721_ASSET_MOCK, amount, expected); }, ); }); @@ -159,24 +150,21 @@ describe('AmountPill', () => { describe('ERC1155', () => { const cases = [ { - isNegative: true, - numeric: new Numeric(-3, 10), + amount: new BigNumber(-3), expected: { text: '- 3 #2748', tooltip: '3 #2748', }, }, { - isNegative: false, - numeric: new Numeric(8, 10), + amount: new BigNumber(8), expected: { text: '+ 8 #2748', tooltip: '8 #2748', }, }, { - isNegative: true, - numeric: new Numeric(-12, 10), + amount: new BigNumber(-12), expected: { text: '- 12 #2748', tooltip: '12 #2748', @@ -186,12 +174,8 @@ describe('AmountPill', () => { it.each(cases)( 'renders the correct sign, amount, and token ID for $expected.text', - ({ isNegative, numeric, expected }) => { - renderAndExpect( - ERC1155_ASSET_MOCK, - { isNegative, numeric } as Amount, - expected, - ); + ({ amount, expected }) => { + renderAndExpect(ERC1155_ASSET_MOCK, amount, expected); }, ); }); diff --git a/ui/pages/confirmations/components/simulation-details/amount-pill.tsx b/ui/pages/confirmations/components/simulation-details/amount-pill.tsx index a57d8cf8700d..1204eaf16bd4 100644 --- a/ui/pages/confirmations/components/simulation-details/amount-pill.tsx +++ b/ui/pages/confirmations/components/simulation-details/amount-pill.tsx @@ -1,4 +1,6 @@ import React from 'react'; +import { BigNumber } from 'bignumber.js'; +import { useSelector } from 'react-redux'; import { Box, Text } from '../../../../components/component-library'; import { AlignItems, @@ -11,21 +13,10 @@ import { } from '../../../../helpers/constants/design-system'; import { hexToDecimal } from '../../../../../shared/modules/conversion.utils'; import { TokenStandard } from '../../../../../shared/constants/transaction'; -import { - DEFAULT_PRECISION_DECIMALS, - MIN_DISPLAY_AMOUNT, -} from '../../../../hooks/useCurrencyDisplay'; import Tooltip from '../../../../components/ui/tooltip'; -import { Amount, AssetIdentifier } from './types'; - -// Format an amount for display. -const formatAmount = (amount: Amount): string => { - const displayAmount = amount.numeric.abs().round(DEFAULT_PRECISION_DECIMALS); - - return displayAmount.isZero() && !amount.numeric.isZero() - ? MIN_DISPLAY_AMOUNT - : displayAmount.toString(); -}; +import { getIntlLocale } from '../../../../ducks/locale/locale'; +import { AssetIdentifier } from './types'; +import { formatAmount, formatAmountMaxPrecision } from './formatAmount'; /** * Displays a pill with an amount and a background color indicating whether the amount @@ -37,23 +28,25 @@ const formatAmount = (amount: Amount): string => { */ export const AmountPill: React.FC<{ asset: AssetIdentifier; - amount: Amount; + amount: BigNumber; }> = ({ asset, amount }) => { - const backgroundColor = amount.isNegative + const locale = useSelector(getIntlLocale); + + const backgroundColor = amount.isNegative() ? BackgroundColor.errorMuted : BackgroundColor.successMuted; - const color = amount.isNegative + const color = amount.isNegative() ? TextColor.errorAlternative : TextColor.successDefault; - const amountParts: string[] = [amount.isNegative ? '-' : '+']; + const amountParts: string[] = [amount.isNegative() ? '-' : '+']; const tooltipParts: string[] = []; - // ERC721 amounts are always 1 are not displayed. + // ERC721 amounts are always 1 and are not displayed. if (asset.standard !== TokenStandard.ERC721) { - const formattedAmount = formatAmount(amount); - const fullPrecisionAmount = amount.numeric.abs().toString(); + const formattedAmount = formatAmount(locale, amount.abs()); + const fullPrecisionAmount = formatAmountMaxPrecision(locale, amount.abs()); amountParts.push(formattedAmount); tooltipParts.push(fullPrecisionAmount); diff --git a/ui/pages/confirmations/components/simulation-details/asset-pill.test.tsx b/ui/pages/confirmations/components/simulation-details/asset-pill.test.tsx index 3bc179edd759..334c79f372c9 100644 --- a/ui/pages/confirmations/components/simulation-details/asset-pill.test.tsx +++ b/ui/pages/confirmations/components/simulation-details/asset-pill.test.tsx @@ -21,7 +21,7 @@ jest.mock('../../../../components/app/name', () => ({ })); describe('AssetPill', () => { - afterEach(() => { + beforeEach(() => { jest.clearAllMocks(); }); diff --git a/ui/pages/confirmations/components/simulation-details/formatAmount.test.ts b/ui/pages/confirmations/components/simulation-details/formatAmount.test.ts new file mode 100644 index 000000000000..8a226ddca4f7 --- /dev/null +++ b/ui/pages/confirmations/components/simulation-details/formatAmount.test.ts @@ -0,0 +1,44 @@ +import { BigNumber } from 'bignumber.js'; +import { formatAmount } from './formatAmount'; + +describe('formatAmount', () => { + const locale = 'en-US'; + + it('returns "0" for zero amount', () => { + expect(formatAmount(locale, new BigNumber(0))).toBe('0'); + }); + + it('returns "<0.000001" for 0 < amount < MIN_AMOUNT', () => { + expect(formatAmount(locale, new BigNumber(0.0000009))).toBe('<0.000001'); + }); + + it.each([ + [0.0000456, '0.000046'], + [0.0004567, '0.000457'], + [0.003456, '0.00346'], + [0.023456, '0.0235'], + [0.125456, '0.125'], + ])( + 'formats amount less than 1 with maximum significant digits (%s => %s)', + (amount, expected) => { + expect(formatAmount(locale, new BigNumber(amount))).toBe(expected); + }, + ); + + it.each([ + [1.0034, '1.003'], + [1.034, '1.034'], + [1.3034, '1.303'], + [12.0345, '12.03'], + [121.456, '121.5'], + [1034.123, '1,034'], + [47361034.006, '47,361,034'], + ['12130982923409.5', '12,130,982,923,410'], + ['1213098292340944.5', '1,213,098,292,340,945'], + ])( + 'formats amount greater than or equal to 1 with appropriate decimal precision (%s => %s)', + (amount, expected) => { + expect(formatAmount(locale, new BigNumber(amount))).toBe(expected); + }, + ); +}); diff --git a/ui/pages/confirmations/components/simulation-details/formatAmount.ts b/ui/pages/confirmations/components/simulation-details/formatAmount.ts new file mode 100644 index 000000000000..1314b3b5f911 --- /dev/null +++ b/ui/pages/confirmations/components/simulation-details/formatAmount.ts @@ -0,0 +1,86 @@ +import { BigNumber } from 'bignumber.js'; +import { + MIN_AMOUNT, + DEFAULT_PRECISION, +} from '../../../../hooks/useCurrencyDisplay'; + +// The number of significant decimals places to show for amounts less than 1. +const MAX_SIGNIFICANT_DECIMAL_PLACES = 3; + +const ZERO_DISPLAY = '0'; + +export function formatAmountMaxPrecision( + locale: string, + num: number | BigNumber, +): string { + return new Intl.NumberFormat(locale, { + minimumSignificantDigits: 1, + }).format(new BigNumber(num.toString()).toNumber()); +} + +/** + * Formats the a token amount with variable precision and significant + * digits. + * + * If |amount| < 1, we display a maximum number of significant + * digits. + * + * If |amount| >= 1, we display all digits left of the decimal point. + * We also display some decimal places for smaller amounts, and + * gradually reduce the decimal precision as the amount + * gets bigger until we only show whole numbers. + * + * Examples: + * + * | Amount | Formatted | + * |----------------------|--------------------| + * | 0 | 0 | + * | 0.0000009 | <0.000001 | + * | 0.0000456 | 0.000046 | + * | 0.0004567 | 0.000457 | + * | 0.003456 | 0.00346 | + * | 0.023456 | 0.0235 | + * | 0.125456 | 0.125 | + * | 1.0034 | 1.003 | + * | 1.034 | 1.034 | + * | 1.3034 | 1.303 | + * | 12.0345 | 12.03 | + * | 121.456 | 121.5 | + * | 1,034.123 | 1,034 | + * | 47,361,034.006 | 47,361,034 | + * | 12,130,982,923,409.5 | 12,130,982,923,410 | + * + * @param locale + * @param amount + */ +export function formatAmount(locale: string, amount: BigNumber): string { + if (amount.isZero()) { + return ZERO_DISPLAY; + } + + if (amount.abs().lessThan(MIN_AMOUNT)) { + return `<${formatAmountMaxPrecision(locale, MIN_AMOUNT)}`; + } + + if (amount.abs().lessThan(1)) { + return new Intl.NumberFormat(locale, { + maximumSignificantDigits: MAX_SIGNIFICANT_DECIMAL_PLACES, + } as Intl.NumberFormatOptions).format( + amount.round(DEFAULT_PRECISION).toNumber(), + ); + } + + // Preserve all digits left of the decimal point. + // Cap the digits right of the decimal point: The more digits present + // on the left side of the decimal point, the less decimal places + // we show on the right side. + const digitsLeftOfDecimal = amount.abs().truncated().toString().length; + const maximumFractionDigits = Math.max( + 0, + MAX_SIGNIFICANT_DECIMAL_PLACES - digitsLeftOfDecimal + 1, + ); + + return new Intl.NumberFormat(locale, { + maximumFractionDigits, + } as Intl.NumberFormatOptions).format(amount.toNumber()); +} diff --git a/ui/pages/confirmations/components/simulation-details/simulation-details.test.tsx b/ui/pages/confirmations/components/simulation-details/simulation-details.test.tsx index d72f6ff7fd28..5971e513e3a1 100644 --- a/ui/pages/confirmations/components/simulation-details/simulation-details.test.tsx +++ b/ui/pages/confirmations/components/simulation-details/simulation-details.test.tsx @@ -5,6 +5,7 @@ import { SimulationData, SimulationErrorCode, } from '@metamask/transaction-controller'; +import { BigNumber } from 'bignumber.js'; import { renderWithProvider } from '../../../../../test/lib/render-helpers'; import mockState from '../../../../../test/data/mock-state.json'; import { SimulationDetails } from './simulation-details'; @@ -35,6 +36,7 @@ const renderSimulationDetails = (simulationData?: Partial) => describe('SimulationDetails', () => { beforeEach(() => { + jest.clearAllMocks(); (useBalanceChanges as jest.Mock).mockReturnValue({ pending: false, value: [], @@ -97,8 +99,8 @@ describe('SimulationDetails', () => { it('passes the correct properties to BalanceChangeList components', () => { const balanceChangesMock = [ - { amount: { isNegative: true } }, - { amount: { isNegative: false } }, + { amount: new BigNumber(-123) }, + { amount: new BigNumber(456) }, ] as BalanceChange[]; (useBalanceChanges as jest.Mock).mockReturnValue({ diff --git a/ui/pages/confirmations/components/simulation-details/simulation-details.tsx b/ui/pages/confirmations/components/simulation-details/simulation-details.tsx index b1c1544f6b75..fecd8540fe04 100644 --- a/ui/pages/confirmations/components/simulation-details/simulation-details.tsx +++ b/ui/pages/confirmations/components/simulation-details/simulation-details.tsx @@ -175,8 +175,6 @@ export const SimulationDetails: React.FC = ({ const balanceChangesResult = useBalanceChanges(simulationData); const loading = !simulationData || balanceChangesResult.pending; - console.log('SimulationDetails', simulationData); - useSimulationMetrics({ balanceChanges: balanceChangesResult.value, loadingTime, @@ -223,8 +221,8 @@ export const SimulationDetails: React.FC = ({ ); } - const outgoing = balanceChanges.filter((change) => change.amount.isNegative); - const incoming = balanceChanges.filter((change) => !change.amount.isNegative); + const outgoing = balanceChanges.filter((bc) => bc.amount.isNegative()); + const incoming = balanceChanges.filter((bc) => !bc.amount.isNegative()); return ( diff --git a/ui/pages/confirmations/components/simulation-details/types.ts b/ui/pages/confirmations/components/simulation-details/types.ts index 913873692b76..7db6a30357cd 100644 --- a/ui/pages/confirmations/components/simulation-details/types.ts +++ b/ui/pages/confirmations/components/simulation-details/types.ts @@ -1,6 +1,6 @@ import { Hex } from '@metamask/utils'; +import { BigNumber } from 'bignumber.js'; import { TokenStandard } from '../../../../../shared/constants/transaction'; -import { Numeric } from '../../../../../shared/modules/Numeric'; export const NATIVE_ASSET_IDENTIFIER: NativeAssetIdentifier = { standard: TokenStandard.none, @@ -36,57 +36,28 @@ export type AssetIdentifier = Readonly< >; /** - * Represents an amount of an asset, including its magnitude and sign. + * Describes a change in an asset's balance to a user's wallet. */ -export type Amount = Readonly<{ - /** - * Indicates whether the amount is negative (e.g., a decrease in balance). - */ - isNegative: boolean; - +export type BalanceChange = Readonly<{ /** - * The quantity of the smallest denomination of the asset (base units), - * represented as a hexadecimal string. - * For example: In the case of ETH, this would be the number of wei. + * The asset identifier for the balance change. */ - quantity: Hex; + asset: AssetIdentifier; /** - * The number of decimal places the associated asset supports. + * The quantity of asset tokens, expressed as a decimal value. * - * This value is the negation of the exponent used when converting - * the quantity to the decimal amount of a token. + * This property represents the amount of tokens, taking into account the + * number of decimals supported by the asset. The value can be positive + * (increase) or negative (decrease). * - * To calculate the token amount in decimal form, use the formula: - * `tokenAmount = hexToDecimal(quantity) / (10 ^ decimals)` - * - * Example: If the asset is ETH, the quantity is expressed in wei - * (the smallest unit of ETH) and decimals would be 18. The amount - * of ETH tokens would be: `ethAmount = quantity / (10 ^ 18)` + * Example: If an asset supports 18 decimals, an `amount` of 1.5 represents + * 1.5 tokens, or more precisely, 1.5 * 10^18 of the smallest divisible unit. */ - decimals: number; - - /** - * The numeric representation of the amount, taking into account the - * sign, quantity and decimals. - */ - numeric: Numeric; -}>; + amount: BigNumber; -/** - * Describes a change in an asset's balance to a user's wallet. - */ -export type BalanceChange = { - /** - * The asset identifier for the balance change. - */ - asset: AssetIdentifier; - /** - * The amount of the asset that changed. - */ - amount: Amount; /** * The amount of fiat currency that corresponds to the asset amount. */ fiatAmount: FiatAmount; -}; +}>; diff --git a/ui/pages/confirmations/components/simulation-details/useBalanceChanges.test.ts b/ui/pages/confirmations/components/simulation-details/useBalanceChanges.test.ts index 0020768a04c4..f7e7d647a213 100644 --- a/ui/pages/confirmations/components/simulation-details/useBalanceChanges.test.ts +++ b/ui/pages/confirmations/components/simulation-details/useBalanceChanges.test.ts @@ -4,10 +4,10 @@ import { SimulationData, SimulationTokenStandard, } from '@metamask/transaction-controller'; +import { BigNumber } from 'bignumber.js'; import { TokenStandard } from '../../../../../shared/constants/transaction'; import { getConversionRate } from '../../../../ducks/metamask/metamask'; import { getTokenStandardAndDetails } from '../../../../store/actions'; -import { Numeric } from '../../../../../shared/modules/Numeric'; import { fetchTokenExchangeRates } from '../../../../helpers/utils/util'; import { useBalanceChanges } from './useBalanceChanges'; import { FIAT_UNAVAILABLE } from './types'; @@ -159,7 +159,7 @@ describe('useBalanceChanges', () => { const { result, waitForNextUpdate } = setupHook([ { ...dummyBalanceChange, - difference: DIFFERENCE_1_MOCK, + difference: '0x11', isDecrease: true, address: ERC20_TOKEN_ADDRESS_1_MOCK, standard: SimulationTokenStandard.erc20, @@ -176,16 +176,11 @@ describe('useBalanceChanges', () => { standard: TokenStandard.ERC20, tokenId: undefined, }, - amount: { - isNegative: true, - quantity: DIFFERENCE_1_MOCK, - decimals: 3, - numeric: expect.any(Numeric), - }, - fiatAmount: Number('-0.0255'), + amount: new BigNumber('-0.017'), + fiatAmount: -0.0255, }, ]); - expect(changes[0].amount.numeric.toString()).toBe('-0.017'); + expect(changes[0].amount.toString()).toBe('-0.017'); }); it('handles multiple token balance changes', async () => { @@ -210,9 +205,9 @@ describe('useBalanceChanges', () => { const changes = result.current.value; expect(changes).toHaveLength(2); - expect(changes[0].amount.numeric.toString()).toBe('-0.017'); + expect(changes[0].amount.toString()).toBe('-0.017'); expect(changes[0].fiatAmount).toBe(Number('-0.0255')); - expect(changes[1].amount.numeric.toString()).toBe('0.0002'); + expect(changes[1].amount.toString()).toBe('0.0002'); expect(changes[1].fiatAmount).toBe(Number('0.0012')); }); @@ -220,7 +215,7 @@ describe('useBalanceChanges', () => { const { result, waitForNextUpdate } = setupHook([ { ...dummyBalanceChange, - difference: DIFFERENCE_1_MOCK, + difference: '0x1', isDecrease: true, address: NFT_TOKEN_ADDRESS_MOCK, standard: SimulationTokenStandard.erc721, @@ -237,12 +232,7 @@ describe('useBalanceChanges', () => { standard: TokenStandard.ERC721, tokenId: TOKEN_ID_1_MOCK, }, - amount: { - isNegative: true, - quantity: DIFFERENCE_1_MOCK, - decimals: 0, - numeric: expect.any(Numeric), - }, + amount: new BigNumber('-1'), fiatAmount: FIAT_UNAVAILABLE, }, ]); @@ -261,7 +251,7 @@ describe('useBalanceChanges', () => { await waitForNextUpdate(); - expect(result.current.value[0].amount.decimals).toBe(18); + expect(result.current.value[0].amount.decimalPlaces()).toBe(18); }); }); @@ -291,18 +281,10 @@ describe('useBalanceChanges', () => { asset: { standard: TokenStandard.none, }, - amount: { - isNegative: true, - quantity: DIFFERENCE_ETH_MOCK, - decimals: 18, - numeric: expect.any(Numeric), - }, + amount: new BigNumber('-5373.003641998677469065'), fiatAmount: Number('-16119.010925996032'), }, ]); - expect(changes[0].amount.numeric.toString()).toBe( - '-5373.003641998677469065', - ); }); it('handles no native balance change', async () => { @@ -340,14 +322,14 @@ describe('useBalanceChanges', () => { expect(changes[0].asset).toEqual({ standard: TokenStandard.none, }); - expect(changes[0].amount.numeric.toString()).toBe( - '-5373.003641998677469065', + expect(changes[0].amount).toEqual( + new BigNumber('-5373.003641998677469065'), ); expect(changes[0].fiatAmount).toBe(Number('-16119.010925996032')); expect(changes[1].asset).toEqual({ address: ERC20_TOKEN_ADDRESS_1_MOCK, standard: TokenStandard.ERC20, }); - expect(changes[1].amount.numeric.toString()).toBe('0.002'); + expect(changes[1].amount).toEqual(new BigNumber('0.002')); }); }); diff --git a/ui/pages/confirmations/components/simulation-details/useBalanceChanges.ts b/ui/pages/confirmations/components/simulation-details/useBalanceChanges.ts index 614ad98428c9..be9f861ebc1a 100644 --- a/ui/pages/confirmations/components/simulation-details/useBalanceChanges.ts +++ b/ui/pages/confirmations/components/simulation-details/useBalanceChanges.ts @@ -6,16 +6,15 @@ import { SimulationTokenBalanceChange, SimulationTokenStandard, } from '@metamask/transaction-controller'; +import { BigNumber } from 'bignumber.js'; import { ContractExchangeRates } from '@metamask/assets-controllers'; import { useAsyncResultOrThrow } from '../../../../hooks/useAsyncResult'; import { getTokenStandardAndDetails } from '../../../../store/actions'; import { TokenStandard } from '../../../../../shared/constants/transaction'; -import { Numeric } from '../../../../../shared/modules/Numeric'; import { getConversionRate } from '../../../../ducks/metamask/metamask'; import { getCurrentChainId, getCurrentCurrency } from '../../../../selectors'; import { fetchTokenExchangeRates } from '../../../../helpers/utils/util'; import { - Amount, BalanceChange, FIAT_UNAVAILABLE, NATIVE_ASSET_IDENTIFIER, @@ -44,12 +43,13 @@ function convertStandard(standard: SimulationTokenStandard) { function getAssetAmount( { isDecrease: isNegative, difference: quantity }: SimulationBalanceChange, decimals: number, -): Amount { - const numeric = Numeric.from(quantity, 16) - .times(isNegative ? -1 : 1, 10) - .toBase(10) - .shiftedBy(decimals); - return { isNegative, quantity, decimals, numeric }; +): BigNumber { + return ( + new BigNumber(quantity, 16) + .times(isNegative ? -1 : 1) + // Shift the decimal point to the left by the number of decimals. + .shift(-decimals) + ); } // Fetches the decimals for the given token address. @@ -106,9 +106,7 @@ function getNativeBalanceChange( } const asset = NATIVE_ASSET_IDENTIFIER; const amount = getAssetAmount(nativeBalanceChange, NATIVE_DECIMALS); - const fiatAmount = amount.numeric - .applyConversionRate(nativeFiatRate) - .toNumber(); + const fiatAmount = amount.times(nativeFiatRate).toNumber(); return { asset, amount, fiatAmount }; } @@ -131,7 +129,7 @@ function getTokenBalanceChanges( const fiatRate = erc20FiatRates[tokenBc.address]; const fiatAmount = fiatRate - ? amount.numeric.applyConversionRate(fiatRate).toNumber() + ? amount.times(fiatRate).toNumber() : FIAT_UNAVAILABLE; return { asset, amount, fiatAmount }; diff --git a/ui/pages/confirmations/components/simulation-details/useSimulationMetrics.test.ts b/ui/pages/confirmations/components/simulation-details/useSimulationMetrics.test.ts index c290a0fc9f9d..152fdf55eb56 100644 --- a/ui/pages/confirmations/components/simulation-details/useSimulationMetrics.test.ts +++ b/ui/pages/confirmations/components/simulation-details/useSimulationMetrics.test.ts @@ -3,6 +3,7 @@ import { SimulationData, SimulationErrorCode, } from '@metamask/transaction-controller'; +import { BigNumber } from 'bignumber.js'; import { useTransactionEventFragment } from '../../hooks/useTransactionEventFragment'; import { TokenStandard } from '../../../../../shared/constants/transaction'; import { @@ -44,7 +45,7 @@ const SYMBOL_MOCK = 'TST'; const BALANCE_CHANGE_MOCK = { asset: { address: ADDRESS_MOCK, standard: TokenStandard.ERC20 }, - amount: { isNegative: true, quantity: '0x1', decimals: 18 }, + amount: new BigNumber(-1), fiatAmount: 1.23, } as unknown as BalanceChange; @@ -178,7 +179,7 @@ describe('useSimulationMetrics', () => { ])('with asset quantity if %s', (_, isNegative, property) => { const balanceChange = { ...BALANCE_CHANGE_MOCK, - amount: { ...BALANCE_CHANGE_MOCK.amount, isNegative }, + amount: new BigNumber(isNegative ? -1 : 1), }; expectUpdateTransactionEventFragmentCalled( @@ -257,7 +258,7 @@ describe('useSimulationMetrics', () => { { ...BALANCE_CHANGE_MOCK, asset: { ...BALANCE_CHANGE_MOCK.asset, standard }, - amount: { ...BALANCE_CHANGE_MOCK.amount, isNegative }, + amount: new BigNumber(isNegative ? -1 : 1), } as BalanceChange, ], }, @@ -303,10 +304,7 @@ describe('useSimulationMetrics', () => { (_, fiatAmount, isNegative, property, expected) => { const balanceChange = { ...BALANCE_CHANGE_MOCK, - amount: { - ...BALANCE_CHANGE_MOCK.amount, - isNegative, - }, + amount: new BigNumber(isNegative ? -1 : 1), fiatAmount, }; @@ -397,10 +395,7 @@ describe('useSimulationMetrics', () => { const balanceChange = { ...BALANCE_CHANGE_MOCK, - amount: { - ...BALANCE_CHANGE_MOCK.amount, - isNegative, - }, + amount: new BigNumber(isNegative ? -1 : 1), asset: { ...BALANCE_CHANGE_MOCK.asset, standard }, }; @@ -423,10 +418,7 @@ describe('useSimulationMetrics', () => { ])('with asset total value if %s', (_, isNegative, property) => { const balanceChange1 = { ...BALANCE_CHANGE_MOCK, - amount: { - ...BALANCE_CHANGE_MOCK.amount, - isNegative, - }, + amount: new BigNumber(isNegative ? -1 : 1), fiatAmount: 1.23, }; diff --git a/ui/pages/confirmations/components/simulation-details/useSimulationMetrics.ts b/ui/pages/confirmations/components/simulation-details/useSimulationMetrics.ts index d1eb80c8dffe..433b1f145b89 100644 --- a/ui/pages/confirmations/components/simulation-details/useSimulationMetrics.ts +++ b/ui/pages/confirmations/components/simulation-details/useSimulationMetrics.ts @@ -81,11 +81,11 @@ export function useSimulationMetrics({ useIncompleteAssetEvent(balanceChanges, displayNamesByAddress); const receivingAssets = balanceChanges.filter( - (change) => !change.amount.isNegative, + (change) => !change.amount.isNegative(), ); - const sendingAssets = balanceChanges.filter( - (change) => change.amount.isNegative, + const sendingAssets = balanceChanges.filter((change) => + change.amount.isNegative(), ); const simulationResponse = getSimulationResponseType(simulationData);