@atomiqlabs/sdk
Version:
atomiq labs SDK for cross-chain swaps between smart chains and bitcoin
197 lines (196 loc) • 8.28 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SingleAddressBitcoinWallet = void 0;
const utils_1 = require("@scure/btc-signer/utils");
const btc_signer_1 = require("@scure/btc-signer");
const buffer_1 = require("buffer");
const BitcoinWallet_1 = require("./BitcoinWallet");
const base_1 = require("@atomiqlabs/base");
const bip32_1 = require("@scure/bip32");
const bip39_1 = require("@scure/bip39");
const english_js_1 = require("@scure/bip39/wordlists/english.js");
const sha2_1 = require("@noble/hashes/sha2");
const logger = (0, base_1.getLogger)("SingleAddressBitcoinWallet: ");
/**
* Bitcoin wallet implementation deriving a single address from a WIF encoded private key
*
* @category Bitcoin
*/
class SingleAddressBitcoinWallet extends BitcoinWallet_1.BitcoinWallet {
constructor(mempoolApi, _network, addressDataOrWIF, feeMultiplier = 1.25, feeOverride) {
const network = typeof (_network) === "object"
? _network
: BitcoinWallet_1.BitcoinWallet.bitcoinNetworkToObject(_network);
super(mempoolApi, network, feeMultiplier, feeOverride);
if (typeof (addressDataOrWIF) === "string") {
try {
this.privKey = (0, btc_signer_1.WIF)(network).decode(addressDataOrWIF);
}
catch (e) {
this.privKey = (0, btc_signer_1.WIF)().decode(addressDataOrWIF);
}
this.pubkey = (0, utils_1.pubECDSA)(this.privKey);
const address = (0, btc_signer_1.getAddress)("wpkh", this.privKey, network);
if (address == null)
throw new Error("Failed to generate p2wpkh address from the provided private key!");
this.address = address;
this.addressType = (0, BitcoinWallet_1.identifyAddressType)(this.address, network);
}
else {
this.address = addressDataOrWIF.address;
this.addressType = (0, BitcoinWallet_1.identifyAddressType)(this.address, network);
this.pubkey = buffer_1.Buffer.from(addressDataOrWIF.publicKey, "hex");
// Some wallets seem to be returning a full 33-byte compressed pubkey instead of a taproot
// 32-byte long X-only key. Handle these cases here
if (this.addressType === "p2tr") {
if (this.pubkey.length !== 33)
return;
const leadingByte = this.pubkey[0];
if (leadingByte !== 0x03 && leadingByte !== 0x02)
throw new Error("Invalid public key passed for taproot bitcoin wallet, expected an X-only 32-byte public key, or a compressed 33-byte public key");
logger.debug(`constructor(): Converting compressed public key ${addressDataOrWIF.publicKey} to taproot X-only 32-byte public key`);
this.pubkey = this.pubkey.slice(1);
}
}
}
/**
* Returns all the wallet addresses controlled by the wallet
*
* @protected
*/
toBitcoinWalletAccounts() {
return [{
pubkey: buffer_1.Buffer.from(this.pubkey).toString("hex"), address: this.address, addressType: this.addressType
}];
}
/**
* @inheritDoc
*/
async sendTransaction(address, amount, feeRate) {
if (!this.privKey)
throw new Error("Not supported.");
const { psbt, fee } = await super._getPsbt(this.toBitcoinWalletAccounts(), address, Number(amount), feeRate);
if (psbt == null)
throw new Error(`Not enough funds, required for fee: ${fee} sats!`);
psbt.sign(this.privKey);
psbt.finalize();
const txHex = buffer_1.Buffer.from(psbt.extract()).toString("hex");
return await super._sendTransaction(txHex);
}
/**
* @inheritDoc
*/
async fundPsbt(inputPsbt, feeRate, utxos, spendFully) {
const { psbt } = await super._fundPsbt(this.toBitcoinWalletAccounts(), inputPsbt, feeRate, utxos, spendFully);
if (psbt == null) {
throw new Error("Not enough balance!");
}
return psbt;
}
/**
* @inheritDoc
*/
async signPsbt(psbt, signInputs) {
if (!this.privKey)
throw new Error("Not supported.");
for (let signInput of signInputs) {
psbt.signIdx(this.privKey, signInput);
}
return psbt;
}
/**
* @inheritDoc
*/
async getTransactionFee(address, amount, feeRate) {
const { fee } = await super._getPsbt(this.toBitcoinWalletAccounts(), address, Number(amount), feeRate);
return fee;
}
/**
* @inheritDoc
*/
async getFundedPsbtFee(basePsbt, feeRate) {
const { fee } = await super._fundPsbt(this.toBitcoinWalletAccounts(), basePsbt, feeRate);
return fee;
}
/**
* @inheritDoc
*/
getReceiveAddress() {
return this.address;
}
/**
* Returns the public key of the wallet
*/
getPublicKey() {
return buffer_1.Buffer.from(this.pubkey).toString("hex");
}
/**
* @inheritDoc
*/
getBalance() {
return this._getBalance(this.address);
}
/**
* @inheritDoc
*/
getSpendableBalance(psbt, feeRate, outputAddressType, utxos) {
return this._getSpendableBalance([{ address: this.address, addressType: this.addressType }], psbt, feeRate, outputAddressType, utxos);
}
/**
* @inheritDoc
*/
async getUtxoPool() {
return this._getUtxoPool(this.address, this.addressType);
}
/**
* Generates a new random private key WIF that can be used to instantiate the bitcoin wallet instance
*
* @returns A WIF encoded bitcoin private key
*/
static generateRandomPrivateKey(network) {
const networkObject = network == null || typeof (network) === "object"
? network
: BitcoinWallet_1.BitcoinWallet.bitcoinNetworkToObject(network);
return (0, btc_signer_1.WIF)(networkObject).encode((0, utils_1.randomPrivateKeyBytes)());
}
/**
* Generates a 12-word long mnemonic from any entropy source with 128-bits or more, the entropy is first hashed
* using sha256, and the first 16 bytes of the hash are used to generate the mnemonic
*
* @param entropy Entropy to use for generating the mnemonic
*/
static mnemonicFromEntropy(entropy) {
if (entropy.length < 16)
throw new Error("Requires at least 128-bit entropy (16 bytes)");
const entropyHash = buffer_1.Buffer.from((0, sha2_1.sha256)(entropy)).subarray(0, 16);
return (0, bip39_1.entropyToMnemonic)(entropyHash, english_js_1.wordlist);
}
/**
* Generates a random 12-word long mnemonic
*/
static generateRandomMnemonic() {
return (0, bip39_1.generateMnemonic)(english_js_1.wordlist, 128);
}
/**
* Generates a WIF private key from mnemonic phrase
*
* @param mnemonic Mnemonic to generate the WIF key from
* @param network Optional bitcoin network to generate the WIF for
* @param derivationPath Optional custom derivation path to use for deriving the wallet
*/
static async mnemonicToPrivateKey(mnemonic, network, derivationPath) {
const networkObject = network == null || typeof (network) === "object"
? network
: BitcoinWallet_1.BitcoinWallet.bitcoinNetworkToObject(network);
derivationPath = networkObject == null || networkObject.bech32 === utils_1.NETWORK.bech32
? "m/84'/0'/0'/0/0" //Mainnet
: "m/84'/1'/0'/0/0"; //Testnet
const seed = await (0, bip39_1.mnemonicToSeed)(mnemonic);
const hdKey = bip32_1.HDKey.fromMasterSeed(seed);
const privateKey = hdKey.derive(derivationPath).privateKey;
if (privateKey == null)
throw new Error("Cannot derive private key from the mnemonic!");
return (0, btc_signer_1.WIF)(networkObject).encode(privateKey);
}
}
exports.SingleAddressBitcoinWallet = SingleAddressBitcoinWallet;