feat: fetch token holders with etherscan api [skip-line-limit]#929
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
WalkthroughReplaced Bitquery-based token-holder provider with an Etherscan+RPC implementation, renamed config/env key, updated module exports and indexer usage, removed Bitquery module and tests, added Etherscan module with log retrieval, voter discovery, and voting-power verification; plus minor formatting/import reorderings. Changes
Sequence Diagram(s)sequenceDiagram
participant Indexer
participant EtherscanClient
participant EtherscanAPI as Etherscan API
participant RPC
rect rgb(220,235,255)
note over Indexer,EtherscanClient: New flow: Etherscan + RPC voting-power verification
end
Indexer->>EtherscanClient: get_token_holders_with_voting_power(token, snapshot_block, rpc_url, threshold)
EtherscanClient->>EtherscanAPI: get_deployment_block(token)
EtherscanAPI-->>EtherscanClient: deployment_block
EtherscanClient->>EtherscanAPI: get_transfer_logs(token, from, to) (paginated)
EtherscanAPI-->>EtherscanClient: transfer_logs
EtherscanClient->>EtherscanAPI: get_delegate_votes_changed_logs(token, from, to) (paginated)
EtherscanAPI-->>EtherscanClient: delegation_logs
EtherscanClient->>EtherscanClient: compute potential voters (from logs)
loop per potential voter
EtherscanClient->>RPC: getPastVotes(token, voter, block)
RPC-->>EtherscanClient: voting_power
end
rect rgb(220,245,220)
note over EtherscanClient: filter by threshold → TokenHolder list
end
EtherscanClient-->>Indexer: Vec<TokenHolder>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
739bb23 to
04543bd
Compare
04543bd to
c47a976
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
examples/CRISP/server/src/server/repo.rs (1)
202-205: Consider removing unused methods if not part of the public API.The pipeline flagged
set_ciphertext_outputandis_finishedas unused. If these methods are not part of an intentional public API or planned for future use, consider removing them to reduce maintenance burden.Also applies to: 244-247
examples/CRISP/server/src/server/indexer.rs (1)
96-96: Document or extract the magic number constant.The hardcoded value
9lacks context. Consider extracting it to a named constant with documentation explaining its purpose (e.g.,DECIMALS,MAX_RETRIES,PAGE_SIZE, etc.).Apply this diff to improve clarity:
+/// Number of decimal places for token balance precision +const TOKEN_DECIMALS: u8 = 9; + pub async fn register_e3_requested( mut indexer: EnclaveIndexer<impl DataStore>, ) -> Result<EnclaveIndexer<impl DataStore>> { // ... existing code ... etherscan_client .get_token_holders_with_voting_power( token_address, - 9, + TOKEN_DECIMALS, &CONFIG.http_rpc_url,
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
examples/CRISP/server/.env.example(1 hunks)examples/CRISP/server/src/config.rs(1 hunks)examples/CRISP/server/src/lib.rs(1 hunks)examples/CRISP/server/src/server/indexer.rs(4 hunks)examples/CRISP/server/src/server/program_server_request.rs(1 hunks)examples/CRISP/server/src/server/repo.rs(2 hunks)examples/CRISP/server/src/server/routes/voting.rs(1 hunks)examples/CRISP/server/src/server/token_holders/bitquery.rs(0 hunks)examples/CRISP/server/src/server/token_holders/etherscan.rs(1 hunks)examples/CRISP/server/src/server/token_holders/hashes.rs(1 hunks)examples/CRISP/server/src/server/token_holders/mod.rs(1 hunks)
💤 Files with no reviewable changes (1)
- examples/CRISP/server/src/server/token_holders/bitquery.rs
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2024-09-26T03:11:29.311Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 107
File: packages/ciphernode/sortition/src/distance.rs:1-1
Timestamp: 2024-09-26T03:11:29.311Z
Learning: In `packages/ciphernode/core/src/events.rs`, the import statements use the correct and updated `alloy::primitives` module.
Applied to files:
examples/CRISP/server/src/server/token_holders/mod.rsexamples/CRISP/server/src/server/token_holders/hashes.rsexamples/CRISP/server/src/lib.rsexamples/CRISP/server/src/server/routes/voting.rsexamples/CRISP/server/src/server/indexer.rs
📚 Learning: 2025-08-25T10:28:56.174Z
Learnt from: ctrlc03
Repo: gnosisguild/enclave PR: 657
File: Cargo.toml:32-34
Timestamp: 2025-08-25T10:28:56.174Z
Learning: The examples/CRISP directory has its own Cargo.toml workspace configuration with members like "server", "wasm-crypto", "program/core", "program/client", etc. The root workspace intentionally excludes "examples/CRISP/server", "examples/CRISP/program", and "examples/CRISP/wasm-crypto" to prevent double workspace membership, which is the correct approach for self-contained example workspaces.
Applied to files:
examples/CRISP/server/src/lib.rs
📚 Learning: 2024-10-29T01:03:50.414Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 156
File: packages/ciphernode/config/src/app_config.rs:21-26
Timestamp: 2024-10-29T01:03:50.414Z
Learning: In `packages/ciphernode/config/src/app_config.rs`, the `rpc_url` field in the `ChainConfig` struct is not considered sensitive and does not need to be encrypted.
Applied to files:
examples/CRISP/server/src/config.rs
📚 Learning: 2024-11-05T06:48:58.177Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 173
File: packages/ciphernode/config/src/app_config.rs:13-21
Timestamp: 2024-11-05T06:48:58.177Z
Learning: In the `packages/ciphernode/config/src/app_config.rs` file, for the `Contract` enum, the team prefers to use `String` type for `address` fields, relying on parsing to handle validation, instead of using the `Address` type.
Applied to files:
examples/CRISP/server/src/config.rsexamples/CRISP/server/src/server/routes/voting.rsexamples/CRISP/server/src/server/indexer.rs
📚 Learning: 2024-10-22T03:42:14.057Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 145
File: packages/ciphernode/router/src/context.rs:94-97
Timestamp: 2024-10-22T03:42:14.057Z
Learning: In `packages/ciphernode/router/src/context.rs`, avoid adding complexity for batching checkpoint operations in code; rely on the database's batching capabilities instead.
Applied to files:
examples/CRISP/server/src/server/routes/voting.rs
📚 Learning: 2024-10-16T09:51:10.038Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 145
File: packages/ciphernode/router/src/committee_meta.rs:43-43
Timestamp: 2024-10-16T09:51:10.038Z
Learning: In `packages/ciphernode/router/src/committee_meta.rs`, avoid suggesting making the `on_event` method in `CommitteMetaFeature` asynchronous or adding error handling for the `write` operation to the data store.
Applied to files:
examples/CRISP/server/src/server/routes/voting.rs
🧬 Code graph analysis (1)
examples/CRISP/server/src/server/indexer.rs (1)
examples/CRISP/server/src/server/token_holders/etherscan.rs (2)
get_mock_token_holders(526-569)new(95-101)
🪛 dotenv-linter (4.0.0)
examples/CRISP/server/.env.example
[warning] 10-10: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
🪛 GitHub Actions: ci
examples/CRISP/server/src/server/repo.rs
[warning] 202-202: methods set_ciphertext_output and is_finished are never used
examples/CRISP/server/src/server/token_holders/etherscan.rs
[warning] 7-7: unused import: crate::server::CONFIG
[warning] 309-309: associated function extract_addresses is never used
🪛 Gitleaks (8.28.0)
examples/CRISP/server/src/server/token_holders/etherscan.rs
[high] 782-782: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 795-795: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 810-810: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 827-827: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🔇 Additional comments (7)
examples/CRISP/server/src/server/repo.rs (2)
94-98: LGTM! Formatting improvement.The multi-line parameter formatting with trailing comma is idiomatic Rust style and improves readability.
261-261: LGTM! Minor whitespace cleanup.The removal of the extra blank line improves consistency.
examples/CRISP/server/src/server/token_holders/hashes.rs (1)
14-14: LGTM: Import path correctly updated.The import path change aligns with the module reorganization from bitquery to etherscan.
examples/CRISP/server/src/lib.rs (1)
7-9: LGTM: Module reordering is acceptable.The
pub mod configdeclaration has been moved but remains public with no functional changes.examples/CRISP/server/src/server/token_holders/mod.rs (1)
7-11: LGTM: Module reorganization correctly implemented.The module exports have been properly updated from bitquery to etherscan, aligning with the PR objectives to replace the Bitquery integration.
examples/CRISP/server/src/server/indexer.rs (2)
7-7: LGTM: Imports updated for Etherscan integration.The import changes correctly reflect the migration from BitqueryClient to EtherscanClient and the addition of U256 for balance threshold handling.
Also applies to: 17-17
78-78: Verify Sepolia testnet (chain ID 11155111) removal from mock data condition.The chain ID check now excludes Sepolia (11155111), meaning it will use the real Etherscan API instead of mocked data. Please confirm:
- This change is intentional
- The Etherscan API key has appropriate access for Sepolia testnet
- The API rate limits are sufficient for your testing needs on Sepolia
76fe2a6 to
5c58f30
Compare
6a9f48c to
02b6073
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
examples/CRISP/server/.env.example (1)
10-10: Consider removing quotes for consistency with dotenv conventions.The empty string value typically doesn't require quotes in
.envfiles. While this works correctly, removing them would align with common dotenv conventions.Apply this diff:
-ETHERSCAN_API_KEY="" +ETHERSCAN_API_KEY=
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
examples/CRISP/Readme.md(2 hunks)examples/CRISP/server/.env.example(1 hunks)examples/CRISP/server/src/config.rs(1 hunks)examples/CRISP/server/src/lib.rs(1 hunks)examples/CRISP/server/src/server/indexer.rs(4 hunks)examples/CRISP/server/src/server/program_server_request.rs(1 hunks)examples/CRISP/server/src/server/repo.rs(2 hunks)examples/CRISP/server/src/server/routes/voting.rs(1 hunks)examples/CRISP/server/src/server/token_holders/bitquery.rs(0 hunks)examples/CRISP/server/src/server/token_holders/etherscan.rs(1 hunks)examples/CRISP/server/src/server/token_holders/hashes.rs(1 hunks)examples/CRISP/server/src/server/token_holders/mod.rs(1 hunks)
💤 Files with no reviewable changes (1)
- examples/CRISP/server/src/server/token_holders/bitquery.rs
🚧 Files skipped from review as they are similar to previous changes (6)
- examples/CRISP/server/src/server/repo.rs
- examples/CRISP/Readme.md
- examples/CRISP/server/src/server/routes/voting.rs
- examples/CRISP/server/src/server/token_holders/hashes.rs
- examples/CRISP/server/src/lib.rs
- examples/CRISP/server/src/config.rs
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2024-09-26T03:11:29.311Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 107
File: packages/ciphernode/sortition/src/distance.rs:1-1
Timestamp: 2024-09-26T03:11:29.311Z
Learning: In `packages/ciphernode/core/src/events.rs`, the import statements use the correct and updated `alloy::primitives` module.
Applied to files:
examples/CRISP/server/src/server/token_holders/mod.rsexamples/CRISP/server/src/server/indexer.rs
📚 Learning: 2024-11-05T06:48:58.177Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 173
File: packages/ciphernode/config/src/app_config.rs:13-21
Timestamp: 2024-11-05T06:48:58.177Z
Learning: In the `packages/ciphernode/config/src/app_config.rs` file, for the `Contract` enum, the team prefers to use `String` type for `address` fields, relying on parsing to handle validation, instead of using the `Address` type.
Applied to files:
examples/CRISP/server/src/server/indexer.rs
📚 Learning: 2024-10-10T23:24:43.341Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 143
File: packages/ciphernode/sortition/src/sortition.rs:4-9
Timestamp: 2024-10-10T23:24:43.341Z
Learning: In the `Sortition` module (`packages/ciphernode/sortition/src/sortition.rs`), errors are sent to the event bus using `self.bus.err`, which handles logging and printing. Therefore, explicit use of the `tracing` crate for logging errors may not be necessary in this context.
Applied to files:
examples/CRISP/server/src/server/token_holders/etherscan.rs
📚 Learning: 2024-10-28T12:00:09.010Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 156
File: packages/ciphernode/enclave/src/commands/wallet/set.rs:17-23
Timestamp: 2024-10-28T12:00:09.010Z
Learning: In the `enclave` package of the `ciphernode` project, prefer using `println!` over logging macros like `error!` from the `tracing` crate for error output in CLI commands.
Applied to files:
examples/CRISP/server/src/server/token_holders/etherscan.rs
📚 Learning: 2024-10-28T10:45:29.100Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 156
File: packages/ciphernode/cipher/src/cipher.rs:164-164
Timestamp: 2024-10-28T10:45:29.100Z
Learning: In test code, it's acceptable to use `.unwrap()` instead of propagating errors with the `?` operator in Rust.
Applied to files:
examples/CRISP/server/src/server/token_holders/etherscan.rs
📚 Learning: 2024-10-16T09:52:53.807Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 145
File: packages/ciphernode/data/src/data_store.rs:74-75
Timestamp: 2024-10-16T09:52:53.807Z
Learning: In this project, the actor model handles potential errors internally, so methods like `checkpoint` in the `Checkpoint` trait do not need to explicitly handle or propagate errors.
Applied to files:
examples/CRISP/server/src/server/token_holders/etherscan.rs
📚 Learning: 2024-11-05T06:49:46.285Z
Learnt from: ryardley
Repo: gnosisguild/enclave PR: 173
File: packages/ciphernode/enclave_node/src/datastore.rs:14-16
Timestamp: 2024-11-05T06:49:46.285Z
Learning: In `packages/ciphernode/enclave_node/src/datastore.rs`, for internal functions like `get_in_mem_store`, adding extensive documentation and error handling may not be necessary if they are not client-facing.
Applied to files:
examples/CRISP/server/src/server/token_holders/etherscan.rs
🧬 Code graph analysis (1)
examples/CRISP/server/src/server/indexer.rs (1)
examples/CRISP/server/src/server/token_holders/etherscan.rs (2)
get_mock_token_holders(553-596)new(95-101)
🪛 dotenv-linter (4.0.0)
examples/CRISP/server/.env.example
[warning] 10-10: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
🪛 Gitleaks (8.28.0)
examples/CRISP/server/src/server/token_holders/etherscan.rs
[high] 807-807: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 820-820: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 835-835: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 852-852: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: integration_prebuild
- GitHub Check: test_contracts
- GitHub Check: build_sdk
- GitHub Check: build_e3_support_dev
- GitHub Check: build_enclave_cli
- GitHub Check: rust_unit
- GitHub Check: test_net
- GitHub Check: rust_integration
🔇 Additional comments (16)
examples/CRISP/server/src/server/program_server_request.rs (1)
30-30: LGTM!The function signature reformatting improves readability without changing functionality.
examples/CRISP/server/src/server/token_holders/mod.rs (1)
7-11: LGTM!The module swap from
bitquerytoetherscancleanly reflects the PR's migration objective.examples/CRISP/server/src/server/indexer.rs (3)
7-7: LGTM!Import updates correctly reflect the migration from Bitquery to Etherscan, and the addition of
U256supports the threshold conversion logic.Also applies to: 17-17
78-78: LGTM!Removing Sepolia (11155111) from the mock data condition is appropriate, as it's a real testnet where the Etherscan API should be used.
91-108: LGTM! Critical issue from previous review resolved.The error handling has been properly updated to propagate parse failures instead of silently defaulting to
U256::ZERO. The new implementation:
- Uses
map_errto convert parse errors into descriptive error messages- Prevents bypassing the balance threshold requirement on parse failures
- Aligns with the Etherscan-based data provider
examples/CRISP/server/src/server/token_holders/etherscan.rs (11)
1-91: LGTM!The imports, type definitions, and contract interface are well-structured. The use of
eyrefor error handling aligns with the previous review feedback.
93-101: LGTM!The constructor is straightforward and correctly initializes the client with the necessary parameters.
103-142: LGTM!The deployment block fetching logic properly handles both hex and decimal formats with appropriate error context.
144-208: LGTM! Previous error handling concern addressed.The error handling now properly distinguishes between "No records found" (which short-circuits pagination) and other failures (which propagate errors). The pagination logic and rate limiting are well-implemented.
210-275: LGTM!The delegation log fetching follows the same robust error handling pattern as transfer logs, properly distinguishing between "No records found" and actual errors.
277-316: LGTM!The potential voter identification logic correctly combines token holders and delegates while properly filtering out zero addresses.
318-349: LGTM! Threshold comparison correctly uses>=.The voting power verification properly uses
>=for the threshold comparison, ensuring addresses with votes equal to the threshold are included as documented. The error handling gracefully logs failures without stopping the entire operation.
351-483: LGTM!The helper methods are well-implemented with appropriate error handling. Notable good practices:
- Using saturating arithmetic for balance updates to prevent overflow/underflow
- Sorting logs by block number before processing for correct chronological order
- Proper filtering of zero addresses throughout
484-550: LGTM!The orchestrator method is well-structured with clear step-by-step flow and comprehensive logging for observability. Error handling with context makes debugging easier.
552-596: LGTM!The mock data function provides useful test data without requiring real API calls, making local development easier.
598-868: LGTM!The test suite provides good coverage:
- Unit tests validate helper functions (parsing, extraction, balance computation)
- Integration tests are properly marked with
#[ignore]to prevent accidental runs without API keys- The static analysis warnings about API key exposure are false positives—the tests use
CONFIGwhich loads from environment variables
fix #909
Summary by CodeRabbit
New Features
Refactor
Documentation
Tests