sensible-sdk
Version:
Sensible-SDK
197 lines (196 loc) • 7.38 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TxComposer = exports.sighashType = void 0;
const bsv = require("../bsv");
const utils_1 = require("../common/utils");
const scryptlib_1 = require("../scryptlib");
const Signature = bsv.crypto.Signature;
exports.sighashType = Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID;
const P2PKH_UNLOCK_SIZE = 1 + 1 + 71 + 1 + 33;
const P2PKH_DUST_AMOUNT = 1;
class TxComposer {
constructor(tx) {
this.sigHashList = [];
this.changeOutputIndex = -1;
this.tx = tx || new bsv.Transaction();
}
toObject() {
let composer = {
tx: this.tx.toObject(),
sigHashList: this.sigHashList,
changeOutputIndex: this.changeOutputIndex,
};
return composer;
}
static fromObject(composerObj) {
let txObj = composerObj.tx;
let tx = new bsv.Transaction();
txObj.inputs.forEach((v) => {
tx.addInput(new bsv.Transaction.Input(v));
});
txObj.outputs.forEach((v) => {
tx.addOutput(new bsv.Transaction.Output(v));
});
tx.nLockTime = txObj.nLockTime;
tx.version = txObj.version;
let txComposer = new TxComposer(tx);
txComposer.sigHashList = composerObj.sigHashList;
txComposer.changeOutputIndex = composerObj.changeOutputIndex;
return txComposer;
}
getRawHex() {
return this.tx.serialize(true);
}
getTx() {
return this.tx;
}
getTxId() {
return this.tx.id;
}
getInput(inputIndex) {
return this.tx.inputs[inputIndex];
}
getOutput(outputIndex) {
return this.tx.outputs[outputIndex];
}
appendP2PKHInput(utxo) {
this.tx.addInput(new bsv.Transaction.Input.PublicKeyHash({
output: new bsv.Transaction.Output({
script: bsv.Script.buildPublicKeyHashOut(utxo.address),
satoshis: utxo.satoshis,
}),
prevTxId: utxo.txId,
outputIndex: utxo.outputIndex,
script: bsv.Script.empty(),
}));
const inputIndex = this.tx.inputs.length - 1;
return inputIndex;
}
appendInput(input) {
this.tx.addInput(new bsv.Transaction.Input({
output: new bsv.Transaction.Output({
script: input.lockingScript,
satoshis: input.satoshis,
}),
prevTxId: input.txId,
outputIndex: input.outputIndex,
script: bsv.Script.empty(),
}));
const inputIndex = this.tx.inputs.length - 1;
return inputIndex;
}
appendP2PKHOutput(output) {
this.tx.addOutput(new bsv.Transaction.Output({
script: new bsv.Script(output.address),
satoshis: output.satoshis,
}));
const outputIndex = this.tx.outputs.length - 1;
return outputIndex;
}
appendOutput(output) {
this.tx.addOutput(new bsv.Transaction.Output({
script: output.lockingScript,
satoshis: output.satoshis,
}));
const outputIndex = this.tx.outputs.length - 1;
return outputIndex;
}
appendOpReturnOutput(opreturnData) {
this.tx.addOutput(new bsv.Transaction.Output({
script: bsv.Script.buildSafeDataOut(opreturnData),
satoshis: 0,
}));
const outputIndex = this.tx.outputs.length - 1;
return outputIndex;
}
clearChangeOutput() {
if (this.changeOutputIndex != -1) {
this.tx.outputs.splice(this.changeOutputIndex, 1);
this.changeOutputIndex = 0;
}
}
appendChangeOutput(changeAddress, feeb = 0.05, extraSize = 0) {
//Calculate the fee and determine whether to change
//If there is change, it will be output in the last item
const unlockSize = this.tx.inputs.filter((v) => v.output.script.isPublicKeyHashOut())
.length * P2PKH_UNLOCK_SIZE;
let fee = Math.ceil((this.tx.toBuffer().length +
unlockSize +
extraSize +
bsv.Transaction.CHANGE_OUTPUT_MAX_SIZE) *
feeb);
let changeAmount = this.getUnspentValue() - fee;
if (changeAmount >= P2PKH_DUST_AMOUNT) {
this.changeOutputIndex = this.appendP2PKHOutput({
address: changeAddress,
satoshis: changeAmount,
});
}
else {
this.changeOutputIndex = -1;
}
return this.changeOutputIndex;
}
unlockP2PKHInput(privateKey, inputIndex, sigtype = exports.sighashType) {
const tx = this.tx;
const sig = new bsv.Transaction.Signature({
publicKey: privateKey.publicKey,
prevTxId: tx.inputs[inputIndex].prevTxId,
outputIndex: tx.inputs[inputIndex].outputIndex,
inputIndex,
signature: bsv.Transaction.Sighash.sign(tx, privateKey, sigtype, inputIndex, tx.inputs[inputIndex].output.script, tx.inputs[inputIndex].output.satoshisBN),
sigtype,
});
tx.inputs[inputIndex].setScript(bsv.Script.buildPublicKeyHashIn(sig.publicKey, sig.signature.toDER(), sig.sigtype));
}
getTxFormatSig(privateKey, inputIndex, sigtype = exports.sighashType) {
let sig = (0, scryptlib_1.signTx)(this.tx, privateKey, this.getInput(inputIndex).output.script.toASM(), this.getInput(inputIndex).output.satoshis, inputIndex, sigtype);
return sig;
}
getInputPreimage(inputIndex, sigtype = exports.sighashType) {
return (0, scryptlib_1.getPreimage)(this.tx, this.getInput(inputIndex).output.script.toASM(), this.getInput(inputIndex).output.satoshis, inputIndex, sigtype);
}
getUnspentValue() {
const inputAmount = this.tx.inputs.reduce((pre, cur) => cur.output.satoshis + pre, 0);
const outputAmount = this.tx.outputs.reduce((pre, cur) => cur.satoshis + pre, 0);
let unspentAmount = inputAmount - outputAmount;
return unspentAmount;
}
getFeeRate() {
let unspent = this.getUnspentValue();
let txSize = this.tx.toBuffer().length;
return unspent / txSize;
}
getSigHashLit() {
this.sigHashList.forEach((v) => {
v.sighash = (0, scryptlib_1.toHex)(bsv.Transaction.Sighash.sighash(this.tx, v.sighashType, v.inputIndex, this.getInput(v.inputIndex).output.script, this.getInput(v.inputIndex).output.satoshisBN));
});
return this.sigHashList;
}
addSigHashInfo({ inputIndex, address, sighashType, contractType, }) {
this.sigHashList.push({
inputIndex,
address,
sighash: "",
sighashType,
contractType,
});
}
getPrevoutsHash() {
let prevouts = Buffer.alloc(0);
this.tx.inputs.forEach((input) => {
const indexBuf = Buffer.alloc(4, 0);
indexBuf.writeUInt32LE(input.outputIndex);
prevouts = Buffer.concat([
prevouts,
Buffer.from(input.prevTxId).reverse(),
indexBuf,
]);
});
return bsv.crypto.Hash.sha256sha256(prevouts).toString("hex");
}
dumpTx(network) {
(0, utils_1.dumpTx)(this.tx, network);
}
}
exports.TxComposer = TxComposer;