Skip to content

feat: back up hardware wallet names - #1173

Open
jvsena42 wants to merge 18 commits into
masterfrom
feat/hw-wallet-name-backup
Open

feat: back up hardware wallet names#1173
jvsena42 wants to merge 18 commits into
masterfrom
feat/hw-wallet-name-backup

Conversation

@jvsena42

@jvsena42 jvsena42 commented Aug 20, 2026

Copy link
Copy Markdown
Member

This PR:

  1. Backs up the name you give a hardware wallet, and restores it when the device is paired again.
  2. Asks, when removing a hardware wallet, whether to keep its name and tags in the backup, defaulting to keeping them.

Follow-up to #1163, which closed #1046 by making hardware wallet tags survive backup and restore.

Description

#1163 made hardware wallet tags survive a restore: they ride the metadata backup as pre-activity metadata keyed by the Core-derived wallet id, and Core re-attaches them once the device watcher recreates the activities. Two gaps were left behind.

The name itself was never backed up. It lives only in the local device store, so restoring on a new phone and pairing the Trezor again brought the tags back but not the name the user chose. Nothing even marked a backup as required when a name changed, so a rename was invisible to the backup system entirely.

The second gap is the more damaging one. Removing a hardware wallet destroyed its tags in the backup, silently. Core deletes a wallet's activities, its activity tags and its pre-activity metadata in one cascade, which is both of the sources the metadata envelope draws hardware tags from, and the deletion then signals that tags changed, so a metadata envelope without them was uploaded within seconds. The dialog said only that funds are safe and coins will not be deleted; it never mentioned the tags. A full wallet wipe was never affected, because wiping suppresses uploads and leaves the stored backup intact.

The name travels on the same key the tags already use. Core's wallet id derives from a device's account extended public keys, is stable across installs and platforms, and does not depend on the order of those keys, so a name keyed by it survives a restore exactly as well as the tags do.

The name of a paired wallet still lives on its device entry, which is untouched. What is new is a small pending map for the two moments when no device entry exists: a name restored from a backup before the device has been paired again, and a name kept when a wallet was removed. Pairing takes the entry over and consumes it in the same write that stores the entry, so the name is never in both places or in neither, and a failed save leaves it pending rather than losing it.

Removal takes the name and tag snapshot before the cascade and writes the tags back after it, carrying the name in the same store write that forgets the device entries. Splitting those two writes published a device list that still held the wallet being removed, and a watcher reconcile reading it restarted that wallet's watcher, which silently re-created the activities the removal had just deleted. The two kinds of data are kept independently: a wallet that was never renamed still keeps its tags, which is the more common shape by far. Turning the toggle off is the current behaviour made explicit: the tags go, and any name kept from an earlier removal of the same wallet is dropped too.

The watcher cleanup that removes activity scopes without a known device now reads its input under the same lock as the removal, and only considers wallets that still have activities to clear. Core drops a wallet's tag metadata along with its activities whether or not any matched, so cleaning up a wallet that has none is not a no-op: it takes the metadata a removal deliberately kept.

Two failure points are treated differently on purpose, because the cascade sits between them. Failing to read the tags happens before anything is deleted, so the wallet is untouched and the removal is refused with a message naming the way through — retry, or remove without keeping the data. Failing to write them back happens after the activities are already gone and the watchers already stopped, where there is nothing to roll back to and reporting a failed removal would be false, so that one is logged and the removal completes.

One behaviour falls out of this for free: re-pairing a wallet that was removed with the toggle on restores its tags with no restore flow involved at all, because the pre-activity metadata rows survived locally and Core re-attaches them as the watcher recreates the activities.

The envelope is shared with iOS, whose model mirrors it field for field. Swift ignores unknown keys when decoding, so an older iOS build restores this envelope without trouble, but it drops the new field whenever it re-uploads, and it re-uploads often. A user running both apps on one seed would lose their backed-up names until the iOS side ships. Unlike the tags in #1163, which rode a field iOS already knew, this is a new field, so a companion iOS change is needed. A passthrough that decodes and re-encodes the field, with no UI, is enough to close the window.

Preview

backup-enabled.mp4
backup-disabled.mp4
restore-wallet.mp4

QA Notes

Manual Tests

  • 1. Hardware Wallet → rename the wallet → Settings → Data Backups: Tags shows as synced again.
  • 2. regression: Disconnect and reconnect the device without renaming → Settings → Data Backups: Tags is not re-uploaded.
  • 3. Rename a hardware wallet and tag one of its transactions → wipe wallet → restore from seed → pair the device again: the tile shows the custom name and the tagged transaction shows its tags.
  • 4a. Hardware Wallet → Remove with the keep toggle on → pair the device again: name and tags return.
    • 4b. Remove with the keep toggle off → pair the device again: the wallet comes back under the device's own name with no tags.
    • 4c. Never-renamed wallet with tagged transactions → Remove with the keep toggle on → pair the device again: the tags return.
  • 5. Settings → Hardware Wallets → trash icon: the same dialog opens, with the toggle on by default.
  • 6. Remove dialog → turn the toggle off → cancel → reopen the dialog: the toggle is on again.
  • 7. Passphrase wallet → Remove with the keep toggle on → pair again with the same passphrase: its own name returns, and the standard wallet's name is unaffected.
  • 8. regression: Remove dialog → cancel: the wallet stays paired with its name and tags.

Automated Checks

  • Unit tests added: cover the envelope and the restore in BackupRepoTest.kt, including that a failed name read fails the backup without uploading, that an envelope written before this field never clears stored names, that a failed name write still records the category as restored, and that a rename triggers a metadata backup while a reconnect does not.
  • Unit tests added: cover the removal in HwWalletRepoTest.kt, pinning that the tag rewrite follows Core's cascade and that the kept name rides the write that forgets the device, plus the toggle-off branch, the default, a wallet keeping its tags without ever having been renamed, a removal surviving a failed rewrite, an unreadable-tags failure leaving the wallet entirely untouched, and a removal deleting a wallet's activities exactly once.
  • The watcher-cleanup interleaving behind the double-removal case needs real concurrency and is not reproducible on the test dispatcher, so it is covered by manual test 11 rather than by a unit test. What is unit tested is the resulting rule: only a wallet with activities left behind is cleaned up.
  • Unit tests added: cover pairing in TrezorRepoTest.kt, covering adopting and consuming a pending name in a single store write, preferring a name set locally, and leaving another identity's name alone.
  • Unit tests added: cover the wallet-scoped tag snapshot in ActivityRepoTest.kt. It unions the stored and the rendered metadata, because the cascade drops both and only the rendered half can be rebuilt from activity tags.
  • Unit tests added: cover the toggle in HwWalletViewModelTest.kt, including that it returns to its default when the dialog is reopened, and that an unreadable-tags failure is reported with its own message rather than the generic retry one.
  • Local just compile, just test and just lint pass, with no new detekt findings.

@jvsena42 jvsena42 self-assigned this Aug 20, 2026
@jvsena42
jvsena42 marked this pull request as ready for review August 21, 2026 10:34
@greptile-apps

greptile-apps Bot commented Aug 21, 2026

Copy link
Copy Markdown

Greptile Summary

The PR backs up custom hardware-wallet names alongside metadata and lets users preserve names and tags when removing a wallet.

  • Adds persistent pending-name storage and metadata backup/restore support.
  • Atomically transfers a pending name into the paired device record.
  • Preserves or clears hardware-wallet names and tag metadata based on the removal-dialog toggle.
  • Adds coverage for backup, removal, pairing, tag snapshots, and dialog state.

Confidence Score: 5/5

The PR appears safe to merge.

The previously reported pending-name loss is fixed because the adopted label and pending-name deletion now occur in one DataStore update, so a failed write retains the pending copy for a later retry; no blocking failure remains.

Important Files Changed

Filename Overview
app/src/main/java/to/bitkit/data/HwWalletStore.kt Adds pending-name persistence and atomically consumes a pending name when saving the device record that adopts it.
app/src/main/java/to/bitkit/repositories/TrezorRepo.kt Applies restored or retained names during pairing and uses the atomic store operation to prevent loss on persistence failure.
app/src/main/java/to/bitkit/repositories/BackupRepo.kt Includes hardware-wallet names in metadata snapshots and restores them as pending names before devices are paired.
app/src/main/java/to/bitkit/repositories/HwWalletRepo.kt Coordinates removal with optional preservation of wallet-scoped names and tag metadata.
app/src/main/java/to/bitkit/repositories/ActivityRepo.kt Adds wallet-scoped tag metadata snapshots combining stored and rendered hardware-wallet metadata.

Sequence Diagram

sequenceDiagram
    participant Backup as Metadata Backup
    participant Store as HwWalletStore
    participant Trezor as TrezorRepo
    participant Device as Paired Device
    Backup->>Store: Restore names into pendingNames
    Device->>Trezor: Pair wallet identity
    Trezor->>Store: Read pending name by walletId
    Trezor->>Store: Atomically save device label and consume pending name
    Store-->>Trezor: Durable updated snapshot
Loading

Reviews (2): Last reviewed commit: "fix: make delete pending name and persis..." | Re-trigger Greptile

Comment thread app/src/main/java/to/bitkit/repositories/TrezorRepo.kt Outdated
@jvsena42
jvsena42 marked this pull request as draft August 21, 2026 10:37
@jvsena42
jvsena42 marked this pull request as ready for review August 21, 2026 12:26
Comment thread app/src/test/java/to/bitkit/repositories/HwWalletRepoTest.kt Fixed
Comment thread app/src/test/java/to/bitkit/repositories/HwWalletRepoTest.kt Fixed
Comment thread app/src/test/java/to/bitkit/repositories/HwWalletRepoTest.kt Fixed
Comment thread app/src/test/java/to/bitkit/repositories/HwWalletRepoTest.kt Fixed
Comment thread app/src/test/java/to/bitkit/repositories/HwWalletRepoTest.kt Fixed
Comment thread app/src/test/java/to/bitkit/repositories/HwWalletRepoTest.kt Fixed
Comment thread app/src/test/java/to/bitkit/repositories/HwWalletRepoTest.kt Fixed
Comment thread app/src/test/java/to/bitkit/repositories/HwWalletRepoTest.kt Fixed
Comment thread app/src/test/java/to/bitkit/repositories/HwWalletRepoTest.kt Fixed
Comment thread app/src/test/java/to/bitkit/repositories/HwWalletRepoTest.kt Fixed
@jvsena42
jvsena42 requested review from ovitrif and piotr-iohk August 21, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hardware Wallet Data Backup & Restore

2 participants