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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions