Skip to content

feat: persist hardware wallet activities - #648

Draft
jvsena42 wants to merge 7 commits into
masterfrom
feat/persist-hardware-activities
Draft

feat: persist hardware wallet activities#648
jvsena42 wants to merge 7 commits into
masterfrom
feat/persist-hardware-activities

Conversation

@jvsena42

Copy link
Copy Markdown
Member

Description

This PR ports bitkit-android #1044 to iOS, making watch-only hardware wallet activity a full participant in bitkit-core's wallet-scoped storage. iOS already stored hardware activity under a derived wallet id, so this fills the gaps that were left behind:

  1. Persists the Electrum transaction details the hardware watcher emits, so Explore shows inputs and outputs for a hardware transaction instead of an empty section.
  2. Reconciles each watcher snapshot against what is already stored rather than only adding to it, so a reorged or replaced transaction stops showing. Transfer information the app wrote itself survives the reconcile, including a pending transfer the watcher has not seen yet.
  3. Records a transfer to spending funded from a hardware wallet against that wallet, so it reads as a single hardware transfer rather than a main-wallet row shadowed by a hardware duplicate. The wallet is resolved before the device is asked to sign, so a transfer is never recorded against the wrong wallet.
  4. Scopes the remaining activity operations to the wallet that owns the activity: seen state, deletion, contacts, and boost relationships. Marking everything as seen and clearing all activity previously skipped hardware wallets entirely.
  5. Enables tags and contacts on hardware activities, which were hidden behind an interim gate while those operations were unscoped. Boost stays unavailable, since a watch-only wallet has no signing keys.
  6. Tells the user when transaction details are unavailable instead of quietly hiding the section.

The PR also moves bitkit-core from 0.4.2 to 0.5.3. Version 0.4.2 was cut from a branch that never merged upstream, and its only unique symbol was the extended-public-key serializer used when claiming a watch-only account. That single dependency pinned the repo to a dead-end tag and blocked every upgrade, so this decodes the key locally instead, matching the approach in #632. Everything the hardware wallet integration relies on is unchanged between the two versions.

Linked Issues/Tasks

Screenshot / Video

Insert relevant screenshot / recording — a hardware receive showing a tag and populated Explore inputs/outputs, and a hardware transfer to spending showing one transfer row.

QA Notes

Manual Tests

  • 1. Settings → Advanced → Dev Settings → Trezor → pair the Bridge emulator: Home shows the hardware wallet tile with its balance and blue activity rows.
  • 2. Fund two hardware addresses with different amounts → mine a block → Home → Show All: both blue received rows appear exactly once.
  • 3a. Blue hardware receive → Activity Item → Tag: the tag button is available and adding a tag succeeds.
    • 3b. back → reopen the same activity: the tag is still there.
    • 3c. Activity Item → Explore: Inputs and Outputs are populated instead of empty.
  • 4. All Activity → tag filter → select the hardware tag: the tagged blue row remains and untagged rows are gone.
  • 5a. Hardware Wallet → Transfer To Spending → 25% → Continue → approve on device → Continue Using Bitkit: Home shows exactly one new blue Transfer / From Savings row, not a main-wallet row plus a hardware duplicate.
    • 5b. Tap that row → Activity Item: shows TO SPENDING.
    • 5c. Wait for the next watcher poll: the row keeps its transfer styling and does not revert to a plain send.
  • 6. All Activity → switch Sent, Received, Other, All: hardware receives appear under Received, the hardware transfer under Other, and All shows the combined list with no duplicate.
  • 7a. Settings → Hardware Wallets Settings → remove the paired device: its activity disappears from Home and All Activity.
    • 7b. Re-pair the same wallet: its activity rebuilds without duplicate rows.
  • 8. regression: App on-chain and LN activity → Activity Item, tags, contacts, Explore, Savings and Spending lists, boost: behaviour matches master.
  • 9. regression: Settings → Advanced → Watch-only accounts → set up an account against Pubky Ring: the claim is still accepted after the local key decoding change.

Automated Checks

  • Unit tests added: BitkitTests/HwSnapshotMergeTests.swift covers the snapshot reconciliation rules — a stored row missing from a snapshot is dropped, a transfer is not, and transfer metadata is carried forward and matched by transaction id rather than activity id.
  • Unit tests added: BitkitTests/WatchOnlyAccountServiceTests.swift pins the local extended-public-key decoding against bitkit-core's own test vectors, plus invalid character, bad checksum, off-curve key, and wrong-length rejection.
  • Unit tests added: BitkitTests/HwWalletManagerTests.swift covers transaction details reaching persistence scoped to the derived wallet id, details deduped across two address-type watchers, an unconfirmed transaction whose timestamp drifts between polls not re-writing to core, and wallet id resolution for a device.
  • Unit tests added: BitkitTests/TransferViewModelHwTests.swift covers an unresolvable wallet id aborting before the device signs or anything is broadcast.
  • Unit tests modified: BitkitTests/HwWalletManagerTests.swift and BitkitTests/HwWalletManagerFundingTests.swift record wallet-scoped snapshots instead of bare activity lists; BitkitTests/HwTransferMocks.swift gains wallet id resolution.
  • Test coverage removed: the hardware-duplicate collapsing test in BitkitTests/ActivityHardwareTests.swift is gone because the funding transaction is now stored only under the hardware wallet, so there is no duplicate to collapse. It is replaced by a case proving the same transaction id in two wallet scopes stays two distinct activities.
  • Local run: 654 tests across the 60 offline unit suites passed on the iPhone 16 simulator. UtxoSelectionTests and DustChangeHelperTests pass on their own but hit the live Blocktank regtest deposit endpoint and flaked once with a 404 during a long run.
  • Not run locally: AddressTypeIntegrationTests, BlocktankTests, LdkMigration, ChannelPurchaseFlow, and BroadcastConnectivityTests need a live node and hang without the regtest stack. The manual tests above were also not run — they need a Trezor Bridge emulator, so the hardware journeys rest on review and QA.
  • Local run: SwiftFormat lint passed for all changed Swift files, and node scripts/validate-translations.js reported no errors.
  • CI: standard build and test checks run by the PR bot.

@jvsena42 jvsena42 self-assigned this Jul 31, 2026
@ovitrif ovitrif added this to the 2.5.0 milestone Jul 31, 2026
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.

2 participants