diff --git a/apps/core/src/hooks/__tests__/useFormatCoin.test.ts b/apps/core/src/hooks/__tests__/useFormatCoin.test.ts index c55d16574c5..a138a90a8ca 100644 --- a/apps/core/src/hooks/__tests__/useFormatCoin.test.ts +++ b/apps/core/src/hooks/__tests__/useFormatCoin.test.ts @@ -24,6 +24,21 @@ describe('formatBalance', () => { expect(formatBalance('0.000', IOTA_DECIMALS)).toEqual('0'); }); + it('formats decimal amounts with less than 4 leading zeroes, truncated with up to 4 decimals', () => { + expect(formatBalance('512345678', IOTA_DECIMALS)).toEqual('0.5'); + expect(formatBalance('51234567', IOTA_DECIMALS)).toEqual('0.05'); + expect(formatBalance('5123456', IOTA_DECIMALS)).toEqual('0.005'); + expect(formatBalance('523456', IOTA_DECIMALS)).toEqual('0.0005'); + }); + + it('formats decimal amounts with 4 or more leading zeroes (after decimal point) with subscripts', () => { + expect(formatBalance('19723', IOTA_DECIMALS)).toEqual('0.0₄19723'); + expect(formatBalance('1234', IOTA_DECIMALS)).toEqual('0.0₅1234'); + expect(formatBalance('123', IOTA_DECIMALS)).toEqual('0.0₆123'); + expect(formatBalance('12', IOTA_DECIMALS)).toEqual('0.0₇12'); + expect(formatBalance('1', IOTA_DECIMALS)).toEqual('0.0₈1'); + }); + it('formats integer amounts correctly', () => { expect(formatBalance(toNano('1'), IOTA_DECIMALS)).toEqual('1'); expect(formatBalance(toNano('1.0001'), IOTA_DECIMALS)).toEqual('1'); diff --git a/apps/core/src/utils/formatAmount.ts b/apps/core/src/utils/formatAmount.ts index a745d5c7552..b241f16556c 100644 --- a/apps/core/src/utils/formatAmount.ts +++ b/apps/core/src/utils/formatAmount.ts @@ -4,7 +4,7 @@ import BigNumber from 'bignumber.js'; -export function formatAmountParts(amount?: BigNumber | bigint | number | string | null) { +export function formatAmountParts(amount?: BigNumber | bigint | number | string | null): string[] { if (typeof amount === 'undefined' || amount === null) { return ['--']; } @@ -29,6 +29,16 @@ export function formatAmountParts(amount?: BigNumber | bigint | number | string bn = bn.decimalPlaces(2, BigNumber.ROUND_DOWN); } + if (bnAbs.gt(0) && bnAbs.lt(1)) { + const leadingZeros = countDecimalLeadingZeros(bn.toFormat()); + + if (leadingZeros >= 4) { + return [formatWithSubscript(bn.toFormat(), leadingZeros), postfix]; + } else { + return [bn.toFormat(leadingZeros + 1), postfix]; + } + } + return [bn.toFormat(), postfix]; } @@ -37,3 +47,45 @@ export function formatAmount(...args: Parameters) { .filter(Boolean) .join(' '); } + +export const countDecimalLeadingZeros = ( + input: BigNumber | bigint | number | string | null, +): number => { + if (input === null) { + return 0; + } + + const [, decimals] = input.toString().split('.'); + + if (!decimals) { + return 0; + } + + let count = 0; + + for (const digit of decimals) { + if (digit === '0') { + count++; + } else { + break; + } + } + + return count; +}; + +const SUBSCRIPTS = ['₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉']; + +export const formatWithSubscript = ( + input: BigNumber | bigint | number | string | null, + zeroCount: number, +): string => { + if (input === null) { + return '0'; + } + + const [, decimals] = input.toString().split('.'); + const remainder = decimals.slice(zeroCount); + + return `0.0${SUBSCRIPTS[zeroCount]}${remainder}`; +}; diff --git a/apps/wallet/src/ui/app/pages/home/tokens/coin-balance/index.tsx b/apps/wallet/src/ui/app/pages/home/tokens/coin-balance/index.tsx index 75aecdf86ca..7660cf9bcd7 100644 --- a/apps/wallet/src/ui/app/pages/home/tokens/coin-balance/index.tsx +++ b/apps/wallet/src/ui/app/pages/home/tokens/coin-balance/index.tsx @@ -2,10 +2,12 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 import { useAppSelector } from '_hooks'; -import { useBalanceInUSD, useFormatCoin } from '@iota/core'; +import { CoinFormat, formatBalance, useBalanceInUSD, useFormatCoin } from '@iota/core'; import { Network } from '@iota/iota-sdk/client'; import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils'; import { useMemo } from 'react'; +import { Tooltip, TooltipPosition } from '@iota/apps-ui-kit'; +import BigNumber from 'bignumber.js'; export interface CoinProps { type: string; @@ -37,17 +39,39 @@ function WalletBalanceUsd({ amount: walletBalance }: WalletBalanceUsdProps) { export function CoinBalance({ amount: walletBalance, type }: CoinProps) { const network = useAppSelector((state) => state.app.network); - const [formatted, symbol] = useFormatCoin(walletBalance, type); + const [formatted, symbol, { data: coinMetadata }] = useFormatCoin(walletBalance, type); + + const iotaDecimals = coinMetadata?.decimals ?? 9; + const bnBalance = new BigNumber(walletBalance.toString()).shiftedBy(-1 * iotaDecimals); + const shouldShowTooltip = bnBalance.gt(0) && bnBalance.lt(1); return ( <>
-
- {formatted} -
+ {shouldShowTooltip ? ( + +
+ {formatted} +
+
+ ) : ( +
+ {formatted} +
+ )}
{symbol}
{network === Network.Mainnet ? : null}