Blockchain Traits

This commit is contained in:
Jim Hamill
2025-04-20 02:22:30 +00:00
parent b097abbd1c
commit 22bff76a55
13 changed files with 302 additions and 189 deletions
@@ -1,7 +1,6 @@
import { BaseBlockchain } from './base-blockchain.js';
import {
type AddressCallback,
type TransactionCallback,
type BlockchainCallback,
type BlockchainTransaction,
type BlockchainUTXO,
} from './types.js';
@@ -53,7 +52,20 @@ export type BlockchainElectrumParams = {
opts?: Partial<BlockchainElectrumOpts>;
};
export class BlockchainElectrum extends BaseBlockchain {
export type AddressStatusPayload = {
type: 'address-status';
status: string | null;
};
export type BlockHeightPayload = {
type: 'block-height';
height: number | null;
};
export class BlockchainElectrum extends BaseBlockchain<
AddressStatusPayload,
BlockHeightPayload
> {
// Composables.
electrumClient!: ElectrumClient<ElectrumProtocolEvents>;
@@ -61,9 +73,17 @@ export class BlockchainElectrum extends BaseBlockchain {
isConnected = false;
blockHeight: number | undefined = undefined;
// Subscriptions State.
addressSubscriptions: Map<string, Set<AddressCallback>> = new Map();
transactionSubscriptions: Map<string, Set<TransactionCallback>> = new Map();
// Address Subscriptions State.
addressSubscriptions: Map<
string,
Set<BlockchainCallback<AddressStatusPayload>>
> = new Map();
// Transaction Subscription State.
transactionSubscriptions: Map<
string,
Set<BlockchainCallback<BlockHeightPayload>>
> = new Map();
// Options
store: BaseStore;
@@ -314,7 +334,7 @@ export class BlockchainElectrum extends BaseBlockchain {
}, timeoutMs);
// Create a subscribe function so that we get notified when transaction is seen by network
const subscribeCallback = (_height: number | null) => {
const subscribeCallback = () => {
clearTimeout(timeout);
this.unsubscribeTransaction(tx.toHashHex(), subscribeCallback);
resolve();
@@ -356,13 +376,47 @@ export class BlockchainElectrum extends BaseBlockchain {
await waitForTxPromise;
}
async subscribeAddress(address: string, callback: AddressCallback) {
async subscribeLockscript(
lockscript: string | Uint8Array,
callback: BlockchainCallback<AddressStatusPayload>,
) {
// Fulcrum does not support subscribing to lockscripts, so we cast it to an address.
const address = Address.fromLockscript(lockscript);
// TODO: Refactor to work around ScriptHash for Fulcrum.
// Otherwise, we may get issues with CashAddr/Legacy Addresses.
// Use our subscribeAddress function.
return await this.subscribeAddress(address.toCashAddr(), callback);
}
async unsubscribeLockscript(
lockscript: string | Uint8Array,
callback: BlockchainCallback<AddressStatusPayload>,
): Promise<boolean> {
// Fulcrum does not support subscribing to lockscripts, so we cast it to an address.
const address = Address.fromLockscript(lockscript);
// TODO: Refactor to work around ScriptHash for Fulcrum.
// Otherwise, we may get issues with CashAddr/Legacy Addresses.
// Use our unsubscribeAddress function.
return await this.unsubscribeAddress(address.toCashAddr(), callback);
}
async subscribeAddress(
address: string,
callback: BlockchainCallback<AddressStatusPayload>,
) {
// Wait for connection to be established before attempting to subscribe.
// TODO: Consider doing this at a higher level.
if (!this.isConnected) {
await this.waitFor('isConnectedUpdated', (isConnected) => isConnected);
}
// TODO: Refactor to work around ScriptHash for Fulcrum.
// Otherwise, we may get issues with CashAddr/Legacy Addresses.
// Get or create the set of callbacks for this transaction
let callbacks = this.addressSubscriptions.get(address);
if (!callbacks) {
@@ -379,17 +433,28 @@ export class BlockchainElectrum extends BaseBlockchain {
callbacks.add(callback);
}
async unsubscribeAddress(address: string, callback: AddressCallback) {
async unsubscribeAddress(
address: string,
callback: BlockchainCallback<AddressStatusPayload>,
) {
// Wait for connection to be established before attempting to subscribe.
// TODO: Consider doing this at a higher level.
if (!this.isConnected) {
await this.waitFor('isConnectedUpdated', (isConnected) => isConnected);
}
// TODO: Refactor to work around ScriptHash for Fulcrum.
// Otherwise, we may get issues with CashAddr/Legacy Addresses.
// Get the callbacks set for this address
const callbacks = this.addressSubscriptions.get(address);
if (!callbacks) {
return;
return false;
}
// Find the index of the callback in the handlers array
if (!callbacks.has(callback)) {
return false;
}
// Remove the specific callback
@@ -405,11 +470,13 @@ export class BlockchainElectrum extends BaseBlockchain {
address,
);
}
return true;
}
async subscribeTransaction(
transactionHash: string,
callback: TransactionCallback,
callback: BlockchainCallback<BlockHeightPayload>,
) {
// Wait for connection to be established before attempting to subscribe.
// TODO: Consider doing this at a higher level.
@@ -435,7 +502,7 @@ export class BlockchainElectrum extends BaseBlockchain {
async unsubscribeTransaction(
transactionHash: string,
callback: TransactionCallback,
callback: BlockchainCallback<BlockHeightPayload>,
) {
// Wait for connection to be established before attempting to subscribe.
// TODO: Consider doing this at a higher level.
@@ -446,7 +513,11 @@ export class BlockchainElectrum extends BaseBlockchain {
// Get the callbacks set for this transaction
const callbacks = this.transactionSubscriptions.get(transactionHash);
if (!callbacks) {
return;
return false;
}
if (!callbacks.has(callback)) {
return false;
}
// Remove the specific callback
@@ -462,6 +533,8 @@ export class BlockchainElectrum extends BaseBlockchain {
transactionHash,
);
}
return true;
}
async request(method: string, ...parameters: Array<RPCParameter>) {
@@ -523,7 +596,7 @@ export class BlockchainElectrum extends BaseBlockchain {
// Execute all callbacks for this transaction.
for (const callback of callbacks) {
try {
callback(status);
callback(address, { type: 'address-status', status });
} catch (error) {
// Handle or log any callback execution errors
console.error(
@@ -549,7 +622,7 @@ export class BlockchainElectrum extends BaseBlockchain {
// Execute all callbacks for this transaction.
for (const callback of callbacks) {
try {
callback(height);
callback(txHash, { type: 'block-height', height });
} catch (error) {
// Handle or log any callback execution errors
console.error(