From 64ba366402af11104fa68115bce521320340e5ff Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 9 Jul 2026 14:55:57 +0200 Subject: [PATCH 01/10] chore: migrate rust/evm_block_explorer to icp-cli MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete dfx.json, BUILD.md, .devcontainer/ (dfx artifacts) - Rewrite icp.yaml: rust@v3.3.0, asset-canister@v2.2.1, evm_rpc pre-built at v2.8.0; add environments block so production sets PUBLIC_CANISTER_ID:evm_rpc to the shared mainnet canister - Update backend/Cargo.toml: edition 2024, ic-cdk 0.20, replace evm-rpc-canister-types + ic0 with evm_rpc_client 0.4.0, evm_rpc_types 3.1.1, ic-canister-runtime 0.2.0, and ic-cdk-management-canister 0.1.1 for ECDSA/Schnorr APIs - Move backend/lib.rs → backend/src/lib.rs; update to new API: EvmRpcClient builder pattern for get_evm_block, ic_cdk::api::env_var_value for runtime canister ID resolution, ic-cdk-management-canister types for ECDSA/Schnorr (EcdsaPublicKeyArgs, SignWithEcdsaArgs, etc.) - Rewrite frontend/vite.config.js: icp-cli only (no dfx fallback), uses icp network status for proxy target and root key - Rewrite frontend/src/actor.js: icp-cli only via safeGetCanisterEnv() - Add test.sh covering ECDSA/Schnorr public key and signing endpoints - Add .github/workflows/evm_block_explorer.yml CI workflow Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/evm_block_explorer.yml | 30 ++++ .../.devcontainer/devcontainer.json | 20 --- rust/evm_block_explorer/BUILD.md | 26 --- rust/evm_block_explorer/README.md | 33 ++-- rust/evm_block_explorer/backend/Cargo.toml | 13 +- rust/evm_block_explorer/backend/lib.rs | 150 ------------------ rust/evm_block_explorer/backend/src/lib.rs | 139 ++++++++++++++++ rust/evm_block_explorer/dfx.json | 41 ----- rust/evm_block_explorer/frontend/src/actor.js | 8 +- .../frontend/vite.config.js | 118 +++++--------- rust/evm_block_explorer/icp.yaml | 18 ++- rust/evm_block_explorer/test.sh | 22 +++ 12 files changed, 277 insertions(+), 341 deletions(-) create mode 100644 .github/workflows/evm_block_explorer.yml delete mode 100644 rust/evm_block_explorer/.devcontainer/devcontainer.json delete mode 100644 rust/evm_block_explorer/BUILD.md delete mode 100644 rust/evm_block_explorer/backend/lib.rs create mode 100644 rust/evm_block_explorer/backend/src/lib.rs delete mode 100644 rust/evm_block_explorer/dfx.json create mode 100755 rust/evm_block_explorer/test.sh diff --git a/.github/workflows/evm_block_explorer.yml b/.github/workflows/evm_block_explorer.yml new file mode 100644 index 0000000000..968e762150 --- /dev/null +++ b/.github/workflows/evm_block_explorer.yml @@ -0,0 +1,30 @@ +name: evm_block_explorer + +on: + push: + branches: [master] + pull_request: + paths: + - rust/evm_block_explorer/** + - .github/workflows/evm_block_explorer.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + rust-evm_block_explorer: + runs-on: ubuntu-24.04 + container: ghcr.io/dfinity/icp-dev-env-rust:1.0.1 + env: + ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Install Node.js dependencies + run: npm install --prefix rust/evm_block_explorer/frontend + - name: Deploy and test + working-directory: rust/evm_block_explorer + run: | + icp network start -d + icp deploy + bash test.sh diff --git a/rust/evm_block_explorer/.devcontainer/devcontainer.json b/rust/evm_block_explorer/.devcontainer/devcontainer.json deleted file mode 100644 index ebb0b8bcc6..0000000000 --- a/rust/evm_block_explorer/.devcontainer/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "ICP Dev Environment", - "image": "ghcr.io/dfinity/icp-dev-env-slim:22", - "forwardPorts": [4943, 5173], - "portsAttributes": { - "4943": { - "label": "dfx", - "onAutoForward": "ignore" - }, - "5173": { - "label": "vite", - "onAutoForward": "openBrowser" - } - }, - "customizations": { - "vscode": { - "extensions": ["dfinity-foundation.vscode-motoko"] - } - } -} diff --git a/rust/evm_block_explorer/BUILD.md b/rust/evm_block_explorer/BUILD.md deleted file mode 100644 index ffb6557ec1..0000000000 --- a/rust/evm_block_explorer/BUILD.md +++ /dev/null @@ -1,26 +0,0 @@ -# Continue building locally - -Projects deployed through ICP Ninja are temporary; they will only be live for 30 minutes before they are removed. To continue building locally, follow these steps. - -### 1. Install developer tools - -Install [Node.js](https://nodejs.org/en/download/) and [icp-cli](https://cli.icp.build): - -```bash -npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm -``` - -Then navigate into your project's directory that you downloaded from ICP Ninja. - -### 2. Deploy locally - -Start the local network and deploy the project: - -```bash -icp network start -d -icp deploy -``` - -## Additional examples - -Additional code examples and sample applications can be found in the [DFINITY examples repo](https://github.com/dfinity/examples). diff --git a/rust/evm_block_explorer/README.md b/rust/evm_block_explorer/README.md index c619068365..ecdd3f705d 100644 --- a/rust/evm_block_explorer/README.md +++ b/rust/evm_block_explorer/README.md @@ -7,20 +7,21 @@ The EVM Block Explorer example demonstrates how an ICP smart contract can obtain information directly from other blockchain networks. Using HTTPS outcalls, smart contracts on ICP can interact with other networks without needing to go through a third-party service such as a bridge or an oracle. Supported interactions with other chains include querying network data, signing transactions, and submitting transactions directly to other networks. In this example, you'll also see how to sign transactions with canister ECDSA or Schnorr signatures. + ## Build and deploy from the command line ### Prerequisites -- [x] Install [Node.js](https://nodejs.org/en/download/) -- [x] Install [icp-cli](https://cli.icp.build): `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` +- [Node.js](https://nodejs.org/) v18+ +- [icp-cli](https://cli.internetcomputer.org/): `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` +- [Rust](https://www.rust-lang.org/tools/install) with `wasm32-unknown-unknown` target: `rustup target add wasm32-unknown-unknown` ### Install @@ -31,24 +32,29 @@ git clone https://github.com/dfinity/examples cd examples/rust/evm_block_explorer ``` -### Deployment +### Deploy and test -Start the local network: +Start the local network, deploy all canisters (including the local EVM RPC canister), and run the tests: ```bash icp network start -d +icp deploy +bash test.sh +icp network stop ``` -Deploy the canisters: +To start a frontend dev server with hot reload during frontend development: ```bash -icp deploy +npm run dev --prefix frontend ``` -Stop the local network when done: +### Deploying on ICP mainnet + +On mainnet, the `evm_rpc` canister is already deployed at `7hfb6-caaaa-aaaar-qadga-cai`. Deploy only the backend and frontend — icp-cli injects the correct canister ID via the `production` environment configuration in `icp.yaml`: ```bash -icp network stop +icp deploy backend frontend -e production ``` ## Updating the Candid interface @@ -58,10 +64,9 @@ The `backend/backend.did` file defines the backend canister's public interface. If you modify the backend's public API, rebuild the canister and regenerate the `.did` file: ```bash -icp build backend -candid-extractor target/wasm32-unknown-unknown/release/backend.wasm > backend/backend.did +icp build backend && candid-extractor target/wasm32-unknown-unknown/release/backend.wasm > backend/backend.did ``` ## Security considerations and best practices -If you base your application on this example, it is recommended that you familiarize yourself with and adhere to the [security best practices](https://docs.internetcomputer.org/building-apps/security/overview) for developing on ICP. This example may not implement all the best practices. +If you base your application on this example, it is recommended that you familiarize yourself with and adhere to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for developing on ICP. This example may not implement all the best practices. diff --git a/rust/evm_block_explorer/backend/Cargo.toml b/rust/evm_block_explorer/backend/Cargo.toml index cda2e2d47f..45a6bd8734 100644 --- a/rust/evm_block_explorer/backend/Cargo.toml +++ b/rust/evm_block_explorer/backend/Cargo.toml @@ -1,18 +1,17 @@ [package] name = "backend" version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +edition = "2024" [lib] crate-type = ["cdylib"] -path = "lib.rs" [dependencies] candid = "0.10.23" -evm-rpc-canister-types = "5.0.1" +evm_rpc_client = "0.4.0" +evm_rpc_types = "3.1.1" hex = "0.4.3" -ic-cdk = "0.17.2" -ic0 = "1.0.1" +ic-cdk = "0.20" +ic-cdk-management-canister = "0.1.1" +ic-canister-runtime = "0.2.0" sha2 = "0.10.8" diff --git a/rust/evm_block_explorer/backend/lib.rs b/rust/evm_block_explorer/backend/lib.rs deleted file mode 100644 index 070be49c4f..0000000000 --- a/rust/evm_block_explorer/backend/lib.rs +++ /dev/null @@ -1,150 +0,0 @@ -use candid::{Nat, Principal}; -use evm_rpc_canister_types::{ - Block, EthMainnetService, EvmRpcCanister, GetBlockByNumberResult, - MultiGetBlockByNumberResult, RpcServices, CANISTER_ID, -}; -use ic_cdk::api::management_canister::{ - ecdsa::{ - ecdsa_public_key, sign_with_ecdsa, EcdsaCurve, EcdsaKeyId, EcdsaPublicKeyArgument, - SignWithEcdsaArgument, - }, - schnorr::{ - schnorr_public_key, sign_with_schnorr, SchnorrAlgorithm, SchnorrKeyId, - SchnorrPublicKeyArgument, SignWithSchnorrArgument, - }, -}; - -const KEY_NAME: &str = "test_key_1"; // Use "key_1" for production - -/// Resolve the EVM RPC canister ID at runtime. icp-cli sets the -/// `PUBLIC_CANISTER_ID:evm_rpc` env var; fall back to the well-known mainnet ID. -fn evm_rpc() -> EvmRpcCanister { - let name = "PUBLIC_CANISTER_ID:evm_rpc"; - let id = if ic0::env_var_name_exists(name) != 0 { - let mut buf = vec![0u8; ic0::env_var_value_size(name)]; - ic0::env_var_value_copy(name, &mut buf, 0); - let val = String::from_utf8(buf).expect("env var is not valid UTF-8"); - Principal::from_text(&val).expect("Invalid evm_rpc canister ID") - } else { - CANISTER_ID - }; - EvmRpcCanister(id) -} - -#[ic_cdk::update] -async fn get_evm_block(height: u128) -> Block { - // Ethereum Mainnet RPC providers - let rpc_providers = RpcServices::EthMainnet(Some(vec![ - EthMainnetService::Llama, - // EthMainnetService::Alchemy, - // EthMainnetService::Cloudflare, - ])); - - // Base Mainnet RPC providers - // Get chain ID and RPC providers from https://chainlist.org/ - // let rpc_providers = RpcServices::Custom { - // chainId: 8453, - // services: vec![ - // evm_rpc_canister_types::RpcApi { - // url: "https://base.llamarpc.com".to_string(), - // headers: None, - // }, - // evm_rpc_canister_types::RpcApi { - // url: "https://base-rpc.publicnode.com".to_string(), - // headers: None, - // }, - // ], - // }; - - // Call `eth_get_block_by_number` RPC method (unused cycles will be refunded) - let cycles = 10_000_000_000; - let (result,) = evm_rpc() - .eth_get_block_by_number( - rpc_providers, - None, - evm_rpc_canister_types::BlockTag::Number(Nat::from(height)), - cycles, - ) - .await - .expect("Failed to call RPC canister"); - - match result { - MultiGetBlockByNumberResult::Consistent(r) => match r { - GetBlockByNumberResult::Ok(block) => block, - GetBlockByNumberResult::Err(err) => panic!("{err:?}"), - }, - MultiGetBlockByNumberResult::Inconsistent(v) => { - panic!("RPC providers gave inconsistent results: {:?}", v) - } - } -} - -fn sha256(input: &String) -> [u8; 32] { - use sha2::Digest; - let mut hasher = sha2::Sha256::new(); - hasher.update(input.as_bytes()); - hasher.finalize().into() -} - -#[ic_cdk::update] -async fn get_ecdsa_public_key() -> String { - let (pub_key,) = ecdsa_public_key(EcdsaPublicKeyArgument { - key_id: EcdsaKeyId { - curve: EcdsaCurve::Secp256k1, - name: KEY_NAME.to_string(), - }, - ..Default::default() - }) - .await - .expect("Failed to get ecdsa public key"); - hex::encode(pub_key.public_key) -} - -#[ic_cdk::update] -async fn get_schnorr_public_key() -> String { - let (pub_key,) = schnorr_public_key(SchnorrPublicKeyArgument { - key_id: SchnorrKeyId { - algorithm: SchnorrAlgorithm::Ed25519, - name: KEY_NAME.to_string(), - }, - ..Default::default() - }) - .await - .expect("Failed to get schnorr public key"); - hex::encode(pub_key.public_key) -} - -#[ic_cdk::update] -async fn sign_message_with_ecdsa(message: String) -> String { - let message_hash = sha256(&message); - let (signature,) = sign_with_ecdsa(SignWithEcdsaArgument { - message_hash: message_hash.to_vec(), - key_id: EcdsaKeyId { - curve: EcdsaCurve::Secp256k1, - name: KEY_NAME.to_string(), - }, - ..Default::default() - }) - .await - .expect("Failed to sign"); - hex::encode(signature.signature) -} - -#[ic_cdk::update] -async fn sign_message_with_schnorr(message: String) -> String { - let message = sha256(&message); - let (signature,) = sign_with_schnorr(SignWithSchnorrArgument { - message: message.to_vec(), - key_id: SchnorrKeyId { - algorithm: SchnorrAlgorithm::Ed25519, - name: KEY_NAME.to_string(), - }, - ..Default::default() - }) - .await - .expect("Failed to sign"); - hex::encode(signature.signature) -} - -// Export the interface for the smart contract. -ic_cdk::export_candid!(); diff --git a/rust/evm_block_explorer/backend/src/lib.rs b/rust/evm_block_explorer/backend/src/lib.rs new file mode 100644 index 0000000000..109f6abf89 --- /dev/null +++ b/rust/evm_block_explorer/backend/src/lib.rs @@ -0,0 +1,139 @@ +use candid::Principal; +use evm_rpc_types::{ + Block, BlockTag, EthMainnetService, MultiRpcResult, Nat256, RpcServices, +}; +use ic_cdk_management_canister::{ + ecdsa_public_key, schnorr_public_key, sign_with_ecdsa, sign_with_schnorr, EcdsaCurve, + EcdsaKeyId, EcdsaPublicKeyArgs, SchnorrAlgorithm, SchnorrKeyId, SchnorrPublicKeyArgs, + SignWithEcdsaArgs, SignWithSchnorrArgs, +}; + +const KEY_NAME: &str = "test_key_1"; + +/// Resolve the EVM RPC canister ID at runtime. icp-cli sets the +/// `PUBLIC_CANISTER_ID:evm_rpc` env var for local deployments; on production +/// it is explicitly configured in icp.yaml to `7hfb6-caaaa-aaaar-qadga-cai`. +fn evm_rpc_id() -> Principal { + let id = ic_cdk::api::env_var_value("PUBLIC_CANISTER_ID:evm_rpc"); + Principal::from_text(&id).expect("Invalid PUBLIC_CANISTER_ID:evm_rpc") +} + +#[ic_cdk::update] +async fn get_evm_block(height: u128) -> Block { + // Ethereum Mainnet RPC providers + let rpc_services = RpcServices::EthMainnet(Some(vec![ + EthMainnetService::Llama, + // EthMainnetService::Alchemy, + // EthMainnetService::Cloudflare, + ])); + + // Base Mainnet RPC providers + // Get chain ID and RPC providers from https://chainlist.org/ + // let rpc_services = RpcServices::Custom { + // chain_id: 8453, + // services: vec![ + // evm_rpc_types::RpcApi { + // url: "https://base.llamarpc.com".to_string(), + // headers: None, + // }, + // evm_rpc_types::RpcApi { + // url: "https://base-rpc.publicnode.com".to_string(), + // headers: None, + // }, + // ], + // }; + + let client = evm_rpc_client::EvmRpcClient::builder( + ic_canister_runtime::IcRuntime::new(), + evm_rpc_id(), + ) + .with_rpc_sources(rpc_services) + .build(); + + // Call `eth_getBlockByNumber` RPC method (unused cycles will be refunded) + let result: MultiRpcResult = client + .get_block_by_number(BlockTag::Number(Nat256::from(height))) + .send() + .await; + + match result { + MultiRpcResult::Consistent(Ok(block)) => block, + MultiRpcResult::Consistent(Err(err)) => panic!("{err:?}"), + MultiRpcResult::Inconsistent(v) => { + panic!("RPC providers gave inconsistent results: {:?}", v) + } + } +} + +fn sha256(input: &String) -> [u8; 32] { + use sha2::Digest; + let mut hasher = sha2::Sha256::new(); + hasher.update(input.as_bytes()); + hasher.finalize().into() +} + +#[ic_cdk::update] +async fn get_ecdsa_public_key() -> String { + let pub_key = ecdsa_public_key(&EcdsaPublicKeyArgs { + canister_id: None, + derivation_path: vec![], + key_id: EcdsaKeyId { + curve: EcdsaCurve::Secp256k1, + name: KEY_NAME.to_string(), + }, + }) + .await + .expect("Failed to get ecdsa public key"); + hex::encode(pub_key.public_key) +} + +#[ic_cdk::update] +async fn get_schnorr_public_key() -> String { + let pub_key = schnorr_public_key(&SchnorrPublicKeyArgs { + canister_id: None, + derivation_path: vec![], + key_id: SchnorrKeyId { + algorithm: SchnorrAlgorithm::Ed25519, + name: KEY_NAME.to_string(), + }, + }) + .await + .expect("Failed to get schnorr public key"); + hex::encode(pub_key.public_key) +} + +#[ic_cdk::update] +async fn sign_message_with_ecdsa(message: String) -> String { + let message_hash = sha256(&message); + let signature = sign_with_ecdsa(&SignWithEcdsaArgs { + message_hash: message_hash.to_vec(), + derivation_path: vec![], + key_id: EcdsaKeyId { + curve: EcdsaCurve::Secp256k1, + name: KEY_NAME.to_string(), + }, + }) + .await + .expect("Failed to sign with ecdsa"); + hex::encode(signature.signature) +} + +#[ic_cdk::update] +async fn sign_message_with_schnorr(message: String) -> String { + let message = sha256(&message); + let signature = sign_with_schnorr(&SignWithSchnorrArgs { + message: message.to_vec(), + derivation_path: vec![], + key_id: SchnorrKeyId { + algorithm: SchnorrAlgorithm::Ed25519, + name: KEY_NAME.to_string(), + }, + aux: None, + }) + .await + .expect("Failed to sign with schnorr"); + hex::encode(signature.signature) +} + +// Export the interface for the smart contract. +ic_cdk::export_candid!(); diff --git a/rust/evm_block_explorer/dfx.json b/rust/evm_block_explorer/dfx.json deleted file mode 100644 index 9a63275c9b..0000000000 --- a/rust/evm_block_explorer/dfx.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "canisters": { - "backend": { - "candid": "backend/backend.did", - "type": "custom", - "shrink": true, - "gzip": true, - "wasm": "target/wasm32-unknown-unknown/release/backend.wasm", - "build": [ - "cargo build --target wasm32-unknown-unknown --release -p backend", - "candid-extractor target/wasm32-unknown-unknown/release/backend.wasm > backend/backend.did" - ], - "metadata": [ - { - "name": "candid:service" - } - ] - }, - "frontend": { - "dependencies": ["backend"], - "frontend": { - "entrypoint": "frontend/index.html" - }, - "source": ["frontend/dist"], - "type": "assets" - }, - "evm_rpc": { - "candid": "https://github.com/dfinity/evm-rpc-canister/releases/download/v2.2.0/evm_rpc.did", - "type": "custom", - "specified_id": "7hfb6-caaaa-aaaar-qadga-cai", - "remote": { - "id": { - "ic": "7hfb6-caaaa-aaaar-qadga-cai" - } - }, - "wasm": "https://github.com/dfinity/evm-rpc-canister/releases/download/v2.2.0/evm_rpc.wasm.gz", - "init_arg": "(record {})" - } - }, - "output_env_file": ".env" -} diff --git a/rust/evm_block_explorer/frontend/src/actor.js b/rust/evm_block_explorer/frontend/src/actor.js index 5d2f0478ac..2deca93941 100644 --- a/rust/evm_block_explorer/frontend/src/actor.js +++ b/rust/evm_block_explorer/frontend/src/actor.js @@ -7,14 +7,12 @@ import { createActor } from "./bindings/backend"; // via Set-Cookie header (see vite.config.js). const canisterEnv = safeGetCanisterEnv(); -// Resolve canister ID: cookie (icp-cli + dev server) -> env var (dfx build-time) -const canisterId = - canisterEnv?.["PUBLIC_CANISTER_ID:backend"] ?? - process.env.CANISTER_ID_BACKEND; +// Resolve canister ID from the ic_env cookie (icp-cli + dev server). +const canisterId = canisterEnv?.["PUBLIC_CANISTER_ID:backend"]; if (!canisterId) { throw new Error( - "Canister ID for 'backend' not found. Run 'icp deploy' or 'dfx deploy' first." + "Canister ID for 'backend' not found. Run 'icp deploy' first." ); } diff --git a/rust/evm_block_explorer/frontend/vite.config.js b/rust/evm_block_explorer/frontend/vite.config.js index b5ac17930c..faf4c2b869 100644 --- a/rust/evm_block_explorer/frontend/vite.config.js +++ b/rust/evm_block_explorer/frontend/vite.config.js @@ -1,88 +1,58 @@ -import { defineConfig, loadEnv } from "vite"; +import { defineConfig } from "vite"; +import { icpBindgen } from "@icp-sdk/bindgen/plugins/vite"; import react from "@vitejs/plugin-react"; import { execSync } from "child_process"; -import { icpBindgen } from "@icp-sdk/bindgen/plugins/vite"; -function getDevServerConfig() { - // Try icp-cli first - try { - const canisterId = execSync("icp canister status backend -e local -i", { - encoding: "utf-8", - stdio: "pipe", - }).trim(); - const networkStatus = JSON.parse( - execSync("icp network status --json", { - encoding: "utf-8", - stdio: "pipe", - }) - ); - return { - headers: { - "Set-Cookie": `ic_env=${encodeURIComponent( - `ic_root_key=${networkStatus.root_key}&PUBLIC_CANISTER_ID:backend=${canisterId}` - )}; SameSite=Lax;`, - }, - proxy: { - "/api": { target: "http://127.0.0.1:8000", changeOrigin: true }, - }, - }; - } catch {} +export default defineConfig(({ command }) => { + const plugins = [ + react(), + icpBindgen({ + didFile: "../backend/backend.did", + outDir: "./src/bindings", + }), + ]; - // Try dfx + if (command !== "serve") { + return { plugins }; + } + + const environment = process.env.ICP_ENVIRONMENT || "local"; + const CANISTER_NAME = "backend"; + + const networkStatus = JSON.parse( + execSync(`icp network status -e ${environment} --json`, { encoding: "utf-8" }) + ); + const rootKey = networkStatus.root_key; + const proxyTarget = networkStatus.api_url; + + let canisterId; try { - const pingResult = JSON.parse( - execSync("dfx ping", { encoding: "utf-8", stdio: "pipe" }) - ); - const rootKeyHex = Buffer.from(pingResult.root_key).toString("hex"); - const canisterId = execSync("dfx canister id backend", { - encoding: "utf-8", - stdio: "pipe", - }).trim(); - return { + canisterId = execSync( + `icp canister status ${CANISTER_NAME} -e ${environment} -i`, + { encoding: "utf-8" } + ).trim(); + } catch { + console.error(` + Backend canister "${CANISTER_NAME}" not found in environment "${environment}" + + Before running the dev server, deploy the backend canister: + + icp deploy ${CANISTER_NAME} -e ${environment} + `); + process.exit(1); + } + + return { + plugins, + server: { headers: { "Set-Cookie": `ic_env=${encodeURIComponent( - `ic_root_key=${rootKeyHex}&PUBLIC_CANISTER_ID:backend=${canisterId}` + `PUBLIC_CANISTER_ID:${CANISTER_NAME}=${canisterId}&ic_root_key=${rootKey}` )}; SameSite=Lax;`, }, proxy: { - "/api": { - target: "http://127.0.0.1:4943", - changeOrigin: true, - }, + "/api": { target: proxyTarget, changeOrigin: true }, }, - host: "127.0.0.1", - }; - } catch {} - - throw new Error( - "No local network running. Start with:\n icp network start -d && icp deploy\nor:\n dfx start --background && dfx deploy" - ); -} - -export default defineConfig(({ command, mode }) => { - // dfx generates ../.env with CANISTER_ID_* vars on deploy. Bake them into the - // bundle so actor.js can fall back to them when the ic_env cookie does not - // contain canister IDs (dfx does not inject PUBLIC_CANISTER_ID:* env vars - // into the asset canister, unlike icp-cli). - const env = loadEnv(mode, "..", ["CANISTER_"]); - - return { - base: "./", - plugins: [ - react(), - icpBindgen({ - didFile: "../backend/backend.did", - outDir: "./src/bindings", - }), - ], - define: { - "process.env.CANISTER_ID_BACKEND": JSON.stringify( - env.CANISTER_ID_BACKEND - ), - }, - optimizeDeps: { - esbuildOptions: { define: { global: "globalThis" } }, }, - server: command === "serve" ? getDevServerConfig() : undefined, }; }); diff --git a/rust/evm_block_explorer/icp.yaml b/rust/evm_block_explorer/icp.yaml index 3c27f5e3d3..3e0c095e0a 100644 --- a/rust/evm_block_explorer/icp.yaml +++ b/rust/evm_block_explorer/icp.yaml @@ -1,14 +1,13 @@ canisters: - name: backend recipe: - type: "@dfinity/rust@v3.2.0" + type: "@dfinity/rust@v3.3.0" configuration: - package: backend candid: backend/backend.did - name: frontend recipe: - type: "@dfinity/asset-canister@v2.1.0" + type: "@dfinity/asset-canister@v2.2.1" configuration: dir: frontend/dist build: @@ -19,5 +18,16 @@ canisters: build: steps: - type: pre-built - url: https://github.com/dfinity/evm-rpc-canister/releases/download/v2.2.0/evm_rpc.wasm.gz + url: https://github.com/dfinity/evm-rpc-canister/releases/download/evm_rpc-v2.8.0/evm_rpc.wasm.gz init_args: "(record {})" + +environments: + - name: local + network: local + + - name: production + network: ic + settings: + backend: + environment_variables: + "PUBLIC_CANISTER_ID:evm_rpc": "7hfb6-caaaa-aaaar-qadga-cai" diff --git a/rust/evm_block_explorer/test.sh b/rust/evm_block_explorer/test.sh new file mode 100755 index 0000000000..75d18983c0 --- /dev/null +++ b/rust/evm_block_explorer/test.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -e + +echo "=== Test 1: get_ecdsa_public_key returns a hex-encoded public key ===" +result=$(icp canister call backend get_ecdsa_public_key '()') +echo "$result" +echo "$result" | grep -qE '[0-9a-f]{60}' && echo "PASS" || (echo "FAIL" && exit 1) + +echo "=== Test 2: get_schnorr_public_key returns a hex-encoded public key ===" +result=$(icp canister call backend get_schnorr_public_key '()') +echo "$result" +echo "$result" | grep -qE '[0-9a-f]{60}' && echo "PASS" || (echo "FAIL" && exit 1) + +echo "=== Test 3: sign_message_with_ecdsa returns a hex-encoded signature ===" +result=$(icp canister call backend sign_message_with_ecdsa '("hello")') +echo "$result" +echo "$result" | grep -qE '[0-9a-f]{60}' && echo "PASS" || (echo "FAIL" && exit 1) + +echo "=== Test 4: sign_message_with_schnorr returns a hex-encoded signature ===" +result=$(icp canister call backend sign_message_with_schnorr '("hello")') +echo "$result" +echo "$result" | grep -qE '[0-9a-f]{60}' && echo "PASS" || (echo "FAIL" && exit 1) From d8a66f165c5dd07bf57d2f92b47cc38c0afb9b91 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 9 Jul 2026 15:06:08 +0200 Subject: [PATCH 02/10] fix(evm_block_explorer): use ic environment name, exclude evm_rpc from ic deploy Co-Authored-By: Claude Sonnet 4.6 --- rust/evm_block_explorer/icp.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rust/evm_block_explorer/icp.yaml b/rust/evm_block_explorer/icp.yaml index 3e0c095e0a..5dbac2e01a 100644 --- a/rust/evm_block_explorer/icp.yaml +++ b/rust/evm_block_explorer/icp.yaml @@ -22,11 +22,9 @@ canisters: init_args: "(record {})" environments: - - name: local - network: local - - - name: production + - name: ic network: ic + canisters: [backend, frontend] settings: backend: environment_variables: From 686f3d04b4778a2b212be7cb720cb2e7ea958651 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 9 Jul 2026 15:50:19 +0200 Subject: [PATCH 03/10] fix(evm_block_explorer): use multiple RPC providers, return Result from get_evm_block Using only Llama caused 521 failures when that provider is down. Now uses Llama + Cloudflare + PublicNode for redundancy. Changed return type to Result so provider errors surface as a Candid Err variant instead of an IC trap. Co-Authored-By: Claude Sonnet 4.6 --- rust/evm_block_explorer/backend/backend.did | 3 ++- rust/evm_block_explorer/backend/src/lib.rs | 17 ++++++++--------- rust/evm_block_explorer/frontend/src/Block.jsx | 8 ++++++-- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/rust/evm_block_explorer/backend/backend.did b/rust/evm_block_explorer/backend/backend.did index ef3adea795..b3fff62539 100644 --- a/rust/evm_block_explorer/backend/backend.did +++ b/rust/evm_block_explorer/backend/backend.did @@ -21,9 +21,10 @@ type Block = record { gasUsed : nat; mixHash : text; }; +type Result = variant { Ok : Block; Err : text }; service : { get_ecdsa_public_key : () -> (text); - get_evm_block : (nat) -> (Block); + get_evm_block : (nat) -> (Result); get_schnorr_public_key : () -> (text); sign_message_with_ecdsa : (text) -> (text); sign_message_with_schnorr : (text) -> (text); diff --git a/rust/evm_block_explorer/backend/src/lib.rs b/rust/evm_block_explorer/backend/src/lib.rs index 109f6abf89..730e351287 100644 --- a/rust/evm_block_explorer/backend/src/lib.rs +++ b/rust/evm_block_explorer/backend/src/lib.rs @@ -19,12 +19,13 @@ fn evm_rpc_id() -> Principal { } #[ic_cdk::update] -async fn get_evm_block(height: u128) -> Block { - // Ethereum Mainnet RPC providers +async fn get_evm_block(height: u128) -> Result { + // Ethereum Mainnet RPC providers — using multiple for redundancy. + // If one provider is temporarily unavailable, others can still serve the request. let rpc_services = RpcServices::EthMainnet(Some(vec![ EthMainnetService::Llama, - // EthMainnetService::Alchemy, - // EthMainnetService::Cloudflare, + EthMainnetService::Cloudflare, + EthMainnetService::PublicNode, ])); // Base Mainnet RPC providers @@ -57,11 +58,9 @@ async fn get_evm_block(height: u128) -> Block { .await; match result { - MultiRpcResult::Consistent(Ok(block)) => block, - MultiRpcResult::Consistent(Err(err)) => panic!("{err:?}"), - MultiRpcResult::Inconsistent(v) => { - panic!("RPC providers gave inconsistent results: {:?}", v) - } + MultiRpcResult::Consistent(Ok(block)) => Ok(block), + MultiRpcResult::Consistent(Err(err)) => Err(format!("{err:?}")), + MultiRpcResult::Inconsistent(v) => Err(format!("RPC providers gave inconsistent results: {:?}", v)), } } diff --git a/rust/evm_block_explorer/frontend/src/Block.jsx b/rust/evm_block_explorer/frontend/src/Block.jsx index 5a58b8135c..d1e634e175 100644 --- a/rust/evm_block_explorer/frontend/src/Block.jsx +++ b/rust/evm_block_explorer/frontend/src/Block.jsx @@ -14,8 +14,12 @@ function Block() { setLoading(true); setError(undefined); // Convert the block number string to BigInt for the backend call - const block = await backend.get_evm_block(BigInt(blockNumber || 420)); - setBlock(block); + const result = await backend.get_evm_block(BigInt(blockNumber || 420)); + if ('Ok' in result) { + setBlock(result.Ok); + } else { + setError(result.Err); + } } catch (err) { console.error(err); setError(String(err)); From 71f5c413610ec004d2adbd52a1d07dd93781698e Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 9 Jul 2026 15:51:35 +0200 Subject: [PATCH 04/10] fix(evm_block_explorer): require valid block number, remove silent 420 default Co-Authored-By: Claude Sonnet 4.6 --- rust/evm_block_explorer/frontend/src/Block.jsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/rust/evm_block_explorer/frontend/src/Block.jsx b/rust/evm_block_explorer/frontend/src/Block.jsx index d1e634e175..181f86043e 100644 --- a/rust/evm_block_explorer/frontend/src/Block.jsx +++ b/rust/evm_block_explorer/frontend/src/Block.jsx @@ -9,12 +9,13 @@ function Block() { const [error, setError] = useState(); const [blockNumber, setBlockNumber] = useState(''); + const isValid = blockNumber !== '' && Number.isInteger(Number(blockNumber)) && Number(blockNumber) >= 0; + const fetchBlock = async () => { try { setLoading(true); setError(undefined); - // Convert the block number string to BigInt for the backend call - const result = await backend.get_evm_block(BigInt(blockNumber || 420)); + const result = await backend.get_evm_block(BigInt(blockNumber)); if ('Ok' in result) { setBlock(result.Ok); } else { @@ -36,6 +37,7 @@ function Block() {
setBlockNumber(e.target.value)} placeholder="Enter block number" @@ -43,8 +45,8 @@ function Block() { /> From 709fb372686170b69efeae36ed51ad608181e3c8 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 9 Jul 2026 15:59:02 +0200 Subject: [PATCH 05/10] fix(evm_block_explorer): use first Ok result when providers are inconsistent When some providers are down and others succeed, MultiRpcResult::Inconsistent previously caused an error. Now falls back to the first successful response so transient provider outages don't block the request entirely. Co-Authored-By: Claude Sonnet 4.6 --- rust/evm_block_explorer/backend/src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rust/evm_block_explorer/backend/src/lib.rs b/rust/evm_block_explorer/backend/src/lib.rs index 730e351287..82312f13fc 100644 --- a/rust/evm_block_explorer/backend/src/lib.rs +++ b/rust/evm_block_explorer/backend/src/lib.rs @@ -60,7 +60,15 @@ async fn get_evm_block(height: u128) -> Result { match result { MultiRpcResult::Consistent(Ok(block)) => Ok(block), MultiRpcResult::Consistent(Err(err)) => Err(format!("{err:?}")), - MultiRpcResult::Inconsistent(v) => Err(format!("RPC providers gave inconsistent results: {:?}", v)), + // Some providers may be temporarily unavailable while others succeed. + // If at least one returned Ok, use it; only fail if every provider errored. + MultiRpcResult::Inconsistent(responses) => { + if let Some(block) = responses.into_iter().find_map(|(_, r)| r.ok()) { + Ok(block) + } else { + Err("All RPC providers failed to return a block".to_string()) + } + } } } From 49f8de1a993f06f11dc1596117c4539289c58e39 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 9 Jul 2026 16:07:24 +0200 Subject: [PATCH 06/10] fix(evm_block_explorer): use PublicNode, document production RPC guidance - Use only PublicNode (no API key, reliable) instead of multiple providers with unreliable Llama/Cloudflare; comment explains how to switch to RpcServices::EthMainnet(None) for production consensus - icp.yaml: add nodesInSubnet=28 to validate cycle costs locally, restore local environment entry, add explanatory comments matching basic_ethereum - README: fix deploy command (ic environment, not production), add RPC providers section documenting the PublicNode default and production upgrade path Co-Authored-By: Claude Sonnet 4.6 --- rust/evm_block_explorer/README.md | 10 ++++- rust/evm_block_explorer/backend/src/lib.rs | 46 +++++++--------------- rust/evm_block_explorer/icp.yaml | 16 +++++++- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/rust/evm_block_explorer/README.md b/rust/evm_block_explorer/README.md index ecdd3f705d..ef4728ec36 100644 --- a/rust/evm_block_explorer/README.md +++ b/rust/evm_block_explorer/README.md @@ -51,10 +51,10 @@ npm run dev --prefix frontend ### Deploying on ICP mainnet -On mainnet, the `evm_rpc` canister is already deployed at `7hfb6-caaaa-aaaar-qadga-cai`. Deploy only the backend and frontend — icp-cli injects the correct canister ID via the `production` environment configuration in `icp.yaml`: +On mainnet, the `evm_rpc` canister is already deployed at `7hfb6-caaaa-aaaar-qadga-cai`. Deploy only the backend and frontend — icp-cli injects the correct canister ID via the `ic` environment configuration in `icp.yaml`: ```bash -icp deploy backend frontend -e production +icp deploy -e ic ``` ## Updating the Candid interface @@ -67,6 +67,12 @@ If you modify the backend's public API, rebuild the canister and regenerate the icp build backend && candid-extractor target/wasm32-unknown-unknown/release/backend.wasm > backend/backend.did ``` +## RPC providers and API keys + +The example uses [PublicNode](https://ethereum-rpc.publicnode.com) by default — a free, no-registration provider that works out of the box locally and on mainnet. This is sufficient for getting started and automated testing. + +For production deployments requiring premium providers (Alchemy, Ankr, BlockPi), refer to the [EVM RPC canister documentation](https://github.com/dfinity/evm-rpc-canister) for how to configure API keys. Once configured, change `RpcServices::EthMainnet(Some(vec![EthMainnetService::PublicNode]))` in `backend/src/lib.rs` to `RpcServices::EthMainnet(None)` to use all configured providers for better consensus. + ## Security considerations and best practices If you base your application on this example, it is recommended that you familiarize yourself with and adhere to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for developing on ICP. This example may not implement all the best practices. diff --git a/rust/evm_block_explorer/backend/src/lib.rs b/rust/evm_block_explorer/backend/src/lib.rs index 82312f13fc..8370d6ea63 100644 --- a/rust/evm_block_explorer/backend/src/lib.rs +++ b/rust/evm_block_explorer/backend/src/lib.rs @@ -1,7 +1,5 @@ use candid::Principal; -use evm_rpc_types::{ - Block, BlockTag, EthMainnetService, MultiRpcResult, Nat256, RpcServices, -}; +use evm_rpc_types::{Block, BlockTag, EthMainnetService, MultiRpcResult, Nat256, RpcServices}; use ic_cdk_management_canister::{ ecdsa_public_key, schnorr_public_key, sign_with_ecdsa, sign_with_schnorr, EcdsaCurve, EcdsaKeyId, EcdsaPublicKeyArgs, SchnorrAlgorithm, SchnorrKeyId, SchnorrPublicKeyArgs, @@ -20,28 +18,20 @@ fn evm_rpc_id() -> Principal { #[ic_cdk::update] async fn get_evm_block(height: u128) -> Result { - // Ethereum Mainnet RPC providers — using multiple for redundancy. - // If one provider is temporarily unavailable, others can still serve the request. - let rpc_services = RpcServices::EthMainnet(Some(vec![ - EthMainnetService::Llama, - EthMainnetService::Cloudflare, - EthMainnetService::PublicNode, - ])); + // Uses PublicNode by default — no API key required, works locally and on mainnet. + // For production deployments requiring premium providers (Alchemy, Ankr, BlockPi), + // configure API keys via the EVM RPC canister, then pass None to use all configured + // providers for better consensus: + // RpcServices::EthMainnet(None) + let rpc_services = RpcServices::EthMainnet(Some(vec![EthMainnetService::PublicNode])); - // Base Mainnet RPC providers - // Get chain ID and RPC providers from https://chainlist.org/ + // To query a different chain, use RpcServices::Custom instead: // let rpc_services = RpcServices::Custom { - // chain_id: 8453, - // services: vec![ - // evm_rpc_types::RpcApi { - // url: "https://base.llamarpc.com".to_string(), - // headers: None, - // }, - // evm_rpc_types::RpcApi { - // url: "https://base-rpc.publicnode.com".to_string(), - // headers: None, - // }, - // ], + // chain_id: 8453, // Base Mainnet — see https://chainlist.org/ for chain IDs + // services: vec![evm_rpc_types::RpcApi { + // url: "https://base-rpc.publicnode.com".to_string(), + // headers: None, + // }], // }; let client = evm_rpc_client::EvmRpcClient::builder( @@ -60,15 +50,7 @@ async fn get_evm_block(height: u128) -> Result { match result { MultiRpcResult::Consistent(Ok(block)) => Ok(block), MultiRpcResult::Consistent(Err(err)) => Err(format!("{err:?}")), - // Some providers may be temporarily unavailable while others succeed. - // If at least one returned Ok, use it; only fail if every provider errored. - MultiRpcResult::Inconsistent(responses) => { - if let Some(block) = responses.into_iter().find_map(|(_, r)| r.ok()) { - Ok(block) - } else { - Err("All RPC providers failed to return a block".to_string()) - } - } + MultiRpcResult::Inconsistent(v) => Err(format!("RPC providers gave inconsistent results: {v:?}")), } } diff --git a/rust/evm_block_explorer/icp.yaml b/rust/evm_block_explorer/icp.yaml index 5dbac2e01a..3b432e069a 100644 --- a/rust/evm_block_explorer/icp.yaml +++ b/rust/evm_block_explorer/icp.yaml @@ -14,14 +14,28 @@ canisters: - npm install --prefix frontend - npm run build --prefix frontend + # evm_rpc is only deployed locally. On ICP mainnet the shared EVM RPC canister + # (7hfb6-caaaa-aaaar-qadga-cai) is used instead — the ic environment below + # restricts deployment to [backend, frontend] only and injects its principal via env vars. - name: evm_rpc build: steps: - type: pre-built url: https://github.com/dfinity/evm-rpc-canister/releases/download/evm_rpc-v2.8.0/evm_rpc.wasm.gz - init_args: "(record {})" + # nodesInSubnet controls how many cycles the EVM RPC canister charges per request + # (each node in a subnet independently makes the HTTPS outcall, so cost scales with N). + # Using the production value (28) locally validates that the cycle amounts in the + # backend are sufficient for a real subnet, even though the local replica only runs 1 node. + init_args: "(record { nodesInSubnet = 28 })" environments: + # Local: deploys backend, frontend, and evm_rpc (pre-built). + # icp-cli auto-injects PUBLIC_CANISTER_ID:evm_rpc into the backend after deploying evm_rpc. + - name: local + network: local + + # IC mainnet: deploys only backend and frontend; the shared EVM RPC canister is already + # running on ICP mainnet at 7hfb6-caaaa-aaaar-qadga-cai. - name: ic network: ic canisters: [backend, frontend] From b239f85882716147cd91878339ac0d17d010e03f Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 9 Jul 2026 16:37:34 +0200 Subject: [PATCH 07/10] fix(evm_block_explorer): restore key_1 production note, add block test, clean workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Restore '// Use "key_1" for production' comment on KEY_NAME (present in the original, carries important production guidance) - Add test 5: fetch Ethereum mainnet block 1 and assert its deterministic hash and miner address (both are immutable historical facts) - Remove redundant npm install step from workflow — icp deploy already runs the build steps defined in icp.yaml Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/evm_block_explorer.yml | 2 -- rust/evm_block_explorer/backend/src/lib.rs | 2 +- rust/evm_block_explorer/test.sh | 7 +++++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/evm_block_explorer.yml b/.github/workflows/evm_block_explorer.yml index 968e762150..739b302c51 100644 --- a/.github/workflows/evm_block_explorer.yml +++ b/.github/workflows/evm_block_explorer.yml @@ -20,8 +20,6 @@ jobs: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Install Node.js dependencies - run: npm install --prefix rust/evm_block_explorer/frontend - name: Deploy and test working-directory: rust/evm_block_explorer run: | diff --git a/rust/evm_block_explorer/backend/src/lib.rs b/rust/evm_block_explorer/backend/src/lib.rs index 8370d6ea63..eb6c084663 100644 --- a/rust/evm_block_explorer/backend/src/lib.rs +++ b/rust/evm_block_explorer/backend/src/lib.rs @@ -6,7 +6,7 @@ use ic_cdk_management_canister::{ SignWithEcdsaArgs, SignWithSchnorrArgs, }; -const KEY_NAME: &str = "test_key_1"; +const KEY_NAME: &str = "test_key_1"; // Use "key_1" for production /// Resolve the EVM RPC canister ID at runtime. icp-cli sets the /// `PUBLIC_CANISTER_ID:evm_rpc` env var for local deployments; on production diff --git a/rust/evm_block_explorer/test.sh b/rust/evm_block_explorer/test.sh index 75d18983c0..5d37976d24 100755 --- a/rust/evm_block_explorer/test.sh +++ b/rust/evm_block_explorer/test.sh @@ -20,3 +20,10 @@ echo "=== Test 4: sign_message_with_schnorr returns a hex-encoded signature ===" result=$(icp canister call backend sign_message_with_schnorr '("hello")') echo "$result" echo "$result" | grep -qE '[0-9a-f]{60}' && echo "PASS" || (echo "FAIL" && exit 1) + +echo "=== Test 5: get_evm_block returns correct data for Ethereum mainnet block 1 ===" +result=$(icp canister call backend get_evm_block '(1)') +echo "$result" +echo "$result" | grep -q "Ok" && echo "PASS (Ok variant)" || (echo "FAIL (expected Ok)" && exit 1) +echo "$result" | grep -q "0x88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6" && echo "PASS (hash)" || (echo "FAIL (wrong hash)" && exit 1) +echo "$result" | grep -q "0x05a56e2d52c817161883f50c441c3228cfe54d9f" && echo "PASS (miner)" || (echo "FAIL (wrong miner)" && exit 1) From b6f4e056d2289a93748a2cf494446ff7203284be Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 9 Jul 2026 16:39:30 +0200 Subject: [PATCH 08/10] fix(evm_block_explorer): fix component names and render errors in signing panels ECDSASigning and SchnorrSigning both declared function Block() internally and had an error state that was set but never rendered. Co-Authored-By: Claude Sonnet 4.6 --- rust/evm_block_explorer/frontend/src/ECDSASigning.jsx | 4 +++- rust/evm_block_explorer/frontend/src/SchnorrSigning.jsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/rust/evm_block_explorer/frontend/src/ECDSASigning.jsx b/rust/evm_block_explorer/frontend/src/ECDSASigning.jsx index 9e792a0c82..09c00e6fc0 100644 --- a/rust/evm_block_explorer/frontend/src/ECDSASigning.jsx +++ b/rust/evm_block_explorer/frontend/src/ECDSASigning.jsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { backend } from './actor'; -function Block() { +function ECDSASigning() { const [loading, setLoading] = useState(false); const [loading2, setLoading2] = useState(false); const [key, setKey] = useState(); @@ -82,6 +82,8 @@ function Block() {
+ {error &&

{error}

} + {signature && (

ECDSA signature:

diff --git a/rust/evm_block_explorer/frontend/src/SchnorrSigning.jsx b/rust/evm_block_explorer/frontend/src/SchnorrSigning.jsx index 95c9afab3f..7fc05ea440 100644 --- a/rust/evm_block_explorer/frontend/src/SchnorrSigning.jsx +++ b/rust/evm_block_explorer/frontend/src/SchnorrSigning.jsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { backend } from './actor'; -function Block() { +function SchnorrSigning() { const [loading, setLoading] = useState(false); const [loading2, setLoading2] = useState(false); const [key, setKey] = useState(); @@ -82,6 +82,8 @@ function Block() {
+ {error &&

{error}

} + {signature && (

Schnorr signature:

From ebf4c3a1bb6e38a7fbebe34302c0d5dce60e41ef Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 9 Jul 2026 16:46:50 +0200 Subject: [PATCH 09/10] refactor(evm_block_explorer): drop signing capabilities, focus on block data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ECDSA/Schnorr signing functions were isolated primitives with no connection to EVM concepts — no address derivation, no transaction construction. Developers wanting signing + EVM interaction should use basic_ethereum instead. Removes: get_ecdsa_public_key, get_schnorr_public_key, sign_message_with_ecdsa, sign_message_with_schnorr from backend and DID; ECDSASigning and SchnorrSigning components; hex, sha2, ic-cdk-management-canister dependencies; signing tests. Co-Authored-By: Claude Sonnet 4.6 --- rust/evm_block_explorer/README.md | 3 +- rust/evm_block_explorer/backend/Cargo.toml | 3 - rust/evm_block_explorer/backend/backend.did | 4 - rust/evm_block_explorer/backend/src/lib.rs | 77 --------------- .../frontend/src/ECDSASigning.jsx | 97 ------------------- .../frontend/src/SchnorrSigning.jsx | 97 ------------------- rust/evm_block_explorer/frontend/src/main.jsx | 4 - rust/evm_block_explorer/test.sh | 22 +---- 8 files changed, 2 insertions(+), 305 deletions(-) delete mode 100644 rust/evm_block_explorer/frontend/src/ECDSASigning.jsx delete mode 100644 rust/evm_block_explorer/frontend/src/SchnorrSigning.jsx diff --git a/rust/evm_block_explorer/README.md b/rust/evm_block_explorer/README.md index ef4728ec36..3e5af2af2d 100644 --- a/rust/evm_block_explorer/README.md +++ b/rust/evm_block_explorer/README.md @@ -4,8 +4,7 @@ ## Overview -The EVM Block Explorer example demonstrates how an ICP smart contract can obtain information directly from other blockchain networks. Using HTTPS outcalls, smart contracts on ICP can interact with other networks without needing to go through a third-party service such as a bridge or an oracle. Supported interactions with other chains include querying network data, signing transactions, and submitting transactions directly to other networks. -In this example, you'll also see how to sign transactions with canister ECDSA or Schnorr signatures. +The EVM Block Explorer example demonstrates how an ICP smart contract can fetch block data directly from Ethereum and other EVM-compatible chains. Using HTTPS outcalls via the [EVM RPC canister](https://github.com/dfinity/evm-rpc-canister), smart contracts on ICP can read on-chain data without a bridge or oracle. The same pattern applies to any EVM-compatible chain supported by the EVM RPC canister.