224 lines
6.5 KiB
TypeScript
224 lines
6.5 KiB
TypeScript
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<string, unknown>;
|
|
|
|
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<WalletEventsType> {
|
|
/**
|
|
* 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<void>;
|
|
|
|
/**
|
|
* Stop monitoring this wallet for any transaction activity.
|
|
*/
|
|
abstract stop(): Promise<void>;
|
|
|
|
/**
|
|
* Remove any event listeners from this wallet.
|
|
*/
|
|
abstract destroy(): Promise<void>;
|
|
|
|
/**
|
|
* 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<WalletTransactions>;
|
|
|
|
/**
|
|
* Fetch all unspents from the blockchain belonging to this wallet.
|
|
*/
|
|
abstract getUnspents(): Promise<BlockchainUTXOs>;
|
|
|
|
/**
|
|
* 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<UTXO | UTXOSignable>`
|
|
* 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<WalletUTXOs>;
|
|
|
|
/**
|
|
* 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<bigint>;
|
|
|
|
/**
|
|
* 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<TokenBalances>;
|
|
|
|
/**
|
|
* Get all addresses belonging to this wallet.
|
|
*/
|
|
abstract getAddresses(): Promise<WalletAddresses>;
|
|
|
|
/**
|
|
* Get the address that this wallet should receive to.
|
|
*/
|
|
abstract getReceivingAddress(): Promise<Address>;
|
|
|
|
/**
|
|
* 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<TransactionTemplate>,
|
|
): Promise<TransactionBuilder>;
|
|
|
|
/**
|
|
* 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<TransactionTemplate>,
|
|
): Promise<Uint8Array>;
|
|
}
|