Use this package as the native BitBox driver for @bitcoinerlab/descriptors to
build descriptors, show addresses, register policies and sign PSBTs.
This package provides the mobile transport and provider client. Descriptors opens the connection through the driver and stays in charge of wallet policy, Miniscript, PSBT creation and finalization.
This example connects to a BitBox over BLE (Bluetooth), builds a single-key relative timelock descriptor, registers it on the device, signs a PSBT and finalizes the input.
// This example uses the bitcoinjs-lib preset. If you prefer @scure/btc-signer
// and noble/scure types, use @bitcoinerlab/descriptors-scure instead.
import { networks, Output, Psbt } from '@bitcoinerlab/descriptors';
import * as bitbox from '@bitcoinerlab/descriptors/bitbox';
const network = networks.bitcoin;
// store: caches xpubs, fingerprint data and registered policies as JSON.
const store = {}; // Or load a previously saved store.
const session = await bitbox.connect({
driver: {
module: import('@bitcoinerlab/bitbox-react-native'),
mode: 'ble',
timeoutMs: 60_000
},
network,
store
});
try {
const bitboxKey = await bitbox.keyExpression({
session,
originPath: "/48'/0'/0'/2'",
keyPath: '/0/*'
});
// Policy-language equivalent with @bitcoinerlab/miniscript-policies:
// const { miniscript } = compilePolicy('and(pk(@bitbox),older(5))');
// const descriptor = `wsh(${miniscript.replace('@bitbox', bitboxKey)})`;
const descriptor = `wsh(and_v(v:pk(${bitboxKey}),older(5)))`;
await bitbox.registerPolicy({
session,
descriptor,
name: '5-block vault'
});
// session.store keeps track of registered policies and is JSON-serializable.
saveWalletStoreJSON(session.store);
const receiveAddress = await bitbox.displayAddress({
session,
descriptor,
index: 0
});
const vaultOutput = new Output({ descriptor, index: 0, network });
const psbt = new Psbt({ network });
// Adds the vault output as a PSBT input. Save the returned finalizer: after
// the BitBox signs, it adds the witness data needed to finish this input.
const finalizeInput = vaultOutput.updatePsbtAsInput({
psbt,
txHex: await fetchPreviousTransactionHex(),
vout: 0
});
// Adds the recipient output: where this transaction sends the bitcoin.
new Output({
descriptor: `addr(${await chooseRecipientAddress()})`,
network
}).updatePsbtAsOutput({ psbt, value: 90_000n });
// The BitBox signs after the user confirms on the device.
await bitbox.signers.sign({ psbt, session });
// Call one finalizer per descriptor input AFTER signing.
finalizeInput({ psbt });
const transactionHex = psbt.extractTransaction().toHex();
await broadcastTransaction(transactionHex);
console.log({ receiveAddress, transactionHex });
} finally {
await session.close();
}For USB on Android, use the same descriptors code and change only the driver mode:
const session = await bitbox.connect({
driver: {
module: import('@bitcoinerlab/bitbox-react-native'),
mode: 'usb',
timeoutMs: 60_000
},
network,
store
});Without a selected device, the driver connects to the first match. To let the
user choose when several devices are available, list them first and pass the
selected record as driver.device:
const driver = await import('@bitcoinerlab/bitbox-react-native');
const devices = await driver.discoverBitBoxNovaBleDevices({
scanDurationMs: 5_000
});
const device = await chooseDevice(devices);
const session = await bitbox.connect({
driver: { module: driver, mode: 'ble', device },
network,
store
});For USB on Android:
const driver = await import('@bitcoinerlab/bitbox-react-native');
const devices = await driver.listAttachedBitBoxUsbDevices();
const device = await chooseDevice(devices);
const session = await bitbox.connect({
driver: { module: driver, mode: 'usb', device },
network,
store
});BLE discovery scans only for BitBox Nova devices. USB listing does not request
permission or open a device; permission is requested when connecting. An Android
USB deviceId identifies the current attachment and may change after reconnecting,
so discover attached USB devices again instead of persisting that ID.
If the app asks you to confirm a pairing code, continue only when it matches the
BitBox display. BLE pairing/bonding is handled by the operating system. USB
Noise pairing approvals are stored in app-private storage for later reconnects.
The React Native driver does not use descriptors' onPairingCode callback; that
callback belongs to the browser and BitBoxBridge bitbox-api flow.
Write wallet templates as descriptor expressions and let
@bitcoinerlab/descriptors/bitbox choose the right BitBox flow.
- Standard single-key scripts use descriptors such as
wpkh(${keyExpression})ortr(${keyExpression}). They use the BitBox standard address and signing flow and do not need policy registration. - Sorted multisig uses
wsh(sortedmulti(2,${keyExpressionA},${keyExpressionB},${keyExpressionC})). Register it withregisterPolicy(...); the BitBox helpers detect it and use the device's specialized multisig registration flow internally. - Custom Miniscript policies use descriptors such as
wsh(and_v(v:pk(${keyExpression}),older(5))). Register them withregisterPolicy(...)before address display or signing.
App code can stay focused on descriptors. It does not need to know which BitBox API call is used for each script type.
For address display and message signing, pass only the position fields needed by the descriptor:
- Fixed descriptor, such as
wpkh(KEY/0/7): pass neitherchangenorindex. - Ranged fixed-branch descriptor, such as
wpkh(KEY/0/*): passindexonly. - Multipath ranged descriptor, such as
wpkh(KEY/**)orwpkh(KEY/<0;1>/*): pass bothchangeandindex.
The descriptors helpers derive the concrete BitBox path from the expanded descriptor and reject extra or missing position fields, so the device path cannot diverge from the descriptor supplied by the app.
This package contains native code. It works in Expo development builds, EAS builds, Expo prebuild apps and bare React Native apps with Expo Modules installed. It does not work in Expo Go.
npx expo install @bitcoinerlab/bitbox-react-native
npx expo install expo-dev-client
npm install @bitcoinerlab/descriptors bufferAdd the config plugin to your Expo app config, usually app.json or
app.config.js:
{
"expo": {
"plugins": ["expo-dev-client", "@bitcoinerlab/bitbox-react-native"]
}
}Rebuild the native app after installing the package or changing plugins:
npx expo run:ios --device
npx expo run:android --deviceThe plugin adds the iOS Bluetooth usage string plus the Android Bluetooth and USB manifest entries used by the native transports.
@bitcoinerlab/bitbox-react-native does not need a Buffer polyfill by itself.
The bitcoinjs-based descriptors preset may need one in React Native/Hermes. Load
it before importing descriptors code:
import { Buffer as BufferPolyfill } from 'buffer';
(
globalThis as typeof globalThis & { Buffer?: typeof BufferPolyfill }
).Buffer ??= BufferPolyfill;import {
connectBitBoxNovaBle,
connectBitBoxUsb,
discoverBitBoxNovaBleDevices,
listAttachedBitBoxUsbDevices
} from '@bitcoinerlab/bitbox-react-native';discoverBitBoxNovaBleDevices(params?): scan for nearby BitBox Nova devices.listAttachedBitBoxUsbDevices(): list attached BitBox USB devices on Android.connectBitBoxNovaBle(params?): connect to BitBox Nova over BLE.connectBitBoxUsb(params?): connect to a BitBox over USB. Android is supported. iOS USB is not supported.
Descriptors calls the connection helpers when this package is supplied as
driver.module to bitbox.connect(...). Apps can also call them directly to use
the raw connected Bitcoin-only provider client:
version()rootFingerprint()btcXpub(apiNetwork, keypath, xpubType, display)btcAddress(...)btcRegisterScriptConfig(...)btcIsScriptConfigRegistered(...)btcSignPSBT(...)btcSignMessage(...)close()
With the recommended bitbox.connect(...) flow, the session owns the client and
session.close() closes it. If the app calls a connection helper directly, the
app owns the returned client and must call client.close() itself.
Do not persist a live session or client. Keep the descriptors store in
memory for the current session or save it if the app wants to reuse it in future
sessions:
const store = JSON.parse((await storage.getItem('bitbox-store')) ?? '{}');
const session = await bitbox.connect({
driver: {
module: import('@bitcoinerlab/bitbox-react-native'),
mode: 'ble'
},
network,
store
});
// If the app wants to reuse the cache in future sessions:
await storage.setItem('bitbox-store', JSON.stringify(session.store));
// When finished:
await session.close();The store caches xpubs, master fingerprint data and the descriptor policies the user has registered on the BitBox for each session. Whether that cache is temporary or permanent is an app decision.
The native module uses Expo Modules API. A bare React Native app can use it, but it must install Expo Modules native support first:
npx install-expo-modules@latest
npm install @bitcoinerlab/bitbox-react-native
npx pod-installIf your app does not use Expo prebuild or config plugins, add the native entries manually:
- iOS:
NSBluetoothAlwaysUsageDescriptioninInfo.plist. - Android: Bluetooth permissions, USB host feature, USB attached intent filter
and
@xml/bitbox_device_filtermetadata.
The iOS pod currently requires deployment target 15.1 or newer.
The supported BLE and USB flows have been validated on physical BitBox hardware.
- iOS BitBox Nova BLE: validated on physical iPhone plus BitBox Nova hardware. Tested flows include connection, xpub/address reads, address display, multisig and policy registration, PSBT signing and message signing.
- Android USB: validated on physical Android hardware through the integration app. Tested flows include connection, pairing, provider-client calls, descriptor-backed wallet flows, PSBT signing and message signing.
- Android BitBox Nova BLE: validated on physical Android hardware through the integration app. Tested flows include connection, pairing, provider-client calls, descriptor-backed wallet flows, PSBT signing and message signing.
- Expo Go: not supported because custom native code is required.
The package includes an Expo Modules API native module for iOS and Android.
- iOS uses CoreBluetooth for BitBox Nova BLE and the vendored gomobile framework
at
ios/Frameworks/Bitboxnative.xcframework. - Android uses BLE, USB Host, a native USB pairing-code dialog, app-private USB
Noise pairing storage and the vendored gomobile AAR at
android/libs/bitboxnative-android.aar. - The protocol layer is a small Go wrapper around
bitbox02-api-gobuilt withgomobile bind.
npm install
npm test
npm run native:go:test
npm run format:checkGo and gomobile are needed only by contributors who regenerate mobile bindings.
Normal app developers should get prebuilt artifacts from the npm package.
To rebuild bindings locally:
npm run native:go:build -- ios,iossimulator
npm run native:go:build -- androidThe API throws a clear error if the BitcoinerlabBitBox native module is missing
or if an unsupported platform method is called.