fix: the delete wallet screens name the wallet's own key material (closes #529)
DeleteWallet and DeleteWalletLostPassword told key and xprv wallets that their recovery phrase was the way back, but those wallets have none. Both screens now name the recovery phrase, the private key or the extended private key, according to the wallet's kind. The noun comes from keyMaterialName() in the popup view helpers, which DeleteAddress now uses too in place of its own two-way choice, so these three screens get it from one place. The lost-password screen's route-back sentence is reworded to fit all three kinds. Model: opus-5-5
This commit was merged in pull request #590.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,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.
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user