2021-08-12 09:58:13 +00:00
|
|
|
//
|
|
|
|
// Copyright 2021 Vulcanize, Inc.
|
|
|
|
//
|
|
|
|
|
2021-07-02 10:56:32 +00:00
|
|
|
import assert from 'assert';
|
|
|
|
import debug from 'debug';
|
2021-08-06 04:55:56 +00:00
|
|
|
import { DeepPartial, QueryRunner } from 'typeorm';
|
2021-07-02 10:56:32 +00:00
|
|
|
import JSONbig from 'json-bigint';
|
2021-07-23 05:30:40 +00:00
|
|
|
import { utils } from 'ethers';
|
2021-08-18 10:20:44 +00:00
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
import { Client as UniClient } from '@vulcanize/uni-watcher';
|
|
|
|
import { Client as ERC20Client } from '@vulcanize/erc20-watcher';
|
2021-07-23 13:52:55 +00:00
|
|
|
import { EthClient } from '@vulcanize/ipld-eth-client';
|
2021-08-18 10:20:44 +00:00
|
|
|
import { IndexerInterface } from '@vulcanize/util';
|
2021-07-22 04:32:06 +00:00
|
|
|
|
|
|
|
import { findEthPerToken, getEthPriceInUSD, getTrackedAmountUSD, sqrtPriceX96ToTokenPrices, WHITELIST_TOKENS } from './utils/pricing';
|
|
|
|
import { updatePoolDayData, updatePoolHourData, updateTokenDayData, updateTokenHourData, updateUniswapDayData } from './utils/interval-updates';
|
|
|
|
import { Token } from './entity/Token';
|
|
|
|
import { convertTokenToDecimal, loadTransaction, safeDiv } from './utils';
|
2021-07-23 05:30:40 +00:00
|
|
|
import { createTick } from './utils/tick';
|
2021-07-22 04:32:06 +00:00
|
|
|
import Decimal from 'decimal.js';
|
|
|
|
import { Position } from './entity/Position';
|
2021-08-03 06:26:25 +00:00
|
|
|
import { Database, QueryOptions, OrderDirection, BlockHeight } from './database';
|
2021-07-02 10:56:32 +00:00
|
|
|
import { Event } from './entity/Event';
|
2021-07-22 06:15:21 +00:00
|
|
|
import { ResultEvent, Block, Transaction, PoolCreatedEvent, InitializeEvent, MintEvent, BurnEvent, SwapEvent, IncreaseLiquidityEvent, DecreaseLiquidityEvent, CollectEvent, TransferEvent } from './events';
|
2021-07-23 05:30:40 +00:00
|
|
|
import { Factory } from './entity/Factory';
|
|
|
|
import { Bundle } from './entity/Bundle';
|
|
|
|
import { Pool } from './entity/Pool';
|
|
|
|
import { Mint } from './entity/Mint';
|
|
|
|
import { Burn } from './entity/Burn';
|
|
|
|
import { Swap } from './entity/Swap';
|
|
|
|
import { PositionSnapshot } from './entity/PositionSnapshot';
|
2021-07-23 13:52:55 +00:00
|
|
|
import { SyncStatus } from './entity/SyncStatus';
|
|
|
|
import { BlockProgress } from './entity/BlockProgress';
|
2021-07-02 10:56:32 +00:00
|
|
|
|
|
|
|
const log = debug('vulcanize:indexer');
|
|
|
|
|
|
|
|
export interface ValueResult {
|
|
|
|
value: string | bigint;
|
|
|
|
proof: {
|
|
|
|
data: string;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-03 06:26:25 +00:00
|
|
|
export { OrderDirection, BlockHeight };
|
2021-07-30 12:19:22 +00:00
|
|
|
|
2021-08-18 10:20:44 +00:00
|
|
|
export class Indexer implements IndexerInterface {
|
2021-07-22 04:32:06 +00:00
|
|
|
_db: Database
|
|
|
|
_uniClient: UniClient
|
|
|
|
_erc20Client: ERC20Client
|
2021-07-23 13:52:55 +00:00
|
|
|
_ethClient: EthClient
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-07-23 13:52:55 +00:00
|
|
|
constructor (db: Database, uniClient: UniClient, erc20Client: ERC20Client, ethClient: EthClient) {
|
2021-07-22 04:32:06 +00:00
|
|
|
assert(db);
|
|
|
|
assert(uniClient);
|
2021-07-23 13:52:55 +00:00
|
|
|
assert(erc20Client);
|
|
|
|
assert(ethClient);
|
2021-07-06 11:25:11 +00:00
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
this._db = db;
|
|
|
|
this._uniClient = uniClient;
|
|
|
|
this._erc20Client = erc20Client;
|
2021-07-23 13:52:55 +00:00
|
|
|
this._ethClient = ethClient;
|
2021-07-02 10:56:32 +00:00
|
|
|
}
|
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
getResultEvent (event: Event): ResultEvent {
|
|
|
|
const block = event.block;
|
|
|
|
const eventFields = JSON.parse(event.eventInfo);
|
2021-08-04 13:12:59 +00:00
|
|
|
const { tx } = JSON.parse(event.extraInfo);
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
return {
|
|
|
|
block: {
|
|
|
|
hash: block.blockHash,
|
|
|
|
number: block.blockNumber,
|
|
|
|
timestamp: block.blockTimestamp,
|
|
|
|
parentHash: block.parentHash
|
|
|
|
},
|
|
|
|
|
2021-08-04 13:12:59 +00:00
|
|
|
tx,
|
2021-07-22 04:32:06 +00:00
|
|
|
contract: event.contract,
|
|
|
|
eventIndex: event.index,
|
2021-08-04 13:12:59 +00:00
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
event: {
|
|
|
|
__typename: event.eventName,
|
|
|
|
...eventFields
|
|
|
|
},
|
|
|
|
|
|
|
|
proof: JSON.parse(event.proof)
|
|
|
|
};
|
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
// Note: Some event names might be unknown at this point, as earlier events might not yet be processed.
|
2021-08-18 10:20:44 +00:00
|
|
|
async getOrFetchBlockEvents (block: DeepPartial<BlockProgress>): Promise<Array<Event>> {
|
|
|
|
assert(block.blockHash);
|
|
|
|
const blockProgress = await this._db.getBlockProgress(block.blockHash);
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
if (!blockProgress) {
|
|
|
|
// Fetch and save events first and make a note in the event sync progress table.
|
|
|
|
await this._fetchAndSaveEvents(block);
|
|
|
|
log('getBlockEvents: db miss, fetching from upstream server');
|
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-18 10:20:44 +00:00
|
|
|
const events = await this._db.getBlockEvents(block.blockHash);
|
2021-07-22 04:32:06 +00:00
|
|
|
log(`getBlockEvents: db hit, num events: ${events.length}`);
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
return events;
|
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-07-28 12:06:43 +00:00
|
|
|
async getBlockEvents (blockHash: string): Promise<Array<Event>> {
|
|
|
|
return this._db.getBlockEvents(blockHash);
|
|
|
|
}
|
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
async processEvent (dbEvent: Event): Promise<void> {
|
|
|
|
const resultEvent = this.getResultEvent(dbEvent);
|
|
|
|
|
|
|
|
// TODO: Process proof (proof.data) in event.
|
2021-07-23 05:30:40 +00:00
|
|
|
const { contract, tx, block, event } = resultEvent;
|
2021-07-23 13:52:55 +00:00
|
|
|
const { __typename: eventName } = event;
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-07-23 13:52:55 +00:00
|
|
|
switch (eventName) {
|
2021-07-22 04:32:06 +00:00
|
|
|
case 'PoolCreatedEvent':
|
|
|
|
log('Factory PoolCreated event', contract);
|
2021-07-22 06:15:21 +00:00
|
|
|
await this._handlePoolCreated(block, contract, tx, event as PoolCreatedEvent);
|
2021-07-22 04:32:06 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'InitializeEvent':
|
|
|
|
log('Pool Initialize event', contract);
|
2021-07-22 06:15:21 +00:00
|
|
|
await this._handleInitialize(block, contract, tx, event as InitializeEvent);
|
2021-07-22 04:32:06 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'MintEvent':
|
|
|
|
log('Pool Mint event', contract);
|
2021-07-22 06:15:21 +00:00
|
|
|
await this._handleMint(block, contract, tx, event as MintEvent);
|
2021-07-22 04:32:06 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'BurnEvent':
|
|
|
|
log('Pool Burn event', contract);
|
2021-07-22 06:15:21 +00:00
|
|
|
await this._handleBurn(block, contract, tx, event as BurnEvent);
|
2021-07-22 04:32:06 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'SwapEvent':
|
|
|
|
log('Pool Swap event', contract);
|
2021-07-22 06:15:21 +00:00
|
|
|
await this._handleSwap(block, contract, tx, event as SwapEvent);
|
2021-07-22 04:32:06 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'IncreaseLiquidityEvent':
|
|
|
|
log('NFPM IncreaseLiquidity event', contract);
|
2021-07-22 06:15:21 +00:00
|
|
|
await this._handleIncreaseLiquidity(block, contract, tx, event as IncreaseLiquidityEvent);
|
2021-07-22 04:32:06 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'DecreaseLiquidityEvent':
|
|
|
|
log('NFPM DecreaseLiquidity event', contract);
|
2021-07-22 06:15:21 +00:00
|
|
|
await this._handleDecreaseLiquidity(block, contract, tx, event as DecreaseLiquidityEvent);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'CollectEvent':
|
|
|
|
log('NFPM Collect event', contract);
|
|
|
|
await this._handleCollect(block, contract, tx, event as CollectEvent);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'TransferEvent':
|
|
|
|
log('NFPM Transfer event', contract);
|
|
|
|
await this._handleTransfer(block, contract, tx, event as TransferEvent);
|
2021-07-22 04:32:06 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2021-07-23 13:52:55 +00:00
|
|
|
log('Event not handled', eventName);
|
2021-07-22 04:32:06 +00:00
|
|
|
break;
|
|
|
|
}
|
2021-07-23 13:52:55 +00:00
|
|
|
|
|
|
|
log('Event processing completed for', eventName);
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateSyncStatus (blockHash: string, blockNumber: number): Promise<SyncStatus> {
|
2021-08-11 09:53:44 +00:00
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
|
|
|
let res;
|
|
|
|
|
|
|
|
try {
|
|
|
|
res = await this._db.updateSyncStatus(dbTx, blockHash, blockNumber);
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
2021-07-23 13:52:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async getSyncStatus (): Promise<SyncStatus | undefined> {
|
2021-08-09 06:00:43 +00:00
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
|
|
|
let res;
|
|
|
|
|
|
|
|
try {
|
|
|
|
res = await this._db.getSyncStatus(dbTx);
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
2021-07-23 13:52:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async getBlock (blockHash: string): Promise<any> {
|
|
|
|
const { block } = await this._ethClient.getLogs({ blockHash });
|
|
|
|
return block;
|
2021-07-22 04:32:06 +00:00
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-13 09:36:58 +00:00
|
|
|
async getBlocks (where: { [key: string]: any } = {}, queryOptions: QueryOptions): Promise<any> {
|
|
|
|
if (where.timestamp_gt) {
|
|
|
|
where.blockTimestamp_gt = where.timestamp_gt;
|
|
|
|
delete where.timestamp_gt;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (where.timestamp_lt) {
|
|
|
|
where.blockTimestamp_lt = where.timestamp_lt;
|
|
|
|
delete where.timestamp_lt;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (queryOptions.orderBy === 'timestamp') {
|
|
|
|
queryOptions.orderBy = 'blockTimestamp';
|
|
|
|
}
|
|
|
|
|
|
|
|
const blocks = await this.getEntities(BlockProgress, {}, where, queryOptions);
|
|
|
|
|
|
|
|
return blocks.map(block => ({
|
|
|
|
timestamp: block.blockTimestamp,
|
|
|
|
number: block.blockNumber,
|
|
|
|
hash: block.blockHash
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
async getEvent (id: string): Promise<Event | undefined> {
|
|
|
|
return this._db.getEvent(id);
|
2021-07-02 10:56:32 +00:00
|
|
|
}
|
|
|
|
|
2021-07-23 13:52:55 +00:00
|
|
|
async getBlockProgress (blockHash: string): Promise<BlockProgress | undefined> {
|
|
|
|
return this._db.getBlockProgress(blockHash);
|
|
|
|
}
|
|
|
|
|
2021-07-28 12:06:43 +00:00
|
|
|
async updateBlockProgress (blockHash: string, lastProcessedEventIndex: number): Promise<void> {
|
2021-08-11 09:53:44 +00:00
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
|
|
|
let res;
|
|
|
|
|
|
|
|
try {
|
|
|
|
res = await this._db.updateBlockProgress(dbTx, blockHash, lastProcessedEventIndex);
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
2021-07-02 10:56:32 +00:00
|
|
|
}
|
|
|
|
|
2021-07-29 09:15:38 +00:00
|
|
|
async getBundle (id: string, block: BlockHeight): Promise<Bundle | undefined> {
|
2021-08-06 04:55:56 +00:00
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
|
|
|
let res;
|
|
|
|
|
|
|
|
try {
|
2021-08-16 12:25:23 +00:00
|
|
|
res = await this._db.getBundle(dbTx, { id, blockHash: block.hash, blockNumber: block.number });
|
2021-08-06 04:55:56 +00:00
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
2021-07-29 12:42:35 +00:00
|
|
|
}
|
|
|
|
|
2021-07-30 12:19:22 +00:00
|
|
|
async getPool (id: string, block: BlockHeight): Promise<Pool | undefined> {
|
2021-08-06 04:55:56 +00:00
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
|
|
|
let res;
|
|
|
|
|
|
|
|
try {
|
|
|
|
res = await this._db.getPool(dbTx, { id, blockHash: block.hash, blockNumber: block.number });
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
2021-07-29 12:42:35 +00:00
|
|
|
}
|
|
|
|
|
2021-08-02 10:16:21 +00:00
|
|
|
async getToken (id: string, block: BlockHeight): Promise<Token | undefined> {
|
2021-08-06 04:55:56 +00:00
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
|
|
|
let res;
|
|
|
|
|
|
|
|
try {
|
2021-08-16 12:25:23 +00:00
|
|
|
res = await this._db.getToken(dbTx, { id, blockHash: block.hash, blockNumber: block.number });
|
2021-08-06 04:55:56 +00:00
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
|
|
|
|
|
|
|
return res;
|
2021-08-02 10:16:21 +00:00
|
|
|
}
|
|
|
|
|
2021-08-03 06:26:25 +00:00
|
|
|
async getEntities<Entity> (entity: new () => Entity, block: BlockHeight, where: { [key: string]: any } = {}, queryOptions: QueryOptions, relations?: string[]): Promise<Entity[]> {
|
2021-08-06 04:55:56 +00:00
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
|
|
|
let res;
|
|
|
|
|
|
|
|
try {
|
|
|
|
where = Object.entries(where).reduce((acc: { [key: string]: any }, [fieldWithSuffix, value]) => {
|
|
|
|
const [field, ...suffix] = fieldWithSuffix.split('_');
|
|
|
|
|
2021-08-13 09:36:58 +00:00
|
|
|
if (!acc[field]) {
|
|
|
|
acc[field] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
const filter = {
|
2021-08-06 04:55:56 +00:00
|
|
|
value,
|
|
|
|
not: false,
|
|
|
|
operator: 'equals'
|
|
|
|
};
|
|
|
|
|
|
|
|
let operator = suffix.shift();
|
|
|
|
|
|
|
|
if (operator === 'not') {
|
2021-08-13 09:36:58 +00:00
|
|
|
filter.not = true;
|
2021-08-06 04:55:56 +00:00
|
|
|
operator = suffix.shift();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (operator) {
|
2021-08-13 09:36:58 +00:00
|
|
|
filter.operator = operator;
|
2021-08-06 04:55:56 +00:00
|
|
|
}
|
|
|
|
|
2021-08-13 09:36:58 +00:00
|
|
|
acc[field].push(filter);
|
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
res = await this._db.getEntities(dbTx, entity, block, where, queryOptions, relations);
|
|
|
|
dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
2021-08-03 06:26:25 +00:00
|
|
|
|
2021-07-30 12:19:22 +00:00
|
|
|
return res;
|
2021-07-29 09:15:38 +00:00
|
|
|
}
|
|
|
|
|
2021-08-18 10:20:44 +00:00
|
|
|
async _fetchAndSaveEvents (block: DeepPartial<BlockProgress>): Promise<void> {
|
|
|
|
assert(block.blockHash);
|
|
|
|
const events = await this._uniClient.getEvents(block.blockHash);
|
2021-07-22 04:32:06 +00:00
|
|
|
const dbEvents: Array<DeepPartial<Event>> = [];
|
|
|
|
|
|
|
|
for (let i = 0; i < events.length; i++) {
|
|
|
|
const {
|
|
|
|
tx,
|
|
|
|
contract,
|
|
|
|
eventIndex,
|
|
|
|
event,
|
|
|
|
proof
|
|
|
|
} = events[i];
|
|
|
|
|
|
|
|
const { __typename: eventName, ...eventInfo } = event;
|
2021-08-04 13:12:59 +00:00
|
|
|
const extraInfo = { tx };
|
2021-07-22 04:32:06 +00:00
|
|
|
|
|
|
|
dbEvents.push({
|
|
|
|
index: eventIndex,
|
|
|
|
txHash: tx.hash,
|
|
|
|
contract,
|
|
|
|
eventName,
|
|
|
|
eventInfo: JSONbig.stringify(eventInfo),
|
2021-08-04 13:12:59 +00:00
|
|
|
extraInfo: JSONbig.stringify(extraInfo),
|
2021-07-22 04:32:06 +00:00
|
|
|
proof: JSONbig.stringify(proof)
|
2021-07-02 10:56:32 +00:00
|
|
|
});
|
2021-07-22 04:32:06 +00:00
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-11 09:53:44 +00:00
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this._db.saveEvents(dbTx, block, dbEvents);
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
}
|
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
async _handlePoolCreated (block: Block, contractAddress: string, tx: Transaction, poolCreatedEvent: PoolCreatedEvent): Promise<void> {
|
|
|
|
const { token0: token0Address, token1: token1Address, fee, pool: poolAddress } = poolCreatedEvent;
|
|
|
|
|
2021-07-23 05:30:40 +00:00
|
|
|
// Temp fix from Subgraph mapping code.
|
|
|
|
if (utils.getAddress(poolAddress) === utils.getAddress('0x8fe8d9bb8eeba3ed688069c3d6b556c9ca258248')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
// Get Tokens.
|
|
|
|
let [token0, token1] = await Promise.all([
|
2021-08-06 04:55:56 +00:00
|
|
|
this._db.getTokenNoTx({ blockHash: block.hash, id: token0Address }),
|
|
|
|
this._db.getTokenNoTx({ blockHash: block.hash, id: token1Address })
|
2021-07-22 04:32:06 +00:00
|
|
|
]);
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
// Create Tokens if not present.
|
|
|
|
if (!token0) {
|
2021-08-06 04:55:56 +00:00
|
|
|
token0 = await this._initToken(block, token0Address);
|
2021-07-22 04:32:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!token1) {
|
2021-08-06 04:55:56 +00:00
|
|
|
token1 = await this._initToken(block, token1Address);
|
2021-07-22 04:32:06 +00:00
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// Save entities to DB.
|
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
try {
|
|
|
|
// Load factory.
|
|
|
|
let factory = await this._db.getFactory(dbTx, { blockHash: block.hash, id: contractAddress });
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
if (!factory) {
|
|
|
|
factory = new Factory();
|
|
|
|
factory.id = contractAddress;
|
|
|
|
factory = await this._db.saveFactory(dbTx, factory, block);
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// Create new bundle for tracking eth price.
|
|
|
|
const bundle = new Bundle();
|
|
|
|
bundle.id = '1';
|
|
|
|
await this._db.saveBundle(dbTx, bundle, block);
|
|
|
|
}
|
2021-07-23 05:30:40 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// Update Factory.
|
|
|
|
factory.poolCount = BigInt(factory.poolCount) + BigInt(1);
|
|
|
|
|
|
|
|
let pool = new Pool();
|
|
|
|
pool.id = poolAddress;
|
|
|
|
|
|
|
|
token0 = await this._db.saveToken(dbTx, token0, block);
|
|
|
|
token1 = await this._db.saveToken(dbTx, token1, block);
|
|
|
|
pool.token0 = token0;
|
|
|
|
pool.token1 = token1;
|
|
|
|
pool.feeTier = BigInt(fee);
|
|
|
|
|
|
|
|
// Skipping adding createdAtTimestamp field as it is not queried in frontend subgraph.
|
|
|
|
|
|
|
|
pool = await this._db.savePool(dbTx, pool, block);
|
|
|
|
|
|
|
|
// Update white listed pools.
|
|
|
|
if (WHITELIST_TOKENS.includes(token0.id)) {
|
|
|
|
token1.whitelistPools.push(pool);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (WHITELIST_TOKENS.includes(token1.id)) {
|
|
|
|
token0.whitelistPools.push(pool);
|
|
|
|
}
|
|
|
|
|
|
|
|
await this._db.saveToken(dbTx, token0, block);
|
|
|
|
await this._db.saveToken(dbTx, token1, block);
|
|
|
|
await this._db.saveFactory(dbTx, factory, block);
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
2021-07-22 04:32:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create new Token.
|
|
|
|
* @param tokenAddress
|
|
|
|
*/
|
2021-08-06 04:55:56 +00:00
|
|
|
async _initToken (block: Block, tokenAddress: string): Promise<Token> {
|
2021-07-23 05:30:40 +00:00
|
|
|
const token = new Token();
|
|
|
|
token.id = tokenAddress;
|
|
|
|
|
|
|
|
const { value: symbol } = await this._erc20Client.getSymbol(block.hash, tokenAddress);
|
|
|
|
const { value: name } = await this._erc20Client.getName(block.hash, tokenAddress);
|
|
|
|
const { value: totalSupply } = await this._erc20Client.getTotalSupply(block.hash, tokenAddress);
|
2021-08-04 13:12:59 +00:00
|
|
|
const { value: decimals } = await this._erc20Client.getDecimals(block.hash, tokenAddress);
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-07-23 05:30:40 +00:00
|
|
|
token.symbol = symbol;
|
|
|
|
token.name = name;
|
|
|
|
token.totalSupply = totalSupply;
|
2021-08-04 13:12:59 +00:00
|
|
|
token.decimals = decimals;
|
2021-07-23 05:30:40 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
return token;
|
2021-07-02 10:56:32 +00:00
|
|
|
}
|
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
async _handleInitialize (block: Block, contractAddress: string, tx: Transaction, initializeEvent: InitializeEvent): Promise<void> {
|
|
|
|
const { sqrtPriceX96, tick } = initializeEvent;
|
2021-08-06 04:55:56 +00:00
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
try {
|
|
|
|
const pool = await this._db.getPool(dbTx, { id: contractAddress, blockHash: block.hash });
|
|
|
|
assert(pool, `Pool ${contractAddress} not found.`);
|
2021-07-23 05:30:40 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// Update Pool.
|
|
|
|
pool.sqrtPrice = BigInt(sqrtPriceX96);
|
|
|
|
pool.tick = BigInt(tick);
|
2021-07-23 05:30:40 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// Update ETH price now that prices could have changed.
|
|
|
|
const bundle = await this._db.getBundle(dbTx, { id: '1', blockHash: block.hash });
|
|
|
|
assert(bundle);
|
|
|
|
bundle.ethPriceUSD = await getEthPriceInUSD(this._db, dbTx, block);
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// Update token prices.
|
|
|
|
const [token0, token1] = await Promise.all([
|
|
|
|
this._db.getToken(dbTx, { id: pool.token0.id, blockHash: block.hash }),
|
|
|
|
this._db.getToken(dbTx, { id: pool.token1.id, blockHash: block.hash })
|
|
|
|
]);
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
assert(token0 && token1, 'Pool tokens not found.');
|
|
|
|
|
|
|
|
token0.derivedETH = await findEthPerToken(token0);
|
|
|
|
token1.derivedETH = await findEthPerToken(token1);
|
|
|
|
|
|
|
|
this._db.savePool(dbTx, pool, block);
|
|
|
|
this._db.saveBundle(dbTx, bundle, block);
|
|
|
|
|
|
|
|
await updatePoolDayData(this._db, dbTx, { contractAddress, block });
|
|
|
|
await updatePoolHourData(this._db, dbTx, { contractAddress, block });
|
|
|
|
|
|
|
|
await Promise.all([
|
|
|
|
this._db.saveToken(dbTx, token0, block),
|
|
|
|
this._db.saveToken(dbTx, token1, block)
|
|
|
|
]);
|
|
|
|
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
2021-07-22 04:32:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async _handleMint (block: Block, contractAddress: string, tx: Transaction, mintEvent: MintEvent): Promise<void> {
|
2021-08-06 04:55:56 +00:00
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
|
|
|
|
|
|
|
try {
|
|
|
|
const bundle = await this._db.getBundle(dbTx, { id: '1', blockHash: block.hash });
|
|
|
|
assert(bundle);
|
|
|
|
const poolAddress = contractAddress;
|
|
|
|
const pool = await this._db.getPool(dbTx, { id: poolAddress, blockHash: block.hash });
|
|
|
|
assert(pool);
|
|
|
|
|
|
|
|
// 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.getEntities(dbTx, Factory, { hash: block.hash }, {}, { limit: 1 });
|
|
|
|
|
|
|
|
const token0 = pool.token0;
|
|
|
|
const token1 = pool.token1;
|
2021-08-06 11:51:30 +00:00
|
|
|
const amount0 = convertTokenToDecimal(BigInt(mintEvent.amount0), BigInt(token0.decimals));
|
|
|
|
const amount1 = convertTokenToDecimal(BigInt(mintEvent.amount1), BigInt(token1.decimals));
|
2021-08-06 04:55:56 +00:00
|
|
|
|
|
|
|
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) + BigInt(mintEvent.amount);
|
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
}
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
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, dbTx, { block, tx });
|
|
|
|
|
|
|
|
const mint = new Mint();
|
|
|
|
mint.id = transaction.id + '#' + pool.txCount.toString();
|
|
|
|
mint.transaction = transaction;
|
|
|
|
mint.timestamp = transaction.timestamp;
|
|
|
|
mint.pool = pool;
|
|
|
|
mint.token0 = pool.token0;
|
|
|
|
mint.token1 = pool.token1;
|
|
|
|
mint.owner = mintEvent.owner;
|
|
|
|
mint.sender = mintEvent.sender;
|
|
|
|
mint.origin = tx.from;
|
2021-08-06 11:51:30 +00:00
|
|
|
mint.amount = BigInt(mintEvent.amount);
|
2021-08-06 04:55:56 +00:00
|
|
|
mint.amount0 = amount0;
|
|
|
|
mint.amount1 = amount1;
|
|
|
|
mint.amountUSD = amountUSD;
|
2021-08-06 11:51:30 +00:00
|
|
|
mint.tickLower = BigInt(mintEvent.tickLower);
|
|
|
|
mint.tickUpper = BigInt(mintEvent.tickUpper);
|
2021-08-06 04:55:56 +00:00
|
|
|
|
|
|
|
// Tick entities.
|
|
|
|
const lowerTickIdx = mintEvent.tickLower;
|
|
|
|
const upperTickIdx = mintEvent.tickUpper;
|
|
|
|
|
|
|
|
const lowerTickId = poolAddress + '#' + mintEvent.tickLower.toString();
|
|
|
|
const upperTickId = poolAddress + '#' + mintEvent.tickUpper.toString();
|
|
|
|
|
|
|
|
let lowerTick = await this._db.getTick(dbTx, { id: lowerTickId, blockHash: block.hash });
|
|
|
|
let upperTick = await this._db.getTick(dbTx, { id: upperTickId, blockHash: block.hash });
|
|
|
|
|
|
|
|
if (!lowerTick) {
|
|
|
|
lowerTick = await createTick(this._db, dbTx, lowerTickId, BigInt(lowerTickIdx), pool, block);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!upperTick) {
|
|
|
|
upperTick = await createTick(this._db, dbTx, upperTickId, BigInt(upperTickIdx), pool, block);
|
|
|
|
}
|
2021-07-23 05:30:40 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
const amount = BigInt(mintEvent.amount);
|
|
|
|
lowerTick.liquidityGross = BigInt(lowerTick.liquidityGross) + amount;
|
|
|
|
lowerTick.liquidityNet = BigInt(lowerTick.liquidityNet) + amount;
|
|
|
|
upperTick.liquidityGross = BigInt(upperTick.liquidityGross) + amount;
|
|
|
|
upperTick.liquidityNet = BigInt(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, dbTx, { block, contractAddress });
|
|
|
|
await updateTokenDayData(this._db, dbTx, token0, { block });
|
|
|
|
await updateTokenDayData(this._db, dbTx, token1, { block });
|
|
|
|
await updateTokenHourData(this._db, dbTx, token0, { block });
|
|
|
|
await updateTokenHourData(this._db, dbTx, token1, { block });
|
|
|
|
|
|
|
|
await updatePoolDayData(this._db, dbTx, { block, contractAddress });
|
|
|
|
await updatePoolHourData(this._db, dbTx, { block, contractAddress });
|
|
|
|
|
|
|
|
await Promise.all([
|
|
|
|
this._db.saveToken(dbTx, token0, block),
|
|
|
|
this._db.saveToken(dbTx, token1, block)
|
|
|
|
]);
|
|
|
|
|
|
|
|
await this._db.savePool(dbTx, pool, block);
|
|
|
|
await this._db.saveFactory(dbTx, factory, block);
|
|
|
|
|
|
|
|
await this._db.saveMint(dbTx, mint, block);
|
|
|
|
|
|
|
|
await Promise.all([
|
|
|
|
await this._db.saveTick(dbTx, lowerTick, block),
|
|
|
|
await this._db.saveTick(dbTx, upperTick, block)
|
|
|
|
]);
|
|
|
|
|
|
|
|
// Skipping update inner tick vars and tick day data as they are not queried.
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
2021-07-23 05:30:40 +00:00
|
|
|
}
|
2021-08-06 04:55:56 +00:00
|
|
|
}
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
async _handleBurn (block: Block, contractAddress: string, tx: Transaction, burnEvent: BurnEvent): Promise<void> {
|
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
try {
|
|
|
|
const bundle = await this._db.getBundle(dbTx, { id: '1', blockHash: block.hash });
|
|
|
|
assert(bundle);
|
|
|
|
const poolAddress = contractAddress;
|
|
|
|
const pool = await this._db.getPool(dbTx, { id: poolAddress, blockHash: block.hash });
|
|
|
|
assert(pool);
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// 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.getEntities(dbTx, Factory, { hash: block.hash }, {}, { limit: 1 });
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
const token0 = pool.token0;
|
|
|
|
const token1 = pool.token1;
|
2021-08-06 11:51:30 +00:00
|
|
|
const amount0 = convertTokenToDecimal(BigInt(burnEvent.amount0), BigInt(token0.decimals));
|
|
|
|
const amount1 = convertTokenToDecimal(BigInt(burnEvent.amount1), BigInt(token1.decimals));
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
const amountUSD = amount0
|
|
|
|
.times(token0.derivedETH.times(bundle.ethPriceUSD))
|
|
|
|
.plus(amount1.times(token1.derivedETH.times(bundle.ethPriceUSD)));
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// Reset tvl aggregates until new amounts calculated.
|
|
|
|
factory.totalValueLockedETH = factory.totalValueLockedETH.minus(pool.totalValueLockedETH);
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// Update globals.
|
|
|
|
factory.txCount = BigInt(factory.txCount) + BigInt(1);
|
|
|
|
|
|
|
|
// Update token0 data.
|
|
|
|
token0.txCount = BigInt(token0.txCount) + BigInt(1);
|
|
|
|
token0.totalValueLocked = token0.totalValueLocked.minus(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.minus(amount1);
|
|
|
|
token1.totalValueLockedUSD = token1.totalValueLocked.times(token1.derivedETH.times(bundle.ethPriceUSD));
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// 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 burn if the position being burnt includes the current tick.
|
2021-08-06 11:51:30 +00:00
|
|
|
if (pool.tick !== null) {
|
|
|
|
if (
|
|
|
|
BigInt(burnEvent.tickLower) <= BigInt(pool.tick) &&
|
|
|
|
BigInt(burnEvent.tickUpper) > BigInt(pool.tick)
|
|
|
|
) {
|
|
|
|
pool.liquidity = BigInt(pool.liquidity) - BigInt(burnEvent.amount);
|
|
|
|
}
|
2021-08-06 04:55:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pool.totalValueLockedToken0 = pool.totalValueLockedToken0.minus(amount0);
|
|
|
|
pool.totalValueLockedToken1 = pool.totalValueLockedToken1.minus(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);
|
|
|
|
|
|
|
|
// Burn entity.
|
|
|
|
const transaction = await loadTransaction(this._db, dbTx, { block, tx });
|
|
|
|
|
|
|
|
const burn = new Burn();
|
|
|
|
burn.id = transaction.id + '#' + pool.txCount.toString();
|
|
|
|
burn.transaction = transaction;
|
|
|
|
burn.timestamp = transaction.timestamp;
|
|
|
|
burn.pool = pool;
|
|
|
|
burn.token0 = pool.token0;
|
|
|
|
burn.token1 = pool.token1;
|
|
|
|
burn.owner = burnEvent.owner;
|
|
|
|
burn.origin = tx.from;
|
2021-08-06 11:51:30 +00:00
|
|
|
burn.amount = BigInt(burnEvent.amount);
|
2021-08-06 04:55:56 +00:00
|
|
|
burn.amount0 = amount0;
|
|
|
|
burn.amount1 = amount1;
|
|
|
|
burn.amountUSD = amountUSD;
|
2021-08-06 11:51:30 +00:00
|
|
|
burn.tickLower = BigInt(burnEvent.tickLower);
|
|
|
|
burn.tickUpper = BigInt(burnEvent.tickUpper);
|
2021-08-06 04:55:56 +00:00
|
|
|
|
|
|
|
// Tick entities.
|
|
|
|
const lowerTickId = poolAddress + '#' + (burnEvent.tickLower).toString();
|
|
|
|
const upperTickId = poolAddress + '#' + (burnEvent.tickUpper).toString();
|
|
|
|
const lowerTick = await this._db.getTick(dbTx, { id: lowerTickId, blockHash: block.hash });
|
|
|
|
const upperTick = await this._db.getTick(dbTx, { id: upperTickId, blockHash: block.hash });
|
|
|
|
assert(lowerTick && upperTick);
|
|
|
|
const amount = BigInt(burnEvent.amount);
|
|
|
|
lowerTick.liquidityGross = BigInt(lowerTick.liquidityGross) - amount;
|
|
|
|
lowerTick.liquidityNet = BigInt(lowerTick.liquidityNet) - amount;
|
|
|
|
upperTick.liquidityGross = BigInt(upperTick.liquidityGross) - amount;
|
|
|
|
upperTick.liquidityNet = BigInt(upperTick.liquidityNet) + amount;
|
|
|
|
|
|
|
|
await updateUniswapDayData(this._db, dbTx, { block, contractAddress });
|
|
|
|
await updateTokenDayData(this._db, dbTx, token0, { block });
|
|
|
|
await updateTokenDayData(this._db, dbTx, token0, { block });
|
|
|
|
await updateTokenHourData(this._db, dbTx, token0, { block });
|
|
|
|
await updateTokenHourData(this._db, dbTx, token0, { block });
|
|
|
|
await updatePoolDayData(this._db, dbTx, { block, contractAddress });
|
|
|
|
await updatePoolHourData(this._db, dbTx, { block, contractAddress });
|
|
|
|
|
|
|
|
await Promise.all([
|
|
|
|
this._db.saveToken(dbTx, token0, block),
|
|
|
|
this._db.saveToken(dbTx, token1, block)
|
|
|
|
]);
|
|
|
|
|
|
|
|
await this._db.savePool(dbTx, pool, block);
|
|
|
|
await this._db.saveFactory(dbTx, factory, block);
|
|
|
|
|
|
|
|
// Skipping update Tick fee and Tick day data as they are not queried.
|
|
|
|
|
|
|
|
await Promise.all([
|
|
|
|
await this._db.saveTick(dbTx, lowerTick, block),
|
|
|
|
await this._db.saveTick(dbTx, upperTick, block)
|
|
|
|
]);
|
|
|
|
|
|
|
|
await this._db.saveBurn(dbTx, burn, block);
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
}
|
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
async _handleSwap (block: Block, contractAddress: string, tx: Transaction, swapEvent: SwapEvent): Promise<void> {
|
2021-08-06 04:55:56 +00:00
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
try {
|
|
|
|
const bundle = await this._db.getBundle(dbTx, { id: '1', blockHash: block.hash });
|
|
|
|
assert(bundle);
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// 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.getEntities(dbTx, Factory, { hash: block.hash }, {}, { limit: 1 });
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
const pool = await this._db.getPool(dbTx, { id: contractAddress, blockHash: block.hash });
|
|
|
|
assert(pool);
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// Hot fix for bad pricing.
|
|
|
|
if (pool.id === '0x9663f2ca0454accad3e094448ea6f77443880454') {
|
|
|
|
return;
|
|
|
|
}
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
const [token0, token1] = await Promise.all([
|
|
|
|
this._db.getToken(dbTx, { id: pool.token0.id, blockHash: block.hash }),
|
|
|
|
this._db.getToken(dbTx, { id: pool.token1.id, blockHash: block.hash })
|
|
|
|
]);
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
assert(token0 && token1, 'Pool tokens not found.');
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// Amounts - 0/1 are token deltas. Can be positive or negative.
|
2021-08-06 11:51:30 +00:00
|
|
|
const amount0 = convertTokenToDecimal(BigInt(swapEvent.amount0), BigInt(token0.decimals));
|
|
|
|
const amount1 = convertTokenToDecimal(BigInt(swapEvent.amount1), BigInt(token1.decimals));
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
// Need absolute amounts for volume.
|
|
|
|
let amount0Abs = amount0;
|
|
|
|
let amount1Abs = amount1;
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
if (amount0.lt(new Decimal(0))) {
|
|
|
|
amount0Abs = amount0.times(new Decimal('-1'));
|
|
|
|
}
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
if (amount1.lt(new Decimal(0))) {
|
|
|
|
amount1Abs = amount1.times(new Decimal('-1'));
|
|
|
|
}
|
|
|
|
|
|
|
|
const amount0ETH = amount0Abs.times(token0.derivedETH);
|
|
|
|
const amount1ETH = amount1Abs.times(token1.derivedETH);
|
|
|
|
const amount0USD = amount0ETH.times(bundle.ethPriceUSD);
|
|
|
|
const amount1USD = amount1ETH.times(bundle.ethPriceUSD);
|
|
|
|
|
|
|
|
// Get amount that should be tracked only - div 2 because cant count both input and output as volume.
|
|
|
|
const trackedAmountUSD = await getTrackedAmountUSD(this._db, dbTx, amount0Abs, token0, amount1Abs, token1);
|
|
|
|
const amountTotalUSDTracked = trackedAmountUSD.div(new Decimal('2'));
|
|
|
|
const amountTotalETHTracked = safeDiv(amountTotalUSDTracked, bundle.ethPriceUSD);
|
|
|
|
const amountTotalUSDUntracked = amount0USD.plus(amount1USD).div(new Decimal('2'));
|
|
|
|
|
|
|
|
const feesETH = amountTotalETHTracked.times(pool.feeTier.toString()).div(new Decimal('1000000'));
|
|
|
|
const feesUSD = amountTotalUSDTracked.times(pool.feeTier.toString()).div(new Decimal('1000000'));
|
|
|
|
|
|
|
|
// Global updates.
|
|
|
|
factory.txCount = BigInt(factory.txCount) + BigInt(1);
|
|
|
|
factory.totalVolumeETH = factory.totalVolumeETH.plus(amountTotalETHTracked);
|
|
|
|
factory.totalVolumeUSD = factory.totalVolumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
factory.untrackedVolumeUSD = factory.untrackedVolumeUSD.plus(amountTotalUSDUntracked);
|
|
|
|
factory.totalFeesETH = factory.totalFeesETH.plus(feesETH);
|
|
|
|
factory.totalFeesUSD = factory.totalFeesUSD.plus(feesUSD);
|
|
|
|
|
|
|
|
// Reset aggregate tvl before individual pool tvl updates.
|
|
|
|
const currentPoolTvlETH = pool.totalValueLockedETH;
|
|
|
|
factory.totalValueLockedETH = factory.totalValueLockedETH.minus(currentPoolTvlETH);
|
|
|
|
|
|
|
|
// pool volume
|
|
|
|
pool.volumeToken0 = pool.volumeToken0.plus(amount0Abs);
|
|
|
|
pool.volumeToken1 = pool.volumeToken1.plus(amount1Abs);
|
|
|
|
pool.volumeUSD = pool.volumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
pool.untrackedVolumeUSD = pool.untrackedVolumeUSD.plus(amountTotalUSDUntracked);
|
|
|
|
pool.feesUSD = pool.feesUSD.plus(feesUSD);
|
|
|
|
pool.txCount = BigInt(pool.txCount) + BigInt(1);
|
|
|
|
|
|
|
|
// Update the pool with the new active liquidity, price, and tick.
|
2021-08-06 11:51:30 +00:00
|
|
|
pool.liquidity = BigInt(swapEvent.liquidity);
|
2021-08-06 04:55:56 +00:00
|
|
|
pool.tick = BigInt(swapEvent.tick);
|
2021-08-06 11:51:30 +00:00
|
|
|
pool.sqrtPrice = BigInt(swapEvent.sqrtPriceX96);
|
2021-08-06 04:55:56 +00:00
|
|
|
pool.totalValueLockedToken0 = pool.totalValueLockedToken0.plus(amount0);
|
|
|
|
pool.totalValueLockedToken1 = pool.totalValueLockedToken1.plus(amount1);
|
|
|
|
|
|
|
|
// Update token0 data.
|
|
|
|
token0.volume = token0.volume.plus(amount0Abs);
|
|
|
|
token0.totalValueLocked = token0.totalValueLocked.plus(amount0);
|
|
|
|
token0.volumeUSD = token0.volumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
token0.untrackedVolumeUSD = token0.untrackedVolumeUSD.plus(amountTotalUSDUntracked);
|
|
|
|
token0.feesUSD = token0.feesUSD.plus(feesUSD);
|
|
|
|
token0.txCount = BigInt(token0.txCount) + BigInt(1);
|
|
|
|
|
|
|
|
// Update token1 data.
|
|
|
|
token1.volume = token1.volume.plus(amount1Abs);
|
|
|
|
token1.totalValueLocked = token1.totalValueLocked.plus(amount1);
|
|
|
|
token1.volumeUSD = token1.volumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
token1.untrackedVolumeUSD = token1.untrackedVolumeUSD.plus(amountTotalUSDUntracked);
|
|
|
|
token1.feesUSD = token1.feesUSD.plus(feesUSD);
|
|
|
|
token1.txCount = BigInt(token1.txCount) + BigInt(1);
|
|
|
|
|
|
|
|
// Updated pool rates.
|
|
|
|
const prices = sqrtPriceX96ToTokenPrices(pool.sqrtPrice, token0 as Token, token1 as Token);
|
|
|
|
pool.token0Price = prices[0];
|
|
|
|
pool.token1Price = prices[1];
|
|
|
|
|
|
|
|
// Update USD pricing.
|
|
|
|
bundle.ethPriceUSD = await getEthPriceInUSD(this._db, dbTx, block);
|
|
|
|
token0.derivedETH = await findEthPerToken(token0);
|
|
|
|
token1.derivedETH = await findEthPerToken(token1);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Things afffected by new USD rates.
|
|
|
|
*/
|
|
|
|
pool.totalValueLockedETH = pool.totalValueLockedToken0
|
|
|
|
.times(token0.derivedETH)
|
|
|
|
.plus(pool.totalValueLockedToken1.times(token1.derivedETH));
|
|
|
|
|
|
|
|
pool.totalValueLockedUSD = pool.totalValueLockedETH.times(bundle.ethPriceUSD);
|
|
|
|
|
|
|
|
factory.totalValueLockedETH = factory.totalValueLockedETH.plus(pool.totalValueLockedETH);
|
|
|
|
factory.totalValueLockedUSD = factory.totalValueLockedETH.times(bundle.ethPriceUSD);
|
|
|
|
|
|
|
|
token0.totalValueLockedUSD = token0.totalValueLocked.times(token0.derivedETH).times(bundle.ethPriceUSD);
|
|
|
|
token1.totalValueLockedUSD = token1.totalValueLocked.times(token1.derivedETH).times(bundle.ethPriceUSD);
|
|
|
|
|
|
|
|
// Create Swap event
|
|
|
|
const transaction = await loadTransaction(this._db, dbTx, { block, tx });
|
|
|
|
|
|
|
|
const swap = new Swap();
|
|
|
|
swap.id = transaction.id + '#' + pool.txCount.toString();
|
|
|
|
swap.transaction = transaction;
|
|
|
|
swap.timestamp = transaction.timestamp;
|
|
|
|
swap.pool = pool;
|
|
|
|
swap.token0 = pool.token0;
|
|
|
|
swap.token1 = pool.token1;
|
|
|
|
swap.sender = swapEvent.sender;
|
|
|
|
swap.origin = tx.from;
|
|
|
|
swap.recipient = swapEvent.recipient;
|
|
|
|
swap.amount0 = amount0;
|
|
|
|
swap.amount1 = amount1;
|
|
|
|
swap.amountUSD = amountTotalUSDTracked;
|
|
|
|
swap.tick = BigInt(swapEvent.tick);
|
2021-08-06 11:51:30 +00:00
|
|
|
swap.sqrtPriceX96 = BigInt(swapEvent.sqrtPriceX96);
|
2021-08-06 04:55:56 +00:00
|
|
|
|
|
|
|
// Skipping update pool fee growth as they are not queried.
|
|
|
|
|
|
|
|
// Interval data.
|
|
|
|
const uniswapDayData = await updateUniswapDayData(this._db, dbTx, { block, contractAddress });
|
|
|
|
const poolDayData = await updatePoolDayData(this._db, dbTx, { block, contractAddress });
|
|
|
|
const poolHourData = await updatePoolHourData(this._db, dbTx, { block, contractAddress });
|
|
|
|
const token0DayData = await updateTokenDayData(this._db, dbTx, token0, { block });
|
|
|
|
const token1DayData = await updateTokenDayData(this._db, dbTx, token0, { block });
|
|
|
|
const token0HourData = await updateTokenHourData(this._db, dbTx, token0, { block });
|
|
|
|
const token1HourData = await updateTokenHourData(this._db, dbTx, token0, { block });
|
|
|
|
|
|
|
|
// Update volume metrics.
|
|
|
|
uniswapDayData.volumeETH = uniswapDayData.volumeETH.plus(amountTotalETHTracked);
|
|
|
|
uniswapDayData.volumeUSD = uniswapDayData.volumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
uniswapDayData.feesUSD = uniswapDayData.feesUSD.plus(feesUSD);
|
|
|
|
|
|
|
|
poolDayData.volumeUSD = poolDayData.volumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
poolDayData.volumeToken0 = poolDayData.volumeToken0.plus(amount0Abs);
|
|
|
|
poolDayData.volumeToken1 = poolDayData.volumeToken1.plus(amount1Abs);
|
|
|
|
poolDayData.feesUSD = poolDayData.feesUSD.plus(feesUSD);
|
|
|
|
|
|
|
|
poolHourData.volumeUSD = poolHourData.volumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
poolHourData.volumeToken0 = poolHourData.volumeToken0.plus(amount0Abs);
|
|
|
|
poolHourData.volumeToken1 = poolHourData.volumeToken1.plus(amount1Abs);
|
|
|
|
poolHourData.feesUSD = poolHourData.feesUSD.plus(feesUSD);
|
|
|
|
|
|
|
|
token0DayData.volume = token0DayData.volume.plus(amount0Abs);
|
|
|
|
token0DayData.volumeUSD = token0DayData.volumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
token0DayData.untrackedVolumeUSD = token0DayData.untrackedVolumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
token0DayData.feesUSD = token0DayData.feesUSD.plus(feesUSD);
|
|
|
|
|
|
|
|
token0HourData.volume = token0HourData.volume.plus(amount0Abs);
|
|
|
|
token0HourData.volumeUSD = token0HourData.volumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
token0HourData.untrackedVolumeUSD = token0HourData.untrackedVolumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
token0HourData.feesUSD = token0HourData.feesUSD.plus(feesUSD);
|
|
|
|
|
|
|
|
token1DayData.volume = token1DayData.volume.plus(amount1Abs);
|
|
|
|
token1DayData.volumeUSD = token1DayData.volumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
token1DayData.untrackedVolumeUSD = token1DayData.untrackedVolumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
token1DayData.feesUSD = token1DayData.feesUSD.plus(feesUSD);
|
|
|
|
|
|
|
|
token1HourData.volume = token1HourData.volume.plus(amount1Abs);
|
|
|
|
token1HourData.volumeUSD = token1HourData.volumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
token1HourData.untrackedVolumeUSD = token1HourData.untrackedVolumeUSD.plus(amountTotalUSDTracked);
|
|
|
|
token1HourData.feesUSD = token1HourData.feesUSD.plus(feesUSD);
|
|
|
|
|
|
|
|
await this._db.saveBundle(dbTx, bundle, block);
|
|
|
|
await this._db.saveSwap(dbTx, swap, block);
|
|
|
|
await this._db.saveTokenDayData(dbTx, token0DayData, block);
|
|
|
|
await this._db.saveTokenDayData(dbTx, token1DayData, block);
|
|
|
|
await this._db.saveUniswapDayData(dbTx, uniswapDayData, block);
|
|
|
|
await this._db.savePoolDayData(dbTx, poolDayData, block);
|
|
|
|
await this._db.saveFactory(dbTx, factory, block);
|
|
|
|
await this._db.savePool(dbTx, pool, block);
|
|
|
|
await this._db.saveToken(dbTx, token0, block);
|
|
|
|
await this._db.saveToken(dbTx, token1, block);
|
|
|
|
|
|
|
|
// Skipping update of inner vars of current or crossed ticks as they are not queried.
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
}
|
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
async _handleIncreaseLiquidity (block: Block, contractAddress: string, tx: Transaction, event: IncreaseLiquidityEvent): Promise<void> {
|
2021-08-06 04:55:56 +00:00
|
|
|
let position = await this._getPosition(block, contractAddress, tx, BigInt(event.tokenId));
|
2021-07-22 04:32:06 +00:00
|
|
|
|
|
|
|
// position was not able to be fetched.
|
|
|
|
if (position === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Temp fix from Subgraph mapping code.
|
|
|
|
if (utils.getAddress(position.pool.id) === utils.getAddress('0x8fe8d9bb8eeba3ed688069c3d6b556c9ca258248')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
await this._updateFeeVars(position, block, contractAddress, BigInt(event.tokenId));
|
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
try {
|
|
|
|
if (!position.transaction) {
|
|
|
|
const transaction = await loadTransaction(this._db, dbTx, { block, tx });
|
|
|
|
position.transaction = transaction;
|
|
|
|
position = await this._db.savePosition(dbTx, position, block);
|
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
const token0 = position.token0;
|
|
|
|
const token1 = position.token1;
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
const amount0 = convertTokenToDecimal(BigInt(event.amount0), BigInt(token0.decimals));
|
|
|
|
const amount1 = convertTokenToDecimal(BigInt(event.amount1), BigInt(token1.decimals));
|
|
|
|
|
|
|
|
position.liquidity = BigInt(position.liquidity) + BigInt(event.liquidity);
|
|
|
|
position.depositedToken0 = position.depositedToken0.plus(amount0);
|
|
|
|
position.depositedToken1 = position.depositedToken1.plus(amount1);
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
await this._db.savePosition(dbTx, position, block);
|
2021-07-22 04:32:06 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
await this._savePositionSnapshot(dbTx, position, block, tx);
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
}
|
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
async _handleDecreaseLiquidity (block: Block, contractAddress: string, tx: Transaction, event: DecreaseLiquidityEvent): Promise<void> {
|
2021-07-23 05:30:40 +00:00
|
|
|
let position = await this._getPosition(block, contractAddress, tx, BigInt(event.tokenId));
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
// Position was not able to be fetched.
|
|
|
|
if (position == null) {
|
|
|
|
return;
|
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
// Temp fix from Subgraph mapping code.
|
|
|
|
if (utils.getAddress(position.pool.id) === utils.getAddress('0x8fe8d9bb8eeba3ed688069c3d6b556c9ca258248')) {
|
|
|
|
return;
|
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
position = await this._updateFeeVars(position, block, contractAddress, BigInt(event.tokenId));
|
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (!position.transaction) {
|
|
|
|
const transaction = await loadTransaction(this._db, dbTx, { block, tx });
|
|
|
|
position.transaction = transaction;
|
|
|
|
position = await this._db.savePosition(dbTx, position, block);
|
|
|
|
}
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
const token0 = position.token0;
|
|
|
|
const token1 = position.token1;
|
|
|
|
const amount0 = convertTokenToDecimal(BigInt(event.amount0), BigInt(token0.decimals));
|
|
|
|
const amount1 = convertTokenToDecimal(BigInt(event.amount1), BigInt(token1.decimals));
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
position.liquidity = BigInt(position.liquidity) - BigInt(event.liquidity);
|
|
|
|
position.depositedToken0 = position.depositedToken0.plus(amount0);
|
|
|
|
position.depositedToken1 = position.depositedToken1.plus(amount1);
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
await this._db.savePosition(dbTx, position, block);
|
2021-07-02 10:56:32 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
await this._savePositionSnapshot(dbTx, position, block, tx);
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
2021-07-22 04:32:06 +00:00
|
|
|
}
|
|
|
|
|
2021-07-22 06:15:21 +00:00
|
|
|
async _handleCollect (block: Block, contractAddress: string, tx: Transaction, event: CollectEvent): Promise<void> {
|
|
|
|
let position = await this._getPosition(block, contractAddress, tx, BigInt(event.tokenId));
|
|
|
|
|
|
|
|
// Position was not able to be fetched.
|
|
|
|
if (position == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Temp fix from Subgraph mapping code.
|
|
|
|
if (utils.getAddress(position.pool.id) === utils.getAddress('0x8fe8d9bb8eeba3ed688069c3d6b556c9ca258248')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
position = await this._updateFeeVars(position, block, contractAddress, BigInt(event.tokenId));
|
2021-08-06 04:55:56 +00:00
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
2021-07-22 06:15:21 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
try {
|
|
|
|
if (!position.transaction) {
|
|
|
|
const transaction = await loadTransaction(this._db, dbTx, { block, tx });
|
|
|
|
position.transaction = transaction;
|
|
|
|
position = await this._db.savePosition(dbTx, position, block);
|
|
|
|
}
|
2021-07-22 06:15:21 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
const token0 = position.token0;
|
|
|
|
const token1 = position.token1;
|
|
|
|
const amount0 = convertTokenToDecimal(BigInt(event.amount0), BigInt(token0.decimals));
|
|
|
|
const amount1 = convertTokenToDecimal(BigInt(event.amount1), BigInt(token1.decimals));
|
|
|
|
position.collectedFeesToken0 = position.collectedFeesToken0.plus(amount0);
|
|
|
|
position.collectedFeesToken1 = position.collectedFeesToken1.plus(amount1);
|
|
|
|
|
|
|
|
await this._db.savePosition(dbTx, position, block);
|
|
|
|
|
|
|
|
await this._savePositionSnapshot(dbTx, position, block, tx);
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
2021-07-22 06:15:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async _handleTransfer (block: Block, contractAddress: string, tx: Transaction, event: TransferEvent): Promise<void> {
|
2021-08-06 04:55:56 +00:00
|
|
|
let position = await this._getPosition(block, contractAddress, tx, BigInt(event.tokenId));
|
2021-07-22 06:15:21 +00:00
|
|
|
// Position was not able to be fetched.
|
|
|
|
if (position === null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
const dbTx = await this._db.createTransactionRunner();
|
2021-07-22 06:15:21 +00:00
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
try {
|
|
|
|
if (!position.transaction) {
|
|
|
|
const transaction = await loadTransaction(this._db, dbTx, { block, tx });
|
|
|
|
position.transaction = transaction;
|
|
|
|
position = await this._db.savePosition(dbTx, position, block);
|
|
|
|
}
|
|
|
|
|
|
|
|
position.owner = event.to;
|
|
|
|
await this._db.savePosition(dbTx, position, block);
|
|
|
|
|
|
|
|
await this._savePositionSnapshot(dbTx, position, block, tx);
|
|
|
|
await dbTx.commitTransaction();
|
|
|
|
} catch (error) {
|
|
|
|
await dbTx.rollbackTransaction();
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
await dbTx.release();
|
|
|
|
}
|
2021-07-22 06:15:21 +00:00
|
|
|
}
|
|
|
|
|
2021-07-22 04:32:06 +00:00
|
|
|
async _getPosition (block: Block, contractAddress: string, tx: Transaction, tokenId: bigint): Promise<Position | null> {
|
2021-07-23 13:52:55 +00:00
|
|
|
const { hash: blockHash } = block;
|
|
|
|
let position = await this._db.getPosition({ id: tokenId.toString(), blockHash });
|
2021-07-22 04:32:06 +00:00
|
|
|
|
|
|
|
if (!position) {
|
|
|
|
const nfpmPosition = await this._uniClient.getPosition(blockHash, tokenId);
|
|
|
|
|
|
|
|
// The contract call reverts in situations where the position is minted and deleted in the same block.
|
|
|
|
// From my investigation this happens in calls from BancorSwap.
|
|
|
|
// (e.g. 0xf7867fa19aa65298fadb8d4f72d0daed5e836f3ba01f0b9b9631cdc6c36bed40)
|
|
|
|
|
|
|
|
if (nfpmPosition) {
|
|
|
|
const { token0: token0Address, token1: token1Address, fee } = await this._uniClient.poolIdToPoolKey(blockHash, nfpmPosition.poolId);
|
|
|
|
|
|
|
|
const { pool: poolAddress } = await this._uniClient.getPool(blockHash, token0Address, token1Address, fee);
|
|
|
|
|
2021-07-23 05:30:40 +00:00
|
|
|
position = new Position();
|
|
|
|
position.id = tokenId.toString();
|
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
const pool = await this._db.getPoolNoTx({ id: poolAddress, blockHash });
|
2021-07-23 05:30:40 +00:00
|
|
|
assert(pool);
|
|
|
|
position.pool = pool;
|
2021-07-22 04:32:06 +00:00
|
|
|
|
|
|
|
const [token0, token1] = await Promise.all([
|
2021-08-06 04:55:56 +00:00
|
|
|
this._db.getTokenNoTx({ id: token0Address, blockHash }),
|
2021-08-10 06:39:00 +00:00
|
|
|
this._db.getTokenNoTx({ id: token1Address, blockHash })
|
2021-07-22 04:32:06 +00:00
|
|
|
]);
|
2021-07-23 05:30:40 +00:00
|
|
|
assert(token0 && token1);
|
|
|
|
position.token0 = token0;
|
|
|
|
position.token1 = token1;
|
2021-07-22 04:32:06 +00:00
|
|
|
|
|
|
|
const [tickLower, tickUpper] = await Promise.all([
|
2021-08-06 04:55:56 +00:00
|
|
|
this._db.getTickNoTx({ id: poolAddress.concat('#').concat(nfpmPosition.tickLower.toString()), blockHash }),
|
|
|
|
this._db.getTickNoTx({ id: poolAddress.concat('#').concat(nfpmPosition.tickUpper.toString()), blockHash })
|
2021-07-22 04:32:06 +00:00
|
|
|
]);
|
2021-07-23 05:30:40 +00:00
|
|
|
assert(tickLower && tickUpper);
|
|
|
|
position.tickLower = tickLower;
|
|
|
|
position.tickUpper = tickUpper;
|
|
|
|
|
|
|
|
position.feeGrowthInside0LastX128 = BigInt(nfpmPosition.feeGrowthInside0LastX128.toString());
|
|
|
|
position.feeGrowthInside1LastX128 = BigInt(nfpmPosition.feeGrowthInside1LastX128.toString());
|
2021-07-22 04:32:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return position || null;
|
|
|
|
}
|
|
|
|
|
|
|
|
async _updateFeeVars (position: Position, block: Block, contractAddress: string, tokenId: bigint): Promise<Position> {
|
|
|
|
const nfpmPosition = await this._uniClient.getPosition(block.hash, tokenId);
|
|
|
|
|
|
|
|
if (nfpmPosition) {
|
|
|
|
position.feeGrowthInside0LastX128 = BigInt(nfpmPosition.feeGrowthInside0LastX128.toString());
|
|
|
|
position.feeGrowthInside1LastX128 = BigInt(nfpmPosition.feeGrowthInside1LastX128.toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
return position;
|
|
|
|
}
|
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
async _savePositionSnapshot (dbTx: QueryRunner, position: Position, block: Block, tx: Transaction): Promise<void> {
|
2021-07-23 05:30:40 +00:00
|
|
|
const positionSnapshot = new PositionSnapshot();
|
|
|
|
positionSnapshot.id = position.id.concat('#').concat(block.number.toString());
|
|
|
|
positionSnapshot.blockNumber = block.number;
|
|
|
|
positionSnapshot.owner = position.owner;
|
|
|
|
positionSnapshot.pool = position.pool;
|
|
|
|
positionSnapshot.position = position;
|
|
|
|
positionSnapshot.timestamp = BigInt(block.timestamp);
|
|
|
|
positionSnapshot.liquidity = position.liquidity;
|
|
|
|
positionSnapshot.depositedToken0 = position.depositedToken0;
|
|
|
|
positionSnapshot.depositedToken1 = position.depositedToken1;
|
|
|
|
positionSnapshot.withdrawnToken0 = position.withdrawnToken0;
|
|
|
|
positionSnapshot.withdrawnToken1 = position.withdrawnToken1;
|
|
|
|
positionSnapshot.collectedFeesToken0 = position.collectedFeesToken0;
|
|
|
|
positionSnapshot.collectedFeesToken1 = position.collectedFeesToken1;
|
2021-08-06 04:55:56 +00:00
|
|
|
positionSnapshot.transaction = await loadTransaction(this._db, dbTx, { block, tx });
|
2021-07-23 05:30:40 +00:00
|
|
|
positionSnapshot.feeGrowthInside0LastX128 = position.feeGrowthInside0LastX128;
|
|
|
|
positionSnapshot.feeGrowthInside1LastX128 = position.feeGrowthInside1LastX128;
|
|
|
|
|
2021-08-06 04:55:56 +00:00
|
|
|
await this._db.savePositionSnapshot(dbTx, positionSnapshot, block);
|
2021-07-02 10:56:32 +00:00
|
|
|
}
|
|
|
|
}
|