import { AppInstanceInfo , AppInstanceJson , AppInstanceProposal , IRpcNodeProvider , Node } from "@counterfactual/types" ; import { BigNumber , bigNumberify } from "ethers/utils" ; import EventEmitter from "eventemitter3" ; import { jsonRpcDeserialize , JsonRpcNotification , JsonRpcResponse , jsonRpcSerializeAsResponse } from "rpc-server" ; import { AppInstance } from "./app-instance" ; import { CounterfactualEvent , EventType } from "./types" ; /**
 * Milliseconds until a method request to the Node is considered timed out.
 */ export const NODE_REQUEST_TIMEOUT = 30000 ; /**
 * Provides convenience methods for interacting with a Counterfactual node
 */ export class Provider { ignore private readonly requestListeners : { [ requestId ] : ( msg : JsonRpcResponse ) => void ; } = { } ; ignore private readonly eventEmitter = new EventEmitter ( ) ; ignore private readonly appInstances : { [ appInstanceId ] : AppInstance ; } = { } ; param nodeProvider constructor ( readonly nodeProvider ) { this . nodeProvider . onMessage ( this . onNodeMessage . bind ( this ) ) ; } note async param appInstanceId return async install ( appInstanceId ) { const response = await this . callRawNodeMethod ( Node . RpcMethodName . INSTALL , { appInstanceId } ) ; const { appInstance } = response . result as Node . InstallResult ; return this . getOrCreateAppInstance ( appInstanceId , appInstance ) ; } note async param appInstanceId param intermediaryIdentifier return async installVirtual ( appInstanceId , intermediaryIdentifier ) { const response = await this . callRawNodeMethod ( Node . RpcMethodName . INSTALL_VIRTUAL , { appInstanceId , intermediaryIdentifier } ) ; const { appInstance } = response . result as Node . InstallVirtualResult ; return this . getOrCreateAppInstance ( appInstanceId , appInstance ) ; } async param appInstanceId async rejectInstall ( appInstanceId ) { await this . callRawNodeMethod ( Node . RpcMethodName . REJECT_INSTALL , { appInstanceId } ) ; } async param owners return async createChannel ( owners ) { const response = await this . callRawNodeMethod ( Node . RpcMethodName . CREATE_CHANNEL , { owners } ) ; const { multisigAddress } = response . result as Node . CreateChannelTransactionResult ; return multisigAddress ; } param multisigAddress return async deployStateDepositHolder ( multisigAddress ) { const deployResponse = await this . callRawNodeMethod ( Node . RpcMethodName . DEPLOY_STATE_DEPOSIT_HOLDER , { multisigAddress } ) ; const { transactionHash } = deployResponse . result as Node . DeployStateDepositHolderResult ; return transactionHash ; } async param multisigAddress param amount async deposit ( multisigAddress , amount ) { await this . callRawNodeMethod ( Node . RpcMethodName . DEPOSIT , { multisigAddress , amount } ) ; } async param multisigAddress param amount param recipient async withdraw ( multisigAddress , amount , recipient ) { await this . callRawNodeMethod ( Node . RpcMethodName . WITHDRAW , { multisigAddress , recipient , amount } ) ; } async param multisigAddress return async getFreeBalanceState ( multisigAddress ) { const response = await this . callRawNodeMethod ( Node . RpcMethodName . GET_FREE_BALANCE_STATE , { multisigAddress } ) ; const freeBalances = Object . keys ( response . result ) . reduce ( ( freeBalances , key ) => { freeBalances [ key ] = bigNumberify ( response . result [ key ] ) ; return freeBalances ; } , { } ) ; return freeBalances as Node . GetFreeBalanceStateResult ; } async param eventType param callback on ( eventType , callback ) { this . eventEmitter . on ( eventType , callback ) ; } param eventType param callback once ( eventType , callback ) { this . eventEmitter . once ( eventType , callback ) ; } param eventType param callback off ( eventType , callback ) { this . eventEmitter . off ( eventType , callback ) ; } param methodName param params async callRawNodeMethod ( methodName , params ) { const requestId = new Date ( ) . valueOf ( ) ; return new Promise < Node . MethodResponse > ( ( resolve , reject ) => { const request = jsonRpcDeserialize ( { params , jsonrpc : "2.0" , method : methodName , id : requestId } ) ; if ( ! request . methodName ) { return this . handleNodeError ( { jsonrpc : "2.0" , result : { type : EventType . ERROR , data : { errorName : "unexpected_event_type" } } , id : requestId } ) ; } this . requestListeners [ requestId ] = response => { if ( response . result . type === Node . ErrorType . ERROR ) { return reject ( jsonRpcSerializeAsResponse ( { ... response . result , type : EventType . ERROR } , requestId ) ) ; } if ( response . result . type !== methodName ) { return reject ( jsonRpcSerializeAsResponse ( { type : EventType . ERROR , data : { errorName : "unexpected_message_type" , message : `Unexpected response type. Expected ${ methodName }, got ${ response . result . type }` } } , requestId ) ) ; } resolve ( response . result ) ; } ; setTimeout ( ( ) => { if ( this . requestListeners [ requestId ] !== undefined ) { reject ( { type : EventType . ERROR , data : { errorName : "request_timeout" , message : `Request timed out: ${ JSON . stringify ( request ) }` } } ) ; delete this . requestListeners [ requestId ] ; } } , NODE_REQUEST_TIMEOUT ) ; this . nodeProvider . sendMessage ( request ) ; } ) ; } param id param info return async getOrCreateAppInstance ( id , info ? ) { if ( ! ( id in this . appInstances ) ) { let newInfo ; if ( info ) { newInfo = info ; } else { const { result } = await this . callRawNodeMethod ( Node . RpcMethodName . GET_APP_INSTANCE_DETAILS , { appInstanceId : id } ) ; newInfo = ( result as Node . GetAppInstanceDetailsResult ) . appInstance ; } this . appInstances [ id ] = new AppInstance ( newInfo , this ) ; } return this . appInstances [ id ] ; } ignore private onNodeMessage ( message ) { const type = message [ "jsonrpc" ] ? ( message as JsonRpcNotification | JsonRpcResponse ) . result . type : ( message as Node . Event ) . type ; if ( Object . values ( Node . ErrorType ) . indexOf ( type ) !== - 1 ) { this . handleNodeError ( message as JsonRpcResponse ) ; } else if ( "id" in message ) { this . handleNodeMethodResponse ( message as JsonRpcResponse ) ; } else { this . handleNodeEvent ( message as JsonRpcNotification | Node . Event ) ; } } ignore private handleNodeError ( error ) { const requestId = error . id ; if ( requestId && this . requestListeners [ requestId ] ) { this . requestListeners [ requestId ] ( error ) ; delete this . requestListeners [ requestId ] ; } this . eventEmitter . emit ( error . result . type , error . result ) ; } ignore private handleNodeMethodResponse ( response ) { const { id } = response ; if ( id in this . requestListeners ) { this . requestListeners [ id ] ( response ) ; delete this . requestListeners [ id ] ; } else { const error = jsonRpcSerializeAsResponse ( { type : EventType . ERROR , data : { errorName : "orphaned_response" , message : `Response has no corresponding inflight request: ${ JSON . stringify ( response ) }` } } , Number ( id ) ) ; this . eventEmitter . emit ( error . result . type , error . result ) ; } } ignore private async handleNodeEvent ( event ) { const nodeEvent = event [ "jsonrpc" ] ? ( event as JsonRpcNotification | JsonRpcResponse ) . result : ( event as Node . Event ) ; switch ( nodeEvent . type ) { case Node . EventName . REJECT_INSTALL : return this . handleRejectInstallEvent ( nodeEvent ) ; case Node . EventName . INSTALL : return this . handleInstallEvent ( nodeEvent ) ; case Node . EventName . INSTALL_VIRTUAL : return this . handleInstallVirtualEvent ( nodeEvent ) ; default : return this . handleGenericEvent ( nodeEvent ) ; } } ignore private handleGenericEvent ( nodeEvent ) { return this . eventEmitter . emit ( nodeEvent . type , nodeEvent ) ; } ignore private async handleInstallEvent ( nodeEvent ) { const { appInstanceId } = nodeEvent . data as Node . InstallEventData ; const appInstance = await this . getOrCreateAppInstance ( appInstanceId ) ; const event = { type : EventType . INSTALL , data : { appInstance } } ; return this . eventEmitter . emit ( event . type , event ) ; } ignore private async handleInstallVirtualEvent ( nodeEvent ) { const { appInstanceId } = nodeEvent . data [ "params" ] ; const appInstance = await this . getOrCreateAppInstance ( appInstanceId ) ; const event = { type : EventType . INSTALL_VIRTUAL , data : { appInstance } } ; return this . eventEmitter . emit ( event . type , event ) ; } ignore private async handleRejectInstallEvent ( nodeEvent ) { const data = nodeEvent . data as Node . RejectInstallEventData ; const info = data . appInstance ; const appInstance = await this . getOrCreateAppInstance ( info . identityHash , info ) ; const event = { type : EventType . REJECT_INSTALL , data : { appInstance } } ; return this . eventEmitter . emit ( event . type , event ) ; } }