import { type TransactionBuilder } from './transaction-builder.js'; import { WalletUTXOs, type InputTemplateBCH, type TransactionTemplate, type WalletAddresses, type WalletBlockchain, type WalletTransactions, } from './types.js'; import { type BlockchainUTXOs } from '../blockchain/index.js'; import { type Activities, type BaseStore } from '../storage/index.js'; import { EventEmitter } from '../utils/index.js'; import { type TokenBalances, type Address, Bytes, Hasher, encodeExtendedJson, } from '@xocash/primitives'; export type WalletGenesisData = Record; export type WalletEvents = { shouldMonitorUpdated: boolean; addressesUpdated: WalletAddresses; transactionsUpdated: WalletTransactions; unspentsUpdated: BlockchainUTXOs; stateUpdated: string | null; }; /** * Common interface for all wallet types. * * @remarks * * All function signatures are deliberately asynchronous to accommodate for cases where wallets might be remote. * For example, we might eventually want to support a multisig service. * Or we might have to support some other custodial "Apple Pay"-like OS-interface to get accepted onto their app store. */ export abstract class BaseWallet< WalletEventsType extends WalletEvents = WalletEvents, > extends EventEmitter { /** * Derive a key to use based on the given Genesis Data. * * @remarks * * These keys can be used for Wallet Id's, Signing Keys, Encryption Keys and for Dapp Samdboxes. */ static deriveKey(genesisData: WalletGenesisData, keyName: string) { // Hash the genesis data using Hash256 and the key ("WalletID") for this derivation using Hash256. const genesisDataHashed = Hasher.fromUTF8( encodeExtendedJson(genesisData), ).toHash256Bytes(); const keyHashed = Hasher.fromUTF8(keyName).toHash256Bytes(); // Combine the hashes and do one final hash for good measure. return Hasher.fromBytes( new Uint8Array([...genesisDataHashed, ...keyHashed]), ).toHash256Bytes(); } static deriveId(genesisData: WalletGenesisData) { return BaseWallet.deriveKey(genesisData, 'walletId'); } // Dependencies. abstract readonly activities: Activities; abstract readonly blockchain: WalletBlockchain; abstract readonly cache: BaseStore; // Genesis Data. abstract readonly genesisData: WalletGenesisData; /** * Get the ID for this wallet. */ async getId() { // NOTE: Use the cache to optimize future calls. return Bytes.from( await this.cache.getOrSet('walletId', async () => BaseWallet.deriveKey(this.genesisData, 'walletId'), ), ).toHex(); } /** * Get the encryption key to use for this wallet. */ async getEncryptionKey() { // NOTE: Use the cache to optimize future calls. return await this.cache.getOrSet('encryptionKey', async () => BaseWallet.deriveKey(this.genesisData, 'encryptionKey'), ); } /** * Get the signing key to use for this wallet. */ async getSigningKey() { // NOTE: Use the cache to optimize future calls. return this.cache.getOrSet('signingKey', async () => BaseWallet.deriveKey(this.genesisData, 'signingKey'), ); } /** * Start monitoring this wallet for any transaction activity. */ abstract start(): Promise; /** * Stop monitoring this wallet for any transaction activity. */ abstract stop(): Promise; /** * Remove any event listeners from this wallet. */ abstract destroy(): Promise; /** * Fetch all transactions from the blockchain involving this wallet. * * @remarks * * We will want to add a query argument so that workers can use this efficiently: * * ```ts * { * limit?: number; * offset?: number; * sort?: () => number; * filter?: () => boolean; * } * ``` * * This "query" argument should be turned into a generic type as we will want to re-use it for Activities. */ abstract getTransactions(): Promise; /** * Fetch all unspents from the blockchain belonging to this wallet. */ abstract getUnspents(): Promise; /** * Fetch all unspents from the blockchain belonging to this wallet. * * @remarks * * I was considering removing this and just storing unspents as "UTXOSignable"s. * But that might not be ideal if we want to have Watch-only wallets. * A better approach might be to have Fetch Unspents return `Array` * Still need to flesh that out. * * EDIT: We're going to want to store state for caching anyway... * May as well just getUnspentDirectives() from the local state instead of fetch. * Likewise for balances. */ abstract getUnspentDirectives(): Promise; /** * Get the satoshi balance of this wallet. * * @remarks * * Consider confirmed/unconfirmed balances (or some other representation). * * This becomes important in some instances because services like BitPay require one confirmation. * This'll also have bearing on how we define our UTXO Selection strategies. */ abstract getBalanceSats(): Promise; /** * Get the token balance of this wallet. * * @remarks * * Consider confirmed/unconfirmed balances (or some other representation). * * This becomes important in some instances because services like BitPay require one confirmation. * This'll also have bearing on how we define our UTXO Selection strategies. */ abstract getBalanceTokens(): Promise; /** * Get all addresses belonging to this wallet. */ abstract getAddresses(): Promise; /** * Get the address that this wallet should receive to. */ abstract getReceivingAddress(): Promise
; /** * Select UTXOs from this wallet to meet the amounts required. * * @remarks * * TODO: We are going to want to return the Transaction that was compiled. * * TODO: This should accept a second argument (probably function) that can filter/sory UTXOs. * The intent here is to provide a "spending policy". E.g. Only use (or prioritise) UTXOs with 1-conf. */ abstract signTransaction( template: Partial, ): Promise; /** * Select UTXOs from this wallet to meet the amounts required and broadcast it to the Blockchain. * * @remarks * * TODO: We are going to want to return the Transaction that was compiled. */ abstract sendTransaction( template: Partial, ): Promise; }