fix: the delete wallet screens name the wallet's own key material (closes #529) #590

Merged
clawbot merged 1 commits from issue-529-delete-wallet-key-wording into next 2026-10-19 06:12:01 +02:00
6 changed files with 69 additions and 17 deletions
+10 -6
View File
@@ -2036,7 +2036,9 @@ view would leave a wallet one click from deletion.
- **Elements**: - **Elements**:
- "Back" button, "Delete Wallet" heading - "Back" button, "Delete Wallet" heading
- Warning naming the wallet and stating that deletion is permanent and any - Warning naming the wallet and stating that deletion is permanent and any
funds are unrecoverable without the recovery phrase funds are unrecoverable without the recovery phrase, the private key or
the extended private key, according to the wallet's kind
(`keyMaterialName()` in `src/popup/views/helpers.js`)
- Password input - Password input
- Error line - Error line
- "Confirm Delete" button - "Confirm Delete" button
@@ -2077,9 +2079,10 @@ view would leave a wallet one click from deletion.
cannot be unlocked again, and that no password is needed to delete it cannot be unlocked again, and that no password is needed to delete it
- What deletion does and does not do: it erases the copy of the key stored - What deletion does and does not do: it erases the copy of the key stored
on this device; nothing on chain changes and no money is moved on this device; nothing on chain changes and no money is moved
- The route back — adding the wallet again with the recovery phrase and a - The route back — adding the wallet again, with a new password, from a kept
new password — and, in bold, that without that phrase written down the copy of its recovery phrase, private key or extended private key (named as
deletion loses everything the wallet holds, forever on DeleteWallet) — and, in bold, that without that copy the deletion loses
everything the wallet holds, forever
- That the other wallets are not touched - That the other wallets are not touched
- The wallet's name, and a text input asking for it to be typed back. A name - The wallet's name, and a text input asking for it to be typed back. A name
that shows nothing at all (only spaces, or only characters that paint that shows nothing at all (only spaces, or only characters that paint
@@ -2137,8 +2140,9 @@ view would leave a wallet one click from deletion.
the address **only if it has on-chain activity**. An address that was the address **only if it has on-chain activity**. An address that was
never used is not found by that scan. The text is written by never used is not found by that scan. The text is written by
`recoveryPathText()` rather than sitting in `index.html`, so it can name `recoveryPathText()` rather than sitting in `index.html`, so it can name
the wallet's own kind of key material: an xprv wallet has no recovery the wallet's own kind of key material through `keyMaterialName()`, as
phrase to re-import. DeleteWallet and DeleteWalletLostPassword do: an xprv wallet has no
recovery phrase to re-import.
- A warning when the address holds anything, ETH or any tracked ERC-20, - A warning when the address holds anything, ETH or any tracked ERC-20,
followed by the holdings themselves via `balanceLinesForAddress()` and the followed by the holdings themselves via `balanceLinesForAddress()` and the
USD total via `formatAddressTotal()` (see USD total via `formatAddressTotal()` (see
+8 -7
View File
@@ -1123,7 +1123,8 @@
<p class="text-xs mb-3"> <p class="text-xs mb-3">
Deleting Deleting
<strong id="delete-wallet-name"></strong> is permanent. Any <strong id="delete-wallet-name"></strong> is permanent. Any
funds will be unrecoverable without your recovery phrase. funds will be unrecoverable without your
<span id="delete-wallet-key-material"></span>.
</p> </p>
<div class="mb-2"> <div class="mb-2">
<label class="block mb-1">Password</label> <label class="block mb-1">Password</label>
@@ -1174,13 +1175,13 @@
money at its addresses is not moved or destroyed. money at its addresses is not moved or destroyed.
</p> </p>
<p class="text-xs mb-2"> <p class="text-xs mb-2">
If you have the recovery phrase for this wallet written If you have kept a copy of this wallet's
down, add the wallet again afterwards with a new password <span id="delete-wallet-lost-key-material"></span>, add the
and you will have it back. wallet again afterwards with a new password and you will
have it back.
<strong <strong
>If you do not have it written down, deleting this >If you have not, deleting this wallet means losing
wallet means losing everything it holds, everything it holds, forever.</strong
forever.</strong
> >
</p> </p>
<p class="text-xs mb-3">Your other wallets are not touched.</p> <p class="text-xs mb-3">Your other wallets are not touched.</p>
+2 -4
View File
@@ -17,9 +17,9 @@ const {
attachCopyHandlers, attachCopyHandlers,
addressHoldsFunds, addressHoldsFunds,
balanceLinesForAddress, balanceLinesForAddress,
keyMaterialName,
} = require("./helpers"); } = require("./helpers");
const { formatAddressTotal, getAddressValue } = require("../../shared/prices"); const { formatAddressTotal, getAddressValue } = require("../../shared/prices");
const { walletHasRecoveryPhrase } = require("../../shared/wallet");
const { state, saveState } = require("../../shared/state"); const { state, saveState } = require("../../shared/state");
const { const {
canRemoveAddress, canRemoveAddress,
@@ -54,9 +54,7 @@ function setFlash(msg) {
// The noun follows the wallet: an xprv wallet holds no recovery phrase, and // The noun follows the wallet: an xprv wallet holds no recovery phrase, and
// this screen is offered on xprv wallets too. // this screen is offered on xprv wallets too.
function recoveryPathText(wallet) { function recoveryPathText(wallet) {
const secret = walletHasRecoveryPhrase(wallet) const secret = keyMaterialName(wallet);
? "recovery phrase"
: "extended private key";
return ( return (
"Getting the address back into this list is not easy, so be sure. " + "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 " + "Adding an address derives the next unused one, not this one, and " +
+7
View File
@@ -7,6 +7,7 @@ const {
goBack, goBack,
clearViewStack, clearViewStack,
onViewLeave, onViewLeave,
keyMaterialName,
} = require("./helpers"); } = require("./helpers");
const { state, saveState } = require("../../shared/state"); const { state, saveState } = require("../../shared/state");
const { decryptWithPassword } = require("../../shared/vault"); const { decryptWithPassword } = require("../../shared/vault");
@@ -75,6 +76,9 @@ function show(walletIdx) {
clear(); clear();
deleteWalletIndex = walletIdx; deleteWalletIndex = walletIdx;
$("delete-wallet-name").textContent = displayName(walletIdx); $("delete-wallet-name").textContent = displayName(walletIdx);
$("delete-wallet-key-material").textContent = keyMaterialName(
state.wallets[walletIdx],
);
showView("delete-wallet-confirm"); showView("delete-wallet-confirm");
} }
@@ -94,6 +98,9 @@ function showLostPassword() {
clearLostPassword(); clearLostPassword();
$("delete-wallet-lost-name").textContent = name; $("delete-wallet-lost-name").textContent = name;
$("delete-wallet-lost-name-echo").textContent = name; $("delete-wallet-lost-name-echo").textContent = name;
$("delete-wallet-lost-key-material").textContent = keyMaterialName(
state.wallets[walletIdx],
);
// showView() runs the leave hook of delete-wallet-confirm, which nulls // showView() runs the leave hook of delete-wallet-confirm, which nulls
// deleteWalletIndex, so this screen's own selection is recorded after // deleteWalletIndex, so this screen's own selection is recorded after
// it and not before. // it and not before.
+12
View File
@@ -25,6 +25,7 @@ const { isDebug } = require("../../shared/log");
const { formatUsd, getPrice } = require("../../shared/prices"); const { formatUsd, getPrice } = require("../../shared/prices");
const { state, saveState, currentNetwork } = require("../../shared/state"); const { state, saveState, currentNetwork } = require("../../shared/state");
const { displaySymbol } = require("../../shared/symbolDisplay"); const { displaySymbol } = require("../../shared/symbolDisplay");
const { walletHasRecoveryPhrase } = require("../../shared/wallet");
const { markViewRendered } = require("../viewRouter"); const { markViewRendered } = require("../viewRouter");
// When views are added, removed, or transitions between them change, // When views are added, removed, or transitions between them change,
@@ -418,6 +419,16 @@ function addressHoldsFunds(addr) {
return false; return false;
} }
// The name of a wallet's key material, which is also its only backup: the
// recovery phrase of an HD wallet, the private key of a key wallet, the
// extended private key of an xprv wallet. DeleteWallet,
// DeleteWalletLostPassword and DeleteAddress name the backup with it.
function keyMaterialName(wallet) {
if (walletHasRecoveryPhrase(wallet)) return "recovery phrase";
if (wallet.type === "key") return "private key";
return "extended private key";
}
// The fewest characters of an address any caller may ask to display. The // The fewest characters of an address any caller may ask to display. The
// 10-character cap inside truncateMiddle() is the other half of the same // 10-character cap inside truncateMiddle() is the other half of the same
// guarantee; this is the half that used to be spelled out at each call // guarantee; this is the half that used to be spelled out at each call
@@ -725,6 +736,7 @@ module.exports = {
balanceLine, balanceLine,
balanceLinesForAddress, balanceLinesForAddress,
addressHoldsFunds, addressHoldsFunds,
keyMaterialName,
unknownableAmount, unknownableAmount,
nativeCurrency, nativeCurrency,
tokenLabel, tokenLabel,
+30
View File
@@ -259,6 +259,36 @@ describe("reaching the screen", () => {
}); });
}); });
// Both screens tell the user what they need to get the wallet back, and a
// key or xprv wallet has no recovery phrase: telling its owner to rely on
// one sends them looking for words that do not exist
// (https://git.eeqj.de/sneak/AutistMask/issues/529).
describe("what the screens say the wallet's backup is", () => {
test.each([
["hd", "recovery phrase"],
["key", "private key"],
["xprv", "extended private key"],
])(
"a wallet of type %s is told about its %s",
async (type, keyMaterial) => {
const { deleteWallet, state } = load();
state.wallets[1].type = type;
deleteWallet.show(1);
expect(state.currentView).toBe("delete-wallet-confirm");
expect(node("delete-wallet-key-material").textContent).toBe(
keyMaterial,
);
await click("btn-delete-wallet-lost-password");
expect(state.currentView).toBe(VIEW);
expect(node("delete-wallet-lost-key-material").textContent).toBe(
keyMaterial,
);
},
);
});
describe("the typed confirmation", () => { describe("the typed confirmation", () => {
test("a name that is not the wallet's deletes nothing", async () => { test("a name that is not the wallet's deletes nothing", async () => {
const { deleteWallet, state, storage } = load(); const { deleteWallet, state, storage } = load();