UNPKG

@bsv/wallet-toolbox

Version:

BRC100 conforming wallet, wallet storage and wallet signer components

563 lines (500 loc) 17.6 kB
import { AbortActionArgs, AbortActionResult, Beef, InternalizeActionArgs, InternalizeActionResult, ListActionsArgs, ListActionsResult, ListCertificatesResult, ListOutputsArgs, ListOutputsResult, RelinquishCertificateArgs, RelinquishOutputArgs, SendWithResult, TXIDHexString } from '@bsv/sdk' import { TableCertificate, TableCertificateField, TableCertificateX, TableCommission, TableMonitorEvent, TableOutput, TableOutputBasket, TableOutputTag, TableOutputTagMap, TableProvenTx, TableProvenTxReq, TableSettings, TableSyncState, TableTransaction, TableTxLabel, TableTxLabelMap, TableUser } from '../storage/schema/tables' import { WalletServices } from './WalletServices.interfaces' import { ValidCreateActionArgs, ValidCreateActionOutput, ValidListActionsArgs, ValidListCertificatesArgs, ValidListOutputsArgs } from './validationHelpers' import { Chain, Paged, ProvenTxReqStatus, TransactionStatus } from './types' import { WalletError } from './WalletError' /** * This is the `WalletStorage` interface implemented by a class such as `WalletStorageManager`, * which manges an active and set of backup storage providers. * * Access and conrol is not directly managed. Typically each request is made with an associated identityKey * and it is left to the providers: physical access or remote channel authentication. */ export interface WalletStorage { /** * @returns false */ isStorageProvider(): boolean isAvailable(): boolean makeAvailable(): Promise<TableSettings> migrate(storageName: string, storageIdentityKey: string): Promise<string> destroy(): Promise<void> setServices(v: WalletServices): void getServices(): WalletServices getSettings(): TableSettings getAuth(): Promise<AuthId> findOrInsertUser(identityKey: string): Promise<{ user: TableUser; isNew: boolean }> abortAction(args: AbortActionArgs): Promise<AbortActionResult> createAction(args: ValidCreateActionArgs): Promise<StorageCreateActionResult> processAction(args: StorageProcessActionArgs): Promise<StorageProcessActionResults> internalizeAction(args: InternalizeActionArgs): Promise<InternalizeActionResult> findCertificates(args: FindCertificatesArgs): Promise<TableCertificateX[]> findOutputBaskets(args: FindOutputBasketsArgs): Promise<TableOutputBasket[]> findOutputs(args: FindOutputsArgs): Promise<TableOutput[]> findProvenTxReqs(args: FindProvenTxReqsArgs): Promise<TableProvenTxReq[]> listActions(args: ListActionsArgs): Promise<ListActionsResult> listCertificates(args: ValidListCertificatesArgs): Promise<ListCertificatesResult> listOutputs(args: ListOutputsArgs): Promise<ListOutputsResult> insertCertificate(certificate: TableCertificateX): Promise<number> relinquishCertificate(args: RelinquishCertificateArgs): Promise<number> relinquishOutput(args: RelinquishOutputArgs): Promise<number> getStores(): WalletStorageInfo[] } /** * Snapshot of the current state of a storage provider configured for an `WalletStorageManager`. */ export interface WalletStorageInfo { isActive: boolean isEnabled: boolean isBackup: boolean isConflicting: boolean userId: number storageIdentityKey: string storageName: string storageClass: string endpointURL?: string } /** * This is the `WalletStorage` interface implemented with authentication checking and * is the actual minimal interface implemented by storage and remoted storage providers. */ export interface WalletStorageProvider extends WalletStorageSync { /** * @returns true if this object's interface can be extended to the full `StorageProvider` interface */ isStorageProvider(): boolean setServices(v: WalletServices): void } export interface WalletStorageSync extends WalletStorageWriter { findOrInsertSyncStateAuth( auth: AuthId, storageIdentityKey: string, storageName: string ): Promise<{ syncState: TableSyncState; isNew: boolean }> /** * Updagte the `activeStorage` property of the authenticated user by their `userId`. * @param auth * @param newActiveStorageIdentityKey */ setActive(auth: AuthId, newActiveStorageIdentityKey: string): Promise<number> getSyncChunk(args: RequestSyncChunkArgs): Promise<SyncChunk> processSyncChunk(args: RequestSyncChunkArgs, chunk: SyncChunk): Promise<ProcessSyncChunkResult> } /** * This is the minimal interface required for a WalletStorageProvider to export data to another provider. */ export interface WalletStorageSyncReader { makeAvailable(): Promise<TableSettings> getSyncChunk(args: RequestSyncChunkArgs): Promise<SyncChunk> } export interface WalletStorageWriter extends WalletStorageReader { makeAvailable(): Promise<TableSettings> migrate(storageName: string, storageIdentityKey: string): Promise<string> destroy(): Promise<void> findOrInsertUser(identityKey: string): Promise<{ user: TableUser; isNew: boolean }> abortAction(auth: AuthId, args: AbortActionArgs): Promise<AbortActionResult> createAction(auth: AuthId, args: ValidCreateActionArgs): Promise<StorageCreateActionResult> processAction(auth: AuthId, args: StorageProcessActionArgs): Promise<StorageProcessActionResults> internalizeAction(auth: AuthId, args: InternalizeActionArgs): Promise<StorageInternalizeActionResult> insertCertificateAuth(auth: AuthId, certificate: TableCertificateX): Promise<number> relinquishCertificate(auth: AuthId, args: RelinquishCertificateArgs): Promise<number> relinquishOutput(auth: AuthId, args: RelinquishOutputArgs): Promise<number> } export interface WalletStorageReader { isAvailable(): boolean getServices(): WalletServices getSettings(): TableSettings findCertificatesAuth(auth: AuthId, args: FindCertificatesArgs): Promise<TableCertificateX[]> findOutputBasketsAuth(auth: AuthId, args: FindOutputBasketsArgs): Promise<TableOutputBasket[]> findOutputsAuth(auth: AuthId, args: FindOutputsArgs): Promise<TableOutput[]> findProvenTxReqs(args: FindProvenTxReqsArgs): Promise<TableProvenTxReq[]> listActions(auth: AuthId, vargs: ValidListActionsArgs): Promise<ListActionsResult> listCertificates(auth: AuthId, vargs: ValidListCertificatesArgs): Promise<ListCertificatesResult> listOutputs(auth: AuthId, vargs: ValidListOutputsArgs): Promise<ListOutputsResult> } export interface AuthId { identityKey: string userId?: number isActive?: boolean } export interface FindSincePagedArgs { since?: Date paged?: Paged trx?: TrxToken /** * Support for orderDescending is implemented in StorageKnex for basic table find methods, * excluding certificate_fields table, map tables, and settings (singleton row table). */ orderDescending?: boolean } export interface FindForUserSincePagedArgs extends FindSincePagedArgs { userId: number } export interface FindPartialSincePagedArgs<T extends object> extends FindSincePagedArgs { partial: Partial<T> } export interface FindCertificatesArgs extends FindSincePagedArgs { partial: Partial<TableCertificate> certifiers?: string[] types?: string[] includeFields?: boolean } export interface FindOutputBasketsArgs extends FindSincePagedArgs { partial: Partial<TableOutputBasket> } export interface FindOutputsArgs extends FindSincePagedArgs { partial: Partial<TableOutput> noScript?: boolean txStatus?: TransactionStatus[] } export type StorageProvidedBy = 'you' | 'storage' | 'you-and-storage' export interface StorageCreateTransactionSdkInput { vin: number sourceTxid: string sourceVout: number sourceSatoshis: number sourceLockingScript: string /** * */ sourceTransaction?: number[] unlockingScriptLength: number providedBy: StorageProvidedBy type: string spendingDescription?: string derivationPrefix?: string derivationSuffix?: string senderIdentityKey?: string } export interface StorageCreateTransactionSdkOutput extends ValidCreateActionOutput { vout: number providedBy: StorageProvidedBy purpose?: string derivationSuffix?: string } export interface StorageCreateActionResult { inputBeef?: number[] inputs: StorageCreateTransactionSdkInput[] outputs: StorageCreateTransactionSdkOutput[] noSendChangeOutputVouts?: number[] derivationPrefix: string version: number lockTime: number reference: string } export interface StorageProcessActionArgs { isNewTx: boolean isSendWith: boolean isNoSend: boolean isDelayed: boolean reference?: string txid?: string rawTx?: number[] sendWith: string[] log?: string } export interface StorageInternalizeActionResult extends InternalizeActionResult { /** true if internalizing outputs on an existing storage transaction */ isMerge: boolean /** txid of transaction being internalized */ txid: string /** net change in change balance for user due to this internalization */ satoshis: number /** valid iff not isMerge and txid was unknown to storage and non-delayed broadcast was not success */ sendWithResults?: SendWithResult[] /** valid iff not isMerge and txid was unknown to storage and non-delayed broadcast was not success */ notDelayedResults?: ReviewActionResult[] } /** * Indicates status of a new Action following a `createAction` or `signAction` in immediate mode: * When `acceptDelayedBroadcast` is falses. * * 'success': The action has been broadcast and accepted by the bitcoin processing network. * 'doulbeSpend': The action has been confirmed to double spend one or more inputs, and by the "first-seen-rule" is the loosing transaction. * 'invalidTx': The action was rejected by the processing network as an invalid bitcoin transaction. * 'serviceError': The broadcast services are currently unable to reach the bitcoin network. The action is now queued for delayed retries. */ export type ReviewActionResultStatus = 'success' | 'doubleSpend' | 'serviceError' | 'invalidTx' export interface ReviewActionResult { txid: TXIDHexString status: ReviewActionResultStatus /** * Any competing txids reported for this txid, valid when status is 'doubleSpend'. */ competingTxs?: string[] /** * Merged beef of competingTxs, valid when status is 'doubleSpend'. */ competingBeef?: number[] } export interface StorageProcessActionResults { sendWithResults?: SendWithResult[] notDelayedResults?: ReviewActionResult[] log?: string } export interface ProvenOrRawTx { proven?: TableProvenTx rawTx?: number[] inputBEEF?: number[] } export interface PurgeParams { purgeCompleted: boolean purgeFailed: boolean purgeSpent: boolean /** * Minimum age in msecs for transient completed transaction data purge. * Default is 14 days. */ purgeCompletedAge?: number /** * Minimum age in msecs for failed transaction data purge. * Default is 14 days. */ purgeFailedAge?: number /** * Minimum age in msecs for failed transaction data purge. * Default is 14 days. */ purgeSpentAge?: number } export interface PurgeResults { count: number log: string } export interface StorageProvenOrReq { proven?: TableProvenTx req?: TableProvenTxReq isNew?: boolean } /** * Specifies the available options for computing transaction fees. */ export interface StorageFeeModel { /** * Available models. Currently only "sat/kb" is supported. */ model: 'sat/kb' /** * When "fee.model" is "sat/kb", this is an integer representing the number of satoshis per kb of block space * the transaction will pay in fees. * * If undefined, the default value is used. */ value?: number } export interface StorageGetBeefOptions { /** if 'known', txids known to local storage as valid are included as txidOnly */ trustSelf?: 'known' /** list of txids to be included as txidOnly if referenced. Validity is known to caller. */ knownTxids?: string[] /** optional. If defined, raw transactions and merkle paths required by txid are merged to this instance and returned. Otherwise a new Beef is constructed and returned. */ mergeToBeef?: Beef | number[] /** optional. Default is false. `storage` is used for raw transaction and merkle proof lookup */ ignoreStorage?: boolean /** optional. Default is false. `getServices` is used for raw transaction and merkle proof lookup */ ignoreServices?: boolean /** optional. Default is false. If true, raw transactions with proofs missing from `storage` and obtained from `getServices` are not inserted to `storage`. */ ignoreNewProven?: boolean /** optional. Default is zero. Ignores available merkle paths until recursion detpth equals or exceeds value */ minProofLevel?: number } export interface StorageSyncReaderOptions { chain: Chain } export interface FindCertificateFieldsArgs extends FindSincePagedArgs { partial: Partial<TableCertificateField> } export interface FindCommissionsArgs extends FindSincePagedArgs { partial: Partial<TableCommission> } export interface FindOutputTagMapsArgs extends FindSincePagedArgs { partial: Partial<TableOutputTagMap> tagIds?: number[] } export interface FindOutputTagsArgs extends FindSincePagedArgs { partial: Partial<TableOutputTag> } export interface FindProvenTxReqsArgs extends FindSincePagedArgs { partial: Partial<TableProvenTxReq> status?: ProvenTxReqStatus[] txids?: string[] } export interface FindProvenTxsArgs extends FindSincePagedArgs { partial: Partial<TableProvenTx> } export interface FindSyncStatesArgs extends FindSincePagedArgs { partial: Partial<TableSyncState> } export interface FindTransactionsArgs extends FindSincePagedArgs { partial: Partial<TableTransaction> status?: TransactionStatus[] noRawTx?: boolean } export interface FindTxLabelMapsArgs extends FindSincePagedArgs { partial: Partial<TableTxLabelMap> labelIds?: number[] } export interface FindTxLabelsArgs extends FindSincePagedArgs { partial: Partial<TableTxLabel> } export interface FindUsersArgs extends FindSincePagedArgs { partial: Partial<TableUser> } export interface FindMonitorEventsArgs extends FindSincePagedArgs { partial: Partial<TableMonitorEvent> } /** * Place holder for the transaction control object used by actual storage provider implementation. */ // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface TrxToken {} export interface UpdateProvenTxReqWithNewProvenTxArgs { provenTxReqId: number txid: string attempts: number status: ProvenTxReqStatus history: string height: number index: number blockHash: string merkleRoot: string merklePath: number[] } export interface UpdateProvenTxReqWithNewProvenTxResult { status: ProvenTxReqStatus history: string provenTxId: number log?: string } /** * success: Last sync of this user from this storage was successful. * * error: Last sync protocol operation for this user to this storage threw and error. * * identified: Configured sync storage has been identified but not sync'ed. * * unknown: Sync protocol state is unknown. */ export type SyncStatus = 'success' | 'error' | 'identified' | 'updated' | 'unknown' export type SyncProtocolVersion = '0.1.0' export interface RequestSyncChunkArgs { /** * The storageIdentityKey of the storage supplying the update SyncChunk data. */ fromStorageIdentityKey: string /** * The storageIdentityKey of the storage consuming the update SyncChunk data. */ toStorageIdentityKey: string /** * The identity of whose data is being requested */ identityKey: string /** * The max updated_at time received from the storage service receiving the request. * Will be undefiend if this is the first request or if no data was previously sync'ed. * * `since` must include items if 'updated_at' is greater or equal. Thus, when not undefined, a sync request should always return at least one item already seen. */ since?: Date /** * A rough limit on how large the response should be. * The item that exceeds the limit is included and ends adding more items. */ maxRoughSize: number /** * The maximum number of items (records) to be returned. */ maxItems: number /** * For each entity in dependency order, the offset at which to start returning items * from `since`. * * The entity order is: * 0 ProvenTxs * 1 ProvenTxReqs * 2 OutputBaskets * 3 TxLabels * 4 OutputTags * 5 Transactions * 6 TxLabelMaps * 7 Commissions * 8 Outputs * 9 OutputTagMaps * 10 Certificates * 11 CertificateFields */ offsets: { name: string; offset: number }[] } /** * Result received from remote `WalletStorage` in response to a `RequestSyncChunkArgs` request. * * Each property is undefined if there was no attempt to update it. Typically this is caused by size and count limits on this result. * * If all properties are empty arrays the sync process has received all available new and updated items. */ export interface SyncChunk { fromStorageIdentityKey: string toStorageIdentityKey: string userIdentityKey: string user?: TableUser provenTxs?: TableProvenTx[] provenTxReqs?: TableProvenTxReq[] outputBaskets?: TableOutputBasket[] txLabels?: TableTxLabel[] outputTags?: TableOutputTag[] transactions?: TableTransaction[] txLabelMaps?: TableTxLabelMap[] commissions?: TableCommission[] outputs?: TableOutput[] outputTagMaps?: TableOutputTagMap[] certificates?: TableCertificate[] certificateFields?: TableCertificateField[] } export interface ProcessSyncChunkResult { done: boolean maxUpdated_at: Date | undefined updates: number inserts: number error?: WalletError }