From 61f211f2d5ddbde69c690891f74186139678175a Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Fri, 9 Jul 2021 12:38:25 +0530 Subject: [PATCH] Handle pool initialize event (#127) * Move getConfig to util package. * Handle Pool initialize event. * Update Bundle entity ethPriceUSD. * Update Pool day and hour data. * Update token derivedETH and complete handleInitialize. Co-authored-by: nabarun --- packages/address-watcher/package.json | 1 + packages/address-watcher/src/config.ts | 40 ---- packages/address-watcher/src/server.ts | 2 +- packages/erc20-watcher/package.json | 1 + packages/erc20-watcher/src/config.ts | 35 --- packages/erc20-watcher/src/server.ts | 2 +- packages/uni-info-watcher/package.json | 1 + packages/uni-info-watcher/src/database.ts | 216 +++++++++++++++--- .../uni-info-watcher/src/entity/Bundle.ts | 13 ++ .../uni-info-watcher/src/entity/Factory.ts | 5 +- packages/uni-info-watcher/src/entity/Pool.ts | 35 ++- .../src/entity/PoolDayData.ts | 58 +++++ .../src/entity/PoolHourData.ts | 58 +++++ packages/uni-info-watcher/src/entity/Token.ts | 13 +- packages/uni-info-watcher/src/events.ts | 106 +++++++-- packages/uni-info-watcher/src/server.ts | 2 +- .../src/utils/intervalUpdates.ts | 103 +++++++++ .../uni-info-watcher/src/utils/pricing.ts | 96 ++++++++ packages/uni-watcher/package.json | 1 + .../uni-watcher/src/cli/watch-contract.ts | 2 +- packages/uni-watcher/src/client.ts | 6 + packages/uni-watcher/src/config.ts | 35 --- packages/uni-watcher/src/indexer.ts | 2 +- packages/uni-watcher/src/server.ts | 2 +- packages/util/.eslintignore | 5 + packages/util/.eslintrc.json | 18 ++ packages/util/index.ts | 1 + packages/util/package.json | 32 +++ .../{uni-info-watcher => util}/src/config.ts | 4 +- packages/util/tsconfig.json | 77 +++++++ 30 files changed, 794 insertions(+), 178 deletions(-) delete mode 100644 packages/address-watcher/src/config.ts delete mode 100644 packages/erc20-watcher/src/config.ts create mode 100644 packages/uni-info-watcher/src/entity/Bundle.ts create mode 100644 packages/uni-info-watcher/src/entity/PoolDayData.ts create mode 100644 packages/uni-info-watcher/src/entity/PoolHourData.ts create mode 100644 packages/uni-info-watcher/src/utils/intervalUpdates.ts create mode 100644 packages/uni-info-watcher/src/utils/pricing.ts delete mode 100644 packages/uni-watcher/src/config.ts create mode 100644 packages/util/.eslintignore create mode 100644 packages/util/.eslintrc.json create mode 100644 packages/util/index.ts create mode 100644 packages/util/package.json rename packages/{uni-info-watcher => util}/src/config.ts (97%) create mode 100644 packages/util/tsconfig.json diff --git a/packages/address-watcher/package.json b/packages/address-watcher/package.json index 8aea95eb..e2a0e0a8 100644 --- a/packages/address-watcher/package.json +++ b/packages/address-watcher/package.json @@ -28,6 +28,7 @@ "@vulcanize/ipld-eth-client": "^0.1.0", "@vulcanize/solidity-mapper": "^0.1.0", "@vulcanize/tracing-client": "^0.1.0", + "@vulcanize/util": "^0.1.0", "apollo-server-express": "^2.25.0", "apollo-type-bigint": "^0.1.3", "debug": "^4.3.1", diff --git a/packages/address-watcher/src/config.ts b/packages/address-watcher/src/config.ts deleted file mode 100644 index 1bb226e9..00000000 --- a/packages/address-watcher/src/config.ts +++ /dev/null @@ -1,40 +0,0 @@ -import fs from 'fs-extra'; -import path from 'path'; -import toml from 'toml'; -import debug from 'debug'; -import { ConnectionOptions } from 'typeorm'; - -import { Config as CacheConfig } from '@vulcanize/cache'; - -const log = debug('vulcanize:config'); - -export interface Config { - server: { - host: string; - port: number; - }; - database: ConnectionOptions; - upstream: { - gqlEndpoint: string; - gqlSubscriptionEndpoint: string; - traceProviderEndpoint: string; - cache: CacheConfig - } - jobQueue: { - dbConnectionString: string; - maxCompletionLag: number; - } -} - -export const getConfig = async (configFile: string): Promise => { - const configFilePath = path.resolve(configFile); - const fileExists = await fs.pathExists(configFilePath); - if (!fileExists) { - throw new Error(`Config file not found: ${configFilePath}`); - } - - const config = toml.parse(await fs.readFile(configFilePath, 'utf8')); - log('config', JSON.stringify(config, null, 2)); - - return config; -}; diff --git a/packages/address-watcher/src/server.ts b/packages/address-watcher/src/server.ts index 47440728..e55f51b6 100644 --- a/packages/address-watcher/src/server.ts +++ b/packages/address-watcher/src/server.ts @@ -10,13 +10,13 @@ import { createServer } from 'http'; import { getCache } from '@vulcanize/cache'; import { EthClient } from '@vulcanize/ipld-eth-client'; import { TracingClient } from '@vulcanize/tracing-client'; +import { getConfig } from '@vulcanize/util'; import typeDefs from './schema'; import { createResolvers } from './resolvers'; import { Indexer } from './indexer'; import { Database } from './database'; -import { getConfig } from './config'; import { TxWatcher } from './tx-watcher'; import { JobQueue } from './job-queue'; diff --git a/packages/erc20-watcher/package.json b/packages/erc20-watcher/package.json index 1bb3a57d..a8a8bef2 100644 --- a/packages/erc20-watcher/package.json +++ b/packages/erc20-watcher/package.json @@ -27,6 +27,7 @@ "@vulcanize/cache": "^0.1.0", "@vulcanize/ipld-eth-client": "^0.1.0", "@vulcanize/solidity-mapper": "^0.1.0", + "@vulcanize/util": "^0.1.0", "apollo-server-express": "^2.25.0", "apollo-type-bigint": "^0.1.3", "debug": "^4.3.1", diff --git a/packages/erc20-watcher/src/config.ts b/packages/erc20-watcher/src/config.ts deleted file mode 100644 index b93cd8f0..00000000 --- a/packages/erc20-watcher/src/config.ts +++ /dev/null @@ -1,35 +0,0 @@ -import fs from 'fs-extra'; -import path from 'path'; -import toml from 'toml'; -import debug from 'debug'; -import { ConnectionOptions } from 'typeorm'; - -import { Config as CacheConfig } from '@vulcanize/cache'; - -const log = debug('vulcanize:config'); - -export interface Config { - server: { - host: string; - port: number; - }; - database: ConnectionOptions; - upstream: { - gqlEndpoint: string; - gqlSubscriptionEndpoint: string; - cache: CacheConfig - } -} - -export const getConfig = async (configFile: string): Promise => { - const configFilePath = path.resolve(configFile); - const fileExists = await fs.pathExists(configFilePath); - if (!fileExists) { - throw new Error(`Config file not found: ${configFilePath}`); - } - - const config = toml.parse(await fs.readFile(configFilePath, 'utf8')); - log('config', JSON.stringify(config, null, 2)); - - return config; -}; diff --git a/packages/erc20-watcher/src/server.ts b/packages/erc20-watcher/src/server.ts index b717b34e..1ac9253b 100644 --- a/packages/erc20-watcher/src/server.ts +++ b/packages/erc20-watcher/src/server.ts @@ -10,6 +10,7 @@ import { createServer } from 'http'; import { getCache } from '@vulcanize/cache'; import { EthClient } from '@vulcanize/ipld-eth-client'; +import { getConfig } from '@vulcanize/util'; import artifacts from './artifacts/ERC20.json'; import typeDefs from './schema'; @@ -19,7 +20,6 @@ import { createResolvers } from './resolvers'; import { Indexer } from './indexer'; import { Database } from './database'; import { EventWatcher } from './events'; -import { getConfig } from './config'; const log = debug('vulcanize:server'); diff --git a/packages/uni-info-watcher/package.json b/packages/uni-info-watcher/package.json index ddf9f0bf..6eef6faa 100644 --- a/packages/uni-info-watcher/package.json +++ b/packages/uni-info-watcher/package.json @@ -8,6 +8,7 @@ "@vulcanize/cache": "^0.1.0", "@vulcanize/erc20-watcher": "^0.1.0", "@vulcanize/ipld-eth-client": "^0.1.0", + "@vulcanize/util": "^0.1.0", "apollo-server-express": "^2.25.0", "apollo-type-bigint": "^0.1.3", "typeorm": "^0.2.32" diff --git a/packages/uni-info-watcher/src/database.ts b/packages/uni-info-watcher/src/database.ts index d274d770..4ae7d83e 100644 --- a/packages/uni-info-watcher/src/database.ts +++ b/packages/uni-info-watcher/src/database.ts @@ -1,12 +1,15 @@ import assert from 'assert'; -import { Connection, ConnectionOptions, createConnection, DeepPartial } from 'typeorm'; +import { Connection, ConnectionOptions, createConnection, DeepPartial, FindConditions, FindOneOptions, LessThanOrEqual } from 'typeorm'; import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; +import { EventSyncProgress } from './entity/EventProgress'; import { Factory } from './entity/Factory'; import { Pool } from './entity/Pool'; import { Event } from './entity/Event'; import { Token } from './entity/Token'; -import { EventSyncProgress } from './entity/EventProgress'; +import { Bundle } from './entity/Bundle'; +import { PoolDayData } from './entity/PoolDayData'; +import { PoolHourData } from './entity/PoolHourData'; export class Database { _config: ConnectionOptions @@ -33,25 +36,54 @@ export class Database { async getToken ({ id, blockNumber }: DeepPartial): Promise { 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(); + const whereOptions: FindConditions = { id }; + + if (blockNumber) { + whereOptions.blockNumber = LessThanOrEqual(blockNumber); + } + + const findOptions: FindOneOptions = { + where: whereOptions, + relations: ['whitelistPools', 'whitelistPools.token0', 'whitelistPools.token1'], + order: { + blockNumber: 'DESC' + } + }; + + return repo.findOne(findOptions); + } + + async getPool ({ id, blockNumber }: DeepPartial): Promise { + const repo = this._conn.getRepository(Pool); + const whereOptions: FindConditions = { id }; + + if (blockNumber) { + whereOptions.blockNumber = LessThanOrEqual(blockNumber); + } + + const findOptions: FindOneOptions = { + where: whereOptions, + relations: ['token0', 'token1'], + order: { + blockNumber: 'DESC' + } + }; + + return repo.findOne(findOptions); } async loadFactory ({ id, blockNumber, ...values }: DeepPartial): Promise { return this._conn.transaction(async (tx) => { const repo = tx.getRepository(Factory); - let entity = await repo.createQueryBuilder('factory') - .where('id = :id AND block_number <= :blockNumber', { - id, - blockNumber - }) - .orderBy('factory.block_number', 'DESC') + let selectQueryBuilder = repo.createQueryBuilder('factory') + .where('id = :id', { id }); + + if (blockNumber) { + selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber }); + } + + let entity = await selectQueryBuilder.orderBy('factory.block_number', 'DESC') .getOne(); if (!entity) { @@ -67,13 +99,21 @@ export class Database { return this._conn.transaction(async (tx) => { const repo = tx.getRepository(Pool); - let entity = await repo.createQueryBuilder('pool') - .where('id = :id AND block_number <= :blockNumber', { - id, - blockNumber - }) - .orderBy('pool.block_number', 'DESC') - .getOne(); + const whereOptions: FindConditions = { id }; + + if (blockNumber) { + whereOptions.blockNumber = LessThanOrEqual(blockNumber); + } + + const findOptions: FindOneOptions = { + where: whereOptions, + relations: ['token0', 'token1'], + order: { + blockNumber: 'DESC' + } + }; + + let entity = await repo.findOne(findOptions); if (!entity) { entity = repo.create({ blockNumber, id, ...values }); @@ -88,12 +128,92 @@ export class Database { return this._conn.transaction(async (tx) => { const repo = tx.getRepository(Token); - let entity = await repo.createQueryBuilder('token') - .where('id = :id AND block_number <= :blockNumber', { - id, - blockNumber - }) - .orderBy('token.block_number', 'DESC') + const whereOptions: FindConditions = { id }; + + if (blockNumber) { + whereOptions.blockNumber = LessThanOrEqual(blockNumber); + } + + const findOptions: FindOneOptions = { + where: whereOptions, + relations: ['whitelistPools', 'whitelistPools.token0', 'whitelistPools.token1'], + order: { + blockNumber: 'DESC' + } + }; + + let entity = await repo.findOne(findOptions); + + if (!entity) { + entity = repo.create({ blockNumber, id, ...values }); + entity = await repo.save(entity); + + // TODO: Find way to preload relations during create. + entity.whitelistPools = []; + } + + return entity; + }); + } + + async loadBundle ({ id, blockNumber, ...values }: DeepPartial): Promise { + return this._conn.transaction(async (tx) => { + const repo = tx.getRepository(Bundle); + + let selectQueryBuilder = repo.createQueryBuilder('bundle') + .where('id = :id', { id }); + + if (blockNumber) { + selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber }); + } + + let entity = await selectQueryBuilder.orderBy('bundle.block_number', 'DESC') + .getOne(); + + if (!entity) { + entity = repo.create({ blockNumber, id, ...values }); + entity = await repo.save(entity); + } + + return entity; + }); + } + + async loadPoolDayData ({ id, blockNumber, ...values }: DeepPartial): Promise { + return this._conn.transaction(async (tx) => { + const repo = tx.getRepository(PoolDayData); + + let selectQueryBuilder = repo.createQueryBuilder('pool_day_data') + .where('id = :id', { id }); + + if (blockNumber) { + selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber }); + } + + let entity = await selectQueryBuilder.orderBy('pool_day_data.block_number', 'DESC') + .getOne(); + + if (!entity) { + entity = repo.create({ blockNumber, id, ...values }); + entity = await repo.save(entity); + } + + return entity; + }); + } + + async loadPoolHourData ({ id, blockNumber, ...values }: DeepPartial): Promise { + return this._conn.transaction(async (tx) => { + const repo = tx.getRepository(PoolHourData); + + let selectQueryBuilder = repo.createQueryBuilder('pool_hour_data') + .where('id = :id', { id }); + + if (blockNumber) { + selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber }); + } + + let entity = await selectQueryBuilder.orderBy('pool_hour_data.block_number', 'DESC') .getOne(); if (!entity) { @@ -113,6 +233,46 @@ export class Database { }); } + async saveBundle (bundle: Bundle, blockNumber: number): Promise { + return this._conn.transaction(async (tx) => { + const repo = tx.getRepository(Bundle); + bundle.blockNumber = blockNumber; + return repo.save(bundle); + }); + } + + async savePool (pool: Pool, blockNumber: number): Promise { + return this._conn.transaction(async (tx) => { + const repo = tx.getRepository(Pool); + pool.blockNumber = blockNumber; + return repo.save(pool); + }); + } + + async savePoolDayData (poolDayData: PoolDayData, blockNumber: number): Promise { + return this._conn.transaction(async (tx) => { + const repo = tx.getRepository(PoolDayData); + poolDayData.blockNumber = blockNumber; + return repo.save(poolDayData); + }); + } + + async savePoolHourData (poolHourData: PoolHourData, blockNumber: number): Promise { + return this._conn.transaction(async (tx) => { + const repo = tx.getRepository(PoolHourData); + poolHourData.blockNumber = blockNumber; + return repo.save(poolHourData); + }); + } + + async saveToken (token: Token, blockNumber: number): Promise { + return this._conn.transaction(async (tx) => { + const repo = tx.getRepository(Token); + token.blockNumber = blockNumber; + return repo.save(token); + }); + } + // Returns true if events have already been synced for the (block, token) combination. async didSyncEvents ({ blockHash, token }: { blockHash: string, token: string }): Promise { const numRows = await this._conn.getRepository(EventSyncProgress) diff --git a/packages/uni-info-watcher/src/entity/Bundle.ts b/packages/uni-info-watcher/src/entity/Bundle.ts new file mode 100644 index 00000000..6d5370bf --- /dev/null +++ b/packages/uni-info-watcher/src/entity/Bundle.ts @@ -0,0 +1,13 @@ +import { Entity, PrimaryColumn, Column } from 'typeorm'; + +@Entity() +export class Bundle { + @PrimaryColumn('varchar', { length: 1 }) + id!: string; + + @PrimaryColumn('integer') + blockNumber!: number; + + @Column('numeric', { default: 0 }) + ethPriceUSD!: number +} diff --git a/packages/uni-info-watcher/src/entity/Factory.ts b/packages/uni-info-watcher/src/entity/Factory.ts index d84cb991..4725a5f4 100644 --- a/packages/uni-info-watcher/src/entity/Factory.ts +++ b/packages/uni-info-watcher/src/entity/Factory.ts @@ -1,12 +1,11 @@ -import { Entity, Column, Index, PrimaryColumn } from 'typeorm'; +import { Entity, Column, PrimaryColumn } from 'typeorm'; @Entity() -@Index(['blockNumber', 'id'], { unique: true }) export class Factory { @PrimaryColumn('varchar', { length: 42 }) id!: string; - @PrimaryColumn('numeric') + @PrimaryColumn('integer') blockNumber!: number; @Column('numeric', { default: BigInt(0) }) diff --git a/packages/uni-info-watcher/src/entity/Pool.ts b/packages/uni-info-watcher/src/entity/Pool.ts index 64175646..461c527b 100644 --- a/packages/uni-info-watcher/src/entity/Pool.ts +++ b/packages/uni-info-watcher/src/entity/Pool.ts @@ -1,14 +1,13 @@ -import { Entity, PrimaryColumn, Column, Index, ManyToOne } from 'typeorm'; +import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; import { Token } from './Token'; @Entity() -@Index(['blockNumber', 'id']) export class Pool { @PrimaryColumn('varchar', { length: 42 }) id!: string; - @PrimaryColumn('numeric') + @PrimaryColumn('integer') blockNumber!: number; @ManyToOne(() => Token) @@ -17,8 +16,38 @@ export class Pool { @ManyToOne(() => Token) token1!: Token; + @Column('numeric', { default: 0 }) + token0Price!: number + + @Column('numeric', { default: 0 }) + token1Price!: number + @Column('numeric') feeTier!: bigint + @Column('numeric', { default: BigInt(0) }) + sqrtPrice!: bigint + + @Column('numeric', { default: BigInt(0) }) + tick!: bigint + + @Column('numeric', { default: BigInt(0) }) + liquidity!: bigint + + @Column('numeric', { default: BigInt(0) }) + feeGrowthGlobal0X128!: bigint + + @Column('numeric', { default: BigInt(0) }) + feeGrowthGlobal1X128!: bigint + + @Column('numeric', { default: 0 }) + totalValueLockedUSD!: number + + @Column('numeric', { default: 0 }) + totalValueLockedToken0!: number + + @Column('numeric', { default: 0 }) + totalValueLockedToken1!: number + // TODO: Add remaining fields when they are used. } diff --git a/packages/uni-info-watcher/src/entity/PoolDayData.ts b/packages/uni-info-watcher/src/entity/PoolDayData.ts new file mode 100644 index 00000000..9f561dd8 --- /dev/null +++ b/packages/uni-info-watcher/src/entity/PoolDayData.ts @@ -0,0 +1,58 @@ +import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; +import { Pool } from './Pool'; + +@Entity() +export class PoolDayData { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('integer') + blockNumber!: number; + + @Column('integer') + date!: number; + + @ManyToOne(() => Pool) + pool!: Pool; + + @Column('numeric') + high!: number; + + @Column('numeric') + low!: number; + + @Column('numeric') + open!: number; + + @Column('numeric') + close!: number; + + @Column('numeric', { default: BigInt(0) }) + sqrtPrice!: bigint + + @Column('numeric', { default: BigInt(0) }) + tick!: bigint + + @Column('numeric', { default: BigInt(0) }) + liquidity!: bigint + + @Column('numeric', { default: BigInt(0) }) + feeGrowthGlobal0X128!: bigint + + @Column('numeric', { default: BigInt(0) }) + feeGrowthGlobal1X128!: bigint + + @Column('numeric', { default: 0 }) + token0Price!: number + + @Column('numeric', { default: 0 }) + token1Price!: number + + @Column('numeric', { default: 0 }) + tvlUSD!: number + + @Column('numeric', { default: BigInt(0) }) + txCount!: bigint + + // TODO: Add remaining fields when they are used. +} diff --git a/packages/uni-info-watcher/src/entity/PoolHourData.ts b/packages/uni-info-watcher/src/entity/PoolHourData.ts new file mode 100644 index 00000000..c39979ce --- /dev/null +++ b/packages/uni-info-watcher/src/entity/PoolHourData.ts @@ -0,0 +1,58 @@ +import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; +import { Pool } from './Pool'; + +@Entity() +export class PoolHourData { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('integer') + blockNumber!: number; + + @Column('integer') + periodStartUnix!: number; + + @ManyToOne(() => Pool) + pool!: Pool; + + @Column('numeric') + high!: number; + + @Column('numeric') + low!: number; + + @Column('numeric') + open!: number; + + @Column('numeric') + close!: number; + + @Column('numeric', { default: BigInt(0) }) + sqrtPrice!: bigint + + @Column('numeric', { default: BigInt(0) }) + tick!: bigint + + @Column('numeric', { default: BigInt(0) }) + liquidity!: bigint + + @Column('numeric', { default: BigInt(0) }) + feeGrowthGlobal0X128!: bigint + + @Column('numeric', { default: BigInt(0) }) + feeGrowthGlobal1X128!: bigint + + @Column('numeric', { default: 0 }) + token0Price!: number + + @Column('numeric', { default: 0 }) + token1Price!: number + + @Column('numeric', { default: 0 }) + tvlUSD!: number + + @Column('numeric', { default: BigInt(0) }) + txCount!: bigint + + // TODO: Add remaining fields when they are used. +} diff --git a/packages/uni-info-watcher/src/entity/Token.ts b/packages/uni-info-watcher/src/entity/Token.ts index d9cb9328..3a50834e 100644 --- a/packages/uni-info-watcher/src/entity/Token.ts +++ b/packages/uni-info-watcher/src/entity/Token.ts @@ -1,12 +1,12 @@ -import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { Entity, PrimaryColumn, Column, ManyToMany, JoinTable } from 'typeorm'; +import { Pool } from './Pool'; @Entity() -@Index(['blockNumber', 'id']) export class Token { @PrimaryColumn('varchar', { length: 42 }) id!: string; - @PrimaryColumn('numeric') + @PrimaryColumn('integer') blockNumber!: number; @Column('varchar') @@ -18,5 +18,12 @@ export class Token { @Column('numeric') totalSupply!: number; + @Column('numeric', { default: 0 }) + derivedETH!: number; + + @ManyToMany(() => Pool) + @JoinTable() + whitelistPools!: Pool[]; + // TODO: Add remaining fields when they are used. } diff --git a/packages/uni-info-watcher/src/events.ts b/packages/uni-info-watcher/src/events.ts index 61021c3c..46ca75a5 100644 --- a/packages/uni-info-watcher/src/events.ts +++ b/packages/uni-info-watcher/src/events.ts @@ -5,6 +5,9 @@ import { Client as ERC20Client } from '@vulcanize/erc20-watcher'; import { BigNumber } from 'ethers'; import { Database } from './database'; +import { findEthPerToken, getEthPriceInUSD, WHITELIST_TOKENS } from './utils/pricing'; +import { updatePoolDayData, updatePoolHourData } from './utils/intervalUpdates'; +import { Token } from './entity/Token'; const log = debug('vulcanize:events'); @@ -16,6 +19,11 @@ interface PoolCreatedEvent { pool: string; } +interface InitializeEvent { + sqrtPriceX96: bigint; + tick: bigint; +} + interface ResultEvent { proof: { data: string @@ -59,10 +67,15 @@ export class EventWatcher { switch (eventType) { case 'PoolCreatedEvent': - log('PoolCreated event', contract); + log('Factory PoolCreated event', contract); this._handlePoolCreated(blockHash, blockNumber, contract, eventValues as PoolCreatedEvent); break; + case 'InitializeEvent': + log('Pool Initialize event', contract); + this._handleInitialize(blockHash, blockNumber, contract, eventValues as InitializeEvent); + break; + default: break; } @@ -85,36 +98,18 @@ export class EventWatcher { this._db.getToken({ blockNumber, id: token1Address }) ]); - // Create Token. - const createToken = async (tokenAddress: string) => { - const { value: symbol } = await this._erc20Client.getSymbol(blockHash, tokenAddress); - 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 - }); - }; - // Create Tokens if not present. if (!token0) { - token0 = await createToken(token0Address); + token0 = await this._createToken(blockHash, blockNumber, token0Address); } if (!token1) { - token1 = await createToken(token1Address); + token1 = await this._createToken(blockHash, blockNumber, token1Address); } // Create new Pool entity. // Skipping adding createdAtTimestamp field as it is not queried in frontend subgraph. - await this._db.loadPool({ + const pool = await this._db.loadPool({ blockNumber, id: poolAddress, token0: token0, @@ -122,9 +117,74 @@ export class EventWatcher { feeTier: BigInt(fee) }); - // Skipping updating token whitelistPools field as it is not queried in frontend subgraph. + // Update white listed pools. + if (WHITELIST_TOKENS.includes(token0.id)) { + token1.whitelistPools.push(pool); + await this._db.saveToken(token1, blockNumber); + } + + if (WHITELIST_TOKENS.includes(token1.id)) { + token0.whitelistPools.push(pool); + await this._db.saveToken(token0, blockNumber); + } // Save entities to DB. await this._db.saveFactory(factory, blockNumber); } + + /** + * Create new Token. + * @param tokenAddress + */ + async _createToken (blockHash: string, blockNumber: number, tokenAddress: string): Promise { + const { value: symbol } = await this._erc20Client.getSymbol(blockHash, tokenAddress); + 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: tokenAddress, + symbol, + name, + totalSupply + }); + } + + async _handleInitialize (blockHash: string, blockNumber: number, contractAddress: string, initializeEvent: InitializeEvent): Promise { + const { sqrtPriceX96, tick } = initializeEvent; + const pool = await this._db.getPool({ id: contractAddress, blockNumber }); + assert(pool, `Pool ${contractAddress} not found.`); + + // Update Pool. + pool.sqrtPrice = BigInt(sqrtPriceX96); + pool.tick = BigInt(tick); + this._db.savePool(pool, blockNumber); + + // Update ETH price now that prices could have changed. + const bundle = await this._db.loadBundle({ id: '1', blockNumber }); + bundle.ethPriceUSD = await getEthPriceInUSD(this._db); + this._db.saveBundle(bundle, blockNumber); + + await updatePoolDayData(this._db, { contractAddress, blockNumber }); + await updatePoolHourData(this._db, { contractAddress, blockNumber }); + + const [token0, token1] = await Promise.all([ + this._db.getToken({ id: pool.token0.id, blockNumber }), + this._db.getToken({ id: pool.token1.id, blockNumber }) + ]); + + assert(token0 && token1, 'Pool tokens not found.'); + + // Update token prices. + token0.derivedETH = await findEthPerToken(token0); + token1.derivedETH = await findEthPerToken(token1); + + await Promise.all([ + this._db.saveToken(token0, blockNumber), + this._db.saveToken(token1, blockNumber) + ]); + } } diff --git a/packages/uni-info-watcher/src/server.ts b/packages/uni-info-watcher/src/server.ts index 99330314..db02e8c9 100644 --- a/packages/uni-info-watcher/src/server.ts +++ b/packages/uni-info-watcher/src/server.ts @@ -12,6 +12,7 @@ import { getCache } from '@vulcanize/cache'; import { EthClient } from '@vulcanize/ipld-eth-client'; import { Client as ERC20Client } from '@vulcanize/erc20-watcher'; import { Client as UniClient } from '@vulcanize/uni-watcher'; +import { getConfig } from '@vulcanize/util'; import typeDefs from './schema'; @@ -20,7 +21,6 @@ import { createResolvers } from './resolvers'; import { Indexer } from './indexer'; import { Database } from './database'; import { EventWatcher } from './events'; -import { getConfig } from './config'; const log = debug('vulcanize:server'); diff --git a/packages/uni-info-watcher/src/utils/intervalUpdates.ts b/packages/uni-info-watcher/src/utils/intervalUpdates.ts new file mode 100644 index 00000000..2665aa3f --- /dev/null +++ b/packages/uni-info-watcher/src/utils/intervalUpdates.ts @@ -0,0 +1,103 @@ +import { BigNumber } from 'ethers'; + +import { Database } from '../database'; +import { PoolDayData } from '../entity/PoolDayData'; +import { PoolHourData } from '../entity/PoolHourData'; + +export const updatePoolDayData = async (db: Database, event: { contractAddress: string, blockNumber: number }): Promise => { + const { contractAddress, blockNumber } = event; + + // TODO: Get block timestamp from event. + // let timestamp = event.block.timestamp.toI32() + const timestamp = Math.floor(Date.now() / 1000); // Unix timestamp. + + const dayID = Math.floor(timestamp / 86400); + const dayStartTimestamp = dayID * 86400; + + const dayPoolID = contractAddress + .concat('-') + .concat(dayID.toString()); + + const pool = await db.loadPool({ id: contractAddress, blockNumber }); + + let poolDayData = await db.loadPoolDayData({ + id: dayPoolID, + blockNumber, + date: dayStartTimestamp, + pool: pool, + open: pool.token0Price, + high: pool.token0Price, + low: pool.token0Price, + close: pool.token0Price + }); + + if (Number(pool.token0Price) > Number(poolDayData.high)) { + poolDayData.high = pool.token0Price; + } + + if (Number(pool.token0Price) < Number(poolDayData.low)) { + poolDayData.low = pool.token0Price; + } + + poolDayData.liquidity = pool.liquidity; + poolDayData.sqrtPrice = pool.sqrtPrice; + poolDayData.feeGrowthGlobal0X128 = pool.feeGrowthGlobal0X128; + poolDayData.feeGrowthGlobal1X128 = pool.feeGrowthGlobal1X128; + poolDayData.token0Price = pool.token0Price; + poolDayData.token1Price = pool.token1Price; + poolDayData.tick = pool.tick; + poolDayData.tvlUSD = pool.totalValueLockedUSD; + poolDayData.txCount = BigInt(BigNumber.from(poolDayData.txCount).add(1).toHexString()); + poolDayData = await db.savePoolDayData(poolDayData, blockNumber); + + return poolDayData; +}; + +export const updatePoolHourData = async (db: Database, event: { contractAddress: string, blockNumber: number }): Promise => { + const { contractAddress, blockNumber } = event; + + // TODO: Get block timestamp from event. + // let timestamp = event.block.timestamp.toI32() + const timestamp = Math.floor(Date.now() / 1000); // Unix timestamp. + + const hourIndex = Math.floor(timestamp / 3600); // Get unique hour within unix history. + const hourStartUnix = hourIndex * 3600; // Want the rounded effect. + + const hourPoolID = contractAddress + .concat('-') + .concat(hourIndex.toString()); + + const pool = await db.loadPool({ id: contractAddress, blockNumber }); + + let poolHourData = await db.loadPoolHourData({ + id: hourPoolID, + blockNumber, + periodStartUnix: hourStartUnix, + pool: pool, + open: pool.token0Price, + high: pool.token0Price, + low: pool.token0Price, + close: pool.token0Price + }); + + if (Number(pool.token0Price) > Number(poolHourData.high)) { + poolHourData.high = pool.token0Price; + } + if (Number(pool.token0Price) < Number(poolHourData.low)) { + poolHourData.low = pool.token0Price; + } + + poolHourData.liquidity = pool.liquidity; + poolHourData.sqrtPrice = pool.sqrtPrice; + poolHourData.token0Price = pool.token0Price; + poolHourData.token1Price = pool.token1Price; + poolHourData.feeGrowthGlobal0X128 = pool.feeGrowthGlobal0X128; + poolHourData.feeGrowthGlobal1X128 = pool.feeGrowthGlobal1X128; + poolHourData.close = pool.token0Price; + poolHourData.tick = pool.tick; + poolHourData.tvlUSD = pool.totalValueLockedUSD; + poolHourData.txCount = BigInt(BigNumber.from(poolHourData.txCount).add(1).toHexString()); + poolHourData = await db.savePoolHourData(poolHourData, blockNumber); + + return poolHourData; +}; diff --git a/packages/uni-info-watcher/src/utils/pricing.ts b/packages/uni-info-watcher/src/utils/pricing.ts new file mode 100644 index 00000000..79b888a2 --- /dev/null +++ b/packages/uni-info-watcher/src/utils/pricing.ts @@ -0,0 +1,96 @@ +import { BigNumber } from 'ethers'; + +import { Database } from '../database'; +import { Token } from '../entity/Token'; + +// TODO: Move constants to config. +const WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; + +const USDC_WETH_03_POOL = '0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8'; + +// Token where amounts should contribute to tracked volume and liquidity. +// Usually tokens that many tokens are paired with. +// TODO: Load whitelisted tokens from config. +export const WHITELIST_TOKENS: string[] = [ + WETH_ADDRESS, // WETH + '0x6b175474e89094c44da98b954eedeac495271d0f', // DAI + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC + '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT + '0x0000000000085d4780b73119b644ae5ecd22b376', // TUSD + '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', // WBTC + '0x5d3a536e4d6dbd6114cc1ead35777bab948e3643', // cDAI + '0x39aa39c021dfbae8fac545936693ac917d5e7563', // cUSDC + '0x86fadb80d8d2cff3c3680819e4da99c10232ba0f', // EBASE + '0x57ab1ec28d129707052df4df418d58a2d46d5f51', // sUSD + '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', // MKR + '0xc00e94cb662c3520282e6f5717214004a7f26888', // COMP + '0x514910771af9ca656af840dff83e8264ecf986ca', // LINK + '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', // SNX + '0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e', // YFI + '0x111111111117dc0aa78b770fa6a738034120c302', // 1INCH + '0xdf5e0e81dff6faf3a7e52ba697820c5e32d806a8', // yCurv + '0x956f47f50a910163d8bf957cf5846d573e7f87ca', // FEI + '0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0', // MATIC + '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9' // AAVE +]; + +const MINIMUM_ETH_LOCKED = 52; + +export const getEthPriceInUSD = async (db: Database): Promise => { + // Fetch eth prices for each stablecoin. + const usdcPool = await db.getPool({ id: USDC_WETH_03_POOL }); // DAI is token0. + + if (usdcPool) { + return usdcPool.token0Price; + } else { + return 0; + } +}; + +/** + * Search through graph to find derived Eth per token. + * @todo update to be derived ETH (add stablecoin estimates) + **/ +export const findEthPerToken = async (token: Token): Promise => { + if (token.id === WETH_ADDRESS) { + return 1; + } + + const whiteList = token.whitelistPools; + // For now just take USD from pool with greatest TVL. + // Need to update this to actually detect best rate based on liquidity distribution. + let largestLiquidityETH = 0; + let priceSoFar = 0; + + for (let i = 0; i < whiteList.length; ++i) { + const pool = whiteList[i]; + + if (BigNumber.from(pool.liquidity).gt(0)) { + if (pool.token0.id === token.id) { + // Whitelist token is token1. + const token1 = pool.token1; + // Get the derived ETH in pool. + const ethLocked = Number(pool.totalValueLockedToken1) * Number(token1.derivedETH); + + if (ethLocked > largestLiquidityETH && ethLocked > MINIMUM_ETH_LOCKED) { + largestLiquidityETH = ethLocked; + // token1 per our token * Eth per token1 + priceSoFar = Number(pool.token1Price) * Number(token1.derivedETH); + } + } + if (pool.token1.id === token.id) { + const token0 = pool.token0; + // Get the derived ETH in pool. + const ethLocked = Number(pool.totalValueLockedToken0) * Number(token0.derivedETH); + + if (ethLocked > largestLiquidityETH && ethLocked > MINIMUM_ETH_LOCKED) { + largestLiquidityETH = ethLocked; + // token0 per our token * ETH per token0 + priceSoFar = Number(pool.token0Price) * Number(token0.derivedETH); + } + } + } + } + + return priceSoFar; // If nothing was found return 0. +}; diff --git a/packages/uni-watcher/package.json b/packages/uni-watcher/package.json index b8d78e84..9133c353 100644 --- a/packages/uni-watcher/package.json +++ b/packages/uni-watcher/package.json @@ -28,6 +28,7 @@ "@vulcanize/cache": "^0.1.0", "@vulcanize/ipld-eth-client": "^0.1.0", "@vulcanize/solidity-mapper": "^0.1.0", + "@vulcanize/util": "^0.1.0", "apollo-server-express": "^2.25.0", "apollo-type-bigint": "^0.1.3", "debug": "^4.3.1", diff --git a/packages/uni-watcher/src/cli/watch-contract.ts b/packages/uni-watcher/src/cli/watch-contract.ts index 196e57bb..c49d42b8 100644 --- a/packages/uni-watcher/src/cli/watch-contract.ts +++ b/packages/uni-watcher/src/cli/watch-contract.ts @@ -3,7 +3,7 @@ import yargs from 'yargs'; import 'reflect-metadata'; import { ethers } from 'ethers'; -import { Config, getConfig } from '../config'; +import { Config, getConfig } from '@vulcanize/util'; import { Database } from '../database'; (async () => { diff --git a/packages/uni-watcher/src/client.ts b/packages/uni-watcher/src/client.ts index f1978cc8..517e414e 100644 --- a/packages/uni-watcher/src/client.ts +++ b/packages/uni-watcher/src/client.ts @@ -25,6 +25,7 @@ export class Client { } event { __typename + ... on PoolCreatedEvent { token0 token1 @@ -32,6 +33,11 @@ export class Client { tickSpacing pool } + + ... on InitializeEvent { + sqrtPriceX96 + tick + } } } } diff --git a/packages/uni-watcher/src/config.ts b/packages/uni-watcher/src/config.ts deleted file mode 100644 index b93cd8f0..00000000 --- a/packages/uni-watcher/src/config.ts +++ /dev/null @@ -1,35 +0,0 @@ -import fs from 'fs-extra'; -import path from 'path'; -import toml from 'toml'; -import debug from 'debug'; -import { ConnectionOptions } from 'typeorm'; - -import { Config as CacheConfig } from '@vulcanize/cache'; - -const log = debug('vulcanize:config'); - -export interface Config { - server: { - host: string; - port: number; - }; - database: ConnectionOptions; - upstream: { - gqlEndpoint: string; - gqlSubscriptionEndpoint: string; - cache: CacheConfig - } -} - -export const getConfig = async (configFile: string): Promise => { - const configFilePath = path.resolve(configFile); - const fileExists = await fs.pathExists(configFilePath); - if (!fileExists) { - throw new Error(`Config file not found: ${configFilePath}`); - } - - const config = toml.parse(await fs.readFile(configFilePath, 'utf8')); - log('config', JSON.stringify(config, null, 2)); - - return config; -}; diff --git a/packages/uni-watcher/src/indexer.ts b/packages/uni-watcher/src/indexer.ts index 6bd0fa2e..d9f0263d 100644 --- a/packages/uni-watcher/src/indexer.ts +++ b/packages/uni-watcher/src/indexer.ts @@ -8,11 +8,11 @@ import { PubSub } from 'apollo-server-express'; import { EthClient } from '@vulcanize/ipld-eth-client'; import { GetStorageAt } from '@vulcanize/solidity-mapper'; +import { Config } from '@vulcanize/util'; import { Database } from './database'; import { Event } from './entity/Event'; import { Contract, KIND_FACTORY, KIND_POOL } from './entity/Contract'; -import { Config } from './config'; import factoryABI from './artifacts/factory.json'; import poolABI from './artifacts/pool.json'; diff --git a/packages/uni-watcher/src/server.ts b/packages/uni-watcher/src/server.ts index 0d4f7cfb..d149b1e2 100644 --- a/packages/uni-watcher/src/server.ts +++ b/packages/uni-watcher/src/server.ts @@ -10,6 +10,7 @@ import { createServer } from 'http'; import { getCache } from '@vulcanize/cache'; import { EthClient } from '@vulcanize/ipld-eth-client'; +import { getConfig } from '@vulcanize/util'; import typeDefs from './schema'; @@ -18,7 +19,6 @@ import { createResolvers } from './resolvers'; import { Indexer } from './indexer'; import { Database } from './database'; import { EventWatcher } from './events'; -import { getConfig } from './config'; const log = debug('vulcanize:server'); diff --git a/packages/util/.eslintignore b/packages/util/.eslintignore new file mode 100644 index 00000000..653874b5 --- /dev/null +++ b/packages/util/.eslintignore @@ -0,0 +1,5 @@ +# Don't lint node_modules. +node_modules + +# Don't lint build output. +dist diff --git a/packages/util/.eslintrc.json b/packages/util/.eslintrc.json new file mode 100644 index 00000000..acccd0d2 --- /dev/null +++ b/packages/util/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "semistandard", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ] +} diff --git a/packages/util/index.ts b/packages/util/index.ts new file mode 100644 index 00000000..1a52b96c --- /dev/null +++ b/packages/util/index.ts @@ -0,0 +1 @@ +export * from './src/config'; diff --git a/packages/util/package.json b/packages/util/package.json new file mode 100644 index 00000000..40cad460 --- /dev/null +++ b/packages/util/package.json @@ -0,0 +1,32 @@ +{ + "name": "@vulcanize/util", + "version": "0.1.0", + "main": "index.js", + "license": "UNLICENSED", + "dependencies": { + "debug": "^4.3.1", + "ethers": "^5.2.0", + "fs-extra": "^10.0.0", + "toml": "^3.0.0" + }, + "devDependencies": { + "@vulcanize/cache": "^0.1.0", + "@types/fs-extra": "^9.0.11", + "@typescript-eslint/eslint-plugin": "^4.25.0", + "@typescript-eslint/parser": "^4.25.0", + "chai": "^4.3.4", + "eslint": "^7.27.0", + "eslint-config-semistandard": "^15.0.1", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-import": "^2.23.3", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.1.0", + "eslint-plugin-standard": "^5.0.0", + "mocha": "^8.4.0", + "typeorm": "^0.2.32" + }, + "scripts": { + "lint": "eslint .", + "build": "tsc" + } +} diff --git a/packages/uni-info-watcher/src/config.ts b/packages/util/src/config.ts similarity index 97% rename from packages/uni-info-watcher/src/config.ts rename to packages/util/src/config.ts index 61ab51ab..aa2f3414 100644 --- a/packages/uni-info-watcher/src/config.ts +++ b/packages/util/src/config.ts @@ -18,7 +18,7 @@ export interface Config { gqlEndpoint: string; gqlSubscriptionEndpoint: string; traceProviderEndpoint: string; - cache: CacheConfig; + cache: CacheConfig, uniWatcher: { gqlEndpoint: string; gqlSubscriptionEndpoint: string; @@ -27,7 +27,7 @@ export interface Config { gqlEndpoint: string; gqlSubscriptionEndpoint: string; } - } + }, jobQueue: { dbConnectionString: string; maxCompletionLag: number; diff --git a/packages/util/tsconfig.json b/packages/util/tsconfig.json new file mode 100644 index 00000000..eff67a7e --- /dev/null +++ b/packages/util/tsconfig.json @@ -0,0 +1,77 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "lib": [ "ES5", "ES6", "ES2020" ], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "dist", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + "typeRoots": [ + "./src/types" + ], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ + "resolveJsonModule": true /* Enabling the option allows importing JSON, and validating the types in that JSON file. */ + }, + "include": ["src"], + "exclude": ["dist"] +}