Add fee checking before extract
This commit is contained in:
parent
ba5f336e02
commit
14eeb309df
3 changed files with 324 additions and 48 deletions
204
src/psbt.js
204
src/psbt.js
|
@ -11,6 +11,45 @@ const bscript = require('./script');
|
||||||
const transaction_1 = require('./transaction');
|
const transaction_1 = require('./transaction');
|
||||||
const varuint = require('varuint-bitcoin');
|
const varuint = require('varuint-bitcoin');
|
||||||
class Psbt extends bip174_1.Psbt {
|
class Psbt extends bip174_1.Psbt {
|
||||||
|
constructor(opts = {}) {
|
||||||
|
super();
|
||||||
|
this.__NON_WITNESS_UTXO_TX_CACHE = [];
|
||||||
|
this.__NON_WITNESS_UTXO_BUF_CACHE = [];
|
||||||
|
// set defaults
|
||||||
|
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
|
||||||
|
this.__TX = transaction_1.Transaction.fromBuffer(this.globalMap.unsignedTx);
|
||||||
|
this.setVersion(2);
|
||||||
|
// set cache
|
||||||
|
const self = this;
|
||||||
|
delete this.globalMap.unsignedTx;
|
||||||
|
Object.defineProperty(this.globalMap, 'unsignedTx', {
|
||||||
|
enumerable: true,
|
||||||
|
get() {
|
||||||
|
if (self.__TX_BUF_CACHE !== undefined) {
|
||||||
|
return self.__TX_BUF_CACHE;
|
||||||
|
} else {
|
||||||
|
self.__TX_BUF_CACHE = self.__TX.toBuffer();
|
||||||
|
return self.__TX_BUF_CACHE;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set(data) {
|
||||||
|
self.__TX_BUF_CACHE = data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Make data hidden when enumerating
|
||||||
|
const dpew = (obj, attr, enumerable, writable) =>
|
||||||
|
Object.defineProperty(obj, attr, {
|
||||||
|
enumerable,
|
||||||
|
writable,
|
||||||
|
});
|
||||||
|
dpew(this, '__TX', false, true);
|
||||||
|
dpew(this, '__EXTRACTED_TX', false, true);
|
||||||
|
dpew(this, '__FEE_RATE', false, true);
|
||||||
|
dpew(this, '__TX_BUF_CACHE', false, true);
|
||||||
|
dpew(this, '__NON_WITNESS_UTXO_TX_CACHE', false, true);
|
||||||
|
dpew(this, '__NON_WITNESS_UTXO_BUF_CACHE', false, true);
|
||||||
|
dpew(this, 'opts', false, true);
|
||||||
|
}
|
||||||
static fromTransaction(txBuf) {
|
static fromTransaction(txBuf) {
|
||||||
const tx = transaction_1.Transaction.fromBuffer(txBuf);
|
const tx = transaction_1.Transaction.fromBuffer(txBuf);
|
||||||
checkTxEmpty(tx);
|
checkTxEmpty(tx);
|
||||||
|
@ -46,44 +85,16 @@ class Psbt extends bip174_1.Psbt {
|
||||||
psbt.__TX = tx;
|
psbt.__TX = tx;
|
||||||
return psbt;
|
return psbt;
|
||||||
}
|
}
|
||||||
constructor(opts = {}) {
|
setMaximumFeeRate(satoshiPerByte) {
|
||||||
super();
|
check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw
|
||||||
// set defaults
|
this.opts.maximumFeeRate = satoshiPerByte;
|
||||||
this.opts = Object.assign({}, DEFAULT_OPTS, opts);
|
|
||||||
this.__TX = transaction_1.Transaction.fromBuffer(this.globalMap.unsignedTx);
|
|
||||||
this.setVersion(2);
|
|
||||||
// set cache
|
|
||||||
const self = this;
|
|
||||||
delete this.globalMap.unsignedTx;
|
|
||||||
Object.defineProperty(this.globalMap, 'unsignedTx', {
|
|
||||||
enumerable: true,
|
|
||||||
get() {
|
|
||||||
if (self.__TX_BUF_CACHE !== undefined) {
|
|
||||||
return self.__TX_BUF_CACHE;
|
|
||||||
} else {
|
|
||||||
self.__TX_BUF_CACHE = self.__TX.toBuffer();
|
|
||||||
return self.__TX_BUF_CACHE;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set(data) {
|
|
||||||
self.__TX_BUF_CACHE = data;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// Make data hidden when enumerating
|
|
||||||
const dpew = (obj, attr, enumerable, writable) =>
|
|
||||||
Object.defineProperty(obj, attr, {
|
|
||||||
enumerable,
|
|
||||||
writable,
|
|
||||||
});
|
|
||||||
dpew(this, '__TX', false, true);
|
|
||||||
dpew(this, '__TX_BUF_CACHE', false, true);
|
|
||||||
dpew(this, 'opts', false, true);
|
|
||||||
}
|
}
|
||||||
setVersion(version) {
|
setVersion(version) {
|
||||||
check32Bit(version);
|
check32Bit(version);
|
||||||
checkInputsForPartialSig(this.inputs, 'setVersion');
|
checkInputsForPartialSig(this.inputs, 'setVersion');
|
||||||
this.__TX.version = version;
|
this.__TX.version = version;
|
||||||
this.__TX_BUF_CACHE = undefined;
|
this.__TX_BUF_CACHE = undefined;
|
||||||
|
this.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
setLocktime(locktime) {
|
setLocktime(locktime) {
|
||||||
|
@ -91,6 +102,7 @@ class Psbt extends bip174_1.Psbt {
|
||||||
checkInputsForPartialSig(this.inputs, 'setLocktime');
|
checkInputsForPartialSig(this.inputs, 'setLocktime');
|
||||||
this.__TX.locktime = locktime;
|
this.__TX.locktime = locktime;
|
||||||
this.__TX_BUF_CACHE = undefined;
|
this.__TX_BUF_CACHE = undefined;
|
||||||
|
this.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
setSequence(inputIndex, sequence) {
|
setSequence(inputIndex, sequence) {
|
||||||
|
@ -101,6 +113,7 @@ class Psbt extends bip174_1.Psbt {
|
||||||
}
|
}
|
||||||
this.__TX.ins[inputIndex].sequence = sequence;
|
this.__TX.ins[inputIndex].sequence = sequence;
|
||||||
this.__TX_BUF_CACHE = undefined;
|
this.__TX_BUF_CACHE = undefined;
|
||||||
|
this.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
addInput(inputData) {
|
addInput(inputData) {
|
||||||
|
@ -159,8 +172,29 @@ class Psbt extends bip174_1.Psbt {
|
||||||
};
|
};
|
||||||
return super.addOutput(outputData, true, outputAdder);
|
return super.addOutput(outputData, true, outputAdder);
|
||||||
}
|
}
|
||||||
extractTransaction() {
|
addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo) {
|
||||||
|
super.addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo);
|
||||||
|
const input = this.inputs[inputIndex];
|
||||||
|
addNonWitnessTxCache(this, input, inputIndex);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
extractTransaction(disableFeeCheck) {
|
||||||
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
|
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
|
||||||
|
if (!disableFeeCheck) {
|
||||||
|
const feeRate = this.__FEE_RATE || this.getFeeRate();
|
||||||
|
const vsize = this.__EXTRACTED_TX.virtualSize();
|
||||||
|
const satoshis = feeRate * vsize;
|
||||||
|
if (feeRate >= this.opts.maximumFeeRate) {
|
||||||
|
throw new Error(
|
||||||
|
`Warning: You are paying around ${satoshis / 1e8} in fees, which ` +
|
||||||
|
`is ${feeRate} satoshi per byte for a transaction with a VSize of ` +
|
||||||
|
`${vsize} bytes (segwit counted as 0.25 byte per byte)\n` +
|
||||||
|
`Use setMaximumFeeRate method to raise your threshold, or pass ` +
|
||||||
|
`true to the first arg of extractTransaction.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.__EXTRACTED_TX) return this.__EXTRACTED_TX;
|
||||||
const tx = this.__TX.clone();
|
const tx = this.__TX.clone();
|
||||||
this.inputs.forEach((input, idx) => {
|
this.inputs.forEach((input, idx) => {
|
||||||
if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
|
if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
|
||||||
|
@ -170,8 +204,51 @@ class Psbt extends bip174_1.Psbt {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.__EXTRACTED_TX = tx;
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
getFeeRate() {
|
||||||
|
if (!this.inputs.every(isFinalized))
|
||||||
|
throw new Error('PSBT must be finalized to calculate fee rate');
|
||||||
|
if (this.__FEE_RATE) return this.__FEE_RATE;
|
||||||
|
let tx;
|
||||||
|
let inputAmount = 0;
|
||||||
|
let mustFinalize = true;
|
||||||
|
if (this.__EXTRACTED_TX) {
|
||||||
|
tx = this.__EXTRACTED_TX;
|
||||||
|
mustFinalize = false;
|
||||||
|
} else {
|
||||||
|
tx = this.__TX.clone();
|
||||||
|
}
|
||||||
|
this.inputs.forEach((input, idx) => {
|
||||||
|
if (mustFinalize && input.finalScriptSig)
|
||||||
|
tx.ins[idx].script = input.finalScriptSig;
|
||||||
|
if (mustFinalize && input.finalScriptWitness) {
|
||||||
|
tx.ins[idx].witness = scriptWitnessToWitnessStack(
|
||||||
|
input.finalScriptWitness,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (input.witnessUtxo) {
|
||||||
|
inputAmount += input.witnessUtxo.value;
|
||||||
|
} else if (input.nonWitnessUtxo) {
|
||||||
|
// @ts-ignore
|
||||||
|
if (!this.__NON_WITNESS_UTXO_TX_CACHE[idx]) {
|
||||||
|
addNonWitnessTxCache(this, input, idx);
|
||||||
|
}
|
||||||
|
const vout = this.__TX.ins[idx].index;
|
||||||
|
const out = this.__NON_WITNESS_UTXO_TX_CACHE[idx].outs[vout];
|
||||||
|
inputAmount += out.value;
|
||||||
|
} else {
|
||||||
|
throw new Error('Missing input value: index #' + idx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.__EXTRACTED_TX = tx;
|
||||||
|
const outputAmount = tx.outs.reduce((total, o) => total + o.value, 0);
|
||||||
|
const fee = inputAmount - outputAmount;
|
||||||
|
const bytes = tx.virtualSize();
|
||||||
|
this.__FEE_RATE = Math.floor(fee / bytes);
|
||||||
|
return this.__FEE_RATE;
|
||||||
|
}
|
||||||
finalizeAllInputs() {
|
finalizeAllInputs() {
|
||||||
const inputResults = range(this.inputs.length).map(idx =>
|
const inputResults = range(this.inputs.length).map(idx =>
|
||||||
this.finalizeInput(idx),
|
this.finalizeInput(idx),
|
||||||
|
@ -188,6 +265,7 @@ class Psbt extends bip174_1.Psbt {
|
||||||
inputIndex,
|
inputIndex,
|
||||||
input,
|
input,
|
||||||
this.__TX,
|
this.__TX,
|
||||||
|
this,
|
||||||
);
|
);
|
||||||
if (!script) return false;
|
if (!script) return false;
|
||||||
const scriptType = classifyScript(script);
|
const scriptType = classifyScript(script);
|
||||||
|
@ -216,6 +294,7 @@ class Psbt extends bip174_1.Psbt {
|
||||||
inputIndex,
|
inputIndex,
|
||||||
keyPair.publicKey,
|
keyPair.publicKey,
|
||||||
this.__TX,
|
this.__TX,
|
||||||
|
this,
|
||||||
);
|
);
|
||||||
const partialSig = {
|
const partialSig = {
|
||||||
pubkey: keyPair.publicKey,
|
pubkey: keyPair.publicKey,
|
||||||
|
@ -232,6 +311,7 @@ class Psbt extends bip174_1.Psbt {
|
||||||
inputIndex,
|
inputIndex,
|
||||||
keyPair.publicKey,
|
keyPair.publicKey,
|
||||||
this.__TX,
|
this.__TX,
|
||||||
|
this,
|
||||||
);
|
);
|
||||||
Promise.resolve(keyPair.sign(hash)).then(signature => {
|
Promise.resolve(keyPair.sign(hash)).then(signature => {
|
||||||
const partialSig = {
|
const partialSig = {
|
||||||
|
@ -247,16 +327,50 @@ class Psbt extends bip174_1.Psbt {
|
||||||
exports.Psbt = Psbt;
|
exports.Psbt = Psbt;
|
||||||
const DEFAULT_OPTS = {
|
const DEFAULT_OPTS = {
|
||||||
network: networks_1.bitcoin,
|
network: networks_1.bitcoin,
|
||||||
|
maximumFeeRate: 5000,
|
||||||
};
|
};
|
||||||
|
function addNonWitnessTxCache(psbt, input, inputIndex) {
|
||||||
|
// @ts-ignore
|
||||||
|
psbt.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo;
|
||||||
|
const tx = transaction_1.Transaction.fromBuffer(input.nonWitnessUtxo);
|
||||||
|
// @ts-ignore
|
||||||
|
psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = tx;
|
||||||
|
const self = psbt;
|
||||||
|
const selfIndex = inputIndex;
|
||||||
|
delete input.nonWitnessUtxo;
|
||||||
|
Object.defineProperty(input, 'nonWitnessUtxo', {
|
||||||
|
enumerable: true,
|
||||||
|
get() {
|
||||||
|
// @ts-ignore
|
||||||
|
if (self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] !== undefined) {
|
||||||
|
// @ts-ignore
|
||||||
|
return self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex];
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
self.__NON_WITNESS_UTXO_BUF_CACHE[
|
||||||
|
selfIndex
|
||||||
|
// @ts-ignore
|
||||||
|
] = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex].toBuffer();
|
||||||
|
// @ts-ignore
|
||||||
|
return self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set(data) {
|
||||||
|
// @ts-ignore
|
||||||
|
self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
function isFinalized(input) {
|
function isFinalized(input) {
|
||||||
return !!input.finalScriptSig || !!input.finalScriptWitness;
|
return !!input.finalScriptSig || !!input.finalScriptWitness;
|
||||||
}
|
}
|
||||||
function getHashAndSighashType(inputs, inputIndex, pubkey, unsignedTx) {
|
function getHashAndSighashType(inputs, inputIndex, pubkey, unsignedTx, psbt) {
|
||||||
const input = utils_1.checkForInput(inputs, inputIndex);
|
const input = utils_1.checkForInput(inputs, inputIndex);
|
||||||
const { hash, sighashType, script } = getHashForSig(
|
const { hash, sighashType, script } = getHashForSig(
|
||||||
inputIndex,
|
inputIndex,
|
||||||
input,
|
input,
|
||||||
unsignedTx,
|
unsignedTx,
|
||||||
|
psbt,
|
||||||
);
|
);
|
||||||
checkScriptForPubkey(pubkey, script);
|
checkScriptForPubkey(pubkey, script);
|
||||||
return {
|
return {
|
||||||
|
@ -375,15 +489,18 @@ function checkScriptForPubkey(pubkey, script) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const getHashForSig = (inputIndex, input, unsignedTx) => {
|
const getHashForSig = (inputIndex, input, unsignedTx, psbt) => {
|
||||||
const sighashType =
|
const sighashType =
|
||||||
input.sighashType || transaction_1.Transaction.SIGHASH_ALL;
|
input.sighashType || transaction_1.Transaction.SIGHASH_ALL;
|
||||||
let hash;
|
let hash;
|
||||||
let script;
|
let script;
|
||||||
if (input.nonWitnessUtxo) {
|
if (input.nonWitnessUtxo) {
|
||||||
const nonWitnessUtxoTx = transaction_1.Transaction.fromBuffer(
|
// @ts-ignore
|
||||||
input.nonWitnessUtxo,
|
if (!psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex]) {
|
||||||
);
|
addNonWitnessTxCache(psbt, input, inputIndex);
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const nonWitnessUtxoTx = psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex];
|
||||||
const prevoutHash = unsignedTx.ins[inputIndex].hash;
|
const prevoutHash = unsignedTx.ins[inputIndex].hash;
|
||||||
const utxoHash = nonWitnessUtxoTx.getHash();
|
const utxoHash = nonWitnessUtxoTx.getHash();
|
||||||
// If a non-witness UTXO is provided, its hash must match the hash specified in the prevout
|
// If a non-witness UTXO is provided, its hash must match the hash specified in the prevout
|
||||||
|
@ -494,7 +611,7 @@ const classifyScript = script => {
|
||||||
if (isP2PK(script)) return 'pubkey';
|
if (isP2PK(script)) return 'pubkey';
|
||||||
return 'nonstandard';
|
return 'nonstandard';
|
||||||
};
|
};
|
||||||
function getScriptFromInput(inputIndex, input, unsignedTx) {
|
function getScriptFromInput(inputIndex, input, unsignedTx, psbt) {
|
||||||
const res = {
|
const res = {
|
||||||
script: null,
|
script: null,
|
||||||
isSegwit: false,
|
isSegwit: false,
|
||||||
|
@ -506,9 +623,12 @@ function getScriptFromInput(inputIndex, input, unsignedTx) {
|
||||||
res.isP2SH = true;
|
res.isP2SH = true;
|
||||||
res.script = input.redeemScript;
|
res.script = input.redeemScript;
|
||||||
} else {
|
} else {
|
||||||
const nonWitnessUtxoTx = transaction_1.Transaction.fromBuffer(
|
// @ts-ignore
|
||||||
input.nonWitnessUtxo,
|
if (!psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex]) {
|
||||||
);
|
addNonWitnessTxCache(psbt, input, inputIndex);
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const nonWitnessUtxoTx = psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex];
|
||||||
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
||||||
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
|
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
|
||||||
}
|
}
|
||||||
|
|
156
ts_src/psbt.ts
156
ts_src/psbt.ts
|
@ -1,5 +1,6 @@
|
||||||
import { Psbt as PsbtBase } from 'bip174';
|
import { Psbt as PsbtBase } from 'bip174';
|
||||||
import {
|
import {
|
||||||
|
NonWitnessUtxo,
|
||||||
PartialSig,
|
PartialSig,
|
||||||
PsbtInput,
|
PsbtInput,
|
||||||
TransactionInput,
|
TransactionInput,
|
||||||
|
@ -13,7 +14,7 @@ import { Signer, SignerAsync } from './ecpair';
|
||||||
import { bitcoin as btcNetwork, Network } from './networks';
|
import { bitcoin as btcNetwork, Network } from './networks';
|
||||||
import * as payments from './payments';
|
import * as payments from './payments';
|
||||||
import * as bscript from './script';
|
import * as bscript from './script';
|
||||||
import { Transaction } from './transaction';
|
import { Output, Transaction } from './transaction';
|
||||||
const varuint = require('varuint-bitcoin');
|
const varuint = require('varuint-bitcoin');
|
||||||
|
|
||||||
export class Psbt extends PsbtBase {
|
export class Psbt extends PsbtBase {
|
||||||
|
@ -65,6 +66,10 @@ export class Psbt extends PsbtBase {
|
||||||
}
|
}
|
||||||
private __TX: Transaction;
|
private __TX: Transaction;
|
||||||
private __TX_BUF_CACHE?: Buffer;
|
private __TX_BUF_CACHE?: Buffer;
|
||||||
|
private __FEE_RATE?: number;
|
||||||
|
private __EXTRACTED_TX?: Transaction;
|
||||||
|
private __NON_WITNESS_UTXO_TX_CACHE: Transaction[] = [];
|
||||||
|
private __NON_WITNESS_UTXO_BUF_CACHE: Buffer[] = [];
|
||||||
private opts: PsbtOpts;
|
private opts: PsbtOpts;
|
||||||
constructor(opts: PsbtOptsOptional = {}) {
|
constructor(opts: PsbtOptsOptional = {}) {
|
||||||
super();
|
super();
|
||||||
|
@ -103,15 +108,25 @@ export class Psbt extends PsbtBase {
|
||||||
writable,
|
writable,
|
||||||
});
|
});
|
||||||
dpew(this, '__TX', false, true);
|
dpew(this, '__TX', false, true);
|
||||||
|
dpew(this, '__EXTRACTED_TX', false, true);
|
||||||
|
dpew(this, '__FEE_RATE', false, true);
|
||||||
dpew(this, '__TX_BUF_CACHE', false, true);
|
dpew(this, '__TX_BUF_CACHE', false, true);
|
||||||
|
dpew(this, '__NON_WITNESS_UTXO_TX_CACHE', false, true);
|
||||||
|
dpew(this, '__NON_WITNESS_UTXO_BUF_CACHE', false, true);
|
||||||
dpew(this, 'opts', false, true);
|
dpew(this, 'opts', false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setMaximumFeeRate(satoshiPerByte: number): void {
|
||||||
|
check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw
|
||||||
|
this.opts.maximumFeeRate = satoshiPerByte;
|
||||||
|
}
|
||||||
|
|
||||||
setVersion(version: number): this {
|
setVersion(version: number): this {
|
||||||
check32Bit(version);
|
check32Bit(version);
|
||||||
checkInputsForPartialSig(this.inputs, 'setVersion');
|
checkInputsForPartialSig(this.inputs, 'setVersion');
|
||||||
this.__TX.version = version;
|
this.__TX.version = version;
|
||||||
this.__TX_BUF_CACHE = undefined;
|
this.__TX_BUF_CACHE = undefined;
|
||||||
|
this.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +135,7 @@ export class Psbt extends PsbtBase {
|
||||||
checkInputsForPartialSig(this.inputs, 'setLocktime');
|
checkInputsForPartialSig(this.inputs, 'setLocktime');
|
||||||
this.__TX.locktime = locktime;
|
this.__TX.locktime = locktime;
|
||||||
this.__TX_BUF_CACHE = undefined;
|
this.__TX_BUF_CACHE = undefined;
|
||||||
|
this.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +147,7 @@ export class Psbt extends PsbtBase {
|
||||||
}
|
}
|
||||||
this.__TX.ins[inputIndex].sequence = sequence;
|
this.__TX.ins[inputIndex].sequence = sequence;
|
||||||
this.__TX_BUF_CACHE = undefined;
|
this.__TX_BUF_CACHE = undefined;
|
||||||
|
this.__EXTRACTED_TX = undefined;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,8 +214,33 @@ export class Psbt extends PsbtBase {
|
||||||
return super.addOutput(outputData, true, outputAdder);
|
return super.addOutput(outputData, true, outputAdder);
|
||||||
}
|
}
|
||||||
|
|
||||||
extractTransaction(): Transaction {
|
addNonWitnessUtxoToInput(
|
||||||
|
inputIndex: number,
|
||||||
|
nonWitnessUtxo: NonWitnessUtxo,
|
||||||
|
): this {
|
||||||
|
super.addNonWitnessUtxoToInput(inputIndex, nonWitnessUtxo);
|
||||||
|
const input = this.inputs[inputIndex];
|
||||||
|
addNonWitnessTxCache(this, input, inputIndex);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
extractTransaction(disableFeeCheck?: boolean): Transaction {
|
||||||
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
|
if (!this.inputs.every(isFinalized)) throw new Error('Not finalized');
|
||||||
|
if (!disableFeeCheck) {
|
||||||
|
const feeRate = this.__FEE_RATE || this.getFeeRate();
|
||||||
|
const vsize = this.__EXTRACTED_TX!.virtualSize();
|
||||||
|
const satoshis = feeRate * vsize;
|
||||||
|
if (feeRate >= this.opts.maximumFeeRate) {
|
||||||
|
throw new Error(
|
||||||
|
`Warning: You are paying around ${satoshis / 1e8} in fees, which ` +
|
||||||
|
`is ${feeRate} satoshi per byte for a transaction with a VSize of ` +
|
||||||
|
`${vsize} bytes (segwit counted as 0.25 byte per byte)\n` +
|
||||||
|
`Use setMaximumFeeRate method to raise your threshold, or pass ` +
|
||||||
|
`true to the first arg of extractTransaction.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.__EXTRACTED_TX) return this.__EXTRACTED_TX;
|
||||||
const tx = this.__TX.clone();
|
const tx = this.__TX.clone();
|
||||||
this.inputs.forEach((input, idx) => {
|
this.inputs.forEach((input, idx) => {
|
||||||
if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
|
if (input.finalScriptSig) tx.ins[idx].script = input.finalScriptSig;
|
||||||
|
@ -208,9 +250,56 @@ export class Psbt extends PsbtBase {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.__EXTRACTED_TX = tx;
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFeeRate(): number {
|
||||||
|
if (!this.inputs.every(isFinalized))
|
||||||
|
throw new Error('PSBT must be finalized to calculate fee rate');
|
||||||
|
if (this.__FEE_RATE) return this.__FEE_RATE;
|
||||||
|
let tx: Transaction;
|
||||||
|
let inputAmount = 0;
|
||||||
|
let mustFinalize = true;
|
||||||
|
if (this.__EXTRACTED_TX) {
|
||||||
|
tx = this.__EXTRACTED_TX;
|
||||||
|
mustFinalize = false;
|
||||||
|
} else {
|
||||||
|
tx = this.__TX.clone();
|
||||||
|
}
|
||||||
|
this.inputs.forEach((input, idx) => {
|
||||||
|
if (mustFinalize && input.finalScriptSig)
|
||||||
|
tx.ins[idx].script = input.finalScriptSig;
|
||||||
|
if (mustFinalize && input.finalScriptWitness) {
|
||||||
|
tx.ins[idx].witness = scriptWitnessToWitnessStack(
|
||||||
|
input.finalScriptWitness,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (input.witnessUtxo) {
|
||||||
|
inputAmount += input.witnessUtxo.value;
|
||||||
|
} else if (input.nonWitnessUtxo) {
|
||||||
|
// @ts-ignore
|
||||||
|
if (!this.__NON_WITNESS_UTXO_TX_CACHE[idx]) {
|
||||||
|
addNonWitnessTxCache(this, input, idx);
|
||||||
|
}
|
||||||
|
const vout = this.__TX.ins[idx].index;
|
||||||
|
const out = this.__NON_WITNESS_UTXO_TX_CACHE[idx].outs[vout] as Output;
|
||||||
|
inputAmount += out.value;
|
||||||
|
} else {
|
||||||
|
throw new Error('Missing input value: index #' + idx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.__EXTRACTED_TX = tx;
|
||||||
|
const outputAmount = (tx.outs as Output[]).reduce(
|
||||||
|
(total, o) => total + o.value,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const fee = inputAmount - outputAmount;
|
||||||
|
const bytes = tx.virtualSize();
|
||||||
|
this.__FEE_RATE = Math.floor(fee / bytes);
|
||||||
|
return this.__FEE_RATE;
|
||||||
|
}
|
||||||
|
|
||||||
finalizeAllInputs(): {
|
finalizeAllInputs(): {
|
||||||
result: boolean;
|
result: boolean;
|
||||||
inputResults: boolean[];
|
inputResults: boolean[];
|
||||||
|
@ -231,6 +320,7 @@ export class Psbt extends PsbtBase {
|
||||||
inputIndex,
|
inputIndex,
|
||||||
input,
|
input,
|
||||||
this.__TX,
|
this.__TX,
|
||||||
|
this,
|
||||||
);
|
);
|
||||||
if (!script) return false;
|
if (!script) return false;
|
||||||
|
|
||||||
|
@ -264,6 +354,7 @@ export class Psbt extends PsbtBase {
|
||||||
inputIndex,
|
inputIndex,
|
||||||
keyPair.publicKey,
|
keyPair.publicKey,
|
||||||
this.__TX,
|
this.__TX,
|
||||||
|
this,
|
||||||
);
|
);
|
||||||
|
|
||||||
const partialSig = {
|
const partialSig = {
|
||||||
|
@ -284,6 +375,7 @@ export class Psbt extends PsbtBase {
|
||||||
inputIndex,
|
inputIndex,
|
||||||
keyPair.publicKey,
|
keyPair.publicKey,
|
||||||
this.__TX,
|
this.__TX,
|
||||||
|
this,
|
||||||
);
|
);
|
||||||
|
|
||||||
Promise.resolve(keyPair.sign(hash)).then(signature => {
|
Promise.resolve(keyPair.sign(hash)).then(signature => {
|
||||||
|
@ -312,16 +404,58 @@ export class Psbt extends PsbtBase {
|
||||||
|
|
||||||
interface PsbtOptsOptional {
|
interface PsbtOptsOptional {
|
||||||
network?: Network;
|
network?: Network;
|
||||||
|
maximumFeeRate?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PsbtOpts {
|
interface PsbtOpts {
|
||||||
network: Network;
|
network: Network;
|
||||||
|
maximumFeeRate: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_OPTS = {
|
const DEFAULT_OPTS = {
|
||||||
network: btcNetwork,
|
network: btcNetwork,
|
||||||
|
maximumFeeRate: 5000, // satoshi per byte
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function addNonWitnessTxCache(
|
||||||
|
psbt: Psbt,
|
||||||
|
input: PsbtInput,
|
||||||
|
inputIndex: number,
|
||||||
|
): void {
|
||||||
|
// @ts-ignore
|
||||||
|
psbt.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo!;
|
||||||
|
|
||||||
|
const tx = Transaction.fromBuffer(input.nonWitnessUtxo!);
|
||||||
|
// @ts-ignore
|
||||||
|
psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = tx;
|
||||||
|
|
||||||
|
const self = psbt;
|
||||||
|
const selfIndex = inputIndex;
|
||||||
|
delete input.nonWitnessUtxo;
|
||||||
|
Object.defineProperty(input, 'nonWitnessUtxo', {
|
||||||
|
enumerable: true,
|
||||||
|
get(): Buffer {
|
||||||
|
// @ts-ignore
|
||||||
|
if (self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] !== undefined) {
|
||||||
|
// @ts-ignore
|
||||||
|
return self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex];
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
self.__NON_WITNESS_UTXO_BUF_CACHE[
|
||||||
|
selfIndex
|
||||||
|
// @ts-ignore
|
||||||
|
] = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex].toBuffer();
|
||||||
|
// @ts-ignore
|
||||||
|
return self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set(data: Buffer): void {
|
||||||
|
// @ts-ignore
|
||||||
|
self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function isFinalized(input: PsbtInput): boolean {
|
function isFinalized(input: PsbtInput): boolean {
|
||||||
return !!input.finalScriptSig || !!input.finalScriptWitness;
|
return !!input.finalScriptSig || !!input.finalScriptWitness;
|
||||||
}
|
}
|
||||||
|
@ -331,6 +465,7 @@ function getHashAndSighashType(
|
||||||
inputIndex: number,
|
inputIndex: number,
|
||||||
pubkey: Buffer,
|
pubkey: Buffer,
|
||||||
unsignedTx: Transaction,
|
unsignedTx: Transaction,
|
||||||
|
psbt: Psbt,
|
||||||
): {
|
): {
|
||||||
hash: Buffer;
|
hash: Buffer;
|
||||||
sighashType: number;
|
sighashType: number;
|
||||||
|
@ -340,6 +475,7 @@ function getHashAndSighashType(
|
||||||
inputIndex,
|
inputIndex,
|
||||||
input,
|
input,
|
||||||
unsignedTx,
|
unsignedTx,
|
||||||
|
psbt,
|
||||||
);
|
);
|
||||||
checkScriptForPubkey(pubkey, script);
|
checkScriptForPubkey(pubkey, script);
|
||||||
return {
|
return {
|
||||||
|
@ -490,13 +626,19 @@ const getHashForSig = (
|
||||||
inputIndex: number,
|
inputIndex: number,
|
||||||
input: PsbtInput,
|
input: PsbtInput,
|
||||||
unsignedTx: Transaction,
|
unsignedTx: Transaction,
|
||||||
|
psbt: Psbt,
|
||||||
): HashForSigData => {
|
): HashForSigData => {
|
||||||
const sighashType = input.sighashType || Transaction.SIGHASH_ALL;
|
const sighashType = input.sighashType || Transaction.SIGHASH_ALL;
|
||||||
let hash: Buffer;
|
let hash: Buffer;
|
||||||
let script: Buffer;
|
let script: Buffer;
|
||||||
|
|
||||||
if (input.nonWitnessUtxo) {
|
if (input.nonWitnessUtxo) {
|
||||||
const nonWitnessUtxoTx = Transaction.fromBuffer(input.nonWitnessUtxo);
|
// @ts-ignore
|
||||||
|
if (!psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex]) {
|
||||||
|
addNonWitnessTxCache(psbt, input, inputIndex);
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const nonWitnessUtxoTx = psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex];
|
||||||
|
|
||||||
const prevoutHash = unsignedTx.ins[inputIndex].hash;
|
const prevoutHash = unsignedTx.ins[inputIndex].hash;
|
||||||
const utxoHash = nonWitnessUtxoTx.getHash();
|
const utxoHash = nonWitnessUtxoTx.getHash();
|
||||||
|
@ -636,6 +778,7 @@ function getScriptFromInput(
|
||||||
inputIndex: number,
|
inputIndex: number,
|
||||||
input: PsbtInput,
|
input: PsbtInput,
|
||||||
unsignedTx: Transaction,
|
unsignedTx: Transaction,
|
||||||
|
psbt: Psbt,
|
||||||
): GetScriptReturn {
|
): GetScriptReturn {
|
||||||
const res: GetScriptReturn = {
|
const res: GetScriptReturn = {
|
||||||
script: null,
|
script: null,
|
||||||
|
@ -648,7 +791,12 @@ function getScriptFromInput(
|
||||||
res.isP2SH = true;
|
res.isP2SH = true;
|
||||||
res.script = input.redeemScript;
|
res.script = input.redeemScript;
|
||||||
} else {
|
} else {
|
||||||
const nonWitnessUtxoTx = Transaction.fromBuffer(input.nonWitnessUtxo);
|
// @ts-ignore
|
||||||
|
if (!psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex]) {
|
||||||
|
addNonWitnessTxCache(psbt, input, inputIndex);
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const nonWitnessUtxoTx = psbt.__NON_WITNESS_UTXO_TX_CACHE[inputIndex];
|
||||||
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
||||||
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
|
res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
|
||||||
}
|
}
|
||||||
|
|
12
types/psbt.d.ts
vendored
12
types/psbt.d.ts
vendored
|
@ -1,6 +1,6 @@
|
||||||
/// <reference types="node" />
|
/// <reference types="node" />
|
||||||
import { Psbt as PsbtBase } from 'bip174';
|
import { Psbt as PsbtBase } from 'bip174';
|
||||||
import { TransactionInput, TransactionOutput } from 'bip174/src/lib/interfaces';
|
import { NonWitnessUtxo, TransactionInput, TransactionOutput } from 'bip174/src/lib/interfaces';
|
||||||
import { Signer, SignerAsync } from './ecpair';
|
import { Signer, SignerAsync } from './ecpair';
|
||||||
import { Network } from './networks';
|
import { Network } from './networks';
|
||||||
import { Transaction } from './transaction';
|
import { Transaction } from './transaction';
|
||||||
|
@ -9,14 +9,21 @@ export declare class Psbt extends PsbtBase {
|
||||||
static fromBuffer<T extends typeof PsbtBase>(this: T, buffer: Buffer): InstanceType<T>;
|
static fromBuffer<T extends typeof PsbtBase>(this: T, buffer: Buffer): InstanceType<T>;
|
||||||
private __TX;
|
private __TX;
|
||||||
private __TX_BUF_CACHE?;
|
private __TX_BUF_CACHE?;
|
||||||
|
private __FEE_RATE?;
|
||||||
|
private __EXTRACTED_TX?;
|
||||||
|
private __NON_WITNESS_UTXO_TX_CACHE;
|
||||||
|
private __NON_WITNESS_UTXO_BUF_CACHE;
|
||||||
private opts;
|
private opts;
|
||||||
constructor(opts?: PsbtOptsOptional);
|
constructor(opts?: PsbtOptsOptional);
|
||||||
|
setMaximumFeeRate(satoshiPerByte: number): void;
|
||||||
setVersion(version: number): this;
|
setVersion(version: number): this;
|
||||||
setLocktime(locktime: number): this;
|
setLocktime(locktime: number): this;
|
||||||
setSequence(inputIndex: number, sequence: number): this;
|
setSequence(inputIndex: number, sequence: number): this;
|
||||||
addInput(inputData: TransactionInput): this;
|
addInput(inputData: TransactionInput): this;
|
||||||
addOutput(outputData: TransactionOutput): this;
|
addOutput(outputData: TransactionOutput): this;
|
||||||
extractTransaction(): Transaction;
|
addNonWitnessUtxoToInput(inputIndex: number, nonWitnessUtxo: NonWitnessUtxo): this;
|
||||||
|
extractTransaction(disableFeeCheck?: boolean): Transaction;
|
||||||
|
getFeeRate(): number;
|
||||||
finalizeAllInputs(): {
|
finalizeAllInputs(): {
|
||||||
result: boolean;
|
result: boolean;
|
||||||
inputResults: boolean[];
|
inputResults: boolean[];
|
||||||
|
@ -27,5 +34,6 @@ export declare class Psbt extends PsbtBase {
|
||||||
}
|
}
|
||||||
interface PsbtOptsOptional {
|
interface PsbtOptsOptional {
|
||||||
network?: Network;
|
network?: Network;
|
||||||
|
maximumFeeRate?: number;
|
||||||
}
|
}
|
||||||
export {};
|
export {};
|
||||||
|
|
Loading…
Add table
Reference in a new issue