@polkadot-apps/utils 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/dist/balance.d.ts +52 -0
- package/dist/balance.js +77 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +5 -3
- package/dist/planck.d.ts +36 -0
- package/dist/planck.js +110 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,37 @@ formatPlanck(1_000_000_000_000n, 12); // "1.0"
|
|
|
62
62
|
parseToPlanck("1.0", 12); // 1_000_000_000_000n
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
### Display formatting
|
|
66
|
+
|
|
67
|
+
Format planck values for display with locale-aware thousand separators, configurable decimal precision, and optional token symbol.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { formatBalance } from "@polkadot-apps/utils";
|
|
71
|
+
|
|
72
|
+
formatBalance(10_000_000_000n); // "1"
|
|
73
|
+
formatBalance(15_000_000_000n, { symbol: "DOT" }); // "1.5 DOT"
|
|
74
|
+
formatBalance(10_000_000_000_000n, { symbol: "DOT" }); // "1,000 DOT"
|
|
75
|
+
formatBalance(12_345_678_900n, { maxDecimals: 2 }); // "1.23"
|
|
76
|
+
formatBalance(0n, { symbol: "DOT" }); // "0 DOT"
|
|
77
|
+
|
|
78
|
+
// Custom chain decimals and locale
|
|
79
|
+
formatBalance(1_000_000_000_000n, { decimals: 12, symbol: "KSM", locale: "de-DE" });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Unlike the `Number()` approach used in some apps, `formatBalance` preserves full BigInt precision for balances of any size.
|
|
83
|
+
|
|
84
|
+
### Balance querying
|
|
85
|
+
|
|
86
|
+
Query on-chain balances with a typed convenience wrapper. Works with any PAPI typed API via structural typing — no extra dependencies.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { getBalance, formatBalance } from "@polkadot-apps/utils";
|
|
90
|
+
|
|
91
|
+
const balance = await getBalance(api.assetHub, aliceAddress);
|
|
92
|
+
console.log(formatBalance(balance.free, { symbol: "DOT" })); // "1,000.5 DOT"
|
|
93
|
+
console.log(formatBalance(balance.reserved, { symbol: "DOT" })); // "50 DOT"
|
|
94
|
+
```
|
|
95
|
+
|
|
65
96
|
## API
|
|
66
97
|
|
|
67
98
|
### Encoding
|
|
@@ -87,6 +118,13 @@ parseToPlanck("1.0", 12); // 1_000_000_000_000n
|
|
|
87
118
|
|---|---|---|
|
|
88
119
|
| `formatPlanck` | `(planck: bigint, decimals?: number)` | `string` |
|
|
89
120
|
| `parseToPlanck` | `(amount: string, decimals?: number)` | `bigint` |
|
|
121
|
+
| `formatBalance` | `(planck: bigint, options?: FormatBalanceOptions)` | `string` |
|
|
122
|
+
|
|
123
|
+
### Balance querying
|
|
124
|
+
|
|
125
|
+
| Function | Signature | Returns |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `getBalance` | `(api: BalanceApi, address: string)` | `Promise<AccountBalance>` |
|
|
90
128
|
|
|
91
129
|
**`formatPlanck(planck, decimals = 10)`**
|
|
92
130
|
|
|
@@ -102,11 +140,25 @@ Parse a decimal string into its planck bigint representation. If the fractional
|
|
|
102
140
|
- Throws `Error` if `amount` is empty or contains invalid characters.
|
|
103
141
|
- Throws `RangeError` if `amount` is negative or `decimals` is invalid.
|
|
104
142
|
|
|
143
|
+
**`formatBalance(planck, options?)`**
|
|
144
|
+
|
|
145
|
+
Format a planck value for display with locale-aware thousand separators. Builds on `formatPlanck` for BigInt precision.
|
|
146
|
+
|
|
147
|
+
Options: `{ decimals?: number, maxDecimals?: number, symbol?: string, locale?: string }`. Defaults: `decimals = 10`, `maxDecimals = 4`, no symbol, user's locale.
|
|
148
|
+
|
|
149
|
+
- Throws `RangeError` if `planck < 0n` or `decimals` is invalid (delegated to `formatPlanck`).
|
|
150
|
+
|
|
151
|
+
**`getBalance(api, address): Promise<AccountBalance>`**
|
|
152
|
+
|
|
153
|
+
Query the free, reserved, and frozen balances for an address. Returns `{ free: bigint, reserved: bigint, frozen: bigint }`. Uses structural typing — works with any PAPI typed API that has `System.Account`.
|
|
154
|
+
|
|
105
155
|
## Common mistakes
|
|
106
156
|
|
|
107
157
|
- **Passing a `0x`-prefixed string to `hexToBytes`.** The `@noble/hashes` implementation expects raw hex without a prefix. Strip it first: `hexToBytes(hex.slice(2))`.
|
|
108
158
|
- **Using `formatPlanck` with the wrong `decimals` for a chain.** DOT uses 10, KSM uses 12, many parachains use 18. Always check the chain's token metadata.
|
|
109
159
|
- **Assuming `parseToPlanck` rounds excess decimals.** It truncates, not rounds. `parseToPlanck("1.999999999999", 10)` gives the same result as `parseToPlanck("1.9999999999", 10)`.
|
|
160
|
+
- **Using `Number()` to format large balances.** `Number(raw) / 10**decimals` loses precision for values > 2^53 planck (~900 DOT). Use `formatBalance` which preserves full BigInt precision.
|
|
161
|
+
- **Passing the ChainAPI wrapper to `getBalance`.** Pass the chain-specific TypedApi (e.g., `api.assetHub`), not the multi-chain `ChainAPI` object.
|
|
110
162
|
|
|
111
163
|
## License
|
|
112
164
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Balance breakdown from a Substrate `System.Account` query. */
|
|
2
|
+
export interface AccountBalance {
|
|
3
|
+
/** Available (transferable) balance in planck. */
|
|
4
|
+
free: bigint;
|
|
5
|
+
/** Reserved (locked by governance, staking, etc.) balance in planck. */
|
|
6
|
+
reserved: bigint;
|
|
7
|
+
/** Frozen (non-transferable but still counted) balance in planck. */
|
|
8
|
+
frozen: bigint;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Minimal structural type for a PAPI typed API with `System.Account`.
|
|
12
|
+
*
|
|
13
|
+
* Structural so it works with any chain that has the System pallet, without
|
|
14
|
+
* importing chain-specific descriptors.
|
|
15
|
+
*/
|
|
16
|
+
export interface BalanceApi {
|
|
17
|
+
query: {
|
|
18
|
+
System: {
|
|
19
|
+
Account: {
|
|
20
|
+
getValue(address: string): Promise<{
|
|
21
|
+
data: {
|
|
22
|
+
free: bigint;
|
|
23
|
+
reserved: bigint;
|
|
24
|
+
frozen: bigint;
|
|
25
|
+
};
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Query the free, reserved, and frozen balances for an on-chain address.
|
|
33
|
+
*
|
|
34
|
+
* Thin typed wrapper around `System.Account.getValue` that returns a clean
|
|
35
|
+
* {@link AccountBalance} object. Uses structural typing so it works with any
|
|
36
|
+
* PAPI typed API that has the System pallet — no chain-specific imports needed.
|
|
37
|
+
*
|
|
38
|
+
* @param api - A PAPI typed API with `query.System.Account`. Pass the chain-specific
|
|
39
|
+
* API (e.g., `api.assetHub`), not the multi-chain `ChainAPI` wrapper.
|
|
40
|
+
* @param address - The SS58 address to query.
|
|
41
|
+
* @returns The account's balance breakdown.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { getBalance } from "@polkadot-apps/utils";
|
|
46
|
+
* import { formatBalance } from "@polkadot-apps/utils";
|
|
47
|
+
*
|
|
48
|
+
* const balance = await getBalance(api.assetHub, aliceAddress);
|
|
49
|
+
* console.log(formatBalance(balance.free, { symbol: "DOT" })); // "1,000.5 DOT"
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function getBalance(api: BalanceApi, address: string): Promise<AccountBalance>;
|
package/dist/balance.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query the free, reserved, and frozen balances for an on-chain address.
|
|
3
|
+
*
|
|
4
|
+
* Thin typed wrapper around `System.Account.getValue` that returns a clean
|
|
5
|
+
* {@link AccountBalance} object. Uses structural typing so it works with any
|
|
6
|
+
* PAPI typed API that has the System pallet — no chain-specific imports needed.
|
|
7
|
+
*
|
|
8
|
+
* @param api - A PAPI typed API with `query.System.Account`. Pass the chain-specific
|
|
9
|
+
* API (e.g., `api.assetHub`), not the multi-chain `ChainAPI` wrapper.
|
|
10
|
+
* @param address - The SS58 address to query.
|
|
11
|
+
* @returns The account's balance breakdown.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { getBalance } from "@polkadot-apps/utils";
|
|
16
|
+
* import { formatBalance } from "@polkadot-apps/utils";
|
|
17
|
+
*
|
|
18
|
+
* const balance = await getBalance(api.assetHub, aliceAddress);
|
|
19
|
+
* console.log(formatBalance(balance.free, { symbol: "DOT" })); // "1,000.5 DOT"
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export async function getBalance(api, address) {
|
|
23
|
+
const account = await api.query.System.Account.getValue(address);
|
|
24
|
+
return {
|
|
25
|
+
free: account.data.free,
|
|
26
|
+
reserved: account.data.reserved,
|
|
27
|
+
frozen: account.data.frozen,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (import.meta.vitest) {
|
|
31
|
+
const { describe, test, expect } = import.meta.vitest;
|
|
32
|
+
function createMockApi(data) {
|
|
33
|
+
return {
|
|
34
|
+
query: {
|
|
35
|
+
System: {
|
|
36
|
+
Account: {
|
|
37
|
+
getValue: async () => ({ data }),
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
describe("getBalance", () => {
|
|
44
|
+
test("returns correct AccountBalance from API", async () => {
|
|
45
|
+
const api = createMockApi({
|
|
46
|
+
free: 10000000000n,
|
|
47
|
+
reserved: 5000000000n,
|
|
48
|
+
frozen: 1000000000n,
|
|
49
|
+
});
|
|
50
|
+
const balance = await getBalance(api, "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY");
|
|
51
|
+
expect(balance.free).toBe(10000000000n);
|
|
52
|
+
expect(balance.reserved).toBe(5000000000n);
|
|
53
|
+
expect(balance.frozen).toBe(1000000000n);
|
|
54
|
+
});
|
|
55
|
+
test("propagates errors from getValue", async () => {
|
|
56
|
+
const api = {
|
|
57
|
+
query: {
|
|
58
|
+
System: {
|
|
59
|
+
Account: {
|
|
60
|
+
getValue: async () => {
|
|
61
|
+
throw new Error("RPC connection failed");
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
await expect(getBalance(api, "5GrwvaEF...")).rejects.toThrow("RPC connection failed");
|
|
68
|
+
});
|
|
69
|
+
test("works with zero balances", async () => {
|
|
70
|
+
const api = createMockApi({ free: 0n, reserved: 0n, frozen: 0n });
|
|
71
|
+
const balance = await getBalance(api, "5GrwvaEF...");
|
|
72
|
+
expect(balance.free).toBe(0n);
|
|
73
|
+
expect(balance.reserved).toBe(0n);
|
|
74
|
+
expect(balance.frozen).toBe(0n);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @polkadot-apps/utils — Encoding, hashing,
|
|
2
|
+
* @polkadot-apps/utils — Encoding, hashing, token formatting, and balance querying for the Polkadot app ecosystem.
|
|
3
3
|
*
|
|
4
4
|
* Provides general-purpose byte encoding/decoding (`bytesToHex`, `hexToBytes`, `utf8ToBytes`,
|
|
5
5
|
* `concatBytes`), 32-byte hash functions (`blake2b256`, `sha256`, `keccak256`),
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Substrate token formatting (`formatPlanck`, `parseToPlanck`, `formatBalance`),
|
|
7
|
+
* and typed balance queries (`getBalance`).
|
|
8
|
+
* All functions are framework-agnostic.
|
|
8
9
|
*
|
|
9
10
|
* @packageDocumentation
|
|
10
11
|
*/
|
|
11
12
|
export * from "./encoding.js";
|
|
12
13
|
export * from "./hashing.js";
|
|
13
14
|
export * from "./planck.js";
|
|
15
|
+
export * from "./balance.js";
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @polkadot-apps/utils — Encoding, hashing,
|
|
2
|
+
* @polkadot-apps/utils — Encoding, hashing, token formatting, and balance querying for the Polkadot app ecosystem.
|
|
3
3
|
*
|
|
4
4
|
* Provides general-purpose byte encoding/decoding (`bytesToHex`, `hexToBytes`, `utf8ToBytes`,
|
|
5
5
|
* `concatBytes`), 32-byte hash functions (`blake2b256`, `sha256`, `keccak256`),
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Substrate token formatting (`formatPlanck`, `parseToPlanck`, `formatBalance`),
|
|
7
|
+
* and typed balance queries (`getBalance`).
|
|
8
|
+
* All functions are framework-agnostic.
|
|
8
9
|
*
|
|
9
10
|
* @packageDocumentation
|
|
10
11
|
*/
|
|
11
12
|
export * from "./encoding.js";
|
|
12
13
|
export * from "./hashing.js";
|
|
13
14
|
export * from "./planck.js";
|
|
15
|
+
export * from "./balance.js";
|
package/dist/planck.d.ts
CHANGED
|
@@ -45,3 +45,39 @@ export declare function formatPlanck(planck: bigint, decimals?: number): string;
|
|
|
45
45
|
* ```
|
|
46
46
|
*/
|
|
47
47
|
export declare function parseToPlanck(amount: string, decimals?: number): bigint;
|
|
48
|
+
/** Options for {@link formatBalance}. */
|
|
49
|
+
export interface FormatBalanceOptions {
|
|
50
|
+
/** Token decimals. Default: 10 (DOT). */
|
|
51
|
+
decimals?: number;
|
|
52
|
+
/** Maximum fraction digits to display. Default: 4. */
|
|
53
|
+
maxDecimals?: number;
|
|
54
|
+
/** Token symbol to append (e.g., `"DOT"`, `"PAS"`). Omitted by default. */
|
|
55
|
+
symbol?: string;
|
|
56
|
+
/** BCP 47 locale tag for grouping and decimal separators (e.g., `"en-US"` → `","` grouping + `"."` decimal, `"de-DE"` → `"."` grouping + `","` decimal). Default: user's locale. */
|
|
57
|
+
locale?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Format a planck value for display with locale-aware thousand separators,
|
|
61
|
+
* decimal truncation, and an optional token symbol.
|
|
62
|
+
*
|
|
63
|
+
* Builds on {@link formatPlanck} for BigInt-safe conversion, then applies
|
|
64
|
+
* presentation formatting. Unlike {@link formatPlanck}, trailing `.0` is
|
|
65
|
+
* omitted — display values show `"1,000"` not `"1,000.0"`.
|
|
66
|
+
*
|
|
67
|
+
* @param planck - The raw planck value as a bigint. Must be non-negative.
|
|
68
|
+
* @param options - Formatting options.
|
|
69
|
+
* @returns A display-ready string (e.g. `"1,000.5 DOT"`).
|
|
70
|
+
* @throws {RangeError} If `planck` is negative or `decimals` is invalid (delegated to {@link formatPlanck}).
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* import { formatBalance } from "@polkadot-apps/utils";
|
|
75
|
+
*
|
|
76
|
+
* formatBalance(10_000_000_000n); // "1"
|
|
77
|
+
* formatBalance(15_000_000_000n, { symbol: "DOT" }); // "1.5 DOT"
|
|
78
|
+
* formatBalance(10_000_000_000_000n, { symbol: "DOT" }); // "1,000 DOT"
|
|
79
|
+
* formatBalance(12_345_678_900n, { maxDecimals: 2 }); // "1.23"
|
|
80
|
+
* formatBalance(0n, { symbol: "DOT" }); // "0 DOT"
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function formatBalance(planck: bigint, options?: FormatBalanceOptions): string;
|
package/dist/planck.js
CHANGED
|
@@ -104,6 +104,54 @@ export function parseToPlanck(amount, decimals = 10) {
|
|
|
104
104
|
const fraction = decimals > 0 ? BigInt(paddedFraction) : 0n;
|
|
105
105
|
return whole + fraction;
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Format a planck value for display with locale-aware thousand separators,
|
|
109
|
+
* decimal truncation, and an optional token symbol.
|
|
110
|
+
*
|
|
111
|
+
* Builds on {@link formatPlanck} for BigInt-safe conversion, then applies
|
|
112
|
+
* presentation formatting. Unlike {@link formatPlanck}, trailing `.0` is
|
|
113
|
+
* omitted — display values show `"1,000"` not `"1,000.0"`.
|
|
114
|
+
*
|
|
115
|
+
* @param planck - The raw planck value as a bigint. Must be non-negative.
|
|
116
|
+
* @param options - Formatting options.
|
|
117
|
+
* @returns A display-ready string (e.g. `"1,000.5 DOT"`).
|
|
118
|
+
* @throws {RangeError} If `planck` is negative or `decimals` is invalid (delegated to {@link formatPlanck}).
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* import { formatBalance } from "@polkadot-apps/utils";
|
|
123
|
+
*
|
|
124
|
+
* formatBalance(10_000_000_000n); // "1"
|
|
125
|
+
* formatBalance(15_000_000_000n, { symbol: "DOT" }); // "1.5 DOT"
|
|
126
|
+
* formatBalance(10_000_000_000_000n, { symbol: "DOT" }); // "1,000 DOT"
|
|
127
|
+
* formatBalance(12_345_678_900n, { maxDecimals: 2 }); // "1.23"
|
|
128
|
+
* formatBalance(0n, { symbol: "DOT" }); // "0 DOT"
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export function formatBalance(planck, options) {
|
|
132
|
+
const decimals = options?.decimals ?? 10;
|
|
133
|
+
const maxDecimals = options?.maxDecimals ?? 4;
|
|
134
|
+
const symbol = options?.symbol;
|
|
135
|
+
const locale = options?.locale;
|
|
136
|
+
if (maxDecimals < 0 || !Number.isInteger(maxDecimals)) {
|
|
137
|
+
throw new RangeError(`maxDecimals must be a non-negative integer, got ${maxDecimals}`);
|
|
138
|
+
}
|
|
139
|
+
const raw = formatPlanck(planck, decimals);
|
|
140
|
+
const dotIndex = raw.indexOf(".");
|
|
141
|
+
const wholePart = dotIndex === -1 ? raw : raw.slice(0, dotIndex);
|
|
142
|
+
const fractionPart = dotIndex === -1 ? "" : raw.slice(dotIndex + 1);
|
|
143
|
+
const formatter = new Intl.NumberFormat(locale, { useGrouping: true });
|
|
144
|
+
// Format whole part with locale-aware grouping (BigInt overload avoids precision loss)
|
|
145
|
+
const formattedWhole = formatter.format(BigInt(wholePart));
|
|
146
|
+
// Extract the locale's decimal separator (e.g., "." for en-US, "," for de-DE)
|
|
147
|
+
const decimalSep = formatter.formatToParts(1.1).find((p) => p.type === "decimal")?.value ?? ".";
|
|
148
|
+
// Truncate fraction to maxDecimals, trim trailing zeros
|
|
149
|
+
const truncated = fractionPart.slice(0, maxDecimals);
|
|
150
|
+
const trimmed = truncated.replace(/0+$/, "");
|
|
151
|
+
const fractionSuffix = trimmed ? `${decimalSep}${trimmed}` : "";
|
|
152
|
+
const symbolSuffix = symbol ? ` ${symbol}` : "";
|
|
153
|
+
return `${formattedWhole}${fractionSuffix}${symbolSuffix}`;
|
|
154
|
+
}
|
|
107
155
|
if (import.meta.vitest) {
|
|
108
156
|
const { describe, test, expect } = import.meta.vitest;
|
|
109
157
|
describe("formatPlanck", () => {
|
|
@@ -191,4 +239,66 @@ if (import.meta.vitest) {
|
|
|
191
239
|
expect(parseToPlanck(formatted)).toBe(original);
|
|
192
240
|
});
|
|
193
241
|
});
|
|
242
|
+
describe("formatBalance", () => {
|
|
243
|
+
test("formats with default options (no symbol, max 4 decimals)", () => {
|
|
244
|
+
expect(formatBalance(15000000000n)).toBe("1.5");
|
|
245
|
+
expect(formatBalance(12345678900n)).toBe("1.2345");
|
|
246
|
+
});
|
|
247
|
+
test("applies thousand separators", () => {
|
|
248
|
+
expect(formatBalance(10000000000000n, { locale: "en-US" })).toBe("1,000");
|
|
249
|
+
expect(formatBalance(1234567000000000n, { locale: "en-US", symbol: "DOT" })).toBe("123,456.7 DOT");
|
|
250
|
+
});
|
|
251
|
+
test("truncates fraction to maxDecimals", () => {
|
|
252
|
+
expect(formatBalance(12345678900n, { maxDecimals: 2 })).toBe("1.23");
|
|
253
|
+
expect(formatBalance(12345678900n, { maxDecimals: 8 })).toBe("1.23456789");
|
|
254
|
+
});
|
|
255
|
+
test("appends symbol", () => {
|
|
256
|
+
expect(formatBalance(15000000000n, { symbol: "DOT" })).toBe("1.5 DOT");
|
|
257
|
+
expect(formatBalance(15000000000n, { symbol: "PAS" })).toBe("1.5 PAS");
|
|
258
|
+
});
|
|
259
|
+
test("omits fraction when all zeros after truncation", () => {
|
|
260
|
+
expect(formatBalance(10000000000n)).toBe("1");
|
|
261
|
+
expect(formatBalance(20000000000n, { symbol: "DOT" })).toBe("2 DOT");
|
|
262
|
+
});
|
|
263
|
+
test("respects maxDecimals: 0", () => {
|
|
264
|
+
expect(formatBalance(15000000000n, { maxDecimals: 0 })).toBe("1");
|
|
265
|
+
expect(formatBalance(19999999999n, { maxDecimals: 0, symbol: "DOT" })).toBe("1 DOT");
|
|
266
|
+
});
|
|
267
|
+
test("handles zero", () => {
|
|
268
|
+
expect(formatBalance(0n)).toBe("0");
|
|
269
|
+
expect(formatBalance(0n, { symbol: "DOT" })).toBe("0 DOT");
|
|
270
|
+
});
|
|
271
|
+
test("handles sub-unit amounts", () => {
|
|
272
|
+
// 1 planck is below 4-decimal display threshold → shows "0"
|
|
273
|
+
expect(formatBalance(1n)).toBe("0");
|
|
274
|
+
// 0.0001 DOT is exactly at the threshold
|
|
275
|
+
expect(formatBalance(1000000n)).toBe("0.0001");
|
|
276
|
+
// With more maxDecimals, sub-unit amounts become visible
|
|
277
|
+
expect(formatBalance(1n, { maxDecimals: 10 })).toBe("0.0000000001");
|
|
278
|
+
});
|
|
279
|
+
test("uses locale-correct decimal separator", () => {
|
|
280
|
+
// German uses . for grouping and , for decimal
|
|
281
|
+
expect(formatBalance(15000000000n, { locale: "de-DE" })).toBe("1,5");
|
|
282
|
+
expect(formatBalance(10000000000000n, { locale: "de-DE" })).toBe("1.000");
|
|
283
|
+
// With fraction
|
|
284
|
+
expect(formatBalance(10005000000000n, { locale: "de-DE" })).toBe("1.000,5");
|
|
285
|
+
});
|
|
286
|
+
test("preserves BigInt precision for large amounts", () => {
|
|
287
|
+
// 2^53 + 1 in planck — would lose precision with Number()
|
|
288
|
+
const largePlanck = 90071992547409920000n; // ~9 billion DOT
|
|
289
|
+
const result = formatBalance(largePlanck, { locale: "en-US", symbol: "DOT" });
|
|
290
|
+
expect(result).toContain("9,007,199,254");
|
|
291
|
+
expect(result).toContain("DOT");
|
|
292
|
+
});
|
|
293
|
+
test("throws on negative planck (delegates to formatPlanck)", () => {
|
|
294
|
+
expect(() => formatBalance(-1n)).toThrow(RangeError);
|
|
295
|
+
});
|
|
296
|
+
test("throws on invalid decimals (delegates to formatPlanck)", () => {
|
|
297
|
+
expect(() => formatBalance(0n, { decimals: -1 })).toThrow(RangeError);
|
|
298
|
+
});
|
|
299
|
+
test("throws on invalid maxDecimals", () => {
|
|
300
|
+
expect(() => formatBalance(0n, { maxDecimals: -1 })).toThrow(RangeError);
|
|
301
|
+
expect(() => formatBalance(0n, { maxDecimals: 1.5 })).toThrow(RangeError);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
194
304
|
}
|
package/package.json
CHANGED