From 47126cd985ba18fdb42af83bd7b4a35c4493d91c Mon Sep 17 00:00:00 2001 From: web3cdn Date: Wed, 9 Aug 2023 19:47:10 +0300 Subject: [PATCH 1/2] Meter.io starter. Initial commit. --- .../.github/workflows/cli-deploy.yml | 33 +++ .../meteor-starter/.github/workflows/pr.yml | 24 ++ .../workflows/scripts/publish-deploy.sh | 14 ++ Meteor/meteor-starter/.gitignore | 59 +++++ Meteor/meteor-starter/LICENSE | 21 ++ Meteor/meteor-starter/README.md | 90 +++++++ Meteor/meteor-starter/abis/erc20.abi.json | 222 ++++++++++++++++++ Meteor/meteor-starter/docker-compose.yml | 63 +++++ .../meteor-starter/docker/load-extensions.sh | 6 + Meteor/meteor-starter/docker/pg-Dockerfile | 12 + Meteor/meteor-starter/package.json | 37 +++ Meteor/meteor-starter/project.yaml | 58 +++++ Meteor/meteor-starter/schema.graphql | 21 ++ Meteor/meteor-starter/src/index.ts | 2 + .../src/mappings/mappingHandlers.ts | 40 ++++ .../src/test/mappingHandlers.test.ts | 12 + Meteor/meteor-starter/tsconfig.json | 16 ++ 17 files changed, 730 insertions(+) create mode 100644 Meteor/meteor-starter/.github/workflows/cli-deploy.yml create mode 100644 Meteor/meteor-starter/.github/workflows/pr.yml create mode 100644 Meteor/meteor-starter/.github/workflows/scripts/publish-deploy.sh create mode 100644 Meteor/meteor-starter/.gitignore create mode 100644 Meteor/meteor-starter/LICENSE create mode 100644 Meteor/meteor-starter/README.md create mode 100644 Meteor/meteor-starter/abis/erc20.abi.json create mode 100644 Meteor/meteor-starter/docker-compose.yml create mode 100644 Meteor/meteor-starter/docker/load-extensions.sh create mode 100644 Meteor/meteor-starter/docker/pg-Dockerfile create mode 100644 Meteor/meteor-starter/package.json create mode 100644 Meteor/meteor-starter/project.yaml create mode 100644 Meteor/meteor-starter/schema.graphql create mode 100644 Meteor/meteor-starter/src/index.ts create mode 100644 Meteor/meteor-starter/src/mappings/mappingHandlers.ts create mode 100644 Meteor/meteor-starter/src/test/mappingHandlers.test.ts create mode 100644 Meteor/meteor-starter/tsconfig.json diff --git a/Meteor/meteor-starter/.github/workflows/cli-deploy.yml b/Meteor/meteor-starter/.github/workflows/cli-deploy.yml new file mode 100644 index 00000000..5edba7c0 --- /dev/null +++ b/Meteor/meteor-starter/.github/workflows/cli-deploy.yml @@ -0,0 +1,33 @@ +name: "CLI deploy" + +on: + workflow_dispatch: + inputs: + projectName: + description: "Project name" + required: true + type: string +jobs: + deploy: + name: CLI Deploy + runs-on: ubuntu-latest + environment: + name: DEPLOYMENT + env: + SUBQL_ACCESS_TOKEN: ${{ secrets.SUBQL_ACCESS_TOKEN }} + steps: + - uses: actions/checkout@v2 + - name: Setup Node.js environment + uses: actions/setup-node@v2 + with: + node-version: 16 + - run: yarn + - name: Codegen + run: yarn codegen + - name: Version + run: npx subql --version + - name: repo + run: echo ${{github.repository}} + - name: Publish and Deploy + run: | + sh .github/workflows/scripts/publish-deploy.sh -o ${{github.repository}} -p ${{github.event.inputs.projectName}} diff --git a/Meteor/meteor-starter/.github/workflows/pr.yml b/Meteor/meteor-starter/.github/workflows/pr.yml new file mode 100644 index 00000000..7a3166c7 --- /dev/null +++ b/Meteor/meteor-starter/.github/workflows/pr.yml @@ -0,0 +1,24 @@ +name: PR +on: + pull_request: + paths-ignore: + - ".github/workflows/**" +jobs: + pr: + name: pr + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Node.js environment + uses: actions/setup-node@v2 + with: + node-version: 16 + - run: yarn + - name: Codegen + run: yarn codegen + - name: Build + run: yarn build + - name: Install subql-node-ethereum + run: yarn global add @subql/node-ethereum + - name: Run tests with Subquery Node + run: subql-node-ethereum test -f ${{ github.workspace }} diff --git a/Meteor/meteor-starter/.github/workflows/scripts/publish-deploy.sh b/Meteor/meteor-starter/.github/workflows/scripts/publish-deploy.sh new file mode 100644 index 00000000..adae267d --- /dev/null +++ b/Meteor/meteor-starter/.github/workflows/scripts/publish-deploy.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +while getopts p:o: flag +do + case "${flag}" in + p) PROJECTNAME=${OPTARG};; + o) ORG=${OPTARG};; + *) echo "Usage: $0 [-p projectname] [-o org]" && exit 1;; + esac +done + +IPFSCID=$(npx subql publish -o -f .) + +npx subql deployment:deploy -d --ipfsCID="$IPFSCID" --projectName="${PROJECTNAME}" --org="${ORG%/*}" \ No newline at end of file diff --git a/Meteor/meteor-starter/.gitignore b/Meteor/meteor-starter/.gitignore new file mode 100644 index 00000000..c139d877 --- /dev/null +++ b/Meteor/meteor-starter/.gitignore @@ -0,0 +1,59 @@ +# These are some examples of commonly ignored file patterns. +# You should customize this list as applicable to your project. +# Learn more about .gitignore: +# https://www.atlassian.com/git/tutorials/saving-changes/gitignore + +# Node artifact files +node_modules/ +dist/ + +# lock files +yarn.lock +package-lock.json + +# Compiled Java class files +*.class + +# Compiled Python bytecode +*.py[cod] + +# Log files +*.log + +# Package files +*.jar + +# Maven +target/ +dist/ +src/types + +# JetBrains IDE +.idea/ + +# Unit test reports +TEST*.xml + +# Generated by MacOS +.DS_Store + +# Generated by Windows +Thumbs.db + +# Applications +*.app +*.exe +*.war + +# Large media files +*.mp4 +*.tiff +*.avi +*.flv +*.mov +*.wmv + +.data +.yarn + +.DS_Store diff --git a/Meteor/meteor-starter/LICENSE b/Meteor/meteor-starter/LICENSE new file mode 100644 index 00000000..76e70ccd --- /dev/null +++ b/Meteor/meteor-starter/LICENSE @@ -0,0 +1,21 @@ +MIT LICENSE + +Copyright 2020-2021 SubQuery Pte Ltd authors & contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Meteor/meteor-starter/README.md b/Meteor/meteor-starter/README.md new file mode 100644 index 00000000..13d1f63d --- /dev/null +++ b/Meteor/meteor-starter/README.md @@ -0,0 +1,90 @@ +# SubQuery - Example Project for Meteor + +[SubQuery](https://subquery.network) is a fast, flexible, and reliable open-source data indexer that provides you with custom APIs for your web3 project across all of our supported networks. To learn about how to get started with SubQuery, [visit our docs](https://academy.subquery.network). + +**This SubQuery project indexes all transfers and approval events for the [wrapped Ether token](https://scan.meter.io/address/0x983147fb73a45fc7f8b4dfa1cd61bdc7b111e5b6) (`0x983147fb73a45fc7f8b4dfa1cd61bdc7b111e5b6`) on Meter Network** + +## Start + +First, install SubQuery CLI globally on your terminal by using NPM `npm install -g @subql/cli` + +You can either clone this GitHub repo, or use the `subql` CLI to bootstrap a clean project in the network of your choosing by running `subql init` and following the prompts. + +Don't forget to install dependencies with `npm install` or `yarn install`! + +## Editing your SubQuery project + +Although this is a working example SubQuery project, you can edit the SubQuery project by changing the following files: + +- The project manifest in `project.yaml` defines the key project configuration and mapping handler filters +- The GraphQL Schema (`schema.graphql`) defines the shape of the resulting data that you are using SubQuery to index +- The Mapping functions in `src/mappings/` directory are typescript functions that handle transformation logic + +SubQuery supports various layer-1 blockchain networks and provides [dedicated quick start guides](https://academy.subquery.network/quickstart/quickstart.html) as well as [detailed technical documentation](https://academy.subquery.network/build/introduction.html) for each of them. + +## Run your project + +_If you get stuck, find out how to get help below._ + +The simplest way to run your project is by running `yarn dev` or `npm run-script dev`. This does all of the following: + +1. `yarn codegen` - Generates types from the GraphQL schema definition and contract ABIs and saves them in the `/src/types` directory. This must be done after each change to the `schema.graphql` file or the contract ABIs +2. `yarn build` - Builds and packages the SubQuery project into the `/dist` directory +3. `docker-compose pull && docker-compose up` - Runs a Docker container with an indexer, PostgeSQL DB, and a query service. This requires [Docker to be installed](https://docs.docker.com/engine/install) and running locally. The configuration for this container is set from your `docker-compose.yml` + +You can observe the three services start, and once all are running (it may take a few minutes on your first start), please open your browser and head to [http://localhost:3000](http://localhost:3000) - you should see a GraphQL playground showing with the schemas ready to query. [Read the docs for more information](https://academy.subquery.network/run_publish/run.html) or [explore the possible service configuration for running SubQuery](https://academy.subquery.network/run_publish/references.html). + +## Query your project + +For this project, you can try to query with the following GraphQL code to get a taste of how it works. + +```graphql +{ + query { + transfers(first: 5, orderBy: VALUE_DESC) { + totalCount + nodes { + id + blockHeight + from + to + value + contractAddress + } + } + } + approvals(first: 5, orderBy: BLOCK_HEIGHT_DESC) { + nodes { + id + blockHeight + owner + spender + value + contractAddress + } + } +} +``` + +You can explore the different possible queries and entities to help you with GraphQL using the documentation draw on the right. + +## Publish your project + +SubQuery is open-source, meaning you have the freedom to run it in the following three ways: + +- Locally on your own computer (or a cloud provider of your choosing), [view the instructions on how to run SubQuery Locally](https://academy.subquery.network/run_publish/run.html) +- By publishing it to our enterprise-level [Managed Service](https://managedservice.subquery.network), where we'll host your SubQuery project in production ready services for mission critical data with zero-downtime blue/green deployments. We even have a generous free tier. [Find out how](https://academy.subquery.network/run_publish/publish.html) +- [Coming Soon] By publishing it to the decentralised [SubQuery Network](https://subquery.network/network), the most open, performant, reliable, and scalable data service for dApp developers. The SubQuery Network indexes and services data to the global community in an incentivised and verifiable way + +## What Next? + +Take a look at some of our advanced features to take your project to the next level! + +- [**Multi-chain indexing support**](https://academy.subquery.network/build/multi-chain.html) - SubQuery allows you to index data from across different layer-1 networks into the same database, this allows you to query a single endpoint to get data for all supported networks. +- [**Dynamic Data Sources**](https://academy.subquery.network/build/dynamicdatasources.html) - When you want to index factory contracts, for example on a DEX or generative NFT project. +- [**Project Optimisation Advice**](https://academy.subquery.network/build/optimisation.html) - Some common tips on how to tweak your project to maximise performance. +- [**GraphQL Subscriptions**](https://academy.subquery.network/run_publish/subscription.html) - Build more reactive front end applications that subscribe to changes in your SubQuery project. + +## Need Help? + +The fastest way to get support is by [searching our documentation](https://academy.subquery.network), or by [joining our discord](https://discord.com/invite/subquery) and messaging us in the `#technical-support` channel. diff --git a/Meteor/meteor-starter/abis/erc20.abi.json b/Meteor/meteor-starter/abis/erc20.abi.json new file mode 100644 index 00000000..405d6b36 --- /dev/null +++ b/Meteor/meteor-starter/abis/erc20.abi.json @@ -0,0 +1,222 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] diff --git a/Meteor/meteor-starter/docker-compose.yml b/Meteor/meteor-starter/docker-compose.yml new file mode 100644 index 00000000..3d7f938c --- /dev/null +++ b/Meteor/meteor-starter/docker-compose.yml @@ -0,0 +1,63 @@ +version: "3" + +services: + postgres: + build: + context: . + dockerfile: ./docker/pg-Dockerfile + ports: + - 5432:5432 + volumes: + - .data/postgres:/var/lib/postgresql/data + environment: + POSTGRES_PASSWORD: postgres + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + + subquery-node: + image: onfinality/subql-node-ethereum:latest + depends_on: + "postgres": + condition: service_healthy + restart: always + environment: + DB_USER: postgres + DB_PASS: postgres + DB_DATABASE: postgres + DB_HOST: postgres + DB_PORT: 5432 + volumes: + - ./:/app + command: + - ${SUB_COMMAND} # set SUB_COMMAND env variable to "test" to run tests + - -f=/app + - --db-schema=app + healthcheck: + test: ["CMD", "curl", "-f", "http://subquery-node:3000/ready"] + interval: 3s + timeout: 5s + retries: 10 + + graphql-engine: + image: onfinality/subql-query:latest + ports: + - 3000:3000 + depends_on: + "postgres": + condition: service_healthy + "subquery-node": + condition: service_healthy + restart: always + environment: + DB_USER: postgres + DB_PASS: postgres + DB_DATABASE: postgres + DB_HOST: postgres + DB_PORT: 5432 + command: + - --name=app + - --playground + - --indexer=http://subquery-node:3000 diff --git a/Meteor/meteor-starter/docker/load-extensions.sh b/Meteor/meteor-starter/docker/load-extensions.sh new file mode 100644 index 00000000..7f5d0206 --- /dev/null +++ b/Meteor/meteor-starter/docker/load-extensions.sh @@ -0,0 +1,6 @@ + +#!/bin/sh + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" < { + logger.info(`New transfer transaction log at block ${log.blockNumber}`); + assert(log.args, "No log.args") + + const transferRecord = Transfer.create({ + id: log.transactionHash, + blockHeight: log.blockNumber.toString(), + value: log.args.value.toBigInt(), + from: log.args.from, + to: log.args.to, + contractAddress: log.address, + }); + + await transferRecord.save(); +} + +export async function handleTransaction(tx: ApproveTransaction): Promise { + assert(tx.args, "No tx.args") + logger.info(`New Approval transaction at block ${tx.blockNumber}`); + + + const approvalRecord = Approval.create({ + id: tx.hash, + blockHeight: tx.blockNumber.toString(), + owner: tx.from, + spender: (await tx.args[0]).toString(), + value: BigInt(await tx.args[1].toString()), + contractAddress: tx.to, + }); + + await approvalRecord.save(); +} + diff --git a/Meteor/meteor-starter/src/test/mappingHandlers.test.ts b/Meteor/meteor-starter/src/test/mappingHandlers.test.ts new file mode 100644 index 00000000..fc12d09a --- /dev/null +++ b/Meteor/meteor-starter/src/test/mappingHandlers.test.ts @@ -0,0 +1,12 @@ +import {subqlTest} from "@subql/testing"; + +/* +// https://academy.subquery.network/build/testing.html +subqlTest( + "testName", // test name + 1000003, // block height to process + [], // dependent entities + [], // expected entities + "handleEvent" //handler name +); +*/ diff --git a/Meteor/meteor-starter/tsconfig.json b/Meteor/meteor-starter/tsconfig.json new file mode 100644 index 00000000..0d8cdbe6 --- /dev/null +++ b/Meteor/meteor-starter/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "esModuleInterop": true, + "declaration": true, + "importHelpers": true, + "resolveJsonModule": true, + "module": "commonjs", + "outDir": "dist", + "rootDir": "src", + "target": "es2017", + "strict": true + }, + "include": ["src/**/*", "node_modules/@subql/types/dist/global.d.ts"] +} From 9e984ed781199fc92430340c54d3335c5003085e Mon Sep 17 00:00:00 2001 From: Web3CdnService Date: Sat, 2 Mar 2024 23:29:54 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=20=E2=99=A8=20Migrated=20FraxSwap=20subgra?= =?UTF-8?q?ph=20from=20Thegraph=20to=20Subquery.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✍ https://github.com/FraxFinance/fraxswap-subgraph ✔️ Chain id: 1 --- Ethereum/ethereum-frax/.eslintignore | 3 + Ethereum/ethereum-frax/.eslintrc | 5 + .../.github/workflows/cli-deploy.yml | 33 + .../ethereum-frax/.github/workflows/pr.yml | 24 + .../workflows/scripts/publish-deploy.sh | 14 + Ethereum/ethereum-frax/.gitignore | 60 ++ Ethereum/ethereum-frax/HELPFUL_QUERIES.txt | 14 + Ethereum/ethereum-frax/abis/ERC20.json | 328 ++++++ .../ethereum-frax/abis/ERC20NameBytes.json | 17 + .../ethereum-frax/abis/ERC20SymbolBytes.json | 17 + .../ethereum-frax/abis/FraxswapFactory.json | 108 ++ Ethereum/ethereum-frax/abis/FraxswapPair.json | 984 ++++++++++++++++++ Ethereum/ethereum-frax/config/mainnet.json | 30 + Ethereum/ethereum-frax/docker-compose.yml | 69 ++ .../ethereum-frax/docker/load-extensions.sh | 6 + Ethereum/ethereum-frax/docker/pg-Dockerfile | 12 + Ethereum/ethereum-frax/networks.json | 10 + Ethereum/ethereum-frax/package.json | 39 + Ethereum/ethereum-frax/project.ts | 120 +++ Ethereum/ethereum-frax/schema.graphql | 543 ++++++++++ Ethereum/ethereum-frax/src/entities/bundle.ts | 14 + Ethereum/ethereum-frax/src/entities/burn.ts | 0 .../ethereum-frax/src/entities/factory.ts | 71 ++ Ethereum/ethereum-frax/src/entities/index.ts | 50 + .../entities/liquidity-position-snapshot.ts | 53 + .../src/entities/liquidity-position.ts | 44 + Ethereum/ethereum-frax/src/entities/mint.ts | 0 .../src/entities/pair-day-data.ts | 57 + .../src/entities/pair-hour-data.ts | 49 + Ethereum/ethereum-frax/src/entities/pair.ts | 89 ++ Ethereum/ethereum-frax/src/entities/swap.ts | 1 + Ethereum/ethereum-frax/src/entities/sync.ts | 0 .../src/entities/token-day-data.ts | 50 + .../src/entities/token-hour-data.ts | 0 Ethereum/ethereum-frax/src/entities/token.ts | 191 ++++ .../ethereum-frax/src/entities/transaction.ts | 0 Ethereum/ethereum-frax/src/entities/user.ts | 44 + Ethereum/ethereum-frax/src/index.ts | 7 + .../src/mappings/fraxswap-pair.ts | 135 +++ .../src/mappings/handlers/Burn.ts | 119 +++ .../src/mappings/handlers/Mint.ts | 93 ++ .../src/mappings/handlers/PairCreated.ts | 57 + .../src/mappings/handlers/Swap.ts | 194 ++++ .../src/mappings/handlers/Sync.ts | 97 ++ .../src/mappings/handlers/Transfer.ts | 187 ++++ .../src/mappings/handlers/index.ts | 6 + .../src/mappings/mappingHandlers.ts | 1 + .../src/packages/constants/index.template.ts | 91 ++ Ethereum/ethereum-frax/src/pricing.ts | 80 ++ .../ethereum-frax/tests/.bin/fraxswap.wasm | Bin 0 -> 49369 bytes Ethereum/ethereum-frax/tests/.latest.json | 4 + Ethereum/ethereum-frax/tests/fraxswap.test.ts | 278 +++++ Ethereum/ethereum-frax/tsconfig.json | 20 + 53 files changed, 4518 insertions(+) create mode 100644 Ethereum/ethereum-frax/.eslintignore create mode 100644 Ethereum/ethereum-frax/.eslintrc create mode 100644 Ethereum/ethereum-frax/.github/workflows/cli-deploy.yml create mode 100644 Ethereum/ethereum-frax/.github/workflows/pr.yml create mode 100644 Ethereum/ethereum-frax/.github/workflows/scripts/publish-deploy.sh create mode 100644 Ethereum/ethereum-frax/.gitignore create mode 100644 Ethereum/ethereum-frax/HELPFUL_QUERIES.txt create mode 100644 Ethereum/ethereum-frax/abis/ERC20.json create mode 100644 Ethereum/ethereum-frax/abis/ERC20NameBytes.json create mode 100644 Ethereum/ethereum-frax/abis/ERC20SymbolBytes.json create mode 100644 Ethereum/ethereum-frax/abis/FraxswapFactory.json create mode 100644 Ethereum/ethereum-frax/abis/FraxswapPair.json create mode 100644 Ethereum/ethereum-frax/config/mainnet.json create mode 100644 Ethereum/ethereum-frax/docker-compose.yml create mode 100644 Ethereum/ethereum-frax/docker/load-extensions.sh create mode 100644 Ethereum/ethereum-frax/docker/pg-Dockerfile create mode 100644 Ethereum/ethereum-frax/networks.json create mode 100644 Ethereum/ethereum-frax/package.json create mode 100644 Ethereum/ethereum-frax/project.ts create mode 100644 Ethereum/ethereum-frax/schema.graphql create mode 100644 Ethereum/ethereum-frax/src/entities/bundle.ts create mode 100644 Ethereum/ethereum-frax/src/entities/burn.ts create mode 100644 Ethereum/ethereum-frax/src/entities/factory.ts create mode 100644 Ethereum/ethereum-frax/src/entities/index.ts create mode 100644 Ethereum/ethereum-frax/src/entities/liquidity-position-snapshot.ts create mode 100644 Ethereum/ethereum-frax/src/entities/liquidity-position.ts create mode 100644 Ethereum/ethereum-frax/src/entities/mint.ts create mode 100644 Ethereum/ethereum-frax/src/entities/pair-day-data.ts create mode 100644 Ethereum/ethereum-frax/src/entities/pair-hour-data.ts create mode 100644 Ethereum/ethereum-frax/src/entities/pair.ts create mode 100644 Ethereum/ethereum-frax/src/entities/swap.ts create mode 100644 Ethereum/ethereum-frax/src/entities/sync.ts create mode 100644 Ethereum/ethereum-frax/src/entities/token-day-data.ts create mode 100644 Ethereum/ethereum-frax/src/entities/token-hour-data.ts create mode 100644 Ethereum/ethereum-frax/src/entities/token.ts create mode 100644 Ethereum/ethereum-frax/src/entities/transaction.ts create mode 100644 Ethereum/ethereum-frax/src/entities/user.ts create mode 100644 Ethereum/ethereum-frax/src/index.ts create mode 100644 Ethereum/ethereum-frax/src/mappings/fraxswap-pair.ts create mode 100644 Ethereum/ethereum-frax/src/mappings/handlers/Burn.ts create mode 100644 Ethereum/ethereum-frax/src/mappings/handlers/Mint.ts create mode 100644 Ethereum/ethereum-frax/src/mappings/handlers/PairCreated.ts create mode 100644 Ethereum/ethereum-frax/src/mappings/handlers/Swap.ts create mode 100644 Ethereum/ethereum-frax/src/mappings/handlers/Sync.ts create mode 100644 Ethereum/ethereum-frax/src/mappings/handlers/Transfer.ts create mode 100644 Ethereum/ethereum-frax/src/mappings/handlers/index.ts create mode 100644 Ethereum/ethereum-frax/src/mappings/mappingHandlers.ts create mode 100644 Ethereum/ethereum-frax/src/packages/constants/index.template.ts create mode 100644 Ethereum/ethereum-frax/src/pricing.ts create mode 100644 Ethereum/ethereum-frax/tests/.bin/fraxswap.wasm create mode 100644 Ethereum/ethereum-frax/tests/.latest.json create mode 100644 Ethereum/ethereum-frax/tests/fraxswap.test.ts create mode 100644 Ethereum/ethereum-frax/tsconfig.json diff --git a/Ethereum/ethereum-frax/.eslintignore b/Ethereum/ethereum-frax/.eslintignore new file mode 100644 index 00000000..c42003a8 --- /dev/null +++ b/Ethereum/ethereum-frax/.eslintignore @@ -0,0 +1,3 @@ +build/ +generated/ +node_modules/ \ No newline at end of file diff --git a/Ethereum/ethereum-frax/.eslintrc b/Ethereum/ethereum-frax/.eslintrc new file mode 100644 index 00000000..ed30f472 --- /dev/null +++ b/Ethereum/ethereum-frax/.eslintrc @@ -0,0 +1,5 @@ +{ + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "extends": ["plugin:@typescript-eslint/recommended", "prettier", "prettier/@typescript-eslint"] +} diff --git a/Ethereum/ethereum-frax/.github/workflows/cli-deploy.yml b/Ethereum/ethereum-frax/.github/workflows/cli-deploy.yml new file mode 100644 index 00000000..5edba7c0 --- /dev/null +++ b/Ethereum/ethereum-frax/.github/workflows/cli-deploy.yml @@ -0,0 +1,33 @@ +name: "CLI deploy" + +on: + workflow_dispatch: + inputs: + projectName: + description: "Project name" + required: true + type: string +jobs: + deploy: + name: CLI Deploy + runs-on: ubuntu-latest + environment: + name: DEPLOYMENT + env: + SUBQL_ACCESS_TOKEN: ${{ secrets.SUBQL_ACCESS_TOKEN }} + steps: + - uses: actions/checkout@v2 + - name: Setup Node.js environment + uses: actions/setup-node@v2 + with: + node-version: 16 + - run: yarn + - name: Codegen + run: yarn codegen + - name: Version + run: npx subql --version + - name: repo + run: echo ${{github.repository}} + - name: Publish and Deploy + run: | + sh .github/workflows/scripts/publish-deploy.sh -o ${{github.repository}} -p ${{github.event.inputs.projectName}} diff --git a/Ethereum/ethereum-frax/.github/workflows/pr.yml b/Ethereum/ethereum-frax/.github/workflows/pr.yml new file mode 100644 index 00000000..7a3166c7 --- /dev/null +++ b/Ethereum/ethereum-frax/.github/workflows/pr.yml @@ -0,0 +1,24 @@ +name: PR +on: + pull_request: + paths-ignore: + - ".github/workflows/**" +jobs: + pr: + name: pr + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Node.js environment + uses: actions/setup-node@v2 + with: + node-version: 16 + - run: yarn + - name: Codegen + run: yarn codegen + - name: Build + run: yarn build + - name: Install subql-node-ethereum + run: yarn global add @subql/node-ethereum + - name: Run tests with Subquery Node + run: subql-node-ethereum test -f ${{ github.workspace }} diff --git a/Ethereum/ethereum-frax/.github/workflows/scripts/publish-deploy.sh b/Ethereum/ethereum-frax/.github/workflows/scripts/publish-deploy.sh new file mode 100644 index 00000000..adae267d --- /dev/null +++ b/Ethereum/ethereum-frax/.github/workflows/scripts/publish-deploy.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +while getopts p:o: flag +do + case "${flag}" in + p) PROJECTNAME=${OPTARG};; + o) ORG=${OPTARG};; + *) echo "Usage: $0 [-p projectname] [-o org]" && exit 1;; + esac +done + +IPFSCID=$(npx subql publish -o -f .) + +npx subql deployment:deploy -d --ipfsCID="$IPFSCID" --projectName="${PROJECTNAME}" --org="${ORG%/*}" \ No newline at end of file diff --git a/Ethereum/ethereum-frax/.gitignore b/Ethereum/ethereum-frax/.gitignore new file mode 100644 index 00000000..53b19635 --- /dev/null +++ b/Ethereum/ethereum-frax/.gitignore @@ -0,0 +1,60 @@ +# These are some examples of commonly ignored file patterns. +# You should customize this list as applicable to your project. +# Learn more about .gitignore: +# https://www.atlassian.com/git/tutorials/saving-changes/gitignore + +# Node artifact files +node_modules/ +dist/ + +# lock files +yarn.lock +package-lock.json + +# Compiled Java class files +*.class + +# Compiled Python bytecode +*.py[cod] + +# Log files +*.log + +# Package files +*.jar + +# Generated files +target/ +dist/ +src/types +project.yaml + +# JetBrains IDE +.idea/ + +# Unit test reports +TEST*.xml + +# Generated by MacOS +.DS_Store + +# Generated by Windows +Thumbs.db + +# Applications +*.app +*.exe +*.war + +# Large media files +*.mp4 +*.tiff +*.avi +*.flv +*.mov +*.wmv + +.data +.yarn + +.DS_Store diff --git a/Ethereum/ethereum-frax/HELPFUL_QUERIES.txt b/Ethereum/ethereum-frax/HELPFUL_QUERIES.txt new file mode 100644 index 00000000..4dcb3203 --- /dev/null +++ b/Ethereum/ethereum-frax/HELPFUL_QUERIES.txt @@ -0,0 +1,14 @@ +{ + pairs(first: 30) { + nodes { + token0 { + symbol + } + token1 { + symbol + } + token0Price + token1Price + } + } +} \ No newline at end of file diff --git a/Ethereum/ethereum-frax/abis/ERC20.json b/Ethereum/ethereum-frax/abis/ERC20.json new file mode 100644 index 00000000..46041f99 --- /dev/null +++ b/Ethereum/ethereum-frax/abis/ERC20.json @@ -0,0 +1,328 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "ERC20", + "sourceName": "contracts/ERC20/ERC20.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "__name", + "type": "string" + }, + { + "internalType": "string", + "name": "__symbol", + "type": "string" + } + ], + "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": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burnFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x60806040523480156200001157600080fd5b50604051620010f6380380620010f6833981016040819052620000349162000134565b60036200004283826200022d565b5060046200005182826200022d565b50506005805460ff1916601217905550620002f9565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200008f57600080fd5b81516001600160401b0380821115620000ac57620000ac62000067565b604051601f8301601f19908116603f01168101908282118183101715620000d757620000d762000067565b81604052838152602092508683858801011115620000f457600080fd5b600091505b83821015620001185785820183015181830184015290820190620000f9565b838211156200012a5760008385830101525b9695505050505050565b600080604083850312156200014857600080fd5b82516001600160401b03808211156200016057600080fd5b6200016e868387016200007d565b935060208501519150808211156200018557600080fd5b5062000194858286016200007d565b9150509250929050565b600181811c90821680620001b357607f821691505b602082108103620001d457634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200022857600081815260208120601f850160051c81016020861015620002035750805b601f850160051c820191505b8181101562000224578281556001016200020f565b5050505b505050565b81516001600160401b0381111562000249576200024962000067565b62000261816200025a84546200019e565b84620001da565b602080601f831160018114620002995760008415620002805750858301515b600019600386901b1c1916600185901b17855562000224565b600085815260208120601f198616915b82811015620002ca57888601518255948401946001909101908401620002a9565b5085821015620002e95787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b610ded80620003096000396000f3fe608060405234801561001057600080fd5b50600436106100df5760003560e01c806342966c681161008c57806395d89b411161006657806395d89b41146101d0578063a457c2d7146101d8578063a9059cbb146101eb578063dd62ed3e146101fe57600080fd5b806342966c681461017257806370a082311461018757806379cc6790146101bd57600080fd5b806323b872dd116100bd57806323b872dd14610137578063313ce5671461014a578063395093511461015f57600080fd5b806306fdde03146100e4578063095ea7b31461010257806318160ddd14610125575b600080fd5b6100ec610244565b6040516100f99190610ae4565b60405180910390f35b610115610110366004610b80565b6102d6565b60405190151581526020016100f9565b6002545b6040519081526020016100f9565b610115610145366004610baa565b6102ec565b60055460405160ff90911681526020016100f9565b61011561016d366004610b80565b610362565b610185610180366004610be6565b6103a5565b005b610129610195366004610bff565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6101856101cb366004610b80565b6103b2565b6100ec6103fe565b6101156101e6366004610b80565b61040d565b6101156101f9366004610b80565b610469565b61012961020c366004610c1a565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260016020908152604080832093909416825291909152205490565b60606003805461025390610c4d565b80601f016020809104026020016040519081016040528092919081815260200182805461027f90610c4d565b80156102cc5780601f106102a1576101008083540402835291602001916102cc565b820191906000526020600020905b8154815290600101906020018083116102af57829003601f168201915b5050505050905090565b60006102e3338484610476565b50600192915050565b60006102f984848461062f565b610358843361035385604051806060016040528060288152602001610d476028913973ffffffffffffffffffffffffffffffffffffffff8a1660009081526001602090815260408083203384529091529020549190610859565b610476565b5060019392505050565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915281205490916102e391859061035390866108ad565b6103af338261092d565b50565b60006103e282604051806060016040528060248152602001610d6f602491396103db863361020c565b9190610859565b90506103ef833383610476565b6103f9838361092d565b505050565b60606004805461025390610c4d565b60006102e3338461035385604051806060016040528060258152602001610d936025913933600090815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff8d1684529091529020549190610859565b60006102e333848461062f565b73ffffffffffffffffffffffffffffffffffffffff831661051d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82166105c0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f73730000000000000000000000000000000000000000000000000000000000006064820152608401610514565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b73ffffffffffffffffffffffffffffffffffffffff83166106d2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f64726573730000000000000000000000000000000000000000000000000000006064820152608401610514565b73ffffffffffffffffffffffffffffffffffffffff8216610775576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f65737300000000000000000000000000000000000000000000000000000000006064820152608401610514565b6107bf81604051806060016040528060268152602001610d216026913973ffffffffffffffffffffffffffffffffffffffff86166000908152602081905260409020549190610859565b73ffffffffffffffffffffffffffffffffffffffff80851660009081526020819052604080822093909355908416815220546107fb90826108ad565b73ffffffffffffffffffffffffffffffffffffffff8381166000818152602081815260409182902094909455518481529092918616917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9101610622565b60008184841115610897576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105149190610ae4565b5060006108a48486610ccf565b95945050505050565b6000806108ba8385610ce6565b905083811015610926576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f7700000000006044820152606401610514565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff82166109d0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f2061646472657360448201527f73000000000000000000000000000000000000000000000000000000000000006064820152608401610514565b610a1a81604051806060016040528060228152602001610cff6022913973ffffffffffffffffffffffffffffffffffffffff85166000908152602081905260409020549190610859565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260208190526040902055600254610a4d9082610aa2565b60025560405181815260009073ffffffffffffffffffffffffffffffffffffffff8416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b600061092683836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250610859565b600060208083528351808285015260005b81811015610b1157858101830151858201604001528201610af5565b81811115610b23576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610b7b57600080fd5b919050565b60008060408385031215610b9357600080fd5b610b9c83610b57565b946020939093013593505050565b600080600060608486031215610bbf57600080fd5b610bc884610b57565b9250610bd660208501610b57565b9150604084013590509250925092565b600060208284031215610bf857600080fd5b5035919050565b600060208284031215610c1157600080fd5b61092682610b57565b60008060408385031215610c2d57600080fd5b610c3683610b57565b9150610c4460208401610b57565b90509250929050565b600181811c90821680610c6157607f821691505b602082108103610c9a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082821015610ce157610ce1610ca0565b500390565b60008219821115610cf957610cf9610ca0565b50019056fe45524332303a206275726e20616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a206275726e20616d6f756e74206578636565647320616c6c6f77616e636545524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa2646970667358221220f2883e2d1c49942a6debf6b486fa5520d95e90e3d5160a8896c8f0be8d328add64736f6c634300080f0033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100df5760003560e01c806342966c681161008c57806395d89b411161006657806395d89b41146101d0578063a457c2d7146101d8578063a9059cbb146101eb578063dd62ed3e146101fe57600080fd5b806342966c681461017257806370a082311461018757806379cc6790146101bd57600080fd5b806323b872dd116100bd57806323b872dd14610137578063313ce5671461014a578063395093511461015f57600080fd5b806306fdde03146100e4578063095ea7b31461010257806318160ddd14610125575b600080fd5b6100ec610244565b6040516100f99190610ae4565b60405180910390f35b610115610110366004610b80565b6102d6565b60405190151581526020016100f9565b6002545b6040519081526020016100f9565b610115610145366004610baa565b6102ec565b60055460405160ff90911681526020016100f9565b61011561016d366004610b80565b610362565b610185610180366004610be6565b6103a5565b005b610129610195366004610bff565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6101856101cb366004610b80565b6103b2565b6100ec6103fe565b6101156101e6366004610b80565b61040d565b6101156101f9366004610b80565b610469565b61012961020c366004610c1a565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260016020908152604080832093909416825291909152205490565b60606003805461025390610c4d565b80601f016020809104026020016040519081016040528092919081815260200182805461027f90610c4d565b80156102cc5780601f106102a1576101008083540402835291602001916102cc565b820191906000526020600020905b8154815290600101906020018083116102af57829003601f168201915b5050505050905090565b60006102e3338484610476565b50600192915050565b60006102f984848461062f565b610358843361035385604051806060016040528060288152602001610d476028913973ffffffffffffffffffffffffffffffffffffffff8a1660009081526001602090815260408083203384529091529020549190610859565b610476565b5060019392505050565b33600081815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716845290915281205490916102e391859061035390866108ad565b6103af338261092d565b50565b60006103e282604051806060016040528060248152602001610d6f602491396103db863361020c565b9190610859565b90506103ef833383610476565b6103f9838361092d565b505050565b60606004805461025390610c4d565b60006102e3338461035385604051806060016040528060258152602001610d936025913933600090815260016020908152604080832073ffffffffffffffffffffffffffffffffffffffff8d1684529091529020549190610859565b60006102e333848461062f565b73ffffffffffffffffffffffffffffffffffffffff831661051d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82166105c0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f73730000000000000000000000000000000000000000000000000000000000006064820152608401610514565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b73ffffffffffffffffffffffffffffffffffffffff83166106d2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f64726573730000000000000000000000000000000000000000000000000000006064820152608401610514565b73ffffffffffffffffffffffffffffffffffffffff8216610775576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f65737300000000000000000000000000000000000000000000000000000000006064820152608401610514565b6107bf81604051806060016040528060268152602001610d216026913973ffffffffffffffffffffffffffffffffffffffff86166000908152602081905260409020549190610859565b73ffffffffffffffffffffffffffffffffffffffff80851660009081526020819052604080822093909355908416815220546107fb90826108ad565b73ffffffffffffffffffffffffffffffffffffffff8381166000818152602081815260409182902094909455518481529092918616917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9101610622565b60008184841115610897576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105149190610ae4565b5060006108a48486610ccf565b95945050505050565b6000806108ba8385610ce6565b905083811015610926576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f7700000000006044820152606401610514565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff82166109d0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f2061646472657360448201527f73000000000000000000000000000000000000000000000000000000000000006064820152608401610514565b610a1a81604051806060016040528060228152602001610cff6022913973ffffffffffffffffffffffffffffffffffffffff85166000908152602081905260409020549190610859565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260208190526040902055600254610a4d9082610aa2565b60025560405181815260009073ffffffffffffffffffffffffffffffffffffffff8416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b600061092683836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250610859565b600060208083528351808285015260005b81811015610b1157858101830151858201604001528201610af5565b81811115610b23576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610b7b57600080fd5b919050565b60008060408385031215610b9357600080fd5b610b9c83610b57565b946020939093013593505050565b600080600060608486031215610bbf57600080fd5b610bc884610b57565b9250610bd660208501610b57565b9150604084013590509250925092565b600060208284031215610bf857600080fd5b5035919050565b600060208284031215610c1157600080fd5b61092682610b57565b60008060408385031215610c2d57600080fd5b610c3683610b57565b9150610c4460208401610b57565b90509250929050565b600181811c90821680610c6157607f821691505b602082108103610c9a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600082821015610ce157610ce1610ca0565b500390565b60008219821115610cf957610cf9610ca0565b50019056fe45524332303a206275726e20616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a206275726e20616d6f756e74206578636565647320616c6c6f77616e636545524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa2646970667358221220f2883e2d1c49942a6debf6b486fa5520d95e90e3d5160a8896c8f0be8d328add64736f6c634300080f0033", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/Ethereum/ethereum-frax/abis/ERC20NameBytes.json b/Ethereum/ethereum-frax/abis/ERC20NameBytes.json new file mode 100644 index 00000000..b9455e4b --- /dev/null +++ b/Ethereum/ethereum-frax/abis/ERC20NameBytes.json @@ -0,0 +1,17 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/Ethereum/ethereum-frax/abis/ERC20SymbolBytes.json b/Ethereum/ethereum-frax/abis/ERC20SymbolBytes.json new file mode 100644 index 00000000..8c329cac --- /dev/null +++ b/Ethereum/ethereum-frax/abis/ERC20SymbolBytes.json @@ -0,0 +1,17 @@ +[ + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/Ethereum/ethereum-frax/abis/FraxswapFactory.json b/Ethereum/ethereum-frax/abis/FraxswapFactory.json new file mode 100644 index 00000000..f05cd7a2 --- /dev/null +++ b/Ethereum/ethereum-frax/abis/FraxswapFactory.json @@ -0,0 +1,108 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_feeToSetter", "type": "address" } + ], + "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" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "allPairs", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "allPairsLength", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "tokenA", "type": "address" }, + { "internalType": "address", "name": "tokenB", "type": "address" } + ], + "name": "createPair", + "outputs": [ + { "internalType": "address", "name": "pair", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "feeTo", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeToSetter", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "getPair", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_feeTo", "type": "address" } + ], + "name": "setFeeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_feeToSetter", "type": "address" } + ], + "name": "setFeeToSetter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/Ethereum/ethereum-frax/abis/FraxswapPair.json b/Ethereum/ethereum-frax/abis/FraxswapPair.json new file mode 100644 index 00000000..60ce1f45 --- /dev/null +++ b/Ethereum/ethereum-frax/abis/FraxswapPair.json @@ -0,0 +1,984 @@ +[ + { "inputs": [], "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": "addr", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "sellToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "unsoldAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "buyToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "purchasedAmount", + "type": "uint256" + } + ], + "name": "CancelLongTermOrder", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "numberOfTimeIntervals", + "type": "uint256" + } + ], + "name": "LongTermSwap0To1", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "numberOfTimeIntervals", + "type": "uint256" + } + ], + "name": "LongTermSwap1To0", + "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" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "blocktimestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newReserve0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newReserve1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTwammReserve0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTwammReserve1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Bought", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Bought", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Sold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Sold", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expiries", + "type": "uint256" + } + ], + "name": "VirtualOrderExecution", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "proceedToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "proceeds", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "orderExpired", + "type": "bool" + } + ], + "name": "WithdrawProceedsFromLongTermOrder", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MINIMUM_LIQUIDITY", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "TWAPObservationHistory", + "outputs": [ + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { + "internalType": "uint256", + "name": "price0CumulativeLast", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "price1CumulativeLast", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "to", "type": "address" }], + "name": "burn", + "outputs": [ + { "internalType": "uint256", "name": "amount0", "type": "uint256" }, + { "internalType": "uint256", "name": "amount1", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "orderId", "type": "uint256" } + ], + "name": "cancelLongTermSwap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "blockTimestamp", "type": "uint256" } + ], + "name": "executeVirtualOrders", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" }, + { "internalType": "uint256", "name": "offset", "type": "uint256" }, + { "internalType": "uint256", "name": "limit", "type": "uint256" } + ], + "name": "getDetailedOrdersForUser", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { + "internalType": "uint256", + "name": "expirationTimestamp", + "type": "uint256" + }, + { "internalType": "uint256", "name": "saleRate", "type": "uint256" }, + { "internalType": "address", "name": "owner", "type": "address" }, + { + "internalType": "address", + "name": "sellTokenAddr", + "type": "address" + }, + { + "internalType": "address", + "name": "buyTokenAddr", + "type": "address" + }, + { "internalType": "bool", "name": "isComplete", "type": "bool" } + ], + "internalType": "struct LongTermOrdersLib.Order[]", + "name": "detailed_orders", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNextOrderID", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "getOrderIDsForUser", + "outputs": [ + { "internalType": "uint256[]", "name": "", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "getOrderIDsForUserLength", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "blockTimestamp", "type": "uint256" } + ], + "name": "getReserveAfterTwamm", + "outputs": [ + { "internalType": "uint112", "name": "_reserve0", "type": "uint112" }, + { "internalType": "uint112", "name": "_reserve1", "type": "uint112" }, + { + "internalType": "uint256", + "name": "lastVirtualOrderTimestamp", + "type": "uint256" + }, + { + "internalType": "uint112", + "name": "_twammReserve0", + "type": "uint112" + }, + { "internalType": "uint112", "name": "_twammReserve1", "type": "uint112" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getReserves", + "outputs": [ + { "internalType": "uint112", "name": "_reserve0", "type": "uint112" }, + { "internalType": "uint112", "name": "_reserve1", "type": "uint112" }, + { + "internalType": "uint32", + "name": "_blockTimestampLast", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTWAPHistoryLength", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "orderId", "type": "uint256" } + ], + "name": "getTwammOrder", + "outputs": [ + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { + "internalType": "uint256", + "name": "expirationTimestamp", + "type": "uint256" + }, + { "internalType": "uint256", "name": "saleRate", "type": "uint256" }, + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "sellTokenAddr", "type": "address" }, + { "internalType": "address", "name": "buyTokenAddr", "type": "address" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "orderId", "type": "uint256" } + ], + "name": "getTwammOrderProceeds", + "outputs": [ + { "internalType": "bool", "name": "orderExpired", "type": "bool" }, + { "internalType": "uint256", "name": "totalReward", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "orderId", "type": "uint256" }, + { "internalType": "uint256", "name": "blockTimestamp", "type": "uint256" } + ], + "name": "getTwammOrderProceedsView", + "outputs": [ + { "internalType": "bool", "name": "orderExpired", "type": "bool" }, + { "internalType": "uint256", "name": "totalReward", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTwammReserves", + "outputs": [ + { "internalType": "uint112", "name": "_reserve0", "type": "uint112" }, + { "internalType": "uint112", "name": "_reserve1", "type": "uint112" }, + { + "internalType": "uint32", + "name": "_blockTimestampLast", + "type": "uint32" + }, + { + "internalType": "uint112", + "name": "_twammReserve0", + "type": "uint112" + }, + { "internalType": "uint112", "name": "_twammReserve1", "type": "uint112" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_blockTimestamp", + "type": "uint256" + } + ], + "name": "getTwammRewardFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "rewardFactorPool0AtTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rewardFactorPool1AtTimestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_blockTimestamp", + "type": "uint256" + } + ], + "name": "getTwammSalesRateEnding", + "outputs": [ + { + "internalType": "uint256", + "name": "orderPool0SalesRateEnding", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "orderPool1SalesRateEnding", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTwammState", + "outputs": [ + { "internalType": "uint256", "name": "token0Rate", "type": "uint256" }, + { "internalType": "uint256", "name": "token1Rate", "type": "uint256" }, + { + "internalType": "uint256", + "name": "lastVirtualOrderTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "orderTimeInterval_rtn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rewardFactorPool0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rewardFactorPool1", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_token0", "type": "address" }, + { "internalType": "address", "name": "_token1", "type": "address" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "kLast", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amount0In", "type": "uint256" }, + { + "internalType": "uint256", + "name": "numberOfTimeIntervals", + "type": "uint256" + } + ], + "name": "longTermSwapFrom0To1", + "outputs": [ + { "internalType": "uint256", "name": "orderId", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amount1In", "type": "uint256" }, + { + "internalType": "uint256", + "name": "numberOfTimeIntervals", + "type": "uint256" + } + ], + "name": "longTermSwapFrom1To0", + "outputs": [ + { "internalType": "uint256", "name": "orderId", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "to", "type": "address" }], + "name": "mint", + "outputs": [ + { "internalType": "uint256", "name": "liquidity", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "newSwapsPaused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "nonces", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "name": "orderIDsForUser", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "orderTimeInterval", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "price0CumulativeLast", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "price1CumulativeLast", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "to", "type": "address" }], + "name": "skim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "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": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sync", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "togglePauseNewSwaps", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "token0", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token1", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "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" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "twammReserve0", + "outputs": [{ "internalType": "uint112", "name": "", "type": "uint112" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "twammReserve1", + "outputs": [{ "internalType": "uint112", "name": "", "type": "uint112" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "twammUpToDate", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "orderId", "type": "uint256" } + ], + "name": "withdrawProceedsFromLongTermSwap", + "outputs": [ + { "internalType": "bool", "name": "is_expired", "type": "bool" }, + { "internalType": "address", "name": "rewardTkn", "type": "address" }, + { "internalType": "uint256", "name": "totalReward", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/Ethereum/ethereum-frax/config/mainnet.json b/Ethereum/ethereum-frax/config/mainnet.json new file mode 100644 index 00000000..54b1b422 --- /dev/null +++ b/Ethereum/ethereum-frax/config/mainnet.json @@ -0,0 +1,30 @@ +{ + "network": "mainnet", + "factory_address": "0xB076b06F669e682609fb4a8C6646D2619717Be4b", + "frax_address": "0x853d955aCEf822Db058eb8505911ED77F175b99e", + "fxs_address": "0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0", + "fxs_start_price": "8380000000000000000", + "frax_fxs_pair_address": "0x8206412c107eF1aDb70B9277974f5163760E128E", + "frax_fxs_pair": "0x8206412c107eF1aDb70B9277974f5163760E128E", + "native_address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "weth_address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "native_start_price": "14776073", + "fraxswap_frax_weth_pair_address": "0x8300f0528e00Ad33b218bb05D396F61A9FDd68Cd", + "fraxswap_frax_weth_pair": "0x8300f0528e00Ad33b218bb05D396F61A9FDd68Cd", + "fraxswap_frax_weth_pair_deploy_block": "14776073", + "minimum_usd_threshold_new_pairs": "3000", + "minimum_liquidity_threshold_eth": "3", + "factory": { + "address": "0xB076b06F669e682609fb4a8C6646D2619717Be4b", + "startBlock": "14775229" + }, + "whitelist": [ + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "0x853d955aCEf822Db058eb8505911ED77F175b99e", + "0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0", + "0x5Ca135cB8527d76e932f34B5145575F9d8cbE08E", + "0xc2544A32872A91F4A553b404C6950e89De901fdb", + "0x11EBe21e9d7BF541A18e1E3aC94939018Ce88F0b", + "0x579CEa1889991f68aCc35Ff5c3dd0621fF29b0C9" + ] +} diff --git a/Ethereum/ethereum-frax/docker-compose.yml b/Ethereum/ethereum-frax/docker-compose.yml new file mode 100644 index 00000000..e0e722f9 --- /dev/null +++ b/Ethereum/ethereum-frax/docker-compose.yml @@ -0,0 +1,69 @@ +version: "3" + +services: + postgres: + build: + context: . + dockerfile: ./docker/pg-Dockerfile + ports: + - 5432:5432 + volumes: + - .data/postgres:/var/lib/postgresql/data + environment: + POSTGRES_PASSWORD: postgres + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + + subquery-node: + image: subquerynetwork/subql-node-ethereum:latest + depends_on: + "postgres": + condition: service_healthy + restart: unless-stopped + environment: + DB_USER: postgres + DB_PASS: postgres + DB_DATABASE: postgres + DB_HOST: postgres + DB_PORT: 5432 + volumes: + - ./:/app + command: + - ${SUB_COMMAND:-} # set SUB_COMMAND env variable to "test" to run tests + - -f=/app +# - --debug=* +# - --log-level=debug + - --db-schema=app + - --workers=1 + - --batch-size=30 + - --unfinalized-blocks=true + + healthcheck: + test: ["CMD", "curl", "-f", "http://subquery-node:3000/ready"] + interval: 3s + timeout: 5s + retries: 10 + + graphql-engine: + image: subquerynetwork/subql-query:latest + ports: + - 3000:3000 + depends_on: + "postgres": + condition: service_healthy + "subquery-node": + condition: service_healthy + restart: always + environment: + DB_USER: postgres + DB_PASS: postgres + DB_DATABASE: postgres + DB_HOST: postgres + DB_PORT: 5432 + command: + - --name=app + - --playground + - --indexer=http://subquery-node:3000 diff --git a/Ethereum/ethereum-frax/docker/load-extensions.sh b/Ethereum/ethereum-frax/docker/load-extensions.sh new file mode 100644 index 00000000..7f5d0206 --- /dev/null +++ b/Ethereum/ethereum-frax/docker/load-extensions.sh @@ -0,0 +1,6 @@ + +#!/bin/sh + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <=3.0.0", + }, + query: { + name: "@subql/query", + version: "*", + }, + }, + schema: { + file: "./schema.graphql", + }, + network: { + chainId: "1", + /** + * These endpoint(s) should be public non-pruned archive node + * We recommend providing more than one endpoint for improved reliability, performance, and uptime + * Public nodes may be rate limited, which can affect indexing speed + * When developing your project we suggest getting a private API key + * If you use a rate limited endpoint, adjust the --batch-size and --workers parameters + * These settings can be found in your docker-compose.yaml, they will slow indexing but prevent your project being rate limited + */ + endpoint: ["https://eth.api.onfinality.io/public"], + }, + dataSources: [ + { + kind: EthereumDatasourceKind.Runtime, + startBlock: 14775229, + options: { + abi: "FraxswapFactory", + address: "0xB076b06F669e682609fb4a8C6646D2619717Be4b", + }, + assets: new Map([ + ["FraxswapFactory", { file: "./abis/FraxswapFactory.json" }], + ["FraxswapPair", { file: "./abis/FraxswapPair.json" }], + ["ERC20", { file: "./abis/ERC20.json" }], + ["ERC20SymbolBytes", { file: "./abis/ERC20SymbolBytes.json" }], + ["ERC20NameBytes", { file: "./abis/ERC20NameBytes.json" }], + ]), + mapping: { + file: "./dist/index.js", + handlers: [ + { + kind: EthereumHandlerKind.Event, + handler: "onPairCreated", + filter: { + topics: [ + "PairCreated(indexed address,indexed address,address,uint256)" + ] + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "onMint", + filter: { + topics: [ + "Mint(indexed address,uint256,uint256)" + ] + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "onBurn", + filter: { + topics: [ + "Burn(indexed address,uint256,uint256,indexed address)" + ] + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "onSwap", + filter: { + topics: [ + "Swap(indexed address,uint256,uint256,uint256,uint256,indexed address)" + ] + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "onTransfer", + filter: { + topics: [ + "Transfer(indexed address,indexed address,uint256)" + ] + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "onSync", + filter: { + topics: [ + "Sync(uint112,uint112)" + ] + }, + }, + + ], + }, + }, + ], + repository: "https://github.com/subquery/ethereum-subql-starter", +}; + +// Must set default to the project instance +export default project; diff --git a/Ethereum/ethereum-frax/schema.graphql b/Ethereum/ethereum-frax/schema.graphql new file mode 100644 index 00000000..490e271b --- /dev/null +++ b/Ethereum/ethereum-frax/schema.graphql @@ -0,0 +1,543 @@ +# User +type User @entity { + # Address + id: ID! + + # Liquidity Positions + liquidityPositions: [LiquidityPosition!]! @derivedFrom(field: "user") +} + +# Bundle +type Bundle @entity { + id: ID! + ethPrice: Float! # price of ETH usd +} + +# Factory +type Factory @entity { + # Contract address + id: ID! + + # Pair count + pairCount: BigInt! + + # Volume USD + volumeUSD: Float! + + # Volume ETH + volumeETH: Float! + + # Untracked volume + untrackedVolumeUSD: Float! + + # Liquidity USD + liquidityUSD: Float! + + # Liquidity ETH + liquidityETH: Float! + + # Transaction count + txCount: BigInt! + + # Token count + tokenCount: BigInt! + + # User count + userCount: BigInt! + + # Pairs + pairs: [Pair!]! @derivedFrom(field: "factory") + + # Tokens + tokens: [Token!]! @derivedFrom(field: "factory") + + # Hour data + hourData: [HourData!]! @derivedFrom(field: "factory") + + # Day data + dayData: [DayData!]! @derivedFrom(field: "factory") +} + +# Hour Data +type HourData @entity { + # start of hour timestamp + id: ID! + + # date + date: Int! + + # factory + factory: Factory! + + # volume + volumeETH: Float! + volumeUSD: Float! + untrackedVolume: Float! + + # liquidity + liquidityETH: Float! + liquidityUSD: Float! + + # tx count + txCount: BigInt! +} + +# Day Data +type DayData @entity { + # timestamp / 86400 + id: ID! + + # date + date: Int! + + # factory + factory: Factory! + + # volume + volumeETH: Float! + volumeUSD: Float! + untrackedVolume: Float! + + # liquidity + liquidityETH: Float! + liquidityUSD: Float! + + # tx count + txCount: BigInt! +} + +# Token +type Token @entity { + # token address + id: ID! + + # factory + factory: Factory! + + # mirrored from the smart contract + symbol: String! + name: String! + decimals: BigInt! + + # used for other stats like marketcap + totalSupply: BigInt! + + # token specific volume + volume: Float! + volumeUSD: Float! + untrackedVolumeUSD: Float! + + # transactions across all pairs + txCount: BigInt! + + # liquidity across all pairs + liquidity: Float! + + derivedETH: Float! + +# whitelistPairs: [Pair!]! @derivedFrom(field: "token0") + # Token hour data + hourData: [TokenHourData!]! @derivedFrom(field: "token") + + # Token day data + dayData: [TokenDayData!]! @derivedFrom(field: "token") + + # Base pairs + basePairs: [Pair!]! @derivedFrom(field: "token0") + + # Quote pairs + quotePairs: [Pair!]! @derivedFrom(field: "token1") + + # Base pairs day data + basePairsDayData: [PairDayData!]! @derivedFrom(field: "token0") + + # Quote pairs day data + quotePairsDayData: [PairDayData!]! @derivedFrom(field: "token1") +} + +# Token hour data +type TokenHourData @entity { + # token id - hour start timestamp + id: ID! + + # date - hour start timestamp + date: Int! + + # token + token: Token! + + # volume + volume: Float! + volumeETH: Float! + volumeUSD: Float! + + # tx count + txCount: BigInt! + + # liquidity + liquidity: Float! + liquidityETH: Float! + liquidityUSD: Float! + + # price usd + priceUSD: Float! +} + +# Token day data +type TokenDayData @entity { + # token id - day start timestamp + id: ID! + + # date - day start timestamp + date: Int! + + # token + token: Token! + + # volume + volume: Float! + volumeETH: Float! + volumeUSD: Float! + + # tx count + txCount: BigInt! + + # liquidity + liquidity: Float! + liquidityETH: Float! + liquidityUSD: Float! + + # price usd + priceUSD: Float! +} + +# Pair +type Pair @entity { + # Contract address + id: ID! + + # Factory + factory: Factory! + + # Name + name: String! + + # mirrored from the smart contract + token0: Token! + token1: Token! + + reserve0: Float! + reserve1: Float! + twammReserve0: Float! + twammReserve1: Float! + + totalSupply: Float! + + # derived liquidity + reserveETH: Float! + reserveUSD: Float! + + # used for separating per pair reserves and global + trackedReserveETH: Float! + + # Price in terms of the asset pair + token0Price: Float! + token1Price: Float! + + # lifetime volume stats + volumeToken0: Float! + volumeToken1: Float! + volumeUSD: Float! + untrackedVolumeUSD: Float! + txCount: BigInt! + + # Fields used to help derived relationship + # used to detect new exchanges + liquidityProviderCount: BigInt! + + # lca: Float! + # lcad: Float! + + # Liquidity positions + liquidityPositions: [LiquidityPosition!]! @derivedFrom(field: "pair") + + # Liquidity position snapshots + liquidityPositionSnapshots: [LiquidityPositionSnapshot!]! @derivedFrom(field: "pair") + + # Pair day data + dayData: [PairDayData!]! @derivedFrom(field: "pair") + + # Pair hour data + hourData: [PairHourData!]! @derivedFrom(field: "pair") + + # Transactions + mints: [Mint!]! @derivedFrom(field: "pair") + burns: [Burn!]! @derivedFrom(field: "pair") + swaps: [Swap!]! @derivedFrom(field: "pair") + + # Created at + timestamp: BigInt! + block: BigInt! +} + +# Pair hour data +type PairHourData @entity { + # pair.id - hour start timestamp + id: ID! + + # date - hour start timestamp + date: Int! + + # pair + pair: Pair! + + # reserves + reserve0: Float! + reserve1: Float! + twammReserve0: Float! + twammReserve1: Float! + + # derived liquidity + reserveUSD: Float! + + # volume + volumeToken0: Float! + volumeToken1: Float! + + # volume usd + volumeUSD: Float! + + # tx count + txCount: BigInt! +} + +# Pair day data +type PairDayData @entity { + # pair id - day start timestamp + id: ID! + + # date - day start timestamp + date: Int! + + # pair + pair: Pair! + + # token0 + token0: Token! + + # token1 + token1: Token! + + # reserves + reserve0: Float! + reserve1: Float! + twammReserve0: Float! + twammReserve1: Float! + + # total supply for LP historical returns + totalSupply: Float! + + # derived liquidity + reserveUSD: Float! + + # volume + volumeToken0: Float! + volumeToken1: Float! + + # volume usd + volumeUSD: Float! + + # tx count + txCount: BigInt! +} + +# liquidity position +type LiquidityPosition @entity { + id: ID! + user: User! + pair: Pair! + liquidityTokenBalance: Float! + snapshots: [LiquidityPositionSnapshot!] @derivedFrom(field: "liquidityPosition") + block: Int! + timestamp: Int! +} + +# saved over time for return calculations, gets created and never updated +type LiquidityPositionSnapshot @entity { + id: ID! + liquidityPosition: LiquidityPosition! + timestamp: Int! # saved for fast historical lookups + block: Int! # saved for fast historical lookups + user: User! # reference to user + pair: Pair! # reference to pair + token0PriceUSD: Float! # snapshot of token0 price + token1PriceUSD: Float! # snapshot of token1 price + reserve0: Float! # snapshot of pair token0 reserves + reserve1: Float! # snapshot of pair token1 reserves + twammReserve0: Float! # snapshot of pair token0 TWAMM reserves + twammReserve1: Float! # snapshot of pair token1 TWAMM reserves + reserveUSD: Float! # snapshot of pair reserves in USD + liquidityTokenTotalSupply: Float! # snapshot of pool token supply + # snapshot of users pool token balance + liquidityTokenBalance: Float! +} + +# transaction +type Transaction @entity { + # transaction hash + id: ID! + blockNumber: BigInt! + timestamp: BigInt! + # This is not the reverse of Mint.transaction; it is only used to + # track incomplete mints (similar for burns and swaps) + mints: [Mint]! @derivedFrom(field: "transaction") + burns: [Burn]! @derivedFrom(field: "transaction") + swaps: [Swap]! @derivedFrom(field: "transaction") +# mints: [Mint] +# burns: [Burn] +# swaps: [Swap] +} + +# mint +type Mint @entity { + # transaction hash - index of mint in transaction mints array + id: ID! + transaction: Transaction! + timestamp: BigInt! # need this to pull recent txns for specific token or pair + pair: Pair! + + # populated from the primary Transfer event + to: String! + liquidity: Float! + + # populated from the Mint event + sender: String + amount0: Float + amount1: Float + logIndex: BigInt + # derived amount based on available prices of tokens + amountUSD: Float + + # optional fee fields, if a Transfer event is fired in _mintFee + feeTo: String + feeLiquidity: Float +} + +# burn +type Burn @entity { + # transaction hash - index of burn in transaction burns array + id: ID! + transaction: Transaction! + timestamp: BigInt! # need this to pull recent txns for specific token or pair + pair: Pair! + + # populated from the primary Transfer event + liquidity: Float! + + # populated from the Burn event + sender: String + amount0: Float + amount1: Float + to: String + logIndex: BigInt + # derived amount based on available prices of tokens + amountUSD: Float + + # mark uncomplete in ETH case + complete: Boolean! + + # optional fee fields, if a Transfer event is fired in _mintFee + feeTo: String + feeLiquidity: Float +} + +# swap +type Swap @entity { + # transaction hash - index of swap in transaction swaps array + id: ID! + transaction: Transaction! + timestamp: BigInt! # need this to pull recent txns for specific token or pair + pair: Pair! + + # populated from the Swap event + sender: String! + amount0In: Float! + amount1In: Float! + amount0Out: Float! + amount1Out: Float! + to: String! + logIndex: BigInt + + # derived info + amountUSD: Float! +}type Approval @entity { + id: ID! + owner: String! # address + spender: String! # address + value: BigInt! # uint256 +} + + +type CancelLongTermOrder @entity { + id: ID! + addr: String! # address + orderId: BigInt! # uint256 + sellToken: String! # address + unsoldAmount: BigInt! # uint256 + buyToken: String! # address + purchasedAmount: BigInt! # uint256 +} + +type LongTermSwap0To1 @entity { + id: ID! + addr: String! # address + orderId: BigInt! # uint256 + amount0In: BigInt! # uint256 + numberOfTimeIntervals: BigInt! # uint256 +} + +type LongTermSwap1To0 @entity { + id: ID! + addr: String! # address + orderId: BigInt! # uint256 + amount1In: BigInt! # uint256 + numberOfTimeIntervals: BigInt! # uint256 +} + +type Sync @entity { + id: ID! + reserve0: BigInt! # uint112 + reserve1: BigInt! # uint112 + twammReserve0: Float! + twammReserve1: Float! +} + +type Transfer @entity { + id: ID! + from: String! # address + to: String! # address + value: BigInt! # uint256 +} + +type VirtualOrderExecution @entity { + id: ID! + blocktimestamp: BigInt! # uint256 + newReserve0: BigInt! # uint256 + newReserve1: BigInt! # uint256 + newTwammReserve0: BigInt! # uint256 + newTwammReserve1: BigInt! # uint256 + token0Bought: BigInt! # uint256 + token1Bought: BigInt! # uint256 + token0Sold: BigInt! # uint256 + token1Sold: BigInt! # uint256 + expiries: BigInt! # uint256 +} + +type WithdrawProceedsFromLongTermOrder @entity { + id: ID! + addr: String! # address + orderId: BigInt! # uint256 + proceedToken: String! # address + proceeds: BigInt! # uint256 + orderExpired: Boolean! # bool +} diff --git a/Ethereum/ethereum-frax/src/entities/bundle.ts b/Ethereum/ethereum-frax/src/entities/bundle.ts new file mode 100644 index 00000000..66c4d90b --- /dev/null +++ b/Ethereum/ethereum-frax/src/entities/bundle.ts @@ -0,0 +1,14 @@ +import { Bundle } from '../types' +import {BigNumber} from "ethers"; +export async function getBundle(): Promise { + let bundle = await Bundle.get('1'); + + if (bundle === null) { + bundle = Bundle.create({ + ethPrice: 0, id: "1" + }) + await bundle.save() + } + + return bundle as Bundle +} diff --git a/Ethereum/ethereum-frax/src/entities/burn.ts b/Ethereum/ethereum-frax/src/entities/burn.ts new file mode 100644 index 00000000..e69de29b diff --git a/Ethereum/ethereum-frax/src/entities/factory.ts b/Ethereum/ethereum-frax/src/entities/factory.ts new file mode 100644 index 00000000..461eae05 --- /dev/null +++ b/Ethereum/ethereum-frax/src/entities/factory.ts @@ -0,0 +1,71 @@ +import { DayData, Factory } from '../types' +import {FACTORY_ADDRESS, BIG_DECIMAL_ZERO, BIG_INT_ZERO} from "../packages/constants/index.template"; +import { EthereumLog } from "@subql/types-ethereum"; +import {BigNumber} from "ethers"; +export async function getFactory(id: String = FACTORY_ADDRESS): Promise { + let factory = await Factory.get(id.toString()) + + if (!factory) { + factory = Factory.create({ + id: "", + liquidityETH: 0, + liquidityUSD: 0, + pairCount: 0n, + tokenCount: 0n, + txCount: 0n, + untrackedVolumeUSD: 0, + userCount: 0n, + volumeETH: 0, + volumeUSD: 0 + }); + await factory.save() + } + + return factory as Factory +} + +export async function getDayData(event: EthereumLog): Promise { + const id = BigNumber.from(event.block.timestamp).div('86400'); + + let dayData = await DayData.get(id.toString()) + + if (!dayData) { + const factory = await getFactory(); + dayData = DayData.create({ + date: 0, + factoryId: "", + id: id.toString(), + liquidityETH: 0, + liquidityUSD: 0, + txCount: 0n, + untrackedVolume: 0, + volumeETH: 0, + volumeUSD: 0 + + }) + dayData.factoryId = factory.id + dayData.date = BigNumber.from(id).mul(86400).toNumber(); + dayData.volumeUSD = BIG_DECIMAL_ZERO.toNumber(); + dayData.volumeETH = BIG_DECIMAL_ZERO.toNumber(); + dayData.untrackedVolume = BIG_DECIMAL_ZERO.toNumber(); + dayData.liquidityUSD = factory.liquidityUSD + dayData.liquidityETH = factory.liquidityETH + dayData.txCount = factory.txCount + } + + return dayData as DayData +} + +export async function updateDayData(event: EthereumLog): Promise { + const factory = await getFactory() + + const dayData = await getDayData(event) + + dayData.liquidityUSD = factory.liquidityUSD + dayData.liquidityETH = factory.liquidityETH + dayData.txCount = factory.txCount + + await dayData.save() + + return dayData as DayData +} diff --git a/Ethereum/ethereum-frax/src/entities/index.ts b/Ethereum/ethereum-frax/src/entities/index.ts new file mode 100644 index 00000000..10c43d55 --- /dev/null +++ b/Ethereum/ethereum-frax/src/entities/index.ts @@ -0,0 +1,50 @@ +// Bar Position +// export * from './bar-position' + +// Bundle +export * from './bundle' + +// Burn +// export * from './burn' + +// Factory +export * from './factory' + +// Liquidity Positoin Snapshot +export * from './liquidity-position-snapshot' + +// Liquidity Positoin +export * from './liquidity-position' + +// Mint +// export * from './mint' + +// Pair Day Data +export * from './pair-day-data' + +// Pair Hour Data +export * from './pair-hour-data' + +// Pair +export * from './pair' + +// Swap +// export * from './swap' + +// Sync +// export * from './sync' + +// Token Day Data +export * from './token-day-data' + +// Token Hour Data +// export * from './token-hour-data' + +// Token +export * from './token' + +// Transaction +// export * from './transaction' + +// User +export * from './user' diff --git a/Ethereum/ethereum-frax/src/entities/liquidity-position-snapshot.ts b/Ethereum/ethereum-frax/src/entities/liquidity-position-snapshot.ts new file mode 100644 index 00000000..ebedf4ef --- /dev/null +++ b/Ethereum/ethereum-frax/src/entities/liquidity-position-snapshot.ts @@ -0,0 +1,53 @@ +import { LiquidityPosition, LiquidityPositionSnapshot, Pair, Token } from '../types' +import { getBundle, getPair, getToken } from '.' +import {BigNumber} from "ethers"; +// import { EthereumLog } from "@subql/types-ethereum"; +import {EthereumBlock} from "@subql/types-ethereum/dist/ethereum/interfaces"; + +export async function createLiquidityPositionSnapshot(position: LiquidityPosition, block: EthereumBlock): Promise { + const timestamp = BigNumber.from(block.timestamp).toNumber(); + + const id = position.id.concat('-').concat(timestamp.toString()) + + const bundle = await getBundle() + + + let pair = await getPair(position.pairId, block) + if (!pair) throw "Pair is null"; + + const token0 = await getToken(pair.token0Id) + if (!token0) throw "token0 is null"; + + const token1 = await getToken(pair.token1Id) + if (!token1) throw "token1 is null"; + + const snapshot = LiquidityPositionSnapshot.create({ + block: block.number, + id: id, + liquidityPositionId: position.id, + liquidityTokenBalance: 0, + liquidityTokenTotalSupply: 0, + pairId: position.pairId, + reserve0: 0, + reserve1: 0, + reserveUSD: 0, + timestamp: timestamp, + token0PriceUSD: 0, + token1PriceUSD: 0, + twammReserve0: 0, + twammReserve1: 0, + userId: position.userId + }); + + snapshot.token0PriceUSD = BigNumber.from(token0.derivedETH).mul(bundle.ethPrice).toNumber(); + snapshot.token1PriceUSD = BigNumber.from(token1.derivedETH).mul(bundle.ethPrice).toNumber(); + snapshot.reserve0 = pair.reserve0 + snapshot.reserve1 = pair.reserve1 + snapshot.twammReserve0 = pair.twammReserve0 + snapshot.twammReserve1 = pair.twammReserve1 + snapshot.reserveUSD = pair.reserveUSD + snapshot.liquidityTokenTotalSupply = pair.totalSupply + snapshot.liquidityTokenBalance = position.liquidityTokenBalance + + await snapshot.save(); +} diff --git a/Ethereum/ethereum-frax/src/entities/liquidity-position.ts b/Ethereum/ethereum-frax/src/entities/liquidity-position.ts new file mode 100644 index 00000000..0c4b6521 --- /dev/null +++ b/Ethereum/ethereum-frax/src/entities/liquidity-position.ts @@ -0,0 +1,44 @@ +import { LiquidityPosition, Pair } from '../types' +import {BigNumber} from "ethers"; + +// TODO: getLiquidityPosition +// export function getLiquidityPosition(id: string): LiquidityPosition {} + +// TODO: getLiquidityPositions +// export function getLiquidityPositions(ids: string[]): LiquidityPosition[] {} + +export async function createLiquidityPosition(user: String, pair: String, block: any): Promise { + const pairAddress = pair.toString(); + + const userAddress = user.toString(); + + const id = pairAddress.concat('-').concat(userAddress) + + let liquidityPosition = await LiquidityPosition.get(id) + + if (!liquidityPosition) { + // const pair = Pair.load(pairAddress) + // const pair = getPair(Address.fromString(pairAddress), block) + + // TODO: We should do the inverse when a liquidity provider becomes inactive (removes all liquidity) + // pair.liquidityProviderCount = pair.liquidityProviderCount.plus(BigInt.fromI32(1)) + // pair.save() + // const pairContract = PairContract.bind(pair) + // const liquidityTokenBalance = pairContract.balanceOf(user).divDecimal(BigDecimal.fromString('1e18')) + + const timestamp = BigNumber.from(block.timestamp).toNumber(); + + liquidityPosition= LiquidityPosition.create({ + block: BigNumber.from(block.number).toNumber(), + id: id, + liquidityTokenBalance: 0, + pairId: pairAddress, + timestamp: timestamp, + userId: userAddress + }); + + await liquidityPosition.save() + } + + return liquidityPosition as LiquidityPosition +} diff --git a/Ethereum/ethereum-frax/src/entities/mint.ts b/Ethereum/ethereum-frax/src/entities/mint.ts new file mode 100644 index 00000000..e69de29b diff --git a/Ethereum/ethereum-frax/src/entities/pair-day-data.ts b/Ethereum/ethereum-frax/src/entities/pair-day-data.ts new file mode 100644 index 00000000..83db5e6e --- /dev/null +++ b/Ethereum/ethereum-frax/src/entities/pair-day-data.ts @@ -0,0 +1,57 @@ +import { Pair, PairDayData } from '../types' +import { EthereumLog } from "@subql/types-ethereum"; +import {BigNumber} from "ethers"; + +export async function updatePairDayData(event: EthereumLog): Promise { + const timestamp = BigNumber.from(event.block.timestamp).toBigInt(); + + const day = BigNumber.from(timestamp).div(86400).toNumber(); + + const date = BigNumber.from(day).mul(86400).toNumber(); + + const id = event.address.toString().concat('-').concat(BigNumber.from(day).toString()) + + const pair = await Pair.get(event.address.toString()) + if (!pair) throw "Pair is null"; + + let pairDayData = await PairDayData.get(id); + + if (!pairDayData) { + pairDayData = PairDayData.create({ + date: BigNumber.from(date).toNumber(), + id: id, + pairId: "", + reserve0: 0, + reserve1: 0, + reserveUSD: 0, + token0Id: "", + token1Id: "", + totalSupply: 0, + twammReserve0: 0, + twammReserve1: 0, + txCount: 0n, + volumeToken0: 0, + volumeToken1: 0, + volumeUSD: 0 + }); + + pairDayData.token0Id = pair.token0Id; + pairDayData.token1Id = pair.token1Id; + pairDayData.pairId = pair.id; + pairDayData.volumeToken0 = BigNumber.from(0).toNumber(); + pairDayData.volumeToken1 = BigNumber.from(0).toNumber(); + pairDayData.volumeUSD = BigNumber.from(0).toNumber(); + pairDayData.txCount = BigNumber.from(0).toBigInt(); + } + + pairDayData.totalSupply = pair.totalSupply + pairDayData.reserve0 = pair.reserve0 + pairDayData.reserve1 = pair.reserve1 + pairDayData.twammReserve0 = pair.twammReserve0 + pairDayData.twammReserve1 = pair.twammReserve1 + pairDayData.reserveUSD = pair.reserveUSD + pairDayData.txCount = BigNumber.from(pairDayData.txCount).add("1").toBigInt(); + await pairDayData.save() + + return pairDayData as PairDayData +} diff --git a/Ethereum/ethereum-frax/src/entities/pair-hour-data.ts b/Ethereum/ethereum-frax/src/entities/pair-hour-data.ts new file mode 100644 index 00000000..e813eb97 --- /dev/null +++ b/Ethereum/ethereum-frax/src/entities/pair-hour-data.ts @@ -0,0 +1,49 @@ +import { Pair, PairHourData } from '../types' +import { EthereumLog } from "@subql/types-ethereum"; +import {BigNumber} from "ethers"; +export async function updatePairHourData(event: EthereumLog): Promise { + const timestamp = BigNumber.from(event.block.timestamp).toBigInt(); + + const hour = BigNumber.from(timestamp).div(3600).toNumber(); + + const date = BigNumber.from(hour).mul(3600).toNumber(); + + const id = event.address.toString().concat('-').concat(BigNumber.from(hour).toString()); + + const pair = await Pair.get(event.address.toString()) + if (!pair) throw "Pair is null"; + + let pairHourData = await PairHourData.get(id); + + if (!pairHourData) { + pairHourData = PairHourData.create({ + date: BigNumber.from(date).toNumber(), + id: id, + pairId: pair.id, + reserve0: 0, + reserve1: 0, + reserveUSD: 0, + twammReserve0: 0, + twammReserve1: 0, + txCount: 0n, + volumeToken0: 0, + volumeToken1: 0, + volumeUSD: 0 + }); + pairHourData.volumeToken0 = BigNumber.from(0).toNumber(); + pairHourData.volumeToken1 = BigNumber.from(0).toNumber(); + pairHourData.volumeUSD = BigNumber.from(0).toNumber(); + pairHourData.txCount = BigNumber.from(0).toBigInt(); + } + + pairHourData.reserve0 = pair.reserve0 + pairHourData.reserve1 = pair.reserve1 + pairHourData.twammReserve0 = pair.twammReserve0 + pairHourData.twammReserve1 = pair.twammReserve1 + pairHourData.reserveUSD = pair.reserveUSD + pairHourData.txCount = BigNumber.from(pairHourData.txCount).add(1).toBigInt(); + + await pairHourData.save() + + return pairHourData as PairHourData +} diff --git a/Ethereum/ethereum-frax/src/entities/pair.ts b/Ethereum/ethereum-frax/src/entities/pair.ts new file mode 100644 index 00000000..44c58e7b --- /dev/null +++ b/Ethereum/ethereum-frax/src/entities/pair.ts @@ -0,0 +1,89 @@ +import { ADDRESS_ZERO, BIG_DECIMAL_ZERO, BIG_INT_ZERO, FACTORY_ADDRESS, WHITELIST } from "../packages/constants/index.template"; + +import { Pair } from '../types' +import { FraxswapPair__factory } from '../types/contracts' +import { getToken } from '.' +import {EthereumBlock} from "@subql/types-ethereum/dist/ethereum/interfaces"; +import {BigNumber} from "ethers"; +import assert from "assert"; + +export async function getPair( + address: String, + block: EthereumBlock, + token0FromParams: String = ADDRESS_ZERO, + token1FromParams: String = ADDRESS_ZERO +): Promise { + + let pair = await Pair.get(address.toString()) + + if (!pair) { + const pairContract = FraxswapPair__factory.connect(address.toString(),api) + + const token0Address = String(token0FromParams || await pairContract.token0()); + + const token0 = await getToken(token0Address) + + if (token0 === null) { + return null + } + + const token1Address = String(token1FromParams || await pairContract.token1()) + const token1 = await getToken(token1Address) + + if (!token1) { + return null + } + + pair = Pair.create({ + block: 0n, + factoryId: "", + id: address.toString(), + liquidityProviderCount: 0n, + name: "", + reserve0: 0, + reserve1: 0, + reserveETH: 0, + reserveUSD: 0, + timestamp: 0n, + token0Id: "", + token0Price: 0, + token1Id: "", + token1Price: 0, + totalSupply: 0, + trackedReserveETH: 0, + twammReserve0: 0, + twammReserve1: 0, + txCount: 0n, + untrackedVolumeUSD: 0, + volumeToken0: 0, + volumeToken1: 0, + volumeUSD: 0 + }); + + + // if (WHITELIST.includes(token0.id)) { + // const newPairs = token1.whitelistPairsId + // newPairs.push(pair.id) + // token1.whitelistPairsId = newPairs + // } + // if (WHITELIST.includes(token1.id)) { + // const newPairs = token0.whitelistPairsId + // newPairs.push(pair.id) + // token0.whitelistPairsId = newPairs + // } + + await token0.save() + await token1.save() + + pair.factoryId = FACTORY_ADDRESS.toString() + + pair.name = token0.symbol.concat('-').concat(token1.symbol) + + pair.token0Id = token0.id + pair.token1Id = token1.id + pair.timestamp = block.timestamp + pair.block = BigNumber.from(block.number).toBigInt() + } + + return pair as Pair +} diff --git a/Ethereum/ethereum-frax/src/entities/swap.ts b/Ethereum/ethereum-frax/src/entities/swap.ts new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/Ethereum/ethereum-frax/src/entities/swap.ts @@ -0,0 +1 @@ + diff --git a/Ethereum/ethereum-frax/src/entities/sync.ts b/Ethereum/ethereum-frax/src/entities/sync.ts new file mode 100644 index 00000000..e69de29b diff --git a/Ethereum/ethereum-frax/src/entities/token-day-data.ts b/Ethereum/ethereum-frax/src/entities/token-day-data.ts new file mode 100644 index 00000000..fb8b3031 --- /dev/null +++ b/Ethereum/ethereum-frax/src/entities/token-day-data.ts @@ -0,0 +1,50 @@ +import { Token, TokenDayData } from '../types' +import { EthereumLog } from "@subql/types-ethereum"; +import {BigNumber} from "ethers"; +import { getBundle } from '.' + +export async function getTokenDayData(token: Token, event: EthereumLog): Promise { + const bundle = await getBundle() + + const day = BigNumber.from(event.block.timestamp).div(86400).toNumber(); + + const date = day * 86400 + + const id = token.id.toString().concat('-').concat(BigNumber.from(day).toString()) + + let tokenDayData = await TokenDayData.get(id); + + if (!tokenDayData) { + tokenDayData = TokenDayData.create({ + date: date, + id: id, + liquidity: 0, + liquidityETH: 0, + liquidityUSD: 0, + priceUSD: BigNumber.from(token.derivedETH).mul(bundle.ethPrice).toNumber(), + tokenId: token.id, + txCount: 0n, + volume: 0, + volumeETH: 0, + volumeUSD: 0 + }); + } + + return tokenDayData as TokenDayData +} + +export async function updateTokenDayData(token: Token, event: EthereumLog): Promise { + const bundle = await getBundle() + + const tokenDayData = await getTokenDayData(token, event) + + tokenDayData.priceUSD = BigNumber.from(token.derivedETH).mul(bundle.ethPrice).toNumber(); + tokenDayData.liquidity = token.liquidity + tokenDayData.liquidityETH = BigNumber.from(token.liquidity).mul(token.derivedETH).toNumber(); + tokenDayData.liquidityUSD = BigNumber.from(tokenDayData.liquidityETH).mul(bundle.ethPrice).toNumber(); + tokenDayData.txCount = BigNumber.from(tokenDayData.txCount).add('1').toBigInt(); + + await tokenDayData.save() + + return tokenDayData as TokenDayData +} diff --git a/Ethereum/ethereum-frax/src/entities/token-hour-data.ts b/Ethereum/ethereum-frax/src/entities/token-hour-data.ts new file mode 100644 index 00000000..e69de29b diff --git a/Ethereum/ethereum-frax/src/entities/token.ts b/Ethereum/ethereum-frax/src/entities/token.ts new file mode 100644 index 00000000..69ee558c --- /dev/null +++ b/Ethereum/ethereum-frax/src/entities/token.ts @@ -0,0 +1,191 @@ +import { BIG_DECIMAL_ZERO, BIG_INT_ZERO, NULL_CALL_RESULT_VALUE } from "../packages/constants/index.template"; + + +import { ERC20__factory } from '../types/contracts'; +import { ERC20NameBytes__factory } from '../types/contracts' +import { ERC20SymbolBytes__factory } from '../types/contracts' +import { Token } from '../types' +import { getFactory } from './factory' +import {BigNumber} from "ethers"; + +export async function getToken(address: String): Promise { + let token = await Token.get(address.toString()) + + if (!token) { + const factory = await getFactory(); + + factory.tokenCount = BigNumber.from(factory.tokenCount).add("1").toBigInt() + await factory.save() + + token= Token.create({ + decimals: 0n, + derivedETH: 0, + factoryId: factory.id.toString(), + id: address.toString(), + liquidity: 0, + name: "", + symbol: await getSymbol(address), + totalSupply: 0n, + txCount: 0n, + untrackedVolumeUSD: 0, + volume: 0, + volumeUSD: 0 + }); + + + + token.symbol = await getSymbol(address); + token.name = await getName(address); + token.totalSupply = (await getTotalSupply(address)).toBigInt(); + const decimals = await getDecimals(address); + + // TODO: Does this ever happen? + if (!decimals) { + logger.warning('Decimals for token {} was null', [address.toString()]) + return null + } + + // token.whitelistPairsId = []; + token.decimals = decimals.toBigInt(); + + await token.save(); + } + + return token as Token +} + +export async function getSymbol(tokenAddress: String): Promise { + // hard coded override + if (tokenAddress.toString() == '0xe0b7927c4af23765cb51314a0e0521a9645f0e2a') { + return 'DGD' + } + if (tokenAddress.toString() == '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9') { + return 'AAVE' + } + if (tokenAddress.toString() == '0x5dbcf33d8c2e976c6b560249878e6f1491bca25c') { + return 'yUSD' + } + if (tokenAddress.toString() == '0x0309c98b1bffa350bcb3f9fb9780970ca32a5060') { + return 'BDI' + } + if (tokenAddress.toString() == '0x3fa729b4548becbad4eab6ef18413470e6d5324c') { + return 'MOVE' + } + if (tokenAddress.toString() == '0xe95a203b1a91a908f9b9ce46459d101078c2c3cb') { + return 'aETHc' + } + + // CELO hardcode + if (tokenAddress.toString() == '0x471ece3750da237f93b8e339c536989b8978a438') { + return 'CELO' + } + + const contract = ERC20__factory.connect(tokenAddress.toString(), api); + const contractSymbolBytes = ERC20SymbolBytes__factory.connect( + tokenAddress.toString(), + api + ); + // try types string and bytes32 for symbol + let symbolValue = "unknown"; + try { + symbolValue = await contract.symbol(); + } catch (e) { + // try { + const symbolResultBytes = await contractSymbolBytes.callStatic.symbol(); + if (!(symbolResultBytes==NULL_CALL_RESULT_VALUE)) { + symbolValue = symbolResultBytes.toString(); + // TODO: hexString -> utf8 string + // throw new Error('Not implemented') + } else { + throw new Error(`Unknown symbol. Token#108`) + } + } + return symbolValue; +} + + + +export async function getName(address: String): Promise { + if (address.toString() == '0xe0b7927c4af23765cb51314a0e0521a9645f0e2a') { + return 'DGD' + } + if (address.toString() == '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9') { + return 'Aave Token' + } + if (address.toString() == '0x5dbcf33d8c2e976c6b560249878e6f1491bca25c') { + return 'yUSD' + } + if (address.toString() == '0xf94b5c5651c888d928439ab6514b93944eee6f48') { + return 'Yield App' + } + if (address.toString() == '0x0309c98b1bffa350bcb3f9fb9780970ca32a5060') { + return 'BasketDAO DeFi Index' + } + if (address.toString() == '0x3fa729b4548becbad4eab6ef18413470e6d5324c') { + return 'Mover' + } + if (address.toString() == '0xe95a203b1a91a908f9b9ce46459d101078c2c3cb') { + return 'Ankr Eth2 Reward Bearing Certificate' + } + + // CELO hardcode + if (address.toString() == '0x471ece3750da237f93b8e339c536989b8978a438') { + return 'Celo Native Asset' + } + + + const contract = ERC20__factory.connect(address.toString(), api); + const contractSNameBytes = ERC20NameBytes__factory.connect( + address.toString(), + api + ); + // try types string and bytes32 for name + let symbolValue = "unknown"; + try { + symbolValue = await contract.name(); + } catch (e) { + // try { + const symbolResultBytes = await contractSNameBytes.callStatic.name(); + if (!(symbolResultBytes==NULL_CALL_RESULT_VALUE)) { + symbolValue = symbolResultBytes.toString(); + // TODO: hexString -> utf8 string + // throw new Error('Not implemented') + } else { + throw new Error(`Unknown symbol. Token#108`) + } + } + return symbolValue; +} + +export async function getTotalSupply(address: String): Promise { + + const contract = ERC20__factory.connect(address.toString(), api); + + try { + return await contract.totalSupply(); // Should return a BigInt + } catch (e) + { + return BigNumber.from("1"); + } + +} + +export async function getDecimals(address: String): Promise { + // hardcode overrides + if (address.toString() == '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9') { + return BigNumber.from("18"); + } + + const contract = ERC20__factory.connect(address.toString(), api); + + // try types uint8 for decimals + let decimalValue = BigNumber.from(0); + + try { + decimalValue=BigNumber.from(await contract.decimals()); + } catch(e) + { + // Exception from contract + } + return decimalValue; +} diff --git a/Ethereum/ethereum-frax/src/entities/transaction.ts b/Ethereum/ethereum-frax/src/entities/transaction.ts new file mode 100644 index 00000000..e69de29b diff --git a/Ethereum/ethereum-frax/src/entities/user.ts b/Ethereum/ethereum-frax/src/entities/user.ts new file mode 100644 index 00000000..28e3a2b5 --- /dev/null +++ b/Ethereum/ethereum-frax/src/entities/user.ts @@ -0,0 +1,44 @@ +import { User } from '../types' +import { getFactory } from './factory' +import {BigNumber} from "ethers"; + +export async function createUser(address: String): Promise { + // Update user count on factory + const factory = await getFactory() + + factory.userCount = BigNumber.from(factory.userCount).add("1").toBigInt(); + + await factory.save() + + const user = User.create({ + id: address.toString() + }); + + await user.save() + + return user as User +} + +export async function getUser(address: String): Promise { + let user = await User.get(address.toString()) + + // If no user, create one + if (!user) { + user = await createUser(address) + } + + return user as User +} + +export async function updateUser(address: String): Promise { + const user = await getUser(address) + + return user as User +} + +export async function updateUsers(addresses: String[]): Promise { + // log.info('Update users', []) + for (let i = 0; i < addresses.length; i++) { + await updateUser(addresses[i]) + } +} diff --git a/Ethereum/ethereum-frax/src/index.ts b/Ethereum/ethereum-frax/src/index.ts new file mode 100644 index 00000000..9009e4de --- /dev/null +++ b/Ethereum/ethereum-frax/src/index.ts @@ -0,0 +1,7 @@ +import { atob } from "abab"; + +if (!global.atob) { + global.atob = atob as any; +} + +export * from "./mappings/mappingHandlers"; \ No newline at end of file diff --git a/Ethereum/ethereum-frax/src/mappings/fraxswap-pair.ts b/Ethereum/ethereum-frax/src/mappings/fraxswap-pair.ts new file mode 100644 index 00000000..84c3c4f3 --- /dev/null +++ b/Ethereum/ethereum-frax/src/mappings/fraxswap-pair.ts @@ -0,0 +1,135 @@ +import { + BIG_INT_ZERO, + MINIMUM_USD_THRESHOLD_NEW_PAIRS, + WHITELIST +} from "../packages/constants/index.template"; +import {BigNumber} from "ethers"; +import { Burn, Mint, Pair, Swap, Token, Transaction } from '../types' + +import { + getBundle, +} from '../entities' + +export const BLACKLIST_EXCHANGE_VOLUME: string[] = [ + '0x9ea3b5b4ec044b70375236a281986106457b20ef', // DELTA +] + + +/** + * Accepts tokens and amounts, return tracked amount based on token whitelist + * If one token on whitelist, return amount in that token converted to USD * 2. + * If both are, return sum of two amounts + * If neither is, return 0 + */ +export async function getTrackedLiquidityUSD( + tokenAmount0: BigNumber|String, + token0: Token, + tokenAmount1: BigNumber|String, + token1: Token +): Promise { + const bundle = await getBundle() + + const price0 = BigNumber.from(token0.derivedETH).mul(bundle.ethPrice); + const price1 = BigNumber.from(token1.derivedETH).mul(bundle.ethPrice); + + // both are whitelist tokens, take average of both amounts + if (WHITELIST.includes(token0.id) && WHITELIST.includes(token1.id)) { + return BigNumber.from(tokenAmount0).mul(price0).add(BigNumber.from(tokenAmount1).mul(price1)).toString(); + } + + // take double value of the whitelisted token amount + if (WHITELIST.includes(token0.id) && !WHITELIST.includes(token1.id)) { + return BigNumber.from(tokenAmount0).mul(price0).mul("2").toString(); + } + + // take double value of the whitelisted token amount + if (!WHITELIST.includes(token0.id) && WHITELIST.includes(token1.id)) { + return BigNumber.from(tokenAmount1).mul(price1).mul("2").toString(); + } + + // neither token is on white list, tracked volume is 0 + return BIG_INT_ZERO.toString() +} + + +/** + * Accepts tokens and amounts, return tracked amount based on token whitelist + * If one token on whitelist, return amount in that token converted to USD. + * If both are, return average of two amounts + * If neither is, return 0 + */ +export async function getTrackedVolumeUSD( + tokenAmount0: BigNumber | String, + token0: Token, + tokenAmount1: BigNumber | String, + token1: Token, + pair: Pair +): Promise { + const bundle = await getBundle() + + const price0 = BigNumber.from(token0.derivedETH).mul(bundle.ethPrice).toBigInt(); + const price1 = BigNumber.from(token1.derivedETH).mul(bundle.ethPrice).toBigInt(); + + + // if less than 5 LPs, require high minimum reserve amount amount or return 0 + if (BigNumber.from(pair.liquidityProviderCount).lt(BigNumber.from(5))) { + + + const reserve0USD = BigNumber.from(pair.reserve0).mul(price0).toBigInt(); + const reserve1USD = BigNumber.from(pair.reserve1).mul(price1).toBigInt(); + + if (WHITELIST.includes(token0.id) && WHITELIST.includes(token1.id)) { + + if (BigNumber.from(reserve0USD).add(reserve1USD).lt(MINIMUM_USD_THRESHOLD_NEW_PAIRS)) { + return BigNumber.from(0).toString(); + } + } + if (WHITELIST.includes(token0.id) && !WHITELIST.includes(token1.id)) { + + if (BigNumber.from(reserve0USD).mul('2').lt(MINIMUM_USD_THRESHOLD_NEW_PAIRS)){ + return BigNumber.from(0).toString(); + } + } + if (!WHITELIST.includes(token0.id) && WHITELIST.includes(token1.id)) { + + if (BigNumber.from(reserve1USD).mul('2').lt(MINIMUM_USD_THRESHOLD_NEW_PAIRS)) { + return BigNumber.from(0).toString(); + } + } + } + + // both are whitelist tokens, take average of both amounts + if (WHITELIST.includes(token0.id) && WHITELIST.includes(token1.id)) { + return (BigNumber.from(tokenAmount0).mul(price0)).add(BigNumber.from(tokenAmount1).mul(price1)).div("2").toString() + } + + // take full value of the whitelisted token amount + if (WHITELIST.includes(token0.id) && !WHITELIST.includes(token1.id)) { + return BigNumber.from(tokenAmount0).mul(price0).toString(); + } + + // take full value of the whitelisted token amount + if (!WHITELIST.includes(token0.id) && WHITELIST.includes(token1.id)) { + return BigNumber.from(tokenAmount1).mul(price1).toString(); + } + + // neither token is on white list, tracked volume is 0 + return "0" +} + + +export function convertTokenToDecimal(tokenAmount: BigNumber, exchangeDecimals: BigInt): BigNumber { + if (BigNumber.from(tokenAmount).eq("0")) { + return BigNumber.from(tokenAmount); + } + + return BigNumber.from(tokenAmount).div(BigNumber.from(exchangeDecimals)); +} + +export async function isCompleteMint(mintId: string): Promise { + const loaded_mint = await Mint.get(mintId); + if (!loaded_mint) throw 'mint not found'; + return loaded_mint.sender != null +} + + diff --git a/Ethereum/ethereum-frax/src/mappings/handlers/Burn.ts b/Ethereum/ethereum-frax/src/mappings/handlers/Burn.ts new file mode 100644 index 00000000..e8d0e7ee --- /dev/null +++ b/Ethereum/ethereum-frax/src/mappings/handlers/Burn.ts @@ -0,0 +1,119 @@ +import {BurnLog as BurnEvent} from "../../types/abi-interfaces/FraxswapPair"; +import {Burn, Token, Transaction} from "../../types"; +import { + createLiquidityPosition, + createLiquidityPositionSnapshot, + getBundle, + getFactory, + getPair, + getToken, updateDayData, updatePairDayData, updatePairHourData, updateTokenDayData +} from "../../entities"; + +import {convertTokenToDecimal} from "../fraxswap-pair"; +import {BigNumber} from "ethers"; + +export async function onBurn(event: BurnEvent): Promise { + const transactionHash = event.transaction.hash.toString() + let transaction = await Transaction.get(transactionHash); + + const pair = await getPair(event.address, event.block) + if (!pair) throw "Pair is null"; + + if (!transaction) { + transaction = Transaction.create({ + blockNumber: 0n, id: transactionHash, timestamp: 0n + }); + // transaction = new Transaction(transactionHash) + transaction.blockNumber = BigNumber.from(event.block.number).toBigInt(); + transaction.timestamp = event.block.timestamp; + await transaction.save(); + } + + let burns: any = []; + let burn: Burn | undefined = undefined; + + // If transaction has burns + if (burns.length) { + burn = await Burn.get(burns[burns.length - 1]); + } + + // If no burn or burn complete, create new burn + if (!burn || burn.complete) { + burn = Burn.create({ + complete: true, + id: (event.transaction.hash.toString().concat('-').concat(BigNumber.from(burns.length).toString()).toString()), + liquidity: 0, + pairId: pair.id.toString(), + timestamp: BigNumber.from(transaction.timestamp).toBigInt(), + transactionId: transaction.id.toString() + }); + } + + const factory = await getFactory() + + //update token info + const token0 = await getToken(pair.token0Id); + const token1 = await getToken(pair.token1Id); + if (!token0) throw "token0 is null"; + if (!token1) throw "token1 is null"; + if (!event.args) throw "event.args is null"; + + const token0Amount = convertTokenToDecimal(event.args.amount0, token0.decimals) + const token1Amount = convertTokenToDecimal(event.args.amount1, token1.decimals) + + // update txn counts + token0.txCount = BigNumber.from(token0.txCount).add("1").toBigInt(); + token1.txCount = BigNumber.from(token1.txCount).add("1").toBigInt(); + if (!token0) throw "token0 is null"; + if (!token1) throw "token1 is null"; + + // get new amounts of USD and ETH for tracking + const bundle = await getBundle() + const amountTotalUSD = BigNumber.from(token1.derivedETH) + .mul(token1Amount) + .add(BigNumber.from(token0.derivedETH).mul(token0Amount)) + .mul(bundle.ethPrice) + + // update txn counts + factory.txCount = BigNumber.from(factory.txCount).add('1').toBigInt(); + pair.txCount = BigNumber.from(factory.txCount).add('1').toBigInt(); + + // update global counter and save + await token0.save() + await token1.save() + await pair.save() + await factory.save() + + // update burn + // burn.sender = event.params.sender + burn.amount0 = token0Amount.toNumber(); + burn.amount1 = token1Amount.toNumber(); + // burn.to = event.params.to + burn.logIndex = BigNumber.from(event.logIndex).toBigInt(); + + burn.amountUSD = amountTotalUSD.toNumber(); + await burn.save() + + // update the LP position + const burnSender = burn.sender; + if (burnSender) + { + const liquidityPosition = await createLiquidityPosition(burnSender, event.address, event.block) + await createLiquidityPositionSnapshot(liquidityPosition, event.block) + } + + // update day data + await updateDayData(event) + + // update pair day data + await updatePairDayData(event) + + // update pair hour data + await updatePairHourData(event) + + // update token0 day data + await updateTokenDayData(token0 as Token, event) + + // update token1 day data + await updateTokenDayData(token1 as Token, event) +} \ No newline at end of file diff --git a/Ethereum/ethereum-frax/src/mappings/handlers/Mint.ts b/Ethereum/ethereum-frax/src/mappings/handlers/Mint.ts new file mode 100644 index 00000000..d922111d --- /dev/null +++ b/Ethereum/ethereum-frax/src/mappings/handlers/Mint.ts @@ -0,0 +1,93 @@ +import {MintLog as MintEvent} from "../../types/abi-interfaces/FraxswapPair"; +import {Mint, Token, Transaction} from "../../types"; +import { + createLiquidityPosition, + createLiquidityPositionSnapshot, + getBundle, + getFactory, + getPair, + getToken, updateDayData, updatePairDayData, updatePairHourData, updateTokenDayData +} from "../../entities"; +import {convertTokenToDecimal} from "../fraxswap-pair"; +import {BigNumber} from "ethers"; + +export async function onMint(event: MintEvent): Promise { + const transaction = await Transaction.get(event.transaction.hash.toString()) + if (!transaction) throw "transaction is null"; + + const mints: any = []; + + if (!mints) { + logger.info('Mints history null onMint event', []) + return + } + + const mint = await Mint.get(mints[mints.length - 1]) + if (!mint) throw "mint is null"; + + const pair = await getPair(event.address, event.block) + if (!pair) throw "Pair is null"; + + const factory = await getFactory() + + const token0 = await getToken(pair.token0Id); + const token1 = await getToken(pair.token1Id); + if (!token0) throw "token0 is null"; + if (!token1) throw "token1 is null"; + if (!event.args) throw "event.args is null"; + + // update exchange info (except balances, sync will cover that) + const token0Amount = convertTokenToDecimal(event.args.amount0, token0.decimals) + const token1Amount = convertTokenToDecimal(event.args.amount1, token1.decimals) + + // update tx counts + token0.txCount = BigNumber.from(token0.txCount).add(1).toBigInt(); + token1.txCount = BigNumber.from(token1.txCount).add(1).toBigInt(); + + // get new amounts of USD and ETH for tracking + const bundle = await getBundle() + const amountTotalUSD = BigNumber.from(token1.derivedETH) + .mul(token1Amount) + .add(BigNumber.from(token0.derivedETH).mul(token0Amount)) + .mul(bundle.ethPrice).toNumber(); + + // update txn counts + pair.txCount = BigNumber.from(pair.txCount).add('1').toBigInt(); + + factory.txCount = BigNumber.from(factory.txCount).add('1').toBigInt(); + + // save entities + await token0.save(); + await token1.save(); + await pair.save(); + await factory.save(); + + mint.sender = event.args.sender + mint.amount0 = token0Amount.toNumber(); + mint.amount1 = token1Amount.toNumber(); + mint.logIndex = BigNumber.from(event.logIndex).toBigInt(); + mint.amountUSD = amountTotalUSD; + await mint.save() + + + // create liquidity position + const liquidityPosition = await createLiquidityPosition(mint.to, event.address, event.block) + + // create liquidity position snapshot + await createLiquidityPositionSnapshot(liquidityPosition, event.block) + + // update day data + await updateDayData(event) + + // update pair day data + await updatePairDayData(event) + + // update pair hour data + await updatePairHourData(event) + + // update token0 day data + await updateTokenDayData(token0 as Token, event) + + // update token1 day data + await updateTokenDayData(token1 as Token, event) +} \ No newline at end of file diff --git a/Ethereum/ethereum-frax/src/mappings/handlers/PairCreated.ts b/Ethereum/ethereum-frax/src/mappings/handlers/PairCreated.ts new file mode 100644 index 00000000..09961157 --- /dev/null +++ b/Ethereum/ethereum-frax/src/mappings/handlers/PairCreated.ts @@ -0,0 +1,57 @@ +import {PairCreatedLog} from "../../types/abi-interfaces/FraxswapFactory"; +import {getFactory, getPair} from "../../entities"; +import {BigNumber} from "ethers"; +import {BIG_INT_ONE} from "../../packages/constants/index.template"; +import {FraxswapPair__factory} from "../../types/contracts"; + +export async function onPairCreated(log: PairCreatedLog): Promise { + const factory = await getFactory() + + + if (!log.args) throw "event.args is null"; + const pair = await getPair(log.args.pair, log.block, log.args.token0, log.args.token1) + + // We returned null for some reason, we should silently bail without creating this pair + if (!pair) { + return + } + + // Now it's safe to save + await pair.save() + + // create the tracked contract based on the template + // log.args.pair + // const entity = Pair.create({ + // block: 0n, + // factoryId: "", + // id: "", + // liquidityProviderCount: 0n, + // name: "", + // reserve0: 0, + // reserve1: 0, + // reserveETH: 0, + // reserveUSD: 0, + // timestamp: 0n, + // token0Id: "", + // token0Price: 0, + // token1Id: "", + // token1Price: 0, + // totalSupply: 0, + // trackedReserveETH: 0, + // twammReserve0: 0, + // twammReserve1: 0, + // txCount: 0n, + // untrackedVolumeUSD: 0, + // volumeToken0: 0, + // volumeToken1: 0, + // volumeUSD: 0 + // }); + + + // FraxswapPair__factory.connect(log.args.pair,api) + + + // Update pair count once we've sucessesfully created a pair + factory.pairCount = BigNumber.from(factory.pairCount).add(BIG_INT_ONE).toBigInt(); + await factory.save() +} \ No newline at end of file diff --git a/Ethereum/ethereum-frax/src/mappings/handlers/Swap.ts b/Ethereum/ethereum-frax/src/mappings/handlers/Swap.ts new file mode 100644 index 00000000..bd0f7c48 --- /dev/null +++ b/Ethereum/ethereum-frax/src/mappings/handlers/Swap.ts @@ -0,0 +1,194 @@ +import {SwapLog as SwapEvent} from "../../types/abi-interfaces/FraxswapPair"; +import { + getBundle, + getFactory, + getPair, + getToken, + updateDayData, + updatePairDayData, + updatePairHourData, updateTokenDayData +} from "../../entities"; +import {Pair, Swap, Token, Transaction} from "../../types"; +import {BIG_DECIMAL_ZERO} from "../../packages/constants/index.template"; +import {convertTokenToDecimal, getTrackedVolumeUSD} from "../fraxswap-pair"; +import {BigNumber} from "ethers"; +import {BLACKLIST_EXCHANGE_VOLUME} from "../fraxswap-pair"; + +export async function onSwap(event: SwapEvent): Promise { + logger.info('onSwap', []) + const pair = await getPair(event.address, event.block) + if (!pair) throw "Pair is null"; + const token0 = await getToken(pair.token0Id); + const token1 = await getToken(pair.token1Id); + if (!token0) throw "token0 is null"; + if (!token1) throw "token1 is null"; + if (!event.args) throw "event.args is null"; + + const amount0In = convertTokenToDecimal(event.args.amount0In, token0.decimals) + const amount1In = convertTokenToDecimal(event.args.amount1In, token1.decimals) + const amount0Out = convertTokenToDecimal(event.args.amount0Out, token0.decimals) + const amount1Out = convertTokenToDecimal(event.args.amount1Out, token1.decimals) + + // totals for volume updates + const amount0Total = amount0Out.add(amount0In) + const amount1Total = amount1Out.add(amount1In) + + // ETH/USD prices + const bundle = await getBundle() + + // get total amounts of derived USD and ETH for tracking + const derivedAmountETH = BigNumber.from(token1.derivedETH) + .mul(amount1Total) + .add(BigNumber.from(token0.derivedETH).mul(amount0Total)) + .div(BigNumber.from('2')); + + const derivedAmountUSD = BigNumber.from(derivedAmountETH).mul(bundle.ethPrice); + + // only accounts for volume through white listed tokens + const trackedAmountUSD = await getTrackedVolumeUSD( + amount0Total, + token0 as Token, + amount1Total, + token1 as Token, + pair as Pair + ) + + let trackedAmountETH: BigNumber; + + if (BigNumber.from(bundle.ethPrice).eq(BIG_DECIMAL_ZERO)) { + trackedAmountETH = BigNumber.from(0); + } else { + trackedAmountETH = BigNumber.from(trackedAmountUSD).div(bundle.ethPrice) + } + + // update token0 global volume and token liquidity stats + token0.volume = BigNumber.from(token0.volume).add(BigNumber.from(amount0In).add(amount0Out)).toNumber(); + token0.volumeUSD = BigNumber.from(token0.volumeUSD).add(BigNumber.from(trackedAmountUSD)).toNumber(); + token0.untrackedVolumeUSD = BigNumber.from(token0.untrackedVolumeUSD).add(derivedAmountUSD).toNumber(); + + // update token1 global volume and token liquidity stats + token1.volume = BigNumber.from(token1.volume).add(BigNumber.from(amount1In).add(amount1Out)).toNumber(); + token1.volumeUSD = BigNumber.from(token1.volumeUSD).add(BigNumber.from(trackedAmountUSD)).toNumber(); + token1.untrackedVolumeUSD = BigNumber.from(token1.untrackedVolumeUSD).add(derivedAmountUSD).toNumber(); + + // update txn counts + token0.txCount = BigNumber.from(token0.txCount).add("1").toBigInt(); + token1.txCount = BigNumber.from(token1.txCount).add("1").toBigInt(); + + // update pair volume data, use tracked amount if we have it as its probably more accurate + pair.volumeUSD = BigNumber.from(pair.volumeUSD).add(BigNumber.from(trackedAmountUSD)).toNumber(); + pair.volumeToken0 = BigNumber.from(pair.volumeToken0).add(amount0Total).toNumber(); + pair.volumeToken1 = BigNumber.from(pair.volumeToken1).add(amount1Total).toNumber(); + pair.untrackedVolumeUSD = BigNumber.from(pair.untrackedVolumeUSD).add(derivedAmountUSD).toNumber(); + pair.txCount = BigNumber.from(pair.txCount).add("1").toBigInt(); + await pair.save() + + // Don't track volume for these tokens in total exchange volume + if (!BLACKLIST_EXCHANGE_VOLUME.includes(token0.id) && !BLACKLIST_EXCHANGE_VOLUME.includes(token1.id)) { + // update global values, only used tracked amounts for volume + const factory = await getFactory() + factory.volumeUSD = BigNumber.from(factory.volumeUSD).add(BigNumber.from(trackedAmountUSD)).toNumber(); + factory.volumeETH = BigNumber.from(factory.volumeETH).add(BigNumber.from(trackedAmountETH)).toNumber(); + factory.untrackedVolumeUSD = BigNumber.from(factory.untrackedVolumeUSD).add(derivedAmountUSD).toNumber(); + factory.txCount = BigNumber.from(factory.txCount).add(1).toBigInt(); + await factory.save() + } + + // save entities + await pair.save() + await token0.save() + await token1.save() + + let transaction = await Transaction.get(event.transaction.hash.toString()); + + if (!transaction) { + transaction = Transaction.create({ + blockNumber: BigNumber.from(event.block.number).toBigInt(), + id: event.transaction.hash.toString(), + timestamp: event.block.timestamp + }); + } + let swaps: any = []; + if (!swaps) swaps = [] + + const swap = Swap.create({ + amount0In: 0, + amount0Out: 0, + amount1In: 0, + amount1Out: 0, + amountUSD: 0, + id: event.transaction.hash.toString().concat('-').concat(BigNumber.from(swaps.length).toString()), + pairId: "", + sender: "", + timestamp: 0n, + to: "", + transactionId: "" + }); + + // update swap event + swap.pairId = pair.id + swap.timestamp = transaction.timestamp + swap.transactionId = transaction.id + swap.sender = event.args.sender + swap.amount0In = amount0In.toNumber(); + swap.amount1In = amount1In.toNumber(); + swap.amount0Out = amount0Out.toNumber(); + swap.amount1Out = amount1Out.toNumber(); + swap.to = event.args.to + swap.logIndex = BigNumber.from(event.logIndex).toBigInt(); + // use the tracked amount if we have it + swap.amountUSD = BigNumber.from((BigNumber.from(trackedAmountUSD).eq("0") ? derivedAmountUSD : trackedAmountUSD)).toNumber(); + await swap.save() + + // update the transaction + // transaction.swapsId = swaps.concat([swap.id]); + + await transaction.save() + + const dayData = await updateDayData(event) + + const pairDayData = await updatePairDayData(event) + const pairHourData = await updatePairHourData(event) + + const token0DayData = await updateTokenDayData(token0 as Token, event) + const token1DayData = await updateTokenDayData(token1 as Token, event) + + // Don't track volume for these tokens in total exchange volume + if (!BLACKLIST_EXCHANGE_VOLUME.includes(token0.id) && !BLACKLIST_EXCHANGE_VOLUME.includes(token1.id)) { + // swap specific updating + dayData.volumeUSD = BigNumber.from(dayData.volumeUSD).add(BigNumber.from(trackedAmountUSD)).toNumber(); + dayData.volumeETH = BigNumber.from(dayData.volumeETH).add(BigNumber.from(trackedAmountETH)).toNumber(); + dayData.untrackedVolume = BigNumber.from(dayData.untrackedVolume).add(derivedAmountUSD).toNumber(); + await dayData.save() + } + + // swap specific updating for pair + pairDayData.volumeToken0 = BigNumber.from(pairDayData.volumeToken0).add(amount0Total).toNumber() + pairDayData.volumeToken1 = BigNumber.from(pairDayData.volumeToken1).add(amount1Total).toNumber(); + pairDayData.volumeUSD = BigNumber.from(pairDayData.volumeUSD).add(BigNumber.from(trackedAmountUSD)).toNumber(); + await pairDayData.save() + + // update hourly pair data + + pairHourData.volumeToken0 = BigNumber.from(pairHourData).add(amount0Total).toNumber(); + pairHourData.volumeToken1 = BigNumber.from(pairHourData.volumeToken1).add(amount1Total).toNumber(); + pairHourData.volumeUSD = BigNumber.from(pairHourData.volumeUSD).add(BigNumber.from(trackedAmountUSD)).toNumber(); + await pairHourData.save() + + // swap specific updating for token0 + token0DayData.volume = BigNumber.from(token0DayData.volume).add(amount0Total).toNumber(); + token0DayData.volumeETH = BigNumber.from(token0DayData.volumeETH).add(BigNumber.from(amount0Total).mul(token1.derivedETH)).toNumber(); + token0DayData.volumeUSD = BigNumber.from(token0DayData.volumeUSD).add( + BigNumber.from(amount0Total).mul(token0.derivedETH).mul(bundle.ethPrice) + ).toNumber(); + await token0DayData.save() + + // swap specific updating + token1DayData.volume = BigNumber.from(token1DayData.volume).add(amount1Total).toNumber(); + token1DayData.volumeETH = BigNumber.from(token1DayData.volumeETH).add(amount1Total.mul(token1.derivedETH)).toNumber(); + token1DayData.volumeUSD = BigNumber.from(token1DayData.volumeUSD).add( + BigNumber.from(amount1Total).mul(token1.derivedETH).mul(bundle.ethPrice) + ).toNumber(); + + await token1DayData.save() +} diff --git a/Ethereum/ethereum-frax/src/mappings/handlers/Sync.ts b/Ethereum/ethereum-frax/src/mappings/handlers/Sync.ts new file mode 100644 index 00000000..5e5db604 --- /dev/null +++ b/Ethereum/ethereum-frax/src/mappings/handlers/Sync.ts @@ -0,0 +1,97 @@ +import {SyncLog as SyncEvent} from "../../types/abi-interfaces/FraxswapPair"; +import {getBundle, getFactory, getPair, getToken} from "../../entities"; +import {BIG_DECIMAL_ZERO} from "../../packages/constants/index.template"; +import {findEthPerToken, getEthPrice} from "../../pricing"; +import {Token} from "../../types"; +import {convertTokenToDecimal, getTrackedLiquidityUSD} from "../fraxswap-pair"; +import {BigNumber} from "ethers"; +import {FraxswapPair__factory} from "../../types/contracts" +export async function onSync(event: SyncEvent): Promise { + const pair = await getPair(event.address, event.block) + if (!pair) throw "Pair is null"; + + const token0 = await getToken(pair.token0Id) + const token1 = await getToken(pair.token1Id) + if (!token0) throw "token0 is null"; + if (!token1) throw "token1 is null"; + + const factory = await getFactory() + + // reset factory liquidity by subtracting only tracked liquidity + factory.liquidityETH = BigNumber.from(factory.liquidityETH).sub(pair.trackedReserveETH).toNumber(); + + // reset token total liquidity amounts + token0.liquidity = BigNumber.from(token0.liquidity).sub(pair.reserve0).toNumber(); + token1.liquidity = BigNumber.from(token1.liquidity).sub(pair.reserve1).toNumber(); + + // Fetch reserves, accounting for outstanding TWAMMs + const pair_contract = FraxswapPair__factory.connect(event.address, api); + + const reserves_true = await pair_contract.getReserveAfterTwamm(event.block.timestamp) + const reserve0_true = reserves_true._reserve0; + const reserve1_true = reserves_true._reserve1; + + + // Set to the true reserve values, accounting for outstanding TWAMMs + pair.reserve0 = convertTokenToDecimal(reserve0_true, token0.decimals).toNumber(); + pair.reserve1 = convertTokenToDecimal(reserve1_true, token1.decimals).toNumber(); + pair.twammReserve0 = convertTokenToDecimal(reserves_true._twammReserve0, token0.decimals).toNumber(); + pair.twammReserve1 = convertTokenToDecimal(reserves_true._twammReserve1, token1.decimals).toNumber(); + + if (!BigNumber.from(pair.reserve1).eq("0")) { + pair.token0Price = BigNumber.from(pair.reserve0).div(pair.reserve1).toNumber(); + } else { + pair.token0Price = BigNumber.from(0).toNumber(); + } + + if (!BigNumber.from(pair.reserve0).eq("0")) { + pair.token1Price = BigNumber.from(pair.reserve1).div(pair.reserve0).toNumber(); + } else { + pair.token1Price = BigNumber.from(0).toNumber(); + } + + await pair.save() + + // update ETH price now that reserves could have changed + const bundle = await getBundle() + // Pass the block so we can get accurate price data before migration + bundle.ethPrice = (await getEthPrice(event.block)).toNumber(); + await bundle.save(); + + token0.derivedETH = (await findEthPerToken(token0 as Token)).toNumber(); + token1.derivedETH = (await findEthPerToken(token1 as Token)).toNumber(); + await token0.save() + await token1.save() + + // get tracked liquidity - will be 0 if neither is in whitelist + let trackedLiquidityETH: BigNumber; + if (!BigNumber.from(bundle.ethPrice).eq("0")) { + trackedLiquidityETH = BigNumber.from(await getTrackedLiquidityUSD(pair.reserve0.toString(), token0 as Token, pair.reserve1.toString(), token1 as Token)).div( + bundle.ethPrice + ) + } else { + trackedLiquidityETH = BigNumber.from(0); + } + + // use derived amounts within pair + pair.trackedReserveETH = trackedLiquidityETH.toNumber(); + pair.reserveETH = BigNumber.from(pair.reserve0) + .mul(token0.derivedETH) + .add(BigNumber.from(pair.reserve1).mul(token1.derivedETH)).toNumber(); + + pair.reserveUSD = BigNumber.from(pair.reserveETH).mul(bundle.ethPrice).toNumber(); + + // use tracked amounts globally + factory.liquidityETH = BigNumber.from(factory.liquidityETH).add(trackedLiquidityETH).toNumber(); + factory.liquidityUSD = BigNumber.from(factory.liquidityETH).mul(bundle.ethPrice).toNumber(); + + // now correctly set liquidity amounts for each token + token0.liquidity = BigNumber.from(token0.liquidity).add(pair.reserve0).toNumber(); + token1.liquidity = BigNumber.from(token1.liquidity).add(pair.reserve1).toNumber(); + + // save entities + await pair.save() + await factory.save() + await token0.save() + await token1.save() +} \ No newline at end of file diff --git a/Ethereum/ethereum-frax/src/mappings/handlers/Transfer.ts b/Ethereum/ethereum-frax/src/mappings/handlers/Transfer.ts new file mode 100644 index 00000000..e3e5c827 --- /dev/null +++ b/Ethereum/ethereum-frax/src/mappings/handlers/Transfer.ts @@ -0,0 +1,187 @@ +import {TransferLog as TransferEvent} from "../../types/abi-interfaces/FraxswapPair"; +import {ADDRESS_ZERO} from "../../packages/constants/index.template"; +import {createLiquidityPosition, createLiquidityPositionSnapshot, getFactory, getUser} from "../../entities"; +import {Burn, Mint, Pair, Transaction} from "../../types"; +import {BigNumber} from "ethers"; +import {isCompleteMint} from "../fraxswap-pair"; +export async function onTransfer(event: TransferEvent): Promise { + if (!event.args) throw "event.args is null"; + // ignore initial transfers for first adds + if (event.args.to == ADDRESS_ZERO && BigNumber.from(event.args.value).eq("1000")) { + return + } + + + const factory = await getFactory() + const transactionHash = event.transaction.hash; + + // Force creation of users if not already known will be lazily created + await getUser(event.args.from) + await getUser(event.args.to) + + const pair = await Pair.get(event.address) + if (!pair) throw "Pair is null"; + + // liquidity token amount being transfered + const value = BigNumber.from(event.args.value).div(BigNumber.from('1e18')); + + let transaction = await Transaction.get(transactionHash) + + if (!transaction) { + transaction = Transaction.create({ + blockNumber: BigNumber.from(event.block.number).toBigInt(), + id: transactionHash.toString(), + timestamp: BigNumber.from(event.block.timestamp).toBigInt() + }); + } + + let mints: any = []; + let burns: any = []; + + // 3 cases, mints, send first on ETH withdrawls, and burns + + if (event.args.from == ADDRESS_ZERO) { + // mints + + // update total supply + pair.totalSupply = BigNumber.from(pair.totalSupply).add(value).toNumber(); + await pair.save() + + // If transaction has no mints or last mint is complete + if (mints.length == 0 || (await isCompleteMint(mints[mints.length - 1]))) { + // log.warning('1-1: NO MINTS OR LAST MINT IS COMPLETE', []) + const mint = Mint.create({ + amount0: 0, + amount1: 0, + amountUSD: 0, + feeLiquidity: 0, + feeTo: "", + id: event.transaction.hash.concat('-').concat(BigNumber.from(mints.length).toString()), + liquidity: value.toNumber(), + logIndex: 0n, + pairId: pair.id, + sender: "", + timestamp: BigNumber.from(transaction.timestamp).toBigInt(), + to: event.args.to, + transactionId: transaction.id.toString() + }); + + await mint.save(); + + // transaction.mintsId = mints.concat([mint.id]); + + // save entities + await transaction.save(); + await factory.save(); + } + } else if (event.args.to == pair.id) { + // case where direct send first on ETH withdrawls + const burn = Burn.create({ + amount0: 0, + amount1: 0, + amountUSD: 0, + complete: false, + feeLiquidity: 0, + feeTo: "", + id: event.transaction.hash.concat('-').concat(BigNumber.from(mints.length).toString()), + liquidity: value.toNumber(), + logIndex: 0n, + pairId: pair.id, + sender: event.args.from, + timestamp: BigNumber.from(transaction.timestamp).toBigInt(), + to: event.args.to, + transactionId: transaction.id + }); + + await burn.save() + + // transaction.burnsId = burns.concat([burn.id]) + + await transaction.save() + } else if (event.args.to == ADDRESS_ZERO && event.args.from == pair.id) { + // burns + pair.totalSupply = BigNumber.from(pair.totalSupply).mod(value).toNumber(); + await pair.save(); + + let burn: Burn | undefined = undefined + + // If transaction has burns + if (burns.length) { + burn = await Burn.get(burns[burns.length - 1]) + } + + // If no burn or burn complete, create new burn + if (burn === undefined || burn.complete) { + burn = Burn.create({ + amount0: 0, + amount1: 0, + amountUSD: 0, + complete: true, + feeLiquidity: 0, + feeTo: "", + id: event.transaction.hash.concat('-').concat(BigNumber.from(mints.length).toString()), + liquidity: value.toNumber(), + logIndex: 0n, + pairId: pair.id, + sender: "", + timestamp: BigNumber.from(transaction.timestamp).toBigInt(), + to: "", + transactionId: transaction.id + }); + } + + // if this logical burn included a fee mint, account for this + if (mints.length != 0 && !(await isCompleteMint(mints[mints.length - 1]))) { + const mint = await Mint.get(mints[mints.length - 1]) + if (!mint) throw "mint is null"; + + burn.feeTo = mint.to + burn.feeLiquidity = mint.liquidity + + // remove the logical mint + // store.remove('Mint', mints[mints.length - 1]) + + // update the transaction + // transaction.mintsId = mints.slice(0, -1) + await transaction.save() + } + + await burn.save() + + if (!burn.complete) { + // Burn is not complete, replace previous tail + // transaction.burnsId = burns.slice(0, -1).concat([burn.id]) + } else { + // Burn is complete, concat to transactions + // transaction.burnsId = burns.concat([burn.id]) + } + + await transaction.save() + } + + + // BURN + if (event.args.from != ADDRESS_ZERO && event.args.from != pair.id) { + const fromUserLiquidityPosition = await createLiquidityPosition(event.args.from, event.address, event.block) + + fromUserLiquidityPosition.liquidityTokenBalance = BigNumber.from(fromUserLiquidityPosition.liquidityTokenBalance).sub(value).toNumber(); + + await fromUserLiquidityPosition.save() + + await createLiquidityPositionSnapshot(fromUserLiquidityPosition, event.block) + } + + // MINT + if (event.args.to != ADDRESS_ZERO && event.args.to.toString() != pair.id) { + + const toUserLiquidityPosition = await createLiquidityPosition(event.args.to, event.address, event.block) + + toUserLiquidityPosition.liquidityTokenBalance = BigNumber.from(toUserLiquidityPosition.liquidityTokenBalance).add(value).toNumber(); + + await toUserLiquidityPosition.save() + + await createLiquidityPositionSnapshot(toUserLiquidityPosition, event.block) + } + + await transaction.save() +} diff --git a/Ethereum/ethereum-frax/src/mappings/handlers/index.ts b/Ethereum/ethereum-frax/src/mappings/handlers/index.ts new file mode 100644 index 00000000..2a327bc6 --- /dev/null +++ b/Ethereum/ethereum-frax/src/mappings/handlers/index.ts @@ -0,0 +1,6 @@ +export {onTransfer} from "./Transfer" +export {onSync} from "./Sync"; +export {onSwap} from "./Swap"; +export {onMint} from "./Mint"; +export {onBurn} from "./Burn"; +export {onPairCreated} from "./PairCreated"; \ No newline at end of file diff --git a/Ethereum/ethereum-frax/src/mappings/mappingHandlers.ts b/Ethereum/ethereum-frax/src/mappings/mappingHandlers.ts new file mode 100644 index 00000000..18aa4161 --- /dev/null +++ b/Ethereum/ethereum-frax/src/mappings/mappingHandlers.ts @@ -0,0 +1 @@ +export * from "./handlers"; \ No newline at end of file diff --git a/Ethereum/ethereum-frax/src/packages/constants/index.template.ts b/Ethereum/ethereum-frax/src/packages/constants/index.template.ts new file mode 100644 index 00000000..9c72104a --- /dev/null +++ b/Ethereum/ethereum-frax/src/packages/constants/index.template.ts @@ -0,0 +1,91 @@ +import {BigNumber} from "ethers"; + +export const NULL_CALL_RESULT_VALUE = '0x0000000000000000000000000000000000000000000000000000000000000001' + +export const ADDRESS_ZERO = ('0x0000000000000000000000000000000000000000') + +export const BIG_DECIMAL_1E6 = BigNumber.from('1e6') + +export const BIG_DECIMAL_1E12 = BigNumber.from('1e12') + +export const BIG_DECIMAL_1E18 = BigNumber.from('1e18') + +export const BIG_DECIMAL_ZERO = BigNumber.from('0'); + +export const BIG_DECIMAL_ONE = BigNumber.from('1'); + +export const BIG_INT_ONE = BigNumber.from('1'); + + +export const BIG_INT_ZERO = BigNumber.from(0); + + +export const FACTORY_ADDRESS = ( + '{{ factory_address }}{{^factory_address}}0x0000000000000000000000000000000000000000{{/factory_address}}' +) + +export const FXS_ADDRESS = ( + '{{ fxs_address }}{{^fxs_address}}0x0000000000000000000000000000000000000000{{/fxs_address}}' +) + +export const BIG_FXS_START_PRICE = BigNumber.from( + '{{ fxs_start_price }}{{^fxs_start_price}}8380000000000000000{{/fxs_start_price}}' +) + +export const FRAX_FXS_PAIR_ADDRESS = ( + '{{ frax_fxs_pair_address }}{{^frax_fxs_pair_address}}0x0000000000000000000000000000000000000000{{/frax_fxs_pair_address}}' +) + +export const FRAX_FXS_PAIR = + '{{ frax_fxs_pair }}{{^frax_fxs_pair}}0x0000000000000000000000000000000000000000{{/frax_fxs_pair}}' + +// minimum liquidity required to count towards tracked volume for pairs with small # of Lps +export const MINIMUM_USD_THRESHOLD_NEW_PAIRS = BigNumber.from( + '{{ minimum_usd_threshold_new_pairs }}{{^minimum_usd_threshold_new_pairs}}3000{{/minimum_usd_threshold_new_pairs}}' +) + +// minimum liquidity for price to get tracked +export const MINIMUM_LIQUIDITY_THRESHOLD_ETH = BigNumber.from( + '{{ minimum_liquidity_threshold_eth }}{{^minimum_liquidity_threshold_eth}}1{{/minimum_liquidity_threshold_eth}}' +) + +export const NATIVE = ( + '{{ native_address }}{{^native_address}}0x0000000000000000000000000000000000000000{{/native_address}}' +) + +export const WETH_ADDRESS = ( + '{{ weth_address }}{{^weth_address}}0x0000000000000000000000000000000000000000{{/weth_address}}' +) + +export const BIG_NATIVE_START_PRICE = BigNumber.from( + '{{ native_start_price }}{{^native_start_price}}2143000000000000000000{{/native_start_price}}' +) + + +export const FRAXSWAP_FRAX_WETH_PAIR_ADDRESS = ( + '{{ fraxswap_frax_weth_pair_address }}{{^fraxswap_frax_weth_pair_address}}0x0000000000000000000000000000000000000000{{/fraxswap_frax_weth_pair_address}}' +) + +export const FRAXSWAP_FRAX_WETH_PAIR = '{{ fraxswap_frax_weth_pair }}{{^fraxswap_frax_weth_pair}}0x0000000000000000000000000000000000000000{{/fraxswap_frax_weth_pair}}' + +export const FRAXSWAP_FRAX_WETH_PAIR_DEPLOY_BLOCK = BigNumber.from( + '{{ fraxswap_frax_weth_pair_deploy_block }}{{^fraxswap_frax_weth_pair_deploy_block}}14776073{{/fraxswap_frax_weth_pair_deploy_block}}' +) + +export const STABLE_WNATIVE_PAIR = + '{{ stable_wnative_pair }}{{^stable_wnative_pair}}0x0000000000000000000000000000000000000000{{/stable_wnative_pair}}' + +export const STABLE_ADDRESS = ( + '{{ stable_address }}{{^stable_address}}0x0000000000000000000000000000000000000000{{/stable_address}}' +) + +export const FRAX_ADDRESS = ( + '{{ frax_address }}{{^frax_address}}0x0000000000000000000000000000000000000000{{/frax_address}}' +) + +export const FRAX = '{{ frax_address }}{{^frax_address}}0x0000000000000000000000000000000000000000{{/frax_address}}' + + +export const WHITELIST: string[] = '{{ whitelist }}'.split(',') + +const CUSTOM_BASES = new Map() diff --git a/Ethereum/ethereum-frax/src/pricing.ts b/Ethereum/ethereum-frax/src/pricing.ts new file mode 100644 index 00000000..e2afa3bb --- /dev/null +++ b/Ethereum/ethereum-frax/src/pricing.ts @@ -0,0 +1,80 @@ +import { + MINIMUM_LIQUIDITY_THRESHOLD_ETH, + FRAX_FXS_PAIR, + FRAXSWAP_FRAX_WETH_PAIR, + FRAX, + NATIVE +} from "./packages/constants/index.template"; +import { Pair, Token } from './types' +import {EthereumBlock} from "@subql/types-ethereum/dist/ethereum/interfaces"; + +import {FraxswapFactory__factory} from "./types/contracts"; +import {BigNumber} from "ethers"; + +// No usage. +// export const uniswapFactoryContract = FraxswapFactory__factory.connect("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", api); +// export const factoryContract = FraxswapFactory__factory.connect(FACTORY_ADDRESS, api) + +export async function getFxsPrice(): Promise { + const pair = await Pair.get(FRAX_FXS_PAIR); + + if (pair) { + return BigNumber.from(pair.token0Price) + } + + return BigNumber.from(0); +} + +export async function getEthPrice(block?: EthereumBlock): Promise { + // Fetch eth prices for each stablecoin + const fraxPair = await Pair.get(FRAXSWAP_FRAX_WETH_PAIR) + + // Can do a weighted average here later + if ( + fraxPair && + fraxPair?.reserveETH && + BigNumber.from(fraxPair.reserveETH).gt(MINIMUM_LIQUIDITY_THRESHOLD_ETH) + ) { + const isFraxFirst = fraxPair.token0Id == FRAX + const fraxPairEth = isFraxFirst ? fraxPair.reserve1 : fraxPair.reserve0 + const totalLiquidityETH = fraxPairEth; + const fraxWeight = !isFraxFirst ? BigNumber.from(fraxPair.reserve0).div(totalLiquidityETH) : BigNumber.from(fraxPair.reserve1).div(totalLiquidityETH) + const fraxPrice = isFraxFirst ? fraxPair.token0Price : fraxPair.token1Price + + return BigNumber.from(fraxPrice).mul(fraxWeight); + } + + else { + logger.warning('No eth pair...', []) + return BigNumber.from(0); + } +} + +export async function findEthPerToken(token: Token): Promise { + if (token.id.toString() == NATIVE) { + return BigNumber.from(1); + } + + // const whitelist = token.whitelistPairsId + + // for (let i = 0; i < whitelist.length; ++i) { + // const pairAddress = whitelist[i]; + // const pair = await Pair.get(pairAddress); + // if (!pair) throw "Pair not found"; + // + // if (pair.token0Id == token.id && BigNumber.from(pair.reserveETH).gt(MINIMUM_LIQUIDITY_THRESHOLD_ETH)) { + // const token1 = await Token.get(pair.token1Id) + // if (!token1) throw "token1 not found"; + // + // return BigNumber.from(pair.token1Price).mul(token1.derivedETH) // return token1 per our token * Eth per token 1 + // } + // + // if (pair.token1Id == token.id && BigNumber.from(pair.reserveETH).gt(MINIMUM_LIQUIDITY_THRESHOLD_ETH)) { + // const token0 = await Token.get(pair.token0Id); + // if (!token0) throw "token0 not found"; + // return BigNumber.from(pair.token0Price).mul(token0.derivedETH) // return token0 per our token * ETH per token 0 + // } + // } + + return BigNumber.from(0) // nothing was found return 0 +} diff --git a/Ethereum/ethereum-frax/tests/.bin/fraxswap.wasm b/Ethereum/ethereum-frax/tests/.bin/fraxswap.wasm new file mode 100644 index 0000000000000000000000000000000000000000..3bb7128a2d1d19a206c45c20e3c0df44deff0ba7 GIT binary patch literal 49369 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 literal 0 HcmV?d00001 diff --git a/Ethereum/ethereum-frax/tests/.latest.json b/Ethereum/ethereum-frax/tests/.latest.json new file mode 100644 index 00000000..0568eb0d --- /dev/null +++ b/Ethereum/ethereum-frax/tests/.latest.json @@ -0,0 +1,4 @@ +{ + "version": "0.5.1", + "timestamp": 1657131739195 +} \ No newline at end of file diff --git a/Ethereum/ethereum-frax/tests/fraxswap.test.ts b/Ethereum/ethereum-frax/tests/fraxswap.test.ts new file mode 100644 index 00000000..acaffeee --- /dev/null +++ b/Ethereum/ethereum-frax/tests/fraxswap.test.ts @@ -0,0 +1,278 @@ + +import { assert, createMockedFunction, clearStore, test, newMockEvent, newMockCall, countEntities, mockIpfsFile, log } from "matchstick-as/assembly/index"; +import { Address, BigInt, Bytes, ethereum, store, Value, ipfs } from "@graphprotocol/graph-ts"; + +import { getPair } from "../src/entities"; +import { onPairCreated } from '../src/mappings/fraxswap-factory' +import { Factory, Pair } from "../generated/schema"; +import { FraxswapFactory as FactoryContract, PairCreated } from '../generated/FraxswapFactory/FraxswapFactory'; +import { FraxswapPair as PairContract } from '../generated/FraxswapFactory/FraxswapPair'; + +// Various Matchstick tests +// https://github.com/goldfinch-eng/mono/tree/23f73c886ca374ec6b843b8ed8620b508404a72d/packages/subgraph/tests + + +// test("Can mock and call function with different argument types", () => { +// let numValue = ethereum.Value.fromI32(152) +// let stringValue = ethereum.Value.fromString("example string value") +// let arrayValue = ethereum.Value.fromI32Array([156666, 123412]) +// let booleanValue = ethereum.Value.fromBoolean(true) +// let objectValue = ethereum.Value.fromAddress(Address.fromString("0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7")) +// let tupleArray: Array = [ethereum.Value.fromI32(152), ethereum.Value.fromString("string value")] +// let tuple = changetype(tupleArray) +// let tupleValue = ethereum.Value.fromTuple(tuple) + +// let argsArray: Array = [numValue, stringValue, arrayValue, booleanValue, objectValue, tupleValue] +// createMockedFunction(Address.fromString("0x90cBa2Bbb19ecc291A12066Fd8329D65FA1f1947"), "funcName", "funcName(int32, string, int32[], bool, address, (int32, string)):(void)") +// .withArgs(argsArray) +// .returns([ethereum.Value.fromString("result")]) +// let val = ethereum.call(new ethereum.SmartContractCall("conName", Address.fromString("0x90cBa2Bbb19ecc291A12066Fd8329D65FA1f1947"), "funcName", "funcName(int32, string, int32[], bool, address, (int32, string)):(void)", argsArray))![0] + +// assert.equals(ethereum.Value.fromString("result"), val) +// }) + +// test("Can test if mocked function reverts", () => { +// createMockedFunction(Address.fromString("0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7"), "revertedFunction", "revertedFunction():(void)").reverts() +// let val = ethereum.call(new ethereum.SmartContractCall("conName", Address.fromString("0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7"), "revertedFunction", "revertedFunction():(void)", [])) + +// assert.assertNull(val) +// }) + +// test("Can mock gravity function correctly", () => { +// let contractAddress = Address.fromString("0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7") +// let expectedResult = Address.fromString("0x90cBa2Bbb19ecc291A12066Fd8329D65FA1f1947") +// let bigIntParam = BigInt.fromString("1234") +// createMockedFunction(contractAddress, "gravatarToOwner", "gravatarToOwner(uint256):(address)") +// .withArgs([ethereum.Value.fromUnsignedBigInt(bigIntParam)]) +// .returns([ethereum.Value.fromAddress(Address.fromString("0x90cBa2Bbb19ecc291A12066Fd8329D65FA1f1947"))]) + +// let gravity = Gravity.bind(contractAddress) +// let result = gravity.gravatarToOwner(bigIntParam) + +// assert.addressEquals(expectedResult, result) +// }) + +// test("Should throw an error", () => { +// throw new Error() +// }, true) + +test("Can manually add FRAX/FXS", () => { + log.info('Using:\n\bblock: {}\n', [ + "14776073", + ]); + + let the_event = newMockEvent(); // https://etherscan.io/block/14776073 + the_event.block.timestamp = BigInt.fromString("1652564576"); + the_event.block.number = BigInt.fromString("14776073"); + + // @ts-ignore + // let the_pair_created: unknown; + // the_pair_created = { + // ...the_event, + // params: { + // _event: the_pair_created, + // token0: Address.fromString("0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0"), // FXS + // token1: Address.fromString("0x853d955aCEf822Db058eb8505911ED77F175b99e"), // FRAX + // pair: Address.fromString("0x8206412c107eF1aDb70B9277974f5163760E128E"), // Fraxswap FRAX/FXS + // param3: BigInt.fromString("3") + // } + // }; + + const pair = getPair( + Address.fromString("0x8206412c107eF1aDb70B9277974f5163760E128E"), // Fraxswap FRAX/FXS + the_event.block, + Address.fromString("0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0"), // FXS + Address.fromString("0x853d955aCEf822Db058eb8505911ED77F175b99e"), // FRAX + ); + + clearStore() +}) + +// test("Can call mappings with custom events", () => { +// // Initialise +// let gravatar = new Gravatar("gravatarId0") +// gravatar.save() + +// // Call mappings +// let newGravatarEvent = createNewGravatarEvent( +// 0xdead, +// "0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7", +// "cap", +// "pac", +// ) + +// let anotherGravatarEvent = createNewGravatarEvent( +// 0xbeef, +// "0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7", +// "cap", +// "pac", +// ) + +// handleNewGravatars([newGravatarEvent, anotherGravatarEvent]) + +// assert.fieldEquals( +// GRAVATAR_ENTITY_TYPE, +// "gravatarId0", +// "id", +// "gravatarId0", +// ) +// assert.fieldEquals(GRAVATAR_ENTITY_TYPE, "0xdead", "id", "0xdead") +// assert.fieldEquals(GRAVATAR_ENTITY_TYPE, "0xbeef", "id", "0xbeef") +// clearStore() +// }) + +// test("Can use entity.load() to retrieve entity from store", () => { +// let gravatar = new Gravatar("gravatarId0") +// gravatar.save() + +// let retrievedGravatar = Gravatar.load("gravatarId0") +// assert.stringEquals("gravatarId0", retrievedGravatar!.get("id")!.toString()) +// }) + +// test("Returns null when calling entity.load() if an entity doesn't exist", () => { +// let retrievedGravatar = Gravatar.load("IDoNotExist") +// assert.assertNull(retrievedGravatar) +// }) + +// test("Can update entity that already exists using Entity.save()", () => { +// let gravatar = new Gravatar("23") +// gravatar.imageUrl = "https://wow.zamimg.com/uploads/screenshots/small/659866.jpg" +// gravatar.save() + +// // Retrieve same entity from the store +// gravatar = Gravatar.load("23") as Gravatar +// gravatar.set("imageUrl", Value.fromString("https://i.ytimg.com/vi/MELP46s8Cic/maxresdefault.jpg")) +// gravatar.save() + +// assert.fieldEquals( +// GRAVATAR_ENTITY_TYPE, +// "23", +// "imageUrl", +// "https://i.ytimg.com/vi/MELP46s8Cic/maxresdefault.jpg", +// ) +// }) + +// test("Can add, get, assert and remove from store", () => { +// let gravatar = new Gravatar("23") +// gravatar.save() + +// assert.fieldEquals( +// GRAVATAR_ENTITY_TYPE, +// "23", +// "id", +// "23", +// ) + +// store.remove(GRAVATAR_ENTITY_TYPE, "23") + +// assert.notInStore(GRAVATAR_ENTITY_TYPE, "23") +// clearStore() +// }) + +// test("Can initialise event with default metadata", () => { +// let newGravatarEvent = changetype(newMockEvent()) + +// let DEFAULT_LOG_TYPE = "default_log_type" +// let DEFAULT_ADDRESS = "0xA16081F360e3847006dB660bae1c6d1b2e17eC2A" +// let DEFAULT_BLOCK_HASH = "0xA16081F360e3847006dB660bae1c6d1b2e17eC2A" +// let DEFAULT_LOG_INDEX = 1 + +// assert.stringEquals(DEFAULT_LOG_TYPE, newGravatarEvent.logType!) +// assert.addressEquals(Address.fromString(DEFAULT_ADDRESS), newGravatarEvent.address) +// assert.bigIntEquals(BigInt.fromI32(DEFAULT_LOG_INDEX), newGravatarEvent.logIndex) +// assert.bytesEquals(Bytes.fromHexString(DEFAULT_BLOCK_HASH) as Bytes, newGravatarEvent.block.hash) +// }) + +// test("Can update event metadata", () => { +// let newGravatarEvent = changetype(newMockEvent()) + +// let UPDATED_LOG_TYPE = "updated_log_type" +// let UPDATED_ADDRESS = "0xB16081F360e3847006dB660bae1c6d1b2e17eC2A" +// let UPDATED_BLOCK_HASH = "0xC16081F360e3847006dB660bae1c6d1b2e17eC2A" +// let UPDATED_LOG_INDEX = 42 + +// newGravatarEvent.logType = UPDATED_LOG_TYPE +// newGravatarEvent.address = Address.fromString(UPDATED_ADDRESS) +// newGravatarEvent.block.hash = Bytes.fromHexString( +// UPDATED_BLOCK_HASH, +// ) as Bytes +// newGravatarEvent.logIndex = BigInt.fromI32(UPDATED_LOG_INDEX) + +// assert.stringEquals(UPDATED_LOG_TYPE, newGravatarEvent.logType!) +// assert.addressEquals(Address.fromString(UPDATED_ADDRESS), newGravatarEvent.address) +// assert.bigIntEquals(BigInt.fromI32(UPDATED_LOG_INDEX), newGravatarEvent.logIndex) +// assert.bytesEquals(Bytes.fromHexString(UPDATED_BLOCK_HASH) as Bytes, newGravatarEvent.block.hash) +// }) + +// test("Can save gravatar from contract", () => { +// let contractAddress = Address.fromString("0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7") +// createMockedFunction(contractAddress, "getGravatar", "getGravatar(address):(string,string)") +// .withArgs([ethereum.Value.fromAddress(contractAddress)]) +// .returns([ethereum.Value.fromString("1st val"), ethereum.Value.fromString("2nd val")]) +// saveGravatarFromContract("48") + +// assert.fieldEquals(GRAVATAR_ENTITY_TYPE, "48", "value0", "1st val") +// assert.fieldEquals(GRAVATAR_ENTITY_TYPE, "48", "value1", "2nd val") +// }) + +// test("Can fail gracefully when saving gravatar from contract with try_getGravatar", () => { +// let contractAddress = Address.fromString("0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7") +// createMockedFunction(contractAddress, "getGravatar", "getGravatar(address):(string,string)") +// .withArgs([ethereum.Value.fromAddress(contractAddress)]) +// .reverts() +// trySaveGravatarFromContract("48") + +// assert.fieldEquals(GRAVATAR_ENTITY_TYPE, "48", "value0", "1st val") +// assert.fieldEquals(GRAVATAR_ENTITY_TYPE, "48", "value1", "2nd val") +// }) + +// test("Can save transaction from call handler", () => { +// let call = changetype(newMockCall()) +// call.inputValues = [new EthereumLogParam("displayName", ethereum.Value.fromString("name")), new EthereumLogParam("imageUrl", ethereum.Value.fromString("example.com"))] +// handleCreateGravatar(call) + +// assert.fieldEquals(TRANSACTION_ENTITY_TYPE, "0xa16081f360e3847006db660bae1c6d1b2e17ec2a", "displayName", "name") +// assert.fieldEquals(TRANSACTION_ENTITY_TYPE, "0xa16081f360e3847006db660bae1c6d1b2e17ec2a", "imageUrl", "example.com") +// }) + +// test("Can assert amount of entities of a certain type in store", () => { +// clearStore() +// assert.entityCount(GRAVATAR_ENTITY_TYPE, 0) + +// let counter = 1 +// while (countEntities(GRAVATAR_ENTITY_TYPE) < 2) { +// let newGravatar = new Gravatar("id" + counter.toString()) +// newGravatar.save() +// counter++ +// } + +// assert.entityCount(GRAVATAR_ENTITY_TYPE, 2) +// }) + +// test("ipfs.cat", () => { +// clearStore() + +// mockIpfsFile("ipfsCatfileHash", "tests/ipfs/cat.json") + +// assert.entityCount(GRAVATAR_ENTITY_TYPE, 0) + +// gravatarFromIpfs() + +// assert.entityCount(GRAVATAR_ENTITY_TYPE, 1) +// assert.fieldEquals(GRAVATAR_ENTITY_TYPE, "1", "imageUrl", "https://i.ytimg.com/vi/MELP46s8Cic/maxresdefault.jpg") + +// clearStore() +// }) + +// test("ipfs.map", () => { +// mockIpfsFile("ipfsMapfileHash", "tests/ipfs/map.json") + +// assert.entityCount(GRAVATAR_ENTITY_TYPE, 0) + +// ipfs.map("ipfsMapfileHash", 'processGravatar', Value.fromString('Gravatar'), ['json']) + +// assert.entityCount(GRAVATAR_ENTITY_TYPE, 3) +// assert.fieldEquals(GRAVATAR_ENTITY_TYPE, "1", "displayName", "Gravatar1") +// assert.fieldEquals(GRAVATAR_ENTITY_TYPE, "2", "displayName", "Gravatar2") +// assert.fieldEquals(GRAVATAR_ENTITY_TYPE, "3", "displayName", "Gravatar3") +// }) \ No newline at end of file diff --git a/Ethereum/ethereum-frax/tsconfig.json b/Ethereum/ethereum-frax/tsconfig.json new file mode 100644 index 00000000..27f321ca --- /dev/null +++ b/Ethereum/ethereum-frax/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "esModuleInterop": true, + "declaration": true, + "importHelpers": true, + "resolveJsonModule": true, + "module": "commonjs", + "outDir": "dist", + "rootDir": "src", + "target": "es2020", + "strict": true + }, + "include": [ + "src/**/*", + "node_modules/@subql/types-core/dist/global.d.ts", + "node_modules/@subql/types-ethereum/dist/global.d.ts" + ] +}