UNPKG

@bithive/bitcoin-sdk

Version:

BitHive SDK

266 lines 10.1 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; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildUnsignedWithdrawPsbt = buildUnsignedWithdrawPsbt; exports.finalizeWithdrawPsbt = finalizeWithdrawPsbt; const types_1 = require("../types"); const bitcoin = __importStar(require("bitcoinjs-lib")); const bufferutils_1 = require("bitcoinjs-lib/src/bufferutils"); const utils_1 = require("../utils"); const buffer_1 = require("buffer"); const bip68_1 = __importDefault(require("bip68")); const select_utxo_1 = require("@bithive/select-utxo"); const script_1 = require("./script"); const deposit_1 = require("./deposit"); async function buildUnsignedWithdrawPsbt({ utxos: depositUtxos, amount, strategy = 'Sequential', recipientScript, feeLimit, feeRate, fee, dustLimit, redepositMetadata, network, }) { if (depositUtxos.length === 0) { throw Error(`Empty deposit UTXOs`); } let utxos = depositUtxos.map((utxo) => { const depositPayment = bitcoin.payments.p2wsh({ redeem: { output: utxo.redeemScript, network, }, network, }); const depositScript = depositPayment.output; if (!depositScript) { throw Error('Bad P2WSH payment'); } return { txHash: utxo.txHash, vout: utxo.vout, value: utxo.value, script: depositScript, redeemScript: utxo.redeemScript, soloWithdrawSequenceHeight: utxo.soloWithdrawSequenceHeight, }; }); let earliestDepositBlockHeight = 0; let txos = []; let redepositAmount = 0; let redepositPosition; let result; if (amount !== undefined) { if (!redepositMetadata) { throw Error('Must specify `redepositMetadata` for partial withdrawal'); } if (amount === 0) { throw Error(`Zero amount`); } const { publicKey, chainSignaturesPublicKey, soloWithdrawSequenceHeight } = redepositMetadata; earliestDepositBlockHeight = redepositMetadata.earliestDepositBlockHeight; // We can not get redeposit amount directly // // One idea is to set the redeposit amount equal to the withdrawal amount, then // calculate the change value WITHOUT fee. At this point, the change value will // be equal to the real redeposit amount, and the withdrawal amount will include the fee // Build redeposit TXOs with withdrawal amount const { txos: fakeTxos } = (0, deposit_1.buildDepositTxos)({ amount, publicKey, chainSignaturesPublicKey, soloWithdrawSequenceHeight, network, }); try { // In the result, change value is equal to real redeposit amount result = (0, select_utxo_1.selectUtxo)({ utxos, txos: fakeTxos, // Fee is included in withdrawal amount fee: 0, changeScript: recipientScript, dustLimit, strategy, }); } catch (e) { if ((0, utils_1.getErrorMessage)(e).includes('Insufficient UTXOs value')) { throw Error(`Withdrawal amount exceeds total value of given deposit UTXOs`); } throw e; } utxos = result.utxos; const change = select_utxo_1.Change.findChange(result.txos); if (change) { redepositAmount = change.value; ({ txos, position: redepositPosition } = (0, deposit_1.buildDepositTxos)({ amount: redepositAmount, publicKey, chainSignaturesPublicKey, soloWithdrawSequenceHeight, network, })); } } try { // In the result, change value is equal to the real withdrawal amount, which // is probably less than the specified withdrawal amount due to deduction of fee result = (0, select_utxo_1.selectUtxo)({ utxos, txos, feeLimit, feeRate, fee, changeScript: recipientScript, // Withdrawal should never be considered as dust dustLimit: 0, // Use all deposit UTXOs strategy: 'Maximal', }); } catch (e) { if ((0, utils_1.getErrorMessage)(e).includes('Insufficient UTXOs value')) { throw Error('Withdrawal amount can not 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 withdrawal after deducting fee'); } const inputs = await utxosToPsbtInputs(utxos); const outputs = result.txos; const lockTime = earliestDepositBlockHeight; const psbt = new bitcoin.Psbt({ network }) .addInputs(inputs) .addOutputs(outputs) .setLocktime(lockTime); return { psbt, metadata: { utxos, amount: change.value, fee: result.fee, redepositAmount, redepositPosition, }, }; } function finalizeWithdrawPsbt({ psbt, inputs, network, }) { inputs.forEach(({ vin, partialSignature, chainPartialSignature }) => { const input = psbt.data.inputs[vin]; if (!input) { throw Error(`Withdrawal not found in input #${vin}`); } const redeemScript = input.witnessScript; if (!redeemScript) { throw Error(`Redeem script not found in input #${vin}`); } const signature = partialSignature.signature ?? input.partialSig?.find((partialSig) => types_1.PublicKey.fromBuffer(partialSig.pubkey).equals(partialSignature.publicKey))?.signature; if (!signature) { throw Error(`Signature not found in input #${vin}`); } let chainSignature; if (chainPartialSignature) { chainSignature = chainPartialSignature.signature ?? input.partialSig?.find((partialSig) => types_1.PublicKey.fromBuffer(partialSig.pubkey).equals(chainPartialSignature.publicKey))?.signature; if (!chainSignature) { throw Error(`Chain signature not found in input #${vin}`); } } const finalizedRedeemScript = (0, script_1.finalizeRedeemScriptV1)({ signature, chainSignature, }); const depositPayment = bitcoin.payments.p2wsh({ redeem: { input: finalizedRedeemScript, output: redeemScript, network, }, network, }); const finalScriptSig = depositPayment.input; const witness = depositPayment.witness; if (!finalScriptSig || !witness) { throw Error('Bad P2WSH payment'); } const finalScriptWitness = witnessStackToFinalScriptWitness(witness); psbt.finalizeInput(vin, () => { return { finalScriptSig, finalScriptWitness, }; }); }); } async function utxosToPsbtInputs(utxos) { return utxos.map((utxo) => { return { hash: utxo.txHash.toHex(), index: utxo.vout, witnessUtxo: { value: utxo.value, script: utxo.script, }, witnessScript: utxo.redeemScript, sequence: utxo.soloWithdrawSequenceHeight !== undefined ? bip68_1.default.encode({ blocks: utxo.soloWithdrawSequenceHeight }) : utils_1.SEQUENCE_RBF, }; }); } function witnessStackToFinalScriptWitness(witness) { let buffer = buffer_1.Buffer.alloc(0); const writeSlice = (slice) => { buffer = buffer_1.Buffer.concat([buffer, slice]); }; const writeVarInt = (i) => { const currentLen = buffer.length; const varintLen = bufferutils_1.varuint.encodingLength(i); buffer = buffer_1.Buffer.concat([buffer, buffer_1.Buffer.allocUnsafe(varintLen)]); bufferutils_1.varuint.encode(i, buffer, currentLen); }; const writeVarSlice = (slice) => { writeVarInt(slice.length); writeSlice(slice); }; const writeVector = (vector) => { writeVarInt(vector.length); vector.forEach(writeVarSlice); }; writeVector(witness); return buffer; } //# sourceMappingURL=withdraw.js.map