@bsv/wallet-toolbox
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
563 lines (500 loc) • 17.6 kB
text/typescript
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
}