import { ConnectorUpdate } from '@web3-react/types' ; import { AbstractConnector } from '@web3-react/abstract-connector' ; import invariant from 'tiny-invariant' ; interface NetworkConnectorArguments { urls : { [ chainId ] : string ; } ; defaultChainId ? : number ; } type AsyncSendable = { isMetaMask ? : boolean ; host ? : string ; path ? : string ; sendAsync ? : ( request : any , callback : ( error : any , response : any ) => void ) => void ; send ? : ( request : any , callback : ( error : any , response : any ) => void ) => void ; } ; class RequestError extends Error { constructor ( message , public code , public data ? ) { super ( message ) ; } } interface BatchItem { request : { jsonrpc : '2.0' ; id : number ; method : string ; params : unknown ; } ; resolve : ( result : any ) => void ; reject : ( error : Error ) => void ; } class MiniRpcProvider implements AsyncSendable { public readonly isMetaMask : false = false ; public readonly chainId : number ; public readonly url : string ; public readonly host : string ; public readonly path : string ; public readonly batchWaitTimeMs : number ; private nextId = 1 ; private batchTimeoutId : ReturnType < typeof setTimeout > | null = null ; private batch : BatchItem [ ] = [ ] ; constructor ( chainId , url , batchWaitTimeMs ? ) { this . chainId = chainId ; this . url = url ; const parsed = new URL ( url ) ; this . host = parsed . host ; this . path = parsed . pathname ; this . batchWaitTimeMs = batchWaitTimeMs ?? 50 ; } public readonly clearBatch = async ( ) => { console . info ( 'Clearing batch' , this . batch ) ; const { batch } = this ; this . batch = [ ] ; this . batchTimeoutId = null ; let response ; try { response = await fetch ( this . url , { method : 'POST' , headers : { 'content-type' : 'application/json' , accept : 'application/json' } , body : JSON . stringify ( batch . map ( ( item ) => item . request ) ) , } ) ; } catch ( error ) { batch . forEach ( ( { reject } ) => reject ( new Error ( 'Failed to send batch call' ) ) ) ; return ; } if ( ! response . ok ) { batch . forEach ( ( { reject } ) => reject ( new RequestError ( `${ response . status }: ${ response . statusText }` , - 32000 ) ) ) ; return ; } let json ; try { json = await response . json ( ) ; } catch ( error ) { batch . forEach ( ( { reject } ) => reject ( new Error ( 'Failed to parse JSON response' ) ) ) ; return ; } const byKey = batch . reduce < { [ id ] : BatchItem ; } > ( ( memo , current ) => { memo [ current . request . id ] = current ; return memo ; } , { } ) ; for ( const result of json ) { const { resolve , reject , request : { method } , } = byKey [ result . id ] ; if ( resolve && reject ) { if ( 'error' in result ) { reject ( new RequestError ( result ?. error ?. message , result ?. error ?. code , result ?. error ?. data ) ) ; } else if ( 'result' in result ) { resolve ( result . result ) ; } else { reject ( new RequestError ( `Received unexpected JSON-RPC response to ${ method } request.` , - 32000 , result ) ) ; } } } } ; public readonly sendAsync = ( request , callback ) : void => { this . request ( request . method , request . params ) . then ( ( result ) => callback ( null , { jsonrpc : '2.0' , id : request . id , result } ) ) . catch ( ( error ) => callback ( error , null ) ) ; } ; public readonly request = async ( method , params ? ) : Promise < unknown > => { if ( typeof method !== 'string' ) { return this . request ( method . method , method . params ) ; } if ( method === 'eth_chainId' ) { return `0x${ this . chainId . toString ( 16 ) }` ; } const promise = new Promise ( ( resolve , reject ) => { this . batch . push ( { request : { jsonrpc : '2.0' , id : this . nextId ++ , method , params , } , resolve , reject , } ) ; } ) ; this . batchTimeoutId = this . batchTimeoutId ?? setTimeout ( this . clearBatch , this . batchWaitTimeMs ) ; return promise ; } ; } export class NetworkConnector extends AbstractConnector { private readonly providers : { [ chainId ] : MiniRpcProvider ; } ; private currentChainId : number ; constructor ( { urls , defaultChainId } ) { invariant ( defaultChainId || Object . keys ( urls ) . length === 1 , 'defaultChainId is a required argument with >1 url' ) ; super ( { supportedChainIds : Object . keys ( urls ) . map ( ( k ) : number => Number ( k ) ) } ) ; this . currentChainId = defaultChainId || Number ( Object . keys ( urls ) [ 0 ] ) ; this . providers = Object . keys ( urls ) . reduce < { [ chainId ] : MiniRpcProvider ; } > ( ( accumulator , chainId ) => { accumulator [ Number ( chainId ) ] = new MiniRpcProvider ( Number ( chainId ) , urls [ Number ( chainId ) ] ) ; return accumulator ; } , { } ) ; } public get provider ( ) : MiniRpcProvider { return this . providers [ this . currentChainId ] ; } public async activate ( ) { return { provider : this . providers [ this . currentChainId ] , chainId : this . currentChainId , account : null } ; } public async getProvider ( ) { return this . providers [ this . currentChainId ] ; } public async getChainId ( ) { return this . currentChainId ; } public async getAccount ( ) { return null ; } public deactivate ( ) { return null ; } } export default NetworkConnector ;