Skip to content

feat(trezor): add FirmwareError and DeviceLocked TrezorException variants #124

Description

@piotr-iohk

Problem

Native apps still classify some Trezor failures outside the typed TrezorException hierarchy:

  1. Firmware faults (protocol code 99) — Android matches message substrings such as "Device error (code 99)" and "Firmware error" in TrezorExceptionExt.kt. This is fragile and duplicates logic core already owns in src/modules/trezor/errors.rs, where DeviceError::DeviceError { code, message } is currently mapped to generic TrezorError::DeviceError.

  2. Locked device — Android detects pinProtection == true && unlocked == false from TrezorFeatures in TrezorRepo.requireUnlocked() and synthesizes TrezorException.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_details for diagnostics.
  • Update test_failure_code_unknown_stays_generic_device_error in src/modules/trezor/tests.rs — that test currently asserts code 99 stays generic; it should assert FirmwareError instead.

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() reports pin_protection && !unlocked before an operation proceeds, returning DeviceLocked instead 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 generic DeviceError).
  • Unrelated device error codes still map to DeviceError.
  • Locked-device path returns DeviceLocked and is distinct from DeviceBusy.
  • Existing DeviceBusy transport mapping tests remain unchanged.

Acceptance criteria

  • FFI exposes TrezorException.FirmwareError and TrezorException.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:

  1. Remove isTrezorFirmwareError() message heuristics; match TrezorException.FirmwareError.
  2. Remove requireUnlocked()DeviceBusy synthesis; match TrezorException.DeviceLocked.
  3. Keep UX mapping:
    • DeviceLocked → unlock info toast
    • FirmwareError → reconnect-hardware toast
    • DeviceBusy → busy / backoff guidance

Related

Priority

Medium — not blocking v1 Trezor ship; apps have working heuristics. Reduces long-term fragility as more Trezor flows are added.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions