├── apps ├── indexer-admin │ ├── .env │ ├── bin │ │ └── run │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── favicon-new.ico │ │ ├── images │ │ │ └── usdc.png │ │ ├── static │ │ │ ├── connectWallet.png │ │ │ └── switch-network.png │ │ └── manifest.json │ ├── src │ │ ├── resources │ │ │ ├── logo.png │ │ │ ├── arrow.svg │ │ │ └── cross.svg │ │ ├── components │ │ │ ├── Copy │ │ │ │ ├── index.ts │ │ │ │ ├── Copy.module.css │ │ │ │ └── Copy.tsx │ │ │ ├── ConnectWallet │ │ │ │ ├── index.ts │ │ │ │ ├── ConnectWallet.module.css │ │ │ │ ├── ChainStatus.module.css │ │ │ │ ├── ChainStatus.tsx │ │ │ │ └── ConnectWallet.tsx │ │ │ ├── asyncRender.tsx │ │ │ ├── avatar.tsx │ │ │ ├── Icon.tsx │ │ │ ├── statusLabel.tsx │ │ │ ├── loading.tsx │ │ │ ├── errorPlaceholder │ │ │ │ └── index.tsx │ │ │ ├── logView.tsx │ │ │ ├── alertView.tsx │ │ │ └── introductionView.tsx │ │ ├── types │ │ │ ├── svg.d.ts │ │ │ ├── react-jazzicon.d.ts │ │ │ └── types.ts │ │ ├── conf │ │ │ └── stableCoin.ts │ │ ├── env.d.ts │ │ ├── pages │ │ │ ├── controllers │ │ │ │ └── types.ts │ │ │ ├── account │ │ │ │ ├── styles.tsx │ │ │ │ ├── types.ts │ │ │ │ └── prompts.ts │ │ │ ├── register │ │ │ │ ├── types.ts │ │ │ │ ├── utils.ts │ │ │ │ ├── styles.tsx │ │ │ │ └── prompts.ts │ │ │ ├── metamask │ │ │ │ ├── config.ts │ │ │ │ ├── styles.tsx │ │ │ │ └── prompts.ts │ │ │ ├── project-details │ │ │ │ ├── payg │ │ │ │ │ ├── styles.tsx │ │ │ │ │ ├── paygIntroduction.tsx │ │ │ │ │ └── projectPayg.tsx │ │ │ │ ├── styles.tsx │ │ │ │ └── components │ │ │ │ │ └── projectUptime.tsx │ │ │ ├── header │ │ │ │ └── styles.tsx │ │ │ ├── index.tsx │ │ │ ├── footer │ │ │ │ ├── styles.tsx │ │ │ │ ├── config.ts │ │ │ │ └── footer.tsx │ │ │ ├── network │ │ │ │ ├── styles.tsx │ │ │ │ └── components │ │ │ │ │ └── networkTabBarView.tsx │ │ │ └── projects │ │ │ │ ├── components │ │ │ │ ├── projecItemsHeader.tsx │ │ │ │ └── projectsEmptyView.tsx │ │ │ │ ├── constant.ts │ │ │ │ └── styles.tsx │ │ ├── index.tsx │ │ ├── hooks │ │ │ ├── web3Hook.ts │ │ │ ├── registerHook.ts │ │ │ ├── useTipForDominatPrice.ts │ │ │ ├── useHasController.ts │ │ │ └── network.ts │ │ ├── containers │ │ │ ├── loadingContext.tsx │ │ │ ├── account.tsx │ │ │ ├── unstated.tsx │ │ │ └── modalContext.tsx │ │ ├── polyfill │ │ │ └── navigatorClip.ts │ │ ├── utils │ │ │ ├── notification.ts │ │ │ ├── account.ts │ │ │ ├── logger.ts │ │ │ ├── table.tsx │ │ │ ├── waitForSomething.ts │ │ │ ├── project.ts │ │ │ ├── units.ts │ │ │ ├── web3.ts │ │ │ ├── apolloClient.ts │ │ │ ├── metamask.ts │ │ │ ├── ipfs.ts │ │ │ └── error.ts │ │ ├── index.css │ │ ├── styles │ │ │ └── input.tsx │ │ └── App.css │ ├── .env.development │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── TODO.md │ ├── README.md │ ├── vite.config.ts │ └── index.html ├── indexer-proxy │ ├── utils │ │ ├── README.md │ │ ├── Cargo.toml │ │ ├── HEADER-GPL3 │ │ └── src │ │ │ ├── traits.rs │ │ │ ├── types.rs │ │ │ ├── lib.rs │ │ │ └── price_oracle.rs │ └── proxy │ │ ├── .gitignore │ │ ├── .dockerignore │ │ ├── src │ │ ├── metadata │ │ │ ├── ai.rs │ │ │ └── mod.rs │ │ ├── response.rs │ │ └── mod_libp2p │ │ │ └── mod.rs │ │ ├── HEADER-GPL3 │ │ ├── Dockerfile │ │ └── Cargo.toml └── indexer-coordinator │ ├── bin │ ├── run.cmd │ └── run │ ├── scripts │ ├── copy-resources.sh │ ├── start-testnet.sh │ └── start-db.sh │ ├── nest-cli.json │ ├── tsconfig.build.json │ ├── .env_template │ ├── test │ ├── jest-e2e.json │ └── app.e2e-spec.ts │ ├── src │ ├── utils │ │ ├── json.ts │ │ ├── subscription.ts │ │ ├── ethProvider.ts │ │ ├── load.ts │ │ ├── queries.ts │ │ ├── network.ts │ │ ├── encrypt.ts │ │ ├── redis.ts │ │ ├── promise.spec.ts │ │ └── logger.ts │ ├── chain │ │ ├── chain.model.ts │ │ ├── chain.module.ts │ │ └── chain.service.ts │ ├── metrics │ │ ├── metrics.model.ts │ │ ├── metrics.resolver.ts │ │ ├── metrics.module.ts │ │ ├── versions.service.ts │ │ └── events.ts │ ├── migration.config.ts │ ├── subscription │ │ ├── subscription.module.ts │ │ └── subscription.service.ts │ ├── network │ │ ├── network.module.ts │ │ ├── network.service.spec.ts │ │ ├── network.resolver.spec.ts │ │ ├── network.type.ts │ │ └── network.resolver.ts │ ├── monitor │ │ └── monitor.module.ts │ ├── stats │ │ ├── stats.module.ts │ │ ├── stats.service.spec.ts │ │ ├── stats.controller.spec.ts │ │ └── stats.controller.ts │ ├── config │ │ ├── config.module.ts │ │ ├── config.model.ts │ │ └── config.resolver.ts │ ├── agreement.controller.ts │ ├── reward │ │ └── reward.module.ts │ ├── payg │ │ ├── test │ │ │ ├── payg.service.spec.ts │ │ │ └── payg.resolver.spec.ts │ │ └── payg.module.ts │ ├── migration │ │ ├── 1742528842664-add-price-ratio-field.ts │ │ ├── 1688676100216-addAgentPayg.ts │ │ ├── 1692668452846-add-token-to-payg.ts │ │ ├── 1721118564278-add-host-type.ts │ │ ├── 1701361246451-add-project-rate-limit.ts │ │ ├── 1720105309219-add-config-table.ts │ │ ├── 1743043042093-add-useDefault-field.ts │ │ ├── 1701943045349-update-stats-data-time.ts │ │ └── 1690365333094-update-project-base-config-to-support-multiple-endpoint.ts │ ├── project │ │ ├── test │ │ │ ├── project.service.spec.ts │ │ │ └── project.resolver.spec.ts │ │ ├── validator │ │ │ └── common.validator.ts │ │ └── project.module.ts │ ├── admin.controller.ts │ ├── main.ts │ ├── core │ │ ├── service.resolver.ts │ │ ├── account.model.ts │ │ ├── types.ts │ │ ├── core.module.ts │ │ └── account.resolver.ts │ └── monitor.controller.ts │ └── tsconfig.json ├── .eslintignore ├── .npmignore ├── .dockerignore ├── .prettierignore ├── yarn.lock ├── .lintstagedrc ├── Cargo.toml ├── docker ├── dev │ ├── 1_install.sh │ ├── .env.example │ ├── README.md │ ├── ipfs │ │ └── ipfs.sh │ ├── 2_start.sh │ └── docker-compose.yml └── test │ └── ipfs │ └── ipfs.sh ├── .prettierrc ├── .github ├── workflows │ ├── scripts │ │ ├── proxyVersion.sh │ │ └── coordinatorVersion.sh │ ├── discord.yml │ ├── proxy-docker-prod.yml │ ├── pr.yml │ ├── proxy-docker-dev.yml │ ├── coordinator-docker-prod.yml │ └── coordinator-docker-dev.yml ├── dependabot.yml └── actions │ └── create-prerelease │ ├── remove-stable-version.js │ └── action.yml ├── deploy ├── metrics │ ├── datasources │ │ └── datasource.yml │ ├── dashboards │ │ └── dashboards.yaml │ ├── prometheus.yml │ └── docker-compose-metrics.yml └── ipfs │ └── ipfs.sh ├── .gitattributes ├── debug_test_proxy.sh ├── package.json ├── README.md ├── rush.json ├── Dockerfile └── .gitignore /apps/indexer-admin/.env: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.css 2 | *.svg -------------------------------------------------------------------------------- /apps/indexer-proxy/utils/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/indexer-proxy/proxy/.gitignore: -------------------------------------------------------------------------------- 1 | .data 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/* 2 | 3 | !build/**/* 4 | !/bin 5 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/bin/run.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | node "%~dp0\run" %* -------------------------------------------------------------------------------- /apps/indexer-admin/bin/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npx serve -s build "$@" 4 | -------------------------------------------------------------------------------- /apps/indexer-proxy/proxy/.dockerignore: -------------------------------------------------------------------------------- 1 | /.git 2 | Dockerfile 3 | .dockerignore 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/.git 3 | **/.data 4 | ./docker 5 | ./common 6 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/bin/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../dist/main.js') 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | node_modules 5 | /src/generated/ -------------------------------------------------------------------------------- /apps/indexer-coordinator/scripts/copy-resources.sh: -------------------------------------------------------------------------------- 1 | cp ./src/utils/template.yml ./dist/utils/template.yml -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{ts,tsx}": "eslint --cache --fix", 3 | "*.{ts,tsx,css,scss,md}": "prettier --write" 4 | } -------------------------------------------------------------------------------- /apps/indexer-admin/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /apps/indexer-admin/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subquery/network-indexer-services/HEAD/apps/indexer-admin/public/favicon.ico -------------------------------------------------------------------------------- /apps/indexer-admin/public/favicon-new.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subquery/network-indexer-services/HEAD/apps/indexer-admin/public/favicon-new.ico -------------------------------------------------------------------------------- /apps/indexer-admin/public/images/usdc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subquery/network-indexer-services/HEAD/apps/indexer-admin/public/images/usdc.png -------------------------------------------------------------------------------- /apps/indexer-admin/src/resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subquery/network-indexer-services/HEAD/apps/indexer-admin/src/resources/logo.png -------------------------------------------------------------------------------- /apps/indexer-coordinator/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /apps/indexer-admin/public/static/connectWallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subquery/network-indexer-services/HEAD/apps/indexer-admin/public/static/connectWallet.png -------------------------------------------------------------------------------- /apps/indexer-admin/public/static/switch-network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subquery/network-indexer-services/HEAD/apps/indexer-admin/public/static/switch-network.png -------------------------------------------------------------------------------- /apps/indexer-coordinator/.env_template: -------------------------------------------------------------------------------- 1 | NODE_ENV=local 2 | DB_NAME=postgres 3 | DB_PASSWORD=postgres 4 | DB_HOST=localhost 5 | INDEXER_ADMIN_ROOT=node_modules/@subql/indexer-admin/build 6 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/Copy/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default } from './Copy'; 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "apps/indexer-proxy/utils", 4 | "apps/indexer-proxy/proxy", 5 | ] 6 | resolver = "2" 7 | 8 | [profile.release] 9 | lto = true 10 | codegen-units = 1 11 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/scripts/start-testnet.sh: -------------------------------------------------------------------------------- 1 | yarn start:prod --postgres-host localhost --postgres-password postgres --network testnet --network-endpoint https://sepolia.base.org --debug --dev --port 8007 -------------------------------------------------------------------------------- /docker/dev/1_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pwd 4 | 5 | SCRIPT_DIR="$(dirname "$0")" 6 | cd $SCRIPT_DIR/../../ 7 | pwd 8 | 9 | yarn install 10 | 11 | # no need to run this script any more 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true, 6 | "printWidth": 100, 7 | "bracketSpacing": true, 8 | "endOfLine": "auto" 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/scripts/proxyVersion.sh: -------------------------------------------------------------------------------- 1 | VERSION=$(cat ./apps/indexer-proxy/proxy/Cargo.toml \ 2 | | grep '^version' \ 3 | | sed -E 's/^version *= *\"([^"]*)\"/\1/g') 4 | 5 | 6 | echo "::set-output name=VERSION::$VERSION" 7 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/ConnectWallet/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export * from './ConnectWallet'; 5 | export * from './ChainStatus'; 6 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/types/svg.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | declare module '*.svg' { 5 | const content: any; 6 | export default content; 7 | } 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" # See documentation for possible values 4 | directory: "/" # Location of package manifests 5 | target-branch: "main" 6 | schedule: 7 | interval: "daily" 8 | -------------------------------------------------------------------------------- /deploy/metrics/datasources/datasource.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | datasources: 4 | - name: Prometheus 5 | type: prometheus 6 | access: proxy 7 | url: http://indexer_prometheus:9090 8 | headers: 9 | Authorization: Bearer thisismyAuthtoken -------------------------------------------------------------------------------- /deploy/metrics/dashboards/dashboards.yaml: -------------------------------------------------------------------------------- 1 | # grafana/provisioning/dashboards/dashboard.yml 2 | apiVersion: 1 3 | 4 | providers: 5 | - name: 'Indexer Dashboard' 6 | updateIntervalSeconds: 10 7 | options: 8 | path: /etc/grafana/provisioning/dashboards -------------------------------------------------------------------------------- /apps/indexer-coordinator/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/resources/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/indexer-admin/.env.development: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | VITE_APP_IPFS_GATEWAY=https://unauthipfs.subquery.network/ipfs/api/v0 3 | VITE_APP_QUERY_REGISTRY_PROJECT=https://api.subquery.network/sq/subquery/kepler-testnet 4 | VITE_APP_GQL_PROXY=https://gql-proxy.thechaindata.com 5 | -------------------------------------------------------------------------------- /apps/indexer-proxy/proxy/src/metadata/ai.rs: -------------------------------------------------------------------------------- 1 | use serde_json::{json, Value}; 2 | use subql_indexer_utils::types::Result; 3 | 4 | use crate::project::Project; 5 | 6 | pub async fn metadata(_project: &Project) -> Result { 7 | Ok(json!({ 8 | "ai": "TODO", 9 | })) 10 | } 11 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/utils/json.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export function safeJSONParse(data: string) { 5 | try { 6 | return JSON.parse(data); 7 | } catch (error) { 8 | return null; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/workflows/scripts/coordinatorVersion.sh: -------------------------------------------------------------------------------- 1 | PACKAGE_VERSION=$(cat ./apps/indexer-coordinator/package.json \ 2 | | grep version \ 3 | | head -1 \ 4 | | awk -F: '{ print $2 }' \ 5 | | sed 's/[",]//g' \ 6 | | tr -d '[[:space:]]') 7 | 8 | 9 | echo "::set-output name=COORDINATOR_VERSION::$PACKAGE_VERSION" 10 | -------------------------------------------------------------------------------- /apps/indexer-admin/tailwind.config.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | module.exports = { 5 | content: ['./src/**/*.{js,jsx,ts,tsx}'], 6 | theme: { 7 | // TODO: add `subquery` theme 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /.github/actions/create-prerelease/remove-stable-version.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const myArgs = process.argv.slice(2); 3 | const pJson = require(`${myArgs[0]}/package.json`) 4 | 5 | if (pJson.stableVersion){ 6 | delete pJson.stableVersion 7 | fs.writeFileSync(`${myArgs[0]}/package.json`, JSON.stringify(pJson, null, 2)) 8 | } 9 | -------------------------------------------------------------------------------- /docker/dev/.env.example: -------------------------------------------------------------------------------- 1 | # if need to change the default values 2 | # copy this file as .env 3 | # and change any value as needed 4 | # must change: LOCAL_IP 5 | 6 | LOCAL_IP=192.168.1.111 7 | NETWORK=testnet 8 | POSTGRES_USERNAME=postgres 9 | POSTGRES_PASSWORD=pos_z8X 10 | NETWORK_ENDPOINT=https://sepolia.base.org 11 | MMRPATH=/home 12 | -------------------------------------------------------------------------------- /docker/dev/README.md: -------------------------------------------------------------------------------- 1 | ## Dev environment 2 | 3 | To change the default values, copy the `.env.example` file as `.env`, and change any value as needed. 4 | And there is one value must be changed: LOCAL_IP. 5 | 6 | To start dev environment, run `bash docker/dev/1_install.sh` to install node modules and `bash docker/dev/2_start.sh` to start the dev server. 7 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/conf/stableCoin.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { STABLE_COIN_ADDRESSES } from '@subql/network-config'; 5 | 6 | import { SUPPORTED_NETWORK } from './rainbowConf'; 7 | 8 | export const STABLE_COIN_ADDRESS = STABLE_COIN_ADDRESSES[SUPPORTED_NETWORK]; 9 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/chain/chain.model.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Column, Entity, PrimaryColumn } from 'typeorm'; 5 | 6 | @Entity() 7 | export class Chain { 8 | @PrimaryColumn() 9 | name: string; 10 | 11 | @Column() 12 | value: string; 13 | } 14 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/env.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Buffer } from 'buffer'; 5 | 6 | import 'vite/client'; 7 | 8 | declare global { 9 | interface Window { 10 | ethereum?: any; 11 | Buffer: typeof Buffer; 12 | env: Record; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/asyncRender.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Spinner } from '@subql/components'; 5 | 6 | // TODO: remove this and use hooks from subql-hooks lib 7 | export const asyncRender = (ready: boolean, component: any) => { 8 | return ready ? component : ; 9 | }; 10 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/controllers/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export type Controller = { 5 | id: string; 6 | address: string; 7 | }; 8 | 9 | export enum ControllerAction { 10 | configController = 'configController', 11 | removeAccount = 'removeAccount', 12 | withdraw = 'widthdraw', 13 | } 14 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/metrics/metrics.model.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Field, Int, ObjectType } from '@nestjs/graphql'; 5 | 6 | @ObjectType('VersionMetrics') 7 | export class VersionMetrics { 8 | @Field(() => [Int]) 9 | coordinator: number[]; 10 | @Field(() => [Int]) 11 | proxy: number[]; 12 | } 13 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/Copy/Copy.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 24px; 3 | width: 24px; 4 | margin: 0 6px; 5 | 6 | border-radius: 100px; 7 | background: rgba(67, 136, 221, 0.08); 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | cursor: pointer; 12 | } 13 | 14 | .content { 15 | width: 100%; 16 | } 17 | 18 | .copy { 19 | color: var(--primary); 20 | } 21 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/migration.config.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { DataSource } from 'typeorm'; 5 | import { dbOption } from './data-source'; 6 | 7 | const datasource = new DataSource(dbOption); // config is one that is defined in datasource.config.ts file 8 | datasource.initialize(); 9 | 10 | export default datasource; 11 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/account/styles.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const Container = styled.div` 7 | display: flex; 8 | flex: 1; 9 | flex-direction: column; 10 | justify-content: center; 11 | align-items: center; 12 | padding: 0px 50px; 13 | min-height: 630px; 14 | `; 15 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/register/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export enum RegisterStep { 5 | onboarding = 'onboarding', 6 | authorisation = 'authorisation', 7 | register = 'register', 8 | sync = 'sync', 9 | } 10 | 11 | export enum StepStatus { 12 | wait = 'wait', 13 | process = 'process', 14 | finish = 'finish', 15 | } 16 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/subscription/subscription.module.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Module } from '@nestjs/common'; 5 | 6 | import { SubscriptionService } from './subscription.service'; 7 | 8 | @Module({ 9 | providers: [SubscriptionService], 10 | exports: [SubscriptionService], 11 | }) 12 | export class SubscriptionModule {} 13 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/ConnectWallet/ConnectWallet.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | padding: 24px; 6 | background: white; 7 | margin: 1rem 0; 8 | border-radius: 8px; 9 | max-width: 420px; 10 | 11 | box-shadow: 0px 10px 20px 0px rgba(67, 136, 221, 0.06); 12 | } 13 | 14 | .connectBtn { 15 | &:hover { 16 | transform: scale(1.03); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/network/network.module.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Module } from '@nestjs/common'; 5 | import { NetworkResolver } from './network.resolver'; 6 | import { NetworkService } from './network.service'; 7 | 8 | @Module({ 9 | providers: [NetworkResolver, NetworkService], 10 | exports: [NetworkService], 11 | }) 12 | export class NetworkModule {} 13 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/avatar.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { FC } from 'react'; 5 | import { toSvg } from 'jdenticon'; 6 | 7 | type Props = { 8 | size: number; 9 | address: string; 10 | }; 11 | 12 | const Avatar: FC = ({ size, address }) => ( 13 | 14 | ); 15 | 16 | export default Avatar; 17 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/utils/subscription.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export enum ProjectEvent { 5 | ProjectStarted = 'project_started', 6 | ProjectStopped = 'project_stopped', 7 | } 8 | 9 | export enum PaygEvent { 10 | Opened = 'channel_opened', 11 | Stopped = 'channel_stopped', 12 | State = 'channel_state', 13 | } 14 | 15 | export enum AccountEvent { 16 | Indexer = 'account_indexer', 17 | Controller = 'account_controller', 18 | } 19 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/ConnectWallet/ChainStatus.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | background: var(--sq-background-gradient); 3 | width: 100%; 4 | min-height: 100%; 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: center; 8 | align-items: center; 9 | z-index: 99999; 10 | } 11 | 12 | .content { 13 | display: flex; 14 | flex-direction: column; 15 | 16 | padding: 40px 24px; 17 | background: white; 18 | border-radius: 8px; 19 | max-width: 420px; 20 | align-items: center; 21 | gap: 24px; 22 | } 23 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/utils/ethProvider.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { providers } from 'ethers'; 5 | import { argv } from '../yargs'; 6 | 7 | let ethProvider: providers.StaticJsonRpcProvider; 8 | 9 | export function getEthProvider(): providers.StaticJsonRpcProvider { 10 | if (!ethProvider) { 11 | const ethJsonRpcUrl = argv['eth-endpoint']; 12 | ethProvider = new providers.StaticJsonRpcProvider(ethJsonRpcUrl); 13 | } 14 | return ethProvider; 15 | } 16 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/metamask/config.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export const extensionInstallUrls = { 5 | Chrome: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn', 6 | Firefox: 'https://addons.mozilla.org/en-US/firefox/addon/ether-metamask/', 7 | Brave: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn', 8 | Edge: 'https://microsoftedge.microsoft.com/addons/detail/metamask/ejbalbakoplchlghecdalmeeeajnimhm', 9 | }; 10 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/account/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { AccountAction } from 'pages/project-details/types'; 5 | 6 | export type Account = string | null | undefined; 7 | 8 | export type AccountButtonItem = { 9 | title: string; 10 | type?: AccountAction; 11 | loading?: boolean; 12 | disabled?: boolean; 13 | onClick: (type?: AccountAction) => void; 14 | }; 15 | 16 | export type IndexerMetadata = { 17 | name: string; 18 | description?: string; 19 | url: string; 20 | }; 21 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/monitor/monitor.module.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Module } from '@nestjs/common'; 5 | import { ScheduleModule } from '@nestjs/schedule'; 6 | import { CoreModule } from '../core/core.module'; 7 | import { ProjectModule } from '../project/project.module'; 8 | import { MonitorService } from './monitor.service'; 9 | 10 | @Module({ 11 | imports: [CoreModule, ProjectModule, ScheduleModule.forRoot()], 12 | providers: [MonitorService], 13 | }) 14 | export class MonitorModule {} 15 | -------------------------------------------------------------------------------- /deploy/metrics/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 20s 3 | evaluation_interval: 10s 4 | 5 | scrape_configs: 6 | - job_name: query_docker_stats 7 | metrics_path: /metrics 8 | scheme: http 9 | static_configs: 10 | - targets: ['host.docker.internal:8000'] # this is targeting coordinator endpoint. 11 | 12 | - job_name: query_count 13 | metrics_path: /metrics 14 | scheme: http 15 | bearer_token: 'thisismyAuthtoken' # this is same as proxy metrics-token 16 | static_configs: 17 | - targets: ['host.docker.internal:80'] # this is targeting proxy endpoint. -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/metrics/metrics.resolver.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Query, Resolver } from '@nestjs/graphql'; 5 | 6 | import { VersionMetrics } from './metrics.model'; 7 | import { VersionsService } from './versions.service'; 8 | 9 | @Resolver(() => VersionMetrics) 10 | export class MetricsResolver { 11 | constructor(private versionsService: VersionsService) {} 12 | 13 | @Query(() => VersionMetrics) 14 | getServicesVersion() { 15 | return this.versionsService.getVersions(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/utils/load.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import fs from 'fs'; 5 | 6 | export function getFileContent(path: string, identifier: string): string { 7 | if (!fs.existsSync(path)) { 8 | const err_msg = `${identifier} file ${path} is does not exist`; 9 | throw new Error(err_msg); 10 | } 11 | 12 | try { 13 | return fs.readFileSync(path).toString(); 14 | } catch (error) { 15 | const err_msg = `Failed to load ${identifier} file, ${error}`; 16 | throw new Error(err_msg); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/stats/stats.module.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Module } from '@nestjs/common'; 5 | import { TypeOrmModule } from '@nestjs/typeorm'; 6 | import { StatsController } from './stats.controller'; 7 | import { ProjectStatisticsEntity } from './stats.model'; 8 | import { StatsService } from './stats.service'; 9 | 10 | @Module({ 11 | imports: [TypeOrmModule.forFeature([ProjectStatisticsEntity])], 12 | controllers: [StatsController], 13 | providers: [StatsService], 14 | }) 15 | export class StatsModule {} 16 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/index.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import * as ReactDOMClient from 'react-dom/client'; 5 | import { Buffer } from 'buffer'; 6 | 7 | import App from './App'; 8 | 9 | import './index.css'; 10 | 11 | window.Buffer = Buffer; 12 | 13 | let container = document.getElementById('root'); 14 | 15 | if (!container) { 16 | container = document.createElement('div'); 17 | container.id = 'root'; 18 | document.body.appendChild(container); 19 | } 20 | 21 | const root = ReactDOMClient.createRoot(container); 22 | root.render(); 23 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/config/config.module.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Module } from '@nestjs/common'; 5 | import { TypeOrmModule } from '@nestjs/typeorm'; 6 | import { ConfigEntity } from './config.model'; 7 | import { ConfigResolver } from './config.resolver'; 8 | import { ConfigService } from './config.service'; 9 | 10 | @Module({ 11 | imports: [TypeOrmModule.forFeature([ConfigEntity])], 12 | providers: [ConfigService, ConfigResolver], 13 | exports: [ConfigService], 14 | }) 15 | @Module({}) 16 | export class ConfigModule {} 17 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/types/react-jazzicon.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // https://github.com/marcusmolchany/react-jazzicon 5 | 6 | declare module 'react-jazzicon' { 7 | import * as React from 'react'; 8 | 9 | type JazziconProps = { 10 | diameter?: number; 11 | paperStyles?: React.CSSProperties; 12 | seed?: number; 13 | svgStyles?: React.CSSProperties; 14 | }; 15 | 16 | const Jazzicon: React.FunctionComponent; 17 | 18 | export function jsNumberForAddress(address: string): number; 19 | 20 | export default Jazzicon; 21 | } 22 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/agreement.controller.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Controller, Get, Param } from '@nestjs/common'; 5 | import { ContractService } from './core/contract.service'; 6 | 7 | @Controller('agreements') 8 | export class AgreementController { 9 | constructor(private contract: ContractService) {} 10 | 11 | @Get(':id') 12 | async index(@Param('id') id: string) { 13 | const argeement = await this.contract 14 | .getSdk() 15 | .serviceAgreementRegistry.getClosedServiceAgreement(id); 16 | return argeement; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/hooks/web3Hook.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { JsonRpcSigner } from '@ethersproject/providers'; 5 | import { providers } from 'ethers'; 6 | 7 | import { useEthersProviderWithPublic, useEthersSigner } from './useEthersProvider'; 8 | 9 | export const useSignerOrProvider = () => { 10 | const { signer } = useEthersSigner(); 11 | const provider = useEthersProviderWithPublic(); 12 | 13 | return signer || provider; 14 | }; 15 | 16 | export type Signer = 17 | | JsonRpcSigner 18 | | providers.JsonRpcProvider 19 | | providers.FallbackProvider 20 | | undefined; 21 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/project-details/payg/styles.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const Container = styled.div` 7 | display: flex; 8 | flex: 1; 9 | flex-direction: column; 10 | align-items: center; 11 | padding: 20px 0px; 12 | `; 13 | 14 | export const InstructionContainer = styled.div` 15 | display: flex; 16 | flex-direction: column; 17 | align-items: center; 18 | width: 550px; 19 | `; 20 | 21 | export const PlansContainer = styled.div` 22 | display: flex; 23 | flex-direction: column; 24 | width: 100%; 25 | `; 26 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/reward/reward.module.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Module } from '@nestjs/common'; 5 | import { ConfigModule } from 'src/config/config.module'; 6 | import { CoreModule } from 'src/core/core.module'; 7 | import { NetworkModule } from 'src/network/network.module'; 8 | import { PaygModule } from 'src/payg/payg.module'; 9 | import { RewardService } from './reward.service'; 10 | 11 | @Module({ 12 | imports: [CoreModule, NetworkModule, PaygModule, ConfigModule], 13 | providers: [RewardService], 14 | exports: [RewardService], 15 | }) 16 | export class RewardModule {} 17 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/register/utils.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { RegisterStep, StepStatus } from './types'; 5 | 6 | export const registerSteps = Object.entries(RegisterStep) 7 | .map(([key]) => key) 8 | .slice(1); 9 | 10 | export const getStepIndex = (step?: RegisterStep): number => 11 | registerSteps.findIndex((s) => s === step); 12 | 13 | export const getStepStatus = (currentIndex: number, index: number): StepStatus => { 14 | if (currentIndex === index) return StepStatus.process; 15 | if (currentIndex > index) return StepStatus.finish; 16 | return StepStatus.wait; 17 | }; 18 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/payg/test/payg.service.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Test, TestingModule } from '@nestjs/testing'; 5 | import { PaygService } from '../payg.service'; 6 | 7 | describe('PaygService', () => { 8 | let service: PaygService; 9 | 10 | beforeEach(async () => { 11 | const module: TestingModule = await Test.createTestingModule({ 12 | providers: [PaygService], 13 | }).compile(); 14 | 15 | service = module.get(PaygService); 16 | }); 17 | 18 | it('should be defined', () => { 19 | expect(service).toBeDefined(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/stats/stats.service.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Test, TestingModule } from '@nestjs/testing'; 5 | import { StatsService } from './stats.service'; 6 | 7 | describe('StatsService', () => { 8 | let service: StatsService; 9 | 10 | beforeEach(async () => { 11 | const module: TestingModule = await Test.createTestingModule({ 12 | providers: [StatsService], 13 | }).compile(); 14 | 15 | service = module.get(StatsService); 16 | }); 17 | 18 | it('should be defined', () => { 19 | expect(service).toBeDefined(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/indexer-admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx", 22 | "baseUrl": "./src" 23 | }, 24 | "include": [ 25 | "src" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/payg/test/payg.resolver.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Test, TestingModule } from '@nestjs/testing'; 5 | import { PaygResolver } from '../payg.resolver'; 6 | 7 | describe('PaygResolver', () => { 8 | let resolver: PaygResolver; 9 | 10 | beforeEach(async () => { 11 | const module: TestingModule = await Test.createTestingModule({ 12 | providers: [PaygResolver], 13 | }).compile(); 14 | 15 | resolver = module.get(PaygResolver); 16 | }); 17 | 18 | it('should be defined', () => { 19 | expect(resolver).toBeDefined(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/migration/1742528842664-add-price-ratio-field.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { MigrationInterface, QueryRunner } from 'typeorm'; 5 | 6 | export class AddPriceRatioField1742528842664 implements MigrationInterface { 7 | name = 'AddPriceRatioField1742528842664'; 8 | 9 | async up(queryRunner: QueryRunner): Promise { 10 | await queryRunner.query(`ALTER TABLE "payg_entity" ADD "priceRatio" integer`); 11 | } 12 | 13 | async down(queryRunner: QueryRunner): Promise { 14 | await queryRunner.query(`ALTER TABLE "payg_entity" DROP COLUMN "priceRatio"`); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/network/network.service.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Test, TestingModule } from '@nestjs/testing'; 5 | import { NetworkService } from './network.service'; 6 | 7 | describe('NetworkService', () => { 8 | let service: NetworkService; 9 | 10 | beforeEach(async () => { 11 | const module: TestingModule = await Test.createTestingModule({ 12 | providers: [NetworkService], 13 | }).compile(); 14 | 15 | service = module.get(NetworkService); 16 | }); 17 | 18 | it('should be defined', () => { 19 | expect(service).toBeDefined(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/containers/loadingContext.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { useState } from 'react'; 5 | 6 | import { createContainer } from './unstated'; 7 | 8 | type LoadingContext = { 9 | pageLoading: boolean; 10 | setPageLoading: (loading: boolean) => void; 11 | }; 12 | 13 | function useLoadingImpl(): LoadingContext { 14 | const [pageLoading, setPageLoading] = useState(false); 15 | return { pageLoading, setPageLoading }; 16 | } 17 | 18 | export const { useContainer: useLoading, Provider: LoadingProvider } = createContainer( 19 | useLoadingImpl, 20 | { displayName: 'Global Loading' } 21 | ); 22 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/polyfill/navigatorClip.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import copy from 'copy-to-clipboard'; 5 | 6 | export const navigatorClip = { 7 | clipboard: { 8 | writeText: (text: string) => { 9 | copy(text); 10 | return Promise.resolve(); 11 | }, 12 | }, 13 | }; 14 | 15 | if (!window?.navigator?.clipboard?.writeText) { 16 | // clipboard API is only available in HTTPS 17 | // So make polyfill for it. 18 | // WalletConnect use it. 19 | // it's read-only if browser inject it. set it would do nothing. 20 | // @ts-ignore 21 | window.navigator.clipboard = navigatorClip.clipboard; 22 | } 23 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/migration/1688676100216-addAgentPayg.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { MigrationInterface, QueryRunner } from 'typeorm'; 5 | 6 | export class AddAgentPayg1688676100216 implements MigrationInterface { 7 | name = 'AddAgentPayg1688676100216'; 8 | 9 | async up(queryRunner: QueryRunner): Promise { 10 | await queryRunner.query( 11 | `ALTER TABLE "channel" ADD "agent" character varying NOT NULL DEFAULT ''` 12 | ); 13 | } 14 | 15 | async down(queryRunner: QueryRunner): Promise { 16 | await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "agent"`); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/project/test/project.service.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Test, TestingModule } from '@nestjs/testing'; 5 | import { ProjectService } from '../project.service'; 6 | 7 | describe('ProjectService', () => { 8 | let service: ProjectService; 9 | 10 | beforeEach(async () => { 11 | const module: TestingModule = await Test.createTestingModule({ 12 | providers: [ProjectService], 13 | }).compile(); 14 | 15 | service = module.get(ProjectService); 16 | }); 17 | 18 | it('should be defined', () => { 19 | expect(service).toBeDefined(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/network/network.resolver.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Test, TestingModule } from '@nestjs/testing'; 5 | import { NetworkResolver } from './network.resolver'; 6 | 7 | describe('NetworkResolver', () => { 8 | let resolver: NetworkResolver; 9 | 10 | beforeEach(async () => { 11 | const module: TestingModule = await Test.createTestingModule({ 12 | providers: [NetworkResolver], 13 | }).compile(); 14 | 15 | resolver = module.get(NetworkResolver); 16 | }); 17 | 18 | it('should be defined', () => { 19 | expect(resolver).toBeDefined(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/project/test/project.resolver.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Test, TestingModule } from '@nestjs/testing'; 5 | import { ProjectResolver } from '../project.resolver'; 6 | 7 | describe('ProjectResolver', () => { 8 | let resolver: ProjectResolver; 9 | 10 | beforeEach(async () => { 11 | const module: TestingModule = await Test.createTestingModule({ 12 | providers: [ProjectResolver], 13 | }).compile(); 14 | 15 | resolver = module.get(ProjectResolver); 16 | }); 17 | 18 | it('should be defined', () => { 19 | expect(resolver).toBeDefined(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/stats/stats.controller.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Test, TestingModule } from '@nestjs/testing'; 5 | import { StatsController } from './stats.controller'; 6 | 7 | describe('StatsController', () => { 8 | let controller: StatsController; 9 | 10 | beforeEach(async () => { 11 | const module: TestingModule = await Test.createTestingModule({ 12 | controllers: [StatsController], 13 | }).compile(); 14 | 15 | controller = module.get(StatsController); 16 | }); 17 | 18 | it('should be defined', () => { 19 | expect(controller).toBeDefined(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /.github/workflows/discord.yml: -------------------------------------------------------------------------------- 1 | name: discord notification 2 | on: 3 | release: 4 | types: 5 | - published 6 | 7 | jobs: 8 | notify: 9 | name: Discord Notification 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Send release details to Discord 14 | uses: rjstone/discord-webhook-notify@v1 15 | with: 16 | webhookUrl: ${{ secrets.KEPLER_DISCORD_WEBHOOK }} 17 | color: '#6499ff' 18 | avatarUrl: https://github.githubassets.com/images/modules/logos_page/Octocat.png 19 | details: ${{ github.event.release.body }} 20 | description: "[Release] ${{ github.event.release.name }}" 21 | footer: ${{ github.event.release.html_url }} 22 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/Icon.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { FC } from 'react'; 5 | import styled from 'styled-components'; 6 | 7 | const Image = styled.img<{ size?: number }>` 8 | width: ${({ size }) => size ?? 30}px; 9 | height: ${({ size }) => size ?? 30}px; 10 | `; 11 | 12 | type Props = { 13 | src: string; 14 | size?: number; 15 | url?: string; 16 | }; 17 | 18 | const Icon: FC = ({ src, size, url }) => 19 | url ? ( 20 | 21 | {url} 22 | 23 | ) : ( 24 | {src} 25 | ); 26 | 27 | export default Icon; 28 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/utils/notification.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export enum AccountNotification { 5 | MetadataUpated = 'MetadataUpated', 6 | ControllerUpdated = 'ControllerUpdated', 7 | } 8 | 9 | export enum ProjectNotification { 10 | NotIndexing = 'NotIndexing', 11 | Started = 'Started', 12 | Indexing = 'Indexing', 13 | Ready = 'Ready', 14 | Terminated = 'Terminated', 15 | } 16 | 17 | export enum TransactionNotification { 18 | Loading = 'Loading', 19 | Succeed = 'Succeed', 20 | Failed = 'Failed', 21 | } 22 | 23 | export const dismiss = (duration = 5000, onScreen = false) => ({ 24 | duration, 25 | onScreen, 26 | }); 27 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/scripts/start-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SERVER="coordinator-server"; 5 | PW="postgres"; 6 | DB="coordinator"; 7 | 8 | echo "stop & remove old docker [$SERVER] and starting new fresh instance of [$SERVER]" 9 | (docker kill $SERVER || :) && \ 10 | (docker rm $SERVER || :) && \ 11 | docker run --name $SERVER -e POSTGRES_PASSWORD=$PW \ 12 | -e PGPASSWORD=$PW \ 13 | -p 5432:5432 \ 14 | -d postgres 15 | 16 | # wait for pg to start 17 | echo "sleep wait for pg-server [$SERVER] to start"; 18 | SLEEP 3; 19 | 20 | # create the db 21 | echo "CREATE DATABASE $DB ENCODING 'UTF-8';" | docker exec -i $SERVER psql -U postgres -c "create database $DB;" 22 | echo "\l" | docker exec -i $SERVER psql -U postgres -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/migration/1692668452846-add-token-to-payg.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { MigrationInterface, QueryRunner } from 'typeorm'; 5 | 6 | export class AddTokenToPayg1692668452846 implements MigrationInterface { 7 | name = 'AddTokenToPayg1692668452846'; 8 | 9 | async up(queryRunner: QueryRunner): Promise { 10 | await queryRunner.query( 11 | `ALTER TABLE "payg_entity" ADD "token" character varying NOT NULL DEFAULT ''` 12 | ); 13 | } 14 | 15 | async down(queryRunner: QueryRunner): Promise { 16 | await queryRunner.query(`ALTER TABLE "payg_entity" DROP COLUMN "token"`); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/subscription/subscription.service.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Injectable } from '@nestjs/common'; 5 | import { PubSub } from 'graphql-subscriptions'; 6 | 7 | @Injectable() 8 | export class SubscriptionService { 9 | private pubSub: PubSub; 10 | constructor() { 11 | this.pubSub = new PubSub(); 12 | } 13 | 14 | asyncIterator(triggers: string | string[]): AsyncIterator { 15 | return this.pubSub.asyncIterator(triggers); 16 | } 17 | 18 | publish(triggerName: string, payload: any): Promise { 19 | return this.pubSub.publish(triggerName, payload); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "esModuleInterop": true, 17 | "noImplicitAny": false, 18 | "strictBindCallApply": false, 19 | "forceConsistentCasingInFileNames": false, 20 | "noFallthroughCasesInSwitch": false, 21 | "resolveJsonModule": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/resources/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/chain/chain.module.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Module } from '@nestjs/common'; 5 | import { TypeOrmModule } from '@nestjs/typeorm'; 6 | 7 | import { CoreModule } from '../core/core.module'; 8 | import { DBModule } from '../db/db.module'; 9 | import { MetricsModule } from '../metrics/metrics.module'; 10 | 11 | import { Chain } from './chain.model'; 12 | import { ChainService } from './chain.service'; 13 | 14 | @Module({ 15 | imports: [CoreModule, DBModule, MetricsModule, TypeOrmModule.forFeature([Chain])], 16 | providers: [ChainService], 17 | exports: [ChainService], 18 | }) 19 | export class ChainModule {} 20 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/types/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export type FormValues = { 5 | [key: string]: string; 6 | }; 7 | 8 | export type HookDependency = boolean | number | string; 9 | 10 | export interface IProjectBaseConfig { 11 | networkEndpoints: string[] | undefined; 12 | networkDictionary: string | undefined; 13 | nodeVersion: string | undefined; 14 | queryVersion: string | undefined; 15 | } 16 | 17 | export interface IProjectAdvancedConfig { 18 | poiEnabled: boolean; 19 | purgeDB: boolean; 20 | timeout: number; 21 | workers: number; 22 | batchSize: number; 23 | cache: number; 24 | cpu: number; 25 | memory: number; 26 | } 27 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/migration/1721118564278-add-host-type.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { MigrationInterface, QueryRunner } from 'typeorm'; 5 | 6 | export class AddHostType1721118564278 implements MigrationInterface { 7 | name = 'AddHostType1721118564278'; 8 | 9 | async up(queryRunner: QueryRunner): Promise { 10 | await queryRunner.query( 11 | `ALTER TABLE "project_entity" ADD "hostType" character varying NOT NULL DEFAULT 'system-managed'` 12 | ); 13 | } 14 | 15 | async down(queryRunner: QueryRunner): Promise { 16 | await queryRunner.query(`ALTER TABLE "project_entity" DROP COLUMN "hostType"`); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/migration/1701361246451-add-project-rate-limit.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { MigrationInterface, QueryRunner } from 'typeorm'; 5 | 6 | export class AddProjectRateLimit1701361246451 implements MigrationInterface { 7 | name = 'AddProjectRateLimit1701361246451'; 8 | 9 | async up(queryRunner: QueryRunner): Promise { 10 | await queryRunner.query( 11 | `ALTER TABLE "project_entity" ADD "rateLimit" integer NOT NULL DEFAULT '0'` 12 | ); 13 | } 14 | 15 | async down(queryRunner: QueryRunner): Promise { 16 | await queryRunner.query(`ALTER TABLE "project_entity" DROP COLUMN "rateLimit"`); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/metamask/styles.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const Container = styled.div` 7 | display: flex; 8 | flex-direction: column; 9 | align-items: center; 10 | justify-content: center; 11 | width: 100%; 12 | width: 100%; 13 | margin-top: -100px; 14 | `; 15 | 16 | export const MetaMaskContainer = styled.div` 17 | display: flex; 18 | padding: 24px; 19 | align-items: center; 20 | justify-content: space-between; 21 | min-width: 450px; 22 | cursor: pointer; 23 | `; 24 | 25 | export const ContentContainer = styled.div` 26 | display: flex; 27 | align-items: center; 28 | `; 29 | -------------------------------------------------------------------------------- /apps/indexer-admin/TODO.md: -------------------------------------------------------------------------------- 1 | ## Plan to refactor this projet 2 | 3 | - Part 1 for the whole project 4 | 5 | - [x] Use Vite 6 | - [x] Arrange environments. 7 | - [ ] Refactor Route part like this https://github.com/subquery/network-explorer/issues/424 8 | - [ ] Use less or sass 9 | - [ ] Use css module replace css-in-js(maybe not) 10 | - [ ] Migrate css-in-js to css module(like above) 11 | - [ ] Remove sentry. 12 | - [ ] Migrate `loadingProvider`, `NotificationProvider`, `ModalProvider` to static method. 13 | - [ ] Use tailwindcss 14 | ... 15 | 16 | - Part 2 for the query and contract 17 | 18 | - [ ] Move the relative codes to togather. 19 | - [ ] Wrap ContractSDK. 20 | ... 21 | 22 | - Part 3 for the pages and hooks. 23 | 24 | ... 25 | -------------------------------------------------------------------------------- /apps/indexer-admin/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "SubQueryNetwork Node Operator admin", 3 | "name": "SubQueryNetwork Node Operator admin", 4 | "description": "SubQueryNetwork Node Operator admin", 5 | "iconPath": "favicon.ico", 6 | "icons": [ 7 | { 8 | "src": "favicon.ico", 9 | "sizes": "64x64 32x32 24x24 16x16", 10 | "type": "image/x-icon" 11 | }, 12 | { 13 | "src": "favicon.ico", 14 | "type": "image/x-icon", 15 | "sizes": "192x192" 16 | }, 17 | { 18 | "src": "favicon.ico", 19 | "type": "image/x-icon", 20 | "sizes": "512x512" 21 | } 22 | ], 23 | "start_url": ".", 24 | "display": "standalone", 25 | "theme_color": "#000000", 26 | "background_color": "#ffffff" 27 | } 28 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/header/styles.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const Container = styled.div` 7 | display: flex; 8 | width: 100vw; 9 | align-items: center; 10 | justify-content: space-between; 11 | min-height: 80px; 12 | padding: 0px 40px; 13 | border-bottom: 1px solid rgba(0, 0, 0, 0.06); 14 | `; 15 | 16 | export const LeftContainer = styled.div` 17 | display: flex; 18 | align-items: center; 19 | `; 20 | 21 | export const RightContainer = styled.div` 22 | display: flex; 23 | justify-content: space-between; 24 | align-items: center; 25 | padding: 0px 20px; 26 | min-width: 260px; 27 | height: 55px; 28 | `; 29 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Don't allow people to merge changes to these generated files, because the result 2 | # may be invalid. You need to run "rush update" again. 3 | pnpm-lock.yaml merge=text 4 | shrinkwrap.yaml merge=binary 5 | npm-shrinkwrap.json merge=binary 6 | yarn.lock merge=binary 7 | 8 | # Rush's JSON config files use JavaScript-style code comments. The rule below prevents pedantic 9 | # syntax highlighters such as GitHub's from highlighting these comments as errors. Your text editor 10 | # may also require a special configuration to allow comments in JSON. 11 | # 12 | # For more information, see this issue: https://github.com/microsoft/rushstack/issues/1088 13 | # 14 | *.json linguist-language=JSON-with-Comments 15 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/utils/account.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { networks } from '@subql/contract-sdk'; 5 | 6 | import { SUPPORTED_NETWORK } from './web3'; 7 | 8 | export function balanceSufficient(balance: string): boolean { 9 | return parseFloat(balance) > parseFloat('0.001'); 10 | } 11 | 12 | export function openAccountExporer(account: string) { 13 | const { blockExplorerUrls } = networks[SUPPORTED_NETWORK].child; 14 | const blockExplorerUrl = blockExplorerUrls[0]; 15 | const url = blockExplorerUrl.endsWith('/') 16 | ? `${blockExplorerUrl}address/${account}` 17 | : `${blockExplorerUrl}/address/${account}`; 18 | 19 | window.open(url, '_blank', 'noopener,noreferrer'); 20 | } 21 | -------------------------------------------------------------------------------- /deploy/metrics/docker-compose-metrics.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | prometheus: 5 | image: prom/prometheus:latest 6 | container_name: indexer_prometheus 7 | volumes: 8 | - ./prometheus.yml:/etc/prometheus/prometheus.yml 9 | command: --config.file=/etc/prometheus/prometheus.yml 10 | ports: 11 | - 9090:9090 12 | 13 | grafana: 14 | image: grafana/grafana:latest 15 | container_name: indexer_grafana 16 | ports: 17 | - 3000:3000 18 | volumes: 19 | - ./datasources:/etc/grafana/provisioning/datasources 20 | - ./dashboards:/etc/grafana/provisioning/dashboards 21 | environment: 22 | - GF_SECURITY_ADMIN_PASSWORD=admin # Update this password for Grafana login 23 | 24 | networks: 25 | default: 26 | name: indexer_services -------------------------------------------------------------------------------- /apps/indexer-proxy/utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "subql-indexer-utils" 3 | version = "2.2.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | axum = "0.7" 8 | base64 = "0.22" 9 | bincode = "1.3" 10 | blake3 = "1.3" 11 | bs58 = "0.5" 12 | ethereum-types = "0.15" 13 | ethers = { git = "https://github.com/gakonst/ethers-rs.git", tag = "ethers-v2.0.7" } 14 | hex = "0.4" 15 | http = "1.1.0" 16 | native-tls = "0.2.12" 17 | once_cell = "1.12" 18 | rand_chacha = "0.3" 19 | reqwest = { version = "0.12", features = ["json", "native-tls"] } 20 | rustc-hex = "2.1" 21 | serde = { version = "1.0", features = ["derive"] } 22 | serde_json = "1.0" 23 | serde_with ={ version = "3.0", features = ["json"] } 24 | subql-contracts = { git = "https://github.com/subquery/network-contracts", tag = "v1.9.0" } 25 | uint = "0.10" 26 | -------------------------------------------------------------------------------- /debug_test_proxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Usage: 4 | # - cargo build 5 | # - comment all the `proxy` section in the file `deploy/docker-compose-testnet.yml` 6 | # - modify `secret-key` in the `coordinator` section in the file `deploy/docker-compose-testnet.yml`, the same with `secret-key` below 7 | # - docker-compose -f deploy/docker-compose-testnet.yml up 8 | # - cargo build 9 | # - ./debug_test_proxy.sh 10 | 11 | 12 | sudo ./target/debug/subql-indexer-proxy \ 13 | --port=80 \ 14 | --auth \ 15 | --network=testnet \ 16 | --jwt-secret= \ 17 | --secret-key= \ 18 | --coordinator-endpoint=http://localhost:8000 \ 19 | --network-endpoint=https://sepolia.base.org \ 20 | --token-duration=24 \ 21 | --redis-endpoint=redis://localhost:6379 \ 22 | --metrics-token=thisismyAuthtoken 23 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import Account from './account/account'; 5 | import ControllerManagement from './controllers/controllersPage'; 6 | import Footer from './footer/footer'; 7 | import GlobalConfig from './global-config/GlobalConfig'; 8 | import Header from './header/header'; 9 | import Network from './network/networkPage'; 10 | import ProjectDetail from './project-details/projectDetailsPage'; 11 | import Projects from './projects/projectsPage'; 12 | import Register from './register/registerPage'; 13 | 14 | export { 15 | Header, 16 | Footer, 17 | Projects, 18 | Account, 19 | ControllerManagement, 20 | ProjectDetail, 21 | Register, 22 | Network, 23 | GlobalConfig, 24 | }; 25 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { INestApplication } from '@nestjs/common'; 5 | import { Test, TestingModule } from '@nestjs/testing'; 6 | import request from 'supertest'; 7 | import { AppModule } from '../src/app.module'; 8 | 9 | describe('AppController (e2e)', () => { 10 | let app: INestApplication; 11 | 12 | beforeEach(async () => { 13 | const moduleFixture: TestingModule = await Test.createTestingModule({ 14 | imports: [AppModule], 15 | }).compile(); 16 | 17 | app = moduleFixture.createNestApplication(); 18 | await app.init(); 19 | }); 20 | 21 | it('/ (GET)', () => { 22 | return request(app.getHttpServer()).get('/').expect(200).expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/chain/chain.service.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Injectable } from '@nestjs/common'; 5 | import { InjectRepository } from '@nestjs/typeorm'; 6 | import { Repository } from 'typeorm'; 7 | import { Chain } from './chain.model'; 8 | 9 | @Injectable() 10 | export class ChainService { 11 | constructor(@InjectRepository(Chain) private chainRepository: Repository) {} 12 | 13 | getBlock(): Promise { 14 | return this.chainRepository.findOneBy({ name: 'block' }); 15 | } 16 | 17 | async updateBlock(value: string): Promise { 18 | const chain = this.chainRepository.create({ 19 | name: 'block', 20 | value: value, 21 | }); 22 | 23 | return this.chainRepository.save(chain); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /deploy/ipfs/ipfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | 4 | addPeers() { 5 | sleep $1; 6 | shift; 7 | 8 | ipfs swarm peers | grep -E "(12D3KooWHEEjciF2JmDukCkWW93tQ7eJYs16PWqEo81GrXz82DUL|12D3KooWForH2nsSRN5cynPhoona6re1nw2EcimQJxHnicd1yqUV|12D3KooWPhsrviSKFTKawpW3bRAdLZ89jhXdYuszAys4YwL3RMn3|12D3KooWCFokEyt9gtuQHTwVAzwBsdjsBqfSxq1D3X1FsAbTwaSN)" 9 | ipfs swarm connect /dns4/ipfs-swarm-a-lh.subquery.network/tcp/19988/p2p/12D3KooWForH2nsSRN5cynPhoona6re1nw2EcimQJxHnicd1yqUV 10 | ipfs swarm connect /dns4/ipfs-swarm-b-lh.subquery.network/tcp/19988/p2p/12D3KooWPhsrviSKFTKawpW3bRAdLZ89jhXdYuszAys4YwL3RMn3 11 | ipfs swarm connect /dns4/ipfs-swarm-c-lh.subquery.network/tcp/19988/p2p/12D3KooWCFokEyt9gtuQHTwVAzwBsdjsBqfSxq1D3X1FsAbTwaSN 12 | } 13 | 14 | loop() { 15 | while : 16 | do 17 | addPeers 600 18 | done 19 | } 20 | 21 | addPeers 10 & 22 | 23 | loop & 24 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/footer/styles.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const Container = styled.div` 7 | display: flex; 8 | flex-direction: column; 9 | background-color: var(--sq-primary-blue); 10 | min-height: 150px; 11 | width: 100%; 12 | `; 13 | 14 | export const ContentContainer = styled.div` 15 | display: flex; 16 | justify-content: space-between; 17 | border-bottom: solid 1px white; 18 | padding-left: 80px; 19 | padding-right: 100px; 20 | align-items: center; 21 | min-height: 120px; 22 | width: 100%; 23 | `; 24 | 25 | export const IconsContainer = styled.div` 26 | display: flex; 27 | align-items: center; 28 | justify-content: space-around; 29 | min-width: 550px; 30 | height: 100%; 31 | `; 32 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/statusLabel.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { FC } from 'react'; 5 | import styled from 'styled-components'; 6 | 7 | const Container = styled.div<{ color?: string }>` 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | background-color: ${(p) => p.color ?? 'lightgreen'}; 12 | border-radius: 5px; 13 | padding: 5px 10px; 14 | min-width: 80px; 15 | `; 16 | 17 | const Text = styled.div` 18 | font-size: 13px; 19 | color: black; 20 | `; 21 | 22 | type Props = { 23 | text: string; 24 | color?: string; 25 | }; 26 | 27 | const StatusLabel: FC = ({ text, color }) => ( 28 | 29 | {text} 30 | 31 | ); 32 | 33 | export default StatusLabel; 34 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/admin.controller.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Controller, Get, Header } from '@nestjs/common'; 5 | import { NETWORK_CONFIGS, SQNetworks } from '@subql/network-config'; 6 | import { argv } from './yargs'; 7 | 8 | @Controller() 9 | export class AdminController { 10 | @Get('env.js') 11 | @Header('content-type', 'text/javascript; charset=utf-8') 12 | getEnv() { 13 | const config = { 14 | NETWORK: argv.network, // mainnet| kepler | testnet 15 | COORDINATOR_SERVICE_PORT: argv.port, 16 | IPFS_GATEWAY: argv.ipfs, 17 | RPC_ENDPOINT: argv['network-endpoint'], 18 | REGISTRY_PROJECT: NETWORK_CONFIGS[argv.network as SQNetworks].gql.network, 19 | }; 20 | 21 | return `window.env = ${JSON.stringify(config)};`; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/network/network.type.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Field, ObjectType } from '@nestjs/graphql'; 5 | 6 | @ObjectType('ProjectDetailsFromNetwork') 7 | export class ProjectDetailsFromNetwork { 8 | @Field() 9 | id: string; 10 | @Field({ nullable: true }) 11 | totalReward: string; 12 | @Field({ nullable: true }) 13 | indexerCount: number; 14 | @Field({ nullable: true }) 15 | totalAgreement: number; 16 | @Field({ nullable: true }) 17 | totalOffer: number; 18 | } 19 | 20 | export class IndexerAllocationSummary { 21 | id?: string; 22 | projectId?: string; 23 | deploymentId?: string; 24 | indexerId?: string; 25 | totalAdded?: bigint; 26 | totalRemoved?: bigint; 27 | totalAmount?: bigint; 28 | createAt?: Date; 29 | updateAt?: Date; 30 | } 31 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | class Logger { 5 | private readonly prefix: string; 6 | 7 | constructor(prefix?: string) { 8 | this.prefix = prefix ?? ''; 9 | } 10 | 11 | getLogger(scope: string): Logger { 12 | return new Logger(`${this.prefix}[${scope}]`); 13 | } 14 | 15 | public l(message: any, ...rest: any[]): void { 16 | console.log(`${this.prefix}${message?.toString()}`, ...rest); 17 | } 18 | 19 | public w(message: any, ...rest: any[]): void { 20 | console.warn(`${this.prefix}${message?.toString()}`, ...rest); 21 | } 22 | 23 | public e(message: any, ...rest: any[]): void { 24 | console.error(`${this.prefix}${message?.toString()}`, ...rest); 25 | } 26 | } 27 | 28 | export const logger = new Logger(); 29 | 30 | export default Logger; 31 | -------------------------------------------------------------------------------- /apps/indexer-proxy/proxy/HEADER-GPL3: -------------------------------------------------------------------------------- 1 | // This file is part of SubQuery. 2 | 3 | // Copyright (C) {\d+(-\d+)?} SubQuery Pte Ltd authors & contributors 4 | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 5 | 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . -------------------------------------------------------------------------------- /apps/indexer-proxy/utils/HEADER-GPL3: -------------------------------------------------------------------------------- 1 | // This file is part of SubQuery. 2 | 3 | // Copyright (C) {\d+(-\d+)?} SubQuery Pte Ltd authors & contributors 4 | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 5 | 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . -------------------------------------------------------------------------------- /apps/indexer-admin/src/utils/table.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { TableText, TableTitle, Tag } from '@subql/components'; 5 | 6 | // TODO: this can migrate to @subql/components -> table as utils 7 | export function createTextColumn(index: T, title: string, toolTip?: string) { 8 | return { 9 | dataIndex: index, 10 | title: , 11 | render: (val: string) => {val}, 12 | }; 13 | } 14 | 15 | export function createTagColumn(index: T, title: string, toolTip?: string) { 16 | return { 17 | dataIndex: index, 18 | title: , 19 | render: ({ state, text }: { state: 'error' | 'success' | 'info'; text: string }) => ( 20 | {text} 21 | ), 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/network/styles.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const Contrainer = styled.div` 7 | display: flex; 8 | flex: 1; 9 | flex-direction: column; 10 | min-width: 1270px; 11 | padding: 30px 50px; 12 | overflow: auto; 13 | `; 14 | 15 | export const LeftContainer = styled.div` 16 | display: flex; 17 | align-items: center; 18 | min-width: 685px; 19 | margin-bottom: 30px; 20 | `; 21 | 22 | export const ContentContainer = styled.div` 23 | display: flex; 24 | flex-direction: column; 25 | justify-content: flex-start; 26 | margin-left: 40px; 27 | `; 28 | 29 | export const VersionContainer = styled.div` 30 | display: flex; 31 | justify-content: space-between; 32 | margin-top: 25px; 33 | height: 50px; 34 | width: 500px; 35 | `; 36 | -------------------------------------------------------------------------------- /docker/dev/ipfs/ipfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | 4 | addPeers() { 5 | sleep $1; 6 | shift; 7 | 8 | ipfs swarm peers | grep -E "(12D3KooWHEEjciF2JmDukCkWW93tQ7eJYs16PWqEo81GrXz82DUL|12D3KooWForH2nsSRN5cynPhoona6re1nw2EcimQJxHnicd1yqUV|12D3KooWPhsrviSKFTKawpW3bRAdLZ89jhXdYuszAys4YwL3RMn3|12D3KooWCFokEyt9gtuQHTwVAzwBsdjsBqfSxq1D3X1FsAbTwaSN)" 9 | ipfs swarm connect /dns4/ipfs-swarm-a-lh.subquery.network/tcp/19988/p2p/12D3KooWForH2nsSRN5cynPhoona6re1nw2EcimQJxHnicd1yqUV 10 | ipfs swarm connect /dns4/ipfs-swarm-b-lh.subquery.network/tcp/19988/p2p/12D3KooWPhsrviSKFTKawpW3bRAdLZ89jhXdYuszAys4YwL3RMn3 11 | ipfs swarm connect /dns4/ipfs-swarm-c-lh.subquery.network/tcp/19988/p2p/12D3KooWCFokEyt9gtuQHTwVAzwBsdjsBqfSxq1D3X1FsAbTwaSN 12 | } 13 | 14 | addPeers 10 & 15 | 16 | ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["*"]' 17 | ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "POST"]' 18 | -------------------------------------------------------------------------------- /docker/test/ipfs/ipfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | 4 | addPeers() { 5 | sleep $1; 6 | shift; 7 | 8 | ipfs swarm peers | grep -E "(12D3KooWHEEjciF2JmDukCkWW93tQ7eJYs16PWqEo81GrXz82DUL|12D3KooWForH2nsSRN5cynPhoona6re1nw2EcimQJxHnicd1yqUV|12D3KooWPhsrviSKFTKawpW3bRAdLZ89jhXdYuszAys4YwL3RMn3|12D3KooWCFokEyt9gtuQHTwVAzwBsdjsBqfSxq1D3X1FsAbTwaSN)" 9 | ipfs swarm connect /dns4/ipfs-swarm-a-lh.subquery.network/tcp/19988/p2p/12D3KooWForH2nsSRN5cynPhoona6re1nw2EcimQJxHnicd1yqUV 10 | ipfs swarm connect /dns4/ipfs-swarm-b-lh.subquery.network/tcp/19988/p2p/12D3KooWPhsrviSKFTKawpW3bRAdLZ89jhXdYuszAys4YwL3RMn3 11 | ipfs swarm connect /dns4/ipfs-swarm-c-lh.subquery.network/tcp/19988/p2p/12D3KooWCFokEyt9gtuQHTwVAzwBsdjsBqfSxq1D3X1FsAbTwaSN 12 | } 13 | 14 | addPeers 10 & 15 | 16 | ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["*"]' 17 | ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "POST"]' 18 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/main.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { NestFactory } from '@nestjs/core'; 5 | import { AppModule } from './app.module'; 6 | import { getLogger, LogCategory, NestLogger } from './utils/logger'; 7 | import { argv } from './yargs'; 8 | 9 | async function bootstrap() { 10 | try { 11 | const port = argv.port; 12 | const app = await NestFactory.create(AppModule, { logger: new NestLogger() }); 13 | 14 | app.enableCors({ 15 | origin: '*', 16 | credentials: true, 17 | }); 18 | 19 | await app.listen(port); 20 | getLogger(LogCategory.coordinator).info('coordinator service started'); 21 | getLogger(LogCategory.admin).info('indexer admin app started'); 22 | } catch (e) { 23 | getLogger(LogCategory.coordinator).error(e, 'coordinator service failed'); 24 | } 25 | } 26 | 27 | void bootstrap(); 28 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/utils/queries.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { gql } from '@apollo/client/core'; 5 | import { DocumentNode } from 'graphql'; 6 | 7 | // @ts-ignore 8 | export const GET_DEPLOYMENT: DocumentNode = gql` 9 | query GetDeployment($id: String!) { 10 | deployment(id: $id) { 11 | id 12 | metadata 13 | createdTimestamp 14 | project { 15 | id 16 | type 17 | metadata 18 | createdTimestamp 19 | owner 20 | } 21 | } 22 | } 23 | `; 24 | 25 | // @ts-ignore 26 | export const GET_INDEXER_PROJECTS: DocumentNode = gql` 27 | query GetIndexerProjects($indexer: String!) { 28 | indexerDeployments(filter: { indexerId: { equalTo: $indexer } }) { 29 | nodes { 30 | indexerId 31 | deploymentId 32 | status 33 | } 34 | } 35 | } 36 | `; 37 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/utils/waitForSomething.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { parseError } from './error'; 5 | 6 | export const sleep = (time = 2000) => new Promise((resolve) => setTimeout(resolve, time)); 7 | 8 | interface waitForSomethingArg { 9 | func: () => boolean | PromiseLike; 10 | timeout?: number; 11 | splitTime?: number; 12 | } 13 | 14 | export const waitForSomething = async ( 15 | { func, timeout, splitTime = 50 }: waitForSomethingArg, 16 | sleepTime = 0 17 | ): Promise => { 18 | if (timeout && sleepTime >= timeout) { 19 | return false; 20 | } 21 | try { 22 | const r = await func(); 23 | 24 | if (r) { 25 | return r; 26 | } 27 | } catch (e) { 28 | parseError(e); 29 | } 30 | 31 | await sleep(splitTime); 32 | 33 | return waitForSomething({ func, timeout }, splitTime + sleepTime); 34 | }; 35 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/containers/account.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { useState } from 'react'; 5 | import { useAccount as useAccountWagmi } from 'wagmi'; 6 | 7 | import { createContainer } from './unstated'; 8 | 9 | type TAccountContext = { 10 | isRegisterIndexer: boolean | undefined; 11 | updateIsRegisterIndexer: (isIndexer: boolean) => void; 12 | 13 | account: string | undefined; 14 | }; 15 | 16 | function useAccountImpl(): TAccountContext { 17 | const { address: account } = useAccountWagmi(); 18 | 19 | const [isRegisterIndexer, updateIsRegisterIndexer] = useState(); 20 | 21 | return { 22 | account, 23 | isRegisterIndexer, 24 | updateIsRegisterIndexer, 25 | }; 26 | } 27 | 28 | export const { useContainer: useAccount, Provider: AccountProvider } = createContainer( 29 | useAccountImpl, 30 | { displayName: 'Global Account' } 31 | ); 32 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/loading.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { FC } from 'react'; 5 | import { Spinner } from '@subql/components'; 6 | import styled from 'styled-components'; 7 | 8 | import { useLoading } from 'containers/loadingContext'; 9 | 10 | const Container = styled.div` 11 | position: absolute; 12 | display: flex; 13 | width: 100%; 14 | height: 100%; 15 | z-index: 1000; 16 | background-color: white; 17 | justify-content: center; 18 | align-items: flex-start; 19 | `; 20 | 21 | const Loading: FC = () => { 22 | const { pageLoading } = useLoading(); 23 | if (!pageLoading) return null; 24 | return ( 25 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | export const LoadingSpinner = () => ( 32 | 33 | 34 | 35 | ); 36 | 37 | export default Loading; 38 | -------------------------------------------------------------------------------- /apps/indexer-proxy/utils/src/traits.rs: -------------------------------------------------------------------------------- 1 | // This file is part of SubQuery. 2 | 3 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 4 | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 5 | 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | pub trait Hash { 20 | fn hash(&self) -> String; 21 | } 22 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/utils/network.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import dns from 'dns'; 5 | import { isIP } from 'net'; 6 | import { promisify } from 'util'; 7 | import { isPrivate } from 'ip'; 8 | 9 | const lookup = promisify(dns.lookup); 10 | 11 | export async function getIpAddress(domain: string): Promise { 12 | const { address } = await lookup(domain); 13 | return address; 14 | } 15 | 16 | export function isIp(ip: string): boolean { 17 | return isIP(ip) !== 0; 18 | } 19 | 20 | export function isPrivateIp(ip: string): boolean { 21 | return isPrivate(ip); 22 | } 23 | 24 | export function getDomain(url: string): string { 25 | const domain = new URL(url).hostname; 26 | return domain; 27 | } 28 | 29 | export function safeGetDomain(url: string): string { 30 | let domain = ''; 31 | try { 32 | domain = new URL(url).hostname; 33 | } finally { 34 | return domain; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /apps/indexer-proxy/utils/src/types.rs: -------------------------------------------------------------------------------- 1 | // This file is part of SubQuery. 2 | 3 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 4 | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 5 | 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | use crate::error::Error; 20 | 21 | pub type Result = std::result::Result; 22 | -------------------------------------------------------------------------------- /apps/indexer-admin/README.md: -------------------------------------------------------------------------------- 1 | # Indexer Admin App 2 | 3 | ## Install dependencies 4 | 5 | Please check the root readme [indexer-coordinator](https://github.com/subquery/indexer-coordinator) 6 | 7 | ## Development 8 | 9 | 1. Create your own `.env.local` file: 10 | 11 | ```conf 12 | VITE_APP_NETWORK=testnet 13 | VITE_APP_COORDINATOR_SERVICE_URL=http://cyrbuzz.space:8000/graphql 14 | ``` 15 | 16 | 2. Start [indexer-coordinator](https://github.com/subquery/network-indexer-services/tree/main/apps/indexer-coordinator) service. 17 | 3. Run `pnpm start`(or `yarn`, `npm`, recommend to use `pnpm`). 18 | 19 | ## Testing with testnet 20 | 21 | Start [indexer-coordinator](https://github.com/subquery/network-indexer-services/tree/main/apps/indexer-coordinator) service locally. 22 | 23 | Open `localhost:8008` to play with the app. 24 | 25 | ## Tools 26 | 27 | - [moonbeam explorer](https://moonbeam-explorer.netlify.app/?network=MoonbeamDevNode) 28 | - [polkadot explorer](https://polkadot.js.org/apps/#/explorer) 29 | 30 | ## 31 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/core/service.resolver.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Resolver, Query, Args } from '@nestjs/graphql'; 5 | import { getLogger } from '../utils/logger'; 6 | import { AccountService } from './account.service'; 7 | import { ContractService } from './contract.service'; 8 | 9 | const logger = getLogger('ServiceResolver'); 10 | 11 | @Resolver() 12 | export class ServiceResolver { 13 | constructor(private accountService: AccountService, private contract: ContractService) {} 14 | 15 | @Query(() => Boolean) 16 | async withdrawController(@Args('id') id: string) { 17 | const indexer = await this.accountService.getIndexer(); 18 | const controller = await this.accountService.getController(id); 19 | if (!controller) { 20 | logger.warn(`Controller: ${id} not exist`); 21 | return false; 22 | } 23 | return this.contract.withdrawAll(id, indexer, controller); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/metrics/metrics.module.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Module } from '@nestjs/common'; 5 | import { PrometheusModule } from '@willsoto/nestjs-prometheus'; 6 | import { CoreModule } from '../core/core.module'; 7 | import { CoordinatorMetricsService } from './coordinator.metric.service'; 8 | import { MetricEventListener } from './event.listener'; 9 | import { MetricsResolver } from './metrics.resolver'; 10 | import { PrometheusProviders } from './promProviders'; 11 | import { VersionsService } from './versions.service'; 12 | 13 | @Module({ 14 | imports: [ 15 | PrometheusModule.register({ 16 | path: 'metrics', 17 | defaultMetrics: { enabled: false }, 18 | }), 19 | CoreModule, 20 | ], 21 | providers: [ 22 | MetricEventListener, 23 | VersionsService, 24 | MetricsResolver, 25 | ...PrometheusProviders, 26 | CoordinatorMetricsService, 27 | ], 28 | }) 29 | export class MetricsModule {} 30 | -------------------------------------------------------------------------------- /docker/dev/2_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pwd 4 | 5 | SCRIPT_DIR="$(dirname "$0")" 6 | cd $SCRIPT_DIR 7 | pwd 8 | 9 | source .env.example 10 | if [ -e ".env" ]; then 11 | source .env 12 | fi 13 | 14 | docker-compose -f docker-compose.yml up -d 15 | 16 | cd ../../ 17 | pwd 18 | 19 | if [ "$1" = "bypass" ] || [ "$2" = "bypass" ]; then 20 | echo "bypass admin build" 21 | yarn build:coordinator 22 | else 23 | yarn build 24 | fi 25 | 26 | FE_DIR="apps/indexer-admin/build" 27 | BE_DIR="apps/indexer-coordinator/dist/indexer-admin" 28 | rm -rf $BE_DIR && cp -R $FE_DIR $BE_DIR 29 | 30 | cd apps/indexer-coordinator 31 | pwd 32 | 33 | yarn start:docker \ 34 | --postgres-host $LOCAL_IP \ 35 | --postgres-username $POSTGRES_USERNAME \ 36 | --postgres-password $POSTGRES_PASSWORD \ 37 | --network $NETWORK \ 38 | --network-endpoint $NETWORK_ENDPOINT \ 39 | --use-prerelease \ 40 | --debug \ 41 | --dev \ 42 | --port 8000 \ 43 | --ipfs http://$LOCAL_IP:8080/api/v0/ \ 44 | --mmrPath $MMRPATH \ 45 | --compose-file-directory ../../docker/dev/.data 46 | -------------------------------------------------------------------------------- /apps/indexer-proxy/proxy/src/metadata/mod.rs: -------------------------------------------------------------------------------- 1 | mod ai; 2 | mod rpc_evm; 3 | mod rpc_substrate; 4 | mod subgraph; 5 | mod subquery; 6 | 7 | pub use ai::metadata as ai_metadata; 8 | pub use rpc_evm::metadata as rpc_evm_metadata; 9 | pub use rpc_substrate::metadata as rpc_substrate_metadata; 10 | pub use subgraph::metadata as subgraph_metadata; 11 | pub use subquery::metadata as subquery_metadata; 12 | 13 | use crate::cli::COMMAND; 14 | use crate::graphql::AUTO_REDUCE_ALLOCATION; 15 | use subql_indexer_utils::request::{graphql_request, GraphQLQuery}; 16 | 17 | pub async fn auto_reduce_allocation_enabled() -> Option { 18 | let url = COMMAND.graphql_url(); 19 | let arae_res = graphql_request(&url, &GraphQLQuery::query(AUTO_REDUCE_ALLOCATION)).await; 20 | match arae_res { 21 | Ok(arae) => match arae.pointer("/data/config") { 22 | Some(target) => target 23 | .as_bool() 24 | .or(target.as_str().unwrap_or("").parse().ok()), 25 | None => None, 26 | }, 27 | Err(_) => None, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/indexer-proxy/proxy/src/response.rs: -------------------------------------------------------------------------------- 1 | use chrono::Utc; 2 | use ethers::{ 3 | abi::{encode, Tokenizable}, 4 | signers::Signer, 5 | utils::keccak256, 6 | }; 7 | use sha2::Digest; 8 | use subql_indexer_utils::payg::{convert_sign_to_string, default_sign}; 9 | 10 | use crate::account::ACCOUNT; 11 | 12 | pub async fn sign_response(data: &[u8]) -> String { 13 | let mut hasher = sha2::Sha256::new(); 14 | hasher.update(&data); 15 | let bytes = hasher.finalize().to_vec(); 16 | 17 | // sign the response 18 | let lock = ACCOUNT.read().await; 19 | let controller = lock.controller.clone(); 20 | let indexer = lock.indexer.clone(); 21 | drop(lock); 22 | 23 | let timestamp = Utc::now().timestamp(); 24 | let payload = encode(&[ 25 | indexer.into_token(), 26 | bytes.into_token(), 27 | timestamp.into_token(), 28 | ]); 29 | let hash = keccak256(payload); 30 | let sign = controller 31 | .sign_message(hash) 32 | .await 33 | .unwrap_or(default_sign()); 34 | format!("{} {}", timestamp, convert_sign_to_string(&sign)) 35 | } 36 | -------------------------------------------------------------------------------- /.github/actions/create-prerelease/action.yml: -------------------------------------------------------------------------------- 1 | # Composite action needed to access github context 2 | 3 | # This is to compensate for yarn 3 issue https://github.com/yarnpkg/berry/issues/3868 4 | name: 'Remove Stable Versions' 5 | description: 'This will remove stableVersion from packages for prerelease' 6 | inputs: 7 | package-path: 8 | description: 'package path to run action e.g. package/common' 9 | required: true 10 | npm-token: 11 | description: 'token to push to npm registry' 12 | required: true 13 | 14 | runs: 15 | using: 'composite' 16 | steps: 17 | - working-directory: ${{ github.workspace }} 18 | run: node ${{ github.action_path }}/remove-stable-version.js ${{ github.workspace }}/${{ inputs.package-path }} 19 | shell: bash 20 | - working-directory: ${{ github.workspace }} 21 | run: npm install -g pnpm@8.6.3 22 | shell: bash 23 | 24 | - working-directory: ${{ inputs.package-path }} 25 | run: echo "Changes exist in ${{ inputs.package-path }}" && pnpm version prerelease 26 | env: 27 | NPM_TOKEN: ${{ inputs.npm-token }} 28 | shell: bash 29 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/footer/config.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import DiscordIcon from 'resources/discord.svg'; 5 | import GithubIcon from 'resources/github.svg'; 6 | import LinkendinIcon from 'resources/linkendin.svg'; 7 | import MatrixIcon from 'resources/matrix.svg'; 8 | import MediumIcon from 'resources/medium.svg'; 9 | import TelegramIcon from 'resources/telegram.svg'; 10 | import TwitterIcon from 'resources/twitter.svg'; 11 | 12 | const createConfig = (src: string, url: string) => ({ src, url }); 13 | 14 | export const linkConfigs = [ 15 | createConfig(TelegramIcon, 'https://t.me/subquerynetwork'), 16 | createConfig(DiscordIcon, 'https://www.linkedin.com/company/subquery--'), 17 | createConfig(GithubIcon, 'https://github.com/subquery/subql'), 18 | createConfig(LinkendinIcon, 'https://www.linkedin.com/company/subquery'), 19 | createConfig(MediumIcon, 'https://subquery.medium.com/'), 20 | createConfig(MatrixIcon, 'https://matrix.to/#/#subquery:matrix.org'), 21 | createConfig(TwitterIcon, 'https://twitter.com/subquerynetwork'), 22 | ]; 23 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/migration/1720105309219-add-config-table.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { MigrationInterface, QueryRunner } from 'typeorm'; 5 | 6 | export class AddConfigTable1720105309219 implements MigrationInterface { 7 | name = 'AddConfigTable1720105309219'; 8 | 9 | async up(queryRunner: QueryRunner): Promise { 10 | await queryRunner.query( 11 | `CREATE TABLE "config" ("created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "id" SERIAL NOT NULL, "key" character varying NOT NULL, "value" character varying DEFAULT '', "sort" integer DEFAULT '0', CONSTRAINT "PK_d0ee79a681413d50b0a4f98cf7b" PRIMARY KEY ("id"))` 12 | ); 13 | await queryRunner.query( 14 | `CREATE UNIQUE INDEX "IDX_26489c99ddbb4c91631ef5cc79" ON "config" ("key") ` 15 | ); 16 | } 17 | 18 | async down(queryRunner: QueryRunner): Promise { 19 | await queryRunner.query(`DROP INDEX "public"."IDX_26489c99ddbb4c91631ef5cc79"`); 20 | await queryRunner.query(`DROP TABLE "config"`); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/payg/payg.module.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Module } from '@nestjs/common'; 5 | import { TypeOrmModule } from '@nestjs/typeorm'; 6 | 7 | import { ConfigModule } from 'src/config/config.module'; 8 | import { CoreModule } from '../core/core.module'; 9 | import { PaygEntity } from '../project/project.model'; 10 | import { SubscriptionModule } from '../subscription/subscription.module'; 11 | 12 | import { ChainInfo, Channel, ChannelLabor } from './payg.model'; 13 | import { PaygQueryService } from './payg.query.service'; 14 | import { PaygResolver } from './payg.resolver'; 15 | import { PaygService } from './payg.service'; 16 | import { PaygSyncService } from './payg.sync.service'; 17 | 18 | @Module({ 19 | imports: [ 20 | SubscriptionModule, 21 | CoreModule, 22 | TypeOrmModule.forFeature([Channel, ChannelLabor, ChainInfo, PaygEntity]), 23 | ConfigModule, 24 | ], 25 | providers: [PaygService, PaygSyncService, PaygQueryService, PaygResolver], 26 | exports: [PaygService], 27 | }) 28 | export class PaygModule {} 29 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/footer/footer.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { useLocation } from 'react-router-dom'; 5 | 6 | import Icon from 'components/Icon'; 7 | import { Text } from 'components/primary'; 8 | 9 | import { linkConfigs } from './config'; 10 | import { Container, ContentContainer, IconsContainer } from './styles'; 11 | 12 | const Header = () => { 13 | const location = useLocation(); 14 | 15 | if (['/project/', '/account'].includes(location.pathname)) return null; 16 | 17 | return ( 18 | 19 | 20 | 21 | Follow Us 22 | 23 | 24 | {linkConfigs.map(({ src, url }) => ( 25 | 26 | ))} 27 | 28 | 29 | 30 | SubQuery © 2024 31 | 32 | 33 | ); 34 | }; 35 | 36 | export default Header; 37 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/utils/project.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { ServiceStatus } from 'pages/project-details/types'; 5 | 6 | export function statusCode(status: string): 'success' | 'error' { 7 | if (status === 'HEALTHY' || status === 'STARTING') return 'success'; 8 | return 'error'; 9 | } 10 | 11 | export function serviceStatusCode(status: ServiceStatus) { 12 | switch (status) { 13 | case ServiceStatus.TERMINATED: 14 | return 'error'; 15 | case ServiceStatus.READY: 16 | return 'success'; 17 | default: 18 | return 'error'; 19 | } 20 | } 21 | 22 | export function projectId(cid: string): string { 23 | return cid.substring(0, 15).toLowerCase(); 24 | } 25 | 26 | export function isTrue(value: boolean | string): boolean { 27 | return value === true || value === 'true'; 28 | } 29 | 30 | export const wrapGqlUrl = ({ indexer, url }: { indexer: string; url: string }) => { 31 | const gqlProxy = import.meta.env.VITE_APP_GQL_PROXY; 32 | 33 | return new URL(`${indexer}/?to=${encodeURIComponent(url)}`, gqlProxy).toString(); 34 | }; 35 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/monitor.controller.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Controller, Get, Post, Body } from '@nestjs/common'; 5 | 6 | class Proxy { 7 | // percentage of used cpu, eg. 25, means 25% 8 | p_cpu: number; 9 | // total memory size, unit is GB 10 | t_mem: number; 11 | // percentage of used memory 12 | p_mem: number; 13 | // total disk size, unit is GB 14 | t_disk: number; 15 | // percentage of used disk 16 | p_disk: number; 17 | // peer id in the p2p network 18 | peer: string; 19 | // peer address in the p2p network 20 | addr: string; 21 | // the number of actived agreements 22 | agreement: number; 23 | // the number of actived state channels 24 | channel: number; 25 | } 26 | 27 | @Controller('monitor') 28 | export class MonitorController { 29 | proxies: Map = new Map(); 30 | 31 | @Get() 32 | index() { 33 | return Array.from(this.proxies.values()); 34 | } 35 | 36 | @Post() 37 | collect(@Body() proxy: Proxy) { 38 | this.proxies.set(proxy.peer, proxy); 39 | return; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@subql/network-indexer-services", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "repository": "https://github.com/subquery/network-indexer-services", 6 | "author": "SubQuery", 7 | "license": "Apache-2.0", 8 | "private": true, 9 | "scripts": { 10 | "start": "yarn start:docker", 11 | "start:docker": "yarn install && sh docker/dev/2_start.sh", 12 | "start:admin": "cd apps/indexer-admin && yarn start", 13 | "start:coordinator": "cd apps/indexer-coordinator && yarn start", 14 | "install": "yarn install:admin && yarn install:coordinator", 15 | "install:admin": "cd apps/indexer-admin && yarn install", 16 | "install:coordinator": "cd apps/indexer-coordinator && yarn install", 17 | "build": "yarn build:admin && yarn build:coordinator", 18 | "build:admin": "cd apps/indexer-admin && yarn build", 19 | "build:coordinator": "cd apps/indexer-coordinator && yarn build", 20 | "lint-staged": "yarn lint-staged:admin && yarn lint-staged:coordinator", 21 | "lint-staged:admin": "cd apps/indexer-admin && yarn lint-staged", 22 | "lint-staged:coordinator": "cd apps/indexer-coordinator && yarn lint-staged" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/network/components/networkTabBarView.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { useCallback, useState, VFC } from 'react'; 5 | import Tab from '@mui/material/Tab'; 6 | import Tabs from '@mui/material/Tabs'; 7 | 8 | import ServiceLogView from 'components/logView'; 9 | 10 | enum TabbarItem { 11 | ServiceLog, 12 | } 13 | 14 | const NetworkTabbarView: VFC = () => { 15 | const [value, setValue] = useState(TabbarItem.ServiceLog); 16 | 17 | const handleChange = (_: any, newValue: TabbarItem) => { 18 | setValue(newValue); 19 | }; 20 | 21 | const renderContent = useCallback(() => { 22 | switch (value) { 23 | default: 24 | return ; 25 | } 26 | }, [value]); 27 | 28 | return ( 29 |
30 | 31 | 32 | 33 | {renderContent()} 34 |
35 | ); 36 | }; 37 | 38 | export default NetworkTabbarView; 39 | -------------------------------------------------------------------------------- /apps/indexer-proxy/utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This file is part of SubQuery. 2 | 3 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 4 | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 5 | 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | #![allow(clippy::map_clone)] 19 | #![allow(clippy::or_fun_call)] 20 | #![allow(clippy::too_many_arguments)] 21 | 22 | pub mod constants; 23 | pub mod eip712; 24 | pub mod error; 25 | pub mod p2p; 26 | pub mod payg; 27 | pub mod price_oracle; 28 | pub mod request; 29 | pub mod tools; 30 | pub mod traits; 31 | pub mod types; 32 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/project-details/payg/paygIntroduction.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Button, Text } from 'components/primary'; 5 | 6 | import prompts from '../prompts'; 7 | import { InstructionContainer } from './styles'; 8 | 9 | const { instruction } = prompts.payg; 10 | 11 | type Props = { 12 | onEnablePayg: () => void; 13 | }; 14 | 15 | export function Introduction({ onEnablePayg }: Props) { 16 | return ( 17 | 18 | 19 | {instruction.title} 20 | 21 | 22 | {instruction.desc[0]} 23 | 24 | 25 | {instruction.desc[1]} 26 | 27 | 28 | {instruction.sub}{' '} 29 |
30 | here 31 | 32 | 33 | 37 | 38 | 39 | ); 40 | } 41 | 42 | return <>{children}; 43 | }; 44 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/metrics/events.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export enum Images { 5 | Coordinator = 'subquerynetwork/indexer-coordinator', 6 | Proxy = 'subquerynetwork/indexer-proxy', 7 | Db = 'postgres', 8 | Redis = 'redis', 9 | } 10 | 11 | export enum Metric { 12 | CoordinatorVersion = 'coordinator_version', 13 | ProxyVersion = 'proxy_version', 14 | ProxyDockerStats = 'proxy_docker_stats', 15 | CoordinatorDockerStats = 'coordinator_docker_stats', 16 | DbDockerStats = 'db_docker_stats', 17 | RedisDockerStats = 'redis_docker_stats', 18 | } 19 | 20 | export const metricNameMap: Record = { 21 | [Images.Coordinator]: Metric.CoordinatorDockerStats, 22 | [Images.Proxy]: Metric.ProxyDockerStats, 23 | [Images.Db]: Metric.DbDockerStats, 24 | [Images.Redis]: Metric.RedisDockerStats, 25 | }; 26 | 27 | export enum ContainerStatus { 28 | exit = 1, 29 | dead, 30 | paused, 31 | restarting, 32 | unhealthy, 33 | healthy, 34 | } 35 | 36 | export interface DockerEventPayload { 37 | cpu_usage: string; 38 | memory_usage: string; 39 | status: ContainerStatus; 40 | } 41 | 42 | const metricPrefix = 'subql_indexer'; 43 | 44 | export function metric(name: string): string { 45 | return `${metricPrefix}_${name}`; 46 | } 47 | 48 | export function cpuMetric(metric: Metric): string { 49 | return `${metric}_cpu`; 50 | } 51 | 52 | export function memoryMetric(metric: Metric): string { 53 | return `${metric}_memory`; 54 | } 55 | 56 | export function statusMetric(metric: Metric): string { 57 | return `${metric}_status`; 58 | } 59 | -------------------------------------------------------------------------------- /rush.json: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the main configuration file for Rush. 3 | * For full documentation, please see https://rushjs.io 4 | */ 5 | { 6 | "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json", 7 | "rushVersion": "5.100.2", 8 | // "pnpmVersion": "8.6.3", 9 | "yarnVersion": "1.22.17", 10 | "nodeSupportedVersionRange": ">=14.15.0 <15.0.0 || >=16.13.0 <17.0.0 || >=18.15.0 <19.0.0", 11 | /** 12 | * If you use Git as your version control system, this section has some additional 13 | * optional features you can use. 14 | */ 15 | "gitPolicy": {}, 16 | "repository": {}, 17 | /** 18 | * Event hooks are customized script actions that Rush executes when specific events occur 19 | */ 20 | "eventHooks": { 21 | /** 22 | * The list of shell commands to run before the Rush installation starts 23 | */ 24 | "preRushInstall": [ 25 | // "common/scripts/pre-rush-install.js" 26 | ], 27 | /** 28 | * The list of shell commands to run after the Rush installation finishes 29 | */ 30 | "postRushInstall": [], 31 | /** 32 | * The list of shell commands to run before the Rush build command starts 33 | */ 34 | "preRushBuild": [], 35 | /** 36 | * The list of shell commands to run after the Rush build command finishes 37 | */ 38 | "postRushBuild": [] 39 | }, 40 | "variants": [], 41 | "projects": [ 42 | { 43 | "packageName": "@subql/indexer-admin", 44 | "projectFolder": "apps/indexer-admin" 45 | }, 46 | { 47 | "packageName": "@subql/indexer-coordinator", 48 | "projectFolder": "apps/indexer-coordinator" 49 | } 50 | ], 51 | } -------------------------------------------------------------------------------- /apps/indexer-admin/src/containers/unstated.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { createContext, ReactNode, useContext, useMemo } from 'react'; 5 | 6 | import Logger from 'utils/logger'; 7 | 8 | const EMPTY: unique symbol = Symbol(undefined); 9 | 10 | export type Props = { 11 | initialState?: State; 12 | children?: ReactNode; 13 | }; 14 | 15 | export type Container = { 16 | Provider: React.ComponentType>; 17 | useContainer: () => V; 18 | }; 19 | 20 | const logger = new Logger(); 21 | 22 | export function createContainer( 23 | useHook: (logger: Logger, initialState?: State) => V, 24 | options?: { displayName?: string } 25 | ): Container { 26 | const Ctx = createContext(EMPTY); 27 | if (options?.displayName) { 28 | Ctx.displayName = options.displayName; 29 | } 30 | 31 | function Provider(props: Props) { 32 | const { initialState, children } = props; 33 | const l = useMemo( 34 | () => (options?.displayName ? logger.getLogger(options?.displayName) : logger), 35 | [] 36 | ); 37 | 38 | const value = useHook(l, initialState); 39 | return {children}; 40 | } 41 | 42 | function useContainer(): V { 43 | const value = useContext(Ctx); 44 | 45 | if (value === EMPTY) { 46 | throw new Error( 47 | `Component must be wrapped with <${Ctx.displayName ?? 'Container'}.Provider>` 48 | ); 49 | } 50 | 51 | return value; 52 | } 53 | 54 | return { Provider, useContainer }; 55 | } 56 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/core/account.resolver.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Args, Mutation, Query, Resolver, Subscription } from '@nestjs/graphql'; 5 | 6 | import { SubscriptionService } from '../subscription/subscription.service'; 7 | import { AccountEvent } from '../utils/subscription'; 8 | 9 | import { AccountMetaDataType, Controller, Indexer } from './account.model'; 10 | import { AccountService } from './account.service'; 11 | 12 | @Resolver() 13 | export class AccountResolver { 14 | constructor(private accountService: AccountService, private pubSub: SubscriptionService) {} 15 | 16 | @Mutation(() => Indexer) 17 | addIndexer(@Args('address') address: string) { 18 | return this.accountService.addIndexer(address); 19 | } 20 | 21 | @Query(() => AccountMetaDataType) 22 | accountMetadata() { 23 | return this.accountService.getAccountMetadata(); 24 | } 25 | 26 | @Mutation(() => String) 27 | addController() { 28 | return this.accountService.addController(); 29 | } 30 | 31 | @Mutation(() => Controller) 32 | removeController(@Args('id') id: string) { 33 | return this.accountService.removeController(id); 34 | } 35 | 36 | @Query(() => [Controller]) 37 | controllers() { 38 | return this.accountService.getControllers(); 39 | } 40 | 41 | @Mutation(() => AccountMetaDataType) 42 | removeAccounts() { 43 | return this.accountService.removeAccounts(); 44 | } 45 | 46 | @Subscription(() => AccountMetaDataType) 47 | accountChanged() { 48 | return this.pubSub.asyncIterator([AccountEvent.Indexer, AccountEvent.Controller]); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /apps/indexer-proxy/proxy/Dockerfile: -------------------------------------------------------------------------------- 1 | # Builder 2 | FROM rust:bullseye AS builder 3 | RUN update-ca-certificates 4 | ENV CARGO_NET_GIT_FETCH_WITH_CLI=true 5 | 6 | ARG SECRETS_SENTRY_DSN 7 | ENV SECRETS_SENTRY_DSN=${SECRETS_SENTRY_DSN} 8 | 9 | WORKDIR /subql 10 | 11 | COPY . . 12 | 13 | RUN --mount=type=cache,id=cargo_bin,target=~/.cargo/bin/ \ 14 | --mount=type=cache,id=cargo_reg_index,target=~/.cargo/registry/index/ \ 15 | --mount=type=cache,id=cargo_reg_cache,target=~/.cargo/registry/cache/ \ 16 | --mount=type=cache,id=cargo_git,target=~/.cargo/git/db/ \ 17 | --mount=type=cache,id=target,target=/subql/target/ \ 18 | cargo update && cargo build --release && mv /subql/target/release/subql-indexer-proxy /subql/ 19 | 20 | # Final image 21 | FROM debian:bullseye-slim 22 | 23 | RUN rm /var/lib/dpkg/info/libc-bin.* && \ 24 | apt-get clean && \ 25 | apt-get update && \ 26 | apt-get install libc-bin && \ 27 | apt-get install -y --no-install-recommends ca-certificates && \ 28 | apt-get --assume-yes install curl && \ 29 | update-ca-certificates 30 | 31 | WORKDIR /subql 32 | 33 | # Copy our build 34 | COPY --from=builder /subql/subql-indexer-proxy . 35 | 36 | # Use an unprivileged user. 37 | RUN groupadd --gid 10001 subql && \ 38 | useradd --home-dir /subql \ 39 | --create-home \ 40 | --shell /bin/bash \ 41 | --gid subql \ 42 | --groups subql \ 43 | --uid 10000 subql 44 | RUN mkdir -p /subql/.local/share && \ 45 | mkdir /subql/data && \ 46 | chown -R subql:subql /subql && \ 47 | ln -s /subql/data /subql/.local/share 48 | USER subql:subql 49 | 50 | ENTRYPOINT ["./subql-indexer-proxy"] 51 | -------------------------------------------------------------------------------- /.github/workflows/coordinator-docker-prod.yml: -------------------------------------------------------------------------------- 1 | name: 'Coordinator-Docker-Prod' 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | node-build-push-docker: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | with: 11 | fetch-depth: 100 12 | token: ${{ secrets.REPO_TOKEN }} 13 | 14 | # build admin and coordinator 15 | - name: Setup Node.js environment 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: 18 19 | - run: yarn install 20 | - run: yarn build 21 | 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v1 24 | 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v1 27 | 28 | - name: Login to DockerHub 29 | uses: docker/login-action@v1 30 | with: 31 | username: subquerynetwork 32 | password: ${{ secrets.SQ_DOCKERHUB_TOKEN }} 33 | 34 | ## node 35 | - name: Get updated coordinator version 36 | id: get-coordinator-version 37 | run: | 38 | sh .github/workflows/scripts/coordinatorVersion.sh 39 | 40 | - name: Build and push 41 | uses: docker/build-push-action@v2 42 | with: 43 | context: . 44 | push: true 45 | platforms: amd64,arm64 46 | file: ./Dockerfile 47 | tags: subquerynetwork/indexer-coordinator:v${{ steps.get-coordinator-version.outputs.COORDINATOR_VERSION }} 48 | build-args: RELEASE_VERSION=${{ steps.get-coordinator-version.outputs.COORDINATOR_VERSION }} 49 | 50 | - name: Image digest 51 | run: echo ${{ steps.docker_build.outputs.digest }} 52 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/errorPlaceholder/index.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import React, { FC } from 'react'; 5 | import { Typography } from '@subql/components'; 6 | 7 | interface IProps { 8 | size?: 'normal' | 'small'; 9 | } 10 | 11 | const ErrorPlaceholder: FC = (props) => { 12 | const { size = 'normal' } = props; 13 | 14 | return ( 15 |
24 | rpc 32 | 33 | Oops! Can't connect to coordinator 34 | 35 | 36 | It looks like the coordinator service is temporarily unavaiable 37 | , please 38 |
39 | check your coordinator service or let us know in 40 | 41 | 48 | Discord. 49 | 50 | 51 |
52 |
53 | ); 54 | }; 55 | export default ErrorPlaceholder; 56 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/utils/apolloClient.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { ApolloClient, gql, InMemoryCache } from '@apollo/client'; 5 | import { LEADERBOARD_SUBQL_ENDPOINTS, SQNetworks } from '@subql/network-config'; 6 | 7 | const COORDINATOR_SERVICE_URL = 8 | import.meta.env.VITE_APP_COORDINATOR_SERVICE_URL || window.env.COORDINATOR_SERVICE_URL; 9 | 10 | export const NETWORK = (import.meta.env.VITE_APP_NETWORK || window.env.NETWORK) as SQNetworks; 11 | 12 | const defaultCoordinatorUrl = '/graphql'; 13 | 14 | export const coordinatorServiceUrl = import.meta.env.DEV 15 | ? COORDINATOR_SERVICE_URL 16 | : defaultCoordinatorUrl; 17 | 18 | export const excellencyServiceUrl = LEADERBOARD_SUBQL_ENDPOINTS[NETWORK]; 19 | 20 | export const proxyServiceUrl = `${window.location.protocol}//${window.location.hostname}`; 21 | 22 | export function createApolloClient(uri: string) { 23 | return new ApolloClient({ 24 | uri, 25 | cache: new InMemoryCache(), 26 | defaultOptions: { 27 | query: { 28 | fetchPolicy: 'network-only', 29 | }, 30 | watchQuery: { 31 | fetchPolicy: 'network-only', 32 | }, 33 | }, 34 | }); 35 | } 36 | 37 | // TODO: update report 38 | const excellencyClient = createApolloClient(excellencyServiceUrl); 39 | 40 | export const excellencyQuery = async ( 41 | query: string 42 | ): Promise<{ data: T; status: number }> => { 43 | const { data, networkStatus } = await excellencyClient.query({ 44 | query: gql` 45 | ${query} 46 | `, 47 | }); 48 | 49 | return { 50 | data, 51 | status: networkStatus, 52 | }; 53 | }; 54 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/projects/components/projectsEmptyView.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { FC } from 'react'; 5 | import { Typography } from '@subql/components'; 6 | import styled from 'styled-components'; 7 | 8 | import IntroductionView from 'components/introductionView'; 9 | import { SUPPORTED_NETWORK_PROJECTS_EXPLORER } from 'utils/web3'; 10 | 11 | export const Container = styled.div` 12 | display: flex; 13 | flex-direction: column; 14 | width: 100%; 15 | height: 100%; 16 | align-items: center; 17 | justify-content: flex-start; 18 | margin-top: 80px; 19 | `; 20 | 21 | type Props = { 22 | onClick: () => void; 23 | }; 24 | 25 | const ExplorerLink = () => ( 26 | 27 | here. 28 | 29 | ); 30 | 31 | const EmptyView: FC = ({ onClick }) => { 32 | return ( 33 | 34 | { 41 | onClick(); 42 | }} 43 | link={ExplorerLink()} 44 | /> 45 | 46 | ); 47 | }; 48 | 49 | export default EmptyView; 50 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/utils/metamask.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // @ts-nocheck 5 | /* eslint-disable */ 6 | import { networks } from '@subql/contract-sdk'; 7 | 8 | import { connect } from 'containers/web3'; 9 | import { ChainID, network, NetworkToChainID } from 'utils/web3'; 10 | 11 | export const NetworkError = { 12 | unSupportedNetworkError: 'UnsupportedChainIdError', 13 | }; 14 | 15 | const ethMethods = { 16 | requestAccount: 'eth_requestAccounts', 17 | switchChain: 'wallet_switchEthereumChain', 18 | addChain: 'wallet_addEthereumChain', 19 | }; 20 | 21 | export const NETWORK_CONFIGS = { 22 | [ChainID.testnet]: networks.testnet, 23 | [ChainID.mainnet]: networks.mainnet, 24 | }; 25 | 26 | export async function connectWithMetaMask(activate: Function) { 27 | if (!window.ethereum) return 'MetaMask is not installed'; 28 | try { 29 | await window.ethereum.request({ method: ethMethods.requestAccount }); 30 | await connect(activate); 31 | return ''; 32 | } catch (e) { 33 | return e.message; 34 | } 35 | } 36 | 37 | export async function switchNetwork() { 38 | const chainId = NetworkToChainID[network]; 39 | if (!window?.ethereum || !network) return; 40 | 41 | try { 42 | await window.ethereum.request({ 43 | method: ethMethods.switchChain, 44 | params: [{ chainId }], 45 | }); 46 | } catch (e) { 47 | console.log('e:', e); 48 | if (e.code === 4902) { 49 | await ethereum.request({ 50 | method: ethMethods.addChain, 51 | params: [NETWORK_CONFIGS[chainId]], 52 | }); 53 | } else { 54 | console.log('Switch Ethereum network failed', e); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.github/workflows/coordinator-docker-dev.yml: -------------------------------------------------------------------------------- 1 | name: 'Coordinator-Docker-Dev' 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | node-build-push-docker: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | with: 11 | fetch-depth: 100 12 | token: ${{ secrets.REPO_TOKEN }} 13 | 14 | # build admin and coordinator 15 | - name: Setup Node.js environment 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: 18 19 | - run: yarn install 20 | - run: yarn build 21 | 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v1 24 | 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v1 27 | 28 | - name: Login to DockerHub 29 | uses: docker/login-action@v1 30 | with: 31 | username: subquerynetwork 32 | password: ${{ secrets.SQ_DOCKERHUB_TOKEN }} 33 | 34 | ## node 35 | - name: Get updated coordinator version 36 | id: get-coordinator-version 37 | run: | 38 | sh .github/workflows/scripts/coordinatorVersion.sh 39 | 40 | - name: Build and push 41 | uses: docker/build-push-action@v2 42 | with: 43 | context: . 44 | push: true 45 | platforms: amd64,arm64 46 | file: ./Dockerfile 47 | tags: subquerynetwork/indexer-coordinator-dev:v${{ steps.get-coordinator-version.outputs.COORDINATOR_VERSION }},subquerynetwork/indexer-coordinator-dev:latest 48 | build-args: RELEASE_VERSION=${{ steps.get-coordinator-version.outputs.COORDINATOR_VERSION }} 49 | 50 | - name: Image digest 51 | run: echo ${{ steps.docker_build.outputs.digest }} 52 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/config/config.resolver.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; 5 | import { ConfigEntity } from './config.model'; 6 | import { ConfigService } from './config.service'; 7 | 8 | @Resolver(() => ConfigEntity) 9 | export class ConfigResolver { 10 | constructor(private configService: ConfigService) {} 11 | 12 | /** 13 | * @deprecated 14 | */ 15 | @Query(() => String) 16 | get(@Args('key') key: string): Promise { 17 | return this.configService.get(key); 18 | } 19 | 20 | @Query(() => String) 21 | config(@Args('key') key: string): Promise { 22 | return this.configService.get(key); 23 | } 24 | 25 | /** 26 | * @deprecated 27 | */ 28 | @Mutation(() => Boolean) 29 | async set(@Args('key') key: string, @Args('value') value: string): Promise { 30 | await this.configService.set(key, value); 31 | return true; 32 | } 33 | 34 | @Mutation(() => Boolean) 35 | async setConfig(@Args('key') key: string, @Args('value') value: string): Promise { 36 | await this.configService.set(key, value); 37 | return true; 38 | } 39 | 40 | /** 41 | * @deprecated 42 | */ 43 | @Query(() => [ConfigEntity]) 44 | async getAll(): Promise { 45 | return await this.configService.getAll(); 46 | } 47 | 48 | @Query(() => [ConfigEntity]) 49 | async allConfig(): Promise { 50 | return await this.configService.getAll(); 51 | } 52 | 53 | @Query(() => [ConfigEntity]) 54 | async tips(): Promise { 55 | return await this.configService.getTips(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/hooks/useTipForDominatPrice.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { useRef } from 'react'; 5 | import { useHistory } from 'react-router'; 6 | import { useMutation, useQuery } from '@apollo/client'; 7 | import { Modal } from 'antd'; 8 | 9 | import { GET_TIPS, SET_CONFIG } from 'utils/queries'; 10 | 11 | export const useTipForDominatPrice = () => { 12 | const history = useHistory(); 13 | const mounted = useRef(false); 14 | const tips = useQuery<{ tips: { key: 'tip_dominant_price'; value: '1' | '0' }[] }>(GET_TIPS); 15 | 16 | const [setTips] = useMutation(SET_CONFIG); 17 | const checkAndTip = () => { 18 | if (mounted.current) return; 19 | if (tips.data) { 20 | mounted.current = true; 21 | const tip = tips?.data?.tips?.find((tip) => tip.key === 'tip_dominant_price'); 22 | 23 | if (tip?.value === '1') { 24 | Modal.info({ 25 | title: 'Set Default Flex Plan Price', 26 | content: 27 | "You haven't set the default price for the Flex Plan. Please set the default price to enable the Flex Plan.", 28 | cancelButtonProps: { 29 | style: { display: 'none' }, 30 | }, 31 | okText: 'Set Default Price', 32 | okButtonProps: { 33 | shape: 'round', 34 | }, 35 | onOk: async () => { 36 | await setTips({ 37 | variables: { 38 | key: 'tip_dominant_price', 39 | value: '0', 40 | }, 41 | }); 42 | history.push('/config#flexplan'); 43 | }, 44 | }); 45 | } 46 | } 47 | }; 48 | 49 | return checkAndTip; 50 | }; 51 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { LoggerService } from '@nestjs/common'; 5 | import { Logger } from '@subql/utils'; 6 | import Pino from 'pino'; 7 | 8 | import { argv } from '../yargs'; 9 | 10 | export enum LogCategory { 11 | coordinator = 'indexer-coordinator', 12 | admin = 'indexer-admin', 13 | } 14 | 15 | export enum TextColor { 16 | RED = 31, 17 | GREEN, 18 | YELLOW, 19 | BLUE, 20 | MAGENTA, 21 | CYAN, 22 | } 23 | 24 | export function colorText(text: string, color = TextColor.CYAN): string { 25 | return `\u001b[${color}m${text}\u001b[39m`; 26 | } 27 | 28 | const logger = new Logger({ 29 | level: argv.debug ? 'debug' : 'info', 30 | outputFormat: 'colored', 31 | nestedKey: 'payload', 32 | }); 33 | 34 | export function getLogger(category: string): Pino.Logger { 35 | return logger.getLogger(category); 36 | } 37 | 38 | export function debugLogger(category: string, msg: string) { 39 | getLogger(category).debug(msg); 40 | } 41 | 42 | export function setLevel(level: Pino.LevelWithSilent): void { 43 | logger.setLevel(level); 44 | } 45 | 46 | export class NestLogger implements LoggerService { 47 | private logger = logger.getLogger('nestjs'); 48 | 49 | error(message: any, trace?: string) { 50 | if (trace) { 51 | this.logger.error({ trace }, message); 52 | } else { 53 | this.logger.error(message); 54 | } 55 | } 56 | 57 | log(message: any): any { 58 | this.logger.info(message); 59 | } 60 | 61 | warn(message: any): any { 62 | this.logger.warn(message); 63 | } 64 | } 65 | 66 | if (argv['log-args']) { 67 | getLogger('yargs').debug('yargs argv: %o', argv); 68 | } 69 | -------------------------------------------------------------------------------- /apps/indexer-proxy/utils/src/price_oracle.rs: -------------------------------------------------------------------------------- 1 | // This file is part of SubQuery. 2 | 3 | // Copyright (C) 2020-2024 SubQuery Pte Ltd authors & contributors 4 | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 5 | 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | use ethers::prelude::*; 20 | use std::sync::Arc; 21 | use subql_contracts::{price_oracle, Network}; 22 | 23 | use crate::error::Error; 24 | 25 | pub async fn convert_price( 26 | asset_from: Address, 27 | asset_to: Address, 28 | amount_from: U256, 29 | client: Arc, 30 | network: Network, 31 | ) -> Result { 32 | if asset_from == Address::default() || asset_from == asset_to { 33 | return Ok(amount_from); 34 | } 35 | 36 | let contract = price_oracle(client, network).map_err(|_| Error::ServiceException(1023))?; 37 | 38 | contract 39 | .method::<_, U256>("convertPrice", (asset_from, asset_to, amount_from)) 40 | .map_err(|_| Error::ServiceException(1028))? 41 | .call() 42 | .await 43 | .map_err(|_| Error::ServiceException(1028)) 44 | } 45 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/styles/input.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const SubqlInput = styled.div` 7 | .ant-input { 8 | padding: 12px; 9 | border-radius: 8px; 10 | border: none; 11 | 12 | &:focus { 13 | border: none; 14 | box-shadow: none; 15 | } 16 | &::-webkit-inner-spin-button { 17 | -webkit-appearance: none; 18 | margin: 0; 19 | } 20 | 21 | &[type='number'] { 22 | -moz-appearance: textfield; 23 | } 24 | 25 | &-affix-wrapper { 26 | padding: 12px; 27 | border-radius: 8px; 28 | } 29 | 30 | &-wrapper { 31 | display: flex; 32 | border: 1px solid var(--sq-gray300); 33 | border-radius: 8px; 34 | 35 | .ant-select { 36 | display: flex; 37 | align-items: center; 38 | flex: 1; 39 | &-selector { 40 | border-color: transparent !important; 41 | } 42 | 43 | &-selection-item { 44 | display: flex; 45 | } 46 | 47 | &-focused { 48 | .ant-select-selector.ant-select-selector.ant-select-selector { 49 | box-shadow: none; 50 | } 51 | } 52 | } 53 | } 54 | 55 | &-group-addon.ant-input-group-addon.ant-input-group-addon.ant-input-group-addon { 56 | background: transparent; 57 | width: 122px; 58 | border-radius: 8px; 59 | background: rgba(67, 136, 221, 0.1); 60 | border: none; 61 | border-start-start-radius: 8px; 62 | border-end-start-radius: 8px; 63 | display: flex; 64 | margin: 6px; 65 | flex-shrink: 0; 66 | align-items: center; 67 | } 68 | } 69 | `; 70 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/project-details/components/projectUptime.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { FC, useState } from 'react'; 5 | import { useParams } from 'react-router'; 6 | import { NetworkStatus } from '@apollo/client'; 7 | import { Typography } from '@subql/components'; 8 | import { useMount } from 'ahooks'; 9 | 10 | import UptimeBar from 'components/uptimeBar'; 11 | import { useCoordinatorIndexer } from 'containers/coordinatorIndexer'; 12 | import { getRequestHistory, IGetRequestHistory } from 'utils/queries'; 13 | 14 | const ProjectUptime: FC = () => { 15 | const { id = '' } = useParams<{ id: string }>(); 16 | const { indexer: account } = useCoordinatorIndexer(); 17 | const [history, setHistory] = useState([]); 18 | 19 | const getHistory = async (): Promise => { 20 | if (!account) return; 21 | const res = await getRequestHistory({ 22 | deploymentId: id, 23 | indexer: account, 24 | }); 25 | 26 | if (res.status === NetworkStatus.ready) { 27 | setHistory(res.data.getIndexerServiceRequestHistory); 28 | } 29 | }; 30 | 31 | useMount(() => { 32 | getHistory(); 33 | }); 34 | 35 | if (!history.length) return <>; 36 | 37 | return ( 38 |
39 | 42 | 43 | Deployment Uptime 44 | 45 |
46 | } 47 | uptimeData={history} 48 | /> 49 | 50 | ); 51 | }; 52 | export default ProjectUptime; 53 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/projects/styles.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import styled from 'styled-components'; 5 | 6 | export const Container = styled.div` 7 | display: flex; 8 | flex: 1; 9 | justify-content: center; 10 | `; 11 | 12 | export const ContentContainer = styled.div` 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | width: 100%; 17 | min-width: 600px; 18 | padding: 10px 100px; 19 | padding-bottom: 150px; 20 | overflow-y: scroll; 21 | `; 22 | 23 | export const HeaderContainer = styled.div` 24 | display: flex; 25 | width: 100%; 26 | min-width: 600px; 27 | align-items: center; 28 | justify-content: space-between; 29 | `; 30 | 31 | export const ItemContainer = styled.div<{ 32 | flex?: number; 33 | pl?: number; 34 | mw?: number; 35 | color?: string; 36 | }>` 37 | display: flex; 38 | flex: ${({ flex }) => flex ?? 1}; 39 | background-color: ${({ color }) => color ?? 'transparent'}; 40 | padding-left: ${({ pl }) => pl ?? 0}px; 41 | min-width: ${({ mw }) => mw ?? 100}px; 42 | align-items: center; 43 | `; 44 | 45 | /// Project item styles 46 | export const ProjectItemContainer = styled.div` 47 | display: flex; 48 | width: 100%; 49 | min-width: 600px; 50 | min-height: 84px; 51 | background-color: white; 52 | padding: 16px 24px; 53 | border-radius: 8px; 54 | border: 1px solid var(--sq-gray300); 55 | transition: all 0.3s ease; 56 | &:hover { 57 | background-color: rgba(67, 136, 221, 0.08); 58 | border-color: var(--sq-blue600); 59 | cursor: pointer; 60 | } 61 | `; 62 | 63 | export const ProfileContainer = styled.div` 64 | display: flex; 65 | flex-direction: column; 66 | width: 80%; 67 | margin-left: 8px; 68 | `; 69 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/pages/register/prompts.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { TOKEN_SYMBOL } from 'utils/web3'; 5 | 6 | import { RegisterStep } from './types'; 7 | 8 | const prompts = { 9 | [RegisterStep.onboarding]: { 10 | title: 'Stake to Become a Subquery Indexer', 11 | desc: `Become an indexer so you can index SubQuery projects. You need to stake a minimum of 200,000 ${TOKEN_SYMBOL} in order to index SubQuery projects.`, 12 | buttonTitle: 'Get Started', 13 | }, 14 | [RegisterStep.authorisation]: { 15 | title: 'Request Approval for Authorisation', 16 | desc: `The Indexer Admin app needs you to approve the authorisation request to deposit the ${TOKEN_SYMBOL} token into the SubQuery Staking contract. This is a one-time operation for the specific account. Please press the approve button and then confirm and send the transaction on Wallet.`, 17 | buttonTitle: 'Approve', 18 | }, 19 | [RegisterStep.register]: { 20 | title: 'Stake to Become a Subquery Indexer', 21 | desc: '', 22 | // desc: 'Become an indexer so you can index SubQuery projects. You need to stake a minimum of 14,000 SQT in order to index SubQuery projects. The time for processing the transaction depends on the current status of the network and the gas fee, and it usually takes around 30 seconds.', 23 | buttonTitle: 'Register Indexer', 24 | }, 25 | [RegisterStep.sync]: { 26 | title: 'Sync the Indexer with Coordinator Service', 27 | desc: 'This account has already been registered as an indexer. Please press the sync button to sync this account with your coordinator service. Alternatively, you can switch your account in the wallet to select the correct account.', 28 | buttonTitle: 'Sync', 29 | }, 30 | }; 31 | 32 | export default prompts; 33 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine AS BUILD_IMAGE 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY ./apps/indexer-coordinator/ ./apps/indexer-coordinator/ 6 | COPY ./apps/indexer-admin ./apps/indexer-admin/ 7 | 8 | WORKDIR /usr/src/app/apps/indexer-coordinator 9 | 10 | RUN if [ -z "$(ls -A /usr/src/app/apps/indexer-coordinator/dist 2>/dev/null)" ]; then yarn install && yarn build; else echo "indexer-coordinator/dist is not empty, will not build coordinator"; fi && \ 11 | rm -rf ./dist/indexer-admin node_modules && \ 12 | yarn install --prod --link-duplicates 13 | 14 | WORKDIR /usr/src/app/apps/indexer-admin 15 | 16 | RUN if [ -z "$(ls -A /usr/src/app/apps/indexer-admin/build 2>/dev/null)" ]; then yarn install && yarn build; else echo "indexer-admin/build is not empty, will not build admin"; fi 17 | 18 | # If this step fails, please use the following parameters to run yarn build. 19 | # RUN if [ -z "$(ls -A /usr/src/app/apps/indexer-admin/build 2>/dev/null)" ]; then yarn install && NODE_OPTIONS="--max-old-space-size=4096" yarn build; else echo "indexer-admin/build is not empty, will not build admin"; fi 20 | 21 | FROM node:16-alpine 22 | 23 | # Find the installed docker-compose and store its path 24 | RUN apk add --no-cache curl docker-compose grep && \ 25 | DOCKER_COMPOSE_PATH=$(find / -name docker-compose -print -quit) && \ 26 | ln -s $DOCKER_COMPOSE_PATH /usr/local/bin/docker-compose 27 | 28 | WORKDIR /usr/src/app 29 | 30 | # Copy from build image 31 | COPY --from=BUILD_IMAGE /usr/src/app/apps/indexer-coordinator/package.json ./package.json 32 | COPY --from=BUILD_IMAGE /usr/src/app/apps/indexer-coordinator/dist ./dist 33 | COPY --from=BUILD_IMAGE /usr/src/app/apps/indexer-coordinator/node_modules ./node_modules 34 | COPY --from=BUILD_IMAGE /usr/src/app/apps/indexer-admin/build ./dist/indexer-admin 35 | 36 | ENTRYPOINT [ "node", "dist/main.js" ] 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # Bower dependency directory (https://bower.io/) 25 | bower_components 26 | 27 | # node-waf configuration 28 | .lock-wscript 29 | 30 | # Compiled binary addons (https://nodejs.org/api/addons.html) 31 | build/Release 32 | 33 | # Dependency directories 34 | node_modules/ 35 | jspm_packages/ 36 | 37 | # Optional npm cache directory 38 | .npm 39 | 40 | # Optional eslint cache 41 | .eslintcache 42 | 43 | # Optional REPL history 44 | .node_repl_history 45 | 46 | # Output of 'npm pack' 47 | *.tgz 48 | 49 | # Yarn Integrity file 50 | .yarn-integrity 51 | 52 | # next.js build output 53 | .next 54 | 55 | # OS X temporary files 56 | .DS_Store 57 | 58 | # IntelliJ IDEA project files; if you want to commit IntelliJ settings, this recipe may be helpful: 59 | # https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 60 | .idea/ 61 | *.iml 62 | 63 | # Rush temporary files 64 | common/deploy/ 65 | common/temp/ 66 | common/autoinstallers/*/.npmrc 67 | **/.rush/temp/ 68 | 69 | # Heft temporary files 70 | .heft 71 | 72 | # vscode 73 | .vscode 74 | 75 | # indexer-admin 76 | .env*.local 77 | apps/indexer-admin/build/* 78 | apps/indexer-admin/public/env.js 79 | 80 | # indexer-coordinator 81 | apps/indexer-coordinator/dist/* 82 | .data 83 | .projects 84 | .eslintcache 85 | .env 86 | 87 | # docker 88 | docker/**/*.local.* 89 | 90 | # indexer-proxy 91 | /target 92 | **/*.rs.bk 93 | Cargo.lock 94 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/logView.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { FC, useEffect, useMemo } from 'react'; 5 | import { useLazyQuery } from '@apollo/client'; 6 | import { LogViewer } from '@patternfly/react-log-viewer'; 7 | import { Button, Spinner } from '@subql/components'; 8 | import styled from 'styled-components'; 9 | 10 | import { GET_LOG } from 'utils/queries'; 11 | 12 | type Props = { 13 | container: string; 14 | height: number; 15 | }; 16 | 17 | const LogView: FC = ({ container, height = 650 }) => { 18 | const [getLog, { loading, data, error }] = useLazyQuery(GET_LOG, { 19 | fetchPolicy: 'network-only', 20 | }); 21 | 22 | useEffect(() => { 23 | getLog({ variables: { container } }); 24 | }, [container, getLog]); 25 | 26 | const log = useMemo(() => { 27 | if (loading || error) return ''; 28 | return data?.getLog.log; 29 | }, [data?.getLog.log, error, loading]); 30 | 31 | return ( 32 | 33 | getLog({ variables: { container } })} 38 | /> 39 | {!!log && ( 40 | 41 | )} 42 | {loading && } 43 | 44 | ); 45 | }; 46 | 47 | export default LogView; 48 | 49 | const Container = styled.div<{ height: number }>` 50 | height: ${({ height }) => height}px; 51 | padding: 30px; 52 | margin-top: 10px; 53 | background-color: var(--sq-gray900); 54 | align-items: center; 55 | `; 56 | 57 | const StyledButton = styled(Button)` 58 | margin-bottom: 20px; 59 | height: 30px; 60 | width: 100px; 61 | margin-right: 20px; 62 | `; 63 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/hooks/useHasController.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { useState } from 'react'; 5 | import { useQuery } from '@apollo/client'; 6 | 7 | import { useCoordinatorIndexer } from 'containers/coordinatorIndexer'; 8 | import { createContainer } from 'containers/unstated'; 9 | import { GET_CONTROLLERS } from 'utils/queries'; 10 | 11 | import { useController } from './indexerHook'; 12 | 13 | export const useHasControllerImpl = () => { 14 | const [hasControllerLoading, setHasControllerLoading] = useState(true); 15 | const { indexer } = useCoordinatorIndexer(); 16 | const { getController } = useController(); 17 | const [hasController, setHasController] = useState(false); 18 | const controllerQuery = useQuery<{ 19 | controllers: { address: string; id: string }[]; 20 | }>(GET_CONTROLLERS, { 21 | fetchPolicy: 'network-only', 22 | }); 23 | 24 | const refetch = async () => { 25 | try { 26 | setHasControllerLoading(true); 27 | 28 | const controller = await getController(indexer); 29 | const result = await controllerQuery.refetch(); 30 | 31 | if (result.data?.controllers?.find((i) => i.address === controller)) { 32 | setHasController(true); 33 | return true; 34 | } 35 | setHasController(false); 36 | return false; 37 | } catch { 38 | return false; 39 | } finally { 40 | setTimeout(() => { 41 | setHasControllerLoading(false); 42 | }); 43 | } 44 | }; 45 | 46 | return { 47 | data: hasController, 48 | loading: hasControllerLoading, 49 | refetch, 50 | }; 51 | }; 52 | 53 | export const { useContainer: useHasController, Provider: HasControllerProvider } = createContainer( 54 | useHasControllerImpl, 55 | { 56 | displayName: 'Has Controller', 57 | } 58 | ); 59 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/alertView.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { FC, useState } from 'react'; 5 | import { Button } from '@subql/components'; 6 | import { Modal } from 'antd'; 7 | import styled from 'styled-components'; 8 | 9 | import { ButtonContainer, Text } from './primary'; 10 | 11 | type Props = { 12 | title: string; 13 | description: string; 14 | }; 15 | 16 | const AlertView: FC = ({ title, description }) => { 17 | const [isOpen, setIsOpen] = useState(true); 18 | const [loading, setLoading] = useState(false); 19 | 20 | const handleOk = () => { 21 | setLoading(true); 22 | setTimeout(() => { 23 | setLoading(false); 24 | setIsOpen(false); 25 | }, 3000); 26 | }; 27 | 28 | const handleCancel = () => { 29 | setIsOpen(false); 30 | }; 31 | 32 | return ( 33 | 34 | 35 | 36 | 37 | {title} 38 | 39 | 40 | {description} 41 | 42 | 43 | 44 | 51 | )} 52 | 53 | 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/introductionView.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { FC } from 'react'; 5 | import { Typography } from '@subql/components'; 6 | import { Button } from 'antd'; 7 | import styled from 'styled-components'; 8 | 9 | export const Container = styled.div` 10 | display: flex; 11 | flex-direction: column; 12 | align-items: center; 13 | border-radius: 18px; 14 | margin-bottom: 50px; 15 | padding: 60px; 16 | min-width: 800px; 17 | max-width: 50%; 18 | min-height: 450px; 19 | `; 20 | 21 | export const TextContainer = styled.div` 22 | display: flex; 23 | flex-direction: column; 24 | align-items: center; 25 | `; 26 | 27 | type Content = { 28 | title: string; 29 | desc: string; 30 | buttonTitle: string; 31 | }; 32 | 33 | type Props = { 34 | item: Content; 35 | onClick: () => void; 36 | link?: JSX.Element; 37 | loading?: boolean; 38 | }; 39 | 40 | const IntroductionView: FC = ({ item, onClick, loading, link }) => { 41 | const { title, desc, buttonTitle } = item; 42 | return ( 43 | 44 | 45 | 50 | {title} 51 | 52 | 53 | {desc} {link} 54 | 55 | 56 | 68 | 69 | ); 70 | }; 71 | 72 | export default IntroductionView; 73 | -------------------------------------------------------------------------------- /apps/indexer-proxy/proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "subql-indexer-proxy" 3 | version = "2.10.1" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | aes-gcm = "0.10" 8 | axum = { version = "0.7", features = ["ws"] } 9 | axum-auth = "0.7" 10 | axum-streams = { version = "0.19", features = ["text"] } 11 | base64 = "0.22" 12 | bincode = "1.3" 13 | cached = "0.53.1" 14 | chrono = "0.4" 15 | digest = '0.10' 16 | ethers = { git = "https://github.com/gakonst/ethers-rs.git", tag = "ethers-v2.0.7" } 17 | futures-util = "0.3.30" 18 | hex = "0.4" 19 | jsonwebtoken = "9.1" 20 | libp2p = { version = "0.55", features = [ 21 | "dns", 22 | "tokio", 23 | "identify", 24 | "json", 25 | "kad", 26 | "ping", 27 | "tls", 28 | "request-response", 29 | "secp256k1", 30 | "serde", 31 | "tcp", 32 | "quic", 33 | "yamux", 34 | "noise", 35 | ] } 36 | once_cell = "1.12" 37 | prometheus-client = "0.22" 38 | redis = { version = "0.27", features = ["tokio-comp"] } 39 | reqwest = { version = "0.12", features = ["json", "blocking"] } 40 | reqwest-streams = { version = "0.8", features = ["json"] } 41 | rustls-webpki = "0.102" 42 | sentry = "0.34.0" 43 | serde = { version = "1.0", features = ["derive"] } 44 | serde_json = "1.0" 45 | serde_with = { version = "3.0", features = ["json"] } 46 | sha2 = '0.10' 47 | solana-client = "2.2" 48 | solana-sdk = "2.2" 49 | solana-transaction-status-client-types = "2.2" 50 | structopt = "0.3" 51 | subql-contracts = { git = "https://github.com/subquery/network-contracts", tag = "v1.9.0" } 52 | subql-indexer-utils = { version = "2", path = "../utils" } 53 | sysinfo = "0.32" 54 | tokenizers = "0.20" 55 | tokio = { version = "1", features = ["full"] } 56 | tokio-stream = { version = "0.1" } 57 | tokio-tungstenite = { version = "0.24.0", features = ["native-tls"] } 58 | tower-http = { version = "0.6", features = ["cors"] } 59 | tracing = "0.1" 60 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 61 | url = "2.2" 62 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/hooks/network.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { useCallback, useEffect, useMemo, useState } from 'react'; 5 | import { useNetwork } from 'wagmi'; 6 | import { base, baseSepolia } from 'wagmi/chains'; 7 | 8 | import { useContractSDK } from 'containers/contractSdk'; 9 | import { useCoordinatorIndexer } from 'containers/coordinatorIndexer'; 10 | 11 | export const useTokenSymbol = () => { 12 | const { chain } = useNetwork(); 13 | const tokenSymbol = useMemo(() => { 14 | if (chain?.id === baseSepolia.id) { 15 | return baseSepolia.nativeCurrency.symbol; 16 | } 17 | if (chain?.id === base.id) { 18 | return base.nativeCurrency.symbol; 19 | } 20 | 21 | return base.nativeCurrency.symbol; 22 | }, [chain]); 23 | 24 | return tokenSymbol; 25 | }; 26 | 27 | export type IndexerEra = { 28 | currentEra: string; 29 | lastClaimedEra: string; 30 | lastSettledEra: string; 31 | }; 32 | 33 | export const useIndexerEra = () => { 34 | const { indexer: account } = useCoordinatorIndexer(); 35 | const sdk = useContractSDK(); 36 | 37 | const [indexerEra, setIndexerEra] = useState(); 38 | 39 | const updateEra = useCallback(async () => { 40 | if (!sdk || !account) return; 41 | 42 | console.log('account:', account); 43 | const lastClaimedEra = (await sdk.rewardsDistributor.getRewardInfo(account)).lastClaimEra; 44 | const [currentEra, lastSettledEra] = await Promise.all([ 45 | sdk.eraManager.eraNumber(), 46 | sdk.rewardsStaking.getLastSettledEra(account), 47 | ]); 48 | 49 | setIndexerEra({ 50 | currentEra: currentEra.toString(), 51 | lastClaimedEra: lastClaimedEra.toString(), 52 | lastSettledEra: lastSettledEra.toString(), 53 | }); 54 | }, [sdk, account]); 55 | 56 | useEffect(() => { 57 | updateEra(); 58 | }, [updateEra]); 59 | 60 | return indexerEra; 61 | }; 62 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/components/Copy/Copy.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import * as React from 'react'; 5 | import { BsCheckLg, BsClipboard } from 'react-icons/bs'; 6 | import { message } from 'antd'; 7 | import clsx from 'clsx'; 8 | 9 | import styles from './Copy.module.css'; 10 | 11 | const sendSuccessMsg = (msg: string, className?: string) => { 12 | message.success({ 13 | content: msg, 14 | className, 15 | }); 16 | }; 17 | 18 | type Props = { 19 | value?: string; 20 | className?: string; 21 | iconClassName?: string; 22 | iconSize?: number; 23 | customIcon?: React.ReactNode; 24 | children?: React.ReactNode; 25 | position?: 'flex-start' | 'flex-center'; 26 | }; 27 | 28 | const Copy: React.FC = ({ 29 | value, 30 | className, 31 | iconClassName, 32 | children, 33 | iconSize, 34 | position = 'flex-center', 35 | }) => { 36 | const [icon, setIcon] = React.useState(false); 37 | 38 | const handleClick = (e: React.MouseEvent) => { 39 | e.stopPropagation(); 40 | setIcon(true); 41 | if (value) { 42 | navigator.clipboard.writeText(value); 43 | sendSuccessMsg('Copied!'); 44 | } 45 | setTimeout(() => setIcon(false), 500); 46 | }; 47 | 48 | return ( 49 | /* eslint-disable jsx-a11y/no-static-element-interactions */ 50 | // eslint-disable-next-line jsx-a11y/click-events-have-key-events 51 |
handleClick(e)} 54 | style={{ display: 'flex', alignItems: 'center' }} 55 | > 56 | {children} 57 |
58 |
59 | {icon ? : } 60 |
61 |
62 |
63 | ); 64 | }; 65 | 66 | export default Copy; 67 | -------------------------------------------------------------------------------- /apps/indexer-proxy/proxy/src/mod_libp2p/mod.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use libp2p::identity::{self, Keypair}; 4 | use tokio::time::sleep; 5 | 6 | use crate::mod_libp2p::network::EventLoop; 7 | 8 | pub mod behavior; 9 | pub mod network; 10 | 11 | pub async fn start_libp2p_process(controller_sk: &str) { 12 | let local_key = make_libp2p_keypair(controller_sk).await; 13 | tokio::spawn(async move { 14 | let mut backoff = Duration::from_secs(1); 15 | loop { 16 | match monitor_libp2p_connection(local_key.clone()).await { 17 | Ok(_) => { 18 | break; 19 | } 20 | Err(_e) => { 21 | sleep(backoff).await; 22 | backoff = (backoff * 2).min(Duration::from_secs(60)); // Cap backoff 23 | } 24 | } 25 | } 26 | }); 27 | } 28 | 29 | pub async fn make_libp2p_keypair(controller_sk: &str) -> Keypair { 30 | if let Ok(private_key_bytes) = hex::decode(&controller_sk) { 31 | if let Ok(secret_key) = identity::secp256k1::SecretKey::try_from_bytes(private_key_bytes) { 32 | identity::secp256k1::Keypair::from(secret_key).into() 33 | } else { 34 | make_fake_libp2p_keypair().await 35 | } 36 | } else { 37 | make_fake_libp2p_keypair().await 38 | } 39 | } 40 | 41 | pub async fn make_fake_libp2p_keypair() -> Keypair { 42 | let private_key_bytes = 43 | hex::decode("0000000000000000000000000000000000000000000000000000000000000001").unwrap(); 44 | let secret_key = identity::secp256k1::SecretKey::try_from_bytes(private_key_bytes).unwrap(); 45 | identity::secp256k1::Keypair::from(secret_key).into() 46 | } 47 | 48 | pub async fn monitor_libp2p_connection( 49 | local_key: Keypair, 50 | ) -> Result<(), Box> { 51 | let mut eventloop = EventLoop::new(local_key).await?; 52 | eventloop.run().await; 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /docker/dev/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | postgres: 5 | image: postgres:12-alpine 6 | container_name: indexer_db 7 | ports: 8 | - 5432:5432 9 | volumes: 10 | - .data/postgres:/var/lib/postgresql/data 11 | environment: 12 | POSTGRES_PASSWORD: pos_z8X 13 | healthcheck: 14 | test: ['CMD-SHELL', 'pg_isready -U postgres'] 15 | interval: 5s 16 | timeout: 5s 17 | retries: 5 18 | 19 | redis: 20 | image: redis:7-alpine 21 | container_name: indexer_cache 22 | ports: 23 | - 6379:6379 24 | environment: 25 | - ALLOW_EMPTY_PASSWORD=yes 26 | healthcheck: 27 | test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping'] 28 | 29 | ipfs: 30 | image: ipfs/kubo:v0.20.0 31 | container_name: indexer_ipfs 32 | volumes: 33 | - .data/ipfs/export:/export 34 | - .data/ipfs/data:/data/ipfs 35 | - ./ipfs/ipfs.sh:/container-init.d/ipfs.sh:ro 36 | ports: 37 | - 4001:4001 38 | - 4001:4001/udp 39 | - 5001:5001 40 | - 8080:8080 41 | 42 | proxy: 43 | image: subquerynetwork/indexer-proxy:v2.0.0-beta.14 44 | container_name: indexer_proxy 45 | ports: 46 | - 7370:7370/udp 47 | - 8001:8001 48 | restart: always 49 | command: 50 | - --port=8001 51 | - --auth 52 | - --debug 53 | - --jwt-secret=JwtSecret 54 | - --secret-key=ThisIsYourSecret 55 | - --metrics-token=MetricsToken 56 | - --token-duration=8 # query auth token validity [hours] 57 | - --network=testnet 58 | - --coordinator-endpoint=http://host.docker.internal:8000 59 | - --network-endpoint=https://sepolia.base.org 60 | - --redis-endpoint=redis://indexer_cache 61 | healthcheck: 62 | test: ['CMD-SHELL', 'curl http://localhost:8001/healthy >/dev/null 2>&1 || exit 1'] 63 | interval: 5s 64 | timeout: 5s 65 | retries: 5 66 | 67 | networks: 68 | default: 69 | name: indexer_services 70 | -------------------------------------------------------------------------------- /apps/indexer-coordinator/src/migration/1690365333094-update-project-base-config-to-support-multiple-endpoint.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { MigrationInterface, QueryRunner } from 'typeorm'; 5 | 6 | interface IProjectBaseConfig { 7 | networkEndpoint?: string; 8 | networkEndpoints?: string[]; 9 | networkDictionary?: string; 10 | nodeVersion?: string; 11 | queryVersion?: string; 12 | } 13 | export class UpdateProjectBaseConfigToSupportMultipleEndpoint1690365333094 14 | implements MigrationInterface 15 | { 16 | name = 'UpdateProjectBaseConfigToSupportMultipleEndpoint1690365333094'; 17 | 18 | async up(queryRunner: QueryRunner): Promise { 19 | const projects = await queryRunner.query(`SELECT id, "baseConfig" FROM project_entity`); 20 | for (const project of projects) { 21 | const baseConfig: IProjectBaseConfig = project.baseConfig; 22 | if (!baseConfig.networkEndpoints) { 23 | baseConfig.networkEndpoints = baseConfig.networkEndpoint 24 | ? [baseConfig.networkEndpoint] 25 | : []; 26 | delete baseConfig.networkEndpoint; 27 | } 28 | await queryRunner.query(`UPDATE project_entity SET "baseConfig" = $1 WHERE id = $2`, [ 29 | baseConfig, 30 | project.id, 31 | ]); 32 | } 33 | } 34 | 35 | async down(queryRunner: QueryRunner): Promise { 36 | const projects = await queryRunner.query(`SELECT id, "baseConfig" FROM project_entity`); 37 | for (const project of projects) { 38 | const baseConfig: IProjectBaseConfig = project.baseConfig; 39 | if (!baseConfig.networkEndpoint) { 40 | baseConfig.networkEndpoint = baseConfig.networkEndpoints[0] ?? ''; 41 | delete baseConfig.networkEndpoints; 42 | } 43 | await queryRunner.query(`UPDATE project_entity SET "baseConfig" = $1 WHERE id = $2`, [ 44 | baseConfig, 45 | project.id, 46 | ]); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/containers/modalContext.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Dispatch, SetStateAction, useState } from 'react'; 5 | import { FormikValues } from 'formik'; 6 | import { ObjectSchema } from 'yup'; 7 | 8 | import { ClickAction, FormSubmit, ModalAction } from 'pages/project-details/types'; 9 | 10 | import { createContainer } from './unstated'; 11 | 12 | export type TFieldItem = { 13 | title: string; 14 | formKey: string; 15 | value?: string | number; 16 | placeholder?: string; 17 | options?: string[]; 18 | }; 19 | 20 | export type FormConfig = { 21 | placeHolder?: string; 22 | formValues: FormikValues; 23 | schema: ObjectSchema; 24 | onFormSubmit: FormSubmit; 25 | items: TFieldItem[]; 26 | }; 27 | 28 | export type StepItem = { 29 | index: number; 30 | title: string; 31 | desc: string; 32 | popupType?: 'modal' | 'drawer'; 33 | buttonTitle: string; 34 | onClick?: ClickAction; 35 | form?: FormConfig; 36 | }; 37 | 38 | export type TModal = { 39 | visible: boolean; 40 | setVisible: Dispatch>; 41 | steps: StepItem[]; 42 | title?: string; 43 | currentStep?: number; 44 | loading?: boolean; 45 | type?: ModalAction; 46 | onClose?: () => void; 47 | }; 48 | 49 | type TModalContext = { 50 | modalData: TModal | undefined; 51 | showModal: (data: TModal) => void; 52 | removeModal: () => void; 53 | }; 54 | 55 | export const noop = () => ({}); 56 | 57 | function useModalImpl(): TModalContext { 58 | const [modalData, setModalData] = useState(); 59 | const removeModal = () => setModalData({ visible: false, steps: [], setVisible: noop }); 60 | const showModal = (data: TModal) => setModalData({ ...data, onClose: removeModal }); 61 | 62 | return { modalData, showModal, removeModal }; 63 | } 64 | 65 | export const { useContainer: useModal, Provider: ModalProvider } = createContainer(useModalImpl, { 66 | displayName: 'Global Modal', 67 | }); 68 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/utils/ipfs.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { IPFS_URLS } from '@subql/network-config'; 5 | import { utils } from 'ethers'; 6 | import { create } from 'ipfs-http-client'; 7 | 8 | export const IPFS_PROJECT_CLIENT = create({ url: IPFS_URLS.metadata }); 9 | export const IPFS_METADATA_CLIENT = create({ 10 | url: 'https://unauthipfs.subquery.network/ipfs/api/v0', 11 | }); 12 | 13 | export function cidToBytes32(cid: string): string { 14 | return `0x${Buffer.from(utils.base58.decode(cid)).slice(2).toString('hex')}`; 15 | } 16 | 17 | export function bytes32ToCid(bytes: string): string { 18 | const hashHex = `1220${bytes.slice(2)}`; 19 | const hashBytes = Buffer.from(hashHex, 'hex'); 20 | return utils.base58.encode(hashBytes); 21 | } 22 | 23 | export function concatU8A(a: Uint8Array, b: Uint8Array): Uint8Array { 24 | const res = new Uint8Array(a.length + b.length); 25 | res.set(a, 0); 26 | res.set(b, a.length); 27 | return res; 28 | } 29 | 30 | export async function createIndexerMetadata( 31 | name: string, 32 | url?: string, 33 | description?: string 34 | ): Promise { 35 | const result = await IPFS_METADATA_CLIENT.add(JSON.stringify({ name, url, description }), { 36 | pin: true, 37 | }); 38 | const cid = result.cid.toV0().toString(); 39 | return cidToBytes32(cid); 40 | } 41 | 42 | export async function cat(cid: string, client = IPFS_METADATA_CLIENT) { 43 | const results = client.cat(cid); 44 | let raw: Uint8Array | undefined; 45 | 46 | // eslint-disable-next-line no-restricted-syntax 47 | for await (const result of results) { 48 | raw = raw ? concatU8A(raw, result) : result; 49 | } 50 | 51 | if (!raw) { 52 | console.error(`Unable to fetch data from ipfs: ${cid}`); 53 | return raw; 54 | } 55 | 56 | const result = Buffer.from(raw).toString('utf8'); 57 | 58 | try { 59 | return JSON.parse(Buffer.from(raw).toString('utf8')); 60 | } catch { 61 | return result; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /apps/indexer-admin/src/utils/error.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 SubQuery Pte Ltd authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import contractErrorCodes from '@subql/contract-sdk/publish/revertcode.json'; 5 | 6 | import { notificationMsg } from 'containers/notificationContext'; 7 | 8 | import { logger } from './logger'; 9 | 10 | const getErrorMsg = (error: any) => { 11 | const rawErrorMsg = error?.data?.message ?? error?.message ?? error?.error ?? error ?? ''; 12 | return rawErrorMsg; 13 | }; 14 | 15 | export const mapContractError = (error: any) => { 16 | const revertCode = Object.keys(contractErrorCodes).find( 17 | (key) => 18 | getErrorMsg(error).toString().match(`reverted: ${key}`) || 19 | getErrorMsg(error).toString().match(`revert: ${key}`) 20 | ) as keyof typeof contractErrorCodes; 21 | 22 | const getExtraExplain = (revertCode: 'IR004' | 'RS002') => { 23 | const msg = { 24 | IR004: 'Please terminate all the projects', 25 | RS002: 'Please check controller account balance enough to do transaction', 26 | }; 27 | 28 | return msg[revertCode]; 29 | }; 30 | 31 | const extraExplain = 32 | revertCode === 'IR004' || revertCode === 'RS002' ? getExtraExplain(revertCode) : ''; 33 | 34 | return revertCode ? `${contractErrorCodes[revertCode]}. ${extraExplain}` : undefined; 35 | }; 36 | 37 | export const parseError = ( 38 | error: any, 39 | conf: { alert?: boolean; rawMsg?: boolean } = { alert: false, rawMsg: false } 40 | ) => { 41 | const { alert = false, rawMsg = false } = conf; 42 | // logger to dev tools 43 | // TODO: will update print msg. 44 | logger.e(error); 45 | 46 | // show tips to users. 47 | const msg = rawMsg 48 | ? getErrorMsg(error) 49 | : mapContractError(error) ?? 'Unfortunately, something went wrong'; 50 | 51 | if (alert) { 52 | notificationMsg({ 53 | title: 'Error', 54 | message: msg, 55 | type: 'danger', 56 | dismiss: { 57 | duration: 5000, 58 | }, 59 | }); 60 | } 61 | 62 | return msg; 63 | }; 64 | --------------------------------------------------------------------------------