@bithive/bitcoin-sdk
Version:
BitHive SDK
381 lines • 12.9 kB
JavaScript
;
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