Fix queries, demo mode to display all tokens (#231)

* Fix queries for frontend info app.

* Fix query for nested relations.

* Add demo mode in uni-info-watcher.

Co-authored-by: nabarun <nabarun@deepstacksoft.com>
This commit is contained in:
Ashwin Phatak 2021-08-26 15:36:16 +05:30 committed by GitHub
parent 5941a929d2
commit 8fa6229562
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 132 additions and 26 deletions

View File

@ -103,6 +103,7 @@ type Transaction {
}
type Token {
decimals: BigInt!
derivedETH: BigDecimal!
feesUSD: BigDecimal!
id: ID!
@ -362,9 +363,11 @@ type Query {
): [TokenHourData!]!
tokens(
first: Int = 100
orderBy: Token_orderBy
orderDirection: OrderDirection
where: Token_filter
block: Block_height
): [Token!]!
transactions(

View File

@ -1,6 +1,9 @@
[server]
host = "127.0.0.1"
port = 3004
# Use mode demo when running watcher locally.
# Mode demo whitelists all tokens so that entity values get updated.
mode = "demo"
[database]
type = "postgres"

View File

@ -18,7 +18,8 @@ import {
DatabaseInterface,
BlockHeight,
QueryOptions,
Where
Where,
Relation
} from '@vulcanize/util';
import { Factory } from './entity/Factory';
@ -412,7 +413,7 @@ export class Database implements DatabaseInterface {
return entity;
}
async getModelEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, block: BlockHeight, where: Where = {}, queryOptions: QueryOptions = {}, relations: string[] = []): Promise<Entity[]> {
async getModelEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, block: BlockHeight, where: Where = {}, queryOptions: QueryOptions = {}, relations: Relation[] = []): Promise<Entity[]> {
return this._baseDatabase.getModelEntities(queryRunner, entity, block, where, queryOptions, relations);
}

View File

@ -50,7 +50,7 @@ export const main = async (): Promise<any> => {
assert(config.server, 'Missing server config');
const { upstream, database: dbConfig, jobQueue: jobQueueConfig } = config;
const { upstream, database: dbConfig, jobQueue: jobQueueConfig, server: { mode } } = config;
assert(dbConfig, 'Missing database config');
@ -74,7 +74,7 @@ export const main = async (): Promise<any> => {
// 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, uniClient, erc20Client, ethClient);
const indexer = new Indexer(db, uniClient, erc20Client, ethClient, mode);
assert(jobQueueConfig, 'Missing job queue config');
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;

View File

@ -11,7 +11,7 @@ import { utils } from 'ethers';
import { Client as UniClient } from '@vulcanize/uni-watcher';
import { Client as ERC20Client } from '@vulcanize/erc20-watcher';
import { EthClient } from '@vulcanize/ipld-eth-client';
import { IndexerInterface, Indexer as BaseIndexer, QueryOptions, OrderDirection, BlockHeight } from '@vulcanize/util';
import { IndexerInterface, Indexer as BaseIndexer, QueryOptions, OrderDirection, BlockHeight, Relation } from '@vulcanize/util';
import { findEthPerToken, getEthPriceInUSD, getTrackedAmountUSD, sqrtPriceX96ToTokenPrices, WHITELIST_TOKENS } from './utils/pricing';
import { updatePoolDayData, updatePoolHourData, updateTokenDayData, updateTokenHourData, updateUniswapDayData } from './utils/interval-updates';
@ -52,8 +52,9 @@ export class Indexer implements IndexerInterface {
_erc20Client: ERC20Client
_ethClient: EthClient
_baseIndexer: BaseIndexer
_isDemo: boolean
constructor (db: Database, uniClient: UniClient, erc20Client: ERC20Client, ethClient: EthClient) {
constructor (db: Database, uniClient: UniClient, erc20Client: ERC20Client, ethClient: EthClient, mode: string) {
assert(db);
assert(uniClient);
assert(erc20Client);
@ -64,6 +65,7 @@ export class Indexer implements IndexerInterface {
this._erc20Client = erc20Client;
this._ethClient = ethClient;
this._baseIndexer = new BaseIndexer(this._db, this._ethClient);
this._isDemo = mode === 'demo';
}
getResultEvent (event: Event): ResultEvent {
@ -255,7 +257,7 @@ export class Indexer implements IndexerInterface {
return res;
}
async getEntities<Entity> (entity: new () => Entity, block: BlockHeight, where: { [key: string]: any } = {}, queryOptions: QueryOptions, relations?: string[]): Promise<Entity[]> {
async getEntities<Entity> (entity: new () => Entity, block: BlockHeight, where: { [key: string]: any } = {}, queryOptions: QueryOptions, relations?: Relation[]): Promise<Entity[]> {
const dbTx = await this._db.createTransactionRunner();
let res;
@ -439,6 +441,11 @@ export class Indexer implements IndexerInterface {
token0 = await this._db.saveToken(dbTx, token0, block);
token1 = await this._db.saveToken(dbTx, token1, block);
token0 = await this._db.getToken(dbTx, token0);
token1 = await this._db.getToken(dbTx, token1);
assert(token0);
assert(token1);
pool.token0 = token0;
pool.token1 = token1;
pool.feeTier = BigInt(fee);
@ -448,11 +455,11 @@ export class Indexer implements IndexerInterface {
pool = await this._db.savePool(dbTx, pool, block);
// Update white listed pools.
if (WHITELIST_TOKENS.includes(token0.id)) {
if (WHITELIST_TOKENS.includes(token0.id) || this._isDemo) {
token1.whitelistPools.push(pool);
}
if (WHITELIST_TOKENS.includes(token1.id)) {
if (WHITELIST_TOKENS.includes(token1.id) || this._isDemo) {
token0.whitelistPools.push(pool);
}
@ -860,7 +867,7 @@ export class Indexer implements IndexerInterface {
const amount1USD = amount1ETH.times(bundle.ethPriceUSD);
// Get amount that should be tracked only - div 2 because cant count both input and output as volume.
const trackedAmountUSD = await getTrackedAmountUSD(this._db, dbTx, amount0Abs, token0, amount1Abs, token1);
const trackedAmountUSD = await getTrackedAmountUSD(this._db, dbTx, amount0Abs, token0, amount1Abs, token1, this._isDemo);
const amountTotalUSDTracked = trackedAmountUSD.div(new Decimal('2'));
const amountTotalETHTracked = safeDiv(amountTotalUSDTracked, bundle.ethPriceUSD);
const amountTotalUSDUntracked = amount0USD.plus(amount1USD).div(new Decimal('2'));

View File

@ -90,7 +90,7 @@ export const main = async (): Promise<any> => {
assert(config.server, 'Missing server config');
const { upstream, database: dbConfig, jobQueue: jobQueueConfig } = config;
const { upstream, database: dbConfig, jobQueue: jobQueueConfig, server: { mode } } = config;
assert(dbConfig, 'Missing database config');
@ -116,7 +116,7 @@ export const main = async (): Promise<any> => {
const erc20Client = new ERC20Client(tokenWatcher);
const indexer = new Indexer(db, uniClient, erc20Client, ethClient);
const indexer = new Indexer(db, uniClient, erc20Client, ethClient, mode);
assert(jobQueueConfig, 'Missing job queue config');

View File

@ -63,7 +63,7 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
burns: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('burns', first, orderBy, orderDirection, where);
return indexer.getEntities(Burn, {}, where, { limit: first, orderBy, orderDirection }, ['pool', 'transaction']);
return indexer.getEntities(Burn, {}, where, { limit: first, orderBy, orderDirection }, ['burn.pool', 'burn.transaction']);
},
factories: async (_: any, { block = {}, first }: { first: number, block: BlockHeight }) => {
@ -75,7 +75,7 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
mints: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('mints', first, orderBy, orderDirection, where);
return indexer.getEntities(Mint, {}, where, { limit: first, orderBy, orderDirection }, ['pool', 'transaction']);
return indexer.getEntities(Mint, {}, where, { limit: first, orderBy, orderDirection }, ['mint.pool', 'mint.transaction']);
},
pool: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
@ -93,13 +93,13 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
pools: async (_: any, { block = {}, first, orderBy, orderDirection, where = {} }: { block: BlockHeight, first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('pools', block, first, orderBy, orderDirection, where);
return indexer.getEntities(Pool, block, where, { limit: first, orderBy, orderDirection }, ['token0', 'token1']);
return indexer.getEntities(Pool, block, where, { limit: first, orderBy, orderDirection }, ['pool.token0', 'pool.token1']);
},
swaps: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('swaps', first, orderBy, orderDirection, where);
return indexer.getEntities(Swap, {}, where, { limit: first, orderBy, orderDirection }, ['pool', 'transaction']);
return indexer.getEntities(Swap, {}, where, { limit: first, orderBy, orderDirection }, ['swap.pool', 'swap.transaction']);
},
ticks: async (_: any, { block = {}, first, skip, where = {} }: { block: BlockHeight, first: number, skip: number, where: { [key: string]: any } }) => {
@ -114,10 +114,10 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
return indexer.getToken(id, block);
},
tokens: async (_: any, { orderBy, orderDirection, where }: { orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
tokens: async (_: any, { block = {}, first, orderBy, orderDirection, where }: { block: BlockHeight, first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('tokens', orderBy, orderDirection, where);
return indexer.getEntities(Token, {}, where, { orderBy, orderDirection });
return indexer.getEntities(Token, block, where, { limit: first, orderBy, orderDirection });
},
tokenDayDatas: async (_: any, { first, skip, orderBy, orderDirection, where }: { first: number, skip: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
@ -135,7 +135,65 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
transactions: async (_: any, { first, orderBy, orderDirection }: { first: number, orderBy: string, orderDirection: OrderDirection}) => {
log('transactions', first, orderBy, orderDirection);
return indexer.getEntities(Transaction, {}, {}, { limit: first, orderBy, orderDirection }, ['burns', 'mints', 'swaps']);
return indexer.getEntities(
Transaction,
{},
{},
{ limit: first, orderBy, orderDirection },
[
'transaction.mints',
'transaction.burns',
'transaction.swaps',
{
property: 'mints.transaction',
alias: 'mintsTransaction'
},
{
property: 'burns.transaction',
alias: 'burnsTransaction'
},
{
property: 'swaps.transaction',
alias: 'swapsTransaction'
},
{
property: 'mints.pool',
alias: 'mintsPool'
},
{
property: 'burns.pool',
alias: 'burnsPool'
},
{
property: 'swaps.pool',
alias: 'swapsPool'
},
{
property: 'mintsPool.token0',
alias: 'mintsPoolToken0'
},
{
property: 'mintsPool.token1',
alias: 'mintsPoolToken1'
},
{
property: 'burnsPool.token0',
alias: 'burnsPoolToken0'
},
{
property: 'burnsPool.token1',
alias: 'burnsPoolToken1'
},
{
property: 'swapsPool.token0',
alias: 'swapsPoolToken0'
},
{
property: 'swapsPool.token1',
alias: 'swapsPoolToken1'
}
]
);
},
uniswapDayDatas: async (_: any, { first, skip, orderBy, orderDirection, where }: { first: number, skip: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
@ -147,7 +205,20 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
positions: async (_: any, { first, where }: { first: number, where: { [key: string]: any } }) => {
log('positions', first, where);
return indexer.getEntities(Position, {}, where, { limit: first }, ['pool', 'token0', 'token1', 'tickLower', 'tickUpper', 'transaction']);
return indexer.getEntities(
Position,
{},
where,
{ limit: first },
[
'position.pool',
'position.token0',
'position.token1',
'position.tickLower',
'position.tickUpper',
'position.transaction'
]
);
},
blocks: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {

View File

@ -109,6 +109,7 @@ type Transaction {
}
type Token {
decimals: BigInt!
derivedETH: BigDecimal!
feesUSD: BigDecimal!
id: ID!
@ -440,9 +441,11 @@ type Query {
): [TokenHourData!]!
tokens(
first: Int = 100
orderBy: Token_orderBy
orderDirection: OrderDirection
where: Token_filter
block: Block_height
): [Token!]!
transactions(

View File

@ -42,7 +42,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, jobQueue: jobQueueConfig } = config;
@ -74,7 +74,7 @@ export const main = async (): Promise<any> => {
const uniClient = new UniClient(uniWatcher);
const erc20Client = new ERC20Client(tokenWatcher);
const indexer = new Indexer(db, uniClient, erc20Client, ethClient);
const indexer = new Indexer(db, uniClient, erc20Client, ethClient, mode);
assert(jobQueueConfig, 'Missing job queue config');

View File

@ -131,7 +131,8 @@ export const getTrackedAmountUSD = async (
tokenAmount0: Decimal,
token0: Token,
tokenAmount1: Decimal,
token1: Token
token1: Token,
isDemo: boolean
): Promise<Decimal> => {
const bundle = await db.getBundle(dbTx, { id: '1' });
assert(bundle);
@ -139,7 +140,8 @@ export const getTrackedAmountUSD = async (
const price1USD = token1.derivedETH.times(bundle.ethPriceUSD);
// Both are whitelist tokens, return sum of both amounts.
if (WHITELIST_TOKENS.includes(token0.id) && WHITELIST_TOKENS.includes(token1.id)) {
// Use demo mode
if ((WHITELIST_TOKENS.includes(token0.id) && WHITELIST_TOKENS.includes(token1.id)) || isDemo) {
return tokenAmount0.times(price0USD).plus(tokenAmount1.times(price1USD));
}

View File

@ -61,6 +61,8 @@ export interface Where {
}]
}
export type Relation = string | { property: string, alias: string }
export class Database {
_config: ConnectionOptions
_conn!: Connection
@ -330,7 +332,7 @@ export class Database {
return await repo.save(entity);
}
async getModelEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, block: BlockHeight, where: Where = {}, queryOptions: QueryOptions = {}, relations: string[] = []): Promise<Entity[]> {
async getModelEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, block: BlockHeight, where: Where = {}, queryOptions: QueryOptions = {}, relations: Relation[] = []): Promise<Entity[]> {
const repo = queryRunner.manager.getRepository(entity);
const { tableName } = repo.metadata;
@ -357,7 +359,17 @@ export class Database {
.setParameters(subQuery.getParameters());
relations.forEach(relation => {
selectQueryBuilder = selectQueryBuilder.leftJoinAndSelect(`${repo.metadata.tableName}.${relation}`, relation);
let alias, property;
if (typeof relation === 'string') {
[, alias] = relation.split('.');
property = relation;
} else {
alias = relation.alias;
property = relation.property;
}
selectQueryBuilder = selectQueryBuilder.leftJoinAndSelect(property, alias);
});
Object.entries(where).forEach(([field, filters]) => {
@ -393,6 +405,10 @@ export class Database {
whereClause += '%';
} else if (operator === 'in') {
whereClause += ')';
if (!value.length) {
whereClause = 'FALSE';
}
}
selectQueryBuilder = selectQueryBuilder.andWhere(whereClause, { [variableName]: value });