UNPKG

@bithive/bitcoin-sdk

Version:

BitHive SDK

381 lines 12.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.validateDepositInTransaction = validateDepositInTransaction; exports.buildUnsignedDepositPsbt = buildUnsignedDepositPsbt; exports.buildDepositTxos = buildDepositTxos; const bitcoin = __importStar(require("bitcoinjs-lib")); const bip371_1 = require("bitcoinjs-lib/src/psbt/bip371"); const select_utxo_1 = require("@bithive/select-utxo"); const utils_1 = require("../utils"); const script_1 = require("./script"); const message_1 = require("./message"); const buffer_1 = require("buffer"); function validateDepositInTransaction({ transaction, amount, position: { vout, messageVout }, metadata: { publicKey, chainSignaturesPublicKey, soloWithdrawSequenceHeight, earliestDepositBlockHeight, }, network, }) { const output = transaction.outs[vout]; if (!output) { throw Error(`Deposit not found in output#${vout}`); } if (amount !== output.value) { throw Error(`Deposit amount mismatching in output#${vout}`); } const redeemScript = (0, script_1.buildRedeemScriptV1)({ publicKey, chainSignaturesPublicKey, soloWithdrawSequenceHeight, }); const payment = bitcoin.payments.p2wsh({ redeem: { output: redeemScript, network, }, network, }); const script = payment.output; if (!script) { throw Error('Bad P2WSH payment'); } if (!output.script.equals(script)) { throw Error(`Deposit is invalid in output#${vout}`); } const messageOutput = transaction.outs[messageVout]; if (!messageOutput) { throw Error(`Deposit message not found in output#${messageVout}`); } const message = (0, message_1.encodeDepositMessageV1)({ vout, publicKey, soloWithdrawSequenceHeight, }); const messagePayment = bitcoin.payments.embed({ data: [message], network, }); const messageScript = messagePayment.output; if (!messageScript) { throw Error('Bad EMBED payment'); } if (!messageOutput.script.equals(messageScript)) { throw Error(`Deposit message is invalid in output#${messageVout}`); } if (earliestDepositBlockHeight > 0) { const depositBlockHeight = transaction.locktime; if (depositBlockHeight >= utils_1.MAX_BLOCK_LOCKTIME) { throw Error('Invalid locktime for blocks'); } if (depositBlockHeight < earliestDepositBlockHeight) { throw Error(`The deposit block height ${depositBlockHeight} should not be earlier than ${earliestDepositBlockHeight}`); } } return { redeemScript, }; } async function buildUnsignedDepositPsbt({ utxos, amount, strategy = 'Sequential', feeLimit, feeRate, fee, changeScript, dustLimit, metadata: { publicKey, chainSignaturesPublicKey, soloWithdrawSequenceHeight, earliestDepositBlockHeight, }, network, }) { if (utxos.length === 0) { throw Error(`Empty UTXOs`); } let result; if (amount !== undefined) { if (!changeScript) { throw Error('Must specify `changeScript` for partial deposit'); } if (amount === 0) { throw Error(`Zero amount`); } } else { // Empty script changeScript = buffer_1.Buffer.alloc(0); // Use all UTXOs strategy = 'Maximal'; // We can not get deposit amount directly // // One idea is to set the deposit amount to 0, then calculate the change value // with fee. At this point, the change value will be equal to the deposit amount const { txos: fakeTxos } = buildDepositTxos({ amount: 0, publicKey, chainSignaturesPublicKey, soloWithdrawSequenceHeight, network, }); // In the result, change value is equal to real deposit amount try { result = (0, select_utxo_1.selectUtxo)({ utxos, txos: fakeTxos, feeLimit, feeRate, fee, changeScript, // Deposit should never be considered as dust dustLimit: 0, strategy, }); } catch (e) { if ((0, utils_1.getErrorMessage)(e).includes('Insufficient UTXOs value')) { throw Error('Total value of given UTXOs is insufficient to cover the fee'); } throw e; } utxos = result.utxos; const change = select_utxo_1.Change.findChange(result.txos); if (!change) { throw Error('No value remains for deposit after deducting fee'); } amount = change.value; } const { txos, position } = buildDepositTxos({ amount, publicKey, chainSignaturesPublicKey, soloWithdrawSequenceHeight, network, }); try { result = (0, select_utxo_1.selectUtxo)({ utxos, txos, feeLimit, feeRate, fee, changeScript, dustLimit, strategy, }); } catch (e) { if ((0, utils_1.getErrorMessage)(e).includes('Insufficient UTXOs value')) { throw Error('Deposit amount exceeds total value of given UTXOs'); } throw e; } utxos = result.utxos; const change = select_utxo_1.Change.findChange(result.txos); const inputs = await utxosToPsbtInputs(utxos, publicKey, network); const outputs = result.txos; const psbt = new bitcoin.Psbt({ network }) .addInputs(inputs) .addOutputs(outputs) .setLocktime(earliestDepositBlockHeight); return { psbt, metadata: { utxos, amount, changeAmount: change?.value ?? 0, fee: result.fee, position, }, }; } function buildDepositTxos({ amount, publicKey, chainSignaturesPublicKey, soloWithdrawSequenceHeight, network, }) { const redeemScript = (0, script_1.buildRedeemScriptV1)({ publicKey, chainSignaturesPublicKey, soloWithdrawSequenceHeight, }); const payment = bitcoin.payments.p2wsh({ redeem: { output: redeemScript, network, }, network, }); const script = payment.output; if (!script) { throw Error('Bad P2WSH payment'); } const message = (0, message_1.encodeDepositMessageV1)({ vout: 0, publicKey, soloWithdrawSequenceHeight, }); const messagePayment = bitcoin.payments.embed({ data: [message], network, }); const messageScript = messagePayment.output; if (!messageScript) { throw Error('Bad EMBED payment'); } const txos = [ { value: amount, script, }, { value: 0, script: messageScript, }, ]; return { txos, position: { vout: 0, messageVout: 1, }, }; } async function utxosToPsbtInputs(utxos, publicKey, network) { const inputs = []; for (const utxo of utxos) { const scriptType = (0, utils_1.getScriptType)(utxo.script); if (scriptType === 'P2WPKH') { const input = await buildPsbtInputForNativeSegwitUtxo(utxo, publicKey, network); inputs.push(input); } else if (scriptType === 'P2SH') { // We don't support common P2SH UTXO and will consider all P2SH UTXOs as Nested Segwit UTXOs const input = await buildPsbtInputForNestedSegwitUtxo(utxo, publicKey, network); inputs.push(input); } else if (scriptType === 'P2PKH') { const input = await buildPsbtInputForLegacyUtxo(utxo, publicKey, network); inputs.push(input); } else if (scriptType === 'P2TR') { const input = await buildPsbtInputForTaprootUtxo(utxo, publicKey, network); inputs.push(input); } else { throw Error(`UTXO (${utxo.txHash}:${utxo.vout}) type (${scriptType}) is not supported`); } } return inputs; } async function buildPsbtInputForNativeSegwitUtxo(utxo, publicKey, network) { const payment = bitcoin.payments.p2wpkh({ pubkey: publicKey.toBuffer(), network, }); const script = payment.output; if (!script) { throw Error('Bad P2WPKH payment'); } if (!script.equals(utxo.script)) { throw Error(`Native Segwit (P2WPKH) UTXO (${utxo.txHash}:${utxo.vout}) does not match the public key (${publicKey})`); } return { hash: utxo.txHash.toHex(), index: utxo.vout, witnessUtxo: { value: utxo.value, script: utxo.script, }, sequence: utils_1.SEQUENCE_RBF, }; } async function buildPsbtInputForNestedSegwitUtxo(utxo, publicKey, network) { const redeemPayment = bitcoin.payments.p2wpkh({ pubkey: publicKey.toBuffer(), network, }); const redeemScript = redeemPayment.output; if (!redeemScript) { throw Error('Bad P2WPKH payment'); } const payment = bitcoin.payments.p2sh({ redeem: redeemPayment, network, }); const script = payment.output; if (!script) { throw Error('Bad P2SH payment'); } if (!script.equals(utxo.script)) { throw Error(`Nested Segwit (P2SH-P2WPKH) UTXO (${utxo.txHash}:${utxo.vout}) does not match the public key (${publicKey})`); } return { hash: utxo.txHash.toHex(), index: utxo.vout, witnessUtxo: { value: utxo.value, script: utxo.script, }, redeemScript, sequence: utils_1.SEQUENCE_RBF, }; } async function buildPsbtInputForLegacyUtxo(utxo, publicKey, network) { const payment = bitcoin.payments.p2pkh({ pubkey: publicKey.toBuffer(), network, }); const script = payment.output; if (!script) { throw Error('Bad P2PKH payment'); } if (!script.equals(utxo.script)) { throw Error(`Legacy (P2PKH) UTXO (${utxo.txHash}:${utxo.vout}) does not match the public key (${publicKey})`); } if (!utxo.transaction) { throw Error(`Legacy (P2PKH) UTXO (${utxo.txHash}:${utxo.vout}) must implement \`transaction\``); } const transaction = await utxo.transaction(); return { hash: utxo.txHash.toHex(), index: utxo.vout, nonWitnessUtxo: transaction.toBuffer(), sequence: utils_1.SEQUENCE_RBF, }; } async function buildPsbtInputForTaprootUtxo(utxo, publicKey, network) { const publicKeyXOnly = (0, bip371_1.toXOnly)(publicKey.toBuffer()); const payment = bitcoin.payments.p2tr({ internalPubkey: publicKeyXOnly, network, }); const script = payment.output; if (!script) { throw Error('Bad P2TR payment'); } if (!script.equals(utxo.script)) { throw Error(`Taproot (P2TR) UTXO (${utxo.txHash}:${utxo.vout}) does not match the public key (${publicKey})`); } return { hash: utxo.txHash.toHex(), index: utxo.vout, witnessUtxo: { value: utxo.value, script: utxo.script, }, tapInternalKey: publicKeyXOnly, sequence: utils_1.SEQUENCE_RBF, }; } //# sourceMappingURL=deposit.js.map