Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-07
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Context

`ConnectChannel.tsx` is the screen that bridges the app to hardware signers over the network channel. It handles multiple interaction modes including `ADDRESS_VERIFICATION`. In the current code, `useVault` (a Realm-backed React hook) is called inside an `if (mode === InteracationMode.ADDRESS_VERIFICATION)` block — a direct violation of React's Rules of Hooks. React requires all hooks to be called unconditionally at the top level of a component; calling a hook inside a conditional causes React to lose track of hook call order, crashing the component on render.

## Goals / Non-Goals

**Goals:**
- Fix the crash by calling `useVault` unconditionally at the top level of `ConnectChannel`.
- Derive address-verification-specific values from the vault result conditionally (after the hook call).

**Non-Goals:**
- Not changing address verification behavior or any UX flow.
- Not refactoring other parts of `ConnectChannel.tsx`.

## Decisions

**Decision: Move `useVault` to the top level, gate the derived values with `mode === ADDRESS_VERIFICATION`.**

The `useVault` hook must always be called. When `vaultId` is provided (which it always is for `ADDRESS_VERIFICATION`), it returns the `activeVault`. The conditional logic that derives `descriptorString`, `miniscriptPolicy`, `addressIndex`, `walletName`, `hmac`, and `receivingAddress` from the vault can remain gated by the mode check — only the hook call itself must be moved.

Alternative considered: extract address-verification data into a separate hook or component. Rejected: unnecessary complexity for a one-line structural fix.

## Risks / Trade-offs

- [Risk] `useVault` is called even when `vaultId` is absent (non-address-verification modes). → Mitigation: `useVault` already handles `vaultId = ''` gracefully, returning `null` for `activeVault`.

## Migration Plan

Single file change — no store migrations, no Realm schema changes, no new dependencies. Deploy as a patch release.

## Open Questions

None.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Why

The app crashes instantly when the user taps "Verify Address" on the Receive screen. The root cause is a React Hooks Rules violation in `ConnectChannel.tsx`: the `useVault` hook is called inside a conditional `if (mode === InteracationMode.ADDRESS_VERIFICATION)` block, which is forbidden by React's rules of hooks. This causes a runtime error on every navigation to `ConnectChannel` with `ADDRESS_VERIFICATION` mode.

## What Changes

- Move the `useVault` hook call from inside the conditional `if (mode === InteracationMode.ADDRESS_VERIFICATION)` block to the top level of the `ConnectChannel` component.
- Derive `descriptorString`, `miniscriptPolicy`, `addressIndex`, `walletName`, `hmac`, and `receivingAddress` from the unconditionally fetched vault, gated by the `mode` condition at the data-derivation level (not the hook-call level).

## Capabilities

### New Capabilities
<!-- none -->

### Modified Capabilities
<!-- No spec-level behavior changes; this is a bug fix restoring existing intended behavior. -->

## Impact

- **File**: `src/screens/Channel/ConnectChannel.tsx`
- **Scope**: Affects all environments (mainnet + testnet) where `ADDRESS_VERIFICATION` mode is used.
- **Hardware signers**: Fixes address verification for BITBOX02, LEDGER, TREZOR, COLDCARD, JADE (all signers routed through `ConnectChannel`).
- **Security/Privacy**: No change to key material handling or network calls; fix is purely structural (hook placement).
- **Subscription**: No gating changes.

## Non-goals

- Not changing the address verification logic or UX flow.
- Not fixing other potential issues in `ConnectChannel.tsx`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
## ADDED Requirements

### Requirement: ConnectChannel renders without crashing in ADDRESS_VERIFICATION mode
The `ConnectChannel` screen SHALL render successfully when navigated to with `mode === InteracationMode.ADDRESS_VERIFICATION`, without throwing a React hooks rules violation error.

#### Scenario: Navigating to Verify Address does not crash the app
- **GIVEN** a user is on the Receive screen for a Vault that has at least one address-verifiable signer
- **WHEN** the user taps the "Verify Address" button and selects a signer
- **THEN** the app SHALL navigate to `ConnectChannel` without crashing
- **AND** the QR scanner SHALL be displayed so the user can scan the desktop channel QR code
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## Analysis

The app crashes in `ConnectChannel.tsx` because `useVault` is called inside a conditional
`if (mode === InteracationMode.ADDRESS_VERIFICATION)` block (lines 141–161). React's Rules
of Hooks require all hooks to be called at the top level of a component, never inside
conditionals, loops, or nested functions. Violating this rule corrupts React's internal
hook state and causes an immediate runtime crash.

**Root cause**: `useVault({ vaultId })` at line 142 is inside an `if` block.

**Fix**: Move `useVault({ vaultId })` to the top level of `ConnectChannel`, then gate
the address-verification derived values (`descriptorString`, `miniscriptPolicy`,
`addressIndex`, `walletName`, `hmac`, `receivingAddress`) with the mode check at the
data-derivation level — not the hook-call level.

## 1. Fix ConnectChannel hook violation

- [x] 1.1 Move `useVault({ vaultId })` call to the top level of `ConnectChannel` (outside the `if (mode === InteracationMode.ADDRESS_VERIFICATION)` block)
- [x] 1.2 Retain the `if (mode === InteracationMode.ADDRESS_VERIFICATION)` conditional block for deriving `descriptorString`, `miniscriptPolicy`, `addressIndex`, `walletName`, `hmac`, and `receivingAddress` from the now-unconditionally fetched vault
14 changes: 14 additions & 0 deletions openspec/specs/verify-address-channel/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# verify-address-channel Specification

## Purpose
TBD - created by archiving change fix-verify-address-crash. Update Purpose after archive.
## Requirements
### Requirement: ConnectChannel renders without crashing in ADDRESS_VERIFICATION mode
The `ConnectChannel` screen SHALL render successfully when navigated to with `mode === InteracationMode.ADDRESS_VERIFICATION`, without throwing a React hooks rules violation error.

#### Scenario: Navigating to Verify Address does not crash the app
- **GIVEN** a user is on the Receive screen for a Vault that has at least one address-verifiable signer
- **WHEN** the user taps the "Verify Address" button and selects a signer
- **THEN** the app SHALL navigate to `ConnectChannel` without crashing
- **AND** the QR scanner SHALL be displayed so the user can scan the desktop channel QR code

3 changes: 2 additions & 1 deletion src/screens/Channel/ConnectChannel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ function ConnectChannel() {
const [newNote, setNewNote] = useState<string | null>(null);
const [infoModal, setInfoModal] = useState(false);

const { activeVault: vault } = useVault({ vaultId });

let descriptorString = null;
let miniscriptPolicy = null;
let addressIndex = null;
Expand All @@ -139,7 +141,6 @@ function ConnectChannel() {
let room;

if (mode === InteracationMode.ADDRESS_VERIFICATION) {
const { activeVault: vault } = useVault({ vaultId });
if (vault.type === VaultType.MINISCRIPT) {
miniscriptPolicy = generateOutputDescriptors(vault);
addressIndex = receiveAddressIndex;
Expand Down