feat: connect hardware flow#614
Conversation
|
Passphrase flow deferred to another PR, It touches too many files |
Greptile SummaryThis PR replaces the stub Hardware Intro sheet with a fully functional guided wizard (Intro → Searching → Found → Paired) that discovers a nearby Trezor over Bluetooth, pairs it as a watch-only device, surfaces a one-time inline pairing code when required, and returns to Home after Finish — completing the connect flow deferred by the earlier intro-sheet PR.
Confidence Score: 5/5The connect flow is well-isolated behind a protocol seam, task cancellation is handled correctly at all exit paths, and the pairing-code interception guard prevents the app-wide sheet from racing with the wizard. No functional defects were found in the new connect flow, BLE task lifecycle, or error-handling paths. The two findings are visual quality concerns (animation foreground-re-entry and missing @2x image assets) that do not affect correctness or data integrity. HwSearchingAnimation.swift (animation restart on foreground return) and the four new 1x-only imageset Contents.json files.
|
| Filename | Overview |
|---|---|
| Bitkit/ViewModels/Trezor/HwConnectViewModel.swift | New ViewModel driving the Intro→Searching→Found→Paired wizard; service protocol cleanly separates BLE logic for testability. Task cancellation is handled correctly at all early-exit paths. |
| Bitkit/Views/Sheets/HardwareConnectSheet.swift | Sheet host wiring Bluetooth state guards, the pairing-code interception flag, and phase-keyed push transitions. onFinished is assigned inside .task{}, but this is a pre-existing open thread item. |
| Bitkit/Views/Sheets/HardwareConnect/HwSearchingAnimation.swift | Counter-rotating ring animation using repeatForever; onAppear starts it but there is no foreground-re-entry hook to restart the animation after backgrounding. |
| Bitkit/Assets.xcassets/Illustrations/hw-searching-ring.imageset/Contents.json | New ring/arrows/coin-stack imagesets use a single scaleless universal asset; other project illustrations use explicit 1x/2x entries, so these may render blurry on Retina devices. |
| Bitkit/Managers/TrezorManager.swift | Adds a Task.isCancelled guard after connect completes (disconnects cleanly) and two new error-message translations for code-verification failure and device-busy states. |
| Bitkit/Services/Trezor/TrezorTransport.swift | Drains stale semaphore signals before each new pairing-code request, preventing a prior cancelled/submitted code from immediately short-circuiting the new handshake. |
| BitkitTests/HwConnectViewModelTests.swift | 9 test cases covering the full connect flow via a fake service; waitUntil silently passes on timeout (pre-existing flagged item). |
| Bitkit/ViewModels/SheetViewModel.swift | Renames hardwareIntro sheet ID to hardwareConnect and adds the hardwareConnectHandlesPairing flag used to suppress the app-wide pairing sheet while the wizard handles it inline. |
| Bitkit/Views/Sheets/HardwareConnect/HwPairCodeView.swift | Inline 6-digit pairing code entry that submits on the sixth digit and collapses digits into a spinner; handles delete key correctly until submit. |
| Bitkit/Views/Sheets/HardwareConnect/HwPairedView.swift | Paired step showing device balance and editable label field over a coins illustration; layout is correct and Finish passes through to ViewModel. |
| Bitkit/MainNavView.swift | Swaps HardwareIntroSheet for HardwareConnectSheet and adds the guard that suppresses the app-wide pairing sheet while the connect wizard is active. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User taps Add Hardware Wallet\nor Home suggestion card] --> B{Bluetooth usable?}
B -- No --> C[Show Bluetooth alert\nwith Open Settings]
B -- Yes --> D[Phase: .searching\nstartSearching loop]
D -- scan error --> D
D -- device found --> E[Phase: .found\nHwFoundView]
E -- Cancel --> Z[Dismiss sheet\nreset]
E -- Connect --> F{connect task}
F -- device requests\npairing code --> G[Phase: .pairCode\nHwPairCodeView\nhardwareConnectHandlesPairing=true]
G -- 6 digits entered --> H[submitPairingCode\nresumes connect task]
H --> F
F -- connect failed --> E
F -- success --> I[Phase: .paired\nHwPairedView\nshow balance + label]
I -- Finish --> J[setDeviceLabel\nonFinished\nhideSheet + reset nav\nHome page 0]
%%{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[User taps Add Hardware Wallet\nor Home suggestion card] --> B{Bluetooth usable?}
B -- No --> C[Show Bluetooth alert\nwith Open Settings]
B -- Yes --> D[Phase: .searching\nstartSearching loop]
D -- scan error --> D
D -- device found --> E[Phase: .found\nHwFoundView]
E -- Cancel --> Z[Dismiss sheet\nreset]
E -- Connect --> F{connect task}
F -- device requests\npairing code --> G[Phase: .pairCode\nHwPairCodeView\nhardwareConnectHandlesPairing=true]
G -- 6 digits entered --> H[submitPairingCode\nresumes connect task]
H --> F
F -- connect failed --> E
F -- success --> I[Phase: .paired\nHwPairedView\nshow balance + label]
I -- Finish --> J[setDeviceLabel\nonFinished\nhideSheet + reset nav\nHome page 0]
Reviews (2): Last reviewed commit: "fix: error parsing" | Re-trigger Greptile
There was a problem hiding this comment.
💡 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".
…e sheet stops scanning immediately instead of idling up to 2s
…ngering previous connectedDevice can't be reported as success
|
Manual QA — Connect Hardware flow Logs: Verdict: partial pass — not merge-ready ❌Connect flow can complete end-to-end after clearing stale iOS Bluetooth bond, and BT toggle reconnect on Home works. Re-pair after Remove Trezor is broken: phone/Trezor THP state desyncs, pairing PIN loops, wrong-PIN path spins without recovery. What I tested (one session, ~05:56–07:52 UTC)Log bundle is one continuous session (foreground logs roll over at app restart:
What worked ✅
What failed ❌1. Stale iOS bond + misleading errorsBefore forgetting in iPhone Settings, logs show 614.mp42. Re-pair after Remove Trezor (primary blocker)What I saw: Trezor shows Connect to Bitkit → enter PIN → back to Connect with a new PIN while Bitkit still shows pair-code/connect spinner. Phone and device get out of sync. Likely cause: THP handshake retries inside one 3. Wrong PIN → spinner with no recoveryLog: 4. Generic errors in connect UI
Suggested fixes (#614)
Likely also needs #613 / core THP retry work ( QA checklist
Summary: Happy path works once stale iOS bond is cleared; BT reconnect on Home is solid. Re-pair after Remove and THP desync block merge; error UX should be fixed in #614 regardless. |
|
Draft for check comment |
Simulator.Screen.Recording.-.iPhone.17.-.2026-07-06.at.08.30.04.movEmulator basic tests after fixes for #614 (comment) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 16293c21b5
ℹ️ 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".
|
Manual QA — Connect Hardware flow (retest) Attachments: Verdict: approve with one fix ✅ /
|
| Check | Result |
|---|---|
| First pair (after wrong code + retry) | ✅ Connected @ 13:33:38 |
| Re-pair after Remove | ✅ @ 13:35:09 |
| Stale iOS bond error surfaced | ✅ @ 13:37:36 (matches screenshot) |
| Pair after BT forget | ✅ @ 13:39:25 |
Wrong code → second getPairingCode |
Backend OK, UI stuck on spinner @ 13:41:21 |
| Recovery via dismiss + reconnect | ✅ @ 13:43:30 (workaround only) |
Wrong-PIN pattern:
13:41:11 submitPairingCode
13:41:12 Code verification failed
13:41:21 getPairingCode: waiting… ← Trezor new code; Bitkit still spinner
Suggested fix
Reset HwPairCodeView when a new pairing prompt arrives mid-handshake (e.g. .id(attempt) or clear submitted/code on retry). onPairingCodeRequested() alone may not fire again if showPairingCode is already true.
Summary: Main stale-connection blocker is fixed ✅. Only blocker left is wrong-code input reset (Android parity).
|
Manual QA — Connect Hardware flow Attachments: Verdict: approve ✅Retested on latest HEAD — all good.
Not exercised: app-wide pairing sheet when connect wizard dismissed mid-pair (5b). Recommendation: merge #614. |

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
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
regression:back or Cancel from Intro / Searching / Found / Paired: dismisses the sheet instead of stepping back through completed steps.regression:reconnect of a known device that requests a pairing code while the wizard is closed: the app-wide Pair Device sheet still appears.Automated Checks
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.xcodebuild buildsucceeded andHwConnectViewModelTestspassed 9/9 on the iPhone 16 simulator withONLY_ACTIVE_ARCH=YES; SwiftFormat reports no changes.Linked Issues/Tasks
Screenshot / Video
home.mov
hw-settings.mov