fix: show balances and fees below 0.000001 as nonzero on the send screens (closes #343)
check / check (push) Failing after 2s
e2e / e2e-chrome (push) Failing after 1m46s
e2e / e2e-firefox (push) Successful in 37s

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
This commit was merged in pull request #417.
This commit is contained in:
2026-10-04 11:07:37 +02:00
parent 5bf8b5ff1f
commit 467b849a13
14 changed files with 624 additions and 88 deletions
+16 -17
View File
@@ -1463,9 +1463,10 @@ test("an over-long flash message keeps to one line (#252)", async (env) => {
// on opposite sides of the reserve while sitting on the same side of the
// estimate.
// The balance the funded fixture serves, and the amounts sent against it.
// The balance the funded fixture serves, as the Send and confirmation screens
// show it, and the amounts sent against it.
const FUNDED_ETH_WEI = 10n ** 18n;
const FUNDED_ETH_TEXT = "1.0";
const FUNDED_ETH_TEXT = "1.0000";
const COMFORTABLE_AMOUNT = "0.1";
const OVER_BALANCE_AMOUNT = "2.0";
@@ -1479,7 +1480,7 @@ const GAP_AMOUNT = formatEther(FUNDED_ETH_WEI - FEE_ESTIMATE_WEI);
// fee test: it covers the expected cost to the wei and falls short of the
// reserve, so the same swap flips this assertion too — through a different
// balance and a different message than the ETH path uses.
const TOKEN_BALANCE_TEXT = "1.5";
const TOKEN_BALANCE_TEXT = "1.5000";
const TOKEN_AMOUNT = "0.25";
const OVER_TOKEN_AMOUNT = "9.0";
const FEE_ONLY_ETH_WEI = FEE_ESTIMATE_WEI;
@@ -1488,16 +1489,15 @@ function toHexWei(wei) {
return "0x" + wei.toString(16);
}
// A fee in wei as the confirmation screen writes it. Deliberately a second
// implementation of formatFeeEth() from src/popup/views/confirmTx.js rather
// than an import of it: that module pulls in the whole popup and cannot be
// required outside a browser, and asserting against an independent rendering
// is stronger than asserting a function equals itself.
// A fee in wei as the confirmation screen writes it: truncated to four decimal
// places (README.md, Display Consistency). Deliberately a second
// implementation rather than an import of src/shared/amountDisplay.js:
// asserting against an independent rendering is stronger than asserting a
// function equals itself. The fixture's fees are above 0.0001 ETH, so the
// nonzero floor never applies here.
function feeEth(wei) {
const parts = formatEther(wei).split(".");
const dec =
parts.length > 1 ? parts[1].slice(0, 6).replace(/0+$/, "") || "0" : "0";
return parts[0] + "." + dec + " ETH";
const [whole, frac = ""] = formatEther(wei).split(".");
return whole + "." + (frac + "0000").slice(0, 4) + " ETH";
}
// What the confirmation screen is showing right now, read out of the DOM in
@@ -1554,11 +1554,10 @@ async function backToAddress(page) {
// Drive the popup to the confirmation screen for one send.
//
// It waits for the send screen to be showing `balance` before filling
// anything in. That figure is the exact number the spend gate compares
// against, so waiting for it — rather than for a refresh to have probably
// landed — is what keeps every assertion below deterministic after a
// fixture change.
// It waits for the send screen to be showing `balance`, the fixture's balance
// as that screen displays it, before filling anything in. Waiting for it —
// rather than for a refresh to have probably landed — is what keeps every
// assertion below deterministic after a fixture change.
async function goToConfirm(page, { token, balance, amount }) {
await backToAddress(page);
await page.click("#btn-send");