233 lines
9.0 KiB
TypeScript
233 lines
9.0 KiB
TypeScript
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<BlockchainEvents>}
|
|
*/
|
|
export abstract class BaseBlockchain<
|
|
AddressNotificationPayload = undefined,
|
|
TransactionNotificationPayload = undefined,
|
|
> extends EventEmitter<BlockchainEvents> {
|
|
// 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<void>} A promise that resolves when the blockchain connection is established
|
|
* @throws {Error} If the connection cannot be established
|
|
*/
|
|
abstract start(): Promise<void>;
|
|
|
|
/**
|
|
* Gracefully terminates the blockchain connection and cleans up resources.
|
|
* Should be called when blockchain operations are no longer needed.
|
|
*
|
|
* @returns {Promise<void>} A promise that resolves when the shutdown is complete
|
|
*/
|
|
abstract stop(): Promise<void>;
|
|
|
|
//-----------------------------------------------------------------------
|
|
// 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<BlockchainUTXOs>} A promise that resolves with the unspent outputs
|
|
* @throws {Error} If the address is invalid or the query fails
|
|
*/
|
|
abstract fetchUnspents(address: string): Promise<BlockchainUTXOs>;
|
|
|
|
/**
|
|
* Retrieves all transactions associated with a specific address.
|
|
*
|
|
* @param {string} address - The blockchain address to query
|
|
* @returns {Promise<BlockchainTransactions>} 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<BlockchainTransactions>;
|
|
|
|
/**
|
|
* Retrieves detailed information about a specific transaction.
|
|
*
|
|
* @param {string} txHash - The transaction hash/ID to look up
|
|
* @returns {Promise<Transaction>} 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<Transaction>;
|
|
|
|
/**
|
|
* Broadcasts a raw transaction to the blockchain network.
|
|
*
|
|
* @param {Uint8Array} transaction - The serialized transaction data to broadcast
|
|
* @returns {Promise<void>} 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<void>;
|
|
|
|
/**
|
|
* 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<void>} A promise that resolves when the subscription is established
|
|
* @throws {Error} If the subscription cannot be created
|
|
*/
|
|
abstract subscribeLockscript(
|
|
lockscript: string | Uint8Array,
|
|
callback: BlockchainCallback<AddressNotificationPayload>,
|
|
): Promise<BlockchainUnsubscribeCallback>;
|
|
|
|
/**
|
|
* 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<void>} A promise that resolves when the subscription is removed
|
|
* @throws {Error} If the subscription cannot be removed
|
|
*/
|
|
abstract unsubscribeLockscript(
|
|
lockscript: string | Uint8Array,
|
|
callback: BlockchainCallback<AddressNotificationPayload>,
|
|
): Promise<boolean>;
|
|
|
|
/**
|
|
* 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<void>} A promise that resolves when the subscription is established
|
|
* @throws {Error} If the subscription cannot be created
|
|
*/
|
|
abstract subscribeAddress(
|
|
address: string,
|
|
callback: BlockchainCallback<AddressNotificationPayload>,
|
|
): Promise<BlockchainUnsubscribeCallback>;
|
|
|
|
/**
|
|
* 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<void>} A promise that resolves when the subscription is removed
|
|
* @throws {Error} If the subscription cannot be removed
|
|
*/
|
|
abstract unsubscribeAddress(
|
|
address: string,
|
|
callback: BlockchainCallback<AddressNotificationPayload>,
|
|
): Promise<boolean>;
|
|
|
|
/**
|
|
* 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<void>} A promise that resolves when the subscription is established
|
|
* @throws {Error} If the subscription cannot be created
|
|
*/
|
|
abstract subscribeTransaction(
|
|
transactionHash: string,
|
|
callback: BlockchainCallback<TransactionNotificationPayload>,
|
|
): Promise<BlockchainUnsubscribeCallback>;
|
|
|
|
/**
|
|
* 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<void>} A promise that resolves when the subscription is removed
|
|
* @throws {Error} If the subscription cannot be removed
|
|
*/
|
|
abstract unsubscribeTransaction(
|
|
transactionHash: string,
|
|
callback: BlockchainCallback<TransactionNotificationPayload>,
|
|
): Promise<boolean>;
|
|
|
|
/**
|
|
* 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<void>((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;
|
|
}
|
|
}
|