Handle pool created event (#124)

* Update Token entities.

* Complete handlePoolCreated in events.

Co-authored-by: nabarun <nabarun@deepstacksoft.com>
This commit is contained in:
Ashwin Phatak 2021-07-07 11:10:10 +05:30 committed by GitHub
parent 194d079d5e
commit f7df56bb75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 170 additions and 55 deletions

View File

@ -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]

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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)

View File

@ -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.
}

View File

@ -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.
}

View File

@ -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.
}

View File

@ -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);
}
}

View File

@ -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
```

View File

@ -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",

View File

@ -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);