Handle mint event (#130)

* Handle mint event and update Factory fields.

* Integrate decimal.js with typeorm.

* Update transaction in Mint event.

* Update day and hour data.

Co-authored-by: nabarun <nabarun@deepstacksoft.com>
This commit is contained in:
Ashwin Phatak 2021-07-13 12:01:54 +05:30 committed by GitHub
parent d71557e963
commit 3a6af9f9cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1042 additions and 177 deletions

View File

@ -11,6 +11,7 @@
"@vulcanize/util": "^0.1.0", "@vulcanize/util": "^0.1.0",
"apollo-server-express": "^2.25.0", "apollo-server-express": "^2.25.0",
"apollo-type-bigint": "^0.1.3", "apollo-type-bigint": "^0.1.3",
"decimal.js": "^10.3.1",
"typeorm": "^0.2.32" "typeorm": "^0.2.32"
}, },
"scripts": { "scripts": {

View File

@ -10,6 +10,12 @@ import { Token } from './entity/Token';
import { Bundle } from './entity/Bundle'; import { Bundle } from './entity/Bundle';
import { PoolDayData } from './entity/PoolDayData'; import { PoolDayData } from './entity/PoolDayData';
import { PoolHourData } from './entity/PoolHourData'; import { PoolHourData } from './entity/PoolHourData';
import { Transaction } from './entity/Transaction';
import { Mint } from './entity/Mint';
import { UniswapDayData } from './entity/UniswapDayData';
import { Tick } from './entity/Tick';
import { TokenDayData } from './entity/TokenDayData';
import { TokenHourData } from './entity/TokenHourData';
export class Database { export class Database {
_config: ConnectionOptions _config: ConnectionOptions
@ -72,6 +78,27 @@ export class Database {
return repo.findOne(findOptions); return repo.findOne(findOptions);
} }
async getFactories ({ blockNumber }: DeepPartial<Factory>, queryOptions: { [key: string]: any }): Promise<Array<Factory>> {
const repo = this._conn.getRepository(Factory);
let selectQueryBuilder = repo.createQueryBuilder('factory')
.distinctOn(['id'])
.orderBy('id')
.addOrderBy('block_number', 'DESC');
if (blockNumber) {
selectQueryBuilder = selectQueryBuilder.where('block_number <= :blockNumber', { blockNumber });
}
const { limit } = queryOptions;
if (limit) {
selectQueryBuilder = selectQueryBuilder.limit(limit);
}
return selectQueryBuilder.getMany();
}
async loadFactory ({ id, blockNumber, ...values }: DeepPartial<Factory>): Promise<Factory> { async loadFactory ({ id, blockNumber, ...values }: DeepPartial<Factory>): Promise<Factory> {
return this._conn.transaction(async (tx) => { return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(Factory); const repo = tx.getRepository(Factory);
@ -83,7 +110,7 @@ export class Database {
selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber }); selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber });
} }
let entity = await selectQueryBuilder.orderBy('factory.block_number', 'DESC') let entity = await selectQueryBuilder.orderBy('block_number', 'DESC')
.getOne(); .getOne();
if (!entity) { if (!entity) {
@ -167,7 +194,7 @@ export class Database {
selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber }); selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber });
} }
let entity = await selectQueryBuilder.orderBy('bundle.block_number', 'DESC') let entity = await selectQueryBuilder.orderBy('block_number', 'DESC')
.getOne(); .getOne();
if (!entity) { if (!entity) {
@ -190,7 +217,7 @@ export class Database {
selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber }); selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber });
} }
let entity = await selectQueryBuilder.orderBy('pool_day_data.block_number', 'DESC') let entity = await selectQueryBuilder.orderBy('block_number', 'DESC')
.getOne(); .getOne();
if (!entity) { if (!entity) {
@ -213,7 +240,145 @@ export class Database {
selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber }); selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber });
} }
let entity = await selectQueryBuilder.orderBy('pool_hour_data.block_number', 'DESC') let entity = await selectQueryBuilder.orderBy('block_number', 'DESC')
.getOne();
if (!entity) {
entity = repo.create({ blockNumber, id, ...values });
entity = await repo.save(entity);
}
return entity;
});
}
async loadTransaction ({ id, blockNumber, ...values }: DeepPartial<Transaction>): Promise<Transaction> {
return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(Transaction);
let selectQueryBuilder = repo.createQueryBuilder('transaction')
.where('id = :id', { id });
if (blockNumber) {
selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber });
}
let entity = await selectQueryBuilder.orderBy('block_number', 'DESC')
.getOne();
if (!entity) {
entity = repo.create({ blockNumber, id, ...values });
entity = await repo.save(entity);
}
return entity;
});
}
async loadMint ({ id, blockNumber, ...values }:DeepPartial<Mint>): Promise<Mint> {
return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(Mint);
let selectQueryBuilder = repo.createQueryBuilder('mint')
.where('id = :id', { id });
if (blockNumber) {
selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber });
}
let entity = await selectQueryBuilder.orderBy('block_number', 'DESC')
.getOne();
if (!entity) {
entity = repo.create({ blockNumber, id, ...values });
entity = await repo.save(entity);
}
return entity;
});
}
async loadTick ({ id, blockNumber, ...values }: DeepPartial<Tick>): Promise<Tick> {
return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(Tick);
let selectQueryBuilder = repo.createQueryBuilder('tick')
.where('id = :id', { id });
if (blockNumber) {
selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber });
}
let entity = await selectQueryBuilder.orderBy('block_number', 'DESC')
.getOne();
if (!entity) {
entity = repo.create({ blockNumber, id, ...values });
entity = await repo.save(entity);
}
return entity;
});
}
async loadUniswapDayData ({ id, blockNumber, ...values }: DeepPartial<UniswapDayData>): Promise<UniswapDayData> {
return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(UniswapDayData);
let selectQueryBuilder = repo.createQueryBuilder('uniswap_day_data')
.where('id = :id', { id });
if (blockNumber) {
selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber });
}
let entity = await selectQueryBuilder.orderBy('block_number', 'DESC')
.getOne();
if (!entity) {
entity = repo.create({ blockNumber, id, ...values });
entity = await repo.save(entity);
}
return entity;
});
}
async loadTokenDayData ({ id, blockNumber, ...values }: DeepPartial<TokenDayData>): Promise<TokenDayData> {
return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(TokenDayData);
let selectQueryBuilder = repo.createQueryBuilder('token_day_data')
.where('id = :id', { id });
if (blockNumber) {
selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber });
}
let entity = await selectQueryBuilder.orderBy('block_number', 'DESC')
.getOne();
if (!entity) {
entity = repo.create({ blockNumber, id, ...values });
entity = await repo.save(entity);
}
return entity;
});
}
async loadTokenHourData ({ id, blockNumber, ...values }: DeepPartial<TokenHourData>): Promise<TokenHourData> {
return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(TokenHourData);
let selectQueryBuilder = repo.createQueryBuilder('token_hour_data')
.where('id = :id', { id });
if (blockNumber) {
selectQueryBuilder = selectQueryBuilder.andWhere('block_number <= :blockNumber', { blockNumber });
}
let entity = await selectQueryBuilder.orderBy('block_number', 'DESC')
.getOne(); .getOne();
if (!entity) { if (!entity) {
@ -273,6 +438,38 @@ export class Database {
}); });
} }
async saveTransaction (transaction: Transaction, blockNumber: number): Promise<Transaction> {
return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(Transaction);
transaction.blockNumber = blockNumber;
return repo.save(transaction);
});
}
async saveUniswapDayData (uniswapDayData: UniswapDayData, blockNumber: number): Promise<UniswapDayData> {
return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(UniswapDayData);
uniswapDayData.blockNumber = blockNumber;
return repo.save(uniswapDayData);
});
}
async saveTokenDayData (tokenDayData: TokenDayData, blockNumber: number): Promise<TokenDayData> {
return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(TokenDayData);
tokenDayData.blockNumber = blockNumber;
return repo.save(tokenDayData);
});
}
async saveTokenHourData (tokenHourData: TokenHourData, blockNumber: number): Promise<TokenHourData> {
return this._conn.transaction(async (tx) => {
const repo = tx.getRepository(TokenHourData);
tokenHourData.blockNumber = blockNumber;
return repo.save(tokenHourData);
});
}
// Returns true if events have already been synced for the (block, token) combination. // Returns true if events have already been synced for the (block, token) combination.
async didSyncEvents ({ blockHash, token }: { blockHash: string, token: string }): Promise<boolean> { async didSyncEvents ({ blockHash, token }: { blockHash: string, token: string }): Promise<boolean> {
const numRows = await this._conn.getRepository(EventSyncProgress) const numRows = await this._conn.getRepository(EventSyncProgress)

View File

@ -1,4 +1,6 @@
import { Entity, PrimaryColumn, Column } from 'typeorm'; import { Entity, PrimaryColumn, Column } from 'typeorm';
import Decimal from 'decimal.js';
import { decimalTransformer } from '@vulcanize/util';
@Entity() @Entity()
export class Bundle { export class Bundle {
@ -8,6 +10,6 @@ export class Bundle {
@PrimaryColumn('integer') @PrimaryColumn('integer')
blockNumber!: number; blockNumber!: number;
@Column('numeric', { default: 0 }) @Column('numeric', { default: 0, transformer: decimalTransformer })
ethPriceUSD!: number ethPriceUSD!: Decimal
} }

View File

@ -1,4 +1,6 @@
import Decimal from 'decimal.js';
import { Entity, Column, PrimaryColumn } from 'typeorm'; import { Entity, Column, PrimaryColumn } from 'typeorm';
import { decimalTransformer } from '@vulcanize/util';
@Entity() @Entity()
export class Factory { export class Factory {
@ -8,8 +10,17 @@ export class Factory {
@PrimaryColumn('integer') @PrimaryColumn('integer')
blockNumber!: number; blockNumber!: number;
@Column('numeric', { default: BigInt(0) }) @Column('bigint', { default: BigInt(0) })
poolCount!: bigint; poolCount!: bigint;
@Column('numeric', { default: 0, transformer: decimalTransformer })
totalValueLockedETH!: Decimal;
@Column('bigint', { default: BigInt(0) })
txCount!: bigint;
@Column('numeric', { default: 0, transformer: decimalTransformer })
totalValueLockedUSD!: Decimal;
// TODO: Add remaining fields when they are used. // TODO: Add remaining fields when they are used.
} }

View File

@ -0,0 +1,59 @@
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import Decimal from 'decimal.js';
import { decimalTransformer } from '@vulcanize/util';
import { Transaction } from './Transaction';
import { Pool } from './Pool';
import { Token } from './Token';
@Entity()
export class Mint {
@PrimaryColumn('varchar')
id!: string;
@PrimaryColumn('integer')
blockNumber!: number;
@ManyToOne(() => Transaction, transaction => transaction.mints)
transaction!: Transaction
@Column('bigint')
timestamp!: BigInt;
@ManyToOne(() => Pool)
pool!: Pool
@ManyToOne(() => Token)
token0!: Token
@ManyToOne(() => Token)
token1!: Token
@Column('varchar', { length: 42 })
owner!: string
@Column('varchar', { length: 42 })
sender!: string
// TODO: Assign origin with Transaction from address.
// @Column('varchar', { length: 42 })
// origin!: string
@Column('bigint')
amount!: bigint
@Column('numeric', { transformer: decimalTransformer })
amount0!: Decimal
@Column('numeric', { transformer: decimalTransformer })
amount1!: Decimal
@Column('numeric', { transformer: decimalTransformer })
amountUSD!: Decimal
@Column('bigint')
tickLower!: bigint
@Column('bigint')
tickUpper!: bigint
}

View File

@ -1,4 +1,6 @@
import Decimal from 'decimal.js';
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { decimalTransformer } from '@vulcanize/util';
import { Token } from './Token'; import { Token } from './Token';
@ -16,11 +18,11 @@ export class Pool {
@ManyToOne(() => Token) @ManyToOne(() => Token)
token1!: Token; token1!: Token;
@Column('numeric', { default: 0 }) @Column('numeric', { default: 0, transformer: decimalTransformer })
token0Price!: number token0Price!: Decimal
@Column('numeric', { default: 0 }) @Column('numeric', { default: 0, transformer: decimalTransformer })
token1Price!: number token1Price!: Decimal
@Column('numeric') @Column('numeric')
feeTier!: bigint feeTier!: bigint
@ -28,8 +30,8 @@ export class Pool {
@Column('numeric', { default: BigInt(0) }) @Column('numeric', { default: BigInt(0) })
sqrtPrice!: bigint sqrtPrice!: bigint
@Column('numeric', { default: BigInt(0) }) @Column('bigint', { nullable: true })
tick!: bigint tick!: bigint | null
@Column('numeric', { default: BigInt(0) }) @Column('numeric', { default: BigInt(0) })
liquidity!: bigint liquidity!: bigint
@ -40,14 +42,20 @@ export class Pool {
@Column('numeric', { default: BigInt(0) }) @Column('numeric', { default: BigInt(0) })
feeGrowthGlobal1X128!: bigint feeGrowthGlobal1X128!: bigint
@Column('numeric', { default: 0 }) @Column('numeric', { default: 0, transformer: decimalTransformer })
totalValueLockedUSD!: number totalValueLockedUSD!: Decimal
@Column('numeric', { default: 0 }) @Column('numeric', { default: 0, transformer: decimalTransformer })
totalValueLockedToken0!: number totalValueLockedToken0!: Decimal
@Column('numeric', { default: 0 }) @Column('numeric', { default: 0, transformer: decimalTransformer })
totalValueLockedToken1!: number totalValueLockedToken1!: Decimal
@Column('numeric', { default: 0, transformer: decimalTransformer })
totalValueLockedETH!: Decimal
@Column('bigint', { default: BigInt(0) })
txCount!: bigint;
// TODO: Add remaining fields when they are used. // TODO: Add remaining fields when they are used.
} }

View File

@ -1,4 +1,7 @@
import Decimal from 'decimal.js';
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { decimalTransformer } from '@vulcanize/util';
import { Pool } from './Pool'; import { Pool } from './Pool';
@Entity() @Entity()
@ -15,23 +18,23 @@ export class PoolDayData {
@ManyToOne(() => Pool) @ManyToOne(() => Pool)
pool!: Pool; pool!: Pool;
@Column('numeric') @Column('numeric', { transformer: decimalTransformer })
high!: number; high!: Decimal;
@Column('numeric') @Column('numeric', { transformer: decimalTransformer })
low!: number; low!: Decimal;
@Column('numeric') @Column('numeric', { transformer: decimalTransformer })
open!: number; open!: Decimal;
@Column('numeric') @Column('numeric', { transformer: decimalTransformer })
close!: number; close!: Decimal;
@Column('numeric', { default: BigInt(0) }) @Column('numeric', { default: BigInt(0) })
sqrtPrice!: bigint sqrtPrice!: bigint
@Column('numeric', { default: BigInt(0) }) @Column('bigint', { nullable: true })
tick!: bigint tick!: bigint | null
@Column('numeric', { default: BigInt(0) }) @Column('numeric', { default: BigInt(0) })
liquidity!: bigint liquidity!: bigint
@ -42,14 +45,14 @@ export class PoolDayData {
@Column('numeric', { default: BigInt(0) }) @Column('numeric', { default: BigInt(0) })
feeGrowthGlobal1X128!: bigint feeGrowthGlobal1X128!: bigint
@Column('numeric', { default: 0 }) @Column('numeric', { default: 0, transformer: decimalTransformer })
token0Price!: number token0Price!: Decimal
@Column('numeric', { default: 0 }) @Column('numeric', { default: 0, transformer: decimalTransformer })
token1Price!: number token1Price!: Decimal
@Column('numeric', { default: 0 }) @Column('numeric', { default: 0, transformer: decimalTransformer })
tvlUSD!: number tvlUSD!: Decimal
@Column('numeric', { default: BigInt(0) }) @Column('numeric', { default: BigInt(0) })
txCount!: bigint txCount!: bigint

View File

@ -1,4 +1,7 @@
import Decimal from 'decimal.js';
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { decimalTransformer } from '@vulcanize/util';
import { Pool } from './Pool'; import { Pool } from './Pool';
@Entity() @Entity()
@ -15,23 +18,23 @@ export class PoolHourData {
@ManyToOne(() => Pool) @ManyToOne(() => Pool)
pool!: Pool; pool!: Pool;
@Column('numeric') @Column('numeric', { transformer: decimalTransformer })
high!: number; high!: Decimal;
@Column('numeric') @Column('numeric', { transformer: decimalTransformer })
low!: number; low!: Decimal;
@Column('numeric') @Column('numeric', { transformer: decimalTransformer })
open!: number; open!: Decimal;
@Column('numeric') @Column('numeric', { transformer: decimalTransformer })
close!: number; close!: Decimal;
@Column('numeric', { default: BigInt(0) }) @Column('numeric', { default: BigInt(0) })
sqrtPrice!: bigint sqrtPrice!: bigint
@Column('numeric', { default: BigInt(0) }) @Column('bigint', { nullable: true })
tick!: bigint tick!: bigint | null
@Column('numeric', { default: BigInt(0) }) @Column('numeric', { default: BigInt(0) })
liquidity!: bigint liquidity!: bigint
@ -42,14 +45,14 @@ export class PoolHourData {
@Column('numeric', { default: BigInt(0) }) @Column('numeric', { default: BigInt(0) })
feeGrowthGlobal1X128!: bigint feeGrowthGlobal1X128!: bigint
@Column('numeric', { default: 0 }) @Column('numeric', { default: 0, transformer: decimalTransformer })
token0Price!: number token0Price!: Decimal
@Column('numeric', { default: 0 }) @Column('numeric', { default: 0, transformer: decimalTransformer })
token1Price!: number token1Price!: Decimal
@Column('numeric', { default: 0 }) @Column('numeric', { default: 0, transformer: decimalTransformer })
tvlUSD!: number tvlUSD!: Decimal
@Column('numeric', { default: BigInt(0) }) @Column('numeric', { default: BigInt(0) })
txCount!: bigint txCount!: bigint

View File

@ -0,0 +1,35 @@
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import Decimal from 'decimal.js';
import { decimalTransformer } from '@vulcanize/util';
import { Pool } from './Pool';
@Entity()
export class Tick {
@PrimaryColumn('varchar')
id!: string;
@PrimaryColumn('integer')
blockNumber!: number;
@Column('bigint')
tickIdx!: BigInt;
@ManyToOne(() => Pool)
pool!: Pool
@Column('varchar', { length: 42 })
poolAddress!: string
@Column('numeric', { transformer: decimalTransformer })
price0!: Decimal
@Column('numeric', { transformer: decimalTransformer })
price1!: Decimal
@Column('bigint', { default: 0 })
liquidityGross!: bigint
@Column('bigint', { default: 0 })
liquidityNet!: bigint
}

View File

@ -1,4 +1,7 @@
import Decimal from 'decimal.js';
import { Entity, PrimaryColumn, Column, ManyToMany, JoinTable } from 'typeorm'; import { Entity, PrimaryColumn, Column, ManyToMany, JoinTable } from 'typeorm';
import { decimalTransformer } from '@vulcanize/util';
import { Pool } from './Pool'; import { Pool } from './Pool';
@Entity() @Entity()
@ -15,11 +18,24 @@ export class Token {
@Column('varchar') @Column('varchar')
name!: string; name!: string;
@Column('numeric') @Column('numeric', { transformer: decimalTransformer })
totalSupply!: number; totalSupply!: Decimal;
@Column('numeric', { default: 0 }) // TODO: Fetch decimals from contract using erc20-watcher. Currently using hardcoded value.
derivedETH!: number; @Column('bigint', { default: 18 })
decimals!: bigint;
@Column('numeric', { default: 0, transformer: decimalTransformer })
derivedETH!: Decimal;
@Column('bigint', { default: BigInt(0) })
txCount!: bigint;
@Column('numeric', { default: 0, transformer: decimalTransformer })
totalValueLocked!: Decimal;
@Column('numeric', { default: 0, transformer: decimalTransformer })
totalValueLockedUSD!: Decimal;
@ManyToMany(() => Pool) @ManyToMany(() => Pool)
@JoinTable() @JoinTable()

View File

@ -0,0 +1,44 @@
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import Decimal from 'decimal.js';
import { decimalTransformer } from '@vulcanize/util';
import { Token } from './Token';
@Entity()
export class TokenDayData {
@PrimaryColumn('varchar')
id!: string;
@PrimaryColumn('integer')
blockNumber!: number;
@Column('integer')
date!: number
@ManyToOne(() => Token)
token!: Token
@Column('numeric', { transformer: decimalTransformer })
high!: Decimal;
@Column('numeric', { transformer: decimalTransformer })
low!: Decimal;
@Column('numeric', { transformer: decimalTransformer })
open!: Decimal;
@Column('numeric', { transformer: decimalTransformer })
close!: Decimal;
@Column('numeric', { transformer: decimalTransformer })
priceUSD!: Decimal
@Column('numeric', { transformer: decimalTransformer })
totalValueLocked!: Decimal
@Column('numeric', { transformer: decimalTransformer })
totalValueLockedUSD!: Decimal
@Column('numeric', { default: 0, transformer: decimalTransformer })
volumeUSD!: Decimal
}

View File

@ -0,0 +1,41 @@
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import Decimal from 'decimal.js';
import { decimalTransformer } from '@vulcanize/util';
import { Token } from './Token';
@Entity()
export class TokenHourData {
@PrimaryColumn('varchar')
id!: string;
@PrimaryColumn('integer')
blockNumber!: number;
@Column('integer')
periodStartUnix!: number
@ManyToOne(() => Token)
token!: Token
@Column('numeric', { transformer: decimalTransformer })
high!: Decimal;
@Column('numeric', { transformer: decimalTransformer })
low!: Decimal;
@Column('numeric', { transformer: decimalTransformer })
open!: Decimal;
@Column('numeric', { transformer: decimalTransformer })
close!: Decimal;
@Column('numeric', { transformer: decimalTransformer })
priceUSD!: Decimal
@Column('numeric', { transformer: decimalTransformer })
totalValueLocked!: Decimal
@Column('numeric', { transformer: decimalTransformer })
totalValueLockedUSD!: Decimal
}

View File

@ -0,0 +1,26 @@
import Decimal from 'decimal.js';
import { Entity, PrimaryColumn, Column, OneToMany } from 'typeorm';
import { decimalTransformer } from '@vulcanize/util';
import { Mint } from './Mint';
@Entity()
export class Transaction {
@PrimaryColumn('varchar')
id!: string;
@PrimaryColumn('integer')
blockNumber!: number;
@Column('numeric', { default: 0, transformer: decimalTransformer })
ethPriceUSD!: Decimal
@Column('bigint')
timestamp!: BigInt;
@OneToMany(() => Mint, mint => mint.transaction)
mints!: Mint[];
// burns: [Burn]!
// swaps: [Swap]!
}

View File

@ -0,0 +1,24 @@
import { Entity, PrimaryColumn, Column } from 'typeorm';
import Decimal from 'decimal.js';
import { decimalTransformer } from '@vulcanize/util';
@Entity()
export class UniswapDayData {
@PrimaryColumn('varchar')
id!: string;
@PrimaryColumn('integer')
blockNumber!: number;
@Column('integer')
date!: number
@Column('numeric', { transformer: decimalTransformer })
tvlUSD!: Decimal
@Column('numeric', { default: 0, transformer: decimalTransformer })
volumeUSD!: Decimal
@Column('bigint')
txCount!: bigint;
}

View File

@ -6,8 +6,10 @@ import { BigNumber } from 'ethers';
import { Database } from './database'; import { Database } from './database';
import { findEthPerToken, getEthPriceInUSD, WHITELIST_TOKENS } from './utils/pricing'; import { findEthPerToken, getEthPriceInUSD, WHITELIST_TOKENS } from './utils/pricing';
import { updatePoolDayData, updatePoolHourData } from './utils/intervalUpdates'; import { updatePoolDayData, updatePoolHourData, updateTokenDayData, updateTokenHourData, updateUniswapDayData } from './utils/interval-updates';
import { Token } from './entity/Token'; import { Token } from './entity/Token';
import { convertTokenToDecimal, loadTransaction } from './utils';
import { loadTick } from './utils/tick';
const log = debug('vulcanize:events'); const log = debug('vulcanize:events');
@ -24,6 +26,16 @@ interface InitializeEvent {
tick: bigint; tick: bigint;
} }
interface MintEvent {
sender: string;
owner: string;
tickLower: bigint;
tickUpper: bigint;
amount: bigint;
amount0: bigint;
amount1: bigint;
}
interface ResultEvent { interface ResultEvent {
proof: { proof: {
data: string data: string
@ -61,19 +73,24 @@ export class EventWatcher {
} }
} }
async _handleEvents ({ blockHash, blockNumber, contract, event }: { blockHash: string, blockNumber: number, contract: string, event: ResultEvent}): Promise<void> { async _handleEvents ({ blockHash, blockNumber, contract, txHash, event }: { blockHash: string, blockNumber: number, contract: string, txHash: string, event: ResultEvent}): Promise<void> {
// TODO: Process proof (proof.data) in event. // TODO: Process proof (proof.data) in event.
const { event: { __typename: eventType, ...eventValues } } = event; const { event: { __typename: eventType, ...eventValues } } = event;
switch (eventType) { switch (eventType) {
case 'PoolCreatedEvent': case 'PoolCreatedEvent':
log('Factory PoolCreated event', contract); log('Factory PoolCreated event', contract);
this._handlePoolCreated(blockHash, blockNumber, contract, eventValues as PoolCreatedEvent); this._handlePoolCreated(blockHash, blockNumber, contract, txHash, eventValues as PoolCreatedEvent);
break; break;
case 'InitializeEvent': case 'InitializeEvent':
log('Pool Initialize event', contract); log('Pool Initialize event', contract);
this._handleInitialize(blockHash, blockNumber, contract, eventValues as InitializeEvent); this._handleInitialize(blockHash, blockNumber, contract, txHash, eventValues as InitializeEvent);
break;
case 'MintEvent':
log('Pool Mint event', contract);
this._handleMint(blockHash, blockNumber, contract, txHash, eventValues as MintEvent);
break; break;
default: default:
@ -81,7 +98,7 @@ export class EventWatcher {
} }
} }
async _handlePoolCreated (blockHash: string, blockNumber: number, contractAddress: string, poolCreatedEvent: PoolCreatedEvent): Promise<void> { async _handlePoolCreated (blockHash: string, blockNumber: number, contractAddress: string, txHash: string, poolCreatedEvent: PoolCreatedEvent): Promise<void> {
const { token0: token0Address, token1: token1Address, fee, pool: poolAddress } = poolCreatedEvent; const { token0: token0Address, token1: token1Address, fee, pool: poolAddress } = poolCreatedEvent;
// Load factory. // Load factory.
@ -153,7 +170,7 @@ export class EventWatcher {
}); });
} }
async _handleInitialize (blockHash: string, blockNumber: number, contractAddress: string, initializeEvent: InitializeEvent): Promise<void> { async _handleInitialize (blockHash: string, blockNumber: number, contractAddress: string, txHash: string, initializeEvent: InitializeEvent): Promise<void> {
const { sqrtPriceX96, tick } = initializeEvent; const { sqrtPriceX96, tick } = initializeEvent;
const pool = await this._db.getPool({ id: contractAddress, blockNumber }); const pool = await this._db.getPool({ id: contractAddress, blockNumber });
assert(pool, `Pool ${contractAddress} not found.`); assert(pool, `Pool ${contractAddress} not found.`);
@ -187,4 +204,126 @@ export class EventWatcher {
this._db.saveToken(token1, blockNumber) this._db.saveToken(token1, blockNumber)
]); ]);
} }
async _handleMint (blockHash: string, blockNumber: number, contractAddress: string, txHash: string, mintEvent: MintEvent): Promise<void> {
const bundle = await this._db.loadBundle({ id: '1', blockNumber });
const poolAddress = contractAddress;
const pool = await this._db.loadPool({ id: poolAddress, blockNumber });
// TODO: In subgraph factory is fetched by hardcoded factory address.
// Currently fetching first factory in database as only one exists.
const [factory] = await this._db.getFactories({ blockNumber }, { limit: 1 });
const token0 = pool.token0;
const token1 = pool.token1;
const amount0 = convertTokenToDecimal(mintEvent.amount0, BigInt(token0.decimals));
const amount1 = convertTokenToDecimal(mintEvent.amount1, BigInt(token1.decimals));
const amountUSD = amount0
.times(token0.derivedETH.times(bundle.ethPriceUSD))
.plus(amount1.times(token1.derivedETH.times(bundle.ethPriceUSD)));
// Reset tvl aggregates until new amounts calculated.
factory.totalValueLockedETH = factory.totalValueLockedETH.minus(pool.totalValueLockedETH);
// Update globals.
factory.txCount = BigInt(factory.txCount) + BigInt(1);
// Update token0 data.
token0.txCount = BigInt(token0.txCount) + BigInt(1);
token0.totalValueLocked = token0.totalValueLocked.plus(amount0);
token0.totalValueLockedUSD = token0.totalValueLocked.times(token0.derivedETH.times(bundle.ethPriceUSD));
// Update token1 data.
token1.txCount = BigInt(token1.txCount) + BigInt(1);
token1.totalValueLocked = token1.totalValueLocked.plus(amount1);
token1.totalValueLockedUSD = token1.totalValueLocked.times(token1.derivedETH.times(bundle.ethPriceUSD));
// Pool data.
pool.txCount = BigInt(pool.txCount) + BigInt(1);
// Pools liquidity tracks the currently active liquidity given pools current tick.
// We only want to update it on mint if the new position includes the current tick.
if (pool.tick !== null) {
if (
BigInt(mintEvent.tickLower) <= BigInt(pool.tick) &&
BigInt(mintEvent.tickUpper) > BigInt(pool.tick)
) {
pool.liquidity = BigInt(pool.liquidity) + mintEvent.amount;
}
}
pool.totalValueLockedToken0 = pool.totalValueLockedToken0.plus(amount0);
pool.totalValueLockedToken1 = pool.totalValueLockedToken1.plus(amount1);
pool.totalValueLockedETH = pool.totalValueLockedToken0.times(token0.derivedETH)
.plus(pool.totalValueLockedToken1.times(token1.derivedETH));
pool.totalValueLockedUSD = pool.totalValueLockedETH.times(bundle.ethPriceUSD);
// Reset aggregates with new amounts.
factory.totalValueLockedETH = factory.totalValueLockedETH.plus(pool.totalValueLockedETH);
factory.totalValueLockedUSD = factory.totalValueLockedETH.times(bundle.ethPriceUSD);
const transaction = await loadTransaction(this._db, { txHash, blockNumber });
await this._db.loadMint({
id: transaction.id + '#' + pool.txCount.toString(),
blockNumber,
transaction,
timestamp: transaction.timestamp,
pool,
token0: pool.token0,
token1: pool.token1,
owner: mintEvent.owner,
sender: mintEvent.sender,
// TODO: Assign origin with Transaction from address.
// origin: event.transaction.from
amount: mintEvent.amount,
amount0: amount0,
amount1: amount1,
amountUSD: amountUSD,
tickLower: mintEvent.tickLower,
tickUpper: mintEvent.tickUpper
});
// Tick entities.
const lowerTickIdx = mintEvent.tickLower;
const upperTickIdx = mintEvent.tickUpper;
const lowerTickId = poolAddress + '#' + mintEvent.tickLower.toString();
const upperTickId = poolAddress + '#' + mintEvent.tickUpper.toString();
const lowerTick = await loadTick(this._db, lowerTickId, lowerTickIdx, pool, blockNumber);
const upperTick = await loadTick(this._db, upperTickId, upperTickIdx, pool, blockNumber);
const amount = mintEvent.amount;
lowerTick.liquidityGross = lowerTick.liquidityGross + amount;
lowerTick.liquidityNet = lowerTick.liquidityNet + amount;
upperTick.liquidityGross = upperTick.liquidityGross + amount;
upperTick.liquidityNet = upperTick.liquidityNet + amount;
// TODO: Update Tick's volume, fees, and liquidity provider count.
// Computing these on the tick level requires reimplementing some of the swapping code from v3-core.
await updateUniswapDayData(this._db, { blockNumber, contractAddress });
await updatePoolDayData(this._db, { blockNumber, contractAddress });
await updatePoolHourData(this._db, { blockNumber, contractAddress });
await updateTokenDayData(this._db, token0, { blockNumber });
await updateTokenDayData(this._db, token1, { blockNumber });
await updateTokenHourData(this._db, token0, { blockNumber });
await updateTokenHourData(this._db, token1, { blockNumber });
await Promise.all([
this._db.saveToken(token0, blockNumber),
this._db.saveToken(token1, blockNumber)
]);
await this._db.savePool(pool, blockNumber);
await this._db.saveFactory(factory, blockNumber);
// Skipping update inner tick vars and tick day data as they are not queried.
}
} }

View File

@ -0,0 +1,71 @@
import Decimal from 'decimal.js';
import { BigNumber } from 'ethers';
import { Transaction } from '../entity/Transaction';
import { Database } from '../database';
export const exponentToBigDecimal = (decimals: bigint): Decimal => {
let bd = new Decimal(1);
for (let i = 0; BigNumber.from(decimals).gte(i); i++) {
bd = bd.times(10);
}
return bd;
};
export const convertTokenToDecimal = (tokenAmount: bigint, exchangeDecimals: bigint): Decimal => {
if (exchangeDecimals === BigInt(0)) {
return new Decimal(tokenAmount.toString());
}
return (new Decimal(tokenAmount.toString())).div(exponentToBigDecimal(exchangeDecimals));
};
export const loadTransaction = async (db: Database, event: { txHash: string, blockNumber: number }): Promise<Transaction> => {
const { txHash, blockNumber } = event;
// TODO: Get block timestamp from event.
// transaction.timestamp = event.block.timestamp
const timestamp = BigInt(Math.floor(Date.now() / 1000)); // Unix timestamp.
const transaction = await db.loadTransaction({
id: txHash,
blockNumber,
timestamp
});
transaction.blockNumber = blockNumber;
transaction.timestamp = timestamp;
return db.saveTransaction(transaction, blockNumber);
};
// Return 0 if denominator is 0 in division.
export const safeDiv = (amount0: Decimal, amount1: Decimal): Decimal => {
if (amount1.isZero()) {
return new Decimal(0);
} else {
return amount0.div(amount1);
}
};
export const bigDecimalExponated = (value: Decimal, power: bigint): Decimal => {
if (power === BigInt(0)) {
return new Decimal(1);
}
const negativePower = power > BigInt(0);
let result = (new Decimal(0)).plus(value);
const powerAbs = BigNumber.from(power).abs();
for (let i = BigNumber.from(1); i.lt(powerAbs); i = i.add(1)) {
result = result.times(value);
}
if (negativePower) {
result = safeDiv(new Decimal(1), result);
}
return result;
};

View File

@ -0,0 +1,230 @@
import { BigNumber } from 'ethers';
import { Database } from '../database';
import { PoolDayData } from '../entity/PoolDayData';
import { PoolHourData } from '../entity/PoolHourData';
import { Token } from '../entity/Token';
import { TokenDayData } from '../entity/TokenDayData';
import { TokenHourData } from '../entity/TokenHourData';
import { UniswapDayData } from '../entity/UniswapDayData';
/**
* Tracks global aggregate data over daily windows.
* @param db
* @param event
*/
export const updateUniswapDayData = async (db: Database, event: { contractAddress: string, blockNumber: number }): Promise<UniswapDayData> => {
const { blockNumber } = event;
// TODO: In subgraph factory is fetched by hardcoded factory address.
// Currently fetching first factory in database as only one exists.
const [factory] = await db.getFactories({ blockNumber }, { limit: 1 });
// 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); // Rounded.
const dayStartTimestamp = dayID * 86400;
const uniswapDayData = await db.loadUniswapDayData({
id: dayID.toString(),
blockNumber,
date: dayStartTimestamp,
tvlUSD: factory.totalValueLockedUSD,
txCount: factory.txCount
});
uniswapDayData.tvlUSD = factory.totalValueLockedUSD;
uniswapDayData.txCount = factory.txCount;
return db.saveUniswapDayData(uniswapDayData, blockNumber);
};
export const updatePoolDayData = async (db: Database, event: { contractAddress: string, blockNumber: number }): Promise<PoolDayData> => {
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<PoolHourData> => {
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;
};
export const updateTokenDayData = async (db: Database, token: Token, event: { blockNumber: number }): Promise<TokenDayData> => {
const { blockNumber } = event;
const bundle = await db.loadBundle({ id: '1', blockNumber });
// 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 tokenDayID = token.id
.concat('-')
.concat(dayID.toString());
const tokenPrice = token.derivedETH.times(bundle.ethPriceUSD);
const tokenDayData = await db.loadTokenDayData({
id: tokenDayID,
blockNumber,
date: dayStartTimestamp,
token,
open: tokenPrice,
high: tokenPrice,
low: tokenPrice,
close: tokenPrice,
priceUSD: token.derivedETH.times(bundle.ethPriceUSD),
totalValueLocked: token.totalValueLocked,
totalValueLockedUSD: token.totalValueLockedUSD
});
if (tokenPrice.gt(tokenDayData.high)) {
tokenDayData.high = tokenPrice;
}
if (tokenPrice.lt(tokenDayData.low)) {
tokenDayData.low = tokenPrice;
}
tokenDayData.close = tokenPrice;
tokenDayData.priceUSD = token.derivedETH.times(bundle.ethPriceUSD);
tokenDayData.totalValueLocked = token.totalValueLocked;
tokenDayData.totalValueLockedUSD = token.totalValueLockedUSD;
return db.saveTokenDayData(tokenDayData, blockNumber);
};
export const updateTokenHourData = async (db: Database, token: Token, event: { blockNumber: number }): Promise<TokenHourData> => {
const { blockNumber } = event;
const bundle = await db.loadBundle({ id: '1', blockNumber });
// 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 tokenHourID = token.id
.concat('-')
.concat(hourIndex.toString());
const tokenPrice = token.derivedETH.times(bundle.ethPriceUSD);
const tokenHourData = await db.loadTokenHourData({
id: tokenHourID,
blockNumber,
periodStartUnix: hourStartUnix,
token: token,
open: tokenPrice,
high: tokenPrice,
low: tokenPrice,
close: tokenPrice,
priceUSD: tokenPrice,
totalValueLocked: token.totalValueLocked,
totalValueLockedUSD: token.totalValueLockedUSD
});
if (tokenPrice.gt(tokenHourData.high)) {
tokenHourData.high = tokenPrice;
}
if (tokenPrice.lt(tokenHourData.low)) {
tokenHourData.low = tokenPrice;
}
tokenHourData.close = tokenPrice;
tokenHourData.priceUSD = tokenPrice;
tokenHourData.totalValueLocked = token.totalValueLocked;
tokenHourData.totalValueLockedUSD = token.totalValueLockedUSD;
return db.saveTokenHourData(tokenHourData, blockNumber);
};

View File

@ -1,103 +0,0 @@
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<PoolDayData> => {
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<PoolHourData> => {
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;
};

View File

@ -1,3 +1,4 @@
import Decimal from 'decimal.js';
import { BigNumber } from 'ethers'; import { BigNumber } from 'ethers';
import { Database } from '../database'; import { Database } from '../database';
@ -34,16 +35,16 @@ export const WHITELIST_TOKENS: string[] = [
'0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9' // AAVE '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9' // AAVE
]; ];
const MINIMUM_ETH_LOCKED = 52; const MINIMUM_ETH_LOCKED = new Decimal(52);
export const getEthPriceInUSD = async (db: Database): Promise<number> => { export const getEthPriceInUSD = async (db: Database): Promise<Decimal> => {
// Fetch eth prices for each stablecoin. // Fetch eth prices for each stablecoin.
const usdcPool = await db.getPool({ id: USDC_WETH_03_POOL }); // DAI is token0. const usdcPool = await db.getPool({ id: USDC_WETH_03_POOL }); // DAI is token0.
if (usdcPool) { if (usdcPool) {
return usdcPool.token0Price; return usdcPool.token0Price;
} else { } else {
return 0; return new Decimal(0);
} }
}; };
@ -51,16 +52,16 @@ export const getEthPriceInUSD = async (db: Database): Promise<number> => {
* Search through graph to find derived Eth per token. * Search through graph to find derived Eth per token.
* @todo update to be derived ETH (add stablecoin estimates) * @todo update to be derived ETH (add stablecoin estimates)
**/ **/
export const findEthPerToken = async (token: Token): Promise<number> => { export const findEthPerToken = async (token: Token): Promise<Decimal> => {
if (token.id === WETH_ADDRESS) { if (token.id === WETH_ADDRESS) {
return 1; return new Decimal(1);
} }
const whiteList = token.whitelistPools; const whiteList = token.whitelistPools;
// For now just take USD from pool with greatest TVL. // For now just take USD from pool with greatest TVL.
// Need to update this to actually detect best rate based on liquidity distribution. // Need to update this to actually detect best rate based on liquidity distribution.
let largestLiquidityETH = 0; let largestLiquidityETH = new Decimal(0);
let priceSoFar = 0; let priceSoFar = new Decimal(0);
for (let i = 0; i < whiteList.length; ++i) { for (let i = 0; i < whiteList.length; ++i) {
const pool = whiteList[i]; const pool = whiteList[i];
@ -70,23 +71,23 @@ export const findEthPerToken = async (token: Token): Promise<number> => {
// Whitelist token is token1. // Whitelist token is token1.
const token1 = pool.token1; const token1 = pool.token1;
// Get the derived ETH in pool. // Get the derived ETH in pool.
const ethLocked = Number(pool.totalValueLockedToken1) * Number(token1.derivedETH); const ethLocked = pool.totalValueLockedToken1.times(token1.derivedETH);
if (ethLocked > largestLiquidityETH && ethLocked > MINIMUM_ETH_LOCKED) { if (ethLocked.gt(largestLiquidityETH) && ethLocked.gt(MINIMUM_ETH_LOCKED)) {
largestLiquidityETH = ethLocked; largestLiquidityETH = ethLocked;
// token1 per our token * Eth per token1 // token1 per our token * Eth per token1
priceSoFar = Number(pool.token1Price) * Number(token1.derivedETH); priceSoFar = pool.token1Price.times(token1.derivedETH);
} }
} }
if (pool.token1.id === token.id) { if (pool.token1.id === token.id) {
const token0 = pool.token0; const token0 = pool.token0;
// Get the derived ETH in pool. // Get the derived ETH in pool.
const ethLocked = Number(pool.totalValueLockedToken0) * Number(token0.derivedETH); const ethLocked = pool.totalValueLockedToken0.times(token0.derivedETH);
if (ethLocked > largestLiquidityETH && ethLocked > MINIMUM_ETH_LOCKED) { if (ethLocked.gt(largestLiquidityETH) && ethLocked.gt(MINIMUM_ETH_LOCKED)) {
largestLiquidityETH = ethLocked; largestLiquidityETH = ethLocked;
// token0 per our token * ETH per token0 // token0 per our token * ETH per token0
priceSoFar = Number(pool.token0Price) * Number(token0.derivedETH); priceSoFar = pool.token0Price.times(token0.derivedETH);
} }
} }
} }

View File

@ -0,0 +1,21 @@
import Decimal from 'decimal.js';
import { Pool } from '../entity/Pool';
import { Database } from '../database';
import { bigDecimalExponated, safeDiv } from '.';
import { Tick } from '../entity/Tick';
export const loadTick = async (db: Database, tickId: string, tickIdx: bigint, pool: Pool, blockNumber: number): Promise<Tick> => {
// 1.0001^tick is token1/token0.
const price0 = bigDecimalExponated(new Decimal('1.0001'), tickIdx);
return db.loadTick({
id: tickId,
blockNumber,
tickIdx: tickIdx,
pool,
poolAddress: pool.id,
price0,
price1: safeDiv(new Decimal(1), price0)
});
};

View File

@ -19,6 +19,7 @@ export class Client {
blockHash blockHash
blockNumber blockNumber
contract contract
txHash
event { event {
proof { proof {
data data
@ -38,6 +39,16 @@ export class Client {
sqrtPriceX96 sqrtPriceX96
tick tick
} }
... on MintEvent {
sender
owner
tickLower
tickUpper
amount
amount0
amount1
}
} }
} }
} }

View File

@ -1 +1,2 @@
export * from './src/config'; export * from './src/config';
export * from './src/database';

View File

@ -0,0 +1,19 @@
import Decimal from 'decimal.js';
import { ValueTransformer } from 'typeorm';
export const decimalTransformer: ValueTransformer = {
to: (value?: Decimal) => {
if (value) {
return value.toString();
}
return value;
},
from: (value?: string) => {
if (value) {
return new Decimal(value);
}
return value;
}
};

View File

@ -5021,6 +5021,11 @@ decamelize@^4.0.0:
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
decimal.js@^10.3.1:
version "10.3.1"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==
decode-uri-component@^0.2.0: decode-uri-component@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"