@coolwallet/kas
Version:
135 lines (124 loc) • 4.62 kB
text/typescript
import { ScriptType, TransactionInput, TransactionOutput, TransactionUtxo, TxData, TxInfo } from '../config/types';
import {
addressToOutScript,
getPubkeyOrScriptHash,
pubkeyOrScriptHashToPayment,
} from '../utils/address';
import { toHex } from '../utils/utils';
import { SIGHASH_ALL } from '../utils/hash';
import BigNumber from 'bignumber.js';
import { getMassAndSize, minimumRequiredTransactionRelayFee } from '../utils/estimateFee';
export class Transaction {
version: number = 0;
inputs: TransactionInput[] = [];
outputs: TransactionOutput[] = [];
lockTime: number = 0;
subnetworkId: string = '0000000000000000000000000000000000000000';
utxos: TransactionUtxo[] = [];
feeValue: string = '0';
static fromTxData(txData: TxData): Transaction {
return new Transaction(txData);
}
constructor(txData: TxData) {
let totalInput = 0;
txData.inputs.forEach((input) => {
this.inputs.push({
previousOutpoint: {
transactionId: input.preTxHash,
index: input.preIndex,
},
signatureScript: '',
sequence: 0,
sigOpCount: 1,
addressIndex: input.addressIndex,
});
const inputPreValueBN = new BigNumber(input.preValue);
const pubKey = input.pubkeyBuf?.toString('hex') as string;
const inputScriptType = input.scriptType as ScriptType;
const { pubkeyOrScriptHash, addressVersion } = getPubkeyOrScriptHash(inputScriptType, pubKey);
this.utxos.push({
version: 0,
pkScript: pubkeyOrScriptHashToPayment(pubkeyOrScriptHash, addressVersion).outScript,
scriptType: inputScriptType,
amount: inputPreValueBN.toNumber(),
});
totalInput = inputPreValueBN.plus(new BigNumber(totalInput)).toNumber();
});
let totalOutput = 0;
const output = txData.output;
const outputValueBN = new BigNumber(output.value);
const { scriptType: outputScriptType, outScript, outPubkeyOrHash } = addressToOutScript(output.address);
this.outputs.push({
scriptPublicKey: {
version: 0,
scriptType: outputScriptType,
scriptPublicKey: toHex(outScript),
publicKeyOrScriptHash: toHex(outPubkeyOrHash),
},
amount: outputValueBN.toNumber(),
});
totalOutput += outputValueBN.plus(new BigNumber(totalOutput)).toNumber();
this.feeValue = new BigNumber(totalInput).minus(new BigNumber(totalOutput)).toFixed();
const change = txData.change;
if (change) {
const changeValueBN = new BigNumber(change.value);
const pubKey = change.pubkeyBuf?.toString('hex') as string;
const changeScriptType = change.scriptType as ScriptType;
const { pubkeyOrScriptHash, addressVersion } = getPubkeyOrScriptHash(changeScriptType, pubKey);
const { outScript: changeScript } = pubkeyOrScriptHashToPayment(pubkeyOrScriptHash, addressVersion);
this.outputs.push({
scriptPublicKey: {
version: 0,
scriptType: changeScriptType,
scriptPublicKey: toHex(changeScript),
publicKeyOrScriptHash: pubkeyOrScriptHash,
},
amount: changeValueBN.toNumber(),
addressIndex: change.addressIndex,
});
this.feeValue = new BigNumber(this.feeValue).minus(changeValueBN).toFixed();
}
}
addSignatures(signatures: Buffer[]): this {
this.inputs.forEach((input, i) => {
if (input.signatureScript.length > 0) return;
const signature = signatures[i];
input.signatureScript = toHex(Buffer.concat([Buffer.from([0x41]), signature, Buffer.from([SIGHASH_ALL])]));
});
return this;
}
getTxInfo(): TxInfo {
return getMassAndSize(this.inputs, this.outputs);
}
estimateFee(feeRate = 1000): number {
const { mass } = this.getTxInfo();
return minimumRequiredTransactionRelayFee(mass, feeRate);
}
getMessage(): string {
return JSON.stringify({
transaction: {
version: this.version,
inputs: this.inputs.map(({ previousOutpoint, signatureScript, sequence, sigOpCount }) => {
return {
previousOutpoint,
signatureScript,
sequence,
sigOpCount,
};
}),
outputs: this.outputs.map(({ amount, scriptPublicKey }) => {
return {
amount,
scriptPublicKey: {
version: 0, // TODO: USE REAL SCRIPT VERSION
scriptPublicKey: scriptPublicKey.scriptPublicKey,
},
};
}),
lockTime: this.lockTime,
subnetworkId: this.subnetworkId,
},
allowOrphan: false,
});
}
}