Files
xo-stack/packages/primitives/src/transaction.ts
T

167 lines
4.3 KiB
TypeScript

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