├── config.toml ├── .env.example ├── .gitmodules ├── src ├── graphql │ ├── query_registry.graphql │ ├── query_block_hash_from_number.graphql │ ├── schema_registry.graphql │ ├── query_network.graphql │ ├── query_indexing_statuses.graphql │ ├── schema_network.graphql │ ├── query_graph_account.graphql │ ├── schema_graph_account.graphql │ ├── schema_graph_node.graphql │ ├── mod.rs │ ├── client_registry.rs │ ├── client_network.rs │ ├── client_graph_node.rs │ └── client_graph_account.rs ├── tree_to_txt.rs ├── callbook.rs ├── bots.rs ├── networks.rs ├── lib.rs └── graphcast_agent │ ├── message_typing.rs │ ├── waku_handling.rs │ └── mod.rs ├── .gitignore ├── scripts ├── docker-run-tests.sh ├── docker-cargo-publish.sh ├── install-go.sh └── release.sh ├── .cargo └── config.toml ├── pull_request_template.md ├── examples └── ping-pong │ ├── Cargo.toml │ └── src │ ├── types.rs │ ├── config.rs │ └── main.rs ├── .github ├── workflows │ ├── auto-approve-n-merge.yml │ └── main.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── dependabot.yml └── release.yml ├── .versionrc.json ├── Dockerfile.dev ├── security.md ├── Cargo.toml ├── README.md ├── code_of_conduct.md ├── contributing.md ├── CHANGELOG.md └── LICENSE /config.toml: -------------------------------------------------------------------------------- 1 | GRAPH_NODE_STATUS_ENDPOINT="" 2 | PRIVATE_KEY="" 3 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY= 2 | GRAPH_NODE_STATUS_ENDPOINT= 3 | REGISTRY_SUBGRAPH= 4 | NETWORK_SUBGRAPH= 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "waku-sys/vendor"] 2 | path = waku-sys/vendor 3 | url = https://github.com/status-im/go-waku.git 4 | -------------------------------------------------------------------------------- /src/graphql/query_registry.graphql: -------------------------------------------------------------------------------- 1 | query SetGraphcastIds($address: String!) { 2 | graphcast_ids: setGraphcastIDs(where:{graphcastID: $address}){ 3 | indexer 4 | graphcastID 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/graphql/query_block_hash_from_number.graphql: -------------------------------------------------------------------------------- 1 | query BlockHashFromNumber($network: String!, $blockNumber: Int!) { 2 | blockHashFromNumber( 3 | network: $network 4 | blockNumber: $blockNumber 5 | ) 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .envrc 2 | .env 3 | /target 4 | examples/target 5 | examples/*/target 6 | /Cargo.lock 7 | /.idea 8 | /.fleet 9 | /examples/target 10 | boot_node_addr.conf 11 | test_tree_zone.txt 12 | txt_records.json 13 | /.vscode 14 | -------------------------------------------------------------------------------- /scripts/docker-run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # NOTE: Run from project root ./scripts/docker-run-tests.sh 4 | 5 | docker build -t graphcast-sdk-dev -f Dockerfile.dev . 6 | docker run --rm -v .:/app graphcast-sdk-dev cargo nextest run 7 | -------------------------------------------------------------------------------- /src/graphql/schema_registry.graphql: -------------------------------------------------------------------------------- 1 | input graphcast_id { 2 | id: String! 3 | } 4 | 5 | type SetGraphcastId { 6 | indexer: String! 7 | graphcastID: String! 8 | } 9 | 10 | type Query { 11 | setGraphcastIDs(filter: graphcast_id): [SetGraphcastId!]! 12 | } 13 | -------------------------------------------------------------------------------- /src/graphql/query_network.graphql: -------------------------------------------------------------------------------- 1 | query IndexerStatus($address: String!) { 2 | indexer(id: $address) { 3 | stakedTokens 4 | allocations{ 5 | subgraphDeployment{ 6 | ipfsHash 7 | } 8 | } 9 | } 10 | graphNetwork(id: 1) { 11 | minimumIndexerStake 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(target_os = "macos")'] 2 | # when using osx, we need to link against some golang libraries, it did just work with this missing flags 3 | # from: https://github.com/golang/go/issues/42459 4 | rustflags = ["-C", "link-args=-framework CoreFoundation -framework Security -framework CoreServices -lresolv"] -------------------------------------------------------------------------------- /scripts/docker-cargo-publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # NOTE: Run from project root ./scripts/docker-cargo-publish.sh 4 | 5 | docker build -t graphcast-sdk-dev -f Dockerfile.dev . 6 | docker run --rm -e CARGO_REGISTRY_TOKEN=$CARGO_REGISTRY_TOKEN -v .:/app graphcast-sdk-dev sh -c "cargo publish --token $CARGO_REGISTRY_TOKEN" 7 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | *Add a brief overview of the changes and motivation behind them here* 3 | 4 | ### Issue link (if applicable) 5 | [Link to issue]() 6 | 7 | ### Checklist 8 | - [ ] Are tests up-to-date with the new changes? 9 | - [ ] Are docs up-to-date with the new changes? (Open PR on docs repo if necessary) 10 | -------------------------------------------------------------------------------- /scripts/install-go.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $(uname -m) = "aarch64" ]; then 4 | wget https://golang.org/dl/go1.20.13.linux-arm64.tar.gz && 5 | tar -C /usr/local -xzf go1.20.13.linux-arm64.tar.gz 6 | else 7 | wget https://golang.org/dl/go1.20.13.linux-amd64.tar.gz && 8 | tar -C /usr/local -xzf go1.20.13.linux-amd64.tar.gz 9 | fi 10 | -------------------------------------------------------------------------------- /examples/ping-pong/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ping-pong" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | graphcast_sdk = { package = "graphcast-sdk", path = "../../" } 10 | once_cell = "1.15" 11 | tokio = { version = "1.1.1", features = ["full"] } 12 | anyhow = "1.0.39" 13 | dotenv = "0.15.0" 14 | -------------------------------------------------------------------------------- /src/graphql/query_indexing_statuses.graphql: -------------------------------------------------------------------------------- 1 | query IndexingStatuses { 2 | indexingStatuses { 3 | subgraph 4 | synced 5 | health 6 | node 7 | fatalError { 8 | handler 9 | message 10 | } 11 | chains { 12 | network 13 | latestBlock { 14 | number 15 | hash 16 | } 17 | chainHeadBlock { 18 | number 19 | hash 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/graphql/schema_network.graphql: -------------------------------------------------------------------------------- 1 | type GraphNetwork { 2 | minimumIndexerStake: String! 3 | } 4 | 5 | type SubgraphDeployment { 6 | ipfsHash: String! 7 | } 8 | 9 | type Allocation { 10 | subgraphDeployment: SubgraphDeployment! 11 | } 12 | 13 | type Indexer { 14 | stakedTokens: String! 15 | allocations: [Allocation!] 16 | } 17 | 18 | type Query { 19 | indexer(id: String!): Indexer 20 | graphNetwork(id: Int!): GraphNetwork! 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/auto-approve-n-merge.yml: -------------------------------------------------------------------------------- 1 | name: Auto approve and merge PRs by dependabot 2 | 3 | on: 4 | pull_request 5 | 6 | jobs: 7 | autoapprove: 8 | name: Auto Approve a PR by dependabot 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Auto approve 12 | uses: hmarr/auto-approve-action@v3.2.1 13 | if: github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]' 14 | with: 15 | github-token: ${{ secrets.DEPENDABOT_TOKEN }} 16 | -------------------------------------------------------------------------------- /src/graphql/query_graph_account.graphql: -------------------------------------------------------------------------------- 1 | query GraphAccount($account_addr: String!, $operator_addr: [String!]!) { 2 | graphAccounts(where:{ 3 | operators_contains: $operator_addr, 4 | id: $account_addr 5 | }) { 6 | id 7 | operators{ 8 | id 9 | } 10 | subgraphs{ 11 | id 12 | currentVersion { 13 | subgraphDeployment{ 14 | ipfsHash 15 | } 16 | } 17 | } 18 | indexer { 19 | id 20 | stakedTokens 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -x 5 | 6 | VERSION="v$(cargo metadata --quiet --format-version 1 | jq -r '.packages[] | select(.name == "graphcast-sdk") | .version')" 7 | 8 | if [[ -z "$VERSION" ]]; then 9 | echo "Usage: $0 " 10 | exit 1 11 | fi 12 | 13 | git-cliff -o CHANGELOG.md 14 | 15 | ( 16 | git add CHANGELOG.md Cargo.lock Cargo.toml scripts/release.sh \ 17 | && git commit -m "chore: release $VERSION" 18 | ) || true 19 | 20 | # Publish to crates.io 21 | cargo publish 22 | -------------------------------------------------------------------------------- /.versionrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | { "type": "feat", "section": "Features" }, 4 | { "type": "fix", "section": "Bug Fixes" }, 5 | { "type": "chore", "hidden": true }, 6 | { "type": "docs", "hidden": true }, 7 | { "type": "style", "hidden": true }, 8 | { "type": "refactor", "hidden": true }, 9 | { "type": "perf", "hidden": true }, 10 | { "type": "test", "hidden": true } 11 | ], 12 | "commitUrlFormat": "https://github.com/mokkapps/changelog-generator-demo/commits/{{hash}}", 13 | "compareUrlFormat": "https://github.com/mokkapps/changelog-generator-demo/compare/{{previousTag}}...{{currentTag}}" 14 | } 15 | -------------------------------------------------------------------------------- /src/graphql/schema_graph_account.graphql: -------------------------------------------------------------------------------- 1 | type Subgraph { 2 | id: String! 3 | currentVersion: Version 4 | } 5 | 6 | type Version{ 7 | subgraphDeployment: SubgraphDeployment! 8 | } 9 | 10 | type SubgraphDeployment { 11 | ipfsHash: String! 12 | } 13 | 14 | type Indexer { 15 | id: String! 16 | stakedTokens: String! 17 | } 18 | 19 | type Operator { 20 | id: String! 21 | } 22 | 23 | type GraphAccount { 24 | id: String! 25 | operators: [Operator!]! 26 | subgraphs: [Subgraph!]! 27 | indexer: Indexer 28 | } 29 | 30 | type Query { 31 | graphAccounts(account_addr: String!, operator_addr: String!): [GraphAccount!]! 32 | } 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feat.Req]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Problem statement** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Expectation proposal** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Alternative considerations** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | open-pull-requests-limit: 3 8 | ignore: 9 | # These are peer deps of Cargo and should not be automatically bumped 10 | - dependency-name: "semver" 11 | - dependency-name: "crates-io" 12 | commit-message: 13 | prefix: chore 14 | include: scope 15 | - package-ecosystem: 'github-actions' 16 | directory: '/' 17 | schedule: 18 | interval: 'monthly' 19 | open-pull-requests-limit: 3 20 | commit-message: 21 | prefix: chore 22 | include: scope 23 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | authors: 6 | - octocat 7 | categories: 8 | - title: Breaking Changes 🛠 9 | labels: 10 | - Semver-Major 11 | - breaking-change 12 | - title: Features 🏕 13 | labels: 14 | - Semver-Minor 15 | - feat 16 | - enhancement 17 | exclude: 18 | labels: 19 | - dependencies 20 | - chore 21 | - title: Dependencies 👒 22 | labels: 23 | - dependencies 24 | - chore 25 | - title: Bug fixes 🐛 26 | labels: 27 | - bug 28 | - title: Refactor 🌱 29 | labels: 30 | - refactor 31 | - title: Other Changes 32 | labels: 33 | - "*" 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Version [e.g. 22] 29 | - Rust Version [e.g. 22] 30 | - Other Versionings [e.g. 22] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM rust:1.82-bullseye 2 | 3 | # Update and install necessary packages, including libc6-dev for libresolv 4 | RUN apt-get update \ 5 | && apt-get install -y --no-install-recommends \ 6 | wget \ 7 | curl \ 8 | libpq-dev \ 9 | pkg-config \ 10 | clang \ 11 | build-essential \ 12 | libc6-dev \ 13 | && apt-get clean \ 14 | && rm -rf /var/lib/apt/lists/* 15 | 16 | # Ensure CA certificates are installed 17 | RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates 18 | 19 | WORKDIR /app 20 | 21 | #Install go 22 | COPY scripts/install-go.sh install-go.sh 23 | RUN ./install-go.sh 24 | ENV PATH=$PATH:/usr/local/go/bin 25 | 26 | # Set Rust flags to link against libresolv 27 | ENV RUSTFLAGS="-C link-arg=-lresolv" 28 | 29 | # Download and install dev environment deps 30 | RUN cargo install --locked cargo-binstall 31 | 32 | # Install nextest for tests... 33 | RUN cargo binstall cargo-nextest --secure 34 | -------------------------------------------------------------------------------- /security.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | The team and community take all security bugs very seriously. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions. 6 | 7 | Report security bugs by emailing the lead maintainer at security@graphops.xyz. 8 | 9 | The maintainer will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours indicating the next steps in handling your report. After the initial reply to your report, the security team will endeavor to keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. 10 | 11 | Report security bugs in third-party modules to the person or team maintaining the module. 12 | 13 | Thank you for improving the security of this code. 14 | 15 | ## Comments on this Policy 16 | 17 | If you have suggestions on how this process could be improved please submit a pull request. 18 | -------------------------------------------------------------------------------- /src/graphql/schema_graph_node.graphql: -------------------------------------------------------------------------------- 1 | type BlockPointer { 2 | number: String! 3 | hash: String! 4 | } 5 | 6 | enum Health { 7 | "Subgraph syncing normally" 8 | healthy 9 | "Subgraph syncing but with errors" 10 | unhealthy 11 | "Subgraph halted due to errors" 12 | failed 13 | } 14 | 15 | type IndexingError { 16 | handler: String 17 | block: BlockPointer 18 | message: String! 19 | deterministic: Boolean! 20 | } 21 | 22 | type ChainIndexingStatus { 23 | network: String! 24 | latestBlock: BlockPointer 25 | chainHeadBlock: BlockPointer 26 | lastHealthyBlock: BlockPointer 27 | } 28 | 29 | type IndexerDeployment { 30 | subgraph: String! 31 | synced: Boolean! 32 | health: Health! 33 | node: String 34 | fatalError: IndexingError 35 | nonFatalErrors: [IndexingError!]! 36 | chains: [ChainIndexingStatus!]! 37 | } 38 | 39 | type Query { 40 | indexingStatuses: [IndexerDeployment!]! 41 | proofOfIndexing( 42 | subgraph: String! 43 | blockNumber: Int! 44 | blockHash: String! 45 | indexer: String 46 | ): String 47 | blockHashFromNumber( 48 | network: String! 49 | blockNumber: UInt! 50 | ): String 51 | } 52 | -------------------------------------------------------------------------------- /src/tree_to_txt.rs: -------------------------------------------------------------------------------- 1 | use serde_json::Value; 2 | use std::fs; 3 | use std::io::{BufWriter, Write}; 4 | 5 | /// Convert TXT records from the tree_creator to standard ZONE file 6 | fn main() { 7 | // Set up file 8 | let json_file = fs::read_to_string("txt_records.json").expect("Unable to read file"); 9 | let json: Value = serde_json::from_str(&json_file).expect("Unable to parse JSON"); 10 | let output_file = fs::File::create("test_tree_zone.txt").expect("Unable to create file"); 11 | let mut output = BufWriter::new(output_file); 12 | 13 | // Initialize with standard configs 14 | writeln!(output, "$ORIGIN testfleet.graphcast.xyz.\n$TTL 86400\ngraphcast.xyz 3600 IN SOA graphcast.xyz root.graphcast.xyz 2042508586 7200 3600 86400 3600\ntestfleet.graphcast.xyz. 86400 IN A 165.227.156.72\ngraphcast.xyz. 1 IN CNAME testfleet.graphcast.xyz.").expect("Failed to write default configs"); 15 | // Iterate over the result tree 16 | for (key, value) in json 17 | .as_object() 18 | .expect("Could not convert JSON to object") 19 | .get("result") 20 | .expect("Unable to get result from JSON file") 21 | .as_object() 22 | .expect("Could not convert JSON to object") 23 | .iter() 24 | { 25 | let record = format!("{key}. IN TXT {value}"); 26 | writeln!(output, "{record}").expect("Unable to write to file"); 27 | } 28 | println!("Finished converting, check test_tree_zone.txt"); 29 | } 30 | -------------------------------------------------------------------------------- /src/callbook.rs: -------------------------------------------------------------------------------- 1 | use derive_getters::Getters; 2 | use serde_derive::{Deserialize, Serialize}; 3 | 4 | use crate::graphql::client_graph_node::indexing_statuses::IndexingStatusesIndexingStatuses; 5 | use crate::graphql::client_graph_node::{ 6 | get_indexing_statuses, query_graph_node_network_block_hash, 7 | }; 8 | use crate::graphql::client_network::{query_network_subgraph, Network}; 9 | use crate::graphql::client_registry::query_registry; 10 | use crate::graphql::QueryError; 11 | 12 | #[derive(Clone, Debug, Getters, Serialize, Deserialize, PartialEq)] 13 | pub struct CallBook { 14 | /// A constant defining Graphcast registry subgraph endpoint 15 | graphcast_registry: String, 16 | /// A constant defining The Graph network subgraph endpoint 17 | graph_network: String, 18 | /// A constant defining the graph node endpoint 19 | graph_node_status: String, 20 | } 21 | 22 | impl CallBook { 23 | pub fn new( 24 | graphcast_registry: String, 25 | graph_network: String, 26 | graph_node_status: Option, 27 | ) -> CallBook { 28 | CallBook { 29 | graphcast_registry, 30 | graph_network, 31 | graph_node_status: graph_node_status.unwrap_or("none".to_string()), 32 | } 33 | } 34 | pub async fn block_hash(&self, network: &str, block_number: u64) -> Result { 35 | query_graph_node_network_block_hash(&self.graph_node_status, network, block_number).await 36 | } 37 | 38 | pub async fn registered_indexer(&self, wallet_address: &str) -> Result { 39 | query_registry(&self.graphcast_registry, wallet_address).await 40 | } 41 | 42 | pub async fn indexing_statuses( 43 | &self, 44 | ) -> Result, QueryError> { 45 | get_indexing_statuses(&self.graph_node_status).await 46 | } 47 | 48 | pub async fn network_subgraph(&self, indexer_address: &str) -> Result { 49 | query_network_subgraph(&self.graph_network, indexer_address).await 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphcast-sdk" 3 | version = "0.7.4" 4 | edition = "2021" 5 | authors = ["GraphOps (axiomatic-aardvark, hopeyen)"] 6 | description = "SDK to build Graphcast Radios" 7 | license = "Apache-2.0" 8 | repository = "https://github.com/graphops/graphcast-sdk" 9 | keywords = ["graphprotocol", "gossip-network", "sdk", "waku", "p2p"] 10 | categories = ["network-programming", "web-programming::http-client"] 11 | 12 | [dependencies] 13 | waku = { version = "=0.6.0", package = "waku-bindings" } 14 | slack-morphism = { version = "1.10", features = ["hyper", "axum"] } 15 | prost = "0.11" 16 | once_cell = "1.17" 17 | derive-getters = "0.2.1" 18 | chrono = "0.4" 19 | serde = "1.0.163" 20 | serde_json = "1.0.96" 21 | tokio = { version = "1.28.1", features = ["full"] } 22 | anyhow = "1.0.71" 23 | graphql_client = "0.12.0" 24 | serde_derive = "1.0.163" 25 | reqwest = { version = "0.11.24", default-features = false, features = ["json", "rustls-tls"] } 26 | ethers = "2.0.4" 27 | ethers-contract = "2.0.4" 28 | ethers-core = "2.0.4" 29 | ethers-derive-eip712 = "1.0.2" 30 | clap = { version = "4.3.1", features = ["derive", "env"] } 31 | toml = "0.7.3" 32 | partial_application = "0.2.1" 33 | prometheus-http-query = { version = "0.6.6", default-features = false, features = ["rustls-tls"] } 34 | num-bigint = "0.4.3" 35 | num-traits = "0.2.15" 36 | lazy_static = "1.4.0" 37 | thiserror = "1.0.40" 38 | secp256k1 = "0.27.0" 39 | data-encoding = "2.3.3" 40 | url = "2.3.1" 41 | rsb_derive = "0.5.1" 42 | dotenv = "0.15.0" 43 | tracing = "0.1" 44 | tracing-subscriber = { version = "0.3", features = [ 45 | "env-filter", 46 | "ansi", 47 | "fmt", 48 | "std", 49 | "json", 50 | ] } 51 | async-graphql = "4.0.16" 52 | async-graphql-axum = "4.0.16" 53 | teloxide = { git = "https://github.com/petkodes/teloxide", version="0.12.2" } 54 | 55 | [dev-dependencies.cargo-husky] 56 | version = "1" 57 | default-features = false 58 | features = ["precommit-hook", "run-cargo-fmt", "run-cargo-clippy"] 59 | 60 | [[example]] 61 | name = "ping-pong" 62 | path = "examples/ping-pong/src/main.rs" 63 | crate-type = ["bin"] 64 | 65 | [[bin]] 66 | name = "rect" 67 | path = "src/tree_to_txt.rs" 68 | -------------------------------------------------------------------------------- /src/graphql/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client_graph_account; 2 | pub mod client_graph_node; 3 | pub mod client_network; 4 | pub mod client_registry; 5 | 6 | #[derive(Debug, thiserror::Error)] 7 | pub enum QueryError { 8 | #[error(transparent)] 9 | Transport(#[from] reqwest::Error), 10 | #[error("The subgraph is in a failed state")] 11 | IndexingError, 12 | #[error("Query response is unexpected: {0}")] 13 | ParseResponseError(String), 14 | #[error("Query response is empty: {0}")] 15 | PrometheusError(#[from] prometheus_http_query::Error), 16 | #[error("Unknown error: {0}")] 17 | Other(anyhow::Error), 18 | } 19 | 20 | pub fn grt_gwei_string_to_f32(input: &str) -> Result { 21 | add_decimal(input) 22 | .parse::() 23 | .map_err(|e| QueryError::ParseResponseError(e.to_string())) 24 | } 25 | 26 | pub fn add_decimal(input: &str) -> String { 27 | if input.len() <= 18 { 28 | return format!("0.{}", input); 29 | } 30 | let (left, right) = input.split_at(input.len() - 18); 31 | format!("{}.{}", left, right) 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::*; 37 | 38 | #[test] 39 | fn test_add_decimal() { 40 | // valid inputs 41 | assert_eq!( 42 | add_decimal("100000000000000000000000"), 43 | "100000.000000000000000000", 44 | ); 45 | assert_eq!(add_decimal("0"), "0.0"); 46 | assert_eq!( 47 | add_decimal("30921273477321769415119223"), 48 | "30921273.477321769415119223", 49 | ); 50 | } 51 | 52 | #[test] 53 | fn test_grt_gwei_string_to_f32() { 54 | // valid inputs 55 | assert!(grt_gwei_string_to_f32("100000000000000000000000").is_ok()); 56 | assert_eq!( 57 | grt_gwei_string_to_f32("100000000000000000000000").unwrap(), 58 | 100000.0, 59 | ); 60 | assert!(grt_gwei_string_to_f32("0").is_ok()); 61 | assert!(grt_gwei_string_to_f32("30921273477321769415119223").is_ok()); 62 | assert_eq!( 63 | grt_gwei_string_to_f32("30921273477321769415119223").unwrap(), 64 | 30921273.477321769415119223, 65 | ); 66 | 67 | // invalid inputs 68 | assert!(grt_gwei_string_to_f32("abc").is_err(),); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/ping-pong/src/types.rs: -------------------------------------------------------------------------------- 1 | use async_graphql::SimpleObject; 2 | use ethers_contract::EthAbiType; 3 | use ethers_core::types::transaction::eip712::Eip712; 4 | use ethers_derive_eip712::*; 5 | 6 | use graphcast_sdk::graphcast_agent::message_typing::{ 7 | GraphcastMessage, MessageError, RadioPayload, 8 | }; 9 | use prost::Message; 10 | use serde::{Deserialize, Serialize}; 11 | 12 | // Import the OnceCell container for lazy initialization of global/static data 13 | use once_cell::sync::OnceCell; 14 | use std::sync::{Arc, Mutex}; 15 | 16 | /// A global static (singleton) instance of A GraphcastMessage vector. 17 | /// It is used to save incoming messages after they've been validated, in order 18 | /// defer their processing for later, because async code is required for the processing but 19 | /// it is not allowed in the handler itself. 20 | pub static MESSAGES: OnceCell>>> = OnceCell::new(); 21 | 22 | /// Make a test radio type 23 | #[derive(Eip712, EthAbiType, Clone, Message, Serialize, Deserialize, SimpleObject)] 24 | #[eip712( 25 | name = "SimpleMessage", 26 | version = "0", 27 | chain_id = 1, 28 | verifying_contract = "0xc944e90c64b2c07662a292be6244bdf05cda44a7" 29 | )] 30 | pub struct SimpleMessage { 31 | #[prost(string, tag = "1")] 32 | pub identifier: String, 33 | #[prost(string, tag = "2")] 34 | pub content: String, 35 | } 36 | 37 | impl RadioPayload for SimpleMessage { 38 | fn valid_outer(&self, outer: &GraphcastMessage) -> Result<&Self, MessageError> { 39 | if self.identifier == outer.identifier { 40 | Ok(self) 41 | } else { 42 | Err(MessageError::InvalidFields(anyhow::anyhow!( 43 | "Radio message wrapped by inconsistent GraphcastMessage: {:#?} <- {:#?}", 44 | &self, 45 | &outer, 46 | ))) 47 | } 48 | } 49 | } 50 | 51 | impl SimpleMessage { 52 | pub fn new(identifier: String, content: String) -> Self { 53 | SimpleMessage { 54 | identifier, 55 | content, 56 | } 57 | } 58 | 59 | pub fn radio_handler(&self) { 60 | MESSAGES 61 | .get() 62 | .expect("Could not retrieve messages") 63 | .lock() 64 | .expect("Could not get lock on messages") 65 | .push(self.clone()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/graphql/client_registry.rs: -------------------------------------------------------------------------------- 1 | use graphql_client::{GraphQLQuery, Response}; 2 | use serde_derive::{Deserialize, Serialize}; 3 | use tracing::{trace, warn}; 4 | 5 | use super::QueryError; 6 | 7 | /// Derived Indexer 8 | #[derive(GraphQLQuery, Serialize, Deserialize, Debug)] 9 | #[graphql( 10 | schema_path = "src/graphql/schema_registry.graphql", 11 | query_path = "src/graphql/query_registry.graphql", 12 | response_derives = "Debug, Serialize, Deserialize" 13 | )] 14 | pub struct SetGraphcastIds; 15 | 16 | /// Query registry subgraph endpoint for resolving Graphcast ID and indexer address 17 | pub async fn perform_graphcast_id_indexer_query( 18 | registry_subgraph_endpoint: &str, 19 | variables: set_graphcast_ids::Variables, 20 | ) -> Result { 21 | let request_body = SetGraphcastIds::build_query(variables); 22 | let client = reqwest::Client::new(); 23 | client 24 | .post(registry_subgraph_endpoint) 25 | .json(&request_body) 26 | .send() 27 | .await? 28 | .error_for_status() 29 | } 30 | 31 | /// Construct GraphQL variables and parse result for indexer address 32 | pub async fn query_registry( 33 | registry_subgraph_endpoint: &str, 34 | wallet_address: &str, 35 | ) -> Result { 36 | let variables: set_graphcast_ids::Variables = set_graphcast_ids::Variables { 37 | address: wallet_address.to_string(), 38 | }; 39 | let queried_result = 40 | perform_graphcast_id_indexer_query(registry_subgraph_endpoint, variables).await?; 41 | trace!( 42 | result = tracing::field::debug(&queried_result), 43 | "Query result for registry indexer" 44 | ); 45 | if !&queried_result.status().is_success() { 46 | warn!( 47 | result = tracing::field::debug(&queried_result), 48 | "Unsuccessful query" 49 | ); 50 | } 51 | let response_body: Response = queried_result.json().await?; 52 | if let Some(data) = response_body.data { 53 | data.graphcast_ids 54 | .first() 55 | .map(|event| event.indexer.clone()) 56 | .ok_or(QueryError::ParseResponseError(format!( 57 | "No indexer data queried from registry for GraphcastID: {wallet_address}" 58 | ))) 59 | } else { 60 | Err(QueryError::ParseResponseError(format!( 61 | "No response data from registry for GraphcastID: {wallet_address}" 62 | ))) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request_review: 5 | types: [submitted] 6 | 7 | name: CI 8 | 9 | jobs: 10 | check: 11 | name: Check 12 | if: github.event.review.state == 'approved' 13 | strategy: 14 | matrix: 15 | include: 16 | - os: ubuntu-latest 17 | toolchain: stable-x86_64-unknown-linux-gnu 18 | - os: macos-13 19 | toolchain: stable-x86_64-apple-darwin 20 | env: 21 | - RUSTFLAGS: -C link-args=-framework CoreFoundation -framework Security -framework CoreServices 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v3 25 | with: 26 | submodules: true 27 | - name: Checkout submodules 28 | run: git submodule update --init --recursive 29 | - uses: actions/setup-go@v4 # we need go to build go-waku 30 | with: 31 | go-version: '1.20' 32 | - uses: actions-rs/toolchain@v1 33 | with: 34 | profile: minimal 35 | toolchain: ${{ matrix.toolchain }} 36 | default: true 37 | override: true 38 | - uses: actions-rs/cargo@v1 39 | continue-on-error: false 40 | with: 41 | command: check 42 | 43 | test: 44 | name: Test Suite 45 | if: github.event.review.state == 'approved' 46 | strategy: 47 | matrix: 48 | include: 49 | - os: ubuntu-latest 50 | toolchain: stable-x86_64-unknown-linux-gnu 51 | - os: macos-13 52 | toolchain: stable-x86_64-apple-darwin 53 | env: 54 | - RUSTFLAGS: -C link-args=-framework CoreFoundation -framework Security -framework CoreServices 55 | runs-on: ${{ matrix.os }} 56 | steps: 57 | - uses: actions/checkout@v3 58 | with: 59 | submodules: true 60 | - name: Checkout submodules 61 | run: git submodule update --init --recursive 62 | - uses: actions/setup-go@v4 # we need go to build go-waku 63 | with: 64 | go-version: '1.20' 65 | 66 | - uses: actions-rs/toolchain@v1 67 | with: 68 | profile: minimal 69 | toolchain: ${{ matrix.toolchain }} 70 | default: true 71 | override: true 72 | - uses: actions-rs/cargo@v1 73 | continue-on-error: false 74 | with: 75 | command: build 76 | - uses: actions-rs/cargo@v1 77 | continue-on-error: false 78 | with: 79 | command: test 80 | 81 | lints: 82 | name: Rust lints 83 | runs-on: ubuntu-latest 84 | steps: 85 | - uses: actions/checkout@v3 86 | with: 87 | submodules: true 88 | - name: Checkout submodules 89 | run: git submodule update --init --recursive 90 | - uses: actions/setup-go@v4 # we need go to build go-waku 91 | with: 92 | go-version: '1.20' 93 | - uses: actions-rs/toolchain@v1 94 | with: 95 | profile: minimal 96 | toolchain: stable 97 | override: true 98 | components: rustfmt, clippy 99 | 100 | - name: Run cargo fmt 101 | uses: actions-rs/cargo@v1 102 | continue-on-error: false 103 | with: 104 | command: fmt 105 | args: --all -- --check 106 | 107 | - name: Run cargo clippy 108 | uses: actions-rs/cargo@v1 109 | continue-on-error: false 110 | with: 111 | command: clippy 112 | args: -- --deny warnings 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Graphcast SDK 2 | 3 | [![Docs](https://img.shields.io/badge/docs-latest-brightgreen.svg)](https://docs.graphops.xyz/graphcast/intro) 4 | [![crates.io](https://img.shields.io/crates/v/graphcast-sdk.svg)](https://crates.io/crates/graphcast-sdk) 5 | 6 | ## Introduction 7 | 8 | Graphcast is a decentralized, distributed peer-to-peer (P2P) communication tool that allows Indexers across the network to exchange information in real-time. Today, network participants coordinate with one another using the protocol by submitting on-chain transactions that update the shared global state in The Graph Network. These transactions cost gas, which makes some types of signaling or coordination between participants too expensive. In other words, sending signals to other network participants has a high cost that is determined by the cost of transacting on the Ethereum blockchain. Graphcast solves this problem. 9 | 10 | To see the full idea behind Graphcast, you can check out the [GRC](https://forum.thegraph.com/t/grc-001-graphcast-a-gossip-network-for-indexers/3544/8) for it. 11 | 12 | ## Upgrading 13 | 14 | Updates to the SDK will be merged into the `main` branch once their respective PR has been approved. 15 | 16 | ## Testing 17 | 18 | We recommend using [nextest](https://nexte.st/) as your test runner. Once you have it installed you can run the the suite using the following command: 19 | 20 | ``` 21 | cargo nextest run 22 | ``` 23 | 24 | Alternatively, to run graphcast-sdk tests using docker to avoid local dependency issues: 25 | ``` 26 | ./scripts/docker-run-tests.sh 27 | ``` 28 | 29 | 30 | ## How does the Graphcast SDK work? 31 | 32 | The SDK is essentially a base layer that Radio developers can use to build their applications without needing to worry about starting everything from scratch. The components that are included in the SDK are: 33 | 34 | - Connecting to the Graphcast network, e.g., a cluster of [Waku](https://waku.org/) nodes. It also provides an interface to subscribe to receive messages on specific topics and to broadcast messages onto the network. 35 | - Interactions with an Ethereum node. 36 | 37 | There is also a POI cross-checker Radio in the `examples/` folder that leverages the base layer and defines the specific logic around constructing and sending messages, as well as receiving and handling them. 38 | 39 | 40 | ## Graphcast Network Configurations 41 | 42 | A Graphcast radio can interact with many parts of The Graph network modularly; below are the network configurations actively supported by our team. You are free to define and use your own Graphcast Network and Graphcast Registry. 43 | 44 | | Graphcast Network | Supporting Network | Graph Network | Graphcast Registry | 45 | | ---------------- | ---------------- | --------------------------- | ----------------- | 46 | | Mainnet | Mainnet | https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet | https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-mainnet | 47 | | Testnet | Goerli | https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-goerli | https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-goerli | 48 | | Testnet | Arbitrum Goerli | https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-arbitrum-goerli | https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-arbitrum-go | 49 | 50 | 51 | ## Contributing 52 | 53 | We welcome and appreciate your contributions! Please see the [Contributor Guide](/CONTRIBUTING.md), [Code Of Conduct](/CODE_OF_CONDUCT.md) and [Security Notes](/SECURITY.md) for this repository. 54 | -------------------------------------------------------------------------------- /src/bots.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use teloxide::types::ParseMode; 3 | use thiserror::Error; 4 | 5 | use rsb_derive::Builder; 6 | use slack_morphism::prelude::*; 7 | 8 | use teloxide::prelude::*; 9 | use teloxide::types::ChatId; 10 | 11 | // DiscordBot 12 | #[derive(Debug, Clone, PartialEq, Eq)] 13 | pub struct DiscordBot {} 14 | 15 | #[derive(Error, Debug)] 16 | pub enum DiscordBotError { 17 | #[error("Request error: {0}")] 18 | RequestError(#[from] reqwest::Error), 19 | } 20 | 21 | impl DiscordBot { 22 | pub async fn send_webhook( 23 | webhook_url: &str, 24 | radio_name: &str, 25 | content: &str, 26 | ) -> Result<(), DiscordBotError> { 27 | let mut map = HashMap::new(); 28 | map.insert( 29 | "content", 30 | format!("🚨 Notification from Radio '{radio_name}' \n{content}"), 31 | ); 32 | 33 | let client = reqwest::Client::new(); 34 | 35 | client.post(webhook_url).json(&map).send().await?; 36 | 37 | Ok(()) 38 | } 39 | } 40 | 41 | // SlackBot 42 | #[derive(Debug, Clone, PartialEq, Eq)] 43 | pub struct SlackBot {} 44 | 45 | #[derive(Error, Debug)] 46 | pub enum SlackBotError { 47 | #[error("Request error: {0}")] 48 | RequestError(#[from] reqwest::Error), 49 | } 50 | 51 | impl SlackBot { 52 | pub async fn send_webhook( 53 | webhook_url: &str, 54 | radio_name: &str, 55 | content: &str, 56 | ) -> Result<(), SlackBotError> { 57 | let mut map = HashMap::new(); 58 | map.insert( 59 | "text", 60 | format!("🚨 Notification from Radio '{radio_name}' \n{content}"), 61 | ); 62 | 63 | let client = reqwest::Client::new(); 64 | 65 | client.post(webhook_url).json(&map).send().await?; 66 | 67 | Ok(()) 68 | } 69 | } 70 | 71 | #[derive(Debug, Clone)] 72 | pub struct TelegramBot { 73 | bot: Bot, 74 | } 75 | 76 | #[derive(Error, Debug)] 77 | pub enum TelegramBotError { 78 | #[error("Request error: {0}")] 79 | RequestError(#[from] teloxide::RequestError), 80 | } 81 | 82 | impl TelegramBot { 83 | pub fn new(bot_token: String) -> Self { 84 | Self { 85 | bot: Bot::new(bot_token), 86 | } 87 | } 88 | 89 | pub async fn send_message( 90 | &self, 91 | chat_id: i64, 92 | radio_name: &str, 93 | content: &str, 94 | ) -> Result<(), TelegramBotError> { 95 | let message = format!( 96 | "🚨 Notification from Radio '{radio_name}' \n{content}", 97 | radio_name = radio_name, 98 | content = content 99 | ); 100 | self.bot 101 | .send_message(ChatId(chat_id), message) 102 | .parse_mode(ParseMode::Html) // or ParseMode::MarkdownV2 103 | .disable_web_page_preview(true) 104 | .send() 105 | .await?; 106 | Ok(()) 107 | } 108 | } 109 | 110 | #[derive(Debug, Clone, Builder)] 111 | pub struct AlertMessageTemplateParams { 112 | pub user_id: Option, 113 | pub radio_name: String, 114 | pub content: String, 115 | } 116 | 117 | impl AlertMessageTemplateParams { 118 | pub fn create(radio_name: String, content: String) -> Self { 119 | Self { 120 | user_id: None, 121 | radio_name, 122 | content, 123 | } 124 | } 125 | } 126 | 127 | impl SlackMessageTemplate for AlertMessageTemplateParams { 128 | fn render_template(&self) -> SlackMessageContent { 129 | let user = match &self.user_id { 130 | Some(id) => format!( 131 | "🚨 Hello {}!, Notification from Radio '{}'", 132 | id.to_slack_format(), 133 | &self.radio_name 134 | ), 135 | None => format!("🚨 Notification from Radio '{}'", &self.radio_name), 136 | }; 137 | SlackMessageContent::new().with_blocks(slack_blocks![ 138 | some_into(SlackSectionBlock::new().with_text(pt!(user))), 139 | some_into(SlackDividerBlock::new()), 140 | some_into(SlackSectionBlock::new().with_text(pt!(&self.content))) 141 | ]) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/networks.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use std::fmt; 3 | 4 | /// Struct for Network and block interval for updates 5 | #[derive(Debug, Clone)] 6 | pub struct Network { 7 | pub name: NetworkName, 8 | pub interval: u64, 9 | } 10 | 11 | /// List of supported networks 12 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 13 | pub enum NetworkName { 14 | Goerli, 15 | Mainnet, 16 | Gnosis, 17 | Hardhat, 18 | ArbitrumOne, 19 | ArbitrumGoerli, 20 | ArbitrumSepolia, 21 | Avalanche, 22 | Matic, // Previously Polygon 23 | Celo, 24 | Optimism, 25 | Fantom, 26 | Base, 27 | Unknown, 28 | } 29 | 30 | impl NetworkName { 31 | pub fn from_string(name: &str) -> Self { 32 | match name { 33 | "goerli" => NetworkName::Goerli, 34 | "mainnet" => NetworkName::Mainnet, 35 | "gnosis" => NetworkName::Gnosis, 36 | "hardhat" => NetworkName::Hardhat, 37 | "arbitrum-one" => NetworkName::ArbitrumOne, 38 | "arbitrum-goerli" => NetworkName::ArbitrumGoerli, 39 | "arbitrum-sepolia" => NetworkName::ArbitrumSepolia, 40 | "avalanche" => NetworkName::Avalanche, 41 | "matic" => NetworkName::Matic, 42 | "celo" => NetworkName::Celo, 43 | "optimism" => NetworkName::Optimism, 44 | "base" => NetworkName::Base, 45 | "fantom" => NetworkName::Fantom, 46 | _ => NetworkName::Unknown, 47 | } 48 | } 49 | } 50 | 51 | impl fmt::Display for NetworkName { 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 53 | let name = match self { 54 | NetworkName::Goerli => "goerli", 55 | NetworkName::Mainnet => "mainnet", 56 | NetworkName::Gnosis => "gnosis", 57 | NetworkName::Hardhat => "hardhat", 58 | NetworkName::ArbitrumOne => "arbitrum-one", 59 | NetworkName::ArbitrumGoerli => "arbitrum-goerli", 60 | NetworkName::ArbitrumSepolia => "arbitrum-sepolia", 61 | NetworkName::Avalanche => "avalanche", 62 | NetworkName::Matic => "matic", 63 | NetworkName::Celo => "celo", 64 | NetworkName::Optimism => "optimism", 65 | NetworkName::Fantom => "fantom", 66 | NetworkName::Base => "base", 67 | NetworkName::Unknown => "unknown", 68 | }; 69 | 70 | write!(f, "{name}") 71 | } 72 | } 73 | 74 | /// Maintained static list of supported Networks, the intervals target ~5 minutes 75 | /// depending on the blockchain average block processing time 76 | pub static NETWORKS: Lazy> = Lazy::new(|| { 77 | vec![ 78 | // Goerli (Ethereum Testnet): ~15 seconds 79 | Network { 80 | name: NetworkName::from_string("goerli"), 81 | interval: 20, 82 | }, 83 | // Mainnet (Ethereum): ~10-12 seconds 84 | Network { 85 | name: NetworkName::from_string("mainnet"), 86 | interval: 30, 87 | }, 88 | // Gnosis: ~5 seconds 89 | Network { 90 | name: NetworkName::from_string("gnosis"), 91 | interval: 60, 92 | }, 93 | // Local test network 94 | Network { 95 | name: NetworkName::from_string("hardhat"), 96 | interval: 10, 97 | }, 98 | // ArbitrumOne: ~0.25-1 second 99 | Network { 100 | name: NetworkName::from_string("arbitrum-one"), 101 | interval: 600, 102 | }, 103 | // ArbitrumGoerli (Arbitrum Testnet): ~0.6 seconds 104 | Network { 105 | name: NetworkName::from_string("arbitrum-goerli"), 106 | interval: 500, 107 | }, 108 | // ArbitrumSepolia: ~0.258 seconds 109 | Network { 110 | name: NetworkName::from_string("arbitrum-sepolia"), 111 | interval: 1160, 112 | }, 113 | // Avalanche: ~3-5 seconds 114 | Network { 115 | name: NetworkName::from_string("avalanche"), 116 | interval: 60, 117 | }, 118 | // Matic: ~2 seconds 119 | Network { 120 | name: NetworkName::from_string("matic"), 121 | interval: 150, 122 | }, 123 | // Celo: ~5-10 seconds 124 | Network { 125 | name: NetworkName::from_string("celo"), 126 | interval: 30, 127 | }, 128 | // Optimism: ~10-15 seconds 129 | Network { 130 | name: NetworkName::from_string("optimism"), 131 | interval: 20, 132 | }, 133 | // Fantom: ~2-3 seconds 134 | Network { 135 | name: NetworkName::from_string("fantom"), 136 | interval: 100, 137 | }, 138 | // Base: ~2-3 seconds 139 | Network { 140 | name: NetworkName::from_string("base"), 141 | interval: 100, 142 | }, 143 | ] 144 | }); 145 | -------------------------------------------------------------------------------- /examples/ping-pong/src/config.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use ethers::signers::WalletError; 3 | use graphcast_sdk::build_wallet; 4 | use graphcast_sdk::graphcast_agent::message_typing::IdentityValidation; 5 | use graphcast_sdk::init_tracing; 6 | use graphcast_sdk::wallet_address; 7 | use graphcast_sdk::LogFormat; 8 | use serde::{Deserialize, Serialize}; 9 | use tracing::info; 10 | 11 | #[derive(Clone, Debug, Parser, Serialize, Deserialize)] 12 | #[clap( 13 | name = "ping-pong-radio", 14 | about = "A simple example for using the Graphcast SDK to build Radios", 15 | author = "GraphOps" 16 | )] 17 | pub struct Config { 18 | #[clap( 19 | long, 20 | value_name = "ENDPOINT", 21 | env = "GRAPH_NODE_STATUS_ENDPOINT", 22 | help = "API endpoint to the Graph Node Status Endpoint" 23 | )] 24 | pub graph_node_endpoint: Option, 25 | #[clap( 26 | long, 27 | value_name = "KEY", 28 | value_parser = Config::parse_key, 29 | env = "PRIVATE_KEY", 30 | hide_env_values = true, 31 | help = "Private key to the Graphcast ID wallet (Precendence over mnemonics)", 32 | )] 33 | pub private_key: Option, 34 | #[clap( 35 | long, 36 | value_name = "KEY", 37 | value_parser = Config::parse_key, 38 | env = "MNEMONIC", 39 | hide_env_values = true, 40 | help = "Mnemonic to the Graphcast ID wallet (first address of the wallet is used; Only one of private key or mnemonic is needed)", 41 | )] 42 | pub mnemonic: Option, 43 | #[clap( 44 | long, 45 | value_name = "SUBGRAPH", 46 | env = "REGISTRY_SUBGRAPH", 47 | help = "Subgraph endpoint to the Graphcast Registry", 48 | default_value = "https://api.thegraph.com/subgraphs/name/hopeyen/graphcast-registry-goerli" 49 | )] 50 | pub registry_subgraph: String, 51 | #[clap( 52 | long, 53 | value_name = "INDEXER_ADDRESS", 54 | env = "INDEXER_ADDRESS", 55 | help = "Graph account corresponding to Graphcast operator" 56 | )] 57 | pub indexer_address: String, 58 | #[clap( 59 | long, 60 | value_name = "SUBGRAPH", 61 | env = "NETWORK_SUBGRAPH", 62 | help = "Subgraph endpoint to The Graph network subgraph", 63 | default_value = "https://gateway.testnet.thegraph.com/network" 64 | )] 65 | pub network_subgraph: String, 66 | #[clap( 67 | long, 68 | value_name = "LOG_FORMAT", 69 | env = "LOG_FORMAT", 70 | help = "Support logging formats: pretty, json, full, compact", 71 | long_help = "pretty: verbose and human readable; json: not verbose and parsable; compact: not verbose and not parsable; full: verbose and not parsible", 72 | default_value = "pretty" 73 | )] 74 | pub log_format: LogFormat, 75 | #[clap( 76 | long, 77 | value_name = "ID_VALIDATION", 78 | value_enum, 79 | env = "ID_VALIDATION", 80 | default_value = "valid-address", 81 | help = "Identity validation mechanism for senders (message signers)", 82 | long_help = "Identity validation mechanism for senders (message signers)\n 83 | no-check: all messages signer is valid, \n 84 | valid-address: signer needs to be an valid Eth address, \n 85 | graphcast-registered: must be registered at Graphcast Registry, \n 86 | graph-network-account: must be a Graph account, \n 87 | registered-indexer: must be registered at Graphcast Registry, correspond to and Indexer statisfying indexer minimum stake requirement, \n 88 | indexer: must be registered at Graphcast Registry or is a Graph Account, correspond to and Indexer statisfying indexer minimum stake requirement" 89 | )] 90 | pub id_validation: IdentityValidation, 91 | #[clap( 92 | long, 93 | value_name = "WAKU_PORT", 94 | help = "Port for the Waku gossip client", 95 | env = "WAKU_PORT" 96 | )] 97 | pub waku_port: Option, 98 | #[clap( 99 | long, 100 | value_name = "NODE_ADDRESSES", 101 | help = "Comma separated static list of waku boot nodes to connect to", 102 | env = "BOOT_NODE_ADDRESSES" 103 | )] 104 | pub boot_node_addresses: Vec, 105 | #[clap( 106 | long, 107 | value_name = "DISCV5_PORT", 108 | help = "Waku node to expose discoverable udp port", 109 | env = "DISCV5_PORT" 110 | )] 111 | pub discv5_port: Option, 112 | } 113 | 114 | impl Config { 115 | /// Parse config arguments 116 | pub fn args() -> Self { 117 | // TODO: load config file before parse (maybe add new level of subcommands) 118 | let config = Config::parse(); 119 | init_tracing(config.log_format.to_string()).expect("Could not set up global default subscriber for logger, check environmental variable `RUST_LOG` or the CLI input `log-level`"); 120 | config 121 | } 122 | 123 | /// Validate that private key as an Eth wallet 124 | fn parse_key(value: &str) -> Result { 125 | // The wallet can be stored instead of the original private key 126 | let wallet = build_wallet(value)?; 127 | let addr = wallet_address(&wallet); 128 | info!(address = addr, "Resolved Graphcast id"); 129 | Ok(String::from(value)) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | conduct@graphops.xyz. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing to the Graphcast SDK 3 | 4 | Welcome to the Graphcast SDK! Thanks a ton for your interest in contributing. 5 | 6 | If you run into any problems feel free to create an issue. PRs are much appreciated for simple things. If it's something more complex we'd appreciate having a quick chat in GitHub Issues or the Graph Discord server. 7 | 8 | Join the conversation on [the Graph Discord](https://thegraph.com/discord). 9 | 10 | Please follow the [Code of Conduct](https://github.com/graphops/graphcast-sdk/blob/main/CODE_OF_CONDUCT.md) for all the communications and at events. Thank you! 11 | 12 | ## Commit messages and pull requests 13 | 14 | We follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). 15 | 16 | In brief, each commit message consists of a header, with optional body and footer: 17 | 18 | ``` 19 | [optional scope]: 20 | 21 | [optional body] 22 | 23 | [optional footer(s)] 24 | ``` 25 | 26 | `` must be one of the following: 27 | - feat: A new feature 28 | - fix: A bug fix 29 | - docs: Documentation only changes 30 | - style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 31 | - refactor: A code change that neither fixes a bug nor adds a feature 32 | - perf: A code change that improves performance 33 | - test: Adding missing tests 34 | - chore: Changes to the build process or auxiliary tools and libraries such as documentation generation 35 | - revert: If the commit reverts a previous commit, contains the header of the reverted commit. 36 | 37 | Make sure to include an exclamation mark after the commit type and scope if there is a breaking change. 38 | 39 | `` optional and could be anything that specifies the place of the commit change, e.g. solver, [filename], tests, lib, ... we are not very restrictive on the scope. The scope should just be lowercase and if possible contain of a single word. 40 | 41 | `` contains succinct description of the change with imperative, present tense. don't capitalize first letter, and no dot (.) at the end. 42 | 43 | `` include the motivation for the change, use the imperative, present tense 44 | 45 | `