feat: remove an address from an HD wallet, behind a confirmation (closes #162)
All checks were successful
check / check (push) Successful in 36s

Address rows on Home gain an [x] control, on wallets that derive addresses from
an extended key and hold more than one, opening a DeleteAddress confirmation
screen.

Removal cannot destroy anything: the key material stays. Derivation indices are
not renumbered, so the next "+" derives the next unused index rather than
resurrecting the removed one. The confirmation states the real route back --
delete the whole wallet in Settings, which asks for the password and destroys
the stored recovery phrase, then import it again -- and notes that the scan
which follows only finds addresses with on-chain activity. The copy varies by
wallet type, since an xprv wallet has no recovery phrase.

Removing an address that holds a balance is allowed, with a warning naming no
figure; the funds are at the address on-chain and stay there either way.
Selection and active address move only when the removed address was the one
selected, and site permissions are dropped for it alone.

The state transition shares its address comparison, permission cleanup and
active-changed broadcast with the wallet-level removal.
This commit was merged in pull request #240.
This commit is contained in:
2026-08-12 11:16:29 +02:00
parent 1f41a07df2
commit 937f699fb1
11 changed files with 905 additions and 19 deletions

View File

@@ -138,8 +138,11 @@ transfer, and the recovery phrase screen — which wallet types are offered it,
that it holds nothing before the password is accepted, that a wrong password
reveals nothing, that leaving it by either route wipes it — including a leave
taken while the decrypt is still running — and that reopening the popup does not
land on it. All outbound network is intercepted at the browser level and served
from fixtures in `tests/e2e/network.js`, so the run is deterministic and fully
land on it. It also covers address removal: which wallets offer the control at
all, that the confirmation states the route back rather than showing an empty
paragraph, that leaving the confirmation removes nothing, and that confirming it
does. All outbound network is intercepted at the browser level and served from
fixtures in `tests/e2e/network.js`, so the run is deterministic and fully
offline; unrecognised outbound requests are reported as failures rather than
silently allowed.
@@ -531,8 +534,9 @@ on ConfirmTx, DeleteWallet, ApproveTx and ApproveSign.
- Wallet list: each wallet shows its name (tap to rename inline) and a "+"
button for HD and xprv wallets, then one block per address with "Address
N" (bold when active), the ENS name if resolved, the full address, an
`[info]` button, the address USD total, and a balance line for ETH and for
each token shown for that address
`[info]` button, an `[x]` button (only on HD and xprv wallets holding more
than one address), the address USD total, and a balance line for ETH and
for each token shown for that address
- "Recent Transactions": up to 25 transactions merged across every address
of every wallet, deduplicated by hash and filtered
- "Add additional wallet..." link at bottom
@@ -542,6 +546,7 @@ on ConfirmTx, DeleteWallet, ApproveTx and ApproveSign.
- Tap wallet name → inline rename field (no screen change)
- "+" on wallet → derives the next address inline (no screen change)
- `[info]` on address → **AddressDetail**
- `[x]` on address → **DeleteAddress**
- "Send" → **Send** (refuses with a flash message on a zero balance)
- "Receive" → **Receive** (shows active address QR)
- Tap home tx row → **TransactionDetail**
@@ -920,6 +925,55 @@ on ConfirmTx, DeleteWallet, ApproveTx and ApproveSign.
nothing deleted
- "Back" → previous screen (Settings)
#### DeleteAddress (`delete-address-confirm`)
- **When**: User tapped the `[x]` next to an address on Home. Offered only on HD
and xprv wallets holding more than one address: the last address of a wallet
is never removable, and a key wallet has exactly one.
- **Elements**:
- "Back" button, "Remove Address" heading
- The address's own label ("Address N") and its wallet's name
- The full address (color dot, etherscan link, tap to copy), with the ENS
name above it if resolved
- Explanation that this only stops the wallet tracking the address: nothing
is destroyed, no key is deleted, and funds stay where they are
- The route back, stated with its limit, because the obvious two are both
refused: "+" derives the next unused index (`nextIndex` is a high-water
mark), and re-importing the wallet's key material is rejected as a
duplicate by `findWalletByXpub` while the wallet is still present. What
works is deleting the whole wallet in Settings — password-gated, and it
destroys the stored secret — then importing again, whereupon
`scanForAddresses()` rediscovers 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 `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 phrase to re-import.
- A warning when the address holds anything, ETH or any tracked ERC-20,
followed by the holdings themselves via `balanceLinesForAddress()` and the
USD total via `getAddressValueUsd()`. The sentence names no figure of its
own: the 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
predicate is `addressHoldsFunds()` in `src/popup/views/helpers.js`,
unrounded and token-aware. A balance is a warning, never a refusal.
- The rule that a wallet always keeps at least one address, and that
removing the last one means deleting the wallet from Settings
- Error line
- "Remove Address" button
- **Transitions**:
- "Remove Address" → removes the address and its site permissions, then →
previous screen (Home) with an "Address removed." flash message
- "Back" → previous screen (Home), nothing removed
- **Deliberately not password-gated**, unlike DeleteWallet: a password gates the
disclosure or destruction of a secret, and this does neither. The address
stays derivable from key material the wallet still holds.
- The active address moves only if it was the address removed, and then to the
wallet's first remaining address, with `AUTISTMASK_ACTIVE_CHANGED` broadcast
so a connected site stops being told about an address the user removed
(`src/shared/walletDelete.js`). A selection in any other wallet is left alone;
one in this wallet follows the splice.
- The wallet's derivation counter (`nextIndex`) is not rewound, so "+" derives a
fresh address rather than handing back the one just removed.
#### SettingsAddToken (`settings-addtoken`)
- **When**: User tapped "+ Add token" in Settings. Tokens added here are tracked
@@ -1403,7 +1457,7 @@ Currently supported:
### Wallet Management
- [x] Delete wallet (with confirmation)
- [ ] Delete address from HD wallet (with confirmation)
- [x] Delete address from HD wallet (with confirmation)
- [x] Show wallet's recovery phrase (requires password)
### Transactions