wallet-storage
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
268 lines (227 loc) • 8.99 kB
text/typescript
/**
* 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])
}
}