Skip to content

Commit

Permalink
feat(wallet): Add formatting with subscripts to amounts with long dec…
Browse files Browse the repository at this point in the history
…imals (#4693)
  • Loading branch information
msarcev authored Jan 8, 2025
1 parent cc1b051 commit c2df0aa
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 9 deletions.
15 changes: 15 additions & 0 deletions apps/core/src/hooks/__tests__/useFormatCoin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
54 changes: 53 additions & 1 deletion apps/core/src/utils/formatAmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ['--'];
}
Expand All @@ -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];
}

Expand All @@ -37,3 +47,45 @@ export function formatAmount(...args: Parameters<typeof formatAmountParts>) {
.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}`;
};
40 changes: 32 additions & 8 deletions apps/wallet/src/ui/app/pages/home/tokens/coin-balance/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 (
<>
<div className="flex items-baseline gap-0.5">
<div
className="text-headline-lg text-neutral-10 dark:text-neutral-92"
data-testid="coin-balance"
>
{formatted}
</div>
{shouldShowTooltip ? (
<Tooltip
text={formatBalance(
walletBalance,
coinMetadata?.decimals ?? 9,
CoinFormat.FULL,
)}
position={TooltipPosition.Bottom}
>
<div
className="text-headline-lg text-neutral-10 dark:text-neutral-92"
data-testid="coin-balance"
>
{formatted}
</div>
</Tooltip>
) : (
<div
className="text-headline-lg text-neutral-10 dark:text-neutral-92"
data-testid="coin-balance"
>
{formatted}
</div>
)}
<div className="text-label-md text-neutral-40">{symbol}</div>
</div>
{network === Network.Mainnet ? <WalletBalanceUsd amount={walletBalance} /> : null}
Expand Down

0 comments on commit c2df0aa

Please sign in to comment.