import { EventEmitter } from '../utils/index.js'; import { type BlockchainCallback, type BlockchainUTXOs, type BlockchainTransactions, type BlockchainUnsubscribeCallback, } from './types.js'; import { Transaction } from '@xocash/primitives'; export type BlockchainEvents = { isConnectedUpdated: boolean; blockHeightUpdated: number | undefined; }; /** * Abstract base class representing a blockchain interface implementation. * Extends EventEmitter to provide blockchain-specific event handling capabilities. * * This class serves as a template for concrete blockchain implementations, * providing a standardized interface for common blockchain operations such as * fetching transactions, managing UTXOs, and handling blockchain subscriptions. * * @abstract * @extends {EventEmitter} */ export abstract class BaseBlockchain< AddressNotificationPayload = undefined, TransactionNotificationPayload = undefined, > extends EventEmitter { // TODO: Lockscript might be better as the Subscribe Handler. // From this, we can convert it to address. // Fulcrum has Scripthash too - but I'm hesitant about this. // Scripthash means SHA256'ing the Lockscript. // This is fine - but, at scale, will become expensive. // For example, imagine a HDWallet with 2000+ addresses. // And we want to subscribe to all of them. // Alternatively, we could just pass in an Address Primitive? // And then have the Adapter decide what it wants to use? /** * Initializes the blockchain connection and required resources. * Should be called before performing any blockchain operations. * * @returns {Promise} A promise that resolves when the blockchain connection is established * @throws {Error} If the connection cannot be established */ abstract start(): Promise; /** * Gracefully terminates the blockchain connection and cleans up resources. * Should be called when blockchain operations are no longer needed. * * @returns {Promise} A promise that resolves when the shutdown is complete */ abstract stop(): Promise; //----------------------------------------------------------------------- // Traits (refer to traits.ts for docs) //----------------------------------------------------------------------- /** * Retrieves all unspent transaction outputs (UTXOs) for a given address. * * @param {string} address - The blockchain address to query * @returns {Promise} A promise that resolves with the unspent outputs * @throws {Error} If the address is invalid or the query fails */ abstract fetchUnspents(address: string): Promise; /** * Retrieves all transactions associated with a specific address. * * @param {string} address - The blockchain address to query * @returns {Promise} A promise that resolves with the list of transactions * @throws {Error} If the address is invalid or the query fails */ abstract fetchAddressHistory( address: string, ): Promise; /** * Retrieves detailed information about a specific transaction. * * @param {string} txHash - The transaction hash/ID to look up * @returns {Promise} A promise that resolves with the transaction details * @throws {Error} If the transaction cannot be found or the query fails */ abstract fetchTransaction(txHash: string): Promise; /** * Broadcasts a raw transaction to the blockchain network. * * @param {Uint8Array} transaction - The serialized transaction data to broadcast * @returns {Promise} A promise that resolves when the transaction is successfully broadcast * @throws {Error} If the transaction is invalid or broadcasting fails */ abstract broadcastTransaction(transaction: Uint8Array): Promise; /** * Subscribes to updates for a specific lockscript. * The provided callback will be invoked when relevant events occur for the address. * * @param lockscript - The blockchain lockscript to monitor * @param callback - The callback function to handle address-related events * @returns {Promise} A promise that resolves when the subscription is established * @throws {Error} If the subscription cannot be created */ abstract subscribeLockscript( lockscript: string | Uint8Array, callback: BlockchainCallback, ): Promise; /** * Removes a subscription for a specific blockchain address. * * @param lockscript - The blockchain lockscript to unsubscribe from * @param callback - The callback function to remove * @returns {Promise} A promise that resolves when the subscription is removed * @throws {Error} If the subscription cannot be removed */ abstract unsubscribeLockscript( lockscript: string | Uint8Array, callback: BlockchainCallback, ): Promise; /** * Subscribes to updates for a specific blockchain address. * The provided callback will be invoked when relevant events occur for the address. * * @param {string} address - The blockchain address to monitor * @param {AddressCallback} callback - The callback function to handle address-related events * @returns {Promise} A promise that resolves when the subscription is established * @throws {Error} If the subscription cannot be created */ abstract subscribeAddress( address: string, callback: BlockchainCallback, ): Promise; /** * Removes a subscription for a specific blockchain address. * * @param {string} address - The blockchain address to unsubscribe from * @param {AddressCallback} callback - The callback function to remove * @returns {Promise} A promise that resolves when the subscription is removed * @throws {Error} If the subscription cannot be removed */ abstract unsubscribeAddress( address: string, callback: BlockchainCallback, ): Promise; /** * Subscribes to updates for a specific transaction. * The provided callback will be invoked when relevant events occur for the transaction. * * @param {string} transactionHash - The transaction hash to monitor * @param {TransactionCallback} callback - The callback function to handle transaction-related events * @returns {Promise} A promise that resolves when the subscription is established * @throws {Error} If the subscription cannot be created */ abstract subscribeTransaction( transactionHash: string, callback: BlockchainCallback, ): Promise; /** * Removes a subscription for a specific transaction. * * @param {string} transactionHash - The transaction hash to unsubscribe from * @param {TransactionCallback} callback - The callback function to remove * @returns {Promise} A promise that resolves when the subscription is removed * @throws {Error} If the subscription cannot be removed */ abstract unsubscribeTransaction( transactionHash: string, callback: BlockchainCallback, ): Promise; /** * Waits for a transaction to be seen by the node. * * @param tx - The transaction to wait for. Can be a Uint8Array, Transaction instance, or a string. * @param timeoutMs - The maximum time to wait in milliseconds before timing out. Defaults to 10,000ms (10 seconds). * @returns A Promise that resolves when the transaction is seen by the node or rejects if the timeout is reached. * @throws Error if the transaction is not seen by the node within the specified timeout. */ async waitForTransaction( tx: Uint8Array | Transaction | string, timeoutMs = 10_000, ) { // Cast the transaction. if (!(tx instanceof Transaction)) { tx = Transaction.from(tx); } const txHash = tx.toHashHex(); // First, subscribe to the transaction let unsubscribeCallback: () => void; const subscriptionPromise = this.subscribeTransaction(txHash, () => { if (unsubscribeCallback) { unsubscribeCallback(); } }); // Then create the wait promise without an async executor const waitForTxPromise = new Promise((resolve, reject) => { const timeout = setTimeout(() => { if (unsubscribeCallback) { unsubscribeCallback(); } reject(new Error(`Failed to broadcast ${txHash}`)); }, timeoutMs); // Create a subscribe function so that we get notified when transaction is seen by network unsubscribeCallback = () => { clearTimeout(timeout); this.unsubscribeTransaction(txHash, unsubscribeCallback); resolve(); }; }); // Wait for subscription to be set up first await subscriptionPromise; // Then wait for either resolution or timeout return waitForTxPromise; } }