Merge branch 'blockchain_subsets' into 'development'

Blockchain Traits

See merge request GeneralProtocols/xo/stack!67
This commit is contained in:
Jim Hamill
2025-04-20 02:22:30 +00:00
13 changed files with 302 additions and 189 deletions
+2
View File
@@ -44,6 +44,8 @@ publish:development:
# Checkout the development branch so that we're not on a detached HEAD (which GitLab CI will default to).
- git checkout development
# Forcefully pull the latest changes.
- git pull
# Setup npm config for npmjs.com
- npm config set registry https://registry.npmjs.org/
+15
View File
@@ -226,6 +226,21 @@ export class Address {
return this.fromRedeemScript(Bytes.fromHex(hex).toBytes(), type);
}
/**
* Attempt to instantiate Address from Locking Bytecode.
*
* @param lockscript The lockscript as bytes or hex.
*
* @throws {Error} If lock script cannot be decoded into an address.
*
* @returns Instance of Address.
*/
public static fromLockscript(lockscript: string | Uint8Array) {
return lockscript instanceof Uint8Array
? this.fromLockscriptBytes(lockscript)
: this.fromLockscriptHex(lockscript);
}
/**
* Attempt to instantiate Address from Locking Bytecode.
*
@@ -1,10 +1,9 @@
import { EventEmitter } from '../utils';
import {
type AddressCallback,
type BlockchainCallback,
type BlockchainUTXOs,
type BlockchainTransactions,
type TransactionCallback,
} from './types.js';
import { type Transaction } from '@xocash/primitives';
@@ -25,7 +24,10 @@ export type BlockchainEvents = {
* @abstract
* @extends {EventEmitter<BlockchainEvents>}
*/
export abstract class BaseBlockchain 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.
@@ -40,7 +42,6 @@ export abstract class BaseBlockchain extends EventEmitter<BlockchainEvents> {
* Initializes the blockchain connection and required resources.
* Should be called before performing any blockchain operations.
*
* @abstract
* @returns {Promise<void>} A promise that resolves when the blockchain connection is established
* @throws {Error} If the connection cannot be established
*/
@@ -50,35 +51,26 @@ export abstract class BaseBlockchain extends EventEmitter<BlockchainEvents> {
* Gracefully terminates the blockchain connection and cleans up resources.
* Should be called when blockchain operations are no longer needed.
*
* @abstract
* @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.
*
* @abstract
* @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 detailed information about a specific transaction.
*
* @abstract
* @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>;
/**
* Retrieves all transactions associated with a specific address.
*
* @abstract
* @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
@@ -87,27 +79,55 @@ export abstract class BaseBlockchain extends EventEmitter<BlockchainEvents> {
address: string,
): Promise<BlockchainTransactions>;
// TODO: We may have a fetchTokenHistory in future, but Fulcrum does not currently support indexing tokens.
// There are actually a few functional discrepancies with the adapters: different adapters grant different capabilities.
// For example, Chaingraph can fetch token and address history. But BCHN P2P network cannot even fetch address history.
// We might want to handle this by having multiple BlockchainInterfaces (e.g. IBlockchainAddressSubscriptions) that describe capabilities.
// But we should return to that later.
/**
* 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.
*
* @abstract
* @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<void>;
/**
* 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.
*
* @abstract
* @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
@@ -115,13 +135,12 @@ export abstract class BaseBlockchain extends EventEmitter<BlockchainEvents> {
*/
abstract subscribeAddress(
address: string,
callback: AddressCallback,
callback: BlockchainCallback<AddressNotificationPayload>,
): Promise<void>;
/**
* Removes a subscription for a specific blockchain address.
*
* @abstract
* @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
@@ -129,14 +148,13 @@ export abstract class BaseBlockchain extends EventEmitter<BlockchainEvents> {
*/
abstract unsubscribeAddress(
address: string,
callback: AddressCallback,
): Promise<void>;
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.
*
* @abstract
* @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
@@ -144,13 +162,12 @@ export abstract class BaseBlockchain extends EventEmitter<BlockchainEvents> {
*/
abstract subscribeTransaction(
transactionHash: string,
callback: TransactionCallback,
callback: BlockchainCallback<TransactionNotificationPayload>,
): Promise<void>;
/**
* Removes a subscription for a specific transaction.
*
* @abstract
* @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
@@ -158,6 +175,6 @@ export abstract class BaseBlockchain extends EventEmitter<BlockchainEvents> {
*/
abstract unsubscribeTransaction(
transactionHash: string,
callback: TransactionCallback,
): Promise<void>;
callback: BlockchainCallback<TransactionNotificationPayload>,
): Promise<boolean>;
}
@@ -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(
@@ -1,7 +1,6 @@
import { BaseBlockchain } from './base-blockchain.js';
import {
type AddressCallback,
type TransactionCallback,
type BlockchainCallback,
type BlockchainTransaction,
type BlockchainTransactions,
type BlockchainUTXO,
@@ -24,10 +23,14 @@ import {
// Local Blockchain Adapter.
// This simulates the BCH Blockchain.
export class BlockchainLocal extends BaseBlockchain {
export class BlockchainInMemory extends BaseBlockchain {
static from() {
return new BlockchainInMemory();
}
private transactions: BlockchainTransactions;
private addressSubscriptions: Map<string, Set<AddressCallback>>;
private transactionSubscriptions: Map<string, Set<TransactionCallback>>;
private addressSubscriptions: Map<string, Set<BlockchainCallback>>;
private transactionSubscriptions: Map<string, Set<BlockchainCallback>>;
constructor() {
super();
@@ -37,13 +40,9 @@ export class BlockchainLocal extends BaseBlockchain {
this.transactionSubscriptions = new Map();
}
async start() {
console.log('BlockchainLocal: Started');
}
async start() {}
async stop() {
console.log('BlockchainLocal: Stopped');
}
async stop() {}
async fetchUnspents(address: string) {
// Derive the current UTXO set.
@@ -117,7 +116,7 @@ export class BlockchainLocal extends BaseBlockchain {
});
// Validate the transaction.
BlockchainLocal.validateTransaction(
BlockchainInMemory.validateTransaction(
tx,
sourceOutputs.map((utxo) => utxo.output),
);
@@ -135,7 +134,35 @@ export class BlockchainLocal extends BaseBlockchain {
this.notifyTransactionSubscribers(tx);
}
async subscribeAddress(address: string, callback: AddressCallback) {
async subscribeLockscript(
lockscript: string | Uint8Array,
callback: BlockchainCallback,
) {
// 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,
): 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) {
// Convert the address to it's lockscript hex so that it's consistently represented.
const lockscriptHex = Address.from(address).toLockscriptHex();
@@ -145,26 +172,35 @@ export class BlockchainLocal extends BaseBlockchain {
}
// Add the address notification callback.
this.addressSubscriptions.get(address)!.add(callback);
this.addressSubscriptions.get(lockscriptHex)!.add(callback);
}
async unsubscribeAddress(address: string, callback: AddressCallback) {
async unsubscribeAddress(address: string, callback: BlockchainCallback) {
// Convert the address to it's lockscript hex so that it's consistently represented.
const lockscriptHex = Address.from(address).toLockscriptHex();
// Find all callbacks this lockscript is subscribed to.
const callbacks = this.addressSubscriptions.get(lockscriptHex);
if (callbacks) {
if (callbacks.has(callback)) {
return false;
}
callbacks.delete(callback);
if (callbacks.size === 0) {
this.addressSubscriptions.delete(address);
}
return true;
}
return false;
}
async subscribeTransaction(
transactionHash: string,
callback: TransactionCallback,
callback: BlockchainCallback,
) {
if (!this.transactionSubscriptions.has(transactionHash)) {
this.transactionSubscriptions.set(transactionHash, new Set());
@@ -174,15 +210,21 @@ export class BlockchainLocal extends BaseBlockchain {
async unsubscribeTransaction(
transactionHash: string,
callback: TransactionCallback,
callback: BlockchainCallback,
) {
const callbacks = this.transactionSubscriptions.get(transactionHash);
if (callbacks) {
if (!callbacks.has(callback)) {
return false;
}
callbacks.delete(callback);
if (callbacks.size === 0) {
this.transactionSubscriptions.delete(transactionHash);
}
}
return false;
}
private notifyAddressSubscribers(tx: Transaction) {
@@ -208,7 +250,7 @@ export class BlockchainLocal extends BaseBlockchain {
const callbacks = this.addressSubscriptions.get(address);
if (callbacks) {
callbacks.forEach((callback) => callback('updated'));
callbacks.forEach((callback) => callback(address));
}
});
@@ -221,7 +263,7 @@ export class BlockchainLocal extends BaseBlockchain {
const callbacks = this.addressSubscriptions.get(address);
if (callbacks) {
callbacks.forEach((callback) => callback('updated'));
callbacks.forEach((callback) => callback(address));
}
});
}
@@ -231,7 +273,7 @@ export class BlockchainLocal extends BaseBlockchain {
const callbacks = this.transactionSubscriptions.get(txHash);
if (callbacks) {
// In this local simulation, we're not tracking block height, so we'll use null
callbacks.forEach((callback) => callback(null));
callbacks.forEach((callback) => callback(txHash));
}
}
@@ -274,6 +316,26 @@ export class BlockchainLocal extends BaseBlockchain {
return utxoMap;
}
async mineBlock(outputs: Array<Output>): Promise<void> {
// Create a transaction that pays to our subscribed address.
const transaction = Transaction.fromRaw({
version: 2,
locktime: 0,
inputs: [
{
outpointIndex: 0,
outpointTransactionHash: new Uint8Array(),
unlockingBytecode: new Uint8Array(),
sequenceNumber: 0,
},
],
outputs,
});
// Broadcast that transaction.
await this.broadcastTransaction(transaction, { skipValidation: true });
}
/**
* @remarks Consider moving this to Transaction Primitive. Trade-off is that it's probably big. Maybe standalone util?
*/
-100
View File
@@ -1,100 +0,0 @@
import { BaseBlockchain } from './base-blockchain.js';
import { type AddressCallback, type TransactionCallback } from './types.js';
import { BlockchainElectrum } from './blockchain-electrum.js';
import { BlockchainLocal } from './blockchain-local.js';
import { Transaction } from '@xocash/primitives';
/**
* The idea here is that this will implement the IBlockchain interface.
* But, it will allow the dev to specify multiple child IBlockchain interfaces for configuration.
* So, if the network is not available, it can fallback to the local and sync later.
*
* @example
* ```ts
* // Actual interface will be more complex, but this is the rough idea.
* const blockchain = new Blockchain([
* new BlockchainLocal(...),
* new BlockchainElectrum(...)
* ])
* ```
*/
export class Blockchain extends BaseBlockchain {
blockchainLocal: BlockchainLocal;
blockchainElectrum: BlockchainElectrum;
constructor() {
super();
this.blockchainLocal = new BlockchainLocal();
this.blockchainElectrum = new BlockchainElectrum();
}
async start() {
await Promise.all([
this.blockchainLocal.start(),
this.blockchainElectrum.start(),
]);
}
async stop() {
await Promise.all([
this.blockchainLocal.stop(),
this.blockchainElectrum.stop(),
]);
}
async fetchUnspents(address: string) {
if (this.blockchainElectrum.isConnected) {
return await this.blockchainElectrum.fetchUnspents(address);
} else {
return await this.blockchainLocal.fetchUnspents(address);
}
}
async fetchTransaction(txHash: string) {
if (this.blockchainElectrum.isConnected) {
return await this.blockchainElectrum.fetchTransaction(txHash);
} else {
return await this.blockchainLocal.fetchTransaction(txHash);
}
}
async fetchAddressHistory(address: string) {
if (this.blockchainElectrum.isConnected) {
return await this.blockchainElectrum.fetchAddressHistory(address);
} else {
return await this.blockchainLocal.fetchAddressHistory(address);
}
}
async broadcastTransaction(tx: Uint8Array | Transaction | string) {
if (this.blockchainElectrum.isConnected) {
return await this.blockchainElectrum.broadcastTransaction(tx);
} else {
return await this.blockchainLocal.broadcastTransaction(tx);
}
}
async subscribeAddress(_address: string, _callback: AddressCallback) {
throw new Error('Not yet implemented');
}
async unsubscribeAddress(_address: string, _callback: AddressCallback) {
throw new Error('Not yet implemented');
}
async subscribeTransaction(
_transactionHash: string,
_callback: TransactionCallback,
) {
throw new Error('Not yet implemented');
}
async unsubscribeTransaction(
_transactionHash: string,
_callback: TransactionCallback,
) {
throw new Error('Not yet implemented');
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
export * from './base-blockchain.js';
export * from './blockchain.js';
export * from './blockchain-electrum.js';
export * from './blockchain-local.js';
export * from './blockchain-inmemory.js';
export * from './traits.js';
export * from './types.js';
+24
View File
@@ -0,0 +1,24 @@
import { type BaseBlockchain } from './base-blockchain.js';
export type BlockchainLockscriptSubscribable<CallbackPayload = undefined> =
Pick<
BaseBlockchain<CallbackPayload>,
'subscribeLockscript' | 'unsubscribeLockscript'
>;
export type BlockchainAddressSubscribable<CallbackPayload = undefined> = Pick<
BaseBlockchain<CallbackPayload>,
'subscribeAddress' | 'unsubscribeAddress'
>;
export type BlockchainTransactionSubscribable<CallbackPayload = undefined> =
Pick<
BaseBlockchain<CallbackPayload>,
'subscribeTransaction' | 'unsubscribeTransaction'
>;
// TODO: We may have a fetchTokenHistory in future, but Fulcrum does not currently support indexing tokens.
// There are actually a few functional discrepancies with the adapters: different adapters grant different capabilities.
// For example, Chaingraph can fetch token and address history. But BCHN P2P network cannot even fetch address history.
// We might want to handle this by having multiple BlockchainInterfaces (e.g. IBlockchainAddressSubscriptions) that describe capabilities.
// But we should return to that later.
+6 -2
View File
@@ -1,7 +1,11 @@
import { Transaction, TransactionHash, UTXO, ExtMap } from '@xocash/primitives';
export type AddressCallback = (status: string | null) => void;
export type TransactionCallback = (height: number | null) => void;
export type BlockchainCallback<Payload = undefined> = (
/** The transaction hash, address, lockscript or scriptHash of this subscription. */
id: string,
/** Additional payload information provided by this subscription */
payload?: Payload | undefined,
) => void;
export type BlockchainTransaction = {
hash: TransactionHash;
+2 -5
View File
@@ -5,10 +5,7 @@ import {
type WalletTransactions,
} from './types.js';
import {
type BlockchainTransactions,
type BlockchainUTXOs,
} from '../blockchain';
import { type BlockchainUTXOs } from '../blockchain';
import { type Activities } from '../storage';
import { EventEmitter } from '../utils';
@@ -16,7 +13,7 @@ import { type TokenBalances, type Address } from '@xocash/primitives';
export type WalletEvents = {
shouldMonitorUpdated: boolean;
transactionsUpdated: BlockchainTransactions;
transactionsUpdated: WalletTransactions;
unspentsUpdated: BlockchainUTXOs;
stateUpdated: string | null;
};
+13 -3
View File
@@ -1,4 +1,9 @@
import { type BaseBlockchain, type BlockchainTransaction } from '../blockchain';
import {
type BaseBlockchain,
type AddressStatusPayload,
type BlockHeightPayload,
type BlockchainTransaction,
} from '../blockchain';
import { type Activities, type BaseStore } from '../storage';
import { type Address, type ExtMap } from '@xocash/primitives';
@@ -12,9 +17,15 @@ import {
type Output,
} from '@bitauth/libauth';
// NOTE: For Wallets, the Blockchain Adapter must have Address and Transaction indexing.
export type WalletBlockchain = BaseBlockchain<
AddressStatusPayload,
BlockHeightPayload
>;
export type WalletDependencies = {
activities?: Activities;
blockchain: BaseBlockchain;
blockchain: WalletBlockchain;
cache?: BaseStore;
};
@@ -24,7 +35,6 @@ export type InputTemplateBCH = InputTemplate<
CompilationData
>;
// TODO: Rename me to WalletUTXO
export type WalletUTXO = {
outpointIndex: number;
outpointTransactionHash: Uint8Array;
+2 -3
View File
@@ -1,10 +1,9 @@
import { BaseWallet } from './base-wallet.js';
import { type TransactionTemplate } from './types.js';
import { TransactionBuilder } from './transaction-builder.js';
import { type WalletDependencies } from './types.js';
import { type WalletBlockchain, type WalletDependencies } from './types.js';
import { WalletP2PKH } from './wallet-p2pkh.js';
import { type BaseBlockchain } from '../blockchain';
import { type BaseStore, Activities, StoreInMemory } from '../storage';
import {
@@ -80,7 +79,7 @@ export class WalletHD extends BaseWallet {
// Dependencies.
public readonly activities: Activities;
public readonly blockchain: BaseBlockchain;
public readonly blockchain: WalletBlockchain;
public readonly cache: BaseStore;
// Genesis Data.
+16 -6
View File
@@ -1,8 +1,15 @@
import { BaseWallet } from './base-wallet.js';
import { type TransactionTemplate, type WalletDependencies } from './types.js';
import {
type TransactionTemplate,
type WalletBlockchain,
type WalletDependencies,
} from './types.js';
import { TransactionBuilder } from './transaction-builder.js';
import { type BaseBlockchain, type BlockchainTransaction } from '../blockchain';
import {
type AddressStatusPayload,
type BlockchainTransaction,
} from '../blockchain';
import { type BaseStore, Activities, StoreInMemory } from '../storage';
import {
@@ -116,7 +123,7 @@ export class WalletP2PKH extends BaseWallet {
// Dependencies.
public readonly activities: Activities;
public readonly blockchain: BaseBlockchain;
public readonly blockchain: WalletBlockchain;
public readonly cache: BaseStore;
// Genesis Data.
@@ -408,11 +415,14 @@ export class WalletP2PKH extends BaseWallet {
await this.blockchain.broadcastTransaction(generatedTxBytes);
}
private async onAddressNotification(status: string | null) {
private async onAddressNotification(
_address: string,
payload?: AddressStatusPayload,
) {
// If wallet state has changed...
if (status !== this.status) {
if (payload && payload.status !== this.status) {
// Set our new status.
this.status = status;
this.status = payload.status;
// Emit an event.
this.emit('stateUpdated', this.status);