import { version } from "../package.json";
import { UnsupportedInteraction } from "./interaction";
import {
COLDCARD,
ColdcardExportPublicKey,
ColdcardExportExtendedPublicKey,
ColdcardSignMultisigTransaction,
ColdcardMultisigWalletConfig,
} from "./coldcard";
import {
CUSTOM,
CustomExportExtendedPublicKey,
CustomSignMultisigTransaction,
} from "./custom";
import {
HERMIT,
HermitExportExtendedPublicKey,
HermitSignMultisigTransaction,
} from "./hermit";
import {
LEDGER,
LedgerGetMetadata,
LedgerExportPublicKey,
LedgerExportExtendedPublicKey,
LedgerSignMultisigTransaction,
} from "./ledger";
import {
TREZOR,
TrezorGetMetadata,
TrezorExportPublicKey,
TrezorExportExtendedPublicKey,
TrezorSignMultisigTransaction,
TrezorConfirmMultisigAddress,
} from "./trezor";
/**
* Current unchained-wallets version.
*
* @type {string}
*/
export const VERSION = version;
export const MULTISIG_ROOT = "m/45'";
/**
* Enumeration of keystores which support direct interactions.
*
* @constant
* @enum {string}
* @default
*/
export const DIRECT_KEYSTORES = {
TREZOR,
LEDGER,
};
/**
* Enumeration of keystores which support indirect interactions.
*
* @constant
* @enum {string}
* @default
*/
export const INDIRECT_KEYSTORES = {
HERMIT,
COLDCARD,
CUSTOM,
};
/**
* Enumeration of supported keystores.
*
* @type {string[]}
*/
export const KEYSTORES = {
...DIRECT_KEYSTORES,
...INDIRECT_KEYSTORES,
};
/**
* Return an interaction class for obtaining metadata from the given
* `keystore`.
*
* **Supported keystores:** Trezor, Ledger
*
* @param {Object} options - options argument
* @param {KEYSTORES} options.keystore - keystore to use
* @return {module:interaction.KeystoreInteraction} keystore-specific interaction instance
* @example
* import {GetMetadata, TREZOR} from "unchained-wallets";
* // Works similarly for Ledger.
* const interaction = GetMetadata({keystore: TREZOR});
* const metadata = await interaction.run();
*/
export function GetMetadata({ keystore }) {
switch (keystore) {
case LEDGER:
return new LedgerGetMetadata();
case TREZOR:
return new TrezorGetMetadata();
default:
return new UnsupportedInteraction({
code: "unsupported",
text: "This keystore does not return a version.",
});
}
}
/**
* Return an interaction class for exporting a public key from the
* given `keystore` for the given `bip32Path` and `network`.
*
* **Supported keystores:** Trezor, Ledger, Hermit
*
* @param {Object} options - options argument
* @param {KEYSTORES} options.keystore - keystore to use
* @param {string} options.network - bitcoin network
* @param {string} options.bip32Path - the BIP32 path of the HD node of the public key
* @param {string} options.includeXFP - also return root fingerprint
* @return {module:interaction.KeystoreInteraction} keystore-specific interaction instance
* @example
* import {MAINNET} from "unchained-bitcoin";
* import {ExportPublicKey, TREZOR, HERMIT} from "unchained-wallets";
* // Works similarly for Ledger
* const interaction = ExportPublicKey({keystore: TREZOR, network: MAINNET, bip32Path: "m/45'/0'/0'/0/0"});
* const publicKey = await interaction.run();
*/
export function ExportPublicKey({ keystore, network, bip32Path, includeXFP }) {
switch (keystore) {
case COLDCARD:
return new ColdcardExportPublicKey({
network,
bip32Path,
includeXFP,
});
case LEDGER:
return new LedgerExportPublicKey({
bip32Path,
includeXFP,
});
case TREZOR:
return new TrezorExportPublicKey({
network,
bip32Path,
includeXFP,
});
default:
return new UnsupportedInteraction({
code: "unsupported",
text: "This keystore is not supported when exporting public keys.",
});
}
}
/**
* Return an interaction class for exporting an extended public key
* from the given `keystore` for the given `bip32Path` and `network`.
*
* **Supported keystores:** Trezor, Hermit, Ledger
*
*
* @param {Object} options - options argument
* @param {KEYSTORES} options.keystore - keystore to use
* @param {string} options.network - bitcoin network
* @param {string} options.bip32Path - the BIP32 path of the HD node of the extended public key
* @param {string} options.includeXFP - also return root fingerprint
* @return {module:interaction.KeystoreInteraction} keystore-specific interaction instance
* @example
* import {MAINNET} from "unchained-bitcoin";
* import {ExportExtendedPublicKey, TREZOR, HERMIT} from "unchained-wallets";
* // Works similarly for Ledger
* const interaction = ExportExtendedPublicKey({keystore: TREZOR, network: MAINNET, bip32Path: "m/45'/0'/0'/0/0"});
* const xpub = await interaction.run();
*/
export function ExportExtendedPublicKey({
keystore,
network,
bip32Path,
includeXFP,
}) {
switch (keystore) {
case COLDCARD:
return new ColdcardExportExtendedPublicKey({
bip32Path,
network,
includeXFP,
});
case CUSTOM:
return new CustomExportExtendedPublicKey({
bip32Path,
network,
includeXFP,
});
case HERMIT:
return new HermitExportExtendedPublicKey({
bip32Path,
});
case LEDGER:
return new LedgerExportExtendedPublicKey({
bip32Path,
network,
includeXFP,
});
case TREZOR:
return new TrezorExportExtendedPublicKey({
bip32Path,
network,
includeXFP,
});
default:
return new UnsupportedInteraction({
code: "unsupported",
text:
"This keystore is not supported when exporting extended public keys.",
});
}
}
/**
* Return an interaction class for signing a multisig transaction with
* the given `keystore`.
*
* The inputs are objects which have `txid`, `index`, and a `multisig`
* object, the last which is a `Multisig` object from
* `unchained-bitcoin`.
*
* The outputs are objects which have `address` and `amountSats` (an
* integer).
*
* `bip32Paths` is an array of BIP32 paths for the public keys on this
* device, one for each input.
*
* **Supported keystores:** Trezor, Ledger, Hermit
*
*
* @param {Object} options - options argument
* @param {KEYSTORES} options.keystore - keystore to use
* @param {string} options.network - bitcoin network
* @param {object[]} options.inputs - transaction inputs
* @param {object[]} options.outputs - transaction outputs
* @param {string[]} options.bip32Paths - the BIP32 paths on this device corresponding to a public key in each input
* @param {string} [options.psbt] - the unsigned_psbt
* @param {object} [options.keyDetails] - Signing Key Fingerprint + Bip32 Root
* @param {boolean} [options.returnSignatureArray] - return an array of signatures instead of a signed PSBT (useful for test suite)
* @return {module:interaction.KeystoreInteraction} keystore-specific interaction instance
* @example
* import {
* generateMultisigFromHex, TESTNET, P2SH,
* } from "unchained-bitcoin";
* import {SignMultisigTransaction, TREZOR} from "unchained-wallets";
* const redeemScript = "5...ae";
* const inputs = [
* {
* txid: "8d276c76b3550b145e44d35c5833bae175e0351b4a5c57dc1740387e78f57b11",
* index: 1,
* multisig: generateMultisigFromHex(TESTNET, P2SH, redeemScript),
* amountSats: '1234000'
* },
* // other inputs...
* ];
* const outputs = [
* {
* amountSats: '1299659',
* address: "2NGHod7V2TAAXC1iUdNmc6R8UUd4TVTuBmp"
* },
* // other outputs...
* ];
* const interaction = SignMultisigTransaction({
* keystore: TREZOR, // works the same for Ledger
* network: TESTNET,
* inputs,
* outputs,
* bip32Paths: ["m/45'/0'/0'/0", // add more, 1 per input],
* });
* const signature = await interaction.run();
* console.log(signatures);
* // ["ababab...", // 1 per input]
*
*/
export function SignMultisigTransaction({
keystore,
network,
inputs,
outputs,
bip32Paths,
psbt,
keyDetails,
returnSignatureArray= false,
}) {
switch (keystore) {
case COLDCARD:
return new ColdcardSignMultisigTransaction({
network,
inputs,
outputs,
bip32Paths,
psbt,
});
case CUSTOM:
return new CustomSignMultisigTransaction({
network,
inputs,
outputs,
bip32Paths,
psbt,
});
case HERMIT:
return new HermitSignMultisigTransaction({
psbt,
returnSignatureArray,
});
case LEDGER:
return new LedgerSignMultisigTransaction({
network,
inputs,
outputs,
bip32Paths,
psbt,
keyDetails,
returnSignatureArray,
});
case TREZOR:
return new TrezorSignMultisigTransaction({
network,
inputs,
outputs,
bip32Paths,
psbt,
keyDetails,
returnSignatureArray,
});
default:
return new UnsupportedInteraction({
code: "unsupported",
text:
"This keystore is not supported when signing multisig transactions.",
});
}
}
/**
* Return an interaction class for confirming a multisig address with
* the given `keystore`.
*
* The `multisig` parameter is a `Multisig` object from
* `unchained-bitcoin`.
*
* `bip32Path` is the BIP32 path for the publiic key in the address on
* this device.
*
* `publicKey` optional, is the public key expected to be at `bip32Path`.
*
* **Supported keystores:** Trezor
*
* @param {Object} options - options argument
* @param {KEYSTORES} options.keystore - keystore to use
* @param {string} options.network - bitcoin network
* @param {object} options.multisig - `Multisig` object representing the address
* @param {string} options.bip32Path - the BIP32 path on this device containing a public key from the address
* @param {string} options.publicKey - optional, the public key expected to be at the given BIP32 path
* @return {module:interaction.KeystoreInteraction} keystore-specific interaction instance
* @example
* import {
* generateMultisigFromHex, TESTNET, P2SH,
* } from "unchained-bitcoin";
* import {
* ConfirmMultisigAddress,
* multisigPublicKeys,
* trezorPublicKey,
* TREZOR} from "unchained-wallets";
* const redeemScript = "5...ae";
* const multisig = generateMultisigFromHex(TESTNET, P2SH, redeemScript);
* const interaction = ConfirmMultisigAddress({
* keystore: TREZOR,
* network: TESTNET,
* multisig,
* bip32Path: "m/45'/1'/0'/0/0",
* });
* await interaction.run();
*
* With publicKey:
* const redeemScript = "5...ae";
* const multisig = generateMultisigFromHex(TESTNET, P2SH, redeemScript);
* const publicKey = trezorPublicKey(multisigPublicKeys(this.multisig)[2])
* const interaction = ConfirmMultisigAddress({
* keystore: TREZOR,
* publicKey,
* network: TESTNET,
* multisig,
* bip32Path: "m/45'/1'/0'/0/0",
* });
* await interaction.run();
*
*
*/
export function ConfirmMultisigAddress({
keystore,
network,
bip32Path,
multisig,
publicKey,
}) {
switch (keystore) {
case TREZOR:
return new TrezorConfirmMultisigAddress({
network,
bip32Path,
multisig,
publicKey,
});
default:
return new UnsupportedInteraction({
code: "unsupported",
text:
"This keystore is not supported when confirming multisig addresses.",
});
}
}
/**
* Return a class for creating a multisig config file for a
* given keystore or coordinator.
*
* @param {string} KEYSTORE - keystore to use
* @param {string} jsonConfig - JSON wallet configuration file e.g. from Caravan
* @returns {ColdcardMultisigWalletConfig|UnsupportedInteraction} - A class that can translate to shape of
* config to match the specified keystore/coordinator requirements
*/
export function ConfigAdapter({ KEYSTORE, jsonConfig }) {
switch (KEYSTORE) {
case COLDCARD:
return new ColdcardMultisigWalletConfig({
jsonConfig,
});
default:
return new UnsupportedInteraction({
code: "unsupported",
text:
"This keystore is not supported when translating external spend configuration files.",
});
}
}
export * from "./interaction";
export * from "./bcur";
export * from "./coldcard";
export * from "./custom";
export * from "./hermit";
export * from "./ledger";
export * from "./trezor";