diff --git a/openspec/changes/archive/2026-05-07-fix-verify-address-crash/.openspec.yaml b/openspec/changes/archive/2026-05-07-fix-verify-address-crash/.openspec.yaml new file mode 100644 index 000000000..8d87be18e --- /dev/null +++ b/openspec/changes/archive/2026-05-07-fix-verify-address-crash/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-07 diff --git a/openspec/changes/archive/2026-05-07-fix-verify-address-crash/design.md b/openspec/changes/archive/2026-05-07-fix-verify-address-crash/design.md new file mode 100644 index 000000000..a4a541eb2 --- /dev/null +++ b/openspec/changes/archive/2026-05-07-fix-verify-address-crash/design.md @@ -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. diff --git a/openspec/changes/archive/2026-05-07-fix-verify-address-crash/proposal.md b/openspec/changes/archive/2026-05-07-fix-verify-address-crash/proposal.md new file mode 100644 index 000000000..08998c633 --- /dev/null +++ b/openspec/changes/archive/2026-05-07-fix-verify-address-crash/proposal.md @@ -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 + + +### Modified Capabilities + + +## 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`. diff --git a/openspec/changes/archive/2026-05-07-fix-verify-address-crash/specs/verify-address-channel/spec.md b/openspec/changes/archive/2026-05-07-fix-verify-address-crash/specs/verify-address-channel/spec.md new file mode 100644 index 000000000..8e0c9b399 --- /dev/null +++ b/openspec/changes/archive/2026-05-07-fix-verify-address-crash/specs/verify-address-channel/spec.md @@ -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 diff --git a/openspec/changes/archive/2026-05-07-fix-verify-address-crash/tasks.md b/openspec/changes/archive/2026-05-07-fix-verify-address-crash/tasks.md new file mode 100644 index 000000000..1292bcc3c --- /dev/null +++ b/openspec/changes/archive/2026-05-07-fix-verify-address-crash/tasks.md @@ -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 diff --git a/openspec/specs/verify-address-channel/spec.md b/openspec/specs/verify-address-channel/spec.md new file mode 100644 index 000000000..144d17677 --- /dev/null +++ b/openspec/specs/verify-address-channel/spec.md @@ -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 + diff --git a/src/screens/Channel/ConnectChannel.tsx b/src/screens/Channel/ConnectChannel.tsx index c4b3dec28..2db572052 100644 --- a/src/screens/Channel/ConnectChannel.tsx +++ b/src/screens/Channel/ConnectChannel.tsx @@ -130,6 +130,8 @@ function ConnectChannel() { const [newNote, setNewNote] = useState(null); const [infoModal, setInfoModal] = useState(false); + const { activeVault: vault } = useVault({ vaultId }); + let descriptorString = null; let miniscriptPolicy = null; let addressIndex = null; @@ -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;