177 lines
6.5 KiB
JavaScript
177 lines
6.5 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,
|
|
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 asks for the password and
|
|
// destroys the stored secret — 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 asks for your " +
|
|
"password and 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.
|
|
function balanceWarningHtml(addr) {
|
|
if (!addressHoldsFunds(addr)) return " ";
|
|
const line = formatAddressTotal(getAddressValue(addr));
|
|
const total = line
|
|
? `<div class="text-xs text-muted mt-1">${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 };
|