├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .graphqlrc.yml ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── .prettierrc.json ├── .project-cid ├── .project-mainnet-cid ├── .project-testnet-cid ├── LICENSE ├── README.md ├── docker-compose-testnet.yml ├── docker-compose.yml ├── docker ├── load-extensions.sh └── pg-Dockerfile ├── package.json ├── project-mainnet.yaml ├── project-testnet.yaml ├── schema.graphql ├── scripts ├── checkFilters.ts ├── fix-webpack-ethers └── postinstall.sh ├── src ├── index.ts ├── interfaces.ts └── mappings │ ├── airdropper.ts │ ├── consumerRegistry.ts │ ├── disputeManager.ts │ ├── eraManager.ts │ ├── index.ts │ ├── indexerRegistry.ts │ ├── l2SQToken.ts │ ├── planManager.ts │ ├── priceOracle.ts │ ├── projectRegistry.ts │ ├── purchaseOffer.ts │ ├── rewardsBooster.ts │ ├── rewardsDistributor.ts │ ├── rewardsStaking.ts │ ├── serviceAgreement.ts │ ├── staking.ts │ ├── stakingAllocation.ts │ ├── stateChannel.ts │ ├── tokenExchange.ts │ ├── transfer.ts │ └── utils │ ├── cache.ts │ ├── constants.ts │ ├── enumToTypes.ts │ ├── enums.ts │ ├── helpers.ts │ ├── index.ts │ └── updateDbFunctions.ts ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | .data 3 | src/types 4 | node_modules 5 | .eslintrc.js 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: [ 7 | 'eslint:recommended', 8 | 'prettier', 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 11 | ], 12 | parser: '@typescript-eslint/parser', 13 | parserOptions: { 14 | ecmaVersion: 'latest', 15 | sourceType: 'module', 16 | project: ['tsconfig.json'], 17 | }, 18 | plugins: ['@typescript-eslint', 'header'], 19 | rules: { 20 | '@typescript-eslint/restrict-template-expressions': [ 21 | 'error', 22 | { 23 | allowNumber: true, 24 | allowBoolean: true, 25 | allowAny: false, 26 | allowNullish: true, 27 | allowRegExp: false, 28 | }, 29 | ], 30 | '@typescript-eslint/no-unsafe-assignment': 'off', 31 | '@typescript-eslint/no-unsafe-call': 'off', 32 | '@typescript-eslint/no-unsafe-member-access': 'warnning', 33 | 'header/header': [ 34 | 2, 35 | 'line', 36 | [ 37 | { 38 | pattern: 39 | ' Copyright \\d{4}(-\\d{4})? SubQuery Pte Ltd authors & contributors', 40 | template: 41 | ' Copyright 2020-2023 SubQuery Pte Ltd authors & contributors', 42 | }, 43 | ' SPDX-License-Identifier: Apache-2.0', 44 | ], 45 | 2, 46 | ], 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # These are some examples of commonly ignored file patterns. 2 | # You should customize this list as applicable to your project. 3 | # Learn more about .gitignore: 4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore 5 | 6 | # Node artifact files 7 | node_modules/ 8 | dist/ 9 | 10 | # lock files 11 | package-lock.json 12 | 13 | # Compiled Java class files 14 | *.class 15 | 16 | # Compiled Python bytecode 17 | *.py[cod] 18 | 19 | # Log files 20 | *.log 21 | 22 | # Package files 23 | *.jar 24 | 25 | # Maven 26 | target/ 27 | dist/ 28 | src/types 29 | 30 | # JetBrains IDE 31 | .idea/ 32 | 33 | # Unit test reports 34 | TEST*.xml 35 | 36 | # Generated by MacOS 37 | .DS_Store 38 | 39 | # Generated by Windows 40 | Thumbs.db 41 | 42 | # Applications 43 | *.app 44 | *.exe 45 | *.war 46 | 47 | # Large media files 48 | *.mp4 49 | *.tiff 50 | *.avi 51 | *.flv 52 | *.mov 53 | *.wmv 54 | 55 | .data 56 | .eslintcache 57 | docker-compose-mainnet.yml 58 | docker-compose-testnet.yml -------------------------------------------------------------------------------- /.graphqlrc.yml: -------------------------------------------------------------------------------- 1 | schema: 'schema.graphql' 2 | documents: 'src/**/*.{graphql,js,ts,jsx,tsx}' 3 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | .data 3 | src/types 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /.project-cid: -------------------------------------------------------------------------------- 1 | QmbyQsXbUbjjHCWUat2YByRiEiFzahEA2srsabT2cT18Jf 2 | -------------------------------------------------------------------------------- /.project-mainnet-cid: -------------------------------------------------------------------------------- 1 | QmWLxEHx7KsybRRrMhhwTyhZ8rQgQ1LQQYjprUWqCE1spK -------------------------------------------------------------------------------- /.project-testnet-cid: -------------------------------------------------------------------------------- 1 | QmYsYBsmNLARsiCgjErtbojECTeF1iiiVbK4GDGV4dBPNU -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SubQuery - Network App Backend Project 2 | 3 | [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). 4 | 5 | **This SubQuery project indexes all data required by the SubQuery Network App** 6 | 7 | ## Start 8 | 9 | First, install SubQuery CLI globally on your terminal by using NPM `npm install -g @subql/cli` 10 | 11 | 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. 12 | 13 | Don't forget to install dependencies with `npm install` or `yarn install`! 14 | 15 | ## Editing your SubQuery project 16 | 17 | Although this is a working example SubQuery project, you can edit the SubQuery project by changing the following files: 18 | 19 | - The project manifest in `project.yaml` defines the key project configuration and mapping handler filters 20 | - The GraphQL Schema (`schema.graphql`) defines the shape of the resulting data that you are using SubQuery to index 21 | - The Mapping functions in `src/mappings/` directory are typescript functions that handle transformation logic 22 | 23 | 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. 24 | 25 | ## Run your project 26 | 27 | _If you get stuck, find out how to get help below._ 28 | 29 | The simplest way to run your project is by running `yarn dev` or `npm run-script dev`. This does all of the following: 30 | 31 | 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 32 | 2. `yarn build` - Builds and packages the SubQuery project into the `/dist` directory 33 | 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` 34 | 35 | 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). 36 | 37 | ## Building for Networks 38 | 39 | For `Testnet`: 40 | 41 | - `yarn` 42 | - `yarn codegen:testnet` 43 | - `yarn build:testnet` 44 | - `yarn deploy:testnet` 45 | 46 | For `Mainnet` 47 | 48 | - `yarn codegen:mainnet` 49 | - `yarn build:mainnet` 50 | - `yarn deploy:mainnet` 51 | 52 | ## Publish your project 53 | 54 | SubQuery is open-source, meaning you have the freedom to run it in the following three ways: 55 | 56 | - 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) 57 | - 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) 58 | - [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 59 | 60 | ``` 61 | export SUBQL_ACCESS_TOKEN= (replace with your own token) 62 | yarn deploy:testnet or yarn deploy:mainnet 63 | ``` 64 | 65 | ## What Next? 66 | 67 | Take a look at some of our advanced features to take your project to the next level! 68 | 69 | - [**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. 70 | - [**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. 71 | - [**Project Optimisation Advice**](https://academy.subquery.network/build/optimisation.html) - Some common tips on how to tweak your project to maximise performance. 72 | - [**GraphQL Subscriptions**](https://academy.subquery.network/run_publish/subscription.html) - Build more reactive front end applications that subscribe to changes in your SubQuery project. 73 | 74 | ## Need Help? 75 | 76 | 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. 77 | -------------------------------------------------------------------------------- /docker-compose-testnet.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | postgres: 5 | build: 6 | context: . 7 | dockerfile: ./docker/pg-Dockerfile 8 | # ports: 9 | # - 5432:5432 10 | volumes: 11 | - .data/postgres:/var/lib/postgresql/data 12 | environment: 13 | POSTGRES_PASSWORD: postgres 14 | healthcheck: 15 | test: ['CMD-SHELL', 'pg_isready -U postgres'] 16 | interval: 5s 17 | timeout: 5s 18 | retries: 5 19 | 20 | subquery-node: 21 | image: onfinality/subql-node-ethereum:v4.1.1 22 | depends_on: 23 | 'postgres': 24 | condition: service_healthy 25 | restart: always 26 | environment: 27 | DB_USER: postgres 28 | DB_PASS: postgres 29 | DB_DATABASE: postgres 30 | DB_HOST: postgres 31 | DB_PORT: 5432 32 | volumes: 33 | - ./:/app 34 | command: 35 | - -f=/app/project-testnet.yaml 36 | - --db-schema=app 37 | healthcheck: 38 | test: ['CMD', 'curl', '-f', 'http://subquery-node:3000/ready'] 39 | interval: 3s 40 | timeout: 5s 41 | retries: 10 42 | 43 | graphql-engine: 44 | image: onfinality/subql-query:latest 45 | ports: 46 | - 3000:3000 47 | depends_on: 48 | 'postgres': 49 | condition: service_healthy 50 | 'subquery-node': 51 | condition: service_healthy 52 | restart: always 53 | environment: 54 | DB_USER: postgres 55 | DB_PASS: postgres 56 | DB_DATABASE: postgres 57 | DB_HOST: postgres 58 | DB_PORT: 5432 59 | command: 60 | - --name=app 61 | - --playground 62 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | postgres: 5 | build: 6 | context: . 7 | dockerfile: ./docker/pg-Dockerfile 8 | # ports: 9 | # - 5432:5432 10 | volumes: 11 | - .data/postgres:/var/lib/postgresql/data 12 | environment: 13 | POSTGRES_PASSWORD: postgres 14 | healthcheck: 15 | test: ['CMD-SHELL', 'pg_isready -U postgres'] 16 | interval: 5s 17 | timeout: 5s 18 | retries: 5 19 | 20 | subquery-node: 21 | image: onfinality/subql-node-ethereum:v4.1.1 22 | depends_on: 23 | 'postgres': 24 | condition: service_healthy 25 | restart: always 26 | environment: 27 | DB_USER: postgres 28 | DB_PASS: postgres 29 | DB_DATABASE: postgres 30 | DB_HOST: postgres 31 | DB_PORT: 5432 32 | volumes: 33 | - ./:/app 34 | command: 35 | - -f=/app/project-mainnet.yaml 36 | - --db-schema=app 37 | healthcheck: 38 | test: ['CMD', 'curl', '-f', 'http://subquery-node:3000/ready'] 39 | interval: 3s 40 | timeout: 5s 41 | retries: 10 42 | 43 | graphql-engine: 44 | image: onfinality/subql-query:latest 45 | ports: 46 | - 3000:3000 47 | depends_on: 48 | 'postgres': 49 | condition: service_healthy 50 | 'subquery-node': 51 | condition: service_healthy 52 | restart: always 53 | environment: 54 | DB_USER: postgres 55 | DB_PASS: postgres 56 | DB_DATABASE: postgres 57 | DB_HOST: postgres 58 | DB_PORT: 5432 59 | command: 60 | - --name=app 61 | - --playground 62 | -------------------------------------------------------------------------------- /docker/load-extensions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <=6", 41 | "js-yaml": "^4.1.0", 42 | "lint-staged": ">=10", 43 | "prettier": "2.5.1", 44 | "ts-node": "^10.7.0", 45 | "typechain": "^8.1.1", 46 | "typescript": "4.5.5" 47 | }, 48 | "dependencies": { 49 | "@ethersproject/abi": "^5.7.0", 50 | "@subql/contract-sdk": "^1.1.1-0", 51 | "@subql/types-ethereum": "^3.5.0", 52 | "@types/pino": "^7.0.5", 53 | "@types/validator": "latest", 54 | "bignumber.js": "^9.1.2", 55 | "bs58": "^4.0.1", 56 | "ethers": "^5.7.2", 57 | "pino": "^7.8.0" 58 | }, 59 | "resolutions": { 60 | "class-transformer": "0.4.0", 61 | "ipfs-unixfs": "6.0.6", 62 | "@ethersproject/base64": "5.7.0", 63 | "@polkadot/util": "12.3.2", 64 | "@polkadot/util-crypto": "12.3.2" 65 | }, 66 | "lint-staged": { 67 | "*.{ts,css,md}": "prettier --write" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /project-mainnet.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 1.0.0 2 | name: subquery-network-subql-project 3 | version: 1.0.0 4 | runner: 5 | node: 6 | name: '@subql/node-ethereum' 7 | version: '>= 4.0.0' 8 | options: 9 | unfinalizedBlocks: true 10 | query: 11 | name: '@subql/query' 12 | version: '*' 13 | description: 'Query registry project for SubQuery Mainnet' 14 | repository: '' 15 | schema: 16 | file: ./schema.graphql 17 | network: 18 | chainId: '8453' # base 19 | endpoint: https://mainnet.base.org 20 | dictionary: '' 21 | x-ethereum: ðereum 22 | kind: ethereum/Runtime 23 | startBlock: 10512216 24 | 25 | assets: 26 | eraManager: 27 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/l2/EraManager.sol/EraManager.json 28 | staking: 29 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/Staking.sol/Staking.json 30 | sqtoken: 31 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/l2/L2SQToken.sol/L2SQToken.json 32 | indexerRegistry: 33 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/IndexerRegistry.sol/IndexerRegistry.json 34 | projectRegistry: 35 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/ProjectRegistry.sol/ProjectRegistry.json 36 | planManager: 37 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/PlanManager.sol/PlanManager.json 38 | purchaseOfferMarket: 39 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/PurchaseOfferMarket.sol/PurchaseOfferMarket.json 40 | serviceAgreementRegistry: 41 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/ServiceAgreementRegistry.sol/ServiceAgreementRegistry.json 42 | consumerRegistry: 43 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/ConsumerRegistry.sol/ConsumerRegistry.json 44 | rewardsDistributor: 45 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/RewardsDistributor.sol/RewardsDistributor.json 46 | rewardsStaking: 47 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/RewardsStaking.sol/RewardsStaking.json 48 | stateChannel: 49 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/StateChannel.sol/StateChannel.json 50 | disputeManager: 51 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/DisputeManager.sol/DisputeManager.json 52 | priceOracle: 53 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/PriceOracle.sol/PriceOracle.json 54 | tokenExchange: 55 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/TokenExchange.sol/TokenExchange.json 56 | rewardsBooster: 57 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/RewardsBooster.sol/RewardsBooster.json 58 | stakingAllocation: 59 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/StakingAllocation.sol/StakingAllocation.json 60 | airdropper: 61 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/Airdropper.sol/Airdropper.json 62 | l2SQToken: 63 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/l2/L2SQToken.sol/L2SQToken.json 64 | 65 | dataSources: 66 | - <<: *ethereum 67 | options: 68 | abi: eraManager 69 | address: '0xf80A8E45a13D2C116Aa8cFFEf3dcb6049cd8059e' 70 | mapping: 71 | file: ./dist/index.js 72 | handlers: 73 | - handler: handleNewEra 74 | kind: ethereum/LogHandler 75 | filter: 76 | topics: 77 | - NewEraStart(uint256 indexed era, address caller) 78 | - <<: *ethereum 79 | options: 80 | abi: indexerRegistry 81 | address: '0xadED5DDFA892250018fE54DB8E8C6CAd45476DC9' 82 | mapping: 83 | file: ./dist/index.js 84 | handlers: 85 | - handler: handleRegisterIndexer 86 | kind: ethereum/LogHandler 87 | filter: 88 | topics: 89 | - RegisterIndexer(address indexed indexer, uint256 amount, bytes32 metadata) 90 | - handler: handleUpdateIndexerMetadata 91 | kind: ethereum/LogHandler 92 | filter: 93 | topics: 94 | - UpdateMetadata(address indexed indexer, bytes32 metadata) 95 | - handler: handleSetControllerAccount 96 | kind: ethereum/LogHandler 97 | filter: 98 | topics: 99 | - SetControllerAccount(address indexed indexer, address indexed controller) 100 | - handler: handleUnregisterIndexer 101 | kind: ethereum/LogHandler 102 | filter: 103 | topics: 104 | - UnregisterIndexer(address indexed indexer) 105 | - handler: handleSetCommissionRate 106 | kind: ethereum/LogHandler 107 | filter: 108 | topics: 109 | - SetCommissionRate(address indexed indexer, uint256 amount) 110 | - handler: handleSetMinimumStakingAmount 111 | kind: ethereum/TransactionHandler 112 | filter: 113 | function: setminimumStakingAmount(uint256 amount) 114 | - <<: *ethereum 115 | options: 116 | abi: staking 117 | address: '0x7A68b10EB116a8b71A9b6f77B32B47EB591B6Ded' 118 | mapping: 119 | file: ./dist/index.js 120 | handlers: 121 | - handler: handleAddDelegation 122 | kind: ethereum/LogHandler 123 | filter: 124 | topics: 125 | - DelegationAdded(address indexed source, address indexed runner, uint256 amount) 126 | - handler: handleRemoveDelegation 127 | kind: ethereum/LogHandler 128 | filter: 129 | topics: 130 | - DelegationRemoved(address indexed source, address indexed runner, uint256 amount) 131 | - handler: handleWithdrawRequested 132 | kind: ethereum/LogHandler 133 | filter: 134 | topics: 135 | - UnbondRequested(address indexed source, address indexed runner, uint256 amount, uint256 index, uint8 _type) 136 | - handler: handleWithdrawClaimed 137 | kind: ethereum/LogHandler 138 | filter: 139 | topics: 140 | - UnbondWithdrawn(address indexed source, uint256 amount, uint256 fee, uint256 index) 141 | - handler: handleWithdrawCancelled 142 | kind: ethereum/LogHandler 143 | filter: 144 | topics: 145 | - UnbondCancelled(address indexed source, address indexed runner, uint256 amount, uint256 index) 146 | - handler: handleSetIndexerLeverageLimit 147 | kind: ethereum/TransactionHandler 148 | filter: 149 | function: setIndexerLeverageLimit(uint256 _indexerLeverageLimit) 150 | - <<: *ethereum 151 | options: 152 | abi: sqtoken 153 | address: '0x858c50C3AF1913b0E849aFDB74617388a1a5340d' 154 | mapping: 155 | file: './dist/index.js' 156 | handlers: 157 | - handler: handleTransfer 158 | kind: ethereum/LogHandler 159 | filter: 160 | topics: 161 | - Transfer(address indexed from, address indexed to, uint256 amount) 162 | - <<: *ethereum 163 | options: 164 | abi: projectRegistry 165 | address: '0x5499c960cc54563E7264Fb96be4E0907a93E825B' 166 | mapping: 167 | file: ./dist/index.js 168 | handlers: 169 | # latest version of events 170 | - handler: handleProjectCreated 171 | kind: ethereum/LogHandler 172 | filter: 173 | topics: 174 | - ProjectCreated(address indexed creator, uint256 indexed projectId, string projectMetadata, uint8 projectType, bytes32 deploymentId, bytes32 deploymentMetadata) 175 | - handler: handlerProjectTransferred 176 | kind: ethereum/LogHandler 177 | filter: 178 | topics: 179 | - Transfer(address indexed from, address indexed to, uint256 indexed tokenId) 180 | - handler: handleProjectMetadataUpdated 181 | kind: ethereum/LogHandler 182 | filter: 183 | topics: 184 | - ProjectMetadataUpdated(address indexed owner, uint256 indexed projectId, string metadata) 185 | - handler: handleProjectDeploymentUpdated 186 | kind: ethereum/LogHandler 187 | filter: 188 | topics: 189 | - ProjectDeploymentUpdated(address indexed owner, uint256 indexed projectId, bytes32 deploymentId, bytes32 metadata) 190 | - handler: handleProjectLatestDeploymentUpdated 191 | kind: ethereum/LogHandler 192 | filter: 193 | topics: 194 | - ProjectLatestDeploymentUpdated(address indexed owner, uint256 projectId, bytes32 deploymentId) 195 | - handler: handleServiceStatusChanged 196 | kind: ethereum/LogHandler 197 | filter: 198 | topics: 199 | - ServiceStatusChanged(address indexed indexer, bytes32 indexed deploymentId, uint8 status) 200 | - <<: *ethereum 201 | options: 202 | abi: planManager 203 | address: '0xbF443a0474AE33C30c2A0dfbc608B0e374A59DcD' 204 | mapping: 205 | file: ./dist/index.js 206 | handlers: 207 | - handler: handlePlanTemplateCreated 208 | kind: ethereum/LogHandler 209 | filter: 210 | topics: 211 | - PlanTemplateCreated(uint256 indexed planTemplateId) 212 | - handler: handlePlanTemplateMetadataUpdated 213 | kind: ethereum/LogHandler 214 | filter: 215 | topics: 216 | - PlanTemplateMetadataChanged(uint256 indexed planTemplateId, bytes32 metadata) 217 | - handler: handlePlanTemplateStatusUpdated 218 | kind: ethereum/LogHandler 219 | filter: 220 | topics: 221 | - PlanTemplateStatusChanged(uint256 indexed planTemplateId, bool active) 222 | - handler: handlePlanCreated 223 | kind: ethereum/LogHandler 224 | filter: 225 | topics: 226 | - PlanCreated(uint256 indexed planId, address creator, bytes32 deploymentId, uint256 planTemplateId, uint256 price) 227 | - handler: handlePlanRemoved 228 | kind: ethereum/LogHandler 229 | filter: 230 | topics: 231 | - PlanRemoved(uint256 indexed planId) 232 | - <<: *ethereum 233 | options: 234 | abi: serviceAgreementRegistry 235 | address: '0xe86861fE07Bfb166FE1010467160ffFD70677403' 236 | mapping: 237 | file: ./dist/index.js 238 | handlers: 239 | - handler: handleServiceAgreementCreated 240 | kind: ethereum/LogHandler 241 | filter: 242 | topics: 243 | - ClosedAgreementCreated(address indexed consumer, address indexed indexer, bytes32 indexed deploymentId, uint256 serviceAgreementId) 244 | - handler: handlerAgreementTransferred 245 | kind: ethereum/LogHandler 246 | filter: 247 | topics: 248 | - Transfer(address indexed from, address indexed to, uint256 indexed tokenId) 249 | - <<: *ethereum 250 | options: 251 | abi: rewardsDistributor 252 | address: '0x18AEC6c407235d446E52Aa243CD1A75421bb264e' 253 | mapping: 254 | file: ./dist/index.js 255 | handlers: 256 | - handler: handleRewardsDistributed 257 | kind: ethereum/LogHandler 258 | filter: 259 | topics: 260 | - DistributeRewards(address indexed runner, uint256 indexed eraIdx, uint256 rewards, uint256 commission) 261 | - handler: handleRewardsClaimed 262 | kind: ethereum/LogHandler 263 | filter: 264 | topics: 265 | - ClaimRewards(address indexed runner, address indexed delegator, uint256 rewards) 266 | - handler: handleRewardsUpdated 267 | kind: ethereum/LogHandler 268 | filter: 269 | topics: 270 | - RewardsChanged(address indexed runner, uint256 indexed eraIdx, uint256 additions, uint256 removals) 271 | - handler: handleInstantRewards 272 | kind: ethereum/LogHandler 273 | filter: 274 | topics: 275 | - InstantRewards(address indexed runner, uint256 indexed eraIdx, uint256 token) 276 | - handler: handleAgreementRewards 277 | kind: ethereum/LogHandler 278 | filter: 279 | topics: 280 | - AgreementRewards(address indexed runner, uint256 agreementId, uint256 token); 281 | - <<: *ethereum 282 | options: 283 | abi: rewardsStaking 284 | address: '0x1c285c5513f2135f8AD12A930E6473dA47581BE8' 285 | mapping: 286 | file: ./dist/index.js 287 | handlers: 288 | - handler: handleRunnerWeightApplied 289 | kind: ethereum/LogHandler 290 | filter: 291 | topics: 292 | - RunnerWeightApplied(address indexed runner, uint256 weight) 293 | - <<: *ethereum 294 | options: 295 | abi: purchaseOfferMarket 296 | address: '0x72E7333bEdf33dc70021519457f05B096e971f37' 297 | mapping: 298 | file: ./dist/index.js 299 | handlers: 300 | - handler: handlePurchaseOfferCreated 301 | kind: ethereum/LogHandler 302 | filter: 303 | topics: 304 | - PurchaseOfferCreated(address consumer, uint256 offerId, bytes32 deploymentId, uint256 planTemplateId, uint256 deposit, uint16 limit, uint256 minimumAcceptHeight, uint256 minimumStakingAmount, uint256 expireDate) 305 | - handler: handlePurchaseOfferCancelled 306 | kind: ethereum/LogHandler 307 | filter: 308 | topics: 309 | - PurchaseOfferCancelled(address indexed creator, uint256 offerId, uint256 penalty) 310 | - handler: handlePurchaseOfferAccepted 311 | kind: ethereum/LogHandler 312 | filter: 313 | topics: 314 | - OfferAccepted(address indexed indexer, uint256 offerId, uint256 agreementId) 315 | - <<: *ethereum 316 | options: 317 | abi: stateChannel 318 | address: '0x6797Df373589dF2AA37FA353c4254FD7834B751A' 319 | mapping: 320 | file: ./dist/index.js 321 | handlers: 322 | - handler: handleChannelOpen 323 | kind: ethereum/LogHandler 324 | filter: 325 | topics: 326 | - ChannelOpen(uint256 indexed channelId, address indexer, address consumer, uint256 total, uint256 price, uint256 expiredAt, bytes32 deploymentId, bytes callback) 327 | - handler: handleChannelExtend 328 | kind: ethereum/LogHandler 329 | filter: 330 | topics: 331 | - ChannelExtend(uint256 channelId, uint256 expiredAt) 332 | - handler: handleChannelFund 333 | kind: ethereum/LogHandler 334 | filter: 335 | topics: 336 | - ChannelFund(uint256 indexed channelId, uint256 realTotal, uint256 total) 337 | - handler: handleChannelCheckpoint 338 | kind: ethereum/LogHandler 339 | filter: 340 | topics: 341 | - ChannelCheckpoint(uint256 indexed channelId, uint256 spent, bool isFinal) 342 | - handler: handleChannelTerminate 343 | kind: ethereum/LogHandler 344 | filter: 345 | topics: 346 | - ChannelTerminate(uint256 channelId, uint256 spent, uint256 terminatedAt, bool terminateByIndexer) 347 | - handler: handleChannelFinalize 348 | kind: ethereum/LogHandler 349 | filter: 350 | topics: 351 | - ChannelFinalize(uint256 channelId, uint256 total, uint256 remain) 352 | - <<: *ethereum 353 | options: 354 | abi: disputeManager 355 | address: '0xd82a8f11645f6FEF2b863CEe19755bA22decD42a' 356 | mapping: 357 | file: ./dist/index.js 358 | handlers: 359 | - handler: handleDisputeOpen 360 | kind: ethereum/LogHandler 361 | filter: 362 | topics: 363 | - DisputeOpen(uint256 indexed disputeId, address fisherman, address indexer, uint8 _type) 364 | - handler: handleDisputeFinalized 365 | kind: ethereum/LogHandler 366 | filter: 367 | topics: 368 | - DisputeFinalized(uint256 indexed disputeId, uint8 state, uint256 slashAmount, uint256 returnAmount) 369 | - <<: *ethereum 370 | options: 371 | abi: priceOracle 372 | address: '0x240f0aCDf5C7c9cD4AE70E1aF61fba9547DF2139' 373 | mapping: 374 | file: ./dist/index.js 375 | handlers: 376 | - handler: handlePricePosted 377 | kind: ethereum/LogHandler 378 | filter: 379 | topics: 380 | - PricePosted(address assetFrom, address assetTo, uint256 previousPrice, uint256 newPrice); 381 | - <<: *ethereum 382 | options: 383 | abi: consumerRegistry 384 | address: '0xd1ce436a883206a87c7e695f0d88B3b57369C477' 385 | mapping: 386 | file: ./dist/index.js 387 | handlers: 388 | - handler: handleConsumerControllerAdded 389 | kind: ethereum/LogHandler 390 | filter: 391 | topics: 392 | - ControllerAdded(address indexed consumer, address controller) 393 | - handler: handleConsumerControllerRemoved 394 | kind: ethereum/LogHandler 395 | filter: 396 | topics: 397 | - ControllerRemoved(address indexed consumer, address controller) 398 | - <<: *ethereum 399 | options: 400 | abi: rewardsBooster 401 | address: '0x7F138D57A5e05b6FBF3bCAdDa9a1252354245464' 402 | mapping: 403 | file: ./dist/index.js 404 | handlers: 405 | - handler: handleDeploymentBoosterAdded 406 | kind: ethereum/LogHandler 407 | filter: 408 | topics: 409 | - DeploymentBoosterAdded(bytes32 indexed deploymentId, address indexed account, uint256 amount) 410 | - handler: handleDeploymentBoosterRemoved 411 | kind: ethereum/LogHandler 412 | filter: 413 | topics: 414 | - DeploymentBoosterRemoved(bytes32 indexed deploymentId, address indexed account, uint256 amount) 415 | - handler: handleMissedLabor 416 | kind: ethereum/LogHandler 417 | filter: 418 | topics: 419 | - MissedLabor(bytes32 indexed deploymentId, address indexed runner, uint256 labor) 420 | - handler: handleAllocationRewardsGiven 421 | kind: ethereum/LogHandler 422 | filter: 423 | topics: 424 | - AllocationRewardsGiven(bytes32 indexed deploymentId, address indexed runner, uint256 amount) 425 | - handler: handleAllocationRewardsBurnt 426 | kind: ethereum/LogHandler 427 | filter: 428 | topics: 429 | - AllocationRewardsBurnt(bytes32 indexed deploymentId, address indexed runner, uint256 amount) 430 | - handler: handleQueryRewardsSpent 431 | kind: ethereum/LogHandler 432 | filter: 433 | topics: 434 | - QueryRewardsSpent(bytes32 indexed deploymentId, address indexed runner, uint256 amount, bytes data) 435 | - handler: handleQueryRewardsRefunded 436 | kind: ethereum/LogHandler 437 | filter: 438 | topics: 439 | - QueryRewardsRefunded(bytes32 indexed deploymentId, address indexed runner, uint256 amount, bytes data) 440 | - <<: *ethereum 441 | options: 442 | abi: stakingAllocation 443 | address: '0x20E4B978b930ce17a499C33BbF958b5b920F70E1' 444 | mapping: 445 | file: ./dist/index.js 446 | handlers: 447 | - handler: handleStakeAllocationAdded 448 | kind: ethereum/LogHandler 449 | filter: 450 | topics: 451 | - StakeAllocationAdded(bytes32 deploymentId, address runner, uint256 amount) 452 | - handler: handleStakeAllocationRemoved 453 | kind: ethereum/LogHandler 454 | filter: 455 | topics: 456 | - StakeAllocationRemoved(bytes32 deploymentId, address runner, uint256 amount) 457 | - handler: handleOverAllocationStarted 458 | kind: ethereum/LogHandler 459 | filter: 460 | topics: 461 | - OverAllocationStarted(address runner, uint256 start) 462 | - handler: handleOverAllocationEnded 463 | kind: ethereum/LogHandler 464 | filter: 465 | topics: 466 | - OverAllocationEnded(address runner, uint256 end, uint256 time) 467 | - <<: *ethereum 468 | options: 469 | abi: airdropper 470 | address: '0x8611F6647C57339b994aE5C1BB7cD8b101eB4716' 471 | mapping: 472 | file: ./dist/index.js 473 | handlers: 474 | - handler: handleRoundCreated 475 | kind: ethereum/LogHandler 476 | filter: 477 | topics: 478 | - RoundCreated(uint256 indexed roundId, address tokenAddress, uint256 roundStartTime, uint256 roundDeadline) 479 | - handler: handleAddAirdrop 480 | kind: ethereum/LogHandler 481 | filter: 482 | topics: 483 | - AddAirdrop(address indexed addr, uint256 roundId, uint256 amount) 484 | - handler: handleAirdropClaimed 485 | kind: ethereum/LogHandler 486 | filter: 487 | topics: 488 | - AirdropClaimed(address indexed addr, uint256 roundId, uint256 amount) 489 | - handler: handleRoundSettled 490 | kind: ethereum/LogHandler 491 | filter: 492 | topics: 493 | - RoundSettled(uint256 indexed roundId, address settleDestination, uint256 unclaimAmount) 494 | - <<: *ethereum 495 | options: 496 | abi: l2SQToken 497 | address: '0x858c50C3AF1913b0E849aFDB74617388a1a5340d' 498 | mapping: 499 | file: ./dist/index.js 500 | handlers: 501 | - handler: handleBurn 502 | kind: ethereum/LogHandler 503 | filter: 504 | topics: 505 | - Burn(address indexed _account, uint256 _amount) 506 | - kind: ethereum/Runtime 507 | startBlock: 12750283 508 | endBlock: 12750283 509 | mapping: 510 | file: ./dist/index.js 511 | handlers: 512 | - handler: handleBlock_12750283 513 | kind: ethereum/BlockHandler 514 | filter: 515 | modulo: 1 516 | -------------------------------------------------------------------------------- /project-testnet.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 1.0.0 2 | name: subquery-network-subql-project 3 | version: 1.0.0 4 | runner: 5 | node: 6 | name: '@subql/node-ethereum' 7 | version: '>= 4.0.0' 8 | options: 9 | unfinalizedBlocks: true 10 | query: 11 | name: '@subql/query' 12 | version: '*' 13 | description: 'Query registry project for SubQuery Testnet' 14 | repository: '' 15 | schema: 16 | file: ./schema.graphql 17 | network: 18 | chainId: '84532' # base-sepolia 19 | endpoint: https://sepolia.base.org 20 | dictionary: '' 21 | 22 | x-ethereum: ðereum 23 | kind: ethereum/Runtime 24 | startBlock: 5073990 25 | 26 | assets: 27 | eraManager: 28 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/l2/EraManager.sol/EraManager.json 29 | staking: 30 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/Staking.sol/Staking.json 31 | sqtoken: 32 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/l2/L2SQToken.sol/L2SQToken.json 33 | indexerRegistry: 34 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/IndexerRegistry.sol/IndexerRegistry.json 35 | projectRegistry: 36 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/ProjectRegistry.sol/ProjectRegistry.json 37 | planManager: 38 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/PlanManager.sol/PlanManager.json 39 | purchaseOfferMarket: 40 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/PurchaseOfferMarket.sol/PurchaseOfferMarket.json 41 | serviceAgreementRegistry: 42 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/ServiceAgreementRegistry.sol/ServiceAgreementRegistry.json 43 | consumerRegistry: 44 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/ConsumerRegistry.sol/ConsumerRegistry.json 45 | rewardsDistributor: 46 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/RewardsDistributor.sol/RewardsDistributor.json 47 | rewardsStaking: 48 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/RewardsStaking.sol/RewardsStaking.json 49 | stateChannel: 50 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/StateChannel.sol/StateChannel.json 51 | disputeManager: 52 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/DisputeManager.sol/DisputeManager.json 53 | priceOracle: 54 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/PriceOracle.sol/PriceOracle.json 55 | tokenExchange: 56 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/TokenExchange.sol/TokenExchange.json 57 | rewardsBooster: 58 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/RewardsBooster.sol/RewardsBooster.json 59 | stakingAllocation: 60 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/StakingAllocation.sol/StakingAllocation.json 61 | airdropper: 62 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/Airdropper.sol/Airdropper.json 63 | l2SQToken: 64 | file: ./node_modules/@subql/contract-sdk/artifacts/contracts/l2/L2SQToken.sol/L2SQToken.json 65 | 66 | dataSources: 67 | - <<: *ethereum 68 | options: 69 | abi: eraManager 70 | address: '0x6E98e462c864Fa2D9Cc165bc4bf4fD891Fc2b8b2' 71 | mapping: 72 | file: ./dist/index.js 73 | handlers: 74 | - handler: handleNewEra 75 | kind: ethereum/LogHandler 76 | filter: 77 | topics: 78 | - NewEraStart(uint256 indexed era, address caller) 79 | - <<: *ethereum 80 | options: 81 | abi: indexerRegistry 82 | address: '0x9b207b3ba85cE57c398d4ca07BA71a46C917EF1A' 83 | mapping: 84 | file: ./dist/index.js 85 | handlers: 86 | - handler: handleRegisterIndexer 87 | kind: ethereum/LogHandler 88 | filter: 89 | topics: 90 | - RegisterIndexer(address indexed indexer, uint256 amount, bytes32 metadata) 91 | - handler: handleUpdateIndexerMetadata 92 | kind: ethereum/LogHandler 93 | filter: 94 | topics: 95 | - UpdateMetadata(address indexed indexer, bytes32 metadata) 96 | - handler: handleSetControllerAccount 97 | kind: ethereum/LogHandler 98 | filter: 99 | topics: 100 | - SetControllerAccount(address indexed indexer, address indexed controller) 101 | - handler: handleUnregisterIndexer 102 | kind: ethereum/LogHandler 103 | filter: 104 | topics: 105 | - UnregisterIndexer(address indexed indexer) 106 | - handler: handleSetCommissionRate 107 | kind: ethereum/LogHandler 108 | filter: 109 | topics: 110 | - SetCommissionRate(address indexed indexer, uint256 amount) 111 | - handler: handleSetMinimumStakingAmount 112 | kind: ethereum/TransactionHandler 113 | filter: 114 | function: setminimumStakingAmount(uint256 amount) 115 | - <<: *ethereum 116 | options: 117 | abi: staking 118 | address: '0x520c2A23000D134780a90bd9dec533df434140a2' 119 | mapping: 120 | file: ./dist/index.js 121 | handlers: 122 | - handler: handleAddDelegation 123 | kind: ethereum/LogHandler 124 | filter: 125 | topics: 126 | - DelegationAdded(address indexed source, address indexed runner, uint256 amount) 127 | - handler: handleRemoveDelegation 128 | kind: ethereum/LogHandler 129 | filter: 130 | topics: 131 | - DelegationRemoved(address indexed source, address indexed runner, uint256 amount) 132 | - handler: handleWithdrawRequested 133 | kind: ethereum/LogHandler 134 | filter: 135 | topics: 136 | - UnbondRequested(address indexed source, address indexed runner, uint256 amount, uint256 index, uint8 _type) 137 | - handler: handleWithdrawClaimed 138 | kind: ethereum/LogHandler 139 | filter: 140 | topics: 141 | - UnbondWithdrawn(address indexed source, uint256 amount, uint256 fee, uint256 index) 142 | - handler: handleWithdrawCancelled 143 | kind: ethereum/LogHandler 144 | filter: 145 | topics: 146 | - UnbondCancelled(address indexed source, address indexed runner, uint256 amount, uint256 index) 147 | - handler: handleSetIndexerLeverageLimit 148 | kind: ethereum/TransactionHandler 149 | filter: 150 | function: setIndexerLeverageLimit(uint256 _indexerLeverageLimit) 151 | - <<: *ethereum 152 | options: 153 | abi: sqtoken 154 | address: '0x37B797EBE14B4490FE64c67390AeCfE20D650953' 155 | mapping: 156 | file: './dist/index.js' 157 | handlers: 158 | - handler: handleTransfer 159 | kind: ethereum/LogHandler 160 | filter: 161 | topics: 162 | - Transfer(address indexed from, address indexed to, uint256 amount) 163 | - <<: *ethereum 164 | options: 165 | abi: projectRegistry 166 | address: '0xaa5c9b4a3975FF78ffDd00321dAbce28EF7F7C7c' 167 | mapping: 168 | file: ./dist/index.js 169 | handlers: 170 | # latest version of events 171 | - handler: handleProjectCreated 172 | kind: ethereum/LogHandler 173 | filter: 174 | topics: 175 | - ProjectCreated(address indexed creator, uint256 indexed projectId, string projectMetadata, uint8 projectType, bytes32 deploymentId, bytes32 deploymentMetadata) 176 | - handler: handlerProjectTransferred 177 | kind: ethereum/LogHandler 178 | filter: 179 | topics: 180 | - Transfer(address indexed from, address indexed to, uint256 indexed tokenId) 181 | - handler: handleProjectMetadataUpdated 182 | kind: ethereum/LogHandler 183 | filter: 184 | topics: 185 | - ProjectMetadataUpdated(address indexed owner, uint256 indexed projectId, string metadata) 186 | - handler: handleProjectDeploymentUpdated 187 | kind: ethereum/LogHandler 188 | filter: 189 | topics: 190 | - ProjectDeploymentUpdated(address indexed owner, uint256 indexed projectId, bytes32 deploymentId, bytes32 metadata) 191 | - handler: handleProjectLatestDeploymentUpdated 192 | kind: ethereum/LogHandler 193 | filter: 194 | topics: 195 | - ProjectLatestDeploymentUpdated(address indexed owner, uint256 projectId, bytes32 deploymentId) 196 | - handler: handleServiceStatusChanged 197 | kind: ethereum/LogHandler 198 | filter: 199 | topics: 200 | - ServiceStatusChanged(address indexed indexer, bytes32 indexed deploymentId, uint8 status) 201 | - <<: *ethereum 202 | options: 203 | abi: planManager 204 | address: '0x61562768175432072cdD6C25De9ED74e24f1a458' 205 | mapping: 206 | file: ./dist/index.js 207 | handlers: 208 | - handler: handlePlanTemplateCreated 209 | kind: ethereum/LogHandler 210 | filter: 211 | topics: 212 | - PlanTemplateCreated(uint256 indexed planTemplateId) 213 | - handler: handlePlanTemplateMetadataUpdated 214 | kind: ethereum/LogHandler 215 | filter: 216 | topics: 217 | - PlanTemplateMetadataChanged(uint256 indexed planTemplateId, bytes32 metadata) 218 | - handler: handlePlanTemplateStatusUpdated 219 | kind: ethereum/LogHandler 220 | filter: 221 | topics: 222 | - PlanTemplateStatusChanged(uint256 indexed planTemplateId, bool active) 223 | - handler: handlePlanCreated 224 | kind: ethereum/LogHandler 225 | filter: 226 | topics: 227 | - PlanCreated(uint256 indexed planId, address creator, bytes32 deploymentId, uint256 planTemplateId, uint256 price) 228 | - handler: handlePlanRemoved 229 | kind: ethereum/LogHandler 230 | filter: 231 | topics: 232 | - PlanRemoved(uint256 indexed planId) 233 | - <<: *ethereum 234 | options: 235 | abi: serviceAgreementRegistry 236 | address: '0x0E0730B1ec1D0E4C1AE15C73b0Be089d07261fBB' 237 | mapping: 238 | file: ./dist/index.js 239 | handlers: 240 | - handler: handleServiceAgreementCreated 241 | kind: ethereum/LogHandler 242 | filter: 243 | topics: 244 | - ClosedAgreementCreated(address indexed consumer, address indexed indexer, bytes32 indexed deploymentId, uint256 serviceAgreementId) 245 | - handler: handlerAgreementTransferred 246 | kind: ethereum/LogHandler 247 | filter: 248 | topics: 249 | - Transfer(address indexed from, address indexed to, uint256 indexed tokenId) 250 | - <<: *ethereum 251 | options: 252 | abi: rewardsDistributor 253 | address: '0x5c0d1F22C4D7aaF35Ade34CA7c7491dBB0A91Cb7' 254 | mapping: 255 | file: ./dist/index.js 256 | handlers: 257 | - handler: handleRewardsDistributed 258 | kind: ethereum/LogHandler 259 | filter: 260 | topics: 261 | - DistributeRewards(address indexed runner, uint256 indexed eraIdx, uint256 rewards, uint256 commission) 262 | - handler: handleRewardsClaimed 263 | kind: ethereum/LogHandler 264 | filter: 265 | topics: 266 | - ClaimRewards(address indexed runner, address indexed delegator, uint256 rewards) 267 | - handler: handleRewardsUpdated 268 | kind: ethereum/LogHandler 269 | filter: 270 | topics: 271 | - RewardsChanged(address indexed runner, uint256 indexed eraIdx, uint256 additions, uint256 removals) 272 | - handler: handleInstantRewards 273 | kind: ethereum/LogHandler 274 | filter: 275 | topics: 276 | - InstantRewards(address indexed runner, uint256 indexed eraIdx, uint256 token) 277 | - handler: handleAgreementRewards 278 | kind: ethereum/LogHandler 279 | filter: 280 | topics: 281 | - AgreementRewards(address indexed runner, uint256 agreementId, uint256 token); 282 | - <<: *ethereum 283 | options: 284 | abi: rewardsStaking 285 | address: '0xB64D73B96358855075576D66746D2a88e043CC1E' 286 | mapping: 287 | file: ./dist/index.js 288 | handlers: 289 | - handler: handleRunnerWeightApplied 290 | kind: ethereum/LogHandler 291 | filter: 292 | topics: 293 | - RunnerWeightApplied(address indexed runner, uint256 weight) 294 | - <<: *ethereum 295 | options: 296 | abi: purchaseOfferMarket 297 | address: '0x60060511d0dfC7B3a6013B7E565aeC8F353639a9' 298 | mapping: 299 | file: ./dist/index.js 300 | handlers: 301 | - handler: handlePurchaseOfferCreated 302 | kind: ethereum/LogHandler 303 | filter: 304 | topics: 305 | - PurchaseOfferCreated(address consumer, uint256 offerId, bytes32 deploymentId, uint256 planTemplateId, uint256 deposit, uint16 limit, uint256 minimumAcceptHeight, uint256 minimumStakingAmount, uint256 expireDate) 306 | - handler: handlePurchaseOfferCancelled 307 | kind: ethereum/LogHandler 308 | filter: 309 | topics: 310 | - PurchaseOfferCancelled(address indexed creator, uint256 offerId, uint256 penalty) 311 | - handler: handlePurchaseOfferAccepted 312 | kind: ethereum/LogHandler 313 | filter: 314 | topics: 315 | - OfferAccepted(address indexed indexer, uint256 offerId, uint256 agreementId) 316 | - <<: *ethereum 317 | options: 318 | abi: stateChannel 319 | address: '0x8C3d312291CC666757daFbb6eD20874Ae573C895' 320 | mapping: 321 | file: ./dist/index.js 322 | handlers: 323 | - handler: handleChannelOpen 324 | kind: ethereum/LogHandler 325 | filter: 326 | topics: 327 | - ChannelOpen(uint256 indexed channelId, address indexer, address consumer, uint256 total, uint256 price, uint256 expiredAt, bytes32 deploymentId, bytes callback) 328 | - handler: handleChannelExtend 329 | kind: ethereum/LogHandler 330 | filter: 331 | topics: 332 | - ChannelExtend(uint256 channelId, uint256 expiredAt) 333 | - handler: handleChannelFund 334 | kind: ethereum/LogHandler 335 | filter: 336 | topics: 337 | - ChannelFund(uint256 indexed channelId, uint256 realTotal, uint256 total) 338 | - handler: handleChannelCheckpoint 339 | kind: ethereum/LogHandler 340 | filter: 341 | topics: 342 | - ChannelCheckpoint(uint256 indexed channelId, uint256 spent, bool isFinal) 343 | - handler: handleChannelTerminate 344 | kind: ethereum/LogHandler 345 | filter: 346 | topics: 347 | - ChannelTerminate(uint256 channelId, uint256 spent, uint256 terminatedAt, bool terminateByIndexer) 348 | - handler: handleChannelFinalize 349 | kind: ethereum/LogHandler 350 | filter: 351 | topics: 352 | - ChannelFinalize(uint256 channelId, uint256 total, uint256 remain) 353 | - <<: *ethereum 354 | options: 355 | abi: disputeManager 356 | address: '0x7c244A53E57aaBEfF7Fb93745AA8993DB307788d' 357 | mapping: 358 | file: ./dist/index.js 359 | handlers: 360 | - handler: handleDisputeOpen 361 | kind: ethereum/LogHandler 362 | filter: 363 | topics: 364 | - DisputeOpen(uint256 indexed disputeId, address fisherman, address indexer, uint8 _type) 365 | - handler: handleDisputeFinalized 366 | kind: ethereum/LogHandler 367 | filter: 368 | topics: 369 | - DisputeFinalized(uint256 indexed disputeId, uint8 state, uint256 slashAmount, uint256 returnAmount) 370 | - <<: *ethereum 371 | options: 372 | abi: priceOracle 373 | address: '0x0D5A4266573975222292601686f2C3CF02E2120A' 374 | mapping: 375 | file: ./dist/index.js 376 | handlers: 377 | - handler: handlePricePosted 378 | kind: ethereum/LogHandler 379 | filter: 380 | topics: 381 | - PricePosted(address assetFrom, address assetTo, uint256 previousPrice, uint256 newPrice); 382 | - <<: *ethereum 383 | options: 384 | abi: consumerRegistry 385 | address: '0x74C3c2273f47dDF4E435f20A01601a44a519a9c0' 386 | mapping: 387 | file: ./dist/index.js 388 | handlers: 389 | - handler: handleConsumerControllerAdded 390 | kind: ethereum/LogHandler 391 | filter: 392 | topics: 393 | - ControllerAdded(address indexed consumer, address controller) 394 | - handler: handleConsumerControllerRemoved 395 | kind: ethereum/LogHandler 396 | filter: 397 | topics: 398 | - ControllerRemoved(address indexed consumer, address controller) 399 | - <<: *ethereum 400 | options: 401 | abi: tokenExchange 402 | address: '0x91514003aDfb9371985cBEB59D10EdC784dec330' 403 | mapping: 404 | file: ./dist/index.js 405 | handlers: 406 | - handler: handleExchangeOrderSent 407 | kind: ethereum/LogHandler 408 | filter: 409 | topics: 410 | - ExchangeOrderSent(uint256 indexed orderId, address sender, address tokenGive, address tokenGet, uint256 amountGive, uint256 amountGet, uint256 tokenGiveBalance) 411 | - handler: handleOrderSettled 412 | kind: ethereum/LogHandler 413 | filter: 414 | topics: 415 | - OrderSettled(uint256 indexed orderId, address tokenGive, address tokenGet, uint256 amountGive) 416 | - handler: handleTraded 417 | kind: ethereum/LogHandler 418 | filter: 419 | topics: 420 | - Trade(uint256 indexed orderId, address tokenGive, address tokenGet, uint256 amountGive, uint256 amountGet); 421 | - <<: *ethereum 422 | options: 423 | abi: rewardsBooster 424 | address: '0x4f6A1045A56EeD1D2795b5f6F6713972B67C09C2' 425 | mapping: 426 | file: ./dist/index.js 427 | handlers: 428 | - handler: handleDeploymentBoosterAdded 429 | kind: ethereum/LogHandler 430 | filter: 431 | topics: 432 | - DeploymentBoosterAdded(bytes32 indexed deploymentId, address indexed account, uint256 amount) 433 | - handler: handleDeploymentBoosterRemoved 434 | kind: ethereum/LogHandler 435 | filter: 436 | topics: 437 | - DeploymentBoosterRemoved(bytes32 indexed deploymentId, address indexed account, uint256 amount) 438 | - handler: handleMissedLabor 439 | kind: ethereum/LogHandler 440 | filter: 441 | topics: 442 | - MissedLabor(bytes32 indexed deploymentId, address indexed runner, uint256 labor) 443 | - handler: handleAllocationRewardsGiven 444 | kind: ethereum/LogHandler 445 | filter: 446 | topics: 447 | - AllocationRewardsGiven(bytes32 indexed deploymentId, address indexed runner, uint256 amount) 448 | - handler: handleAllocationRewardsBurnt 449 | kind: ethereum/LogHandler 450 | filter: 451 | topics: 452 | - AllocationRewardsBurnt(bytes32 indexed deploymentId, address indexed runner, uint256 amount) 453 | - handler: handleQueryRewardsSpent 454 | kind: ethereum/LogHandler 455 | filter: 456 | topics: 457 | - QueryRewardsSpent(bytes32 indexed deploymentId, address indexed runner, uint256 amount, bytes data) 458 | - handler: handleQueryRewardsRefunded 459 | kind: ethereum/LogHandler 460 | filter: 461 | topics: 462 | - QueryRewardsRefunded(bytes32 indexed deploymentId, address indexed runner, uint256 amount, bytes data) 463 | - <<: *ethereum 464 | options: 465 | abi: stakingAllocation 466 | address: '0x505B8fD4331080e5130A21349E5438951D4d2e4a' 467 | mapping: 468 | file: ./dist/index.js 469 | handlers: 470 | - handler: handleStakeAllocationAdded 471 | kind: ethereum/LogHandler 472 | filter: 473 | topics: 474 | - StakeAllocationAdded(bytes32 deploymentId, address runner, uint256 amount) 475 | - handler: handleStakeAllocationRemoved 476 | kind: ethereum/LogHandler 477 | filter: 478 | topics: 479 | - StakeAllocationRemoved(bytes32 deploymentId, address runner, uint256 amount) 480 | - handler: handleOverAllocationStarted 481 | kind: ethereum/LogHandler 482 | filter: 483 | topics: 484 | - OverAllocationStarted(address runner, uint256 start) 485 | - handler: handleOverAllocationEnded 486 | kind: ethereum/LogHandler 487 | filter: 488 | topics: 489 | - OverAllocationEnded(address runner, uint256 end, uint256 time) 490 | - <<: *ethereum 491 | options: 492 | abi: airdropper 493 | address: '0x26B7e6B239e77eb13004CAdf7Ea3AeCB3A902586' 494 | mapping: 495 | file: ./dist/index.js 496 | handlers: 497 | - handler: handleRoundCreated 498 | kind: ethereum/LogHandler 499 | filter: 500 | topics: 501 | - RoundCreated(uint256 indexed roundId, address tokenAddress, uint256 roundStartTime, uint256 roundDeadline) 502 | - handler: handleAddAirdrop 503 | kind: ethereum/LogHandler 504 | filter: 505 | topics: 506 | - AddAirdrop(address indexed addr, uint256 roundId, uint256 amount) 507 | - handler: handleAirdropClaimed 508 | kind: ethereum/LogHandler 509 | filter: 510 | topics: 511 | - AirdropClaimed(address indexed addr, uint256 roundId, uint256 amount) 512 | - handler: handleRoundSettled 513 | kind: ethereum/LogHandler 514 | filter: 515 | topics: 516 | - RoundSettled(uint256 indexed roundId, address settleDestination, uint256 unclaimAmount) 517 | - <<: *ethereum 518 | options: 519 | abi: l2SQToken 520 | address: '0x37B797EBE14B4490FE64c67390AeCfE20D650953' 521 | mapping: 522 | file: ./dist/index.js 523 | handlers: 524 | - handler: handleBurn 525 | kind: ethereum/LogHandler 526 | filter: 527 | topics: 528 | - Burn(address indexed _account, uint256 _amount) 529 | -------------------------------------------------------------------------------- /schema.graphql: -------------------------------------------------------------------------------- 1 | enum ProjectType { 2 | SUBQUERY 3 | RPC 4 | SQ_DICT 5 | SUBGRAPH 6 | } 7 | 8 | enum OrderType { 9 | SERVICE_AGREEMENT 10 | STATE_CHANNEL 11 | UNKNOWN 12 | } 13 | 14 | enum ServiceStatus { 15 | READY 16 | TERMINATED 17 | } 18 | 19 | enum WithdrawalStatus { 20 | ONGOING 21 | CLAIMED 22 | CANCELLED 23 | } 24 | 25 | enum WithdrawalType { 26 | UNDELEGATION 27 | UNSTAKE 28 | COMMISSION 29 | MERGE 30 | } 31 | 32 | enum ChannelStatus { 33 | FINALIZED 34 | OPEN 35 | TERMINATING 36 | } 37 | 38 | enum DisputeType { 39 | POI 40 | QUERY 41 | } 42 | 43 | enum DisputeState { 44 | ONGOING 45 | ACCEPTED 46 | REJECTED 47 | CANCELLED 48 | } 49 | 50 | type Project @entity { 51 | id: ID! 52 | 53 | owner: String! 54 | type: ProjectType! 55 | metadata: String! # IPFS hash 56 | deploymentId: String! # IPFS hash 57 | deploymentMetadata: String! # IPFS hash 58 | updatedTimestamp: Date! 59 | createdTimestamp: Date! 60 | totalReward: BigInt! 61 | 62 | deployments: [Deployment] @derivedFrom(field: "project") 63 | 64 | createdBlock: Int 65 | lastEvent: String #topicHandler:blockNumber 66 | } 67 | 68 | type Deployment @entity { 69 | id: ID! # deploymentId, IPFS hash to deployment 70 | metadata: String! # IPFS hash to metadata info 71 | project: Project! 72 | indexers: [IndexerDeployment]! @derivedFrom(field: "deployment") 73 | createdTimestamp: Date! 74 | 75 | plans: [Plan]! @derivedFrom(field: "deployment") 76 | 77 | createdBlock: Int 78 | lastEvent: String #topicHandler:blockNumber 79 | } 80 | 81 | type IndexerDeployment @entity { 82 | id: ID! # indexer address + deployment id 83 | indexer: Indexer! 84 | deployment: Deployment! 85 | 86 | timestamp: Date 87 | status: ServiceStatus! 88 | 89 | createdBlock: Int 90 | lastEvent: String #topicHandler:blockNumber 91 | } 92 | 93 | type Era @entity { 94 | id: ID! # Era id 95 | startTime: Date! # Block timestamp the Era is started 96 | endTime: Date # Block timestamp that the successive Era is started 97 | forceNext: Boolean # True when force save previous Era 98 | createdBlock: Int 99 | lastEvent: String #topicHandler:blockNumber 100 | } 101 | 102 | # Necessary without https://github.com/subquery/subql/issues/442 103 | type JSONBigInt @jsonField { 104 | type: String! # Always "bigint", 105 | value: String! # Hex encoded string 106 | } 107 | 108 | type EraValue @jsonField { 109 | era: Int! 110 | value: JSONBigInt! 111 | valueAfter: JSONBigInt! 112 | 113 | createdBlock: Int 114 | lastEvent: String #topicHandler:blockNumber 115 | } 116 | 117 | type Indexer @entity { 118 | id: ID! # Indexers address 119 | metadata: String! # IPFS cid 120 | controller: String # Controller address for the indexer 121 | commission: EraValue! 122 | totalStake: EraValue! 123 | selfStake: EraValue! 124 | capacity: EraValue! 125 | maxUnstakeAmount: JSONBigInt! 126 | 127 | controllers: [Controller] @derivedFrom(field: "indexer") 128 | projects: [IndexerDeployment] @derivedFrom(field: "indexer") 129 | delegations: [Delegation] @derivedFrom(field: "indexer") 130 | rewards: [IndexerReward] @derivedFrom(field: "indexer") 131 | 132 | lastRewardedEra: String # hex encoding 133 | active: Boolean! # @index 134 | createdBlock: Int 135 | lastEvent: String #topicHandler:blockNumber 136 | } 137 | 138 | type IndexerCommissionRate @entity { 139 | id: ID! # indexer address + era id 140 | indexer: Indexer! 141 | era: Era! 142 | eraIdx: Int! 143 | commissionRate: Int! 144 | } 145 | 146 | type Controller @entity { 147 | id: ID! # indexer Address + controller Address 148 | controller: String! 149 | indexer: Indexer! 150 | 151 | isActive: Boolean! # Controller currently set to this indexer 152 | createdBlock: Int 153 | lastEvent: String #topicHandler:blockNumber 154 | } 155 | 156 | type Delegator @entity { 157 | id: ID! # Address 158 | totalDelegations: EraValue! 159 | delegations: [Delegation] @derivedFrom(field: "delegator") 160 | claimedRewards: [Reward] @derivedFrom(field: "delegator") 161 | unclaimedRewards: [UnclaimedReward] @derivedFrom(field: "delegator") 162 | startEra: Int! 163 | exitEra: Int! 164 | 165 | createdBlock: Int 166 | lastEvent: String #topicHandler:blockNumber 167 | } 168 | 169 | type Delegation @entity { 170 | id: ID! # nominator + indexer addresses 171 | delegator: Delegator! 172 | indexer: Indexer! 173 | 174 | amount: EraValue! 175 | exitEra: Int 176 | 177 | createdBlock: Int 178 | lastEvent: String #topicHandler:blockNumber 179 | } 180 | 181 | type DelegationFrom @jsonField { 182 | delegator: String! 183 | amount: BigInt! 184 | } 185 | 186 | type DelegationTo @jsonField { 187 | indexer: String! 188 | amount: BigInt! 189 | } 190 | 191 | type EraIndexerDelegator @entity { 192 | id: ID! # indexer! + era! or indexer! for latest data 193 | indexer: String! 194 | era: Int! 195 | delegators: [DelegationFrom!]! 196 | totalStake: BigInt! 197 | selfStake: BigInt! 198 | } 199 | 200 | type EraDelegatorIndexer @entity { 201 | id: ID! # delegator! + era! or delegator! for latest data 202 | delegator: String! 203 | era: Int! 204 | indexers: [DelegationTo!]! 205 | totalStake: BigInt! 206 | selfStake: BigInt! 207 | } 208 | 209 | type Withdrawl @entity { 210 | id: ID! # nominator + index 211 | index: BigInt! # Index of the withdrawl 212 | delegator: String! @index # nominator address 213 | indexer: String! # nominator address 214 | startTime: Date! 215 | amount: BigInt! 216 | type: WithdrawalType! 217 | 218 | status: WithdrawalStatus! 219 | 220 | createdBlock: Int 221 | lastEvent: String #topicHandler:blockNumber 222 | } 223 | 224 | type ConsumerController @entity { 225 | id: ID! # _ 226 | controller: String! @index 227 | consumer: String! @index 228 | 229 | createdBlock: Int 230 | lastEvent: String #topicHandler:blockNumber 231 | } 232 | 233 | type PlanTemplate @entity { 234 | id: ID! # Index, hex encoded 235 | period: BigInt! 236 | dailyReqCap: BigInt! 237 | rateLimit: BigInt! 238 | priceToken: String! 239 | metadata: String 240 | active: Boolean! 241 | 242 | createdBlock: Int 243 | lastEvent: String #topicHandler:blockNumber 244 | } 245 | 246 | type Plan @entity { 247 | id: ID! # Index, hex encoded 248 | price: BigInt! 249 | creator: String! @index # Address 250 | planTemplate: PlanTemplate! 251 | active: Boolean! 252 | 253 | deployment: Deployment 254 | 255 | createdBlock: Int 256 | lastEvent: String #topicHandler:blockNumber 257 | } 258 | 259 | type ServiceAgreement @entity { 260 | id: ID! # contract address 261 | indexerAddress: String! @index 262 | consumerAddress: String! @index 263 | deployment: Deployment! 264 | planTemplate: PlanTemplate! 265 | 266 | period: BigInt! 267 | startTime: Date! 268 | endTime: Date! @index 269 | lockedAmount: BigInt! 270 | 271 | createdBlock: Int 272 | lastEvent: String #topicHandler:blockNumber 273 | } 274 | 275 | type UnclaimedReward @entity { 276 | id: ID! # indexer + delegator 277 | indexerAddress: String! 278 | delegatorAddress: String! @index 279 | delegator: Delegator 280 | 281 | amount: BigInt! 282 | 283 | createdBlock: Int 284 | lastEvent: String #topicHandler:blockNumber 285 | } 286 | 287 | type Reward @entity { 288 | id: ID! # indexer + delegator + tx hash 289 | indexerAddress: String! 290 | delegatorAddress: String! @index 291 | delegator: Delegator 292 | 293 | amount: BigInt! 294 | claimedTime: Date! 295 | 296 | createdBlock: Int 297 | lastEvent: String #topicHandler:blockNumber 298 | } 299 | 300 | # Expected total reward an indexer will get each epoch 301 | type IndexerReward @entity { 302 | id: ID! # indexer + era 303 | indexer: Indexer! 304 | 305 | # era: Era 306 | eraIdx: String! #TODO: depreciate 307 | eraId: BigInt! 308 | 309 | amount: BigInt! 310 | 311 | createdBlock: Int 312 | lastEvent: String #topicHandler:blockNumber 313 | } 314 | 315 | type EraReward @entity { 316 | id: ID! # indexer! + delegator! + era! + commission? 317 | indexer: Indexer! 318 | delegator: Delegator! 319 | era: Era! 320 | eraIdx: Int! 321 | 322 | isIndexer: Boolean! 323 | isCommission: Boolean! 324 | 325 | amount: BigInt! 326 | claimed: Boolean! 327 | 328 | createdBlock: Int 329 | createdTimestamp: Date! 330 | } 331 | 332 | type EraRewardClaimed @entity { 333 | id: ID! # indexer + delegator 334 | lastClaimedEra: Int! 335 | } 336 | 337 | type IndexerStakeSummary @entity { 338 | id: ID! # indexer address or 0x00 for global 339 | era: Era! 340 | eraIdx: Int! 341 | totalStake: BigInt! 342 | indexerStake: BigInt! 343 | delegatorStake: BigInt! 344 | nextTotalStake: BigInt! 345 | nextIndexerStake: BigInt! 346 | nextDelegatorStake: BigInt! 347 | } 348 | 349 | type IndexerStake @entity { 350 | id: ID! # indexer address + era id or era id for all indexers 351 | indexer: Indexer! 352 | era: Era! 353 | eraIdx: Int! 354 | totalStake: BigInt! 355 | indexerStake: BigInt! 356 | delegatorStake: BigInt! 357 | } 358 | 359 | type EraStake @entity { 360 | id: ID! # indexer address + delegator address + era id 361 | indexer: Indexer! 362 | delegator: Delegator! 363 | era: Era! 364 | eraIdx: Int! 365 | stake: BigInt! 366 | } 367 | 368 | type EraStakeUpdate @entity { 369 | id: ID! # indexer address + delegator address 370 | lastUpdateEra: Era! 371 | } 372 | 373 | type Sqtoken @entity { 374 | id: ID! # token address 375 | totalSupply: BigInt! # total supply 376 | circulatingSupply: BigInt! # totalSupply - airdrop locked - staking locked - treasury holdings 377 | tokenHolders: [TokenHolder] @derivedFrom(field: "token") 378 | } 379 | 380 | type TokenHolder @entity { 381 | id: ID! # holder address 382 | balance: BigInt! 383 | token: Sqtoken! 384 | } 385 | 386 | type Transfer @entity { 387 | id: ID! # tx hash+eventidx 388 | amount: BigInt! 389 | from: String! 390 | to: String! 391 | txHash: String! 392 | timestamp: Date! 393 | blockheight: BigInt! 394 | } 395 | 396 | type Offer @entity { 397 | id: ID! # offerID 398 | consumer: String! @index 399 | deployment: Deployment! 400 | planTemplate: PlanTemplate! 401 | deposit: BigInt! 402 | minimumAcceptHeight: BigInt! 403 | minimumStakingAmount: BigInt! 404 | expireDate: Date! @index 405 | limit: Int! # indexer cap 406 | accepted: Int! # accepted indexer amount 407 | reachLimit: Boolean! 408 | withdrawn: Boolean! # withdraw by cancel event 409 | withdrawPenalty: BigInt 410 | 411 | createdBlock: Int 412 | lastEvent: String #topicHandler:blockNumber 413 | } 414 | 415 | type AcceptedOffer @entity { 416 | id: ID! # offerID:indexer 417 | indexer: Indexer! 418 | offer: Offer! 419 | serviceAgreement: ServiceAgreement! 420 | 421 | createdBlock: Int 422 | lastEvent: String #topicHandler:blockNumber 423 | } 424 | 425 | type TotalLock @entity { 426 | id: ID! # TotalLock 427 | totalDelegation: EraValue! 428 | totalStake: EraValue! 429 | 430 | createdBlock: Int 431 | lastEvent: String #topicHandler:blockNumber 432 | } 433 | 434 | type Exception @entity { 435 | id: ID! #eventBlock:txhash 436 | handler: String! 437 | error: String! 438 | } 439 | 440 | type StateChannel @entity { 441 | id: ID! 442 | 443 | indexer: String! @index 444 | consumer: String! @index 445 | agent: String 446 | status: ChannelStatus! 447 | deployment: Deployment! 448 | realTotal: BigInt! 449 | total: BigInt! 450 | price: BigInt! 451 | spent: BigInt! 452 | isFinal: Boolean! 453 | expiredAt: Date! @index 454 | terminatedAt: Date 455 | terminateByIndexer: Boolean! 456 | startTime: Date! 457 | 458 | lastEvent: String #topicHandler:blockNumber 459 | } 460 | 461 | type Disputes @entity { 462 | id: ID! #disputeId 463 | disputeType: DisputeType! 464 | state: DisputeState! 465 | isFinalized: Boolean! 466 | 467 | fisherman: String! 468 | runner: String! 469 | 470 | slashAmount: BigInt 471 | returnAmount: BigInt 472 | } 473 | 474 | type PriceOracle @entity { 475 | id: ID! #oracleId 476 | fromToken: String! 477 | toToken: String! 478 | beforePrice: BigInt! 479 | afterPrice: BigInt! 480 | createdBlock: Int 481 | } 482 | 483 | enum OrderStatus { 484 | ACTIVE 485 | INACTIVE 486 | } 487 | 488 | type Order @entity { 489 | id: ID! #orderId 490 | sender: String! 491 | tokenGive: String! 492 | tokenGet: String! 493 | amountGive: BigInt! 494 | amountGet: BigInt! 495 | tokenGiveBalance: BigInt! #amount SQT left for swapping 496 | status: OrderStatus! # Inactive when cancel order 497 | createAt: Date! 498 | updateAt: Date! 499 | } 500 | 501 | type Cache @entity { 502 | id: ID! 503 | value: String! 504 | } 505 | 506 | type DeploymentBooster @entity { 507 | id: ID! # deploymentId:consumer:txHash 508 | project: Project! 509 | deployment: Deployment! 510 | consumer: String! @index 511 | amountAdded: BigInt! 512 | amountRemoved: BigInt! 513 | eraIdx: Int! @index 514 | createAt: Date! 515 | } 516 | 517 | type DeploymentBoosterSummary @entity { 518 | id: ID! # deploymentId:consumer 519 | project: Project! 520 | deployment: Deployment! 521 | consumer: String! @index 522 | totalAdded: BigInt! 523 | totalRemoved: BigInt! 524 | totalAmount: BigInt! 525 | createAt: Date! 526 | updateAt: Date! 527 | } 528 | 529 | type IndexerAllocation @entity { 530 | id: ID! # deploymentId:indexerId:txHash 531 | project: Project! 532 | deployment: Deployment! 533 | indexer: Indexer! 534 | amountAdded: BigInt! 535 | amountRemoved: BigInt! 536 | eraIdx: Int! @index 537 | createAt: Date! 538 | } 539 | 540 | type IndexerAllocationSummary @entity { 541 | id: ID! # deploymentId:indexerId 542 | project: Project! 543 | deployment: Deployment! 544 | indexer: Indexer! 545 | totalAdded: BigInt! 546 | totalRemoved: BigInt! 547 | totalAmount: BigInt! 548 | createAt: Date! 549 | updateAt: Date! 550 | } 551 | 552 | type IndexerAllocationOverflow @entity { 553 | id: ID! # indexerId:txHash 554 | indexer: Indexer! 555 | overflowStart: Date! 556 | overflowEnd: Date! @index 557 | overflowTime: BigInt! @index 558 | eraIdxStart: Int! @index 559 | eraIdxEnd: Int! @index 560 | createAt: Date! 561 | updateAt: Date! 562 | } 563 | 564 | type IndexerLatestAllocationOverflow @entity { 565 | id: ID! # indexerId 566 | overflowId: IndexerAllocationOverflow! 567 | createAt: Date! 568 | updateAt: Date! 569 | } 570 | 571 | type IndexerMissedLabor @entity { 572 | id: ID! # deploymentId:indexerId:txHash 573 | deployment: Deployment! 574 | indexer: Indexer! 575 | missedLabor: BigInt! 576 | eraIdx: Int! @index 577 | createAt: Date! 578 | } 579 | 580 | type IndexerAllocationReward @entity { 581 | id: ID! # deploymentId:indexerId:txHash 582 | project: Project! 583 | deployment: Deployment! 584 | indexer: Indexer! 585 | reward: BigInt! 586 | burnt: BigInt! 587 | eraIdx: Int! @index 588 | createAt: Date! 589 | } 590 | 591 | type IndexerAllocationRewardSummary @entity { 592 | id: ID! # deploymentId:indexerId 593 | project: Project! 594 | deployment: Deployment! 595 | indexer: Indexer! 596 | totalReward: BigInt! 597 | totalBurnt: BigInt! 598 | createAt: Date! 599 | updateAt: Date! 600 | } 601 | 602 | type ConsumerQueryReward @entity { 603 | id: ID! # deploymentId:consumer:orderType:orderId 604 | project: Project! 605 | deployment: Deployment! 606 | consumer: String! @index 607 | orderType: OrderType! 608 | orderId: String! 609 | spent: BigInt! 610 | refunded: BigInt! 611 | createAt: Date! 612 | updateAt: Date! 613 | } 614 | 615 | type ConsumerQueryRewardSummary @entity { 616 | id: ID! # deploymentId:consumer:orderType 617 | project: Project! 618 | deployment: Deployment! 619 | consumer: String! @index 620 | orderType: OrderType! 621 | totalSpent: BigInt! 622 | totalRefunded: BigInt! 623 | createAt: Date! 624 | updateAt: Date! 625 | } 626 | 627 | # Airdrop 628 | enum AirdropClaimStatus { 629 | CLAIMED 630 | UNCLAIMED 631 | } 632 | 633 | type Airdrop @entity { 634 | id: ID! # Round ID 635 | tokenAddress: String! 636 | startTime: Date! 637 | endTime: Date! 638 | withdrawAmount: BigInt 639 | hasWithdrawn: Boolean 640 | createAt: String 641 | updateAt: String 642 | } 643 | 644 | type AirdropUser @entity { 645 | id: ID! # roundId:userAccount 646 | user: String! 647 | airdrop: Airdrop! 648 | amount: BigInt! 649 | status: AirdropClaimStatus! 650 | createAt: String 651 | updateAt: String 652 | } 653 | 654 | type AirdropAmount @entity { 655 | id: ID! # userAccount 656 | totalAirdropAmount: BigInt! # Claimed + unClaimed + unlocked + locked 657 | claimedAmount: BigInt! # claim total 658 | createAt: String 659 | updateAt: String 660 | } 661 | 662 | type Withdraw @entity @compositeIndexes(fields: [["sender", "blockheight"]]) { 663 | id: ID! # txHash:logIndex 664 | txHash: String! 665 | sender: String! 666 | amount: BigInt! 667 | createAt: Date! 668 | blockheight: Int! 669 | } 670 | 671 | type EraIndexerApy @entity { 672 | id: ID! # indexerId:eraId 673 | eraIdx: Int! 674 | indexer: Indexer! 675 | indexerReward: BigInt! 676 | indexerApy: BigInt! 677 | delegatorReward: BigInt! 678 | delegatorApy: BigInt! 679 | createAt: Date! 680 | updateAt: Date! 681 | } 682 | 683 | type IndexerApySummary @entity { 684 | id: ID! # indexerId 685 | eraIdx: Int! 686 | indexer: Indexer! 687 | indexerReward: BigInt! 688 | indexerApy: BigInt! 689 | delegatorReward: BigInt! 690 | delegatorApy: BigInt! 691 | createAt: Date! 692 | updateAt: Date! 693 | } 694 | 695 | type EraIndexerDeploymentApy @entity { 696 | id: ID! # indexerId:deploymentId:eraId 697 | eraIdx: Int! 698 | indexer: Indexer! 699 | deployment: Deployment! 700 | agreementReward: BigInt! 701 | flexPlanReward: BigInt! 702 | allocationReward: BigInt! 703 | apy: BigInt! 704 | createAt: Date! 705 | updateAt: Date! 706 | } 707 | 708 | type EraDelegatorIndexerApy @entity { 709 | id: ID! # delegatorId:indexerId:eraId 710 | eraIdx: Int! 711 | delegator: Delegator! 712 | indexer: Indexer! 713 | reward: BigInt! 714 | stake: BigInt! 715 | apy: BigInt! 716 | createAt: Date! 717 | updateAt: Date! 718 | } 719 | 720 | type EraDelegatorApy @entity { 721 | id: ID! # delegatorId:eraId 722 | eraIdx: Int! 723 | delegator: Delegator! 724 | reward: BigInt! 725 | apy: BigInt! 726 | createAt: Date! 727 | updateAt: Date! 728 | } 729 | 730 | type IndexerStakeWeight @entity { 731 | id: ID! # indexerId 732 | indexer: Indexer! 733 | eraIdx: Int! 734 | weight: BigInt! 735 | createAt: Date! 736 | updateAt: Date! 737 | } 738 | -------------------------------------------------------------------------------- /scripts/checkFilters.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /* 5 | * Checks that ethereum/LogHandler topics 6 | */ 7 | 8 | import fs from 'fs'; 9 | import path from 'path'; 10 | import yaml from 'js-yaml'; 11 | import { 12 | Interface, 13 | EventFragment /*, FunctionFragment*/, 14 | } from '@ethersproject/abi'; 15 | 16 | function getProjectFile() { 17 | const network = process.argv[2]; 18 | switch (network) { 19 | case 'testnet': 20 | return path.resolve(__dirname, '../project-testnet.yaml'); 21 | case 'mainnet': 22 | return path.resolve(__dirname, '../project-mainnet.yaml'); 23 | default: 24 | throw new Error('Unknown network'); 25 | } 26 | } 27 | 28 | function buildInterface(abiPath: string): Interface { 29 | const abi = fs.readFileSync(abiPath, 'utf8'); 30 | if (!abi) { 31 | throw new Error('Abi not found'); 32 | } 33 | 34 | let abiObj = JSON.parse(abi) as string[]; 35 | 36 | /* 37 | * Allows parsing JSON artifacts as well as ABIs 38 | * https://trufflesuite.github.io/artifact-updates/background.html#what-are-artifacts 39 | */ 40 | if (!Array.isArray(abiObj) && (abiObj as { abi: string[] }).abi) { 41 | abiObj = (abiObj as { abi: string[] }).abi; 42 | } 43 | 44 | return new Interface(abiObj); 45 | } 46 | 47 | type Project = { 48 | dataSources: { 49 | assets?: Record; 50 | processor?: { 51 | options?: { 52 | abi: string; 53 | }; 54 | }; 55 | mapping: { 56 | handlers: { 57 | kind: string; 58 | filter?: { 59 | topics?: string[]; 60 | }; 61 | }[]; 62 | }; 63 | }[]; 64 | }; 65 | 66 | function checkFilters() { 67 | console.log('Checking filters exist in ABIs'); 68 | 69 | const file = getProjectFile(); 70 | const project = yaml.load(fs.readFileSync(file, 'utf-8')) as Project; 71 | 72 | const issues: string[] = []; 73 | 74 | project.dataSources.forEach((ds) => { 75 | ds.mapping.handlers 76 | .filter((handler) => handler.kind === 'ethereum/LogHandler') 77 | .forEach((handler) => { 78 | // Check event filters 79 | const topics: string[] | undefined = handler?.filter?.topics; 80 | if (topics?.[0] && ds.assets && ds.processor?.options) { 81 | const topic = topics[0]; 82 | 83 | const iface = buildInterface( 84 | path.resolve(ds.assets[ds.processor.options.abi].file) 85 | ); 86 | const matches = Object.values(iface.events).find( 87 | (val) => val.format() === EventFragment.fromString(topic).format() 88 | ); 89 | 90 | if (!matches) { 91 | issues.push(`Topic: "${topic}" not found in contract interface`); 92 | } 93 | } 94 | }); 95 | }); 96 | 97 | if (issues.length) { 98 | console.warn('Found issues with filters'); 99 | 100 | issues.forEach((i) => console.warn(i)); 101 | } else { 102 | console.log('SUCCESS: No issues found with filters'); 103 | } 104 | } 105 | 106 | checkFilters(); 107 | -------------------------------------------------------------------------------- /scripts/fix-webpack-ethers: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # exit when any command fails 4 | set -e 5 | 6 | 7 | # Removes the module field from a json file 8 | function remove_module() { 9 | echo "$1" 10 | 11 | tmp=$(mktemp) 12 | jq "del(.module)" "$1" > "$tmp" 13 | mv "$tmp" "$1" 14 | } 15 | 16 | 17 | # Because of this issue we need to modify all the package.json files relating to ethers 18 | # https://github.com/ethers-io/ethers.js/issues/1108 19 | 20 | for path in "$(dirname "$0")"/../node_modules/@ethersproject/*/package.json; do 21 | remove_module "$path" 22 | done 23 | 24 | remove_module "$(dirname "$0")/../node_modules/ethers/package.json" 25 | 26 | -------------------------------------------------------------------------------- /scripts/postinstall.sh: -------------------------------------------------------------------------------- 1 | # FIXME: this is a hack code to resolve the build issue for using `base64` module 2 | sed -i -e "s/\"module\".*/\"module\": \".\/lib\/index.js\",/g" ./node_modules/@ethersproject/base64/package.json 3 | 4 | ERROR_FILE="./node_modules/@ethersproject/base64/package.json-e" 5 | 6 | if [ -f $ERROR_FILE ] ; then 7 | rm $ERROR_FILE 8 | fi -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export * from './mappings'; 5 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { BigNumber } from '@ethersproject/bignumber'; 5 | import { EthereumLog } from '@subql/types-ethereum'; 6 | import { ServiceStatus, WithdrawalStatus } from './types'; 7 | import { WithdrawalType } from './types/enums'; 8 | 9 | export interface CreateWithdrawlParams { 10 | id: string; 11 | delegator: string; 12 | indexer: string; 13 | index: BigNumber; 14 | amount: BigNumber; 15 | status: WithdrawalStatus; 16 | type: WithdrawalType; 17 | event: EthereumLog; 18 | } 19 | 20 | export interface CreateIndexerParams { 21 | address: string; 22 | metadata?: string; 23 | active?: boolean; 24 | createdBlock?: number; 25 | lastEvent?: string; 26 | controller?: string; 27 | event: EthereumLog; 28 | } 29 | 30 | export interface ISaveIndexerDeployment { 31 | indexerId: string; 32 | deploymentId: string; 33 | blockHeight?: bigint; 34 | timestamp?: Date; 35 | mmrRoot?: string; 36 | status: ServiceStatus; 37 | lastEvent?: string; 38 | } 39 | -------------------------------------------------------------------------------- /src/mappings/airdropper.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { 5 | AddAirdropEvent, 6 | AirdropClaimedEvent, 7 | RoundCreatedEvent, 8 | RoundSettledEvent, 9 | } from '@subql/contract-sdk/typechain/contracts/Airdropper'; 10 | import { EthereumLog } from '@subql/types-ethereum'; 11 | import assert from 'assert'; 12 | import { Airdrop, AirdropClaimStatus, AirdropUser } from '../types'; 13 | import { reportException, upsertAirdropper } from './utils'; 14 | 15 | const getAirdropUserId = (roundId: string, address: string) => 16 | `${roundId}:${address}`; 17 | 18 | export async function handleRoundCreated( 19 | event: EthereumLog 20 | ): Promise { 21 | const HANDLER = 'handleRoundCreated'; 22 | logger.info(HANDLER); 23 | assert(event.args, 'No event args'); 24 | 25 | const { roundId, roundDeadline, roundStartTime, tokenAddress } = event.args; 26 | 27 | const airdropRound = Airdrop.create({ 28 | id: roundId.toString(), 29 | startTime: new Date(roundStartTime.toNumber() * 1000), // seconds return from contract and manipulate into milliseconds / Date object. 30 | endTime: new Date(roundDeadline.toNumber() * 1000), // seconds return from contract and manipulate into milliseconds / Date object. 31 | tokenAddress, 32 | createAt: `${HANDLER}:${event.blockNumber}`, 33 | }); 34 | 35 | await airdropRound.save(); 36 | } 37 | 38 | export async function handleRoundSettled( 39 | event: EthereumLog 40 | ): Promise { 41 | const HANDLER = 'handleRoundSettled'; 42 | logger.info(HANDLER); 43 | assert(event.args, 'No event args'); 44 | 45 | const { roundId, unclaimAmount } = event.args; 46 | const roundIdString = roundId.toString(); 47 | const airdrop = await Airdrop.get(roundIdString); 48 | 49 | if (airdrop) { 50 | airdrop.withdrawAmount = unclaimAmount.toBigInt(); 51 | airdrop.hasWithdrawn = true; 52 | airdrop.updateAt = `${HANDLER}:${event.blockNumber}`; 53 | } else { 54 | const error = `Expect roundId - ${roundIdString} exit`; 55 | await reportException(HANDLER, error, event); 56 | logger.error(error); 57 | } 58 | } 59 | 60 | export async function handleAddAirdrop( 61 | event: EthereumLog 62 | ): Promise { 63 | const HANDLER = 'handleAddAirdrop'; 64 | logger.info(HANDLER); 65 | assert(event.args, 'No event args'); 66 | 67 | const { addr, roundId, amount } = event.args; 68 | const roundIdString = roundId.toString(); 69 | const airdrop = await Airdrop.get(roundIdString); 70 | logger.info(`handleAddAirdrop: ${roundIdString}`); 71 | 72 | if (airdrop) { 73 | logger.info(`upsertUser: ${getAirdropUserId(roundIdString, addr)}`); 74 | await upsertAirdropper(addr, amount, '0', event); 75 | 76 | const airdropUser = AirdropUser.create({ 77 | id: getAirdropUserId(roundIdString, addr), 78 | user: addr, 79 | airdropId: roundId.toString(), 80 | amount: amount.toBigInt(), 81 | status: AirdropClaimStatus.UNCLAIMED, 82 | createAt: `${HANDLER}:${event.blockNumber}`, 83 | }); 84 | 85 | await airdropUser.save(); 86 | } else { 87 | const error = `Expect roundId - ${roundIdString} exit`; 88 | await reportException(HANDLER, error, event); 89 | logger.error(error); 90 | } 91 | } 92 | 93 | export async function handleAirdropClaimed( 94 | event: EthereumLog 95 | ): Promise { 96 | const HANDLER = 'handleAirdropClaimed'; 97 | logger.info(HANDLER); 98 | assert(event.args, 'No event args'); 99 | 100 | const { addr, roundId, amount } = event.args; 101 | const roundIdString = roundId.toString(); 102 | const airdrop = await Airdrop.get(roundIdString); 103 | const airdropUserId = getAirdropUserId(roundIdString, addr); 104 | const airdropUser = await AirdropUser.get(airdropUserId); 105 | 106 | if (!airdrop) { 107 | const error = `Expect roundId - ${roundIdString} exit`; 108 | await reportException(HANDLER, error, event); 109 | logger.error(error); 110 | return; 111 | } 112 | 113 | if (!airdropUser) { 114 | const error = `Expect airdropUser - ${airdropUserId} exit`; 115 | await reportException(HANDLER, error, event); 116 | logger.error(error); 117 | return; 118 | } 119 | 120 | await upsertAirdropper(addr, '0', amount.toString(), event); 121 | 122 | airdropUser.status = AirdropClaimStatus.CLAIMED; 123 | (airdropUser.updateAt = `${HANDLER}:${event.blockNumber}`), 124 | await airdropUser.save(); 125 | } 126 | -------------------------------------------------------------------------------- /src/mappings/consumerRegistry.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { 5 | ControllerAddedLog, 6 | ControllerRemovedLog, 7 | } from '../types/abi-interfaces/ConsumerRegistry'; 8 | import assert from 'assert'; 9 | import { ConsumerController } from '../types'; 10 | 11 | export async function handleConsumerControllerAdded( 12 | event: ControllerAddedLog 13 | ): Promise { 14 | assert(event.args, 'No event args'); 15 | const { consumer, controller } = event.args; 16 | logger.info(`handleConsumerControllerAdded: ${consumer} ${controller}`); 17 | await ConsumerController.create({ 18 | id: `${consumer}_${controller}`, 19 | consumer: consumer, 20 | controller: controller, 21 | 22 | createdBlock: event.blockNumber, 23 | lastEvent: `handleConsumerControllerAdded: ${event.blockNumber}`, 24 | }).save(); 25 | } 26 | 27 | export async function handleConsumerControllerRemoved( 28 | event: ControllerRemovedLog 29 | ): Promise { 30 | assert(event.args, 'No event args'); 31 | const { consumer, controller } = event.args; 32 | logger.info(`handleConsumerControllerRemoved: ${consumer} ${controller}`); 33 | await ConsumerController.remove(`${consumer}_${controller}`); 34 | } 35 | -------------------------------------------------------------------------------- /src/mappings/disputeManager.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { 5 | DisputeFinalizedEvent, 6 | DisputeOpenEvent, 7 | } from '@subql/contract-sdk/typechain/contracts/DisputeManager'; 8 | import { EthereumLog } from '@subql/types-ethereum'; 9 | import assert from 'assert'; 10 | import { Disputes, DisputeState } from '../types'; 11 | import { getDisputeState, getDisputeType } from './utils/enumToTypes'; 12 | 13 | export async function handleDisputeOpen( 14 | event: EthereumLog 15 | ): Promise { 16 | logger.info('handleDisputeOpen'); 17 | assert(event.args, 'No event args'); 18 | 19 | const { disputeId, fisherman, runner, _type } = event.args; 20 | 21 | const disputeType = getDisputeType(_type); 22 | 23 | const dispute = Disputes.create({ 24 | id: disputeId.toHexString(), 25 | state: DisputeState.ONGOING, 26 | disputeType, 27 | isFinalized: false, 28 | runner, 29 | fisherman, 30 | }); 31 | 32 | await dispute.save(); 33 | } 34 | 35 | export async function handleDisputeFinalized( 36 | event: EthereumLog 37 | ): Promise { 38 | logger.info('handleDisputeFinalized'); 39 | assert(event.args, 'No event args'); 40 | 41 | const { disputeId, state: _state, slashAmount, returnAmount } = event.args; 42 | 43 | const state = getDisputeState(_state); 44 | const dispute = await Disputes.get(disputeId.toHexString()); 45 | 46 | assert(dispute, `Dispute not found. disputeId="${disputeId.toHexString()}"`); 47 | 48 | dispute.isFinalized = true; 49 | dispute.state = state; 50 | (dispute.slashAmount = slashAmount.toBigInt()), 51 | (dispute.returnAmount = returnAmount.toBigInt()); 52 | await dispute.save(); 53 | } 54 | -------------------------------------------------------------------------------- /src/mappings/eraManager.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { EthereumLog } from '@subql/types-ethereum'; 5 | import { NewEraStartEvent } from '@subql/contract-sdk/typechain/contracts/l2/EraManager'; 6 | import assert from 'assert'; 7 | import { Era } from '../types'; 8 | import { biToDate, Contracts, getContractAddress } from './utils'; 9 | import { EraManager__factory } from '../types/contracts/factories/EraManager__factory'; 10 | import { cacheGetNumber, CacheKey, cacheSet } from './utils/cache'; 11 | 12 | /* Era Handlers */ 13 | export async function handleNewEra( 14 | event: EthereumLog 15 | ): Promise { 16 | logger.info('handleNewEra'); 17 | assert(event.args, 'No event args'); 18 | const { era: id } = event.args; 19 | 20 | await cacheSet(CacheKey.Era, id.toString()); 21 | 22 | if (id.gt(1)) { 23 | const previousId = id.sub(1); 24 | const previousEra = await Era.get(previousId.toHexString()); 25 | if (previousEra) { 26 | previousEra.endTime = biToDate(event.block.timestamp); 27 | previousEra.lastEvent = `handleNewEra:${event.blockNumber}`; 28 | await previousEra.save(); 29 | } else { 30 | const network = await api.getNetwork(); 31 | const eraManager = EraManager__factory.connect( 32 | getContractAddress(network.chainId, Contracts.ERA_MANAGER_ADDRESS), 33 | api 34 | ); 35 | const eraPeriod = await eraManager.eraPeriod(); 36 | 37 | const a = biToDate(event.block.timestamp); 38 | const startTime = new Date( 39 | a.getTime() - eraPeriod.toNumber() * 1000 // eraPeriod: seconds unit 40 | ); 41 | 42 | const previousEra = Era.create({ 43 | id: previousId.toHexString(), 44 | startTime, 45 | endTime: biToDate(event.block.timestamp), 46 | forceNext: true, 47 | createdBlock: event.blockNumber, 48 | }); 49 | await previousEra.save(); 50 | } 51 | } 52 | 53 | const era = Era.create({ 54 | id: id.toHexString(), 55 | startTime: biToDate(event.block.timestamp), 56 | forceNext: false, 57 | createdBlock: event.blockNumber, 58 | }); 59 | 60 | await era.save(); 61 | } 62 | 63 | export async function getCurrentEra(): Promise { 64 | let era = await cacheGetNumber(CacheKey.Era); 65 | if (era === undefined) { 66 | const network = await api.getNetwork(); 67 | const eraManager = EraManager__factory.connect( 68 | getContractAddress(network.chainId, Contracts.ERA_MANAGER_ADDRESS), 69 | api 70 | ); 71 | era = await eraManager.eraNumber().then((r) => r.toNumber()); 72 | await cacheSet(CacheKey.Era, era!.toString()); 73 | } 74 | return era!; 75 | } 76 | -------------------------------------------------------------------------------- /src/mappings/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export * from './consumerRegistry'; 5 | export * from './eraManager'; 6 | export * from './indexerRegistry'; 7 | export * from './planManager'; 8 | export * from './priceOracle'; 9 | export * from './projectRegistry'; 10 | export * from './purchaseOffer'; 11 | export * from './rewardsBooster'; 12 | export * from './rewardsDistributor'; 13 | export * from './rewardsStaking'; 14 | export * from './serviceAgreement'; 15 | export * from './staking'; 16 | export * from './stakingAllocation'; 17 | export * from './stateChannel'; 18 | export * from './tokenExchange'; 19 | export * from './transfer'; 20 | export * from './airdropper'; 21 | export * from './l2SQToken'; 22 | -------------------------------------------------------------------------------- /src/mappings/indexerRegistry.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { 5 | RegisterIndexerEvent, 6 | SetCommissionRateEvent, 7 | SetControllerAccountEvent, 8 | UnregisterIndexerEvent, 9 | UpdateMetadataEvent, 10 | } from '@subql/contract-sdk/typechain/contracts/IndexerRegistry'; 11 | import assert from 'assert'; 12 | import { Controller, Indexer, IndexerCommissionRate } from '../types'; 13 | import { 14 | bytesToIpfsCid, 15 | Contracts, 16 | createIndexer, 17 | getContractAddress, 18 | reportIndexerNonExistException, 19 | upsertControllerAccount, 20 | upsertEraValue, 21 | } from './utils'; 22 | import { IndexerRegistry__factory } from '@subql/contract-sdk'; 23 | import { EthereumLog } from '@subql/types-ethereum'; 24 | import { BigNumber } from 'ethers'; 25 | import { SetminimumStakingAmountTransaction } from '../types/abi-interfaces/IndexerRegistry'; 26 | import { cacheGetBigNumber, CacheKey, cacheSet } from './utils/cache'; 27 | 28 | /* Indexer Registry Handlers */ 29 | export async function handleRegisterIndexer( 30 | event: EthereumLog 31 | ): Promise { 32 | logger.info('handleRegisterIndexer'); 33 | assert(event.args, 'No event args'); 34 | const { indexer: indexerAddress, metadata } = event.args; 35 | 36 | const indexer = await Indexer.get(indexerAddress); 37 | const cid = bytesToIpfsCid(metadata); 38 | 39 | if (indexer) { 40 | indexer.metadata = cid; 41 | indexer.active = true; 42 | indexer.lastEvent = `handleRegisterIndexer:${event.blockNumber}`; 43 | await indexer.save(); 44 | } else { 45 | await createIndexer({ 46 | address: indexerAddress, 47 | metadata: cid, 48 | createdBlock: event.blockNumber, 49 | lastEvent: `handleRegisterIndexer:${event.blockNumber}`, 50 | event, 51 | }); 52 | } 53 | 54 | /* WARNING, other events are emitted before this handler (AddDelegation, SetCommissionRate), 55 | * their handlers are used to set their relevant values. 56 | */ 57 | } 58 | 59 | export async function handleUnregisterIndexer( 60 | event: EthereumLog 61 | ): Promise { 62 | logger.info('handleUnregisterIndexer'); 63 | assert(event.args, 'No event args'); 64 | 65 | const indexer = await Indexer.get(event.args.indexer); 66 | const lastEvent = `handleUnregisterIndexer:${event.blockNumber}`; 67 | 68 | const network = await api.getNetwork(); 69 | const IndexerRegistry = IndexerRegistry__factory.connect( 70 | getContractAddress(network.chainId, Contracts.INDEXER_REGISTRY_ADDRESS), 71 | api 72 | ); 73 | const controllerAddress = await IndexerRegistry.getController( 74 | event.args.indexer 75 | ); 76 | 77 | if (indexer) { 78 | indexer.active = false; 79 | indexer.lastEvent = lastEvent; 80 | delete indexer.controller; 81 | await indexer.save(); 82 | } else { 83 | await reportIndexerNonExistException( 84 | 'HandleUnregisterIndexer', 85 | event.args.indexer, 86 | event 87 | ); 88 | } 89 | 90 | const controller = await Controller.get( 91 | `${event.args.indexer}:${controllerAddress}` 92 | ); 93 | 94 | if (controller) { 95 | controller.lastEvent = lastEvent; 96 | controller.isActive = false; 97 | await controller.save(); 98 | } 99 | } 100 | 101 | export async function handleUpdateIndexerMetadata( 102 | event: EthereumLog 103 | ): Promise { 104 | logger.info('handleUpdateIndexerMetadata'); 105 | assert(event.args, 'No event args'); 106 | const { indexer: address, metadata } = event.args; 107 | 108 | const indexer = await Indexer.get(address); 109 | const lastEvent = `handleUpdateIndexerMetadata: ${event.blockNumber}`; 110 | 111 | if (indexer) { 112 | indexer.lastEvent = lastEvent; 113 | indexer.metadata = bytesToIpfsCid(metadata); 114 | await indexer.save(); 115 | } else { 116 | await reportIndexerNonExistException( 117 | 'HandleUpdateIndexerMetadata', 118 | event.args.indexer, 119 | event 120 | ); 121 | } 122 | } 123 | 124 | export async function handleSetControllerAccount( 125 | event: EthereumLog 126 | ): Promise { 127 | logger.info('handleSetControllerAccount'); 128 | assert(event.args, 'No event args'); 129 | const { indexer: indexerAddress, controller: controllerAddress } = event.args; 130 | 131 | const indexer = await Indexer.get(indexerAddress); 132 | const lastEvent = `handleSetControllerAccount:${event.blockNumber}`; 133 | 134 | if (indexer) { 135 | const prevController = await Controller.get( 136 | `${indexerAddress}:${indexer.controller}` 137 | ); 138 | if (prevController) { 139 | prevController.isActive = false; 140 | await prevController.save(); 141 | } 142 | 143 | indexer.controller = event.args.controller; 144 | indexer.lastEvent = lastEvent; 145 | await indexer.save(); 146 | await upsertControllerAccount( 147 | indexerAddress, 148 | controllerAddress, 149 | event, 150 | lastEvent 151 | ); 152 | } else { 153 | await reportIndexerNonExistException( 154 | 'handleSetControllerAccount', 155 | event.args.indexer, 156 | event 157 | ); 158 | } 159 | } 160 | 161 | export async function handleSetCommissionRate( 162 | event: EthereumLog 163 | ): Promise { 164 | logger.info('handleSetCommissionRate'); 165 | assert(event.args, 'No event args'); 166 | 167 | const address = event.args.indexer; 168 | 169 | const lastEvent = `handleSetCommissionRate:${event.blockNumber}`; 170 | let indexer = await Indexer.get(address); 171 | 172 | if (!indexer) { 173 | indexer = await createIndexer({ 174 | address, 175 | lastEvent, 176 | createdBlock: event.blockNumber, 177 | event, 178 | }); 179 | } 180 | 181 | indexer.commission = await upsertEraValue( 182 | indexer.commission, 183 | event.args.amount.toBigInt(), 184 | 'replace', 185 | // Apply instantly when era is -1, this is an indication that indexer has just registered 186 | indexer.commission.era === -1 187 | ); 188 | indexer.lastEvent = `handleSetCommissionRate:${event.blockNumber}`; 189 | 190 | await indexer.save(); 191 | 192 | await updateIndexerCommissionRate( 193 | indexer.id, 194 | indexer.commission.era, 195 | Number(BigInt.fromJSONType(indexer.commission.value)), 196 | Number(BigInt.fromJSONType(indexer.commission.valueAfter)) 197 | ); 198 | } 199 | 200 | async function updateIndexerCommissionRate( 201 | indexerId: string, 202 | eraIdx: number, 203 | commissionRate: number, 204 | nextCommissionRate: number 205 | ): Promise { 206 | const currentEraId = `${indexerId}:${BigNumber.from(eraIdx).toHexString()}`; 207 | const next1EraId = `${indexerId}:${BigNumber.from(eraIdx + 1).toHexString()}`; 208 | const next2EraId = `${indexerId}:${BigNumber.from(eraIdx + 2).toHexString()}`; 209 | await IndexerCommissionRate.create({ 210 | id: currentEraId, 211 | indexerId, 212 | eraId: BigNumber.from(eraIdx).toHexString(), 213 | eraIdx: eraIdx, 214 | commissionRate, 215 | }).save(); 216 | await IndexerCommissionRate.create({ 217 | id: next1EraId, 218 | indexerId, 219 | eraId: BigNumber.from(eraIdx + 1).toHexString(), 220 | eraIdx: eraIdx + 1, 221 | commissionRate, 222 | }).save(); 223 | await IndexerCommissionRate.create({ 224 | id: next2EraId, 225 | indexerId, 226 | eraId: BigNumber.from(eraIdx + 2).toHexString(), 227 | eraIdx: eraIdx + 2, 228 | commissionRate: nextCommissionRate, 229 | }).save(); 230 | } 231 | 232 | export async function getMinimumStakingAmount(): Promise { 233 | let minimumStakingAmount = await cacheGetBigNumber( 234 | CacheKey.MinimumStakingAmount 235 | ); 236 | if (minimumStakingAmount === undefined) { 237 | const network = await api.getNetwork(); 238 | const indexerRegistry = IndexerRegistry__factory.connect( 239 | getContractAddress(network.chainId, Contracts.INDEXER_REGISTRY_ADDRESS), 240 | api 241 | ); 242 | 243 | minimumStakingAmount = await indexerRegistry.minimumStakingAmount(); 244 | await cacheSet( 245 | CacheKey.MinimumStakingAmount, 246 | minimumStakingAmount.toString() 247 | ); 248 | } 249 | return minimumStakingAmount; 250 | } 251 | 252 | export async function handleSetMinimumStakingAmount( 253 | tx: SetminimumStakingAmountTransaction 254 | ): Promise { 255 | const receipt = await tx.receipt(); 256 | if (receipt.status) { 257 | const amount = tx.args?.[0] as BigNumber; 258 | await cacheSet(CacheKey.MinimumStakingAmount, amount.toString()); 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /src/mappings/l2SQToken.ts: -------------------------------------------------------------------------------- 1 | import { EthereumLog } from '@subql/types-ethereum'; 2 | import { BurnEvent } from '../types/contracts/L2SQToken'; 3 | import assert from 'assert'; 4 | import { Withdraw } from '../types'; 5 | import { biToDate } from './utils'; 6 | 7 | export async function handleBurn(event: EthereumLog) { 8 | logger.info('handleBurn'); 9 | assert(event.args, 'No event args'); 10 | const { _account: account, _amount: amount } = event.args; 11 | 12 | await Withdraw.create({ 13 | id: `${event.transactionHash}:${event.logIndex}`, 14 | txHash: event.transactionHash, 15 | sender: account, 16 | amount: amount.toBigInt(), 17 | createAt: biToDate(event.block.timestamp), 18 | blockheight: event.blockNumber, 19 | }).save(); 20 | } 21 | -------------------------------------------------------------------------------- /src/mappings/planManager.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { 5 | PlanCreatedEvent, 6 | PlanRemovedEvent, 7 | PlanTemplateCreatedEvent, 8 | PlanTemplateMetadataChangedEvent, 9 | PlanTemplateStatusChangedEvent, 10 | } from '@subql/contract-sdk/typechain/contracts/PlanManager'; 11 | 12 | import { PlanManager__factory } from '@subql/contract-sdk'; 13 | import { PlanTemplateV2Struct } from '@subql/contract-sdk/typechain/contracts/interfaces/IPlanManager'; 14 | import { EthereumLog } from '@subql/types-ethereum'; 15 | import assert from 'assert'; 16 | import { BigNumber, constants } from 'ethers'; 17 | import { Plan, PlanTemplate } from '../types'; 18 | import { Contracts, bytesToIpfsCid, getContractAddress } from './utils'; 19 | 20 | export async function handlePlanTemplateCreated( 21 | event: EthereumLog 22 | ): Promise { 23 | logger.info('handlePlanTemplateCreated'); 24 | assert(event.args, 'No event args'); 25 | 26 | const network = await api.getNetwork(); 27 | 28 | let rawPlanTemplate: PlanTemplateV2Struct; 29 | const planManagerAddress = getContractAddress( 30 | network.chainId, 31 | Contracts.PLAN_MANAGER_ADDRESS 32 | ); 33 | 34 | const planManager = PlanManager__factory.connect(planManagerAddress, api); 35 | rawPlanTemplate = await planManager.getPlanTemplate(event.args.templateId); 36 | 37 | const planTemplate = PlanTemplate.create({ 38 | id: event.args.templateId.toHexString(), 39 | period: BigNumber.from(rawPlanTemplate.period).toBigInt(), 40 | dailyReqCap: BigNumber.from(rawPlanTemplate.dailyReqCap).toBigInt(), 41 | rateLimit: BigNumber.from(rawPlanTemplate.rateLimit).toBigInt(), 42 | priceToken: rawPlanTemplate.priceToken, 43 | metadata: 44 | constants.HashZero === rawPlanTemplate.metadata 45 | ? undefined 46 | : bytesToIpfsCid(rawPlanTemplate.metadata.toString()), 47 | active: true, 48 | createdBlock: event.blockNumber, 49 | }); 50 | 51 | await planTemplate.save(); 52 | } 53 | 54 | export async function handlePlanTemplateMetadataUpdated( 55 | event: EthereumLog 56 | ): Promise { 57 | logger.info('handlePlanTemplateMetadataUpdated'); 58 | assert(event.args, 'No event args'); 59 | 60 | const id = event.args.templateId.toHexString(); 61 | 62 | const planTemplate = await PlanTemplate.get(id); 63 | assert(planTemplate, `Plan template not found. templateId="${id}"`); 64 | planTemplate.metadata = bytesToIpfsCid(event.args.metadata); 65 | planTemplate.lastEvent = `handlePlanTemplateMetadataUpdated:${event.blockNumber}`; 66 | 67 | await planTemplate.save(); 68 | } 69 | 70 | export async function handlePlanTemplateStatusUpdated( 71 | event: EthereumLog 72 | ): Promise { 73 | logger.info('handlePlanTemplateStatusUpdated'); 74 | assert(event.args, 'No event args'); 75 | 76 | const id = event.args.templateId.toHexString(); 77 | const planTemplate = await PlanTemplate.get(id); 78 | assert(planTemplate, `Plan template not found. templateId="${id}"`); 79 | 80 | planTemplate.active = event.args.active; 81 | planTemplate.lastEvent = `handlePlanTemplateStatusUpdated:${event.blockNumber}`; 82 | 83 | await planTemplate.save(); 84 | } 85 | 86 | export async function handlePlanCreated( 87 | event: EthereumLog 88 | ): Promise { 89 | assert(event.args, 'No event args'); 90 | logger.info(`handlePlanCreated: ${event.args.planId.toHexString()}`); 91 | 92 | const plan = Plan.create({ 93 | id: event.args.planId.toHexString(), 94 | planTemplateId: event.args.planTemplateId.toHexString(), 95 | creator: event.args.creator, 96 | price: event.args.price.toBigInt(), 97 | active: true, 98 | deploymentId: 99 | constants.HashZero === event.args.deploymentId 100 | ? undefined 101 | : bytesToIpfsCid(event.args.deploymentId), 102 | createdBlock: event.blockNumber, 103 | }); 104 | 105 | await plan.save(); 106 | } 107 | 108 | export async function handlePlanRemoved( 109 | event: EthereumLog 110 | ): Promise { 111 | logger.info('handlePlanRemoved'); 112 | assert(event.args, 'No event args'); 113 | const planId = event.args.planId.toHexString(); 114 | const plan = await Plan.get(planId); 115 | 116 | assert(plan, `Plan not found. planId="${planId}"`); 117 | 118 | plan.active = false; 119 | plan.lastEvent = `handlePlanRemoved:${event.blockNumber}`; 120 | 121 | await plan.save(); 122 | } 123 | -------------------------------------------------------------------------------- /src/mappings/priceOracle.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { PricePostedEvent } from '@subql/contract-sdk/typechain/contracts/PriceOracle'; 5 | import { EthereumLog } from '@subql/types-ethereum'; 6 | import assert from 'assert'; 7 | import { PriceOracle } from '../types'; 8 | 9 | export async function handlePricePosted( 10 | event: EthereumLog 11 | ): Promise { 12 | logger.info('handlePricePosted'); 13 | assert(event.args, 'No event args'); 14 | 15 | const { assetFrom, assetTo, previousPrice, newPrice } = event.args; 16 | 17 | const blockNum = event.blockNumber; 18 | 19 | const price = PriceOracle.create({ 20 | id: `${assetFrom}-${assetTo}-${blockNum}`, 21 | fromToken: assetFrom, 22 | toToken: assetTo, 23 | beforePrice: previousPrice.toBigInt(), 24 | afterPrice: newPrice.toBigInt(), 25 | createdBlock: blockNum, 26 | }); 27 | 28 | await price.save(); 29 | } 30 | -------------------------------------------------------------------------------- /src/mappings/projectRegistry.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2023 SubProject Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { 5 | ProjectCreatedEvent, 6 | ServiceStatusChangedEvent, 7 | TransferEvent, 8 | ProjectDeploymentUpdatedEvent, 9 | ProjectMetadataUpdatedEvent, 10 | ProjectLatestDeploymentUpdatedEvent, 11 | } from '@subql/contract-sdk/typechain/contracts/ProjectRegistry'; 12 | import { EthereumLog } from '@subql/types-ethereum'; 13 | import assert from 'assert'; 14 | import { constants } from 'ethers'; 15 | import { 16 | Deployment, 17 | IndexerDeployment, 18 | Project, 19 | ProjectType, 20 | ServiceStatus, 21 | } from '../types'; 22 | import { biToDate, bytesToIpfsCid } from './utils'; 23 | 24 | const projectTypes: Record = { 25 | 0: ProjectType.SUBQUERY, 26 | 1: ProjectType.RPC, 27 | 2: ProjectType.SQ_DICT, 28 | 3: ProjectType.SUBGRAPH, 29 | }; 30 | 31 | const serviceStatus: Record = { 32 | 0: ServiceStatus.TERMINATED, 33 | 1: ServiceStatus.READY, 34 | }; 35 | 36 | function getIndexerDeploymentId(indexer: string, deploymentId: string): string { 37 | return `${indexer}:${deploymentId}`; 38 | } 39 | 40 | // latest handlers 41 | export async function handleProjectCreated( 42 | event: EthereumLog 43 | ): Promise { 44 | logger.info('handleProjectCreated'); 45 | assert(event.args, 'No event args'); 46 | 47 | const { 48 | creator, 49 | projectId, 50 | projectMetadata, 51 | projectType, 52 | deploymentId, 53 | deploymentMetadata, 54 | } = event.args; 55 | const type = projectTypes[projectType]; 56 | assert(type, `Unknown project type: ${projectType}`); 57 | 58 | const project = Project.create({ 59 | id: projectId.toHexString(), 60 | owner: creator, 61 | type, 62 | // note that project metadata is pure CID string without prefix 63 | metadata: projectMetadata, 64 | totalReward: BigInt(0), 65 | deploymentId: bytesToIpfsCid(deploymentId), 66 | deploymentMetadata: bytesToIpfsCid(deploymentMetadata), 67 | updatedTimestamp: biToDate(event.block.timestamp), 68 | createdTimestamp: biToDate(event.block.timestamp), 69 | createdBlock: event.blockNumber, 70 | }); 71 | 72 | await project.save(); 73 | 74 | const deployment = Deployment.create({ 75 | id: bytesToIpfsCid(deploymentId), 76 | metadata: bytesToIpfsCid(deploymentMetadata), 77 | createdTimestamp: biToDate(event.block.timestamp), 78 | projectId: projectId.toHexString(), 79 | createdBlock: event.blockNumber, 80 | }); 81 | 82 | await deployment.save(); 83 | } 84 | 85 | export async function handlerProjectTransferred( 86 | event: EthereumLog 87 | ): Promise { 88 | logger.info('handlerProjectTransferred'); 89 | assert(event.args, 'No event args'); 90 | 91 | const { from, to, tokenId } = event.args; 92 | // Ignore `mint` event 93 | if (from === constants.AddressZero) return; 94 | 95 | const project = await Project.get(tokenId.toHexString()); 96 | assert(project, `Expected query (${tokenId}) to exist`); 97 | assert(project.owner === from, `Expected owner to be ${from}`); 98 | 99 | project.owner = to; 100 | project.updatedTimestamp = biToDate(event.block.timestamp); 101 | project.lastEvent = `handlerProjectTransferred:${event.blockNumber}`; 102 | 103 | await project.save(); 104 | } 105 | 106 | export async function handleProjectMetadataUpdated( 107 | event: EthereumLog 108 | ): Promise { 109 | logger.info('handleProjectMetadataUpdated'); 110 | assert(event.args, 'No event args'); 111 | 112 | const { projectId, metadata } = event.args; 113 | const project = await Project.get(projectId.toHexString()); 114 | 115 | assert(project, `Expected query (${projectId}) to exist`); 116 | 117 | // note that metadata is pure CID string without prefix 118 | project.metadata = metadata; 119 | project.updatedTimestamp = biToDate(event.block.timestamp); 120 | project.lastEvent = `handleProjectMetadataUpdated:${event.blockNumber}`; 121 | 122 | await project.save(); 123 | } 124 | 125 | export async function handleProjectDeploymentUpdated( 126 | event: EthereumLog 127 | ): Promise { 128 | logger.info('handleProjectDeploymentUpdated'); 129 | assert(event.args, 'No event args'); 130 | 131 | const projectId = event.args.projectId.toHexString(); 132 | const deploymentId = bytesToIpfsCid(event.args.deploymentId); 133 | const metadata = bytesToIpfsCid(event.args.metadata); 134 | const timestamp = biToDate(event.block.timestamp); 135 | 136 | const deployment = await Deployment.get(deploymentId); 137 | if (!deployment) { 138 | const deployment = Deployment.create({ 139 | id: deploymentId, 140 | metadata, 141 | projectId, 142 | createdTimestamp: timestamp, 143 | createdBlock: event.blockNumber, 144 | }); 145 | 146 | await deployment.save(); 147 | } else { 148 | deployment.metadata = metadata; 149 | deployment.lastEvent = `handleProjectDeploymentUpdated:${event.blockNumber}`; 150 | await deployment.save(); 151 | } 152 | } 153 | 154 | export async function handleProjectLatestDeploymentUpdated( 155 | event: EthereumLog 156 | ): Promise { 157 | logger.info('handlerProjectLatestDeploymentUpdated'); 158 | assert(event.args, 'No event args'); 159 | 160 | const { projectId } = event.args; 161 | const project = await Project.get(projectId.toHexString()); 162 | assert(project, `Expected query (${projectId}) to exist`); 163 | 164 | const deploymentId = bytesToIpfsCid(event.args.deploymentId); 165 | const deployment = await Deployment.get(deploymentId); 166 | assert(deployment, `Expected deployment (${deploymentId}) to exist`); 167 | 168 | project.deploymentId = deploymentId; 169 | project.deploymentMetadata = deployment.metadata; 170 | project.updatedTimestamp = biToDate(event.block.timestamp); 171 | project.lastEvent = `handlerProjectLatestDeploymentUpdated:${event.blockNumber}`; 172 | 173 | await project.save(); 174 | } 175 | 176 | export async function handleServiceStatusChanged( 177 | event: EthereumLog 178 | ): Promise { 179 | logger.info('handleStartIndexing'); 180 | assert(event.args, 'No event args'); 181 | 182 | const deploymentId = bytesToIpfsCid(event.args.deploymentId); 183 | const id = getIndexerDeploymentId(event.args.indexer, deploymentId); 184 | const timestamp = biToDate(event.block.timestamp); 185 | const status = serviceStatus[event.args.status]; 186 | 187 | assert(status, `Unknown status: ${event.args.status}`); 188 | 189 | let indexerDeployment = await IndexerDeployment.get(id); 190 | if (!indexerDeployment) { 191 | indexerDeployment = IndexerDeployment.create({ 192 | id: getIndexerDeploymentId(event.args.indexer, deploymentId), 193 | indexerId: event.args.indexer, 194 | deploymentId: deploymentId, 195 | status, 196 | createdBlock: event.blockNumber, 197 | timestamp, 198 | }); 199 | await indexerDeployment.save(); 200 | return; 201 | } 202 | 203 | indexerDeployment.status = status; 204 | indexerDeployment.timestamp = timestamp; 205 | indexerDeployment.lastEvent = `handleIndexingReady:${event.blockNumber}`; 206 | 207 | await indexerDeployment.save(); 208 | } 209 | -------------------------------------------------------------------------------- /src/mappings/purchaseOffer.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { 5 | OfferAcceptedEvent, 6 | PurchaseOfferCancelledEvent, 7 | PurchaseOfferCreatedEvent, 8 | } from '@subql/contract-sdk/typechain/contracts/PurchaseOfferMarket'; 9 | import { EthereumLog } from '@subql/types-ethereum'; 10 | import assert from 'assert'; 11 | import { AcceptedOffer, Offer } from '../types'; 12 | import { biToDate, bytesToIpfsCid } from './utils'; 13 | 14 | export async function handlePurchaseOfferCreated( 15 | event: EthereumLog 16 | ): Promise { 17 | logger.info('handlePurchaseOfferCreated'); 18 | assert(event.args, 'No event args'); 19 | 20 | const offer = Offer.create({ 21 | id: event.args.offerId.toString(), 22 | consumer: event.args.consumer, 23 | deploymentId: bytesToIpfsCid(event.args.deploymentId), 24 | planTemplateId: event.args.planTemplateId.toHexString(), 25 | deposit: event.args.deposit.toBigInt(), 26 | minimumAcceptHeight: event.args.minimumAcceptHeight.toBigInt(), 27 | minimumStakingAmount: event.args.minimumStakingAmount.toBigInt(), 28 | expireDate: new Date(event.args.expireDate.toNumber() * 1000), 29 | limit: event.args.limit, 30 | accepted: 0, 31 | reachLimit: false, 32 | withdrawn: false, 33 | createdBlock: event.blockNumber, 34 | }); 35 | 36 | await offer.save(); 37 | } 38 | 39 | export async function handlePurchaseOfferCancelled( 40 | event: EthereumLog 41 | ): Promise { 42 | logger.info('handlePurchaseOfferCancelled'); 43 | assert(event.args, 'No event args'); 44 | 45 | const offer = await Offer.get(event.args.offerId.toString()); 46 | assert(offer, `offer not found. offerID="${event.args.offerId.toString()}"`); 47 | 48 | offer.expireDate = biToDate(event.block.timestamp); 49 | offer.withdrawn = true; 50 | offer.withdrawPenalty = event.args.penalty.toBigInt(); 51 | offer.lastEvent = `handlePurchaseOfferCancelled:${event.blockNumber}`; 52 | 53 | await offer.save(); 54 | } 55 | 56 | export async function handlePurchaseOfferAccepted( 57 | event: EthereumLog 58 | ): Promise { 59 | logger.info('handlePurchaseOfferAccepted'); 60 | assert(event.args, 'No event args'); 61 | 62 | const eventOfferId = event.args.offerId.toString(); 63 | const eventAgreementId = event.args.agreementId.toString(); 64 | 65 | const offer = await Offer.get(eventOfferId); 66 | assert(offer, `offer not found. offerID="${eventOfferId}"`); 67 | 68 | try { 69 | if (offer.accepted < offer.limit) { 70 | const acceptedAmount = offer.accepted + 1; 71 | offer.accepted = acceptedAmount; 72 | offer.reachLimit = acceptedAmount === offer.limit; 73 | if (offer.reachLimit) { 74 | offer.expireDate = biToDate(event.block.timestamp); 75 | } 76 | offer.lastEvent = `handlePurchaseOfferAccepted:${event.blockNumber}`; 77 | 78 | await offer.save(); 79 | 80 | const acceptedOffer = AcceptedOffer.create({ 81 | id: `${eventOfferId}:${event.args.indexer}`, 82 | indexerId: event.args.indexer, 83 | offerId: eventOfferId, 84 | serviceAgreementId: eventAgreementId, 85 | createdBlock: event.blockNumber, 86 | }); 87 | 88 | await acceptedOffer.save(); 89 | } else { 90 | throw new Error( 91 | 'Method handlePurchaseOfferAccepted: max limit of offer acceptance exceed.' 92 | ); 93 | } 94 | } catch (e) { 95 | logger.info('handlePurchaseOfferAccepted', JSON.stringify(event, null, 2)); 96 | logger.error(e); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/mappings/rewardsBooster.ts: -------------------------------------------------------------------------------- 1 | import { EthereumLog } from '@subql/types-ethereum'; 2 | import { 3 | AllocationRewardsBurntEvent, 4 | AllocationRewardsGivenEvent, 5 | DeploymentBoosterAddedEvent, 6 | DeploymentBoosterRemovedEvent, 7 | MissedLaborEvent, 8 | QueryRewardsRefundedEvent, 9 | QueryRewardsSpentEvent, 10 | } from '../types/contracts/RewardsBooster'; 11 | import assert from 'assert'; 12 | import { 13 | ConsumerQueryReward, 14 | ConsumerQueryRewardSummary, 15 | Deployment, 16 | DeploymentBooster, 17 | DeploymentBoosterSummary, 18 | IndexerAllocationReward, 19 | IndexerAllocationRewardSummary, 20 | IndexerMissedLabor, 21 | OrderType, 22 | Project, 23 | ProjectType, 24 | ServiceAgreement, 25 | StateChannel, 26 | } from '../types'; 27 | import { biToDate, bytesToIpfsCid } from './utils'; 28 | import { getCurrentEra } from './eraManager'; 29 | import { BigNumber } from 'ethers'; 30 | import { upsertEraIndexerDeploymentApy } from './rewardsDistributor'; 31 | import { RewardType } from './utils/enums'; 32 | 33 | const preboostedCids = [ 34 | 'Qmc9svij5SxCEGApMZzV9MwWgy8TuMTtGgsrWxR1yaUqZ9', 35 | 'QmeBTNuhahUo2EhTRxV3qVAVf5bC8zVQRrrHd3SUDXgtbF', 36 | ]; 37 | const preboostedPids: any = { 38 | Qmc9svij5SxCEGApMZzV9MwWgy8TuMTtGgsrWxR1yaUqZ9: '0x21', 39 | QmeBTNuhahUo2EhTRxV3qVAVf5bC8zVQRrrHd3SUDXgtbF: '0x21', 40 | }; 41 | 42 | export async function handleDeploymentBoosterAdded( 43 | event: EthereumLog 44 | ): Promise { 45 | logger.info(`handleDeploymentBoosterAdded`); 46 | assert(event.args, 'No event args'); 47 | const { account: consumer, amount: amountAdded } = event.args; 48 | const deploymentId = bytesToIpfsCid(event.args.deploymentId); 49 | 50 | let deployment = await Deployment.get(deploymentId); 51 | if (!deployment && preboostedCids.includes(deploymentId)) { 52 | deployment = Deployment.create({ 53 | id: deploymentId, 54 | metadata: '', 55 | projectId: preboostedPids[deploymentId], 56 | createdTimestamp: new Date(), 57 | }); 58 | } 59 | assert(deployment, `Deployment ${deploymentId} not found`); 60 | 61 | let project = await Project.get(deployment.projectId); 62 | if (!project && preboostedCids.includes(deploymentId)) { 63 | project = Project.create({ 64 | id: deployment.projectId, 65 | owner: '', 66 | type: ProjectType.SUBQUERY, 67 | metadata: '', 68 | deploymentId: '', 69 | deploymentMetadata: '', 70 | updatedTimestamp: new Date(), 71 | createdTimestamp: new Date(), 72 | totalReward: BigInt(0), 73 | }); 74 | } 75 | assert(project, `Project ${deployment.projectId} not found`); 76 | 77 | const boosterId = `${deploymentId}:${consumer}:${event.transactionHash}`; 78 | 79 | let booster = await DeploymentBooster.get(boosterId); 80 | assert(!booster, 'Booster already exists'); 81 | 82 | booster = DeploymentBooster.create({ 83 | id: boosterId, 84 | projectId: project.id, 85 | deploymentId: deployment.id, 86 | consumer, 87 | amountAdded: amountAdded.toBigInt(), 88 | amountRemoved: BigInt(0), 89 | eraIdx: await getCurrentEra(), 90 | createAt: biToDate(event.block.timestamp), 91 | }); 92 | await booster.save(); 93 | 94 | const summaryId = `${deploymentId}:${consumer}`; 95 | let summary = await DeploymentBoosterSummary.get(summaryId); 96 | if (!summary) { 97 | summary = DeploymentBoosterSummary.create({ 98 | id: summaryId, 99 | projectId: project.id, 100 | deploymentId: deployment.id, 101 | consumer, 102 | totalAdded: amountAdded.toBigInt(), 103 | totalRemoved: BigInt(0), 104 | totalAmount: amountAdded.toBigInt(), 105 | createAt: biToDate(event.block.timestamp), 106 | updateAt: biToDate(event.block.timestamp), 107 | }); 108 | } else { 109 | summary.totalAdded += amountAdded.toBigInt(); 110 | summary.totalAmount = summary.totalAdded - summary.totalRemoved; 111 | summary.updateAt = biToDate(event.block.timestamp); 112 | } 113 | await summary.save(); 114 | } 115 | 116 | export async function handleDeploymentBoosterRemoved( 117 | event: EthereumLog 118 | ): Promise { 119 | logger.info(`handleDeploymentBoosterRemoved`); 120 | assert(event.args, 'No event args'); 121 | const { account: consumer, amount: amountRemoved } = event.args; 122 | const deploymentId = bytesToIpfsCid(event.args.deploymentId); 123 | 124 | const deployment = await Deployment.get(deploymentId); 125 | assert(deployment, `Deployment ${deploymentId} not found`); 126 | 127 | const project = await Project.get(deployment.projectId); 128 | assert(project, `Project ${deployment.projectId} not found`); 129 | 130 | const boosterId = `${deploymentId}:${consumer}:${event.transactionHash}`; 131 | let booster = await DeploymentBooster.get(boosterId); 132 | assert(!booster, 'Booster already exists'); 133 | 134 | booster = DeploymentBooster.create({ 135 | id: boosterId, 136 | projectId: project.id, 137 | deploymentId: deployment.id, 138 | consumer: consumer, 139 | amountAdded: BigInt(0), 140 | amountRemoved: amountRemoved.toBigInt(), 141 | eraIdx: await getCurrentEra(), 142 | createAt: biToDate(event.block.timestamp), 143 | }); 144 | await booster.save(); 145 | 146 | const summaryId = `${deploymentId}:${consumer}`; 147 | let summary = await DeploymentBoosterSummary.get(summaryId); 148 | if (!summary) { 149 | summary = DeploymentBoosterSummary.create({ 150 | id: summaryId, 151 | projectId: project.id, 152 | deploymentId: deployment.id, 153 | consumer: consumer, 154 | totalAdded: BigInt(0), 155 | totalRemoved: amountRemoved.toBigInt(), 156 | totalAmount: BigInt(0), 157 | createAt: biToDate(event.block.timestamp), 158 | updateAt: biToDate(event.block.timestamp), 159 | }); 160 | } else { 161 | summary.totalRemoved += amountRemoved.toBigInt(); 162 | summary.totalAmount = summary.totalAdded - summary.totalRemoved; 163 | summary.updateAt = biToDate(event.block.timestamp); 164 | } 165 | await summary.save(); 166 | } 167 | 168 | export async function handleMissedLabor( 169 | event: EthereumLog 170 | ): Promise { 171 | logger.info(`handleMissedLabor`); 172 | assert(event.args, 'No event args'); 173 | const { runner: indexerId, labor } = event.args; 174 | const deploymentId = bytesToIpfsCid(event.args.deploymentId); 175 | 176 | const missedLaborId = `${deploymentId}:${indexerId}:${event.transactionHash}`; 177 | let missedLabor = await IndexerMissedLabor.get(missedLaborId); 178 | assert(!missedLabor, 'Missed labor already exists'); 179 | 180 | missedLabor = IndexerMissedLabor.create({ 181 | id: missedLaborId, 182 | deploymentId, 183 | indexerId, 184 | missedLabor: labor.toBigInt(), 185 | eraIdx: await getCurrentEra(), 186 | createAt: biToDate(event.block.timestamp), 187 | }); 188 | await missedLabor.save(); 189 | } 190 | 191 | export async function handleAllocationRewardsGiven( 192 | event: EthereumLog 193 | ): Promise { 194 | logger.info(`handleAllocationRewardsGiven`); 195 | assert(event.args, 'No event args'); 196 | const { runner: indexerId, amount: reward } = event.args; 197 | const deploymentId = bytesToIpfsCid(event.args.deploymentId); 198 | 199 | const deployment = await Deployment.get(deploymentId); 200 | assert(deployment, `Deployment ${deploymentId} not found`); 201 | 202 | const project = await Project.get(deployment.projectId); 203 | assert(project, `Project ${deployment.projectId} not found`); 204 | 205 | const rewardId = `${deploymentId}:${indexerId}:${event.transactionHash}`; 206 | let allocationReward = await IndexerAllocationReward.get(rewardId); 207 | assert(!allocationReward, 'Allocation reward already exists'); 208 | 209 | const eraIdx = await getCurrentEra(); 210 | 211 | allocationReward = IndexerAllocationReward.create({ 212 | id: rewardId, 213 | projectId: project.id, 214 | deploymentId, 215 | indexerId, 216 | reward: reward.toBigInt(), 217 | burnt: BigInt(0), 218 | eraIdx, 219 | createAt: biToDate(event.block.timestamp), 220 | }); 221 | await allocationReward.save(); 222 | 223 | const summaryId = `${deploymentId}:${indexerId}`; 224 | let summary = await IndexerAllocationRewardSummary.get(summaryId); 225 | if (!summary) { 226 | summary = IndexerAllocationRewardSummary.create({ 227 | id: summaryId, 228 | projectId: project.id, 229 | deploymentId, 230 | indexerId, 231 | totalReward: reward.toBigInt(), 232 | totalBurnt: BigInt(0), 233 | createAt: biToDate(event.block.timestamp), 234 | updateAt: biToDate(event.block.timestamp), 235 | }); 236 | } else { 237 | summary.totalReward += reward.toBigInt(); 238 | summary.updateAt = biToDate(event.block.timestamp); 239 | } 240 | await summary.save(); 241 | 242 | project.totalReward += reward.toBigInt(); 243 | await project.save(); 244 | 245 | await upsertEraIndexerDeploymentApy( 246 | indexerId, 247 | deploymentId, 248 | eraIdx, 249 | RewardType.ALLOCATION, 250 | reward.toBigInt(), 251 | BigInt(0), 252 | biToDate(event.block.timestamp) 253 | ); 254 | } 255 | 256 | export async function handleAllocationRewardsBurnt( 257 | event: EthereumLog 258 | ): Promise { 259 | logger.info(`handleAllocationRewardsBurnt`); 260 | assert(event.args, 'No event args'); 261 | const { runner: indexerId, amount: burnt } = event.args; 262 | const deploymentId = bytesToIpfsCid(event.args.deploymentId); 263 | 264 | const deployment = await Deployment.get(deploymentId); 265 | assert(deployment, `Deployment ${deploymentId} not found`); 266 | 267 | const project = await Project.get(deployment.projectId); 268 | assert(project, `Project ${deployment.projectId} not found`); 269 | 270 | const rewardId = `${deploymentId}:${indexerId}:${event.transactionHash}`; 271 | let allocationReward = await IndexerAllocationReward.get(rewardId); 272 | if (!allocationReward) { 273 | allocationReward = IndexerAllocationReward.create({ 274 | id: rewardId, 275 | projectId: project.id, 276 | deploymentId, 277 | indexerId, 278 | reward: BigInt(0), 279 | burnt: burnt.toBigInt(), 280 | eraIdx: await getCurrentEra(), 281 | createAt: biToDate(event.block.timestamp), 282 | }); 283 | await allocationReward.save(); 284 | } 285 | 286 | allocationReward.burnt = burnt.toBigInt(); 287 | await allocationReward.save(); 288 | 289 | const summaryId = `${deploymentId}:${indexerId}`; 290 | let summary = await IndexerAllocationRewardSummary.get(summaryId); 291 | if (!summary) { 292 | summary = IndexerAllocationRewardSummary.create({ 293 | id: summaryId, 294 | projectId: project.id, 295 | deploymentId, 296 | indexerId, 297 | totalReward: BigInt(0), 298 | totalBurnt: burnt.toBigInt(), 299 | createAt: biToDate(event.block.timestamp), 300 | updateAt: biToDate(event.block.timestamp), 301 | }); 302 | } else { 303 | summary.totalBurnt += burnt.toBigInt(); 304 | summary.updateAt = biToDate(event.block.timestamp); 305 | } 306 | await summary.save(); 307 | } 308 | 309 | export async function handleQueryRewardsSpent( 310 | event: EthereumLog 311 | ): Promise { 312 | logger.info(`handleQueryRewardsSpent`); 313 | assert(event.args, 'No event args'); 314 | const { spender: consumer, amount: spent, data } = event.args; 315 | const deploymentId = bytesToIpfsCid(event.args.deploymentId); 316 | 317 | const deployment = await Deployment.get(deploymentId); 318 | assert(deployment, `Deployment ${deploymentId} not found`); 319 | 320 | const project = await Project.get(deployment.projectId); 321 | assert(project, `Project ${deployment.projectId} not found`); 322 | 323 | logger.info(`handleQueryRewardsSpent orderId [data]: ${data}`); 324 | 325 | const agreement = await ServiceAgreement.get(BigNumber.from(data).toString()); 326 | const channel = await StateChannel.get(BigNumber.from(data).toHexString()); 327 | assert(agreement || channel, 'No agreement or channel found'); 328 | 329 | let orderType: OrderType = OrderType.UNKNOWN; 330 | let orderId = ''; 331 | if (agreement) { 332 | orderType = OrderType.SERVICE_AGREEMENT; 333 | orderId = agreement.id; 334 | } else if (channel) { 335 | orderType = OrderType.STATE_CHANNEL; 336 | orderId = channel.id; 337 | } 338 | 339 | const rewardId = `${deploymentId}:${consumer}:${orderType}:${orderId}`; 340 | let queryReward = await ConsumerQueryReward.get(rewardId); 341 | 342 | if (!queryReward) { 343 | queryReward = ConsumerQueryReward.create({ 344 | id: rewardId, 345 | projectId: project.id, 346 | deploymentId, 347 | consumer, 348 | orderType, 349 | orderId, 350 | spent: spent.toBigInt(), 351 | refunded: BigInt(0), 352 | createAt: biToDate(event.block.timestamp), 353 | updateAt: biToDate(event.block.timestamp), 354 | }); 355 | } else { 356 | queryReward.spent += spent.toBigInt(); 357 | queryReward.updateAt = biToDate(event.block.timestamp); 358 | } 359 | await queryReward.save(); 360 | 361 | const summaryId = `${deploymentId}:${consumer}:${orderType}`; 362 | let summary = await ConsumerQueryRewardSummary.get(summaryId); 363 | if (!summary) { 364 | summary = ConsumerQueryRewardSummary.create({ 365 | id: summaryId, 366 | projectId: project.id, 367 | deploymentId, 368 | consumer, 369 | orderType, 370 | totalSpent: spent.toBigInt(), 371 | totalRefunded: BigInt(0), 372 | createAt: biToDate(event.block.timestamp), 373 | updateAt: biToDate(event.block.timestamp), 374 | }); 375 | } else { 376 | summary.totalSpent += spent.toBigInt(); 377 | summary.updateAt = biToDate(event.block.timestamp); 378 | } 379 | await summary.save(); 380 | } 381 | 382 | export async function handleQueryRewardsRefunded( 383 | event: EthereumLog 384 | ): Promise { 385 | logger.info(`handleQueryRewardsRefunded`); 386 | assert(event.args, 'No event args'); 387 | const { spender: consumer, amount: refunded, data } = event.args; 388 | const deploymentId = bytesToIpfsCid(event.args.deploymentId); 389 | 390 | const deployment = await Deployment.get(deploymentId); 391 | assert(deployment, `Deployment ${deploymentId} not found`); 392 | 393 | const project = await Project.get(deployment.projectId); 394 | assert(project, `Project ${deployment.projectId} not found`); 395 | 396 | logger.info(`handleQueryRewardsRefunded orderId [data]: ${data}`); 397 | 398 | const agreement = await ServiceAgreement.get(BigNumber.from(data).toString()); 399 | const channel = await StateChannel.get(BigNumber.from(data).toHexString()); 400 | assert(agreement || channel, 'No agreement or channel found'); 401 | 402 | let orderType: OrderType = OrderType.UNKNOWN; 403 | let orderId = ''; 404 | if (agreement) { 405 | orderType = OrderType.SERVICE_AGREEMENT; 406 | orderId = agreement.id; 407 | } else if (channel) { 408 | orderType = OrderType.STATE_CHANNEL; 409 | orderId = channel.id; 410 | } 411 | 412 | const rewardId = `${deploymentId}:${consumer}:${orderType}:${orderId}`; 413 | let queryReward = await ConsumerQueryReward.get(rewardId); 414 | 415 | if (!queryReward) { 416 | queryReward = ConsumerQueryReward.create({ 417 | id: rewardId, 418 | projectId: project.id, 419 | deploymentId, 420 | consumer, 421 | orderType, 422 | orderId, 423 | spent: BigInt(0), 424 | refunded: refunded.toBigInt(), 425 | createAt: biToDate(event.block.timestamp), 426 | updateAt: biToDate(event.block.timestamp), 427 | }); 428 | } else { 429 | queryReward.refunded += refunded.toBigInt(); 430 | queryReward.updateAt = biToDate(event.block.timestamp); 431 | } 432 | await queryReward.save(); 433 | 434 | const summaryId = `${deploymentId}:${consumer}:${orderType}`; 435 | let summary = await ConsumerQueryRewardSummary.get(summaryId); 436 | if (!summary) { 437 | summary = ConsumerQueryRewardSummary.create({ 438 | id: summaryId, 439 | projectId: project.id, 440 | deploymentId, 441 | consumer, 442 | orderType, 443 | totalSpent: BigInt(0), 444 | totalRefunded: refunded.toBigInt(), 445 | createAt: biToDate(event.block.timestamp), 446 | updateAt: biToDate(event.block.timestamp), 447 | }); 448 | } else { 449 | summary.totalRefunded += refunded.toBigInt(); 450 | summary.updateAt = biToDate(event.block.timestamp); 451 | } 452 | await summary.save(); 453 | } 454 | -------------------------------------------------------------------------------- /src/mappings/rewardsDistributor.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import assert from 'assert'; 5 | import { 6 | Delegation, 7 | IndexerReward, 8 | Reward, 9 | UnclaimedReward, 10 | EraReward, 11 | EraRewardClaimed, 12 | Era, 13 | EraIndexerDelegator, 14 | EraIndexerApy, 15 | EraDelegatorIndexerApy, 16 | EraDelegatorApy, 17 | EraDelegatorIndexer, 18 | EraIndexerDeploymentApy, 19 | IndexerAllocationSummary, 20 | IndexerApySummary, 21 | IndexerStakeWeight, 22 | } from '../types'; 23 | import { 24 | EraManager__factory, 25 | ServiceAgreementRegistry__factory, 26 | } from '@subql/contract-sdk'; 27 | import { 28 | ClaimRewardsEvent, 29 | DistributeRewardsEvent, 30 | RewardsChangedEvent, 31 | } from '@subql/contract-sdk/typechain/contracts/RewardsDistributor'; 32 | import { 33 | biToDate, 34 | bnToDate, 35 | calcApy, 36 | Contracts, 37 | getContractAddress, 38 | getDelegationId, 39 | toBigInt, 40 | } from './utils'; 41 | import { EthereumLog } from '@subql/types-ethereum'; 42 | import { BigNumber } from '@ethersproject/bignumber'; 43 | import BignumberJs from 'bignumber.js'; 44 | import { getCurrentEra } from './eraManager'; 45 | import { 46 | AgreementRewardsEvent, 47 | InstantRewardsEvent, 48 | } from '../types/contracts/RewardsDistributor'; 49 | import { RewardType } from './utils/enums'; 50 | import { PER_MILL } from './utils/constants'; 51 | 52 | function buildRewardId(indexer: string, delegator: string): string { 53 | return `${indexer}:${delegator}`; 54 | } 55 | 56 | function getIndexerRewardId(indexer: string, eraIdx: BigNumber): string { 57 | return `${indexer}:${eraIdx.toHexString()}`; 58 | } 59 | 60 | function getPrevIndexerRewardId(indexer: string, eraIdx: BigNumber): string { 61 | return getIndexerRewardId(indexer, eraIdx.sub(1)); 62 | } 63 | 64 | export async function handleRewardsDistributed( 65 | event: EthereumLog 66 | ): Promise { 67 | logger.info( 68 | `handleRewardsDistributed: ${event.blockNumber}-${event.transactionHash}-${event.logIndex}` 69 | ); 70 | assert(event.args, 'No event args'); 71 | 72 | const { runner, eraIdx, rewards: totalRewards, commission } = event.args; 73 | const eraIndexerDelegator = 74 | (await EraIndexerDelegator.get(`${runner}:${eraIdx.toHexString()}`)) || 75 | (await EraIndexerDelegator.get(runner)); 76 | if (!eraIndexerDelegator) return; 77 | if (eraIndexerDelegator.era > eraIdx.toNumber()) { 78 | throw new Error( 79 | `EraIndexerDelegator era is greater than the current era: ${ 80 | eraIndexerDelegator.era 81 | } > ${eraIdx.toNumber()}` 82 | ); 83 | } 84 | const delegations = eraIndexerDelegator?.delegators; 85 | const totalDelegation = eraIndexerDelegator.totalStake; 86 | 87 | for (const delegationFrom of delegations) { 88 | const delegationAmount = toBigInt(delegationFrom.amount.toString()); 89 | let calculatedDelegationAmount = delegationAmount; 90 | let calculatedTotalDelegation = totalDelegation; 91 | if (delegationFrom.delegator === runner) { 92 | const indexerStakeWeight = await IndexerStakeWeight.get(runner); 93 | const weight = indexerStakeWeight?.weight || PER_MILL; 94 | if (weight !== PER_MILL) { 95 | calculatedDelegationAmount = (delegationAmount * weight) / PER_MILL; 96 | calculatedTotalDelegation = 97 | totalDelegation - delegationAmount + calculatedDelegationAmount; 98 | } 99 | } 100 | const estimatedRewards = totalRewards 101 | .sub(commission) 102 | .mul(calculatedDelegationAmount) 103 | .div(calculatedTotalDelegation); 104 | 105 | const id = buildRewardId(runner, delegationFrom.delegator); 106 | let reward = await UnclaimedReward.get(id); 107 | if (!reward) { 108 | reward = UnclaimedReward.create({ 109 | id, 110 | delegatorAddress: delegationFrom.delegator, 111 | delegatorId: delegationFrom.delegator, 112 | indexerAddress: runner, 113 | amount: estimatedRewards.toBigInt(), 114 | createdBlock: event.blockNumber, 115 | }); 116 | } else { 117 | reward.amount += estimatedRewards.toBigInt(); 118 | reward.lastEvent = `handleRewardsDistributed:${event.blockNumber}`; 119 | } 120 | await reward.save(); 121 | 122 | const delegationId = getDelegationId(delegationFrom.delegator, runner); 123 | const delegation = await Delegation.get(delegationId); 124 | assert(delegation, `delegation not found: ${delegationId}`); 125 | if (delegation.exitEra && delegation.exitEra <= eraIdx.toNumber()) { 126 | assert( 127 | estimatedRewards.eq(0), 128 | `exited delegator should not have reward changed: ${delegation.id} / ${ 129 | reward.indexerAddress 130 | }, ${estimatedRewards.toNumber()}` 131 | ); 132 | logger.info(`Delegation remove: ${delegation.id}`); 133 | await Delegation.remove(delegation.id); 134 | } 135 | 136 | if (estimatedRewards.gt(0)) { 137 | const eraReward = await createEraReward({ 138 | indexerId: runner, 139 | delegatorId: delegationFrom.delegator, 140 | eraId: eraIdx.toHexString(), 141 | eraIdx: eraIdx.toNumber(), 142 | isCommission: false, 143 | claimed: false, 144 | amount: estimatedRewards.toBigInt(), 145 | createdBlock: event.blockNumber, 146 | createdTimestamp: biToDate(event.block.timestamp), 147 | }); 148 | await upsertEraApy(eraReward); 149 | } 150 | } 151 | 152 | if (commission.gt(0)) { 153 | const eraReward = await createEraReward({ 154 | indexerId: runner, 155 | delegatorId: runner, 156 | eraId: eraIdx.toHexString(), 157 | eraIdx: eraIdx.toNumber(), 158 | isCommission: true, 159 | claimed: true, // commission rewards already in indexer's account 160 | amount: commission.toBigInt(), 161 | createdBlock: event.blockNumber, 162 | createdTimestamp: biToDate(event.block.timestamp), 163 | }); 164 | await upsertEraApy(eraReward); 165 | } 166 | } 167 | 168 | function getEraDelegationAmount( 169 | delegation: Delegation, 170 | eraIdx: BigNumber 171 | ): BigNumber { 172 | const value = BigNumber.from(delegation.amount.value.value); 173 | const valueAfter = BigNumber.from(delegation.amount.valueAfter.value); 174 | return eraIdx.gt(delegation.amount.era) ? valueAfter : value; 175 | } 176 | 177 | export async function handleRewardsClaimed( 178 | event: EthereumLog 179 | ): Promise { 180 | logger.info('handleRewardsClaimed'); 181 | assert(event.args, 'No event args'); 182 | 183 | const id = buildRewardId(event.args.runner, event.args.delegator); 184 | 185 | await UnclaimedReward.remove(id); 186 | 187 | await Reward.create({ 188 | id: `${id}:${event.transactionHash}`, 189 | indexerAddress: event.args.runner, 190 | delegatorAddress: event.args.delegator, 191 | delegatorId: event.args.delegator, 192 | amount: event.args.rewards.toBigInt(), 193 | claimedTime: biToDate(event.block.timestamp), 194 | createdBlock: event.blockNumber, 195 | }).save(); 196 | 197 | await updateEraRewardClaimed(event); 198 | } 199 | 200 | interface EraRewardData { 201 | indexerId: string; 202 | delegatorId: string; 203 | eraId: string; 204 | eraIdx: number; 205 | isCommission: boolean; 206 | claimed: boolean; 207 | amount: bigint; 208 | createdBlock: number; 209 | createdTimestamp: Date; 210 | } 211 | 212 | // once each era 213 | async function createEraReward(data: EraRewardData): Promise { 214 | logger.info('updateEraReward', data); 215 | 216 | const id = `${data.indexerId}:${data.delegatorId}:${data.eraId}${ 217 | data.isCommission ? '_commission' : '' 218 | }`; 219 | 220 | const eraReward = EraReward.create({ 221 | id, 222 | indexerId: data.indexerId, 223 | delegatorId: data.delegatorId, 224 | eraId: data.eraId, 225 | eraIdx: data.eraIdx, 226 | isIndexer: data.indexerId === data.delegatorId, 227 | isCommission: data.isCommission, 228 | claimed: data.claimed, 229 | amount: data.amount, 230 | createdBlock: data.createdBlock, 231 | createdTimestamp: data.createdTimestamp, 232 | }); 233 | 234 | await eraReward.save(); 235 | return eraReward; 236 | } 237 | 238 | async function updateEraRewardClaimed( 239 | event: EthereumLog 240 | ): Promise { 241 | logger.info('updateEraRewardClaimed'); 242 | assert(event.args, 'No event args'); 243 | 244 | const { runner, delegator } = event.args; 245 | const id = `${runner}:${delegator}`; 246 | 247 | let eraRewardClaimed = await EraRewardClaimed.get(id); 248 | if (!eraRewardClaimed) { 249 | eraRewardClaimed = EraRewardClaimed.create({ 250 | id, 251 | lastClaimedEra: 0, 252 | }); 253 | } 254 | 255 | const currentEra = await getCurrentEra(); 256 | let lastClaimedEra = eraRewardClaimed.lastClaimedEra; 257 | let nextClaimEra = lastClaimedEra + 1; 258 | 259 | while (nextClaimEra < currentEra) { 260 | nextClaimEra++; 261 | const eraRewardId = `${id}:${BigNumber.from(nextClaimEra).toHexString()}`; 262 | const eraReward = await EraReward.get(eraRewardId); 263 | 264 | if (!eraReward || eraReward.claimed) continue; 265 | 266 | eraReward.claimed = true; 267 | await eraReward.save(); 268 | 269 | lastClaimedEra = nextClaimEra; 270 | } 271 | 272 | if (lastClaimedEra > eraRewardClaimed.lastClaimedEra) { 273 | eraRewardClaimed.lastClaimedEra = lastClaimedEra; 274 | await eraRewardClaimed.save(); 275 | } 276 | } 277 | 278 | async function upsertEraApy(eraReward: EraReward) { 279 | await upsertEraIndexerApy(eraReward); 280 | if (!eraReward.isIndexer) { 281 | await upsertEraDelegatorApy(eraReward); 282 | } 283 | } 284 | 285 | async function upsertEraIndexerApy(eraReward: EraReward) { 286 | const eraIndexerApyId = `${eraReward.indexerId}:${eraReward.eraId}`; 287 | let eraIndexerApy = await EraIndexerApy.get(eraIndexerApyId); 288 | if (!eraIndexerApy) { 289 | eraIndexerApy = EraIndexerApy.create({ 290 | id: eraIndexerApyId, 291 | indexerId: eraReward.indexerId, 292 | eraIdx: eraReward.eraIdx, 293 | indexerReward: BigInt(0), 294 | indexerApy: BigInt(0), 295 | delegatorReward: BigInt(0), 296 | delegatorApy: BigInt(0), 297 | createAt: eraReward.createdTimestamp, 298 | updateAt: eraReward.createdTimestamp, 299 | }); 300 | } 301 | 302 | const eraIndexerDelegator = 303 | (await EraIndexerDelegator.get( 304 | `${eraReward.indexerId}:${eraReward.eraId}` 305 | )) || (await EraIndexerDelegator.get(eraReward.indexerId)); 306 | assert(eraIndexerDelegator, 'EraIndexerDelegator not found'); 307 | const selfStake = eraIndexerDelegator.selfStake; 308 | const delegatorStake = eraIndexerDelegator.totalStake - selfStake; 309 | 310 | if (eraReward.isIndexer) { 311 | eraIndexerApy.indexerReward += eraReward.amount; 312 | eraIndexerApy.indexerApy = calcApy(eraIndexerApy.indexerReward, selfStake); 313 | } else { 314 | eraIndexerApy.delegatorReward += eraReward.amount; 315 | eraIndexerApy.delegatorApy = calcApy( 316 | eraIndexerApy.delegatorReward, 317 | delegatorStake 318 | ); 319 | } 320 | eraIndexerApy.updateAt = eraReward.createdTimestamp; 321 | await eraIndexerApy.save(); 322 | 323 | await IndexerApySummary.create({ 324 | id: `${eraReward.indexerId}`, 325 | indexerId: eraIndexerApy.indexerId, 326 | eraIdx: eraIndexerApy.eraIdx, 327 | indexerReward: eraIndexerApy.indexerReward, 328 | indexerApy: eraIndexerApy.indexerApy, 329 | delegatorReward: eraIndexerApy.delegatorReward, 330 | delegatorApy: eraIndexerApy.delegatorApy, 331 | createAt: eraIndexerApy.createAt, 332 | updateAt: eraIndexerApy.updateAt, 333 | }).save(); 334 | } 335 | 336 | async function upsertEraDelegatorApy(eraReward: EraReward) { 337 | const eraDelegatorApyId = `${eraReward.delegatorId}:${eraReward.eraId}`; 338 | let eraDelegatorApy = await EraDelegatorApy.get(eraDelegatorApyId); 339 | if (!eraDelegatorApy) { 340 | eraDelegatorApy = EraDelegatorApy.create({ 341 | id: eraDelegatorApyId, 342 | delegatorId: eraReward.delegatorId, 343 | eraIdx: eraReward.eraIdx, 344 | reward: BigInt(0), 345 | apy: BigInt(0), 346 | createAt: eraReward.createdTimestamp, 347 | updateAt: eraReward.createdTimestamp, 348 | }); 349 | } 350 | 351 | const eraDelegatorIndexer = 352 | (await EraDelegatorIndexer.get( 353 | `${eraReward.delegatorId}:${eraReward.eraId}` 354 | )) || (await EraDelegatorIndexer.get(eraReward.delegatorId)); 355 | assert(eraDelegatorIndexer, 'EraDelegatorIndexer not found'); 356 | 357 | eraDelegatorApy.reward += eraReward.amount; 358 | eraDelegatorApy.apy = calcApy( 359 | eraDelegatorApy.reward, 360 | eraDelegatorIndexer.totalStake - eraDelegatorIndexer.selfStake 361 | ); 362 | eraDelegatorApy.updateAt = eraReward.createdTimestamp; 363 | await eraDelegatorApy.save(); 364 | 365 | const eraDelegatorIndxerApyId = `${eraReward.delegatorId}:${eraReward.indexerId}:${eraReward.eraId}`; 366 | let eraDelegatorIndexerApy = await EraDelegatorIndexerApy.get( 367 | eraDelegatorIndxerApyId 368 | ); 369 | if (!eraDelegatorIndexerApy) { 370 | eraDelegatorIndexerApy = EraDelegatorIndexerApy.create({ 371 | id: eraDelegatorIndxerApyId, 372 | eraIdx: eraReward.eraIdx, 373 | delegatorId: eraReward.delegatorId, 374 | indexerId: eraReward.indexerId, 375 | reward: BigInt(0), 376 | stake: BigInt(0), 377 | apy: BigInt(0), 378 | createAt: eraReward.createdTimestamp, 379 | updateAt: eraReward.createdTimestamp, 380 | }); 381 | } 382 | 383 | eraDelegatorIndexerApy.reward += eraReward.amount; 384 | eraDelegatorIndexerApy.stake = 385 | toBigInt( 386 | eraDelegatorIndexer.indexers 387 | .find((i) => i.indexer === eraReward.indexerId) 388 | ?.amount?.toString() 389 | ) ?? BigInt(0); 390 | 391 | eraDelegatorIndexerApy.apy = calcApy( 392 | eraDelegatorIndexerApy.reward, 393 | eraDelegatorIndexerApy.stake 394 | ); 395 | eraDelegatorIndexerApy.updateAt = eraReward.createdTimestamp; 396 | await eraDelegatorIndexerApy.save(); 397 | } 398 | 399 | export async function upsertEraIndexerDeploymentApy( 400 | indexerId: string, 401 | deploymentId: string, 402 | eraIdx: number, 403 | rewardType: RewardType, 404 | add: bigint, 405 | remove: bigint, 406 | updateAt: Date 407 | ) { 408 | const apyId = `${indexerId}:${deploymentId}:${eraIdx}`; 409 | let apy = await EraIndexerDeploymentApy.get(apyId); 410 | if (!apy) { 411 | apy = EraIndexerDeploymentApy.create({ 412 | id: apyId, 413 | indexerId, 414 | deploymentId, 415 | eraIdx, 416 | agreementReward: BigInt(0), 417 | flexPlanReward: BigInt(0), 418 | allocationReward: BigInt(0), 419 | apy: BigInt(0), 420 | createAt: updateAt, 421 | updateAt: updateAt, 422 | }); 423 | } 424 | switch (rewardType) { 425 | case RewardType.AGREEMENT: 426 | apy.agreementReward += add - remove; 427 | break; 428 | case RewardType.FLEX_PLAN: 429 | apy.flexPlanReward += add - remove; 430 | break; 431 | case RewardType.ALLOCATION: { 432 | apy.allocationReward += add - remove; 433 | const allocation = 434 | (await IndexerAllocationSummary.get(`${deploymentId}:${indexerId}`)) 435 | ?.totalAmount || BigInt(0); 436 | apy.apy = calcApy(apy.allocationReward, allocation); 437 | break; 438 | } 439 | } 440 | apy.updateAt = updateAt; 441 | await apy.save(); 442 | } 443 | 444 | export async function handleRewardsUpdated( 445 | event: EthereumLog 446 | ): Promise {} 447 | 448 | const updateOrCreateIndexerReward = async ( 449 | id: string, 450 | amount: BigNumber, 451 | runner: string, 452 | eraIdx: BigNumber, 453 | blockNumber: number, 454 | lastEventString: string 455 | ) => { 456 | const existIndexerRewards = await IndexerReward.get(id); 457 | if (existIndexerRewards) { 458 | existIndexerRewards.amount += amount.toBigInt(); 459 | await existIndexerRewards.save(); 460 | return; 461 | } 462 | 463 | const newIndexerRewards = IndexerReward.create({ 464 | id, 465 | indexerId: runner, 466 | eraIdx: eraIdx.toHexString(), 467 | eraId: eraIdx.toBigInt(), 468 | amount: amount.toBigInt(), 469 | createdBlock: blockNumber, 470 | lastEvent: `${lastEventString}:${blockNumber}`, 471 | }); 472 | 473 | await newIndexerRewards.save(); 474 | }; 475 | 476 | export async function handleInstantRewards( 477 | event: EthereumLog 478 | ): Promise { 479 | logger.info('handleInstantRewardsUpdated'); 480 | assert(event.args, 'No event args'); 481 | 482 | const { runner, eraIdx, token: amount } = event.args; 483 | const id = getIndexerRewardId(runner, eraIdx); 484 | await updateOrCreateIndexerReward( 485 | id, 486 | amount, 487 | runner, 488 | eraIdx, 489 | event.blockNumber, 490 | 'handleInstantRewards' 491 | ); 492 | } 493 | 494 | export async function handleAgreementRewards( 495 | event: EthereumLog 496 | ): Promise { 497 | logger.info('handleAgreementRewardsUpdated'); 498 | assert(event.args, 'No event args'); 499 | 500 | const { runner, agreementId, token: amount } = event.args; 501 | 502 | const network = await api.getNetwork(); 503 | const serviceAgreementContract = ServiceAgreementRegistry__factory.connect( 504 | getContractAddress(network.chainId, Contracts.SA_REGISTRY_ADDRESS), 505 | api 506 | ); 507 | 508 | const eraManager = EraManager__factory.connect( 509 | getContractAddress(network.chainId, Contracts.ERA_MANAGER_ADDRESS), 510 | api 511 | ); 512 | 513 | const currentEra = await getCurrentEra(); 514 | const currentEraInfo = await Era.get( 515 | BigNumber.from(currentEra).toHexString() 516 | ); 517 | 518 | if (!currentEraInfo) { 519 | logger.error(`current era not found in records: ${currentEra}`); 520 | return; 521 | } 522 | 523 | const currentEraStartDate = new Date(currentEraInfo.startTime); 524 | 525 | const eraPeriod = await eraManager.eraPeriod(); 526 | const { startDate, period, deploymentId } = 527 | await serviceAgreementContract.getClosedServiceAgreement(agreementId); 528 | 529 | const agreementStartDate = bnToDate(startDate); 530 | 531 | const agreementFirstEraRate = BignumberJs(1).minus( 532 | // the agreement start - era start is the time passed, these time they shouldn't get tokens 533 | // then / eraPeriod to get the rate. 534 | BignumberJs(agreementStartDate.getTime() - currentEraStartDate.getTime()) 535 | .div(1000) 536 | .div(eraPeriod.toString()) 537 | ); 538 | 539 | const agreementLastEraNumbers = BignumberJs(period.toString()).div( 540 | eraPeriod.toString() 541 | ); 542 | const everyEraAmount = BignumberJs(amount.toString()).div( 543 | agreementLastEraNumbers.toString() 544 | ); 545 | 546 | // split the agreement to first ... last 547 | // first amount should be calculated by the rate of the first era 548 | const agreementFirstEraAmount = everyEraAmount.multipliedBy( 549 | agreementFirstEraRate 550 | ); 551 | 552 | // this agreement less than 1 era 553 | if (agreementLastEraNumbers.lte(1)) { 554 | // if the agreement less than 1 era and will end before the next era 555 | if ( 556 | +bnToDate(startDate.add(period)) < 557 | +currentEraInfo.startTime + eraPeriod.mul(1000).toNumber() 558 | ) { 559 | await updateOrCreateIndexerReward( 560 | getIndexerRewardId(runner, BigNumber.from(currentEra)), 561 | BigNumber.from(amount.toString()), 562 | runner, 563 | BigNumber.from(currentEra), 564 | event.blockNumber, 565 | 'handleServicesAgreementRewards' 566 | ); 567 | await upsertEraIndexerDeploymentApy( 568 | runner, 569 | deploymentId, 570 | currentEra, 571 | RewardType.AGREEMENT, 572 | amount.toBigInt(), 573 | BigInt(0), 574 | biToDate(event.block.timestamp) 575 | ); 576 | return; 577 | } 578 | // otherwise can use same process as the agreement has more than 1 era 579 | } 580 | 581 | await updateOrCreateIndexerReward( 582 | getIndexerRewardId(runner, BigNumber.from(currentEra)), 583 | BigNumber.from(agreementFirstEraAmount.toFixed(0)), 584 | runner, 585 | BigNumber.from(currentEra), 586 | event.blockNumber, 587 | 'handleServicesAgreementRewards' 588 | ); 589 | await upsertEraIndexerDeploymentApy( 590 | runner, 591 | deploymentId, 592 | currentEra, 593 | RewardType.AGREEMENT, 594 | BigInt(agreementFirstEraAmount.toFixed(0)), 595 | BigInt(0), 596 | biToDate(event.block.timestamp) 597 | ); 598 | 599 | // minus first rate and then less than 1 indicates this agreement only have two era 600 | if (agreementLastEraNumbers.minus(agreementFirstEraRate).lte(1)) { 601 | const eraId = BigNumber.from(currentEra + 1); 602 | const leftAmount = BignumberJs(amount.toString()).minus( 603 | agreementFirstEraAmount 604 | ); 605 | await updateOrCreateIndexerReward( 606 | getIndexerRewardId(runner, eraId), 607 | BigNumber.from(leftAmount.toFixed(0)), 608 | runner, 609 | eraId, 610 | event.blockNumber, 611 | 'handleServicesAgreementRewards' 612 | ); 613 | await upsertEraIndexerDeploymentApy( 614 | runner, 615 | deploymentId, 616 | eraId.toNumber(), 617 | RewardType.AGREEMENT, 618 | BigInt(leftAmount.toFixed(0)), 619 | BigInt(0), 620 | biToDate(event.block.timestamp) 621 | ); 622 | return; 623 | } 624 | 625 | // if the agreement has more than 2 era 626 | const leftEra = agreementLastEraNumbers.minus(agreementFirstEraRate); 627 | const integerPart = leftEra.integerValue(BignumberJs.ROUND_DOWN); 628 | const decimalPart = leftEra.minus(integerPart); 629 | const decimalPartAmount = everyEraAmount.multipliedBy(decimalPart); 630 | const lastEra = leftEra.integerValue(BignumberJs.ROUND_CEIL); 631 | 632 | for (let index = 0; index < integerPart.toNumber(); index++) { 633 | await updateOrCreateIndexerReward( 634 | getIndexerRewardId(runner, BigNumber.from(currentEra + index + 1)), 635 | BigNumber.from(everyEraAmount.toFixed(0)), 636 | runner, 637 | BigNumber.from(currentEra + index + 1), 638 | event.blockNumber, 639 | 'handleServicesAgreementRewards' 640 | ); 641 | await upsertEraIndexerDeploymentApy( 642 | runner, 643 | deploymentId, 644 | currentEra + index + 1, 645 | RewardType.AGREEMENT, 646 | BigInt(everyEraAmount.toFixed(0)), 647 | BigInt(0), 648 | biToDate(event.block.timestamp) 649 | ); 650 | } 651 | await updateOrCreateIndexerReward( 652 | getIndexerRewardId(runner, BigNumber.from(currentEra + lastEra.toNumber())), 653 | BigNumber.from(decimalPartAmount.toFixed(0)), 654 | runner, 655 | BigNumber.from(currentEra + lastEra.toNumber()), 656 | event.blockNumber, 657 | 'handleServicesAgreementRewards' 658 | ); 659 | await upsertEraIndexerDeploymentApy( 660 | runner, 661 | deploymentId, 662 | currentEra + lastEra.toNumber(), 663 | RewardType.AGREEMENT, 664 | BigInt(decimalPartAmount.toFixed(0)), 665 | BigInt(0), 666 | biToDate(event.block.timestamp) 667 | ); 668 | } 669 | -------------------------------------------------------------------------------- /src/mappings/rewardsStaking.ts: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { EthereumLog } from '@subql/types-ethereum'; 3 | import { IndexerStakeWeight } from '../types'; 4 | import { biToDate } from './utils'; 5 | import { getCurrentEra } from './eraManager'; 6 | import { RunnerWeightAppliedEvent } from '@subql/contract-sdk/typechain/contracts/RewardsStaking'; 7 | 8 | export async function handleRunnerWeightApplied( 9 | event: EthereumLog 10 | ) { 11 | logger.info(`handleRunnerWeightApplied`); 12 | assert(event.args, 'No event args'); 13 | const { runner: indexer, weight } = event.args; 14 | 15 | const eraIdx = await getCurrentEra(); 16 | 17 | let indexerStakeWeight = await IndexerStakeWeight.get(indexer); 18 | if (!indexerStakeWeight) { 19 | indexerStakeWeight = IndexerStakeWeight.create({ 20 | id: indexer, 21 | indexerId: indexer, 22 | eraIdx, 23 | weight: weight.toBigInt(), 24 | createAt: biToDate(event.block.timestamp), 25 | updateAt: biToDate(event.block.timestamp), 26 | }); 27 | } 28 | indexerStakeWeight.eraIdx = eraIdx; 29 | indexerStakeWeight.weight = weight.toBigInt(); 30 | indexerStakeWeight.updateAt = biToDate(event.block.timestamp); 31 | await indexerStakeWeight.save(); 32 | } 33 | -------------------------------------------------------------------------------- /src/mappings/serviceAgreement.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { IServiceAgreementRegistry__factory } from '@subql/contract-sdk'; 5 | import { 6 | ClosedAgreementCreatedEvent, 7 | TransferEvent, 8 | } from '@subql/contract-sdk/typechain/contracts/ServiceAgreementRegistry'; 9 | import { EthereumLog } from '@subql/types-ethereum'; 10 | import assert from 'assert'; 11 | import { constants } from 'ethers'; 12 | import { Deployment, Project, ServiceAgreement } from '../types'; 13 | import { 14 | Contracts, 15 | biToDate, 16 | bytesToIpfsCid, 17 | getContractAddress, 18 | } from './utils'; 19 | 20 | export async function handleServiceAgreementCreated( 21 | event: EthereumLog 22 | ): Promise { 23 | logger.info('handleClosedServiceAgreementCreated'); 24 | assert(event.args, 'No event args'); 25 | 26 | const { indexer, consumer, deploymentId, serviceAgreementId } = event.args; 27 | 28 | const network = await api.getNetwork(); 29 | const agreementRegistry = IServiceAgreementRegistry__factory.connect( 30 | getContractAddress(network.chainId, Contracts.SA_REGISTRY_ADDRESS), 31 | api 32 | ); 33 | 34 | const agreement = await agreementRegistry.getClosedServiceAgreement( 35 | serviceAgreementId 36 | ); 37 | 38 | const { period, lockedAmount, planTemplateId } = agreement; 39 | const endTime = biToDate(event.block.timestamp); 40 | endTime.setSeconds(endTime.getSeconds() + period.toNumber()); 41 | 42 | const sa = ServiceAgreement.create({ 43 | id: serviceAgreementId.toString(), 44 | indexerAddress: indexer, 45 | consumerAddress: consumer, 46 | deploymentId: bytesToIpfsCid(deploymentId), 47 | planTemplateId: planTemplateId.toHexString(), 48 | period: period.toBigInt(), 49 | startTime: biToDate(event.block.timestamp), 50 | endTime, 51 | lockedAmount: lockedAmount.toBigInt(), 52 | createdBlock: event.blockNumber, 53 | }); 54 | 55 | await sa.save(); 56 | 57 | const deployment = await Deployment.get(sa.deploymentId); 58 | assert(deployment, `deployment ${sa.deploymentId} not found`); 59 | const project = await Project.get(deployment.projectId); 60 | assert(project, `project ${deployment.projectId} not found`); 61 | project.totalReward += lockedAmount.toBigInt(); 62 | await project.save(); 63 | } 64 | 65 | export async function handlerAgreementTransferred( 66 | event: EthereumLog 67 | ): Promise { 68 | logger.info('handlerAgreementTransferred'); 69 | assert(event.args, 'No event args'); 70 | 71 | const { from, to, tokenId } = event.args; 72 | // Ignore `mint` event 73 | if (from === constants.AddressZero) return; 74 | 75 | const agreement = await ServiceAgreement.get(tokenId.toString()); 76 | assert(agreement, `Expected query (${tokenId}) to exist`); 77 | assert(agreement.consumerAddress === from, `Expected owner to be ${from}`); 78 | 79 | agreement.consumerAddress = to; 80 | agreement.lastEvent = `handlerAgreementTransferred:${event.blockNumber}`; 81 | 82 | await agreement.save(); 83 | } 84 | -------------------------------------------------------------------------------- /src/mappings/stakingAllocation.ts: -------------------------------------------------------------------------------- 1 | import { EthereumLog } from '@subql/types-ethereum'; 2 | import { 3 | OverAllocationEndedEvent, 4 | OverAllocationStartedEvent, 5 | StakeAllocationAddedEvent, 6 | StakeAllocationRemovedEvent, 7 | } from '../types/contracts/StakingAllocation'; 8 | import assert from 'assert'; 9 | import { 10 | Deployment, 11 | IndexerAllocation, 12 | IndexerAllocationOverflow, 13 | IndexerAllocationSummary, 14 | IndexerLatestAllocationOverflow, 15 | Project, 16 | } from '../types'; 17 | import { biToDate, bytesToIpfsCid } from './utils'; 18 | import { getCurrentEra } from './eraManager'; 19 | 20 | export async function handleStakeAllocationAdded( 21 | event: EthereumLog 22 | ): Promise { 23 | logger.info('handleStakeAllocationAdded'); 24 | assert(event.args, 'No event args'); 25 | const { runner: indexerId, amount: amountAdded } = event.args; 26 | const deploymentId = bytesToIpfsCid(event.args.deploymentId); 27 | 28 | const deployment = await Deployment.get(deploymentId); 29 | assert(deployment, `Deployment ${deploymentId} not found`); 30 | 31 | const project = await Project.get(deployment.projectId); 32 | assert(project, `Project ${deployment.projectId} not found`); 33 | 34 | const allocationId = `${deploymentId}:${indexerId}:${event.transactionHash}`; 35 | let allocation = await IndexerAllocation.get(allocationId); 36 | assert(!allocation, 'Allocation already exists'); 37 | 38 | const eraIdx = await getCurrentEra(); 39 | 40 | allocation = IndexerAllocation.create({ 41 | id: allocationId, 42 | projectId: project.id, 43 | deploymentId, 44 | indexerId, 45 | amountAdded: amountAdded.toBigInt(), 46 | amountRemoved: BigInt(0), 47 | eraIdx, 48 | createAt: biToDate(event.block.timestamp), 49 | }); 50 | await allocation.save(); 51 | 52 | const summaryId = `${deploymentId}:${indexerId}`; 53 | let summary = await IndexerAllocationSummary.get(summaryId); 54 | if (!summary) { 55 | summary = IndexerAllocationSummary.create({ 56 | id: summaryId, 57 | projectId: project.id, 58 | deploymentId, 59 | indexerId, 60 | totalAdded: amountAdded.toBigInt(), 61 | totalRemoved: BigInt(0), 62 | totalAmount: amountAdded.toBigInt(), 63 | createAt: biToDate(event.block.timestamp), 64 | updateAt: biToDate(event.block.timestamp), 65 | }); 66 | } else { 67 | summary.totalAdded += amountAdded.toBigInt(); 68 | summary.totalAmount = summary.totalAdded - summary.totalRemoved; 69 | summary.updateAt = biToDate(event.block.timestamp); 70 | } 71 | await summary.save(); 72 | } 73 | 74 | export async function handleStakeAllocationRemoved( 75 | event: EthereumLog 76 | ): Promise { 77 | logger.info('handleStakeAllocationRemoved'); 78 | assert(event.args, 'No event args'); 79 | const { runner: indexerId, amount: amountRemoved } = event.args; 80 | const deploymentId = bytesToIpfsCid(event.args.deploymentId); 81 | 82 | const deployment = await Deployment.get(deploymentId); 83 | assert(deployment, `Deployment ${deploymentId} not found`); 84 | 85 | const project = await Project.get(deployment.projectId); 86 | assert(project, `Project ${deployment.projectId} not found`); 87 | 88 | const allocationId = `${deploymentId}:${indexerId}:${event.transactionHash}`; 89 | let allocation = await IndexerAllocation.get(allocationId); 90 | assert(!allocation, 'Allocation already exists'); 91 | 92 | const eraIdx = await getCurrentEra(); 93 | 94 | allocation = IndexerAllocation.create({ 95 | id: allocationId, 96 | projectId: project.id, 97 | deploymentId, 98 | indexerId, 99 | amountAdded: BigInt(0), 100 | amountRemoved: amountRemoved.toBigInt(), 101 | eraIdx, 102 | createAt: biToDate(event.block.timestamp), 103 | }); 104 | await allocation.save(); 105 | 106 | const summaryId = `${deploymentId}:${indexerId}`; 107 | let summary = await IndexerAllocationSummary.get(summaryId); 108 | if (!summary) { 109 | summary = IndexerAllocationSummary.create({ 110 | id: summaryId, 111 | projectId: project.id, 112 | deploymentId, 113 | indexerId, 114 | totalAdded: BigInt(0), 115 | totalRemoved: amountRemoved.toBigInt(), 116 | totalAmount: BigInt(0), 117 | createAt: biToDate(event.block.timestamp), 118 | updateAt: biToDate(event.block.timestamp), 119 | }); 120 | } else { 121 | summary.totalRemoved += amountRemoved.toBigInt(); 122 | summary.totalAmount = summary.totalAdded - summary.totalRemoved; 123 | summary.updateAt = biToDate(event.block.timestamp); 124 | } 125 | await summary.save(); 126 | } 127 | 128 | export async function handleOverAllocationStarted( 129 | event: EthereumLog 130 | ): Promise { 131 | logger.info('handleOverAllocationStarted'); 132 | assert(event.args, 'No event args'); 133 | const { runner, start } = event.args; 134 | 135 | const latestOverflowId = `${runner}`; 136 | let latestOverflow = await IndexerLatestAllocationOverflow.get( 137 | latestOverflowId 138 | ); 139 | assert(!latestOverflow, 'Latest overflow already exists'); 140 | 141 | const overflowId = `${runner}:${event.transactionHash}`; 142 | let overflow = await IndexerAllocationOverflow.get(overflowId); 143 | assert(!overflow, 'Overflow already exists'); 144 | 145 | overflow = IndexerAllocationOverflow.create({ 146 | id: overflowId, 147 | indexerId: runner, 148 | overflowStart: biToDate(start.toBigInt()), 149 | overflowEnd: new Date(0), 150 | overflowTime: BigInt(0), 151 | eraIdxStart: await getCurrentEra(), 152 | eraIdxEnd: -1, 153 | createAt: biToDate(event.block.timestamp), 154 | updateAt: biToDate(event.block.timestamp), 155 | }); 156 | await overflow.save(); 157 | 158 | latestOverflow = IndexerLatestAllocationOverflow.create({ 159 | id: latestOverflowId, 160 | overflowIdId: overflowId, 161 | createAt: biToDate(event.block.timestamp), 162 | updateAt: biToDate(event.block.timestamp), 163 | }); 164 | await latestOverflow.save(); 165 | } 166 | 167 | export async function handleOverAllocationEnded( 168 | event: EthereumLog 169 | ): Promise { 170 | logger.info('handleOverAllocationEnded'); 171 | assert(event.args, 'No event args'); 172 | const { runner, end, time } = event.args; 173 | 174 | const latestOverflowId = `${runner}`; 175 | const latestOverflow = await IndexerLatestAllocationOverflow.get( 176 | latestOverflowId 177 | ); 178 | assert(latestOverflow, 'Latest overflow not found'); 179 | 180 | const overflowId = latestOverflow.overflowIdId; 181 | const overflow = await IndexerAllocationOverflow.get(overflowId); 182 | assert(overflow, 'Overflow not found'); 183 | 184 | overflow.overflowEnd = biToDate(end.toBigInt()); 185 | overflow.overflowTime = time.toBigInt(); 186 | overflow.eraIdxEnd = await getCurrentEra(); 187 | overflow.updateAt = biToDate(event.block.timestamp); 188 | await overflow.save(); 189 | 190 | await IndexerLatestAllocationOverflow.remove(latestOverflowId); 191 | } 192 | -------------------------------------------------------------------------------- /src/mappings/stateChannel.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { 5 | ChannelCheckpointEvent, 6 | ChannelExtendEvent, 7 | ChannelFinalizeEvent, 8 | ChannelFundEvent, 9 | ChannelOpenEvent, 10 | ChannelTerminateEvent, 11 | } from '@subql/contract-sdk/typechain/contracts/StateChannel'; 12 | import { EthereumLog } from '@subql/types-ethereum'; 13 | import assert from 'assert'; 14 | import { logger, utils } from 'ethers'; 15 | import { ChannelStatus, Deployment, Project, StateChannel } from '../types'; 16 | import { biToDate, bytesToIpfsCid } from './utils'; 17 | import { upsertEraIndexerDeploymentApy } from './rewardsDistributor'; 18 | import { RewardType } from './utils/enums'; 19 | import { getCurrentEra } from './eraManager'; 20 | 21 | export async function handleChannelOpen( 22 | event: EthereumLog 23 | ): Promise { 24 | assert(event.args, 'No event args'); 25 | 26 | const { 27 | channelId, 28 | indexer, 29 | consumer: _consumer, 30 | total, 31 | price, 32 | expiredAt, 33 | deploymentId, 34 | callback, 35 | } = event.args; 36 | 37 | logger.info( 38 | `handleChannelOpen: channel: ${channelId.toHexString()}, at ${ 39 | event.blockNumber 40 | }-${event.blockHash}-${event.transactionHash}` 41 | ); 42 | let consumer = _consumer; 43 | let agent: string | undefined = undefined; 44 | try { 45 | consumer = utils.defaultAbiCoder.decode(['address'], callback)[0] as string; 46 | agent = _consumer; 47 | } catch (e) { 48 | logger.info(`Channel created by ${indexer}`); 49 | } 50 | 51 | const sc = StateChannel.create({ 52 | id: channelId.toHexString(), 53 | indexer, 54 | consumer, 55 | agent, 56 | status: ChannelStatus.OPEN, 57 | realTotal: total.toBigInt(), 58 | total: total.toBigInt(), 59 | price: price.toBigInt(), 60 | spent: BigInt(0), 61 | isFinal: false, 62 | expiredAt: new Date(expiredAt.toNumber() * 1000), 63 | deploymentId: bytesToIpfsCid(deploymentId), 64 | terminateByIndexer: false, 65 | startTime: biToDate(event.block.timestamp), 66 | lastEvent: `handleChannelOpen:${event.transactionHash}`, 67 | }); 68 | 69 | await sc.save(); 70 | logger.info(`handleChannelOpen Done: channel: ${channelId.toHexString()}`); 71 | } 72 | 73 | export async function handleChannelExtend( 74 | event: EthereumLog 75 | ): Promise { 76 | logger.info('handleChannelExtend'); 77 | assert(event.args, 'No event args'); 78 | 79 | const { channelId, expiredAt } = event.args; 80 | const sc = await StateChannel.get(channelId.toHexString()); 81 | assert(sc, `Expected StateChannel (${channelId.toHexString()}) to exist`); 82 | sc.expiredAt = new Date(expiredAt.toNumber() * 1000); 83 | await sc.save(); 84 | } 85 | 86 | export async function handleChannelFund( 87 | event: EthereumLog 88 | ): Promise { 89 | logger.info('handleChannelFund'); 90 | assert(event.args, 'No event args'); 91 | 92 | const { channelId, total, realTotal } = event.args; 93 | const sc = await StateChannel.get(channelId.toHexString()); 94 | assert(sc, `Expected StateChannel (${channelId.toHexString()}) to exist`); 95 | sc.total = total.toBigInt(); 96 | sc.realTotal = realTotal.toBigInt(); 97 | await sc.save(); 98 | } 99 | 100 | export async function handleChannelCheckpoint( 101 | event: EthereumLog 102 | ): Promise { 103 | logger.info('handleChannelCheckpoint'); 104 | assert(event.args, 'No event args'); 105 | 106 | const { channelId, spent, isFinal } = event.args; 107 | const sc = await StateChannel.get(channelId.toHexString()); 108 | assert(sc, `Expected StateChannel (${channelId.toHexString()}) to exist`); 109 | const diff = spent.toBigInt() - sc.spent; 110 | sc.spent = spent.toBigInt(); 111 | sc.isFinal = isFinal; 112 | await sc.save(); 113 | if (diff > 0) { 114 | const deployment = await Deployment.get(sc.deploymentId); 115 | assert(deployment, `deployment ${sc.deploymentId} not found`); 116 | const project = await Project.get(deployment.projectId); 117 | assert(project, `project ${deployment.projectId} not found`); 118 | project.totalReward += diff; 119 | await project.save(); 120 | 121 | await upsertEraIndexerDeploymentApy( 122 | sc.indexer, 123 | sc.deploymentId, 124 | await getCurrentEra(), 125 | RewardType.FLEX_PLAN, 126 | diff, 127 | BigInt(0), 128 | biToDate(event.block.timestamp) 129 | ); 130 | } 131 | } 132 | 133 | export async function handleChannelTerminate( 134 | event: EthereumLog 135 | ): Promise { 136 | logger.info('handleChannelTerminate'); 137 | assert(event.args, 'No event args'); 138 | 139 | const { channelId, spent, terminatedAt, terminateByIndexer } = event.args; 140 | const sc = await StateChannel.get(channelId.toHexString()); 141 | assert(sc, `Expected StateChannel (${channelId.toHexString()}) to exist`); 142 | 143 | sc.terminatedAt = new Date(terminatedAt.toNumber() * 1000); 144 | sc.terminateByIndexer = terminateByIndexer; 145 | 146 | if (sc.status === ChannelStatus.FINALIZED) { 147 | await sc.save(); 148 | return; 149 | } 150 | 151 | const diff = spent.toBigInt() - sc.spent; 152 | sc.spent = spent.toBigInt(); 153 | sc.status = ChannelStatus.TERMINATING; 154 | await sc.save(); 155 | if (diff > 0) { 156 | const deployment = await Deployment.get(sc.deploymentId); 157 | assert(deployment, `deployment ${sc.deploymentId} not found`); 158 | const project = await Project.get(deployment.projectId); 159 | assert(project, `project ${deployment.projectId} not found`); 160 | project.totalReward += diff; 161 | await project.save(); 162 | } 163 | } 164 | 165 | export async function handleChannelFinalize( 166 | event: EthereumLog 167 | ): Promise { 168 | logger.info('handleChannelCheckpoint'); 169 | assert(event.args, 'No event args'); 170 | 171 | const { channelId, total, remain } = event.args; 172 | const sc = await StateChannel.get(channelId.toHexString()); 173 | assert(sc, `Expected StateChannel (${channelId.toHexString()}) to exist`); 174 | sc.status = ChannelStatus.FINALIZED; 175 | const diff = total.toBigInt() - remain.toBigInt() - sc.spent; 176 | sc.spent = total.toBigInt() - remain.toBigInt(); 177 | await sc.save(); 178 | if (diff > 0) { 179 | const deployment = await Deployment.get(sc.deploymentId); 180 | assert(deployment, `deployment ${sc.deploymentId} not found`); 181 | const project = await Project.get(deployment.projectId); 182 | assert(project, `project ${deployment.projectId} not found`); 183 | project.totalReward += diff; 184 | await project.save(); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/mappings/tokenExchange.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { 5 | ExchangeOrderSentEvent, 6 | OrderSettledEvent, 7 | TradeEvent, 8 | } from '@subql/contract-sdk/typechain/contracts/TokenExchange'; 9 | import { EthereumLog } from '@subql/types-ethereum'; 10 | import assert from 'assert'; 11 | import { Order, OrderStatus } from '../types'; 12 | import { biToDate } from './utils'; 13 | 14 | export async function handleExchangeOrderSent( 15 | event: EthereumLog 16 | ): Promise { 17 | logger.info('handleExchangeOrderSent'); 18 | assert(event.args, 'No event args'); 19 | 20 | const { 21 | orderId, 22 | sender, 23 | tokenGive, 24 | tokenGet, 25 | amountGive, 26 | amountGet, 27 | tokenGiveBalance, 28 | } = event.args; 29 | 30 | const order = Order.create({ 31 | id: orderId.toString(), 32 | sender, 33 | tokenGive, 34 | tokenGet, 35 | amountGive: amountGive.toBigInt(), 36 | amountGet: amountGet.toBigInt(), 37 | tokenGiveBalance: tokenGiveBalance.toBigInt(), 38 | status: OrderStatus.ACTIVE, 39 | createAt: biToDate(event.block.timestamp), 40 | updateAt: biToDate(event.block.timestamp), 41 | }); 42 | 43 | await order.save(); 44 | } 45 | 46 | export async function handleOrderSettled( 47 | event: EthereumLog 48 | ): Promise { 49 | logger.info('handleOrderSettled'); 50 | assert(event.args, 'No event args'); 51 | 52 | const { orderId } = event.args; 53 | const order = await Order.get(orderId.toString()); 54 | assert(order, `Order ${orderId.toString()} not found`); 55 | 56 | order.status = OrderStatus.INACTIVE; 57 | order.tokenGiveBalance = BigInt(0); 58 | order.updateAt = biToDate(event.block.timestamp); 59 | 60 | await order.save(); 61 | } 62 | -------------------------------------------------------------------------------- /src/mappings/transfer.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { TransferEvent } from '@subql/contract-sdk/typechain/contracts/l2/L2SQToken'; 5 | import { EthereumLog } from '@subql/types-ethereum'; 6 | import { ethers } from 'ethers'; 7 | import { Sqtoken, TokenHolder, Transfer } from '../types'; 8 | import assert from 'assert'; 9 | 10 | const TreasuryAddr = '0x34c35136ECe9CBD6DfDf2F896C6e29be01587c0C'.toLowerCase(); 11 | const AirdropperAddr = 12 | '0x22Ab0be7a2eC82a983883839f7d5b4B12F5EbddC'.toLowerCase(); 13 | 14 | function isReservedContract(address: string): boolean { 15 | return [TreasuryAddr, AirdropperAddr].includes(address.toLowerCase()); 16 | } 17 | 18 | export async function handleTransfer( 19 | event: EthereumLog 20 | ): Promise { 21 | logger.info(`New transfer transaction log at block ${event.blockNumber}`); 22 | assert(event.args, 'No event args'); 23 | const { from, to, value } = event.args; 24 | const transfer = Transfer.create({ 25 | id: `${event.transactionHash}-${event.logIndex}`, 26 | from, 27 | to, 28 | txHash: event.transactionHash, 29 | amount: value.toBigInt(), 30 | timestamp: new Date(Number(event.block.timestamp) * 1000), 31 | blockheight: BigInt(event.blockNumber), 32 | }); 33 | await transfer.save(); 34 | const tokenAddr = event.address; 35 | // #1 Process TokenHolder, (skip empty address) 36 | if (from !== ethers.constants.AddressZero) { 37 | let fromAccount = await TokenHolder.get(from); 38 | if (!fromAccount) { 39 | fromAccount = new TokenHolder(from, BigInt(0), tokenAddr); 40 | } else { 41 | fromAccount.balance = fromAccount.balance - event.args.value.toBigInt(); 42 | } 43 | await fromAccount.save(); 44 | } 45 | if (to !== ethers.constants.AddressZero) { 46 | let toAccount = await TokenHolder.get(to); 47 | if (!toAccount) { 48 | toAccount = new TokenHolder(to, BigInt(0), tokenAddr); 49 | } 50 | toAccount.balance = toAccount.balance + event.args.value.toBigInt(); 51 | await toAccount.save(); 52 | } 53 | // #2 Maintain circulatingSupply 54 | // mint: add circulatingSupply 55 | logger.info(`found transfer from ${from} to ${to}`); 56 | let token = await Sqtoken.get(tokenAddr); 57 | if (!token) { 58 | token = new Sqtoken(tokenAddr, BigInt(0), BigInt(0)); 59 | } 60 | let addCirculating = false; 61 | let removeCirculating = false; 62 | // mint 63 | if (from === ethers.constants.AddressZero) { 64 | logger.info(`Mint at block ${event.blockNumber} from ${from}`); 65 | token.totalSupply += event.args.value.toBigInt(); 66 | 67 | if (!isReservedContract(to)) { 68 | addCirculating = true; 69 | } 70 | } 71 | // burn: remove circulatingSupply 72 | if (to === ethers.constants.AddressZero) { 73 | logger.info(`Burn at block ${event.blockNumber} from ${from}`); 74 | token.totalSupply = token.totalSupply - event.args.value.toBigInt(); 75 | 76 | if (!isReservedContract(from)) { 77 | removeCirculating = true; 78 | } 79 | } 80 | // treasury out: add circulatingSupply 81 | if (isReservedContract(from)) { 82 | addCirculating = true; 83 | } 84 | // treasury in: remove circulatingSupply 85 | if (isReservedContract(to)) { 86 | removeCirculating = true; 87 | } 88 | 89 | if (addCirculating && !removeCirculating) { 90 | token.circulatingSupply += event.args.value.toBigInt(); 91 | logger.info( 92 | `circulatingSupply increase ${event.args.value.toBigInt()} to ${ 93 | token.circulatingSupply 94 | }` 95 | ); 96 | } 97 | if (removeCirculating && !addCirculating) { 98 | token.circulatingSupply -= event.args.value.toBigInt(); 99 | logger.info( 100 | `circulatingSupply decrease ${event.args.value.toBigInt()} to ${ 101 | token.circulatingSupply 102 | }` 103 | ); 104 | } 105 | await token.save(); 106 | } 107 | -------------------------------------------------------------------------------- /src/mappings/utils/cache.ts: -------------------------------------------------------------------------------- 1 | import { Cache } from '../../types'; 2 | import { BigNumber } from '@ethersproject/bignumber'; 3 | 4 | export enum CacheKey { 5 | Era = 'era', 6 | MinimumStakingAmount = 'minimumStakingAmount', 7 | IndexerLeverageLimit = 'indexerLeverageLimit', 8 | } 9 | 10 | export async function cacheSet(key: CacheKey, value: string) { 11 | await Cache.create({ 12 | id: key.toString(), 13 | value: value, 14 | }).save(); 15 | } 16 | 17 | export async function cacheGet(key: CacheKey): Promise { 18 | return (await Cache.get(key.toString()))?.value; 19 | } 20 | 21 | export async function cacheGetNumber( 22 | key: CacheKey 23 | ): Promise { 24 | const cached = await Cache.get(key.toString()); 25 | return cached ? Number(cached.value) : undefined; 26 | } 27 | 28 | export async function cacheGetBigNumber( 29 | key: CacheKey 30 | ): Promise { 31 | const cached = await Cache.get(key.toString()); 32 | return cached ? BigNumber.from(cached.value) : undefined; 33 | } 34 | -------------------------------------------------------------------------------- /src/mappings/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const PER_MILL = BigInt('1000000'); 2 | export const PER_BILL = BigInt('1000000000'); 3 | export const PER_TRILL = BigInt('1000000000000'); 4 | export const PER_QUINTILL = BigInt('1000000000000000000'); 5 | -------------------------------------------------------------------------------- /src/mappings/utils/enumToTypes.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { DisputeType, DisputeState, WithdrawalType } from '../../types'; 5 | 6 | const disputeTypes = [DisputeType.POI, DisputeType.QUERY]; 7 | const disputeStates = [ 8 | DisputeState.ONGOING, 9 | DisputeState.ACCEPTED, 10 | DisputeState.REJECTED, 11 | DisputeState.CANCELLED, 12 | ]; 13 | const withdrawalTypes = [ 14 | WithdrawalType.UNDELEGATION, 15 | WithdrawalType.UNSTAKE, 16 | WithdrawalType.COMMISSION, 17 | WithdrawalType.MERGE, 18 | ]; 19 | 20 | export function getWithdrawalType(type: number): WithdrawalType { 21 | if (type < withdrawalTypes.length) { 22 | return withdrawalTypes[type]; 23 | } else { 24 | throw new Error( 25 | `Unexpected withdrawal type "${type}" provided to function getWithdrawalType` 26 | ); 27 | } 28 | } 29 | 30 | export function getDisputeType(type: number): DisputeType { 31 | if (type < disputeTypes.length) { 32 | return disputeTypes[type]; 33 | } else { 34 | throw new Error( 35 | `Unexpected dispute type "${type}" provided to function getDisputeType` 36 | ); 37 | } 38 | } 39 | 40 | export function getDisputeState(state: number): DisputeState { 41 | if (state < disputeStates.length) { 42 | return disputeStates[state]; 43 | } else { 44 | throw new Error( 45 | `Unexpected dispute state "${state}" provided to function getDisputeState` 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/mappings/utils/enums.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export enum RewardType { 5 | AGREEMENT = 'agreement', 6 | FLEX_PLAN = 'flex_plan', 7 | ALLOCATION = 'allocation', 8 | } 9 | -------------------------------------------------------------------------------- /src/mappings/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { BigNumber } from '@ethersproject/bignumber'; 5 | import mainnetDeploymentFile from '@subql/contract-sdk/publish/mainnet.json'; 6 | import testnetDeploymentFile from '@subql/contract-sdk/publish/testnet.json'; 7 | import { EthereumLog } from '@subql/types-ethereum'; 8 | import bs58 from 'bs58'; 9 | 10 | import assert from 'assert'; 11 | import { AirdropAmount, Exception, JSONBigInt } from '../../types'; 12 | import { BigNumberish } from 'ethers'; 13 | import { PER_QUINTILL } from './constants'; 14 | 15 | export enum Contracts { 16 | ERA_MANAGER_ADDRESS = 'EraManager', 17 | STAKING_ADDRESS = 'Staking', 18 | INDEXER_REGISTRY_ADDRESS = 'IndexerRegistry', 19 | PLAN_MANAGER_ADDRESS = 'PlanManager', 20 | SA_REGISTRY_ADDRESS = 'ServiceAgreementRegistry', 21 | REWARD_DIST_ADDRESS = 'RewardsDistributor', 22 | SQT_ADDRESS = 'L2SQToken', 23 | } 24 | 25 | const deploymentMap = { 26 | 84532: testnetDeploymentFile, 27 | 8453: mainnetDeploymentFile, 28 | }; 29 | 30 | export function getContractAddress( 31 | networkId: number, 32 | contract: Contracts 33 | ): string { 34 | // @ts-ignore 35 | const deploymentFile = deploymentMap[networkId]; 36 | assert(deploymentFile, `Deployment file not found for network: ${networkId}`); 37 | return deploymentFile.child[contract].address; 38 | } 39 | 40 | declare global { 41 | interface BigIntConstructor { 42 | fromJSONType(value: unknown): bigint; 43 | } 44 | interface BigInt { 45 | toJSON(): string; 46 | toJSONType(): JSONBigInt; 47 | fromJSONType(value: unknown): bigint; 48 | } 49 | } 50 | 51 | BigInt.prototype.toJSON = function (): string { 52 | return BigNumber.from(this).toHexString(); 53 | }; 54 | 55 | BigInt.prototype.toJSONType = function () { 56 | return { 57 | type: 'bigint', 58 | value: this.toJSON(), 59 | }; 60 | }; 61 | 62 | BigInt.fromJSONType = function (value: JSONBigInt): bigint { 63 | if (value?.type !== 'bigint' && !value.value) { 64 | throw new Error('Value is not JSONBigInt'); 65 | } 66 | 67 | return BigNumber.from(value.value).toBigInt(); 68 | }; 69 | 70 | export function bigNumbertoJSONType(value: BigNumber): JSONBigInt { 71 | return { 72 | type: 'bigint', 73 | value: value.toHexString(), 74 | }; 75 | } 76 | 77 | export function bytesToIpfsCid(raw: string): string { 78 | // Add our default ipfs values for first 2 bytes: 79 | // function:0x12=sha2, size:0x20=256 bits 80 | // and cut off leading "0x" 81 | const hashHex = '1220' + raw.slice(2); 82 | const hashBytes = Buffer.from(hashHex, 'hex'); 83 | return bs58.encode(hashBytes); 84 | } 85 | 86 | export function cidToBytes32(cid: string): string { 87 | return '0x' + Buffer.from(bs58.decode(cid)).slice(2).toString('hex'); 88 | } 89 | 90 | export function bnToDate(bn: BigNumber): Date { 91 | return new Date(bn.toNumber() * 1000); 92 | } 93 | 94 | export function biToDate(bi: bigint): Date { 95 | return new Date(Number(bi) * 1000); 96 | } 97 | 98 | export const operations: Record bigint> = { 99 | add: (a, b) => a + b, 100 | sub: (a, b) => a - b, 101 | replace: (a, b) => b, 102 | }; 103 | 104 | export function min(a: BigNumber, b: BigNumber): BigNumber { 105 | return a.lte(b) ? a : b; 106 | } 107 | 108 | export function getDelegationId(delegator: string, indexer: string): string { 109 | return `${delegator}:${indexer}`; 110 | } 111 | 112 | export function getWithdrawlId(delegator: string, index: BigNumber): string { 113 | return `${delegator}:${index.toHexString()}`; 114 | } 115 | 116 | export function bigNumberFrom(value: unknown): BigNumber { 117 | try { 118 | return BigNumber.from(value); 119 | } catch (e) { 120 | return BigNumber.from(0); 121 | } 122 | } 123 | 124 | export async function reportIndexerNonExistException( 125 | handler: string, 126 | indexerAddress: string, 127 | event: EthereumLog 128 | ): Promise { 129 | logger.error(`${handler}: Expected indexer to exist: ${indexerAddress}`); 130 | 131 | return reportException( 132 | handler, 133 | `Expected indexer to exist: ${indexerAddress}`, 134 | event 135 | ); 136 | } 137 | 138 | export async function reportException( 139 | handler: string, 140 | error: string, 141 | event: EthereumLog 142 | ): Promise { 143 | const id = `${event.blockNumber}:${event.transactionHash}`; 144 | 145 | const exception = Exception.create({ 146 | id, 147 | error: error || `Error: ${id}`, 148 | handler, 149 | }); 150 | 151 | await exception.save(); 152 | 153 | assert(false, `${id}: Error at ${handler}: ${error});`); 154 | } 155 | 156 | export const toBigNumber = (amount: BigNumberish): BigNumber => 157 | BigNumber.from(amount.toString()); 158 | 159 | export const toBigInt = (amount: string | undefined | null): bigint => 160 | BigInt((amount || '').replace('n', '')); 161 | 162 | // airdropper 163 | export const upsertAirdropper = async ( 164 | address: string, 165 | airdropAmount: BigNumberish, 166 | claimedAmount: BigNumberish, 167 | event: EthereumLog 168 | ): Promise => { 169 | const HANDLER = 'upsertUser'; 170 | const user = await AirdropAmount.get(address); 171 | 172 | if (user) { 173 | user.totalAirdropAmount = toBigNumber(user.totalAirdropAmount) 174 | .add(toBigNumber(airdropAmount)) 175 | .toBigInt(); 176 | user.claimedAmount = toBigNumber(user.claimedAmount) 177 | .add(toBigNumber(claimedAmount)) 178 | .toBigInt(); 179 | user.updateAt = `${HANDLER}:${event.blockNumber}`; 180 | 181 | await user.save(); 182 | } else { 183 | logger.info( 184 | `${HANDLER} - create: ${address} - ${event.transactionHash ?? ''}` 185 | ); 186 | const newAidropAmount = new AirdropAmount( 187 | address, 188 | toBigNumber(airdropAmount).toBigInt(), 189 | toBigNumber(claimedAmount).toBigInt() 190 | ); 191 | 192 | newAidropAmount.createAt = `${HANDLER}:${event.blockNumber}`; 193 | await newAidropAmount.save(); 194 | } 195 | }; 196 | 197 | export function calcApy(reward: bigint, stake: bigint): bigint { 198 | if (stake === BigInt(0)) { 199 | return BigInt(0); 200 | } 201 | 202 | return (((reward * PER_QUINTILL) / BigInt(7)) * BigInt(365)) / stake; 203 | } 204 | -------------------------------------------------------------------------------- /src/mappings/utils/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export * from './helpers'; 5 | export * from './updateDbFunctions'; 6 | -------------------------------------------------------------------------------- /src/mappings/utils/updateDbFunctions.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Staking__factory } from '@subql/contract-sdk'; 5 | import { EthereumLog } from '@subql/types-ethereum'; 6 | import { BigNumber } from 'ethers'; 7 | import { CreateIndexerParams } from '../../interfaces'; 8 | import { 9 | Indexer, 10 | EraValue, 11 | JSONBigInt, 12 | Delegation, 13 | Delegator, 14 | TotalLock, 15 | Controller, 16 | IndexerApySummary, 17 | } from '../../types'; 18 | import { 19 | bigNumberFrom, 20 | bigNumbertoJSONType, 21 | biToDate, 22 | Contracts, 23 | getContractAddress, 24 | getDelegationId, 25 | min, 26 | operations, 27 | reportIndexerNonExistException, 28 | } from './helpers'; 29 | import { getCurrentEra } from '../eraManager'; 30 | import { getMinimumStakingAmount } from '../indexerRegistry'; 31 | import { getIndexerLeverageLimit } from '../staking'; 32 | 33 | export async function createIndexer({ 34 | address, 35 | metadata = '', 36 | active = true, 37 | createdBlock, 38 | lastEvent, 39 | controller, 40 | event, 41 | }: CreateIndexerParams): Promise { 42 | const indexer = Indexer.create({ 43 | id: address, 44 | metadata, 45 | capacity: { 46 | era: -1, 47 | value: BigInt(0).toJSONType(), 48 | valueAfter: BigInt(0).toJSONType(), 49 | }, 50 | selfStake: { 51 | era: -1, 52 | value: BigInt(0).toJSONType(), 53 | valueAfter: BigInt(0).toJSONType(), 54 | }, 55 | totalStake: { 56 | era: -1, 57 | value: BigInt(0).toJSONType(), 58 | valueAfter: BigInt(0).toJSONType(), 59 | }, 60 | maxUnstakeAmount: BigInt(0).toJSONType(), 61 | commission: { 62 | era: -1, 63 | value: BigInt(0).toJSONType(), 64 | valueAfter: BigInt(0).toJSONType(), 65 | }, 66 | active, 67 | controller, 68 | createdBlock, 69 | lastEvent, 70 | }); 71 | await indexer.save(); 72 | 73 | await IndexerApySummary.create({ 74 | id: address, 75 | eraIdx: -1, 76 | indexerId: address, 77 | indexerReward: BigInt(0), 78 | indexerApy: BigInt(0), 79 | delegatorReward: BigInt(0), 80 | delegatorApy: BigInt(0), 81 | createAt: biToDate(event.block.timestamp), 82 | updateAt: biToDate(event.block.timestamp), 83 | }).save(); 84 | 85 | return indexer; 86 | } 87 | 88 | export async function upsertControllerAccount( 89 | indexerAddress: string, 90 | controllerAddress: string, 91 | event: EthereumLog, 92 | lastEvent: string 93 | ): Promise { 94 | let controller = await Controller.get(controllerAddress); 95 | 96 | if (!controller) { 97 | controller = Controller.create({ 98 | id: `${indexerAddress}:${controllerAddress}`, 99 | indexerId: indexerAddress, 100 | controller: controllerAddress, 101 | lastEvent, 102 | createdBlock: event.blockNumber, 103 | isActive: true, 104 | }); 105 | } else { 106 | controller.indexerId = indexerAddress; 107 | controller.lastEvent = lastEvent; 108 | controller.isActive = true; 109 | } 110 | await controller.save(); 111 | } 112 | 113 | export async function upsertEraValue( 114 | eraValue: EraValue | undefined, 115 | value: bigint, 116 | operation: keyof typeof operations = 'add', 117 | applyInstantly?: boolean 118 | ): Promise { 119 | const currentEra = await getCurrentEra(); 120 | 121 | if (!eraValue) { 122 | return { 123 | era: currentEra, 124 | value: (applyInstantly ? value : BigInt(0)).toJSONType(), 125 | valueAfter: value.toJSONType(), 126 | }; 127 | } 128 | 129 | const applyOperation = (existing: JSONBigInt) => 130 | operations[operation](BigInt.fromJSONType(existing), value).toJSONType(); 131 | 132 | const valueAfter = applyOperation(eraValue.valueAfter); 133 | 134 | if (eraValue.era === currentEra) { 135 | const newValue = applyInstantly 136 | ? applyOperation(eraValue.value) 137 | : eraValue.value; 138 | 139 | return { 140 | era: currentEra, 141 | value: newValue, 142 | valueAfter, 143 | }; 144 | } 145 | 146 | const newValue = applyInstantly 147 | ? applyOperation(eraValue.valueAfter) 148 | : eraValue.valueAfter; 149 | 150 | return { 151 | era: currentEra, 152 | value: newValue, 153 | valueAfter, 154 | }; 155 | } 156 | 157 | export async function updateMaxUnstakeAmount( 158 | indexerAddress: string, 159 | event: EthereumLog 160 | ): Promise { 161 | const leverageLimit = await getIndexerLeverageLimit(); 162 | const minStakingAmount = await getMinimumStakingAmount(); 163 | 164 | const indexer = await Indexer.get(indexerAddress); 165 | 166 | if (indexer) { 167 | const { totalStake } = indexer; 168 | 169 | const delegationId = getDelegationId(indexerAddress, indexerAddress); 170 | const { amount: ownStake } = (await Delegation.get(delegationId)) || {}; 171 | 172 | const totalStakingAmountAfter = bigNumberFrom(totalStake.valueAfter.value); 173 | const ownStakeAfter = bigNumberFrom(ownStake?.valueAfter.value); 174 | 175 | if (leverageLimit.eq(1)) { 176 | indexer.maxUnstakeAmount = bigNumbertoJSONType( 177 | ownStakeAfter.sub(minStakingAmount) 178 | ); 179 | } else { 180 | const maxUnstakeAmount = min( 181 | ownStakeAfter.sub(minStakingAmount), 182 | ownStakeAfter 183 | .mul(leverageLimit) 184 | .sub(totalStakingAmountAfter) 185 | .div(leverageLimit.sub(1)) 186 | ); 187 | 188 | indexer.maxUnstakeAmount = bigNumbertoJSONType( 189 | maxUnstakeAmount.isNegative() ? BigNumber.from(0) : maxUnstakeAmount 190 | ); 191 | } 192 | 193 | await indexer.save(); 194 | } else { 195 | await reportIndexerNonExistException( 196 | 'updateMaxUnstakeAmount', 197 | indexerAddress, 198 | event 199 | ); 200 | } 201 | } 202 | 203 | export async function updateTotalStake( 204 | indexerAddress: string, 205 | amount: bigint, 206 | operation: keyof typeof operations, 207 | event: EthereumLog, 208 | selfStake: boolean, 209 | applyInstantly: boolean 210 | ): Promise { 211 | const indexer = await Indexer.get(indexerAddress); 212 | 213 | if (indexer) { 214 | indexer.totalStake = await upsertEraValue( 215 | indexer.totalStake, 216 | amount, 217 | operation, 218 | applyInstantly 219 | ); 220 | if (selfStake) { 221 | indexer.selfStake = await upsertEraValue( 222 | indexer.selfStake, 223 | amount, 224 | operation, 225 | applyInstantly 226 | ); 227 | } 228 | 229 | await indexer.save(); 230 | // await updateIndexerCapacity(indexerAddress, event); 231 | } else { 232 | await reportIndexerNonExistException( 233 | 'updateTotalStake', 234 | indexerAddress, 235 | event 236 | ); 237 | } 238 | } 239 | 240 | export async function updateTotalDelegation( 241 | delegatorAddress: string, 242 | amount: bigint, 243 | operation: keyof typeof operations = 'add', 244 | applyInstantly?: boolean 245 | ): Promise { 246 | const currentEra = await getCurrentEra(); 247 | let delegator = await Delegator.get(delegatorAddress); 248 | 249 | if (!delegator) { 250 | delegator = Delegator.create({ 251 | id: delegatorAddress, 252 | totalDelegations: await upsertEraValue( 253 | undefined, 254 | amount, 255 | operation, 256 | applyInstantly 257 | ), 258 | startEra: applyInstantly ? currentEra : currentEra + 1, 259 | exitEra: -1, 260 | }); 261 | } else { 262 | delegator.totalDelegations = await upsertEraValue( 263 | delegator.totalDelegations, 264 | amount, 265 | operation, 266 | applyInstantly 267 | ); 268 | if (BigNumber.from(delegator.totalDelegations.valueAfter.value).lte(0)) { 269 | delegator.exitEra = currentEra + 1; 270 | } else { 271 | const prevExitEra = delegator.exitEra; 272 | delegator.exitEra = -1; 273 | if (prevExitEra != -1 && currentEra >= prevExitEra) { 274 | delegator.startEra = applyInstantly ? currentEra : currentEra + 1; 275 | } 276 | } 277 | } 278 | 279 | await delegator.save(); 280 | } 281 | 282 | export async function updateIndexerCapacity( 283 | address: string, 284 | event: EthereumLog 285 | ): Promise { 286 | const indexer = await Indexer.get(address); 287 | const leverageLimit = await getIndexerLeverageLimit(); 288 | 289 | if (indexer) { 290 | const indexerSelfStake = indexer.selfStake; 291 | const indexerTotalStake = indexer.totalStake; 292 | 293 | const currentEra = await getCurrentEra(); 294 | 295 | const selfStakeCurr = bigNumberFrom( 296 | currentEra > indexerSelfStake.era 297 | ? indexerSelfStake.valueAfter.value 298 | : indexerSelfStake.value.value 299 | ); 300 | const selfStakeAfter = bigNumberFrom(indexerSelfStake.valueAfter.value); 301 | 302 | const totalStakeCurr = bigNumberFrom( 303 | currentEra > indexerTotalStake.era 304 | ? indexerTotalStake.valueAfter.value 305 | : indexerTotalStake.value.value 306 | ); 307 | const totalStakeAfter = bigNumberFrom(indexerTotalStake.valueAfter.value); 308 | 309 | const current = selfStakeCurr.mul(leverageLimit).sub(totalStakeCurr); 310 | const after = selfStakeAfter.mul(leverageLimit).sub(totalStakeAfter); 311 | 312 | indexer.capacity = { 313 | era: currentEra, 314 | value: current.toBigInt().toJSONType(), 315 | valueAfter: after.toBigInt().toJSONType(), 316 | }; 317 | 318 | await indexer.save(); 319 | } else { 320 | await reportIndexerNonExistException( 321 | 'updateIndexerCapacity', 322 | address, 323 | event 324 | ); 325 | } 326 | } 327 | 328 | export async function updateTotalLock( 329 | amount: bigint, 330 | operation: keyof typeof operations = 'add', 331 | isSelf: boolean, 332 | event: EthereumLog 333 | ): Promise { 334 | const totalLockID = 'TotalLock'; 335 | let totalLock = await TotalLock.get(totalLockID); 336 | const updatedStakeAmount = isSelf 337 | ? BigNumber.from(amount) 338 | : BigNumber.from(0); 339 | const updatedDelegateAmount = isSelf 340 | ? BigNumber.from(0) 341 | : BigNumber.from(amount); 342 | 343 | if (!totalLock) { 344 | totalLock = TotalLock.create({ 345 | id: totalLockID, 346 | totalStake: await upsertEraValue( 347 | undefined, 348 | updatedStakeAmount.toBigInt(), 349 | operation 350 | ), 351 | totalDelegation: await upsertEraValue( 352 | undefined, 353 | updatedDelegateAmount.toBigInt(), 354 | operation 355 | ), 356 | createdBlock: event.blockNumber, 357 | }); 358 | } else { 359 | totalLock.totalStake = await upsertEraValue( 360 | totalLock.totalStake, 361 | updatedStakeAmount.toBigInt(), 362 | operation 363 | ); 364 | totalLock.totalDelegation = await upsertEraValue( 365 | totalLock.totalDelegation, 366 | updatedDelegateAmount.toBigInt(), 367 | operation 368 | ); 369 | totalLock.lastEvent = `updateTotalLock - ${event.transactionHash}`; 370 | } 371 | 372 | await totalLock.save(); 373 | } 374 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "importHelpers": true, 8 | "resolveJsonModule": true, 9 | "module": "commonjs", 10 | "outDir": "dist", 11 | "target": "es2017", 12 | "noImplicitAny": true, 13 | "noImplicitThis": true, 14 | "strictNullChecks": true 15 | }, 16 | "include": [ 17 | "scripts/**/*.ts", 18 | "src/**/*", 19 | "node_modules/@subql/types-core/dist/global.d.ts", 20 | "node_modules/@subql/types-ethereum/dist/global.d.ts" 21 | ] 22 | } 23 | --------------------------------------------------------------------------------