Expo development-client app for testing the React Native hardware-wallet APIs in
@bitcoinerlab/descriptors on physical iOS and Android devices.
The app runs the same descriptor, address, policy, PSBT, and message-signing workflows against BitBox and Ledger. It is an integration harness, not a wallet: it does not hold funds or broadcast transactions. Descriptor stores, PSBTs, and logs remain in memory; native modules may retain their own pairing state.
For the selected wallet, transport, and descriptor scenario, the app can:
- Connect and read the device or Bitcoin app version.
- Read the live master fingerprint.
- Derive an xpub-backed descriptor key expression.
- Build standard, multisig, and Miniscript descriptors.
- Register or check hardware-wallet policies.
- Derive an address locally and confirm it on the device.
- Generate and sign a fake PSBT backed by a synthetic, no-funds transaction.
- Exercise a three-input mixed-ownership PSBT, which Ledger signs without touching its foreign input and BitBox currently rejects with a provider error.
- Sign legacy Bitcoin messages for standard
wpkhscenarios. - Close the owned hardware connection after every connected action.
The selectable descriptor scenarios are:
- Ranged
wpkh(KEY/0/*). - Fixed
wpkh(KEY/0/7). - Native
wsh(sortedmulti(...)). - Ordered
wsh(multi(...)). - Relative-timelock
wsh(and_v(v:pk(...),older(5))).
The app uses Bitcoin mainnet derivation paths and networks.bitcoin. The fake
PSBT contains no real UTXO and is not intended for broadcast.
On iOS, the app exposes:
- BitBox Nova over BLE.
- Ledger over BLE.
On Android, the app exposes:
- BitBox Nova over BLE or USB.
- Ledger over BLE or USB/HID.
These are the paths available in the app. Current physical status:
| Path | Status |
|---|---|
| BitBox Nova BLE on iOS | Physical connection and message signing passed; full matrix incomplete |
| BitBox Nova BLE on Android | Native build passed; physical device not online |
| BitBox Nova USB on Android | Native build passed; physical device not online |
| Classic BitBox02 USB on Android | Not tested; hardware unavailable |
| Ledger BLE on iOS | Native build passed; physical device not online |
| Ledger BLE on Android | Native build passed; physical device not online |
| Ledger USB/HID on Android | Native build passed; physical device not online |
Classic BitBox02 USB remains structurally supported through the shared USB
VID/PID and canonical bitbox02-multi / bitbox02-btconly product detection,
but this repository does not claim physical validation without that hardware.
Expo Go cannot load the required native modules. Use an Expo development client.
Descriptors exposes separate BitBox and Ledger entrypoints:
import * as bitbox from "@bitcoinerlab/descriptors/bitbox";
import * as ledger from "@bitcoinerlab/descriptors/ledger";
import * as bitboxDriverModule from "@bitcoinerlab/bitbox-react-native";These entrypoints provide the descriptor operations for each wallet family. The
driver argument tells an entrypoint which runtime modules it should use to open
the hardware. Literal imports let Metro resolve the exact modules used by these
branches.
BitBox receives one module that exposes its React Native BLE and USB connection
functions. mode chooses which function to call:
const session = await bitbox.connect({
driver: {
module: bitboxDriverModule,
mode: "ble",
...(selectedDevice ? { device: selectedDevice } : {})
},
network: networks.bitcoin,
store: bitboxStore
});Ledger receives two modules: a transport module for opening the device and the Ledger Bitcoin API for wallet policies and commands:
const session = await ledger.connect({
driver: {
transport: import("@ledgerhq/react-native-hw-transport-ble"),
bitcoinApi: import("@ledgerhq/ledger-bitcoin"),
app: { name: "Bitcoin", minVersion: "2.1.0" }
},
network: networks.bitcoin,
store: ledgerStore
});For Android Ledger USB/HID, the transport is instead:
import("@ledgerhq/react-native-hid")Ledger omits driver.device and uses its transport's create() behavior.
BitBox passes the selected discovery record when present; automatic mode omits
it and asks the provider for its first matching device.
Every returned session owns its connection. The app always releases it with:
await session.close();Both connection paths read the live master fingerprint before returning. If an existing store belongs to another wallet, connection fails and the new resource is closed.
The BitBox section can scan for Nova BLE devices on either platform and list
attached USB devices on Android. Selection is optional: automatic mode retains
the first-matching-device behavior. Discovery labels use name when available
and otherwise show deviceId; Android USB ids stay only in component state.
After connection, the app logs the native transport, canonical product, and
firmware version. Nova products use the upstream canonical names
bitbox02-plus-btconly and bitbox02-plus-multi.
BitBox and Ledger stores are separate JSON objects because their cached policy
metadata is different. The app copies session.store back into the selected
wallet's JSON field after each connected action.
Stores are held only in React state and are lost when the app restarts. You can copy the JSON elsewhere when testing reconnection or fingerprint binding. Never persist a live session.
- Node.js 20.19.4 or newer.
- npm.
- Xcode and CocoaPods for iOS builds.
- Android Studio and the Android SDK for Android builds.
- A physical BitBox Nova or Ledger device.
Ledger tests require an unlocked device with the mainnet Bitcoin app open at
version 2.1.0 or newer.
Install the published packages from the npm registry:
npm installThis repository uses npm and tracks package-lock.json. The lockfile must
resolve @bitcoinerlab/descriptors, its descriptors-core dependency, and
@bitcoinerlab/bitbox-react-native from https://registry.npmjs.org/.
Keep react-native-ble-plx at 3.4.0. Ledger BLE 6.41.0 depends on that exact
version, and both must resolve to one native installation:
npm ls @ledgerhq/react-native-hw-transport-ble react-native-ble-plx --allnpm run typecheck
npm run test:bundle:ios
npm run test:bundle:androidThe Expo export and Metro bundle checks verify that all literal imports resolve for each platform. They do not test native linking, permissions, Bluetooth, USB, or hardware communication.
After changing native dependencies or Expo config plugins, regenerate the native projects:
npx expo prebuild --cleanBuild and launch on a physical device:
npx expo run:ios --device
npx expo run:android --deviceTracked native configuration lives in app.json. The generated ios/ and
android/ directories are ignored and should not be edited manually.
- Select BitBox or Ledger.
- Select a transport available on the current platform.
- For BitBox, optionally discover and select a device; automatic mode remains available.
- Select a descriptor scenario.
- Leave the provider store as
{}for a new test, or paste a previous store. - Run an individual action or Run Full Workflow.
- Allow any Bluetooth or USB permission prompts from the operating system.
- Confirm address, policy, and signing prompts on the hardware wallet.
- Inspect or share the on-screen log.
The individual actions make it easier to isolate failures in connection, derivation, policy registration, address display, PSBT signing, or message signing. Run Full Workflow exercises the complete scenario through one owned session. Scenario-specific actions are shown only when applicable. The explicit policy action reports whether BitBox already stored the policy; Ledger does not expose equivalent persistent device-storage status.
Sign Auto-Generated Fake PSBT builds and signs its deterministic no-funds PSBT in one connection. The harness does not accept arbitrary pasted PSBTs; the read-only PSBT field shows the latest generated or signed result.
Sign Mixed-Ownership PSBT is available for ranged wpkh. It creates three
unique synthetic inputs: a pre-signed foreign software-wallet input and two
hardware-owned inputs at /0/0 and /0/1. Ledger must preserve the foreign
signature and all metadata, add one signature to each owned input, keep the PSBT
partial, and preserve its input and output counts. BitBox currently rejects the
foreign input; the app surfaces that provider error verbatim rather than
reporting a Bluetooth failure. The action never updates the normal PSBT field,
finalizes, or broadcasts the transaction. It generates this mixed PSBT itself
and does not depend on the normal fake-PSBT action.
For each supported device path, verify:
- Runtime permission handling.
- Connection and cleanup.
- Version and fingerprint reads.
- Key-expression and descriptor construction.
- Address confirmation on the hardware wallet.
- Policy registration for non-standard descriptors.
- Fake PSBT signing.
- Mixed-ownership PSBT signing and foreign-signature preservation where the provider supports it.
- Message signing where the scenario supports it.
- Store reuse and fingerprint-mismatch rejection.
Record a path as validated only after every applicable step succeeds on the
physical device. Complete path validation covers all five scenarios; message
signing applies only to the two wpkh scenarios.
Stale BitBox policy restoration is covered by descriptors unit tests. Do not reset or damage a physical device solely to reproduce stale device state.