From 854f814379efe13724864dc09d2d31672995cffe Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Thu, 6 Jun 2024 16:30:34 +0530 Subject: [PATCH] Generate sushiswap v2 watcher --- packages/v2-watcher/.eslintignore | 2 + packages/v2-watcher/.eslintrc.json | 28 + packages/v2-watcher/.gitignore | 8 + packages/v2-watcher/.husky/pre-commit | 4 + packages/v2-watcher/.npmrc | 1 + packages/v2-watcher/LICENSE | 661 +++++ packages/v2-watcher/README.md | 212 ++ packages/v2-watcher/environments/local.toml | 109 + packages/v2-watcher/package.json | 77 + .../v2-watcher/src/artifacts/Factory.json | 195 ++ packages/v2-watcher/src/artifacts/Pair.json | 715 +++++ .../src/cli/checkpoint-cmds/create.ts | 44 + .../src/cli/checkpoint-cmds/verify.ts | 40 + packages/v2-watcher/src/cli/checkpoint.ts | 39 + packages/v2-watcher/src/cli/export-state.ts | 38 + packages/v2-watcher/src/cli/import-state.ts | 39 + packages/v2-watcher/src/cli/index-block.ts | 38 + packages/v2-watcher/src/cli/inspect-cid.ts | 38 + .../src/cli/reset-cmds/job-queue.ts | 22 + .../v2-watcher/src/cli/reset-cmds/state.ts | 24 + .../v2-watcher/src/cli/reset-cmds/watcher.ts | 37 + packages/v2-watcher/src/cli/reset.ts | 24 + packages/v2-watcher/src/cli/watch-contract.ts | 38 + packages/v2-watcher/src/client.ts | 55 + packages/v2-watcher/src/database.ts | 299 +++ .../v2-watcher/src/entity/BlockProgress.ts | 48 + packages/v2-watcher/src/entity/Bundle.ts | 29 + packages/v2-watcher/src/entity/Burn.ts | 65 + packages/v2-watcher/src/entity/Contract.ts | 27 + packages/v2-watcher/src/entity/Event.ts | 38 + .../v2-watcher/src/entity/FrothyEntity.ts | 21 + .../src/entity/LiquidityPosition.ts | 35 + .../src/entity/LiquidityPositionSnapshot.ts | 62 + packages/v2-watcher/src/entity/Mint.ts | 62 + packages/v2-watcher/src/entity/Pair.ts | 80 + packages/v2-watcher/src/entity/PairDayData.ts | 62 + .../v2-watcher/src/entity/PairHourData.ts | 56 + packages/v2-watcher/src/entity/State.ts | 31 + .../v2-watcher/src/entity/StateSyncStatus.ts | 17 + packages/v2-watcher/src/entity/Subscriber.ts | 21 + packages/v2-watcher/src/entity/Swap.ts | 62 + packages/v2-watcher/src/entity/SyncStatus.ts | 45 + packages/v2-watcher/src/entity/Token.ts | 56 + .../v2-watcher/src/entity/TokenDayData.ts | 56 + packages/v2-watcher/src/entity/Transaction.ts | 40 + .../v2-watcher/src/entity/UniswapDayData.ts | 53 + .../v2-watcher/src/entity/UniswapFactory.ts | 47 + packages/v2-watcher/src/entity/User.ts | 29 + packages/v2-watcher/src/fill.ts | 48 + packages/v2-watcher/src/gql/index.ts | 3 + .../v2-watcher/src/gql/mutations/index.ts | 4 + .../src/gql/mutations/watchContract.gql | 3 + packages/v2-watcher/src/gql/queries/_meta.gql | 11 + .../v2-watcher/src/gql/queries/bundle.gql | 6 + .../v2-watcher/src/gql/queries/bundles.gql | 6 + packages/v2-watcher/src/gql/queries/burn.gql | 40 + packages/v2-watcher/src/gql/queries/burns.gql | 40 + .../v2-watcher/src/gql/queries/events.gql | 63 + .../src/gql/queries/eventsInRange.gql | 63 + .../v2-watcher/src/gql/queries/getState.gql | 15 + .../src/gql/queries/getStateByCID.gql | 15 + .../src/gql/queries/getSyncStatus.gql | 12 + packages/v2-watcher/src/gql/queries/index.ts | 39 + .../src/gql/queries/liquidityPosition.gql | 29 + .../gql/queries/liquidityPositionSnapshot.gql | 41 + .../queries/liquidityPositionSnapshots.gql | 41 + .../src/gql/queries/liquidityPositions.gql | 29 + packages/v2-watcher/src/gql/queries/mint.gql | 39 + packages/v2-watcher/src/gql/queries/mints.gql | 39 + packages/v2-watcher/src/gql/queries/pair.gql | 115 + .../src/gql/queries/pairDayData.gql | 41 + .../src/gql/queries/pairDayDatas.gql | 41 + .../src/gql/queries/pairHourData.gql | 33 + .../src/gql/queries/pairHourDatas.gql | 33 + packages/v2-watcher/src/gql/queries/pairs.gql | 115 + packages/v2-watcher/src/gql/queries/swap.gql | 39 + packages/v2-watcher/src/gql/queries/swaps.gql | 39 + packages/v2-watcher/src/gql/queries/token.gql | 91 + .../src/gql/queries/tokenDayData.gql | 27 + .../src/gql/queries/tokenDayDatas.gql | 27 + .../v2-watcher/src/gql/queries/tokens.gql | 91 + .../src/gql/queries/transaction.gql | 47 + .../src/gql/queries/transactions.gql | 47 + .../src/gql/queries/uniswapDayData.gql | 14 + .../src/gql/queries/uniswapDayDatas.gql | 14 + .../src/gql/queries/uniswapFactories.gql | 12 + .../src/gql/queries/uniswapFactory.gql | 12 + packages/v2-watcher/src/gql/queries/user.gql | 10 + packages/v2-watcher/src/gql/queries/users.gql | 10 + .../v2-watcher/src/gql/subscriptions/index.ts | 4 + .../src/gql/subscriptions/onEvent.gql | 63 + packages/v2-watcher/src/hooks.ts | 86 + packages/v2-watcher/src/indexer.ts | 1072 ++++++++ packages/v2-watcher/src/job-runner.ts | 48 + packages/v2-watcher/src/resolvers.ts | 944 +++++++ packages/v2-watcher/src/schema.gql | 2330 +++++++++++++++++ packages/v2-watcher/src/server.ts | 43 + packages/v2-watcher/src/types.ts | 3 + .../subgraph-build/Factory/Factory.wasm | Bin 0 -> 225762 bytes .../subgraph-build/Factory/abis/ERC20.json | 222 ++ .../Factory/abis/ERC20NameBytes.json | 17 + .../Factory/abis/ERC20SymbolBytes.json | 17 + .../subgraph-build/Factory/abis/factory.json | 193 ++ .../subgraph-build/Pair/abis/factory.json | 193 ++ .../subgraph-build/Pair/abis/pair.json | 713 +++++ .../v2-watcher/subgraph-build/schema.graphql | 301 +++ .../v2-watcher/subgraph-build/subgraph.yaml | 63 + .../subgraph-build/templates/Pair/Pair.wasm | Bin 0 -> 293443 bytes packages/v2-watcher/tsconfig.json | 74 + 109 files changed, 11659 insertions(+) create mode 100644 packages/v2-watcher/.eslintignore create mode 100644 packages/v2-watcher/.eslintrc.json create mode 100644 packages/v2-watcher/.gitignore create mode 100644 packages/v2-watcher/.husky/pre-commit create mode 100644 packages/v2-watcher/.npmrc create mode 100644 packages/v2-watcher/LICENSE create mode 100644 packages/v2-watcher/README.md create mode 100644 packages/v2-watcher/environments/local.toml create mode 100644 packages/v2-watcher/package.json create mode 100644 packages/v2-watcher/src/artifacts/Factory.json create mode 100644 packages/v2-watcher/src/artifacts/Pair.json create mode 100644 packages/v2-watcher/src/cli/checkpoint-cmds/create.ts create mode 100644 packages/v2-watcher/src/cli/checkpoint-cmds/verify.ts create mode 100644 packages/v2-watcher/src/cli/checkpoint.ts create mode 100644 packages/v2-watcher/src/cli/export-state.ts create mode 100644 packages/v2-watcher/src/cli/import-state.ts create mode 100644 packages/v2-watcher/src/cli/index-block.ts create mode 100644 packages/v2-watcher/src/cli/inspect-cid.ts create mode 100644 packages/v2-watcher/src/cli/reset-cmds/job-queue.ts create mode 100644 packages/v2-watcher/src/cli/reset-cmds/state.ts create mode 100644 packages/v2-watcher/src/cli/reset-cmds/watcher.ts create mode 100644 packages/v2-watcher/src/cli/reset.ts create mode 100644 packages/v2-watcher/src/cli/watch-contract.ts create mode 100644 packages/v2-watcher/src/client.ts create mode 100644 packages/v2-watcher/src/database.ts create mode 100644 packages/v2-watcher/src/entity/BlockProgress.ts create mode 100644 packages/v2-watcher/src/entity/Bundle.ts create mode 100644 packages/v2-watcher/src/entity/Burn.ts create mode 100644 packages/v2-watcher/src/entity/Contract.ts create mode 100644 packages/v2-watcher/src/entity/Event.ts create mode 100644 packages/v2-watcher/src/entity/FrothyEntity.ts create mode 100644 packages/v2-watcher/src/entity/LiquidityPosition.ts create mode 100644 packages/v2-watcher/src/entity/LiquidityPositionSnapshot.ts create mode 100644 packages/v2-watcher/src/entity/Mint.ts create mode 100644 packages/v2-watcher/src/entity/Pair.ts create mode 100644 packages/v2-watcher/src/entity/PairDayData.ts create mode 100644 packages/v2-watcher/src/entity/PairHourData.ts create mode 100644 packages/v2-watcher/src/entity/State.ts create mode 100644 packages/v2-watcher/src/entity/StateSyncStatus.ts create mode 100644 packages/v2-watcher/src/entity/Subscriber.ts create mode 100644 packages/v2-watcher/src/entity/Swap.ts create mode 100644 packages/v2-watcher/src/entity/SyncStatus.ts create mode 100644 packages/v2-watcher/src/entity/Token.ts create mode 100644 packages/v2-watcher/src/entity/TokenDayData.ts create mode 100644 packages/v2-watcher/src/entity/Transaction.ts create mode 100644 packages/v2-watcher/src/entity/UniswapDayData.ts create mode 100644 packages/v2-watcher/src/entity/UniswapFactory.ts create mode 100644 packages/v2-watcher/src/entity/User.ts create mode 100644 packages/v2-watcher/src/fill.ts create mode 100644 packages/v2-watcher/src/gql/index.ts create mode 100644 packages/v2-watcher/src/gql/mutations/index.ts create mode 100644 packages/v2-watcher/src/gql/mutations/watchContract.gql create mode 100644 packages/v2-watcher/src/gql/queries/_meta.gql create mode 100644 packages/v2-watcher/src/gql/queries/bundle.gql create mode 100644 packages/v2-watcher/src/gql/queries/bundles.gql create mode 100644 packages/v2-watcher/src/gql/queries/burn.gql create mode 100644 packages/v2-watcher/src/gql/queries/burns.gql create mode 100644 packages/v2-watcher/src/gql/queries/events.gql create mode 100644 packages/v2-watcher/src/gql/queries/eventsInRange.gql create mode 100644 packages/v2-watcher/src/gql/queries/getState.gql create mode 100644 packages/v2-watcher/src/gql/queries/getStateByCID.gql create mode 100644 packages/v2-watcher/src/gql/queries/getSyncStatus.gql create mode 100644 packages/v2-watcher/src/gql/queries/index.ts create mode 100644 packages/v2-watcher/src/gql/queries/liquidityPosition.gql create mode 100644 packages/v2-watcher/src/gql/queries/liquidityPositionSnapshot.gql create mode 100644 packages/v2-watcher/src/gql/queries/liquidityPositionSnapshots.gql create mode 100644 packages/v2-watcher/src/gql/queries/liquidityPositions.gql create mode 100644 packages/v2-watcher/src/gql/queries/mint.gql create mode 100644 packages/v2-watcher/src/gql/queries/mints.gql create mode 100644 packages/v2-watcher/src/gql/queries/pair.gql create mode 100644 packages/v2-watcher/src/gql/queries/pairDayData.gql create mode 100644 packages/v2-watcher/src/gql/queries/pairDayDatas.gql create mode 100644 packages/v2-watcher/src/gql/queries/pairHourData.gql create mode 100644 packages/v2-watcher/src/gql/queries/pairHourDatas.gql create mode 100644 packages/v2-watcher/src/gql/queries/pairs.gql create mode 100644 packages/v2-watcher/src/gql/queries/swap.gql create mode 100644 packages/v2-watcher/src/gql/queries/swaps.gql create mode 100644 packages/v2-watcher/src/gql/queries/token.gql create mode 100644 packages/v2-watcher/src/gql/queries/tokenDayData.gql create mode 100644 packages/v2-watcher/src/gql/queries/tokenDayDatas.gql create mode 100644 packages/v2-watcher/src/gql/queries/tokens.gql create mode 100644 packages/v2-watcher/src/gql/queries/transaction.gql create mode 100644 packages/v2-watcher/src/gql/queries/transactions.gql create mode 100644 packages/v2-watcher/src/gql/queries/uniswapDayData.gql create mode 100644 packages/v2-watcher/src/gql/queries/uniswapDayDatas.gql create mode 100644 packages/v2-watcher/src/gql/queries/uniswapFactories.gql create mode 100644 packages/v2-watcher/src/gql/queries/uniswapFactory.gql create mode 100644 packages/v2-watcher/src/gql/queries/user.gql create mode 100644 packages/v2-watcher/src/gql/queries/users.gql create mode 100644 packages/v2-watcher/src/gql/subscriptions/index.ts create mode 100644 packages/v2-watcher/src/gql/subscriptions/onEvent.gql create mode 100644 packages/v2-watcher/src/hooks.ts create mode 100644 packages/v2-watcher/src/indexer.ts create mode 100644 packages/v2-watcher/src/job-runner.ts create mode 100644 packages/v2-watcher/src/resolvers.ts create mode 100644 packages/v2-watcher/src/schema.gql create mode 100644 packages/v2-watcher/src/server.ts create mode 100644 packages/v2-watcher/src/types.ts create mode 100644 packages/v2-watcher/subgraph-build/Factory/Factory.wasm create mode 100644 packages/v2-watcher/subgraph-build/Factory/abis/ERC20.json create mode 100644 packages/v2-watcher/subgraph-build/Factory/abis/ERC20NameBytes.json create mode 100644 packages/v2-watcher/subgraph-build/Factory/abis/ERC20SymbolBytes.json create mode 100644 packages/v2-watcher/subgraph-build/Factory/abis/factory.json create mode 100644 packages/v2-watcher/subgraph-build/Pair/abis/factory.json create mode 100644 packages/v2-watcher/subgraph-build/Pair/abis/pair.json create mode 100644 packages/v2-watcher/subgraph-build/schema.graphql create mode 100644 packages/v2-watcher/subgraph-build/subgraph.yaml create mode 100644 packages/v2-watcher/subgraph-build/templates/Pair/Pair.wasm create mode 100644 packages/v2-watcher/tsconfig.json diff --git a/packages/v2-watcher/.eslintignore b/packages/v2-watcher/.eslintignore new file mode 100644 index 0000000..55cb522 --- /dev/null +++ b/packages/v2-watcher/.eslintignore @@ -0,0 +1,2 @@ +# Don't lint build output. +dist diff --git a/packages/v2-watcher/.eslintrc.json b/packages/v2-watcher/.eslintrc.json new file mode 100644 index 0000000..a2b842c --- /dev/null +++ b/packages/v2-watcher/.eslintrc.json @@ -0,0 +1,28 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "semistandard", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "indent": ["error", 2, { "SwitchCase": 1 }], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/explicit-module-boundary-types": [ + "warn", + { + "allowArgumentsExplicitlyTypedAsAny": true + } + ] + } +} diff --git a/packages/v2-watcher/.gitignore b/packages/v2-watcher/.gitignore new file mode 100644 index 0000000..549d70b --- /dev/null +++ b/packages/v2-watcher/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +out/ + +.vscode +.idea + +gql-logs/ diff --git a/packages/v2-watcher/.husky/pre-commit b/packages/v2-watcher/.husky/pre-commit new file mode 100644 index 0000000..9dcd433 --- /dev/null +++ b/packages/v2-watcher/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn lint diff --git a/packages/v2-watcher/.npmrc b/packages/v2-watcher/.npmrc new file mode 100644 index 0000000..6b64c5b --- /dev/null +++ b/packages/v2-watcher/.npmrc @@ -0,0 +1 @@ +@cerc-io:registry=https://git.vdb.to/api/packages/cerc-io/npm/ diff --git a/packages/v2-watcher/LICENSE b/packages/v2-watcher/LICENSE new file mode 100644 index 0000000..331f7cf --- /dev/null +++ b/packages/v2-watcher/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for + software and other kinds of works, specifically designed to ensure + cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed + to take away your freedom to share and change the works. By contrast, + our General Public Licenses are intended to guarantee your freedom to + share and change all versions of a program--to make sure it remains free + software for all its users. + + When we speak of free software, we are referring to freedom, not + price. Our General Public Licenses are designed to make sure that you + have the freedom to distribute copies of free software (and charge for + them if you wish), that you receive source code or can get it if you + want it, that you can change the software or use pieces of it in new + free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights + with two steps: (1) assert copyright on the software, and (2) offer + you this License which gives you legal permission to copy, distribute + and/or modify the software. + + A secondary benefit of defending all users' freedom is that + improvements made in alternate versions of the program, if they + receive widespread use, become available for other developers to + incorporate. Many developers of free software are heartened and + encouraged by the resulting cooperation. However, in the case of + software used on network servers, this result may fail to come about. + The GNU General Public License permits making a modified version and + letting the public access it on a server without ever releasing its + source code to the public. + + The GNU Affero General Public License is designed specifically to + ensure that, in such cases, the modified source code becomes available + to the community. It requires the operator of a network server to + provide the source code of the modified version running there to the + users of that server. Therefore, public use of a modified version, on + a publicly accessible server, gives the public access to the source + code of the modified version. + + An older license, called the Affero General Public License and + published by Affero, was designed to accomplish similar goals. This is + a different license, not a version of the Affero GPL, but Affero has + released a new version of the Affero GPL which permits relicensing under + this license. + + The precise terms and conditions for copying, distribution and + modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of + works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this + License. Each licensee is addressed as "you". "Licensees" and + "recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work + in a fashion requiring copyright permission, other than the making of an + exact copy. The resulting work is called a "modified version" of the + earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based + on the Program. + + To "propagate" a work means to do anything with it that, without + permission, would make you directly or secondarily liable for + infringement under applicable copyright law, except executing it on a + computer or modifying a private copy. Propagation includes copying, + distribution (with or without modification), making available to the + public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other + parties to make or receive copies. Mere interaction with a user through + a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" + to the extent that it includes a convenient and prominently visible + feature that (1) displays an appropriate copyright notice, and (2) + tells the user that there is no warranty for the work (except to the + extent that warranties are provided), that licensees may convey the + work under this License, and how to view a copy of this License. If + the interface presents a list of user commands or options, such as a + menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work + for making modifications to it. "Object code" means any non-source + form of a work. + + A "Standard Interface" means an interface that either is an official + standard defined by a recognized standards body, or, in the case of + interfaces specified for a particular programming language, one that + is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other + than the work as a whole, that (a) is included in the normal form of + packaging a Major Component, but which is not part of that Major + Component, and (b) serves only to enable use of the work with that + Major Component, or to implement a Standard Interface for which an + implementation is available to the public in source code form. A + "Major Component", in this context, means a major essential component + (kernel, window system, and so on) of the specific operating system + (if any) on which the executable work runs, or a compiler used to + produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all + the source code needed to generate, install, and (for an executable + work) run the object code and to modify the work, including scripts to + control those activities. However, it does not include the work's + System Libraries, or general-purpose tools or generally available free + programs which are used unmodified in performing those activities but + which are not part of the work. For example, Corresponding Source + includes interface definition files associated with source files for + the work, and the source code for shared libraries and dynamically + linked subprograms that the work is specifically designed to require, + such as by intimate data communication or control flow between those + subprograms and other parts of the work. + + The Corresponding Source need not include anything that users + can regenerate automatically from other parts of the Corresponding + Source. + + The Corresponding Source for a work in source code form is that + same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of + copyright on the Program, and are irrevocable provided the stated + conditions are met. This License explicitly affirms your unlimited + permission to run the unmodified Program. The output from running a + covered work is covered by this License only if the output, given its + content, constitutes a covered work. This License acknowledges your + rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not + convey, without conditions so long as your license otherwise remains + in force. You may convey covered works to others for the sole purpose + of having them make modifications exclusively for you, or provide you + with facilities for running those works, provided that you comply with + the terms of this License in conveying all material for which you do + not control copyright. Those thus making or running the covered works + for you must do so exclusively on your behalf, under your direction + and control, on terms that prohibit them from making any copies of + your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under + the conditions stated below. Sublicensing is not allowed; section 10 + makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological + measure under any applicable law fulfilling obligations under article + 11 of the WIPO copyright treaty adopted on 20 December 1996, or + similar laws prohibiting or restricting circumvention of such + measures. + + When you convey a covered work, you waive any legal power to forbid + circumvention of technological measures to the extent such circumvention + is effected by exercising rights under this License with respect to + the covered work, and you disclaim any intention to limit operation or + modification of the work as a means of enforcing, against the work's + users, your or third parties' legal rights to forbid circumvention of + technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you + receive it, in any medium, provided that you conspicuously and + appropriately publish on each copy an appropriate copyright notice; + keep intact all notices stating that this License and any + non-permissive terms added in accord with section 7 apply to the code; + keep intact all notices of the absence of any warranty; and give all + recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, + and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to + produce it from the Program, in the form of source code under the + terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent + works, which are not by their nature extensions of the covered work, + and which are not combined with it such as to form a larger program, + in or on a volume of a storage or distribution medium, is called an + "aggregate" if the compilation and its resulting copyright are not + used to limit the access or legal rights of the compilation's users + beyond what the individual works permit. Inclusion of a covered work + in an aggregate does not cause this License to apply to the other + parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms + of sections 4 and 5, provided that you also convey the + machine-readable Corresponding Source under the terms of this License, + in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded + from the Corresponding Source as a System Library, need not be + included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any + tangible personal property which is normally used for personal, family, + or household purposes, or (2) anything designed or sold for incorporation + into a dwelling. In determining whether a product is a consumer product, + doubtful cases shall be resolved in favor of coverage. For a particular + product received by a particular user, "normally used" refers to a + typical or common use of that class of product, regardless of the status + of the particular user or of the way in which the particular user + actually uses, or expects or is expected to use, the product. A product + is a consumer product regardless of whether the product has substantial + commercial, industrial or non-consumer uses, unless such uses represent + the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, + procedures, authorization keys, or other information required to install + and execute modified versions of a covered work in that User Product from + a modified version of its Corresponding Source. The information must + suffice to ensure that the continued functioning of the modified object + code is in no case prevented or interfered with solely because + modification has been made. + + If you convey an object code work under this section in, or with, or + specifically for use in, a User Product, and the conveying occurs as + part of a transaction in which the right of possession and use of the + User Product is transferred to the recipient in perpetuity or for a + fixed term (regardless of how the transaction is characterized), the + Corresponding Source conveyed under this section must be accompanied + by the Installation Information. But this requirement does not apply + if neither you nor any third party retains the ability to install + modified object code on the User Product (for example, the work has + been installed in ROM). + + The requirement to provide Installation Information does not include a + requirement to continue to provide support service, warranty, or updates + for a work that has been modified or installed by the recipient, or for + the User Product in which it has been modified or installed. Access to a + network may be denied when the modification itself materially and + adversely affects the operation of the network or violates the rules and + protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, + in accord with this section must be in a format that is publicly + documented (and with an implementation available to the public in + source code form), and must require no special password or key for + unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this + License by making exceptions from one or more of its conditions. + Additional permissions that are applicable to the entire Program shall + be treated as though they were included in this License, to the extent + that they are valid under applicable law. If additional permissions + apply only to part of the Program, that part may be used separately + under those permissions, but the entire Program remains governed by + this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option + remove any additional permissions from that copy, or from any part of + it. (Additional permissions may be written to require their own + removal in certain cases when you modify the work.) You may place + additional permissions on material, added by you to a covered work, + for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you + add to a covered work, you may (if authorized by the copyright holders of + that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further + restrictions" within the meaning of section 10. If the Program as you + received it, or any part of it, contains a notice stating that it is + governed by this License along with a term that is a further + restriction, you may remove that term. If a license document contains + a further restriction but permits relicensing or conveying under this + License, you may add to a covered work material governed by the terms + of that license document, provided that the further restriction does + not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you + must place, in the relevant source files, a statement of the + additional terms that apply to those files, or a notice indicating + where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the + form of a separately written license, or stated as exceptions; + the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly + provided under this License. Any attempt otherwise to propagate or + modify it is void, and will automatically terminate your rights under + this License (including any patent licenses granted under the third + paragraph of section 11). + + However, if you cease all violation of this License, then your + license from a particular copyright holder is reinstated (a) + provisionally, unless and until the copyright holder explicitly and + finally terminates your license, and (b) permanently, if the copyright + holder fails to notify you of the violation by some reasonable means + prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is + reinstated permanently if the copyright holder notifies you of the + violation by some reasonable means, this is the first time you have + received notice of violation of this License (for any work) from that + copyright holder, and you cure the violation prior to 30 days after + your receipt of the notice. + + Termination of your rights under this section does not terminate the + licenses of parties who have received copies or rights from you under + this License. If your rights have been terminated and not permanently + reinstated, you do not qualify to receive new licenses for the same + material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or + run a copy of the Program. Ancillary propagation of a covered work + occurring solely as a consequence of using peer-to-peer transmission + to receive a copy likewise does not require acceptance. However, + nothing other than this License grants you permission to propagate or + modify any covered work. These actions infringe copyright if you do + not accept this License. Therefore, by modifying or propagating a + covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically + receives a license from the original licensors, to run, modify and + propagate that work, subject to this License. You are not responsible + for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an + organization, or substantially all assets of one, or subdividing an + organization, or merging organizations. If propagation of a covered + work results from an entity transaction, each party to that + transaction who receives a copy of the work also receives whatever + licenses to the work the party's predecessor in interest had or could + give under the previous paragraph, plus a right to possession of the + Corresponding Source of the work from the predecessor in interest, if + the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the + rights granted or affirmed under this License. For example, you may + not impose a license fee, royalty, or other charge for exercise of + rights granted under this License, and you may not initiate litigation + (including a cross-claim or counterclaim in a lawsuit) alleging that + any patent claim is infringed by making, using, selling, offering for + sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this + License of the Program or a work on which the Program is based. The + work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims + owned or controlled by the contributor, whether already acquired or + hereafter acquired, that would be infringed by some manner, permitted + by this License, of making, using, or selling its contributor version, + but do not include claims that would be infringed only as a + consequence of further modification of the contributor version. For + purposes of this definition, "control" includes the right to grant + patent sublicenses in a manner consistent with the requirements of + this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free + patent license under the contributor's essential patent claims, to + make, use, sell, offer for sale, import and otherwise run, modify and + propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express + agreement or commitment, however denominated, not to enforce a patent + (such as an express permission to practice a patent or covenant not to + sue for patent infringement). To "grant" such a patent license to a + party means to make such an agreement or commitment not to enforce a + patent against the party. + + If you convey a covered work, knowingly relying on a patent license, + and the Corresponding Source of the work is not available for anyone + to copy, free of charge and under the terms of this License, through a + publicly available network server or other readily accessible means, + then you must either (1) cause the Corresponding Source to be so + available, or (2) arrange to deprive yourself of the benefit of the + patent license for this particular work, or (3) arrange, in a manner + consistent with the requirements of this License, to extend the patent + license to downstream recipients. "Knowingly relying" means you have + actual knowledge that, but for the patent license, your conveying the + covered work in a country, or your recipient's use of the covered work + in a country, would infringe one or more identifiable patents in that + country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or + arrangement, you convey, or propagate by procuring conveyance of, a + covered work, and grant a patent license to some of the parties + receiving the covered work authorizing them to use, propagate, modify + or convey a specific copy of the covered work, then the patent license + you grant is automatically extended to all recipients of the covered + work and works based on it. + + A patent license is "discriminatory" if it does not include within + the scope of its coverage, prohibits the exercise of, or is + conditioned on the non-exercise of one or more of the rights that are + specifically granted under this License. You may not convey a covered + work if you are a party to an arrangement with a third party that is + in the business of distributing software, under which you make payment + to the third party based on the extent of your activity of conveying + the work, and under which the third party grants, to any of the + parties who would receive the covered work from you, a discriminatory + patent license (a) in connection with copies of the covered work + conveyed by you (or copies made from those copies), or (b) primarily + for and in connection with specific products or compilations that + contain the covered work, unless you entered into that arrangement, + or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting + any implied license or other defenses to infringement that may + otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot convey a + covered work so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you may + not convey it at all. For example, if you agree to terms that obligate you + to collect a royalty for further conveying from those to whom you convey + the Program, the only way you could satisfy both those terms and this + License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the + Program, your modified version must prominently offer all users + interacting with it remotely through a computer network (if your version + supports such interaction) an opportunity to receive the Corresponding + Source of your version by providing access to the Corresponding Source + from a network server at no charge, through some standard or customary + means of facilitating copying of software. This Corresponding Source + shall include the Corresponding Source for any work covered by version 3 + of the GNU General Public License that is incorporated pursuant to the + following paragraph. + + Notwithstanding any other provision of this License, you have + permission to link or combine any covered work with a work licensed + under version 3 of the GNU General Public License into a single + combined work, and to convey the resulting work. The terms of this + License will continue to apply to the part which is the covered work, + but the work with which it is combined will remain governed by version + 3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of + the GNU Affero General Public License from time to time. Such new versions + will be similar in spirit to the present version, but may differ in detail to + address new problems or concerns. + + Each version is given a distinguishing version number. If the + Program specifies that a certain numbered version of the GNU Affero General + Public License "or any later version" applies to it, you have the + option of following the terms and conditions either of that numbered + version or of any later version published by the Free Software + Foundation. If the Program does not specify a version number of the + GNU Affero General Public License, you may choose any version ever published + by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future + versions of the GNU Affero General Public License can be used, that proxy's + public statement of acceptance of a version permanently authorizes you + to choose that version for the Program. + + Later license versions may give you additional or different + permissions. However, no additional obligations are imposed on any + author or copyright holder as a result of your choosing to follow a + later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY + APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT + HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY + OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM + IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF + ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS + THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY + GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE + USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF + DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD + PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), + EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF + SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided + above cannot be given local legal effect according to their terms, + reviewing courts shall apply local law that most closely approximates + an absolute waiver of all civil liability in connection with the + Program, unless a warranty or assumption of liability accompanies a + copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest + to attach them to the start of each source file to most effectively + state the exclusion of warranty; and each file should have at least + the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer + network, you should also make sure that it provides a way for users to + get its source. For example, if your program is a web application, its + interface could display a "Source" link that leads users to an archive + of the code. There are many ways you could offer source, and different + solutions will be better for different programs; see section 13 for the + specific requirements. + + You should also get your employer (if you work as a programmer) or school, + if any, to sign a "copyright disclaimer" for the program, if necessary. + For more information on this, and how to apply and follow the GNU AGPL, see + . diff --git a/packages/v2-watcher/README.md b/packages/v2-watcher/README.md new file mode 100644 index 0000000..03dc91d --- /dev/null +++ b/packages/v2-watcher/README.md @@ -0,0 +1,212 @@ +# v2-watcher + +## Setup + +* Run the following command to install required packages: + + ```bash + yarn + ``` + +* Create a postgres12 database for the watcher: + + ```bash + sudo su - postgres + createdb v2-watcher + ``` + +* If the watcher is an `active` watcher: + + Create database for the job queue and enable the `pgcrypto` extension on them (https://github.com/timgit/pg-boss/blob/master/docs/usage.md#intro): + + ``` + createdb v2-watcher-job-queue + ``` + + ``` + postgres@tesla:~$ psql -U postgres -h localhost v2-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. + + v2-watcher-job-queue=# CREATE EXTENSION pgcrypto; + CREATE EXTENSION + v2-watcher-job-queue=# exit + ``` + +* In the [config file](./environments/local.toml): + + * Update the database connection settings. + + * Update the `upstream` config and provide the `ipld-eth-server` GQL API endpoint. + + * Update the `server` config with state checkpoint settings. + +## Customize + +* Indexing on an event: + + * Edit the custom hook function `handleEvent` (triggered on an event) in [hooks.ts](./src/hooks.ts) to perform corresponding indexing using the `Indexer` object. + + * While using the indexer storage methods for indexing, pass `diff` as true if default state is desired to be generated using the state variables being indexed. + +* Generating state: + + * Edit the custom hook function `createInitialState` (triggered if the watcher passes the start block, checkpoint: `true`) in [hooks.ts](./src/hooks.ts) to save an initial `State` using the `Indexer` object. + + * Edit the custom hook function `createStateDiff` (triggered on a block) in [hooks.ts](./src/hooks.ts) to save the state in a `diff` `State` using the `Indexer` object. The default state (if exists) is updated. + + * Edit the custom hook function `createStateCheckpoint` (triggered just before default and CLI checkpoint) in [hooks.ts](./src/hooks.ts) to save the state in a `checkpoint` `State` using the `Indexer` object. + +### GQL Caching + +To enable GQL requests caching: + +* Update the `server.gql.cache` config with required settings. + +* In the GQL [schema file](./src/schema.gql), use the `cacheControl` directive to apply cache hints at schema level. + + * Eg. Set `inheritMaxAge` to true for non-scalar fields of a type. + +* In the GQL [resolvers file](./src/resolvers.ts), uncomment the `setGQLCacheHints()` calls in resolvers for required queries. + +## Run + +* If the watcher is a `lazy` watcher: + + * Run the server: + + ```bash + yarn server + ``` + + GQL console: http://localhost:3008/graphql + +* If the watcher is an `active` watcher: + + * Run the job-runner: + + ```bash + yarn job-runner + ``` + + * Run the server: + + ```bash + yarn server + ``` + + GQL console: http://localhost:3008/graphql + + * To watch a contract: + + ```bash + yarn watch:contract --address --kind --checkpoint --starting-block [block-number] + ``` + + * `address`: Address or identifier of the contract to be watched. + * `kind`: Kind of the contract. + * `checkpoint`: Turn checkpointing on (`true` | `false`). + * `starting-block`: Starting block for the contract (default: `1`). + + Examples: + + Watch a contract with its address and checkpointing on: + + ```bash + yarn watch:contract --address 0x1F78641644feB8b64642e833cE4AFE93DD6e7833 --kind ERC20 --checkpoint true + ``` + + Watch a contract with its identifier and checkpointing on: + + ```bash + yarn watch:contract --address MyProtocol --kind protocol --checkpoint true + ``` + + * To fill a block range: + + ```bash + yarn fill --start-block --end-block + ``` + + * `start-block`: Block number to start filling from. + * `end-block`: Block number till which to fill. + + * To create a checkpoint for a contract: + + ```bash + yarn checkpoint create --address --block-hash [block-hash] + ``` + + * `address`: Address or identifier of the contract for which to create a checkpoint. + * `block-hash`: Hash of a block (in the pruned region) at which to create the checkpoint (default: latest canonical block hash). + + * To verify a checkpoint: + + ```bash + yarn checkpoint verify --cid + ``` + + `cid`: CID of the checkpoint for which to verify. + + * To reset the watcher to a previous block number: + + * Reset watcher: + + ```bash + yarn reset watcher --block-number + ``` + + * Reset job-queue: + + ```bash + yarn reset job-queue + ``` + + * Reset state: + + ```bash + yarn reset state --block-number + ``` + + * `block-number`: Block number to which to reset the watcher. + + * To export and import the watcher state: + + * In source watcher, export watcher state: + + ```bash + yarn export-state --export-file [export-file-path] --block-number [snapshot-block-height] + ``` + + * `export-file`: Path of file to which to export the watcher data. + * `block-number`: Block height at which to take snapshot for export. + + * In target watcher, run job-runner: + + ```bash + yarn job-runner + ``` + + * Import watcher state: + + ```bash + yarn import-state --import-file + ``` + + * `import-file`: Path of file from which to import the watcher data. + + * Run server: + + ```bash + yarn server + ``` + + * To inspect a CID: + + ```bash + yarn inspect-cid --cid + ``` + + * `cid`: CID to be inspected. diff --git a/packages/v2-watcher/environments/local.toml b/packages/v2-watcher/environments/local.toml new file mode 100644 index 0000000..37984bd --- /dev/null +++ b/packages/v2-watcher/environments/local.toml @@ -0,0 +1,109 @@ +[server] + host = "127.0.0.1" + port = 3008 + kind = "active" + + # Checkpointing state. + checkpointing = true + + # Checkpoint interval in number of blocks. + checkpointInterval = 2000 + + # Enable state creation + # CAUTION: Disable only if state creation is not desired or can be filled subsequently + enableState = true + + subgraphPath = "./subgraph-build" + + # Interval to restart wasm instance periodically + wasmRestartBlocksInterval = 20 + + # Interval in number of blocks at which to clear entities cache. + clearEntitiesCacheInterval = 1000 + + # Flag to specify whether RPC endpoint supports block hash as block tag parameter + rpcSupportsBlockHashParam = true + + # Server GQL config + [server.gql] + path = "/graphql" + + # Max block range for which to return events in eventsInRange GQL query. + # Use -1 for skipping check on block range. + maxEventsBlockRange = 1000 + + # Log directory for GQL requests + logDir = "./gql-logs" + + # GQL cache settings + [server.gql.cache] + enabled = true + + # Max in-memory cache size (in bytes) (default 8 MB) + # maxCacheSize + + # GQL cache-control max-age settings (in seconds) + maxAge = 15 + timeTravelMaxAge = 86400 # 1 day + +[metrics] + host = "127.0.0.1" + port = 9000 + [metrics.gql] + port = 9001 + +[database] + type = "postgres" + host = "localhost" + port = 5432 + database = "v2-watcher" + username = "postgres" + password = "postgres" + synchronize = true + logging = false + +[upstream] + [upstream.ethServer] + gqlApiEndpoint = "http://127.0.0.1:8082/graphql" + rpcProviderEndpoints = [ + "http://127.0.0.1:8081" + ] + + # Boolean flag to specify if rpc-eth-client should be used for RPC endpoint instead of ipld-eth-client (ipld-eth-server GQL client) + rpcClient = false + + # Boolean flag to specify if rpcProviderEndpoint is an FEVM RPC endpoint + isFEVM = false + + # Boolean flag to filter event logs by contracts + filterLogsByAddresses = true + # Boolean flag to filter event logs by topics + filterLogsByTopics = true + + [upstream.cache] + name = "requests" + enabled = false + deleteOnStart = false + +[jobQueue] + dbConnectionString = "postgres://postgres:postgres@localhost/v2-watcher-job-queue" + maxCompletionLagInSecs = 300 + jobDelayInMilliSecs = 100 + eventsInBatch = 50 + subgraphEventsOrder = true + blockDelayInMilliSecs = 2000 + + # Boolean to switch between modes of processing events when starting the server. + # Setting to true will fetch filtered events and required blocks in a range of blocks and then process them. + # Setting to false will fetch blocks consecutively with its events and then process them (Behaviour is followed in realtime processing near head). + useBlockRanges = true + + # Block range in which logs are fetched during historical blocks processing + historicalLogsBlockRange = 2000 + + # Max block range of historical processing after which it waits for completion of events processing + # If set to -1 historical processing does not wait for events processing and completes till latest canonical block + historicalMaxFetchAhead = 10000 + + # Max number of retries to fetch new block after which watcher will failover to other RPC endpoints + maxNewBlockRetries = 3 diff --git a/packages/v2-watcher/package.json b/packages/v2-watcher/package.json new file mode 100644 index 0000000..e10ef00 --- /dev/null +++ b/packages/v2-watcher/package.json @@ -0,0 +1,77 @@ +{ + "name": "@cerc-io/v2-watcher", + "version": "0.1.0", + "description": "v2-watcher", + "private": true, + "main": "dist/index.js", + "scripts": { + "lint": "eslint --max-warnings=0 .", + "build": "yarn clean && tsc && yarn copy-assets", + "clean": "rm -rf ./dist", + "prepare": "husky install", + "copy-assets": "copyfiles -u 1 src/**/*.gql dist/", + "server": "DEBUG=vulcanize:* YARN_CHILD_PROCESS=true node --enable-source-maps dist/server.js", + "server:dev": "DEBUG=vulcanize:* YARN_CHILD_PROCESS=true ts-node src/server.ts", + "job-runner": "DEBUG=vulcanize:* YARN_CHILD_PROCESS=true node --enable-source-maps dist/job-runner.js", + "job-runner:dev": "DEBUG=vulcanize:* YARN_CHILD_PROCESS=true ts-node src/job-runner.ts", + "watch:contract": "DEBUG=vulcanize:* ts-node src/cli/watch-contract.ts", + "fill": "DEBUG=vulcanize:* ts-node src/fill.ts", + "fill:state": "DEBUG=vulcanize:* ts-node src/fill.ts --state", + "reset": "DEBUG=vulcanize:* ts-node src/cli/reset.ts", + "checkpoint": "DEBUG=vulcanize:* node --enable-source-maps dist/cli/checkpoint.js", + "checkpoint:dev": "DEBUG=vulcanize:* ts-node src/cli/checkpoint.ts", + "export-state": "DEBUG=vulcanize:* node --enable-source-maps dist/cli/export-state.js", + "export-state:dev": "DEBUG=vulcanize:* ts-node src/cli/export-state.ts", + "import-state": "DEBUG=vulcanize:* node --enable-source-maps dist/cli/import-state.js", + "import-state:dev": "DEBUG=vulcanize:* ts-node src/cli/import-state.ts", + "inspect-cid": "DEBUG=vulcanize:* ts-node src/cli/inspect-cid.ts", + "index-block": "DEBUG=vulcanize:* ts-node src/cli/index-block.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/cerc-io/watcher-ts.git" + }, + "author": "", + "license": "AGPL-3.0", + "bugs": { + "url": "https://github.com/cerc-io/watcher-ts/issues" + }, + "homepage": "https://github.com/cerc-io/watcher-ts#readme", + "dependencies": { + "@apollo/client": "^3.3.19", + "@cerc-io/cli": "^0.2.93", + "@cerc-io/ipld-eth-client": "^0.2.93", + "@cerc-io/solidity-mapper": "^0.2.93", + "@cerc-io/util": "^0.2.93", + "@cerc-io/graph-node": "^0.2.93", + "@ethersproject/providers": "^5.4.4", + "debug": "^4.3.1", + "decimal.js": "^10.3.1", + "ethers": "^5.4.4", + "graphql": "^15.5.0", + "json-bigint": "^1.0.0", + "reflect-metadata": "^0.1.13", + "typeorm": "0.2.37", + "yargs": "^17.0.1" + }, + "devDependencies": { + "@ethersproject/abi": "^5.3.0", + "@types/debug": "^4.1.5", + "@types/json-bigint": "^1.0.0", + "@types/yargs": "^17.0.0", + "@typescript-eslint/eslint-plugin": "^5.47.1", + "@typescript-eslint/parser": "^5.47.1", + "copyfiles": "^2.4.1", + "eslint": "^8.35.0", + "eslint-config-semistandard": "^15.0.1", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.1.0", + "eslint-plugin-standard": "^5.0.0", + "husky": "^7.0.2", + "ts-node": "^10.2.1", + "typescript": "^5.0.2", + "winston": "^3.13.0" + } +} diff --git a/packages/v2-watcher/src/artifacts/Factory.json b/packages/v2-watcher/src/artifacts/Factory.json new file mode 100644 index 0000000..855f67d --- /dev/null +++ b/packages/v2-watcher/src/artifacts/Factory.json @@ -0,0 +1,195 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_feeToSetter", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "pair", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "PairCreated", + "type": "event" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allPairs", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "allPairsLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + } + ], + "name": "createPair", + "outputs": [ + { + "internalType": "address", + "name": "pair", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "feeTo", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "feeToSetter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "getPair", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "_feeTo", + "type": "address" + } + ], + "name": "setFeeTo", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "_feeToSetter", + "type": "address" + } + ], + "name": "setFeeToSetter", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/packages/v2-watcher/src/artifacts/Pair.json b/packages/v2-watcher/src/artifacts/Pair.json new file mode 100644 index 0000000..65145a6 --- /dev/null +++ b/packages/v2-watcher/src/artifacts/Pair.json @@ -0,0 +1,715 @@ +{ + "abi": [ + { + "inputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0Out", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1Out", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint112", + "name": "reserve0", + "type": "uint112" + }, + { + "indexed": false, + "internalType": "uint112", + "name": "reserve1", + "type": "uint112" + } + ], + "name": "Sync", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "constant": true, + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "MINIMUM_LIQUIDITY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getReserves", + "outputs": [ + { + "internalType": "uint112", + "name": "_reserve0", + "type": "uint112" + }, + { + "internalType": "uint112", + "name": "_reserve1", + "type": "uint112" + }, + { + "internalType": "uint32", + "name": "_blockTimestampLast", + "type": "uint32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "_token0", + "type": "address" + }, + { + "internalType": "address", + "name": "_token1", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "kLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "price0CumulativeLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "price1CumulativeLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "skim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "amount0Out", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Out", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "sync", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "token0", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "token1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ] +} \ No newline at end of file diff --git a/packages/v2-watcher/src/cli/checkpoint-cmds/create.ts b/packages/v2-watcher/src/cli/checkpoint-cmds/create.ts new file mode 100644 index 0000000..e771c70 --- /dev/null +++ b/packages/v2-watcher/src/cli/checkpoint-cmds/create.ts @@ -0,0 +1,44 @@ +// +// Copyright 2022 Vulcanize, Inc. +// + +import { CreateCheckpointCmd } from '@cerc-io/cli'; +import { getGraphDbAndWatcher } from '@cerc-io/graph-node'; + +import { Database, ENTITY_QUERY_TYPE_MAP, ENTITY_TO_LATEST_ENTITY_MAP } from '../../database'; +import { Indexer } from '../../indexer'; + +export const command = 'create'; + +export const desc = 'Create checkpoint'; + +export const builder = { + address: { + type: 'string', + require: true, + demandOption: true, + describe: 'Contract address to create the checkpoint for.' + }, + blockHash: { + type: 'string', + describe: 'Blockhash at which to create the checkpoint.' + } +}; + +export const handler = async (argv: any): Promise => { + const createCheckpointCmd = new CreateCheckpointCmd(); + await createCheckpointCmd.init(argv, Database); + + const { graphWatcher } = await getGraphDbAndWatcher( + createCheckpointCmd.config.server, + createCheckpointCmd.clients.ethClient, + createCheckpointCmd.ethProvider, + createCheckpointCmd.database.baseDatabase, + ENTITY_QUERY_TYPE_MAP, + ENTITY_TO_LATEST_ENTITY_MAP + ); + + await createCheckpointCmd.initIndexer(Indexer, graphWatcher); + + await createCheckpointCmd.exec(); +}; diff --git a/packages/v2-watcher/src/cli/checkpoint-cmds/verify.ts b/packages/v2-watcher/src/cli/checkpoint-cmds/verify.ts new file mode 100644 index 0000000..3709f54 --- /dev/null +++ b/packages/v2-watcher/src/cli/checkpoint-cmds/verify.ts @@ -0,0 +1,40 @@ +// +// Copyright 2022 Vulcanize, Inc. +// + +import { VerifyCheckpointCmd } from '@cerc-io/cli'; +import { getGraphDbAndWatcher } from '@cerc-io/graph-node'; + +import { Database, ENTITY_QUERY_TYPE_MAP, ENTITY_TO_LATEST_ENTITY_MAP } from '../../database'; +import { Indexer } from '../../indexer'; + +export const command = 'verify'; + +export const desc = 'Verify checkpoint'; + +export const builder = { + cid: { + type: 'string', + alias: 'c', + demandOption: true, + describe: 'Checkpoint CID to be verified' + } +}; + +export const handler = async (argv: any): Promise => { + const verifyCheckpointCmd = new VerifyCheckpointCmd(); + await verifyCheckpointCmd.init(argv, Database); + + const { graphWatcher, graphDb } = await getGraphDbAndWatcher( + verifyCheckpointCmd.config.server, + verifyCheckpointCmd.clients.ethClient, + verifyCheckpointCmd.ethProvider, + verifyCheckpointCmd.database.baseDatabase, + ENTITY_QUERY_TYPE_MAP, + ENTITY_TO_LATEST_ENTITY_MAP + ); + + await verifyCheckpointCmd.initIndexer(Indexer, graphWatcher); + + await verifyCheckpointCmd.exec(graphDb); +}; diff --git a/packages/v2-watcher/src/cli/checkpoint.ts b/packages/v2-watcher/src/cli/checkpoint.ts new file mode 100644 index 0000000..d05ad8a --- /dev/null +++ b/packages/v2-watcher/src/cli/checkpoint.ts @@ -0,0 +1,39 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import yargs from 'yargs'; +import 'reflect-metadata'; +import debug from 'debug'; + +import { DEFAULT_CONFIG_PATH } from '@cerc-io/util'; + +import { hideBin } from 'yargs/helpers'; + +const log = debug('vulcanize:checkpoint'); + +const main = async () => { + return 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 + } + }) + .commandDir('checkpoint-cmds', { extensions: ['ts', 'js'], exclude: /([a-zA-Z0-9\s_\\.\-:])+(.d.ts)$/ }) + .demandCommand(1) + .help() + .argv; +}; + +main().then(() => { + process.exit(); +}).catch(err => { + log(err); +}); diff --git a/packages/v2-watcher/src/cli/export-state.ts b/packages/v2-watcher/src/cli/export-state.ts new file mode 100644 index 0000000..bcd1c8a --- /dev/null +++ b/packages/v2-watcher/src/cli/export-state.ts @@ -0,0 +1,38 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import 'reflect-metadata'; +import debug from 'debug'; + +import { ExportStateCmd } from '@cerc-io/cli'; +import { getGraphDbAndWatcher } from '@cerc-io/graph-node'; + +import { Database, ENTITY_QUERY_TYPE_MAP, ENTITY_TO_LATEST_ENTITY_MAP } from '../database'; +import { Indexer } from '../indexer'; + +const log = debug('vulcanize:export-state'); + +const main = async (): Promise => { + const exportStateCmd = new ExportStateCmd(); + await exportStateCmd.init(Database); + + const { graphWatcher } = await getGraphDbAndWatcher( + exportStateCmd.config.server, + exportStateCmd.clients.ethClient, + exportStateCmd.ethProvider, + exportStateCmd.database.baseDatabase, + ENTITY_QUERY_TYPE_MAP, + ENTITY_TO_LATEST_ENTITY_MAP + ); + + await exportStateCmd.initIndexer(Indexer, graphWatcher); + + await exportStateCmd.exec(); +}; + +main().catch(err => { + log(err); +}).finally(() => { + process.exit(0); +}); diff --git a/packages/v2-watcher/src/cli/import-state.ts b/packages/v2-watcher/src/cli/import-state.ts new file mode 100644 index 0000000..04ce0e8 --- /dev/null +++ b/packages/v2-watcher/src/cli/import-state.ts @@ -0,0 +1,39 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import 'reflect-metadata'; +import debug from 'debug'; + +import { ImportStateCmd } from '@cerc-io/cli'; +import { getGraphDbAndWatcher } from '@cerc-io/graph-node'; + +import { Database, ENTITY_QUERY_TYPE_MAP, ENTITY_TO_LATEST_ENTITY_MAP } from '../database'; +import { Indexer } from '../indexer'; +import { State } from '../entity/State'; + +const log = debug('vulcanize:import-state'); + +export const main = async (): Promise => { + const importStateCmd = new ImportStateCmd(); + await importStateCmd.init(Database); + + const { graphWatcher, graphDb } = await getGraphDbAndWatcher( + importStateCmd.config.server, + importStateCmd.clients.ethClient, + importStateCmd.ethProvider, + importStateCmd.database.baseDatabase, + ENTITY_QUERY_TYPE_MAP, + ENTITY_TO_LATEST_ENTITY_MAP + ); + + await importStateCmd.initIndexer(Indexer, graphWatcher); + + await importStateCmd.exec(State, graphDb); +}; + +main().catch(err => { + log(err); +}).finally(() => { + process.exit(0); +}); diff --git a/packages/v2-watcher/src/cli/index-block.ts b/packages/v2-watcher/src/cli/index-block.ts new file mode 100644 index 0000000..19a302a --- /dev/null +++ b/packages/v2-watcher/src/cli/index-block.ts @@ -0,0 +1,38 @@ +// +// Copyright 2022 Vulcanize, Inc. +// + +import 'reflect-metadata'; +import debug from 'debug'; + +import { IndexBlockCmd } from '@cerc-io/cli'; +import { getGraphDbAndWatcher } from '@cerc-io/graph-node'; + +import { Database, ENTITY_QUERY_TYPE_MAP, ENTITY_TO_LATEST_ENTITY_MAP } from '../database'; +import { Indexer } from '../indexer'; + +const log = debug('vulcanize:index-block'); + +const main = async (): Promise => { + const indexBlockCmd = new IndexBlockCmd(); + await indexBlockCmd.init(Database); + + const { graphWatcher } = await getGraphDbAndWatcher( + indexBlockCmd.config.server, + indexBlockCmd.clients.ethClient, + indexBlockCmd.ethProvider, + indexBlockCmd.database.baseDatabase, + ENTITY_QUERY_TYPE_MAP, + ENTITY_TO_LATEST_ENTITY_MAP + ); + + await indexBlockCmd.initIndexer(Indexer, graphWatcher); + + await indexBlockCmd.exec(); +}; + +main().catch(err => { + log(err); +}).finally(() => { + process.exit(0); +}); diff --git a/packages/v2-watcher/src/cli/inspect-cid.ts b/packages/v2-watcher/src/cli/inspect-cid.ts new file mode 100644 index 0000000..4f5955e --- /dev/null +++ b/packages/v2-watcher/src/cli/inspect-cid.ts @@ -0,0 +1,38 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import 'reflect-metadata'; +import debug from 'debug'; + +import { InspectCIDCmd } from '@cerc-io/cli'; +import { getGraphDbAndWatcher } from '@cerc-io/graph-node'; + +import { Database, ENTITY_QUERY_TYPE_MAP, ENTITY_TO_LATEST_ENTITY_MAP } from '../database'; +import { Indexer } from '../indexer'; + +const log = debug('vulcanize:inspect-cid'); + +const main = async (): Promise => { + const inspectCIDCmd = new InspectCIDCmd(); + await inspectCIDCmd.init(Database); + + const { graphWatcher } = await getGraphDbAndWatcher( + inspectCIDCmd.config.server, + inspectCIDCmd.clients.ethClient, + inspectCIDCmd.ethProvider, + inspectCIDCmd.database.baseDatabase, + ENTITY_QUERY_TYPE_MAP, + ENTITY_TO_LATEST_ENTITY_MAP + ); + + await inspectCIDCmd.initIndexer(Indexer, graphWatcher); + + await inspectCIDCmd.exec(); +}; + +main().catch(err => { + log(err); +}).finally(() => { + process.exit(0); +}); diff --git a/packages/v2-watcher/src/cli/reset-cmds/job-queue.ts b/packages/v2-watcher/src/cli/reset-cmds/job-queue.ts new file mode 100644 index 0000000..c33cbfd --- /dev/null +++ b/packages/v2-watcher/src/cli/reset-cmds/job-queue.ts @@ -0,0 +1,22 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import debug from 'debug'; + +import { getConfig, resetJobs, Config } from '@cerc-io/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 => { + const config: Config = await getConfig(argv.configFile); + await resetJobs(config); + + log('Job queue reset successfully'); +}; diff --git a/packages/v2-watcher/src/cli/reset-cmds/state.ts b/packages/v2-watcher/src/cli/reset-cmds/state.ts new file mode 100644 index 0000000..33211d6 --- /dev/null +++ b/packages/v2-watcher/src/cli/reset-cmds/state.ts @@ -0,0 +1,24 @@ +// +// Copyright 2022 Vulcanize, Inc. +// + +import { ResetStateCmd } from '@cerc-io/cli'; + +import { Database } from '../../database'; + +export const command = 'state'; + +export const desc = 'Reset State to a given block number'; + +export const builder = { + blockNumber: { + type: 'number' + } +}; + +export const handler = async (argv: any): Promise => { + const resetStateCmd = new ResetStateCmd(); + await resetStateCmd.init(argv, Database); + + await resetStateCmd.exec(); +}; diff --git a/packages/v2-watcher/src/cli/reset-cmds/watcher.ts b/packages/v2-watcher/src/cli/reset-cmds/watcher.ts new file mode 100644 index 0000000..827fd28 --- /dev/null +++ b/packages/v2-watcher/src/cli/reset-cmds/watcher.ts @@ -0,0 +1,37 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { ResetWatcherCmd } from '@cerc-io/cli'; +import { getGraphDbAndWatcher } from '@cerc-io/graph-node'; + +import { Database, ENTITY_QUERY_TYPE_MAP, ENTITY_TO_LATEST_ENTITY_MAP } from '../../database'; +import { Indexer } from '../../indexer'; + +export const command = 'watcher'; + +export const desc = 'Reset watcher to a block number'; + +export const builder = { + blockNumber: { + type: 'number' + } +}; + +export const handler = async (argv: any): Promise => { + const resetWatcherCmd = new ResetWatcherCmd(); + await resetWatcherCmd.init(argv, Database); + + const { graphWatcher } = await getGraphDbAndWatcher( + resetWatcherCmd.config.server, + resetWatcherCmd.clients.ethClient, + resetWatcherCmd.ethProvider, + resetWatcherCmd.database.baseDatabase, + ENTITY_QUERY_TYPE_MAP, + ENTITY_TO_LATEST_ENTITY_MAP + ); + + await resetWatcherCmd.initIndexer(Indexer, graphWatcher); + + await resetWatcherCmd.exec(); +}; diff --git a/packages/v2-watcher/src/cli/reset.ts b/packages/v2-watcher/src/cli/reset.ts new file mode 100644 index 0000000..95648c8 --- /dev/null +++ b/packages/v2-watcher/src/cli/reset.ts @@ -0,0 +1,24 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import 'reflect-metadata'; +import debug from 'debug'; + +import { getResetYargs } from '@cerc-io/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); +}); diff --git a/packages/v2-watcher/src/cli/watch-contract.ts b/packages/v2-watcher/src/cli/watch-contract.ts new file mode 100644 index 0000000..7d6ce1a --- /dev/null +++ b/packages/v2-watcher/src/cli/watch-contract.ts @@ -0,0 +1,38 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import 'reflect-metadata'; +import debug from 'debug'; + +import { WatchContractCmd } from '@cerc-io/cli'; +import { getGraphDbAndWatcher } from '@cerc-io/graph-node'; + +import { Database, ENTITY_QUERY_TYPE_MAP, ENTITY_TO_LATEST_ENTITY_MAP } from '../database'; +import { Indexer } from '../indexer'; + +const log = debug('vulcanize:watch-contract'); + +const main = async (): Promise => { + const watchContractCmd = new WatchContractCmd(); + await watchContractCmd.init(Database); + + const { graphWatcher } = await getGraphDbAndWatcher( + watchContractCmd.config.server, + watchContractCmd.clients.ethClient, + watchContractCmd.ethProvider, + watchContractCmd.database.baseDatabase, + ENTITY_QUERY_TYPE_MAP, + ENTITY_TO_LATEST_ENTITY_MAP + ); + + await watchContractCmd.initIndexer(Indexer, graphWatcher); + + await watchContractCmd.exec(); +}; + +main().catch(err => { + log(err); +}).finally(() => { + process.exit(0); +}); diff --git a/packages/v2-watcher/src/client.ts b/packages/v2-watcher/src/client.ts new file mode 100644 index 0000000..8bb2bb0 --- /dev/null +++ b/packages/v2-watcher/src/client.ts @@ -0,0 +1,55 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { gql } from '@apollo/client/core'; +import { GraphQLClient, GraphQLConfig } from '@cerc-io/ipld-eth-client'; + +import { queries, mutations, subscriptions } from './gql'; + +export class Client { + _config: GraphQLConfig; + _client: GraphQLClient; + + constructor (config: GraphQLConfig) { + this._config = config; + + this._client = new GraphQLClient(config); + } + + async getEvents (blockHash: string, contractAddress: string, name: string): Promise { + const { events } = await this._client.query( + gql(queries.events), + { blockHash, contractAddress, name } + ); + + return events; + } + + async getEventsInRange (fromBlockNumber: number, toBlockNumber: number): Promise { + const { eventsInRange } = await this._client.query( + gql(queries.eventsInRange), + { fromBlockNumber, toBlockNumber } + ); + + return eventsInRange; + } + + async watchContract (contractAddress: string, startingBlock?: number): Promise { + const { watchContract } = await this._client.mutate( + gql(mutations.watchContract), + { contractAddress, startingBlock } + ); + + return watchContract; + } + + async watchEvents (onNext: (value: any) => void): Promise { + return this._client.subscribe( + gql(subscriptions.onEvent), + ({ data }) => { + onNext(data.onEvent); + } + ); + } +} diff --git a/packages/v2-watcher/src/database.ts b/packages/v2-watcher/src/database.ts new file mode 100644 index 0000000..4ed726c --- /dev/null +++ b/packages/v2-watcher/src/database.ts @@ -0,0 +1,299 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import assert from 'assert'; +import { Connection, ConnectionOptions, DeepPartial, FindConditions, QueryRunner, FindManyOptions, EntityTarget } from 'typeorm'; +import path from 'path'; + +import { + ENTITY_QUERY_TYPE, + Database as BaseDatabase, + DatabaseInterface, + QueryOptions, + StateKind, + Where +} from '@cerc-io/util'; + +import { Contract } from './entity/Contract'; +import { Event } from './entity/Event'; +import { SyncStatus } from './entity/SyncStatus'; +import { StateSyncStatus } from './entity/StateSyncStatus'; +import { BlockProgress } from './entity/BlockProgress'; +import { State } from './entity/State'; +import { UniswapFactory } from './entity/UniswapFactory'; +import { Token } from './entity/Token'; +import { Pair } from './entity/Pair'; +import { User } from './entity/User'; +import { LiquidityPosition } from './entity/LiquidityPosition'; +import { LiquidityPositionSnapshot } from './entity/LiquidityPositionSnapshot'; +import { Transaction } from './entity/Transaction'; +import { Mint } from './entity/Mint'; +import { Burn } from './entity/Burn'; +import { Swap } from './entity/Swap'; +import { Bundle } from './entity/Bundle'; +import { UniswapDayData } from './entity/UniswapDayData'; +import { PairHourData } from './entity/PairHourData'; +import { PairDayData } from './entity/PairDayData'; +import { TokenDayData } from './entity/TokenDayData'; + +export const SUBGRAPH_ENTITIES = new Set([UniswapFactory, Token, Pair, User, LiquidityPosition, LiquidityPositionSnapshot, Transaction, Mint, Burn, Swap, Bundle, UniswapDayData, PairHourData, PairDayData, TokenDayData]); +export const ENTITIES = [...SUBGRAPH_ENTITIES]; +// Map: Entity to suitable query type. +export const ENTITY_QUERY_TYPE_MAP = new Map any, ENTITY_QUERY_TYPE>([]); + +export const ENTITY_TO_LATEST_ENTITY_MAP: Map = new Map(); + +export class Database implements DatabaseInterface { + _config: ConnectionOptions; + _conn!: Connection; + _baseDatabase: BaseDatabase; + _propColMaps: { [key: string]: Map; }; + + constructor (config: ConnectionOptions) { + assert(config); + + this._config = { + ...config, + subscribers: [path.join(__dirname, 'entity/Subscriber.*')], + entities: [path.join(__dirname, 'entity/*')] + }; + + this._baseDatabase = new BaseDatabase(this._config); + this._propColMaps = {}; + } + + get baseDatabase (): BaseDatabase { + return this._baseDatabase; + } + + async init (): Promise { + this._conn = await this._baseDatabase.init(); + } + + async close (): Promise { + return this._baseDatabase.close(); + } + + getNewState (): State { + return new State(); + } + + async getStates (where: FindConditions): Promise { + const repo = this._conn.getRepository(State); + + return this._baseDatabase.getStates(repo, where); + } + + async getLatestState (contractAddress: string, kind: StateKind | null, blockNumber?: number): Promise { + const repo = this._conn.getRepository(State); + + return this._baseDatabase.getLatestState(repo, contractAddress, kind, blockNumber); + } + + async getPrevState (blockHash: string, contractAddress: string, kind?: string): Promise { + const repo = this._conn.getRepository(State); + + return this._baseDatabase.getPrevState(repo, blockHash, contractAddress, kind); + } + + // Fetch all diff States after the specified block number. + async getDiffStatesInRange (contractAddress: string, startblock: number, endBlock: number): Promise { + const repo = this._conn.getRepository(State); + + return this._baseDatabase.getDiffStatesInRange(repo, contractAddress, startblock, endBlock); + } + + async saveOrUpdateState (dbTx: QueryRunner, state: State): Promise { + const repo = dbTx.manager.getRepository(State); + + return this._baseDatabase.saveOrUpdateState(repo, state); + } + + async removeStates (dbTx: QueryRunner, blockNumber: number, kind: string): Promise { + const repo = dbTx.manager.getRepository(State); + + await this._baseDatabase.removeStates(repo, blockNumber, kind); + } + + async removeStatesAfterBlock (dbTx: QueryRunner, blockNumber: number): Promise { + const repo = dbTx.manager.getRepository(State); + + await this._baseDatabase.removeStatesAfterBlock(repo, blockNumber); + } + + async getStateSyncStatus (): Promise { + const repo = this._conn.getRepository(StateSyncStatus); + + return this._baseDatabase.getStateSyncStatus(repo); + } + + async updateStateSyncStatusIndexedBlock (queryRunner: QueryRunner, blockNumber: number, force?: boolean): Promise { + const repo = queryRunner.manager.getRepository(StateSyncStatus); + + return this._baseDatabase.updateStateSyncStatusIndexedBlock(repo, blockNumber, force); + } + + async updateStateSyncStatusCheckpointBlock (queryRunner: QueryRunner, blockNumber: number, force?: boolean): Promise { + const repo = queryRunner.manager.getRepository(StateSyncStatus); + + return this._baseDatabase.updateStateSyncStatusCheckpointBlock(repo, blockNumber, force); + } + + async getContracts (): Promise { + const repo = this._conn.getRepository(Contract); + + return this._baseDatabase.getContracts(repo); + } + + async createTransactionRunner (): Promise { + 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> { + const repo = this._conn.getRepository(Event); + + return this._baseDatabase.getEventsInRange(repo, fromBlockNumber, toBlockNumber); + } + + async saveEventEntity (queryRunner: QueryRunner, entity: Event): Promise { + const repo = queryRunner.manager.getRepository(Event); + return this._baseDatabase.saveEventEntity(repo, entity); + } + + async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise { + const repo = this._conn.getRepository(Event); + + return this._baseDatabase.getBlockEvents(repo, blockHash, where, queryOptions); + } + + async saveBlockWithEvents (queryRunner: QueryRunner, block: DeepPartial, events: DeepPartial[]): Promise { + const blockRepo = queryRunner.manager.getRepository(BlockProgress); + const eventRepo = queryRunner.manager.getRepository(Event); + + return this._baseDatabase.saveBlockWithEvents(blockRepo, eventRepo, block, events); + } + + async saveEvents (queryRunner: QueryRunner, events: Event[]): Promise { + const eventRepo = queryRunner.manager.getRepository(Event); + + return this._baseDatabase.saveEvents(eventRepo, events); + } + + async saveBlockProgress (queryRunner: QueryRunner, block: DeepPartial): Promise { + const repo = queryRunner.manager.getRepository(BlockProgress); + + return this._baseDatabase.saveBlockProgress(repo, block); + } + + async saveContract (queryRunner: QueryRunner, address: string, kind: string, checkpoint: boolean, startingBlock: number, context?: any): Promise { + const repo = queryRunner.manager.getRepository(Contract); + + return this._baseDatabase.saveContract(repo, address, kind, checkpoint, startingBlock, context); + } + + async updateSyncStatusIndexedBlock (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force = false): Promise { + 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 { + 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 { + const repo = queryRunner.manager.getRepository(SyncStatus); + + return this._baseDatabase.updateSyncStatusChainHead(repo, blockHash, blockNumber, force); + } + + async updateSyncStatusProcessedBlock (queryRunner: QueryRunner, blockHash: string, blockNumber: number, force = false): Promise { + const repo = queryRunner.manager.getRepository(SyncStatus); + + return this._baseDatabase.updateSyncStatusProcessedBlock(repo, blockHash, blockNumber, force); + } + + async updateSyncStatusIndexingError (queryRunner: QueryRunner, hasIndexingError: boolean): Promise { + const repo = queryRunner.manager.getRepository(SyncStatus); + + return this._baseDatabase.updateSyncStatusIndexingError(repo, hasIndexingError); + } + + async updateSyncStatus (queryRunner: QueryRunner, syncStatus: DeepPartial): Promise { + const repo = queryRunner.manager.getRepository(SyncStatus); + + return this._baseDatabase.updateSyncStatus(repo, syncStatus); + } + + async getSyncStatus (queryRunner: QueryRunner): Promise { + const repo = queryRunner.manager.getRepository(SyncStatus); + + return this._baseDatabase.getSyncStatus(repo); + } + + async getEvent (id: string): Promise { + const repo = this._conn.getRepository(Event); + + return this._baseDatabase.getEvent(repo, id); + } + + async getBlocksAtHeight (height: number, isPruned: boolean): Promise { + const repo = this._conn.getRepository(BlockProgress); + + return this._baseDatabase.getBlocksAtHeight(repo, height, isPruned); + } + + async markBlocksAsPruned (queryRunner: QueryRunner, blocks: BlockProgress[]): Promise { + const repo = queryRunner.manager.getRepository(BlockProgress); + + return this._baseDatabase.markBlocksAsPruned(repo, blocks); + } + + async getBlockProgress (blockHash: string): Promise { + const repo = this._conn.getRepository(BlockProgress); + return this._baseDatabase.getBlockProgress(repo, blockHash); + } + + async getBlockProgressEntities (where: FindConditions, options: FindManyOptions): Promise { + const repo = this._conn.getRepository(BlockProgress); + + return this._baseDatabase.getBlockProgressEntities(repo, where, options); + } + + async getEntitiesForBlock (blockHash: string, tableName: string): Promise { + return this._baseDatabase.getEntitiesForBlock(blockHash, tableName); + } + + async updateBlockProgress (queryRunner: QueryRunner, block: BlockProgress, lastProcessedEventIndex: number): Promise { + const repo = queryRunner.manager.getRepository(BlockProgress); + + return this._baseDatabase.updateBlockProgress(repo, block, lastProcessedEventIndex); + } + + async removeEntities (queryRunner: QueryRunner, entity: new () => Entity, findConditions?: FindManyOptions | FindConditions): Promise { + return this._baseDatabase.removeEntities(queryRunner, entity, findConditions); + } + + async deleteEntitiesByConditions (queryRunner: QueryRunner, entity: EntityTarget, findConditions: FindConditions): Promise { + await this._baseDatabase.deleteEntitiesByConditions(queryRunner, entity, findConditions); + } + + async getAncestorAtHeight (blockHash: string, height: number): Promise { + return this._baseDatabase.getAncestorAtHeight(blockHash, height); + } + + _getPropertyColumnMapForEntity (entityName: string): Map { + return this._conn.getMetadata(entityName).ownColumns.reduce((acc, curr) => { + return acc.set(curr.propertyName, curr.databaseName); + }, new Map()); + } +} diff --git a/packages/v2-watcher/src/entity/BlockProgress.ts b/packages/v2-watcher/src/entity/BlockProgress.ts new file mode 100644 index 0000000..ded4a86 --- /dev/null +++ b/packages/v2-watcher/src/entity/BlockProgress.ts @@ -0,0 +1,48 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryGeneratedColumn, Column, Index, CreateDateColumn } from 'typeorm'; +import { BlockProgressInterface } from '@cerc-io/util'; + +@Entity() +@Index(['blockHash'], { unique: true }) +@Index(['blockNumber']) +@Index(['parentHash']) +export class BlockProgress implements BlockProgressInterface { + @PrimaryGeneratedColumn() + id!: number; + + @Column('varchar', { nullable: true }) + cid!: string | null; + + @Column('varchar', { length: 66 }) + blockHash!: string; + + @Column('varchar', { length: 66 }) + 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; +} diff --git a/packages/v2-watcher/src/entity/Bundle.ts b/packages/v2-watcher/src/entity/Bundle.ts new file mode 100644 index 0000000..2623f11 --- /dev/null +++ b/packages/v2-watcher/src/entity/Bundle.ts @@ -0,0 +1,29 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class Bundle { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('numeric', { transformer: decimalTransformer }) + ethPrice!: Decimal; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/Burn.ts b/packages/v2-watcher/src/entity/Burn.ts new file mode 100644 index 0000000..45c54e9 --- /dev/null +++ b/packages/v2-watcher/src/entity/Burn.ts @@ -0,0 +1,65 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer, bigintTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class Burn { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('varchar') + transaction!: string; + + @Column('numeric', { transformer: bigintTransformer }) + timestamp!: bigint; + + @Column('varchar') + pair!: string; + + @Column('numeric', { transformer: decimalTransformer }) + liquidity!: Decimal; + + @Column('varchar', { nullable: true }) + sender!: string | null; + + @Column('numeric', { nullable: true, transformer: decimalTransformer }) + amount0!: Decimal | null; + + @Column('numeric', { nullable: true, transformer: decimalTransformer }) + amount1!: Decimal | null; + + @Column('varchar', { nullable: true }) + to!: string | null; + + @Column('numeric', { nullable: true, transformer: bigintTransformer }) + logIndex!: bigint | null; + + @Column('numeric', { nullable: true, transformer: decimalTransformer }) + amountUSD!: Decimal | null; + + @Column('boolean') + needsComplete!: boolean; + + @Column('varchar', { nullable: true }) + feeTo!: string | null; + + @Column('numeric', { nullable: true, transformer: decimalTransformer }) + feeLiquidity!: Decimal | null; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/Contract.ts b/packages/v2-watcher/src/entity/Contract.ts new file mode 100644 index 0000000..e4defa8 --- /dev/null +++ b/packages/v2-watcher/src/entity/Contract.ts @@ -0,0 +1,27 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm'; + +@Entity() +@Index(['address'], { unique: true }) +export class Contract { + @PrimaryGeneratedColumn() + id!: number; + + @Column('varchar', { length: 42 }) + address!: string; + + @Column('varchar') + kind!: string; + + @Column('boolean') + checkpoint!: boolean; + + @Column('integer') + startingBlock!: number; + + @Column('jsonb', { nullable: true }) + context!: Record; +} diff --git a/packages/v2-watcher/src/entity/Event.ts b/packages/v2-watcher/src/entity/Event.ts new file mode 100644 index 0000000..91f1e6d --- /dev/null +++ b/packages/v2-watcher/src/entity/Event.ts @@ -0,0 +1,38 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryGeneratedColumn, Column, Index, ManyToOne } from 'typeorm'; +import { BlockProgress } from './BlockProgress'; + +@Entity() +@Index(['block', 'contract']) +@Index(['block', 'contract', 'eventName']) +export class Event { + @PrimaryGeneratedColumn() + id!: number; + + @ManyToOne(() => BlockProgress, { onDelete: 'CASCADE' }) + block!: BlockProgress; + + @Column('varchar', { length: 66 }) + txHash!: string; + + @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; +} diff --git a/packages/v2-watcher/src/entity/FrothyEntity.ts b/packages/v2-watcher/src/entity/FrothyEntity.ts new file mode 100644 index 0000000..9898ce8 --- /dev/null +++ b/packages/v2-watcher/src/entity/FrothyEntity.ts @@ -0,0 +1,21 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; + +@Entity() +@Index(['blockNumber']) +export class FrothyEntity { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar') + name!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; +} diff --git a/packages/v2-watcher/src/entity/LiquidityPosition.ts b/packages/v2-watcher/src/entity/LiquidityPosition.ts new file mode 100644 index 0000000..cd23620 --- /dev/null +++ b/packages/v2-watcher/src/entity/LiquidityPosition.ts @@ -0,0 +1,35 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class LiquidityPosition { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('varchar') + user!: string; + + @Column('varchar') + pair!: string; + + @Column('numeric', { transformer: decimalTransformer }) + liquidityTokenBalance!: Decimal; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/LiquidityPositionSnapshot.ts b/packages/v2-watcher/src/entity/LiquidityPositionSnapshot.ts new file mode 100644 index 0000000..79e32c3 --- /dev/null +++ b/packages/v2-watcher/src/entity/LiquidityPositionSnapshot.ts @@ -0,0 +1,62 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class LiquidityPositionSnapshot { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('varchar') + liquidityPosition!: string; + + @Column('integer') + timestamp!: number; + + @Column('integer') + block!: number; + + @Column('varchar') + user!: string; + + @Column('varchar') + pair!: string; + + @Column('numeric', { transformer: decimalTransformer }) + token0PriceUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + token1PriceUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + reserve0!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + reserve1!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + reserveUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + liquidityTokenTotalSupply!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + liquidityTokenBalance!: Decimal; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/Mint.ts b/packages/v2-watcher/src/entity/Mint.ts new file mode 100644 index 0000000..ec1512a --- /dev/null +++ b/packages/v2-watcher/src/entity/Mint.ts @@ -0,0 +1,62 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer, bigintTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class Mint { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('varchar') + transaction!: string; + + @Column('numeric', { transformer: bigintTransformer }) + timestamp!: bigint; + + @Column('varchar') + pair!: string; + + @Column('varchar') + to!: string; + + @Column('numeric', { transformer: decimalTransformer }) + liquidity!: Decimal; + + @Column('varchar', { nullable: true }) + sender!: string | null; + + @Column('numeric', { nullable: true, transformer: decimalTransformer }) + amount0!: Decimal | null; + + @Column('numeric', { nullable: true, transformer: decimalTransformer }) + amount1!: Decimal | null; + + @Column('numeric', { nullable: true, transformer: bigintTransformer }) + logIndex!: bigint | null; + + @Column('numeric', { nullable: true, transformer: decimalTransformer }) + amountUSD!: Decimal | null; + + @Column('varchar', { nullable: true }) + feeTo!: string | null; + + @Column('numeric', { nullable: true, transformer: decimalTransformer }) + feeLiquidity!: Decimal | null; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/Pair.ts b/packages/v2-watcher/src/entity/Pair.ts new file mode 100644 index 0000000..fa0bf2c --- /dev/null +++ b/packages/v2-watcher/src/entity/Pair.ts @@ -0,0 +1,80 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer, bigintTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class Pair { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('varchar') + token0!: string; + + @Column('varchar') + token1!: string; + + @Column('numeric', { transformer: decimalTransformer }) + reserve0!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + reserve1!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + totalSupply!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + reserveETH!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + reserveUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + trackedReserveETH!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + token0Price!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + token1Price!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + volumeToken0!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + volumeToken1!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + volumeUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + untrackedVolumeUSD!: Decimal; + + @Column('numeric', { transformer: bigintTransformer }) + txCount!: bigint; + + @Column('numeric', { transformer: bigintTransformer }) + createdAtTimestamp!: bigint; + + @Column('numeric', { transformer: bigintTransformer }) + createdAtBlockNumber!: bigint; + + @Column('numeric', { transformer: bigintTransformer }) + liquidityProviderCount!: bigint; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/PairDayData.ts b/packages/v2-watcher/src/entity/PairDayData.ts new file mode 100644 index 0000000..8ce018b --- /dev/null +++ b/packages/v2-watcher/src/entity/PairDayData.ts @@ -0,0 +1,62 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer, bigintTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class PairDayData { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('integer') + date!: number; + + @Column('varchar') + pairAddress!: string; + + @Column('varchar') + token0!: string; + + @Column('varchar') + token1!: string; + + @Column('numeric', { transformer: decimalTransformer }) + reserve0!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + reserve1!: Decimal; + + @Column('numeric', { nullable: true, transformer: decimalTransformer }) + totalSupply!: Decimal | null; + + @Column('numeric', { transformer: decimalTransformer }) + reserveUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + dailyVolumeToken0!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + dailyVolumeToken1!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + dailyVolumeUSD!: Decimal; + + @Column('numeric', { transformer: bigintTransformer }) + dailyTxns!: bigint; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/PairHourData.ts b/packages/v2-watcher/src/entity/PairHourData.ts new file mode 100644 index 0000000..173c8fc --- /dev/null +++ b/packages/v2-watcher/src/entity/PairHourData.ts @@ -0,0 +1,56 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer, bigintTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class PairHourData { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('integer') + hourStartUnix!: number; + + @Column('varchar') + pair!: string; + + @Column('numeric', { transformer: decimalTransformer }) + reserve0!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + reserve1!: Decimal; + + @Column('numeric', { nullable: true, transformer: decimalTransformer }) + totalSupply!: Decimal | null; + + @Column('numeric', { transformer: decimalTransformer }) + reserveUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + hourlyVolumeToken0!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + hourlyVolumeToken1!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + hourlyVolumeUSD!: Decimal; + + @Column('numeric', { transformer: bigintTransformer }) + hourlyTxns!: bigint; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/State.ts b/packages/v2-watcher/src/entity/State.ts new file mode 100644 index 0000000..bc05bca --- /dev/null +++ b/packages/v2-watcher/src/entity/State.ts @@ -0,0 +1,31 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryGeneratedColumn, Column, Index, ManyToOne } from 'typeorm'; +import { StateKind } from '@cerc-io/util'; +import { BlockProgress } from './BlockProgress'; + +@Entity() +@Index(['cid'], { unique: true }) +@Index(['block', 'contractAddress']) +@Index(['block', 'contractAddress', 'kind'], { unique: true }) +export class State { + @PrimaryGeneratedColumn() + id!: number; + + @ManyToOne(() => BlockProgress, { onDelete: 'CASCADE' }) + block!: BlockProgress; + + @Column('varchar', { length: 42 }) + contractAddress!: string; + + @Column('varchar') + cid!: string; + + @Column({ type: 'enum', enum: StateKind }) + kind!: StateKind; + + @Column('bytea') + data!: Buffer; +} diff --git a/packages/v2-watcher/src/entity/StateSyncStatus.ts b/packages/v2-watcher/src/entity/StateSyncStatus.ts new file mode 100644 index 0000000..1535eb4 --- /dev/null +++ b/packages/v2-watcher/src/entity/StateSyncStatus.ts @@ -0,0 +1,17 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; + +@Entity() +export class StateSyncStatus { + @PrimaryGeneratedColumn() + id!: number; + + @Column('integer') + latestIndexedBlockNumber!: number; + + @Column('integer') + latestCheckpointBlockNumber!: number; +} diff --git a/packages/v2-watcher/src/entity/Subscriber.ts b/packages/v2-watcher/src/entity/Subscriber.ts new file mode 100644 index 0000000..2cccb84 --- /dev/null +++ b/packages/v2-watcher/src/entity/Subscriber.ts @@ -0,0 +1,21 @@ +// +// Copyright 2022 Vulcanize, Inc. +// + +import { EventSubscriber, EntitySubscriberInterface, InsertEvent, UpdateEvent } from 'typeorm'; + +import { afterEntityInsertOrUpdate } from '@cerc-io/util'; + +import { FrothyEntity } from './FrothyEntity'; +import { ENTITY_TO_LATEST_ENTITY_MAP, SUBGRAPH_ENTITIES } from '../database'; + +@EventSubscriber() +export class EntitySubscriber implements EntitySubscriberInterface { + async afterInsert (event: InsertEvent): Promise { + await afterEntityInsertOrUpdate(FrothyEntity, SUBGRAPH_ENTITIES, event, ENTITY_TO_LATEST_ENTITY_MAP); + } + + async afterUpdate (event: UpdateEvent): Promise { + await afterEntityInsertOrUpdate(FrothyEntity, SUBGRAPH_ENTITIES, event, ENTITY_TO_LATEST_ENTITY_MAP); + } +} diff --git a/packages/v2-watcher/src/entity/Swap.ts b/packages/v2-watcher/src/entity/Swap.ts new file mode 100644 index 0000000..d21d58e --- /dev/null +++ b/packages/v2-watcher/src/entity/Swap.ts @@ -0,0 +1,62 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer, bigintTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class Swap { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('varchar') + transaction!: string; + + @Column('numeric', { transformer: bigintTransformer }) + timestamp!: bigint; + + @Column('varchar') + pair!: string; + + @Column('varchar') + sender!: string; + + @Column('varchar') + from!: string; + + @Column('numeric', { transformer: decimalTransformer }) + amount0In!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + amount1In!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + amount0Out!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + amount1Out!: Decimal; + + @Column('varchar') + to!: string; + + @Column('numeric', { nullable: true, transformer: bigintTransformer }) + logIndex!: bigint | null; + + @Column('numeric', { transformer: decimalTransformer }) + amountUSD!: Decimal; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/SyncStatus.ts b/packages/v2-watcher/src/entity/SyncStatus.ts new file mode 100644 index 0000000..cc13c70 --- /dev/null +++ b/packages/v2-watcher/src/entity/SyncStatus.ts @@ -0,0 +1,45 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; +import { SyncStatusInterface } from '@cerc-io/util'; + +@Entity() +export class SyncStatus implements SyncStatusInterface { + @PrimaryGeneratedColumn() + id!: number; + + @Column('varchar', { length: 66 }) + chainHeadBlockHash!: string; + + @Column('integer') + chainHeadBlockNumber!: number; + + @Column('varchar', { length: 66 }) + latestIndexedBlockHash!: string; + + @Column('integer') + latestIndexedBlockNumber!: number; + + @Column('varchar', { length: 66 }) + latestProcessedBlockHash!: string; + + @Column('integer') + latestProcessedBlockNumber!: number; + + @Column('varchar', { length: 66 }) + latestCanonicalBlockHash!: string; + + @Column('integer') + latestCanonicalBlockNumber!: number; + + @Column('varchar', { length: 66 }) + initialIndexedBlockHash!: string; + + @Column('integer') + initialIndexedBlockNumber!: number; + + @Column('boolean', { default: false }) + hasIndexingError!: boolean; +} diff --git a/packages/v2-watcher/src/entity/Token.ts b/packages/v2-watcher/src/entity/Token.ts new file mode 100644 index 0000000..9abb9f6 --- /dev/null +++ b/packages/v2-watcher/src/entity/Token.ts @@ -0,0 +1,56 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer, bigintTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class Token { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('varchar') + symbol!: string; + + @Column('varchar') + name!: string; + + @Column('numeric', { transformer: bigintTransformer }) + decimals!: bigint; + + @Column('numeric', { transformer: bigintTransformer }) + totalSupply!: bigint; + + @Column('numeric', { transformer: decimalTransformer }) + tradeVolume!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + tradeVolumeUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + untrackedVolumeUSD!: Decimal; + + @Column('numeric', { transformer: bigintTransformer }) + txCount!: bigint; + + @Column('numeric', { transformer: decimalTransformer }) + totalLiquidity!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + derivedETH!: Decimal; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/TokenDayData.ts b/packages/v2-watcher/src/entity/TokenDayData.ts new file mode 100644 index 0000000..e9ccbaa --- /dev/null +++ b/packages/v2-watcher/src/entity/TokenDayData.ts @@ -0,0 +1,56 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer, bigintTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class TokenDayData { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('integer') + date!: number; + + @Column('varchar') + token!: string; + + @Column('numeric', { transformer: decimalTransformer }) + dailyVolumeToken!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + dailyVolumeETH!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + dailyVolumeUSD!: Decimal; + + @Column('numeric', { transformer: bigintTransformer }) + dailyTxns!: bigint; + + @Column('numeric', { transformer: decimalTransformer }) + totalLiquidityToken!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + totalLiquidityETH!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + totalLiquidityUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + priceUSD!: Decimal; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/Transaction.ts b/packages/v2-watcher/src/entity/Transaction.ts new file mode 100644 index 0000000..62c44e0 --- /dev/null +++ b/packages/v2-watcher/src/entity/Transaction.ts @@ -0,0 +1,40 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { bigintTransformer } from '@cerc-io/util'; + +@Entity() +@Index(['blockNumber']) +export class Transaction { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('numeric', { transformer: bigintTransformer }) + _blockNumber!: bigint; + + @Column('numeric', { transformer: bigintTransformer }) + timestamp!: bigint; + + @Column('varchar', { array: true }) + mints!: string[]; + + @Column('varchar', { array: true }) + burns!: string[]; + + @Column('varchar', { array: true }) + swaps!: string[]; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/UniswapDayData.ts b/packages/v2-watcher/src/entity/UniswapDayData.ts new file mode 100644 index 0000000..bb35d61 --- /dev/null +++ b/packages/v2-watcher/src/entity/UniswapDayData.ts @@ -0,0 +1,53 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer, bigintTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class UniswapDayData { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('integer') + date!: number; + + @Column('numeric', { transformer: decimalTransformer }) + dailyVolumeETH!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + dailyVolumeUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + dailyVolumeUntracked!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + totalVolumeETH!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + totalLiquidityETH!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + totalVolumeUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + totalLiquidityUSD!: Decimal; + + @Column('numeric', { transformer: bigintTransformer }) + txCount!: bigint; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/UniswapFactory.ts b/packages/v2-watcher/src/entity/UniswapFactory.ts new file mode 100644 index 0000000..cb67ebd --- /dev/null +++ b/packages/v2-watcher/src/entity/UniswapFactory.ts @@ -0,0 +1,47 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer, bigintTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class UniswapFactory { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('integer') + pairCount!: number; + + @Column('numeric', { transformer: decimalTransformer }) + totalVolumeUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + totalVolumeETH!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + untrackedVolumeUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + totalLiquidityUSD!: Decimal; + + @Column('numeric', { transformer: decimalTransformer }) + totalLiquidityETH!: Decimal; + + @Column('numeric', { transformer: bigintTransformer }) + txCount!: bigint; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/entity/User.ts b/packages/v2-watcher/src/entity/User.ts new file mode 100644 index 0000000..df69b26 --- /dev/null +++ b/packages/v2-watcher/src/entity/User.ts @@ -0,0 +1,29 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import { Entity, PrimaryColumn, Column, Index } from 'typeorm'; +import { decimalTransformer } from '@cerc-io/util'; +import { Decimal } from 'decimal.js'; + +@Entity() +@Index(['blockNumber']) +export class User { + @PrimaryColumn('varchar') + id!: string; + + @PrimaryColumn('varchar', { length: 66 }) + blockHash!: string; + + @Column('integer') + blockNumber!: number; + + @Column('numeric', { transformer: decimalTransformer }) + usdSwapped!: Decimal; + + @Column('boolean', { default: false }) + isPruned!: boolean; + + @Column('boolean', { default: false }) + isRemoved!: boolean; +} diff --git a/packages/v2-watcher/src/fill.ts b/packages/v2-watcher/src/fill.ts new file mode 100644 index 0000000..210341e --- /dev/null +++ b/packages/v2-watcher/src/fill.ts @@ -0,0 +1,48 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import 'reflect-metadata'; +import debug from 'debug'; + +import { FillCmd } from '@cerc-io/cli'; +import { getContractEntitiesMap } from '@cerc-io/util'; +import { getGraphDbAndWatcher } from '@cerc-io/graph-node'; + +import { Database, ENTITY_QUERY_TYPE_MAP, ENTITY_TO_LATEST_ENTITY_MAP } from './database'; +import { Indexer } from './indexer'; + +const log = debug('vulcanize:fill'); + +export const main = async (): Promise => { + const fillCmd = new FillCmd(); + await fillCmd.init(Database); + + const { graphWatcher } = await getGraphDbAndWatcher( + fillCmd.config.server, + fillCmd.clients.ethClient, + fillCmd.ethProvider, + fillCmd.database.baseDatabase, + ENTITY_QUERY_TYPE_MAP, + ENTITY_TO_LATEST_ENTITY_MAP + ); + + await fillCmd.initIndexer(Indexer, graphWatcher); + + // Get contractEntitiesMap required for fill-state + // NOTE: Assuming each entity type is only mapped to a single contract + const contractEntitiesMap = getContractEntitiesMap(graphWatcher.dataSources); + + await fillCmd.exec(contractEntitiesMap); +}; + +main().catch(err => { + log(err); +}).finally(() => { + process.exit(); +}); + +process.on('SIGINT', () => { + log(`Exiting process ${process.pid} with code 0`); + process.exit(0); +}); diff --git a/packages/v2-watcher/src/gql/index.ts b/packages/v2-watcher/src/gql/index.ts new file mode 100644 index 0000000..4732f68 --- /dev/null +++ b/packages/v2-watcher/src/gql/index.ts @@ -0,0 +1,3 @@ +export * as mutations from './mutations'; +export * as queries from './queries'; +export * as subscriptions from './subscriptions'; diff --git a/packages/v2-watcher/src/gql/mutations/index.ts b/packages/v2-watcher/src/gql/mutations/index.ts new file mode 100644 index 0000000..0c3bd85 --- /dev/null +++ b/packages/v2-watcher/src/gql/mutations/index.ts @@ -0,0 +1,4 @@ +import fs from 'fs'; +import path from 'path'; + +export const watchContract = fs.readFileSync(path.join(__dirname, 'watchContract.gql'), 'utf8'); diff --git a/packages/v2-watcher/src/gql/mutations/watchContract.gql b/packages/v2-watcher/src/gql/mutations/watchContract.gql new file mode 100644 index 0000000..2ecc74f --- /dev/null +++ b/packages/v2-watcher/src/gql/mutations/watchContract.gql @@ -0,0 +1,3 @@ +mutation watchContract($address: String!, $kind: String!, $checkpoint: Boolean!, $startingBlock: Int){ + watchContract(address: $address, kind: $kind, checkpoint: $checkpoint, startingBlock: $startingBlock) +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/_meta.gql b/packages/v2-watcher/src/gql/queries/_meta.gql new file mode 100644 index 0000000..d686e04 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/_meta.gql @@ -0,0 +1,11 @@ +query _meta($block: Block_height){ + _meta(block: $block){ + block{ + hash + number + timestamp + } + deployment + hasIndexingErrors + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/bundle.gql b/packages/v2-watcher/src/gql/queries/bundle.gql new file mode 100644 index 0000000..e0c66ab --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/bundle.gql @@ -0,0 +1,6 @@ +query bundle($id: ID!, $block: Block_height){ + bundle(id: $id, block: $block){ + id + ethPrice + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/bundles.gql b/packages/v2-watcher/src/gql/queries/bundles.gql new file mode 100644 index 0000000..e1f7d95 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/bundles.gql @@ -0,0 +1,6 @@ +query bundles($block: Block_height, $where: Bundle_filter, $orderBy: Bundle_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + bundles(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + ethPrice + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/burn.gql b/packages/v2-watcher/src/gql/queries/burn.gql new file mode 100644 index 0000000..8514d42 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/burn.gql @@ -0,0 +1,40 @@ +query burn($id: ID!, $block: Block_height){ + burn(id: $id, block: $block){ + id + transaction{ + id + blockNumber + timestamp + } + timestamp + pair{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + liquidity + sender + amount0 + amount1 + to + logIndex + amountUSD + needsComplete + feeTo + feeLiquidity + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/burns.gql b/packages/v2-watcher/src/gql/queries/burns.gql new file mode 100644 index 0000000..bc6fe9b --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/burns.gql @@ -0,0 +1,40 @@ +query burns($block: Block_height, $where: Burn_filter, $orderBy: Burn_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + burns(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + transaction{ + id + blockNumber + timestamp + } + timestamp + pair{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + liquidity + sender + amount0 + amount1 + to + logIndex + amountUSD + needsComplete + feeTo + feeLiquidity + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/events.gql b/packages/v2-watcher/src/gql/queries/events.gql new file mode 100644 index 0000000..03b8121 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/events.gql @@ -0,0 +1,63 @@ +query events($blockHash: String!, $contractAddress: String!, $name: String){ + events(blockHash: $blockHash, contractAddress: $contractAddress, name: $name){ + block{ + cid + hash + number + timestamp + parentHash + } + tx{ + hash + index + from + to + } + contract + eventIndex + event{ + ... on PairCreatedEvent { + token0 + token1 + pair + null + } + ... on ApprovalEvent { + owner + spender + value + } + ... on BurnEvent { + sender + amount0 + amount1 + to + } + ... on MintEvent { + sender + amount0 + amount1 + } + ... on SwapEvent { + sender + amount0In + amount1In + amount0Out + amount1Out + to + } + ... on SyncEvent { + reserve0 + reserve1 + } + ... on TransferEvent { + from + to + value + } + } + proof{ + data + } + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/eventsInRange.gql b/packages/v2-watcher/src/gql/queries/eventsInRange.gql new file mode 100644 index 0000000..edf319a --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/eventsInRange.gql @@ -0,0 +1,63 @@ +query eventsInRange($fromBlockNumber: Int!, $toBlockNumber: Int!){ + eventsInRange(fromBlockNumber: $fromBlockNumber, toBlockNumber: $toBlockNumber){ + block{ + cid + hash + number + timestamp + parentHash + } + tx{ + hash + index + from + to + } + contract + eventIndex + event{ + ... on PairCreatedEvent { + token0 + token1 + pair + null + } + ... on ApprovalEvent { + owner + spender + value + } + ... on BurnEvent { + sender + amount0 + amount1 + to + } + ... on MintEvent { + sender + amount0 + amount1 + } + ... on SwapEvent { + sender + amount0In + amount1In + amount0Out + amount1Out + to + } + ... on SyncEvent { + reserve0 + reserve1 + } + ... on TransferEvent { + from + to + value + } + } + proof{ + data + } + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/getState.gql b/packages/v2-watcher/src/gql/queries/getState.gql new file mode 100644 index 0000000..3b8f605 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/getState.gql @@ -0,0 +1,15 @@ +query getState($blockHash: String!, $contractAddress: String!, $kind: String){ + getState(blockHash: $blockHash, contractAddress: $contractAddress, kind: $kind){ + block{ + cid + hash + number + timestamp + parentHash + } + contractAddress + cid + kind + data + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/getStateByCID.gql b/packages/v2-watcher/src/gql/queries/getStateByCID.gql new file mode 100644 index 0000000..6c3c4fd --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/getStateByCID.gql @@ -0,0 +1,15 @@ +query getStateByCID($cid: String!){ + getStateByCID(cid: $cid){ + block{ + cid + hash + number + timestamp + parentHash + } + contractAddress + cid + kind + data + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/getSyncStatus.gql b/packages/v2-watcher/src/gql/queries/getSyncStatus.gql new file mode 100644 index 0000000..48175b4 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/getSyncStatus.gql @@ -0,0 +1,12 @@ +query getSyncStatus{ + getSyncStatus{ + latestIndexedBlockHash + latestIndexedBlockNumber + latestCanonicalBlockHash + latestCanonicalBlockNumber + initialIndexedBlockHash + initialIndexedBlockNumber + latestProcessedBlockHash + latestProcessedBlockNumber + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/index.ts b/packages/v2-watcher/src/gql/queries/index.ts new file mode 100644 index 0000000..f96d1b3 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/index.ts @@ -0,0 +1,39 @@ +import fs from 'fs'; +import path from 'path'; + +export const events = fs.readFileSync(path.join(__dirname, 'events.gql'), 'utf8'); +export const eventsInRange = fs.readFileSync(path.join(__dirname, 'eventsInRange.gql'), 'utf8'); +export const uniswapFactory = fs.readFileSync(path.join(__dirname, 'uniswapFactory.gql'), 'utf8'); +export const uniswapFactories = fs.readFileSync(path.join(__dirname, 'uniswapFactories.gql'), 'utf8'); +export const token = fs.readFileSync(path.join(__dirname, 'token.gql'), 'utf8'); +export const tokens = fs.readFileSync(path.join(__dirname, 'tokens.gql'), 'utf8'); +export const pair = fs.readFileSync(path.join(__dirname, 'pair.gql'), 'utf8'); +export const pairs = fs.readFileSync(path.join(__dirname, 'pairs.gql'), 'utf8'); +export const user = fs.readFileSync(path.join(__dirname, 'user.gql'), 'utf8'); +export const users = fs.readFileSync(path.join(__dirname, 'users.gql'), 'utf8'); +export const liquidityPosition = fs.readFileSync(path.join(__dirname, 'liquidityPosition.gql'), 'utf8'); +export const liquidityPositions = fs.readFileSync(path.join(__dirname, 'liquidityPositions.gql'), 'utf8'); +export const liquidityPositionSnapshot = fs.readFileSync(path.join(__dirname, 'liquidityPositionSnapshot.gql'), 'utf8'); +export const liquidityPositionSnapshots = fs.readFileSync(path.join(__dirname, 'liquidityPositionSnapshots.gql'), 'utf8'); +export const transaction = fs.readFileSync(path.join(__dirname, 'transaction.gql'), 'utf8'); +export const transactions = fs.readFileSync(path.join(__dirname, 'transactions.gql'), 'utf8'); +export const mint = fs.readFileSync(path.join(__dirname, 'mint.gql'), 'utf8'); +export const mints = fs.readFileSync(path.join(__dirname, 'mints.gql'), 'utf8'); +export const burn = fs.readFileSync(path.join(__dirname, 'burn.gql'), 'utf8'); +export const burns = fs.readFileSync(path.join(__dirname, 'burns.gql'), 'utf8'); +export const swap = fs.readFileSync(path.join(__dirname, 'swap.gql'), 'utf8'); +export const swaps = fs.readFileSync(path.join(__dirname, 'swaps.gql'), 'utf8'); +export const bundle = fs.readFileSync(path.join(__dirname, 'bundle.gql'), 'utf8'); +export const bundles = fs.readFileSync(path.join(__dirname, 'bundles.gql'), 'utf8'); +export const uniswapDayData = fs.readFileSync(path.join(__dirname, 'uniswapDayData.gql'), 'utf8'); +export const uniswapDayDatas = fs.readFileSync(path.join(__dirname, 'uniswapDayDatas.gql'), 'utf8'); +export const pairHourData = fs.readFileSync(path.join(__dirname, 'pairHourData.gql'), 'utf8'); +export const pairHourDatas = fs.readFileSync(path.join(__dirname, 'pairHourDatas.gql'), 'utf8'); +export const pairDayData = fs.readFileSync(path.join(__dirname, 'pairDayData.gql'), 'utf8'); +export const pairDayDatas = fs.readFileSync(path.join(__dirname, 'pairDayDatas.gql'), 'utf8'); +export const tokenDayData = fs.readFileSync(path.join(__dirname, 'tokenDayData.gql'), 'utf8'); +export const tokenDayDatas = fs.readFileSync(path.join(__dirname, 'tokenDayDatas.gql'), 'utf8'); +export const _meta = fs.readFileSync(path.join(__dirname, '_meta.gql'), 'utf8'); +export const getStateByCID = fs.readFileSync(path.join(__dirname, 'getStateByCID.gql'), 'utf8'); +export const getState = fs.readFileSync(path.join(__dirname, 'getState.gql'), 'utf8'); +export const getSyncStatus = fs.readFileSync(path.join(__dirname, 'getSyncStatus.gql'), 'utf8'); diff --git a/packages/v2-watcher/src/gql/queries/liquidityPosition.gql b/packages/v2-watcher/src/gql/queries/liquidityPosition.gql new file mode 100644 index 0000000..4622ad6 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/liquidityPosition.gql @@ -0,0 +1,29 @@ +query liquidityPosition($id: ID!, $block: Block_height){ + liquidityPosition(id: $id, block: $block){ + id + user{ + id + usdSwapped + } + pair{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + liquidityTokenBalance + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/liquidityPositionSnapshot.gql b/packages/v2-watcher/src/gql/queries/liquidityPositionSnapshot.gql new file mode 100644 index 0000000..e41fbfc --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/liquidityPositionSnapshot.gql @@ -0,0 +1,41 @@ +query liquidityPositionSnapshot($id: ID!, $block: Block_height){ + liquidityPositionSnapshot(id: $id, block: $block){ + id + liquidityPosition{ + id + liquidityTokenBalance + } + timestamp + block + user{ + id + usdSwapped + } + pair{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + token0PriceUSD + token1PriceUSD + reserve0 + reserve1 + reserveUSD + liquidityTokenTotalSupply + liquidityTokenBalance + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/liquidityPositionSnapshots.gql b/packages/v2-watcher/src/gql/queries/liquidityPositionSnapshots.gql new file mode 100644 index 0000000..8cf98e8 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/liquidityPositionSnapshots.gql @@ -0,0 +1,41 @@ +query liquidityPositionSnapshots($block: Block_height, $where: LiquidityPositionSnapshot_filter, $orderBy: LiquidityPositionSnapshot_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + liquidityPositionSnapshots(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + liquidityPosition{ + id + liquidityTokenBalance + } + timestamp + block + user{ + id + usdSwapped + } + pair{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + token0PriceUSD + token1PriceUSD + reserve0 + reserve1 + reserveUSD + liquidityTokenTotalSupply + liquidityTokenBalance + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/liquidityPositions.gql b/packages/v2-watcher/src/gql/queries/liquidityPositions.gql new file mode 100644 index 0000000..6340f3c --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/liquidityPositions.gql @@ -0,0 +1,29 @@ +query liquidityPositions($block: Block_height, $where: LiquidityPosition_filter, $orderBy: LiquidityPosition_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + liquidityPositions(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + user{ + id + usdSwapped + } + pair{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + liquidityTokenBalance + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/mint.gql b/packages/v2-watcher/src/gql/queries/mint.gql new file mode 100644 index 0000000..12746e0 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/mint.gql @@ -0,0 +1,39 @@ +query mint($id: ID!, $block: Block_height){ + mint(id: $id, block: $block){ + id + transaction{ + id + blockNumber + timestamp + } + timestamp + pair{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + to + liquidity + sender + amount0 + amount1 + logIndex + amountUSD + feeTo + feeLiquidity + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/mints.gql b/packages/v2-watcher/src/gql/queries/mints.gql new file mode 100644 index 0000000..e731cef --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/mints.gql @@ -0,0 +1,39 @@ +query mints($block: Block_height, $where: Mint_filter, $orderBy: Mint_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + mints(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + transaction{ + id + blockNumber + timestamp + } + timestamp + pair{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + to + liquidity + sender + amount0 + amount1 + logIndex + amountUSD + feeTo + feeLiquidity + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/pair.gql b/packages/v2-watcher/src/gql/queries/pair.gql new file mode 100644 index 0000000..1e289fd --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/pair.gql @@ -0,0 +1,115 @@ +query pair($id: ID!, $block: Block_height){ + pair(id: $id, block: $block){ + id + token0{ + id + symbol + name + decimals + totalSupply + tradeVolume + tradeVolumeUSD + untrackedVolumeUSD + txCount + totalLiquidity + derivedETH + } + token1{ + id + symbol + name + decimals + totalSupply + tradeVolume + tradeVolumeUSD + untrackedVolumeUSD + txCount + totalLiquidity + derivedETH + } + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + pairHourData{ + id + hourStartUnix + reserve0 + reserve1 + totalSupply + reserveUSD + hourlyVolumeToken0 + hourlyVolumeToken1 + hourlyVolumeUSD + hourlyTxns + } + liquidityPositions{ + id + liquidityTokenBalance + } + liquidityPositionSnapshots{ + id + timestamp + block + token0PriceUSD + token1PriceUSD + reserve0 + reserve1 + reserveUSD + liquidityTokenTotalSupply + liquidityTokenBalance + } + mints{ + id + timestamp + to + liquidity + sender + amount0 + amount1 + logIndex + amountUSD + feeTo + feeLiquidity + } + burns{ + id + timestamp + liquidity + sender + amount0 + amount1 + to + logIndex + amountUSD + needsComplete + feeTo + feeLiquidity + } + swaps{ + id + timestamp + sender + from + amount0In + amount1In + amount0Out + amount1Out + to + logIndex + amountUSD + } + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/pairDayData.gql b/packages/v2-watcher/src/gql/queries/pairDayData.gql new file mode 100644 index 0000000..1c3f22e --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/pairDayData.gql @@ -0,0 +1,41 @@ +query pairDayData($id: ID!, $block: Block_height){ + pairDayData(id: $id, block: $block){ + id + date + pairAddress + token0{ + id + symbol + name + decimals + totalSupply + tradeVolume + tradeVolumeUSD + untrackedVolumeUSD + txCount + totalLiquidity + derivedETH + } + token1{ + id + symbol + name + decimals + totalSupply + tradeVolume + tradeVolumeUSD + untrackedVolumeUSD + txCount + totalLiquidity + derivedETH + } + reserve0 + reserve1 + totalSupply + reserveUSD + dailyVolumeToken0 + dailyVolumeToken1 + dailyVolumeUSD + dailyTxns + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/pairDayDatas.gql b/packages/v2-watcher/src/gql/queries/pairDayDatas.gql new file mode 100644 index 0000000..cc79be7 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/pairDayDatas.gql @@ -0,0 +1,41 @@ +query pairDayDatas($block: Block_height, $where: PairDayData_filter, $orderBy: PairDayData_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + pairDayDatas(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + date + pairAddress + token0{ + id + symbol + name + decimals + totalSupply + tradeVolume + tradeVolumeUSD + untrackedVolumeUSD + txCount + totalLiquidity + derivedETH + } + token1{ + id + symbol + name + decimals + totalSupply + tradeVolume + tradeVolumeUSD + untrackedVolumeUSD + txCount + totalLiquidity + derivedETH + } + reserve0 + reserve1 + totalSupply + reserveUSD + dailyVolumeToken0 + dailyVolumeToken1 + dailyVolumeUSD + dailyTxns + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/pairHourData.gql b/packages/v2-watcher/src/gql/queries/pairHourData.gql new file mode 100644 index 0000000..c81d7f1 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/pairHourData.gql @@ -0,0 +1,33 @@ +query pairHourData($id: ID!, $block: Block_height){ + pairHourData(id: $id, block: $block){ + id + hourStartUnix + pair{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + reserve0 + reserve1 + totalSupply + reserveUSD + hourlyVolumeToken0 + hourlyVolumeToken1 + hourlyVolumeUSD + hourlyTxns + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/pairHourDatas.gql b/packages/v2-watcher/src/gql/queries/pairHourDatas.gql new file mode 100644 index 0000000..4c05f58 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/pairHourDatas.gql @@ -0,0 +1,33 @@ +query pairHourDatas($block: Block_height, $where: PairHourData_filter, $orderBy: PairHourData_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + pairHourDatas(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + hourStartUnix + pair{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + reserve0 + reserve1 + totalSupply + reserveUSD + hourlyVolumeToken0 + hourlyVolumeToken1 + hourlyVolumeUSD + hourlyTxns + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/pairs.gql b/packages/v2-watcher/src/gql/queries/pairs.gql new file mode 100644 index 0000000..d331bdd --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/pairs.gql @@ -0,0 +1,115 @@ +query pairs($block: Block_height, $where: Pair_filter, $orderBy: Pair_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + pairs(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + token0{ + id + symbol + name + decimals + totalSupply + tradeVolume + tradeVolumeUSD + untrackedVolumeUSD + txCount + totalLiquidity + derivedETH + } + token1{ + id + symbol + name + decimals + totalSupply + tradeVolume + tradeVolumeUSD + untrackedVolumeUSD + txCount + totalLiquidity + derivedETH + } + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + pairHourData{ + id + hourStartUnix + reserve0 + reserve1 + totalSupply + reserveUSD + hourlyVolumeToken0 + hourlyVolumeToken1 + hourlyVolumeUSD + hourlyTxns + } + liquidityPositions{ + id + liquidityTokenBalance + } + liquidityPositionSnapshots{ + id + timestamp + block + token0PriceUSD + token1PriceUSD + reserve0 + reserve1 + reserveUSD + liquidityTokenTotalSupply + liquidityTokenBalance + } + mints{ + id + timestamp + to + liquidity + sender + amount0 + amount1 + logIndex + amountUSD + feeTo + feeLiquidity + } + burns{ + id + timestamp + liquidity + sender + amount0 + amount1 + to + logIndex + amountUSD + needsComplete + feeTo + feeLiquidity + } + swaps{ + id + timestamp + sender + from + amount0In + amount1In + amount0Out + amount1Out + to + logIndex + amountUSD + } + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/swap.gql b/packages/v2-watcher/src/gql/queries/swap.gql new file mode 100644 index 0000000..b2cb718 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/swap.gql @@ -0,0 +1,39 @@ +query swap($id: ID!, $block: Block_height){ + swap(id: $id, block: $block){ + id + transaction{ + id + blockNumber + timestamp + } + timestamp + pair{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + sender + from + amount0In + amount1In + amount0Out + amount1Out + to + logIndex + amountUSD + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/swaps.gql b/packages/v2-watcher/src/gql/queries/swaps.gql new file mode 100644 index 0000000..5859394 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/swaps.gql @@ -0,0 +1,39 @@ +query swaps($block: Block_height, $where: Swap_filter, $orderBy: Swap_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + swaps(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + transaction{ + id + blockNumber + timestamp + } + timestamp + pair{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + sender + from + amount0In + amount1In + amount0Out + amount1Out + to + logIndex + amountUSD + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/token.gql b/packages/v2-watcher/src/gql/queries/token.gql new file mode 100644 index 0000000..6713900 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/token.gql @@ -0,0 +1,91 @@ +query token($id: ID!, $block: Block_height){ + token(id: $id, block: $block){ + id + symbol + name + decimals + totalSupply + tradeVolume + tradeVolumeUSD + untrackedVolumeUSD + txCount + totalLiquidity + derivedETH + tokenDayData{ + id + date + dailyVolumeToken + dailyVolumeETH + dailyVolumeUSD + dailyTxns + totalLiquidityToken + totalLiquidityETH + totalLiquidityUSD + priceUSD + } + pairDayDataBase{ + id + date + pairAddress + reserve0 + reserve1 + totalSupply + reserveUSD + dailyVolumeToken0 + dailyVolumeToken1 + dailyVolumeUSD + dailyTxns + } + pairDayDataQuote{ + id + date + pairAddress + reserve0 + reserve1 + totalSupply + reserveUSD + dailyVolumeToken0 + dailyVolumeToken1 + dailyVolumeUSD + dailyTxns + } + pairBase{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + pairQuote{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/tokenDayData.gql b/packages/v2-watcher/src/gql/queries/tokenDayData.gql new file mode 100644 index 0000000..659d3fe --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/tokenDayData.gql @@ -0,0 +1,27 @@ +query tokenDayData($id: ID!, $block: Block_height){ + tokenDayData(id: $id, block: $block){ + id + date + token{ + id + symbol + name + decimals + totalSupply + tradeVolume + tradeVolumeUSD + untrackedVolumeUSD + txCount + totalLiquidity + derivedETH + } + dailyVolumeToken + dailyVolumeETH + dailyVolumeUSD + dailyTxns + totalLiquidityToken + totalLiquidityETH + totalLiquidityUSD + priceUSD + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/tokenDayDatas.gql b/packages/v2-watcher/src/gql/queries/tokenDayDatas.gql new file mode 100644 index 0000000..0000fce --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/tokenDayDatas.gql @@ -0,0 +1,27 @@ +query tokenDayDatas($block: Block_height, $where: TokenDayData_filter, $orderBy: TokenDayData_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + tokenDayDatas(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + date + token{ + id + symbol + name + decimals + totalSupply + tradeVolume + tradeVolumeUSD + untrackedVolumeUSD + txCount + totalLiquidity + derivedETH + } + dailyVolumeToken + dailyVolumeETH + dailyVolumeUSD + dailyTxns + totalLiquidityToken + totalLiquidityETH + totalLiquidityUSD + priceUSD + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/tokens.gql b/packages/v2-watcher/src/gql/queries/tokens.gql new file mode 100644 index 0000000..e6f5364 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/tokens.gql @@ -0,0 +1,91 @@ +query tokens($block: Block_height, $where: Token_filter, $orderBy: Token_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + tokens(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + symbol + name + decimals + totalSupply + tradeVolume + tradeVolumeUSD + untrackedVolumeUSD + txCount + totalLiquidity + derivedETH + tokenDayData{ + id + date + dailyVolumeToken + dailyVolumeETH + dailyVolumeUSD + dailyTxns + totalLiquidityToken + totalLiquidityETH + totalLiquidityUSD + priceUSD + } + pairDayDataBase{ + id + date + pairAddress + reserve0 + reserve1 + totalSupply + reserveUSD + dailyVolumeToken0 + dailyVolumeToken1 + dailyVolumeUSD + dailyTxns + } + pairDayDataQuote{ + id + date + pairAddress + reserve0 + reserve1 + totalSupply + reserveUSD + dailyVolumeToken0 + dailyVolumeToken1 + dailyVolumeUSD + dailyTxns + } + pairBase{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + pairQuote{ + id + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + } + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/transaction.gql b/packages/v2-watcher/src/gql/queries/transaction.gql new file mode 100644 index 0000000..3e396e9 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/transaction.gql @@ -0,0 +1,47 @@ +query transaction($id: ID!, $block: Block_height){ + transaction(id: $id, block: $block){ + id + blockNumber + timestamp + mints{ + id + timestamp + to + liquidity + sender + amount0 + amount1 + logIndex + amountUSD + feeTo + feeLiquidity + } + burns{ + id + timestamp + liquidity + sender + amount0 + amount1 + to + logIndex + amountUSD + needsComplete + feeTo + feeLiquidity + } + swaps{ + id + timestamp + sender + from + amount0In + amount1In + amount0Out + amount1Out + to + logIndex + amountUSD + } + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/transactions.gql b/packages/v2-watcher/src/gql/queries/transactions.gql new file mode 100644 index 0000000..9715d72 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/transactions.gql @@ -0,0 +1,47 @@ +query transactions($block: Block_height, $where: Transaction_filter, $orderBy: Transaction_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + transactions(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + blockNumber + timestamp + mints{ + id + timestamp + to + liquidity + sender + amount0 + amount1 + logIndex + amountUSD + feeTo + feeLiquidity + } + burns{ + id + timestamp + liquidity + sender + amount0 + amount1 + to + logIndex + amountUSD + needsComplete + feeTo + feeLiquidity + } + swaps{ + id + timestamp + sender + from + amount0In + amount1In + amount0Out + amount1Out + to + logIndex + amountUSD + } + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/uniswapDayData.gql b/packages/v2-watcher/src/gql/queries/uniswapDayData.gql new file mode 100644 index 0000000..5469ff5 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/uniswapDayData.gql @@ -0,0 +1,14 @@ +query uniswapDayData($id: ID!, $block: Block_height){ + uniswapDayData(id: $id, block: $block){ + id + date + dailyVolumeETH + dailyVolumeUSD + dailyVolumeUntracked + totalVolumeETH + totalLiquidityETH + totalVolumeUSD + totalLiquidityUSD + txCount + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/uniswapDayDatas.gql b/packages/v2-watcher/src/gql/queries/uniswapDayDatas.gql new file mode 100644 index 0000000..66d6599 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/uniswapDayDatas.gql @@ -0,0 +1,14 @@ +query uniswapDayDatas($block: Block_height, $where: UniswapDayData_filter, $orderBy: UniswapDayData_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + uniswapDayDatas(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + date + dailyVolumeETH + dailyVolumeUSD + dailyVolumeUntracked + totalVolumeETH + totalLiquidityETH + totalVolumeUSD + totalLiquidityUSD + txCount + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/uniswapFactories.gql b/packages/v2-watcher/src/gql/queries/uniswapFactories.gql new file mode 100644 index 0000000..63fe9f9 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/uniswapFactories.gql @@ -0,0 +1,12 @@ +query uniswapFactories($block: Block_height, $where: UniswapFactory_filter, $orderBy: UniswapFactory_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + uniswapFactories(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + pairCount + totalVolumeUSD + totalVolumeETH + untrackedVolumeUSD + totalLiquidityUSD + totalLiquidityETH + txCount + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/uniswapFactory.gql b/packages/v2-watcher/src/gql/queries/uniswapFactory.gql new file mode 100644 index 0000000..c8d0469 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/uniswapFactory.gql @@ -0,0 +1,12 @@ +query uniswapFactory($id: ID!, $block: Block_height){ + uniswapFactory(id: $id, block: $block){ + id + pairCount + totalVolumeUSD + totalVolumeETH + untrackedVolumeUSD + totalLiquidityUSD + totalLiquidityETH + txCount + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/user.gql b/packages/v2-watcher/src/gql/queries/user.gql new file mode 100644 index 0000000..1f0eb45 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/user.gql @@ -0,0 +1,10 @@ +query user($id: ID!, $block: Block_height){ + user(id: $id, block: $block){ + id + liquidityPositions{ + id + liquidityTokenBalance + } + usdSwapped + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/queries/users.gql b/packages/v2-watcher/src/gql/queries/users.gql new file mode 100644 index 0000000..d1db194 --- /dev/null +++ b/packages/v2-watcher/src/gql/queries/users.gql @@ -0,0 +1,10 @@ +query users($block: Block_height, $where: User_filter, $orderBy: User_orderBy, $orderDirection: OrderDirection, $first: Int, $skip: Int){ + users(block: $block, where: $where, orderBy: $orderBy, orderDirection: $orderDirection, first: $first, skip: $skip){ + id + liquidityPositions{ + id + liquidityTokenBalance + } + usdSwapped + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/gql/subscriptions/index.ts b/packages/v2-watcher/src/gql/subscriptions/index.ts new file mode 100644 index 0000000..f12910c --- /dev/null +++ b/packages/v2-watcher/src/gql/subscriptions/index.ts @@ -0,0 +1,4 @@ +import fs from 'fs'; +import path from 'path'; + +export const onEvent = fs.readFileSync(path.join(__dirname, 'onEvent.gql'), 'utf8'); diff --git a/packages/v2-watcher/src/gql/subscriptions/onEvent.gql b/packages/v2-watcher/src/gql/subscriptions/onEvent.gql new file mode 100644 index 0000000..305777a --- /dev/null +++ b/packages/v2-watcher/src/gql/subscriptions/onEvent.gql @@ -0,0 +1,63 @@ +subscription onEvent{ + onEvent{ + block{ + cid + hash + number + timestamp + parentHash + } + tx{ + hash + index + from + to + } + contract + eventIndex + event{ + ... on PairCreatedEvent { + token0 + token1 + pair + null + } + ... on ApprovalEvent { + owner + spender + value + } + ... on BurnEvent { + sender + amount0 + amount1 + to + } + ... on MintEvent { + sender + amount0 + amount1 + } + ... on SwapEvent { + sender + amount0In + amount1In + amount0Out + amount1Out + to + } + ... on SyncEvent { + reserve0 + reserve1 + } + ... on TransferEvent { + from + to + value + } + } + proof{ + data + } + } +} \ No newline at end of file diff --git a/packages/v2-watcher/src/hooks.ts b/packages/v2-watcher/src/hooks.ts new file mode 100644 index 0000000..d45498b --- /dev/null +++ b/packages/v2-watcher/src/hooks.ts @@ -0,0 +1,86 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import assert from 'assert'; + +import { + ResultEvent, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + updateStateForMappingType, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + updateStateForElementaryType +} from '@cerc-io/util'; + +import { Indexer } from './indexer'; + +/** + * Hook function to store an initial state. + * @param indexer Indexer instance. + * @param blockHash Hash of the concerned block. + * @param contractAddress Address of the concerned contract. + * @returns Data block to be stored. + */ +export async function createInitialState (indexer: Indexer, contractAddress: string, blockHash: string): Promise { + assert(indexer); + assert(blockHash); + assert(contractAddress); + + // Store an empty State. + const stateData: any = { + state: {} + }; + + // Use updateStateForElementaryType to update initial state with an elementary property. + // Eg. const stateData = updateStateForElementaryType(stateData, '_totalBalance', result.value.toString()); + + // Use updateStateForMappingType to update initial state with a nested property. + // Eg. const stateData = updateStateForMappingType(stateData, '_allowances', [owner, spender], allowance.value.toString()); + + // Return initial state data to be saved. + return stateData; +} + +/** + * Hook function to create state diff. + * @param indexer Indexer instance that contains methods to fetch the contract variable values. + * @param blockHash Block hash of the concerned block. + */ +export async function createStateDiff (indexer: Indexer, blockHash: string): Promise { + assert(indexer); + assert(blockHash); + + // Use indexer.createDiff() method to save custom state diff(s). +} + +/** + * Hook function to create state checkpoint + * @param indexer Indexer instance. + * @param contractAddress Address of the concerned contract. + * @param blockHash Block hash of the concerned block. + * @returns Whether to disable default checkpoint. If false, the state from this hook is updated with that from default checkpoint. + */ +export async function createStateCheckpoint (indexer: Indexer, contractAddress: string, blockHash: string): Promise { + assert(indexer); + assert(blockHash); + assert(contractAddress); + + // Use indexer.createStateCheckpoint() method to create a custom checkpoint. + + // Return false to update the state created by this hook by auto-generated checkpoint state. + // Return true to disable update of the state created by this hook by auto-generated checkpoint state. + return false; +} + +/** + * Event hook function. + * @param indexer Indexer instance that contains methods to fetch and update the contract values in the database. + * @param eventData ResultEvent object containing event information. + */ +export async function handleEvent (indexer: Indexer, eventData: ResultEvent): Promise { + assert(indexer); + assert(eventData); + + // Use indexer methods to index data. + // Pass `diff` parameter to indexer methods as true to save an auto-generated state from the indexed data. +} diff --git a/packages/v2-watcher/src/indexer.ts b/packages/v2-watcher/src/indexer.ts new file mode 100644 index 0000000..46dd0a3 --- /dev/null +++ b/packages/v2-watcher/src/indexer.ts @@ -0,0 +1,1072 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import assert from 'assert'; +import { DeepPartial, FindConditions, FindManyOptions, ObjectLiteral } from 'typeorm'; +import debug from 'debug'; +import { ethers, constants } from 'ethers'; +import { GraphQLResolveInfo } from 'graphql'; + +import { JsonFragment } from '@ethersproject/abi'; +import { BaseProvider } from '@ethersproject/providers'; +import { MappingKey, StorageLayout } from '@cerc-io/solidity-mapper'; +import { + Indexer as BaseIndexer, + IndexerInterface, + ValueResult, + ServerConfig, + JobQueue, + Where, + QueryOptions, + BlockHeight, + ResultMeta, + updateSubgraphState, + dumpSubgraphState, + GraphWatcherInterface, + StateKind, + StateStatus, + ResultEvent, + getResultEvent, + DatabaseInterface, + Clients, + EthClient, + UpstreamConfig, + EthFullBlock, + EthFullTransaction, + ExtraEventData +} from '@cerc-io/util'; +import { GraphWatcher } from '@cerc-io/graph-node'; + +import FactoryArtifacts from './artifacts/Factory.json'; +import PairArtifacts from './artifacts/Pair.json'; +import { Database, ENTITIES, SUBGRAPH_ENTITIES } from './database'; +import { createInitialState, handleEvent, createStateDiff, createStateCheckpoint } from './hooks'; +import { Contract } from './entity/Contract'; +import { Event } from './entity/Event'; +import { SyncStatus } from './entity/SyncStatus'; +import { StateSyncStatus } from './entity/StateSyncStatus'; +import { BlockProgress } from './entity/BlockProgress'; +import { State } from './entity/State'; +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { UniswapFactory } from './entity/UniswapFactory'; +import { Token } from './entity/Token'; +import { Pair } from './entity/Pair'; +import { User } from './entity/User'; +import { LiquidityPosition } from './entity/LiquidityPosition'; +import { LiquidityPositionSnapshot } from './entity/LiquidityPositionSnapshot'; +import { Transaction } from './entity/Transaction'; +import { Mint } from './entity/Mint'; +import { Burn } from './entity/Burn'; +import { Swap } from './entity/Swap'; +import { Bundle } from './entity/Bundle'; +import { UniswapDayData } from './entity/UniswapDayData'; +import { PairHourData } from './entity/PairHourData'; +import { PairDayData } from './entity/PairDayData'; +import { TokenDayData } from './entity/TokenDayData'; +/* eslint-enable @typescript-eslint/no-unused-vars */ + +import { FrothyEntity } from './entity/FrothyEntity'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const log = debug('vulcanize:indexer'); + +const KIND_FACTORY = 'Factory'; + +const KIND_PAIR = 'Pair'; + +export class Indexer implements IndexerInterface { + _db: Database; + _ethClient: EthClient; + _ethProvider: BaseProvider; + _baseIndexer: BaseIndexer; + _serverConfig: ServerConfig; + _upstreamConfig: UpstreamConfig; + _graphWatcher: GraphWatcher; + + _abiMap: Map; + _storageLayoutMap: Map; + _contractMap: Map; + eventSignaturesMap: Map; + + _entityTypesMap: Map; + _relationsMap: Map; + + _subgraphStateMap: Map; + + constructor ( + config: { + server: ServerConfig; + upstream: UpstreamConfig; + }, + db: DatabaseInterface, + clients: Clients, + ethProvider: BaseProvider, + jobQueue: JobQueue, + graphWatcher?: GraphWatcherInterface + ) { + assert(db); + assert(clients.ethClient); + + this._db = db as Database; + this._ethClient = clients.ethClient; + this._ethProvider = ethProvider; + this._serverConfig = config.server; + this._upstreamConfig = config.upstream; + this._baseIndexer = new BaseIndexer(config, this._db, this._ethClient, this._ethProvider, jobQueue); + assert(graphWatcher); + this._graphWatcher = graphWatcher as GraphWatcher; + + this._abiMap = new Map(); + this._storageLayoutMap = new Map(); + this._contractMap = new Map(); + this.eventSignaturesMap = new Map(); + + const { abi: FactoryABI } = FactoryArtifacts; + + const { abi: PairABI } = PairArtifacts; + + assert(FactoryABI); + this._abiMap.set(KIND_FACTORY, FactoryABI); + + const FactoryContractInterface = new ethers.utils.Interface(FactoryABI); + this._contractMap.set(KIND_FACTORY, FactoryContractInterface); + + const FactoryEventSignatures = Object.values(FactoryContractInterface.events).map(value => { + return FactoryContractInterface.getEventTopic(value); + }); + this.eventSignaturesMap.set(KIND_FACTORY, FactoryEventSignatures); + + assert(PairABI); + this._abiMap.set(KIND_PAIR, PairABI); + + const PairContractInterface = new ethers.utils.Interface(PairABI); + this._contractMap.set(KIND_PAIR, PairContractInterface); + + const PairEventSignatures = Object.values(PairContractInterface.events).map(value => { + return PairContractInterface.getEventTopic(value); + }); + this.eventSignaturesMap.set(KIND_PAIR, PairEventSignatures); + + this._entityTypesMap = new Map(); + this._populateEntityTypesMap(); + + this._relationsMap = new Map(); + this._populateRelationsMap(); + + this._subgraphStateMap = new Map(); + } + + get serverConfig (): ServerConfig { + return this._serverConfig; + } + + get upstreamConfig (): UpstreamConfig { + return this._upstreamConfig; + } + + get storageLayoutMap (): Map { + return this._storageLayoutMap; + } + + get graphWatcher (): GraphWatcher { + return this._graphWatcher; + } + + async init (): Promise { + await this._baseIndexer.fetchContracts(); + await this._baseIndexer.fetchStateStatus(); + } + + switchClients ({ ethClient, ethProvider }: { ethClient: EthClient, ethProvider: BaseProvider }): void { + this._ethClient = ethClient; + this._ethProvider = ethProvider; + this._baseIndexer.switchClients({ ethClient, ethProvider }); + this._graphWatcher.switchClients({ ethClient, ethProvider }); + } + + async getMetaData (block: BlockHeight): Promise { + return this._baseIndexer.getMetaData(block); + } + + getResultEvent (event: Event): ResultEvent { + return getResultEvent(event); + } + + async getStorageValue (storageLayout: StorageLayout, blockHash: string, contractAddress: string, variable: string, ...mappingKeys: MappingKey[]): Promise { + return this._baseIndexer.getStorageValue( + storageLayout, + blockHash, + contractAddress, + variable, + ...mappingKeys + ); + } + + async getEntitiesForBlock (blockHash: string, tableName: string): Promise { + return this._db.getEntitiesForBlock(blockHash, tableName); + } + + async processInitialState (contractAddress: string, blockHash: string): Promise { + // Call initial state hook. + return createInitialState(this, contractAddress, blockHash); + } + + async processStateCheckpoint (contractAddress: string, blockHash: string): Promise { + // Call checkpoint hook. + return createStateCheckpoint(this, contractAddress, blockHash); + } + + async processCanonicalBlock (blockHash: string, blockNumber: number): Promise { + console.time('time:indexer#processCanonicalBlock-finalize_auto_diffs'); + // Finalize staged diff blocks if any. + await this._baseIndexer.finalizeDiffStaged(blockHash); + console.timeEnd('time:indexer#processCanonicalBlock-finalize_auto_diffs'); + + // Call custom stateDiff hook. + await createStateDiff(this, blockHash); + + this._graphWatcher.pruneEntityCacheFrothyBlocks(blockHash, blockNumber); + } + + async processCheckpoint (blockHash: string): Promise { + // Return if checkpointInterval is <= 0. + const checkpointInterval = this._serverConfig.checkpointInterval; + if (checkpointInterval <= 0) return; + + console.time('time:indexer#processCheckpoint-checkpoint'); + await this._baseIndexer.processCheckpoint(this, blockHash, checkpointInterval); + console.timeEnd('time:indexer#processCheckpoint-checkpoint'); + } + + async processCLICheckpoint (contractAddress: string, blockHash?: string): Promise { + return this._baseIndexer.processCLICheckpoint(this, contractAddress, blockHash); + } + + async getPrevState (blockHash: string, contractAddress: string, kind?: string): Promise { + return this._db.getPrevState(blockHash, contractAddress, kind); + } + + async getLatestState (contractAddress: string, kind: StateKind | null, blockNumber?: number): Promise { + return this._db.getLatestState(contractAddress, kind, blockNumber); + } + + async getStatesByHash (blockHash: string): Promise { + return this._baseIndexer.getStatesByHash(blockHash); + } + + async getStateByCID (cid: string): Promise { + return this._baseIndexer.getStateByCID(cid); + } + + async getStates (where: FindConditions): Promise { + return this._db.getStates(where); + } + + getStateData (state: State): any { + return this._baseIndexer.getStateData(state); + } + + // Method used to create auto diffs (diff_staged). + async createDiffStaged (contractAddress: string, blockHash: string, data: any): Promise { + console.time('time:indexer#createDiffStaged-auto_diff'); + await this._baseIndexer.createDiffStaged(contractAddress, blockHash, data); + console.timeEnd('time:indexer#createDiffStaged-auto_diff'); + } + + // Method to be used by createStateDiff hook. + async createDiff (contractAddress: string, blockHash: string, data: any): Promise { + const block = await this.getBlockProgress(blockHash); + assert(block); + + await this._baseIndexer.createDiff(contractAddress, block, data); + } + + // Method to be used by createStateCheckpoint hook. + async createStateCheckpoint (contractAddress: string, blockHash: string, data: any): Promise { + const block = await this.getBlockProgress(blockHash); + assert(block); + + return this._baseIndexer.createStateCheckpoint(contractAddress, block, data); + } + + // Method to be used by export-state CLI. + async createCheckpoint (contractAddress: string, blockHash: string): Promise { + const block = await this.getBlockProgress(blockHash); + assert(block); + + return this._baseIndexer.createCheckpoint(this, contractAddress, block); + } + + // Method to be used by fill-state CLI. + async createInit (blockHash: string, blockNumber: number): Promise { + // Create initial state for contracts. + await this._baseIndexer.createInit(this, blockHash, blockNumber); + } + + async saveOrUpdateState (state: State): Promise { + return this._baseIndexer.saveOrUpdateState(state); + } + + async removeStates (blockNumber: number, kind: StateKind): Promise { + await this._baseIndexer.removeStates(blockNumber, kind); + } + + async getSubgraphEntity ( + entity: new () => Entity, + id: string, + block: BlockHeight, + queryInfo: GraphQLResolveInfo + ): Promise { + const data = await this._graphWatcher.getEntity(entity, id, this._relationsMap, block, queryInfo); + + return data; + } + + async getSubgraphEntities ( + entity: new () => Entity, + block: BlockHeight, + where: { [key: string]: any } = {}, + queryOptions: QueryOptions = {}, + queryInfo: GraphQLResolveInfo + ): Promise { + return this._graphWatcher.getEntities(entity, this._relationsMap, block, where, queryOptions, queryInfo); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async triggerIndexingOnEvent (event: Event, extraData: ExtraEventData): Promise { + const resultEvent = this.getResultEvent(event); + + console.time('time:indexer#processEvent-mapping_code'); + // Call subgraph handler for event. + await this._graphWatcher.handleEvent(resultEvent, extraData); + console.timeEnd('time:indexer#processEvent-mapping_code'); + + // Call custom hook function for indexing on event. + await handleEvent(this, resultEvent); + } + + async processEvent (event: Event, extraData: ExtraEventData): Promise { + // Trigger indexing of data based on the event. + await this.triggerIndexingOnEvent(event, extraData); + } + + async processBlock (blockProgress: BlockProgress): Promise { + console.time('time:indexer#processBlock-init_state'); + // Call a function to create initial state for contracts. + await this._baseIndexer.createInit(this, blockProgress.blockHash, blockProgress.blockNumber); + console.timeEnd('time:indexer#processBlock-init_state'); + + this._graphWatcher.updateEntityCacheFrothyBlocks(blockProgress); + } + + async processBlockAfterEvents (blockHash: string, blockNumber: number, extraData: ExtraEventData): Promise { + console.time('time:indexer#processBlockAfterEvents-mapping_code'); + // Call subgraph handler for block. + await this._graphWatcher.handleBlock(blockHash, blockNumber, extraData); + console.timeEnd('time:indexer#processBlockAfterEvents-mapping_code'); + + console.time('time:indexer#processBlockAfterEvents-dump_subgraph_state'); + // Persist subgraph state to the DB. + await this.dumpSubgraphState(blockHash); + console.timeEnd('time:indexer#processBlockAfterEvents-dump_subgraph_state'); + } + + parseEventNameAndArgs (kind: string, logObj: any): { eventParsed: boolean, eventDetails: any } { + const { topics, data } = logObj; + + const contract = this._contractMap.get(kind); + assert(contract); + + let logDescription: ethers.utils.LogDescription; + try { + logDescription = contract.parseLog({ data, topics }); + } catch (err) { + // Return if no matching event found + if ((err as Error).message.includes('no matching event')) { + log(`WARNING: Skipping event for contract ${kind} as no matching event found in the ABI`); + return { eventParsed: false, eventDetails: {} }; + } + + throw err; + } + + const { eventName, eventInfo, eventSignature } = this._baseIndexer.parseEvent(logDescription); + + return { + eventParsed: true, + eventDetails: { + eventName, + eventInfo, + eventSignature + } + }; + } + + async getStateSyncStatus (): Promise { + return this._db.getStateSyncStatus(); + } + + async updateStateSyncStatusIndexedBlock (blockNumber: number, force?: boolean): Promise { + if (!this._serverConfig.enableState) { + return; + } + + const dbTx = await this._db.createTransactionRunner(); + let res; + + try { + res = await this._db.updateStateSyncStatusIndexedBlock(dbTx, blockNumber, force); + await dbTx.commitTransaction(); + } catch (error) { + await dbTx.rollbackTransaction(); + throw error; + } finally { + await dbTx.release(); + } + + return res; + } + + async updateStateSyncStatusCheckpointBlock (blockNumber: number, force?: boolean): Promise { + const dbTx = await this._db.createTransactionRunner(); + let res; + + try { + res = await this._db.updateStateSyncStatusCheckpointBlock(dbTx, blockNumber, force); + await dbTx.commitTransaction(); + } catch (error) { + await dbTx.rollbackTransaction(); + throw error; + } finally { + await dbTx.release(); + } + + return res; + } + + async getLatestCanonicalBlock (): Promise { + const syncStatus = await this.getSyncStatus(); + assert(syncStatus); + + if (syncStatus.latestCanonicalBlockHash === constants.HashZero) { + return; + } + + const latestCanonicalBlock = await this.getBlockProgress(syncStatus.latestCanonicalBlockHash); + assert(latestCanonicalBlock); + + return latestCanonicalBlock; + } + + async getLatestStateIndexedBlock (): Promise { + return this._baseIndexer.getLatestStateIndexedBlock(); + } + + async addContracts (): Promise { + // Watching all the contracts in the subgraph. + await this._graphWatcher.addContracts(); + } + + async watchContract (address: string, kind: string, checkpoint: boolean, startingBlock: number, context?: any): Promise { + return this._baseIndexer.watchContract(address, kind, checkpoint, startingBlock, context); + } + + updateStateStatusMap (address: string, stateStatus: StateStatus): void { + this._baseIndexer.updateStateStatusMap(address, stateStatus); + } + + cacheContract (contract: Contract): void { + return this._baseIndexer.cacheContract(contract); + } + + async saveEventEntity (dbEvent: Event): Promise { + return this._baseIndexer.saveEventEntity(dbEvent); + } + + async saveEvents (dbEvents: Event[]): Promise { + return this._baseIndexer.saveEvents(dbEvents); + } + + async getEventsByFilter (blockHash: string, contract?: string, name?: string): Promise> { + return this._baseIndexer.getEventsByFilter(blockHash, contract, name); + } + + isWatchedContract (address : string): Contract | undefined { + return this._baseIndexer.isWatchedContract(address); + } + + getWatchedContracts (): Contract[] { + return this._baseIndexer.getWatchedContracts(); + } + + getContractsByKind (kind: string): Contract[] { + return this._baseIndexer.getContractsByKind(kind); + } + + async getProcessedBlockCountForRange (fromBlockNumber: number, toBlockNumber: number): Promise<{ expected: number, actual: number }> { + return this._baseIndexer.getProcessedBlockCountForRange(fromBlockNumber, toBlockNumber); + } + + async getEventsInRange (fromBlockNumber: number, toBlockNumber: number): Promise> { + return this._baseIndexer.getEventsInRange(fromBlockNumber, toBlockNumber, this._serverConfig.gql.maxEventsBlockRange); + } + + async getSyncStatus (): Promise { + return this._baseIndexer.getSyncStatus(); + } + + async getBlocks (blockFilter: { blockHash?: string, blockNumber?: number }): Promise { + return this._baseIndexer.getBlocks(blockFilter); + } + + async updateSyncStatusIndexedBlock (blockHash: string, blockNumber: number, force = false): Promise { + return this._baseIndexer.updateSyncStatusIndexedBlock(blockHash, blockNumber, force); + } + + async updateSyncStatusChainHead (blockHash: string, blockNumber: number, force = false): Promise { + return this._baseIndexer.updateSyncStatusChainHead(blockHash, blockNumber, force); + } + + async updateSyncStatusCanonicalBlock (blockHash: string, blockNumber: number, force = false): Promise { + const syncStatus = this._baseIndexer.updateSyncStatusCanonicalBlock(blockHash, blockNumber, force); + await this.pruneFrothyEntities(blockNumber); + + return syncStatus; + } + + async updateSyncStatusProcessedBlock (blockHash: string, blockNumber: number, force = false): Promise { + return this._baseIndexer.updateSyncStatusProcessedBlock(blockHash, blockNumber, force); + } + + async updateSyncStatusIndexingError (hasIndexingError: boolean): Promise { + return this._baseIndexer.updateSyncStatusIndexingError(hasIndexingError); + } + + async updateSyncStatus (syncStatus: DeepPartial): Promise { + return this._baseIndexer.updateSyncStatus(syncStatus); + } + + async getEvent (id: string): Promise { + return this._baseIndexer.getEvent(id); + } + + async getBlockProgress (blockHash: string): Promise { + return this._baseIndexer.getBlockProgress(blockHash); + } + + async getBlockProgressEntities (where: FindConditions, options: FindManyOptions): Promise { + return this._baseIndexer.getBlockProgressEntities(where, options); + } + + async getBlocksAtHeight (height: number, isPruned: boolean): Promise { + return this._baseIndexer.getBlocksAtHeight(height, isPruned); + } + + async fetchAndSaveFilteredEventsAndBlocks (startBlock: number, endBlock: number): Promise<{ + blockProgress: BlockProgress, + events: DeepPartial[], + ethFullBlock: EthFullBlock; + ethFullTransactions: EthFullTransaction[]; + }[]> { + return this._baseIndexer.fetchAndSaveFilteredEventsAndBlocks(startBlock, endBlock, this.eventSignaturesMap, this.parseEventNameAndArgs.bind(this)); + } + + async fetchEventsForContracts (blockHash: string, blockNumber: number, addresses: string[]): Promise[]> { + return this._baseIndexer.fetchEventsForContracts(blockHash, blockNumber, addresses, this.eventSignaturesMap, this.parseEventNameAndArgs.bind(this)); + } + + async saveBlockAndFetchEvents (block: DeepPartial): Promise<[ + BlockProgress, + DeepPartial[], + EthFullTransaction[] + ]> { + return this._saveBlockAndFetchEvents(block); + } + + async getBlockEvents (blockHash: string, where: Where, queryOptions: QueryOptions): Promise> { + return this._baseIndexer.getBlockEvents(blockHash, where, queryOptions); + } + + async removeUnknownEvents (block: BlockProgress): Promise { + return this._baseIndexer.removeUnknownEvents(Event, block); + } + + async markBlocksAsPruned (blocks: BlockProgress[]): Promise { + await this._baseIndexer.markBlocksAsPruned(blocks); + + await this._graphWatcher.pruneEntities(FrothyEntity, blocks, SUBGRAPH_ENTITIES); + } + + async pruneFrothyEntities (blockNumber: number): Promise { + await this._graphWatcher.pruneFrothyEntities(FrothyEntity, blockNumber); + } + + async resetLatestEntities (blockNumber: number): Promise { + await this._graphWatcher.resetLatestEntities(blockNumber); + } + + async updateBlockProgress (block: BlockProgress, lastProcessedEventIndex: number): Promise { + return this._baseIndexer.updateBlockProgress(block, lastProcessedEventIndex); + } + + async getAncestorAtHeight (blockHash: string, height: number): Promise { + return this._baseIndexer.getAncestorAtHeight(blockHash, height); + } + + async resetWatcherToBlock (blockNumber: number): Promise { + const entities = [...ENTITIES, FrothyEntity]; + await this._baseIndexer.resetWatcherToBlock(blockNumber, entities); + + await this.resetLatestEntities(blockNumber); + } + + async clearProcessedBlockData (block: BlockProgress): Promise { + const entities = [...ENTITIES, FrothyEntity]; + await this._baseIndexer.clearProcessedBlockData(block, entities); + + await this.resetLatestEntities(block.blockNumber); + } + + getEntityTypesMap (): Map { + return this._entityTypesMap; + } + + getRelationsMap (): Map { + return this._relationsMap; + } + + updateSubgraphState (contractAddress: string, data: any): void { + return updateSubgraphState(this._subgraphStateMap, contractAddress, data); + } + + async dumpSubgraphState (blockHash: string, isStateFinalized = false): Promise { + return dumpSubgraphState(this, this._subgraphStateMap, blockHash, isStateFinalized); + } + + _populateEntityTypesMap (): void { + this._entityTypesMap.set('UniswapFactory', { + id: 'ID', + pairCount: 'Int', + totalVolumeUSD: 'BigDecimal', + totalVolumeETH: 'BigDecimal', + untrackedVolumeUSD: 'BigDecimal', + totalLiquidityUSD: 'BigDecimal', + totalLiquidityETH: 'BigDecimal', + txCount: 'BigInt' + }); + this._entityTypesMap.set('Token', { + id: 'ID', + symbol: 'String', + name: 'String', + decimals: 'BigInt', + totalSupply: 'BigInt', + tradeVolume: 'BigDecimal', + tradeVolumeUSD: 'BigDecimal', + untrackedVolumeUSD: 'BigDecimal', + txCount: 'BigInt', + totalLiquidity: 'BigDecimal', + derivedETH: 'BigDecimal' + }); + this._entityTypesMap.set('Pair', { + id: 'ID', + token0: 'Token', + token1: 'Token', + reserve0: 'BigDecimal', + reserve1: 'BigDecimal', + totalSupply: 'BigDecimal', + reserveETH: 'BigDecimal', + reserveUSD: 'BigDecimal', + trackedReserveETH: 'BigDecimal', + token0Price: 'BigDecimal', + token1Price: 'BigDecimal', + volumeToken0: 'BigDecimal', + volumeToken1: 'BigDecimal', + volumeUSD: 'BigDecimal', + untrackedVolumeUSD: 'BigDecimal', + txCount: 'BigInt', + createdAtTimestamp: 'BigInt', + createdAtBlockNumber: 'BigInt', + liquidityProviderCount: 'BigInt' + }); + this._entityTypesMap.set('User', { + id: 'ID', + usdSwapped: 'BigDecimal' + }); + this._entityTypesMap.set('LiquidityPosition', { + id: 'ID', + user: 'User', + pair: 'Pair', + liquidityTokenBalance: 'BigDecimal' + }); + this._entityTypesMap.set('LiquidityPositionSnapshot', { + id: 'ID', + liquidityPosition: 'LiquidityPosition', + timestamp: 'Int', + block: 'Int', + user: 'User', + pair: 'Pair', + token0PriceUSD: 'BigDecimal', + token1PriceUSD: 'BigDecimal', + reserve0: 'BigDecimal', + reserve1: 'BigDecimal', + reserveUSD: 'BigDecimal', + liquidityTokenTotalSupply: 'BigDecimal', + liquidityTokenBalance: 'BigDecimal' + }); + this._entityTypesMap.set('Transaction', { + id: 'ID', + blockNumber: 'BigInt', + timestamp: 'BigInt', + mints: 'Mint', + burns: 'Burn', + swaps: 'Swap' + }); + this._entityTypesMap.set('Mint', { + id: 'ID', + transaction: 'Transaction', + timestamp: 'BigInt', + pair: 'Pair', + to: 'Bytes', + liquidity: 'BigDecimal', + sender: 'Bytes', + amount0: 'BigDecimal', + amount1: 'BigDecimal', + logIndex: 'BigInt', + amountUSD: 'BigDecimal', + feeTo: 'Bytes', + feeLiquidity: 'BigDecimal' + }); + this._entityTypesMap.set('Burn', { + id: 'ID', + transaction: 'Transaction', + timestamp: 'BigInt', + pair: 'Pair', + liquidity: 'BigDecimal', + sender: 'Bytes', + amount0: 'BigDecimal', + amount1: 'BigDecimal', + to: 'Bytes', + logIndex: 'BigInt', + amountUSD: 'BigDecimal', + needsComplete: 'Boolean', + feeTo: 'Bytes', + feeLiquidity: 'BigDecimal' + }); + this._entityTypesMap.set('Swap', { + id: 'ID', + transaction: 'Transaction', + timestamp: 'BigInt', + pair: 'Pair', + sender: 'Bytes', + from: 'Bytes', + amount0In: 'BigDecimal', + amount1In: 'BigDecimal', + amount0Out: 'BigDecimal', + amount1Out: 'BigDecimal', + to: 'Bytes', + logIndex: 'BigInt', + amountUSD: 'BigDecimal' + }); + this._entityTypesMap.set('Bundle', { + id: 'ID', + ethPrice: 'BigDecimal' + }); + this._entityTypesMap.set('UniswapDayData', { + id: 'ID', + date: 'Int', + dailyVolumeETH: 'BigDecimal', + dailyVolumeUSD: 'BigDecimal', + dailyVolumeUntracked: 'BigDecimal', + totalVolumeETH: 'BigDecimal', + totalLiquidityETH: 'BigDecimal', + totalVolumeUSD: 'BigDecimal', + totalLiquidityUSD: 'BigDecimal', + txCount: 'BigInt' + }); + this._entityTypesMap.set('PairHourData', { + id: 'ID', + hourStartUnix: 'Int', + pair: 'Pair', + reserve0: 'BigDecimal', + reserve1: 'BigDecimal', + totalSupply: 'BigDecimal', + reserveUSD: 'BigDecimal', + hourlyVolumeToken0: 'BigDecimal', + hourlyVolumeToken1: 'BigDecimal', + hourlyVolumeUSD: 'BigDecimal', + hourlyTxns: 'BigInt' + }); + this._entityTypesMap.set('PairDayData', { + id: 'ID', + date: 'Int', + pairAddress: 'Bytes', + token0: 'Token', + token1: 'Token', + reserve0: 'BigDecimal', + reserve1: 'BigDecimal', + totalSupply: 'BigDecimal', + reserveUSD: 'BigDecimal', + dailyVolumeToken0: 'BigDecimal', + dailyVolumeToken1: 'BigDecimal', + dailyVolumeUSD: 'BigDecimal', + dailyTxns: 'BigInt' + }); + this._entityTypesMap.set('TokenDayData', { + id: 'ID', + date: 'Int', + token: 'Token', + dailyVolumeToken: 'BigDecimal', + dailyVolumeETH: 'BigDecimal', + dailyVolumeUSD: 'BigDecimal', + dailyTxns: 'BigInt', + totalLiquidityToken: 'BigDecimal', + totalLiquidityETH: 'BigDecimal', + totalLiquidityUSD: 'BigDecimal', + priceUSD: 'BigDecimal' + }); + } + + _populateRelationsMap (): void { + this._relationsMap.set(Token, { + tokenDayData: { + entity: TokenDayData, + isArray: true, + isDerived: true, + field: 'token' + }, + pairDayDataBase: { + entity: PairDayData, + isArray: true, + isDerived: true, + field: 'token0' + }, + pairDayDataQuote: { + entity: PairDayData, + isArray: true, + isDerived: true, + field: 'token1' + }, + pairBase: { + entity: Pair, + isArray: true, + isDerived: true, + field: 'token0' + }, + pairQuote: { + entity: Pair, + isArray: true, + isDerived: true, + field: 'token1' + } + }); + this._relationsMap.set(Pair, { + token0: { + entity: Token, + isArray: false, + isDerived: false + }, + token1: { + entity: Token, + isArray: false, + isDerived: false + }, + pairHourData: { + entity: PairHourData, + isArray: true, + isDerived: true, + field: 'pair' + }, + liquidityPositions: { + entity: LiquidityPosition, + isArray: true, + isDerived: true, + field: 'pair' + }, + liquidityPositionSnapshots: { + entity: LiquidityPositionSnapshot, + isArray: true, + isDerived: true, + field: 'pair' + }, + mints: { + entity: Mint, + isArray: true, + isDerived: true, + field: 'pair' + }, + burns: { + entity: Burn, + isArray: true, + isDerived: true, + field: 'pair' + }, + swaps: { + entity: Swap, + isArray: true, + isDerived: true, + field: 'pair' + } + }); + this._relationsMap.set(User, { + liquidityPositions: { + entity: LiquidityPosition, + isArray: true, + isDerived: true, + field: 'user' + } + }); + this._relationsMap.set(LiquidityPosition, { + user: { + entity: User, + isArray: false, + isDerived: false + }, + pair: { + entity: Pair, + isArray: false, + isDerived: false + } + }); + this._relationsMap.set(LiquidityPositionSnapshot, { + liquidityPosition: { + entity: LiquidityPosition, + isArray: false, + isDerived: false + }, + user: { + entity: User, + isArray: false, + isDerived: false + }, + pair: { + entity: Pair, + isArray: false, + isDerived: false + } + }); + this._relationsMap.set(Transaction, { + mints: { + entity: Mint, + isArray: true, + isDerived: false + }, + burns: { + entity: Burn, + isArray: true, + isDerived: false + }, + swaps: { + entity: Swap, + isArray: true, + isDerived: false + } + }); + this._relationsMap.set(Mint, { + transaction: { + entity: Transaction, + isArray: false, + isDerived: false + }, + pair: { + entity: Pair, + isArray: false, + isDerived: false + } + }); + this._relationsMap.set(Burn, { + transaction: { + entity: Transaction, + isArray: false, + isDerived: false + }, + pair: { + entity: Pair, + isArray: false, + isDerived: false + } + }); + this._relationsMap.set(Swap, { + transaction: { + entity: Transaction, + isArray: false, + isDerived: false + }, + pair: { + entity: Pair, + isArray: false, + isDerived: false + } + }); + this._relationsMap.set(PairHourData, { + pair: { + entity: Pair, + isArray: false, + isDerived: false + } + }); + this._relationsMap.set(PairDayData, { + token0: { + entity: Token, + isArray: false, + isDerived: false + }, + token1: { + entity: Token, + isArray: false, + isDerived: false + } + }); + this._relationsMap.set(TokenDayData, { + token: { + entity: Token, + isArray: false, + isDerived: false + } + }); + } + + async _saveBlockAndFetchEvents ({ + cid: blockCid, + blockHash, + blockNumber, + blockTimestamp, + parentHash + }: DeepPartial): Promise<[ + BlockProgress, + DeepPartial[], + EthFullTransaction[] + ]> { + assert(blockHash); + assert(blockNumber); + + const { events: dbEvents, transactions } = await this._baseIndexer.fetchEvents(blockHash, blockNumber, this.eventSignaturesMap, this.parseEventNameAndArgs.bind(this)); + + const dbTx = await this._db.createTransactionRunner(); + try { + const block = { + cid: blockCid, + blockHash, + blockNumber, + blockTimestamp, + parentHash + }; + + console.time(`time:indexer#_saveBlockAndFetchEvents-db-save-${blockNumber}`); + const blockProgress = await this._db.saveBlockWithEvents(dbTx, block, dbEvents); + await dbTx.commitTransaction(); + console.timeEnd(`time:indexer#_saveBlockAndFetchEvents-db-save-${blockNumber}`); + + return [blockProgress, [], transactions]; + } catch (error) { + await dbTx.rollbackTransaction(); + throw error; + } finally { + await dbTx.release(); + } + } + + async getFullTransactions (txHashList: string[]): Promise { + return this._baseIndexer.getFullTransactions(txHashList); + } +} diff --git a/packages/v2-watcher/src/job-runner.ts b/packages/v2-watcher/src/job-runner.ts new file mode 100644 index 0000000..93d6820 --- /dev/null +++ b/packages/v2-watcher/src/job-runner.ts @@ -0,0 +1,48 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import debug from 'debug'; + +import { JobRunnerCmd } from '@cerc-io/cli'; +import { JobRunner } from '@cerc-io/util'; +import { getGraphDbAndWatcher } from '@cerc-io/graph-node'; + +import { Indexer } from './indexer'; +import { Database, ENTITY_QUERY_TYPE_MAP, ENTITY_TO_LATEST_ENTITY_MAP } from './database'; + +const log = debug('vulcanize:job-runner'); + +export const main = async (): Promise => { + const jobRunnerCmd = new JobRunnerCmd(); + await jobRunnerCmd.init(Database); + + const { graphWatcher } = await getGraphDbAndWatcher( + jobRunnerCmd.config.server, + jobRunnerCmd.clients.ethClient, + jobRunnerCmd.ethProvider, + jobRunnerCmd.database.baseDatabase, + ENTITY_QUERY_TYPE_MAP, + ENTITY_TO_LATEST_ENTITY_MAP + ); + + await jobRunnerCmd.initIndexer(Indexer, graphWatcher); + + await jobRunnerCmd.exec(async (jobRunner: JobRunner): Promise => { + await jobRunner.subscribeBlockProcessingQueue(); + await jobRunner.subscribeHistoricalProcessingQueue(); + await jobRunner.subscribeEventProcessingQueue(); + await jobRunner.subscribeBlockCheckpointQueue(); + await jobRunner.subscribeHooksQueue(); + }); +}; + +main().then(() => { + log('Starting job runner...'); +}).catch(err => { + log(err); +}); + +process.on('uncaughtException', err => { + log('uncaughtException', err); +}); diff --git a/packages/v2-watcher/src/resolvers.ts b/packages/v2-watcher/src/resolvers.ts new file mode 100644 index 0000000..9e7ed05 --- /dev/null +++ b/packages/v2-watcher/src/resolvers.ts @@ -0,0 +1,944 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import assert from 'assert'; +import debug from 'debug'; +import { GraphQLResolveInfo } from 'graphql'; +import { ExpressContext } from 'apollo-server-express'; +import winston from 'winston'; + +import { + gqlTotalQueryCount, + gqlQueryCount, + gqlQueryDuration, + getResultState, + IndexerInterface, + GraphQLBigInt, + GraphQLBigDecimal, + BlockHeight, + OrderDirection, + jsonBigIntStringReplacer, + EventWatcher, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setGQLCacheHints +} from '@cerc-io/util'; + +import { Indexer } from './indexer'; + +import { UniswapFactory } from './entity/UniswapFactory'; +import { Token } from './entity/Token'; +import { Pair } from './entity/Pair'; +import { User } from './entity/User'; +import { LiquidityPosition } from './entity/LiquidityPosition'; +import { LiquidityPositionSnapshot } from './entity/LiquidityPositionSnapshot'; +import { Transaction } from './entity/Transaction'; +import { Mint } from './entity/Mint'; +import { Burn } from './entity/Burn'; +import { Swap } from './entity/Swap'; +import { Bundle } from './entity/Bundle'; +import { UniswapDayData } from './entity/UniswapDayData'; +import { PairHourData } from './entity/PairHourData'; +import { PairDayData } from './entity/PairDayData'; +import { TokenDayData } from './entity/TokenDayData'; + +const log = debug('vulcanize:resolver'); + +const executeAndRecordMetrics = async ( + indexer: Indexer, + gqlLogger: winston.Logger, + opName: string, + expressContext: ExpressContext, + operation: () => Promise +) => { + gqlTotalQueryCount.inc(1); + gqlQueryCount.labels(opName).inc(1); + const endTimer = gqlQueryDuration.labels(opName).startTimer(); + + try { + const [result, syncStatus] = await Promise.all([ + operation(), + indexer.getSyncStatus() + ]); + + gqlLogger.info({ + opName, + query: expressContext.req.body.query, + variables: expressContext.req.body.variables, + latestIndexedBlockNumber: syncStatus?.latestIndexedBlockNumber, + urlPath: expressContext.req.path, + apiKey: expressContext.req.header('x-api-key'), + origin: expressContext.req.headers.origin + }); + return result; + } catch (error) { + gqlLogger.error({ + opName, + error, + query: expressContext.req.body.query, + variables: expressContext.req.body.variables, + urlPath: expressContext.req.path, + apiKey: expressContext.req.header('x-api-key'), + origin: expressContext.req.headers.origin + }); + } finally { + endTimer(); + } +}; + +export const createResolvers = async ( + indexerArg: IndexerInterface, + eventWatcher: EventWatcher, + gqlLogger: winston.Logger +): Promise => { + const indexer = indexerArg as Indexer; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const gqlCacheConfig = indexer.serverConfig.gql.cache; + + return { + BigInt: GraphQLBigInt, + + BigDecimal: GraphQLBigDecimal, + + Event: { + __resolveType: (obj: any) => { + assert(obj.__typename); + + return obj.__typename; + } + }, + + Subscription: { + onEvent: { + subscribe: () => eventWatcher.getEventIterator() + } + }, + + Mutation: { + watchContract: async (_: any, { address, kind, checkpoint, startingBlock = 1 }: { address: string, kind: string, checkpoint: boolean, startingBlock: number }): Promise => { + log('watchContract', address, kind, checkpoint, startingBlock); + await indexer.watchContract(address, kind, checkpoint, startingBlock); + + return true; + } + }, + + Query: { + uniswapFactory: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('uniswapFactory', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'uniswapFactory', + expressContext, + async () => indexer.getSubgraphEntity(UniswapFactory, id, block, info) + ); + }, + + uniswapFactories: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('uniswapFactories', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'uniswapFactories', + expressContext, + async () => indexer.getSubgraphEntities( + UniswapFactory, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + token: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('token', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'token', + expressContext, + async () => indexer.getSubgraphEntity(Token, id, block, info) + ); + }, + + tokens: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('tokens', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'tokens', + expressContext, + async () => indexer.getSubgraphEntities( + Token, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + pair: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('pair', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'pair', + expressContext, + async () => indexer.getSubgraphEntity(Pair, id, block, info) + ); + }, + + pairs: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('pairs', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'pairs', + expressContext, + async () => indexer.getSubgraphEntities( + Pair, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + user: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('user', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'user', + expressContext, + async () => indexer.getSubgraphEntity(User, id, block, info) + ); + }, + + users: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('users', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'users', + expressContext, + async () => indexer.getSubgraphEntities( + User, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + liquidityPosition: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('liquidityPosition', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'liquidityPosition', + expressContext, + async () => indexer.getSubgraphEntity(LiquidityPosition, id, block, info) + ); + }, + + liquidityPositions: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('liquidityPositions', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'liquidityPositions', + expressContext, + async () => indexer.getSubgraphEntities( + LiquidityPosition, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + liquidityPositionSnapshot: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('liquidityPositionSnapshot', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'liquidityPositionSnapshot', + expressContext, + async () => indexer.getSubgraphEntity(LiquidityPositionSnapshot, id, block, info) + ); + }, + + liquidityPositionSnapshots: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('liquidityPositionSnapshots', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'liquidityPositionSnapshots', + expressContext, + async () => indexer.getSubgraphEntities( + LiquidityPositionSnapshot, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + transaction: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('transaction', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'transaction', + expressContext, + async () => indexer.getSubgraphEntity(Transaction, id, block, info) + ); + }, + + transactions: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('transactions', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'transactions', + expressContext, + async () => indexer.getSubgraphEntities( + Transaction, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + mint: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('mint', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'mint', + expressContext, + async () => indexer.getSubgraphEntity(Mint, id, block, info) + ); + }, + + mints: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('mints', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'mints', + expressContext, + async () => indexer.getSubgraphEntities( + Mint, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + burn: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('burn', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'burn', + expressContext, + async () => indexer.getSubgraphEntity(Burn, id, block, info) + ); + }, + + burns: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('burns', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'burns', + expressContext, + async () => indexer.getSubgraphEntities( + Burn, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + swap: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('swap', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'swap', + expressContext, + async () => indexer.getSubgraphEntity(Swap, id, block, info) + ); + }, + + swaps: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('swaps', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'swaps', + expressContext, + async () => indexer.getSubgraphEntities( + Swap, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + bundle: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('bundle', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'bundle', + expressContext, + async () => indexer.getSubgraphEntity(Bundle, id, block, info) + ); + }, + + bundles: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('bundles', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'bundles', + expressContext, + async () => indexer.getSubgraphEntities( + Bundle, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + uniswapDayData: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('uniswapDayData', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'uniswapDayData', + expressContext, + async () => indexer.getSubgraphEntity(UniswapDayData, id, block, info) + ); + }, + + uniswapDayDatas: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('uniswapDayDatas', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'uniswapDayDatas', + expressContext, + async () => indexer.getSubgraphEntities( + UniswapDayData, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + pairHourData: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('pairHourData', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'pairHourData', + expressContext, + async () => indexer.getSubgraphEntity(PairHourData, id, block, info) + ); + }, + + pairHourDatas: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('pairHourDatas', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'pairHourDatas', + expressContext, + async () => indexer.getSubgraphEntities( + PairHourData, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + pairDayData: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('pairDayData', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'pairDayData', + expressContext, + async () => indexer.getSubgraphEntity(PairDayData, id, block, info) + ); + }, + + pairDayDatas: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('pairDayDatas', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'pairDayDatas', + expressContext, + async () => indexer.getSubgraphEntities( + PairDayData, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + tokenDayData: async ( + _: any, + { id, block = {} }: { id: string, block: BlockHeight }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('tokenDayData', id, JSON.stringify(block, jsonBigIntStringReplacer)); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'tokenDayData', + expressContext, + async () => indexer.getSubgraphEntity(TokenDayData, id, block, info) + ); + }, + + tokenDayDatas: async ( + _: any, + { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, + expressContext: ExpressContext, + info: GraphQLResolveInfo + ) => { + log('tokenDayDatas', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); + + // Set cache-control hints + // setGQLCacheHints(info, block, gqlCacheConfig); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'tokenDayDatas', + expressContext, + async () => indexer.getSubgraphEntities( + TokenDayData, + block, + where, + { limit: first, skip, orderBy, orderDirection }, + info + ) + ); + }, + + events: async ( + _: any, + { blockHash, contractAddress, name }: { blockHash: string, contractAddress: string, name?: string }, + expressContext: ExpressContext + ) => { + log('events', blockHash, contractAddress, name); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'events', + expressContext, + async () => { + const block = await indexer.getBlockProgress(blockHash); + if (!block || !block.isComplete) { + throw new Error(`Block hash ${blockHash} number ${block?.blockNumber} not processed yet`); + } + + const events = await indexer.getEventsByFilter(blockHash, contractAddress, name); + return events.map(event => indexer.getResultEvent(event)); + } + ); + }, + + eventsInRange: async ( + _: any, + { fromBlockNumber, toBlockNumber }: { fromBlockNumber: number, toBlockNumber: number }, + expressContext: ExpressContext + ) => { + log('eventsInRange', fromBlockNumber, toBlockNumber); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'eventsInRange', + expressContext, + async () => { + const syncStatus = await indexer.getSyncStatus(); + + if (!syncStatus) { + throw new Error('No blocks processed yet'); + } + + if ((fromBlockNumber < syncStatus.initialIndexedBlockNumber) || (toBlockNumber > syncStatus.latestProcessedBlockNumber)) { + throw new Error(`Block range should be between ${syncStatus.initialIndexedBlockNumber} and ${syncStatus.latestProcessedBlockNumber}`); + } + + const events = await indexer.getEventsInRange(fromBlockNumber, toBlockNumber); + return events.map(event => indexer.getResultEvent(event)); + } + ); + }, + + getStateByCID: async ( + _: any, + { cid }: { cid: string }, + expressContext: ExpressContext + ) => { + log('getStateByCID', cid); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'getStateByCID', + expressContext, + async () => { + const state = await indexer.getStateByCID(cid); + + return state && state.block.isComplete ? getResultState(state) : undefined; + } + ); + }, + + getState: async ( + _: any, + { blockHash, contractAddress, kind }: { blockHash: string, contractAddress: string, kind: string }, + expressContext: ExpressContext + ) => { + log('getState', blockHash, contractAddress, kind); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'getState', + expressContext, + async () => { + const state = await indexer.getPrevState(blockHash, contractAddress, kind); + + return state && state.block.isComplete ? getResultState(state) : undefined; + } + ); + }, + + _meta: async ( + _: any, + { block = {} }: { block: BlockHeight }, + expressContext: ExpressContext + ) => { + log('_meta'); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + '_meta', + expressContext, + async () => indexer.getMetaData(block) + ); + }, + + getSyncStatus: async ( + _: any, + __: Record, + expressContext: ExpressContext + ) => { + log('getSyncStatus'); + + return executeAndRecordMetrics( + indexer, + gqlLogger, + 'getSyncStatus', + expressContext, + async () => indexer.getSyncStatus() + ); + } + } + }; +}; diff --git a/packages/v2-watcher/src/schema.gql b/packages/v2-watcher/src/schema.gql new file mode 100644 index 0000000..4750421 --- /dev/null +++ b/packages/v2-watcher/src/schema.gql @@ -0,0 +1,2330 @@ +directive @cacheControl(maxAge: Int, inheritMaxAge: Boolean, scope: CacheControlScope) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION + +enum CacheControlScope { + PUBLIC + PRIVATE +} + +scalar BigInt + +scalar BigDecimal + +scalar Bytes + +type Proof { + data: String! +} + +type _Block_ { + cid: String + hash: String! + number: Int! + timestamp: Int! + parentHash: String! +} + +type _Transaction_ { + hash: String! + index: Int! + from: String! + to: String! +} + +type ResultEvent { + block: _Block_! + tx: _Transaction_! + contract: String! + eventIndex: Int! + event: Event! + proof: Proof +} + +union Event = PairCreatedEvent | ApprovalEvent | BurnEvent | MintEvent | SwapEvent | SyncEvent | TransferEvent + +type PairCreatedEvent { + token0: String! + token1: String! + pair: String! + null: BigInt! +} + +type ApprovalEvent { + owner: String! + spender: String! + value: BigInt! +} + +type BurnEvent { + sender: String! + amount0: BigInt! + amount1: BigInt! + to: String! +} + +type MintEvent { + sender: String! + amount0: BigInt! + amount1: BigInt! +} + +type SwapEvent { + sender: String! + amount0In: BigInt! + amount1In: BigInt! + amount0Out: BigInt! + amount1Out: BigInt! + to: String! +} + +type SyncEvent { + reserve0: BigInt! + reserve1: BigInt! +} + +type TransferEvent { + from: String! + to: String! + value: BigInt! +} + +input Block_height { + hash: Bytes + number: Int +} + +input BlockChangedFilter { + number_gte: Int! +} + +enum OrderDirection { + asc + desc +} + +enum UniswapFactory_orderBy { + id + pairCount + totalVolumeUSD + totalVolumeETH + untrackedVolumeUSD + totalLiquidityUSD + totalLiquidityETH + txCount +} + +input UniswapFactory_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + pairCount: Int + pairCount_not: Int + pairCount_gt: Int + pairCount_lt: Int + pairCount_gte: Int + pairCount_lte: Int + pairCount_in: [Int!] + pairCount_not_in: [Int!] + totalVolumeUSD: BigDecimal + totalVolumeUSD_not: BigDecimal + totalVolumeUSD_gt: BigDecimal + totalVolumeUSD_lt: BigDecimal + totalVolumeUSD_gte: BigDecimal + totalVolumeUSD_lte: BigDecimal + totalVolumeUSD_in: [BigDecimal!] + totalVolumeUSD_not_in: [BigDecimal!] + totalVolumeETH: BigDecimal + totalVolumeETH_not: BigDecimal + totalVolumeETH_gt: BigDecimal + totalVolumeETH_lt: BigDecimal + totalVolumeETH_gte: BigDecimal + totalVolumeETH_lte: BigDecimal + totalVolumeETH_in: [BigDecimal!] + totalVolumeETH_not_in: [BigDecimal!] + untrackedVolumeUSD: BigDecimal + untrackedVolumeUSD_not: BigDecimal + untrackedVolumeUSD_gt: BigDecimal + untrackedVolumeUSD_lt: BigDecimal + untrackedVolumeUSD_gte: BigDecimal + untrackedVolumeUSD_lte: BigDecimal + untrackedVolumeUSD_in: [BigDecimal!] + untrackedVolumeUSD_not_in: [BigDecimal!] + totalLiquidityUSD: BigDecimal + totalLiquidityUSD_not: BigDecimal + totalLiquidityUSD_gt: BigDecimal + totalLiquidityUSD_lt: BigDecimal + totalLiquidityUSD_gte: BigDecimal + totalLiquidityUSD_lte: BigDecimal + totalLiquidityUSD_in: [BigDecimal!] + totalLiquidityUSD_not_in: [BigDecimal!] + totalLiquidityETH: BigDecimal + totalLiquidityETH_not: BigDecimal + totalLiquidityETH_gt: BigDecimal + totalLiquidityETH_lt: BigDecimal + totalLiquidityETH_gte: BigDecimal + totalLiquidityETH_lte: BigDecimal + totalLiquidityETH_in: [BigDecimal!] + totalLiquidityETH_not_in: [BigDecimal!] + txCount: BigInt + txCount_not: BigInt + txCount_gt: BigInt + txCount_lt: BigInt + txCount_gte: BigInt + txCount_lte: BigInt + txCount_in: [BigInt!] + txCount_not_in: [BigInt!] + _change_block: BlockChangedFilter + and: [UniswapFactory_filter] + or: [UniswapFactory_filter] +} + +enum Token_orderBy { + id + symbol + name + decimals + totalSupply + tradeVolume + tradeVolumeUSD + untrackedVolumeUSD + txCount + totalLiquidity + derivedETH + tokenDayData + pairDayDataBase + pairDayDataQuote + pairBase + pairQuote +} + +input Token_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + symbol: String + symbol_not: String + symbol_gt: String + symbol_lt: String + symbol_gte: String + symbol_lte: String + symbol_in: [String!] + symbol_not_in: [String!] + symbol_starts_with: String + symbol_starts_with_nocase: String + symbol_not_starts_with: String + symbol_not_starts_with_nocase: String + symbol_ends_with: String + symbol_ends_with_nocase: String + symbol_not_ends_with: String + symbol_not_ends_with_nocase: String + symbol_contains: String + symbol_not_contains: String + symbol_contains_nocase: String + symbol_not_contains_nocase: String + name: String + name_not: String + name_gt: String + name_lt: String + name_gte: String + name_lte: String + name_in: [String!] + name_not_in: [String!] + name_starts_with: String + name_starts_with_nocase: String + name_not_starts_with: String + name_not_starts_with_nocase: String + name_ends_with: String + name_ends_with_nocase: String + name_not_ends_with: String + name_not_ends_with_nocase: String + name_contains: String + name_not_contains: String + name_contains_nocase: String + name_not_contains_nocase: String + decimals: BigInt + decimals_not: BigInt + decimals_gt: BigInt + decimals_lt: BigInt + decimals_gte: BigInt + decimals_lte: BigInt + decimals_in: [BigInt!] + decimals_not_in: [BigInt!] + totalSupply: BigInt + totalSupply_not: BigInt + totalSupply_gt: BigInt + totalSupply_lt: BigInt + totalSupply_gte: BigInt + totalSupply_lte: BigInt + totalSupply_in: [BigInt!] + totalSupply_not_in: [BigInt!] + tradeVolume: BigDecimal + tradeVolume_not: BigDecimal + tradeVolume_gt: BigDecimal + tradeVolume_lt: BigDecimal + tradeVolume_gte: BigDecimal + tradeVolume_lte: BigDecimal + tradeVolume_in: [BigDecimal!] + tradeVolume_not_in: [BigDecimal!] + tradeVolumeUSD: BigDecimal + tradeVolumeUSD_not: BigDecimal + tradeVolumeUSD_gt: BigDecimal + tradeVolumeUSD_lt: BigDecimal + tradeVolumeUSD_gte: BigDecimal + tradeVolumeUSD_lte: BigDecimal + tradeVolumeUSD_in: [BigDecimal!] + tradeVolumeUSD_not_in: [BigDecimal!] + untrackedVolumeUSD: BigDecimal + untrackedVolumeUSD_not: BigDecimal + untrackedVolumeUSD_gt: BigDecimal + untrackedVolumeUSD_lt: BigDecimal + untrackedVolumeUSD_gte: BigDecimal + untrackedVolumeUSD_lte: BigDecimal + untrackedVolumeUSD_in: [BigDecimal!] + untrackedVolumeUSD_not_in: [BigDecimal!] + txCount: BigInt + txCount_not: BigInt + txCount_gt: BigInt + txCount_lt: BigInt + txCount_gte: BigInt + txCount_lte: BigInt + txCount_in: [BigInt!] + txCount_not_in: [BigInt!] + totalLiquidity: BigDecimal + totalLiquidity_not: BigDecimal + totalLiquidity_gt: BigDecimal + totalLiquidity_lt: BigDecimal + totalLiquidity_gte: BigDecimal + totalLiquidity_lte: BigDecimal + totalLiquidity_in: [BigDecimal!] + totalLiquidity_not_in: [BigDecimal!] + derivedETH: BigDecimal + derivedETH_not: BigDecimal + derivedETH_gt: BigDecimal + derivedETH_lt: BigDecimal + derivedETH_gte: BigDecimal + derivedETH_lte: BigDecimal + derivedETH_in: [BigDecimal!] + derivedETH_not_in: [BigDecimal!] + tokenDayData_: TokenDayData_filter + pairDayDataBase_: PairDayData_filter + pairDayDataQuote_: PairDayData_filter + pairBase_: Pair_filter + pairQuote_: Pair_filter + _change_block: BlockChangedFilter + and: [Token_filter] + or: [Token_filter] +} + +enum Pair_orderBy { + id + token0 + token0__id + token0__symbol + token0__name + token0__decimals + token0__totalSupply + token0__tradeVolume + token0__tradeVolumeUSD + token0__untrackedVolumeUSD + token0__txCount + token0__totalLiquidity + token0__derivedETH + token1 + token1__id + token1__symbol + token1__name + token1__decimals + token1__totalSupply + token1__tradeVolume + token1__tradeVolumeUSD + token1__untrackedVolumeUSD + token1__txCount + token1__totalLiquidity + token1__derivedETH + reserve0 + reserve1 + totalSupply + reserveETH + reserveUSD + trackedReserveETH + token0Price + token1Price + volumeToken0 + volumeToken1 + volumeUSD + untrackedVolumeUSD + txCount + createdAtTimestamp + createdAtBlockNumber + liquidityProviderCount + pairHourData + liquidityPositions + liquidityPositionSnapshots + mints + burns + swaps +} + +input Pair_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + token0: String + token0_not: String + token0_gt: String + token0_lt: String + token0_gte: String + token0_lte: String + token0_in: [String!] + token0_not_in: [String!] + token0_starts_with: String + token0_starts_with_nocase: String + token0_not_starts_with: String + token0_not_starts_with_nocase: String + token0_ends_with: String + token0_ends_with_nocase: String + token0_not_ends_with: String + token0_not_ends_with_nocase: String + token0_contains: String + token0_not_contains: String + token0_contains_nocase: String + token0_not_contains_nocase: String + token0_: Token_filter + token1: String + token1_not: String + token1_gt: String + token1_lt: String + token1_gte: String + token1_lte: String + token1_in: [String!] + token1_not_in: [String!] + token1_starts_with: String + token1_starts_with_nocase: String + token1_not_starts_with: String + token1_not_starts_with_nocase: String + token1_ends_with: String + token1_ends_with_nocase: String + token1_not_ends_with: String + token1_not_ends_with_nocase: String + token1_contains: String + token1_not_contains: String + token1_contains_nocase: String + token1_not_contains_nocase: String + token1_: Token_filter + reserve0: BigDecimal + reserve0_not: BigDecimal + reserve0_gt: BigDecimal + reserve0_lt: BigDecimal + reserve0_gte: BigDecimal + reserve0_lte: BigDecimal + reserve0_in: [BigDecimal!] + reserve0_not_in: [BigDecimal!] + reserve1: BigDecimal + reserve1_not: BigDecimal + reserve1_gt: BigDecimal + reserve1_lt: BigDecimal + reserve1_gte: BigDecimal + reserve1_lte: BigDecimal + reserve1_in: [BigDecimal!] + reserve1_not_in: [BigDecimal!] + totalSupply: BigDecimal + totalSupply_not: BigDecimal + totalSupply_gt: BigDecimal + totalSupply_lt: BigDecimal + totalSupply_gte: BigDecimal + totalSupply_lte: BigDecimal + totalSupply_in: [BigDecimal!] + totalSupply_not_in: [BigDecimal!] + reserveETH: BigDecimal + reserveETH_not: BigDecimal + reserveETH_gt: BigDecimal + reserveETH_lt: BigDecimal + reserveETH_gte: BigDecimal + reserveETH_lte: BigDecimal + reserveETH_in: [BigDecimal!] + reserveETH_not_in: [BigDecimal!] + reserveUSD: BigDecimal + reserveUSD_not: BigDecimal + reserveUSD_gt: BigDecimal + reserveUSD_lt: BigDecimal + reserveUSD_gte: BigDecimal + reserveUSD_lte: BigDecimal + reserveUSD_in: [BigDecimal!] + reserveUSD_not_in: [BigDecimal!] + trackedReserveETH: BigDecimal + trackedReserveETH_not: BigDecimal + trackedReserveETH_gt: BigDecimal + trackedReserveETH_lt: BigDecimal + trackedReserveETH_gte: BigDecimal + trackedReserveETH_lte: BigDecimal + trackedReserveETH_in: [BigDecimal!] + trackedReserveETH_not_in: [BigDecimal!] + token0Price: BigDecimal + token0Price_not: BigDecimal + token0Price_gt: BigDecimal + token0Price_lt: BigDecimal + token0Price_gte: BigDecimal + token0Price_lte: BigDecimal + token0Price_in: [BigDecimal!] + token0Price_not_in: [BigDecimal!] + token1Price: BigDecimal + token1Price_not: BigDecimal + token1Price_gt: BigDecimal + token1Price_lt: BigDecimal + token1Price_gte: BigDecimal + token1Price_lte: BigDecimal + token1Price_in: [BigDecimal!] + token1Price_not_in: [BigDecimal!] + volumeToken0: BigDecimal + volumeToken0_not: BigDecimal + volumeToken0_gt: BigDecimal + volumeToken0_lt: BigDecimal + volumeToken0_gte: BigDecimal + volumeToken0_lte: BigDecimal + volumeToken0_in: [BigDecimal!] + volumeToken0_not_in: [BigDecimal!] + volumeToken1: BigDecimal + volumeToken1_not: BigDecimal + volumeToken1_gt: BigDecimal + volumeToken1_lt: BigDecimal + volumeToken1_gte: BigDecimal + volumeToken1_lte: BigDecimal + volumeToken1_in: [BigDecimal!] + volumeToken1_not_in: [BigDecimal!] + volumeUSD: BigDecimal + volumeUSD_not: BigDecimal + volumeUSD_gt: BigDecimal + volumeUSD_lt: BigDecimal + volumeUSD_gte: BigDecimal + volumeUSD_lte: BigDecimal + volumeUSD_in: [BigDecimal!] + volumeUSD_not_in: [BigDecimal!] + untrackedVolumeUSD: BigDecimal + untrackedVolumeUSD_not: BigDecimal + untrackedVolumeUSD_gt: BigDecimal + untrackedVolumeUSD_lt: BigDecimal + untrackedVolumeUSD_gte: BigDecimal + untrackedVolumeUSD_lte: BigDecimal + untrackedVolumeUSD_in: [BigDecimal!] + untrackedVolumeUSD_not_in: [BigDecimal!] + txCount: BigInt + txCount_not: BigInt + txCount_gt: BigInt + txCount_lt: BigInt + txCount_gte: BigInt + txCount_lte: BigInt + txCount_in: [BigInt!] + txCount_not_in: [BigInt!] + createdAtTimestamp: BigInt + createdAtTimestamp_not: BigInt + createdAtTimestamp_gt: BigInt + createdAtTimestamp_lt: BigInt + createdAtTimestamp_gte: BigInt + createdAtTimestamp_lte: BigInt + createdAtTimestamp_in: [BigInt!] + createdAtTimestamp_not_in: [BigInt!] + createdAtBlockNumber: BigInt + createdAtBlockNumber_not: BigInt + createdAtBlockNumber_gt: BigInt + createdAtBlockNumber_lt: BigInt + createdAtBlockNumber_gte: BigInt + createdAtBlockNumber_lte: BigInt + createdAtBlockNumber_in: [BigInt!] + createdAtBlockNumber_not_in: [BigInt!] + liquidityProviderCount: BigInt + liquidityProviderCount_not: BigInt + liquidityProviderCount_gt: BigInt + liquidityProviderCount_lt: BigInt + liquidityProviderCount_gte: BigInt + liquidityProviderCount_lte: BigInt + liquidityProviderCount_in: [BigInt!] + liquidityProviderCount_not_in: [BigInt!] + pairHourData_: PairHourData_filter + liquidityPositions_: LiquidityPosition_filter + liquidityPositionSnapshots_: LiquidityPositionSnapshot_filter + mints_: Mint_filter + burns_: Burn_filter + swaps_: Swap_filter + _change_block: BlockChangedFilter + and: [Pair_filter] + or: [Pair_filter] +} + +enum User_orderBy { + id + liquidityPositions + usdSwapped +} + +input User_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + liquidityPositions_: LiquidityPosition_filter + usdSwapped: BigDecimal + usdSwapped_not: BigDecimal + usdSwapped_gt: BigDecimal + usdSwapped_lt: BigDecimal + usdSwapped_gte: BigDecimal + usdSwapped_lte: BigDecimal + usdSwapped_in: [BigDecimal!] + usdSwapped_not_in: [BigDecimal!] + _change_block: BlockChangedFilter + and: [User_filter] + or: [User_filter] +} + +enum LiquidityPosition_orderBy { + id + user + user__id + user__usdSwapped + pair + pair__id + pair__reserve0 + pair__reserve1 + pair__totalSupply + pair__reserveETH + pair__reserveUSD + pair__trackedReserveETH + pair__token0Price + pair__token1Price + pair__volumeToken0 + pair__volumeToken1 + pair__volumeUSD + pair__untrackedVolumeUSD + pair__txCount + pair__createdAtTimestamp + pair__createdAtBlockNumber + pair__liquidityProviderCount + liquidityTokenBalance +} + +input LiquidityPosition_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + user: String + user_not: String + user_gt: String + user_lt: String + user_gte: String + user_lte: String + user_in: [String!] + user_not_in: [String!] + user_starts_with: String + user_starts_with_nocase: String + user_not_starts_with: String + user_not_starts_with_nocase: String + user_ends_with: String + user_ends_with_nocase: String + user_not_ends_with: String + user_not_ends_with_nocase: String + user_contains: String + user_not_contains: String + user_contains_nocase: String + user_not_contains_nocase: String + user_: User_filter + pair: String + pair_not: String + pair_gt: String + pair_lt: String + pair_gte: String + pair_lte: String + pair_in: [String!] + pair_not_in: [String!] + pair_starts_with: String + pair_starts_with_nocase: String + pair_not_starts_with: String + pair_not_starts_with_nocase: String + pair_ends_with: String + pair_ends_with_nocase: String + pair_not_ends_with: String + pair_not_ends_with_nocase: String + pair_contains: String + pair_not_contains: String + pair_contains_nocase: String + pair_not_contains_nocase: String + pair_: Pair_filter + liquidityTokenBalance: BigDecimal + liquidityTokenBalance_not: BigDecimal + liquidityTokenBalance_gt: BigDecimal + liquidityTokenBalance_lt: BigDecimal + liquidityTokenBalance_gte: BigDecimal + liquidityTokenBalance_lte: BigDecimal + liquidityTokenBalance_in: [BigDecimal!] + liquidityTokenBalance_not_in: [BigDecimal!] + _change_block: BlockChangedFilter + and: [LiquidityPosition_filter] + or: [LiquidityPosition_filter] +} + +enum LiquidityPositionSnapshot_orderBy { + id + liquidityPosition + liquidityPosition__id + liquidityPosition__liquidityTokenBalance + timestamp + block + user + user__id + user__usdSwapped + pair + pair__id + pair__reserve0 + pair__reserve1 + pair__totalSupply + pair__reserveETH + pair__reserveUSD + pair__trackedReserveETH + pair__token0Price + pair__token1Price + pair__volumeToken0 + pair__volumeToken1 + pair__volumeUSD + pair__untrackedVolumeUSD + pair__txCount + pair__createdAtTimestamp + pair__createdAtBlockNumber + pair__liquidityProviderCount + token0PriceUSD + token1PriceUSD + reserve0 + reserve1 + reserveUSD + liquidityTokenTotalSupply + liquidityTokenBalance +} + +input LiquidityPositionSnapshot_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + liquidityPosition: String + liquidityPosition_not: String + liquidityPosition_gt: String + liquidityPosition_lt: String + liquidityPosition_gte: String + liquidityPosition_lte: String + liquidityPosition_in: [String!] + liquidityPosition_not_in: [String!] + liquidityPosition_starts_with: String + liquidityPosition_starts_with_nocase: String + liquidityPosition_not_starts_with: String + liquidityPosition_not_starts_with_nocase: String + liquidityPosition_ends_with: String + liquidityPosition_ends_with_nocase: String + liquidityPosition_not_ends_with: String + liquidityPosition_not_ends_with_nocase: String + liquidityPosition_contains: String + liquidityPosition_not_contains: String + liquidityPosition_contains_nocase: String + liquidityPosition_not_contains_nocase: String + liquidityPosition_: LiquidityPosition_filter + timestamp: Int + timestamp_not: Int + timestamp_gt: Int + timestamp_lt: Int + timestamp_gte: Int + timestamp_lte: Int + timestamp_in: [Int!] + timestamp_not_in: [Int!] + block: Int + block_not: Int + block_gt: Int + block_lt: Int + block_gte: Int + block_lte: Int + block_in: [Int!] + block_not_in: [Int!] + user: String + user_not: String + user_gt: String + user_lt: String + user_gte: String + user_lte: String + user_in: [String!] + user_not_in: [String!] + user_starts_with: String + user_starts_with_nocase: String + user_not_starts_with: String + user_not_starts_with_nocase: String + user_ends_with: String + user_ends_with_nocase: String + user_not_ends_with: String + user_not_ends_with_nocase: String + user_contains: String + user_not_contains: String + user_contains_nocase: String + user_not_contains_nocase: String + user_: User_filter + pair: String + pair_not: String + pair_gt: String + pair_lt: String + pair_gte: String + pair_lte: String + pair_in: [String!] + pair_not_in: [String!] + pair_starts_with: String + pair_starts_with_nocase: String + pair_not_starts_with: String + pair_not_starts_with_nocase: String + pair_ends_with: String + pair_ends_with_nocase: String + pair_not_ends_with: String + pair_not_ends_with_nocase: String + pair_contains: String + pair_not_contains: String + pair_contains_nocase: String + pair_not_contains_nocase: String + pair_: Pair_filter + token0PriceUSD: BigDecimal + token0PriceUSD_not: BigDecimal + token0PriceUSD_gt: BigDecimal + token0PriceUSD_lt: BigDecimal + token0PriceUSD_gte: BigDecimal + token0PriceUSD_lte: BigDecimal + token0PriceUSD_in: [BigDecimal!] + token0PriceUSD_not_in: [BigDecimal!] + token1PriceUSD: BigDecimal + token1PriceUSD_not: BigDecimal + token1PriceUSD_gt: BigDecimal + token1PriceUSD_lt: BigDecimal + token1PriceUSD_gte: BigDecimal + token1PriceUSD_lte: BigDecimal + token1PriceUSD_in: [BigDecimal!] + token1PriceUSD_not_in: [BigDecimal!] + reserve0: BigDecimal + reserve0_not: BigDecimal + reserve0_gt: BigDecimal + reserve0_lt: BigDecimal + reserve0_gte: BigDecimal + reserve0_lte: BigDecimal + reserve0_in: [BigDecimal!] + reserve0_not_in: [BigDecimal!] + reserve1: BigDecimal + reserve1_not: BigDecimal + reserve1_gt: BigDecimal + reserve1_lt: BigDecimal + reserve1_gte: BigDecimal + reserve1_lte: BigDecimal + reserve1_in: [BigDecimal!] + reserve1_not_in: [BigDecimal!] + reserveUSD: BigDecimal + reserveUSD_not: BigDecimal + reserveUSD_gt: BigDecimal + reserveUSD_lt: BigDecimal + reserveUSD_gte: BigDecimal + reserveUSD_lte: BigDecimal + reserveUSD_in: [BigDecimal!] + reserveUSD_not_in: [BigDecimal!] + liquidityTokenTotalSupply: BigDecimal + liquidityTokenTotalSupply_not: BigDecimal + liquidityTokenTotalSupply_gt: BigDecimal + liquidityTokenTotalSupply_lt: BigDecimal + liquidityTokenTotalSupply_gte: BigDecimal + liquidityTokenTotalSupply_lte: BigDecimal + liquidityTokenTotalSupply_in: [BigDecimal!] + liquidityTokenTotalSupply_not_in: [BigDecimal!] + liquidityTokenBalance: BigDecimal + liquidityTokenBalance_not: BigDecimal + liquidityTokenBalance_gt: BigDecimal + liquidityTokenBalance_lt: BigDecimal + liquidityTokenBalance_gte: BigDecimal + liquidityTokenBalance_lte: BigDecimal + liquidityTokenBalance_in: [BigDecimal!] + liquidityTokenBalance_not_in: [BigDecimal!] + _change_block: BlockChangedFilter + and: [LiquidityPositionSnapshot_filter] + or: [LiquidityPositionSnapshot_filter] +} + +enum Transaction_orderBy { + id + blockNumber + timestamp + mints + burns + swaps +} + +input Transaction_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + blockNumber: BigInt + blockNumber_not: BigInt + blockNumber_gt: BigInt + blockNumber_lt: BigInt + blockNumber_gte: BigInt + blockNumber_lte: BigInt + blockNumber_in: [BigInt!] + blockNumber_not_in: [BigInt!] + timestamp: BigInt + timestamp_not: BigInt + timestamp_gt: BigInt + timestamp_lt: BigInt + timestamp_gte: BigInt + timestamp_lte: BigInt + timestamp_in: [BigInt!] + timestamp_not_in: [BigInt!] + mints: [String!] + mints_not: [String!] + mints_contains: [String!] + mints_not_contains: [String!] + mints_contains_nocase: [String!] + mints_not_contains_nocase: [String!] + mints_: Mint_filter + burns: [String!] + burns_not: [String!] + burns_contains: [String!] + burns_not_contains: [String!] + burns_contains_nocase: [String!] + burns_not_contains_nocase: [String!] + burns_: Burn_filter + swaps: [String!] + swaps_not: [String!] + swaps_contains: [String!] + swaps_not_contains: [String!] + swaps_contains_nocase: [String!] + swaps_not_contains_nocase: [String!] + swaps_: Swap_filter + _change_block: BlockChangedFilter + and: [Transaction_filter] + or: [Transaction_filter] +} + +enum Mint_orderBy { + id + transaction + transaction__id + transaction__blockNumber + transaction__timestamp + timestamp + pair + pair__id + pair__reserve0 + pair__reserve1 + pair__totalSupply + pair__reserveETH + pair__reserveUSD + pair__trackedReserveETH + pair__token0Price + pair__token1Price + pair__volumeToken0 + pair__volumeToken1 + pair__volumeUSD + pair__untrackedVolumeUSD + pair__txCount + pair__createdAtTimestamp + pair__createdAtBlockNumber + pair__liquidityProviderCount + to + liquidity + sender + amount0 + amount1 + logIndex + amountUSD + feeTo + feeLiquidity +} + +input Mint_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + transaction: String + transaction_not: String + transaction_gt: String + transaction_lt: String + transaction_gte: String + transaction_lte: String + transaction_in: [String!] + transaction_not_in: [String!] + transaction_starts_with: String + transaction_starts_with_nocase: String + transaction_not_starts_with: String + transaction_not_starts_with_nocase: String + transaction_ends_with: String + transaction_ends_with_nocase: String + transaction_not_ends_with: String + transaction_not_ends_with_nocase: String + transaction_contains: String + transaction_not_contains: String + transaction_contains_nocase: String + transaction_not_contains_nocase: String + transaction_: Transaction_filter + timestamp: BigInt + timestamp_not: BigInt + timestamp_gt: BigInt + timestamp_lt: BigInt + timestamp_gte: BigInt + timestamp_lte: BigInt + timestamp_in: [BigInt!] + timestamp_not_in: [BigInt!] + pair: String + pair_not: String + pair_gt: String + pair_lt: String + pair_gte: String + pair_lte: String + pair_in: [String!] + pair_not_in: [String!] + pair_starts_with: String + pair_starts_with_nocase: String + pair_not_starts_with: String + pair_not_starts_with_nocase: String + pair_ends_with: String + pair_ends_with_nocase: String + pair_not_ends_with: String + pair_not_ends_with_nocase: String + pair_contains: String + pair_not_contains: String + pair_contains_nocase: String + pair_not_contains_nocase: String + pair_: Pair_filter + to: Bytes + to_not: Bytes + to_gt: Bytes + to_lt: Bytes + to_gte: Bytes + to_lte: Bytes + to_in: [Bytes!] + to_not_in: [Bytes!] + to_contains: Bytes + to_not_contains: Bytes + liquidity: BigDecimal + liquidity_not: BigDecimal + liquidity_gt: BigDecimal + liquidity_lt: BigDecimal + liquidity_gte: BigDecimal + liquidity_lte: BigDecimal + liquidity_in: [BigDecimal!] + liquidity_not_in: [BigDecimal!] + sender: Bytes + sender_not: Bytes + sender_gt: Bytes + sender_lt: Bytes + sender_gte: Bytes + sender_lte: Bytes + sender_in: [Bytes!] + sender_not_in: [Bytes!] + sender_contains: Bytes + sender_not_contains: Bytes + amount0: BigDecimal + amount0_not: BigDecimal + amount0_gt: BigDecimal + amount0_lt: BigDecimal + amount0_gte: BigDecimal + amount0_lte: BigDecimal + amount0_in: [BigDecimal!] + amount0_not_in: [BigDecimal!] + amount1: BigDecimal + amount1_not: BigDecimal + amount1_gt: BigDecimal + amount1_lt: BigDecimal + amount1_gte: BigDecimal + amount1_lte: BigDecimal + amount1_in: [BigDecimal!] + amount1_not_in: [BigDecimal!] + logIndex: BigInt + logIndex_not: BigInt + logIndex_gt: BigInt + logIndex_lt: BigInt + logIndex_gte: BigInt + logIndex_lte: BigInt + logIndex_in: [BigInt!] + logIndex_not_in: [BigInt!] + amountUSD: BigDecimal + amountUSD_not: BigDecimal + amountUSD_gt: BigDecimal + amountUSD_lt: BigDecimal + amountUSD_gte: BigDecimal + amountUSD_lte: BigDecimal + amountUSD_in: [BigDecimal!] + amountUSD_not_in: [BigDecimal!] + feeTo: Bytes + feeTo_not: Bytes + feeTo_gt: Bytes + feeTo_lt: Bytes + feeTo_gte: Bytes + feeTo_lte: Bytes + feeTo_in: [Bytes!] + feeTo_not_in: [Bytes!] + feeTo_contains: Bytes + feeTo_not_contains: Bytes + feeLiquidity: BigDecimal + feeLiquidity_not: BigDecimal + feeLiquidity_gt: BigDecimal + feeLiquidity_lt: BigDecimal + feeLiquidity_gte: BigDecimal + feeLiquidity_lte: BigDecimal + feeLiquidity_in: [BigDecimal!] + feeLiquidity_not_in: [BigDecimal!] + _change_block: BlockChangedFilter + and: [Mint_filter] + or: [Mint_filter] +} + +enum Burn_orderBy { + id + transaction + transaction__id + transaction__blockNumber + transaction__timestamp + timestamp + pair + pair__id + pair__reserve0 + pair__reserve1 + pair__totalSupply + pair__reserveETH + pair__reserveUSD + pair__trackedReserveETH + pair__token0Price + pair__token1Price + pair__volumeToken0 + pair__volumeToken1 + pair__volumeUSD + pair__untrackedVolumeUSD + pair__txCount + pair__createdAtTimestamp + pair__createdAtBlockNumber + pair__liquidityProviderCount + liquidity + sender + amount0 + amount1 + to + logIndex + amountUSD + needsComplete + feeTo + feeLiquidity +} + +input Burn_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + transaction: String + transaction_not: String + transaction_gt: String + transaction_lt: String + transaction_gte: String + transaction_lte: String + transaction_in: [String!] + transaction_not_in: [String!] + transaction_starts_with: String + transaction_starts_with_nocase: String + transaction_not_starts_with: String + transaction_not_starts_with_nocase: String + transaction_ends_with: String + transaction_ends_with_nocase: String + transaction_not_ends_with: String + transaction_not_ends_with_nocase: String + transaction_contains: String + transaction_not_contains: String + transaction_contains_nocase: String + transaction_not_contains_nocase: String + transaction_: Transaction_filter + timestamp: BigInt + timestamp_not: BigInt + timestamp_gt: BigInt + timestamp_lt: BigInt + timestamp_gte: BigInt + timestamp_lte: BigInt + timestamp_in: [BigInt!] + timestamp_not_in: [BigInt!] + pair: String + pair_not: String + pair_gt: String + pair_lt: String + pair_gte: String + pair_lte: String + pair_in: [String!] + pair_not_in: [String!] + pair_starts_with: String + pair_starts_with_nocase: String + pair_not_starts_with: String + pair_not_starts_with_nocase: String + pair_ends_with: String + pair_ends_with_nocase: String + pair_not_ends_with: String + pair_not_ends_with_nocase: String + pair_contains: String + pair_not_contains: String + pair_contains_nocase: String + pair_not_contains_nocase: String + pair_: Pair_filter + liquidity: BigDecimal + liquidity_not: BigDecimal + liquidity_gt: BigDecimal + liquidity_lt: BigDecimal + liquidity_gte: BigDecimal + liquidity_lte: BigDecimal + liquidity_in: [BigDecimal!] + liquidity_not_in: [BigDecimal!] + sender: Bytes + sender_not: Bytes + sender_gt: Bytes + sender_lt: Bytes + sender_gte: Bytes + sender_lte: Bytes + sender_in: [Bytes!] + sender_not_in: [Bytes!] + sender_contains: Bytes + sender_not_contains: Bytes + amount0: BigDecimal + amount0_not: BigDecimal + amount0_gt: BigDecimal + amount0_lt: BigDecimal + amount0_gte: BigDecimal + amount0_lte: BigDecimal + amount0_in: [BigDecimal!] + amount0_not_in: [BigDecimal!] + amount1: BigDecimal + amount1_not: BigDecimal + amount1_gt: BigDecimal + amount1_lt: BigDecimal + amount1_gte: BigDecimal + amount1_lte: BigDecimal + amount1_in: [BigDecimal!] + amount1_not_in: [BigDecimal!] + to: Bytes + to_not: Bytes + to_gt: Bytes + to_lt: Bytes + to_gte: Bytes + to_lte: Bytes + to_in: [Bytes!] + to_not_in: [Bytes!] + to_contains: Bytes + to_not_contains: Bytes + logIndex: BigInt + logIndex_not: BigInt + logIndex_gt: BigInt + logIndex_lt: BigInt + logIndex_gte: BigInt + logIndex_lte: BigInt + logIndex_in: [BigInt!] + logIndex_not_in: [BigInt!] + amountUSD: BigDecimal + amountUSD_not: BigDecimal + amountUSD_gt: BigDecimal + amountUSD_lt: BigDecimal + amountUSD_gte: BigDecimal + amountUSD_lte: BigDecimal + amountUSD_in: [BigDecimal!] + amountUSD_not_in: [BigDecimal!] + needsComplete: Boolean + needsComplete_not: Boolean + needsComplete_gt: Boolean + needsComplete_lt: Boolean + needsComplete_gte: Boolean + needsComplete_lte: Boolean + needsComplete_in: [Boolean!] + needsComplete_not_in: [Boolean!] + feeTo: Bytes + feeTo_not: Bytes + feeTo_gt: Bytes + feeTo_lt: Bytes + feeTo_gte: Bytes + feeTo_lte: Bytes + feeTo_in: [Bytes!] + feeTo_not_in: [Bytes!] + feeTo_contains: Bytes + feeTo_not_contains: Bytes + feeLiquidity: BigDecimal + feeLiquidity_not: BigDecimal + feeLiquidity_gt: BigDecimal + feeLiquidity_lt: BigDecimal + feeLiquidity_gte: BigDecimal + feeLiquidity_lte: BigDecimal + feeLiquidity_in: [BigDecimal!] + feeLiquidity_not_in: [BigDecimal!] + _change_block: BlockChangedFilter + and: [Burn_filter] + or: [Burn_filter] +} + +enum Swap_orderBy { + id + transaction + transaction__id + transaction__blockNumber + transaction__timestamp + timestamp + pair + pair__id + pair__reserve0 + pair__reserve1 + pair__totalSupply + pair__reserveETH + pair__reserveUSD + pair__trackedReserveETH + pair__token0Price + pair__token1Price + pair__volumeToken0 + pair__volumeToken1 + pair__volumeUSD + pair__untrackedVolumeUSD + pair__txCount + pair__createdAtTimestamp + pair__createdAtBlockNumber + pair__liquidityProviderCount + sender + from + amount0In + amount1In + amount0Out + amount1Out + to + logIndex + amountUSD +} + +input Swap_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + transaction: String + transaction_not: String + transaction_gt: String + transaction_lt: String + transaction_gte: String + transaction_lte: String + transaction_in: [String!] + transaction_not_in: [String!] + transaction_starts_with: String + transaction_starts_with_nocase: String + transaction_not_starts_with: String + transaction_not_starts_with_nocase: String + transaction_ends_with: String + transaction_ends_with_nocase: String + transaction_not_ends_with: String + transaction_not_ends_with_nocase: String + transaction_contains: String + transaction_not_contains: String + transaction_contains_nocase: String + transaction_not_contains_nocase: String + transaction_: Transaction_filter + timestamp: BigInt + timestamp_not: BigInt + timestamp_gt: BigInt + timestamp_lt: BigInt + timestamp_gte: BigInt + timestamp_lte: BigInt + timestamp_in: [BigInt!] + timestamp_not_in: [BigInt!] + pair: String + pair_not: String + pair_gt: String + pair_lt: String + pair_gte: String + pair_lte: String + pair_in: [String!] + pair_not_in: [String!] + pair_starts_with: String + pair_starts_with_nocase: String + pair_not_starts_with: String + pair_not_starts_with_nocase: String + pair_ends_with: String + pair_ends_with_nocase: String + pair_not_ends_with: String + pair_not_ends_with_nocase: String + pair_contains: String + pair_not_contains: String + pair_contains_nocase: String + pair_not_contains_nocase: String + pair_: Pair_filter + sender: Bytes + sender_not: Bytes + sender_gt: Bytes + sender_lt: Bytes + sender_gte: Bytes + sender_lte: Bytes + sender_in: [Bytes!] + sender_not_in: [Bytes!] + sender_contains: Bytes + sender_not_contains: Bytes + from: Bytes + from_not: Bytes + from_gt: Bytes + from_lt: Bytes + from_gte: Bytes + from_lte: Bytes + from_in: [Bytes!] + from_not_in: [Bytes!] + from_contains: Bytes + from_not_contains: Bytes + amount0In: BigDecimal + amount0In_not: BigDecimal + amount0In_gt: BigDecimal + amount0In_lt: BigDecimal + amount0In_gte: BigDecimal + amount0In_lte: BigDecimal + amount0In_in: [BigDecimal!] + amount0In_not_in: [BigDecimal!] + amount1In: BigDecimal + amount1In_not: BigDecimal + amount1In_gt: BigDecimal + amount1In_lt: BigDecimal + amount1In_gte: BigDecimal + amount1In_lte: BigDecimal + amount1In_in: [BigDecimal!] + amount1In_not_in: [BigDecimal!] + amount0Out: BigDecimal + amount0Out_not: BigDecimal + amount0Out_gt: BigDecimal + amount0Out_lt: BigDecimal + amount0Out_gte: BigDecimal + amount0Out_lte: BigDecimal + amount0Out_in: [BigDecimal!] + amount0Out_not_in: [BigDecimal!] + amount1Out: BigDecimal + amount1Out_not: BigDecimal + amount1Out_gt: BigDecimal + amount1Out_lt: BigDecimal + amount1Out_gte: BigDecimal + amount1Out_lte: BigDecimal + amount1Out_in: [BigDecimal!] + amount1Out_not_in: [BigDecimal!] + to: Bytes + to_not: Bytes + to_gt: Bytes + to_lt: Bytes + to_gte: Bytes + to_lte: Bytes + to_in: [Bytes!] + to_not_in: [Bytes!] + to_contains: Bytes + to_not_contains: Bytes + logIndex: BigInt + logIndex_not: BigInt + logIndex_gt: BigInt + logIndex_lt: BigInt + logIndex_gte: BigInt + logIndex_lte: BigInt + logIndex_in: [BigInt!] + logIndex_not_in: [BigInt!] + amountUSD: BigDecimal + amountUSD_not: BigDecimal + amountUSD_gt: BigDecimal + amountUSD_lt: BigDecimal + amountUSD_gte: BigDecimal + amountUSD_lte: BigDecimal + amountUSD_in: [BigDecimal!] + amountUSD_not_in: [BigDecimal!] + _change_block: BlockChangedFilter + and: [Swap_filter] + or: [Swap_filter] +} + +enum Bundle_orderBy { + id + ethPrice +} + +input Bundle_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + ethPrice: BigDecimal + ethPrice_not: BigDecimal + ethPrice_gt: BigDecimal + ethPrice_lt: BigDecimal + ethPrice_gte: BigDecimal + ethPrice_lte: BigDecimal + ethPrice_in: [BigDecimal!] + ethPrice_not_in: [BigDecimal!] + _change_block: BlockChangedFilter + and: [Bundle_filter] + or: [Bundle_filter] +} + +enum UniswapDayData_orderBy { + id + date + dailyVolumeETH + dailyVolumeUSD + dailyVolumeUntracked + totalVolumeETH + totalLiquidityETH + totalVolumeUSD + totalLiquidityUSD + txCount +} + +input UniswapDayData_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + date: Int + date_not: Int + date_gt: Int + date_lt: Int + date_gte: Int + date_lte: Int + date_in: [Int!] + date_not_in: [Int!] + dailyVolumeETH: BigDecimal + dailyVolumeETH_not: BigDecimal + dailyVolumeETH_gt: BigDecimal + dailyVolumeETH_lt: BigDecimal + dailyVolumeETH_gte: BigDecimal + dailyVolumeETH_lte: BigDecimal + dailyVolumeETH_in: [BigDecimal!] + dailyVolumeETH_not_in: [BigDecimal!] + dailyVolumeUSD: BigDecimal + dailyVolumeUSD_not: BigDecimal + dailyVolumeUSD_gt: BigDecimal + dailyVolumeUSD_lt: BigDecimal + dailyVolumeUSD_gte: BigDecimal + dailyVolumeUSD_lte: BigDecimal + dailyVolumeUSD_in: [BigDecimal!] + dailyVolumeUSD_not_in: [BigDecimal!] + dailyVolumeUntracked: BigDecimal + dailyVolumeUntracked_not: BigDecimal + dailyVolumeUntracked_gt: BigDecimal + dailyVolumeUntracked_lt: BigDecimal + dailyVolumeUntracked_gte: BigDecimal + dailyVolumeUntracked_lte: BigDecimal + dailyVolumeUntracked_in: [BigDecimal!] + dailyVolumeUntracked_not_in: [BigDecimal!] + totalVolumeETH: BigDecimal + totalVolumeETH_not: BigDecimal + totalVolumeETH_gt: BigDecimal + totalVolumeETH_lt: BigDecimal + totalVolumeETH_gte: BigDecimal + totalVolumeETH_lte: BigDecimal + totalVolumeETH_in: [BigDecimal!] + totalVolumeETH_not_in: [BigDecimal!] + totalLiquidityETH: BigDecimal + totalLiquidityETH_not: BigDecimal + totalLiquidityETH_gt: BigDecimal + totalLiquidityETH_lt: BigDecimal + totalLiquidityETH_gte: BigDecimal + totalLiquidityETH_lte: BigDecimal + totalLiquidityETH_in: [BigDecimal!] + totalLiquidityETH_not_in: [BigDecimal!] + totalVolumeUSD: BigDecimal + totalVolumeUSD_not: BigDecimal + totalVolumeUSD_gt: BigDecimal + totalVolumeUSD_lt: BigDecimal + totalVolumeUSD_gte: BigDecimal + totalVolumeUSD_lte: BigDecimal + totalVolumeUSD_in: [BigDecimal!] + totalVolumeUSD_not_in: [BigDecimal!] + totalLiquidityUSD: BigDecimal + totalLiquidityUSD_not: BigDecimal + totalLiquidityUSD_gt: BigDecimal + totalLiquidityUSD_lt: BigDecimal + totalLiquidityUSD_gte: BigDecimal + totalLiquidityUSD_lte: BigDecimal + totalLiquidityUSD_in: [BigDecimal!] + totalLiquidityUSD_not_in: [BigDecimal!] + txCount: BigInt + txCount_not: BigInt + txCount_gt: BigInt + txCount_lt: BigInt + txCount_gte: BigInt + txCount_lte: BigInt + txCount_in: [BigInt!] + txCount_not_in: [BigInt!] + _change_block: BlockChangedFilter + and: [UniswapDayData_filter] + or: [UniswapDayData_filter] +} + +enum PairHourData_orderBy { + id + hourStartUnix + pair + pair__id + pair__reserve0 + pair__reserve1 + pair__totalSupply + pair__reserveETH + pair__reserveUSD + pair__trackedReserveETH + pair__token0Price + pair__token1Price + pair__volumeToken0 + pair__volumeToken1 + pair__volumeUSD + pair__untrackedVolumeUSD + pair__txCount + pair__createdAtTimestamp + pair__createdAtBlockNumber + pair__liquidityProviderCount + reserve0 + reserve1 + totalSupply + reserveUSD + hourlyVolumeToken0 + hourlyVolumeToken1 + hourlyVolumeUSD + hourlyTxns +} + +input PairHourData_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + hourStartUnix: Int + hourStartUnix_not: Int + hourStartUnix_gt: Int + hourStartUnix_lt: Int + hourStartUnix_gte: Int + hourStartUnix_lte: Int + hourStartUnix_in: [Int!] + hourStartUnix_not_in: [Int!] + pair: String + pair_not: String + pair_gt: String + pair_lt: String + pair_gte: String + pair_lte: String + pair_in: [String!] + pair_not_in: [String!] + pair_starts_with: String + pair_starts_with_nocase: String + pair_not_starts_with: String + pair_not_starts_with_nocase: String + pair_ends_with: String + pair_ends_with_nocase: String + pair_not_ends_with: String + pair_not_ends_with_nocase: String + pair_contains: String + pair_not_contains: String + pair_contains_nocase: String + pair_not_contains_nocase: String + pair_: Pair_filter + reserve0: BigDecimal + reserve0_not: BigDecimal + reserve0_gt: BigDecimal + reserve0_lt: BigDecimal + reserve0_gte: BigDecimal + reserve0_lte: BigDecimal + reserve0_in: [BigDecimal!] + reserve0_not_in: [BigDecimal!] + reserve1: BigDecimal + reserve1_not: BigDecimal + reserve1_gt: BigDecimal + reserve1_lt: BigDecimal + reserve1_gte: BigDecimal + reserve1_lte: BigDecimal + reserve1_in: [BigDecimal!] + reserve1_not_in: [BigDecimal!] + totalSupply: BigDecimal + totalSupply_not: BigDecimal + totalSupply_gt: BigDecimal + totalSupply_lt: BigDecimal + totalSupply_gte: BigDecimal + totalSupply_lte: BigDecimal + totalSupply_in: [BigDecimal!] + totalSupply_not_in: [BigDecimal!] + reserveUSD: BigDecimal + reserveUSD_not: BigDecimal + reserveUSD_gt: BigDecimal + reserveUSD_lt: BigDecimal + reserveUSD_gte: BigDecimal + reserveUSD_lte: BigDecimal + reserveUSD_in: [BigDecimal!] + reserveUSD_not_in: [BigDecimal!] + hourlyVolumeToken0: BigDecimal + hourlyVolumeToken0_not: BigDecimal + hourlyVolumeToken0_gt: BigDecimal + hourlyVolumeToken0_lt: BigDecimal + hourlyVolumeToken0_gte: BigDecimal + hourlyVolumeToken0_lte: BigDecimal + hourlyVolumeToken0_in: [BigDecimal!] + hourlyVolumeToken0_not_in: [BigDecimal!] + hourlyVolumeToken1: BigDecimal + hourlyVolumeToken1_not: BigDecimal + hourlyVolumeToken1_gt: BigDecimal + hourlyVolumeToken1_lt: BigDecimal + hourlyVolumeToken1_gte: BigDecimal + hourlyVolumeToken1_lte: BigDecimal + hourlyVolumeToken1_in: [BigDecimal!] + hourlyVolumeToken1_not_in: [BigDecimal!] + hourlyVolumeUSD: BigDecimal + hourlyVolumeUSD_not: BigDecimal + hourlyVolumeUSD_gt: BigDecimal + hourlyVolumeUSD_lt: BigDecimal + hourlyVolumeUSD_gte: BigDecimal + hourlyVolumeUSD_lte: BigDecimal + hourlyVolumeUSD_in: [BigDecimal!] + hourlyVolumeUSD_not_in: [BigDecimal!] + hourlyTxns: BigInt + hourlyTxns_not: BigInt + hourlyTxns_gt: BigInt + hourlyTxns_lt: BigInt + hourlyTxns_gte: BigInt + hourlyTxns_lte: BigInt + hourlyTxns_in: [BigInt!] + hourlyTxns_not_in: [BigInt!] + _change_block: BlockChangedFilter + and: [PairHourData_filter] + or: [PairHourData_filter] +} + +enum PairDayData_orderBy { + id + date + pairAddress + token0 + token0__id + token0__symbol + token0__name + token0__decimals + token0__totalSupply + token0__tradeVolume + token0__tradeVolumeUSD + token0__untrackedVolumeUSD + token0__txCount + token0__totalLiquidity + token0__derivedETH + token1 + token1__id + token1__symbol + token1__name + token1__decimals + token1__totalSupply + token1__tradeVolume + token1__tradeVolumeUSD + token1__untrackedVolumeUSD + token1__txCount + token1__totalLiquidity + token1__derivedETH + reserve0 + reserve1 + totalSupply + reserveUSD + dailyVolumeToken0 + dailyVolumeToken1 + dailyVolumeUSD + dailyTxns +} + +input PairDayData_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + date: Int + date_not: Int + date_gt: Int + date_lt: Int + date_gte: Int + date_lte: Int + date_in: [Int!] + date_not_in: [Int!] + pairAddress: Bytes + pairAddress_not: Bytes + pairAddress_gt: Bytes + pairAddress_lt: Bytes + pairAddress_gte: Bytes + pairAddress_lte: Bytes + pairAddress_in: [Bytes!] + pairAddress_not_in: [Bytes!] + pairAddress_contains: Bytes + pairAddress_not_contains: Bytes + token0: String + token0_not: String + token0_gt: String + token0_lt: String + token0_gte: String + token0_lte: String + token0_in: [String!] + token0_not_in: [String!] + token0_starts_with: String + token0_starts_with_nocase: String + token0_not_starts_with: String + token0_not_starts_with_nocase: String + token0_ends_with: String + token0_ends_with_nocase: String + token0_not_ends_with: String + token0_not_ends_with_nocase: String + token0_contains: String + token0_not_contains: String + token0_contains_nocase: String + token0_not_contains_nocase: String + token0_: Token_filter + token1: String + token1_not: String + token1_gt: String + token1_lt: String + token1_gte: String + token1_lte: String + token1_in: [String!] + token1_not_in: [String!] + token1_starts_with: String + token1_starts_with_nocase: String + token1_not_starts_with: String + token1_not_starts_with_nocase: String + token1_ends_with: String + token1_ends_with_nocase: String + token1_not_ends_with: String + token1_not_ends_with_nocase: String + token1_contains: String + token1_not_contains: String + token1_contains_nocase: String + token1_not_contains_nocase: String + token1_: Token_filter + reserve0: BigDecimal + reserve0_not: BigDecimal + reserve0_gt: BigDecimal + reserve0_lt: BigDecimal + reserve0_gte: BigDecimal + reserve0_lte: BigDecimal + reserve0_in: [BigDecimal!] + reserve0_not_in: [BigDecimal!] + reserve1: BigDecimal + reserve1_not: BigDecimal + reserve1_gt: BigDecimal + reserve1_lt: BigDecimal + reserve1_gte: BigDecimal + reserve1_lte: BigDecimal + reserve1_in: [BigDecimal!] + reserve1_not_in: [BigDecimal!] + totalSupply: BigDecimal + totalSupply_not: BigDecimal + totalSupply_gt: BigDecimal + totalSupply_lt: BigDecimal + totalSupply_gte: BigDecimal + totalSupply_lte: BigDecimal + totalSupply_in: [BigDecimal!] + totalSupply_not_in: [BigDecimal!] + reserveUSD: BigDecimal + reserveUSD_not: BigDecimal + reserveUSD_gt: BigDecimal + reserveUSD_lt: BigDecimal + reserveUSD_gte: BigDecimal + reserveUSD_lte: BigDecimal + reserveUSD_in: [BigDecimal!] + reserveUSD_not_in: [BigDecimal!] + dailyVolumeToken0: BigDecimal + dailyVolumeToken0_not: BigDecimal + dailyVolumeToken0_gt: BigDecimal + dailyVolumeToken0_lt: BigDecimal + dailyVolumeToken0_gte: BigDecimal + dailyVolumeToken0_lte: BigDecimal + dailyVolumeToken0_in: [BigDecimal!] + dailyVolumeToken0_not_in: [BigDecimal!] + dailyVolumeToken1: BigDecimal + dailyVolumeToken1_not: BigDecimal + dailyVolumeToken1_gt: BigDecimal + dailyVolumeToken1_lt: BigDecimal + dailyVolumeToken1_gte: BigDecimal + dailyVolumeToken1_lte: BigDecimal + dailyVolumeToken1_in: [BigDecimal!] + dailyVolumeToken1_not_in: [BigDecimal!] + dailyVolumeUSD: BigDecimal + dailyVolumeUSD_not: BigDecimal + dailyVolumeUSD_gt: BigDecimal + dailyVolumeUSD_lt: BigDecimal + dailyVolumeUSD_gte: BigDecimal + dailyVolumeUSD_lte: BigDecimal + dailyVolumeUSD_in: [BigDecimal!] + dailyVolumeUSD_not_in: [BigDecimal!] + dailyTxns: BigInt + dailyTxns_not: BigInt + dailyTxns_gt: BigInt + dailyTxns_lt: BigInt + dailyTxns_gte: BigInt + dailyTxns_lte: BigInt + dailyTxns_in: [BigInt!] + dailyTxns_not_in: [BigInt!] + _change_block: BlockChangedFilter + and: [PairDayData_filter] + or: [PairDayData_filter] +} + +enum TokenDayData_orderBy { + id + date + token + token__id + token__symbol + token__name + token__decimals + token__totalSupply + token__tradeVolume + token__tradeVolumeUSD + token__untrackedVolumeUSD + token__txCount + token__totalLiquidity + token__derivedETH + dailyVolumeToken + dailyVolumeETH + dailyVolumeUSD + dailyTxns + totalLiquidityToken + totalLiquidityETH + totalLiquidityUSD + priceUSD +} + +input TokenDayData_filter { + id: ID + id_not: ID + id_gt: ID + id_lt: ID + id_gte: ID + id_lte: ID + id_in: [ID!] + id_not_in: [ID!] + date: Int + date_not: Int + date_gt: Int + date_lt: Int + date_gte: Int + date_lte: Int + date_in: [Int!] + date_not_in: [Int!] + token: String + token_not: String + token_gt: String + token_lt: String + token_gte: String + token_lte: String + token_in: [String!] + token_not_in: [String!] + token_starts_with: String + token_starts_with_nocase: String + token_not_starts_with: String + token_not_starts_with_nocase: String + token_ends_with: String + token_ends_with_nocase: String + token_not_ends_with: String + token_not_ends_with_nocase: String + token_contains: String + token_not_contains: String + token_contains_nocase: String + token_not_contains_nocase: String + token_: Token_filter + dailyVolumeToken: BigDecimal + dailyVolumeToken_not: BigDecimal + dailyVolumeToken_gt: BigDecimal + dailyVolumeToken_lt: BigDecimal + dailyVolumeToken_gte: BigDecimal + dailyVolumeToken_lte: BigDecimal + dailyVolumeToken_in: [BigDecimal!] + dailyVolumeToken_not_in: [BigDecimal!] + dailyVolumeETH: BigDecimal + dailyVolumeETH_not: BigDecimal + dailyVolumeETH_gt: BigDecimal + dailyVolumeETH_lt: BigDecimal + dailyVolumeETH_gte: BigDecimal + dailyVolumeETH_lte: BigDecimal + dailyVolumeETH_in: [BigDecimal!] + dailyVolumeETH_not_in: [BigDecimal!] + dailyVolumeUSD: BigDecimal + dailyVolumeUSD_not: BigDecimal + dailyVolumeUSD_gt: BigDecimal + dailyVolumeUSD_lt: BigDecimal + dailyVolumeUSD_gte: BigDecimal + dailyVolumeUSD_lte: BigDecimal + dailyVolumeUSD_in: [BigDecimal!] + dailyVolumeUSD_not_in: [BigDecimal!] + dailyTxns: BigInt + dailyTxns_not: BigInt + dailyTxns_gt: BigInt + dailyTxns_lt: BigInt + dailyTxns_gte: BigInt + dailyTxns_lte: BigInt + dailyTxns_in: [BigInt!] + dailyTxns_not_in: [BigInt!] + totalLiquidityToken: BigDecimal + totalLiquidityToken_not: BigDecimal + totalLiquidityToken_gt: BigDecimal + totalLiquidityToken_lt: BigDecimal + totalLiquidityToken_gte: BigDecimal + totalLiquidityToken_lte: BigDecimal + totalLiquidityToken_in: [BigDecimal!] + totalLiquidityToken_not_in: [BigDecimal!] + totalLiquidityETH: BigDecimal + totalLiquidityETH_not: BigDecimal + totalLiquidityETH_gt: BigDecimal + totalLiquidityETH_lt: BigDecimal + totalLiquidityETH_gte: BigDecimal + totalLiquidityETH_lte: BigDecimal + totalLiquidityETH_in: [BigDecimal!] + totalLiquidityETH_not_in: [BigDecimal!] + totalLiquidityUSD: BigDecimal + totalLiquidityUSD_not: BigDecimal + totalLiquidityUSD_gt: BigDecimal + totalLiquidityUSD_lt: BigDecimal + totalLiquidityUSD_gte: BigDecimal + totalLiquidityUSD_lte: BigDecimal + totalLiquidityUSD_in: [BigDecimal!] + totalLiquidityUSD_not_in: [BigDecimal!] + priceUSD: BigDecimal + priceUSD_not: BigDecimal + priceUSD_gt: BigDecimal + priceUSD_lt: BigDecimal + priceUSD_gte: BigDecimal + priceUSD_lte: BigDecimal + priceUSD_in: [BigDecimal!] + priceUSD_not_in: [BigDecimal!] + _change_block: BlockChangedFilter + and: [TokenDayData_filter] + or: [TokenDayData_filter] +} + +type _MetaBlock_ { + hash: Bytes + number: Int! + timestamp: Int +} + +type _Meta_ { + block: _MetaBlock_! + deployment: String! + hasIndexingErrors: Boolean! +} + +type ResultState { + block: _Block_! + contractAddress: String! + cid: String! + kind: String! + data: String! +} + +type SyncStatus { + latestIndexedBlockHash: String! + latestIndexedBlockNumber: Int! + latestCanonicalBlockHash: String! + latestCanonicalBlockNumber: Int! + initialIndexedBlockHash: String! + initialIndexedBlockNumber: Int! + latestProcessedBlockHash: String! + latestProcessedBlockNumber: Int! +} + +type Query { + events(blockHash: String!, contractAddress: String!, name: String): [ResultEvent!] + eventsInRange(fromBlockNumber: Int!, toBlockNumber: Int!): [ResultEvent!] + uniswapFactory(id: ID!, block: Block_height): UniswapFactory + uniswapFactories(block: Block_height, where: UniswapFactory_filter, orderBy: UniswapFactory_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [UniswapFactory!]! + token(id: ID!, block: Block_height): Token + tokens(block: Block_height, where: Token_filter, orderBy: Token_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [Token!]! + pair(id: ID!, block: Block_height): Pair + pairs(block: Block_height, where: Pair_filter, orderBy: Pair_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [Pair!]! + user(id: ID!, block: Block_height): User + users(block: Block_height, where: User_filter, orderBy: User_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [User!]! + liquidityPosition(id: ID!, block: Block_height): LiquidityPosition + liquidityPositions(block: Block_height, where: LiquidityPosition_filter, orderBy: LiquidityPosition_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [LiquidityPosition!]! + liquidityPositionSnapshot(id: ID!, block: Block_height): LiquidityPositionSnapshot + liquidityPositionSnapshots(block: Block_height, where: LiquidityPositionSnapshot_filter, orderBy: LiquidityPositionSnapshot_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [LiquidityPositionSnapshot!]! + transaction(id: ID!, block: Block_height): Transaction + transactions(block: Block_height, where: Transaction_filter, orderBy: Transaction_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [Transaction!]! + mint(id: ID!, block: Block_height): Mint + mints(block: Block_height, where: Mint_filter, orderBy: Mint_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [Mint!]! + burn(id: ID!, block: Block_height): Burn + burns(block: Block_height, where: Burn_filter, orderBy: Burn_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [Burn!]! + swap(id: ID!, block: Block_height): Swap + swaps(block: Block_height, where: Swap_filter, orderBy: Swap_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [Swap!]! + bundle(id: ID!, block: Block_height): Bundle + bundles(block: Block_height, where: Bundle_filter, orderBy: Bundle_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [Bundle!]! + uniswapDayData(id: ID!, block: Block_height): UniswapDayData + uniswapDayDatas(block: Block_height, where: UniswapDayData_filter, orderBy: UniswapDayData_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [UniswapDayData!]! + pairHourData(id: ID!, block: Block_height): PairHourData + pairHourDatas(block: Block_height, where: PairHourData_filter, orderBy: PairHourData_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [PairHourData!]! + pairDayData(id: ID!, block: Block_height): PairDayData + pairDayDatas(block: Block_height, where: PairDayData_filter, orderBy: PairDayData_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [PairDayData!]! + tokenDayData(id: ID!, block: Block_height): TokenDayData + tokenDayDatas(block: Block_height, where: TokenDayData_filter, orderBy: TokenDayData_orderBy, orderDirection: OrderDirection, first: Int = 100, skip: Int = 0): [TokenDayData!]! + _meta(block: Block_height): _Meta_ + getStateByCID(cid: String!): ResultState + getState(blockHash: String!, contractAddress: String!, kind: String): ResultState + getSyncStatus: SyncStatus +} + +type UniswapFactory { + id: ID! + pairCount: Int! + totalVolumeUSD: BigDecimal! + totalVolumeETH: BigDecimal! + untrackedVolumeUSD: BigDecimal! + totalLiquidityUSD: BigDecimal! + totalLiquidityETH: BigDecimal! + txCount: BigInt! +} + +type Token { + id: ID! + symbol: String! + name: String! + decimals: BigInt! + totalSupply: BigInt! + tradeVolume: BigDecimal! + tradeVolumeUSD: BigDecimal! + untrackedVolumeUSD: BigDecimal! + txCount: BigInt! + totalLiquidity: BigDecimal! + derivedETH: BigDecimal! + tokenDayData: [TokenDayData!]! + pairDayDataBase: [PairDayData!]! + pairDayDataQuote: [PairDayData!]! + pairBase: [Pair!]! + pairQuote: [Pair!]! +} + +type TokenDayData { + id: ID! + date: Int! + token: Token! + dailyVolumeToken: BigDecimal! + dailyVolumeETH: BigDecimal! + dailyVolumeUSD: BigDecimal! + dailyTxns: BigInt! + totalLiquidityToken: BigDecimal! + totalLiquidityETH: BigDecimal! + totalLiquidityUSD: BigDecimal! + priceUSD: BigDecimal! +} + +type PairDayData { + id: ID! + date: Int! + pairAddress: Bytes! + token0: Token! + token1: Token! + reserve0: BigDecimal! + reserve1: BigDecimal! + totalSupply: BigDecimal + reserveUSD: BigDecimal! + dailyVolumeToken0: BigDecimal! + dailyVolumeToken1: BigDecimal! + dailyVolumeUSD: BigDecimal! + dailyTxns: BigInt! +} + +type Pair { + id: ID! + token0: Token! + token1: Token! + reserve0: BigDecimal! + reserve1: BigDecimal! + totalSupply: BigDecimal! + reserveETH: BigDecimal! + reserveUSD: BigDecimal! + trackedReserveETH: BigDecimal! + token0Price: BigDecimal! + token1Price: BigDecimal! + volumeToken0: BigDecimal! + volumeToken1: BigDecimal! + volumeUSD: BigDecimal! + untrackedVolumeUSD: BigDecimal! + txCount: BigInt! + createdAtTimestamp: BigInt! + createdAtBlockNumber: BigInt! + liquidityProviderCount: BigInt! + pairHourData: [PairHourData!]! + liquidityPositions: [LiquidityPosition!]! + liquidityPositionSnapshots: [LiquidityPositionSnapshot!]! + mints: [Mint!]! + burns: [Burn!]! + swaps: [Swap!]! +} + +type PairHourData { + id: ID! + hourStartUnix: Int! + pair: Pair! + reserve0: BigDecimal! + reserve1: BigDecimal! + totalSupply: BigDecimal + reserveUSD: BigDecimal! + hourlyVolumeToken0: BigDecimal! + hourlyVolumeToken1: BigDecimal! + hourlyVolumeUSD: BigDecimal! + hourlyTxns: BigInt! +} + +type LiquidityPosition { + id: ID! + user: User! + pair: Pair! + liquidityTokenBalance: BigDecimal! +} + +type User { + id: ID! + liquidityPositions: [LiquidityPosition!] + usdSwapped: BigDecimal! +} + +type LiquidityPositionSnapshot { + id: ID! + liquidityPosition: LiquidityPosition! + timestamp: Int! + block: Int! + user: User! + pair: Pair! + token0PriceUSD: BigDecimal! + token1PriceUSD: BigDecimal! + reserve0: BigDecimal! + reserve1: BigDecimal! + reserveUSD: BigDecimal! + liquidityTokenTotalSupply: BigDecimal! + liquidityTokenBalance: BigDecimal! +} + +type Mint { + id: ID! + transaction: Transaction! + timestamp: BigInt! + pair: Pair! + to: Bytes! + liquidity: BigDecimal! + sender: Bytes + amount0: BigDecimal + amount1: BigDecimal + logIndex: BigInt + amountUSD: BigDecimal + feeTo: Bytes + feeLiquidity: BigDecimal +} + +type Transaction { + id: ID! + blockNumber: BigInt! + timestamp: BigInt! + mints: [Mint!]! + burns: [Burn!]! + swaps: [Swap!]! +} + +type Burn { + id: ID! + transaction: Transaction! + timestamp: BigInt! + pair: Pair! + liquidity: BigDecimal! + sender: Bytes + amount0: BigDecimal + amount1: BigDecimal + to: Bytes + logIndex: BigInt + amountUSD: BigDecimal + needsComplete: Boolean! + feeTo: Bytes + feeLiquidity: BigDecimal +} + +type Swap { + id: ID! + transaction: Transaction! + timestamp: BigInt! + pair: Pair! + sender: Bytes! + from: Bytes! + amount0In: BigDecimal! + amount1In: BigDecimal! + amount0Out: BigDecimal! + amount1Out: BigDecimal! + to: Bytes! + logIndex: BigInt + amountUSD: BigDecimal! +} + +type Bundle { + id: ID! + ethPrice: BigDecimal! +} + +type UniswapDayData { + id: ID! + date: Int! + dailyVolumeETH: BigDecimal! + dailyVolumeUSD: BigDecimal! + dailyVolumeUntracked: BigDecimal! + totalVolumeETH: BigDecimal! + totalLiquidityETH: BigDecimal! + totalVolumeUSD: BigDecimal! + totalLiquidityUSD: BigDecimal! + txCount: BigInt! +} + +type Mutation { + watchContract(address: String!, kind: String!, checkpoint: Boolean!, startingBlock: Int): Boolean! +} + +type Subscription { + onEvent: ResultEvent! +} diff --git a/packages/v2-watcher/src/server.ts b/packages/v2-watcher/src/server.ts new file mode 100644 index 0000000..679134f --- /dev/null +++ b/packages/v2-watcher/src/server.ts @@ -0,0 +1,43 @@ +// +// Copyright 2021 Vulcanize, Inc. +// + +import fs from 'fs'; +import path from 'path'; +import 'reflect-metadata'; +import debug from 'debug'; + +import { ServerCmd } from '@cerc-io/cli'; +import { getGraphDbAndWatcher } from '@cerc-io/graph-node'; + +import { createResolvers } from './resolvers'; +import { Indexer } from './indexer'; +import { Database, ENTITY_QUERY_TYPE_MAP, ENTITY_TO_LATEST_ENTITY_MAP } from './database'; + +const log = debug('vulcanize:server'); + +export const main = async (): Promise => { + const serverCmd = new ServerCmd(); + await serverCmd.init(Database); + + const { graphWatcher } = await getGraphDbAndWatcher( + serverCmd.config.server, + serverCmd.clients.ethClient, + serverCmd.ethProvider, + serverCmd.database.baseDatabase, + ENTITY_QUERY_TYPE_MAP, + ENTITY_TO_LATEST_ENTITY_MAP + ); + + await serverCmd.initIndexer(Indexer, graphWatcher); + + const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.gql')).toString(); + + return serverCmd.exec(createResolvers, typeDefs); +}; + +main().then(() => { + log('Starting server...'); +}).catch(err => { + log(err); +}); diff --git a/packages/v2-watcher/src/types.ts b/packages/v2-watcher/src/types.ts new file mode 100644 index 0000000..c456217 --- /dev/null +++ b/packages/v2-watcher/src/types.ts @@ -0,0 +1,3 @@ +// +// Copyright 2021 Vulcanize, Inc. +// diff --git a/packages/v2-watcher/subgraph-build/Factory/Factory.wasm b/packages/v2-watcher/subgraph-build/Factory/Factory.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ee4f9c975ef1b52c8cf1bf06afea5fdc2bd12746 GIT binary patch literal 225762 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 y*9PsYho+V3usbi_w27+Y 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 literal 0 HcmV?d00001 diff --git a/packages/v2-watcher/tsconfig.json b/packages/v2-watcher/tsconfig.json new file mode 100644 index 0000000..f4b8852 --- /dev/null +++ b/packages/v2-watcher/tsconfig.json @@ -0,0 +1,74 @@ +{ + "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": ["es2019"], /* 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": [], /* 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/**/*"] +}