UNPKG

wallet-storage

Version:

BRC100 conforming wallet, wallet storage and wallet signer components

268 lines (227 loc) 8.99 kB
/** * StorageClient.ts * * A client-side "remoted" WalletStorage that fulfills the WalletStorage interface * by sending JSON-RPC calls to a configured remote WalletStorageServer. */ import { AbortActionArgs, AbortActionResult, InternalizeActionArgs, InternalizeActionResult, ListActionsResult, ListCertificatesResult, ListOutputsResult, RelinquishCertificateArgs, RelinquishOutputArgs, WalletInterface } from '@bsv/sdk' import { sdk, table } from "../../index.client"; import { AuthFetch } from '@bsv/sdk'; export class StorageClient implements sdk.WalletStorageProvider { private endpointUrl: string private nextId = 1 private authClient: AuthFetch // Track ephemeral (in-memory) "settings" if you wish to align with isAvailable() checks public settings?: table.Settings constructor(wallet: WalletInterface, endpointUrl: string) { this.authClient = new AuthFetch(wallet) this.endpointUrl = endpointUrl } isStorageProvider(): boolean { return false } ////////////////////////////////////////////////////////////////////////////// // JSON-RPC helper ////////////////////////////////////////////////////////////////////////////// /** * Make a JSON-RPC call to the remote server. * @param method The WalletStorage method name to call. * @param params The array of parameters to pass to the method in order. */ private async rpcCall<T>(method: string, params: unknown[]): Promise<T> { const id = this.nextId++ const body = { jsonrpc: "2.0", method, params, id } const response = await this.authClient.fetch(this.endpointUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }) if (!response.ok) { throw new Error(`WalletStorageClient rpcCall: network error ${response.status} ${response.statusText}`) } const json = await response.json() if (json.error) { const { code, message, data } = json.error const err = new Error(`RPC Error: ${message}`) // You could attach more info here if you like: ; (err as any).code = code ; (err as any).data = data throw err } return json.result } ////////////////////////////////////////////////////////////////////////////// // In a real environment, you might do lazy or real "makeAvailable" logic // For demonstration, we assume that the remote store might return its "settings" // and we store them locally in `this.settings`. ////////////////////////////////////////////////////////////////////////////// isAvailable(): boolean { // We'll just say "yes" if we have settings return !!this.settings } getSettings(): table.Settings { if (!this.settings) { throw new sdk.WERR_INVALID_OPERATION('call makeAvailable at least once before getSettings') } return this.settings } async makeAvailable(): Promise<table.Settings> { if (!this.settings) { this.settings = await this.rpcCall<table.Settings>("makeAvailable", []) } return this.settings } ////////////////////////////////////////////////////////////////////////////// // // Implementation of all WalletStorage interface methods // They are simple pass-thrus to rpcCall // // IMPORTANT: The parameter ordering must match exactly as in your interface. ////////////////////////////////////////////////////////////////////////////// async destroy(): Promise<void> { return this.rpcCall<void>("destroy", []) } async migrate(storageName: string, storageIdentityKey: string): Promise<string> { return this.rpcCall<string>("migrate", [storageName]) } getServices(): sdk.WalletServices { // Typically, the client would not store or retrieve "Services" from a remote server. // The "services" in local in-memory usage is a no-op or your own approach: throw new Error("getServices() not implemented in remote client. This method typically is not used remotely.") } setServices(v: sdk.WalletServices): void { // Typically no-op for remote client // Because "services" are usually local definitions to the Storage. } async internalizeAction( auth: sdk.AuthId, args: InternalizeActionArgs, ): Promise<InternalizeActionResult> { return this.rpcCall<InternalizeActionResult>("internalizeAction", [auth, args]) } async createAction( auth: sdk.AuthId, args: sdk.ValidCreateActionArgs, ): Promise<sdk.StorageCreateActionResult> { return this.rpcCall<sdk.StorageCreateActionResult>("createAction", [auth, args]) } async processAction( auth: sdk.AuthId, args: sdk.StorageProcessActionArgs, ): Promise<sdk.StorageProcessActionResults> { return this.rpcCall<sdk.StorageProcessActionResults>("processAction", [auth, args]) } async abortAction( auth: sdk.AuthId, args: AbortActionArgs, ): Promise<AbortActionResult> { return this.rpcCall<AbortActionResult>("abortAction", [auth, args]) } async findOrInsertUser( identityKey ): Promise<{ user: table.User; isNew: boolean; }> { return this.rpcCall<{ user: table.User; isNew: boolean }>("findOrInsertUser", [identityKey]) } async findOrInsertSyncStateAuth( auth: sdk.AuthId, storageIdentityKey: string, storageName: string ): Promise<{ syncState: table.SyncState; isNew: boolean; }> { return this.rpcCall<{ syncState: table.SyncState; isNew: boolean }>("findOrInsertSyncStateAuth", [auth, storageIdentityKey, storageName]) } async insertCertificateAuth( auth: sdk.AuthId, certificate: table.CertificateX ): Promise<number> { return this.rpcCall<number>("insertCertificateAuth", [auth, certificate]) } async listActions( auth: sdk.AuthId, vargs: sdk.ValidListActionsArgs, ): Promise<ListActionsResult> { return this.rpcCall<ListActionsResult>("listActions", [auth, vargs]) } async listOutputs( auth: sdk.AuthId, vargs: sdk.ValidListOutputsArgs, ): Promise<ListOutputsResult> { return this.rpcCall<ListOutputsResult>("listOutputs", [auth, vargs]) } async listCertificates( auth: sdk.AuthId, vargs: sdk.ValidListCertificatesArgs, ): Promise<ListCertificatesResult> { return this.rpcCall<ListCertificatesResult>("listCertificates", [auth, vargs]) } async findCertificatesAuth( auth: sdk.AuthId, args: sdk.FindCertificatesArgs ): Promise<table.Certificate[]> { return this.rpcCall<table.Certificate[]>("findCertificatesAuth", [auth, args]) } async findOutputBasketsAuth( auth: sdk.AuthId, args: sdk.FindOutputBasketsArgs ): Promise<table.OutputBasket[]> { return this.rpcCall<table.OutputBasket[]>("findOutputBaskets", [auth, args]) } async findOutputsAuth( auth: sdk.AuthId, args: sdk.FindOutputsArgs ): Promise<table.Output[]> { return this.rpcCall<table.Output[]>("findOutputsAuth", [auth, args]) } findProvenTxReqs( args: sdk.FindProvenTxReqsArgs ): Promise<table.ProvenTxReq[]> { return this.rpcCall<table.ProvenTxReq[]>("findProvenTxReqs", [args]) } async relinquishCertificate( auth: sdk.AuthId, args: RelinquishCertificateArgs ): Promise<number> { return this.rpcCall<number>("relinquishCertificate", [auth, args]) } async relinquishOutput( auth: sdk.AuthId, args: RelinquishOutputArgs ): Promise<number> { return this.rpcCall<number>("relinquishOutput", [auth, args]) } async processSyncChunk( args: sdk.RequestSyncChunkArgs, chunk: sdk.SyncChunk ): Promise<sdk.ProcessSyncChunkResult> { return this.rpcCall<sdk.ProcessSyncChunkResult>("processSyncChunk", [args, chunk]) } async getSyncChunk( args: sdk.RequestSyncChunkArgs ): Promise<sdk.SyncChunk> { return this.rpcCall<sdk.SyncChunk>("getSyncChunk", [args]) } async updateProvenTxReqWithNewProvenTx( args: sdk.UpdateProvenTxReqWithNewProvenTxArgs ): Promise<sdk.UpdateProvenTxReqWithNewProvenTxResult> { return this.rpcCall<sdk.UpdateProvenTxReqWithNewProvenTxResult>("updateProvenTxReqWithNewProvenTx", [args]) } async setActive( auth: sdk.AuthId, newActiveStorageIdentityKey: string ): Promise<number> { return this.rpcCall<number>("setActive", [auth, newActiveStorageIdentityKey]) } }