diff --git a/src/components/DollarValue/DollarValue.tsx b/src/components/DollarValue/DollarValue.tsx
index 47cb66db..c5572bfe 100644
--- a/src/components/DollarValue/DollarValue.tsx
+++ b/src/components/DollarValue/DollarValue.tsx
@@ -4,7 +4,7 @@ import React from 'react';
import { ComponentPropsWithoutRef } from 'react';
import { FiAlertTriangle } from 'react-icons/fi';
import { Tooltip } from '../Tooltip';
-import { formatFTAmount, parseAmount } from '../../lib/util/parseAmount';
+import { formatAmount, parseAmount } from '../../lib/util/parseAmount';
export interface DollarValueProps extends ComponentPropsWithoutRef<'p'> {
dollarValue?: string;
@@ -31,9 +31,7 @@ export function DollarValue(props: DollarValueProps) {
let dollarValueFormatted = '';
if (dollarValue !== undefined && dollarValue !== '') {
- const dollarValueBigInt = parseAmount(dollarValue, 2);
- const { amountFormattedPreview } = formatFTAmount(dollarValueBigInt, 2);
- dollarValueFormatted = amountFormattedPreview;
+ dollarValueFormatted = formatAmount(parseAmount(dollarValue, 2), 2).preview;
}
if (dollarValue !== undefined && dollarValue !== '') {
diff --git a/src/components/Token/Token.tsx b/src/components/Token/Token.tsx
index d16867e4..b15bc936 100644
--- a/src/components/Token/Token.tsx
+++ b/src/components/Token/Token.tsx
@@ -6,7 +6,7 @@ import { FiTrash2 } from 'react-icons/fi';
import { Button } from '../Button';
import { Tooltip } from '../Tooltip';
import { DollarValue } from '../DollarValue';
-import { formatFTAmount } from '../../lib/util/parseAmount';
+import { formatAmount } from '../../lib/util/parseAmount';
export interface TokenProps extends ComponentPropsWithoutRef<'div'> {
logo?: React.ReactNode;
@@ -41,12 +41,9 @@ export function Token(props: TokenProps) {
let bigintBalance = BigInt(0);
if (balance !== '') {
bigintBalance = BigInt(balance);
- const { amountFormattedPreview, amountFormattedFull } = formatFTAmount(
- bigintBalance,
- decimals,
- );
- rawBalance = amountFormattedFull;
- formattedBalance = amountFormattedPreview;
+ const { preview, full } = formatAmount(bigintBalance, decimals);
+ rawBalance = full;
+ formattedBalance = preview;
} else {
formattedBalance = undefined;
rawBalance = undefined;
diff --git a/src/lib/ConnectMassaWallets/components/MASBalance.tsx b/src/lib/ConnectMassaWallets/components/MASBalance.tsx
index ecb08224..7ea99ac7 100644
--- a/src/lib/ConnectMassaWallets/components/MASBalance.tsx
+++ b/src/lib/ConnectMassaWallets/components/MASBalance.tsx
@@ -29,7 +29,7 @@ export function MASBalance() {
const formattedBalance = formatAmount(
fromMAS(balance?.candidateBalance || '0').toString(),
9,
- ).amountFormattedFull;
+ ).full;
return (
diff --git a/src/lib/util/handlePercent.test.ts b/src/lib/util/handlePercent.test.ts
index 05ab6c74..06bd80e2 100644
--- a/src/lib/util/handlePercent.test.ts
+++ b/src/lib/util/handlePercent.test.ts
@@ -12,7 +12,7 @@ describe('handlePercent', () => {
18,
'ETH',
);
- expect(result).toBe('0.250000000000000000');
+ expect(result).toBe('0.25');
});
it('should return correctly formatted amount when symbol is massaToken and newAmount is within balance', () => {
@@ -24,7 +24,7 @@ describe('handlePercent', () => {
9,
massaToken,
);
- expect(result).toBe('0.500000000');
+ expect(result).toBe('0.5');
});
it('should return 0 when balance minus fees is less than 0', () => {
@@ -36,7 +36,7 @@ describe('handlePercent', () => {
9,
massaToken,
);
- expect(result).toBe('0.000000000');
+ expect(result).toBe('0');
});
it('should return balance minus fees when newAmount exceeds balance', () => {
@@ -48,11 +48,11 @@ describe('handlePercent', () => {
9,
massaToken,
);
- expect(result).toBe('0.900000000');
+ expect(result).toBe('0.9');
});
it('should handle zero amount correctly', () => {
const result = handlePercent(0n, 10n, 0n, 1000n, 9, massaToken);
- expect(result).toBe('0.000000000');
+ expect(result).toBe('0');
});
});
diff --git a/src/lib/util/handlePercent.ts b/src/lib/util/handlePercent.ts
index 88704ed1..a5e1cb05 100644
--- a/src/lib/util/handlePercent.ts
+++ b/src/lib/util/handlePercent.ts
@@ -21,5 +21,5 @@ export function handlePercent(
}
}
- return formatAmount(newAmount.toString(), decimals).amountFormattedFull;
+ return formatAmount(newAmount.toString(), decimals).full;
}
diff --git a/src/lib/util/parseAmount.stories.tsx b/src/lib/util/parseAmount.stories.tsx
new file mode 100644
index 00000000..f87c27d7
--- /dev/null
+++ b/src/lib/util/parseAmount.stories.tsx
@@ -0,0 +1,45 @@
+import { formatAmount } from './parseAmount';
+
+export default { title: 'Amount/Format' };
+
+const inputs = [
+ '123456789123456789',
+ '1234567891234567890000',
+ '1234567',
+ '123',
+ '0',
+ '',
+ '1000000000200000000',
+ '1000000000',
+ '1000000000000000000',
+ '1000000000000000000234',
+ '1000000000000000000000000000000000',
+ '1000000000000000000000000000000001',
+];
+
+export const _FormatAmount = {
+ render: () => {
+ return (
+
+
input: preview / full
+ {[2, 9, 18].map((decimals) => {
+ return (
+ <>
+
+ Format amount that have {decimals} decimals
+
+ {inputs.map((input) => {
+ const { preview, full } = formatAmount(input, decimals);
+ return (
+
+ {input}: {preview} / {full}
+
+ );
+ })}
+ >
+ );
+ })}
+
+ );
+ },
+};
diff --git a/src/lib/util/parseAmount.test.ts b/src/lib/util/parseAmount.test.ts
index 66e5dd4a..f92748b2 100644
--- a/src/lib/util/parseAmount.test.ts
+++ b/src/lib/util/parseAmount.test.ts
@@ -1,161 +1,156 @@
import {
formatAmount,
- formatStandard,
- roundDecimalPartToOneSignificantDigit,
+ roundDecimalPartToTwoSignificantDigit,
} from './parseAmount';
describe('formatAmount', () => {
test('formats an empty string', () => {
const result = formatAmount('', 18);
expect(result).toEqual({
- amountFormattedPreview: '0.0',
- amountFormattedFull: '0.000000000000000000',
+ preview: '0',
+ full: '0',
});
});
test('formats an amount with default parameters', () => {
const result = formatAmount('123456789012345678901', 18);
expect(result).toEqual({
- amountFormattedPreview: '123.46',
- amountFormattedFull: '123.456789012345678901',
+ preview: '123.46',
+ full: '123.456789012345678901',
+ });
+ });
+
+ test('formats an amount with BigInt', () => {
+ const result = formatAmount(123456789012345678901n, 18);
+ expect(result).toEqual({
+ preview: '123.46',
+ full: '123.456789012345678901',
});
});
test('formats an amount with less than the specified decimals', () => {
const result = formatAmount('12345', 8);
expect(result).toEqual({
- amountFormattedPreview: '0.0001',
- amountFormattedFull: '0.00012345',
+ preview: '0.00012',
+ full: '0.00012345',
});
});
test('formats an amount with custom separator', () => {
const result = formatAmount('123456789012345678901', 9, "'");
expect(result).toEqual({
- amountFormattedPreview: "123'456'789'012.35",
- amountFormattedFull: "123'456'789'012.345678901",
+ preview: "123'456'789'012.35",
+ full: "123'456'789'012.345678901",
});
});
test('adds padding zeroes when necessary', () => {
const result = formatAmount('1', 18, ',');
expect(result).toEqual({
- amountFormattedPreview: '0.000000000000000001',
- amountFormattedFull: '0.000000000000000001',
+ preview: '0.000000000000000001',
+ full: '0.000000000000000001',
});
});
test('handles amount with exact decimals length', () => {
const result = formatAmount('1000000000000000000', 18);
expect(result).toEqual({
- amountFormattedPreview: '1.00',
- amountFormattedFull: '1.000000000000000000',
+ preview: '1',
+ full: '1',
});
});
test('formats an amount with less than the specified decimals and round up', () => {
- const result = formatAmount('69000', 9);
+ const result = formatAmount('69500', 9);
expect(result).toEqual({
- amountFormattedPreview: '0.00007',
- amountFormattedFull: '0.000069000',
+ preview: '0.00007',
+ full: '0.0000695',
});
});
-});
-
-describe('roundDecimalPartToOneSignificantDigit', () => {
- test("should return '0' when all digits are zero", () => {
- expect(roundDecimalPartToOneSignificantDigit('000')).toEqual('0');
- });
- test('should handle a single zero without leading zeroes', () => {
- expect(roundDecimalPartToOneSignificantDigit('0')).toEqual('0');
- });
-
- test('should handle a single digit without rounding', () => {
- expect(roundDecimalPartToOneSignificantDigit('4')).toEqual('4');
- });
-
- test('should round down when the second digit is less than 5', () => {
- expect(roundDecimalPartToOneSignificantDigit('004300')).toEqual('004');
- });
-
- test('should round up when the second digit is 5 or more', () => {
- expect(roundDecimalPartToOneSignificantDigit('00046')).toEqual('0005');
- });
-
- test('should round up and handle carry-over with trailing zeroes', () => {
- expect(roundDecimalPartToOneSignificantDigit('0099')).toEqual('01');
- });
-});
-
-describe('formatStandard', () => {
test('formats an empty string', () => {
- const result = formatStandard('', 18);
+ const result = formatAmount('', 18).full;
expect(result).toEqual('0');
});
test('formats an amount with default parameters', () => {
- const result = formatStandard('123456789012345678901', 18);
+ const result = formatAmount('123456789012345678901', 18).full;
expect(result).toEqual('123.456789012345678901');
});
test('formats an amount with less than the specified decimals', () => {
- const result = formatStandard('12345', 8);
+ const result = formatAmount('12345', 8).full;
expect(result).toEqual('0.00012345');
});
test('adds padding zeroes when necessary', () => {
- const result = formatStandard('1', 18);
+ const result = formatAmount('1', 18).full;
expect(result).toEqual('0.000000000000000001');
});
test('handles amount with exact decimals length', () => {
- const result = formatStandard('1000000000000000000', 18);
+ const result = formatAmount('1000000000000000000', 18).full;
expect(result).toEqual('1');
});
test('formats an amount with less than the specified decimals and round up', () => {
- const result = formatStandard('69000', 9);
+ const result = formatAmount('69000', 9).full;
expect(result).toEqual('0.000069');
});
- it('formatStandard with min string value', () => {
+ it('formatAmount with min string value', () => {
const value = '0000000000';
-
- const result = formatStandard(value.toString());
-
+ const result = formatAmount(value.toString()).full;
expect(result).toBe('0');
});
- it('formatStandard with min bigint value', () => {
+ it('formatAmount with min bigint value', () => {
const value = 0n;
-
- const result = formatStandard(value.toString());
-
+ const result = formatAmount(value.toString()).full;
expect(result).toBe('0');
});
- it('formatStandard with mid range string value', () => {
+ it('formatAmount with mid range string value', () => {
const value = '10000000000000';
-
- const result = formatStandard(value.toString());
-
+ const result = formatAmount(value.toString()).full;
expect(result).toBe('10,000');
});
- it('formatStandard with mid range bigint value', () => {
+ it('formatAmount with mid range bigint value', () => {
const value = 10000000000000n;
-
- const result = formatStandard(value.toString());
-
+ const result = formatAmount(value.toString()).full;
expect(result).toBe('10,000');
});
- it('formatStandard with max string value', () => {
+ it('formatAmount with max string value', () => {
const value = '922337203600000000000';
+ const result = formatAmount(value.toString()).full;
+ expect(result).toBe('922,337,203,600');
+ });
+});
- const result = formatStandard(value.toString());
+describe('roundDecimalPartToOneSignificantDigit', () => {
+ test("should return '0' when all digits are zero", () => {
+ expect(roundDecimalPartToTwoSignificantDigit('000')).toEqual('0');
+ });
- expect(result).toBe('922,337,203,600');
+ test('should handle a single zero without leading zeroes', () => {
+ expect(roundDecimalPartToTwoSignificantDigit('0')).toEqual('0');
+ });
+
+ test('should handle a single digit without rounding', () => {
+ expect(roundDecimalPartToTwoSignificantDigit('4')).toEqual('4');
+ });
+
+ test('should round down when the second digit is less than 5', () => {
+ expect(roundDecimalPartToTwoSignificantDigit('0043100')).toEqual('0043');
+ });
+
+ test('should round up when the second digit is 5 or more', () => {
+ expect(roundDecimalPartToTwoSignificantDigit('000468')).toEqual('00047');
+ });
+
+ test('should round up and handle carry-over with trailing zeroes', () => {
+ expect(roundDecimalPartToTwoSignificantDigit('0099')).toEqual('0099');
});
});
diff --git a/src/lib/util/parseAmount.ts b/src/lib/util/parseAmount.ts
index 40a6a388..d14088c0 100644
--- a/src/lib/util/parseAmount.ts
+++ b/src/lib/util/parseAmount.ts
@@ -1,22 +1,7 @@
import currency from 'currency.js';
-import { formatValue } from 'react-currency-input-field';
-import { formatUnits, parseUnits } from 'viem';
+import { parseUnits } from 'viem';
-export interface FormattedAmount {
- amountFormattedPreview: string;
- amountFormattedFull: string;
-}
-
-export function removeTrailingZeros(numStr: string): string {
- return numStr.replace(/\.?0+$/, '');
-}
-
-// Like format amount but remove the trailing zeros
-export function formatStandard(amount: string, decimals = 9): string {
- return removeTrailingZeros(
- formatAmount(amount, decimals).amountFormattedFull,
- );
-}
+// Parse
/**
* reverse format FT amount
@@ -25,35 +10,23 @@ export function parseAmount(amount: string, tokenDecimal: number): bigint {
return parseUnits(amount, tokenDecimal);
}
-export function formatFTAmount(
- bigintBalance: bigint,
- decimals: number,
-): FormattedAmount {
- const amountFormattedPreview = formatValue({
- value: formatUnits(bigintBalance, decimals).toString(),
- groupSeparator: ',',
- decimalSeparator: '.',
- decimalScale: 2,
- });
-
- const amountFormattedFull = formatValue({
- value: formatUnits(bigintBalance, decimals).toString(),
- groupSeparator: ',',
- decimalSeparator: '.',
- decimalScale: decimals,
- });
-
- return {
- amountFormattedPreview,
- amountFormattedFull,
- };
+// Format
+export interface FormattedAmount {
+ preview: string;
+ full: string;
}
+// Format the amount, parameter must be a string in the smallest unit or a bigint
export function formatAmount(
- amount: string,
+ amount: string | bigint,
decimals = 9,
separator = ',',
): FormattedAmount {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ if (typeof amount === 'bigint') {
+ amount = amount.toString();
+ }
+ amount = amount as string;
const decimal = '.';
if (amount.length < decimals) {
@@ -70,83 +43,82 @@ export function formatAmount(
precision: 0,
}).format();
- let amountFormattedPreview: string;
+ let preview: string;
if (formattedIntegerPart === '0' && decimalPart.startsWith('00')) {
- amountFormattedPreview = `${formattedIntegerPart}${decimal}${roundDecimalPartToOneSignificantDigit(
+ preview = `${formattedIntegerPart}${decimal}${roundDecimalPartToTwoSignificantDigit(
decimalPart,
)}`;
} else {
- amountFormattedPreview = currency(
- `${formattedIntegerPart}${decimal}${decimalPart}`,
- {
- separator: separator,
- decimal: decimal,
- symbol: '',
- },
- ).format();
+ preview = currency(`${formattedIntegerPart}${decimal}${decimalPart}`, {
+ separator,
+ decimal,
+ symbol: '',
+ }).format();
}
return {
- amountFormattedPreview,
- amountFormattedFull: `${formattedIntegerPart}${decimal}${decimalPart}`,
+ preview: removeTrailingZeros(preview),
+ full: removeTrailingZeros(
+ `${formattedIntegerPart}${decimal}${decimalPart}`,
+ ),
};
}
+// Internal functions
+
+// Internal function to pad a string with zeros
function padWithZeros(input: string, length: number): string {
return input.padStart(length, '0');
}
-export function roundDecimalPartToOneSignificantDigit(
+// Internal function to remove trailing zeros
+function removeTrailingZeros(numStr: string): string {
+ return numStr.replace(/\.?0+$/, '');
+}
+
+const leadingZeroes = /^0+/;
+
+function removeLeadingZeros(numStr: string): string {
+ return numStr.replace(leadingZeroes, '');
+}
+
+// Internal function to round the decimal part to significant digit
+export function roundDecimalPartToTwoSignificantDigit(
decimalPart: string,
): string {
function countLeadingZeros(str: string) {
// Match leading zeros using a regular expression
- const result = str.match(/^0+/);
+ const result = str.match(leadingZeroes);
// If the result isn't null (meaning there are leading zeros), return the length, otherwise return 0
return result ? result[0].length : 0;
}
// Strip leading zeroes
- const significantPart = decimalPart.replace(/^0+/, '');
+ const significantPart = removeLeadingZeros(decimalPart);
if (significantPart === '') {
// Input is all zeroes
return '0';
}
- // The first digit of the significant part is our significant digit
- const firstDigit = significantPart[0];
-
- // Determine if we need to round up
- const shouldRoundUp =
- significantPart.length > 1 && parseInt(significantPart[1]) >= 5;
-
- // Prepare the significant digit after rounding if necessary
- const roundedDigit = shouldRoundUp
- ? (parseInt(firstDigit) + 1).toString()
- : firstDigit;
+ const formattedByCurrency = currency(`0.${significantPart}`, {
+ separator: '',
+ decimal: '.',
+ symbol: '',
+ }).format({
+ // precision option is not taken in account
+ // https://github.com/scurker/currency.js/issues/293
+ decimal: '',
+ });
+ const trimmedZero = removeLeadingZeros(
+ removeTrailingZeros(formattedByCurrency),
+ );
// If rounding causes a carry-over (e.g. 0.009 -> 0.01), handle it
- if (roundedDigit === '10') {
- return '0'.repeat(countLeadingZeros(decimalPart) - 1) + roundedDigit[0];
+ if (formattedByCurrency.startsWith('1.')) {
+ return '0'.repeat(countLeadingZeros(decimalPart) - 1) + trimmedZero;
}
- return '0'.repeat(countLeadingZeros(decimalPart)) + roundedDigit;
-}
-
-export function formatAmountToDisplay(
- amount: string | undefined,
- tokenDecimal: number | undefined,
-): FormattedAmount {
- if (!tokenDecimal || !amount) {
- return {
- amountFormattedFull: '0',
- amountFormattedPreview: '0',
- };
- }
- // parsing to Bigint to get correct amount
- const amt = parseUnits(amount, tokenDecimal);
- // formatting it to string for display
- return formatAmount(amt.toString(), tokenDecimal);
+ return '0'.repeat(countLeadingZeros(decimalPart)) + trimmedZero;
}