Packages versions
guardian-dashboard v0.1.0, commit 85929c0 (main, 2026-08-21)
Bug description
normalizeAmount() in lib/token-registry.ts converts the raw on-chain amount
string with Number(rawAmount) before dividing by 10**decimals:
export function normalizeAmount(faucetId: string, rawAmount: string): number {
const n = Number(rawAmount);
...
return n / Math.pow(10, decimals);
}
Number() silently rounds any integer string above
Number.MAX_SAFE_INTEGER (2^53 - 1 = 9,007,199,254,740,992). A realistic raw
balance is well past that threshold for common decimal counts (e.g. 10,000
units of an 18-decimal token is "10000000000000000000000"), so aggregate
totals can be silently wrong by a non-trivial amount with no error, no
warning, and no test catching it.
This function is the sole totalizer for the dashboard-wide asset sums
(overview/stats) and for the "Total assets (USD)" column in the CSV export
(lib/format.ts, accountsToCsv) — both derive from
lib/account-cache.ts:260, which calls normalizeAmount() per asset and sums
the results with plain floating-point +.
The codebase already has the correct pattern one file away:
components/accounts/AccountDetail.tsx:357 renders the same raw amount field
via BigInt(asset.amount).toLocaleString(), which does not lose precision.
So a single account's detail page can show the exact figure while the
dashboard-wide total / CSV export derived from the same underlying data is
silently off.
How can this be reproduced?
-
In a Node/TS REPL, import normalizeAmount (or inline the function) and
call it with a raw amount at or above 2^53 in base units, e.g.:
normalizeAmount("faucetX", "9007199254740993") // decimals default 6
Compare the internal Number("9007199254740993") step against the input:
it silently becomes 9007199254740992 before any division happens.
-
In the running app: seed/mock a Guardian account whose fungible asset
amount field is a large base-unit integer (e.g. "10000000000000000000000"
for a token registered with 18 decimals in GUARDIAN_TOKEN_DECIMALS), load
the Accounts overview or export the CSV, and compare the displayed/exported
total against the account's own detail page (which uses BigInt and is
correct).
Relevant log output
No exception/log is produced — that's the bug. The failure is a silently
wrong number, not a crash. The only observable signal is the discrepancy
between AccountDetail.tsx's BigInt-based per-account figure and the
Number()-based aggregate/CSV total for the same account.
Packages versions
guardian-dashboard v0.1.0, commit 85929c0 (main, 2026-08-21)
Bug description
normalizeAmount()in lib/token-registry.ts converts the raw on-chain amountstring with
Number(rawAmount)before dividing by 10**decimals:Number()silently rounds any integer string aboveNumber.MAX_SAFE_INTEGER (2^53 - 1 = 9,007,199,254,740,992). A realistic raw
balance is well past that threshold for common decimal counts (e.g. 10,000
units of an 18-decimal token is "10000000000000000000000"), so aggregate
totals can be silently wrong by a non-trivial amount with no error, no
warning, and no test catching it.
This function is the sole totalizer for the dashboard-wide asset sums
(overview/stats) and for the "Total assets (USD)" column in the CSV export
(lib/format.ts, accountsToCsv) — both derive from
lib/account-cache.ts:260, which calls normalizeAmount() per asset and sums
the results with plain floating-point
+.The codebase already has the correct pattern one file away:
components/accounts/AccountDetail.tsx:357 renders the same raw amount field
via
BigInt(asset.amount).toLocaleString(), which does not lose precision.So a single account's detail page can show the exact figure while the
dashboard-wide total / CSV export derived from the same underlying data is
silently off.
How can this be reproduced?
In a Node/TS REPL, import normalizeAmount (or inline the function) and
call it with a raw amount at or above 2^53 in base units, e.g.:
normalizeAmount("faucetX", "9007199254740993") // decimals default 6
Compare the internal
Number("9007199254740993")step against the input:it silently becomes 9007199254740992 before any division happens.
In the running app: seed/mock a Guardian account whose fungible asset
amountfield is a large base-unit integer (e.g. "10000000000000000000000"for a token registered with 18 decimals in GUARDIAN_TOKEN_DECIMALS), load
the Accounts overview or export the CSV, and compare the displayed/exported
total against the account's own detail page (which uses BigInt and is
correct).
Relevant log output