import { TransactionHash } from './transaction-hash.js'; import { binToHex, hexToBin, encodeTransaction, decodeTransaction, isHex, } from '@bitauth/libauth'; import type { TransactionBCH } from '@bitauth/libauth'; /** * Transaction Primitive. */ export class Transaction { constructor(public transaction: TransactionBCH) {} /** * Instantiates a transaction from either: * * 1. Transaction bytecode * 2. Transaction bytecode (in hexadecimal format) * * @param transaction The transaction in one of the above formats. * * @throws {Error} If transaction data cannot be decoded. * * @returns The created Transaction Primitive. */ public static from(transaction: Uint8Array | string) { // Handle string (hex) case if (typeof transaction === 'string') { return Transaction.fromHex(transaction); } // Handle Uint8Array case if (transaction instanceof Uint8Array) { return Transaction.fromBytes(transaction); } throw new TypeError( 'Invalid transaction input type. Expected Uint8Array, or hex string', ); } /** * Create a Transaction Primitive from the given byte data. * * @param bytes The raw byte data of the transaction. * * @throws {Error} If transaction data cannot be decoded. * * @returns {Transaction} The created Transaction Primitive. */ public static fromBytes(bytes: Uint8Array): Transaction { // Attempt to decode the raw transaction bytes. const decodeResult = decodeTransaction(bytes); // If a string is returned, this indicates an error... if (typeof decodeResult === 'string') { throw new Error(decodeResult); } // Return a new instance of the Transaction Primitive created from the decode result. return new Transaction(decodeResult); } /** * Create a Transaction Primitive from the given byte data encoded as hexadecimal string. * * @param hex The raw byte data (encoded as hexadecimal string) of the transaction. * * @throws {Error} If transaction cannot be decoded. * * @returns {Transaction} The created Transaction Primitive. */ public static fromHex(hex: string): Transaction { // Ensure that the given string is hex. if (!isHex(hex)) { throw new Error('The given string is not valid hexadecimal.'); } // Convert our hex to binary. const bytes = hexToBin(hex); // Return the transaction constructed from our bytes. return Transaction.fromBytes(bytes); } /** * Create a Transaction Primitive from LibAuth's TransactionBCH type. * * @remarks This method is UNSAFE and should only be used where input is trusted. * * @param transaction The transaction object (as per LibAuth). * * @returns The created Transaction Primitive. */ public static fromRaw(transaction: TransactionBCH) { return new this(transaction); } public toBytes(): Uint8Array { const bytes = encodeTransaction(this.transaction); return bytes; } public toHex(): string { const bytes = this.toBytes(); return binToHex(bytes); } public toHash(): TransactionHash { return TransactionHash.fromTransactionBytes(this.toBytes()); } public toHashHex(): string { return this.toHash().toHex(); } public toHashBytes(): Uint8Array { return this.toHash().toBytes(); } public toRaw(): TransactionBCH { return this.transaction; } public isCoinbase(): boolean { // A coinbase transaction MUST only have one input if (this.getInputs().length !== 1) { return false; } const input = this.getInputs()[0]; // Check if the outpoint transaction hash is all zeros (32 bytes of 0x00) const isOutpointZero = input.outpointTransactionHash.every( (byte) => byte === 0x00, ); // Check if the outpoint index is 0xFFFFFFFF (or -1 in signed 32-bit integer representation) const isOutpointIndexMax = input.outpointIndex === 0xffffffff; // Both conditions must be true for a coinbase transaction return isOutpointZero && isOutpointIndexMax; } public getVersion() { return this.transaction.version; } public getInputs() { return this.transaction.inputs; } public getOutputs() { return this.transaction.outputs; } public getLocktime() { return this.transaction.locktime; } }