diff --git a/packages/caravan-wallets/src/bitbox.ts b/packages/caravan-wallets/src/bitbox.ts
index 88cde75e..946953bc 100644
--- a/packages/caravan-wallets/src/bitbox.ts
+++ b/packages/caravan-wallets/src/bitbox.ts
@@ -36,6 +36,9 @@ import {
*/
export const BITBOX = "bitbox";
+// Callback for showing the BitBox pairing code. It returns a function to hide it again.
+export type TShowPairingCode = (pairingCode: string) => (() => void) | null;
+
function convertNetwork(network: BitcoinNetwork): BtcCoin {
return network === 'mainnet' ? 'btc' : 'tbtc';
}
@@ -103,12 +106,21 @@ async function convertMultisig(pairedBitBox: PairedBitBox, walletConfig: Multisi
* const result = await interaction.run();
* console.log(result); // whatever value `app.doSomething(...)` returns
*
+ * The `showPairingCode` callback can be supplied to display and hide the BitBox pairing code.
+ * If not provided, the default is to open a browser popup showing the pairing code.
*/
export class BitBoxInteraction extends DirectKeystoreInteraction {
appVersion?: string;
appName?: string;
+ showPairingCode?: TShowPairingCode;
+
+ constructor({ showPairingCode }: { showPairingCode?: TShowPairingCode }) {
+ super();
+ this.showPairingCode = showPairingCode;
+ }
+
/**
* Adds `pending` messages at the `info` level about ensuring the
* device is plugged in (`device.connect`) and unlocked
@@ -132,19 +144,60 @@ export class BitBoxInteraction extends DirectKeystoreInteraction {
return messages;
}
+ showPairingCodePopup(pairingCode: string): (() => void) | null {
+ if (this.showPairingCode) {
+ return this.showPairingCode(pairingCode);
+ }
+ const htmlContent = `
+
+
+
+
+
+ BitBox02 pairing
+
+
+ BitBox02 pairing code
+ ${pairingCode}
+
+
+`;
+ const popup = window.open(
+ '',
+ 'popupWindow',
+ 'width=400,height=300',
+ );
+ if (popup) {
+ popup.document.write(htmlContent);
+ popup.document.close();
+ return () => {
+ popup.close();
+ };
+ }
+ return null;
+ }
+
async withDevice(f: (device: PairedBitBox) => Promise): Promise {
const bitbox = await import('bitbox-api');
+ let hidePairingCode: (() => void) | null = null;
try {
const unpaired = await bitbox.bitbox02ConnectAuto(() => {
+ if (hidePairingCode) {
+ hidePairingCode();
+ }
})
const pairing = await unpaired.unlockAndPair()
const pairingCode = pairing.getPairingCode()
if (pairingCode) {
+ hidePairingCode = this.showPairingCodePopup(pairingCode);
// TODO: display pairing code to the user.
console.log('Pairing code:', pairingCode);
}
const pairedBitBox = await pairing.waitConfirm()
+ if (hidePairingCode) {
+ hidePairingCode();
+ }
try {
return await f(pairedBitBox)
} finally {
@@ -156,7 +209,9 @@ export class BitBoxInteraction extends DirectKeystoreInteraction {
const errorMessage = isErrorUnknown ? typedErr.err! : typedErr.message
throw new Error(errorMessage);
} finally {
- // TODO hide pairing code if still shown.
+ if (hidePairingCode) {
+ hidePairingCode();
+ }
}
}
@@ -209,6 +264,10 @@ export class BitBoxInteraction extends DirectKeystoreInteraction {
*
*/
export class BitBoxGetMetadata extends BitBoxInteraction {
+ constructor({ showPairingCode }: { showPairingCode?: TShowPairingCode }) {
+ super({ showPairingCode });
+ }
+
async run() {
return this.withDevice(async (pairedBitBox) => {
const product = pairedBitBox.product();
@@ -244,8 +303,13 @@ export class BitBoxExportPublicKey extends BitBoxInteraction {
* @param {string} bip32Path path
* @param {boolean} includeXFP - return xpub with root fingerprint concatenated
*/
- constructor({ network, bip32Path, includeXFP }) {
- super();
+ constructor({ showPairingCode, network, bip32Path, includeXFP }: {
+ showPairingCode?: TShowPairingCode;
+ network: BitcoinNetwork;
+ bip32Path: string;
+ includeXFP: boolean;
+ }) {
+ super({ showPairingCode });
this.network = network;
this.bip32Path = bip32Path;
this.includeXFP = includeXFP;
@@ -290,8 +354,13 @@ export class BitBoxExportExtendedPublicKey extends BitBoxInteraction {
* @param {string} network bitcoin network
* @param {boolean} includeXFP - return xpub with root fingerprint concatenated
*/
- constructor({ bip32Path, network, includeXFP }) {
- super();
+ constructor({ showPairingCode, bip32Path, network, includeXFP }: {
+ showPairingCode?: TShowPairingCode;
+ network: BitcoinNetwork;
+ bip32Path: string;
+ includeXFP: boolean;
+ }) {
+ super({ showPairingCode });
this.bip32Path = bip32Path;
this.network = network;
this.includeXFP = includeXFP;
@@ -329,9 +398,13 @@ export class BitBoxRegisterWalletPolicy extends BitBoxInteraction {
walletConfig: MultisigWalletConfig;
constructor({
+ showPairingCode,
walletConfig
- }: { walletConfig: MultisigWalletConfig}) {
- super();
+ }: {
+ showPairingCode?: TShowPairingCode;
+ walletConfig: MultisigWalletConfig;
+ }) {
+ super({ showPairingCode });
this.walletConfig = walletConfig;
}
@@ -355,8 +428,13 @@ export class BitBoxConfirmMultisigAddress extends BitBoxInteraction {
walletConfig: MultisigWalletConfig;
- constructor({ network, bip32Path, walletConfig }) {
- super();
+ constructor({ showPairingCode, network, bip32Path, walletConfig }: {
+ showPairingCode?: TShowPairingCode;
+ network: BitcoinNetwork;
+ bip32Path: string;
+ walletConfig: MultisigWalletConfig;
+ }) {
+ super({ showPairingCode });
this.network = network;
this.bip32Path = bip32Path;
this.walletConfig = walletConfig;
@@ -410,11 +488,17 @@ export class BitBoxSignMultisigTransaction extends BitBoxInteraction {
private unsignedPsbt: string;
constructor({
+ showPairingCode,
walletConfig,
psbt,
returnSignatureArray = false,
+ }: {
+ showPairingCode?: TShowPairingCode;
+ walletConfig: MultisigWalletConfig;
+ psbt: any;
+ returnSignatureArray: boolean;
}) {
- super();
+ super({ showPairingCode });
this.walletConfig = walletConfig;
this.returnSignatureArray = returnSignatureArray;
diff --git a/packages/caravan-wallets/src/index.ts b/packages/caravan-wallets/src/index.ts
index 6b11ac7d..7fef3746 100644
--- a/packages/caravan-wallets/src/index.ts
+++ b/packages/caravan-wallets/src/index.ts
@@ -115,7 +115,7 @@ export type KEYSTORE_TYPES = (typeof KEYSTORES)[KEYSTORE_KEYS];
export function GetMetadata({ keystore }: { keystore: KEYSTORE_TYPES }) {
switch (keystore) {
case BITBOX:
- return new BitBoxGetMetadata();
+ return new BitBoxGetMetadata({});
case LEDGER:
return new LedgerGetMetadata();
case TREZOR: