import { Bytes } from './bytes.js'; import { binToHex, hexToBin, encodeTransaction, decodeTransaction, decodeTransactionBCH, hashTransaction, } from '@bitauth/libauth'; import type { ResolvedTransactionBCH, TransactionBCH } from '@bitauth/libauth'; class BaseTransaction { protected readonly transaction: TransactionBCH; protected constructor(transaction: TransactionBCH) { this.transaction = transaction; } /** * Serializes the transaction to a byte array. * @returns The transaction serialized as a Uint8Array */ public toBytes(): Uint8Array { const bytes = encodeTransaction(this.transaction); return bytes; } /** * Serializes the transaction to a byte array (represented as a hex string). * @returns The transaction serialized in hex */ public toHex(): string { const bytes = this.toBytes(); return binToHex(bytes); } /** * Hashes the transaction and returns a TransactionHash primitive. * @returns The transaction hash primitive. */ public toHash(): TransactionHash { return TransactionHash.fromTransactionBytes(this.toBytes()); } /** * Hashes the transaction and returns the hash as hexadecimal. * @returns The transaction hash hex. */ public toHashHex(): string { return this.toHash().toHex(); } /** * Hashes the transaction and returns the hash as a Uint8Array. * @returns The transaction hash bytes. */ public toHashBytes(): Uint8Array { return this.toHash().toBytes(); } /** * Gets the version for this transaction. * * @experimental * * @returns The version of this transaction */ public getVersion() { return this.transaction.version; } /** * Gets the inputs in this transaction. * * @experimental * * @returns The inputs in this transaction */ public getInputs() { return this.transaction.inputs; } /** * Gets the outputs in this transaction. * * @experimental * * @returns The outputs in this transaction */ public getOutputs() { return this.transaction.outputs; } /** * Gets the locktime for this transaction. * * @experimental * * @returns The locktime of this transaction */ public getLocktime() { return this.transaction.locktime; } /** * Checks whether this transaction is a Coinbase Transaction. * * @remarks UNTESTED * * @experimental * * @returns True if this is a Coinbase 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; } } /** * Transaction Primitive. * * @example * * ```ts * // Import our primitives. * import { Transaction } from '@xocash/primitive'; * * // Instantiate primitive from Transaction Hex. * const transaction = Transaction.from('transactionHex'); * * // Output Transaction Information. * console.log(transaction.getVersion()); * console.log(transaction.getLocktime()); * console.log(transaction.getInputs()); * console.log(transaction.getOutputs()); * * // Output the Transaction Hash. * console.log(transaction.toHash().toHex()); * ``` */ export class Transaction extends BaseTransaction { protected constructor(transaction: TransactionBCH) { super(transaction); } /** * 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) { return transaction instanceof Uint8Array ? this.fromBytes(transaction) : this.fromHex(transaction); } /** * 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 this(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 { const bytes = Bytes.fromHex(hex).toBytes(); return this.fromBytes(bytes); } /** * Create a Transaction Primitive from LibAuth's TransactionBCH type. * * @remarks This method is UNSAFE and MUST only be used where input is already verified or trusted. * * @param transaction The transaction object (as per LibAuth). * * @returns The created Transaction Primitive. */ public static fromRaw(transaction: TransactionBCH) { return new this(transaction); } /** * Validates the structure of a transaction. * * @param transaction The transaction (as hex or bytes). * * @returns boolean indicating whether the transaction structure is valid. */ public static isValid(transaction: Uint8Array | string): boolean { try { this.from(transaction); } catch (_error) { return false; } return true; } /** * Converts transaction to a raw object representation. * @returns A TransactionBCH LibAuth object. */ public toRaw(): TransactionBCH { return { ...this.transaction }; } } export class TransactionResolved extends BaseTransaction { protected sourceOutputs: Array; protected constructor( transaction: TransactionBCH, sourceOutputs: Array, ) { super(transaction); this.sourceOutputs = sourceOutputs; } /** * 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, sourceOutputs: Array, ) { return transaction instanceof Uint8Array ? this.fromBytes(transaction, sourceOutputs) : this.fromHex(transaction, sourceOutputs); } /** * 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, sourceOutputs: Array) { // Attempt to decode the raw transaction bytes. const decodeResult = decodeTransactionBCH(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 this(decodeResult, sourceOutputs); } /** * 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, sourceOutputs: Array) { const bytes = Bytes.fromHex(hex).toBytes(); return this.fromBytes(bytes, sourceOutputs); } /** * Create a Transaction Primitive from LibAuth's TransactionBCH type. * * @remarks This method is UNSAFE and MUST only be used where input is already verified or trusted. * * @param transaction The transaction object (as per LibAuth). * * @returns The created Transaction Primitive. */ public static fromRaw(resolvedTransaction: ResolvedTransactionBCH) { return new this( resolvedTransaction.transaction, resolvedTransaction.sourceOutputs, ); } /** * Validates the structure of a transaction. * * @param transaction The transaction (as hex or bytes). * * @returns boolean indicating whether the transaction structure is valid. */ public static isValid( transaction: Uint8Array | string, sourceOutputs: Array, ): boolean { try { this.from(transaction, sourceOutputs); } catch (_error) { return false; } return true; } public toRaw() { return { transaction: this.transaction, sourceOutputs: this.sourceOutputs, }; } } /** * Transaction Hash Primitive. */ export class TransactionHash { /** * Instantiates TransactionHash from a Transaction Hash's bytes. * * @param bytes {Uint8Array} The Transaction Hash's bytes. */ protected constructor(protected readonly bytes: Uint8Array) { this.bytes = bytes; } /** * Instantiates a TransactionHash from either the Hash Hex or Hash Bytes. * * @param hash The Transaction Hash as either hex or bytes. * * @throws {Error} If the hash is invalid. * * @returns The created Public Key Primitive. */ public static from(hash: Uint8Array | string) { return hash instanceof Uint8Array ? this.fromBytes(hash) : this.fromHex(hash); } /** * Instantiates TransactionHash from Hash Bytes. * * @param bytes The hash as bytes. * * @throws {Error} If hash is invalid. * * @returns {TransactionHash} Instance of TransactionHash. */ public static fromBytes(bytes: Uint8Array): TransactionHash { // If bytes is not exactly 32, then this is an invalid transaction hash... if (bytes.length !== 32) { throw new Error( `Invalid Transaction Hash: Expected 32 bytes but received ${bytes.length}`, ); } return new this(bytes); } /** * Instantiates TransactionHash from a hex string. * * @param hex {string} The hex string. * * @throws {Error} If hex is invalid. * * @returns {TransactionHash} Instance of TransactionHash. */ public static fromHex(hex: string): TransactionHash { const bytes = Bytes.fromHex(hex).toBytes(); return this.fromBytes(bytes); } /** * Instantiates a TransactionHash from either the Transaction Hex or Transaction Hash. * * @param hash The Transaction as either hex or bytes. * * @throws {Error} If the public key cannot be decoded. * * @returns The created Public Key Primitive. */ public static fromTransaction(transaction: Uint8Array | string) { return transaction instanceof Uint8Array ? this.fromTransactionBytes(transaction) : this.fromTransactionHex(transaction); } /** * Instantiates TransactionHash from a Transaction's bytes. * * @param transactionBytes {Uint8Array} The Transaction's bytes. * * @returns {TransactionHash} Instance of TransactionHash. */ public static fromTransactionBytes( transactionBytes: Uint8Array, ): TransactionHash { const transactionHash = hashTransaction(transactionBytes); return new this(hexToBin(transactionHash)); } /** * Instantiates TransactionHash from a Transaction's bytes in hex. * * @param transactionHex {Uint8Array} The Transaction's bytes in hex. * * @throws {Error} If hex is invalid. * * @returns {TransactionHash} Instance of TransactionHash. */ public static fromTransactionHex(transactionHex: string): TransactionHash { const bytes = Bytes.fromHex(transactionHex).toBytes(); return this.fromTransactionBytes(bytes); } /** * Creates an instance from serialized data. * * @remarks This method is UNSAFE and MUST only be used where input is already verified or trusted. * * @param bytes - Serialized Primitive Data * * @returns A new instance of this primitive. */ public static fromRaw(bytes: Uint8Array) { return new this(bytes); } /** * Returns transaction hash as a hex string. * * @returns {string} Hex string of the Transaction Hash. */ public toHex(): string { return binToHex(this.bytes); } /** * Returns transaction hash as a Uint8Array. * * @returns {Uint8Array} Uint8Array of the Transaction Hash. */ public toBytes(): Uint8Array { return this.bytes; } /** * Converts this primitive to a serializable representation. * * @returns Serialzied primitive data. */ toRaw() { return this.bytes; } }