mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-07-27 02:32:07 +00:00
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 <nabarun@deepstacksoft.com>
This commit is contained in:
parent
fd1ab0780c
commit
c677e5942c
@ -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"
|
||||
|
17
packages/erc20-watcher/src/artifacts/ERC20NameBytes.json
Normal file
17
packages/erc20-watcher/src/artifacts/ERC20NameBytes.json
Normal file
@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
17
packages/erc20-watcher/src/artifacts/ERC20SymbolBytes.json
Normal file
17
packages/erc20-watcher/src/artifacts/ERC20SymbolBytes.json
Normal file
@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
@ -21,6 +21,6 @@ export class Allowance {
|
||||
@Column('numeric')
|
||||
value!: bigint;
|
||||
|
||||
@Column('text')
|
||||
@Column('text', { nullable: true })
|
||||
proof!: string;
|
||||
}
|
||||
|
@ -18,6 +18,6 @@ export class Balance {
|
||||
@Column('numeric')
|
||||
value!: bigint;
|
||||
|
||||
@Column('text')
|
||||
@Column('text', { nullable: true })
|
||||
proof!: string;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<ValueResult> {
|
||||
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<ValueResult> {
|
||||
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<ValueResult> {
|
||||
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<void> {
|
||||
// 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<ValueResult> {
|
||||
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<EventsResult> {
|
||||
@ -206,7 +277,7 @@ export class Indexer {
|
||||
return result;
|
||||
}
|
||||
|
||||
async triggerIndexingOnEvent (blockHash: string, token: string, receipt: any, logIndex: number): Promise<void> {
|
||||
async triggerIndexingOnEvent (blockHash: string, blockNumber: number, token: string, receipt: any, logIndex: number): Promise<void> {
|
||||
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<void> {
|
||||
async processEvent (blockHash: string, blockNumber: number, token: string, receipt: any, logIndex: number): Promise<void> {
|
||||
// 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);
|
||||
|
@ -62,7 +62,7 @@ export const createResolvers = async (indexer: Indexer): Promise<any> => {
|
||||
|
||||
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 }) => {
|
||||
|
@ -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<any> => {
|
||||
|
||||
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<any> => {
|
||||
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<any> => {
|
||||
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();
|
||||
|
110
packages/erc20-watcher/src/utils/index.ts
Normal file
110
packages/erc20-watcher/src/utils/index.ts
Normal file
@ -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<string> => {
|
||||
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<string> => {
|
||||
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<bigint> => {
|
||||
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<bigint> => {
|
||||
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';
|
||||
};
|
94
packages/erc20-watcher/src/utils/static-token-definition.ts
Normal file
94
packages/erc20-watcher/src/utils/static-token-definition.ts
Normal file
@ -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<StaticTokenDefinition> {
|
||||
const staticDefinitions = new Array<StaticTokenDefinition>(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;
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -648,7 +648,7 @@ export class Database {
|
||||
async _getPrevEntityVersion<Entity> (repo: Repository<Entity>, findOptions: { [key: string]: any }): Promise<Entity | undefined> {
|
||||
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) {
|
||||
|
@ -47,7 +47,7 @@ export class EventWatcher {
|
||||
await this.initEventProcessingOnCompleteHandler();
|
||||
}
|
||||
|
||||
async watchBlocksAtChainHead () {
|
||||
async watchBlocksAtChainHead (): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
this._jobQueue.onComplete(QUEUE_EVENT_PROCESSING, async (job) => {
|
||||
const { data: { request, failed, state, createdOn } } = job;
|
||||
|
||||
|
@ -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: {
|
||||
|
Loading…
Reference in New Issue
Block a user