Skip to content

feat: connect hardware flow#614

Open
jvsena42 wants to merge 23 commits into
masterfrom
feat/hw-wallet-connect
Open

feat: connect hardware flow#614
jvsena42 wants to merge 23 commits into
masterfrom
feat/hw-wallet-connect

Conversation

@jvsena42

@jvsena42 jvsena42 commented Jul 2, 2026

Copy link
Copy Markdown
Member

Refs #589

This PR adds the guided Connect Hardware flow for pairing a Trezor as a watch-only wallet over Bluetooth, reachable from the Home hardware suggestion card and Hardware Wallets settings. It completes the "later PR" the intro sheet was deferring to and is an iOS port of bitkit-android's connect hardware flow (synonymdev/bitkit-android#1033).

Description

  • Turns the Hardware intro sheet into a guided in-sheet wizard: Intro → Searching → Found → Paired, driving device discovery, connection, balance display, an editable Label Funds field, and Finish.
  • Discovers a nearby Trezor over Bluetooth, connects and pairs it as a watch-only device, then shows its balance and lets you label the funds before finishing.
  • Surfaces the one-time pairing code inline within the flow when a device asks for it, with the code characters collapsing into the submit spinner while pairing completes.
  • Adds the Figma dashed-ring searching animation (counter-rotating rings and arrows) and the paired coins illustration.
  • Gates Bluetooth: when Bluetooth is off, unauthorized, or unsupported, Continue shows a recovery alert with an Open Settings action instead of starting a scan.
  • After Finish the flow returns to Home so the paired hardware-wallet tile is visible immediately.
  • Keeps the flow sheet-native: cancel or back at any step dismisses the sheet rather than stepping backward, and the app-wide pairing-code sheet no longer replaces the wizard while it is open.

This builds on the hardware-wallet backend already on the branch (device scan/connect, known-device storage, funds label, name resolution, watch-only balances). Because iOS is Bluetooth-only, the Android USB attach handling, USB device identifiers, scan-before-connect, and runtime permission dialog are intentionally omitted; Bluetooth availability is handled via CoreBluetooth state and a Settings deep-link.

QA Notes

OBS: couldn't test some flows on emulator

Manual Tests

  • 1. Settings → General → Payments → Hardware Wallets → Add Hardware Wallet → Continue: in-sheet nav runs Intro → Searching with the dashed-ring loader, then advances to Found when a Trezor is discovered.
  • 2. Home → Hardware suggestion card → Continue: the same connect flow opens and pairs a Trezor. → Navigate to home
  • 3. Found → Connect → Paired: shows the device balance and editable Label Funds field; Finish returns to Home and the Home tile / Settings list show the entered label.
  • 4. First-ever pair → enter the 6-digit code: the code row collapses into the spinner, then the flow advances to Paired.
  • 5a. regression: back or Cancel from Intro / Searching / Found / Paired: dismisses the sheet instead of stepping back through completed steps.
    • 5b. regression: reconnect of a known device that requests a pairing code while the wizard is closed: the app-wide Pair Device sheet still appears.
  • 6. Bluetooth off or unauthorized → Continue: a Bluetooth alert appears with an Open Settings action, and no scan starts.

Automated Checks

  • Unit tests added: BitkitTests/HwConnectViewModelTests.swift (9 cases) cover Intro→Searching discovery, search failure surfacing, Connect→Paired, connect-failure return to Found, inline pairing-code navigation (and its guard when not connecting), connected-wallet balance update, label 50-char cap, and Finish persisting the label.
  • Local build + tests: xcodebuild build succeeded and HwConnectViewModelTests passed 9/9 on the iPhone 16 simulator with ONLY_ACTIVE_ARCH=YES; SwiftFormat reports no changes.

Linked Issues/Tasks

Screenshot / Video

home.mov
hw-settings.mov

@jvsena42 jvsena42 self-assigned this Jul 2, 2026
@jvsena42 jvsena42 mentioned this pull request Jul 2, 2026
8 tasks
@jvsena42 jvsena42 added this to the 2.4.0 milestone Jul 2, 2026
@jvsena42

jvsena42 commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

Passphrase flow deferred to another PR, It touches too many files
#613 (comment)

@jvsena42 jvsena42 marked this pull request as ready for review July 3, 2026 13:09
@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces the disabled HardwareIntroSheet with a fully guided HardwareConnectSheet wizard (Intro → Searching → Found → Paired, plus an inline pairing-code step), wiring together a new HwConnectViewModel backed by a HwConnectServicing protocol over TrezorManager.

  • Adds four new step views (HwSearchingView, HwFoundView, HwPairedView, HwPairCodeView) plus a Figma-matched dashed-ring searching animation, gated by Bluetooth availability checks that surface a Settings deep-link alert when BLE is off/unauthorized.
  • Inlines the one-time pairing code request within the wizard so the app-wide hardwarePairing sheet is suppressed while the flow is open, and uses TrezorKnownDeviceStorage.isKnown to filter the BLE scan to unpaired devices only.
  • Ships 9 HwConnectViewModelTests exercising all major phase transitions via a FakeHwConnectService seam.

Confidence Score: 4/5

The core pairing flow is well-structured and safe to merge; the noted issues are all quality-of-life items with no data-loss risk in the happy path.

The wizard's phase transitions, BLE gating, inline pairing-code step, and teardown on dismiss are all correctly wired. Three areas deserve a follow-up: the BLE scan loop swallows Task cancellation during its 2-second sleep, onFinish() will persist an empty string if the user clears the label field, and onFinished is assigned inside .task{} leaving a narrow async window. None of these affect the typical happy path tested in QA.

HwConnectViewModel.swift — scan loop cancellation and label validation; HardwareConnectSheet.swiftonFinished wiring timing.

Important Files Changed

Filename Overview
Bitkit/ViewModels/Trezor/HwConnectViewModel.swift New ViewModel backing the connect wizard — well-structured with protocol seam for testing, but the BLE scan loop swallows CancellationError during sleep (up to 2s delay on cancel) and onFinish() persists empty labels without validation.
Bitkit/Views/Sheets/HardwareConnectSheet.swift Main sheet orchestrator for the wizard phases; Bluetooth gating, alert, and inline pairing code handling look correct. onFinished is wired inside .task{} which introduces a small async window before the closure is set.
BitkitTests/HwConnectViewModelTests.swift 9 tests covering all major phase transitions with a FakeHwConnectService seam; waitUntil helper silently passes on timeout, making failures harder to diagnose.
Bitkit/Views/Sheets/HardwareConnect/HwPairCodeView.swift Inline 6-digit pairing code entry with collapse-to-spinner animation; digit handling and submit guard look correct.
Bitkit/Views/Sheets/HardwareConnect/HwPairedView.swift Paired step with editable label TextField and coin illustration; balance display uses Int(clamping:) which is safe at any realistic BTC value.
Bitkit/Views/Sheets/HardwareConnect/HwFoundView.swift Found step showing discovered device with Connect/Cancel buttons; uses existing trezor-device asset and correctly surfaces connect errors inline.
Bitkit/Views/Sheets/HardwareConnect/HwSearchingView.swift Searching step composing the ring animation with inline error display; straightforward and correct.
Bitkit/Views/Sheets/HardwareConnect/HwSearchingAnimation.swift Counter-rotating ring/arrow animation; onAppear trigger and .id(viewModel.phase) reset are correct.
Bitkit/MainNavView.swift Sheet registration updated from hardwareIntro to hardwareConnect; the guard preventing app-wide pairing sheet from replacing the wizard is well-placed.
Bitkit/ViewModels/SheetViewModel.swift SheetID enum and computed property renamed from hardwareIntro to hardwareConnect; mechanical rename, no logic changes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Intro] -->|Continue + BT OK| B[Searching]
    A -->|Continue + BT off/unauth| BTA[Bluetooth Alert]
    BTA -->|Open Settings| SET[iOS Settings]
    BTA -->|Cancel| A
    B -->|Device found| C[Found]
    B -->|Scan error| B
    B -->|Cancel| DISMISS[Sheet dismissed]
    C -->|Connect| D{connect task}
    D -->|success| E[Paired]
    D -->|device requests code| F[PairCode]
    F -->|6 digits entered| G[submitPairingCode]
    G -->|connect completes| E
    D -->|error| C
    C -->|Cancel| DISMISS
    E -->|Finish| H[setDeviceLabel]
    H --> I[hideSheet + nav.reset + Home]
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"}}}%%
flowchart TD
    A[Intro] -->|Continue + BT OK| B[Searching]
    A -->|Continue + BT off/unauth| BTA[Bluetooth Alert]
    BTA -->|Open Settings| SET[iOS Settings]
    BTA -->|Cancel| A
    B -->|Device found| C[Found]
    B -->|Scan error| B
    B -->|Cancel| DISMISS[Sheet dismissed]
    C -->|Connect| D{connect task}
    D -->|success| E[Paired]
    D -->|device requests code| F[PairCode]
    F -->|6 digits entered| G[submitPairingCode]
    G -->|connect completes| E
    D -->|error| C
    C -->|Cancel| DISMISS
    E -->|Finish| H[setDeviceLabel]
    H --> I[hideSheet + nav.reset + Home]
Loading

Comments Outside Diff (3)

  1. BitkitTests/HwConnectViewModelTests.swift, line 1188-1193 (link)

    P2 waitUntil silently passes on timeout

    waitUntil exits when the deadline passes without asserting, so if a phase transition never arrives, the next XCTAssertEqual fires with a confusing inequality message rather than a clear timeout failure. Replace the helper with an XCTestExpectation-based approach, or add an explicit XCTFail at the end of waitUntil so the cause is immediately obvious.

  2. Bitkit/ViewModels/Trezor/HwConnectViewModel.swift, line 293-298 (link)

    P2 Empty label is persisted without validation

    onFinish() calls service.setDeviceLabel(id:label:) unconditionally with whatever is in labelInput. If the user clears the text field completely and taps Finish, the device gets renamed to an empty string, leaving it nameless in the UI. Consider adding a guard so that an empty labelInput falls back to deviceName before persisting.

  3. Bitkit/Views/Sheets/HardwareConnectSheet.swift, line 823-829 (link)

    P2 onFinished closure is set inside .task {} — could race with early navigation

    .task { viewModel.onFinished = { … } } executes asynchronously on the first runloop after the view appears. If onIntroContinue() is somehow triggered before .task has run (e.g., a very fast Bluetooth state change), onFinished would be nil and the flow would complete without dismissing the sheet or resetting navigation. Setting viewModel.onFinished in init or onAppear (synchronously) avoids the window entirely.

Reviews (1): Last reviewed commit: "refactor: comments cleanup" | Re-trigger Greptile

Comment thread Bitkit/ViewModels/Trezor/HwConnectViewModel.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: d366be64eb

ℹ️ 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/ViewModels/Trezor/HwConnectViewModel.swift Outdated
Comment thread Bitkit/MainNavView.swift Outdated
@jvsena42 jvsena42 requested a review from piotr-iohk July 3, 2026 15:04
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.

1 participant