The stored ETH and token balances and the send-confirm screen's fee were each cut to six decimal places, and a token holding cut to zero was dropped, so a value below 0.000001 read as zero. Balances are now stored exactly, whatever decimals a token declares, and every nonzero token holding is kept; the balance check reads a token balance to its first 18 places. The balance lists, the send-screen token selector, the address total and the remove-address warning leave out a holding below 0.000001 themselves, through isBelowOneMillionth(). The send and send-confirm screens' balances, reserve and insufficient-balance messages go through truncateAmountNeverZero(). The send-confirm and approval screens both render the fee through formatFee(), which prices the exact fee in USD. Model: opus-5-5
61 lines
2.9 KiB
JavaScript
61 lines
2.9 KiB
JavaScript
// The 4-decimal amount rule from README.md's Display Consistency section, and
|
|
// the one exception to it, in one place. Three call sites had grown their own
|
|
// copy of the truncation — the history and balance lists
|
|
// (`src/shared/transactions.js`), the approval screen's ERC-20 amount line
|
|
// (`src/popup/views/approval.js`) and its Uniswap swap detail lines
|
|
// (`src/shared/uniswap.js`) — and a fix applied to one of them left the other
|
|
// two showing a different number for the same value.
|
|
//
|
|
// The two truncation functions below are the two policies, not two
|
|
// implementations of one: summary lists truncate, and the screens that state
|
|
// what is being authorized truncate with a floor. Keeping them adjacent is the
|
|
// point, so a change to the rule cannot reach one screen and miss another.
|
|
|
|
// Truncate to exactly four decimal places. Truncation, never rounding: an
|
|
// amount must never be displayed as larger than it is, so 0.99999 stays
|
|
// 0.9999.
|
|
function truncateAmount(val) {
|
|
const parts = val.split(".");
|
|
if (parts.length === 1) return val + ".0000";
|
|
return parts[0] + "." + (parts[1] + "0000").slice(0, 4);
|
|
}
|
|
|
|
// The same rule, plus the invariant the approval and confirmation screens
|
|
// hold: a nonzero amount never renders as zero. Truncating to four decimals
|
|
// does exactly that to an amount below 0.0001 — one base unit of an 18-decimal
|
|
// token, 500 of an 8-decimal one — and a real transfer or allowance then reads
|
|
// as "nothing is being moved" on the screen whose whole job is to say what is
|
|
// being authorized.
|
|
//
|
|
// When the truncated string carries no significant digit and the value does,
|
|
// the amount is extended to its first significant digit instead. It stays in
|
|
// token units, the same unit as the symbol printed beside it. A genuine zero
|
|
// still renders 0.0000, and anything at or above the floor is untouched.
|
|
function truncateAmountNeverZero(val) {
|
|
const truncated = truncateAmount(val);
|
|
// Tests the whole truncated string, integer part included: 1.00005 has a
|
|
// significant digit already and stays 1.0000.
|
|
if (/[1-9]/.test(truncated)) return truncated;
|
|
const parts = val.split(".");
|
|
if (parts.length === 1) return truncated;
|
|
const sig = parts[1].search(/[1-9]/);
|
|
if (sig === -1) return truncated;
|
|
return parts[0] + "." + parts[1].slice(0, sig + 1);
|
|
}
|
|
|
|
// Whether a stored token balance is a holding below 0.000001. The balance
|
|
// lists, the send-screen token selector, the address total and the
|
|
// remove-address warning leave such a holding out; the Send and confirmation
|
|
// screens show it when its token is the one being sent. Exact, because
|
|
// src/shared/balances.js stores plain decimal digits: below 0.000001 the
|
|
// balance reads "0.000000" and then more digits.
|
|
function isBelowOneMillionth(balance) {
|
|
return typeof balance === "string" && balance.startsWith("0.000000");
|
|
}
|
|
|
|
module.exports = {
|
|
truncateAmount,
|
|
truncateAmountNeverZero,
|
|
isBelowOneMillionth,
|
|
};
|