From ddc5d3422d364fae116a7ba32433fb041b845e01 Mon Sep 17 00:00:00 2001 From: godlin Date: Thu, 4 Jan 2024 12:48:09 +0100 Subject: [PATCH 1/5] Featured Memecoin Starter --- .../.github/workflows/cli-deploy.yml | 33 +++ .../fantom-memecoins/.github/workflows/pr.yml | 24 ++ .../workflows/scripts/publish-deploy.sh | 14 ++ Fantom/fantom-memecoins/.gitignore | 60 +++++ Fantom/fantom-memecoins/LICENSE | 21 ++ Fantom/fantom-memecoins/README.md | 166 +++++++++++++ Fantom/fantom-memecoins/abis/erc20.abi.json | 222 ++++++++++++++++++ Fantom/fantom-memecoins/docker-compose.yml | 67 ++++++ .../docker/load-extensions.sh | 6 + Fantom/fantom-memecoins/docker/pg-Dockerfile | 12 + Fantom/fantom-memecoins/package.json | 37 +++ Fantom/fantom-memecoins/project.ts | 90 +++++++ Fantom/fantom-memecoins/schema.graphql | 26 ++ Fantom/fantom-memecoins/src/index.ts | 8 + .../src/mappings/mappingHandlers.ts | 67 ++++++ Fantom/fantom-memecoins/tsconfig.json | 20 ++ 16 files changed, 873 insertions(+) create mode 100644 Fantom/fantom-memecoins/.github/workflows/cli-deploy.yml create mode 100644 Fantom/fantom-memecoins/.github/workflows/pr.yml create mode 100644 Fantom/fantom-memecoins/.github/workflows/scripts/publish-deploy.sh create mode 100644 Fantom/fantom-memecoins/.gitignore create mode 100644 Fantom/fantom-memecoins/LICENSE create mode 100644 Fantom/fantom-memecoins/README.md create mode 100644 Fantom/fantom-memecoins/abis/erc20.abi.json create mode 100644 Fantom/fantom-memecoins/docker-compose.yml create mode 100644 Fantom/fantom-memecoins/docker/load-extensions.sh create mode 100644 Fantom/fantom-memecoins/docker/pg-Dockerfile create mode 100644 Fantom/fantom-memecoins/package.json create mode 100644 Fantom/fantom-memecoins/project.ts create mode 100644 Fantom/fantom-memecoins/schema.graphql create mode 100644 Fantom/fantom-memecoins/src/index.ts create mode 100644 Fantom/fantom-memecoins/src/mappings/mappingHandlers.ts create mode 100644 Fantom/fantom-memecoins/tsconfig.json diff --git a/Fantom/fantom-memecoins/.github/workflows/cli-deploy.yml b/Fantom/fantom-memecoins/.github/workflows/cli-deploy.yml new file mode 100644 index 00000000..5edba7c0 --- /dev/null +++ b/Fantom/fantom-memecoins/.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/Fantom/fantom-memecoins/.github/workflows/pr.yml b/Fantom/fantom-memecoins/.github/workflows/pr.yml new file mode 100644 index 00000000..7a3166c7 --- /dev/null +++ b/Fantom/fantom-memecoins/.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/Fantom/fantom-memecoins/.github/workflows/scripts/publish-deploy.sh b/Fantom/fantom-memecoins/.github/workflows/scripts/publish-deploy.sh new file mode 100644 index 00000000..adae267d --- /dev/null +++ b/Fantom/fantom-memecoins/.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/Fantom/fantom-memecoins/.gitignore b/Fantom/fantom-memecoins/.gitignore new file mode 100644 index 00000000..53b19635 --- /dev/null +++ b/Fantom/fantom-memecoins/.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/Fantom/fantom-memecoins/LICENSE b/Fantom/fantom-memecoins/LICENSE new file mode 100644 index 00000000..76e70ccd --- /dev/null +++ b/Fantom/fantom-memecoins/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/Fantom/fantom-memecoins/README.md b/Fantom/fantom-memecoins/README.md new file mode 100644 index 00000000..d3fe634f --- /dev/null +++ b/Fantom/fantom-memecoins/README.md @@ -0,0 +1,166 @@ +# SubQuery - Memecoin Example Project for Fantom Opera + +[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 [CHILL Token](https://ftmscan.com/address/0xe47d957F83F8887063150AaF7187411351643392) on Fantom Opera Network. Moreover, it tracks token holders' balances.** + +## 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.ts` 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 + } + } +} +``` + +Your result should look similar to the following: + +```json +{ + "data": { + "query": { + "transfers": { + "totalCount": 459, + "nodes": [ + { + "id": "0x57b54d4bf53caca4c60772761f4949e4dc02d92f62a02b180d5b382d50b7787d", + "blockHeight": "67295406", + "from": "0x31F63A33141fFee63D4B26755430a390ACdD8a4d", + "to": "0x0000000000000000000000000000000000000000", + "value": "176970961833699983570796", + "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" + }, + { + "id": "0x128198372b0080d144f01041bdeb97e39155981010337abc8dc18878727af227", + "blockHeight": "67295494", + "from": "0x31F63A33141fFee63D4B26755430a390ACdD8a4d", + "to": "0x4EE115137ac73A3e5F99598564905465C101b11F", + "value": "160977046912584985744989", + "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" + }, + { + "id": "0xaca3354ec2d60bc8816590e32c755a87269a07d1eef7c7a49f808d9d6aee9f18", + "blockHeight": "67296279", + "from": "0x38C2853E569125Fc9Af310Ab145FCEfB2A07A322", + "to": "0x0000000000000000000000000000000000000000", + "value": "10000000000000000000000", + "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" + }, + { + "id": "0x5f549d1546f590146b87091c9bdfde18ff1f3d33b6ed852fc454af810a4c0e32", + "blockHeight": "67296232", + "from": "0x5BAB9d61f84630A76fA9e2f67739f2da694B5402", + "to": "0x245cD6d33578de9aF75a3C0c636c726b1A8cbdAa", + "value": "6996500000000000000000", + "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" + }, + { + "id": "0x6158b4cc15013f08e89c91cef0d1610cd37d7d303126299900689790ecb8124e", + "blockHeight": "67295446", + "from": "0x31F63A33141fFee63D4B26755430a390ACdD8a4d", + "to": "0x0000000000000000000000000000000000000000", + "value": "6844335953031983950296", + "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" + } + ] + } + }, + "approvals": { + "nodes": [ + { + "id": "0x7e08e7e27996561ba385b9ffc6a9a02d51ad17a22a9bbb9e79a6ad059f269720", + "blockHeight": null, + "owner": "0xDEc89FC2ECfF1F2197204126EaAc55043155153b", + "spender": "0x1111111254EEB25477B68fb85Ed929f73A960582", + "value": "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" + }, + { + "id": "0xa00b913d56a1e91a6fdc52e05f56db54e518a1fbbd81e94ccc4b0d3521c72c53", + "blockHeight": null, + "owner": "0xDEc89FC2ECfF1F2197204126EaAc55043155153b", + "spender": "0x1111111254EEB25477B68fb85Ed929f73A960582", + "value": "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" + } + ] + } + } +} +``` + +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/Fantom/fantom-memecoins/abis/erc20.abi.json b/Fantom/fantom-memecoins/abis/erc20.abi.json new file mode 100644 index 00000000..405d6b36 --- /dev/null +++ b/Fantom/fantom-memecoins/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/Fantom/fantom-memecoins/docker-compose.yml b/Fantom/fantom-memecoins/docker-compose.yml new file mode 100644 index 00000000..209cf449 --- /dev/null +++ b/Fantom/fantom-memecoins/docker-compose.yml @@ -0,0 +1,67 @@ +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: 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 + - --workers=4 + - --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/Fantom/fantom-memecoins/docker/load-extensions.sh b/Fantom/fantom-memecoins/docker/load-extensions.sh new file mode 100644 index 00000000..7f5d0206 --- /dev/null +++ b/Fantom/fantom-memecoins/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 is the EVM Chain ID, for Fantom Opera this is 250 + * https://chainlist.org/chain/250 + */ + chainId: "250", + /** + * 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://fantom-pokt.nodies.app"], + }, + dataSources: [ + { + kind: EthereumDatasourceKind.Runtime, + startBlock: 73694090, + options: { + // Must be a key of assets + abi: "erc20", + // This is the contract address for CHILL Token https://ftmscan.com/address/0xe47d957F83F8887063150AaF7187411351643392 + address: "0xe47d957F83F8887063150AaF7187411351643392", + }, + assets: new Map([["erc20", { file: "./abis/erc20.abi.json" }]]), + mapping: { + file: "./dist/index.js", + handlers: [ + { + kind: EthereumHandlerKind.Call, + handler: "handleTransaction", + filter: { + /** + * The function can either be the function fragment or signature + * function: '0x095ea7b3' + * function: '0x7ff36ab500000000000000000000000000000000000000000000000000000000' + */ + function: "approve(address spender, uint256 rawAmount)", + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleLog", + filter: { + /** + * Follows standard log filters https://docs.ethers.io/v5/concepts/events/ + * address: "0x60781C2586D68229fde47564546784ab3fACA982" + */ + topics: [ + "Transfer(address indexed from, address indexed to, uint256 amount)", + ], + }, + }, + ], + }, + }, + ], + repository: "https://github.com/subquery/ethereum-subql-starter", +}; + +// Must set default to the project instance +export default project; diff --git a/Fantom/fantom-memecoins/schema.graphql b/Fantom/fantom-memecoins/schema.graphql new file mode 100644 index 00000000..af1cec28 --- /dev/null +++ b/Fantom/fantom-memecoins/schema.graphql @@ -0,0 +1,26 @@ +# To improve query performance, we strongly suggest adding indexes to any field that you plan to filter or sort by +# Add the `@index` or `@index(unique: true)` annotation after any non-key field +# https://academy.subquery.network/build/graphql.html#indexing-by-non-primary-key-field + +type Transfer @entity { + id: ID! # Transaction hash + blockHeight: BigInt + to: Address! + from: Address! + value: BigInt! + contractAddress: String! +} + +type Approval @entity { + id: ID! # Transaction hash + blockHeight: BigInt + owner: Address! + spender: Address! + value: BigInt! + contractAddress: String! +} + +type Address @entity { + id: ID! + balance: BigInt! +} diff --git a/Fantom/fantom-memecoins/src/index.ts b/Fantom/fantom-memecoins/src/index.ts new file mode 100644 index 00000000..6a240b55 --- /dev/null +++ b/Fantom/fantom-memecoins/src/index.ts @@ -0,0 +1,8 @@ +import { atob } from "abab"; + +if (!global.atob) { + global.atob = atob as any; +} + +//Exports all handler functions +export * from "./mappings/mappingHandlers"; diff --git a/Fantom/fantom-memecoins/src/mappings/mappingHandlers.ts b/Fantom/fantom-memecoins/src/mappings/mappingHandlers.ts new file mode 100644 index 00000000..fef2d615 --- /dev/null +++ b/Fantom/fantom-memecoins/src/mappings/mappingHandlers.ts @@ -0,0 +1,67 @@ +import { Approval, Transfer, Address } from "../types"; +import { + ApproveTransaction, + TransferLog, +} from "../types/abi-interfaces/Erc20Abi"; +import assert from "assert"; +import { Erc20Abi__factory } from "../types/contracts"; + +const erc20 = Erc20Abi__factory.connect( + // This argument needs to match precisely with the one found in `dataSources`.`options`.`address` within `project.ts`. + "0xe47d957F83F8887063150AaF7187411351643392", + api +); + +async function getOrCreateAddress(accountAddress: string): Promise
{ + let address = await Address.get(accountAddress); + + if (address == undefined) { + address = Address.create({ + id: accountAddress, + balance: (await erc20.balanceOf(accountAddress)).toBigInt(), + }); + } + + address.save(); + return address; +} + +export async function handleLog(log: TransferLog): Promise { + logger.info(`New transfer transaction log at block ${log.blockNumber}`); + assert(log.args, "No log.args"); + + let from = await getOrCreateAddress(log.args.from); + let to = await getOrCreateAddress(log.args.to); + let value = log.args.value.toBigInt(); + + const transfer = Transfer.create({ + id: log.transactionHash + "-" + log.logIndex, + blockHeight: BigInt(log.blockNumber), + toId: to.id, + fromId: from.id, + value: value, + contractAddress: log.address, + }); + + from.balance = BigInt(from.balance) - value; + to.balance = BigInt(to.balance) + value; + + await transfer.save(); + await from.save(); + await to.save(); +} + +export async function handleTransaction(tx: ApproveTransaction): Promise { + logger.info(`New Approval transaction at block ${tx.blockNumber}`); + assert(tx.args, "No tx.args"); + + const approval = Approval.create({ + id: tx.hash, + ownerId: (await getOrCreateAddress(tx.from)).id, + spenderId: (await getOrCreateAddress(tx.args[0])).id, + value: BigInt(await tx.args[1].toString()), + contractAddress: tx.to, + }); + + await approval.save(); +} diff --git a/Fantom/fantom-memecoins/tsconfig.json b/Fantom/fantom-memecoins/tsconfig.json new file mode 100644 index 00000000..90e06e25 --- /dev/null +++ b/Fantom/fantom-memecoins/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": "es2017", + "strict": true + }, + "include": [ + "src/**/*", + "node_modules/@subql/types-core/dist/global.d.ts", + "node_modules/@subql/types-ethereum/dist/global.d.ts" + ] +} From 6eb9d3481140443f882422d7d805cbeffc371af8 Mon Sep 17 00:00:00 2001 From: godlin Date: Thu, 4 Jan 2024 12:50:28 +0100 Subject: [PATCH 2/5] Fixed Queries --- Fantom/fantom-memecoins/README.md | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Fantom/fantom-memecoins/README.md b/Fantom/fantom-memecoins/README.md index d3fe634f..2157e7b8 100644 --- a/Fantom/fantom-memecoins/README.md +++ b/Fantom/fantom-memecoins/README.md @@ -46,8 +46,8 @@ For this project, you can try to query with the following GraphQL code to get a nodes { id blockHeight - from - to + fromId + toId value contractAddress } @@ -57,8 +57,8 @@ For this project, you can try to query with the following GraphQL code to get a nodes { id blockHeight - owner - spender + ownerId + spenderId value contractAddress } @@ -78,40 +78,40 @@ Your result should look similar to the following: { "id": "0x57b54d4bf53caca4c60772761f4949e4dc02d92f62a02b180d5b382d50b7787d", "blockHeight": "67295406", - "from": "0x31F63A33141fFee63D4B26755430a390ACdD8a4d", - "to": "0x0000000000000000000000000000000000000000", + "fromId": "0x31F63A33141fFee63D4B26755430a390ACdD8a4d", + "toId": "0x0000000000000000000000000000000000000000", "value": "176970961833699983570796", "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" }, { "id": "0x128198372b0080d144f01041bdeb97e39155981010337abc8dc18878727af227", "blockHeight": "67295494", - "from": "0x31F63A33141fFee63D4B26755430a390ACdD8a4d", - "to": "0x4EE115137ac73A3e5F99598564905465C101b11F", + "fromId": "0x31F63A33141fFee63D4B26755430a390ACdD8a4d", + "toId": "0x4EE115137ac73A3e5F99598564905465C101b11F", "value": "160977046912584985744989", "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" }, { "id": "0xaca3354ec2d60bc8816590e32c755a87269a07d1eef7c7a49f808d9d6aee9f18", "blockHeight": "67296279", - "from": "0x38C2853E569125Fc9Af310Ab145FCEfB2A07A322", - "to": "0x0000000000000000000000000000000000000000", + "fromId": "0x38C2853E569125Fc9Af310Ab145FCEfB2A07A322", + "toId": "0x0000000000000000000000000000000000000000", "value": "10000000000000000000000", "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" }, { "id": "0x5f549d1546f590146b87091c9bdfde18ff1f3d33b6ed852fc454af810a4c0e32", "blockHeight": "67296232", - "from": "0x5BAB9d61f84630A76fA9e2f67739f2da694B5402", - "to": "0x245cD6d33578de9aF75a3C0c636c726b1A8cbdAa", + "fromId": "0x5BAB9d61f84630A76fA9e2f67739f2da694B5402", + "toId": "0x245cD6d33578de9aF75a3C0c636c726b1A8cbdAa", "value": "6996500000000000000000", "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" }, { "id": "0x6158b4cc15013f08e89c91cef0d1610cd37d7d303126299900689790ecb8124e", "blockHeight": "67295446", - "from": "0x31F63A33141fFee63D4B26755430a390ACdD8a4d", - "to": "0x0000000000000000000000000000000000000000", + "fromId": "0x31F63A33141fFee63D4B26755430a390ACdD8a4d", + "toId": "0x0000000000000000000000000000000000000000", "value": "6844335953031983950296", "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" } @@ -123,16 +123,16 @@ Your result should look similar to the following: { "id": "0x7e08e7e27996561ba385b9ffc6a9a02d51ad17a22a9bbb9e79a6ad059f269720", "blockHeight": null, - "owner": "0xDEc89FC2ECfF1F2197204126EaAc55043155153b", - "spender": "0x1111111254EEB25477B68fb85Ed929f73A960582", + "ownerId": "0xDEc89FC2ECfF1F2197204126EaAc55043155153b", + "spenderId": "0x1111111254EEB25477B68fb85Ed929f73A960582", "value": "115792089237316195423570985008687907853269984665640564039457584007913129639935", "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" }, { "id": "0xa00b913d56a1e91a6fdc52e05f56db54e518a1fbbd81e94ccc4b0d3521c72c53", "blockHeight": null, - "owner": "0xDEc89FC2ECfF1F2197204126EaAc55043155153b", - "spender": "0x1111111254EEB25477B68fb85Ed929f73A960582", + "ownerId": "0xDEc89FC2ECfF1F2197204126EaAc55043155153b", + "spenderId": "0x1111111254EEB25477B68fb85Ed929f73A960582", "value": "115792089237316195423570985008687907853269984665640564039457584007913129639935", "contractAddress": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83" } From 737699ea77d14af1ac0b0a64aae237ff1195c5d3 Mon Sep 17 00:00:00 2001 From: godlin Date: Thu, 4 Jan 2024 13:11:30 +0100 Subject: [PATCH 3/5] Contract Address as `const` --- Fantom/fantom-memecoins/project.ts | 3 ++- Fantom/fantom-memecoins/src/const.ts | 2 ++ Fantom/fantom-memecoins/src/mappings/mappingHandlers.ts | 7 ++----- 3 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 Fantom/fantom-memecoins/src/const.ts diff --git a/Fantom/fantom-memecoins/project.ts b/Fantom/fantom-memecoins/project.ts index 8f447e31..c05508a7 100644 --- a/Fantom/fantom-memecoins/project.ts +++ b/Fantom/fantom-memecoins/project.ts @@ -3,6 +3,7 @@ import { EthereumDatasourceKind, EthereumHandlerKind, } from "@subql/types-ethereum"; +import { contractAddress } from "./src/const"; // Can expand the Datasource processor types via the generic param const project: EthereumProject = { @@ -48,7 +49,7 @@ const project: EthereumProject = { // Must be a key of assets abi: "erc20", // This is the contract address for CHILL Token https://ftmscan.com/address/0xe47d957F83F8887063150AaF7187411351643392 - address: "0xe47d957F83F8887063150AaF7187411351643392", + address: contractAddress, }, assets: new Map([["erc20", { file: "./abis/erc20.abi.json" }]]), mapping: { diff --git a/Fantom/fantom-memecoins/src/const.ts b/Fantom/fantom-memecoins/src/const.ts new file mode 100644 index 00000000..95136fc1 --- /dev/null +++ b/Fantom/fantom-memecoins/src/const.ts @@ -0,0 +1,2 @@ +export const contractAddress: string = + "0xe47d957F83F8887063150AaF7187411351643392"; diff --git a/Fantom/fantom-memecoins/src/mappings/mappingHandlers.ts b/Fantom/fantom-memecoins/src/mappings/mappingHandlers.ts index fef2d615..dce0a925 100644 --- a/Fantom/fantom-memecoins/src/mappings/mappingHandlers.ts +++ b/Fantom/fantom-memecoins/src/mappings/mappingHandlers.ts @@ -5,12 +5,9 @@ import { } from "../types/abi-interfaces/Erc20Abi"; import assert from "assert"; import { Erc20Abi__factory } from "../types/contracts"; +import { contractAddress } from "../const"; -const erc20 = Erc20Abi__factory.connect( - // This argument needs to match precisely with the one found in `dataSources`.`options`.`address` within `project.ts`. - "0xe47d957F83F8887063150AaF7187411351643392", - api -); +const erc20 = Erc20Abi__factory.connect(contractAddress, api); async function getOrCreateAddress(accountAddress: string): Promise
{ let address = await Address.get(accountAddress); From 9da516b3ade737bc725544c6470e3f2edd93c069 Mon Sep 17 00:00:00 2001 From: godlin Date: Fri, 5 Jan 2024 16:19:46 +0100 Subject: [PATCH 4/5] example query updated --- Fantom/fantom-memecoins/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Fantom/fantom-memecoins/README.md b/Fantom/fantom-memecoins/README.md index 2157e7b8..5913b879 100644 --- a/Fantom/fantom-memecoins/README.md +++ b/Fantom/fantom-memecoins/README.md @@ -63,6 +63,12 @@ For this project, you can try to query with the following GraphQL code to get a contractAddress } } + addresses(first: 5) { + nodes { + id + balance + } + } } ``` From 9aefc8b720d06e74673c3523861e61a25eceea4e Mon Sep 17 00:00:00 2001 From: godlin Date: Fri, 5 Jan 2024 16:20:22 +0100 Subject: [PATCH 5/5] performance improved; fixes --- .../src/mappings/mappingHandlers.ts | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/Fantom/fantom-memecoins/src/mappings/mappingHandlers.ts b/Fantom/fantom-memecoins/src/mappings/mappingHandlers.ts index dce0a925..5e2e1b75 100644 --- a/Fantom/fantom-memecoins/src/mappings/mappingHandlers.ts +++ b/Fantom/fantom-memecoins/src/mappings/mappingHandlers.ts @@ -27,25 +27,35 @@ export async function handleLog(log: TransferLog): Promise { logger.info(`New transfer transaction log at block ${log.blockNumber}`); assert(log.args, "No log.args"); - let from = await getOrCreateAddress(log.args.from); - let to = await getOrCreateAddress(log.args.to); + const [from, to, fromBalance, toBalance] = await Promise.all([ + getOrCreateAddress(log.args.from), + getOrCreateAddress(log.args.to), + (await erc20.balanceOf(log.args.from)).toBigInt(), + (await erc20.balanceOf(log.args.to)).toBigInt(), + ]); let value = log.args.value.toBigInt(); - const transfer = Transfer.create({ - id: log.transactionHash + "-" + log.logIndex, - blockHeight: BigInt(log.blockNumber), - toId: to.id, - fromId: from.id, - value: value, - contractAddress: log.address, - }); - - from.balance = BigInt(from.balance) - value; - to.balance = BigInt(to.balance) + value; - - await transfer.save(); - await from.save(); - await to.save(); + await Promise.all([ + (async () => { + const transfer = Transfer.create({ + id: log.transactionHash + "-" + log.logIndex, + blockHeight: BigInt(log.blockNumber), + toId: to.id, + fromId: from.id, + value: value, + contractAddress: log.address, + }); + await transfer.save(); + })(), + (async () => { + from.balance = fromBalance; + await from.save(); + })(), + (async () => { + to.balance = toBalance; + await to.save(); + })(), + ]); } export async function handleTransaction(tx: ApproveTransaction): Promise {