diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6674205..197c742 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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/ diff --git a/packages/primitives/src/address.ts b/packages/primitives/src/address.ts index bacf7ff..3b969ae 100644 --- a/packages/primitives/src/address.ts +++ b/packages/primitives/src/address.ts @@ -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. * diff --git a/packages/stack/src/blockchain/base-blockchain.ts b/packages/stack/src/blockchain/base-blockchain.ts index 95c6bee..58db6e3 100644 --- a/packages/stack/src/blockchain/base-blockchain.ts +++ b/packages/stack/src/blockchain/base-blockchain.ts @@ -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} */ -export abstract class BaseBlockchain 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. @@ -40,7 +42,6 @@ export abstract class BaseBlockchain extends EventEmitter { * Initializes the blockchain connection and required resources. * Should be called before performing any blockchain operations. * - * @abstract * @returns {Promise} 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 { * Gracefully terminates the blockchain connection and cleans up resources. * Should be called when blockchain operations are no longer needed. * - * @abstract * @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. * - * @abstract * @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 detailed information about a specific transaction. - * - * @abstract - * @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; - /** * Retrieves all transactions associated with a specific address. * - * @abstract * @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 @@ -87,27 +79,55 @@ export abstract class BaseBlockchain extends EventEmitter { address: string, ): Promise; - // 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} 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. * - * @abstract * @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. * - * @abstract * @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 @@ -115,13 +135,12 @@ export abstract class BaseBlockchain extends EventEmitter { */ abstract subscribeAddress( address: string, - callback: AddressCallback, + callback: BlockchainCallback, ): Promise; /** * 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} A promise that resolves when the subscription is removed @@ -129,14 +148,13 @@ export abstract class BaseBlockchain extends EventEmitter { */ abstract unsubscribeAddress( address: string, - callback: AddressCallback, - ): Promise; + callback: BlockchainCallback, + ): Promise; /** * 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} A promise that resolves when the subscription is established @@ -144,13 +162,12 @@ export abstract class BaseBlockchain extends EventEmitter { */ abstract subscribeTransaction( transactionHash: string, - callback: TransactionCallback, + callback: BlockchainCallback, ): Promise; /** * 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} A promise that resolves when the subscription is removed @@ -158,6 +175,6 @@ export abstract class BaseBlockchain extends EventEmitter { */ abstract unsubscribeTransaction( transactionHash: string, - callback: TransactionCallback, - ): Promise; + callback: BlockchainCallback, + ): Promise; } diff --git a/packages/stack/src/blockchain/blockchain-electrum.ts b/packages/stack/src/blockchain/blockchain-electrum.ts index 5a5deb2..a4cad0b 100644 --- a/packages/stack/src/blockchain/blockchain-electrum.ts +++ b/packages/stack/src/blockchain/blockchain-electrum.ts @@ -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; }; -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; @@ -61,9 +73,17 @@ export class BlockchainElectrum extends BaseBlockchain { isConnected = false; blockHeight: number | undefined = undefined; - // Subscriptions State. - addressSubscriptions: Map> = new Map(); - transactionSubscriptions: Map> = new Map(); + // Address Subscriptions State. + addressSubscriptions: Map< + string, + Set> + > = new Map(); + + // Transaction Subscription State. + transactionSubscriptions: Map< + string, + Set> + > = 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, + ) { + // 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 { + // 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, + ) { // 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, + ) { // 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, ) { // 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, ) { // 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) { @@ -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( diff --git a/packages/stack/src/blockchain/blockchain-local.ts b/packages/stack/src/blockchain/blockchain-inmemory.ts similarity index 75% rename from packages/stack/src/blockchain/blockchain-local.ts rename to packages/stack/src/blockchain/blockchain-inmemory.ts index 5f02e5c..bc486be 100644 --- a/packages/stack/src/blockchain/blockchain-local.ts +++ b/packages/stack/src/blockchain/blockchain-inmemory.ts @@ -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>; - private transactionSubscriptions: Map>; + private addressSubscriptions: Map>; + private transactionSubscriptions: Map>; 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 { + // 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): Promise { + // 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? */ diff --git a/packages/stack/src/blockchain/blockchain.ts b/packages/stack/src/blockchain/blockchain.ts deleted file mode 100644 index 4f44eb2..0000000 --- a/packages/stack/src/blockchain/blockchain.ts +++ /dev/null @@ -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'); - } -} diff --git a/packages/stack/src/blockchain/index.ts b/packages/stack/src/blockchain/index.ts index 425fa3f..86c29e1 100644 --- a/packages/stack/src/blockchain/index.ts +++ b/packages/stack/src/blockchain/index.ts @@ -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'; diff --git a/packages/stack/src/blockchain/traits.ts b/packages/stack/src/blockchain/traits.ts new file mode 100644 index 0000000..fd4a909 --- /dev/null +++ b/packages/stack/src/blockchain/traits.ts @@ -0,0 +1,24 @@ +import { type BaseBlockchain } from './base-blockchain.js'; + +export type BlockchainLockscriptSubscribable = + Pick< + BaseBlockchain, + 'subscribeLockscript' | 'unsubscribeLockscript' + >; + +export type BlockchainAddressSubscribable = Pick< + BaseBlockchain, + 'subscribeAddress' | 'unsubscribeAddress' +>; + +export type BlockchainTransactionSubscribable = + Pick< + BaseBlockchain, + '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. diff --git a/packages/stack/src/blockchain/types.ts b/packages/stack/src/blockchain/types.ts index a0a6204..38b5459 100644 --- a/packages/stack/src/blockchain/types.ts +++ b/packages/stack/src/blockchain/types.ts @@ -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 = ( + /** 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; diff --git a/packages/stack/src/wallet/base-wallet.ts b/packages/stack/src/wallet/base-wallet.ts index 0a18e3a..6cbcbf6 100644 --- a/packages/stack/src/wallet/base-wallet.ts +++ b/packages/stack/src/wallet/base-wallet.ts @@ -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; }; diff --git a/packages/stack/src/wallet/types.ts b/packages/stack/src/wallet/types.ts index 05d1a42..731b0d2 100644 --- a/packages/stack/src/wallet/types.ts +++ b/packages/stack/src/wallet/types.ts @@ -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; diff --git a/packages/stack/src/wallet/wallet-hd.ts b/packages/stack/src/wallet/wallet-hd.ts index 3e844a0..939090a 100644 --- a/packages/stack/src/wallet/wallet-hd.ts +++ b/packages/stack/src/wallet/wallet-hd.ts @@ -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. diff --git a/packages/stack/src/wallet/wallet-p2pkh.ts b/packages/stack/src/wallet/wallet-p2pkh.ts index e778bdc..d2a1c14 100644 --- a/packages/stack/src/wallet/wallet-p2pkh.ts +++ b/packages/stack/src/wallet/wallet-p2pkh.ts @@ -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);