mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-01-08 12:28:05 +00:00
Handle pool created event (#124)
* Update Token entities. * Complete handlePoolCreated in events. Co-authored-by: nabarun <nabarun@deepstacksoft.com>
This commit is contained in:
parent
194d079d5e
commit
f7df56bb75
@ -22,7 +22,7 @@
|
||||
subscribersDir = "src/subscriber"
|
||||
|
||||
[upstream]
|
||||
gqlEndpoint = "http://127.0.0.1:8083/graphql"
|
||||
gqlEndpoint = "http://127.0.0.1:8082/graphql"
|
||||
gqlSubscriptionEndpoint = "http://127.0.0.1:5000/graphql"
|
||||
|
||||
[upstream.cache]
|
||||
|
@ -1,24 +1,19 @@
|
||||
import { gql } from 'apollo-server-express';
|
||||
import { GraphQLClient } from '@vulcanize/ipld-eth-client';
|
||||
import { gql } from '@apollo/client/core';
|
||||
import { GraphQLClient, GraphQLConfig } from '@vulcanize/ipld-eth-client';
|
||||
|
||||
import { querySymbol } from './queries';
|
||||
|
||||
interface Config {
|
||||
gqlEndpoint: string;
|
||||
gqlSubscriptionEndpoint: string;
|
||||
}
|
||||
import { queryName, queryDecimals, queryTotalSupply, querySymbol } from './queries';
|
||||
|
||||
export class Client {
|
||||
_config: Config;
|
||||
_config: GraphQLConfig;
|
||||
_client: GraphQLClient;
|
||||
|
||||
constructor (config: Config) {
|
||||
constructor (config: GraphQLConfig) {
|
||||
this._config = config;
|
||||
|
||||
this._client = new GraphQLClient(config);
|
||||
}
|
||||
|
||||
async getSymbol (blockHash: string | undefined, token: string): Promise<any> {
|
||||
async getSymbol (blockHash: string, token: string): Promise<any> {
|
||||
const { symbol } = await this._client.query(
|
||||
gql(querySymbol),
|
||||
{ blockHash, token }
|
||||
@ -26,4 +21,31 @@ export class Client {
|
||||
|
||||
return symbol;
|
||||
}
|
||||
|
||||
async getName (blockHash: string, token: string): Promise<any> {
|
||||
const { name } = await this._client.query(
|
||||
gql(queryName),
|
||||
{ blockHash, token }
|
||||
);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
async getTotalSupply (blockHash: string, token: string): Promise<any> {
|
||||
const { totalSupply } = await this._client.query(
|
||||
gql(queryTotalSupply),
|
||||
{ blockHash, token }
|
||||
);
|
||||
|
||||
return totalSupply;
|
||||
}
|
||||
|
||||
async getDecimals (blockHash: string, token: string): Promise<any> {
|
||||
const { decimals } = await this._client.query(
|
||||
gql(queryDecimals),
|
||||
{ blockHash, token }
|
||||
);
|
||||
|
||||
return decimals;
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,9 @@ import { Cache } from '@vulcanize/cache';
|
||||
|
||||
import ethQueries from './eth-queries';
|
||||
import { padKey } from './utils';
|
||||
import { GraphQLClient } from './graphql-client';
|
||||
import { GraphQLClient, GraphQLConfig } from './graphql-client';
|
||||
|
||||
interface Config {
|
||||
gqlEndpoint: string;
|
||||
gqlSubscriptionEndpoint: string;
|
||||
interface Config extends GraphQLConfig {
|
||||
cache: Cache | undefined;
|
||||
}
|
||||
|
||||
|
@ -10,16 +10,16 @@ import { WebSocketLink } from '@apollo/client/link/ws';
|
||||
|
||||
const log = debug('vulcanize:client');
|
||||
|
||||
interface Config {
|
||||
export interface GraphQLConfig {
|
||||
gqlEndpoint: string;
|
||||
gqlSubscriptionEndpoint: string;
|
||||
}
|
||||
|
||||
export class GraphQLClient {
|
||||
_config: Config;
|
||||
_config: GraphQLConfig;
|
||||
_client: ApolloClient<NormalizedCacheObject>;
|
||||
|
||||
constructor (config: Config) {
|
||||
constructor (config: GraphQLConfig) {
|
||||
this._config = config;
|
||||
|
||||
const { gqlEndpoint, gqlSubscriptionEndpoint } = config;
|
||||
|
@ -30,7 +30,19 @@ export class Database {
|
||||
return this._conn.close();
|
||||
}
|
||||
|
||||
async loadFactory ({ id, blockNumber }: DeepPartial<Factory>): Promise<Factory> {
|
||||
async getToken ({ id, blockNumber }: DeepPartial<Token>): Promise<Token | undefined> {
|
||||
const repo = this._conn.getRepository(Token);
|
||||
|
||||
return repo.createQueryBuilder('token')
|
||||
.where('id = :id AND block_number <= :blockNumber', {
|
||||
id,
|
||||
blockNumber
|
||||
})
|
||||
.orderBy('token.block_number', 'DESC')
|
||||
.getOne();
|
||||
}
|
||||
|
||||
async loadFactory ({ id, blockNumber, ...values }: DeepPartial<Factory>): Promise<Factory> {
|
||||
return this._conn.transaction(async (tx) => {
|
||||
const repo = tx.getRepository(Factory);
|
||||
|
||||
@ -39,10 +51,11 @@ export class Database {
|
||||
id,
|
||||
blockNumber
|
||||
})
|
||||
.orderBy('factory.block_number', 'DESC')
|
||||
.getOne();
|
||||
|
||||
if (!entity) {
|
||||
entity = repo.create({ blockNumber, id });
|
||||
entity = repo.create({ blockNumber, id, ...values });
|
||||
entity = await repo.save(entity);
|
||||
}
|
||||
|
||||
@ -50,7 +63,7 @@ export class Database {
|
||||
});
|
||||
}
|
||||
|
||||
async loadPool ({ id, blockNumber }: DeepPartial<Pool>): Promise<Pool> {
|
||||
async loadPool ({ id, blockNumber, ...values }: DeepPartial<Pool>): Promise<Pool> {
|
||||
return this._conn.transaction(async (tx) => {
|
||||
const repo = tx.getRepository(Pool);
|
||||
|
||||
@ -59,10 +72,11 @@ export class Database {
|
||||
id,
|
||||
blockNumber
|
||||
})
|
||||
.orderBy('pool.block_number', 'DESC')
|
||||
.getOne();
|
||||
|
||||
if (!entity) {
|
||||
entity = repo.create({ blockNumber, id });
|
||||
entity = repo.create({ blockNumber, id, ...values });
|
||||
entity = await repo.save(entity);
|
||||
}
|
||||
|
||||
@ -70,7 +84,7 @@ export class Database {
|
||||
});
|
||||
}
|
||||
|
||||
async loadToken ({ id, blockNumber }: DeepPartial<Token>, getValues: () => Promise<DeepPartial<Token>>): Promise<Token> {
|
||||
async loadToken ({ id, blockNumber, ...values }: DeepPartial<Token>): Promise<Token> {
|
||||
return this._conn.transaction(async (tx) => {
|
||||
const repo = tx.getRepository(Token);
|
||||
|
||||
@ -79,11 +93,11 @@ export class Database {
|
||||
id,
|
||||
blockNumber
|
||||
})
|
||||
.orderBy('token.block_number', 'DESC')
|
||||
.getOne();
|
||||
|
||||
if (!entity) {
|
||||
const tokenValues = await getValues();
|
||||
entity = repo.create({ blockNumber, id, ...tokenValues });
|
||||
entity = repo.create({ blockNumber, id, ...values });
|
||||
entity = await repo.save(entity);
|
||||
}
|
||||
|
||||
@ -91,6 +105,14 @@ export class Database {
|
||||
});
|
||||
}
|
||||
|
||||
async saveFactory (factory: Factory, blockNumber: number): Promise<Factory> {
|
||||
return this._conn.transaction(async (tx) => {
|
||||
const repo = tx.getRepository(Factory);
|
||||
factory.blockNumber = blockNumber;
|
||||
return repo.save(factory);
|
||||
});
|
||||
}
|
||||
|
||||
// Returns true if events have already been synced for the (block, token) combination.
|
||||
async didSyncEvents ({ blockHash, token }: { blockHash: string, token: string }): Promise<boolean> {
|
||||
const numRows = await this._conn.getRepository(EventSyncProgress)
|
||||
|
@ -6,9 +6,11 @@ export class Factory {
|
||||
@PrimaryColumn('varchar', { length: 42 })
|
||||
id!: string;
|
||||
|
||||
@Column('numeric')
|
||||
@PrimaryColumn('numeric')
|
||||
blockNumber!: number;
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
poolCount!: number;
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
poolCount!: bigint;
|
||||
|
||||
// TODO: Add remaining fields when they are used.
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { Entity, PrimaryColumn, Column, Index } from 'typeorm';
|
||||
import { Entity, PrimaryColumn, Column, Index, ManyToOne } from 'typeorm';
|
||||
|
||||
import { Token } from './Token';
|
||||
|
||||
@Entity()
|
||||
@Index(['blockNumber', 'id'])
|
||||
@ -6,6 +8,17 @@ export class Pool {
|
||||
@PrimaryColumn('varchar', { length: 42 })
|
||||
id!: string;
|
||||
|
||||
@Column('numeric')
|
||||
@PrimaryColumn('numeric')
|
||||
blockNumber!: number;
|
||||
|
||||
@ManyToOne(() => Token)
|
||||
token0!: Token;
|
||||
|
||||
@ManyToOne(() => Token)
|
||||
token1!: Token;
|
||||
|
||||
@Column('numeric')
|
||||
feeTier!: bigint
|
||||
|
||||
// TODO: Add remaining fields when they are used.
|
||||
}
|
||||
|
@ -6,9 +6,17 @@ export class Token {
|
||||
@PrimaryColumn('varchar', { length: 42 })
|
||||
id!: string;
|
||||
|
||||
@Column('numeric')
|
||||
@PrimaryColumn('numeric')
|
||||
blockNumber!: number;
|
||||
|
||||
@Column('varchar')
|
||||
symbol!: string;
|
||||
|
||||
@Column('varchar')
|
||||
name!: string;
|
||||
|
||||
@Column('numeric')
|
||||
totalSupply!: number;
|
||||
|
||||
// TODO: Add remaining fields when they are used.
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import assert from 'assert';
|
||||
import debug from 'debug';
|
||||
import { Client as UniClient } from '@vulcanize/uni-watcher';
|
||||
import { Client as ERC20Client } from '@vulcanize/erc20-watcher';
|
||||
import { BigNumber } from 'ethers';
|
||||
|
||||
import { Database } from './database';
|
||||
|
||||
@ -58,6 +59,7 @@ export class EventWatcher {
|
||||
|
||||
switch (eventType) {
|
||||
case 'PoolCreatedEvent':
|
||||
log('PoolCreated event', contract);
|
||||
this._handlePoolCreated(blockHash, blockNumber, contract, eventValues as PoolCreatedEvent);
|
||||
break;
|
||||
|
||||
@ -67,28 +69,62 @@ export class EventWatcher {
|
||||
}
|
||||
|
||||
async _handlePoolCreated (blockHash: string, blockNumber: number, contractAddress: string, poolCreatedEvent: PoolCreatedEvent): Promise<void> {
|
||||
const { token0: token0Address, token1: token1Address, fee, tickSpacing, pool: poolAddress } = poolCreatedEvent;
|
||||
const { token0: token0Address, token1: token1Address, fee, pool: poolAddress } = poolCreatedEvent;
|
||||
|
||||
// Load factory.
|
||||
const factory = await this._db.loadFactory({ blockNumber, id: contractAddress });
|
||||
factory.poolCount = factory.poolCount + 1;
|
||||
|
||||
// Create new Pool entity.
|
||||
const pool = this._db.loadPool({ blockNumber, id: poolAddress });
|
||||
// Update Factory.
|
||||
let factoryPoolCount = BigNumber.from(factory.poolCount);
|
||||
factoryPoolCount = factoryPoolCount.add(1);
|
||||
factory.poolCount = BigInt(factoryPoolCount.toHexString());
|
||||
|
||||
// TODO: Load Token entities.
|
||||
const getTokenValues = async (tokenAddress: string) => {
|
||||
// Get Tokens.
|
||||
let [token0, token1] = await Promise.all([
|
||||
this._db.getToken({ blockNumber, id: token0Address }),
|
||||
this._db.getToken({ blockNumber, id: token1Address })
|
||||
]);
|
||||
|
||||
// Create Token.
|
||||
const createToken = async (tokenAddress: string) => {
|
||||
const { value: symbol } = await this._erc20Client.getSymbol(blockHash, tokenAddress);
|
||||
return { symbol };
|
||||
const { value: name } = await this._erc20Client.getName(blockHash, tokenAddress);
|
||||
const { value: totalSupply } = await this._erc20Client.getTotalSupply(blockHash, tokenAddress);
|
||||
|
||||
// TODO: decimals not implemented by erc20-watcher.
|
||||
// const { value: decimals } = await this._erc20Client.getDecimals(blockHash, tokenAddress);
|
||||
|
||||
return this._db.loadToken({
|
||||
blockNumber,
|
||||
id: token1Address,
|
||||
symbol,
|
||||
name,
|
||||
totalSupply
|
||||
});
|
||||
};
|
||||
|
||||
const token0 = this._db.loadToken({ blockNumber, id: token0Address }, () => getTokenValues(token0Address));
|
||||
const token1 = this._db.loadToken({ blockNumber, id: token1Address }, () => getTokenValues(token1Address));
|
||||
// Create Tokens if not present.
|
||||
if (!token0) {
|
||||
token0 = await createToken(token0Address);
|
||||
}
|
||||
|
||||
// TODO: Update Token entities.
|
||||
if (!token1) {
|
||||
token1 = await createToken(token1Address);
|
||||
}
|
||||
|
||||
// TODO: Update Pool entity.
|
||||
// Create new Pool entity.
|
||||
// Skipping adding createdAtTimestamp field as it is not queried in frontend subgraph.
|
||||
await this._db.loadPool({
|
||||
blockNumber,
|
||||
id: poolAddress,
|
||||
token0: token0,
|
||||
token1: token1,
|
||||
feeTier: BigInt(fee)
|
||||
});
|
||||
|
||||
// TODO: Save entities to DB.
|
||||
// Skipping updating token whitelistPools field as it is not queried in frontend subgraph.
|
||||
|
||||
// Save entities to DB.
|
||||
await this._db.saveFactory(factory, blockNumber);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,24 @@ Start watching the factory contract:
|
||||
Example:
|
||||
|
||||
```bash
|
||||
$ npx ts-node src/cli/watch-contract.ts --configFile environments/local.toml --address 0xfE0034a874c2707c23F91D7409E9036F5e08ac34 --kind factory --startingBlock 100
|
||||
$ yarn watch:contract --address 0xfE0034a874c2707c23F91D7409E9036F5e08ac34 --kind factory --startingBlock 100
|
||||
```
|
||||
|
||||
## Scripts
|
||||
|
||||
* `yarn server`
|
||||
|
||||
Start the GraphQL server.
|
||||
|
||||
* `yarn watch:contract`
|
||||
|
||||
Add contract to watch.
|
||||
|
||||
* `yarn lint`
|
||||
|
||||
Lint files.
|
||||
|
||||
```bash
|
||||
# Lint fix.
|
||||
$ yarn lint --fix
|
||||
```
|
||||
|
@ -9,7 +9,8 @@
|
||||
"server:mock": "MOCK=1 nodemon src/server.ts -f environments/local.toml",
|
||||
"test": "mocha -r ts-node/register src/**/*.spec.ts",
|
||||
"lint": "eslint .",
|
||||
"build": "tsc"
|
||||
"build": "tsc",
|
||||
"watch:contract": "ts-node src/cli/watch-contract.ts --configFile environments/local.toml"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,16 +1,11 @@
|
||||
import { gql } from 'apollo-server-express';
|
||||
import { GraphQLClient } from '@vulcanize/ipld-eth-client';
|
||||
|
||||
interface Config {
|
||||
gqlEndpoint: string;
|
||||
gqlSubscriptionEndpoint: string;
|
||||
}
|
||||
import { gql } from '@apollo/client/core';
|
||||
import { GraphQLClient, GraphQLConfig } from '@vulcanize/ipld-eth-client';
|
||||
|
||||
export class Client {
|
||||
_config: Config;
|
||||
_config: GraphQLConfig;
|
||||
_client: GraphQLClient;
|
||||
|
||||
constructor (config: Config) {
|
||||
constructor (config: GraphQLConfig) {
|
||||
this._config = config;
|
||||
|
||||
this._client = new GraphQLClient(config);
|
||||
|
Loading…
Reference in New Issue
Block a user