import { encodePublicKeyAddressData } from "./address.js"; import { derivePublicKeys, parseXpub } from "./bip32.js"; import { generateAddressesFromDescriptor, getOutputDescriptorBranchIds, isOutputDescriptor, selectOutputDescriptor, } from "./descriptor.js"; const DEFAULT_START_INDEX = 0; const DEFAULT_ADDRESS_COUNT = 20; const MAX_ADDRESS_COUNT = 100; const addrTypeByScript = /** @type {const} */ ({ p2pkh: "p2pkh", p2sh_p2wpkh: "p2sh", v0_p2wpkh: "v0_p2wpkh", v1_p2tr: "v1_p2tr", v0_p2wsh_sortedmulti: "v0_p2wsh", }); /** * @typedef {import("./address.js").AddressScript} AddressScript * @typedef {import("./address.js").BitcoinNetwork} BitcoinNetwork * @typedef {(typeof addrTypeByScript)[keyof typeof addrTypeByScript]} AddressType */ /** * @typedef {Object} GeneratedAddress * @property {number} index * @property {string} address * @property {Uint8Array} payload * @property {Uint8Array} [publicKey] * @property {AddressScript} script * @property {BitcoinNetwork} network * @property {AddressType} addrType */ /** * @typedef {Object} GenerateAddressesOptions * @property {number} [start] * @property {number} [count] * @property {AddressScript} [script] * @property {readonly number[]} [path] * @property {string} [branchId] */ /** * @param {number | undefined} value */ function readStart(value) { if (value === undefined) return DEFAULT_START_INDEX; if (!Number.isSafeInteger(value) || value < 0) { throw new Error("Expected a non-negative start index"); } return value; } /** * @param {number | undefined} value */ function readCount(value) { const count = value ?? DEFAULT_ADDRESS_COUNT; if (!Number.isSafeInteger(count) || count < 1 || count > MAX_ADDRESS_COUNT) { throw new Error(`Expected an address count from 1 to ${MAX_ADDRESS_COUNT}`); } return count; } /** * @param {string} source * @param {GenerateAddressesOptions} [options] * @returns {Promise} */ export async function generateAddressesFromKey(source, options = {}) { const key = await parseXpub(source); const start = readStart(options.start); const count = readCount(options.count); const script = options.script ?? key.version.script; const addrType = addrTypeByScript[script]; const children = await derivePublicKeys(key, start, count, options.path); const addresses = /** @type {GeneratedAddress[]} */ ([]); for (const child of children) { const addressData = await encodePublicKeyAddressData( child.publicKey, script, key.version.network, ); addresses.push({ index: child.index, address: addressData.address, payload: addressData.payload, publicKey: child.publicKey, script, network: key.version.network, addrType, }); } return addresses; } /** * @param {string} source * @param {GenerateAddressesOptions} [options] * @returns {Promise} */ export async function generateAddressesFromWalletSource(source, options = {}) { const start = readStart(options.start); const count = readCount(options.count); if (isOutputDescriptor(source)) { return generateAddressesFromDescriptor( selectOutputDescriptor(source, options.branchId), { start, count }, ); } return generateAddressesFromKey(source, { ...options, start, count, }); } export { getOutputDescriptorBranchIds, isOutputDescriptor, };