mirror of
https://github.com/cerc-io/watcher-ts
synced 2024-11-19 12:26:19 +00:00
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:
parent
d71557e963
commit
3a6af9f9cc
@ -11,6 +11,7 @@
|
||||
"@vulcanize/util": "^0.1.0",
|
||||
"apollo-server-express": "^2.25.0",
|
||||
"apollo-type-bigint": "^0.1.3",
|
||||
"decimal.js": "^10.3.1",
|
||||
"typeorm": "^0.2.32"
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -10,6 +10,12 @@ import { Token } from './entity/Token';
|
||||
import { Bundle } from './entity/Bundle';
|
||||
import { PoolDayData } from './entity/PoolDayData';
|
||||
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 {
|
||||
_config: ConnectionOptions
|
||||
@ -72,6 +78,27 @@ export class Database {
|
||||
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> {
|
||||
return this._conn.transaction(async (tx) => {
|
||||
const repo = tx.getRepository(Factory);
|
||||
@ -83,7 +110,7 @@ export class Database {
|
||||
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();
|
||||
|
||||
if (!entity) {
|
||||
@ -167,7 +194,7 @@ export class Database {
|
||||
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();
|
||||
|
||||
if (!entity) {
|
||||
@ -190,7 +217,7 @@ export class Database {
|
||||
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();
|
||||
|
||||
if (!entity) {
|
||||
@ -213,7 +240,145 @@ export class Database {
|
||||
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();
|
||||
|
||||
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.
|
||||
async didSyncEvents ({ blockHash, token }: { blockHash: string, token: string }): Promise<boolean> {
|
||||
const numRows = await this._conn.getRepository(EventSyncProgress)
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { Entity, PrimaryColumn, Column } from 'typeorm';
|
||||
import Decimal from 'decimal.js';
|
||||
import { decimalTransformer } from '@vulcanize/util';
|
||||
|
||||
@Entity()
|
||||
export class Bundle {
|
||||
@ -8,6 +10,6 @@ export class Bundle {
|
||||
@PrimaryColumn('integer')
|
||||
blockNumber!: number;
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
ethPriceUSD!: number
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
ethPriceUSD!: Decimal
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
import Decimal from 'decimal.js';
|
||||
import { Entity, Column, PrimaryColumn } from 'typeorm';
|
||||
import { decimalTransformer } from '@vulcanize/util';
|
||||
|
||||
@Entity()
|
||||
export class Factory {
|
||||
@ -8,8 +10,17 @@ export class Factory {
|
||||
@PrimaryColumn('integer')
|
||||
blockNumber!: number;
|
||||
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
@Column('bigint', { default: BigInt(0) })
|
||||
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.
|
||||
}
|
||||
|
59
packages/uni-info-watcher/src/entity/Mint.ts
Normal file
59
packages/uni-info-watcher/src/entity/Mint.ts
Normal 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
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
import Decimal from 'decimal.js';
|
||||
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { decimalTransformer } from '@vulcanize/util';
|
||||
|
||||
import { Token } from './Token';
|
||||
|
||||
@ -16,11 +18,11 @@ export class Pool {
|
||||
@ManyToOne(() => Token)
|
||||
token1!: Token;
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
token0Price!: number
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
token0Price!: Decimal
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
token1Price!: number
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
token1Price!: Decimal
|
||||
|
||||
@Column('numeric')
|
||||
feeTier!: bigint
|
||||
@ -28,8 +30,8 @@ export class Pool {
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
sqrtPrice!: bigint
|
||||
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
tick!: bigint
|
||||
@Column('bigint', { nullable: true })
|
||||
tick!: bigint | null
|
||||
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
liquidity!: bigint
|
||||
@ -40,14 +42,20 @@ export class Pool {
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
feeGrowthGlobal1X128!: bigint
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
totalValueLockedUSD!: number
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
totalValueLockedUSD!: Decimal
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
totalValueLockedToken0!: number
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
totalValueLockedToken0!: Decimal
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
totalValueLockedToken1!: number
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
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.
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
import Decimal from 'decimal.js';
|
||||
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { decimalTransformer } from '@vulcanize/util';
|
||||
|
||||
import { Pool } from './Pool';
|
||||
|
||||
@Entity()
|
||||
@ -15,23 +18,23 @@ export class PoolDayData {
|
||||
@ManyToOne(() => Pool)
|
||||
pool!: Pool;
|
||||
|
||||
@Column('numeric')
|
||||
high!: number;
|
||||
@Column('numeric', { transformer: decimalTransformer })
|
||||
high!: Decimal;
|
||||
|
||||
@Column('numeric')
|
||||
low!: number;
|
||||
@Column('numeric', { transformer: decimalTransformer })
|
||||
low!: Decimal;
|
||||
|
||||
@Column('numeric')
|
||||
open!: number;
|
||||
@Column('numeric', { transformer: decimalTransformer })
|
||||
open!: Decimal;
|
||||
|
||||
@Column('numeric')
|
||||
close!: number;
|
||||
@Column('numeric', { transformer: decimalTransformer })
|
||||
close!: Decimal;
|
||||
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
sqrtPrice!: bigint
|
||||
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
tick!: bigint
|
||||
@Column('bigint', { nullable: true })
|
||||
tick!: bigint | null
|
||||
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
liquidity!: bigint
|
||||
@ -42,14 +45,14 @@ export class PoolDayData {
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
feeGrowthGlobal1X128!: bigint
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
token0Price!: number
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
token0Price!: Decimal
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
token1Price!: number
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
token1Price!: Decimal
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
tvlUSD!: number
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
tvlUSD!: Decimal
|
||||
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
txCount!: bigint
|
||||
|
@ -1,4 +1,7 @@
|
||||
import Decimal from 'decimal.js';
|
||||
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { decimalTransformer } from '@vulcanize/util';
|
||||
|
||||
import { Pool } from './Pool';
|
||||
|
||||
@Entity()
|
||||
@ -15,23 +18,23 @@ export class PoolHourData {
|
||||
@ManyToOne(() => Pool)
|
||||
pool!: Pool;
|
||||
|
||||
@Column('numeric')
|
||||
high!: number;
|
||||
@Column('numeric', { transformer: decimalTransformer })
|
||||
high!: Decimal;
|
||||
|
||||
@Column('numeric')
|
||||
low!: number;
|
||||
@Column('numeric', { transformer: decimalTransformer })
|
||||
low!: Decimal;
|
||||
|
||||
@Column('numeric')
|
||||
open!: number;
|
||||
@Column('numeric', { transformer: decimalTransformer })
|
||||
open!: Decimal;
|
||||
|
||||
@Column('numeric')
|
||||
close!: number;
|
||||
@Column('numeric', { transformer: decimalTransformer })
|
||||
close!: Decimal;
|
||||
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
sqrtPrice!: bigint
|
||||
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
tick!: bigint
|
||||
@Column('bigint', { nullable: true })
|
||||
tick!: bigint | null
|
||||
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
liquidity!: bigint
|
||||
@ -42,14 +45,14 @@ export class PoolHourData {
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
feeGrowthGlobal1X128!: bigint
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
token0Price!: number
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
token0Price!: Decimal
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
token1Price!: number
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
token1Price!: Decimal
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
tvlUSD!: number
|
||||
@Column('numeric', { default: 0, transformer: decimalTransformer })
|
||||
tvlUSD!: Decimal
|
||||
|
||||
@Column('numeric', { default: BigInt(0) })
|
||||
txCount!: bigint
|
||||
|
35
packages/uni-info-watcher/src/entity/Tick.ts
Normal file
35
packages/uni-info-watcher/src/entity/Tick.ts
Normal 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
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
import Decimal from 'decimal.js';
|
||||
import { Entity, PrimaryColumn, Column, ManyToMany, JoinTable } from 'typeorm';
|
||||
import { decimalTransformer } from '@vulcanize/util';
|
||||
|
||||
import { Pool } from './Pool';
|
||||
|
||||
@Entity()
|
||||
@ -15,11 +18,24 @@ export class Token {
|
||||
@Column('varchar')
|
||||
name!: string;
|
||||
|
||||
@Column('numeric')
|
||||
totalSupply!: number;
|
||||
@Column('numeric', { transformer: decimalTransformer })
|
||||
totalSupply!: Decimal;
|
||||
|
||||
@Column('numeric', { default: 0 })
|
||||
derivedETH!: number;
|
||||
// TODO: Fetch decimals from contract using erc20-watcher. Currently using hardcoded value.
|
||||
@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)
|
||||
@JoinTable()
|
||||
|
44
packages/uni-info-watcher/src/entity/TokenDayData.ts
Normal file
44
packages/uni-info-watcher/src/entity/TokenDayData.ts
Normal 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
|
||||
}
|
41
packages/uni-info-watcher/src/entity/TokenHourData.ts
Normal file
41
packages/uni-info-watcher/src/entity/TokenHourData.ts
Normal 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
|
||||
}
|
26
packages/uni-info-watcher/src/entity/Transaction.ts
Normal file
26
packages/uni-info-watcher/src/entity/Transaction.ts
Normal 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]!
|
||||
}
|
24
packages/uni-info-watcher/src/entity/UniswapDayData.ts
Normal file
24
packages/uni-info-watcher/src/entity/UniswapDayData.ts
Normal 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;
|
||||
}
|
@ -6,8 +6,10 @@ import { BigNumber } from 'ethers';
|
||||
|
||||
import { Database } from './database';
|
||||
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 { convertTokenToDecimal, loadTransaction } from './utils';
|
||||
import { loadTick } from './utils/tick';
|
||||
|
||||
const log = debug('vulcanize:events');
|
||||
|
||||
@ -24,6 +26,16 @@ interface InitializeEvent {
|
||||
tick: bigint;
|
||||
}
|
||||
|
||||
interface MintEvent {
|
||||
sender: string;
|
||||
owner: string;
|
||||
tickLower: bigint;
|
||||
tickUpper: bigint;
|
||||
amount: bigint;
|
||||
amount0: bigint;
|
||||
amount1: bigint;
|
||||
}
|
||||
|
||||
interface ResultEvent {
|
||||
proof: {
|
||||
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.
|
||||
const { event: { __typename: eventType, ...eventValues } } = event;
|
||||
|
||||
switch (eventType) {
|
||||
case 'PoolCreatedEvent':
|
||||
log('Factory PoolCreated event', contract);
|
||||
this._handlePoolCreated(blockHash, blockNumber, contract, eventValues as PoolCreatedEvent);
|
||||
this._handlePoolCreated(blockHash, blockNumber, contract, txHash, eventValues as PoolCreatedEvent);
|
||||
break;
|
||||
|
||||
case 'InitializeEvent':
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
// 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 pool = await this._db.getPool({ id: contractAddress, blockNumber });
|
||||
assert(pool, `Pool ${contractAddress} not found.`);
|
||||
@ -187,4 +204,126 @@ export class EventWatcher {
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
71
packages/uni-info-watcher/src/utils/index.ts
Normal file
71
packages/uni-info-watcher/src/utils/index.ts
Normal 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;
|
||||
};
|
230
packages/uni-info-watcher/src/utils/interval-updates.ts
Normal file
230
packages/uni-info-watcher/src/utils/interval-updates.ts
Normal 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);
|
||||
};
|
@ -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;
|
||||
};
|
@ -1,3 +1,4 @@
|
||||
import Decimal from 'decimal.js';
|
||||
import { BigNumber } from 'ethers';
|
||||
|
||||
import { Database } from '../database';
|
||||
@ -34,16 +35,16 @@ export const WHITELIST_TOKENS: string[] = [
|
||||
'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.
|
||||
const usdcPool = await db.getPool({ id: USDC_WETH_03_POOL }); // DAI is token0.
|
||||
|
||||
if (usdcPool) {
|
||||
return usdcPool.token0Price;
|
||||
} 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.
|
||||
* @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) {
|
||||
return 1;
|
||||
return new Decimal(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;
|
||||
let largestLiquidityETH = new Decimal(0);
|
||||
let priceSoFar = new Decimal(0);
|
||||
|
||||
for (let i = 0; i < whiteList.length; ++i) {
|
||||
const pool = whiteList[i];
|
||||
@ -70,23 +71,23 @@ export const findEthPerToken = async (token: Token): Promise<number> => {
|
||||
// Whitelist token is token1.
|
||||
const token1 = pool.token1;
|
||||
// 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;
|
||||
// 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) {
|
||||
const token0 = pool.token0;
|
||||
// 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;
|
||||
// token0 per our token * ETH per token0
|
||||
priceSoFar = Number(pool.token0Price) * Number(token0.derivedETH);
|
||||
priceSoFar = pool.token0Price.times(token0.derivedETH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
packages/uni-info-watcher/src/utils/tick.ts
Normal file
21
packages/uni-info-watcher/src/utils/tick.ts
Normal 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)
|
||||
});
|
||||
};
|
@ -19,6 +19,7 @@ export class Client {
|
||||
blockHash
|
||||
blockNumber
|
||||
contract
|
||||
txHash
|
||||
event {
|
||||
proof {
|
||||
data
|
||||
@ -38,6 +39,16 @@ export class Client {
|
||||
sqrtPriceX96
|
||||
tick
|
||||
}
|
||||
|
||||
... on MintEvent {
|
||||
sender
|
||||
owner
|
||||
tickLower
|
||||
tickUpper
|
||||
amount
|
||||
amount0
|
||||
amount1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
export * from './src/config';
|
||||
export * from './src/database';
|
||||
|
19
packages/util/src/database.ts
Normal file
19
packages/util/src/database.ts
Normal 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;
|
||||
}
|
||||
};
|
@ -5021,6 +5021,11 @@ decamelize@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
|
||||
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:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||
|
Loading…
Reference in New Issue
Block a user