UNPKG

0xweb

Version:

Contract package manager and other web3 tools

155 lines (131 loc) 5.22 kB
import { RpcTransport } from './transports/RpcTransport'; import { RpcError } from './RpcError'; import { TTransport } from './transports/ITransport'; import { RpcSubscription } from './RpcSubscription'; import { $rpc } from './$rpc'; import { RpcFunction } from './RpcFunction'; import { $array } from '@dequanto/utils/$array'; import { $hex } from '@dequanto/utils/$hex'; let ID = 0; export abstract class RpcBase { protected _transport: TTransport.Transport; public fns = {} as { [name: string]: (...params) => Promise<any> }; constructor (protected transportInfo?: TTransport.Options.Any) { this._transport = RpcTransport.create(this.transportInfo); } async request <TResult = any> (req: TRpc.IRpcAction): Promise<TResult> { let body = this._wrapBody(req); let resp = await this._transport.request(body); if ('error' in resp) { let params = { ...(req.params ?? {}) }; for (let key in params) { let val = params[key]; if (typeof val === 'string' && val.length > 200) { params[key] = val.substring(0, 100) + '....' + val.slice(-100); } } throw new RpcError(resp.error, `${this._transport.id} | ${req.method} ${JSON.stringify(req.params)}`); } let result = this._unwrapBody(resp); return this._deserialize(req.method, result); } async batch (arr: TRpc.IRpcAction[]): Promise<any[]> { let body = arr.map(req => this._wrapBody(req)); let resp = await this._transport.request(body); if (Array.isArray(resp) === false && arr.length === 1 && 'error' in resp === false && 'result' in resp === true) { // Some RPCs return a single response instead of an array for 1 RpcAction in Batch resp = [ resp ]; } if (Array.isArray(resp) === false) { if ('error' in resp) { let error = (resp as any).error; throw new RpcError(error); } throw new Error(`RpcBatch: invalid response, array expected: ${ JSON.stringify(resp) }`); } return resp.map((resp, i) => { let req = arr[i]; if ('error' in resp) { throw new RpcError(resp.error, `BatchRequest ${i + 1}/${arr.length} (${req.method} ${JSON.stringify(req.params)})`); } let result = this._unwrapBody(resp); return this._deserialize(req.method, result); }); } extend (rpcInfos: { name: string call: string params?: any[] output?: any }[]) { rpcInfos.forEach(rpcInfo => { let fn = new RpcFunction(this, { ...rpcInfo, schemas: this.returnSchemas?.schemas }); this.fns[rpcInfo.name] = fn.caller(); }); } protected async subscribe <TReturn = any> (req: TRpc.IRpcAction): Promise<RpcSubscription<TReturn>> { let body = this._wrapBody(req); let subscription = await this._transport.subscribe<TReturn>(body); let mapped = RpcSubscription.createMapping(subscription, this._transport, x => this._deserialize(`${req.method}.${req.params[0]}`, x)) return mapped as RpcSubscription<TReturn>; //return subscription.map(x => this._deserialize(`${req.method}.${req.params[0]}`, x)); } private _wrapBody (req: TRpc.IRpcAction): TTransport.Request private _wrapBody (req: TRpc.IRpcAction | TRpc.IRpcAction[]): TTransport.Request | TTransport.Request[] { if (Array.isArray(req)) { return req.map(x => this._wrapBody(x)); } if (req.params?.length > 0) { if (req.params[req.params.length - 1] == null) { req.params = $array.trimEnd(req.params); } for (let i = 0; i < req.params.length; i++) { let x = req.params[i]; if (typeof x === 'bigint' || typeof x === 'number') { req.params[i] = $hex.ensure(x); } } } return { id: ID++, jsonrpc: '2.0', method: req.method, params: req.params }; } private _unwrapBody (resp: any): any { return resp.result; } private _deserialize (method: string, result: any) { let { methods, schemas } = this.returnSchemas; let schema = methods[method]; if (method === 'eth_subscribe.newHeads') { schema = 'Block'; } if (schema in schemas) { schema = schemas[schema]; } if (schema == null || result == null) { return result; } return $rpc.deserialize(result, schema, this.returnSchemas?.schemas); } protected abstract returnSchemas } export namespace TRpc { export interface IRpc { request <TResult = any> (req: IRpcAction): Promise<TResult>; } export interface IRpcAction { // from?: TEth.Address // to?: TEth.Address method: string params?: any[] //value?: bigint | TEth.Hex } }