import { Injectable , OnApplicationShutdown } from '@nestjs/common' ; import { EventEmitter2 } from '@nestjs/event-emitter' ; import { ApiPromise , HttpProvider , WsProvider } from '@polkadot/api' ; import { ApiOptions , RpcMethodResult } from '@polkadot/api/types' ; import { BlockHash , RuntimeVersion } from '@polkadot/types/interfaces' ; import { AnyFunction } from '@polkadot/types/types' ; import { SubqueryProject } from '../configure/project.model' ; import { getLogger } from '../utils/logger' ; import { IndexerEvent , NetworkMetadataPayload } from './events' ; import { ApiAt } from './types' ; const NOT_SUPPORT = ( name ) => ( ) => { throw new Error ( `${ name }() is not supported` ) ; } ; const logger = getLogger ( 'api' ) ; @ Injectable ( ) export class ApiService implements OnApplicationShutdown { private api : ApiPromise ; private currentBlockHash : string ; private currentRuntimeVersion : RuntimeVersion ; private apiOption : ApiOptions ; networkMeta : NetworkMetadataPayload ; constructor ( protected project , private eventEmitter ) { } async onApplicationShutdown ( ) { await Promise . all ( [ this . api ?. disconnect ( ) ] ) ; } async init ( ) { let chainTypes , network ; try { chainTypes = this . project . chainTypes ; network = this . project . network ; } catch ( e ) { logger . error ( e ) ; process . exit ( 1 ) ; } let provider ; let throwOnConnect = false ; if ( network . endpoint . startsWith ( 'ws' ) ) { provider = new WsProvider ( network . endpoint ) ; } else if ( network . endpoint . startsWith ( 'http' ) ) { provider = new HttpProvider ( network . endpoint ) ; throwOnConnect = true ; } this . apiOption = { provider , throwOnConnect , ... chainTypes , } ; this . api = await ApiPromise . create ( this . apiOption ) ; this . eventEmitter . emit ( IndexerEvent . ApiConnected , { value : 1 } ) ; this . api . on ( 'connected' , ( ) => { this . eventEmitter . emit ( IndexerEvent . ApiConnected , { value : 1 } ) ; } ) ; this . api . on ( 'disconnected' , ( ) => { this . eventEmitter . emit ( IndexerEvent . ApiConnected , { value : 0 } ) ; } ) ; this . networkMeta = { chain : this . api . runtimeChain . toString ( ) , specName : this . api . runtimeVersion . specName . toString ( ) , genesisHash : this . api . genesisHash . toString ( ) , } ; if ( network . genesisHash && network . genesisHash !== this . networkMeta . genesisHash ) { const err = new Error ( `Network genesisHash doesn't match expected genesisHash. expected="${ network . genesisHash }" actual="${ this . networkMeta . genesisHash }` ) ; logger . error ( err , err . message ) ; throw err ; } return this ; } getApi ( ) { return this . api ; } async getPatchedApi ( blockHash , parentBlockHash ? ) { this . currentBlockHash = blockHash . toString ( ) ; if ( parentBlockHash ) { this . currentRuntimeVersion = await this . api . rpc . state . getRuntimeVersion ( parentBlockHash ) ; } const apiAt = ( await this . api . at ( blockHash , this . currentRuntimeVersion ) ) as ApiAt ; this . patchApiRpc ( this . api , apiAt ) ; return apiAt ; } private redecorateRpcFunction < T extends 'promise' | 'rxjs' > ( original ) { if ( original . meta . params ) { const hashIndex = original . meta . params . findIndex ( ( { isHistoric , name } ) => isHistoric ) ; if ( hashIndex > - 1 ) { const ret = ( ( ... args ) => { const argsClone = [ ... args ] ; argsClone [ hashIndex ] = this . currentBlockHash ; return original ( ... argsClone ) ; } ) as RpcMethodResult < T , AnyFunction > ; ret . raw = NOT_SUPPORT ( 'api.rpc.*.*.raw' ) ; ret . meta = original . meta ; return ret ; } } const ret = NOT_SUPPORT ( 'api.rpc.*.*' ) as unknown as RpcMethodResult < T , AnyFunction > ; ret . raw = NOT_SUPPORT ( 'api.rpc.*.*.raw' ) ; ret . meta = original . meta ; return ret ; } private patchApiRpc ( api , apiAt ) { apiAt . rpc = Object . entries ( api . rpc ) . reduce ( ( acc , [ module , rpcMethods ] ) => { acc [ module ] = Object . entries ( rpcMethods ) . reduce ( ( accInner , [ name , rpcPromiseResult ] ) => { accInner [ name ] = this . redecorateRpcFunction ( rpcPromiseResult ) ; return accInner ; } , { } ) ; return acc ; } , { } as ApiPromise [ 'rpc' ] ) ; } }