From c677e5942c3923f57c518f285e316db72da56a41 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Wed, 28 Jul 2021 10:04:07 +0530 Subject: [PATCH] ERC20 watcher based on eth_call (#165) * Implement eth_call for ERC20 totalSupply. * Use eth_call for erc20-watcher. * Implement fallback for ERC20 symbol method call. * Implement fallback for ERC20 name and totalSupply. * implement fallback for erc20 decimals method. * Lint fixes. Co-authored-by: nabarun --- .../erc20-watcher/environments/local.toml | 2 + .../src/artifacts/ERC20NameBytes.json | 17 +++ .../src/artifacts/ERC20SymbolBytes.json | 17 +++ .../erc20-watcher/src/entity/Allowance.ts | 2 +- packages/erc20-watcher/src/entity/Balance.ts | 2 +- packages/erc20-watcher/src/events.ts | 4 +- packages/erc20-watcher/src/indexer.ts | 107 ++++++++++++++--- packages/erc20-watcher/src/resolvers.ts | 2 +- packages/erc20-watcher/src/server.ts | 9 +- packages/erc20-watcher/src/utils/index.ts | 110 ++++++++++++++++++ .../src/utils/static-token-definition.ts | 94 +++++++++++++++ packages/ipld-eth-client/src/eth-queries.ts | 15 ++- packages/uni-info-watcher/src/database.ts | 2 +- packages/uni-watcher/src/events.ts | 6 +- packages/util/src/config.ts | 2 + 15 files changed, 353 insertions(+), 38 deletions(-) create mode 100644 packages/erc20-watcher/src/artifacts/ERC20NameBytes.json create mode 100644 packages/erc20-watcher/src/artifacts/ERC20SymbolBytes.json create mode 100644 packages/erc20-watcher/src/utils/index.ts create mode 100644 packages/erc20-watcher/src/utils/static-token-definition.ts diff --git a/packages/erc20-watcher/environments/local.toml b/packages/erc20-watcher/environments/local.toml index 3b7f95ea..5464d7ab 100644 --- a/packages/erc20-watcher/environments/local.toml +++ b/packages/erc20-watcher/environments/local.toml @@ -1,6 +1,7 @@ [server] host = "127.0.0.1" port = 3001 + mode = "eth_call" [database] type = "postgres" @@ -25,6 +26,7 @@ [upstream.ethServer] gqlApiEndpoint = "http://127.0.0.1:8082/graphql" gqlPostgraphileEndpoint = "http://127.0.0.1:5000/graphql" + rpcProviderEndpoint = "http://127.0.0.1:8545" [upstream.cache] name = "requests" diff --git a/packages/erc20-watcher/src/artifacts/ERC20NameBytes.json b/packages/erc20-watcher/src/artifacts/ERC20NameBytes.json new file mode 100644 index 00000000..2d3c877a --- /dev/null +++ b/packages/erc20-watcher/src/artifacts/ERC20NameBytes.json @@ -0,0 +1,17 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] diff --git a/packages/erc20-watcher/src/artifacts/ERC20SymbolBytes.json b/packages/erc20-watcher/src/artifacts/ERC20SymbolBytes.json new file mode 100644 index 00000000..a76d6163 --- /dev/null +++ b/packages/erc20-watcher/src/artifacts/ERC20SymbolBytes.json @@ -0,0 +1,17 @@ +[ + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] diff --git a/packages/erc20-watcher/src/entity/Allowance.ts b/packages/erc20-watcher/src/entity/Allowance.ts index 92091d9a..e268d850 100644 --- a/packages/erc20-watcher/src/entity/Allowance.ts +++ b/packages/erc20-watcher/src/entity/Allowance.ts @@ -21,6 +21,6 @@ export class Allowance { @Column('numeric') value!: bigint; - @Column('text') + @Column('text', { nullable: true }) proof!: string; } diff --git a/packages/erc20-watcher/src/entity/Balance.ts b/packages/erc20-watcher/src/entity/Balance.ts index b3aff06e..36c27e32 100644 --- a/packages/erc20-watcher/src/entity/Balance.ts +++ b/packages/erc20-watcher/src/entity/Balance.ts @@ -18,6 +18,6 @@ export class Balance { @Column('numeric') value!: bigint; - @Column('text') + @Column('text', { nullable: true }) proof!: string; } diff --git a/packages/erc20-watcher/src/events.ts b/packages/erc20-watcher/src/events.ts index 98dcbcb9..e5788677 100644 --- a/packages/erc20-watcher/src/events.ts +++ b/packages/erc20-watcher/src/events.ts @@ -39,11 +39,11 @@ export class EventWatcher { if (isWatchedContract) { // TODO: Move processing to background task runner. - const { ethTransactionCidByTxId: { ethHeaderCidByHeaderId: { blockHash } } } = receipt; + const { ethTransactionCidByTxId: { ethHeaderCidByHeaderId: { blockHash, blockNumber } } } = receipt; await this._indexer.getEvents(blockHash, contractAddress, null); // Trigger other indexer methods based on event topic. - await this._indexer.processEvent(blockHash, contractAddress, receipt, logIndex); + await this._indexer.processEvent(blockHash, blockNumber, contractAddress, receipt, logIndex); } } } diff --git a/packages/erc20-watcher/src/indexer.ts b/packages/erc20-watcher/src/indexer.ts index bcb3a406..32a99b47 100644 --- a/packages/erc20-watcher/src/indexer.ts +++ b/packages/erc20-watcher/src/indexer.ts @@ -4,7 +4,8 @@ import { invert } from 'lodash'; import { JsonFragment } from '@ethersproject/abi'; import { DeepPartial } from 'typeorm'; import JSONbig from 'json-bigint'; -import { ethers } from 'ethers'; +import { BigNumber, ethers } from 'ethers'; +import { BaseProvider } from '@ethersproject/providers'; import { PubSub } from 'apollo-server-express'; import { EthClient, topictoAddress } from '@vulcanize/ipld-eth-client'; @@ -12,9 +13,12 @@ import { getEventNameTopics, getStorageValue, GetStorageAt, StorageLayout } from import { Database } from './database'; import { Event } from './entity/Event'; +import { fetchTokenDecimals, fetchTokenName, fetchTokenSymbol, fetchTokenTotalSupply } from './utils'; const log = debug('vulcanize:indexer'); +const ETH_CALL_MODE = 'eth_call'; + interface Artifacts { abi: JsonFragment[]; storageLayout: StorageLayout; @@ -22,7 +26,7 @@ interface Artifacts { export interface ValueResult { value: string | bigint; - proof: { + proof?: { data: string; } } @@ -36,7 +40,7 @@ type EventsResult = Array<{ value?: BigInt; __typename: string; } - proof: string; + proof?: string; }> export class Indexer { @@ -44,12 +48,14 @@ export class Indexer { _ethClient: EthClient _pubsub: PubSub _getStorageAt: GetStorageAt + _ethProvider: BaseProvider _abi: JsonFragment[] _storageLayout: StorageLayout _contract: ethers.utils.Interface + _serverMode: string - constructor (db: Database, ethClient: EthClient, pubsub: PubSub, artifacts: Artifacts) { + constructor (db: Database, ethClient: EthClient, ethProvider: BaseProvider, pubsub: PubSub, artifacts: Artifacts, serverMode: string) { assert(db); assert(ethClient); assert(pubsub); @@ -62,8 +68,10 @@ export class Indexer { this._db = db; this._ethClient = ethClient; + this._ethProvider = ethProvider; this._pubsub = pubsub; this._getStorageAt = this._ethClient.getStorageAt.bind(this._ethClient); + this._serverMode = serverMode; this._abi = abi; this._storageLayout = storageLayout; @@ -76,10 +84,18 @@ export class Indexer { } async totalSupply (blockHash: string, token: string): Promise { - const result = await this._getStorageValue(blockHash, token, '_totalSupply'); + let result: ValueResult; + + if (this._serverMode === ETH_CALL_MODE) { + const value = await fetchTokenTotalSupply(this._ethProvider, token); + + result = { value }; + } else { + result = await this._getStorageValue(blockHash, token, '_totalSupply'); + } // https://github.com/GoogleChromeLabs/jsbi/issues/30#issuecomment-521460510 - // log(JSONbig.stringify(result, null, 2)); + log(JSONbig.stringify(result, null, 2)); return result; } @@ -96,9 +112,25 @@ export class Indexer { } log('balanceOf: db miss, fetching from upstream server'); - const result = await this._getStorageValue(blockHash, token, '_balances', owner); + let result: ValueResult; - // log(JSONbig.stringify(result, null, 2)); + if (this._serverMode === ETH_CALL_MODE) { + const contract = new ethers.Contract(token, this._abi, this._ethProvider); + const { block } = await this._ethClient.getBlockByHash(blockHash); + const { number } = block; + const blockNumber = BigNumber.from(number).toNumber(); + + // eth_call doesnt support calling method by blockHash https://eth.wiki/json-rpc/API#the-default-block-parameter + const value = await contract.balanceOf(owner, { blockTag: blockNumber }); + + result = { + value: BigInt(value.toString()) + }; + } else { + result = await this._getStorageValue(blockHash, token, '_balances', owner); + } + + log(JSONbig.stringify(result, null, 2)); const { value, proof } = result; await this._db.saveBalance({ blockHash, token, owner, value: BigInt(value), proof: JSONbig.stringify(proof) }); @@ -118,7 +150,21 @@ export class Indexer { } log('allowance: db miss, fetching from upstream server'); - const result = await this._getStorageValue(blockHash, token, '_allowances', owner, spender); + let result: ValueResult; + + if (this._serverMode === ETH_CALL_MODE) { + const contract = new ethers.Contract(token, this._abi, this._ethProvider); + const { block } = await this._ethClient.getBlockByHash(blockHash); + const { number } = block; + const blockNumber = BigNumber.from(number).toNumber(); + const value = await contract.allowance(owner, spender, { blockTag: blockNumber }); + + result = { + value: BigInt(value.toString()) + }; + } else { + result = await this._getStorageValue(blockHash, token, '_allowances', owner, spender); + } // log(JSONbig.stringify(result, null, 2)); @@ -129,7 +175,15 @@ export class Indexer { } async name (blockHash: string, token: string): Promise { - const result = await this._getStorageValue(blockHash, token, '_name'); + let result: ValueResult; + + if (this._serverMode === ETH_CALL_MODE) { + const value = await fetchTokenName(this._ethProvider, token); + + result = { value }; + } else { + result = await this._getStorageValue(blockHash, token, '_name'); + } // log(JSONbig.stringify(result, null, 2)); @@ -137,18 +191,35 @@ export class Indexer { } async symbol (blockHash: string, token: string): Promise { - const result = await this._getStorageValue(blockHash, token, '_symbol'); + let result: ValueResult; + + if (this._serverMode === ETH_CALL_MODE) { + const value = await fetchTokenSymbol(this._ethProvider, token); + + result = { value }; + } else { + result = await this._getStorageValue(blockHash, token, '_symbol'); + } // log(JSONbig.stringify(result, null, 2)); return result; } - async decimals (): Promise { - // Not a state variable, uses hardcoded return value in contract function. - // See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol#L86 + async decimals (blockHash: string, token: string): Promise { + let result: ValueResult; - throw new Error('Not implemented.'); + if (this._serverMode === ETH_CALL_MODE) { + const value = await fetchTokenDecimals(this._ethProvider, token); + + result = { value }; + } else { + // Not a state variable, uses hardcoded return value in contract function. + // See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol#L86 + throw new Error('Not implemented.'); + } + + return result; } async getEvents (blockHash: string, token: string, name: string | null): Promise { @@ -206,7 +277,7 @@ export class Indexer { return result; } - async triggerIndexingOnEvent (blockHash: string, token: string, receipt: any, logIndex: number): Promise { + async triggerIndexingOnEvent (blockHash: string, blockNumber: number, token: string, receipt: any, logIndex: number): Promise { const topics = []; // We only care about the event type for now. @@ -257,9 +328,9 @@ export class Indexer { }); } - async processEvent (blockHash: string, token: string, receipt: any, logIndex: number): Promise { + async processEvent (blockHash: string, blockNumber: number, token: string, receipt: any, logIndex: number): Promise { // Trigger indexing of data based on the event. - await this.triggerIndexingOnEvent(blockHash, token, receipt, logIndex); + await this.triggerIndexingOnEvent(blockHash, blockNumber, token, receipt, logIndex); // Also trigger downstream event watcher subscriptions. await this.publishEventToSubscribers(blockHash, token, logIndex); diff --git a/packages/erc20-watcher/src/resolvers.ts b/packages/erc20-watcher/src/resolvers.ts index a90548fa..19d279e3 100644 --- a/packages/erc20-watcher/src/resolvers.ts +++ b/packages/erc20-watcher/src/resolvers.ts @@ -62,7 +62,7 @@ export const createResolvers = async (indexer: Indexer): Promise => { decimals: (_: any, { blockHash, token }: { blockHash: string, token: string }) => { log('decimals', blockHash, token); - return indexer.decimals(); + return indexer.decimals(blockHash, token); }, events: async (_: any, { blockHash, token, name }: { blockHash: string, token: string, name: string }) => { diff --git a/packages/erc20-watcher/src/server.ts b/packages/erc20-watcher/src/server.ts index 53eb1382..0ad447f2 100644 --- a/packages/erc20-watcher/src/server.ts +++ b/packages/erc20-watcher/src/server.ts @@ -7,6 +7,7 @@ import { hideBin } from 'yargs/helpers'; import debug from 'debug'; import 'graphql-import-node'; import { createServer } from 'http'; +import { getDefaultProvider } from 'ethers'; import { getCache } from '@vulcanize/cache'; import { EthClient } from '@vulcanize/ipld-eth-client'; @@ -37,7 +38,7 @@ export const main = async (): Promise => { assert(config.server, 'Missing server config'); - const { host, port } = config.server; + const { host, port, mode } = config.server; const { upstream, database: dbConfig } = config; @@ -47,7 +48,7 @@ export const main = async (): Promise => { await db.init(); assert(upstream, 'Missing upstream config'); - const { ethServer: { gqlApiEndpoint, gqlPostgraphileEndpoint }, cache: cacheConfig } = upstream; + const { ethServer: { gqlApiEndpoint, gqlPostgraphileEndpoint, rpcProviderEndpoint }, cache: cacheConfig } = upstream; assert(gqlApiEndpoint, 'Missing upstream ethServer.gqlApiEndpoint'); assert(gqlPostgraphileEndpoint, 'Missing upstream ethServer.gqlPostgraphileEndpoint'); @@ -58,10 +59,12 @@ export const main = async (): Promise => { cache }); + const ethProvider = getDefaultProvider(rpcProviderEndpoint); + // Note: In-memory pubsub works fine for now, as each watcher is a single process anyway. // Later: https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries const pubsub = new PubSub(); - const indexer = new Indexer(db, ethClient, pubsub, artifacts); + const indexer = new Indexer(db, ethClient, ethProvider, pubsub, artifacts, mode); const eventWatcher = new EventWatcher(ethClient, indexer); await eventWatcher.start(); diff --git a/packages/erc20-watcher/src/utils/index.ts b/packages/erc20-watcher/src/utils/index.ts new file mode 100644 index 00000000..b626f3a2 --- /dev/null +++ b/packages/erc20-watcher/src/utils/index.ts @@ -0,0 +1,110 @@ +import { Contract, utils } from 'ethers'; +import { BaseProvider } from '@ethersproject/providers'; + +import { abi } from '../artifacts/ERC20.json'; +import ERC20SymbolBytesABI from '../artifacts/ERC20SymbolBytes.json'; +import ERC20NameBytesABI from '../artifacts/ERC20NameBytes.json'; +import { StaticTokenDefinition } from './static-token-definition'; + +export const fetchTokenSymbol = async (ethProvider: BaseProvider, tokenAddress: string): Promise => { + const contract = new Contract(tokenAddress, abi, ethProvider); + const contractSymbolBytes = new Contract(tokenAddress, ERC20SymbolBytesABI, ethProvider); + let symbolValue = 'unknown'; + + // Try types string and bytes32 for symbol. + try { + const result = await contract.symbol(); + symbolValue = result; + } catch (error) { + try { + const result = await contractSymbolBytes.symbol(); + + // For broken pairs that have no symbol function exposed. + if (!isNullEthValue(result)) { + symbolValue = utils.parseBytes32String(result); + } else { + // Try with the static definition. + const staticTokenDefinition = StaticTokenDefinition.fromAddress(tokenAddress); + + if (staticTokenDefinition !== null) { + symbolValue = staticTokenDefinition.symbol; + } + } + } catch (error) { + // symbolValue is unknown if the calls revert. + } + } + + return symbolValue; +}; + +export const fetchTokenName = async (ethProvider: BaseProvider, tokenAddress: string): Promise => { + const contract = new Contract(tokenAddress, abi, ethProvider); + const contractNameBytes = new Contract(tokenAddress, ERC20NameBytesABI, ethProvider); + let nameValue = 'unknown'; + + // Try types string and bytes32 for name. + try { + const result = await contract.name(); + nameValue = result; + } catch (error) { + try { + const result = await contractNameBytes.name(); + + // For broken pairs that have no name function exposed. + if (!isNullEthValue(result)) { + nameValue = utils.parseBytes32String(result); + } else { + // Try with the static definition. + const staticTokenDefinition = StaticTokenDefinition.fromAddress(tokenAddress); + + if (staticTokenDefinition !== null) { + nameValue = staticTokenDefinition.name; + } + } + } catch (error) { + // nameValue is unknown if the calls revert. + } + } + + return nameValue; +}; + +export const fetchTokenTotalSupply = async (ethProvider: BaseProvider, tokenAddress: string): Promise => { + const contract = new Contract(tokenAddress, abi, ethProvider); + let totalSupplyValue = null; + + try { + const result = await contract.totalSupply(); + totalSupplyValue = result.toString(); + } catch (error) { + totalSupplyValue = 0; + } + + return BigInt(totalSupplyValue); +}; + +export const fetchTokenDecimals = async (ethProvider: BaseProvider, tokenAddress: string): Promise => { + const contract = new Contract(tokenAddress, abi, ethProvider); + + // Try types uint8 for decimals. + let decimalValue = null; + + try { + const result = await contract.decimals(); + decimalValue = result.toString(); + } catch (error) { + // Try with the static definition. + const staticTokenDefinition = StaticTokenDefinition.fromAddress(tokenAddress); + + if (staticTokenDefinition != null) { + return staticTokenDefinition.decimals; + } + } + + return BigInt(decimalValue); +}; + +const isNullEthValue = (value: string): boolean => { + return value === '0x0000000000000000000000000000000000000000000000000000000000000001'; +}; diff --git a/packages/erc20-watcher/src/utils/static-token-definition.ts b/packages/erc20-watcher/src/utils/static-token-definition.ts new file mode 100644 index 00000000..9f85aa4e --- /dev/null +++ b/packages/erc20-watcher/src/utils/static-token-definition.ts @@ -0,0 +1,94 @@ +import { utils } from 'ethers'; + +// Initialize a Token Definition with the attributes. +export class StaticTokenDefinition { + address : string + symbol: string + name: string + decimals: bigint + + // Initialize a Token Definition with its attributes. + constructor (address: string, symbol: string, name: string, decimals: bigint) { + this.address = address; + this.symbol = symbol; + this.name = name; + this.decimals = decimals; + } + + // Get all tokens with a static defintion + static getStaticDefinitions (): Array { + const staticDefinitions = new Array(6); + + // Add DGD. + const tokenDGD = new StaticTokenDefinition( + utils.getAddress('0xe0b7927c4af23765cb51314a0e0521a9645f0e2a'), + 'DGD', + 'DGD', + BigInt(9) + ); + staticDefinitions.push(tokenDGD); + + // Add AAVE. + const tokenAAVE = new StaticTokenDefinition( + utils.getAddress('0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9'), + 'AAVE', + 'Aave Token', + BigInt(18) + ); + staticDefinitions.push(tokenAAVE); + + // Add LIF. + const tokenLIF = new StaticTokenDefinition( + utils.getAddress('0xeb9951021698b42e4399f9cbb6267aa35f82d59d'), + 'LIF', + 'Lif', + BigInt(18) + ); + staticDefinitions.push(tokenLIF); + + // Add SVD. + const tokenSVD = new StaticTokenDefinition( + utils.getAddress('0xbdeb4b83251fb146687fa19d1c660f99411eefe3'), + 'SVD', + 'savedroid', + BigInt(18) + ); + staticDefinitions.push(tokenSVD); + + // Add TheDAO. + const tokenTheDAO = new StaticTokenDefinition( + utils.getAddress('0xbb9bc244d798123fde783fcc1c72d3bb8c189413'), + 'TheDAO', + 'TheDAO', + BigInt(16) + ); + staticDefinitions.push(tokenTheDAO); + + // Add HPB. + const tokenHPB = new StaticTokenDefinition( + utils.getAddress('0x38c6a68304cdefb9bec48bbfaaba5c5b47818bb2'), + 'HPB', + 'HPBCoin', + BigInt(18) + ); + staticDefinitions.push(tokenHPB); + + return staticDefinitions; + } + + // Helper for hardcoded tokens. + static fromAddress (tokenAddress: string) : StaticTokenDefinition | null { + const staticDefinitions = this.getStaticDefinitions(); + + // Search the definition using the address. + for (let i = 0; i < staticDefinitions.length; i++) { + const staticDefinition = staticDefinitions[i]; + if (utils.getAddress(staticDefinition.address) === utils.getAddress(tokenAddress)) { + return staticDefinition; + } + } + + // If not found, return null. + return null; + } +} diff --git a/packages/ipld-eth-client/src/eth-queries.ts b/packages/ipld-eth-client/src/eth-queries.ts index 236184df..605a3683 100644 --- a/packages/ipld-eth-client/src/eth-queries.ts +++ b/packages/ipld-eth-client/src/eth-queries.ts @@ -56,15 +56,14 @@ query allEthHeaderCids($blockNumber: BigInt) { `; export const getBlockByHash = gql` -query allEthHeaderCids($blockHash: Bytes32) { - allEthHeaderCids(condition: { blockHash: $blockHash }) { - nodes { - cid - blockNumber - blockHash - parentHash - timestamp +query block($blockHash: Bytes32) { + block(hash: $blockHash) { + number + hash + parent { + hash } + timestamp } } `; diff --git a/packages/uni-info-watcher/src/database.ts b/packages/uni-info-watcher/src/database.ts index 5ddb5762..e2b6990a 100644 --- a/packages/uni-info-watcher/src/database.ts +++ b/packages/uni-info-watcher/src/database.ts @@ -648,7 +648,7 @@ export class Database { async _getPrevEntityVersion (repo: Repository, findOptions: { [key: string]: any }): Promise { assert(findOptions.order.blockNumber); const { canonicalBlockNumber, blockHashes } = await this._getBranchInfo(findOptions.where.blockHash); - findOptions.where.blockHash = In(blockHashes) + findOptions.where.blockHash = In(blockHashes); let entity = await repo.findOne(findOptions); if (!entity) { diff --git a/packages/uni-watcher/src/events.ts b/packages/uni-watcher/src/events.ts index 6b157b5e..2f421169 100644 --- a/packages/uni-watcher/src/events.ts +++ b/packages/uni-watcher/src/events.ts @@ -47,7 +47,7 @@ export class EventWatcher { await this.initEventProcessingOnCompleteHandler(); } - async watchBlocksAtChainHead () { + async watchBlocksAtChainHead (): Promise { log('Started watching upstream blocks...'); this._subscription = await this._ethClient.watchBlocks(async (value) => { const { blockHash, blockNumber, parentHash } = _.get(value, 'data.listen.relatedNode'); @@ -59,7 +59,7 @@ export class EventWatcher { }); } - async initBlockProcessingOnCompleteHandler () { + async initBlockProcessingOnCompleteHandler (): Promise { this._jobQueue.onComplete(QUEUE_BLOCK_PROCESSING, async (job) => { const { data: { request: { data: { blockHash, blockNumber } } } } = job; log(`Job onComplete block ${blockHash} ${blockNumber}`); @@ -70,7 +70,7 @@ export class EventWatcher { }); } - async initEventProcessingOnCompleteHandler () { + async initEventProcessingOnCompleteHandler (): Promise { this._jobQueue.onComplete(QUEUE_EVENT_PROCESSING, async (job) => { const { data: { request, failed, state, createdOn } } = job; diff --git a/packages/util/src/config.ts b/packages/util/src/config.ts index 7365b4ae..dcaa3088 100644 --- a/packages/util/src/config.ts +++ b/packages/util/src/config.ts @@ -12,6 +12,7 @@ export interface Config { server: { host: string; port: number; + mode: string; }; database: ConnectionOptions; upstream: { @@ -19,6 +20,7 @@ export interface Config { ethServer: { gqlApiEndpoint: string; gqlPostgraphileEndpoint: string; + rpcProviderEndpoint: string } traceProviderEndpoint: string; uniWatcher: {