import NodeCache from "node-cache"; import { PluginAPI } from "@lumeweb/interface-relay"; import stringify from "json-stringify-deterministic"; export type RPCRequest = { method: string; params: any[]; }; export type RPCRequestRaw = RPCRequest & { jsonrpc: string; id: string; }; export type RPCResponse = { success: boolean; result: any; }; export type ProviderConfig = { URL: string; unsupportedMethods?: string[]; supportBatchRequests?: boolean; batchSize?: number; }; export class RPC { get cache(): NodeCache { return this._cache; } private provider: ProviderConfig; private _cache = new NodeCache({ stdTTL: 60 * 60 * 12 }); constructor(provider: ProviderConfig) { this.provider = provider; } private _pluginApi?: PluginAPI; set pluginApi(value: PluginAPI) { this._pluginApi = value; } async request(request: RPCRequest) { if (this.provider.unsupportedMethods?.includes(request.method)) { throw new Error("method not supported by the provider"); } return await this._retryRequest(request); } async requestBatch(requests: RPCRequest[]) { if ( this.provider?.unsupportedMethods && requests.some((r) => this.provider.unsupportedMethods!.includes(r.method)) ) { throw new Error("method not supported by the provider"); } const res: RPCResponse[] = []; for (const request of requests) { const r = await this._retryRequest(request); res.push(r); } return res; } protected async _request(request: RPCRequestRaw): Promise { try { const response = await ( await fetch(this.provider.URL, { method: "POST", body: JSON.stringify(request), }) ).json(); if (response.result) { const tempRequest = { method: request.method, params: request.params, }; const hash = (this._pluginApi as PluginAPI).util.crypto .createHash(stringify(tempRequest)) .toString("hex"); // this._cache.set(hash, response); } return { success: !response.error, result: response.error || response.result, }; } catch (e) { return { success: false, result: { message: `request failed: ${e}` }, }; } } private async _retryRequest( _request: RPCRequest, retry = 5, ): Promise { const request = { ..._request, jsonrpc: "2.0", id: this.generateId(), }; for (let i = retry; i > 0; i--) { const res = await this._request(request); if (res.success) { return res; } else if (i == 1) { console.error( `RPC batch request failed after maximum retries: ${JSON.stringify( request, null, 2, )} ${JSON.stringify(res, null, 2)}`, ); } } throw new Error("RPC request failed"); } private generateId(): string { return Math.floor(Math.random() * 2 ** 64).toFixed(); } /* public getCachedRequest(request: RPCRequest): RPCResponse | null { const hash = this.hashRequest(request); if (this.cache.has(hash)) { this.cache.ttl(hash); return this.cache.get(hash); } return null; }*/ public deleteCachedRequest(request: RPCRequest): void { const hash = this.hashRequest(request); this.cache.del(hash); } private hashRequest(request: RPCRequest): string { const tempRequest = { method: request.method, params: request.params, }; return (this._pluginApi as PluginAPI).util.crypto .createHash(stringify(tempRequest)) .toString("hex"); } }