Files
AutistMask/src/popup/views/deleteAddress.js
T
clawbot 467b849a13
check / check (push) Failing after 2s
e2e / e2e-chrome (push) Failing after 1m46s
e2e / e2e-firefox (push) Successful in 37s
fix: show balances and fees below 0.000001 as nonzero on the send screens (closes #343)
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
2026-10-04 11:07:37 +02:00

178 lines
6.6 KiB
JavaScript

// Confirmation screen for removing one address from a wallet that derives
// its addresses from an extended key.
//
// No password is asked for, unlike delete-wallet. A password gates the
// disclosure or destruction of a secret, and this does neither: the address
// is derived from key material the wallet still holds, so removing it only
// stops the wallet tracking it. An explicit confirmation screen is the
// proportionate treatment.
const {
$,
showView,
showFlash,
escapeHtml,
goBack,
renderAddressHtml,
attachCopyHandlers,
addressHoldsFunds,
balanceLinesForAddress,
} = require("./helpers");
const { formatAddressTotal, getAddressValue } = require("../../shared/prices");
const { walletHasRecoveryPhrase } = require("../../shared/wallet");
const { state, saveState } = require("../../shared/state");
const {
canRemoveAddress,
removeAddressFromState,
broadcastActiveChanged,
} = require("../../shared/walletDelete");
// The wallet and address indices this screen is confirming, or null when it
// is not confirming anything.
let target = null;
let ctx = null;
function setFlash(msg) {
const el = $("delete-address-flash");
el.textContent = msg;
el.style.visibility = msg ? "visible" : "hidden";
}
// What it actually takes to get the address back, which is not what the
// screen used to claim.
//
// Neither obvious route works: "+" derives the next unused index, because
// wallet.nextIndex is a high-water mark and is deliberately not rewound; and
// re-importing this wallet's key material is refused as a duplicate by
// findWalletByXpub() for as long as the wallet is here. What remains is to
// delete the whole wallet in Settings — which destroys the stored secret,
// with or without the password — and import again, after which
// scanForAddresses() rediscovers the address only if it has on-chain
// activity. An address that was never used is not found by that scan, and
// the copy must not imply otherwise.
//
// The noun follows the wallet: an xprv wallet holds no recovery phrase, and
// this screen is offered on xprv wallets too.
function recoveryPathText(wallet) {
const secret = walletHasRecoveryPhrase(wallet)
? "recovery phrase"
: "extended private key";
return (
"Getting the address back into this list is not easy, so be sure. " +
"Adding an address derives the next unused one, not this one, and " +
"importing this " +
secret +
" again is refused while this wallet is still here. The way back is " +
"to delete the whole wallet in Settings, which destroys the stored " +
secret +
", and then import that " +
secret +
" again. The scan that follows only finds addresses that have " +
"on-chain activity, so an address that has never been used is not " +
"found by it."
);
}
// The balance warning, or a blank line when the address holds nothing.
//
// A balance is a reason to be careful, not a reason to refuse: the funds are
// at the address, not in this list, and stay there either way.
//
// "Holds" means ETH or any ERC-20 the wallet knows about — an address with no
// ETH and a five-figure stablecoin position must not get the blank line on
// the one screen whose job is to warn. The sentence names no figure of its
// own: the rendered lines round to four decimals, so a sentence built from a
// rounded number would report "0.0000 ETH" for an address holding real money.
// The lines below it carry the amounts, in the same format as Home and
// AddressDetail, followed by the USD total when there is one to give — no
// total line at all on testnet or before the first price fetch, and no figure
// when every holding here is one with no price, since "$0.00" directly under
// "This address holds a balance." is a contradiction. A token holding below
// 0.000001 does not count, as the lines below leave it out.
function balanceWarningHtml(addr) {
if (!addressHoldsFunds(addr)) return " ";
const line = formatAddressTotal(getAddressValue(addr));
const total = line
? `<div class="text-xs text-muted mt-1">${escapeHtml(line)}</div>`
: "";
return (
`<p class="mb-1">This address holds a balance. Removing it does not ` +
`move or spend anything; the balance stays at the address.</p>` +
balanceLinesForAddress(addr, state.trackedTokens, false) +
total
);
}
function show(walletIdx, addrIdx) {
const wallet = state.wallets[walletIdx];
const addr = wallet && wallet.addresses[addrIdx];
if (!addr) return;
target = { walletIdx, addrIdx };
$("delete-address-label").textContent = "Address " + (addrIdx + 1);
$("delete-address-wallet-name").textContent =
wallet.name || "Wallet " + (walletIdx + 1);
const value = $("delete-address-value");
value.innerHTML = renderAddressHtml(addr.address, {
ensName: addr.ensName,
});
attachCopyHandlers(value);
$("delete-address-recovery").textContent = recoveryPathText(wallet);
$("delete-address-balance").innerHTML = balanceWarningHtml(addr);
setFlash("");
showView("delete-address-confirm");
}
function init(_ctx) {
ctx = _ctx;
$("btn-delete-address-back").addEventListener("click", () => {
target = null;
goBack();
});
$("btn-delete-address-confirm").addEventListener("click", async () => {
if (target === null) {
setFlash("No address is selected for removal.");
return;
}
const { walletIdx, addrIdx } = target;
if (!canRemoveAddress(state.wallets[walletIdx])) {
setFlash(
"This address cannot be removed, because a wallet always " +
"keeps at least one address.",
);
return;
}
const { removed, activeAddressChanged } = removeAddressFromState(
state,
walletIdx,
addrIdx,
);
if (!removed) {
setFlash("This address could not be removed.");
return;
}
target = null;
// Save before broadcasting: the background reads the active address
// back out of storage to build accountsChanged.
await saveState();
if (activeAddressChanged) broadcastActiveChanged();
ctx.renderWalletList();
goBack();
showFlash("Address removed.");
});
}
// recoveryPathText and balanceWarningHtml are exported so the two pieces of
// copy that carry the screen's substance can be tested without a DOM; show()
// is a one-line assignment for each.
module.exports = { init, show, recoveryPathText, balanceWarningHtml };