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, }); } }