Skip to content

feat(status): expose cached device status for unseeded-device detection#29

Merged
TaprootFreak merged 3 commits into
developfrom
fix/bitbox-uninitialized-device-status
Jun 9, 2026
Merged

feat(status): expose cached device status for unseeded-device detection#29
TaprootFreak merged 3 commits into
developfrom
fix/bitbox-uninitialized-device-status

Conversation

@TaprootFreak

Copy link
Copy Markdown

Problem

A BitBox paired without a wallet set up (no seed) cannot derive an ETH address. The device-side error makes ETHGetAddress return "", which the host app could not tell apart from a transient empty read after a BLE stall. Both failed the same way, so a brand-new, unseeded device dropped the user into a silent retry loop with no explanation.

The host needs a reliable, proactive signal that the device has no seed — before it tries (and fails) to read an address.

What changed

Expose the SDK's cached firmware status as a new gomobile-exported DeviceStatus(), returning uninitialized / seeded / initialized (empty string when no device). It reads the locally cached firmware.Statusno device round-trip, cannot block — following the same boundary discipline as getChannelHash.

Wired through every layer per CONTRIBUTING's "Adding a new platform method":

  • Go: Status() added to the bitboxDevice interface; new //export DeviceStatus guarded by recoverPanic.
  • Android: GetDeviceStatusOperation + registration in BitboxFlutterPlugin.
  • iOS: getDeviceStatus case in the method-channel handler.
  • Dart: BitboxUsbPlatform.getDeviceStatus()MethodChannelBitboxUsbBitboxManager.getDeviceStatus().
  • Testkit: SimulatedBitboxPlatform simulates the status (deviceStatus, default initialized) + SimulatedBitboxMethod.getDeviceStatus.
  • Artefacts: api.aar, api-sources.jar, Api.xcframework regenerated via gomobile.

The host-app side (detect unseeded device after pairing → dedicated state) lands in a companion RealUnitCH/app PR once this is tagged.

Test plan

  • go vet ./... clean
  • go test -race ./... green — DeviceStatus asserted in the pairing harness (status initialized) and the nil-device harness (returns "")
  • dart format --set-exit-if-changed clean
  • flutter analyze --no-fatal-infos — no issues
  • flutter test — all green, incl. two new testkit cases (default + unseeded status)
  • On-device: pair an uninitialized BitBox → getDeviceStatus() returns uninitialized

A BitBox paired without a wallet (no seed) cannot derive an ETH address:
ETHGetAddress returns "" on the device error, which the host could not tell
apart from a transient empty read after a BLE stall. Both surfaced the same way,
so a brand-new, unseeded device dropped the user into a silent retry loop with
no explanation.

Expose the SDK's cached firmware status (uninitialized / seeded / initialized)
as a new gomobile-exported DeviceStatus(), wired through the Android and iOS
bridges, the Dart platform interface, BitboxManager, and the testkit. It reads
the locally cached status with no device round-trip, so the host app can detect
an unseeded device up front and show a dedicated state instead of the generic
failure.

Regenerated api.aar, api-sources.jar and Api.xcframework via gomobile.
Brings in #30's 16 KB page-alignment fix. The api.aar conflict is resolved by
rebuilding the gomobile binding with BOTH the 16 KB alignment flag
(-extldflags=-Wl,-z,max-page-size=16384, from the merged build script) AND the
new DeviceStatus export, so the merged artefact keeps Android 15+ support and
exposes the device status. libgojni.so PT_LOAD p_align verified at 0x4000.
@TaprootFreak TaprootFreak marked this pull request as draft June 9, 2026 18:50
@TaprootFreak

Copy link
Copy Markdown
Author

Code review (quality + logic) clean and CI green, but holding as Draft on purpose: the native artefacts (api.aar / xcframework) were rebuilt and are not yet on-device verified. Before merge + tag, please run an on-device smoke test — pair an already set-up BitBox and confirm it reaches the wallet normally (proves the rebuilt binding + new status read don't regress the existing flow), and optionally an un-set-up device → explanatory screen. My DeviceStatus lands in v0.0.10 (v0.0.9 = the 16 KB alignment release).

@TaprootFreak TaprootFreak marked this pull request as ready for review June 9, 2026 22:18
@TaprootFreak TaprootFreak merged commit b0832b3 into develop Jun 9, 2026
3 checks passed
TaprootFreak added a commit to RealUnitCH/app that referenced this pull request Jun 9, 2026
## Problem

Connecting a **brand-new BitBox that has no wallet set up** (no seed)
left the user stuck. Pairing succeeds, but the device has no seed to
derive an ETH address from, so `getETHAddress` comes back empty. That
empty read failed as a generic error → `BitboxNotConnected` → a SnackBar
"something went wrong" → and because the re-scan timer is re-armed, the
device is immediately found again and the user is walked through the
pairing code → fail → SnackBar **loop**, with no hint that the real
problem is simply an un-set-up device.

(The earlier fix #710 stopped the empty address from being *persisted* —
no more grey screen — but did not distinguish "no seed" from a transient
empty read.)

## What changed

After channel-hash verify, read the device's firmware status via the new
`bitbox_flutter` `getDeviceStatus()` (cached read, no device
round-trip). When it reports `uninitialized`, emit a dedicated
**`BitboxNotInitialized`** state that explains the user must set up /
restore a wallet on the device first.

- The state offers a **retry** (`recheckDeviceStatus`) that re-reads the
status — if the user has since set up a wallet, the connection continues
without re-pairing.
- It deliberately **does not arm the re-scan timer**, so the silent
re-pair loop is gone.
- Only `uninitialized` is treated as "no wallet". Other non-ready
statuses (e.g. firmware-upgrade-required) intentionally keep the
existing failure path rather than being mislabelled.

The address-derivation/observe/sign tail of `confirmPairing` was
extracted into `_acquireWalletAndConnect()` so the initial flow and the
retry share one path.

## Dependency

Requires `bitbox_flutter` `getDeviceStatus()` from
**[DFXswiss/bitbox_flutter#29](DFXswiss/bitbox_flutter#29.
This PR temporarily pins the plugin to that fix branch; it will be moved
to the **`v0.0.9`** tag once #29 is merged and tagged. **Kept as Draft
until then.**

## Test plan

- [x] `flutter analyze` — clean (only the pre-existing
generated-`i18n.dart` warning)
- [x] `flutter test` — bitbox cubit / service / view suites all green
- [x] Cubit: unseeded → `BitboxNotInitialized`, no wallet created, no
re-scan loop (state stays stable); retry continues once seeded; retry
stays while still unseeded; no-op off-state
- [x] Service: `getDeviceStatus` pass-through via the simulator
- [x] Widget: `BitboxNotInitialized` renders retry + cancel; retry calls
`recheckDeviceStatus`
- [x] i18n: new keys in both `de`/`en` ARBs (case-sensitive ASCII
order), regenerated
- [ ] On-device: pair an un-set-up BitBox → lands on the explanatory
screen; set up a wallet + retry → continues to the dashboard
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