Skip to content

feat: hardware wallet detail screen#611

Open
jvsena42 wants to merge 7 commits into
masterfrom
feat/hw-wallet-activity
Open

feat: hardware wallet detail screen#611
jvsena42 wants to merge 7 commits into
masterfrom
feat/hw-wallet-activity

Conversation

@jvsena42

@jvsena42 jvsena42 commented Jun 29, 2026

Copy link
Copy Markdown
Member

Part of #589

This PR adds a detail screen for paired hardware wallets. Tapping a device's tile on the home screen now opens a Savings/Spending-style overview of that watch-only wallet instead of a "Coming Soon" notice.

Description

  • Adds the Hardware Wallet detail screen: the device name with a blue Bitcoin icon in the top bar, a balance header, the device's on-chain activity grouped by date with blue hardware icons, and a Remove button.
  • Adds a Transfer To Spending button on funded devices; it shows a "not yet implemented" notice for now (the on-device signed transfer flow lands in a later PR).
  • Removing a device shows a confirmation dialog explaining funds stay safe, then stops the device's watchers, deletes its stored activity, and forgets every paired entry for that wallet, so its tile and balance disappear from the home screen.
  • Empty / zero-balance devices show only the balance header and the Remove button.
  • Renders the global Send / Scan / Receive bar on the new route, matching the Savings and Spending screens.

This is the iOS port of synonymdev/bitkit-android#1022. iOS already had the watch-only data layer (per-device deviceIds, removeDevice, walletsLoaded, blue hardware activity icons, and walletId-scoped activity queries), so this PR is mainly the detail screen and its navigation wiring.

Linked Issues/Tasks

Screenshot / Video

with-activities.mov
delete.mov

QA Notes

OBS: Only tested with emulator

Manual Tests

  • 1. Pair a Trezor -> Home -> tap the hardware tile -> Hardware Wallet opens: device name with a blue Bitcoin icon in the top bar, balance header, recent device activity with blue icons.
  • 2. Hardware Wallet (funded) -> tap Transfer To Spending: "Transfer to spending not yet implemented." toast shows.
  • 3. Hardware Wallet -> tap an activity item -> Activity Item opens.
  • 4a. Hardware Wallet -> tap Remove -> confirm dialog (funds-safe copy) -> Cancel: dialog closes, still on Hardware Wallet.
    • 4b. tap Remove -> Remove: returns to Home, the tile is gone, the headline total drops the hardware balance, no error toast.
  • 5. Empty / zero-balance device -> Hardware Wallet: ₿0 balance header, no Transfer button, Remove button shown, Send/Receive bar visible.
  • 6. regression: Home, Savings, Spending, and All Activity -> bottom Send/Scan/Receive bar visible on each.

Automated Checks

  • Build passes: xcodebuild -workspace Bitkit.xcodeproj/project.xcworkspace -scheme Bitkit -configuration Debug -destination 'platform=iOS Simulator,id=<iPhone 16>' ONLY_ACTIVE_ARCH=YES build (concrete simulator UDID + ONLY_ACTIVE_ARCH=YES because the Rust xcframeworks are arm64-only).
  • Tests pass: BitkitTests/HwWalletManagerTests (29 tests) — existing coverage for removeDevice (stops watchers, deletes activities) and forget-via-update. No new tests added because the new code is SwiftUI view orchestration over the already-tested HwWalletManager.removeDevice and TrezorManager.forgetDevice.
  • SwiftFormat: clean on changed files.
  • node scripts/validate-translations.js could not run locally (missing glob dependency / no package.json in this checkout); the added English keys are well-formed.
  • CI: standard build and test checks run by the PR bot.

@jvsena42 jvsena42 self-assigned this Jun 29, 2026
@jvsena42 jvsena42 added this to the 2.4.0 milestone Jun 29, 2026
@jvsena42

Copy link
Copy Markdown
Member Author

Waiting for #605 merge to open this branch for review

@piotr-iohk

Copy link
Copy Markdown
Collaborator

Observations from testing

Testing observations (Trezor Safe 7, regtest + iPhone 13, stacked on #605 + #611)

Logs:
bitkit_logs_2026-06-30_09-12-00.zip

Ran through the QA checklist in #611 — detail screen, transfer placeholder, activity drill-down, remove flow, Send/Scan/Receive bar regression. All listed manual tests pass from a product perspective. Non-blocking follow-ups tracked in #613.


QA checklist coverage

Test Result
1. Tap hardware tile → detail screen (name, blue icon, balance, activity)
2. Transfer To Spending → not-yet-implemented toast
3. Tap activity → Activity Detail
4a. Remove → Cancel → stay on detail
4b. Remove → confirm → Home, tile gone, headline total updated
5. Empty / zero-balance device layout ✅ tested on Trezor emulator
6. Regression: Send/Scan/Receive bar on Home, Savings, Spending, All Activity

1. Remove from detail → Trezor settings still shows connected ❌

After 4b (Remove from Hardware Wallet detail): Home tile and balance correctly disappear. But opening Settings → Advanced → Trezor Hardware Wallet still shows the device as connected (video attached).

ScreenRecording_06-30-2026.11-09-36_1.MP4

Likely cause: HardwareWalletScreen.removeWallet() calls hwWalletManager.removeDevice + trezorManager.forgetDevice, which clears known-device storage and credentials, but forgetDevice does not clear TrezorManager.connectedDevice or call disconnect() when the forgotten device is still the active session. The dev Trezor screen (TrezorConnectedView) keys off trezorManager.isConnected / connectedDevice, not the watch-only tile.

Session log at remove (09:09:47 UTC): Forgot device: ble:C428B3C1… — no matching Disconnected from Trezor / clearDisconnectedDeviceState until later manual disconnect elsewhere.

Suggestion: when removing/forgetting the currently connected device, also disconnect and clear connection state (mirror Android TrezorRepo.forgetDevice). Either fix in this PR or track in #613.


2. Device busy / ping cadence while Trezor is locked ℹ️

While connecting with the Trezor locked (waiting for user to unlock), Bitkit keeps reopening the transport instead of backing off — same class of issue as Android bitkit-android#1030 (Device-state / ping-cadence). Not introduced by #611; track in #613.

Observed in session logs ~09:10:41–09:11:02 UTC:

Time (UTC) Event
09:10:41 User-initiated connect while device locked
09:10:49 THP handshake → DeviceLocked
09:10:49–51 Handshake retry: closeDevice → wait 2000ms → openDevice again
09:10:51 / 09:10:56 / 09:11:00 Further openDevice attempts on same path
09:11:02 Connection failed: THP handshake failed: … DeviceLocked

While the user is unlocking on-device, repeated transport opens/handshake retries can keep the Trezor busy.


Follow-up tracking

  • §1 (dev screen still connected after Remove): either fix here, or move to #613.
  • §2 (device busy / repeated opens while locked): move to #613.

Overall: #611 detail screen looks good — happy to approve. §1 is the only gap worth a quick fix in this PR; otherwise both items can land in #613.

Base automatically changed from feat/home-hw-wallet-ui to master June 30, 2026 11:07
@jvsena42 jvsena42 marked this pull request as ready for review June 30, 2026 11:09
@jvsena42 jvsena42 marked this pull request as draft June 30, 2026 11:11
@jvsena42

Copy link
Copy Markdown
Member Author

draft for implementing the first fix

@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a HardwareWalletScreen that replaces the "Coming Soon" toast with a Savings/Spending-style detail view for paired hardware wallets, including balance display, date-grouped activity, a Transfer-to-Spending placeholder, and a Remove flow. It also fixes a latent bug in TrezorManager.forgetDevice where forgetting the currently-connected device left the BLE/USB session alive.

  • New screen: HardwareWalletScreen shows the device name + blue BTC icon, balance header, on-chain activity (filtered by walletId), a Transfer button for funded wallets, and a Remove button with a confirmation dialog. Auto-pops when the wallet disappears from HwWalletManager.wallets, guarded by walletsLoaded to avoid spurious pops during startup.
  • forgetDevice fix: isActiveSession is now captured before any await suspension points; disconnect() is called after storage cleanup and loadKnownDevices(), so autoReconnect sees the trimmed device list before a reconnect attempt can fire.
  • Navigation / tab-bar wiring: .hardwareWallet(deviceId:) added to Route, registered in MainNavView, and included in the shouldShow switch in TabBar so the Send/Scan/Receive bar appears on the new screen.

Confidence Score: 5/5

Safe to merge — the new screen is additive, the remove flow is well-guarded, and the forgetDevice fix is a clear correctness improvement.

The HardwareWalletScreen follows the same observable patterns as the existing Savings/Spending screens. The removeWallet flow correctly uses the local wallet capture, stops all group watchers via removeDevice, and lets the reactive onChange handle navigation. The forgetDevice change resolves a real gap (orphaned session) without introducing any new state transitions. No functional regressions were found in the changed files.

No files require special attention.

Important Files Changed

Filename Overview
Bitkit/Views/Wallets/HardwareWalletScreen.swift New detail screen for paired hardware wallets; mirrors Savings/Spending layout with balance header, activity list, transfer placeholder, and remove flow. Logic is correct; let wallet = wallet in body correctly eliminates repeated computed-property evaluation; auto-pop on wallet removal is guarded by walletsLoaded.
Bitkit/Managers/TrezorManager.swift Adds active-session disconnect to forgetDevice: captures isActiveSession before any await suspension points, then calls disconnect() after storage cleanup. Ordering is correct — loadKnownDevices() runs before disconnect() so autoReconnect sees the updated (empty or trimmed) device list.
Bitkit/Components/TabBar/TabBar.swift Replaces Set-based lookup with explicit switch; adds .hardwareWallet to the tab-bar-visible routes. Swift correctly pattern-matches associated-value cases without binding, so the case .hardwareWallet: clause compiles and behaves as intended.
Bitkit/ViewModels/NavigationViewModel.swift Adds hardwareWallet(deviceId: String) route enum case. Straightforward and consistent with the existing route definitions.
Bitkit/Views/Home/HomeWalletView.swift Replaces "Coming Soon" toast with navigation to the new hardwareWallet route; injects NavigationViewModel as @EnvironmentObject. Clean and minimal change.
Bitkit/Resources/Localization/en.lproj/Localizable.strings Adds four new localization keys for the remove dialog, remove button, and the transfer-not-implemented toast. Keys follow existing naming conventions and are well-formed.
Bitkit/MainNavView.swift Registers HardwareWalletScreen as the destination for the new .hardwareWallet(deviceId:) route. One-line addition consistent with existing route registrations.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    actor User
    participant Home as HomeWalletView
    participant Nav as NavigationViewModel
    participant HW as HardwareWalletScreen
    participant HWM as HwWalletManager
    participant TM as TrezorManager

    User->>Home: Tap hardware wallet tile
    Home->>Nav: navigate(.hardwareWallet(deviceId:))
    Nav->>HW: Push screen

    HW->>HWM: wallet (computed, via deviceIds.contains)
    HW->>HWM: loadActivities (via CoreService, walletId scoped)

    User->>HW: Tap Remove device
    HW->>HW: "showRemoveDialog = true"
    User->>HW: Confirm remove
    HW->>HWM: removeDevice(id: wallet.id) stops watchers, deletes activities
    loop for each id in wallet.deviceIds
        HW->>TM: await forgetDevice(id:) clears credentials, removes from storage
        TM->>TM: loadKnownDevices()
        TM->>TM: await disconnect() if isActiveSession
    end
    HWM-->>HW: wallets updated (wallet gone)
    HW->>Nav: navigateBack() via onChange
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    actor User
    participant Home as HomeWalletView
    participant Nav as NavigationViewModel
    participant HW as HardwareWalletScreen
    participant HWM as HwWalletManager
    participant TM as TrezorManager

    User->>Home: Tap hardware wallet tile
    Home->>Nav: navigate(.hardwareWallet(deviceId:))
    Nav->>HW: Push screen

    HW->>HWM: wallet (computed, via deviceIds.contains)
    HW->>HWM: loadActivities (via CoreService, walletId scoped)

    User->>HW: Tap Remove device
    HW->>HW: "showRemoveDialog = true"
    User->>HW: Confirm remove
    HW->>HWM: removeDevice(id: wallet.id) stops watchers, deletes activities
    loop for each id in wallet.deviceIds
        HW->>TM: await forgetDevice(id:) clears credentials, removes from storage
        TM->>TM: loadKnownDevices()
        TM->>TM: await disconnect() if isActiveSession
    end
    HWM-->>HW: wallets updated (wallet gone)
    HW->>Nav: navigateBack() via onChange
Loading

Reviews (2): Last reviewed commit: "fix: reduce wallet computation and asser..." | Re-trigger Greptile

Comment thread Bitkit/Views/Wallets/HardwareWalletScreen.swift
Comment thread Bitkit/Views/Wallets/HardwareWalletScreen.swift
Comment thread Bitkit/Views/Wallets/HardwareWalletScreen.swift

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 758a1dbe6b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Bitkit/Views/Wallets/HardwareWalletScreen.swift
@jvsena42 jvsena42 marked this pull request as ready for review June 30, 2026 12:28

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 080608a8c6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Bitkit/Views/Wallets/HardwareWalletScreen.swift

@piotr-iohk piotr-iohk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retested: fixed — after Remove, dev Trezor screen no longer shows a stale connected session.

@jvsena42 jvsena42 requested a review from ovitrif June 30, 2026 12:55
@jvsena42 jvsena42 enabled auto-merge June 30, 2026 14:40

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

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.

3 participants