Remove Uniswap watchers and related artifacts (#163)

* Remove Uniswap watchers and related code

* Remove Uniswap artifacts from graph-node

* Remove Uniswap artifacts from graph-test-watcher
This commit is contained in:
prathamesh0 2022-08-17 14:04:20 +05:30 committed by GitHub
parent bc1c267813
commit 80682e2755
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
123 changed files with 6 additions and 26144 deletions

View File

@ -81,8 +81,6 @@ Create the databases for the watchers:
```
createdb erc20-watcher
createdb address-watcher
createdb uni-watcher
createdb uni-info-watcher
```
Create the databases for the job queues and enable the `pgcrypto` extension on them (https://github.com/timgit/pg-boss/blob/master/docs/usage.md#intro):
@ -90,8 +88,6 @@ Create the databases for the job queues and enable the `pgcrypto` extension on t
```
createdb erc20-watcher-job-queue
createdb address-watcher-job-queue
createdb uni-watcher-job-queue
createdb uni-info-watcher-job-queue
```
```
@ -118,30 +114,6 @@ CREATE EXTENSION
address-watcher-job-queue=# exit
```
```
postgres@tesla:~$ psql -U postgres -h localhost uni-watcher-job-queue
Password for user postgres:
psql (12.7 (Ubuntu 12.7-1.pgdg18.04+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
uni-watcher-job-queue=# CREATE EXTENSION pgcrypto;
CREATE EXTENSION
uni-watcher-job-queue=# exit
```
```
postgres@tesla:~$ psql -U postgres -h localhost uni-info-watcher-job-queue
Password for user postgres:
psql (12.7 (Ubuntu 12.7-1.pgdg18.04+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
uni-info-watcher-job-queue=# CREATE EXTENSION pgcrypto;
CREATE EXTENSION
uni-info-watcher-job-queue=# exit
```
#### Reset
Reset the databases used by the watchers:

View File

@ -17,7 +17,6 @@
"test": "lerna run test --stream --ignore @vulcanize/*-watcher",
"build": "lerna run build --stream",
"build:watch": "lerna run build --stream --parallel -- -w",
"build:contracts": "lerna run build:contracts",
"db:reset": "sudo ./scripts/reset-dbs.sh",
"prepare": "husky install"
}

View File

@ -1,148 +0,0 @@
[
{ "inputs": [], "stateMutability": "nonpayable", "type": "constructor" },
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"name": "FeeAmountEnabled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"indexed": true,
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"indexed": false,
"internalType": "address",
"name": "pool",
"type": "address"
}
],
"name": "PoolCreated",
"type": "event"
},
{
"inputs": [
{ "internalType": "address", "name": "tokenA", "type": "address" },
{ "internalType": "address", "name": "tokenB", "type": "address" },
{ "internalType": "uint24", "name": "fee", "type": "uint24" }
],
"name": "createPool",
"outputs": [
{ "internalType": "address", "name": "pool", "type": "address" }
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint24", "name": "fee", "type": "uint24" },
{ "internalType": "int24", "name": "tickSpacing", "type": "int24" }
],
"name": "enableFeeAmount",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "uint24", "name": "", "type": "uint24" }],
"name": "feeAmountTickSpacing",
"outputs": [{ "internalType": "int24", "name": "", "type": "int24" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "", "type": "address" },
{ "internalType": "address", "name": "", "type": "address" },
{ "internalType": "uint24", "name": "", "type": "uint24" }
],
"name": "getPool",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "parameters",
"outputs": [
{ "internalType": "address", "name": "factory", "type": "address" },
{ "internalType": "address", "name": "token0", "type": "address" },
{ "internalType": "address", "name": "token1", "type": "address" },
{ "internalType": "uint24", "name": "fee", "type": "uint24" },
{ "internalType": "int24", "name": "tickSpacing", "type": "int24" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "_owner", "type": "address" }
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

View File

@ -1,988 +0,0 @@
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "Burn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"name": "Collect",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"name": "CollectProtocol",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "paid0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "paid1",
"type": "uint256"
}
],
"name": "Flash",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "observationCardinalityNextOld",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "observationCardinalityNextNew",
"type": "uint16"
}
],
"name": "IncreaseObservationCardinalityNext",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"indexed": false,
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "Initialize",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "Mint",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol0Old",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol1Old",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol0New",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol1New",
"type": "uint8"
}
],
"name": "SetFeeProtocol",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "int256",
"name": "amount0",
"type": "int256"
},
{
"indexed": false,
"internalType": "int256",
"name": "amount1",
"type": "int256"
},
{
"indexed": false,
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"indexed": false,
"internalType": "uint128",
"name": "liquidity",
"type": "uint128"
},
{
"indexed": false,
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "Swap",
"type": "event"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint128",
"name": "amount",
"type": "uint128"
}
],
"name": "burn",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint128",
"name": "amount0Requested",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1Requested",
"type": "uint128"
}
],
"name": "collect",
"outputs": [
{
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint128",
"name": "amount0Requested",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1Requested",
"type": "uint128"
}
],
"name": "collectProtocol",
"outputs": [
{
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "factory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "fee",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeGrowthGlobal0X128",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeGrowthGlobal1X128",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "flash",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "observationCardinalityNext",
"type": "uint16"
}
],
"name": "increaseObservationCardinalityNext",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "liquidity",
"outputs": [
{
"internalType": "uint128",
"name": "",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxLiquidityPerTick",
"outputs": [
{
"internalType": "uint128",
"name": "",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint128",
"name": "amount",
"type": "uint128"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "mint",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "observations",
"outputs": [
{
"internalType": "uint32",
"name": "blockTimestamp",
"type": "uint32"
},
{
"internalType": "int56",
"name": "tickCumulative",
"type": "int56"
},
{
"internalType": "uint160",
"name": "secondsPerLiquidityCumulativeX128",
"type": "uint160"
},
{
"internalType": "bool",
"name": "initialized",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32[]",
"name": "secondsAgos",
"type": "uint32[]"
}
],
"name": "observe",
"outputs": [
{
"internalType": "int56[]",
"name": "tickCumulatives",
"type": "int56[]"
},
{
"internalType": "uint160[]",
"name": "secondsPerLiquidityCumulativeX128s",
"type": "uint160[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "key",
"type": "bytes32"
}
],
"name": "positions",
"outputs": [
{
"internalType": "uint128",
"name": "_liquidity",
"type": "uint128"
},
{
"internalType": "uint256",
"name": "feeGrowthInside0LastX128",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "feeGrowthInside1LastX128",
"type": "uint256"
},
{
"internalType": "uint128",
"name": "tokensOwed0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "tokensOwed1",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolFees",
"outputs": [
{
"internalType": "uint128",
"name": "token0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "token1",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "feeProtocol0",
"type": "uint8"
},
{
"internalType": "uint8",
"name": "feeProtocol1",
"type": "uint8"
}
],
"name": "setFeeProtocol",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "slot0",
"outputs": [
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"internalType": "int24",
"name": "tick",
"type": "int24"
},
{
"internalType": "uint16",
"name": "observationIndex",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "observationCardinality",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "observationCardinalityNext",
"type": "uint16"
},
{
"internalType": "uint8",
"name": "feeProtocol",
"type": "uint8"
},
{
"internalType": "bool",
"name": "unlocked",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
}
],
"name": "snapshotCumulativesInside",
"outputs": [
{
"internalType": "int56",
"name": "tickCumulativeInside",
"type": "int56"
},
{
"internalType": "uint160",
"name": "secondsPerLiquidityInsideX128",
"type": "uint160"
},
{
"internalType": "uint32",
"name": "secondsInside",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "bool",
"name": "zeroForOne",
"type": "bool"
},
{
"internalType": "int256",
"name": "amountSpecified",
"type": "int256"
},
{
"internalType": "uint160",
"name": "sqrtPriceLimitX96",
"type": "uint160"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "swap",
"outputs": [
{
"internalType": "int256",
"name": "amount0",
"type": "int256"
},
{
"internalType": "int256",
"name": "amount1",
"type": "int256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "int16",
"name": "wordPosition",
"type": "int16"
}
],
"name": "tickBitmap",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tickSpacing",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "ticks",
"outputs": [
{
"internalType": "uint128",
"name": "liquidityGross",
"type": "uint128"
},
{
"internalType": "int128",
"name": "liquidityNet",
"type": "int128"
},
{
"internalType": "uint256",
"name": "feeGrowthOutside0X128",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "feeGrowthOutside1X128",
"type": "uint256"
},
{
"internalType": "int56",
"name": "tickCumulativeOutside",
"type": "int56"
},
{
"internalType": "uint160",
"name": "secondsPerLiquidityOutsideX128",
"type": "uint160"
},
{
"internalType": "uint32",
"name": "secondsOutside",
"type": "uint32"
},
{
"internalType": "bool",
"name": "initialized",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "token0",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "token1",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]

View File

@ -1,10 +0,0 @@
import { PoolCreated } from '../../generated/Factory/Factory';
import { Pool as PoolTemplate } from '../../generated/templates';
import { log } from '@graphprotocol/graph-ts';
export function handlePoolCreated (event: PoolCreated): void {
log.debug('PoolCreated event', []);
// create the tracked contract based on the template
PoolTemplate.create(event.params.pool);
}

View File

@ -1,14 +0,0 @@
import { dataSource, ethereum, log } from '@graphprotocol/graph-ts';
import { Initialize } from '../../generated/templates/Pool/Pool';
export function handleInitialize (event: Initialize): void {
log.debug('event.address: {}', [event.address.toHexString()]);
log.debug('event.params.sqrtPriceX96: {}', [event.params.sqrtPriceX96.toString()]);
log.debug('event.params.tick: {}', [event.params.tick.toString()]);
}
export function handleBlock (block: ethereum.Block): void {
log.debug('block info: {}', [block.number.toString()]);
log.debug('dataSource address: {}', [dataSource.address().toHex()]);
}

View File

@ -1,238 +0,0 @@
{
"abi": [
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"name": "FeeAmountEnabled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"indexed": true,
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"indexed": false,
"internalType": "address",
"name": "pool",
"type": "address"
}
],
"name": "PoolCreated",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
}
],
"name": "createPool",
"outputs": [
{
"internalType": "address",
"name": "pool",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"name": "enableFeeAmount",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"name": "feeAmountTickSpacing",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"name": "getPool",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "parameters",
"outputs": [
{
"internalType": "address",
"name": "factory",
"type": "address"
},
{
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
}

View File

@ -1,990 +0,0 @@
{
"abi": [
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "Burn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"name": "Collect",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"name": "CollectProtocol",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "paid0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "paid1",
"type": "uint256"
}
],
"name": "Flash",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "observationCardinalityNextOld",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "observationCardinalityNextNew",
"type": "uint16"
}
],
"name": "IncreaseObservationCardinalityNext",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"indexed": false,
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "Initialize",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "Mint",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol0Old",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol1Old",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol0New",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol1New",
"type": "uint8"
}
],
"name": "SetFeeProtocol",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "int256",
"name": "amount0",
"type": "int256"
},
{
"indexed": false,
"internalType": "int256",
"name": "amount1",
"type": "int256"
},
{
"indexed": false,
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"indexed": false,
"internalType": "uint128",
"name": "liquidity",
"type": "uint128"
},
{
"indexed": false,
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "Swap",
"type": "event"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint128",
"name": "amount",
"type": "uint128"
}
],
"name": "burn",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint128",
"name": "amount0Requested",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1Requested",
"type": "uint128"
}
],
"name": "collect",
"outputs": [
{
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint128",
"name": "amount0Requested",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1Requested",
"type": "uint128"
}
],
"name": "collectProtocol",
"outputs": [
{
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "factory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "fee",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeGrowthGlobal0X128",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeGrowthGlobal1X128",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "flash",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "observationCardinalityNext",
"type": "uint16"
}
],
"name": "increaseObservationCardinalityNext",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "liquidity",
"outputs": [
{
"internalType": "uint128",
"name": "",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxLiquidityPerTick",
"outputs": [
{
"internalType": "uint128",
"name": "",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint128",
"name": "amount",
"type": "uint128"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "mint",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "observations",
"outputs": [
{
"internalType": "uint32",
"name": "blockTimestamp",
"type": "uint32"
},
{
"internalType": "int56",
"name": "tickCumulative",
"type": "int56"
},
{
"internalType": "uint160",
"name": "secondsPerLiquidityCumulativeX128",
"type": "uint160"
},
{
"internalType": "bool",
"name": "initialized",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32[]",
"name": "secondsAgos",
"type": "uint32[]"
}
],
"name": "observe",
"outputs": [
{
"internalType": "int56[]",
"name": "tickCumulatives",
"type": "int56[]"
},
{
"internalType": "uint160[]",
"name": "secondsPerLiquidityCumulativeX128s",
"type": "uint160[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "key",
"type": "bytes32"
}
],
"name": "positions",
"outputs": [
{
"internalType": "uint128",
"name": "_liquidity",
"type": "uint128"
},
{
"internalType": "uint256",
"name": "feeGrowthInside0LastX128",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "feeGrowthInside1LastX128",
"type": "uint256"
},
{
"internalType": "uint128",
"name": "tokensOwed0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "tokensOwed1",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolFees",
"outputs": [
{
"internalType": "uint128",
"name": "token0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "token1",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "feeProtocol0",
"type": "uint8"
},
{
"internalType": "uint8",
"name": "feeProtocol1",
"type": "uint8"
}
],
"name": "setFeeProtocol",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "slot0",
"outputs": [
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"internalType": "int24",
"name": "tick",
"type": "int24"
},
{
"internalType": "uint16",
"name": "observationIndex",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "observationCardinality",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "observationCardinalityNext",
"type": "uint16"
},
{
"internalType": "uint8",
"name": "feeProtocol",
"type": "uint8"
},
{
"internalType": "bool",
"name": "unlocked",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
}
],
"name": "snapshotCumulativesInside",
"outputs": [
{
"internalType": "int56",
"name": "tickCumulativeInside",
"type": "int56"
},
{
"internalType": "uint160",
"name": "secondsPerLiquidityInsideX128",
"type": "uint160"
},
{
"internalType": "uint32",
"name": "secondsInside",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "bool",
"name": "zeroForOne",
"type": "bool"
},
{
"internalType": "int256",
"name": "amountSpecified",
"type": "int256"
},
{
"internalType": "uint160",
"name": "sqrtPriceLimitX96",
"type": "uint160"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "swap",
"outputs": [
{
"internalType": "int256",
"name": "amount0",
"type": "int256"
},
{
"internalType": "int256",
"name": "amount1",
"type": "int256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "int16",
"name": "wordPosition",
"type": "int16"
}
],
"name": "tickBitmap",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tickSpacing",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "ticks",
"outputs": [
{
"internalType": "uint128",
"name": "liquidityGross",
"type": "uint128"
},
{
"internalType": "int128",
"name": "liquidityNet",
"type": "int128"
},
{
"internalType": "uint256",
"name": "feeGrowthOutside0X128",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "feeGrowthOutside1X128",
"type": "uint256"
},
{
"internalType": "int56",
"name": "tickCumulativeOutside",
"type": "int56"
},
{
"internalType": "uint160",
"name": "secondsPerLiquidityOutsideX128",
"type": "uint160"
},
{
"internalType": "uint32",
"name": "secondsOutside",
"type": "uint32"
},
{
"internalType": "bool",
"name": "initialized",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "token0",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "token1",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]
}

View File

@ -38,8 +38,6 @@ import { IpldStatus } from './entity/IpldStatus';
import { BlockProgress } from './entity/BlockProgress';
import { IPLDBlock } from './entity/IPLDBlock';
import Example1Artifacts from './artifacts/Example.json';
import FactoryArtifacts from './artifacts/Factory.json';
import PoolArtifacts from './artifacts/Pool.json';
import { createInitialState, handleEvent, createStateDiff, createStateCheckpoint } from './hooks';
import { Author } from './entity/Author';
import { Blog } from './entity/Blog';
@ -49,8 +47,6 @@ const log = debug('vulcanize:indexer');
const JSONbigNative = JSONbig({ useNativeBigInt: true });
const KIND_EXAMPLE1 = 'Example1';
const KIND_FACTORY = 'Factory';
const KIND_POOL = 'Pool';
export type ResultEvent = {
block: {
@ -128,28 +124,12 @@ export class Indexer implements IPLDIndexerInterface {
storageLayout: Example1StorageLayout
} = Example1Artifacts;
const {
abi: FactoryABI
} = FactoryArtifacts;
const {
abi: PoolABI
} = PoolArtifacts;
assert(Example1ABI);
assert(Example1StorageLayout);
this._abiMap.set(KIND_EXAMPLE1, Example1ABI);
this._storageLayoutMap.set(KIND_EXAMPLE1, Example1StorageLayout);
this._contractMap.set(KIND_EXAMPLE1, new ethers.utils.Interface(Example1ABI));
assert(FactoryABI);
this._abiMap.set(KIND_FACTORY, FactoryABI);
this._contractMap.set(KIND_FACTORY, new ethers.utils.Interface(FactoryABI));
assert(PoolABI);
this._abiMap.set(KIND_POOL, PoolABI);
this._contractMap.set(KIND_POOL, new ethers.utils.Interface(PoolABI));
this._entityTypesMap = new Map();
this._populateEntityTypesMap();

View File

@ -57,7 +57,7 @@ type ResultEvent {
proof: Proof
}
union Event = TestEvent | PoolCreatedEvent | InitializeEvent
union Event = TestEvent
type TestEvent {
param1: String!
@ -65,19 +65,6 @@ type TestEvent {
param3: BigInt!
}
type PoolCreatedEvent {
token0: String!
token1: String!
fee: Int!
tickSpacing: Int!
pool: String!
}
type InitializeEvent {
sqrtPriceX96: BigInt!
tick: Int!
}
type ResultIPLDBlock {
block: Block!
contractAddress: String!

View File

@ -1,5 +0,0 @@
# Don't lint node_modules.
node_modules
# Don't lint build output.
dist

View File

@ -1,35 +0,0 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"semistandard",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": [
"warn",
{
"allowArgumentsExplicitlyTypedAsAny": true
}
]
},
"overrides": [
{
"files": ["*.test.ts", "test/*.ts"],
"rules": {
"no-unused-expressions": "off"
}
}
]
}

View File

@ -1,6 +0,0 @@
.idea/
.vscode/
node_modules/
build/
tmp/
temp/

View File

@ -1,4 +0,0 @@
timeout: '60000'
bail: true
exit: true # TODO: Find out why the program doesn't exit on its own.
require: 'ts-node/register'

View File

@ -1,298 +0,0 @@
# uni-info-watcher
## Instructions
### Setup
Create a postgres12 database for the job queue:
```
sudo su - postgres
createdb uni-info-watcher-job-queue
```
Enable the `pgcrypto` extension on the job queue database (https://github.com/timgit/pg-boss/blob/master/docs/usage.md#intro).
Example:
```
postgres@tesla:~$ psql -U postgres -h localhost uni-info-watcher-job-queue
Password for user postgres:
psql (12.7 (Ubuntu 12.7-1.pgdg18.04+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
uni-watcher-job-queue=# CREATE EXTENSION pgcrypto;
CREATE EXTENSION
uni-info-watcher-job-queue=# exit
```
Create a postgres12 database for the uni-info watcher:
```
sudo su - postgres
createdb uni-info-watcher
```
Update `environments/local.toml` with database connection settings for both the databases.
### Run
* Build files:
```bash
$ yarn build
```
* Start the server:
```bash
$ yarn server
# For development.
$ yarn server:dev
# For specifying config file.
$ yarn server -f environments/local.toml
```
* Start the job runner:
```bash
$ yarn job-runner
# For development.
$ yarn job-runner:dev
# For specifying config file.
$ yarn job-runner -f environments/local.toml
```
* Run `yarn server:mock` to run server with mock data.
## Mock Queries
```graphql
{
bundle(id: "1", block: { number: 2 }) {
id
ethPriceUSD
}
bundles(first: 1, block: { number: 2 }) {
id
ethPriceUSD
}
burns(first: 2, orderBy: timestamp) {
amount0
amount1
amountUSD
id
origin
owner
pool {
id
}
timestamp
transaction {
id
}
}
factories(first: 1, block: { number: 2 }) {
id
totalFeesUSD
totalValueLockedUSD
totalVolumeUSD
txCount
}
mints(first: 2) {
amount0
amount1
amountUSD
id
origin
owner
pool {
id
}
timestamp
transaction {
id
}
sender
}
pools(first: 2, block: { number:2 }) {
feeTier
id
liquidity
sqrtPrice
tick
token0 {
name
}
token0Price
token1 {
name
}
token1Price
totalValueLockedToken0
totalValueLockedToken1
totalValueLockedUSD
txCount
volumeUSD
}
tokens {
derivedETH
feesUSD
id
name
symbol
totalValueLocked
totalValueLockedUSD
txCount
volume
volumeUSD
}
transactions(first: 2) {
burns {
id
}
id
mints {
id
}
swaps{
id
}
timestamp
}
swaps(first: 2) {
amount0
amount1
amountUSD
id
origin
pool {
id
}
timestamp
transaction {
id
}
}
poolDayDatas(skip: 1, first: 2) {
date
id
tvlUSD
volumeUSD
}
tokenDayDatas(first: 2, where: {}) {
date
id
totalValueLockedUSD
volumeUSD
}
uniswapDayDatas(skip:1, first: 2) {
date
id
tvlUSD
volumeUSD
}
ticks(skip: 1, first: 2, block: { number: 2 }) {
id
liquidityGross
liquidityNet
price0
price1
tickIdx
}
tokenHourDatas(skip: 1, first: 2) {
close
high
id
low
open
periodStartUnix
}
}
```
Queries with ID param
```graphql
{
pool(id: "0x38bb4e5eb41aeaeec59e60ba075298f4d4dfd2a2") {
feeTier
id
liquidity
sqrtPrice
tick
token0 {
name
}
token0Price
token1 {
name
}
token1Price
totalValueLockedToken0
totalValueLockedToken1
totalValueLockedUSD
txCount
volumeUSD
}
token(id: "0xb87ddd8af3242e56e52318bacf27fe9dcc75c15a", block: { number:2}) {
derivedETH
feesUSD
id
name
symbol
totalValueLocked
totalValueLockedUSD
txCount
volume
volumeUSD
}
}
```
## Scripts
* **generate:schema**
Generate schema for uniswap subgraph in graphql format. The `get-graphql-schema` tool is used to generate the schema (https://github.com/prisma-labs/get-graphql-schema). The uniswap subgraph graphql endpoint is provided in the script to generate the schema.
* **lint:schema**
Lint schema graphql files:
```bash
$ yarn lint:schema docs/analysis/schema/frontend.graphql
```
## Test
### Smoke test
To run a smoke test:
* Start the server in `packages/erc-20-watcher`.
* Start the server and the job-runner in `packages/uni-watcher`.
* Start the server and the job-runner in `packages/uni-info-watcher`.
* Run the smoke test in `packages/uni-watcher` atleast once.
* Run:
```bash
$ yarn smoke-test
```

View File

@ -1,965 +0,0 @@
# Aggregation in Entities
- Pool
* id (pool address)
- Factory PoolCreated event
```ts
let pool = new Pool(event.params.pool.toHexString()) as Pool
```
* feeTier (fee amount)
- Factory PoolCreated event
```ts
pool.feeTier = BigInt(event.params.fee)
```
* sqrtPrice (current price tracker)
- Pool Initialize event, Swap event
```ts
pool.sqrtPrice = event.params.sqrtPriceX96
```
* tick (current tick)
- Pool Initialize event, Swap event
```ts
pool.tick = BigInt(event.params.tick)
```
* token0Price (token0 per token1), token1Price(token1 per token0)
- Pool Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
let prices = sqrtPriceX96ToTokenPrices(pool.sqrtPrice, token0 as Token, token1 as Token)
pool.token0Price = prices[0]
pool.token1Price = prices[1]
```
* sqrtPriceX96ToTokenPrices (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/pricing.ts#L39)
- Uses Token entity `decimals` field
* totalValueLockedToken0 (total token 0 across all ticks), totalValueLockedToken1 (total token 1 across all ticks)
- Pool Mint event, Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
pool.totalValueLockedToken0 = pool.totalValueLockedToken0.plus(amount0)
pool.totalValueLockedToken1 = pool.totalValueLockedToken1.plus(amount1)
```
* convertTokenToDecimal (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/index.ts#L72)
- Pool Burn event
```ts
pool.totalValueLockedToken0 = pool.totalValueLockedToken0.minus(amount0)
pool.totalValueLockedToken1 = pool.totalValueLockedToken1.minus(amount1)
```
* totalValueLockedUSD (tvl USD)
- Pool Initialize event, Burn event, Swap event
```ts
let bundle = Bundle.load('1')
let pool = Pool.load(event.address.toHexString())
pool.totalValueLockedUSD = pool.totalValueLockedETH.times(bundle.ethPriceUSD)
```
* totalValueLockedETH (tvl derived ETH)
- Pool Mint event, Burn event, Swap event
```ts
let pool = Pool.load(poolAddress)
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
pool.totalValueLockedETH = pool.totalValueLockedToken0
.times(token0.derivedETH)
.plus(pool.totalValueLockedToken1.times(token1.derivedETH))
```
* txCount (all time number of transactions)
- Pool Mint event, Burn event, Swap event
```ts
let pool = Pool.load(event.address.toHexString())
// Constant ONE_BI is BigInt.fromI32(1)
pool.txCount = pool.txCount.plus(ONE_BI)
```
* volumeUSD (all time USD swapped)
- Pool Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
let amount0Abs = amount0
// Constant ZERO_BD is BigDecimal.fromString('0')
if (amount0.lt(ZERO_BD)) {
amount0Abs = amount0.times(BigDecimal.fromString('-1'))
}
let amount1Abs = amount1
if (amount1.lt(ZERO_BD)) {
amount1Abs = amount1.times(BigDecimal.fromString('-1'))
}
let amountTotalUSDTracked = getTrackedAmountUSD(amount0Abs, token0 as Token, amount1Abs, token1 as Token).div(
BigDecimal.fromString('2')
)
pool.volumeUSD = pool.volumeUSD.plus(amountTotalUSDTracked)
```
* getTrackedAmountUSD (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/pricing.ts#L110)
- Uses Bundle entity `ethPriceUSD` field
- Uses Token entity `derivedEth` field
* convertTokenToDecimal (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/index.ts#L72)
* liquidity (in range liquidity)
- Pool Mint event
```ts
let pool = Pool.load(event.address.toHexString())
if (
pool.tick !== null &&
BigInt.fromI32(event.params.tickLower).le(pool.tick as BigInt) &&
BigInt.fromI32(event.params.tickUpper).gt(pool.tick as BigInt)
) {
pool.liquidity = pool.liquidity.plus(event.params.amount)
}
```
- Token
* id (pool address)
- Factory PoolCreated event
```ts
let token0 = Token.load(event.params.token0.toHexString())
```
* decimals (token decimals)
- Factory PoolCreated event
```ts
let token0 = Token.load(event.params.token0.toHexString())
let decimals = fetchTokenDecimals(event.params.token0)
token0.decimals = decimals
```
* fetchTokenDecimals(https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/token.ts#L75)
- Uses ERC20 contract view method call `decimals`
* derivedETH (derived price in ETH)
- Pool Initialize event, Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
token0.derivedETH = findEthPerToken(token0 as Token)
```
* findEthPerToken (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/pricing.ts#L65)
- Uses Token entity `whitelistPools` field
- Uses Pool entity `liquidity`, `totalValueLockedToken1`, `token0Price` fields
* whitelistPools (pools token is in that are white listed for USD pricing)
- Factory PoolCreated event
```ts
let pool = new Pool(event.params.pool.toHexString()) as Pool
let token1 = Token.load(event.params.token1.toHexString())
if (WHITELIST_TOKENS.includes(token0.id)) {
let newPools = token1.whitelistPools
newPools.push(pool.id)
token1.whitelistPools = newPools
}
```
* Constant WHITELIST_TOKENS (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/pricing.ts#L12)
* feesUSD (fees in USD)
- Pool Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
let amount0Abs = amount0
if (amount0.lt(ZERO_BD)) {
amount0Abs = amount0.times(BigDecimal.fromString('-1'))
}
let amount1Abs = amount1
if (amount1.lt(ZERO_BD)) {
amount1Abs = amount1.times(BigDecimal.fromString('-1'))
}
let amountTotalUSDTracked = getTrackedAmountUSD(amount0Abs, token0 as Token, amount1Abs, token1 as Token).div(
BigDecimal.fromString('2')
)
let feesUSD = amountTotalUSDTracked.times(pool.feeTier.toBigDecimal()).div(BigDecimal.fromString('1000000'))
token0.feesUSD = token0.feesUSD.plus(feesUSD)
```
* totalValueLocked (liquidity across all pools in token units)
- Pool Mint event, Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
token0.totalValueLocked = token0.totalValueLocked.plus(amount0)
```
- Pool Burn event
```ts
token0.totalValueLocked = token0.totalValueLocked.minus(amount0)
```
* totalValueLockedUSD (liquidity across all pools in derived USD)
- Pool Mint event, Burn event, Swap event
```ts
let bundle = Bundle.load('1')
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
token0.totalValueLockedUSD = token0.totalValueLocked.times(token0.derivedETH.times(bundle.ethPriceUSD))
```
* txCount (transactions across all pools that include this token)
- Pool Mint event, Burn event, Swap event
```ts
token0.txCount = token0.txCount.plus(ONE_BI)
```
* volume (volume in token units)
- Pool Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
let amount0Abs = amount0
if (amount0.lt(ZERO_BD)) {
amount0Abs = amount0.times(BigDecimal.fromString('-1'))
}
token0.volume = token0.volume.plus(amount0Abs)
```
* volumeUSD (volume in derived USD)
- Pool Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
let amount0Abs = amount0
if (amount0.lt(ZERO_BD)) {
amount0Abs = amount0.times(BigDecimal.fromString('-1'))
}
let amount1Abs = amount1
if (amount1.lt(ZERO_BD)) {
amount1Abs = amount1.times(BigDecimal.fromString('-1'))
}
let amountTotalUSDTracked = getTrackedAmountUSD(amount0Abs, token0 as Token, amount1Abs, token1 as Token).div(
BigDecimal.fromString('2')
)
token0.volumeUSD = token0.volumeUSD.plus(amountTotalUSDTracked)
```
- Factory
* id (factory address)
- Factory PoolCreated event
```ts
let factory = Factory.load(FACTORY_ADDRESS)
```
* Constant FACTORY_ADDRESS is in https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/constants.ts#L6
* totalFeesUSD (total swap fees all time in USD)
- Pool Swap event
```ts
let factory = Factory.load(FACTORY_ADDRESS)
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
let amount0Abs = amount0
if (amount0.lt(ZERO_BD)) {
amount0Abs = amount0.times(BigDecimal.fromString('-1'))
}
let amount1Abs = amount1
if (amount1.lt(ZERO_BD)) {
amount1Abs = amount1.times(BigDecimal.fromString('-1'))
}
let amountTotalUSDTracked = getTrackedAmountUSD(amount0Abs, token0 as Token, amount1Abs, token1 as Token).div(
BigDecimal.fromString('2')
)
let feesUSD = amountTotalUSDTracked.times(pool.feeTier.toBigDecimal()).div(BigDecimal.fromString('1000000'))
factory.totalFeesUSD = factory.totalFeesUSD.plus(feesUSD)
```
* totalValueLockedUSD (TVL derived in USD)
- Pool Mint event, Burn event, Swap event
```ts
let bundle = Bundle.load('1')
let factory = Factory.load(FACTORY_ADDRESS)
factory.totalValueLockedUSD = factory.totalValueLockedETH.times(bundle.ethPriceUSD)
```
* totalValueLockedETH (TVL derived in ETH)
- Pool Mint event, Burn event, Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let factory = Factory.load(FACTORY_ADDRESS)
factory.totalValueLockedETH = factory.totalValueLockedETH.minus(pool.totalValueLockedETH)
// After change in pool.totalValueLockedETH
factory.totalValueLockedETH = factory.totalValueLockedETH.plus(pool.totalValueLockedETH)
```
* totalVolumeUSD (total volume all time in derived USD)
- Pool Swap event
```ts
let factory = Factory.load(FACTORY_ADDRESS)
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
let amount0Abs = amount0
if (amount0.lt(ZERO_BD)) {
amount0Abs = amount0.times(BigDecimal.fromString('-1'))
}
let amount1Abs = amount1
if (amount1.lt(ZERO_BD)) {
amount1Abs = amount1.times(BigDecimal.fromString('-1'))
}
let amountTotalUSDTracked = getTrackedAmountUSD(amount0Abs, token0 as Token, amount1Abs, token1 as Token).div(
BigDecimal.fromString('2')
)
factory.totalVolumeUSD = factory.totalVolumeUSD.plus(amountTotalUSDTracked)
```
* txCount (amount of transactions all time)
- Pool Mint event, Burn event, Swap event
```ts
let factory = Factory.load(FACTORY_ADDRESS)
factory.txCount = factory.txCount.plus(ONE_BI)
```
- Bundle
Stores for USD calculations.
* id - Stores only one instance.
```ts
let bundle = Bundle.load('1')
```
* ethPriceUSD (price of ETH in usd)
- Pool Initialize event, Swap event
```ts
bundle.ethPriceUSD = getEthPriceInUSD()
```
* getEthPriceInUSD (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/pricing.ts#L51)
- Uses Pool entity `token0Price` field
- Tick
* id (format: `<pool address>#<tick index>`)
- Pool Mint event
```ts
let poolAddress = event.address.toHexString()
let pool = Pool.load(poolAddress)
let lowerTickIdx = event.params.tickLower
let lowerTickId = poolAddress + '#' + BigInt.fromI32(event.params.tickLower).toString()
let lowerTick = Tick.load(lowerTickId)
if (lowerTick === null) {
lowerTick = createTick(lowerTickId, lowerTickIdx, pool.id, event)
}
```
* createTick (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/tick.ts#L8)
* liquidityGross (total liquidity pool has as tick lower or upper)
- Pool Mint event
```ts
lowerTick.liquidityGross = lowerTick.liquidityGross.plus(event.params.amount)
```
- Pool Burn event
```ts
lowerTick.liquidityGross = lowerTick.liquidityGross.minus(event.params.amount)
```
* liquidityNet (how much liquidity changes when tick crossed)
- Pool Mint event
```ts
lowerTick.liquidityNet = lowerTick.liquidityNet.plus(event.params.amount)
```
- Pool Burn event
```ts
lowerTick.liquidityNet = lowerTick.liquidityNet.minus(event.params.amount)
```
* price0 (calculated price of token0 of tick within this pool - constant)
- Pool Mint event
```ts
lowerTick = createTick(lowerTickId, lowerTickIdx, pool.id, event)
// Inside createTick
// tickIdx = lowerTickIdx
tick.price0 = bigDecimalExponated(BigDecimal.fromString('1.0001'), BigInt.fromI32(tickIdx))
```
* price1 (calculated price of token0 of tick within this pool - constant)
- Pool Mint event
```ts
lowerTick = createTick(lowerTickId, lowerTickIdx, pool.id, event)
// Inside createTick
// tickIdx = lowerTickIdx
let price0 = bigDecimalExponated(BigDecimal.fromString('1.0001'), BigInt.fromI32(tickIdx))
tick.price1 = safeDiv(ONE_BD, price0)
```
* tickIdx (tick index)
- Pool Mint event
```ts
lowerTick = createTick(lowerTickId, lowerTickIdx, pool.id, event)
// Inside createTick
// tickIdx = lowerTickIdx
tick.tickIdx = BigInt.fromI32(tickIdx)
```
- Mint
* id (transaction hash + "#" + index in mints Transaction array)
- Pool Mint event
```ts
let mint = new Mint(transaction.id.toString() + '#' + pool.txCount.toString())
```
* amount0 (amount of token 0 minted), amount1 (amount of token 1 minted)
- Pool Mint event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
mint.amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
mint.amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
```
* amountUSD (derived amount based on available prices of tokens)
- Pool Mint event
```ts
let bundle = Bundle.load('1')
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
mint.amountUSD = amount0
.times(token0.derivedETH.times(bundle.ethPriceUSD))
.plus(amount1.times(token1.derivedETH.times(bundle.ethPriceUSD)))
```
* origin (txn origin)
- Pool Mint event
```ts
mint.origin = event.transaction.from
```
* owner (owner of position where liquidity minted to)
- Pool Mint event
```ts
mint.owner = event.params.owner
```
* pool (pool position is within)
- Pool Mint event
```ts
let poolAddress = event.address.toHexString()
let pool = Pool.load(poolAddress)
mint.pool = pool.id
```
* sender (the address that minted the liquidity)
- Pool Mint event
```ts
mint.sender = event.params.sender
```
* timestamp (time of txn)
- Pool Mint event
```ts
let transaction = loadTransaction(event)
mint.timestamp = transaction.timestamp
```
* loadTransaction (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/index.ts#L83)
- Uses Transaction entity
* transaction (which txn the mint was included in)
- Pool Mint event
```ts
let transaction = loadTransaction(event)
mint.transaction = transaction.id
```
- Burn
* id (transaction hash + "#" + index in mints Transaction array)
- Pool Burn event
```ts
let pool = Pool.load(event.address.toHexString())
let transaction = loadTransaction(event)
let mint = new Burn(transaction.id + '#' + pool.txCount.toString())
```
* amount0 (amount of token 0 burned), amount1 (amount of token 1 burned)
- Pool Burn event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
burn.amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
burn.amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
```
* amountUSD (derived amount based on available prices of tokens)
- Pool Burn event
```ts
let bundle = Bundle.load('1')
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
burn.amountUSD = amount0
.times(token0.derivedETH.times(bundle.ethPriceUSD))
.plus(amount1.times(token1.derivedETH.times(bundle.ethPriceUSD)))
```
* origin (txn origin)
- Pool Burn event
```ts
burn.origin = event.transaction.from
```
* owner (owner of position where liquidity was burned)
- Pool Burn event
```ts
burn.owner = event.params.owner
```
* pool (pool position is within)
- Pool Burn event
```ts
let poolAddress = event.address.toHexString()
let pool = Pool.load(poolAddress)
burn.pool = pool.id
```
* timestamp (need this to pull recent txns for specific token or pool)
- Pool Burn event
```ts
let transaction = loadTransaction(event)
burn.timestamp = transaction.timestamp
```
* transaction (txn burn was included in)
- Pool Burn event
```ts
let transaction = loadTransaction(event)
burn.transaction = transaction.id
```
- Swap
* id (transaction hash + "#" + index in swaps Transaction array)
- Pool Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let transaction = loadTransaction(event)
let swap = new Swap(transaction.id + '#' + pool.txCount.toString())
```
* amount0 (allow indexing by tokens), amount1 (allow indexing by tokens)
- Pool Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
swap.amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
swap.amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
```
* amountUSD (derived info)
- Pool Swap event
```ts
let bundle = Bundle.load('1')
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
let amount0Abs = amount0
if (amount0.lt(ZERO_BD)) {
amount0Abs = amount0.times(BigDecimal.fromString('-1'))
}
let amount1Abs = amount1
if (amount1.lt(ZERO_BD)) {
amount1Abs = amount1.times(BigDecimal.fromString('-1'))
}
let amountTotalUSDTracked = getTrackedAmountUSD(amount0Abs, token0 as Token, amount1Abs, token1 as Token).div(
BigDecimal.fromString('2')
)
swap.amountUSD = amountTotalUSDTracked
```
* origin (txn origin, the EOA that initiated the txn)
- Pool Swap event
```ts
swap.origin = event.transaction.from
```
* pool (pool swap occured within)
- Pool Swap event
```ts
let poolAddress = event.address.toHexString()
let pool = Pool.load(poolAddress)
swap.pool = pool.id
```
* timestamp (timestamp of transaction)
- Pool Swap event
```ts
let transaction = loadTransaction(event)
swap.timestamp = transaction.timestamp
```
* transaction (pointer to transaction)
- Pool Swap event
```ts
let transaction = loadTransaction(event)
swap.transaction = transaction.id
```
- Transaction
* id (txn hash)
- Pool Mint event, Burn event, Swap event
NonfungiblePositionManager IncreaseLiquidity event, DecreaseLiquidity event, Collect event, Transfer event
```ts
let transaction = loadTransaction(event)
// Inside loadTransaction
let transaction = Transaction.load(event.transaction.hash.toHexString())
if (transaction === null) {
transaction = new Transaction(event.transaction.hash.toHexString())
}
```
* loadTransaction (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/index.ts#L83)
* timestamp (timestamp txn was confirmed)
- Pool Mint event, Burn event, Swap event
NonfungiblePositionManager IncreaseLiquidity event, DecreaseLiquidity event, Collect event, Transfer event
```ts
// Inside loadTransaction
let transaction = Transaction.load(event.transaction.hash.toHexString())
transaction.timestamp = event.block.timestamp
```
* burns, mints, swaps (derived values)
These fields are derived from reverse lookups.
https://thegraph.com/docs/define-a-subgraph#reverse-lookups
- UniswapDayData (Data accumulated and condensed into day stats for all of Uniswap)
* id (timestamp rounded to current day by dividing by 86400)
- Pool Mint event, Burn event, Swap event
```ts
updateUniswapDayData(event)
// Inside updateUniswapDayData
let timestamp = event.block.timestamp.toI32()
let dayID = timestamp / 86400
let uniswapDayData = UniswapDayData.load(dayID.toString())
if (uniswapDayData === null) {
uniswapDayData = new UniswapDayData(dayID.toString())
}
```
* updateUniswapDayData (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/intervalUpdates.ts#L23)
- Uses Factory entity `totalValueLockedUSD`, `txCount` fields
* date (timestamp rounded to current day by dividing by 86400)
- Pool Mint event, Burn event, Swap event
```ts
// Inside updateUniswapDayData
let timestamp = event.block.timestamp.toI32()
let dayID = timestamp / 86400
let dayStartTimestamp = dayID * 86400
let uniswapDayData = UniswapDayData.load(dayID.toString())
if (uniswapDayData === null) {
uniswapDayData = new UniswapDayData(dayID.toString())
uniswapDayData.date = dayStartTimestamp
}
```
* tvlUSD (tvl in terms of USD)
- Pool Mint event, Burn event, Swap event
```ts
let uniswap = Factory.load(FACTORY_ADDRESS)
let uniswapDayData = UniswapDayData.load(dayID.toString())
uniswapDayData.tvlUSD = uniswap.totalValueLockedUSD
```
* volumeUSD (total daily volume in Uniswap derived in terms of USD)
- Pool Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
let amount0Abs = amount0
if (amount0.lt(ZERO_BD)) {
amount0Abs = amount0.times(BigDecimal.fromString('-1'))
}
let amount1Abs = amount1
if (amount1.lt(ZERO_BD)) {
amount1Abs = amount1.times(BigDecimal.fromString('-1'))
}
let amountTotalUSDTracked = getTrackedAmountUSD(amount0Abs, token0 as Token, amount1Abs, token1 as Token).div(
BigDecimal.fromString('2')
)
let uniswapDayData = updateUniswapDayData(event)
uniswapDayData.volumeUSD = uniswapDayData.volumeUSD.plus(amountTotalUSDTracked)
```
- PoolDayData (Data accumulated and condensed into day stats for each pool)
* id (timestamp rounded to current day by dividing by 86400)
- Pool Initialize event, Mint event, Burn event, Swap event
```ts
let poolDayData = updatePoolDayData(event)
// Inside updatePoolDayData
let timestamp = event.block.timestamp.toI32()
let dayID = timestamp / 86400
let dayPoolID = event.address
.toHexString()
.concat('-')
.concat(dayID.toString())
let poolDayData = PoolDayData.load(dayPoolID)
if (poolDayData === null) {
poolDayData = new PoolDayData(dayPoolID)
}
```
* updatePoolDayData (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/intervalUpdates.ts#L43)
- Uses Pool entity `token0Price`, `token1Price`, `liquidity`, `sqrtPrice`, `feeGrowthGlobal0X128`, `feeGrowthGlobal1X128`, `tick`, `totalValueLockedUSD` fields
* date (timestamp rounded to current day by dividing by 86400)
- Pool Initialize event, Mint event, Burn event, Swap event
```ts
// Inside updatePoolDayData
let timestamp = event.block.timestamp.toI32()
let dayID = timestamp / 86400
let dayStartTimestamp = dayID * 86400
poolDayData.date = dayStartTimestamp
```
* tvlUSD (tvl derived in USD at end of period)
- Pool Initialize event, Mint event, Burn event, Swap event
```ts
// Inside updatePoolDayData
let pool = Pool.load(event.address.toHexString())
poolDayData.tvlUSD = pool.totalValueLockedUSD
```
* volumeUSD (volume in USD)
- Pool Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
let amount0Abs = amount0
if (amount0.lt(ZERO_BD)) {
amount0Abs = amount0.times(BigDecimal.fromString('-1'))
}
let amount1Abs = amount1
if (amount1.lt(ZERO_BD)) {
amount1Abs = amount1.times(BigDecimal.fromString('-1'))
}
let amountTotalUSDTracked = getTrackedAmountUSD(amount0Abs, token0 as Token, amount1Abs, token1 as Token).div(
BigDecimal.fromString('2')
)
poolDayData.volumeUSD = poolDayData.volumeUSD.plus(amountTotalUSDTracked)
```
- TokenDayData
* id (token address concatendated with date)
- Pool Mint event, Burn event, Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token0DayData = updateTokenDayData(token0 as Token, event)
// Inside updateTokenDayData
// token = token0
let timestamp = event.block.timestamp.toI32()
let dayID = timestamp / 86400
let tokenDayID = token.id
.toString()
.concat('-')
.concat(dayID.toString())
let tokenDayData = TokenDayData.load(tokenDayID)
if (tokenDayData === null) {
tokenDayData = new TokenDayData(tokenDayID)
}
```
* updateTokenDayData (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/intervalUpdates.ts#L143)
- Uses Bundle entity `ethPriceUSD` field
* date (timestamp rounded to current day by dividing by 86400)
- Pool Mint event, Burn event, Swap event
```ts
// Inside updateTokenDayData
let dayStartTimestamp = dayID * 86400
if (tokenDayData === null) {
tokenDayData = new TokenDayData(tokenDayID)
tokenDayData.date = dayStartTimestamp
}
```
* totalValueLockedUSD (liquidity across all pools in derived USD)
- Pool Mint event, Burn event, Swap event
```ts
// Inside updateTokenDayData
let tokenDayData = TokenDayData.load(tokenDayID)
tokenDayData.totalValueLockedUSD = token.totalValueLockedUSD
```
* volumeUSD (volume in derived USD)
- Pool Swap event
```ts
let pool = Pool.load(event.address.toHexString())
let token0 = Token.load(pool.token0)
let token1 = Token.load(pool.token1)
let amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals)
let amount1 = convertTokenToDecimal(event.params.amount1, token1.decimals)
let amount0Abs = amount0
if (amount0.lt(ZERO_BD)) {
amount0Abs = amount0.times(BigDecimal.fromString('-1'))
}
let amount1Abs = amount1
if (amount1.lt(ZERO_BD)) {
amount1Abs = amount1.times(BigDecimal.fromString('-1'))
}
let amountTotalUSDTracked = getTrackedAmountUSD(amount0Abs, token0 as Token, amount1Abs, token1 as Token).div(
BigDecimal.fromString('2')
)
token0DayData.volumeUSD = token0DayData.volumeUSD.plus(amountTotalUSDTracked)
```
- TokenHourData
* id (token address concatendated with date)
- Pool Mint event, Burn event, Swap event
```ts
let token0HourData = updateTokenHourData(token0 as Token, event)
// Inside updateTokenHourData
// token = token0
let tokenHourID = token.id
.toString()
.concat('-')
.concat(hourIndex.toString())
let tokenHourData = TokenHourData.load(tokenHourID)
if (tokenHourData === null) {
tokenHourData = new TokenHourData(tokenHourID)
}
```
* updateTokenDayData (https://github.com/Uniswap/uniswap-v3-subgraph/blob/main/src/utils/intervalUpdates.ts#L186)
- Uses Bundle entity `ethPriceUSD` field
* close (close price USD)
- Pool Mint event, Burn event, Swap event
```ts
// Inside updateTokenHourData
let bundle = Bundle.load('1')
let tokenPrice = token.derivedETH.times(bundle.ethPriceUSD)
tokenHourData.close = tokenPrice
```
* high (high price USD)
- Pool Mint event, Burn event, Swap event
```ts
// Inside updateTokenHourData
let tokenHourData = TokenHourData.load(tokenHourID)
if (tokenHourData === null) {
tokenHourData = new TokenHourData(tokenHourID)
tokenHourData.high = tokenPrice
}
if (tokenPrice.gt(tokenHourData.high)) {
tokenHourData.high = tokenPrice
}
```
* low (low price USD)
- Pool Mint event, Burn event, Swap event
```ts
// Inside updateTokenHourData
let tokenHourData = TokenHourData.load(tokenHourID)
if (tokenHourData === null) {
tokenHourData = new TokenHourData(tokenHourID)
tokenHourData.low = tokenPrice
}
if (tokenPrice.lt(tokenHourData.low)) {
tokenHourData.low = tokenPrice
}
```
* open (opening price USD)
- Pool Mint event, Burn event, Swap event
```ts
// Inside updateTokenHourData
if (tokenHourData === null) {
tokenHourData = new TokenHourData(tokenHourID)
tokenHourData.open = tokenPrice
}
```
* periodStartUnix (unix timestamp for start of hour)
- Pool Mint event, Burn event, Swap event
```ts
// Inside updateTokenHourData
if (tokenHourData === null) {
tokenHourData = new TokenHourData(tokenHourID)
tokenHourData.periodStartUnix = hourStartUnix
}
```

View File

@ -1,117 +0,0 @@
# Contract Analysis
## View Methods in Uniswap V3 Core
* https://github.com/Uniswap/uniswap-v3-core/blob/main/contracts/NoDelegateCall.sol
- checkNotDelegateCall (private)
* https://github.com/Uniswap/uniswap-v3-core/blob/main/contracts/UniswapV3Pool.sol#L158
- _blockTimestamp (internal)
- balance0 (private)
- balance1 (private)
- snapshotCumulativesInside (external)
- observe (external)
* https://github.com/Uniswap/uniswap-v3-core/blob/main/contracts/libraries/Oracle.sol
- binarySearch (private)
- getSurroundingObservations (private)
- observeSingle (internal)
- observe (internal)
* https://github.com/Uniswap/uniswap-v3-core/blob/main/contracts/libraries/Position.sol
- get (internal)
* https://github.com/Uniswap/uniswap-v3-core/blob/main/contracts/libraries/Tick.sol
- getFeeGrowthInside (internal)
* https://github.com/Uniswap/uniswap-v3-core/blob/main/contracts/libraries/TickBitmap.sol
- nextInitializedTickWithinOneWord (internal)
## Mapping Event handlers in Uniswap subgraph
* handlePoolCreated (Factory contract - PoolCreated event)
- Data from event
- Entities
* Factory
* Bundle
* Pool
* Token
- Contract calls
* ERC20 (symbol, name, totalSupply, decimals)
* ERC20SymbolBytes (symbol)
* ERC20NameBytes (name)
- Create new Template contract (Pool)
* NonfungiblePositionManager contract
- Handlers (Similar code)
* handleIncreaseLiquidity (IncreaseLiquidity event)
* handleDecreaseLiquidity (DecreaseLiquidity event)
* handleCollect (Collect event)
* handleTransfer (Transfer event)
- Data from event
- Entities
* Position
* Transaction
* Token
- Contract calls
* NonfungiblePositionManager (positions)
* Factory (getPool)
* handleInitialize (Pool contract - Initialize event)
- Data from event
- Entities
* Pool
* Token
* Bundle
* PoolDayData
* PoolHourData
* handleSwap (Pool contract - Swap event)
- Data from event
- Entities
* Bundle
* Factory
* Pool
* Token
* Transaction
* Swap
* UniswapDayData
* PoolDayData
* PoolHourData
* TokenDayData
* TokenHourData
- Contract calls
* Pool (feeGrowthGlobal0X128, feeGrowthGlobal1X128)
* handleMint (Pool contract - Mint event)
- Data from event
- Entities
* Bundle
* Pool
* Factory
* Token
* Transaction
* Mint
* Tick
* UniswapDayData
* PoolDayData
* PoolHourData
* TokenDayData
* TokenHourData
* handleBurn (Pool contract - Burn event)
- Data from event
- Entities
* Bundle
* Pool
* Factory
* Token
* Burn
* Tick
* UniswapDayData
* PoolDayData
* PoolHourData
* TokenDayData
* TokenHourData
- Extra methods
* store.remove (remove Tick entity)

View File

@ -1,19 +0,0 @@
# Design Notes
## Watchers
* uniswap-watcher
* Provides events to downstream subscribers, access to core/periphery contract data,
* uniswap-info-watcher
* Subscribes to uniswap-watcher
* Performs computation/derivation of entity properties required by info frontend
* Filler (old to new block)
## Issues
* Filler should process block by block (old to new) starting from contract deployment block
* Otherwise, values of computed props will be incorrect
* "last_processed_block_number"
* Use audit/proof table to record changes to entities instead of aggregating in code (too slow)
* Handling reorgs
* ERC20 variants (storage layout)

View File

@ -1,595 +0,0 @@
# Frontend (Info App) Queries
- uniswap-v3 endpoint (https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-alt)
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/pools/chartData.ts
```
query poolDayDatas($startTime: Int!, $skip: Int!, $address: Bytes!) {
poolDayDatas(
first: 1000
skip: $skip
where: { pool: $address, date_gt: $startTime }
orderBy: date
orderDirection: asc
) {
date
volumeUSD
tvlUSD
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/pools/poolData.ts
```
query pools {
pools(where: {id_in: $poolString}, block: {number: $block}, orderBy: totalValueLockedUSD, orderDirection: desc) {
id
feeTier
liquidity
sqrtPrice
tick
token0 {
id
symbol
name
decimals
derivedETH
}
token1 {
id
symbol
name
decimals
derivedETH
}
token0Price
token1Price
volumeUSD
txCount
totalValueLockedToken0
totalValueLockedToken1
totalValueLockedUSD
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/pools/tickData.ts
```
query surroundingTicks(
$poolAddress: String!
$tickIdxLowerBound: BigInt!
$tickIdxUpperBound: BigInt!
$skip: Int!
) {
ticks(
first: 1000
skip: $skip
where: { poolAddress: $poolAddress, tickIdx_lte: $tickIdxUpperBound, tickIdx_gte: $tickIdxLowerBound }
) {
tickIdx
liquidityGross
liquidityNet
price0
price1
}
}
query pool($poolAddress: String!) {
pool(id: $poolAddress) {
tick
token0 {
symbol
id
decimals
}
token1 {
symbol
id
decimals
}
feeTier
sqrtPrice
liquidity
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/pools/topPools.ts
```
query topPools {
pools(first: 50, orderBy: totalValueLockedUSD, orderDirection: desc) {
id
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/pools/transactions.ts
```
query transactions($address: Bytes!) {
mints(first: 100, orderBy: timestamp, orderDirection: desc, where: { pool: $address }) {
timestamp
transaction {
id
}
pool {
token0 {
id
symbol
}
token1 {
id
symbol
}
}
owner
sender
origin
amount0
amount1
amountUSD
}
swaps(first: 100, orderBy: timestamp, orderDirection: desc, where: { pool: $address }) {
timestamp
transaction {
id
}
pool {
token0 {
id
symbol
}
token1 {
id
symbol
}
}
origin
amount0
amount1
amountUSD
}
burns(first: 100, orderBy: timestamp, orderDirection: desc, where: { pool: $address }) {
timestamp
transaction {
id
}
pool {
token0 {
id
symbol
}
token1 {
id
symbol
}
}
owner
amount0
amount1
amountUSD
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/protocol/chart.ts
```
query uniswapDayDatas($startTime: Int!, $skip: Int!) {
uniswapDayDatas(first: 1000, skip: $skip, where: { date_gt: $startTime }, orderBy: date, orderDirection: asc) {
id
date
volumeUSD
tvlUSD
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/protocol/overview.ts
```
query uniswapFactories {
factories(
block: { number: $block }
first: 1) {
txCount
totalVolumeUSD
totalFeesUSD
totalValueLockedUSD
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/protocol/transactions.ts
```
query transactions {
transactions(first: 500, orderBy: timestamp, orderDirection: desc) {
id
timestamp
mints {
pool {
token0 {
id
symbol
}
token1 {
id
symbol
}
}
owner
sender
origin
amount0
amount1
amountUSD
}
swaps {
pool {
token0 {
id
symbol
}
token1 {
id
symbol
}
}
origin
amount0
amount1
amountUSD
}
burns {
pool {
token0 {
id
symbol
}
token1 {
id
symbol
}
}
owner
origin
amount0
amount1
amountUSD
}
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/search/index.ts
```
query tokens($value: String, $id: String) {
asSymbol: tokens(where: { symbol_contains: $value }, orderBy: totalValueLockedUSD, orderDirection: desc) {
id
symbol
name
totalValueLockedUSD
}
asName: tokens(where: { name_contains: $value }, orderBy: totalValueLockedUSD, orderDirection: desc) {
id
symbol
name
totalValueLockedUSD
}
asAddress: tokens(where: { id: $id }, orderBy: totalValueLockedUSD, orderDirection: desc) {
id
symbol
name
totalValueLockedUSD
}
}
query pools($tokens: [Bytes]!, $id: String) {
as0: pools(where: { token0_in: $tokens }) {
id
feeTier
token0 {
id
symbol
name
}
token1 {
id
symbol
name
}
}
as1: pools(where: { token1_in: $tokens }) {
id
feeTier
token0 {
id
symbol
name
}
token1 {
id
symbol
name
}
}
asAddress: pools(where: { id: $id }) {
id
feeTier
token0 {
id
symbol
name
}
token1 {
id
symbol
name
}
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/chartData.ts
```
query tokenDayDatas($startTime: Int!, $skip: Int!, $address: Bytes!) {
tokenDayDatas(
first: 1000
skip: $skip
where: { token: $address, date_gt: $startTime }
orderBy: date
orderDirection: asc
) {
date
volumeUSD
totalValueLockedUSD
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/poolsForToken.ts
```
query topPools($address: Bytes!) {
asToken0: pools(first: 200, orderBy: totalValueLockedUSD, orderDirection: desc, where: { token0: $address }) {
id
}
asToken1: pools(first: 200, orderBy: totalValueLockedUSD, orderDirection: desc, where: { token1: $address }) {
id
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/priceData.ts
```
query blocks {
tBlockTimestamp:token(id:$tokenAddress, block: { number: $blockNumber }) {
derivedETH
},
bBlockTimestamp: bundle(id:"1", block: { number: $blockNumber }) {
ethPriceUSD
}
}
query tokenHourDatas($startTime: Int!, $skip: Int!, $address: Bytes!) {
tokenHourDatas(
first: 100
skip: $skip
where: { token: $address, periodStartUnix_gt: $startTime }
orderBy: periodStartUnix
orderDirection: asc
) {
periodStartUnix
high
low
open
close
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/tokenData.ts
```
query tokens {
tokens(where: {id_in: $tokenString}, block: {number: $block}), orderBy: totalValueLockedUSD, orderDirection: desc) {
id
symbol
name
derivedETH
volumeUSD
volume
txCount
totalValueLocked
feesUSD
totalValueLockedUSD
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/topTokens.ts
```
query topPools {
tokens(first: 50, orderBy: totalValueLockedUSD, orderDirection: desc) {
id
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/transactions.ts
```
query transactions($address: Bytes!) {
mintsAs0: mints(first: 500, orderBy: timestamp, orderDirection: desc, where: { token0: $address }) {
timestamp
transaction {
id
}
pool {
token0 {
id
symbol
}
token1 {
id
symbol
}
}
owner
sender
origin
amount0
amount1
amountUSD
}
mintsAs1: mints(first: 500, orderBy: timestamp, orderDirection: desc, where: { token0: $address }) {
timestamp
transaction {
id
}
pool {
token0 {
id
symbol
}
token1 {
id
symbol
}
}
owner
sender
origin
amount0
amount1
amountUSD
}
swapsAs0: swaps(first: 500, orderBy: timestamp, orderDirection: desc, where: { token0: $address }) {
timestamp
transaction {
id
}
pool {
token0 {
id
symbol
}
token1 {
id
symbol
}
}
origin
amount0
amount1
amountUSD
}
swapsAs1: swaps(first: 500, orderBy: timestamp, orderDirection: desc, where: { token1: $address }) {
timestamp
transaction {
id
}
pool {
token0 {
id
symbol
}
token1 {
id
symbol
}
}
origin
amount0
amount1
amountUSD
}
burnsAs0: burns(first: 500, orderBy: timestamp, orderDirection: desc, where: { token0: $address }) {
timestamp
transaction {
id
}
pool {
token0 {
id
symbol
}
token1 {
id
symbol
}
}
owner
amount0
amount1
amountUSD
}
burnsAs1: burns(first: 500, orderBy: timestamp, orderDirection: desc, where: { token1: $address }) {
timestamp
transaction {
id
}
pool {
token0 {
id
symbol
}
token1 {
id
symbol
}
}
owner
amount0
amount1
amountUSD
}
}
```
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/hooks/useEthPrices.ts
```
query prices($block24: Int!, $block48: Int!, $blockWeek: Int!) {
current: bundles(first: 1) {
ethPriceUSD
}
oneDay: bundles(first: 1, block: { number: $block24 }) {
ethPriceUSD
}
twoDay: bundles(first: 1, block: { number: $block48 }) {
ethPriceUSD
}
oneWeek: bundles(first: 1, block: { number: $blockWeek }) {
ethPriceUSD
}
}
```
- ethereum-blocks endpoint (https://api.thegraph.com/subgraphs/name/blocklytics/ethereum-blocks)
https://github.com/Uniswap/uniswap-v3-info/blob/master/src/hooks/useBlocksFromTimestamps.ts
```
query blocks {
tTimestamp1:blocks(first: 1, orderBy: timestamp, orderDirection: desc, where: { timestamp_gt: $timestamp1, timestamp_lt: $timestamp1Plus600 }) {
number
}
tTimestamp2:blocks(first: 1, orderBy: timestamp, orderDirection: desc, where: { timestamp_gt: $timestamp2, timestamp_lt: $timestamp2Plus600 }) {
number
}
}
```
- Checking subgraph health (https://thegraph.com/docs/deploy-a-subgraph#checking-subgraph-health)
https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/application/index.ts
```
query health {
indexingStatusForCurrentVersion(subgraphName: "uniswap/uniswap-v2") {
synced
health
chains {
chainHeadBlock {
number
}
latestBlock {
number
}
}
}
}
```

View File

@ -1,64 +0,0 @@
# Summary of Queries in Uniswap Info App
Actual queries are listed in [queries](./queries.md) file.
- uniswap-v3 endpoint (https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-alt)
* PoolDayData
- poolDayDatas (https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/pools/chartData.ts)
* Pool
- pools
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/pools/poolData.ts
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/pools/topPools.ts
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/search/index.ts
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/poolsForToken.ts
- pool
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/pools/tickData.ts
* Tick
- tick (https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/pools/tickData.ts)
* Mint
- mints
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/pools/transactions.ts
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/transactions.ts
* UniswapDayData
- uniswapDayDatas (https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/protocol/chart.ts)
* Factory
- factories (https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/protocol/overview.ts)
* Transaction
- transactions (https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/protocol/transactions.ts)
* Token
- tokens
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/search/index.ts
* (queried by block) https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/tokenData.ts
* https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/topTokens.ts
- token queried by block (https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/priceData.ts)
* TokenDayData
- tokenDayDatas (https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/chartData.ts)
* Bundle
- bundle queried by block (https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/priceData.ts)
- bundles (https://github.com/Uniswap/uniswap-v3-info/blob/master/src/hooks/useEthPrices.ts)
* TokenHourData
- tokenHourDatas (https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/priceData.ts)
* Swap
- swaps (https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/transactions.ts)
* Burn
- burns (https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/tokens/transactions.ts)
- ethereum-blocks endpoint (https://api.thegraph.com/subgraphs/name/blocklytics/ethereum-blocks)
t${timestamp}:blocks (https://github.com/Uniswap/uniswap-v3-info/blob/master/src/hooks/useBlocksFromTimestamps.ts)
- Checking subgraph health (https://thegraph.com/docs/deploy-a-subgraph#checking-subgraph-health)
https://github.com/Uniswap/uniswap-v3-info/blob/master/src/data/application/index.ts#L5

View File

@ -1,386 +0,0 @@
# lint-disable fields-have-descriptions, arguments-have-descriptions, types-have-descriptions, enum-values-all-caps, enum-values-have-descriptions, input-object-values-have-descriptions, input-object-values-are-camel-cased, relay-page-info-spec
scalar BigDecimal
scalar BigInt
scalar Bytes
input Block_height {
hash: Bytes
number: Int
}
type Pool {
feeTier: BigInt!
id: ID!
liquidity: BigInt!
sqrtPrice: BigInt!
tick: BigInt
token0: Token!
token0Price: BigDecimal!
token1: Token!
token1Price: BigDecimal!
totalValueLockedToken0: BigDecimal!
totalValueLockedToken1: BigDecimal!
totalValueLockedUSD: BigDecimal!
txCount: BigInt!
volumeUSD: BigDecimal!
}
type PoolDayData {
date: Int!
id: ID!
tvlUSD: BigDecimal!
volumeUSD: BigDecimal!
}
type Tick {
id: ID!
liquidityGross: BigInt!
liquidityNet: BigInt!
price0: BigDecimal!
price1: BigDecimal!
tickIdx: BigInt!
}
type Mint {
amount0: BigDecimal!
amount1: BigDecimal!
amountUSD: BigDecimal
id: ID!
origin: Bytes!
owner: Bytes!
pool: Pool!
sender: Bytes
timestamp: BigInt!
transaction: Transaction!
}
type Swap {
amount0: BigDecimal!
amount1: BigDecimal!
amountUSD: BigDecimal!
id: ID!
origin: Bytes!
pool: Pool!
timestamp: BigInt!
transaction: Transaction!
}
type Burn {
amount0: BigDecimal!
amount1: BigDecimal!
amountUSD: BigDecimal
id: ID!
origin: Bytes!
owner: Bytes
pool: Pool!
timestamp: BigInt!
transaction: Transaction!
}
type UniswapDayData {
date: Int!
id: ID!
tvlUSD: BigDecimal!
volumeUSD: BigDecimal!
}
type Factory {
id: ID!
totalFeesUSD: BigDecimal!
totalValueLockedUSD: BigDecimal!
totalVolumeUSD: BigDecimal!
txCount: BigInt!
}
type Transaction {
burns(skip: Int = 0, first: Int = 100, orderBy: Burn_orderBy, orderDirection: OrderDirection, where: Burn_filter): [Burn]!
id: ID!
mints(skip: Int = 0, first: Int = 100, orderBy: Mint_orderBy, orderDirection: OrderDirection, where: Mint_filter): [Mint]!
swaps(skip: Int = 0, first: Int = 100, orderBy: Swap_orderBy, orderDirection: OrderDirection, where: Swap_filter): [Swap]!
timestamp: BigInt!
}
type Token {
decimals: BigInt!
derivedETH: BigDecimal!
feesUSD: BigDecimal!
id: ID!
name: String!
symbol: String!
totalValueLocked: BigDecimal!
totalValueLockedUSD: BigDecimal!
txCount: BigInt!
volume: BigDecimal!
volumeUSD: BigDecimal!
}
type TokenDayData {
date: Int!
id: ID!
totalValueLockedUSD: BigDecimal!
volumeUSD: BigDecimal!
}
type Bundle {
ethPriceUSD: BigDecimal!
id: ID!
}
type TokenHourData {
close: BigDecimal!
high: BigDecimal!
id: ID!
low: BigDecimal!
open: BigDecimal!
periodStartUnix: Int!
}
enum OrderDirection {
asc
desc
}
input PoolDayData_filter {
date_gt: Int
pool: String
}
enum PoolDayData_orderBy {
date
}
input Pool_filter {
id: ID
id_in: [ID!]
token0: String
token0_in: [String!]
token1: String
token1_in: [String!]
}
enum Pool_orderBy {
totalValueLockedUSD
}
input Tick_filter {
poolAddress: String
tickIdx_gte: BigInt
tickIdx_lte: BigInt
}
input Mint_filter {
pool: String
token0: String
token1: String
}
enum Mint_orderBy {
timestamp
}
input Swap_filter {
pool: String
token0: String
token1: String
}
enum Swap_orderBy {
timestamp
}
input Burn_filter {
pool: String
token0: String
token1: String
}
enum Burn_orderBy {
timestamp
}
enum UniswapDayData_orderBy {
date
}
input UniswapDayData_filter {
date_gt: Int
}
enum Transaction_orderBy {
timestamp
}
input Token_filter {
id: ID
id_in: [ID!]
name_contains: String
symbol_contains: String
}
enum Token_orderBy {
totalValueLockedUSD
}
input TokenDayData_filter {
date_gt: Int
token: String
}
enum TokenDayData_orderBy {
date
}
input TokenHourData_filter {
periodStartUnix_gt: Int
token: String
}
enum TokenHourData_orderBy {
periodStartUnix
}
type Query {
bundle(
id: ID!
"""
The block at which the query should be executed. Can either be an `{ number:
Int }` containing the block number or a `{ hash: Bytes }` value containing a
block hash. Defaults to the latest block when omitted.
"""
block: Block_height
): Bundle
bundles(
first: Int = 100
"""
The block at which the query should be executed. Can either be an `{ number:
Int }` containing the block number or a `{ hash: Bytes }` value containing a
block hash. Defaults to the latest block when omitted.
"""
block: Block_height
): [Bundle!]!
burns(
first: Int = 100
orderBy: Burn_orderBy
orderDirection: OrderDirection
where: Burn_filter
): [Burn!]!
factories(
first: Int = 100
"""
The block at which the query should be executed. Can either be an `{ number:
Int }` containing the block number or a `{ hash: Bytes }` value containing a
block hash. Defaults to the latest block when omitted.
"""
block: Block_height
): [Factory!]!
mints(
first: Int = 100
orderBy: Mint_orderBy
orderDirection: OrderDirection
where: Mint_filter
): [Mint!]!
pool(
id: ID!
): Pool
poolDayDatas(
skip: Int = 0
first: Int = 100
orderBy: PoolDayData_orderBy
orderDirection: OrderDirection
where: PoolDayData_filter
): [PoolDayData!]!
pools(
first: Int = 100
orderBy: Pool_orderBy
orderDirection: OrderDirection
where: Pool_filter
"""
The block at which the query should be executed. Can either be an `{ number:
Int }` containing the block number or a `{ hash: Bytes }` value containing a
block hash. Defaults to the latest block when omitted.
"""
block: Block_height
): [Pool!]!
swaps(
first: Int = 100
orderBy: Swap_orderBy
orderDirection: OrderDirection
where: Swap_filter
): [Swap!]!
ticks(
skip: Int = 0
first: Int = 100
where: Tick_filter
"""
The block at which the query should be executed. Can either be an `{ number:
Int }` containing the block number or a `{ hash: Bytes }` value containing a
block hash. Defaults to the latest block when omitted.
"""
block: Block_height
): [Tick!]!
token(
id: ID!
"""
The block at which the query should be executed. Can either be an `{ number:
Int }` containing the block number or a `{ hash: Bytes }` value containing a
block hash. Defaults to the latest block when omitted.
"""
block: Block_height
): Token
tokenDayDatas(
skip: Int = 0
first: Int = 100
orderBy: TokenDayData_orderBy
orderDirection: OrderDirection
where: TokenDayData_filter
): [TokenDayData!]!
tokenHourDatas(
skip: Int = 0
first: Int = 100
orderBy: TokenHourData_orderBy
orderDirection: OrderDirection
where: TokenHourData_filter
): [TokenHourData!]!
tokens(
first: Int = 100
orderBy: Token_orderBy
orderDirection: OrderDirection
where: Token_filter
block: Block_height
): [Token!]!
transactions(
first: Int = 100
orderBy: Transaction_orderBy
orderDirection: OrderDirection
): [Transaction!]!
uniswapDayDatas(
skip: Int = 0
first: Int = 100
orderBy: UniswapDayData_orderBy
orderDirection: OrderDirection
where: UniswapDayData_filter
): [UniswapDayData!]!
}

View File

@ -1,66 +0,0 @@
scalar BigInt
type Block {
hash: Bytes!
number: BigInt!
}
scalar Bytes
interface ChainIndexingStatus {
network: String!
chainHeadBlock: Block
earliestBlock: Block
latestBlock: Block
lastHealthyBlock: Block
}
type EthereumIndexingStatus implements ChainIndexingStatus {
network: String!
chainHeadBlock: Block
earliestBlock: Block
latestBlock: Block
lastHealthyBlock: Block
}
enum Health {
"""Subgraph syncing normally"""
healthy
"""Subgraph syncing but with errors"""
unhealthy
"""Subgraph halted due to errors"""
failed
}
type Query {
indexingStatusForCurrentVersion(subgraphName: String!): SubgraphIndexingStatus
indexingStatusForPendingVersion(subgraphName: String!): SubgraphIndexingStatus
indexingStatusesForSubgraphName(subgraphName: String!): [SubgraphIndexingStatus!]!
indexingStatuses(subgraphs: [String!]): [SubgraphIndexingStatus!]!
proofOfIndexing(subgraph: String!, blockNumber: Int!, blockHash: Bytes!, indexer: Bytes): Bytes
}
type SubgraphError {
message: String!
block: Block
handler: String
deterministic: Boolean!
}
type SubgraphIndexingStatus {
subgraph: String!
synced: Boolean!
health: Health!
"""If the subgraph has failed, this is the error caused it"""
fatalError: SubgraphError
"""Sorted from first to last, limited to first 1000"""
nonFatalErrors: [SubgraphError!]!
chains: [ChainIndexingStatus!]!
entityCount: BigInt!
node: String
}

View File

@ -1,45 +0,0 @@
[server]
host = "127.0.0.1"
port = 3004
# Use mode demo when running watcher locally.
# Mode demo whitelists all tokens so that entity values get updated.
mode = "demo"
[metrics]
host = "127.0.0.1"
port = 9000
[database]
type = "postgres"
host = "localhost"
port = 5432
database = "uni-info-watcher"
username = "postgres"
password = "postgres"
synchronize = true
logging = false
[upstream]
[upstream.ethServer]
gqlApiEndpoint = "http://127.0.0.1:8082/graphql"
rpcProviderEndpoint = "http://127.0.0.1:8081"
blockDelayInMilliSecs = 2000
[upstream.cache]
name = "requests"
enabled = false
deleteOnStart = false
[upstream.uniWatcher]
gqlEndpoint = "http://127.0.0.1:3003/graphql"
gqlSubscriptionEndpoint = "http://127.0.0.1:3003/graphql"
[upstream.tokenWatcher]
gqlEndpoint = "http://127.0.0.1:3001/graphql"
gqlSubscriptionEndpoint = "http://127.0.0.1:3001/graphql"
[jobQueue]
dbConnectionString = "postgres://postgres:postgres@localhost/uni-info-watcher-job-queue"
maxCompletionLagInSecs = 300
jobDelayInMilliSecs = 1000
eventsInBatch = 50

View File

@ -1,41 +0,0 @@
[server]
host = "127.0.0.1"
port = 3004
# Use mode demo when running watcher locally.
# Mode demo whitelists all tokens so that entity values get updated.
mode = "demo"
[database]
type = "postgres"
host = "localhost"
port = 5432
database = "uni-info-watcher"
username = "postgres"
password = "postgres"
synchronize = true
logging = false
[upstream]
[upstream.ethServer]
gqlApiEndpoint = "http://127.0.0.1:8082/graphql"
rpcProviderEndpoint = "http://127.0.0.1:8545"
blockDelayInMilliSecs = 2000
[upstream.cache]
name = "requests"
enabled = false
deleteOnStart = false
[upstream.uniWatcher]
gqlEndpoint = "http://127.0.0.1:3003/graphql"
gqlSubscriptionEndpoint = "http://127.0.0.1:3003/graphql"
[upstream.tokenWatcher]
gqlEndpoint = "http://127.0.0.1:3001/graphql"
gqlSubscriptionEndpoint = "http://127.0.0.1:3001/graphql"
[jobQueue]
dbConnectionString = "postgres://postgres:postgres@localhost/uni-info-watcher-job-queue"
maxCompletionLagInSecs = 300
jobDelayInMilliSecs = 1000
eventsInBatch = 50

View File

@ -1,74 +0,0 @@
{
"name": "@vulcanize/uni-info-watcher",
"version": "0.1.0",
"main": "dist/index.js",
"license": "AGPL-3.0",
"private": true,
"dependencies": {
"@apollo/client": "^3.3.19",
"@vulcanize/cache": "^0.1.0",
"@vulcanize/erc20-watcher": "^0.1.0",
"@vulcanize/ipld-eth-client": "^0.1.0",
"@vulcanize/uni-watcher": "^0.1.0",
"@vulcanize/util": "^0.1.0",
"apollo-server-express": "^2.25.0",
"apollo-type-bigint": "^0.1.3",
"debug": "^4.3.1",
"express": "^4.17.1",
"graphql": "^15.5.0",
"graphql-import-node": "^0.0.4",
"graphql-request": "^3.4.0",
"json-bigint": "^1.0.0",
"reflect-metadata": "^0.1.13",
"typeorm": "^0.2.32",
"yargs": "^17.0.1"
},
"scripts": {
"lint": "eslint .",
"test": "mocha src/**/*.test.ts",
"test:gpev": "mocha src/get-prev-entity.test.ts",
"test:server": "mocha src/server.test.ts",
"build": "tsc",
"server": "DEBUG=vulcanize:* node --enable-source-maps dist/server.js",
"server:prof": "DEBUG=vulcanize:* node --require pprof --enable-source-maps dist/server.js",
"server:dev": "DEBUG=vulcanize:* nodemon --watch src src/server.ts",
"server:mock": "MOCK=1 nodemon src/server.ts",
"job-runner": "DEBUG=vulcanize:* node --enable-source-maps dist/job-runner.js",
"job-runner:prof": "DEBUG=vulcanize:* node --require pprof --enable-source-maps dist/job-runner.js",
"job-runner:dev": "DEBUG=vulcanize:* nodemon --watch src src/job-runner.ts",
"smoke-test": "mocha src/smoke.test.ts",
"fill": "DEBUG=vulcanize:* node --enable-source-maps dist/fill.js",
"fill:prof": "DEBUG=vulcanize:* node --require pprof --enable-source-maps dist/fill.js",
"fill:dev": "DEBUG=vulcanize:* ts-node src/fill.ts",
"generate:schema": "get-graphql-schema https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-alt > docs/analysis/schema/full-schema.graphql",
"generate:health-schema": "get-graphql-schema https://api.thegraph.com/index-node/graphql > docs/analysis/schema/health-schema.graphql",
"lint:schema": "graphql-schema-linter",
"reset": "DEBUG=vulcanize:* node --enable-source-maps dist/cli/reset.js",
"reset:dev": "DEBUG=vulcanize:* ts-node src/cli/reset.ts"
},
"devDependencies": {
"@types/chance": "^1.1.2",
"@types/express": "^4.17.11",
"@typescript-eslint/eslint-plugin": "^4.25.0",
"@typescript-eslint/parser": "^4.25.0",
"@uniswap/v3-core": "1.0.0",
"chai": "^4.3.4",
"chance": "^1.1.7",
"eslint": "^7.27.0",
"eslint-config-semistandard": "^15.0.1",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.23.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-standard": "^5.0.0",
"ethers": "^5.4.4",
"get-graphql-schema": "^2.1.2",
"graphql-schema-linter": "^2.0.1",
"lodash": "^4.17.21",
"mocha": "^8.4.0",
"nodemon": "^2.0.7",
"pprof": "^3.2.0",
"ts-node": "^10.0.0",
"typescript": "^4.3.2"
}
}

View File

@ -1,22 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import debug from 'debug';
import { getConfig, resetJobs } from '@vulcanize/util';
const log = debug('vulcanize:reset-job-queue');
export const command = 'job-queue';
export const desc = 'Reset job queue';
export const builder = {};
export const handler = async (argv: any): Promise<void> => {
const config = await getConfig(argv.configFile);
await resetJobs(config);
log('Job queue reset successfully');
};

View File

@ -1,102 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import debug from 'debug';
import { MoreThan } from 'typeorm';
import assert from 'assert';
import { getConfig, initClients, JobQueue, resetJobs } from '@vulcanize/util';
import { Client as ERC20Client } from '@vulcanize/erc20-watcher';
import { Client as UniClient } from '@vulcanize/uni-watcher';
import { Database } from '../../database';
import { Indexer } from '../../indexer';
import { BlockProgress } from '../../entity/BlockProgress';
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';
import { Position } from '../../entity/Position';
import { Token } from '../../entity/Token';
const log = debug('vulcanize:reset-state');
export const command = 'state';
export const desc = 'Reset state to block number';
export const builder = {
blockNumber: {
type: 'number'
}
};
export const handler = async (argv: any): Promise<void> => {
const config = await getConfig(argv.configFile);
await resetJobs(config);
const { jobQueue: jobQueueConfig } = config;
const { ethClient, ethProvider } = await initClients(config);
// Initialize database.
const db = new Database(config.database);
await db.init();
const {
uniWatcher,
tokenWatcher
} = config.upstream;
const uniClient = new UniClient(uniWatcher);
const erc20Client = new ERC20Client(tokenWatcher);
assert(jobQueueConfig, 'Missing job queue config');
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;
assert(dbConnectionString, 'Missing job queue db connection string');
const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs });
await jobQueue.start();
const indexer = new Indexer(config.server, db, uniClient, erc20Client, ethClient, ethProvider, jobQueue);
const syncStatus = await indexer.getSyncStatus();
assert(syncStatus, 'Missing syncStatus');
const blockProgresses = await indexer.getBlocksAtHeight(argv.blockNumber, false);
assert(blockProgresses.length, `No blocks at specified block number ${argv.blockNumber}`);
assert(!blockProgresses.some(block => !block.isComplete), `Incomplete block at block number ${argv.blockNumber} with unprocessed events`);
const [blockProgress] = blockProgresses;
const dbTx = await db.createTransactionRunner();
try {
const removeEntitiesPromise = [BlockProgress, Factory, Bundle, Pool, Mint, Burn, Swap, PositionSnapshot, Position, Token].map(async entityClass => {
return db.removeEntities<any>(dbTx, entityClass, { blockNumber: MoreThan(argv.blockNumber) });
});
await Promise.all(removeEntitiesPromise);
if (syncStatus.latestIndexedBlockNumber > blockProgress.blockNumber) {
await indexer.updateSyncStatusIndexedBlock(blockProgress.blockHash, blockProgress.blockNumber, true);
}
if (syncStatus.latestCanonicalBlockNumber > blockProgress.blockNumber) {
await indexer.updateSyncStatusCanonicalBlock(blockProgress.blockHash, blockProgress.blockNumber, true);
}
await indexer.updateSyncStatusChainHead(blockProgress.blockHash, blockProgress.blockNumber, true);
dbTx.commitTransaction();
} catch (error) {
await dbTx.rollbackTransaction();
throw error;
} finally {
await dbTx.release();
}
log('Reset state successfully');
};

View File

@ -1,24 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import 'reflect-metadata';
import debug from 'debug';
import { getResetYargs } from '@vulcanize/util';
const log = debug('vulcanize:reset');
const main = async () => {
return getResetYargs()
.commandDir('reset-cmds', { extensions: ['ts', 'js'], exclude: /([a-zA-Z0-9\s_\\.\-:])+(.d.ts)$/ })
.demandCommand(1)
.help()
.argv;
};
main().then(() => {
process.exit();
}).catch(err => {
log(err);
});

View File

@ -1,241 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { gql } from '@apollo/client/core';
import { GraphQLClient, GraphQLConfig } from '@vulcanize/ipld-eth-client';
import { BlockHeight, OrderDirection } from '@vulcanize/util';
import {
queryBundles,
queryBurns,
queryFactories,
queryMints,
queryPoolById,
queryPoolDayDatas,
queryPools,
queryPositions,
querySwaps,
queryTicks,
queryToken,
queryTokenDayDatas,
queryTokenHourDatas,
queryTransactions,
queryUniswapDayDatas
} from './queries';
export class Client {
_config: GraphQLConfig;
_client: GraphQLClient;
constructor (config: GraphQLConfig) {
this._config = config;
this._client = new GraphQLClient(config);
}
async getToken (tokenId: string, block?: BlockHeight): Promise<any> {
const { token } = await this._client.query(
gql(queryToken),
{
block,
id: tokenId
}
);
return token;
}
async getFactories (first?: number, block?: BlockHeight): Promise<any> {
const { factories } = await this._client.query(
gql(queryFactories),
{
block,
first
}
);
return factories;
}
async getBundles (first?: number, block?: BlockHeight): Promise<any> {
const { bundles } = await this._client.query(
gql(queryBundles),
{
block,
first
}
);
return bundles;
}
async getPoolById (id: string): Promise<any> {
const { pool } = await this._client.query(
gql(queryPoolById),
{
id
}
);
return pool;
}
async getTicks (where?: any, skip?: number, first?: number, block?: BlockHeight): Promise<any> {
const { ticks } = await this._client.query(
gql(queryTicks),
{
where,
skip,
first,
block
}
);
return ticks;
}
async getPools (where?: any, first?: number, orderBy?: string, orderDirection?: OrderDirection): Promise<any> {
const { pools } = await this._client.query(
gql(queryPools),
{
where,
first,
orderBy,
orderDirection
}
);
return pools;
}
async getUniswapDayDatas (where?: any, skip?: number, first?: number, orderBy?: string, orderDirection?: OrderDirection): Promise<any> {
const { uniswapDayDatas } = await this._client.query(
gql(queryUniswapDayDatas),
{
where,
skip,
first,
orderBy,
orderDirection
}
);
return uniswapDayDatas;
}
async getPoolDayDatas (where?: any, skip?: number, first?: number, orderBy?: string, orderDirection?: OrderDirection): Promise<any> {
const { poolDayDatas } = await this._client.query(
gql(queryPoolDayDatas),
{
where,
skip,
first,
orderBy,
orderDirection
}
);
return poolDayDatas;
}
async getTokenDayDatas (where?: any, skip?: number, first?: number, orderBy?: string, orderDirection?: OrderDirection): Promise<any> {
const { tokenDayDatas } = await this._client.query(
gql(queryTokenDayDatas),
{
where,
skip,
first,
orderBy,
orderDirection
}
);
return tokenDayDatas;
}
async getTokenHourDatas (where?: any, skip?: number, first?: number, orderBy?: string, orderDirection?: OrderDirection): Promise<any> {
const { tokenHourDatas } = await this._client.query(
gql(queryTokenHourDatas),
{
where,
skip,
first,
orderBy,
orderDirection
}
);
return tokenHourDatas;
}
async getMints (where?: any, first?: number, orderBy?: string, orderDirection?: OrderDirection): Promise<any> {
const { mints } = await this._client.query(
gql(queryMints),
{
where,
first,
orderBy,
orderDirection
}
);
return mints;
}
async getBurns (where?: any, first?: number, orderBy?: string, orderDirection?: OrderDirection): Promise<any> {
const { burns } = await this._client.query(
gql(queryBurns),
{
where,
first,
orderBy,
orderDirection
}
);
return burns;
}
async getSwaps (where?: any, first?: number, orderBy?: string, orderDirection?: OrderDirection): Promise<any> {
const { swaps } = await this._client.query(
gql(querySwaps),
{
where,
first,
orderBy,
orderDirection
}
);
return swaps;
}
async getTransactions (first?: number, { orderBy, mintOrderBy, burnOrderBy, swapOrderBy }: {[key: string]: string} = {}, orderDirection?: OrderDirection): Promise<any> {
const { transactions } = await this._client.query(
gql(queryTransactions),
{
first,
orderBy,
orderDirection,
mintOrderBy,
burnOrderBy,
swapOrderBy
}
);
return transactions;
}
async getPositions (where?: any, first?: number): Promise<any> {
const { positions } = await this._client.query(
gql(queryPositions),
{
where,
first
}
);
return positions;
}
}

View File

@ -1,87 +0,0 @@
# database
## Hierarchical Queries
For fetching previous entity that would be updated at a particular blockHash, we need to traverse the parent hashes. As the same entity might be present on a different branch chain with different values. These branches occur in the frothy region and so a recursive query is done to get the blockHash of the previous entity in this region.
Let the blockHash be `0xBlockHash` and the entity id be `entityId`, then the hierarchical query is
```pgsql
WITH RECURSIVE cte_query AS
(
SELECT
b.block_hash,
b.block_number,
b.parent_hash,
1 as depth,
e.id
FROM
block_progress b
LEFT JOIN
entityTable e ON e.block_hash = b.block_hash
WHERE
b.block_hash = '0xBlockHash'
UNION ALL
SELECT
b.block_hash,
b.block_number,
b.parent_hash,
c.depth + 1,
e.id
FROM
block_progress b
LEFT JOIN
${repo.metadata.tableName} e
ON e.block_hash = b.block_hash
AND e.id = 'entityId'
INNER JOIN
cte_query c ON c.parent_hash = b.block_hash
WHERE
c.id IS NULL AND c.depth < 16
)
SELECT
block_hash, block_number, id
FROM
cte_query
ORDER BY block_number ASC
LIMIT 1;
```
The second WHERE clause checks that the loop continues only till MAX_REORG_DEPTH `16` which specifies the frothy region or stop when the entity is found.
The resulting blockHash is then used to fetch the previous entity.
For fetching multiple entities, we fetch all the blockHashes in the frothy region. So it fetches the entities from the correct branch in the frothy and then from the pruned region.
Hierarchical query for getting blockHashes in the frothy region
```pgsql
WITH RECURSIVE cte_query AS
(
SELECT
block_hash,
block_number,
parent_hash,
1 as depth
FROM
block_progress
WHERE
block_hash = '0xBlockHash'
UNION ALL
SELECT
b.block_hash,
b.block_number,
b.parent_hash,
c.depth + 1
FROM
block_progress b
INNER JOIN
cte_query c ON c.parent_hash = b.block_hash
WHERE
c.depth < 16
)
SELECT
block_hash, block_number
FROM
cte_query;
```

View File

@ -1,693 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import {
Connection,
ConnectionOptions,
DeepPartial,
FindConditions,
FindManyOptions,
FindOneOptions,
LessThanOrEqual,
QueryRunner
} from 'typeorm';
import path from 'path';
import {
Database as BaseDatabase,
DatabaseInterface,
BlockHeight,
QueryOptions,
Where,
Relation
} from '@vulcanize/util';
import { Factory } from './entity/Factory';
import { Pool } from './entity/Pool';
import { Event } from './entity/Event';
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';
import { Burn } from './entity/Burn';
import { Swap } from './entity/Swap';
import { Position } from './entity/Position';
import { PositionSnapshot } from './entity/PositionSnapshot';
import { BlockProgress } from './entity/BlockProgress';
import { Block } from './events';
import { SyncStatus } from './entity/SyncStatus';
import { TickDayData } from './entity/TickDayData';
export class Database implements DatabaseInterface {
_config: ConnectionOptions
_conn!: Connection
_baseDatabase: BaseDatabase
constructor (config: ConnectionOptions) {
assert(config);
this._config = {
...config,
entities: [path.join(__dirname, 'entity/*')]
};
this._baseDatabase = new BaseDatabase(this._config);
}
async init (): Promise<void> {
this._conn = await this._baseDatabase.init();
}
async close (): Promise<void> {
return this._baseDatabase.close();
}
async getFactory (queryRunner: QueryRunner, { id, blockHash }: DeepPartial<Factory>): Promise<Factory | undefined> {
const repo = queryRunner.manager.getRepository(Factory);
const whereOptions: FindConditions<Factory> = { id };
if (blockHash) {
whereOptions.blockHash = blockHash;
}
const findOptions = {
where: whereOptions,
order: {
blockNumber: 'DESC'
}
};
let entity = await repo.findOne(findOptions as FindOneOptions<Factory>);
if (!entity && findOptions.where.blockHash) {
entity = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
return entity;
}
async getBundle (queryRunner: QueryRunner, { id, blockHash, blockNumber }: DeepPartial<Bundle>): Promise<Bundle | undefined> {
const repo = queryRunner.manager.getRepository(Bundle);
const whereOptions: FindConditions<Bundle> = { id };
if (blockHash) {
whereOptions.blockHash = blockHash;
}
if (blockNumber) {
whereOptions.blockNumber = LessThanOrEqual(blockNumber);
}
const findOptions = {
where: whereOptions,
order: {
blockNumber: 'DESC'
}
};
let entity = await repo.findOne(findOptions as FindOneOptions<Bundle>);
if (!entity && findOptions.where.blockHash) {
entity = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
return entity;
}
async getToken (queryRunner: QueryRunner, { id, blockHash }: DeepPartial<Token>): Promise<Token | undefined> {
const repo = queryRunner.manager.getRepository(Token);
const whereOptions: FindConditions<Token> = { id };
if (blockHash) {
whereOptions.blockHash = blockHash;
}
const findOptions = {
where: whereOptions,
relations: ['whitelistPools', 'whitelistPools.token0', 'whitelistPools.token1'],
order: {
blockNumber: 'DESC'
}
};
let entity = await repo.findOne(findOptions as FindOneOptions<Token>);
if (!entity && findOptions.where.blockHash) {
entity = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
return entity;
}
async getTokenNoTx ({ id, blockHash }: DeepPartial<Token>): Promise<Token | undefined> {
const queryRunner = this._conn.createQueryRunner();
let res;
try {
await queryRunner.connect();
res = await this.getToken(queryRunner, { id, blockHash });
} finally {
await queryRunner.release();
}
return res;
}
async getPool (queryRunner: QueryRunner, { id, blockHash, blockNumber }: DeepPartial<Pool>): Promise<Pool | undefined> {
const repo = queryRunner.manager.getRepository(Pool);
const whereOptions: FindConditions<Pool> = { id };
if (blockHash) {
whereOptions.blockHash = blockHash;
}
if (blockNumber) {
whereOptions.blockNumber = LessThanOrEqual(blockNumber);
}
const findOptions = {
where: whereOptions,
relations: ['token0', 'token1'],
order: {
blockNumber: 'DESC'
}
};
let entity = await repo.findOne(findOptions as FindOneOptions<Pool>);
if (!entity && findOptions.where.blockHash) {
entity = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
return entity;
}
async getPoolNoTx ({ id, blockHash, blockNumber }: DeepPartial<Pool>): Promise<Pool | undefined> {
const queryRunner = this._conn.createQueryRunner();
let res;
try {
await queryRunner.connect();
res = await this.getPool(queryRunner, { id, blockHash, blockNumber });
} finally {
await queryRunner.release();
}
return res;
}
async getPosition ({ id, blockHash }: DeepPartial<Position>): Promise<Position | undefined> {
const queryRunner = this._conn.createQueryRunner();
let entity;
try {
await queryRunner.connect();
const repo = queryRunner.manager.getRepository(Position);
const whereOptions: FindConditions<Position> = { id };
if (blockHash) {
whereOptions.blockHash = blockHash;
}
const findOptions = {
where: whereOptions,
relations: ['pool', 'token0', 'token1', 'tickLower', 'tickUpper', 'transaction'],
order: {
blockNumber: 'DESC'
}
};
entity = await repo.findOne(findOptions as FindOneOptions<Position>);
if (!entity && findOptions.where.blockHash) {
entity = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
} finally {
await queryRunner.release();
}
return entity;
}
async getTick (queryRunner: QueryRunner, { id, blockHash }: DeepPartial<Tick>): Promise<Tick | undefined> {
const repo = queryRunner.manager.getRepository(Tick);
const whereOptions: FindConditions<Tick> = { id };
if (blockHash) {
whereOptions.blockHash = blockHash;
}
const findOptions = {
where: whereOptions,
relations: ['pool'],
order: {
blockNumber: 'DESC'
}
};
let entity = await repo.findOne(findOptions as FindOneOptions<Tick>);
if (!entity && findOptions.where.blockHash) {
entity = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
return entity;
}
async getTickNoTx ({ id, blockHash }: DeepPartial<Tick>): Promise<Tick | undefined> {
const queryRunner = this._conn.createQueryRunner();
let res;
try {
await queryRunner.connect();
res = await this.getTick(queryRunner, { id, blockHash });
} finally {
await queryRunner.release();
}
return res;
}
async getPoolDayData (queryRunner: QueryRunner, { id, blockHash }: DeepPartial<PoolDayData>): Promise<PoolDayData | undefined> {
const repo = queryRunner.manager.getRepository(PoolDayData);
const whereOptions: FindConditions<PoolDayData> = { id };
if (blockHash) {
whereOptions.blockHash = blockHash;
}
const findOptions = {
where: whereOptions,
order: {
blockNumber: 'DESC'
},
relations: ['pool']
};
let entity = await repo.findOne(findOptions as FindOneOptions<PoolDayData>);
if (!entity && findOptions.where.blockHash) {
entity = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
return entity;
}
async getPoolHourData (queryRunner: QueryRunner, { id, blockHash }: DeepPartial<PoolHourData>): Promise<PoolHourData | undefined> {
const repo = queryRunner.manager.getRepository(PoolHourData);
const whereOptions: FindConditions<PoolHourData> = { id };
if (blockHash) {
whereOptions.blockHash = blockHash;
}
const findOptions = {
where: whereOptions,
order: {
blockNumber: 'DESC'
}
};
let entity = await repo.findOne(findOptions as FindOneOptions<PoolHourData>);
if (!entity && findOptions.where.blockHash) {
entity = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
return entity;
}
async getUniswapDayData (queryRunner: QueryRunner, { id, blockHash }: DeepPartial<UniswapDayData>): Promise<UniswapDayData | undefined> {
const repo = queryRunner.manager.getRepository(UniswapDayData);
const whereOptions: FindConditions<UniswapDayData> = { id };
if (blockHash) {
whereOptions.blockHash = blockHash;
}
const findOptions = {
where: whereOptions,
order: {
blockNumber: 'DESC'
}
};
let entity = await repo.findOne(findOptions as FindOneOptions<UniswapDayData>);
if (!entity && findOptions.where.blockHash) {
entity = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
return entity;
}
async getTokenDayData (queryRunner: QueryRunner, { id, blockHash }: DeepPartial<TokenDayData>): Promise<TokenDayData | undefined> {
const repo = queryRunner.manager.getRepository(TokenDayData);
const whereOptions: FindConditions<TokenDayData> = { id };
if (blockHash) {
whereOptions.blockHash = blockHash;
}
const findOptions = {
where: whereOptions,
order: {
blockNumber: 'DESC'
}
};
let entity = await repo.findOne(findOptions as FindOneOptions<TokenDayData>);
if (!entity && findOptions.where.blockHash) {
entity = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
return entity;
}
async getTokenHourData (queryRunner: QueryRunner, { id, blockHash }: DeepPartial<TokenHourData>): Promise<TokenHourData | undefined> {
const repo = queryRunner.manager.getRepository(TokenHourData);
const whereOptions: FindConditions<TokenHourData> = { id };
if (blockHash) {
whereOptions.blockHash = blockHash;
}
const findOptions = {
where: whereOptions,
order: {
blockNumber: 'DESC'
}
};
let entity = await repo.findOne(findOptions as FindOneOptions<TokenHourData>);
if (!entity && findOptions.where.blockHash) {
entity = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
return entity;
}
async getTickDayData (queryRunner: QueryRunner, { id, blockHash }: DeepPartial<TickDayData>): Promise<TickDayData | undefined> {
const repo = queryRunner.manager.getRepository(TickDayData);
const whereOptions: FindConditions<TickDayData> = { id };
if (blockHash) {
whereOptions.blockHash = blockHash;
}
const findOptions = {
where: whereOptions,
order: {
blockNumber: 'DESC'
}
};
let entity = await repo.findOne(findOptions as FindOneOptions<TickDayData>);
if (!entity && findOptions.where.blockHash) {
entity = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
return entity;
}
async getTransaction (queryRunner: QueryRunner, { id, blockHash }: DeepPartial<Transaction>): Promise<Transaction | undefined> {
const repo = queryRunner.manager.getRepository(Transaction);
const whereOptions: FindConditions<Transaction> = { id };
if (blockHash) {
whereOptions.blockHash = blockHash;
}
const findOptions = {
where: whereOptions,
order: {
blockNumber: 'DESC'
}
};
let entity = await repo.findOne(findOptions as FindOneOptions<Transaction>);
if (!entity && findOptions.where.blockHash) {
entity = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
return entity;
}
async getModelEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, block: BlockHeight, where: Where = {}, queryOptions: QueryOptions = {}, relations: Relation[] = []): Promise<Entity[]> {
return this._baseDatabase.getModelEntities(queryRunner, entity, block, where, queryOptions, relations);
}
async getModelEntitiesNoTx<Entity> (entity: new () => Entity, block: BlockHeight, where: Where = {}, queryOptions: QueryOptions = {}, relations: Relation[] = []): Promise<Entity[]> {
const queryRunner = this._conn.createQueryRunner();
let res;
try {
await queryRunner.connect();
res = await this.getModelEntities(queryRunner, entity, block, where, queryOptions, relations);
} finally {
await queryRunner.release();
}
return res;
}
async saveFactory (queryRunner: QueryRunner, factory: Factory, block: Block): Promise<Factory> {
const repo = queryRunner.manager.getRepository(Factory);
factory.blockNumber = block.number;
factory.blockHash = block.hash;
return repo.save(factory);
}
async saveBundle (queryRunner: QueryRunner, bundle: Bundle, block: Block): Promise<Bundle> {
const repo = queryRunner.manager.getRepository(Bundle);
bundle.blockNumber = block.number;
bundle.blockHash = block.hash;
return repo.save(bundle);
}
async savePool (queryRunner: QueryRunner, pool: Pool, block: Block): Promise<Pool> {
const repo = queryRunner.manager.getRepository(Pool);
pool.blockNumber = block.number;
pool.blockHash = block.hash;
return repo.save(pool);
}
async savePoolDayData (queryRunner: QueryRunner, poolDayData: PoolDayData, block: Block): Promise<PoolDayData> {
const repo = queryRunner.manager.getRepository(PoolDayData);
poolDayData.blockNumber = block.number;
poolDayData.blockHash = block.hash;
return repo.save(poolDayData);
}
async savePoolHourData (queryRunner: QueryRunner, poolHourData: PoolHourData, block: Block): Promise<PoolHourData> {
const repo = queryRunner.manager.getRepository(PoolHourData);
poolHourData.blockNumber = block.number;
poolHourData.blockHash = block.hash;
return repo.save(poolHourData);
}
async saveToken (queryRunner: QueryRunner, token: Token, block: Block): Promise<Token> {
const repo = queryRunner.manager.getRepository(Token);
token.blockNumber = block.number;
token.blockHash = block.hash;
return repo.save(token);
}
async saveTransaction (queryRunner: QueryRunner, transaction: Transaction, block: Block): Promise<Transaction> {
const repo = queryRunner.manager.getRepository(Transaction);
transaction.blockNumber = block.number;
transaction.blockHash = block.hash;
return repo.save(transaction);
}
async saveUniswapDayData (queryRunner: QueryRunner, uniswapDayData: UniswapDayData, block: Block): Promise<UniswapDayData> {
const repo = queryRunner.manager.getRepository(UniswapDayData);
uniswapDayData.blockNumber = block.number;
uniswapDayData.blockHash = block.hash;
return repo.save(uniswapDayData);
}
async saveTokenDayData (queryRunner: QueryRunner, tokenDayData: TokenDayData, block: Block): Promise<TokenDayData> {
const repo = queryRunner.manager.getRepository(TokenDayData);
tokenDayData.blockNumber = block.number;
tokenDayData.blockHash = block.hash;
return repo.save(tokenDayData);
}
async saveTokenHourData (queryRunner: QueryRunner, tokenHourData: TokenHourData, block: Block): Promise<TokenHourData> {
const repo = queryRunner.manager.getRepository(TokenHourData);
tokenHourData.blockNumber = block.number;
tokenHourData.blockHash = block.hash;
return repo.save(tokenHourData);
}
async saveTick (queryRunner: QueryRunner, tick: Tick, block: Block): Promise<Tick> {
const repo = queryRunner.manager.getRepository(Tick);
tick.blockNumber = block.number;
tick.blockHash = block.hash;
return repo.save(tick);
}
async saveTickDayData (queryRunner: QueryRunner, tickDayData: TickDayData, block: Block): Promise<TickDayData> {
const repo = queryRunner.manager.getRepository(TickDayData);
tickDayData.blockNumber = block.number;
tickDayData.blockHash = block.hash;
return repo.save(tickDayData);
}
async savePosition (queryRunner: QueryRunner, position: Position, block: Block): Promise<Position> {
const repo = queryRunner.manager.getRepository(Position);
position.blockNumber = block.number;
position.blockHash = block.hash;
return repo.save(position);
}
async savePositionSnapshot (queryRunner: QueryRunner, positionSnapshot: PositionSnapshot, block: Block): Promise<PositionSnapshot> {
const repo = queryRunner.manager.getRepository(PositionSnapshot);
positionSnapshot.blockNumber = block.number;
positionSnapshot.blockHash = block.hash;
return repo.save(positionSnapshot);
}
async saveMint (queryRunner: QueryRunner, mint: Mint, block: Block): Promise<Mint> {
const repo = queryRunner.manager.getRepository(Mint);
mint.blockNumber = block.number;
mint.blockHash = block.hash;
return repo.save(mint);
}
async saveBurn (queryRunner: QueryRunner, burn: Burn, block: Block): Promise<Burn> {
const repo = queryRunner.manager.getRepository(Burn);
burn.blockNumber = block.number;
burn.blockHash = block.hash;
return repo.save(burn);
}
async saveSwap (queryRunner: QueryRunner, swap: Swap, block: Block): Promise<Swap> {
const repo = queryRunner.manager.getRepository(Swap);
swap.blockNumber = block.number;
swap.blockHash = block.hash;
return repo.save(swap);
}
async createTransactionRunner (): Promise<QueryRunner> {
return this._baseDatabase.createTransactionRunner();
}
async getProcessedBlockCountForRange (fromBlockNumber: number, toBlockNumber: number): Promise<{ expected: number, actual: number }> {
const repo = this._conn.getRepository(BlockProgress);
return this._baseDatabase.getProcessedBlockCountForRange(repo, fromBlockNumber, toBlockNumber);
}
async getEventsInRange (fromBlockNumber: number, toBlockNumber: number): Promise<Array<Event>> {
const repo = this._conn.getRepository(Event);
return this._baseDatabase.getEventsInRange(repo, fromBlockNumber, toBlockNumber);
}
async saveEventEntity (queryRunner: QueryRunner, entity: Event): Promise<Event> {
const repo = queryRunner.manager.getRepository(Event);
return this._baseDatabase.saveEventEntity(repo, entity);
}
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Event[]> {
const repo = this._conn.getRepository(Event);
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
}
async saveEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
const eventRepo = queryRunner.manager.getRepository(Event);
return this._baseDatabase.saveEvents(blockRepo, eventRepo, block, events);
}
async updateSyncStatusIndexedBlock (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force = false): Promise<SyncStatus> {
const repo = queryRunner.manager.getRepository(SyncStatus);
return this._baseDatabase.updateSyncStatusIndexedBlock(repo, blockHash, blockNumber, force);
}
async updateSyncStatusCanonicalBlock (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force = false): Promise<SyncStatus> {
const repo = queryRunner.manager.getRepository(SyncStatus);
return this._baseDatabase.updateSyncStatusCanonicalBlock(repo, blockHash, blockNumber, force);
}
async updateSyncStatusChainHead (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force = false): Promise<SyncStatus> {
const repo = queryRunner.manager.getRepository(SyncStatus);
return this._baseDatabase.updateSyncStatusChainHead(repo, blockHash, blockNumber, force);
}
async getSyncStatus (queryRunner: QueryRunner): Promise<SyncStatus | undefined> {
const repo = queryRunner.manager.getRepository(SyncStatus);
return this._baseDatabase.getSyncStatus(repo);
}
async getEvent (id: string): Promise<Event | undefined> {
const repo = this._conn.getRepository(Event);
return this._baseDatabase.getEvent(repo, id);
}
async getBlocksAtHeight (height: number, isPruned: boolean): Promise<BlockProgress[]> {
const repo = this._conn.getRepository(BlockProgress);
return this._baseDatabase.getBlocksAtHeight(repo, height, isPruned);
}
async markBlocksAsPruned (queryRunner: QueryRunner, blocks: BlockProgress[]): Promise<void> {
const repo = queryRunner.manager.getRepository(BlockProgress);
return this._baseDatabase.markBlocksAsPruned(repo, blocks);
}
async getBlockProgress (blockHash: string): Promise<BlockProgress | undefined> {
const repo = this._conn.getRepository(BlockProgress);
return this._baseDatabase.getBlockProgress(repo, blockHash);
}
async getBlockProgressEntities (where: FindConditions<BlockProgress>, options: FindManyOptions<BlockProgress>): Promise<BlockProgress[]> {
const repo = this._conn.getRepository(BlockProgress);
return this._baseDatabase.getBlockProgressEntities(repo, where, options);
}
async updateBlockProgress (queryRunner: QueryRunner, block: BlockProgress, lastProcessedEventIndex: number): Promise<BlockProgress> {
const repo = queryRunner.manager.getRepository(BlockProgress);
return this._baseDatabase.updateBlockProgress(repo, block, lastProcessedEventIndex);
}
async getEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, findConditions?: FindConditions<Entity>): Promise<Entity[]> {
return this._baseDatabase.getEntities(queryRunner, entity, findConditions);
}
async removeEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, findConditions?: FindManyOptions<Entity> | FindConditions<Entity>): Promise<void> {
return this._baseDatabase.removeEntities(queryRunner, entity, findConditions);
}
async isEntityEmpty<Entity> (entity: new () => Entity): Promise<boolean> {
return this._baseDatabase.isEntityEmpty(entity);
}
async getAncestorAtDepth (blockHash: string, depth: number): Promise<string> {
return this._baseDatabase.getAncestorAtDepth(blockHash, depth);
}
}

View File

@ -1,49 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryGeneratedColumn, Column, Index, CreateDateColumn } from 'typeorm';
import { BlockProgressInterface } from '@vulcanize/util';
@Entity()
@Index(['blockHash'], { unique: true })
@Index(['blockNumber'])
@Index(['parentHash'])
export class BlockProgress implements BlockProgressInterface {
@PrimaryGeneratedColumn()
id!: number;
@Column('varchar')
cid!: string;
@Column('varchar', { length: 66 })
blockHash!: string;
@Column('varchar', { length: 66, nullable: true })
parentHash!: string;
@Column('integer')
blockNumber!: number;
@Column('integer')
blockTimestamp!: number;
@Column('integer')
numEvents!: number;
@Column('integer')
numProcessedEvents!: number;
@Column('integer')
lastProcessedEventIndex!: number;
@Column('boolean')
isComplete!: boolean
@Column('boolean', { default: false })
isPruned!: boolean
@CreateDateColumn()
createdAt!: Date;
}

View File

@ -1,22 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal } from '@vulcanize/util';
@Entity()
export class Bundle {
@PrimaryColumn('varchar', { length: 1 })
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
ethPriceUSD!: GraphDecimal
}

View File

@ -1,62 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { bigintTransformer, graphDecimalTransformer, GraphDecimal } from '@vulcanize/util';
import { Transaction } from './Transaction';
import { Pool } from './Pool';
import { Token } from './Token';
@Entity()
export class Burn {
@PrimaryColumn('varchar')
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@ManyToOne(() => Transaction, transaction => transaction.burns, { onDelete: 'CASCADE' })
transaction!: Transaction
@Column('numeric', { transformer: bigintTransformer })
timestamp!: bigint;
@ManyToOne(() => Pool, { onDelete: 'CASCADE' })
pool!: Pool
@ManyToOne(() => Token, { onDelete: 'CASCADE' })
token0!: Token
@ManyToOne(() => Token, { onDelete: 'CASCADE' })
token1!: Token
@Column('varchar', { length: 42 })
owner!: string
@Column('varchar', { length: 42 })
origin!: string
@Column('numeric', { transformer: bigintTransformer })
amount!: bigint
@Column('numeric', { transformer: graphDecimalTransformer })
amount0!: GraphDecimal
@Column('numeric', { transformer: graphDecimalTransformer })
amount1!: GraphDecimal
@Column('numeric', { transformer: graphDecimalTransformer })
amountUSD!: GraphDecimal
@Column('numeric', { transformer: bigintTransformer })
tickLower!: bigint
@Column('numeric', { transformer: bigintTransformer })
tickUpper!: bigint
}

View File

@ -1,39 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryGeneratedColumn, Column, Index, ManyToOne } from 'typeorm';
import { BlockProgress } from './BlockProgress';
@Entity()
// Index to query all events for a contract efficiently.
@Index(['contract'])
export class Event {
@PrimaryGeneratedColumn()
id!: number;
@ManyToOne(() => BlockProgress, { onDelete: 'CASCADE' })
block!: BlockProgress;
@Column('varchar', { length: 66 })
txHash!: string;
// Index of the log in the block.
@Column('integer')
index!: number;
@Column('varchar', { length: 42 })
contract!: string;
@Column('varchar', { length: 256 })
eventName!: string;
@Column('text')
eventInfo!: string;
@Column('text')
extraInfo!: string;
@Column('text')
proof!: string;
}

View File

@ -1,24 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
// Stores a row if events for a (block, token) combination have already been fetched.
//
// Required as a particular block may not have events from a particular contract,
// and we need to differentiate between that case and the case where data hasn't
// yet been synced from upstream.
//
@Entity()
@Index(['blockHash', 'token'], { unique: true })
export class EventSyncProgress {
@PrimaryGeneratedColumn()
id!: number;
@Column('varchar', { length: 66 })
blockHash!: string;
@Column('varchar', { length: 42 })
token!: string;
}

View File

@ -1,48 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, Column, PrimaryColumn } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal, bigintTransformer } from '@vulcanize/util';
@Entity()
export class Factory {
@PrimaryColumn('varchar', { length: 42 })
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
poolCount!: bigint;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
totalValueLockedETH!: GraphDecimal;
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
txCount!: bigint;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
totalValueLockedUSD!: GraphDecimal;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
totalVolumeUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
totalVolumeETH!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
totalFeesUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
totalFeesETH!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
untrackedVolumeUSD!: GraphDecimal
// TODO: Add remaining fields when they are used.
}

View File

@ -1,65 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal, bigintTransformer } from '@vulcanize/util';
import { Transaction } from './Transaction';
import { Pool } from './Pool';
import { Token } from './Token';
@Entity()
export class Mint {
@PrimaryColumn('varchar')
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@ManyToOne(() => Transaction, transaction => transaction.mints, { onDelete: 'CASCADE' })
transaction!: Transaction
@Column('numeric', { transformer: bigintTransformer })
timestamp!: bigint;
@ManyToOne(() => Pool, { onDelete: 'CASCADE' })
pool!: Pool
@ManyToOne(() => Token, { onDelete: 'CASCADE' })
token0!: Token
@ManyToOne(() => Token, { onDelete: 'CASCADE' })
token1!: Token
@Column('varchar', { length: 42 })
owner!: string
@Column('varchar', { length: 42 })
sender!: string
@Column('varchar', { length: 42 })
origin!: string
@Column('numeric', { transformer: bigintTransformer })
amount!: bigint
@Column('numeric', { transformer: graphDecimalTransformer })
amount0!: GraphDecimal
@Column('numeric', { transformer: graphDecimalTransformer })
amount1!: GraphDecimal
@Column('numeric', { transformer: graphDecimalTransformer })
amountUSD!: GraphDecimal
@Column('numeric', { transformer: bigintTransformer })
tickLower!: bigint
@Column('numeric', { transformer: bigintTransformer })
tickUpper!: bigint
}

View File

@ -1,83 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal, bigintTransformer } from '@vulcanize/util';
import { Token } from './Token';
@Entity()
export class Pool {
@PrimaryColumn('varchar', { length: 42 })
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@ManyToOne(() => Token, { onDelete: 'CASCADE' })
token0!: Token;
@ManyToOne(() => Token, { onDelete: 'CASCADE' })
token1!: Token;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
token0Price!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
token1Price!: GraphDecimal
@Column('numeric', { transformer: bigintTransformer })
feeTier!: bigint
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
sqrtPrice!: bigint
@Column('numeric', { nullable: true, transformer: bigintTransformer })
tick!: bigint | null
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
liquidity!: bigint
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
feeGrowthGlobal0X128!: bigint
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
feeGrowthGlobal1X128!: bigint
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
totalValueLockedUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
totalValueLockedToken0!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
totalValueLockedToken1!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
totalValueLockedETH!: GraphDecimal
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
txCount!: bigint;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volumeToken0!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volumeToken1!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volumeUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
untrackedVolumeUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
feesUSD!: GraphDecimal
// TODO: Add remaining fields when they are used.
}

View File

@ -1,80 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal, bigintTransformer } from '@vulcanize/util';
import { Pool } from './Pool';
@Entity()
export class PoolDayData {
@PrimaryColumn('varchar')
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@Column('integer')
date!: number;
@ManyToOne(() => Pool, { onDelete: 'CASCADE' })
pool!: Pool;
@Column('numeric', { transformer: graphDecimalTransformer })
high!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
low!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
open!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
close!: GraphDecimal;
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
sqrtPrice!: bigint
@Column('numeric', { nullable: true, transformer: bigintTransformer })
tick!: bigint | null
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
liquidity!: bigint
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
feeGrowthGlobal0X128!: bigint
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
feeGrowthGlobal1X128!: bigint
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
token0Price!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
token1Price!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
tvlUSD!: GraphDecimal
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
txCount!: bigint
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volumeToken0!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volumeToken1!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volumeUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
feesUSD!: GraphDecimal
// TODO: Add remaining fields when they are used.
}

View File

@ -1,80 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal, bigintTransformer } from '@vulcanize/util';
import { Pool } from './Pool';
@Entity()
export class PoolHourData {
@PrimaryColumn('varchar')
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@Column('integer')
periodStartUnix!: number;
@ManyToOne(() => Pool, { onDelete: 'CASCADE' })
pool!: Pool;
@Column('numeric', { transformer: graphDecimalTransformer })
high!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
low!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
open!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
close!: GraphDecimal;
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
sqrtPrice!: bigint
@Column('numeric', { nullable: true, transformer: bigintTransformer })
tick!: bigint | null
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
liquidity!: bigint
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
feeGrowthGlobal0X128!: bigint
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
feeGrowthGlobal1X128!: bigint
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
token0Price!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
token1Price!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
tvlUSD!: GraphDecimal
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
txCount!: bigint
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volumeToken0!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volumeToken1!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volumeUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
feesUSD!: GraphDecimal
// TODO: Add remaining fields when they are used.
}

View File

@ -1,73 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal, bigintTransformer } from '@vulcanize/util';
import { Pool } from './Pool';
import { Token } from './Token';
import { Tick } from './Tick';
import { Transaction } from './Transaction';
import { ADDRESS_ZERO } from '../utils/constants';
@Entity()
export class Position {
@PrimaryColumn('varchar')
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@Column('numeric', { transformer: bigintTransformer })
feeGrowthInside0LastX128!: bigint
@Column('numeric', { transformer: bigintTransformer })
feeGrowthInside1LastX128!: bigint
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
liquidity!: bigint
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
depositedToken0!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
depositedToken1!: GraphDecimal
@Column('varchar', { length: 42, default: ADDRESS_ZERO })
owner!: string
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
withdrawnToken0!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
withdrawnToken1!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
collectedFeesToken0!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
collectedFeesToken1!: GraphDecimal
@ManyToOne(() => Pool, { onDelete: 'CASCADE' })
pool!: Pool
@ManyToOne(() => Token)
token0!: Token
@ManyToOne(() => Token)
token1!: Token
@ManyToOne(() => Tick)
tickLower!: Tick
@ManyToOne(() => Tick)
tickUpper!: Tick
@ManyToOne(() => Transaction)
transaction!: Transaction
}

View File

@ -1,66 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal, bigintTransformer } from '@vulcanize/util';
import { Pool } from './Pool';
import { Transaction } from './Transaction';
import { ADDRESS_ZERO } from '../utils/constants';
import { Position } from './Position';
@Entity()
export class PositionSnapshot {
@PrimaryColumn('varchar')
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@Column('numeric', { transformer: bigintTransformer })
timestamp!: bigint;
@Column('numeric', { transformer: bigintTransformer })
feeGrowthInside0LastX128!: bigint
@Column('numeric', { transformer: bigintTransformer })
feeGrowthInside1LastX128!: bigint
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
liquidity!: bigint
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
depositedToken0!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
depositedToken1!: GraphDecimal
@Column('varchar', { length: 42, default: ADDRESS_ZERO })
owner!: string
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
withdrawnToken0!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
withdrawnToken1!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
collectedFeesToken0!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
collectedFeesToken1!: GraphDecimal
@ManyToOne(() => Pool, { onDelete: 'CASCADE' })
pool!: Pool
@ManyToOne(() => Position, { onDelete: 'CASCADE' })
position!: Position
@ManyToOne(() => Transaction, { onDelete: 'CASCADE' })
transaction!: Transaction
}

View File

@ -1,62 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal, bigintTransformer } from '@vulcanize/util';
import { Transaction } from './Transaction';
import { Pool } from './Pool';
import { Token } from './Token';
@Entity()
export class Swap {
@PrimaryColumn('varchar')
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@ManyToOne(() => Transaction, transaction => transaction.swaps, { onDelete: 'CASCADE' })
transaction!: Transaction
@Column('numeric', { transformer: bigintTransformer })
timestamp!: bigint;
@ManyToOne(() => Pool, { onDelete: 'CASCADE' })
pool!: Pool
@ManyToOne(() => Token, { onDelete: 'CASCADE' })
token0!: Token
@ManyToOne(() => Token, { onDelete: 'CASCADE' })
token1!: Token
@Column('varchar', { length: 42 })
sender!: string
@Column('varchar', { length: 42 })
origin!: string
@Column('varchar', { length: 42 })
recipient!: string
@Column('numeric', { transformer: graphDecimalTransformer })
amount0!: GraphDecimal
@Column('numeric', { transformer: graphDecimalTransformer })
amount1!: GraphDecimal
@Column('numeric', { transformer: graphDecimalTransformer })
amountUSD!: GraphDecimal
@Column('numeric', { transformer: bigintTransformer })
tick!: bigint
@Column('numeric', { transformer: bigintTransformer })
sqrtPriceX96!: bigint
}

View File

@ -1,43 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { SyncStatusInterface } from '@vulcanize/util';
@Entity()
export class SyncStatus implements SyncStatusInterface {
@PrimaryGeneratedColumn()
id!: number;
// Latest block hash and number from the chain itself.
@Column('varchar', { length: 66 })
chainHeadBlockHash!: string;
@Column('integer')
chainHeadBlockNumber!: number;
// Most recent block hash that's been indexed.
@Column('varchar', { length: 66 })
latestIndexedBlockHash!: string;
// Most recent block number that's been indexed.
@Column('integer')
latestIndexedBlockNumber!: number;
// Most recent block hash and number that we can consider as part
// of the canonical/finalized chain. Reorgs older than this block
// cannot be processed and processing will halt.
@Column('varchar', { length: 66 })
latestCanonicalBlockHash!: string;
@Column('integer')
latestCanonicalBlockNumber!: number;
@Column('varchar', { length: 66 })
initialIndexedBlockHash!: string;
@Column('integer')
initialIndexedBlockNumber!: number;
}

View File

@ -1,42 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal, bigintTransformer } from '@vulcanize/util';
import { Pool } from './Pool';
@Entity()
export class Tick {
@PrimaryColumn('varchar')
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@Column('numeric', { transformer: bigintTransformer })
tickIdx!: bigint;
@ManyToOne(() => Pool, { onDelete: 'CASCADE' })
pool!: Pool
@Column('varchar', { length: 42 })
poolAddress!: string
@Column('numeric', { transformer: graphDecimalTransformer })
price0!: GraphDecimal
@Column('numeric', { transformer: graphDecimalTransformer })
price1!: GraphDecimal
@Column('numeric', { default: 0, transformer: bigintTransformer })
liquidityGross!: bigint
@Column('numeric', { default: 0, transformer: bigintTransformer })
liquidityNet!: bigint
}

View File

@ -1,37 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { bigintTransformer } from '@vulcanize/util';
import { Pool } from './Pool';
import { Tick } from './Tick';
@Entity()
export class TickDayData {
@PrimaryColumn('varchar')
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@Column('integer')
date!: number
@ManyToOne(() => Pool, { onDelete: 'CASCADE' })
pool!: Pool;
@ManyToOne(() => Tick, { onDelete: 'CASCADE' })
tick!: Tick
@Column('numeric', { transformer: bigintTransformer })
liquidityGross!: bigint;
@Column('numeric', { transformer: bigintTransformer })
liquidityNet!: bigint;
}

View File

@ -1,63 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToMany, JoinTable } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal, bigintTransformer } from '@vulcanize/util';
import { Pool } from './Pool';
@Entity()
export class Token {
@PrimaryColumn('varchar', { length: 42 })
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@Column('varchar')
symbol!: string;
@Column('varchar')
name!: string;
@Column('numeric', { transformer: bigintTransformer })
totalSupply!: bigint;
@Column('numeric', { transformer: bigintTransformer })
decimals!: bigint;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
derivedETH!: GraphDecimal;
@Column('numeric', { default: BigInt(0), transformer: bigintTransformer })
txCount!: bigint;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
totalValueLocked!: GraphDecimal;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
totalValueLockedUSD!: GraphDecimal;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volume!: GraphDecimal;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volumeUSD!: GraphDecimal;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
untrackedVolumeUSD!: GraphDecimal;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
feesUSD!: GraphDecimal;
@ManyToMany(() => Pool)
@JoinTable()
whitelistPools!: Pool[];
// TODO: Add remaining fields when they are used.
}

View File

@ -1,60 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal } from '@vulcanize/util';
import { Token } from './Token';
@Entity()
export class TokenDayData {
@PrimaryColumn('varchar')
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@Column('integer')
date!: number
@ManyToOne(() => Token, { onDelete: 'CASCADE' })
token!: Token
@Column('numeric', { transformer: graphDecimalTransformer })
high!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
low!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
open!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
close!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
priceUSD!: GraphDecimal
@Column('numeric', { transformer: graphDecimalTransformer })
totalValueLocked!: GraphDecimal
@Column('numeric', { transformer: graphDecimalTransformer })
totalValueLockedUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volumeUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volume!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
untrackedVolumeUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
feesUSD!: GraphDecimal
}

View File

@ -1,60 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal } from '@vulcanize/util';
import { Token } from './Token';
@Entity()
export class TokenHourData {
@PrimaryColumn('varchar')
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@Column('integer')
periodStartUnix!: number
@ManyToOne(() => Token, { onDelete: 'CASCADE' })
token!: Token
@Column('numeric', { transformer: graphDecimalTransformer })
high!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
low!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
open!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
close!: GraphDecimal;
@Column('numeric', { transformer: graphDecimalTransformer })
priceUSD!: GraphDecimal
@Column('numeric', { transformer: graphDecimalTransformer })
totalValueLocked!: GraphDecimal
@Column('numeric', { transformer: graphDecimalTransformer })
totalValueLockedUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volumeUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volume!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
untrackedVolumeUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
feesUSD!: GraphDecimal
}

View File

@ -1,38 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column, OneToMany } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal, bigintTransformer } from '@vulcanize/util';
import { Mint } from './Mint';
import { Burn } from './Burn';
import { Swap } from './Swap';
@Entity()
export class Transaction {
@PrimaryColumn('varchar')
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
ethPriceUSD!: GraphDecimal
@Column('numeric', { transformer: bigintTransformer })
timestamp!: bigint;
@OneToMany(() => Mint, mint => mint.transaction)
mints!: Mint[];
@OneToMany(() => Burn, burn => burn.transaction)
burns!: Burn[];
@OneToMany(() => Swap, swap => swap.transaction)
swaps!: Swap[];
}

View File

@ -1,37 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column } from 'typeorm';
import { graphDecimalTransformer, GraphDecimal, bigintTransformer } from '@vulcanize/util';
@Entity()
export class UniswapDayData {
@PrimaryColumn('varchar')
id!: string;
// https://typeorm.io/#/entities/primary-columns
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string
@Column('integer')
blockNumber!: number;
@Column('integer')
date!: number
@Column('numeric', { transformer: graphDecimalTransformer })
tvlUSD!: GraphDecimal
@Column('numeric', { default: 0, transformer: graphDecimalTransformer })
volumeUSD!: GraphDecimal
@Column('numeric', { transformer: bigintTransformer })
txCount!: bigint;
@Column('numeric', { transformer: graphDecimalTransformer, default: 0 })
volumeETH!: GraphDecimal
@Column('numeric', { transformer: graphDecimalTransformer, default: 0 })
feesUSD!: GraphDecimal
}

View File

@ -1,178 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import debug from 'debug';
import { PubSub } from 'apollo-server-express';
import { EthClient } from '@vulcanize/ipld-eth-client';
import { EventWatcher as BaseEventWatcher, EventWatcherInterface, JobQueue, QUEUE_BLOCK_PROCESSING, QUEUE_EVENT_PROCESSING, UpstreamConfig } from '@vulcanize/util';
import { Indexer } from './indexer';
const log = debug('vulcanize:events');
export interface PoolCreatedEvent {
__typename: 'PoolCreatedEvent';
token0: string;
token1: string;
fee: string;
tickSpacing: string;
pool: string;
}
export interface InitializeEvent {
__typename: 'InitializeEvent';
sqrtPriceX96: string;
tick: string;
}
export interface MintEvent {
__typename: 'MintEvent';
sender: string;
owner: string;
tickLower: string;
tickUpper: string;
amount: string;
amount0: string;
amount1: string;
}
export interface BurnEvent {
__typename: 'BurnEvent';
owner: string;
tickLower: string;
tickUpper: string;
amount: string;
amount0: string;
amount1: string;
}
export interface SwapEvent {
__typename: 'SwapEvent';
sender: string;
recipient: string;
amount0: string;
amount1: string;
sqrtPriceX96: string;
liquidity: string;
tick: string;
}
export interface IncreaseLiquidityEvent {
__typename: 'IncreaseLiquidityEvent';
tokenId: string;
liquidity: string;
amount0: string;
amount1: string;
}
export interface DecreaseLiquidityEvent {
__typename: 'DecreaseLiquidityEvent';
tokenId: string;
liquidity: string;
amount0: string;
amount1: string;
}
export interface CollectEvent {
__typename: 'CollectEvent';
tokenId: string;
recipient: string;
amount0: string;
amount1: string;
}
export interface TransferEvent {
__typename: 'TransferEvent';
from: string;
to: string;
tokenId: string;
}
export interface Block {
cid: string;
number: number;
hash: string;
timestamp: number;
parentHash: string;
}
export interface Transaction {
hash: string;
from: string;
to: string;
index: number;
}
export interface ResultEvent {
block: Block;
tx: Transaction;
contract: string;
eventIndex: number;
event: PoolCreatedEvent | InitializeEvent | MintEvent | BurnEvent | SwapEvent | IncreaseLiquidityEvent | DecreaseLiquidityEvent | CollectEvent | TransferEvent;
proof: {
data: string;
}
}
export class EventWatcher implements EventWatcherInterface {
_ethClient: EthClient
_indexer: Indexer
_subscription?: ZenObservable.Subscription
_pubsub: PubSub
_jobQueue: JobQueue
_baseEventWatcher: BaseEventWatcher
constructor (upstreamConfig: UpstreamConfig, ethClient: EthClient, indexer: Indexer, pubsub: PubSub, jobQueue: JobQueue) {
this._ethClient = ethClient;
this._indexer = indexer;
this._pubsub = pubsub;
this._jobQueue = jobQueue;
this._baseEventWatcher = new BaseEventWatcher(upstreamConfig, this._ethClient, this._indexer, this._pubsub, this._jobQueue);
}
getBlockProgressEventIterator (): AsyncIterator<any> {
return this._baseEventWatcher.getBlockProgressEventIterator();
}
async start (): Promise<void> {
assert(!this._subscription, 'subscription already started');
log('Started watching upstream events...');
await this.initBlockProcessingOnCompleteHandler();
await this.initEventProcessingOnCompleteHandler();
this._baseEventWatcher.startBlockProcessing();
}
async stop (): Promise<void> {
this._baseEventWatcher.stop();
}
async initBlockProcessingOnCompleteHandler (): Promise<void> {
await this._jobQueue.onComplete(QUEUE_BLOCK_PROCESSING, async (job) => {
const { id, data: { failed } } = job;
if (failed) {
log(`Job ${id} for queue ${QUEUE_BLOCK_PROCESSING} failed`);
return;
}
await this._baseEventWatcher.blockProcessingCompleteHandler(job);
});
}
async initEventProcessingOnCompleteHandler (): Promise<void> {
await this._jobQueue.onComplete(QUEUE_EVENT_PROCESSING, async (job) => {
const { id, data: { failed } } = job;
if (failed) {
log(`Job ${id} for queue ${QUEUE_EVENT_PROCESSING} failed`);
return;
}
await this._baseEventWatcher.eventProcessingCompleteHandler(job);
});
}
}

View File

@ -1,98 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import 'reflect-metadata';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import debug from 'debug';
import { getConfig, Config, fillBlocks, JobQueue, DEFAULT_CONFIG_PATH, initClients } from '@vulcanize/util';
import { Client as UniClient } from '@vulcanize/uni-watcher';
import { Client as ERC20Client } from '@vulcanize/erc20-watcher';
import { Database } from './database';
import { PubSub } from 'apollo-server-express';
import { Indexer } from './indexer';
import { EventWatcher } from './events';
const log = debug('vulcanize:server');
export const main = async (): Promise<any> => {
const argv = await yargs(hideBin(process.argv)).parserConfiguration({
'parse-numbers': false
}).options({
configFile: {
alias: 'f',
type: 'string',
require: true,
demandOption: true,
describe: 'configuration file path (toml)',
default: DEFAULT_CONFIG_PATH
},
startBlock: {
type: 'number',
require: true,
demandOption: true,
describe: 'Block number to start processing at'
},
endBlock: {
type: 'number',
require: true,
demandOption: true,
describe: 'Block number to stop processing at'
},
prefetch: {
type: 'boolean',
default: false,
describe: 'Block and events prefetch mode'
},
batchBlocks: {
type: 'number',
default: 10,
describe: 'Number of blocks prefetched in batch'
}
}).argv;
const config: Config = await getConfig(argv.configFile);
const { ethClient, ethProvider } = await initClients(config);
const db = new Database(config.database);
await db.init();
const { uniWatcher, tokenWatcher, ethServer: { blockDelayInMilliSecs } } = config.upstream;
const uniClient = new UniClient(uniWatcher);
const erc20Client = new ERC20Client(tokenWatcher);
// Note: In-memory pubsub works fine for now, as each watcher is a single process anyway.
// Later: https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries
const pubsub = new PubSub();
const jobQueueConfig = config.jobQueue;
assert(jobQueueConfig, 'Missing job queue config');
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;
assert(dbConnectionString, 'Missing job queue db connection string');
const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs });
await jobQueue.start();
const indexer = new Indexer(config.server, db, uniClient, erc20Client, ethClient, ethProvider, jobQueue);
const eventWatcher = new EventWatcher(config.upstream, ethClient, indexer, pubsub, jobQueue);
await fillBlocks(jobQueue, indexer, eventWatcher, blockDelayInMilliSecs, argv);
};
main().catch(err => {
log(err);
}).finally(() => {
process.exit();
});
process.on('SIGINT', () => {
log(`Exiting process ${process.pid} with code 0`);
process.exit(0);
});

View File

@ -1,622 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { expect, assert } from 'chai';
import 'mocha';
import _ from 'lodash';
import {
getConfig
} from '@vulcanize/util';
import { removeEntities } from '@vulcanize/util/test';
import { Database } from './database';
import { createTestBlockTree, insertDummyToken } from '../test/utils';
import { Block } from './events';
import { BlockProgress } from './entity/BlockProgress';
import { SyncStatus } from './entity/SyncStatus';
import { Token } from './entity/Token';
const CONFIG_FILE = './environments/test.toml';
describe('getPrevEntityVersion', () => {
let db: Database;
let blocks: Block[][];
let tail: Block;
let head: Block;
let isDbEmptyBeforeTest: boolean;
before(async () => {
// Get config.
const config = await getConfig(CONFIG_FILE);
const { database: dbConfig } = config;
assert(dbConfig, 'Missing dbConfig.');
// Initialize database.
db = new Database(dbConfig);
await db.init();
// Check if database is empty.
const isBlockProgressEmpty = await db.isEntityEmpty(BlockProgress);
const isTokenEmpty = await db.isEntityEmpty(Token);
const isSyncStatusEmpty = await db.isEntityEmpty(SyncStatus);
isDbEmptyBeforeTest = isBlockProgressEmpty && isTokenEmpty && isSyncStatusEmpty;
assert(isDbEmptyBeforeTest, 'Abort: Database not empty.');
// Create BlockProgress test data.
blocks = await createTestBlockTree(db);
tail = blocks[0][0];
head = blocks[3][10];
});
after(async () => {
if (isDbEmptyBeforeTest) {
await removeEntities(db, BlockProgress);
await removeEntities(db, SyncStatus);
}
await db.close();
});
afterEach(async () => {
await removeEntities(db, Token);
});
//
// +---+
// head----->| 21|
// +---+
// |
// |
// +---+ +---+
// | 20| | 15|------Token (token44)
// +---+ +---+
// | /
// | /
// 8 Blocks 3 Blocks
// | /
// | /
// +---+ +---+ +---+
// | 11| | 11| | 11|
// +---+ +---+ +---+
// \ | /
// \ | /
// +---+ +---+
// | 10| | 10|
// +---+ +---+
// \ |
// \ |
// +---+
// | 9 |
// +---+
// |
// |
// 7 Blocks
// |
// |
// +---+
// tail----->| 1 |------Token (token00)
// +---+ (Target)
//
it('should fetch Token in pruned region', async () => {
// Insert a Token entity at the tail.
const token00 = await insertDummyToken(db, tail);
const token44 = _.cloneDeep(token00);
token44.txCount++;
await insertDummyToken(db, blocks[4][4], token44);
const dbTx = await db.createTransactionRunner();
try {
const searchedToken = await db.getToken(dbTx, { id: token00.id, blockHash: head.hash });
expect(searchedToken).to.not.be.empty;
expect(searchedToken?.id).to.be.equal(token00.id);
expect(searchedToken?.txCount).to.be.equal(token00.txCount);
expect(searchedToken?.blockNumber).to.be.equal(token00.blockNumber);
expect(searchedToken?.blockHash).to.be.equal(token00.blockHash);
dbTx.commitTransaction();
} catch (error) {
await dbTx.rollbackTransaction();
throw error;
} finally {
await dbTx.release();
}
});
//
// +---+
// head----->| 21|
// +---+
// |
// |
// +---+ +---+
// | 20| | 15|------Token (token44)
// +---+ +---+
// | /
// | /
// 8 Blocks 3 Blocks
// | /
// | /
// +---+ +---+ +---+
// | 11| | 11| | 11|
// +---+ +---+ +---+
// \ | /
// \ | /
// +---+ +---+
// | 10| | 10|
// +---+ +---+
// \ |
// \ |
// +---+
// | 9 |
// +---+
// |
// |
// 5 Blocks
// |
// |
// +---+
// | 3 |------Token (token02)
// +---+ (Target)
// |
// |
// +---+
// | 2 |
// +---+
// |
// |
// +---+
// tail----->| 1 |------Token (token00)
// +---+
//
it('should fetch updated Token in pruned region', async () => {
// Insert a Token entity at the tail and update in pruned region.
const token00 = await insertDummyToken(db, tail);
const token02 = _.cloneDeep(token00);
token02.txCount++;
await insertDummyToken(db, blocks[0][2], token02);
const token44 = _.cloneDeep(token00);
token44.txCount++;
await insertDummyToken(db, blocks[4][4], token44);
const dbTx = await db.createTransactionRunner();
try {
const searchedToken = await db.getToken(dbTx, { id: token00.id, blockHash: head.hash });
expect(searchedToken).to.not.be.empty;
expect(searchedToken?.id).to.be.equal(token02.id);
expect(searchedToken?.txCount).to.be.equal(token02.txCount);
expect(searchedToken?.blockNumber).to.be.equal(token02.blockNumber);
expect(searchedToken?.blockHash).to.be.equal(token02.blockHash);
dbTx.commitTransaction();
} catch (error) {
await dbTx.rollbackTransaction();
throw error;
} finally {
await dbTx.release();
}
});
//
// +---+
// head----->| 21|
// +---+
// |
// |
// +---+ +---+
// | 20| | 15|------Token (token44)
// +---+ +---+
// | /
// Token (token30)-------\ | /
// (Target) -\ 8 Blocks 3 Blocks
// -\ | /
// -\ | /
// +---+ +---+ +---+
// | 11| | 11| | 11|
// +---+ +---+ +---+
// \ | /
// \ | /
// +---+ +---+
// | 10| | 10|
// +---+ +---+
// \ |
// \ |
// +---+
// | 9 |
// +---+
// |
// |
// 7 Blocks
// |
// |
// +---+
// tail----->| 1 |------Token (token00)
// +---+
//
it('should fetch the Token in frothy region', async () => {
// Insert a Token entity at tail and in the frothy region.
const token00 = await insertDummyToken(db, tail);
const token30 = _.cloneDeep(token00);
token30.txCount++;
await insertDummyToken(db, blocks[3][0], token30);
const token44 = _.cloneDeep(token00);
token44.txCount++;
await insertDummyToken(db, blocks[4][4], token44);
const dbTx = await db.createTransactionRunner();
try {
const searchedToken = await db.getToken(dbTx, { id: token00.id, blockHash: head.hash });
expect(searchedToken).to.not.be.empty;
expect(searchedToken?.id).to.be.equal(token30.id);
expect(searchedToken?.txCount).to.be.equal(token30.txCount);
expect(searchedToken?.blockNumber).to.be.equal(token30.blockNumber);
expect(searchedToken?.blockHash).to.be.equal(token30.blockHash);
dbTx.commitTransaction();
} catch (error) {
await dbTx.rollbackTransaction();
throw error;
} finally {
await dbTx.release();
}
});
//
// +---+
// head----->| 21|
// +---+
// |
// |
// +---+ +---+
// | 20| | 15|
// +---+ +---+
// | /
// Token (token30)-------\ | /
// (Target) -\ 8 Blocks 3 Blocks
// -\ | /
// -\ | /
// +---+ +---+ +---+
// Token------| 11| | 11| | 11|------Token (token40)
// (token11) +---+ +---+ +---+
// \ | /
// \ | /
// +---+ +---+
// | 10| | 10|
// +---+ +---+
// \ |
// \ |
// +---+
// | 9 |------Token (token08)
// +---+
// |
// |
// 7 Blocks
// |
// |
// +---+
// tail----->| 1 |
// +---+
//
it('should fetch the Token in frothy region (same block number)', async () => {
// Insert a Token entity in the frothy region at same block numbers.
const token08 = await insertDummyToken(db, blocks[0][8]);
const token11 = _.cloneDeep(token08);
token11.txCount++;
await insertDummyToken(db, blocks[1][1], token11);
const token30 = _.cloneDeep(token08);
token30.txCount++;
await insertDummyToken(db, blocks[3][0], token30);
const token40 = _.cloneDeep(token08);
token40.txCount++;
await insertDummyToken(db, blocks[4][0], token40);
const dbTx = await db.createTransactionRunner();
try {
const searchedToken = await db.getToken(dbTx, { id: token08.id, blockHash: head.hash });
expect(searchedToken).to.not.be.empty;
expect(searchedToken?.id).to.be.equal(token30.id);
expect(searchedToken?.txCount).to.be.equal(token30.txCount);
expect(searchedToken?.blockNumber).to.be.equal(token30.blockNumber);
expect(searchedToken?.blockHash).to.be.equal(token30.blockHash);
dbTx.commitTransaction();
} catch (error) {
await dbTx.rollbackTransaction();
throw error;
} finally {
await dbTx.release();
}
});
//
// +---+
// head----->| 21|
// +---+
// |
// |
// +---+ +---+
// | 20| | 15|------Token (token44)
// +---+ +---+
// | /
// | /
// 8 Blocks 3 Blocks
// | /
// | /
// +---+ +---+ +---+
// | 11| | 11| | 11|
// +---+ +---+ +---+
// \ | /
// \ | /
// +---+ +---+
// | 10| | 10|
// +---+ +---+
// \ |
// \ |
// +---+
// | 9 |
// +---+
// |
// |
// 7 Blocks
// |
// |
// +---+
// tail----->| 1 |
// +---+
//
it('should not fetch the Token from a side branch in frothy region', async () => {
// Insert a Token entity in the frothy region in a side branch.
const token44 = await insertDummyToken(db, blocks[4][4]);
const dbTx = await db.createTransactionRunner();
try {
const searchedToken = await db.getToken(dbTx, { id: token44.id, blockHash: head.hash });
expect(searchedToken).to.be.undefined;
dbTx.commitTransaction();
} catch (error) {
await dbTx.rollbackTransaction();
throw error;
} finally {
await dbTx.release();
}
});
//
// +---+
// head----->| 21|
// +---+
// |
// |
// +---+ +---+
// | 20| | 15|------TokenA (tokenA44)
// +---+ +---+
// | /
// | /
// 8 Blocks 3 Blocks
// | /
// | /
// +---+ +---+ +---+
// | 11| | 11| | 11|
// +---+ +---+ +---+
// \ | /
// \ | /
// +---+ +---+
// | 10| | 10|
// +---+ +---+
// \ |
// \ |
// +---+
// | 9 |
// +---+
// |
// |
// 6 Blocks
// |
// |
// +---+
// | 2 |------TokenB
// +---+
// |
// |
// +---+
// tail----->| 1 |------TokenA (tokenA00)
// +---+ (Target)
//
it('should fetch Token in pruned region (multiple tokens)', async () => {
// Insert multiple Token entities in the pruned region.
const tokenA00 = await insertDummyToken(db, tail);
await insertDummyToken(db, blocks[0][1]);
const tokenA44 = _.cloneDeep(tokenA00);
tokenA44.txCount++;
await insertDummyToken(db, blocks[4][4], tokenA44);
const dbTx = await db.createTransactionRunner();
try {
const searchedToken = await db.getToken(dbTx, { id: tokenA00.id, blockHash: head.hash });
expect(searchedToken).to.not.be.empty;
expect(searchedToken?.id).to.be.equal(tokenA00.id);
expect(searchedToken?.txCount).to.be.equal(tokenA00.txCount);
expect(searchedToken?.blockNumber).to.be.equal(tokenA00.blockNumber);
expect(searchedToken?.blockHash).to.be.equal(tokenA00.blockHash);
dbTx.commitTransaction();
} catch (error) {
await dbTx.rollbackTransaction();
throw error;
} finally {
await dbTx.release();
}
});
//
// +---+
// head----->| 21|
// +---+
// |
// |
// +---+ +---+
// TokenB (tokenB39)------| 20| | 15|------TokenA (tokenA44)
// +---+ +---+
// | /
// TokenA (tokenA30)-------\ | /
// (Target) -\ 8 Blocks 3 Blocks
// -\ | /
// -\ | /
// +---+ +---+ +---+
// | 11| | 11| | 11|
// +---+ +---+ +---+
// \ | /
// \ | /
// +---+ +---+
// | 10| | 10|
// +---+ +---+
// \ |
// \ |
// +---+
// | 9 |
// +---+
// |
// |
// 6 Blocks
// |
// |
// +---+
// | 2 |------TokenB (tokenB01)
// +---+
// |
// |
// +---+
// tail----->| 1 |------TokenA (tokenA00)
// +---+
//
it('should fetch the Token in frothy region (multiple tokens)', async () => {
// Insert multiple Token entities in the pruned region and in the frothy region.
const tokenA00 = await insertDummyToken(db, tail);
const tokenB01 = await insertDummyToken(db, blocks[0][1]);
const tokenA30 = _.cloneDeep(tokenA00);
tokenA30.txCount++;
await insertDummyToken(db, blocks[3][0], tokenA30);
const tokenA44 = _.cloneDeep(tokenA00);
tokenA44.txCount++;
await insertDummyToken(db, blocks[4][4], tokenA44);
const tokenB39 = _.cloneDeep(tokenB01);
tokenB39.txCount++;
await insertDummyToken(db, blocks[3][9], tokenB39);
const dbTx = await db.createTransactionRunner();
try {
const searchedToken = await db.getToken(dbTx, { id: tokenA00.id, blockHash: head.hash });
expect(searchedToken).to.not.be.empty;
expect(searchedToken?.id).to.be.equal(tokenA30.id);
expect(searchedToken?.txCount).to.be.equal(tokenA30.txCount);
expect(searchedToken?.blockNumber).to.be.equal(tokenA30.blockNumber);
expect(searchedToken?.blockHash).to.be.equal(tokenA30.blockHash);
dbTx.commitTransaction();
} catch (error) {
await dbTx.rollbackTransaction();
throw error;
} finally {
await dbTx.release();
}
});
//
// +---+
// head----->| 21|
// +---+
// |
// |
// +---+ +---+
// | 20| | 15|
// +---+ +---+
// | /
// | /
// 7 Blocks 2 Blocks
// | /
// | /
// TokenB (tokenB31) +---+ +---+
// TokenA (tokenA31)------| 12| | 12|------TokenA (tokenA41)
// (Target) +---+ +---+
// | /
// | /
// +---+ +---+ +---+
// | 11| | 11| | 11|
// +---+ +---+ +---+
// \ | /
// \ | /
// +---+ +---+
// | 10| | 10|
// +---+ +---+
// \ |
// \ |
// +---+
// | 9 |------TokenA (tokenA08)
// +---+
// |
// |
// +---+
// | 8 |------TokenB (tokenB07)
// +---+
// |
// |
// 6 Blocks
// |
// |
// +---+
// tail----->| 1 |
// +---+
//
it('should fetch the Token in frothy region (same block number) (multiple tokens)', async () => {
// Insert multiple Token entities in the frothy region at same block numbers.
const tokenB07 = await insertDummyToken(db, blocks[0][7]);
const tokenA08 = await insertDummyToken(db, blocks[0][8]);
const tokenA31 = _.cloneDeep(tokenA08);
tokenA31.txCount++;
await insertDummyToken(db, blocks[3][1], tokenA31);
const tokenB31 = _.cloneDeep(tokenB07);
tokenB31.txCount++;
await insertDummyToken(db, blocks[3][1], tokenB31);
const tokenA41 = _.cloneDeep(tokenA08);
tokenA41.txCount++;
await insertDummyToken(db, blocks[4][1], tokenA41);
const dbTx = await db.createTransactionRunner();
try {
const searchedToken = await db.getToken(dbTx, { id: tokenA08.id, blockHash: head.hash });
expect(searchedToken).to.not.be.empty;
expect(searchedToken?.id).to.be.equal(tokenA31.id);
expect(searchedToken?.txCount).to.be.equal(tokenA31.txCount);
expect(searchedToken?.blockNumber).to.be.equal(tokenA31.blockNumber);
expect(searchedToken?.blockHash).to.be.equal(tokenA31.blockHash);
dbTx.commitTransaction();
} catch (error) {
await dbTx.rollbackTransaction();
throw error;
} finally {
await dbTx.release();
}
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,120 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import 'reflect-metadata';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import debug from 'debug';
import { Client as ERC20Client } from '@vulcanize/erc20-watcher';
import { Client as UniClient } from '@vulcanize/uni-watcher';
import {
getConfig,
Config,
JobQueue,
QUEUE_BLOCK_PROCESSING,
QUEUE_EVENT_PROCESSING,
JobRunner as BaseJobRunner,
JobQueueConfig,
DEFAULT_CONFIG_PATH,
initClients,
startMetricsServer
} from '@vulcanize/util';
import { Indexer } from './indexer';
import { Database } from './database';
const log = debug('vulcanize:job-runner');
export class JobRunner {
_indexer: Indexer
_jobQueue: JobQueue
_baseJobRunner: BaseJobRunner
_jobQueueConfig: JobQueueConfig
constructor (jobQueueConfig: JobQueueConfig, indexer: Indexer, jobQueue: JobQueue) {
this._jobQueueConfig = jobQueueConfig;
this._indexer = indexer;
this._jobQueue = jobQueue;
this._baseJobRunner = new BaseJobRunner(this._jobQueueConfig, this._indexer, this._jobQueue);
}
async start (): Promise<void> {
await this.subscribeBlockProcessingQueue();
await this.subscribeEventProcessingQueue();
}
async subscribeBlockProcessingQueue (): Promise<void> {
await this._jobQueue.subscribe(QUEUE_BLOCK_PROCESSING, async (job) => {
await this._baseJobRunner.processBlock(job);
await this._jobQueue.markComplete(job);
});
}
async subscribeEventProcessingQueue (): Promise<void> {
await this._jobQueue.subscribe(QUEUE_EVENT_PROCESSING, async (job) => {
await this._baseJobRunner.processEvent(job);
});
}
}
export const main = async (): Promise<any> => {
const argv = await yargs(hideBin(process.argv))
.option('f', {
alias: 'config-file',
demandOption: true,
describe: 'configuration file path (toml)',
type: 'string',
default: DEFAULT_CONFIG_PATH
})
.argv;
const config: Config = await getConfig(argv.f);
const { ethClient, ethProvider } = await initClients(config);
const db = new Database(config.database);
await db.init();
const {
uniWatcher,
tokenWatcher
} = config.upstream;
const uniClient = new UniClient(uniWatcher);
const erc20Client = new ERC20Client(tokenWatcher);
const jobQueueConfig = config.jobQueue;
assert(jobQueueConfig, 'Missing job queue config');
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;
assert(dbConnectionString, 'Missing job queue db connection string');
const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs });
await jobQueue.start();
const indexer = new Indexer(config.server, db, uniClient, erc20Client, ethClient, ethProvider, jobQueue);
const jobRunner = new JobRunner(jobQueueConfig, indexer, jobQueue);
await jobRunner.start();
startMetricsServer(config, indexer);
};
main().then(() => {
log('Starting job runner...');
}).catch(err => {
log(err);
});
process.on('uncaughtException', err => {
log('uncaughtException', err);
});
process.on('SIGINT', () => {
log(`Exiting process ${process.pid} with code 0`);
process.exit(0);
});

View File

@ -1,256 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import Chance from 'chance';
import { ethers } from 'ethers';
export const NO_OF_BLOCKS = 3;
export interface Entity {
blockNumber: number
id: string
[field: string]: any
}
export class Data {
static _instance: Data;
_entities: {[key: string]: Array<Entity>} = {
bundles: [],
burns: [],
transactions: [],
pools: [],
tokens: [],
factories: [],
mints: [],
swaps: [],
poolDayDatas: [],
tokenDayDatas: [],
uniswapDayDatas: [],
ticks: [],
tokenHourDatas: []
}
_chance: Chance.Chance
constructor () {
this._chance = new Chance();
this._generateData();
}
static getInstance (): Data {
if (!this._instance) {
this._instance = new Data();
}
return this._instance;
}
get entities (): {[key: string]: Array<Entity>} {
return this._entities;
}
_generateData (): void {
const factoryAddress = this._getRandomAddress();
// Generate data for each block.
Array.from(Array(NO_OF_BLOCKS))
.forEach((_, blockNumber) => {
// Generate data for Factory.
this._entities.factories.push({
blockNumber,
id: factoryAddress,
totalFeesUSD: this._chance.floating({ min: 1, fixed: 2 }),
totalValueLockedUSD: this._chance.floating({ min: 1, fixed: 2 }),
totalVolumeUSD: this._chance.floating({ min: 1, fixed: 2 }),
txCount: this._chance.integer({ min: 1 })
});
// Generate Bundle.
this._entities.bundles.push({
blockNumber,
id: '1',
ethPriceUSD: this._chance.floating({ min: 1, fixed: 2 })
});
// Generate Pools.
Array.from(Array(3))
.forEach(() => {
const token0 = {
blockNumber: blockNumber,
id: this._getRandomAddress(),
symbol: this._chance.string({ length: 3, casing: 'upper', alpha: false }),
name: this._chance.word({ syllables: 1 }),
volume: this._chance.integer({ min: 1 }),
volumeUSD: this._chance.floating({ min: 1, fixed: 2 }),
feesUSD: this._chance.floating({ min: 1, fixed: 2 }),
txCount: this._chance.integer({ min: 1 }),
totalValueLocked: this._chance.integer({ min: 1 }),
totalValueLockedUSD: this._chance.floating({ min: 1, fixed: 2 }),
derivedETH: this._chance.floating({ min: 1, fixed: 2 })
};
const token1 = {
blockNumber: blockNumber,
id: this._getRandomAddress(),
symbol: this._chance.string({ length: 3, casing: 'upper', alpha: false }),
name: this._chance.word({ syllables: 1 }),
volume: this._chance.integer({ min: 1 }),
volumeUSD: this._chance.floating({ min: 1, fixed: 2 }),
feesUSD: this._chance.floating({ min: 1, fixed: 2 }),
txCount: this._chance.integer({ min: 1 }),
totalValueLocked: this._chance.integer({ min: 1 }),
totalValueLockedUSD: this._chance.floating({ min: 1, fixed: 2 }),
derivedETH: this._chance.floating({ min: 1, fixed: 2 })
};
const pool = {
blockNumber: blockNumber,
id: this._getRandomAddress(),
token0: token0.id,
token1: token1.id,
feeTier: this._chance.integer({ min: 1 }),
liquidity: this._chance.integer({ min: 1 }),
sqrtPrice: this._chance.integer({ min: 1 }),
token0Price: this._chance.floating({ min: 1, fixed: 2 }),
token1Price: this._chance.floating({ min: 1, fixed: 2 }),
tick: this._chance.integer({ min: 1 }),
volumeUSD: this._chance.floating({ min: 1, fixed: 2 }),
txCount: this._chance.integer({ min: 1 }),
totalValueLockedToken0: this._chance.integer({ min: 1 }),
totalValueLockedToken1: this._chance.integer({ min: 1 }),
totalValueLockedUSD: this._chance.floating({ min: 1, fixed: 2 })
};
const timestamp = this._chance.timestamp();
this._entities.poolDayDatas.push({
blockNumber,
date: timestamp,
id: String(timestamp),
tvlUSD: this._chance.floating({ min: 1, fixed: 2 }),
volumeUSD: this._chance.floating({ min: 1, fixed: 2 })
});
this._entities.tokenDayDatas.push(
{
blockNumber,
date: timestamp,
id: `${token0.id}-${timestamp}`,
totalValueLockedUSD: this._chance.floating({ min: 1, fixed: 2 }),
volumeUSD: this._chance.floating({ min: 1, fixed: 2 })
},
{
blockNumber,
date: timestamp,
id: `${token1.id}-${timestamp}`,
totalValueLockedUSD: this._chance.floating({ min: 1, fixed: 2 }),
volumeUSD: this._chance.floating({ min: 1, fixed: 2 })
}
);
this._entities.uniswapDayDatas.push({
blockNumber,
date: timestamp,
id: String(timestamp),
tvlUSD: this._chance.floating({ min: 1, fixed: 2 }),
volumeUSD: this._chance.floating({ min: 1, fixed: 2 })
});
this._entities.ticks.push({
blockNumber,
id: `${pool.id}#${this._chance.integer({ min: 1 })}`,
liquidityGross: this._chance.integer({ min: 1 }),
liquidityNet: this._chance.integer({ min: 1 }),
price0: this._chance.floating({ min: 1, fixed: 2 }),
price1: this._chance.floating({ min: 1, fixed: 2 }),
tickIdx: this._chance.integer({ min: 1 })
});
this._entities.tokenHourDatas.push(
{
blockNumber,
close: this._chance.floating({ min: 1, fixed: 2 }),
high: this._chance.floating({ min: 1, fixed: 2 }),
id: `${token0.id}-${timestamp}`,
low: this._chance.floating({ min: 1, fixed: 2 }),
open: this._chance.floating({ min: 1, fixed: 2 }),
periodStartUnix: timestamp
},
{
blockNumber,
close: this._chance.floating({ min: 1, fixed: 2 }),
high: this._chance.floating({ min: 1, fixed: 2 }),
id: `${token1.id}-${timestamp}`,
low: this._chance.floating({ min: 1, fixed: 2 }),
open: this._chance.floating({ min: 1, fixed: 2 }),
periodStartUnix: timestamp
}
);
this._entities.tokens.push(token0, token1);
this._entities.pools.push(pool);
// Generate Transactions.
Array.from(Array(3))
.forEach((_, transactionIndex) => {
const transactionHash = ethers.utils.hexlify(ethers.utils.randomBytes(32));
const transaction = {
blockNumber,
id: transactionHash,
timestamp: this._chance.timestamp()
};
this._entities.transactions.push(transaction);
// Generate Burns
this._entities.burns.push({
id: `${transaction.id}#${transactionIndex}`,
blockNumber,
transaction: transaction.id,
pool: pool.id,
timestamp: this._chance.timestamp(),
owner: this._getRandomAddress(),
origin: this._getRandomAddress(),
amount0: this._chance.integer({ min: 1 }),
amount1: this._chance.integer({ min: 1 }),
amountUSD: this._chance.floating({ min: 1, fixed: 2 })
});
// Generate Mints
this._entities.mints.push({
id: `${transaction.id}#${transactionIndex}`,
blockNumber,
transaction: transaction.id,
pool: pool.id,
timestamp: this._chance.timestamp(),
owner: this._getRandomAddress(),
origin: this._getRandomAddress(),
amount0: this._chance.integer({ min: 1 }),
amount1: this._chance.integer({ min: 1 }),
amountUSD: this._chance.floating({ min: 1, fixed: 2 }),
sender: this._getRandomAddress()
});
// Generate Swaps
this._entities.swaps.push({
id: `${transaction.id}#${transactionIndex}`,
blockNumber,
transaction: transaction.id,
pool: pool.id,
timestamp: this._chance.timestamp(),
origin: this._getRandomAddress(),
amount0: this._chance.integer({ min: 1 }),
amount1: this._chance.integer({ min: 1 }),
amountUSD: this._chance.floating({ min: 1, fixed: 2 })
});
});
});
});
}
_getRandomAddress (): string {
return ethers.utils.hexlify(ethers.utils.randomBytes(20));
}
}

View File

@ -1,462 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
/* eslint-disable camelcase */
import debug from 'debug';
import BigInt from 'apollo-type-bigint';
import { BlockHeight, OrderDirection } from '@vulcanize/util';
import { Data, Entity, NO_OF_BLOCKS } from './data';
const log = debug('vulcanize:test');
enum BurnOrderBy {
timestamp
}
interface BurnFilter {
pool: string;
token0: string;
token1: string;
}
enum MintOrderBy {
timestamp
}
interface MintFilter {
pool: string;
token0: string;
token1: string;
}
enum PoolOrderBy {
totalValueLockedUSD
}
interface PoolFilter {
id: string;
id_in: [string];
token0: string;
token0_in: [string];
token1: string;
token1_in: [string];
}
enum TokenOrderBy {
totalValueLockedUSD
}
interface TokenFilter {
id: string;
id_in: [string];
name_contains: string;
symbol_contains: string;
}
enum TransactionOrderBy {
timestamp
}
interface SwapFilter {
pool: string;
token0: string;
token1: string;
}
enum SwapOrderBy {
timestamp
}
enum DayDataOrderBy {
date
}
interface DayDataFilter {
date_gt: number;
pool: string;
}
interface TickFilter {
poolAddress: string;
tickIdx_gte: number;
tickIdx_lte: number;
}
enum TokenHourDataOrderBy {
periodStartUnix
}
interface TokenHourDataFilter {
periodStartUnix_gt: number;
token: string;
}
export const createResolvers = async (): Promise<any> => {
const latestBlockNumber = NO_OF_BLOCKS - 1;
const data = Data.getInstance();
const { bundles, burns, pools, transactions, factories, mints, tokens, swaps, poolDayDatas, tokenDayDatas, uniswapDayDatas, ticks, tokenHourDatas } = data.entities;
return {
BigInt: new BigInt('bigInt'),
Query: {
bundle: (_: any, { id: bundleId, block }: { id: string, block: BlockHeight }) => {
log('bundle', bundleId, block);
const res = bundles.find((bundle: Entity) => bundle.blockNumber === block.number && bundle.id === bundleId);
if (res) {
const { ethPriceUSD, id } = res;
return { ethPriceUSD, id };
}
},
bundles: (_: any, { first, block }: { first: number, block: BlockHeight }) => {
log('bundles', first, block);
const res = bundles.filter((bundle: Entity) => bundle.blockNumber === block.number)
.slice(0, first)
.map(({ ethPriceUSD, id }) => ({ ethPriceUSD, id }));
return res;
},
burns: (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: BurnOrderBy, orderDirection: OrderDirection, where: BurnFilter }) => {
log('burns', first, orderBy, orderDirection, where);
const res = burns.filter((burn: Entity) => {
if (burn.blockNumber === latestBlockNumber) {
return Object.entries(where || {})
.every(([field, value]) => burn[field] === value);
}
return false;
}).slice(0, first)
.sort((a: any, b: any) => {
a = a[orderBy];
b = b[orderBy];
return orderDirection === OrderDirection.asc ? (a - b) : (b - a);
})
.map(burn => {
return {
...burn,
pool: pools.find(pool => pool.id === burn.pool),
transaction: transactions.find(transaction => transaction.id === burn.transaction)
};
});
return res;
},
factories: (_: any, { first, block }: { first: number, block: BlockHeight }) => {
log('factories', first, block);
const res = factories.filter((factory: Entity) => factory.blockNumber === block.number)
.slice(0, first);
return res;
},
mints: (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: MintOrderBy, orderDirection: OrderDirection, where: MintFilter }) => {
log('mints', first, orderBy, orderDirection, where);
const res = mints.filter((mint: Entity) => {
if (mint.blockNumber === latestBlockNumber) {
return Object.entries(where || {})
.every(([field, value]) => mint[field] === value);
}
return false;
}).slice(0, first)
.sort((a: any, b: any) => {
a = a[orderBy];
b = b[orderBy];
return orderDirection === OrderDirection.asc ? (a - b) : (b - a);
})
.map(mint => {
return {
...mint,
pool: pools.find(pool => pool.id === mint.pool),
transaction: transactions.find(transaction => transaction.id === mint.transaction)
};
});
return res;
},
pool: (_: any, { id: poolId }: { id: string }) => {
log('pool', poolId);
const res = pools.find((pool: Entity) => pool.id === poolId);
if (res) {
return {
...res,
token0: tokens.find(token => token.id === res.token0),
token1: tokens.find(token => token.id === res.token1)
};
}
},
pools: (_: any, { first, orderBy, orderDirection, where, block }: { first: number, orderBy: PoolOrderBy, orderDirection: OrderDirection, where: PoolFilter, block: BlockHeight }) => {
log('pools', first, orderBy, orderDirection, where, block);
const res = pools.filter((pool: Entity) => {
if (pool.blockNumber === latestBlockNumber) {
return Object.entries(where || {})
.every(([filter, value]) => {
if (filter.endsWith('_in')) {
const field = filter.substring(0, filter.length - 3);
return value.some((el: any) => el === pool[field]);
}
return pool[filter] === value;
});
}
return false;
}).slice(0, first)
.sort((a: any, b: any) => {
a = a[orderBy];
b = b[orderBy];
return orderDirection === OrderDirection.asc ? (a - b) : (b - a);
})
.map(pool => {
return {
...pool,
token0: tokens.find(token => token.id === pool.token0),
token1: tokens.find(token => token.id === pool.token1)
};
});
return res;
},
token: (_: any, { id: tokenId, block }: { id: string, block: BlockHeight }) => {
log('token', tokenId, block);
const res = tokens.find((token: Entity) => token.blockNumber === block.number && token.id === tokenId);
return res;
},
tokens: (_: any, { orderBy, orderDirection, where }: { orderBy: TokenOrderBy, orderDirection: OrderDirection, where: TokenFilter }) => {
log('tokens', orderBy, orderDirection, where);
const res = tokens.filter((token: Entity) => {
if (token.blockNumber === latestBlockNumber) {
return Object.entries(where || {})
.every(([filter, value]) => {
if (filter.endsWith('_in')) {
const field = filter.substring(0, filter.length - 3);
return value.some((el: any) => el === token[field]);
}
return token[filter] === value;
});
}
return false;
}).sort((a: any, b: any) => {
a = a[orderBy];
b = b[orderBy];
return orderDirection === OrderDirection.asc ? (a - b) : (b - a);
});
return res;
},
transactions: (_: any, { first, orderBy, orderDirection }: { first: number, orderBy: TransactionOrderBy, orderDirection: OrderDirection }) => {
log('transactions', first, orderBy, orderDirection);
const res = transactions.filter((transaction: Entity) => transaction.blockNumber === latestBlockNumber)
.slice(0, first)
.sort((a: any, b: any) => {
a = a[orderBy];
b = b[orderBy];
return orderDirection === OrderDirection.asc ? (a - b) : (b - a);
})
.map(transaction => {
return {
...transaction,
burns: burns.filter(burn => burn.transaction === transaction.id),
mints: mints.filter(mint => mint.transaction === transaction.id),
swaps: swaps.filter(swap => swap.transaction === transaction.id)
};
});
return res;
},
swaps: (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: SwapOrderBy, orderDirection: OrderDirection, where: SwapFilter }) => {
log('swaps', first, orderBy, orderDirection, where);
const res = swaps.filter((swap: Entity) => {
if (swap.blockNumber === latestBlockNumber) {
return Object.entries(where || {})
.every(([field, value]) => swap[field] === value);
}
return false;
}).slice(0, first)
.sort((a: any, b: any) => {
a = a[orderBy];
b = b[orderBy];
return orderDirection === OrderDirection.asc ? (a - b) : (b - a);
})
.map(swap => {
return {
...swap,
pool: pools.find(pool => pool.id === swap.pool),
transaction: transactions.find(transaction => transaction.id === swap.transaction)
};
});
return res;
},
poolDayDatas: (_: any, { skip, first, orderBy, orderDirection, where }: { skip: number, first: number, orderBy: DayDataOrderBy, orderDirection: OrderDirection, where: DayDataFilter }) => {
log('poolDayDatas', skip, first, orderBy, orderDirection, where);
const res = poolDayDatas.filter((poolDayData: Entity) => {
if (poolDayData.blockNumber === latestBlockNumber) {
return Object.entries(where || {})
.every(([filter, value]) => {
if (filter.endsWith('_gt')) {
const field = filter.substring(0, filter.length - 3);
return poolDayData[field] > value;
}
return poolDayData[filter] === value;
});
}
return false;
}).slice(skip, skip + first)
.sort((a: any, b: any) => {
a = a[orderBy];
b = b[orderBy];
return orderDirection === OrderDirection.asc ? (a - b) : (b - a);
});
return res;
},
tokenDayDatas: (_: any, { skip, first, orderBy, orderDirection, where }: { skip: number, first: number, orderBy: DayDataOrderBy, orderDirection: OrderDirection, where: DayDataFilter }) => {
log('tokenDayDatas', skip, first, orderBy, orderDirection, where);
const res = tokenDayDatas.filter((tokenDayData: Entity) => {
if (tokenDayData.blockNumber === latestBlockNumber) {
return Object.entries(where || {})
.every(([filter, value]) => {
if (filter.endsWith('_gt')) {
const field = filter.substring(0, filter.length - 3);
return tokenDayData[field] > value;
}
return tokenDayData[filter] === value;
});
}
return false;
}).slice(skip, skip + first)
.sort((a: any, b: any) => {
a = a[orderBy];
b = b[orderBy];
return orderDirection === OrderDirection.asc ? (a - b) : (b - a);
});
return res;
},
uniswapDayDatas: (_: any, { skip, first, orderBy, orderDirection, where }: { skip: number, first: number, orderBy: DayDataOrderBy, orderDirection: OrderDirection, where: DayDataFilter }) => {
log('uniswapDayDatas', skip, first, orderBy, orderDirection, where);
const res = uniswapDayDatas.filter((uniswapDayData: Entity) => {
if (uniswapDayData.blockNumber === latestBlockNumber) {
return Object.entries(where || {})
.every(([filter, value]) => {
if (filter.endsWith('_gt')) {
const field = filter.substring(0, filter.length - 3);
return uniswapDayData[field] > value;
}
return uniswapDayData[filter] === value;
});
}
return false;
}).slice(skip, skip + first)
.sort((a: any, b: any) => {
a = a[orderBy];
b = b[orderBy];
return orderDirection === OrderDirection.asc ? (a - b) : (b - a);
});
return res;
},
ticks: (_: any, { skip, first, where, block }: { skip: number, first: number, where: TickFilter, block: BlockHeight }) => {
log('ticks', skip, first, where, block);
const res = ticks.filter((tick: Entity) => {
if (tick.blockNumber === block.number) {
return Object.entries(where || {})
.every(([filter, value]) => {
if (filter.endsWith('_gte')) {
const field = filter.substring(0, filter.length - 3);
return tick[field] >= value;
}
if (filter.endsWith('_lte')) {
const field = filter.substring(0, filter.length - 3);
return tick[field] <= value;
}
return tick[filter] === value;
});
}
return false;
}).slice(skip, skip + first);
return res;
},
tokenHourDatas: (_: any, { skip, first, orderBy, orderDirection, where }: { skip: number, first: number, orderBy: TokenHourDataOrderBy, orderDirection: OrderDirection, where: TokenHourDataFilter }) => {
log('tokenHourDatas', skip, first, orderBy, orderDirection, where);
const res = tokenHourDatas.filter((tokenHourData: Entity) => {
if (tokenHourData.blockNumber === latestBlockNumber) {
return Object.entries(where || {})
.every(([filter, value]) => {
if (filter.endsWith('_gt')) {
const field = filter.substring(0, filter.length - 3);
return tokenHourData[field] > value;
}
return tokenHourData[filter] === value;
});
}
return false;
}).slice(skip, skip + first)
.sort((a: any, b: any) => {
a = a[orderBy];
b = b[orderBy];
return orderDirection === OrderDirection.asc ? (a - b) : (b - a);
});
return res;
}
}
};
};

View File

@ -1,29 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import 'mocha';
import { expect } from 'chai';
import { GraphQLClient } from 'graphql-request';
import { queryBundles } from '../queries';
import { Data } from './data';
describe('server', () => {
const client = new GraphQLClient('http://localhost:3004/graphql');
const data = Data.getInstance();
it('query bundle', async () => {
const { bundles } = data.entities;
expect(bundles.length).to.be.greaterThan(0);
for (let i = 0; i < bundles.length; i++) {
const { id, blockNumber, ethPriceUSD } = bundles[i];
// Bundle query.
const [bundle] = await client.request(queryBundles, { first: 1, block: { number: blockNumber } });
expect(bundle.id).to.equal(id);
expect(bundle.ethPriceUSD).to.equal(ethPriceUSD);
}
});
});

View File

@ -1,283 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { gql } from 'graphql-request';
const resultPool = `
{
id,
feeTier,
liquidity,
sqrtPrice,
tick,
token0 {
id
},
token0Price,
token1 {
id
},
token1Price,
totalValueLockedToken0,
totalValueLockedToken1,
totalValueLockedUSD,
txCount,
volumeUSD,
}
`;
export const queryToken = gql`
query queryToken($id: ID!, $block: Block_height) {
token(id: $id, block: $block) {
derivedETH
feesUSD
id
name
symbol
totalValueLocked
totalValueLockedUSD
txCount
volume
volumeUSD
}
}`;
export const queryFactories = gql`
query queryFactories($block: Block_height, $first: Int) {
factories(first: $first, block: $block) {
id
totalFeesUSD
totalValueLockedUSD
totalVolumeUSD
txCount
}
}`;
export const queryBundles = gql`
query queryBundles($block: Block_height, $first: Int) {
bundles(first: $first, block: $block) {
id
ethPriceUSD
}
}`;
// Getting Pool by id.
export const queryPoolById = gql`
query queryPoolById($id: ID!) {
pool(id: $id)
${resultPool}
}`;
export const queryTicks = gql`
query queryTicks($skip: Int, $first: Int, $where: Tick_filter, $block: Block_height) {
ticks(skip: $skip, first: $first, where: $where, block: $block) {
id
liquidityGross
liquidityNet
price0
price1
tickIdx
}
}`;
// Getting Pool(s).
export const queryPools = gql`
query queryPools($where: Pool_filter, $first: Int, $orderBy: Pool_orderBy, $orderDirection: OrderDirection) {
pools(where: $where, first: $first, orderBy: $orderBy, orderDirection: $orderDirection)
${resultPool}
}`;
// Getting UniswapDayData(s).
export const queryUniswapDayDatas = gql`
query queryUniswapDayDatas($first: Int, $skip: Int, $orderBy: UniswapDayData_orderBy, $orderDirection: OrderDirection, $where: UniswapDayData_filter) {
uniswapDayDatas(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: $orderDirection, where: $where) {
id,
date,
tvlUSD,
volumeUSD
}
}`;
// Getting PoolDayData(s).
export const queryPoolDayDatas = gql`
query queryPoolDayDatas($first: Int, $skip: Int, $orderBy: PoolDayData_orderBy, $orderDirection: OrderDirection, $where: PoolDayData_filter) {
poolDayDatas(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: $orderDirection, where: $where) {
id,
date,
tvlUSD,
volumeUSD
}
}`;
// Getting TokenDayDatas(s).
export const queryTokenDayDatas = gql`
query queryTokenDayData($first: Int, $skip: Int, $orderBy: TokenDayData_orderBy, $orderDirection: OrderDirection, $where: TokenDayData_filter) {
tokenDayDatas(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: $orderDirection, where: $where) {
id,
date,
totalValueLockedUSD,
volumeUSD
}
}`;
// Getting TokenDayDatas(s).
export const queryTokenHourDatas = gql`
query queryTokenHourData($first: Int, $skip: Int, $orderBy: TokenHourData_orderBy, $orderDirection: OrderDirection, $where: TokenHourData_filter) {
tokenHourDatas(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: $orderDirection, where: $where) {
id,
low,
high,
open,
close,
periodStartUnix
}
}`;
// Getting mint(s).
export const queryMints = gql`
query queryMints(
$first: Int,
$orderBy: Mint_orderBy,
$orderDirection: OrderDirection,
$where: Mint_filter) {
mints(
first: $first,
orderBy: $orderBy,
orderDirection: $orderDirection,
where: $where) {
amount0,
amount1,
amountUSD,
id,
origin,
owner,
sender,
timestamp,
pool {
id
},
transaction {
id
}
}
}`;
// Getting burns(s).
export const queryBurns = gql`
query queryBurns(
$first: Int,
$orderBy: Burn_orderBy,
$orderDirection: OrderDirection,
$where: Burn_filter) {
burns(
first: $first,
orderBy: $orderBy,
orderDirection: $orderDirection,
where: $where) {
amount0,
amount1,
amountUSD,
id,
origin,
owner,
timestamp,
pool {
id
},
transaction {
id
}
}
}`;
// Getting swap(s) .
export const querySwaps = gql`
query querySwaps(
$first: Int,
$orderBy: Swap_orderBy,
$orderDirection: OrderDirection,
$where: Swap_filter) {
swaps(
first: $first,
orderBy: $orderBy,
orderDirection: $orderDirection,
where: $where) {
amount0,
amount1,
amountUSD,
id,
origin,
timestamp,
pool {
id
},
transaction {
id
}
}
}`;
// Getting transactions(s).
export const queryTransactions = gql`
query queryTransactions(
$first: Int,
$orderBy: Transaction_orderBy,
$mintOrderBy: Mint_orderBy,
$burnOrderBy: Burn_orderBy,
$swapOrderBy: Swap_orderBy,
$orderDirection: OrderDirection) {
transactions(
first: $first,
orderBy: $orderBy,
orderDirection: $orderDirection) {
id,
mints( first: $first, orderBy: $mintOrderBy, orderDirection: $orderDirection) {
id,
timestamp
},
burns( first: $first, orderBy: $burnOrderBy, orderDirection: $orderDirection) {
id,
timestamp
},
swaps( first: $first, orderBy: $swapOrderBy, orderDirection: $orderDirection) {
id,
timestamp
},
timestamp
}
}`;
// Getting positions.
export const queryPositions = gql`
query queryPositions($first: Int, $where: Position_filter) {
positions(first: $first, where: $where) {
id,
pool {
id
},
token0 {
id
},
token1 {
id
},
tickLower {
id
},
tickUpper {
id
},
transaction {
id
},
liquidity,
depositedToken0,
depositedToken1,
collectedFeesToken0,
collectedFeesToken1,
owner,
feeGrowthInside0LastX128,
feeGrowthInside1LastX128
}
}`;

View File

@ -1,251 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import BigInt from 'apollo-type-bigint';
import debug from 'debug';
import { GraphQLScalarType } from 'graphql';
import { BlockHeight, GraphDecimal, OrderDirection } from '@vulcanize/util';
import { Indexer } from './indexer';
import { Burn } from './entity/Burn';
import { Bundle } from './entity/Bundle';
import { Factory } from './entity/Factory';
import { Mint } from './entity/Mint';
import { PoolDayData } from './entity/PoolDayData';
import { Pool } from './entity/Pool';
import { Swap } from './entity/Swap';
import { Tick } from './entity/Tick';
import { Token } from './entity/Token';
import { TokenDayData } from './entity/TokenDayData';
import { TokenHourData } from './entity/TokenHourData';
import { Transaction } from './entity/Transaction';
import { UniswapDayData } from './entity/UniswapDayData';
import { Position } from './entity/Position';
import { EventWatcher } from './events';
const log = debug('vulcanize:resolver');
export { BlockHeight };
export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatcher): Promise<any> => {
assert(indexer);
return {
BigInt: new BigInt('bigInt'),
BigDecimal: new GraphQLScalarType({
name: 'BigDecimal',
description: 'BigDecimal custom scalar type',
parseValue (value) {
// value from the client
return new GraphDecimal(value);
},
serialize (value: GraphDecimal) {
// value sent to the client
return value.toFixed();
}
}),
ChainIndexingStatus: {
__resolveType: () => {
return 'EthereumIndexingStatus';
}
},
Subscription: {
onBlockProgressEvent: {
subscribe: () => eventWatcher.getBlockProgressEventIterator()
}
},
Query: {
bundle: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('bundle', id, block);
return indexer.getBundle(id, block);
},
bundles: async (_: any, { block = {}, first }: { first: number, block: BlockHeight }) => {
log('bundles', block, first);
return indexer.getEntities(Bundle, block, {}, { limit: first });
},
burns: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('burns', first, orderBy, orderDirection, where);
return indexer.getEntities(Burn, {}, where, { limit: first, orderBy, orderDirection }, ['burn.pool', 'burn.transaction', 'pool.token0', 'pool.token1']);
},
factories: async (_: any, { block = {}, first }: { first: number, block: BlockHeight }) => {
log('factories', block, first);
return indexer.getEntities(Factory, block, {}, { limit: first });
},
mints: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('mints', first, orderBy, orderDirection, where);
return indexer.getEntities(Mint, {}, where, { limit: first, orderBy, orderDirection }, ['mint.pool', 'mint.transaction', 'pool.token0', 'pool.token1']);
},
pool: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('pool', id, block);
return indexer.getPool(id, block);
},
poolDayDatas: async (_: any, { first, skip, orderBy, orderDirection, where }: { first: number, skip: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('poolDayDatas', first, skip, orderBy, orderDirection, where);
return indexer.getEntities(PoolDayData, {}, where, { limit: first, skip, orderBy, orderDirection });
},
pools: async (_: any, { block = {}, first, orderBy, orderDirection, where = {} }: { block: BlockHeight, first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('pools', block, first, orderBy, orderDirection, where);
return indexer.getEntities(Pool, block, where, { limit: first, orderBy, orderDirection }, ['pool.token0', 'pool.token1']);
},
swaps: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('swaps', first, orderBy, orderDirection, where);
return indexer.getEntities(Swap, {}, where, { limit: first, orderBy, orderDirection }, ['swap.pool', 'swap.transaction', 'pool.token0', 'pool.token1']);
},
ticks: async (_: any, { block = {}, first, skip, where = {} }: { block: BlockHeight, first: number, skip: number, where: { [key: string]: any } }) => {
log('ticks', block, first, skip, where);
return indexer.getEntities(Tick, block, where, { limit: first, skip });
},
token: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('token', id, block);
return indexer.getToken(id, block);
},
tokens: async (_: any, { block = {}, first, orderBy, orderDirection, where }: { block: BlockHeight, first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('tokens', orderBy, orderDirection, where);
return indexer.getEntities(Token, block, where, { limit: first, orderBy, orderDirection }, ['token.whitelistPools']);
},
tokenDayDatas: async (_: any, { first, skip, orderBy, orderDirection, where }: { first: number, skip: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('tokenDayDatas', first, skip, orderBy, orderDirection, where);
return indexer.getEntities(TokenDayData, {}, where, { limit: first, skip, orderBy, orderDirection });
},
tokenHourDatas: async (_: any, { first, skip, orderBy, orderDirection, where }: { first: number, skip: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('tokenHourDatas', first, skip, orderBy, orderDirection, where);
return indexer.getEntities(TokenHourData, {}, where, { limit: first, skip, orderBy, orderDirection });
},
transactions: async (_: any, { first, orderBy, orderDirection }: { first: number, orderBy: string, orderDirection: OrderDirection}) => {
log('transactions', first, orderBy, orderDirection);
return indexer.getEntities(
Transaction,
{},
{},
{ limit: first, orderBy, orderDirection },
[
'transaction.mints',
'transaction.burns',
'transaction.swaps',
{
property: 'mints.transaction',
alias: 'mintsTransaction'
},
{
property: 'burns.transaction',
alias: 'burnsTransaction'
},
{
property: 'swaps.transaction',
alias: 'swapsTransaction'
},
{
property: 'mints.pool',
alias: 'mintsPool'
},
{
property: 'burns.pool',
alias: 'burnsPool'
},
{
property: 'swaps.pool',
alias: 'swapsPool'
},
{
property: 'mintsPool.token0',
alias: 'mintsPoolToken0'
},
{
property: 'mintsPool.token1',
alias: 'mintsPoolToken1'
},
{
property: 'burnsPool.token0',
alias: 'burnsPoolToken0'
},
{
property: 'burnsPool.token1',
alias: 'burnsPoolToken1'
},
{
property: 'swapsPool.token0',
alias: 'swapsPoolToken0'
},
{
property: 'swapsPool.token1',
alias: 'swapsPoolToken1'
}
]
);
},
uniswapDayDatas: async (_: any, { first, skip, orderBy, orderDirection, where }: { first: number, skip: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('uniswapDayDatas', first, skip, orderBy, orderDirection, where);
return indexer.getEntities(UniswapDayData, {}, where, { limit: first, skip, orderBy, orderDirection });
},
positions: async (_: any, { first, where }: { first: number, where: { [key: string]: any } }) => {
log('positions', first, where);
return indexer.getEntities(
Position,
{},
where,
{ limit: first },
[
'position.pool',
'position.token0',
'position.token1',
'position.tickLower',
'position.tickUpper',
'position.transaction'
]
);
},
blocks: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('blocks', first, orderBy, orderDirection, where);
return indexer.getBlockEntities(where, { limit: first, orderBy, orderDirection });
},
indexingStatusForCurrentVersion: async (_: any, { subgraphName }: { subgraphName: string }) => {
log('health', subgraphName);
return indexer.getIndexingStatus();
}
}
};
};

View File

@ -1,490 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { gql } from '@apollo/client/core';
export default gql`
scalar BigDecimal
scalar BigInt
scalar Bytes
input Block_height {
hash: Bytes
number: Int
}
type Pool {
feeTier: BigInt!
id: ID!
liquidity: BigInt!
sqrtPrice: BigInt!
tick: BigInt
token0: Token!
token0Price: BigDecimal!
token1: Token!
token1Price: BigDecimal!
totalValueLockedToken0: BigDecimal!
totalValueLockedToken1: BigDecimal!
totalValueLockedUSD: BigDecimal!
txCount: BigInt!
volumeUSD: BigDecimal!
}
type PoolDayData {
date: Int!
id: ID!
tvlUSD: BigDecimal!
volumeUSD: BigDecimal!
}
type Tick {
id: ID!
liquidityGross: BigInt!
liquidityNet: BigInt!
price0: BigDecimal!
price1: BigDecimal!
tickIdx: BigInt!
}
type Mint {
amount0: BigDecimal!
amount1: BigDecimal!
amountUSD: BigDecimal
id: ID!
origin: Bytes!
owner: Bytes!
pool: Pool!
sender: Bytes
timestamp: BigInt!
transaction: Transaction!
}
type Swap {
amount0: BigDecimal!
amount1: BigDecimal!
amountUSD: BigDecimal!
id: ID!
origin: Bytes!
pool: Pool!
timestamp: BigInt!
transaction: Transaction!
}
type Burn {
amount0: BigDecimal!
amount1: BigDecimal!
amountUSD: BigDecimal
id: ID!
origin: Bytes!
owner: Bytes
pool: Pool!
timestamp: BigInt!
transaction: Transaction!
}
type UniswapDayData {
date: Int!
id: ID!
tvlUSD: BigDecimal!
volumeUSD: BigDecimal!
}
type Factory {
id: ID!
totalFeesUSD: BigDecimal!
totalValueLockedUSD: BigDecimal!
totalVolumeUSD: BigDecimal!
txCount: BigInt!
}
type Transaction {
burns(skip: Int = 0, first: Int = 100, orderBy: Burn_orderBy, orderDirection: OrderDirection, where: Burn_filter): [Burn]!
id: ID!
mints(skip: Int = 0, first: Int = 100, orderBy: Mint_orderBy, orderDirection: OrderDirection, where: Mint_filter): [Mint]!
swaps(skip: Int = 0, first: Int = 100, orderBy: Swap_orderBy, orderDirection: OrderDirection, where: Swap_filter): [Swap]!
timestamp: BigInt!
}
type Token {
decimals: BigInt!
derivedETH: BigDecimal!
feesUSD: BigDecimal!
id: ID!
name: String!
symbol: String!
totalValueLocked: BigDecimal!
totalValueLockedUSD: BigDecimal!
txCount: BigInt!
volume: BigDecimal!
volumeUSD: BigDecimal!
whitelistPools: [Pool]
}
type TokenDayData {
date: Int!
id: ID!
totalValueLockedUSD: BigDecimal!
volumeUSD: BigDecimal!
}
type Bundle {
ethPriceUSD: BigDecimal!
id: ID!
}
type TokenHourData {
close: BigDecimal!
high: BigDecimal!
id: ID!
low: BigDecimal!
open: BigDecimal!
periodStartUnix: Int!
}
type Position {
id: ID!
pool: Pool!
token0: Token!
token1: Token!
tickLower: Tick!
tickUpper: Tick!
transaction: Transaction!
liquidity: BigInt!
depositedToken0: BigDecimal!
depositedToken1: BigDecimal!
collectedFeesToken0: BigDecimal!
collectedFeesToken1: BigDecimal!
owner: Bytes!
feeGrowthInside0LastX128: BigInt!
feeGrowthInside1LastX128: BigInt!
}
type Block {
number: Int!
hash: Bytes!
timestamp: Int!
}
type BlockProgressEvent {
blockNumber: Int!
blockHash: String!
numEvents: Int!
numProcessedEvents: Int!
isComplete: Boolean!
}
enum OrderDirection {
asc
desc
}
input PoolDayData_filter {
date_gt: Int
pool: String
}
enum PoolDayData_orderBy {
date
}
input Pool_filter {
id: ID
id_in: [ID!]
token0: String
token0_in: [String!]
token1: String
token1_in: [String!]
}
enum Pool_orderBy {
totalValueLockedUSD
}
input Tick_filter {
poolAddress: String
tickIdx_gte: BigInt
tickIdx_lte: BigInt
}
input Mint_filter {
pool: String
token0: String
token1: String
}
enum Mint_orderBy {
timestamp
}
input Swap_filter {
pool: String
token0: String
token1: String
}
enum Swap_orderBy {
timestamp
}
input Burn_filter {
pool: String
token0: String
token1: String
}
enum Burn_orderBy {
timestamp
}
enum UniswapDayData_orderBy {
date
}
input UniswapDayData_filter {
date_gt: Int
}
enum Transaction_orderBy {
timestamp
}
input Token_filter {
id: ID
id_in: [ID!]
name_contains: String
symbol_contains: String
}
enum Token_orderBy {
totalValueLockedUSD
}
input TokenDayData_filter {
date_gt: Int
token: String
}
enum TokenDayData_orderBy {
date
}
input TokenHourData_filter {
periodStartUnix_gt: Int
token: String
}
enum TokenHourData_orderBy {
periodStartUnix
}
input Position_filter {
id: ID
}
input Block_filter {
timestamp_gt: Int
timestamp_lt: Int
}
enum Block_orderBy {
timestamp
}
interface ChainIndexingStatus {
chainHeadBlock: Block
latestBlock: Block
}
type EthereumIndexingStatus implements ChainIndexingStatus {
chainHeadBlock: Block
latestBlock: Block
}
enum Health {
"""Syncing normally"""
healthy
"""Syncing but with errors"""
unhealthy
"""Halted due to errors"""
failed
}
type SubgraphIndexingStatus {
synced: Boolean!
health: Health!
chains: [ChainIndexingStatus!]!
}
type Query {
bundle(
id: ID!
"""
The block at which the query should be executed. Can either be an '{ number:
Int }' containing the block number or a '{ hash: Bytes }' value containing a
block hash. Defaults to the latest block when omitted.
"""
block: Block_height
): Bundle
bundles(
first: Int = 100
"""
The block at which the query should be executed. Can either be an '{ number:
Int }' containing the block number or a '{ hash: Bytes }' value containing a
block hash. Defaults to the latest block when omitted.
"""
block: Block_height
): [Bundle!]!
burns(
first: Int = 100
orderBy: Burn_orderBy
orderDirection: OrderDirection
where: Burn_filter
): [Burn!]!
factories(
first: Int = 100
"""
The block at which the query should be executed. Can either be an '{ number:
Int }' containing the block number or a '{ hash: Bytes }' value containing a
block hash. Defaults to the latest block when omitted.
"""
block: Block_height
): [Factory!]!
mints(
first: Int = 100
orderBy: Mint_orderBy
orderDirection: OrderDirection
where: Mint_filter
): [Mint!]!
pool(
id: ID!
): Pool
poolDayDatas(
skip: Int = 0
first: Int = 100
orderBy: PoolDayData_orderBy
orderDirection: OrderDirection
where: PoolDayData_filter
): [PoolDayData!]!
pools(
first: Int = 100
orderBy: Pool_orderBy
orderDirection: OrderDirection
where: Pool_filter
"""
The block at which the query should be executed. Can either be an '{ number:
Int }' containing the block number or a '{ hash: Bytes }' value containing a
block hash. Defaults to the latest block when omitted.
"""
block: Block_height
): [Pool!]!
swaps(
first: Int = 100
orderBy: Swap_orderBy
orderDirection: OrderDirection
where: Swap_filter
): [Swap!]!
ticks(
skip: Int = 0
first: Int = 100
where: Tick_filter
"""
The block at which the query should be executed. Can either be an '{ number:
Int }' containing the block number or a '{ hash: Bytes }' value containing a
block hash. Defaults to the latest block when omitted.
"""
block: Block_height
): [Tick!]!
token(
id: ID!
"""
The block at which the query should be executed. Can either be an '{ number:
Int }' containing the block number or a '{ hash: Bytes }' value containing a
block hash. Defaults to the latest block when omitted.
"""
block: Block_height
): Token
tokenDayDatas(
skip: Int = 0
first: Int = 100
orderBy: TokenDayData_orderBy
orderDirection: OrderDirection
where: TokenDayData_filter
): [TokenDayData!]!
tokenHourDatas(
skip: Int = 0
first: Int = 100
orderBy: TokenHourData_orderBy
orderDirection: OrderDirection
where: TokenHourData_filter
): [TokenHourData!]!
tokens(
first: Int = 100
orderBy: Token_orderBy
orderDirection: OrderDirection
where: Token_filter
block: Block_height
): [Token!]!
transactions(
first: Int = 100
orderBy: Transaction_orderBy
orderDirection: OrderDirection
): [Transaction!]!
uniswapDayDatas(
skip: Int = 0
first: Int = 100
orderBy: UniswapDayData_orderBy
orderDirection: OrderDirection
where: UniswapDayData_filter
): [UniswapDayData!]!
positions(
first: Int = 100
where: Position_filter
): [Position!]!
blocks(
first: Int = 100
orderBy: Block_orderBy
orderDirection: OrderDirection
where: Block_filter
): [Block!]!
indexingStatusForCurrentVersion(
subgraphName: String
): SubgraphIndexingStatus
}
#
# Subscriptions
#
type Subscription {
# Watch for block progress events from filler process.
onBlockProgressEvent: BlockProgressEvent!
}
`;

View File

@ -1,103 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import 'reflect-metadata';
import express, { Application } from 'express';
import { ApolloServer, PubSub } from 'apollo-server-express';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import debug from 'debug';
import 'graphql-import-node';
import { createServer } from 'http';
import { Client as ERC20Client } from '@vulcanize/erc20-watcher';
import { Client as UniClient } from '@vulcanize/uni-watcher';
import { DEFAULT_CONFIG_PATH, getConfig, Config, getCustomProvider, JobQueue, initClients } from '@vulcanize/util';
import typeDefs from './schema';
import { createResolvers as createMockResolvers } from './mock/resolvers';
import { createResolvers } from './resolvers';
import { Indexer } from './indexer';
import { Database } from './database';
import { EventWatcher } from './events';
const log = debug('vulcanize:server');
export const main = async (): Promise<any> => {
const argv = await yargs(hideBin(process.argv))
.option('f', {
alias: 'config-file',
demandOption: true,
describe: 'configuration file path (toml)',
type: 'string',
default: DEFAULT_CONFIG_PATH
})
.argv;
const config: Config = await getConfig(argv.f);
const { ethClient } = await initClients(config);
const { host, port } = config.server;
const db = new Database(config.database);
await db.init();
const { uniWatcher, tokenWatcher, ethServer: { rpcProviderEndpoint } } = config.upstream;
const uniClient = new UniClient(uniWatcher);
const erc20Client = new ERC20Client(tokenWatcher);
const ethProvider = getCustomProvider(rpcProviderEndpoint);
const jobQueueConfig = config.jobQueue;
assert(jobQueueConfig, 'Missing job queue config');
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;
assert(dbConnectionString, 'Missing job queue db connection string');
const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs });
await jobQueue.start();
const indexer = new Indexer(config.server, db, uniClient, erc20Client, ethClient, ethProvider, jobQueue);
const pubSub = new PubSub();
const eventWatcher = new EventWatcher(config.upstream, ethClient, indexer, pubSub, jobQueue);
await eventWatcher.start();
const resolvers = process.env.MOCK ? await createMockResolvers() : await createResolvers(indexer, eventWatcher);
const app: Application = express();
const server = new ApolloServer({
typeDefs,
resolvers
});
await server.start();
server.applyMiddleware({ app });
const httpServer = createServer(app);
server.installSubscriptionHandlers(httpServer);
httpServer.listen(port, host, () => {
log(`Server is listening on host ${host} port ${port}`);
});
return { app, server };
};
main().then(() => {
log('Starting server...');
}).catch(err => {
log(err);
});
process.on('uncaughtException', err => {
log('uncaughtException', err);
});
process.on('SIGINT', () => {
log(`Exiting process ${process.pid} with code 0`);
process.exit(0);
});

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
// https://medium.com/@steveruiz/using-a-javascript-library-without-type-declarations-in-a-typescript-project-3643490015f3
declare module 'canonical-json'

View File

@ -1,6 +0,0 @@
{
"name": "common",
"version": "0.1.0",
"license": "AGPL-3.0",
"typings": "main.d.ts"
}

View File

@ -1,7 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { utils } from 'ethers';
export const ADDRESS_ZERO = utils.getAddress('0x0000000000000000000000000000000000000000');

View File

@ -1,76 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { BigNumber, utils } from 'ethers';
import { QueryRunner } from 'typeorm';
import { GraphDecimal } from '@vulcanize/util';
import { Transaction as TransactionEntity } from '../entity/Transaction';
import { Database } from '../database';
import { Block, Transaction } from '../events';
export const exponentToBigDecimal = (decimals: bigint): GraphDecimal => {
let bd = new GraphDecimal(1);
for (let i = 0; BigNumber.from(decimals).gte(i); i++) {
bd = bd.times(10);
}
return bd;
};
export const convertTokenToDecimal = (tokenAmount: bigint, exchangeDecimals: bigint): GraphDecimal => {
if (exchangeDecimals === BigInt(0)) {
return new GraphDecimal(tokenAmount.toString());
}
return (new GraphDecimal(tokenAmount.toString())).div(exponentToBigDecimal(exchangeDecimals));
};
export const loadTransaction = async (db: Database, dbTx: QueryRunner, event: { block: Block, tx: Transaction }): Promise<TransactionEntity> => {
const { tx, block } = event;
// Get the txHash in lowercase.
const txHash = utils.hexlify(tx.hash);
let transaction = await db.getTransaction(dbTx, { id: txHash, blockHash: block.hash });
if (!transaction) {
transaction = new TransactionEntity();
transaction.id = txHash;
}
transaction.blockNumber = block.number;
transaction.timestamp = BigInt(block.timestamp);
return db.saveTransaction(dbTx, transaction, block);
};
// Return 0 if denominator is 0 in division.
export const safeDiv = (amount0: GraphDecimal, amount1: GraphDecimal): GraphDecimal => {
if (amount1.isZero()) {
return new GraphDecimal(0);
} else {
return amount0.div(amount1);
}
};
export const bigDecimalExponated = (value: GraphDecimal, power: bigint): GraphDecimal => {
if (power === BigInt(0)) {
return new GraphDecimal(1);
}
const negativePower = power > BigInt(0);
let result = (new GraphDecimal(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 GraphDecimal(1), result);
}
return result;
};

View File

@ -1,259 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import { BigNumber, utils } from 'ethers';
import { QueryRunner } from 'typeorm';
import { Database } from '../database';
import { Factory } from '../entity/Factory';
import { PoolDayData } from '../entity/PoolDayData';
import { PoolHourData } from '../entity/PoolHourData';
import { Tick } from '../entity/Tick';
import { TickDayData } from '../entity/TickDayData';
import { Token } from '../entity/Token';
import { TokenDayData } from '../entity/TokenDayData';
import { TokenHourData } from '../entity/TokenHourData';
import { UniswapDayData } from '../entity/UniswapDayData';
import { Block } from '../events';
/**
* Tracks global aggregate data over daily windows.
* @param db
* @param event
*/
export const updateUniswapDayData = async (db: Database, dbTx: QueryRunner, event: { contractAddress: string, block: Block }): Promise<UniswapDayData> => {
const { block } = 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.getModelEntities(dbTx, Factory, { hash: block.hash }, {}, { limit: 1 });
const dayID = Math.floor(block.timestamp / 86400); // Rounded.
const dayStartTimestamp = dayID * 86400;
let uniswapDayData = await db.getUniswapDayData(dbTx, { id: dayID.toString(), blockHash: block.hash });
if (!uniswapDayData) {
uniswapDayData = new UniswapDayData();
uniswapDayData.id = dayID.toString();
uniswapDayData.date = dayStartTimestamp;
uniswapDayData.tvlUSD = factory.totalValueLockedUSD;
uniswapDayData.txCount = factory.txCount;
}
uniswapDayData.tvlUSD = factory.totalValueLockedUSD;
uniswapDayData.txCount = factory.txCount;
return db.saveUniswapDayData(dbTx, uniswapDayData, block);
};
export const updatePoolDayData = async (db: Database, dbTx: QueryRunner, event: { contractAddress: string, block: Block }): Promise<PoolDayData> => {
const { contractAddress, block } = event;
const dayID = Math.floor(block.timestamp / 86400);
const dayStartTimestamp = dayID * 86400;
const dayPoolID = utils.hexlify(contractAddress)
.concat('-')
.concat(dayID.toString());
const pool = await db.getPool(dbTx, { id: utils.hexlify(contractAddress), blockHash: block.hash });
assert(pool);
let poolDayData = await db.getPoolDayData(dbTx, { id: dayPoolID, blockHash: block.hash });
if (!poolDayData) {
poolDayData = new PoolDayData();
poolDayData.id = dayPoolID;
poolDayData.date = dayStartTimestamp;
poolDayData.pool = pool;
poolDayData.open = pool.token0Price;
poolDayData.high = pool.token0Price;
poolDayData.low = pool.token0Price;
poolDayData.close = pool.token0Price;
poolDayData = await db.savePoolDayData(dbTx, poolDayData, block);
}
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(dbTx, poolDayData, block);
return poolDayData;
};
export const updatePoolHourData = async (db: Database, dbTx: QueryRunner, event: { contractAddress: string, block: Block }): Promise<PoolHourData> => {
const { contractAddress, block } = event;
const hourIndex = Math.floor(block.timestamp / 3600); // Get unique hour within unix history.
const hourStartUnix = hourIndex * 3600; // Want the rounded effect.
const hourPoolID = utils.hexlify(contractAddress)
.concat('-')
.concat(hourIndex.toString());
const pool = await db.getPool(dbTx, { id: utils.hexlify(contractAddress), blockHash: block.hash });
assert(pool);
let poolHourData = await db.getPoolHourData(dbTx, { id: hourPoolID, blockHash: block.hash });
if (!poolHourData) {
poolHourData = new PoolHourData();
poolHourData.id = hourPoolID;
poolHourData.periodStartUnix = hourStartUnix;
poolHourData.pool = pool;
poolHourData.open = pool.token0Price;
poolHourData.high = pool.token0Price;
poolHourData.low = pool.token0Price;
poolHourData.close = pool.token0Price;
poolHourData = await db.savePoolHourData(dbTx, poolHourData, block);
}
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(dbTx, poolHourData, block);
return poolHourData;
};
export const updateTokenDayData = async (db: Database, dbTx: QueryRunner, token: Token, event: { block: Block }): Promise<TokenDayData> => {
const { block } = event;
const bundle = await db.getBundle(dbTx, { id: '1', blockHash: block.hash });
assert(bundle);
const dayID = Math.floor(block.timestamp / 86400);
const dayStartTimestamp = dayID * 86400;
const tokenDayID = token.id
.concat('-')
.concat(dayID.toString());
const tokenPrice = token.derivedETH.times(bundle.ethPriceUSD);
let tokenDayData = await db.getTokenDayData(dbTx, { id: tokenDayID, blockHash: block.hash });
if (!tokenDayData) {
tokenDayData = new TokenDayData();
tokenDayData.id = tokenDayID;
tokenDayData.date = dayStartTimestamp;
tokenDayData.token = token;
tokenDayData.open = tokenPrice;
tokenDayData.high = tokenPrice;
tokenDayData.low = tokenPrice;
tokenDayData.close = tokenPrice;
tokenDayData.priceUSD = token.derivedETH.times(bundle.ethPriceUSD);
tokenDayData.totalValueLocked = token.totalValueLocked;
tokenDayData.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(dbTx, tokenDayData, block);
};
export const updateTokenHourData = async (db: Database, dbTx: QueryRunner, token: Token, event: { block: Block }): Promise<TokenHourData> => {
const { block } = event;
const bundle = await db.getBundle(dbTx, { id: '1', blockHash: block.hash });
assert(bundle);
const hourIndex = Math.floor(block.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);
let tokenHourData = await db.getTokenHourData(dbTx, { id: tokenHourID, blockHash: block.hash });
if (!tokenHourData) {
tokenHourData = new TokenHourData();
tokenHourData.id = tokenHourID;
tokenHourData.periodStartUnix = hourStartUnix;
tokenHourData.token = token;
tokenHourData.open = tokenPrice;
tokenHourData.high = tokenPrice;
tokenHourData.low = tokenPrice;
tokenHourData.close = tokenPrice;
tokenHourData.priceUSD = tokenPrice;
tokenHourData.totalValueLocked = token.totalValueLocked;
tokenHourData.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(dbTx, tokenHourData, block);
};
export const updateTickDayData = async (db: Database, dbTx: QueryRunner, tick: Tick, event: { block: Block }): Promise<TickDayData> => {
const { block } = event;
const timestamp = block.timestamp;
const dayID = Math.floor(timestamp / 86400);
const dayStartTimestamp = dayID * 86400;
const tickDayDataID = tick.id
.concat('-')
.concat(dayID.toString());
let tickDayData = await db.getTickDayData(dbTx, { id: tickDayDataID, blockHash: block.hash });
if (!tickDayData) {
tickDayData = new TickDayData();
tickDayData.id = tickDayDataID;
tickDayData.date = dayStartTimestamp;
tickDayData.pool = tick.pool;
tickDayData.tick = tick;
}
tickDayData.liquidityGross = tick.liquidityGross;
tickDayData.liquidityNet = tick.liquidityNet;
return db.saveTickDayData(dbTx, tickDayData, block);
};

View File

@ -1,190 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import { BigNumber } from 'ethers';
import { QueryRunner } from 'typeorm';
import { GraphDecimal } from '@vulcanize/util';
import { exponentToBigDecimal, safeDiv } from '.';
import { Database } from '../database';
import { Token } from '../entity/Token';
import { Block } from '../events';
// TODO: Move constants to config.
const WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
const USDC_WETH_03_POOL = '0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8';
// Token where amounts should contribute to tracked volume and liquidity.
// Usually tokens that many tokens are paired with.
// TODO: Load whitelisted tokens from config.
export const WHITELIST_TOKENS: string[] = [
WETH_ADDRESS, // WETH
'0x6b175474e89094c44da98b954eedeac495271d0f', // DAI
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC
'0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT
'0x0000000000085d4780b73119b644ae5ecd22b376', // TUSD
'0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', // WBTC
'0x5d3a536e4d6dbd6114cc1ead35777bab948e3643', // cDAI
'0x39aa39c021dfbae8fac545936693ac917d5e7563', // cUSDC
'0x86fadb80d8d2cff3c3680819e4da99c10232ba0f', // EBASE
'0x57ab1ec28d129707052df4df418d58a2d46d5f51', // sUSD
'0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', // MKR
'0xc00e94cb662c3520282e6f5717214004a7f26888', // COMP
'0x514910771af9ca656af840dff83e8264ecf986ca', // LINK
'0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f', // SNX
'0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e', // YFI
'0x111111111117dc0aa78b770fa6a738034120c302', // 1INCH
'0xdf5e0e81dff6faf3a7e52ba697820c5e32d806a8', // yCurv
'0x956f47f50a910163d8bf957cf5846d573e7f87ca', // FEI
'0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0', // MATIC
'0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9' // AAVE
];
const STABLE_COINS: string[] = [
'0x6b175474e89094c44da98b954eedeac495271d0f',
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
'0xdac17f958d2ee523a2206206994597c13d831ec7',
'0x0000000000085d4780b73119b644ae5ecd22b376',
'0x956f47f50a910163d8bf957cf5846d573e7f87ca',
'0x4dd28568d05f09b02220b09c2cb307bfd837cb95'
];
const MINIMUM_ETH_LOCKED = new GraphDecimal(52);
const Q192 = 2 ** 192;
// Constants used in demo.
const ETH_PRICE_IN_USD = '3200.00';
export const sqrtPriceX96ToTokenPrices = (sqrtPriceX96: bigint, token0: Token, token1: Token): GraphDecimal[] => {
const num = new GraphDecimal((sqrtPriceX96 * sqrtPriceX96).toString());
const denom = new GraphDecimal(Q192.toString());
const price1 = num
.div(denom)
.times(exponentToBigDecimal(token0.decimals))
.div(exponentToBigDecimal(token1.decimals));
const price0 = safeDiv(new GraphDecimal('1'), price1);
return [price0, price1];
};
export const getEthPriceInUSD = async (db: Database, dbTx: QueryRunner, block: Block, isDemo: boolean): Promise<GraphDecimal> => {
if (isDemo) {
// For demo purpose in local development.
const ethPriceInUSD = new GraphDecimal(ETH_PRICE_IN_USD);
return ethPriceInUSD;
}
// Fetch eth prices for each stablecoin.
const usdcPool = await db.getPool(dbTx, { id: USDC_WETH_03_POOL, blockHash: block.hash }); // DAI is token0.
if (usdcPool) {
return usdcPool.token0Price;
} else {
return new GraphDecimal(0);
}
};
/**
* Search through graph to find derived Eth per token.
* @todo update to be derived ETH (add stablecoin estimates)
**/
export const findEthPerToken = async (db: Database, dbTx: QueryRunner, token: Token, isDemo: boolean): Promise<GraphDecimal> => {
if (token.id === WETH_ADDRESS || isDemo) {
return new GraphDecimal(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 = new GraphDecimal(0);
let priceSoFar = new GraphDecimal(0);
const bundle = await db.getBundle(dbTx, { id: '1' });
assert(bundle);
// hardcoded fix for incorrect rates
// if whitelist includes token - get the safe price
if (STABLE_COINS.includes(token.id)) {
priceSoFar = safeDiv(new GraphDecimal(1), bundle.ethPriceUSD);
} else {
for (let i = 0; i < whiteList.length; ++i) {
const poolAddress = whiteList[i].id;
const pool = await db.getPool(dbTx, { id: poolAddress });
assert(pool);
if (BigNumber.from(pool.liquidity).gt(0)) {
if (pool.token0.id === token.id) {
// whitelist token is token1
const token1 = pool.token1;
// get the derived ETH in pool
const ethLocked = pool.totalValueLockedToken1.times(token1.derivedETH);
if (ethLocked.gt(largestLiquidityETH) && ethLocked.gt(MINIMUM_ETH_LOCKED)) {
largestLiquidityETH = ethLocked;
// token1 per our token * Eth per token1
priceSoFar = pool.token1Price.times(token1.derivedETH);
}
}
}
if (pool.token1.id === token.id) {
const token0 = pool.token0;
// Get the derived ETH in pool.
const ethLocked = pool.totalValueLockedToken0.times(token0.derivedETH);
if (ethLocked.gt(largestLiquidityETH) && ethLocked.gt(MINIMUM_ETH_LOCKED)) {
largestLiquidityETH = ethLocked;
// token0 per our token * ETH per token0
priceSoFar = pool.token0Price.times(token0.derivedETH);
}
}
}
}
return priceSoFar; // If nothing was found return 0.
};
/**
* Accepts tokens and amounts, return tracked amount based on token whitelist.
* If one token on whitelist, return amount in that token converted to USD * 2.
* If both are, return sum of two amounts.
* If neither is, return 0.
*/
export const getTrackedAmountUSD = async (
db: Database,
dbTx: QueryRunner,
tokenAmount0: GraphDecimal,
token0: Token,
tokenAmount1: GraphDecimal,
token1: Token,
isDemo: boolean
): Promise<GraphDecimal> => {
const bundle = await db.getBundle(dbTx, { id: '1' });
assert(bundle);
const price0USD = token0.derivedETH.times(bundle.ethPriceUSD);
const price1USD = token1.derivedETH.times(bundle.ethPriceUSD);
// Both are whitelist tokens, return sum of both amounts.
// Use demo mode
if ((WHITELIST_TOKENS.includes(token0.id) && WHITELIST_TOKENS.includes(token1.id)) || isDemo) {
return tokenAmount0.times(price0USD).plus(tokenAmount1.times(price1USD));
}
// Take double value of the whitelisted token amount.
if (WHITELIST_TOKENS.includes(token0.id) && !WHITELIST_TOKENS.includes(token1.id)) {
return tokenAmount0.times(price0USD).times(new GraphDecimal('2'));
}
// Take double value of the whitelisted token amount.
if (!WHITELIST_TOKENS.includes(token0.id) && WHITELIST_TOKENS.includes(token1.id)) {
return tokenAmount1.times(price1USD).times(new GraphDecimal('2'));
}
// Neither token is on white list, tracked amount is 0.
return new GraphDecimal(0);
};

View File

@ -1,46 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { QueryRunner } from 'typeorm';
import { GraphDecimal } from '@vulcanize/util';
import { Pool } from '../entity/Pool';
import { Database } from '../database';
import { bigDecimalExponated, safeDiv } from '.';
import { Tick } from '../entity/Tick';
import { Block } from '../events';
export const createTick = async (db: Database, dbTx: QueryRunner, tickId: string, tickIdx: bigint, pool: Pool, block: Block): Promise<Tick> => {
const tick = new Tick();
tick.id = tickId;
tick.tickIdx = tickIdx;
tick.pool = pool;
tick.poolAddress = pool.id;
// 1.0001^tick is token1/token0.
const price0 = bigDecimalExponated(new GraphDecimal('1.0001'), tickIdx);
tick.price0 = price0;
tick.price1 = safeDiv(new GraphDecimal(1), price0);
return db.saveTick(dbTx, tick, block);
};
export const feeTierToTickSpacing = (feeTier: bigint): bigint => {
if (feeTier === BigInt(10000)) {
return BigInt(200);
}
if (feeTier === BigInt(3000)) {
return BigInt(60);
}
if (feeTier === BigInt(500)) {
return BigInt(10);
}
if (feeTier === BigInt(100)) {
return BigInt(1);
}
throw Error('Unexpected fee tier');
};

View File

@ -1,221 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { expect } from 'chai';
import { ethers } from 'ethers';
import _ from 'lodash';
import { OrderDirection, GraphDecimal } from '@vulcanize/util';
import { insertNDummyBlocks } from '@vulcanize/util/test';
import { Database } from '../src/database';
import { Block } from '../src/events';
import { Token } from '../src/entity/Token';
import { Client } from '../src/client';
export const checkUniswapDayData = async (client: Client): Promise<void> => {
// Checked values: date, tvlUSD.
// Unchecked values: volumeUSD.
// Get the latest UniswapDayData.
const uniswapDayDatas = await client.getUniswapDayDatas({}, 0, 1, 'date', OrderDirection.desc);
expect(uniswapDayDatas).to.not.be.empty;
const id: string = uniswapDayDatas[0].id;
const dayID = Number(id);
const date = uniswapDayDatas[0].date;
const tvlUSD = uniswapDayDatas[0].tvlUSD;
const dayStartTimestamp = dayID * 86400;
const factories = await client.getFactories(1);
const totalValueLockedUSD: string = factories[0].totalValueLockedUSD;
expect(date).to.be.equal(dayStartTimestamp);
expect(tvlUSD).to.be.equal(totalValueLockedUSD);
};
export const checkPoolDayData = async (client: Client, poolAddress: string): Promise<void> => {
// Checked values: id, date, tvlUSD.
// Unchecked values: volumeUSD.
// Get the latest PoolDayData.
const poolDayDatas = await client.getPoolDayDatas({ pool: poolAddress }, 0, 1, 'date', OrderDirection.desc);
expect(poolDayDatas).to.not.be.empty;
const dayPoolID: string = poolDayDatas[0].id;
const poolID: string = dayPoolID.split('-')[0];
const dayID = Number(dayPoolID.split('-')[1]);
const date = poolDayDatas[0].date;
const tvlUSD = poolDayDatas[0].tvlUSD;
const dayStartTimestamp = dayID * 86400;
const poolData = await client.getPoolById(poolAddress);
const totalValueLockedUSD: string = poolData.pool.totalValueLockedUSD;
expect(poolID).to.be.equal(poolAddress);
expect(date).to.be.equal(dayStartTimestamp);
expect(tvlUSD).to.be.equal(totalValueLockedUSD);
};
export const checkTokenDayData = async (client: Client, tokenAddress: string): Promise<void> => {
// Checked values: id, date, totalValueLockedUSD.
// Unchecked values: volumeUSD.
// Get the latest TokenDayData.
const tokenDayDatas = await client.getTokenDayDatas({ token: tokenAddress }, 0, 1, 'date', OrderDirection.desc);
expect(tokenDayDatas).to.not.be.empty;
const tokenDayID: string = tokenDayDatas[0].id;
const tokenID: string = tokenDayID.split('-')[0];
const dayID = Number(tokenDayID.split('-')[1]);
const date = tokenDayDatas[0].date;
const tvlUSD = tokenDayDatas[0].totalValueLockedUSD;
const dayStartTimestamp = dayID * 86400;
const tokenData = await client.getToken(tokenAddress);
const totalValueLockedUSD: string = tokenData.token.totalValueLockedUSD;
expect(tokenID).to.be.equal(tokenAddress);
expect(date).to.be.equal(dayStartTimestamp);
expect(tvlUSD).to.be.equal(totalValueLockedUSD);
};
export const checkTokenHourData = async (client: Client, tokenAddress: string): Promise<void> => {
// Checked values: id, periodStartUnix, low, high, open, close.
// Unchecked values:
// Get the latest TokenHourData.
const tokenHourDatas = await client.getTokenHourDatas({ token: tokenAddress }, 0, 1, 'periodStartUnix', OrderDirection.desc);
expect(tokenHourDatas).to.not.be.empty;
const tokenHourID: string = tokenHourDatas[0].id;
const tokenID: string = tokenHourID.split('-')[0];
const hourIndex = Number(tokenHourID.split('-')[1]);
const periodStartUnix = tokenHourDatas[0].periodStartUnix;
const low = tokenHourDatas[0].low;
const high = tokenHourDatas[0].high;
const open = tokenHourDatas[0].open;
const close = tokenHourDatas[0].close;
const hourStartUnix = hourIndex * 3600;
const tokenData = await client.getToken(tokenAddress);
const bundles = await client.getBundles(1);
const tokenPrice = new GraphDecimal(tokenData.token.derivedETH).times(bundles[0].ethPriceUSD);
expect(tokenID).to.be.equal(tokenAddress);
expect(periodStartUnix).to.be.equal(hourStartUnix);
expect(low).to.be.equal(tokenPrice.toString());
expect(high).to.be.equal(tokenPrice.toString());
expect(open).to.be.equal(tokenPrice.toString());
expect(close).to.be.equal(tokenPrice.toString());
};
export const fetchTransaction = async (client: Client): Promise<{transaction: any}> => {
// Get the latest Transaction.
// Get only the latest mint, burn and swap entity in the transaction.
const transactions = await client.getTransactions(
1,
{
orderBy: 'timestamp',
mintOrderBy: 'timestamp',
burnOrderBy: 'timestamp',
swapOrderBy: 'timestamp'
},
OrderDirection.desc
);
expect(transactions).to.not.be.empty;
const transaction = transactions[0];
expect(transaction.mints).to.be.an.instanceOf(Array);
expect(transaction.burns).to.be.an.instanceOf(Array);
expect(transaction.swaps).to.be.an.instanceOf(Array);
return transaction;
};
export const createTestBlockTree = async (db: Database): Promise<Block[][]> => {
// Create BlockProgress test data.
//
// +---+
// head----->| 21|
// +---+
// |
// |
// +---+ +---+
// | 20| | 15|
// +---+ +---+
// | /
// | /
// 8 Blocks 3 Blocks
// | /
// | /
// +---+ +---+ +---+
// | 11| | 11| | 11|
// +---+ +---+ +---+
// \ | /
// \ | /
// +---+ +---+
// | 10| | 10|
// +---+ +---+
// \ |
// \ |
// +---+
// | 9 |
// +---+
// |
// |
// 7 Blocks
// |
// |
// +---+
// tail----->| 1 |
// +---+
//
const blocks: Block[][] = [];
const firstSeg = await insertNDummyBlocks(db, 9);
const secondSeg = await insertNDummyBlocks(db, 2, _.last(firstSeg));
const thirdSeg = await insertNDummyBlocks(db, 1, _.last(firstSeg));
const fourthSeg = await insertNDummyBlocks(db, 11, _.last(thirdSeg));
const fifthSeg = await insertNDummyBlocks(db, 5, _.last(thirdSeg));
blocks.push(firstSeg);
blocks.push(secondSeg);
blocks.push(thirdSeg);
blocks.push(fourthSeg);
blocks.push(fifthSeg);
return blocks;
};
export const insertDummyToken = async (db: Database, block: Block, token?: Token): Promise<Token> => {
// Insert a dummy Token entity at block.
if (!token) {
const randomByte = ethers.utils.randomBytes(20);
const tokenAddress = ethers.utils.hexValue(randomByte);
token = new Token();
token.symbol = 'TEST';
token.name = 'TestToken';
token.id = tokenAddress;
token.totalSupply = BigInt(0);
token.decimals = BigInt(0);
}
const dbTx = await db.createTransactionRunner();
try {
token = await db.saveToken(dbTx, token, block);
dbTx.commitTransaction();
return token;
} catch (error) {
await dbTx.rollbackTransaction();
throw error;
} finally {
await dbTx.release();
}
};

View File

@ -1,77 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"lib": [ "ES5", "ES6", "ES2020" ], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
"downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
"typeRoots": [
"./src/types"
], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
"resolveJsonModule": true /* Enabling the option allows importing JSON, and validating the types in that JSON file. */
},
"include": ["src"],
"exclude": ["dist", "src/**/*.test.ts"]
}

View File

@ -1,5 +0,0 @@
# Don't lint node_modules.
node_modules
# Don't lint build output.
dist

View File

@ -1,35 +0,0 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"semistandard",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": [
"warn",
{
"allowArgumentsExplicitlyTypedAsAny": true
}
]
},
"overrides": [
{
"files": ["*.test.ts", "test/*.ts"],
"rules": {
"no-unused-expressions": "off"
}
}
]
}

View File

@ -1,10 +0,0 @@
.idea/
.vscode/
node_modules/
build/
tmp/
temp/
#Hardhat files
cache
artifacts

View File

@ -1,4 +0,0 @@
timeout: '70000'
bail: true
exit: true # TODO: Find out why the program doesn't exit on its own.
require: 'ts-node/register'

View File

@ -1,133 +0,0 @@
# Uniswap Watcher
## Setup
Create a postgres12 database for the job queue:
```
sudo su - postgres
createdb uni-watcher-job-queue
```
Enable the `pgcrypto` extension on the job queue database (https://github.com/timgit/pg-boss/blob/master/docs/usage.md#intro).
Example:
```
postgres@tesla:~$ psql -U postgres -h localhost uni-watcher-job-queue
Password for user postgres:
psql (12.7 (Ubuntu 12.7-1.pgdg18.04+1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
uni-watcher-job-queue=# CREATE EXTENSION pgcrypto;
CREATE EXTENSION
uni-watcher-job-queue=# exit
```
Create a postgres12 database for the address watcher:
```
sudo su - postgres
createdb uni-watcher
```
Update `environments/local.toml` with database connection settings for both the databases.
## Run
Build files:
```bash
$ yarn build
```
Run the server:
```bash
$ yarn server
# For development.
$ yarn server:dev
# For specifying config file.
$ yarn server -f environments/local.toml
```
Start the job runner:
```bash
$ yarn job-runner
# For development.
$ yarn job-runner:dev
# For specifying config file.
$ yarn job-runner -f environments/local.toml
```
Start watching the factory contract:
```bash
$ yarn watch:contract --address 0xContractAddress --kind <contract-kind> --startingBlock <start-block>
# For specifying config file.
$ yarn watch:contract -f environments/local.toml --address 0xContractAddress --kind <contract-kind> --startingBlock <start-block>
```
Example:
```bash
$ yarn watch:contract --address 0xfE0034a874c2707c23F91D7409E9036F5e08ac34 --kind factory --startingBlock 100
```
Start watching the NonFungiblePositionManager contract:
Example:
```bash
$ yarn watch:contract --address 0xB171168C0df9457Ff3E3D795aE25Bf4f41e2FFE3 --kind nfpm --startingBlock 100
```
To fill a block range:
```bash
yarn fill --startBlock <from-block> --endBlock <to-block>
# For specifying config file.
$ yarn fill -f environments/local.toml --startBlock <from-block> --endBlock <to-block>
```
Example:
```bash
$ yarn fill --startBlock 1000 --endBlock 2000
```
## Test
To test the watchers locally:
* Deploy the Uniswap contracts
* Watch the Factory and NonFungiblePositionManager contracts
* Send transactions to trigger events
See https://github.com/vulcanize/uniswap-v3-periphery/blob/watcher-ts/demo.md for instructions.
### Smoke test
To run a smoke test:
* Start the server and the job-runner.
* To build contracts for tests, run the command below in root of the repository:
```bash
$ yarn build:contracts
```
* Run:
```bash
$ yarn smoke-test
```

View File

@ -1,34 +0,0 @@
[server]
host = "127.0.0.1"
port = 3003
[metrics]
host = "127.0.0.1"
port = 9000
[database]
type = "postgres"
host = "localhost"
port = 5432
database = "uni-watcher"
username = "postgres"
password = "postgres"
synchronize = true
logging = false
[upstream]
[upstream.ethServer]
gqlApiEndpoint = "http://127.0.0.1:8082/graphql"
rpcProviderEndpoint = "http://127.0.0.1:8081"
blockDelayInMilliSecs = 2000
[upstream.cache]
name = "requests"
enabled = false
deleteOnStart = false
[jobQueue]
dbConnectionString = "postgres://postgres:postgres@localhost/uni-watcher-job-queue"
maxCompletionLagInSecs = 300
jobDelayInMilliSecs = 100
eventsInBatch = 50

View File

@ -1,30 +0,0 @@
[server]
host = "127.0.0.1"
port = 3003
[database]
type = "postgres"
host = "localhost"
port = 5432
database = "uni-watcher"
username = "postgres"
password = "postgres"
synchronize = true
logging = false
[upstream]
[upstream.ethServer]
gqlApiEndpoint = "http://127.0.0.1:8082/graphql"
rpcProviderEndpoint = "http://127.0.0.1:8545"
blockDelayInMilliSecs = 2000
[upstream.cache]
name = "requests"
enabled = false
deleteOnStart = false
[jobQueue]
dbConnectionString = "postgres://postgres:postgres@localhost/uni-watcher-job-queue"
maxCompletionLagInSecs = 300
jobDelayInMilliSecs = 100
eventsInBatch = 50

View File

@ -1,83 +0,0 @@
{
"name": "@vulcanize/uni-watcher",
"version": "0.1.0",
"description": "Uniswap v3 Watcher",
"private": true,
"main": "dist/index.js",
"scripts": {
"lint": "eslint .",
"test": "mocha src/**/*.test.ts",
"test:init": "ts-node test/init.ts",
"test:chain-pruning": "mocha src/chain-pruning.test.ts",
"build": "tsc",
"server": "DEBUG=vulcanize:* node --enable-source-maps dist/server.js",
"server:prof": "DEBUG=vulcanize:* node --require pprof --enable-source-maps dist/server.js",
"server:dev": "DEBUG=vulcanize:* nodemon --watch src src/server.ts",
"server:mock": "MOCK=1 nodemon src/server.ts ",
"job-runner": "DEBUG=vulcanize:* node --enable-source-maps dist/job-runner.js",
"job-runner:prof": "DEBUG=vulcanize:* node --require pprof --enable-source-maps dist/job-runner.js",
"job-runner:dev": "DEBUG=vulcanize:* nodemon --watch src src/job-runner.ts",
"smoke-test": "yarn test:init && mocha src/smoke.test.ts",
"fill": "DEBUG=vulcanize:* node --enable-source-maps dist/fill.js",
"fill:prof": "DEBUG=vulcanize:* node --require pprof --enable-source-maps dist/fill.js",
"fill:dev": "DEBUG=vulcanize:* ts-node src/fill.ts",
"watch:contract": "node --enable-source-maps dist/cli/watch-contract.js",
"watch:contract:dev": "ts-node src/cli/watch-contract.ts",
"reset": "DEBUG=vulcanize:* node --enable-source-maps dist/cli/reset.js",
"reset:dev": "DEBUG=vulcanize:* ts-node src/cli/reset.ts"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vulcanize/watcher-ts.git"
},
"author": "",
"license": "AGPL-3.0",
"bugs": {
"url": "https://github.com/vulcanize/watcher-ts/issues"
},
"homepage": "https://github.com/vulcanize/watcher-ts#readme",
"dependencies": {
"@apollo/client": "^3.3.19",
"@types/lodash": "^4.14.168",
"@vulcanize/cache": "^0.1.0",
"@vulcanize/ipld-eth-client": "^0.1.0",
"@vulcanize/solidity-mapper": "^0.1.0",
"@vulcanize/util": "^0.1.0",
"apollo-server-express": "^2.25.0",
"apollo-type-bigint": "^0.1.3",
"debug": "^4.3.1",
"ethers": "^5.4.4",
"express": "^4.17.1",
"graphql": "^15.5.0",
"graphql-import-node": "^0.0.4",
"graphql-request": "^3.4.0",
"json-bigint": "^1.0.0",
"lodash": "^4.17.21",
"reflect-metadata": "^0.1.13",
"typeorm": "^0.2.32",
"yargs": "^17.0.1"
},
"devDependencies": {
"@types/chai": "^4.2.18",
"@types/express": "^4.17.11",
"@types/json-bigint": "^1.0.0",
"@types/mocha": "^8.2.2",
"@types/yargs": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^4.25.0",
"@typescript-eslint/parser": "^4.25.0",
"@uniswap/v3-core": "1.0.0",
"chai": "^4.3.4",
"eslint": "^7.27.0",
"eslint-config-semistandard": "^15.0.1",
"eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.23.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-standard": "^5.0.0",
"mocha": "^8.4.0",
"nodemon": "^2.0.7",
"pprof": "^3.2.0",
"ts-node": "^10.0.0",
"typescript": "^4.3.2"
}
}

View File

@ -1,366 +0,0 @@
{
"abi": [
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"name": "FeeAmountEnabled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"indexed": true,
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"indexed": false,
"internalType": "address",
"name": "pool",
"type": "address"
}
],
"name": "PoolCreated",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
}
],
"name": "createPool",
"outputs": [
{
"internalType": "address",
"name": "pool",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"name": "enableFeeAmount",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"name": "feeAmountTickSpacing",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"name": "getPool",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "parameters",
"outputs": [
{
"internalType": "address",
"name": "factory",
"type": "address"
},
{
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
],
"storageLayout": {
"storage": [
{
"astId": 2840,
"contract": "contracts/UniswapV3Factory.sol:UniswapV3Factory",
"label": "parameters",
"offset": 0,
"slot": "0",
"type": "t_struct(Parameters)2836_storage"
},
{
"astId": 56,
"contract": "contracts/UniswapV3Factory.sol:UniswapV3Factory",
"label": "owner",
"offset": 0,
"slot": "3",
"type": "t_address"
},
{
"astId": 62,
"contract": "contracts/UniswapV3Factory.sol:UniswapV3Factory",
"label": "feeAmountTickSpacing",
"offset": 0,
"slot": "4",
"type": "t_mapping(t_uint24,t_int24)"
},
{
"astId": 72,
"contract": "contracts/UniswapV3Factory.sol:UniswapV3Factory",
"label": "getPool",
"offset": 0,
"slot": "5",
"type": "t_mapping(t_address,t_mapping(t_address,t_mapping(t_uint24,t_address)))"
}
],
"types": {
"t_address": {
"encoding": "inplace",
"label": "address",
"numberOfBytes": "20"
},
"t_int24": {
"encoding": "inplace",
"label": "int24",
"numberOfBytes": "3"
},
"t_mapping(t_address,t_mapping(t_address,t_mapping(t_uint24,t_address)))": {
"encoding": "mapping",
"key": "t_address",
"label": "mapping(address => mapping(address => mapping(uint24 => address)))",
"numberOfBytes": "32",
"value": "t_mapping(t_address,t_mapping(t_uint24,t_address))"
},
"t_mapping(t_address,t_mapping(t_uint24,t_address))": {
"encoding": "mapping",
"key": "t_address",
"label": "mapping(address => mapping(uint24 => address))",
"numberOfBytes": "32",
"value": "t_mapping(t_uint24,t_address)"
},
"t_mapping(t_uint24,t_address)": {
"encoding": "mapping",
"key": "t_uint24",
"label": "mapping(uint24 => address)",
"numberOfBytes": "32",
"value": "t_address"
},
"t_mapping(t_uint24,t_int24)": {
"encoding": "mapping",
"key": "t_uint24",
"label": "mapping(uint24 => int24)",
"numberOfBytes": "32",
"value": "t_int24"
},
"t_struct(Parameters)2836_storage": {
"encoding": "inplace",
"label": "struct UniswapV3PoolDeployer.Parameters",
"members": [
{
"astId": 2827,
"contract": "contracts/UniswapV3Factory.sol:UniswapV3Factory",
"label": "factory",
"offset": 0,
"slot": "0",
"type": "t_address"
},
{
"astId": 2829,
"contract": "contracts/UniswapV3Factory.sol:UniswapV3Factory",
"label": "token0",
"offset": 0,
"slot": "1",
"type": "t_address"
},
{
"astId": 2831,
"contract": "contracts/UniswapV3Factory.sol:UniswapV3Factory",
"label": "token1",
"offset": 0,
"slot": "2",
"type": "t_address"
},
{
"astId": 2833,
"contract": "contracts/UniswapV3Factory.sol:UniswapV3Factory",
"label": "fee",
"offset": 20,
"slot": "2",
"type": "t_uint24"
},
{
"astId": 2835,
"contract": "contracts/UniswapV3Factory.sol:UniswapV3Factory",
"label": "tickSpacing",
"offset": 23,
"slot": "2",
"type": "t_int24"
}
],
"numberOfBytes": "96"
},
"t_uint24": {
"encoding": "inplace",
"label": "uint24",
"numberOfBytes": "3"
}
}
}
}

View File

@ -1,988 +0,0 @@
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "Burn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"name": "Collect",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"name": "CollectProtocol",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "paid0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "paid1",
"type": "uint256"
}
],
"name": "Flash",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "observationCardinalityNextOld",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "observationCardinalityNextNew",
"type": "uint16"
}
],
"name": "IncreaseObservationCardinalityNext",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"indexed": false,
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "Initialize",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "Mint",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol0Old",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol1Old",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol0New",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol1New",
"type": "uint8"
}
],
"name": "SetFeeProtocol",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "int256",
"name": "amount0",
"type": "int256"
},
{
"indexed": false,
"internalType": "int256",
"name": "amount1",
"type": "int256"
},
{
"indexed": false,
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"indexed": false,
"internalType": "uint128",
"name": "liquidity",
"type": "uint128"
},
{
"indexed": false,
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "Swap",
"type": "event"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint128",
"name": "amount",
"type": "uint128"
}
],
"name": "burn",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint128",
"name": "amount0Requested",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1Requested",
"type": "uint128"
}
],
"name": "collect",
"outputs": [
{
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint128",
"name": "amount0Requested",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1Requested",
"type": "uint128"
}
],
"name": "collectProtocol",
"outputs": [
{
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "factory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "fee",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeGrowthGlobal0X128",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeGrowthGlobal1X128",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "flash",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "observationCardinalityNext",
"type": "uint16"
}
],
"name": "increaseObservationCardinalityNext",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "liquidity",
"outputs": [
{
"internalType": "uint128",
"name": "",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxLiquidityPerTick",
"outputs": [
{
"internalType": "uint128",
"name": "",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint128",
"name": "amount",
"type": "uint128"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "mint",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "observations",
"outputs": [
{
"internalType": "uint32",
"name": "blockTimestamp",
"type": "uint32"
},
{
"internalType": "int56",
"name": "tickCumulative",
"type": "int56"
},
{
"internalType": "uint160",
"name": "secondsPerLiquidityCumulativeX128",
"type": "uint160"
},
{
"internalType": "bool",
"name": "initialized",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32[]",
"name": "secondsAgos",
"type": "uint32[]"
}
],
"name": "observe",
"outputs": [
{
"internalType": "int56[]",
"name": "tickCumulatives",
"type": "int56[]"
},
{
"internalType": "uint160[]",
"name": "secondsPerLiquidityCumulativeX128s",
"type": "uint160[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "key",
"type": "bytes32"
}
],
"name": "positions",
"outputs": [
{
"internalType": "uint128",
"name": "_liquidity",
"type": "uint128"
},
{
"internalType": "uint256",
"name": "feeGrowthInside0LastX128",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "feeGrowthInside1LastX128",
"type": "uint256"
},
{
"internalType": "uint128",
"name": "tokensOwed0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "tokensOwed1",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolFees",
"outputs": [
{
"internalType": "uint128",
"name": "token0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "token1",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "feeProtocol0",
"type": "uint8"
},
{
"internalType": "uint8",
"name": "feeProtocol1",
"type": "uint8"
}
],
"name": "setFeeProtocol",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "slot0",
"outputs": [
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"internalType": "int24",
"name": "tick",
"type": "int24"
},
{
"internalType": "uint16",
"name": "observationIndex",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "observationCardinality",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "observationCardinalityNext",
"type": "uint16"
},
{
"internalType": "uint8",
"name": "feeProtocol",
"type": "uint8"
},
{
"internalType": "bool",
"name": "unlocked",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
}
],
"name": "snapshotCumulativesInside",
"outputs": [
{
"internalType": "int56",
"name": "tickCumulativeInside",
"type": "int56"
},
{
"internalType": "uint160",
"name": "secondsPerLiquidityInsideX128",
"type": "uint160"
},
{
"internalType": "uint32",
"name": "secondsInside",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "bool",
"name": "zeroForOne",
"type": "bool"
},
{
"internalType": "int256",
"name": "amountSpecified",
"type": "int256"
},
{
"internalType": "uint160",
"name": "sqrtPriceLimitX96",
"type": "uint160"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "swap",
"outputs": [
{
"internalType": "int256",
"name": "amount0",
"type": "int256"
},
{
"internalType": "int256",
"name": "amount1",
"type": "int256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "int16",
"name": "wordPosition",
"type": "int16"
}
],
"name": "tickBitmap",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tickSpacing",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "ticks",
"outputs": [
{
"internalType": "uint128",
"name": "liquidityGross",
"type": "uint128"
},
{
"internalType": "int128",
"name": "liquidityNet",
"type": "int128"
},
{
"internalType": "uint256",
"name": "feeGrowthOutside0X128",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "feeGrowthOutside1X128",
"type": "uint256"
},
{
"internalType": "int56",
"name": "tickCumulativeOutside",
"type": "int56"
},
{
"internalType": "uint160",
"name": "secondsPerLiquidityOutsideX128",
"type": "uint160"
},
{
"internalType": "uint32",
"name": "secondsOutside",
"type": "uint32"
},
{
"internalType": "bool",
"name": "initialized",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "token0",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "token1",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]

View File

@ -1,415 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { expect, assert } from 'chai';
import { AssertionError } from 'assert';
import 'mocha';
import _ from 'lodash';
import { getConfig, getCustomProvider, JobQueue, JobRunner, JOB_KIND_PRUNE } from '@vulcanize/util';
import { getCache } from '@vulcanize/cache';
import { EthClient } from '@vulcanize/ipld-eth-client';
import { insertNDummyBlocks, removeEntities } from '@vulcanize/util/test';
import { Indexer } from './indexer';
import { Database } from './database';
import { BlockProgress } from './entity/BlockProgress';
import { SyncStatus } from './entity/SyncStatus';
const CONFIG_FILE = './environments/test.toml';
describe('chain pruning', () => {
let db: Database;
let indexer: Indexer;
let jobRunner: JobRunner;
before(async () => {
// Get config.
const config = await getConfig(CONFIG_FILE);
const { upstream, database: dbConfig, jobQueue: jobQueueConfig } = config;
assert(dbConfig, 'Missing database config');
// Initialize database.
db = new Database(dbConfig);
await db.init();
// Check if database is empty.
const isBlockProgressEmpty = await db.isEntityEmpty(BlockProgress);
const isSyncStatusEmpty = await db.isEntityEmpty(SyncStatus);
const isDbEmptyBeforeTest = isBlockProgressEmpty && isSyncStatusEmpty;
assert(isDbEmptyBeforeTest, 'Abort: Database not empty.');
// Create an Indexer object.
assert(upstream, 'Missing upstream config');
const { ethServer: { gqlApiEndpoint, rpcProviderEndpoint }, cache: cacheConfig } = upstream;
assert(gqlApiEndpoint, 'Missing upstream ethServer.gqlApiEndpoint');
const cache = await getCache(cacheConfig);
const ethClient = new EthClient({
gqlEndpoint: gqlApiEndpoint,
cache
});
const ethProvider = getCustomProvider(rpcProviderEndpoint);
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;
assert(dbConnectionString, 'Missing job queue db connection string');
const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs });
indexer = new Indexer(config.server, db, ethClient, ethProvider, jobQueue);
assert(indexer, 'Could not create indexer object.');
jobRunner = new JobRunner(jobQueueConfig, indexer, jobQueue);
});
afterEach(async () => {
await removeEntities(db, BlockProgress);
await removeEntities(db, SyncStatus);
});
after(async () => {
await db.close();
});
//
// +---+
// head----->| 20|
// +---+
// |
// |
// +---+
// | 19|
// +---+
// |
// |
// 12 Blocks
// |
// |
// +---+
// | 6 |
// +---+
// |
// |
// +---+
// | 5 |
// +---+
// |
// |
// +---+
// | 4 | ------> Block Height to be pruned
// +---+
// |
// |
// 2 Blocks
// |
// |
// +---+
// tail----->| 1 |
// +---+
//
it('should prune a block in chain without branches', async () => {
// Create BlockProgress test data.
await insertNDummyBlocks(db, 20);
const pruneBlockHeight = 4;
// Should return only one block as there are no branches.
const blocks = await indexer.getBlocksAtHeight(pruneBlockHeight, false);
expect(blocks).to.have.lengthOf(1);
const job = { data: { kind: JOB_KIND_PRUNE, pruneBlockHeight } };
await jobRunner.processBlock(job);
// Only one canonical (not pruned) block should exist at the pruned height.
const blocksAfterPruning = await indexer.getBlocksAtHeight(pruneBlockHeight, false);
expect(blocksAfterPruning).to.have.lengthOf(1);
});
//
// +---+
// | 20|
// +---+
// |
// |
// +---+
// | 19|
// +---+
// |
// |
// 13 Blocks
// |
// |
// +---+ +---+
// | 5 | | 5 |
// +---+ +---+
// | /
// | /
// +---+ +---+ +----
// | 4 | | 4 | | 4 | ----> Block Height to be pruned
// +---+ +---+ +---+
// \ | /
// \ | /
// +---+ +---+
// | 3 | | 3 |
// +---+ +---+
// \ |
// \ |
// +---+
// | 2 |
// +---+
// |
// |
// +---+
// | 1 |
// +---+
//
it('should prune block at height with branches', async () => {
// Create BlockProgress test data.
const firstSeg = await insertNDummyBlocks(db, 2);
const secondSeg = await insertNDummyBlocks(db, 2, _.last(firstSeg));
expect(_.last(secondSeg).number).to.equal(4);
const thirdSeg = await insertNDummyBlocks(db, 1, _.last(firstSeg));
const fourthSeg = await insertNDummyBlocks(db, 2, _.last(thirdSeg));
expect(_.last(fourthSeg).number).to.equal(5);
const fifthSeg = await insertNDummyBlocks(db, 17, _.last(thirdSeg));
expect(_.last(fifthSeg).number).to.equal(20);
const expectedCanonicalBlock = fifthSeg[0];
const expectedPrunedBlocks = [secondSeg[1], fourthSeg[0]];
const pruneBlockHeight = 4;
// Should return multiple blocks that are not pruned.
const blocksBeforePruning = await indexer.getBlocksAtHeight(pruneBlockHeight, false);
expect(blocksBeforePruning).to.have.lengthOf(3);
const job = { data: { kind: JOB_KIND_PRUNE, pruneBlockHeight } };
await jobRunner.processBlock(job);
// Only one canonical (not pruned) block should exist at the pruned height.
const blocksAfterPruning = await indexer.getBlocksAtHeight(pruneBlockHeight, false);
expect(blocksAfterPruning).to.have.lengthOf(1);
// Assert that correct block is canonical.
expect(blocksAfterPruning[0].blockHash).to.equal(expectedCanonicalBlock.hash);
// Assert that correct blocks are pruned.
const prunedBlocks = await indexer.getBlocksAtHeight(pruneBlockHeight, true);
expect(prunedBlocks).to.have.lengthOf(2);
const prunedBlockHashes = prunedBlocks.map(({ blockHash }) => blockHash);
const expectedPrunedBlockHashes = expectedPrunedBlocks.map(({ hash }) => hash);
expect(prunedBlockHashes).to.have.members(expectedPrunedBlockHashes);
});
//
// +---+ +---+
// | 20| | 20|
// +---+ +---+
// | /
// | /
// +---+ +----
// | 19| | 19|
// +---+ +---+
// | /
// | /
// +----
// | 18|
// +---+
// |
// |
// +---+
// | 17|
// +---+
// |
// |
// 11 Blocks
// |
// |
// +---+ +---+
// | 5 | | 5 |
// +---+ +---+
// | /
// | /
// +---+ +----
// | 4 | | 4 | ----> Block Height to be pruned
// +---+ +---+
// | /
// | /
// +----
// | 3 |
// +---+
// |
// |
// +---+
// | 2 |
// +---+
// |
// |
// +---+
// | 1 |
// +---+
//
it('should prune block with multiple branches at chain head', async () => {
// Create BlockProgress test data.
const firstSeg = await insertNDummyBlocks(db, 3);
const secondSeg = await insertNDummyBlocks(db, 2, _.last(firstSeg));
expect(_.last(secondSeg).number).to.equal(5);
const thirdSeg = await insertNDummyBlocks(db, 15, _.last(firstSeg));
const fourthSeg = await insertNDummyBlocks(db, 2, _.last(thirdSeg));
expect(_.last(fourthSeg).number).to.equal(20);
const fifthSeg = await insertNDummyBlocks(db, 2, _.last(thirdSeg));
expect(_.last(fifthSeg).number).to.equal(20);
const expectedCanonicalBlock = thirdSeg[0];
const expectedPrunedBlock = secondSeg[0];
const pruneBlockHeight = 4;
// Should return multiple blocks that are not pruned.
const blocksBeforePruning = await indexer.getBlocksAtHeight(pruneBlockHeight, false);
expect(blocksBeforePruning).to.have.lengthOf(2);
const job = { data: { kind: JOB_KIND_PRUNE, pruneBlockHeight } };
await jobRunner.processBlock(job);
// Only one canonical (not pruned) block should exist at the pruned height.
const blocksAfterPruning = await indexer.getBlocksAtHeight(pruneBlockHeight, false);
expect(blocksAfterPruning).to.have.lengthOf(1);
expect(blocksAfterPruning[0].blockHash).to.equal(expectedCanonicalBlock.hash);
// Assert that correct blocks are pruned.
const prunedBlocks = await indexer.getBlocksAtHeight(pruneBlockHeight, true);
expect(prunedBlocks).to.have.lengthOf(1);
expect(prunedBlocks[0].blockHash).to.equal(expectedPrunedBlock.hash);
});
//
// +---+
// | 21| ----> Latest Indexed
// +---+
// |
// |
// +---+
// | 20|
// +---+
// |
// |
// 15 Blocks
// |
// |
// +---+ +---+
// | 4 | | 4 |
// +---+ +---+
// \ |
// \ |
// +---+ +---+
// | 3 | | 3 | ----> Block Height to be pruned
// +---+ +---+
// \ |
// \ |
// +---+
// | 2 |
// +---+
// |
// |
// +---+
// | 1 |
// +---+
//
it('should prune block at depth greater than max reorg depth from latest indexed block', async () => {
// Create BlockProgress test data.
const firstSeg = await insertNDummyBlocks(db, 2);
const secondSeg = await insertNDummyBlocks(db, 2, _.last(firstSeg));
expect(_.last(secondSeg).number).to.equal(4);
const thirdSeg = await insertNDummyBlocks(db, 19, _.last(firstSeg));
expect(_.last(thirdSeg).number).to.equal(21);
const expectedCanonicalBlock = thirdSeg[0];
const expectedPrunedBlock = secondSeg[0];
const pruneBlockHeight = 3;
// Should return multiple blocks that are not pruned.
const blocksBeforePruning = await indexer.getBlocksAtHeight(pruneBlockHeight, false);
expect(blocksBeforePruning).to.have.lengthOf(2);
const job = { data: { kind: JOB_KIND_PRUNE, pruneBlockHeight } };
await jobRunner.processBlock(job);
// Only one canonical (not pruned) block should exist at the pruned height.
const blocksAfterPruning = await indexer.getBlocksAtHeight(pruneBlockHeight, false);
expect(blocksAfterPruning).to.have.lengthOf(1);
expect(blocksAfterPruning[0].blockHash).to.equal(expectedCanonicalBlock.hash);
// Assert that correct blocks are pruned.
const prunedBlocks = await indexer.getBlocksAtHeight(pruneBlockHeight, true);
expect(prunedBlocks).to.have.lengthOf(1);
expect(prunedBlocks[0].blockHash).to.equal(expectedPrunedBlock.hash);
});
//
// +---+
// | 20|
// +---+
// |
// |
// +---+
// | 19|
// +---+
// |
// |
// 8 Blocks
// |
// |
// +---+ +---+
// | 10| | 10|
// +---+ +---+
// | /
// | /
// +---+ +----
// | 9 | | 9 | ----> Block Height to be pruned
// +---+ +---+
// | /
// | /
// +---+
// | 8 |
// +---+
// |
// |
// 6 Blocks
// |
// |
// +---+
// | 1 |
// +---+
//
it('should avoid pruning block in frothy region', async () => {
// Create BlockProgress test data.
const firstSeg = await insertNDummyBlocks(db, 8);
const secondSeg = await insertNDummyBlocks(db, 2, _.last(firstSeg));
expect(_.last(secondSeg).number).to.equal(10);
const thirdSeg = await insertNDummyBlocks(db, 12, _.last(firstSeg));
expect(_.last(thirdSeg).number).to.equal(20);
const pruneBlockHeight = 9;
// Should return multiple blocks that are not pruned.
const blocksBeforePruning = await indexer.getBlocksAtHeight(pruneBlockHeight, false);
expect(blocksBeforePruning).to.have.lengthOf(2);
try {
const job = { data: { kind: JOB_KIND_PRUNE, pruneBlockHeight } };
await jobRunner.processBlock(job);
expect.fail('Job Runner should throw error for pruning at frothy region');
} catch (error) {
expect(error).to.be.instanceof(AssertionError);
}
// No blocks should be pruned at frothy region.
const blocksAfterPruning = await indexer.getBlocksAtHeight(pruneBlockHeight, true);
expect(blocksAfterPruning).to.have.lengthOf(0);
});
});

View File

@ -1,22 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import debug from 'debug';
import { getConfig, resetJobs } from '@vulcanize/util';
const log = debug('vulcanize:reset-job-queue');
export const command = 'job-queue';
export const desc = 'Reset job queue';
export const builder = {};
export const handler = async (argv: any): Promise<void> => {
const config = await getConfig(argv.configFile);
await resetJobs(config);
log('Job queue reset successfully');
};

View File

@ -1,77 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import debug from 'debug';
import { MoreThan } from 'typeorm';
import assert from 'assert';
import { getConfig, initClients, resetJobs, JobQueue } from '@vulcanize/util';
import { Database } from '../../database';
import { Indexer } from '../../indexer';
import { BlockProgress } from '../../entity/BlockProgress';
const log = debug('vulcanize:reset-state');
export const command = 'state';
export const desc = 'Reset state to block number';
export const builder = {
blockNumber: {
type: 'number'
}
};
export const handler = async (argv: any): Promise<void> => {
const config = await getConfig(argv.configFile);
await resetJobs(config);
const { ethClient, ethProvider } = await initClients(config);
// Initialize database.
const db = new Database(config.database);
await db.init();
assert(config.jobQueue, 'Missing job queue config');
const { dbConnectionString, maxCompletionLagInSecs } = config.jobQueue;
assert(dbConnectionString, 'Missing job queue db connection string');
const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs });
const indexer = new Indexer(config.server, db, ethClient, ethProvider, jobQueue);
const syncStatus = await indexer.getSyncStatus();
assert(syncStatus, 'Missing syncStatus');
const blockProgresses = await indexer.getBlocksAtHeight(argv.blockNumber, false);
assert(blockProgresses.length, `No blocks at specified block number ${argv.blockNumber}`);
assert(!blockProgresses.some(block => !block.isComplete), `Incomplete block at block number ${argv.blockNumber} with unprocessed events`);
const [blockProgress] = blockProgresses;
const dbTx = await db.createTransactionRunner();
try {
await db.removeEntities(dbTx, BlockProgress, { blockNumber: MoreThan(blockProgress.blockNumber) });
if (syncStatus.latestIndexedBlockNumber > blockProgress.blockNumber) {
await indexer.updateSyncStatusIndexedBlock(blockProgress.blockHash, blockProgress.blockNumber, true);
}
if (syncStatus.latestCanonicalBlockNumber > blockProgress.blockNumber) {
await indexer.updateSyncStatusCanonicalBlock(blockProgress.blockHash, blockProgress.blockNumber, true);
}
await indexer.updateSyncStatusChainHead(blockProgress.blockHash, blockProgress.blockNumber, true);
dbTx.commitTransaction();
} catch (error) {
await dbTx.rollbackTransaction();
throw error;
} finally {
await dbTx.release();
}
log('Reset state successfully');
};

View File

@ -1,24 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import 'reflect-metadata';
import debug from 'debug';
import { getResetYargs } from '@vulcanize/util';
const log = debug('vulcanize:reset');
const main = async () => {
return getResetYargs()
.commandDir('reset-cmds', { extensions: ['js', 'ts'], exclude: /([a-zA-Z0-9\s_\\.\-:])+(.d.ts)$/ })
.demandCommand(1)
.help()
.argv;
};
main().then(() => {
process.exit();
}).catch(err => {
log(err);
});

View File

@ -1,75 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import yargs from 'yargs';
import 'reflect-metadata';
import { Config, DEFAULT_CONFIG_PATH, getConfig, initClients, JobQueue } from '@vulcanize/util';
import { Database } from '../database';
import { Indexer } from '../indexer';
(async () => {
const argv = await yargs.parserConfiguration({
'parse-numbers': false
}).options({
configFile: {
alias: 'f',
type: 'string',
require: true,
demandOption: true,
describe: 'configuration file path (toml)',
default: DEFAULT_CONFIG_PATH
},
address: {
type: 'string',
require: true,
demandOption: true,
describe: 'Address of the deployed contract'
},
kind: {
type: 'string',
require: true,
demandOption: true,
describe: 'Kind of contract (factory|pool|nfpm)'
},
checkpoint: {
type: 'boolean',
default: false,
describe: 'Turn checkpointing on'
},
startingBlock: {
type: 'number',
default: 1,
describe: 'Starting block'
}
}).argv;
const config: Config = await getConfig(argv.configFile);
const { database: dbConfig, jobQueue: jobQueueConfig } = config;
const { ethClient, ethProvider } = await initClients(config);
assert(dbConfig);
const db = new Database(dbConfig);
await db.init();
assert(jobQueueConfig, 'Missing job queue config');
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;
assert(dbConnectionString, 'Missing job queue db connection string');
const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs });
await jobQueue.start();
const indexer = new Indexer(config.server, db, ethClient, ethProvider, jobQueue);
await indexer.init();
await indexer.watchContract(argv.address, argv.kind, argv.checkpoint, argv.startingBlock);
await db.close();
await jobQueue.stop();
process.exit();
})();

View File

@ -1,139 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { gql } from '@apollo/client/core';
import { GraphQLClient, GraphQLConfig } from '@vulcanize/ipld-eth-client';
import {
queryGetPool,
queryPoolIdToPoolKey,
queryPosition,
queryEvents,
subscribeEvents,
queryGetContract,
queryEventsInRange,
queryCallGetPool,
queryPositions
} from './queries';
export class Client {
_config: GraphQLConfig;
_client: GraphQLClient;
constructor (config: GraphQLConfig) {
this._config = config;
this._client = new GraphQLClient(config);
}
async watchEvents (onNext: (value: any) => void): Promise<ZenObservable.Subscription> {
return this._client.subscribe(
gql(subscribeEvents),
({ data }) => {
onNext(data.onEvent);
}
);
}
async getEvents (blockHash: string, contract?: string): Promise<any> {
const { events } = await this._client.query(
gql(queryEvents),
{
blockHash,
contract
}
);
return events;
}
async getPosition (blockHash: string, tokenId: bigint): Promise<any> {
const { position } = await this._client.query(
gql(queryPosition),
{
blockHash,
tokenId: tokenId.toString()
}
);
return position;
}
async poolIdToPoolKey (blockHash: string, poolId: bigint): Promise<any> {
const { poolIdToPoolKey } = await this._client.query(
gql(queryPoolIdToPoolKey),
{
blockHash,
poolId: poolId.toString()
}
);
return poolIdToPoolKey;
}
async getPool (blockHash: string, token0: string, token1: string, fee: bigint): Promise<any> {
const { getPool } = await this._client.query(
gql(queryGetPool),
{
blockHash,
token0,
token1,
fee: fee.toString()
}
);
return getPool;
}
async callGetPool (blockHash: string, contractAddress: string, key0: string, key1: string, key2: number): Promise<any> {
const { callGetPool } = await this._client.query(
gql(queryCallGetPool),
{
blockHash,
contractAddress,
key0,
key1,
key2
}
);
return callGetPool;
}
async positions (blockHash: string, contractAddress: string, tokenId: bigint): Promise<any> {
const { positions } = await this._client.query(
gql(queryPositions),
{
blockHash,
contractAddress,
tokenId: tokenId.toString()
}
);
return positions;
}
async getContract (type: string): Promise<any> {
const { getContract } = await this._client.query(
gql(queryGetContract),
{
type
}
);
return getContract;
}
async getEventsInRange (fromblockNumber: number, toBlockNumber: number): Promise<any> {
const { events } = await this._client.query(
gql(queryEventsInRange),
{
fromblockNumber,
toBlockNumber
}
);
return events;
}
}

View File

@ -1,168 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import { Connection, ConnectionOptions, DeepPartial, QueryRunner, FindConditions, FindManyOptions } from 'typeorm';
import path from 'path';
import { Database as BaseDatabase, DatabaseInterface, QueryOptions, Where } from '@vulcanize/util';
import { Event } from './entity/Event';
import { Contract } from './entity/Contract';
import { BlockProgress } from './entity/BlockProgress';
import { SyncStatus } from './entity/SyncStatus';
export class Database implements DatabaseInterface {
_config: ConnectionOptions
_conn!: Connection
_baseDatabase: BaseDatabase
constructor (config: ConnectionOptions) {
assert(config);
this._config = {
...config,
entities: [path.join(__dirname, 'entity/*')]
};
this._baseDatabase = new BaseDatabase(this._config);
}
async init (): Promise<void> {
this._conn = await this._baseDatabase.init();
}
async close (): Promise<void> {
return this._baseDatabase.close();
}
async getLatestContract (kind: string): Promise<Contract | undefined> {
return this._conn.getRepository(Contract)
.createQueryBuilder('contract')
.where('kind = :kind', { kind })
.orderBy('id', 'DESC')
.getOne();
}
async getContracts (): Promise<Contract[]> {
const repo = this._conn.getRepository(Contract);
return this._baseDatabase.getContracts(repo);
}
async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number): Promise<Contract> {
const repo = queryRunner.manager.getRepository(Contract);
return this._baseDatabase.saveContract(repo, address, kind, checkpoint, startingBlock);
}
async createTransactionRunner (): Promise<QueryRunner> {
return this._baseDatabase.createTransactionRunner();
}
async getProcessedBlockCountForRange (fromBlockNumber: number, toBlockNumber: number): Promise<{ expected: number, actual: number }> {
const repo = this._conn.getRepository(BlockProgress);
return this._baseDatabase.getProcessedBlockCountForRange(repo, fromBlockNumber, toBlockNumber);
}
async getEventsInRange (fromBlockNumber: number, toBlockNumber: number): Promise<Array<Event>> {
const repo = this._conn.getRepository(Event);
return this._baseDatabase.getEventsInRange(repo, fromBlockNumber, toBlockNumber);
}
async saveEventEntity (queryRunner: QueryRunner, entity: Event): Promise<Event> {
const repo = queryRunner.manager.getRepository(Event);
return this._baseDatabase.saveEventEntity(repo, entity);
}
async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise<Event[]> {
const repo = this._conn.getRepository(Event);
return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions);
}
async saveEvents (queryRunner: QueryRunner, block: DeepPartial<BlockProgress>, events: DeepPartial<Event>[]): Promise<BlockProgress> {
const blockRepo = queryRunner.manager.getRepository(BlockProgress);
const eventRepo = queryRunner.manager.getRepository(Event);
return this._baseDatabase.saveEvents(blockRepo, eventRepo, block, events);
}
async updateSyncStatusIndexedBlock (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force = false): Promise<SyncStatus> {
const repo = queryRunner.manager.getRepository(SyncStatus);
return this._baseDatabase.updateSyncStatusIndexedBlock(repo, blockHash, blockNumber, force);
}
async updateSyncStatusCanonicalBlock (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force = false): Promise<SyncStatus> {
const repo = queryRunner.manager.getRepository(SyncStatus);
return this._baseDatabase.updateSyncStatusCanonicalBlock(repo, blockHash, blockNumber, force);
}
async updateSyncStatusChainHead (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force = false): Promise<SyncStatus> {
const repo = queryRunner.manager.getRepository(SyncStatus);
return this._baseDatabase.updateSyncStatusChainHead(repo, blockHash, blockNumber, force);
}
async getSyncStatus (queryRunner: QueryRunner): Promise<SyncStatus | undefined> {
const repo = queryRunner.manager.getRepository(SyncStatus);
return this._baseDatabase.getSyncStatus(repo);
}
async getEvent (id: string): Promise<Event | undefined> {
const repo = this._conn.getRepository(Event);
return this._baseDatabase.getEvent(repo, id);
}
async getBlocksAtHeight (height: number, isPruned: boolean): Promise<BlockProgress[]> {
const repo = this._conn.getRepository(BlockProgress);
return this._baseDatabase.getBlocksAtHeight(repo, height, isPruned);
}
async markBlocksAsPruned (queryRunner: QueryRunner, blocks: BlockProgress[]): Promise<void> {
const repo = queryRunner.manager.getRepository(BlockProgress);
return this._baseDatabase.markBlocksAsPruned(repo, blocks);
}
async getBlockProgress (blockHash: string): Promise<BlockProgress | undefined> {
const repo = this._conn.getRepository(BlockProgress);
return this._baseDatabase.getBlockProgress(repo, blockHash);
}
async getBlockProgressEntities (where: FindConditions<BlockProgress>, options: FindManyOptions<BlockProgress>): Promise<BlockProgress[]> {
const repo = this._conn.getRepository(BlockProgress);
return this._baseDatabase.getBlockProgressEntities(repo, where, options);
}
async updateBlockProgress (queryRunner: QueryRunner, block: BlockProgress, lastProcessedEventIndex: number): Promise<BlockProgress> {
const repo = queryRunner.manager.getRepository(BlockProgress);
return this._baseDatabase.updateBlockProgress(repo, block, lastProcessedEventIndex);
}
async getEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, findConditions?: FindConditions<Entity>): Promise<Entity[]> {
return this._baseDatabase.getEntities(queryRunner, entity, findConditions);
}
async removeEntities<Entity> (queryRunner: QueryRunner, entity: new () => Entity, findConditions?: FindManyOptions<Entity> | FindConditions<Entity>): Promise<void> {
return this._baseDatabase.removeEntities(queryRunner, entity, findConditions);
}
async isEntityEmpty<Entity> (entity: new () => Entity): Promise<boolean> {
return this._baseDatabase.isEntityEmpty(entity);
}
async getAncestorAtDepth (blockHash: string, depth: number): Promise<string> {
return this._baseDatabase.getAncestorAtDepth(blockHash, depth);
}
}

View File

@ -1,49 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryGeneratedColumn, Column, Index, CreateDateColumn } from 'typeorm';
import { BlockProgressInterface } from '@vulcanize/util';
@Entity()
@Index(['blockHash'], { unique: true })
@Index(['blockNumber'])
@Index(['parentHash'])
export class BlockProgress implements BlockProgressInterface {
@PrimaryGeneratedColumn()
id!: number;
@Column('varchar')
cid!: string;
@Column('varchar', { length: 66 })
blockHash!: string;
@Column('varchar', { length: 66, nullable: true })
parentHash!: string;
@Column('integer')
blockNumber!: number;
@Column('integer')
blockTimestamp!: number;
@Column('integer')
numEvents!: number;
@Column('integer')
numProcessedEvents!: number;
@Column('integer')
lastProcessedEventIndex!: number;
@Column('boolean')
isComplete!: boolean
@Column('boolean', { default: false })
isPruned!: boolean
@CreateDateColumn()
createdAt!: Date;
}

View File

@ -1,28 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
export const KIND_FACTORY = 'factory';
export const KIND_POOL = 'pool';
export const KIND_NFPM = 'nfpm';
@Entity()
@Index(['address'], { unique: true })
export class Contract {
@PrimaryGeneratedColumn()
id!: number;
@Column('varchar', { length: 42 })
address!: string;
@Column('varchar', { length: 8 })
kind!: string;
@Column('boolean')
checkpoint!: boolean;
@Column('integer')
startingBlock!: number;
}

View File

@ -1,41 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, Index } from 'typeorm';
import { BlockProgress } from './BlockProgress';
export const UNKNOWN_EVENT_NAME = '__unknown__';
@Entity()
// Index to query all events for a contract efficiently.
@Index(['contract'])
export class Event {
@PrimaryGeneratedColumn()
id!: number;
@ManyToOne(() => BlockProgress, { onDelete: 'CASCADE' })
block!: BlockProgress;
@Column('varchar', { length: 66 })
txHash!: string;
// Index of the log in the block.
@Column('integer')
index!: number;
@Column('varchar', { length: 42 })
contract!: string;
@Column('varchar', { length: 256 })
eventName!: string;
@Column('text')
eventInfo!: string;
@Column('text')
extraInfo!: string;
@Column('text')
proof!: string;
}

View File

@ -1,43 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { SyncStatusInterface } from '@vulcanize/util';
@Entity()
export class SyncStatus implements SyncStatusInterface {
@PrimaryGeneratedColumn()
id!: number;
// Latest block hash and number from the chain itself.
@Column('varchar', { length: 66 })
chainHeadBlockHash!: string;
@Column('integer')
chainHeadBlockNumber!: number;
// Most recent block hash that's been indexed.
@Column('varchar', { length: 66 })
latestIndexedBlockHash!: string;
// Most recent block number that's been indexed.
@Column('integer')
latestIndexedBlockNumber!: number;
// Most recent block hash and number that we can consider as part
// of the canonical/finalized chain. Reorgs older than this block
// cannot be processed and processing will halt.
@Column('varchar', { length: 66 })
latestCanonicalBlockHash!: string;
@Column('integer')
latestCanonicalBlockNumber!: number;
@Column('varchar', { length: 66 })
initialIndexedBlockHash!: string;
@Column('integer')
initialIndexedBlockNumber!: number;
}

View File

@ -1,117 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import debug from 'debug';
import { PubSub } from 'apollo-server-express';
import { EthClient } from '@vulcanize/ipld-eth-client';
import {
JobQueue,
EventWatcher as BaseEventWatcher,
QUEUE_BLOCK_PROCESSING,
QUEUE_EVENT_PROCESSING,
EventWatcherInterface,
UpstreamConfig
} from '@vulcanize/util';
import { Indexer } from './indexer';
import { Event, UNKNOWN_EVENT_NAME } from './entity/Event';
const log = debug('vulcanize:events');
export const UniswapEvent = 'uniswap-event';
export class EventWatcher implements EventWatcherInterface {
_ethClient: EthClient
_indexer: Indexer
_subscription?: ZenObservable.Subscription
_pubsub: PubSub
_jobQueue: JobQueue
_baseEventWatcher: BaseEventWatcher
constructor (upstreamConfig: UpstreamConfig, ethClient: EthClient, indexer: Indexer, pubsub: PubSub, jobQueue: JobQueue) {
this._ethClient = ethClient;
this._indexer = indexer;
this._pubsub = pubsub;
this._jobQueue = jobQueue;
this._baseEventWatcher = new BaseEventWatcher(upstreamConfig, this._ethClient, this._indexer, this._pubsub, this._jobQueue);
}
getEventIterator (): AsyncIterator<any> {
return this._pubsub.asyncIterator([UniswapEvent]);
}
getBlockProgressEventIterator (): AsyncIterator<any> {
return this._baseEventWatcher.getBlockProgressEventIterator();
}
async start (): Promise<void> {
assert(!this._subscription, 'subscription already started');
await this.initBlockProcessingOnCompleteHandler();
await this.initEventProcessingOnCompleteHandler();
this._baseEventWatcher.startBlockProcessing();
}
async stop (): Promise<void> {
this._baseEventWatcher.stop();
}
async initBlockProcessingOnCompleteHandler (): Promise<void> {
this._jobQueue.onComplete(QUEUE_BLOCK_PROCESSING, async (job) => {
const { id, data: { failed } } = job;
if (failed) {
log(`Job ${id} for queue ${QUEUE_BLOCK_PROCESSING} failed`);
return;
}
await this._baseEventWatcher.blockProcessingCompleteHandler(job);
});
}
async initEventProcessingOnCompleteHandler (): Promise<void> {
await this._jobQueue.onComplete(QUEUE_EVENT_PROCESSING, async (job) => {
const { id, data: { request, failed, state, createdOn } } = job;
if (failed) {
log(`Job ${id} for queue ${QUEUE_EVENT_PROCESSING} failed`);
return;
}
const dbEvents = await this._baseEventWatcher.eventProcessingCompleteHandler(job);
const timeElapsedInSeconds = (Date.now() - Date.parse(createdOn)) / 1000;
// Cannot publish individual event as they are processed together in a single job.
// TODO: Use a different pubsub to publish event from job-runner.
// https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries
for (const dbEvent of dbEvents) {
log(`Job onComplete event ${dbEvent.id} publish ${!!request.data.publish}`);
if (!failed && state === 'completed' && request.data.publish) {
// Check for max acceptable lag time between request and sending results to live subscribers.
if (timeElapsedInSeconds <= this._jobQueue.maxCompletionLag) {
await this.publishUniswapEventToSubscribers(dbEvent, timeElapsedInSeconds);
} else {
log(`event ${dbEvent.id} is too old (${timeElapsedInSeconds}s), not broadcasting to live subscribers`);
}
}
}
});
}
async publishUniswapEventToSubscribers (dbEvent: Event, timeElapsedInSeconds: number): Promise<void> {
if (dbEvent && dbEvent.eventName !== UNKNOWN_EVENT_NAME) {
const resultEvent = this._indexer.getResultEvent(dbEvent);
log(`pushing event to GQL subscribers (${timeElapsedInSeconds}s elapsed): ${resultEvent.event.__typename}`);
// Publishing the event here will result in pushing the payload to GQL subscribers for `onEvent`.
await this._pubsub.publish(UniswapEvent, {
onEvent: resultEvent
});
}
}
}

View File

@ -1,92 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert';
import 'reflect-metadata';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import debug from 'debug';
import { PubSub } from 'apollo-server-express';
import { getConfig, Config, fillBlocks, JobQueue, DEFAULT_CONFIG_PATH, initClients } from '@vulcanize/util';
import { Database } from './database';
import { Indexer } from './indexer';
import { EventWatcher } from './events';
const log = debug('vulcanize:server');
export const main = async (): Promise<any> => {
const argv = await yargs(hideBin(process.argv)).parserConfiguration({
'parse-numbers': false
}).options({
configFile: {
alias: 'f',
type: 'string',
require: true,
demandOption: true,
describe: 'configuration file path (toml)',
default: DEFAULT_CONFIG_PATH
},
startBlock: {
type: 'number',
require: true,
demandOption: true,
describe: 'Block number to start processing at'
},
endBlock: {
type: 'number',
require: true,
demandOption: true,
describe: 'Block number to stop processing at'
},
prefetch: {
type: 'boolean',
default: false,
describe: 'Block and events prefetch mode'
},
batchBlocks: {
type: 'number',
default: 10,
describe: 'Number of blocks prefetched in batch'
}
}).argv;
const config: Config = await getConfig(argv.configFile);
const { ethClient, ethProvider } = await initClients(config);
const db = new Database(config.database);
await db.init();
// Note: In-memory pubsub works fine for now, as each watcher is a single process anyway.
// Later: https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries
const pubsub = new PubSub();
const jobQueueConfig = config.jobQueue;
assert(jobQueueConfig, 'Missing job queue config');
const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig;
assert(dbConnectionString, 'Missing job queue db connection string');
const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs });
await jobQueue.start();
const indexer = new Indexer(config.server, db, ethClient, ethProvider, jobQueue);
await indexer.init();
const eventWatcher = new EventWatcher(config.upstream, ethClient, indexer, pubsub, jobQueue);
await fillBlocks(jobQueue, indexer, eventWatcher, config.upstream.ethServer.blockDelayInMilliSecs, argv);
};
main().catch(err => {
log(err);
}).finally(() => {
process.exit();
});
process.on('SIGINT', () => {
log(`Exiting process ${process.pid} with code 0`);
process.exit(0);
});

View File

@ -1,6 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
export * from './client';
export * from './utils/index';

Some files were not shown because too many files have changed in this diff Show More