Repository navigation
feat(trezor): add FirmwareError and DeviceLocked TrezorException variants #124
Description
Problem
Native apps still classify some Trezor failures outside the typed TrezorException hierarchy:
-
Firmware faults (protocol code 99) — Android matches message substrings such as
"Device error (code 99)"and"Firmware error"inTrezorExceptionExt.kt. This is fragile and duplicates logic core already owns insrc/modules/trezor/errors.rs, whereDeviceError::DeviceError { code, message }is currently mapped to genericTrezorError::DeviceError. -
Locked device — Android detects
pinProtection == true && unlocked == falsefromTrezorFeaturesinTrezorRepo.requireUnlocked()and synthesizesTrezorException.DeviceBusy(). That conflates two distinct UX states:- Device locked → unlock guidance
- Device busy → wait / reconnect guidance
iOS has similar string-based handling in TrezorManager.swift.
Consumer-side heuristics are easy to break when error copy changes and force both apps to maintain parallel classification logic.
Proposed change
Extend the exported TrezorError / TrezorException enum in src/modules/trezor/errors.rs:
FirmwareError
Map Trezor protocol failure code 99 (Failure_FirmwareError) to a dedicated variant instead of generic DeviceError.
- Preserve
error_detailsfor diagnostics. - Update
test_failure_code_unknown_stays_generic_device_errorinsrc/modules/trezor/tests.rs— that test currently asserts code 99 stays generic; it should assertFirmwareErrorinstead.
DeviceLocked
Add a distinct variant for “device has PIN protection and is currently locked”.
Options (pick one in implementation):
- Map from an existing trezor-connect-rs signal if available at connect/features refresh time.
- Or expose locked state explicitly when
get_features()reportspin_protection && !unlockedbefore an operation proceeds, returningDeviceLockedinstead of requiring apps to reimplement the rule.
Keep DeviceBusy for transport/session contention only. Do not overload it for locked state.
Regenerate UniFFI bindings after the enum change.
Tests
- Code 99 device failure maps to
TrezorError::FirmwareError(not genericDeviceError). - Unrelated device error codes still map to
DeviceError. - Locked-device path returns
DeviceLockedand is distinct fromDeviceBusy. - Existing
DeviceBusytransport mapping tests remain unchanged.
Acceptance criteria
- FFI exposes
TrezorException.FirmwareErrorandTrezorException.DeviceLocked. - No message-string sniffing required in native apps for these two cases.
- Rust tests, Clippy, and formatting pass.
- Bindings published in a core release.
Consumer follow-up
After release, update bitkit-android and bitkit-ios:
- Remove
isTrezorFirmwareError()message heuristics; matchTrezorException.FirmwareError. - Remove
requireUnlocked()→DeviceBusysynthesis; matchTrezorException.DeviceLocked. - Keep UX mapping:
DeviceLocked→ unlock info toastFirmwareError→ reconnect-hardware toastDeviceBusy→ busy / backoff guidance
Related
- fix: polish trezor reliability and ux bitkit-android#1067 — Corey review follow-up
- fix: polish trezor reliability and ux bitkit-ios#621
- Prior art: fix(onchain): return canonical txid for already-known raw broadcasts #123 moved broadcast txid recovery into core instead of app string matching
Priority
Medium — not blocking v1 Trezor ship; apps have working heuristics. Reduces long-term fragility as more Trezor flows are added.
Activity