From 41bc0055623adf988c6ec97ecc2000433cb80c6a Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 16 Jun 2026 19:48:46 +0200 Subject: [PATCH 01/29] chore: migrate rust/basic_ethereum to icp-cli Replaces dfx.json with icp.yaml using @dfinity/rust@v3.3.0 recipe. Moves Rust source from src/ into backend/, adds workspace Cargo.toml, Makefile with local-testable ethereum_address tests, updated README with icp-cli deploy instructions preserving Ethereum integration details, and CI workflow with icp-dev-env-rust:1.0.0. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/basic_ethereum.yml | 28 +++ .../.devcontainer/devcontainer.json | 20 -- rust/basic_ethereum/BUILD.md | 113 ----------- rust/basic_ethereum/Cargo.lock | 2 +- rust/basic_ethereum/Cargo.toml | 29 +-- rust/basic_ethereum/Makefile | 26 +++ rust/basic_ethereum/README.md | 180 ++++++------------ rust/basic_ethereum/backend/Cargo.toml | 26 +++ rust/basic_ethereum/{src => backend}/ecdsa.rs | 0 .../{src => backend}/ethereum_wallet.rs | 0 rust/basic_ethereum/{src => backend}/lib.rs | 0 rust/basic_ethereum/{src => backend}/state.rs | 0 rust/basic_ethereum/basic_ethereum.did | 57 ------ rust/basic_ethereum/dfx.json | 37 ---- rust/basic_ethereum/icp.yaml | 15 ++ 15 files changed, 160 insertions(+), 373 deletions(-) create mode 100644 .github/workflows/basic_ethereum.yml delete mode 100644 rust/basic_ethereum/.devcontainer/devcontainer.json delete mode 100644 rust/basic_ethereum/BUILD.md create mode 100644 rust/basic_ethereum/Makefile create mode 100644 rust/basic_ethereum/backend/Cargo.toml rename rust/basic_ethereum/{src => backend}/ecdsa.rs (100%) rename rust/basic_ethereum/{src => backend}/ethereum_wallet.rs (100%) rename rust/basic_ethereum/{src => backend}/lib.rs (100%) rename rust/basic_ethereum/{src => backend}/state.rs (100%) delete mode 100644 rust/basic_ethereum/basic_ethereum.did delete mode 100644 rust/basic_ethereum/dfx.json create mode 100644 rust/basic_ethereum/icp.yaml diff --git a/.github/workflows/basic_ethereum.yml b/.github/workflows/basic_ethereum.yml new file mode 100644 index 0000000000..a67628f1ef --- /dev/null +++ b/.github/workflows/basic_ethereum.yml @@ -0,0 +1,28 @@ +name: basic_ethereum + +on: + push: + branches: [master] + pull_request: + paths: + - rust/basic_ethereum/** + - .github/workflows/basic_ethereum.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + rust-basic_ethereum: + runs-on: ubuntu-24.04 + container: ghcr.io/dfinity/icp-dev-env-rust:1.0.0 + env: + ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Deploy and test + working-directory: rust/basic_ethereum + run: | + icp network start -d + icp deploy + make test diff --git a/rust/basic_ethereum/.devcontainer/devcontainer.json b/rust/basic_ethereum/.devcontainer/devcontainer.json deleted file mode 100644 index ebb0b8bcc6..0000000000 --- a/rust/basic_ethereum/.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/basic_ethereum/BUILD.md b/rust/basic_ethereum/BUILD.md deleted file mode 100644 index 24cfcb7547..0000000000 --- a/rust/basic_ethereum/BUILD.md +++ /dev/null @@ -1,113 +0,0 @@ -# Continue building locally - -Projects deployed through ICP Ninja are temporary; they will only be live for 20 minutes before they are removed. The command-line tool `dfx` can be used to continue building your ICP Ninja project locally and deploy it to the mainnet. - -To migrate your ICP Ninja project off of the web browser and develop it locally, follow these steps. - -### 1. Install developer tools. - -You can install the developer tools natively or use Dev Containers. - -#### Option 1: Natively install developer tools - -> Installing `dfx` natively is currently only supported on macOS and Linux systems. On Windows, it is recommended to use the Dev Containers option. - -1. Install `dfx` with the following command: - -``` - -sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)" - -``` - -> On Apple Silicon (e.g., Apple M1 chip), make sure you have Rosetta installed (`softwareupdate --install-rosetta`). - -2. [Install NodeJS](https://nodejs.org/en/download/package-manager). - -3. For Rust projects, you will also need to: - -- Install [Rust](https://doc.rust-lang.org/cargo/getting-started/installation.html#install-rust-and-cargo): `curl https://sh.rustup.rs -sSf | sh` - -- Install [candid-extractor](https://crates.io/crates/candid-extractor): `cargo install candid-extractor` - -4. For Motoko projects, you will also need to: - -- Install the Motoko package manager [Mops](https://docs.mops.one/quick-start#2-install-mops-cli): `npm i -g ic-mops` - -Lastly, navigate into your project's directory that you downloaded from ICP Ninja. - -#### Option 2: Dev Containers - -Continue building your projects locally by installing the [Dev Container extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) for VS Code and [Docker](https://docs.docker.com/engine/install/). - -Make sure Docker is running, then navigate into your project's directory that you downloaded from ICP Ninja and start the Dev Container by selecting `Dev-Containers: Reopen in Container` in VS Code's command palette (F1 or Ctrl/Cmd+Shift+P). - -> Note that local development ports (e.g. the ports used by `dfx` or `vite`) are forwarded from the Dev Container to your local machine. In the VS code terminal, use Ctrl/Cmd+Click on the displayed local URLs to open them in your browser. To view the current port mappings, click the "Ports" tab in the VS Code terminal window. - -### 2. Start the local development environment. - -``` -dfx start --background -``` - -### 3. Create a local developer identity. - -To manage your project's canisters, it is recommended that you create a local [developer identity](https://internetcomputer.org/docs/building-apps/getting-started/identities) rather than use the `dfx` default identity that is not stored securely. - -To create a new identity, run the commands: - -``` - -dfx identity new IDENTITY_NAME - -dfx identity use IDENTITY_NAME - -``` - -Replace `IDENTITY_NAME` with your preferred identity name. The first command `dfx start --background` starts the local `dfx` processes, then `dfx identity new` will create a new identity and return your identity's seed phase. Be sure to save this in a safe, secure location. - -The third command `dfx identity use` will tell `dfx` to use your new identity as the active identity. Any canister smart contracts created after running `dfx identity use` will be owned and controlled by the active identity. - -Your identity will have a principal ID associated with it. Principal IDs are used to identify different entities on ICP, such as users and canisters. - -[Learn more about ICP developer identities](https://internetcomputer.org/docs/building-apps/getting-started/identities). - -### 4. Deploy the project locally. - -Deploy your project to your local developer environment with: - -``` -npm install -dfx deploy - -``` - -Your project will be hosted on your local machine. The local canister URLs for your project will be shown in the terminal window as output of the `dfx deploy` command. You can open these URLs in your web browser to view the local instance of your project. - -### 5. Obtain cycles. - -To deploy your project to the mainnet for long-term public accessibility, first you will need [cycles](https://internetcomputer.org/docs/building-apps/getting-started/tokens-and-cycles). Cycles are used to pay for the resources your project uses on the mainnet, such as storage and compute. - -> This cost model is known as ICP's [reverse gas model](https://internetcomputer.org/docs/building-apps/essentials/gas-cost), where developers pay for their project's gas fees rather than users pay for their own gas fees. This model provides an enhanced end user experience since they do not need to hold tokens or sign transactions when using a dapp deployed on ICP. - -> Learn how much a project may cost by using the [pricing calculator](https://internetcomputer.org/docs/building-apps/essentials/cost-estimations-and-examples). - -Cycles can be obtained through [converting ICP tokens into cycles using `dfx`](https://internetcomputer.org/docs/building-apps/developer-tools/dfx/dfx-cycles#dfx-cycles-convert). - -### 6. Deploy to the mainnet. - -Once you have cycles, run the command: - -``` - -dfx deploy --network ic - -``` - -After your project has been deployed to the mainnet, it will continuously require cycles to pay for the resources it uses. You will need to [top up](https://internetcomputer.org/docs/building-apps/canister-management/topping-up) your project's canisters or set up automatic cycles management through a service such as [CycleOps](https://cycleops.dev/). - -> If your project's canisters run out of cycles, they will be removed from the network. - -## 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/basic_ethereum/Cargo.lock b/rust/basic_ethereum/Cargo.lock index f33f3b585e..34fcad71e9 100644 --- a/rust/basic_ethereum/Cargo.lock +++ b/rust/basic_ethereum/Cargo.lock @@ -316,7 +316,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] -name = "basic_ethereum" +name = "backend" version = "0.1.0" dependencies = [ "alloy-consensus", diff --git a/rust/basic_ethereum/Cargo.toml b/rust/basic_ethereum/Cargo.toml index ad2551f2da..d1e49e317a 100644 --- a/rust/basic_ethereum/Cargo.toml +++ b/rust/basic_ethereum/Cargo.toml @@ -1,26 +1,3 @@ -[package] -name = "basic_ethereum" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -alloy-consensus = "0.1.3" -alloy-eips = "0.1.3" -alloy-primitives = "0.7.6" -candid = "0.10" -evm-rpc-canister-types = "0.1.2" -# transitive dependency: ic-crypto-ecdsa-secp256k1 -> k256 -> ecdsa -> elliptic-curve -> crypto-bigint -> rand_core -> getrandom -# See https://forum.dfinity.org/t/module-imports-function-wbindgen-describe-from-wbindgen-placeholder-that-is-not-exported-by-the-runtime/11545/8 -getrandom = { version = "*", default-features = false, features = ["custom"] } -ic-cdk = "0.15" -ic-secp256k1 = { git = "https://github.com/dfinity/ic", tag = "release-2025-07-03_03-27-base", package = "ic-secp256k1" } -ic-sha3 = { git = "https://github.com/dfinity/ic", tag = "release-2025-07-03_03-27-base", package = "ic-sha3" } -ic-ethereum-types = { git = "https://github.com/dfinity/ic", tag = "release-2025-07-03_03-27-base", package = "ic-ethereum-types" } -serde = "1.0" -serde_json = "1.0" -serde_bytes = "0.11.15" -num-traits = "0.2.19" -num = "0.4.3" +[workspace] +members = ["backend"] +resolver = "2" diff --git a/rust/basic_ethereum/Makefile b/rust/basic_ethereum/Makefile new file mode 100644 index 0000000000..b05ae028d6 --- /dev/null +++ b/rust/basic_ethereum/Makefile @@ -0,0 +1,26 @@ +.PHONY: test + +# Note: get_balance, transaction_count, and send_eth require live HTTPS outcalls +# to the Ethereum network and cannot be fully tested locally. The tests below +# verify canister deployment and the ethereum_address function, which derives +# an address using threshold ECDSA (available locally). +# For full end-to-end testing, deploy to ICP mainnet with Sepolia testnet. +test: + @echo "=== Test 1: ethereum_address returns a valid 0x-prefixed Ethereum address ===" + @result=$$(icp canister call backend ethereum_address '(null)') && \ + echo "$$result" && \ + echo "$$result" | grep -qE '"0x[0-9a-fA-F]{40}"' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 2: ethereum_address for a specific principal returns a valid address ===" + @result=$$(icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")') && \ + echo "$$result" && \ + echo "$$result" | grep -qE '"0x[0-9a-fA-F]{40}"' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 3: different principals derive different Ethereum addresses ===" + @addr1=$$(icp canister call backend ethereum_address '(null)' | grep -oE '0x[0-9a-fA-F]{40}') && \ + addr2=$$(icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")' | grep -oE '0x[0-9a-fA-F]{40}') && \ + echo "addr1=$$addr1 addr2=$$addr2" && \ + [ "$$addr1" != "$$addr2" ] && \ + echo "PASS" || (echo "FAIL" && exit 1) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index 1733e2d771..e542b418a4 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -1,173 +1,115 @@ # Basic Ethereum -This tutorial will walk you through how to deploy a -sample [canister smart contract](https://internetcomputer.org/docs/current/developer-docs/multi-chain/ethereum/overview) -**that can send and receive Ether (ETH)** on the Internet Computer. +This example demonstrates how to deploy a canister smart contract on the Internet Computer that can **send and receive Ether (ETH)** on the Ethereum network. The canister uses threshold ECDSA to sign Ethereum transactions and HTTPS outcalls to communicate with the Ethereum network via the EVM RPC canister. ## Architecture -This example internally leverages -the [threshold ECDSA](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/encryption/t-ecdsa) -and [HTTPs outcalls](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/advanced-features/https-outcalls/https-outcalls-overview) -features of the Internet Computer. +This example internally leverages: +- [Threshold ECDSA](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/encryption/t-ecdsa): Each user's Ethereum address is derived deterministically from the canister's master ECDSA key using a derivation path based on the user's IC principal. This means each user has a unique, stable Ethereum address controlled by the canister. +- [HTTPS outcalls](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/advanced-features/https-outcalls/https-outcalls-overview): The canister communicates with the Ethereum network via the [EVM RPC canister](https://github.com/internet-computer-protocol/evm-rpc-canister/tree/main) (canister ID `7hfb6-caaaa-aaaar-qadga-cai` on ICP mainnet). -For a deeper understanding of the ICP < > ETH integration, see -the [Ethereum integration overview](https://internetcomputer.org/docs/current/developer-docs/multi-chain/ethereum/overview). +For a deeper understanding of the ICP ↔ ETH integration, see the [Ethereum integration overview](https://internetcomputer.org/docs/current/developer-docs/multi-chain/ethereum/overview). -## Deploying from ICP Ninja +> **Note:** `get_balance`, `transaction_count`, and `send_eth` require live HTTPS outcalls to the Ethereum network and work only when deployed on ICP mainnet. The `ethereum_address` function can be tested locally as it only uses threshold ECDSA, which is available in the local replica. -When viewing this project in ICP Ninja, you can deploy it directly to the mainnet for free by clicking "Run" in the upper right corner. Open this project in ICP Ninja: +## Build and deploy from the command line -[![](https://icp.ninja/assets/open.svg)](https://icp.ninja/i?g=https://github.com/dfinity/examples/rust/basic_ethereum) +### Prerequisites +- [Node.js](https://nodejs.org/) +- icp-cli: `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` -## Deploying with `dfx` - -- [x] Install the [IC SDK](https://internetcomputer.org/docs/current/developer-docs/getting-started/install). - -## Step 1: Building and deploying sample code - -### Clone the smart contract - -To clone and build the smart contract in **Rust**: +### Install ```bash git clone https://github.com/dfinity/examples cd examples/rust/basic_ethereum -git submodule update --init --recursive ``` -**If you are using MacOS, you'll need to install Homebrew and run `brew install llvm` to be able to compile the example. -** +### Deploy and test locally -### Acquire cycles to deploy +```bash +icp network start -d +icp deploy +make test +icp network stop +``` -Deploying to the Internet Computer requires [cycles](https://internetcomputer.org/docs/current/developer-docs/getting-started/tokens-and-cycles) (the equivalent of "gas" on other blockchains). +### Deploy to ICP mainnet (Sepolia testnet) -### Deploy the smart contract to the Internet Computer +To interact with the live Ethereum Sepolia testnet: ```bash -dfx deploy --ic basic_ethereum --argument '(opt record {ethereum_network = opt variant {Sepolia}; ecdsa_key_name = opt variant {TestKey1}})' +icp deploy --network ic --argument '(opt record {ethereum_network = opt variant {Sepolia}; ecdsa_key_name = opt variant {TestKey1}})' ``` -#### What this does - -- `dfx deploy` tells the command line interface to `deploy` the smart contract -- `--ic` tells the command line to deploy the smart contract to the mainnet ICP blockchain -- `--argument (opt record {ethereum_network = opt variant {Sepolia}; ecdsa_key_name = opt variant {TestKey1}})` - initializes the smart contract with the provided arguments: - - `ethereum_network = opt variant {Sepolia}`: the canister uses - the [Ethereum Testnet Sepolia](https://github.com/ethereum-lists/chains/blob/master/_data/chains/eip155-11155111.json) - network. - - `ecdsa_key_name = opt variant {TestKey1}`: the canister uses a test key for signing via threshold ECDSA that is - available on the ICP mainnet. - See [signing messages](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/encryption/signing-messages#signing-messages-1) - for more details. +- `ethereum_network = opt variant {Sepolia}`: uses the [Ethereum Testnet Sepolia](https://github.com/ethereum-lists/chains/blob/master/_data/chains/eip155-11155111.json). +- `ecdsa_key_name = opt variant {TestKey1}`: uses a test key for threshold ECDSA signing available on ICP mainnet. See [signing messages](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/encryption/t-ecdsa) for more details. -If successful, you should see an output that looks like this: +For production use with real ETH on Ethereum mainnet: ```bash -Deploying: basic_ethereum -Building canisters... -... -Deployed canisters. -URLs: -Candid: - basic_ethereum: https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io/?id= +icp deploy --network ic --argument '(opt record {ethereum_network = opt variant {Mainnet}; ecdsa_key_name = opt variant {ProductionKey1}})' ``` -Your canister is live and ready to use! You can interact with it using either the command line or using the Candid UI, -which is the link you see in the output above. +## Interacting with the deployed canister -In the output above, to see the Candid Web UI for your ethereum canister, you would use the -URL `https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io/?id=`. You should see the methods specified in the Candid file `basic_ethereum.did`. +### Step 1: Get your Ethereum address -## Step 2: Generating an Ethereum address +An Ethereum address is derived from the caller's IC principal via the canister's threshold ECDSA key. The same principal always maps to the same Ethereum address: -An Ethereum address can be derived from an ECDSA public key. To derive a user's specific address, identified on the IC -by a principal, the canister uses its own threshold ECDSA public key to derive a new public key deterministically for -each requested principal. To retrieve your Ethereum address, you can call the `ethereum_address` method on the -previously deployed canister: - -```shell -dfx canister --ic call basic_ethereum ethereum_address +```bash +icp canister call backend ethereum_address '(null)' +# Returns e.g. ("0x378a452B20d1f06008C06c581b1656BdC5313c0C") ``` -This will return an Ethereum address such as `("0x378a452B20d1f06008C06c581b1656BdC5313c0C")` that is tied to your -principal. Your address will be different. You can view such addresses on any Ethereum block explorer such as [Etherscan](https://etherscan.io/). +To get the Ethereum address for a different principal: -If you want to send some ETH to someone else, you can also use the above method to enquire about their Ethereum address -given their IC principal: - -```shell -dfx canister --ic call basic_ethereum ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")' +```bash +icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")' +# Returns e.g. ("0x8d68f7B3cdb40A2E77071077658b01A9EA4B040F") ``` -This will return a different Ethereum address as the one above, such -as `("0x8d68f7B3cdb40A2E77071077658b01A9EA4B040F")`. - -## Step 3: Receiving ETH - -Now that you have your Ethereum address, let us send some (Sepolia) ETH to it: - -1. Get some Sepolia ETH if you don't have any. You can for example use [this faucet](https://www.alchemy.com/faucets/ethereum-sepolia). -2. Send some Sepolia ETH to the address you obtained in the previous step. You can use any Ethereum wallet (e.g., - Metamask) to do so. +You can view these addresses on any Ethereum block explorer such as [Etherscan](https://etherscan.io/). -Once the transaction has at least one confirmation, which can take a few seconds, -you'll be able to see it in your Ethereum address's balance, which should be visible in an Ethereum block explorer, -e.g., https://sepolia.etherscan.io/address/0x378a452b20d1f06008c06c581b1656bdc5313c0c. +### Step 2: Receive ETH (mainnet only) -## Step 4: Sending ETH +1. Get some Sepolia ETH from a faucet, for example [Alchemy's Sepolia faucet](https://www.alchemy.com/faucets/ethereum-sepolia). +2. Send Sepolia ETH to the address from Step 1 using any Ethereum wallet (e.g. MetaMask). +3. Once the transaction has at least one confirmation, verify the balance on [Sepolia Etherscan](https://sepolia.etherscan.io/). -You can send ETH using the `send_eth` endpoint on your canister, specifying an Ethereum destination address and an -amount in the smallest unit (Wei). For example, to send 1 Wei to `0xdd2851Cdd40aE6536831558DD46db62fAc7A844d`, run the following command: +### Step 3: Check your balance (mainnet only) -```shell -dfx canister --ic call basic_ethereum send_eth '("0xdd2851Cdd40aE6536831558DD46db62fAc7A844d", 1)' +```bash +icp canister call backend get_balance '(null)' ``` -The `send_eth` endpoint sends ETH by executing the following steps: +### Step 4: Send ETH (mainnet only) -1. Retrieving the transaction count for the sender's address at `Latest` block height. This is necessary because - Ethereum transactions for a given sender's address are ordered by a `nonce`, which is a monotonically incrementally - increasing non-negative counter. -2. Estimating the current transaction fees. For simplicity, the current gas fees are hard-coded with a generous limit. A - real world application would dynamically fetch the latest transaction fees, for example using - the [`eth_feeHistory`](https://github.com/internet-computer-protocol/evm-rpc-canister/blob/3cce151d4c1338d83e6741afa354ccf11dff41e8/candid/evm_rpc.did#L254) - method in the [EVM-RPC canister](https://github.com/internet-computer-protocol/evm-rpc-canister/tree/main). -3. Building an Ethereum transaction ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)) to send the specified amount - to the given receiver's address. -4. Signing the Ethereum transaction using - the [sign_with_ecdsa API](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/encryption/signing-messages). -5. Sending the signed transaction to the Ethereum network using - the [`eth_sendRawTransaction`](https://github.com/internet-computer-protocol/evm-rpc-canister/blob/3cce151d4c1338d83e6741afa354ccf11dff41e8/candid/evm_rpc.did#L261) - method in the [EVM-RPC canister](https://github.com/internet-computer-protocol/evm-rpc-canister/tree/main). +Send 1 Wei to a destination address: -The `send_eth` endpoint returns the hash of the transaction sent to the Ethereum network, which can for example be used -to track the transaction on an Ethereum blockchain explorer. +```bash +icp canister call backend send_eth '("0xdd2851Cdd40aE6536831558DD46db62fAc7A844d", 1)' +``` -## Conclusion +The `send_eth` endpoint executes the following steps: -In this tutorial, you were able to: +1. Retrieves the transaction count (nonce) for the sender's address at `Latest` block height. Ethereum transactions are ordered by nonce, a monotonically increasing counter. +2. Estimates transaction fees. For simplicity, fees are hard-coded with a generous limit. A production application would dynamically fetch the latest fees via the [`eth_feeHistory`](https://github.com/internet-computer-protocol/evm-rpc-canister/blob/3cce151d4c1338d83e6741afa354ccf11dff41e8/candid/evm_rpc.did#L254) method. +3. Builds an [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) transaction. +4. Signs the transaction using the [sign_with_ecdsa API](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/encryption/t-ecdsa). +5. Sends the signed transaction to the Ethereum network via [`eth_sendRawTransaction`](https://github.com/internet-computer-protocol/evm-rpc-canister/blob/3cce151d4c1338d83e6741afa354ccf11dff41e8/candid/evm_rpc.did#L261) in the EVM RPC canister. -* Deploy a canister smart contract on the ICP blockchain that can receive and send ETH. -* Acquire cycles to deploy the canister to the ICP mainnet. -* Connect the canister to the Ethereum Sepolia testnet. -* Send the canister some Sepolia ETH. -* Use the canister to send ETH to another Ethereum address. +Returns the transaction hash, which can be used to track the transaction on a block explorer. -Additional examples regarding the ICP < > ETH integration can be -found [here](https://internetcomputer.org/docs/current/developer-docs/multi-chain/examples#ethereum--evm-examples). +> **Note:** Due to the replicated nature of HTTPS outcalls, errors such as "transaction already known" or "nonce too low" may be reported even if the transaction was successfully broadcast. Verify by checking Etherscan or confirming that the transaction count for the address increased. ## Security considerations and best practices -If you base your application on this example, we recommend you familiarize yourself with and adhere to -the [security best practices](https://internetcomputer.org/docs/current/references/security/) for developing on the -Internet Computer. This example may not implement all the best practices. +If you base your application on this example, we recommend you familiarize yourself with and adhere to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for developing on the Internet Computer. This example may not implement all the best practices. For example, the following aspects are particularly relevant for this app: -* [Certify query responses if they are relevant for security](https://internetcomputer.org/docs/current/references/security/general-security-best-practices#certify-query-responses-if-they-are-relevant-for-security), - since the app offers a method to read balances, for example. -* [Use a decentralized governance system like SNS to make a canister have a decentralized controller](https://internetcomputer.org/docs/current/references/security/rust-canister-development-security-best-practices#use-a-decentralized-governance-system-like-sns-to-make-a-canister-have-a-decentralized-controller), - since decentralized control may be essential for canisters holding ETH on behalf of users. +- [Certify query responses if they are relevant for security](https://docs.internetcomputer.org/guides/security/overview), since the app offers a method to read balances. +- [Use a decentralized governance system like SNS to make a canister have a decentralized controller](https://docs.internetcomputer.org/guides/security/overview), since decentralized control may be essential for canisters holding ETH on behalf of users. + +Additional examples for the ICP ↔ ETH integration can be found in the [ICP developer docs](https://internetcomputer.org/docs/current/developer-docs/multi-chain/ethereum/overview). diff --git a/rust/basic_ethereum/backend/Cargo.toml b/rust/basic_ethereum/backend/Cargo.toml new file mode 100644 index 0000000000..0c5180067f --- /dev/null +++ b/rust/basic_ethereum/backend/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "backend" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +alloy-consensus = "0.1.3" +alloy-eips = "0.1.3" +alloy-primitives = "0.7.6" +candid = "0.10" +evm-rpc-canister-types = "0.1.2" +# transitive dependency: ic-crypto-ecdsa-secp256k1 -> k256 -> ecdsa -> elliptic-curve -> crypto-bigint -> rand_core -> getrandom +# See https://forum.dfinity.org/t/module-imports-function-wbindgen-describe-from-wbindgen-placeholder-that-is-not-exported-by-the-runtime/11545/8 +getrandom = { version = "*", default-features = false, features = ["custom"] } +ic-cdk = "0.15" +ic-secp256k1 = { git = "https://github.com/dfinity/ic", tag = "release-2025-07-03_03-27-base", package = "ic-secp256k1" } +ic-sha3 = { git = "https://github.com/dfinity/ic", tag = "release-2025-07-03_03-27-base", package = "ic-sha3" } +ic-ethereum-types = { git = "https://github.com/dfinity/ic", tag = "release-2025-07-03_03-27-base", package = "ic-ethereum-types" } +serde = "1.0" +serde_json = "1.0" +serde_bytes = "0.11.15" +num-traits = "0.2.19" +num = "0.4.3" diff --git a/rust/basic_ethereum/src/ecdsa.rs b/rust/basic_ethereum/backend/ecdsa.rs similarity index 100% rename from rust/basic_ethereum/src/ecdsa.rs rename to rust/basic_ethereum/backend/ecdsa.rs diff --git a/rust/basic_ethereum/src/ethereum_wallet.rs b/rust/basic_ethereum/backend/ethereum_wallet.rs similarity index 100% rename from rust/basic_ethereum/src/ethereum_wallet.rs rename to rust/basic_ethereum/backend/ethereum_wallet.rs diff --git a/rust/basic_ethereum/src/lib.rs b/rust/basic_ethereum/backend/lib.rs similarity index 100% rename from rust/basic_ethereum/src/lib.rs rename to rust/basic_ethereum/backend/lib.rs diff --git a/rust/basic_ethereum/src/state.rs b/rust/basic_ethereum/backend/state.rs similarity index 100% rename from rust/basic_ethereum/src/state.rs rename to rust/basic_ethereum/backend/state.rs diff --git a/rust/basic_ethereum/basic_ethereum.did b/rust/basic_ethereum/basic_ethereum.did deleted file mode 100644 index 36ff95e4e5..0000000000 --- a/rust/basic_ethereum/basic_ethereum.did +++ /dev/null @@ -1,57 +0,0 @@ -type InitArg = record { - // The canister will interact with this Ethereum network. - ethereum_network : opt EthereumNetwork; - - // The name of the ECDSA key to use. - ecdsa_key_name : opt EcdsaKeyName; -}; - -type EthereumNetwork = variant { - // The public Ethereum mainnet. - Mainnet; - // The public Ethereum Sepolia testnet. - Sepolia; -}; - -type EcdsaKeyName = variant { - // For local development with `dfx` - TestKeyLocalDevelopment; - // For testing with the Internet Computer's test key. - TestKey1; - // For running the canister in a production environment using the Internet Computer's production key. - ProductionKey1; -}; - -type BlockTag = variant { - Earliest; - Safe; - Finalized; - Latest; - Number : nat; - Pending; -}; - -// Base unit of ETH, i.e., 1 ETH = 10^18 Wei. -type Wei = nat; - -service : (opt InitArg) -> { - // Returns the Ethereum address to which the owner should send ETH - // before sending the amount to another address via the canister using the [send_eth] - // endpoint. - // - // If the owner is not set, it defaults to the caller's principal. - ethereum_address : (owner: opt principal) -> (text); - - // Returns the balance of the given Ethereum address. - // If no address is provided, the address derived from the caller's principal is used. - get_balance : (address: opt text) -> (Wei); - - // Returns the transaction count for the Ethereum address derived for the given principal. - // - // If the owner is not set, it defaults to the caller's principal. - transaction_count : (owner: opt principal, block_height: opt BlockTag) -> (nat); - - // Sends the given amount of ETH in base unit (Wei) to the given Ethereum address. - // Returns the transaction hash - send_eth : (to: text, amount: Wei) -> (text); -} diff --git a/rust/basic_ethereum/dfx.json b/rust/basic_ethereum/dfx.json deleted file mode 100644 index 75f38573a6..0000000000 --- a/rust/basic_ethereum/dfx.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "canisters": { - "basic_ethereum": { - "candid": "basic_ethereum.did", - "package": "basic_ethereum", - "type": "custom", - "build": [ - "cargo build --no-default-features --target wasm32-unknown-unknown --release -p basic_ethereum" - ], - "wasm": "target/wasm32-unknown-unknown/release/basic_ethereum.wasm", - "metadata": [ - { - "name": "candid:service" - } - ] - }, - "evm_rpc": { - "type": "custom", - "candid": "https://github.com/internet-computer-protocol/evm-rpc-canister/releases/download/release-2024-05-23/evm_rpc.did", - "wasm": "https://github.com/internet-computer-protocol/evm-rpc-canister/releases/download/release-2024-05-23/evm_rpc.wasm.gz", - "remote": { - "id": { - "ic": "7hfb6-caaaa-aaaar-qadga-cai" - } - }, - "specified_id": "7hfb6-caaaa-aaaar-qadga-cai", - "init_arg": "(record { nodesInSubnet = 28 })" - } - }, - "defaults": { - "build": { - "args": "", - "packtool": "" - } - }, - "version": 1 -} diff --git a/rust/basic_ethereum/icp.yaml b/rust/basic_ethereum/icp.yaml new file mode 100644 index 0000000000..ae999e12f4 --- /dev/null +++ b/rust/basic_ethereum/icp.yaml @@ -0,0 +1,15 @@ +networks: + - name: local + mode: managed + +canisters: + - name: backend + recipe: + type: "@dfinity/rust@v3.3.0" + + - name: evm_rpc + build: + steps: + - type: pre-built + url: https://github.com/dfinity/evm-rpc-canister/releases/download/v2.2.0/evm_rpc.wasm.gz + init_args: "(record { nodesInSubnet = 28 })" From 4a9e7a5ed4cdb9a8f4a992dda2813b7e5fd2ad98 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 17 Jun 2026 19:00:07 +0200 Subject: [PATCH 02/29] chore(rust/basic_ethereum): test.sh over Makefile, bump CI image to 1.0.1 Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/basic_ethereum.yml | 4 ++-- rust/basic_ethereum/Makefile | 26 -------------------------- rust/basic_ethereum/README.md | 2 +- rust/basic_ethereum/test.sh | 27 +++++++++++++++++++++++++++ 4 files changed, 30 insertions(+), 29 deletions(-) delete mode 100644 rust/basic_ethereum/Makefile create mode 100755 rust/basic_ethereum/test.sh diff --git a/.github/workflows/basic_ethereum.yml b/.github/workflows/basic_ethereum.yml index a67628f1ef..8382913431 100644 --- a/.github/workflows/basic_ethereum.yml +++ b/.github/workflows/basic_ethereum.yml @@ -15,7 +15,7 @@ concurrency: jobs: rust-basic_ethereum: runs-on: ubuntu-24.04 - container: ghcr.io/dfinity/icp-dev-env-rust:1.0.0 + container: ghcr.io/dfinity/icp-dev-env-rust:1.0.1 env: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: @@ -25,4 +25,4 @@ jobs: run: | icp network start -d icp deploy - make test + bash test.sh diff --git a/rust/basic_ethereum/Makefile b/rust/basic_ethereum/Makefile deleted file mode 100644 index b05ae028d6..0000000000 --- a/rust/basic_ethereum/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -.PHONY: test - -# Note: get_balance, transaction_count, and send_eth require live HTTPS outcalls -# to the Ethereum network and cannot be fully tested locally. The tests below -# verify canister deployment and the ethereum_address function, which derives -# an address using threshold ECDSA (available locally). -# For full end-to-end testing, deploy to ICP mainnet with Sepolia testnet. -test: - @echo "=== Test 1: ethereum_address returns a valid 0x-prefixed Ethereum address ===" - @result=$$(icp canister call backend ethereum_address '(null)') && \ - echo "$$result" && \ - echo "$$result" | grep -qE '"0x[0-9a-fA-F]{40}"' && \ - echo "PASS" || (echo "FAIL" && exit 1) - - @echo "=== Test 2: ethereum_address for a specific principal returns a valid address ===" - @result=$$(icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")') && \ - echo "$$result" && \ - echo "$$result" | grep -qE '"0x[0-9a-fA-F]{40}"' && \ - echo "PASS" || (echo "FAIL" && exit 1) - - @echo "=== Test 3: different principals derive different Ethereum addresses ===" - @addr1=$$(icp canister call backend ethereum_address '(null)' | grep -oE '0x[0-9a-fA-F]{40}') && \ - addr2=$$(icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")' | grep -oE '0x[0-9a-fA-F]{40}') && \ - echo "addr1=$$addr1 addr2=$$addr2" && \ - [ "$$addr1" != "$$addr2" ] && \ - echo "PASS" || (echo "FAIL" && exit 1) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index e542b418a4..763f03dba1 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -30,7 +30,7 @@ cd examples/rust/basic_ethereum ```bash icp network start -d icp deploy -make test +bash test.sh icp network stop ``` diff --git a/rust/basic_ethereum/test.sh b/rust/basic_ethereum/test.sh new file mode 100755 index 0000000000..92048f6c14 --- /dev/null +++ b/rust/basic_ethereum/test.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -e + +# Note: get_balance, transaction_count, and send_eth require live HTTPS outcalls +# to the Ethereum network and cannot be fully tested locally. The tests below +# verify canister deployment and the ethereum_address function, which derives +# an address using threshold ECDSA (available locally). +# For full end-to-end testing, deploy to ICP mainnet with Sepolia testnet. + +echo "=== Test 1: ethereum_address returns a valid 0x-prefixed Ethereum address ===" +result=$(icp canister call backend ethereum_address '(null)') && \ + echo "$result" && \ + echo "$result" | grep -qE '"0x[0-9a-fA-F]{40}"' && \ + echo "PASS" || (echo "FAIL" && exit 1) + +echo "=== Test 2: ethereum_address for a specific principal returns a valid address ===" +result=$(icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")') && \ + echo "$result" && \ + echo "$result" | grep -qE '"0x[0-9a-fA-F]{40}"' && \ + echo "PASS" || (echo "FAIL" && exit 1) + +echo "=== Test 3: different principals derive different Ethereum addresses ===" +addr1=$(icp canister call backend ethereum_address '(null)' | grep -oE '0x[0-9a-fA-F]{40}') && \ + addr2=$(icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")' | grep -oE '0x[0-9a-fA-F]{40}') && \ + echo "addr1=$addr1 addr2=$addr2" && \ + [ "$addr1" != "$addr2" ] && \ + echo "PASS" || (echo "FAIL" && exit 1) From 27e2ba767790a4e61571577b71a066963caadc3a Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 13:06:49 +0200 Subject: [PATCH 03/29] fix(rust/basic_ethereum): path=lib.rs, EVM RPC canister via env var, image 1.0.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cargo.toml: add path = "lib.rs" (files are at backend/*.rs not src/) - lib.rs: replace hardcoded mainnet EVM RPC principal with ic_cdk::api::env_var_value("PUBLIC_CANISTER_ID:evm_rpc") — read at runtime as a canister environment variable - icp.yaml: add environments block; local uses auto-injected PUBLIC_CANISTER_ID:evm_rpc, production sets EVM_RPC_CANISTER_ID to the mainnet canister (7hfb6-caaaa-aaaar-qadga-cai) - CI: bump image to icp-dev-env-rust:1.0.1 Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/backend/Cargo.toml | 1 + rust/basic_ethereum/backend/lib.rs | 18 ++++++++++++------ rust/basic_ethereum/icp.yaml | 19 +++++++++++++++---- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/rust/basic_ethereum/backend/Cargo.toml b/rust/basic_ethereum/backend/Cargo.toml index 0c5180067f..de30c39d17 100644 --- a/rust/basic_ethereum/backend/Cargo.toml +++ b/rust/basic_ethereum/backend/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [lib] crate-type = ["cdylib"] +path = "lib.rs" [dependencies] alloy-consensus = "0.1.3" diff --git a/rust/basic_ethereum/backend/lib.rs b/rust/basic_ethereum/backend/lib.rs index 82473c33e9..e9accbe585 100644 --- a/rust/basic_ethereum/backend/lib.rs +++ b/rust/basic_ethereum/backend/lib.rs @@ -17,9 +17,15 @@ use ic_ethereum_types::Address; use num::{BigUint, Num}; use std::str::FromStr; -pub const EVM_RPC_CANISTER_ID: Principal = - Principal::from_slice(b"\x00\x00\x00\x00\x02\x30\x00\xCC\x01\x01"); // 7hfb6-caaaa-aaaar-qadga-cai -pub const EVM_RPC: EvmRpcCanister = EvmRpcCanister(EVM_RPC_CANISTER_ID); +// The EVM RPC canister ID is configured as a canister environment variable: +// local: PUBLIC_CANISTER_ID:evm_rpc injected by icp-cli after deploying the pre-built canister +// production: EVM_RPC_CANISTER_ID = 7hfb6-caaaa-aaaar-qadga-cai (shared mainnet EVM RPC) +// +// See icp.yaml for the environment configuration. +fn evm_rpc_canister() -> EvmRpcCanister { + let id = ic_cdk::api::env_var_value("PUBLIC_CANISTER_ID:evm_rpc"); + EvmRpcCanister(Principal::from_text(&id).expect("invalid EVM_RPC_CANISTER_ID")) +} #[init] pub fn init(maybe_init: Option) { @@ -55,7 +61,7 @@ pub async fn get_balance(address: Option) -> Nat { EthereumNetwork::Sepolia => RpcService::EthSepolia(EthSepoliaService::PublicNode), }; - let (response,) = EVM_RPC + let (response,) = evm_rpc_canister() .request(rpc_service, json, max_response_size_bytes, num_cycles) .await .expect("RPC call failed"); @@ -88,7 +94,7 @@ pub async fn transaction_count(owner: Option, block: Option address: wallet.ethereum_address().to_string(), block: block.unwrap_or(BlockTag::Finalized), }; - let (result,) = EVM_RPC + let (result,) = evm_rpc_canister() .eth_get_transaction_count(rpc_services, None, args.clone(), 2_000_000_000_u128) .await .unwrap_or_else(|e| { @@ -154,7 +160,7 @@ pub async fn send_eth(to: String, amount: Nat) -> String { // For demonstration purposes, the canister uses a single provider to send the signed transaction, // but in production multiple providers (e.g., using a round-robin strategy) should be used to avoid a single point of failure. let single_rpc_service = read_state(|s| s.single_evm_rpc_service()); - let (result,) = EVM_RPC + let (result,) = evm_rpc_canister() .eth_send_raw_transaction( single_rpc_service, None, diff --git a/rust/basic_ethereum/icp.yaml b/rust/basic_ethereum/icp.yaml index ae999e12f4..fb1f73f89e 100644 --- a/rust/basic_ethereum/icp.yaml +++ b/rust/basic_ethereum/icp.yaml @@ -1,7 +1,3 @@ -networks: - - name: local - mode: managed - canisters: - name: backend recipe: @@ -13,3 +9,18 @@ canisters: - type: pre-built url: https://github.com/dfinity/evm-rpc-canister/releases/download/v2.2.0/evm_rpc.wasm.gz init_args: "(record { nodesInSubnet = 28 })" + +# EVM_RPC_CANISTER_ID is injected as a canister environment variable at deploy time. +# Locally, icp-cli injects PUBLIC_CANISTER_ID:evm_rpc automatically after deploying the +# pre-built canister above. On mainnet, the production environment sets it to the +# shared EVM RPC canister (7hfb6-caaaa-aaaar-qadga-cai). +environments: + - name: local + network: local + + - name: production + network: ic + settings: + backend: + environment_variables: + EVM_RPC_CANISTER_ID: "7hfb6-caaaa-aaaar-qadga-cai" From 6f692a0615fcaee3c06b409219aa0a0557483bfb Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 13:35:29 +0200 Subject: [PATCH 04/29] feat(rust/basic_ethereum): migrate to evm_rpc_types + evm_rpc_client Replace evm-rpc-canister-types (ic-cdk 0.14 conflict) with: - evm_rpc_types 3.1.1: Candid types for EVM RPC canister - evm_rpc_client 0.4.0: high-level client with automatic cycle management transaction_count: raw inter-canister call using evm_rpc_types + ic_cdk transaction_count_with_client: uses EvmRpcClient for automatic cycle management, consensus across providers, and cleaner API Also update rust-toolchain.toml to 1.88 (required by ic-cdk 0.20) and fix ic_cdk API changes: caller() -> msg_caller(), tuple error unwrapping -> single error, EcdsaPublicKeyResponse -> EcdsaPublicKeyResult. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/Cargo.lock | 1631 +++-------------- rust/basic_ethereum/backend/Cargo.toml | 7 +- rust/basic_ethereum/backend/ecdsa.rs | 2 +- .../basic_ethereum/backend/ethereum_wallet.rs | 6 +- rust/basic_ethereum/backend/lib.rs | 142 +- rust/basic_ethereum/backend/state.rs | 14 +- rust/basic_ethereum/rust-toolchain.toml | 2 +- 7 files changed, 415 insertions(+), 1389 deletions(-) diff --git a/rust/basic_ethereum/Cargo.lock b/rust/basic_ethereum/Cargo.lock index 34fcad71e9..09bd96c5b3 100644 --- a/rust/basic_ethereum/Cargo.lock +++ b/rust/basic_ethereum/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aho-corasick" version = "1.1.3" @@ -102,12 +87,6 @@ version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" -[[package]] -name = "arbitrary" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" - [[package]] name = "ark-ff" version = "0.3.0" @@ -138,7 +117,7 @@ dependencies = [ "ark-std 0.4.0", "derivative", "digest 0.10.7", - "itertools 0.10.5", + "itertools", "num-bigint", "num-traits", "paste", @@ -245,20 +224,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] -name = "ascii-canvas" -version = "3.0.0" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ - "term", + "proc-macro2", + "quote", + "syn 2.0.104", ] -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "auto_impl" version = "1.3.0" @@ -276,45 +251,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" - [[package]] name = "backend" version = "0.1.0" @@ -323,9 +259,12 @@ dependencies = [ "alloy-eips", "alloy-primitives", "candid", - "evm-rpc-canister-types", + "evm_rpc_client", + "evm_rpc_types", "getrandom 0.2.16", - "ic-cdk 0.15.2", + "ic-canister-runtime", + "ic-cdk", + "ic-cdk-management-canister", "ic-ethereum-types", "ic-secp256k1", "ic-sha3 1.0.0 (git+https://github.com/dfinity/ic?tag=release-2025-07-03_03-27-base)", @@ -337,10 +276,22 @@ dependencies = [ ] [[package]] -name = "beef" -version = "0.5.2" +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64ct" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "binread" @@ -365,30 +316,15 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec 0.6.3", -] - [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec 0.8.0", + "bit-vec", ] -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bit-vec" version = "0.8.0" @@ -434,12 +370,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - [[package]] name = "byte-slice-cast" version = "1.2.3" @@ -475,9 +405,9 @@ dependencies = [ [[package]] name = "candid" -version = "0.10.14" +version = "0.10.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d90f5a1426d0489283a0bd5da9ed406fb3e69597e0d823dcb88a1965bb58d2" +checksum = "f8f781afa4a1303e3eab4ada0720a874942bcfa936ce01b816ac6378945c43a9" dependencies = [ "anyhow", "binread", @@ -498,9 +428,9 @@ dependencies = [ [[package]] name = "candid_derive" -version = "0.6.6" +version = "0.10.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de398570c386726e7a59d9887b68763c481477f9a043fb998a2e09d428df1a9" +checksum = "ad6ae8e7944dd0035651bc0e7b3a3e4cb16f5fc43f8ae4fd76b36ff2cd52759f" dependencies = [ "lazy_static", "proc-macro2", @@ -509,22 +439,30 @@ dependencies = [ ] [[package]] -name = "candid_parser" -version = "0.1.4" +name = "canlog" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48a3da76f989cd350b7342c64c6c6008341bb6186f6832ef04e56dc50ba0fd76" +checksum = "bd21b4c7140d033fe006495a65ec5ae24d79219eca550a74e0ba140ff9aa71dc" dependencies = [ - "anyhow", "candid", - "codespan-reporting", - "convert_case 0.6.0", - "hex", - "lalrpop", - "lalrpop-util", - "logos", - "num-bigint", - "pretty", - "thiserror 1.0.69", + "canlog_derive", + "ic-canister-log", + "ic0", + "regex", + "serde", + "serde_json", +] + +[[package]] +name = "canlog_derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6620814f83b784938b29ca6ca8867967a93a4f7f08c1b7ec6256d12c1b53abe8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] @@ -542,16 +480,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "const-hex" version = "1.14.1" @@ -597,31 +525,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - [[package]] name = "cpufeatures" version = "0.2.17" @@ -668,6 +571,75 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.104", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.104", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.104", +] + [[package]] name = "data-encoding" version = "2.9.0" @@ -711,7 +683,7 @@ version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ - "convert_case 0.4.0", + "convert_case", "proc-macro2", "quote", "rustc_version 0.4.1", @@ -739,27 +711,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "displaydoc" version = "0.2.5" @@ -811,24 +762,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ena" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" -dependencies = [ - "log", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -846,18 +779,36 @@ dependencies = [ ] [[package]] -name = "evm-rpc-canister-types" -version = "0.1.3" +name = "evm_rpc_client" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43365151c7779a57aacf1e4370519c9c1250fd09ca6c02c77e6b6f7a9335052c" +checksum = "023af59e299f1591daf2ac441129ba338fc9c6759c884bd05e222a99ffc19c9c" dependencies = [ + "async-trait", "candid", - "candid_parser", - "ic-cdk 0.14.2", - "reqwest", + "evm_rpc_types", + "ic-canister-runtime", "serde", - "serde_bytes", "serde_json", + "strum 0.27.2", +] + +[[package]] +name = "evm_rpc_types" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c348e834a97e2ca221259d6ec813009346904860be95cf756123630c44b79d5c" +dependencies = [ + "candid", + "canlog", + "hex", + "ic-error-types", + "ic-management-canister-types 0.5.0", + "num-bigint", + "serde", + "strum 0.27.2", + "thiserror 2.0.18", + "url", ] [[package]] @@ -910,33 +861,12 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -952,56 +882,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1036,12 +916,6 @@ dependencies = [ "wasi 0.14.2+wasi-0.2.4", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" version = "0.3.2" @@ -1059,31 +933,18 @@ dependencies = [ "subtle", ] -[[package]] -name = "h2" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.5.2" @@ -1115,195 +976,126 @@ dependencies = [ ] [[package]] -name = "http" -version = "1.3.1" +name = "ic-canister-log" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "cb82c4f617ecff6e452fe65af0489626ec7330ffe3eedd9ea14e6178eea48d1a" dependencies = [ - "bytes", - "fnv", - "itoa", + "serde", ] [[package]] -name = "http-body" -version = "1.0.1" +name = "ic-canister-runtime" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "da5bf7cb1426e5485da0e2b0b8da4f11b7b24a2a56cb127b53720ecf8495c2e0" dependencies = [ - "bytes", - "http", + "async-trait", + "candid", + "ic-cdk", + "ic-cdk-management-canister", + "ic-error-types", + "serde", + "thiserror 2.0.18", ] [[package]] -name = "http-body-util" -version = "0.1.3" +name = "ic-cdk" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "6a7971f4983db147afbbc4e7f87f60b09fcd60ac707af37ff3d2468dcddbf551" dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", + "candid", + "ic-cdk-executor", + "ic-cdk-macros", + "ic-error-types", + "ic0", "pin-project-lite", + "serde", + "thiserror 2.0.18", ] [[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.6.0" +name = "ic-cdk-executor" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "33716b730ded33690b8a704bff3533fda87d229e58046823647d28816e9bcee7" dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", + "ic0", + "slotmap", "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", ] [[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.15" +name = "ic-cdk-macros" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "a7c20c002200c720958f321bb78b4d4987fc2c380bf9ef69ed4404730810f45f" dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry", + "candid", + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] -name = "ic-cdk" -version = "0.14.2" +name = "ic-cdk-management-canister" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae86da27ed9cebcfc7889a9f5cec5bdf52b4dcb9e2e6f1baf823f291eeb36279" +checksum = "92c1319a274caebf0ab70ab826b8905c29e8563498289356b9a59464f2a85c56" dependencies = [ "candid", - "ic-cdk-executor", - "ic-cdk-macros 0.14.0", - "ic0", + "ic-cdk", + "ic-management-canister-types 0.7.1", "serde", "serde_bytes", + "thiserror 2.0.18", ] [[package]] -name = "ic-cdk" -version = "0.15.2" +name = "ic-error-types" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3234b25809a51792d33a8ac3859cc72881db7478055cdcb25fc0faf5741b0413" +checksum = "bbeeb3d91aa179d6496d7293becdacedfc413c825cac79fd54ea1906f003ee55" dependencies = [ - "candid", - "ic-cdk-executor", - "ic-cdk-macros 0.15.0", - "ic0", "serde", - "serde_bytes", + "strum 0.26.3", + "strum_macros 0.26.4", ] [[package]] -name = "ic-cdk-executor" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903057edd3d4ff4b3fe44a64eaee1ceb73f579ba29e3ded372b63d291d7c16c2" - -[[package]] -name = "ic-cdk-macros" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01dc6bc425ec048d6ac4137c7c0f2cfbd6f8b0be8efc568feae2b265f566117c" +name = "ic-ethereum-types" +version = "1.0.0" +source = "git+https://github.com/dfinity/ic?tag=release-2025-07-03_03-27-base#e915efecc8af90993ccfc499721ebe826aadba60" dependencies = [ - "candid", - "proc-macro2", - "quote", + "hex", + "ic-sha3 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "minicbor", + "minicbor-derive", "serde", - "serde_tokenstream", - "syn 2.0.104", ] [[package]] -name = "ic-cdk-macros" -version = "0.15.0" +name = "ic-management-canister-types" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af44fb4ec3a4b18831c9d3303ca8fa2ace846c4022d50cb8df4122635d3782e" +checksum = "3149217e24186df3f13dc45eee14cdb3e5cad07d0b2b67bd53555c1c55462957" dependencies = [ "candid", - "proc-macro2", - "quote", "serde", - "serde_tokenstream", - "syn 2.0.104", + "serde_bytes", ] [[package]] -name = "ic-ethereum-types" -version = "1.0.0" -source = "git+https://github.com/dfinity/ic?tag=release-2025-07-03_03-27-base#e915efecc8af90993ccfc499721ebe826aadba60" +name = "ic-management-canister-types" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51705516ed4d23f24e8d714a70fe9d7ec17106cfd830d5434a1b29f583ef70ee" dependencies = [ - "hex", - "ic-sha3 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "minicbor", - "minicbor-derive", + "candid", "serde", + "serde_bytes", ] [[package]] @@ -1342,9 +1134,9 @@ dependencies = [ [[package]] name = "ic0" -version = "0.23.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" +checksum = "c77c8932bff1f09502d0d8c079d5a206a06afe523e35e816162cf4d30b5bf80d" [[package]] name = "ic_principal" @@ -1352,7 +1144,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1762deb6f7c8d8c2bdee4b6c5a47b60195b74e9b5280faa5ba29692f8e17429c" dependencies = [ - "arbitrary", "crc32fast", "data-encoding", "serde", @@ -1446,6 +1237,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -1497,33 +1294,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "io-uring" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "itertools" version = "0.10.5" @@ -1533,31 +1303,12 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - [[package]] name = "k256" version = "0.13.4" @@ -1591,37 +1342,6 @@ dependencies = [ "sha3-asm", ] -[[package]] -name = "lalrpop" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" -dependencies = [ - "ascii-canvas", - "bit-set 0.5.3", - "ena", - "itertools 0.11.0", - "lalrpop-util", - "petgraph", - "pico-args", - "regex", - "regex-syntax 0.8.5", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", - "walkdir", -] - -[[package]] -name = "lalrpop-util" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" -dependencies = [ - "regex-automata", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -1646,16 +1366,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" -[[package]] -name = "libredox" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" -dependencies = [ - "bitflags", - "libc", -] - [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -1668,66 +1378,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" -[[package]] -name = "lock_api" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "logos" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1" -dependencies = [ - "logos-derive", -] - -[[package]] -name = "logos-codegen" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68" -dependencies = [ - "beef", - "fnv", - "proc-macro2", - "quote", - "regex-syntax 0.6.29", - "syn 2.0.104", -] - -[[package]] -name = "logos-derive" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e" -dependencies = [ - "logos-codegen", -] - [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "minicbor" version = "0.19.1" @@ -1748,49 +1404,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", -] - -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - [[package]] name = "num" version = "0.4.3" @@ -1882,65 +1495,12 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "openssl" -version = "0.10.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -1970,33 +1530,10 @@ dependencies = [ ] [[package]] -name = "parking_lot" -version = "0.12.4" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pem" @@ -2004,7 +1541,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" dependencies = [ - "base64 0.13.1", + "base64", ] [[package]] @@ -2029,46 +1566,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror 2.0.12", + "thiserror 2.0.18", "ucd-trie", ] -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkcs8" @@ -2080,12 +1586,6 @@ dependencies = [ "spki", ] -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - [[package]] name = "potential_utf" version = "0.1.2" @@ -2110,12 +1610,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - [[package]] name = "pretty" version = "0.12.4" @@ -2149,9 +1643,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -2162,15 +1656,15 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ - "bit-set 0.8.0", - "bit-vec 0.8.0", + "bit-set", + "bit-vec", "bitflags", "lazy_static", "num-traits", "rand 0.9.1", "rand_chacha 0.9.0", "rand_xorshift", - "regex-syntax 0.8.5", + "regex-syntax", "rusty-fork", "tempfile", "unarray", @@ -2280,26 +1774,6 @@ dependencies = [ "rand_core 0.9.3", ] -[[package]] -name = "redox_syscall" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 1.0.69", -] - [[package]] name = "regex" version = "1.11.1" @@ -2309,7 +1783,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] @@ -2320,63 +1794,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "reqwest" -version = "0.12.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "rfc6979" version = "0.4.0" @@ -2387,20 +1813,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - [[package]] name = "rlp" version = "0.5.2" @@ -2444,12 +1856,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" -[[package]] -name = "rustc-demangle" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" - [[package]] name = "rustc-hex" version = "2.1.0" @@ -2487,39 +1893,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "rustls" -version = "0.23.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.21" @@ -2544,30 +1917,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "sec1" version = "0.7.3" @@ -2582,29 +1931,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "0.11.0" @@ -2670,30 +1996,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_tokenstream" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.104", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha2" version = "0.10.9" @@ -2749,21 +2051,18 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.12", + "thiserror 2.0.18", "time", ] [[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - -[[package]] -name = "slab" -version = "0.4.10" +name = "slotmap" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] [[package]] name = "smallvec" @@ -2771,16 +2070,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "spki" version = "0.7.3" @@ -2817,15 +2106,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "string_cache" -version = "0.8.9" +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros 0.27.2", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared", - "precomputed-hash", + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.104", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] @@ -2856,15 +2182,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - [[package]] name = "synstructure" version = "0.13.2" @@ -2876,27 +2193,6 @@ dependencies = [ "syn 2.0.104", ] -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tap" version = "1.0.1" @@ -2916,26 +2212,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -2947,11 +2223,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.18", ] [[package]] @@ -2967,9 +2243,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -3035,56 +2311,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tokio" -version = "1.46.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" -dependencies = [ - "backtrace", - "bytes", - "io-uring", - "libc", - "mio", - "pin-project-lite", - "slab", - "socket2", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "toml_datetime" version = "0.6.11" @@ -3102,76 +2328,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "typed-arena" version = "2.0.2" @@ -3214,12 +2370,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - [[package]] name = "unicode-width" version = "0.1.14" @@ -3232,12 +2382,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - [[package]] name = "url" version = "2.5.4" @@ -3261,12 +2405,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" @@ -3282,25 +2420,6 @@ dependencies = [ "libc", ] -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3316,162 +2435,6 @@ dependencies = [ "wit-bindgen-rt", ] -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.104", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.59.0" diff --git a/rust/basic_ethereum/backend/Cargo.toml b/rust/basic_ethereum/backend/Cargo.toml index de30c39d17..fd2973326f 100644 --- a/rust/basic_ethereum/backend/Cargo.toml +++ b/rust/basic_ethereum/backend/Cargo.toml @@ -12,11 +12,14 @@ alloy-consensus = "0.1.3" alloy-eips = "0.1.3" alloy-primitives = "0.7.6" candid = "0.10" -evm-rpc-canister-types = "0.1.2" +evm_rpc_types = "3.1.1" +evm_rpc_client = "0.4.0" +ic-canister-runtime = "0.2" # transitive dependency: ic-crypto-ecdsa-secp256k1 -> k256 -> ecdsa -> elliptic-curve -> crypto-bigint -> rand_core -> getrandom # See https://forum.dfinity.org/t/module-imports-function-wbindgen-describe-from-wbindgen-placeholder-that-is-not-exported-by-the-runtime/11545/8 getrandom = { version = "*", default-features = false, features = ["custom"] } -ic-cdk = "0.15" +ic-cdk = "0.20" +ic-cdk-management-canister = "0.1.1" ic-secp256k1 = { git = "https://github.com/dfinity/ic", tag = "release-2025-07-03_03-27-base", package = "ic-secp256k1" } ic-sha3 = { git = "https://github.com/dfinity/ic", tag = "release-2025-07-03_03-27-base", package = "ic-sha3" } ic-ethereum-types = { git = "https://github.com/dfinity/ic", tag = "release-2025-07-03_03-27-base", package = "ic-ethereum-types" } diff --git a/rust/basic_ethereum/backend/ecdsa.rs b/rust/basic_ethereum/backend/ecdsa.rs index f6685b2e14..f9e403b4ec 100644 --- a/rust/basic_ethereum/backend/ecdsa.rs +++ b/rust/basic_ethereum/backend/ecdsa.rs @@ -1,4 +1,4 @@ -use ic_cdk::api::management_canister::ecdsa::EcdsaPublicKeyResponse; +use ic_cdk_management_canister::EcdsaPublicKeyResult as EcdsaPublicKeyResponse; use ic_secp256k1::{PublicKey, DerivationPath}; use ic_ethereum_types::Address; diff --git a/rust/basic_ethereum/backend/ethereum_wallet.rs b/rust/basic_ethereum/backend/ethereum_wallet.rs index 6fe940af1b..42c9726a86 100644 --- a/rust/basic_ethereum/backend/ethereum_wallet.rs +++ b/rust/basic_ethereum/backend/ethereum_wallet.rs @@ -37,12 +37,12 @@ impl EthereumWallet { } pub async fn sign_with_ecdsa(&self, message_hash: [u8; 32]) -> ([u8; 64], RecoveryId) { - use ic_cdk::api::management_canister::ecdsa::SignWithEcdsaArgument; + use ic_cdk_management_canister::{sign_with_ecdsa, SignWithEcdsaArgs}; let derivation_path = derivation_path(&self.owner); let key_id = read_state(|s| s.ecdsa_key_id()); - let (result,) = - ic_cdk::api::management_canister::ecdsa::sign_with_ecdsa(SignWithEcdsaArgument { + let result = + sign_with_ecdsa(&SignWithEcdsaArgs { message_hash: message_hash.to_vec(), derivation_path, key_id, diff --git a/rust/basic_ethereum/backend/lib.rs b/rust/basic_ethereum/backend/lib.rs index e9accbe585..b9e0473d4d 100644 --- a/rust/basic_ethereum/backend/lib.rs +++ b/rust/basic_ethereum/backend/lib.rs @@ -7,11 +7,11 @@ use crate::state::{init_state, read_state}; use alloy_consensus::{SignableTransaction, TxEip1559, TxEnvelope}; use alloy_primitives::{hex, Signature, TxKind, U256}; use candid::{CandidType, Deserialize, Nat, Principal}; -use evm_rpc_canister_types::{ - BlockTag, EthMainnetService, EthSepoliaService, EvmRpcCanister, GetTransactionCountArgs, - GetTransactionCountResult, MultiGetTransactionCountResult, RequestResult, RpcService, +use evm_rpc_types::{ + BlockTag, EthMainnetService, EthSepoliaService, GetTransactionCountArgs, Hex20, + MultiRpcResult, Nat256, RpcService, }; -use ic_cdk::api::management_canister::ecdsa::{EcdsaCurve, EcdsaKeyId}; +use ic_cdk_management_canister::{EcdsaCurve, EcdsaKeyId}; use ic_cdk::{init, update}; use ic_ethereum_types::Address; use num::{BigUint, Num}; @@ -22,9 +22,9 @@ use std::str::FromStr; // production: EVM_RPC_CANISTER_ID = 7hfb6-caaaa-aaaar-qadga-cai (shared mainnet EVM RPC) // // See icp.yaml for the environment configuration. -fn evm_rpc_canister() -> EvmRpcCanister { +fn evm_rpc_id() -> Principal { let id = ic_cdk::api::env_var_value("PUBLIC_CANISTER_ID:evm_rpc"); - EvmRpcCanister(Principal::from_text(&id).expect("invalid EVM_RPC_CANISTER_ID")) + Principal::from_text(&id).expect("invalid EVM_RPC_CANISTER_ID") } #[init] @@ -61,13 +61,17 @@ pub async fn get_balance(address: Option) -> Nat { EthereumNetwork::Sepolia => RpcService::EthSepolia(EthSepoliaService::PublicNode), }; - let (response,) = evm_rpc_canister() - .request(rpc_service, json, max_response_size_bytes, num_cycles) + use evm_rpc_types::RpcResult; + let (response,): (RpcResult,) = ic_cdk::call::Call::bounded_wait(evm_rpc_id(), "request") + .with_args(&(rpc_service, json, max_response_size_bytes)) + .with_cycles(num_cycles) .await - .expect("RPC call failed"); + .expect("RPC call failed") + .candid_tuple() + .expect("failed to decode response"); let hex_balance = match response { - RequestResult::Ok(balance_result) => { + Ok(balance_result) => { // The response to a successful `eth_getBalance` call has the following format: // { "id": "[ID]", "jsonrpc": "2.0", "result": "[BALANCE IN HEX]" } let response: serde_json::Value = serde_json::from_str(&balance_result).unwrap(); @@ -77,7 +81,7 @@ pub async fn get_balance(address: Option) -> Nat { .unwrap() .to_string() } - RequestResult::Err(e) => panic!("Received an error response: {:?}", e), + Err(e) => panic!("Received an error response: {:?}", e), }; // Remove the "0x" prefix before converting to a decimal number. @@ -90,32 +94,86 @@ pub async fn transaction_count(owner: Option, block: Option let owner = owner.unwrap_or(caller); let wallet = EthereumWallet::new(owner).await; let rpc_services = read_state(|s| s.evm_rpc_services()); + let address: Hex20 = wallet + .ethereum_address() + .to_string() + .parse() + .expect("failed to parse ethereum address"); let args = GetTransactionCountArgs { - address: wallet.ethereum_address().to_string(), + address, block: block.unwrap_or(BlockTag::Finalized), }; - let (result,) = evm_rpc_canister() - .eth_get_transaction_count(rpc_services, None, args.clone(), 2_000_000_000_u128) - .await - .unwrap_or_else(|e| { - panic!( - "failed to get transaction count for {:?}, error: {:?}", - args, e - ) - }); + let (result,): (MultiRpcResult,) = + ic_cdk::call::Call::bounded_wait(evm_rpc_id(), "eth_getTransactionCount") + .with_args(&(rpc_services, Option::::None, args.clone())) + .with_cycles(2_000_000_000_u128) + .await + .unwrap_or_else(|e| { + panic!( + "failed to get transaction count for {:?}, error: {:?}", + args, e + ) + }) + .candid_tuple() + .expect("failed to decode response"); + match result { - MultiGetTransactionCountResult::Consistent(consistent_result) => match consistent_result { - GetTransactionCountResult::Ok(count) => count, - GetTransactionCountResult::Err(error) => { - ic_cdk::trap(&format!("failed to get transaction count for {:?}, error: {:?}",args, error)) + MultiRpcResult::Consistent(consistent_result) => match consistent_result { + Ok(count) => Nat(count.as_ref().0.clone()), + Err(error) => { + ic_cdk::trap(&format!("failed to get transaction count for {:?}, error: {:?}", args, error)) } }, - MultiGetTransactionCountResult::Inconsistent(inconsistent_results) => { + MultiRpcResult::Inconsistent(inconsistent_results) => { ic_cdk::trap(&format!("inconsistent results when retrieving transaction count for {:?}. Received results: {:?}", args, inconsistent_results)) } } } +/// Demonstrates the high-level `EvmRpcClient` pattern: no manual cycle amounts, automatic +/// consensus across providers, and a cleaner API surface compared to the raw inter-canister +/// call used in `transaction_count`. +#[update] +pub async fn transaction_count_with_client(owner: Option, block: Option) -> Nat { + let caller = validate_caller_not_anonymous(); + let owner = owner.unwrap_or(caller); + let wallet = EthereumWallet::new(owner).await; + let rpc_services = read_state(|s| s.evm_rpc_services()); + + let address: Hex20 = wallet + .ethereum_address() + .to_string() + .parse() + .expect("failed to parse ethereum address"); + let block_tag = block.unwrap_or(BlockTag::Finalized); + + let canister_id = evm_rpc_id(); + let client = evm_rpc_client::EvmRpcClient::builder( + ic_canister_runtime::IcRuntime::new(), + canister_id, + ) + .with_rpc_sources(rpc_services) + .build(); + + let result: MultiRpcResult = client + .get_transaction_count((address, block_tag)) + .send() + .await; + + match result { + MultiRpcResult::Consistent(Ok(count)) => Nat(count.as_ref().0.clone()), + MultiRpcResult::Consistent(Err(error)) => { + ic_cdk::trap(&format!("failed to get transaction count, error: {:?}", error)) + } + MultiRpcResult::Inconsistent(inconsistent_results) => { + ic_cdk::trap(&format!( + "inconsistent results when retrieving transaction count. Received results: {:?}", + inconsistent_results + )) + } + } +} + #[update] pub async fn send_eth(to: String, amount: Nat) -> String { use alloy_eips::eip2718::Encodable2718; @@ -160,20 +218,22 @@ pub async fn send_eth(to: String, amount: Nat) -> String { // For demonstration purposes, the canister uses a single provider to send the signed transaction, // but in production multiple providers (e.g., using a round-robin strategy) should be used to avoid a single point of failure. let single_rpc_service = read_state(|s| s.single_evm_rpc_service()); - let (result,) = evm_rpc_canister() - .eth_send_raw_transaction( - single_rpc_service, - None, - raw_transaction_hex.clone(), - 2_000_000_000_u128, - ) - .await - .unwrap_or_else(|e| { - panic!( - "failed to send raw transaction {}, error: {:?}", - raw_transaction_hex, e - ) - }); + + use evm_rpc_types::{MultiRpcResult as SendResult, SendRawTransactionStatus}; + let (result,): (SendResult,) = + ic_cdk::call::Call::bounded_wait(evm_rpc_id(), "eth_sendRawTransaction") + .with_args(&(single_rpc_service, Option::::None, raw_transaction_hex.clone())) + .with_cycles(2_000_000_000_u128) + .await + .unwrap_or_else(|e| { + panic!( + "failed to send raw transaction {}, error: {:?}", + raw_transaction_hex, e + ) + }) + .candid_tuple() + .expect("failed to decode response"); + ic_cdk::println!( "Result of sending raw transaction {}: {:?}. \ Due to the replicated nature of HTTPs outcalls, an error such as transaction already known or nonce too low could be reported, \ @@ -244,7 +304,7 @@ impl From<&EcdsaKeyName> for EcdsaKeyId { } pub fn validate_caller_not_anonymous() -> Principal { - let principal = ic_cdk::caller(); + let principal = ic_cdk::api::msg_caller(); if principal == Principal::anonymous() { panic!("anonymous principal is not allowed"); } diff --git a/rust/basic_ethereum/backend/state.rs b/rust/basic_ethereum/backend/state.rs index 73efb8e35f..65b62ad2b0 100644 --- a/rust/basic_ethereum/backend/state.rs +++ b/rust/basic_ethereum/backend/state.rs @@ -1,7 +1,7 @@ use crate::ecdsa::EcdsaPublicKey; use crate::{EcdsaKeyName, EthereumNetwork, InitArg}; -use evm_rpc_canister_types::{EthMainnetService, EthSepoliaService, RpcServices}; -use ic_cdk::api::management_canister::ecdsa::EcdsaKeyId; +use evm_rpc_types::{EthMainnetService, EthSepoliaService, RpcServices}; +use ic_cdk_management_canister::EcdsaKeyId; use std::cell::RefCell; use std::ops::{Deref, DerefMut}; @@ -70,22 +70,22 @@ impl From for State { } pub async fn lazy_call_ecdsa_public_key() -> EcdsaPublicKey { - use ic_cdk::api::management_canister::ecdsa::{ecdsa_public_key, EcdsaPublicKeyArgument}; + use ic_cdk_management_canister::{ecdsa_public_key, EcdsaPublicKeyArgs as EcdsaPublicKeyArgument}; if let Some(ecdsa_pk) = read_state(|s| s.ecdsa_public_key.clone()) { return ecdsa_pk; } let key_id = read_state(|s| s.ecdsa_key_id()); - let (response,) = ecdsa_public_key(EcdsaPublicKeyArgument { + let response = ecdsa_public_key(&EcdsaPublicKeyArgument { canister_id: None, derivation_path: vec![], key_id, }) .await - .unwrap_or_else(|(error_code, message)| { + .unwrap_or_else(|e| { ic_cdk::trap(&format!( - "failed to get canister's public key: {} (error code = {:?})", - message, error_code, + "failed to get canister's public key: {:?}", + e, )) }); let pk = EcdsaPublicKey::from(response); diff --git a/rust/basic_ethereum/rust-toolchain.toml b/rust/basic_ethereum/rust-toolchain.toml index d4f7c253a4..b863946e6e 100644 --- a/rust/basic_ethereum/rust-toolchain.toml +++ b/rust/basic_ethereum/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.85" +channel = "1.88" targets = ["wasm32-unknown-unknown"] components = ["rustfmt", "clippy"] From 2889518e83c358b36b4f769a6a164a93da4835e4 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 13:35:50 +0200 Subject: [PATCH 05/29] fix(rust/basic_ethereum): remove pinned Rust channel from rust-toolchain.toml The dev-env image already pins the toolchain; a per-example pin is redundant and risks drifting out of sync. Matches the pattern used in all other migrated Rust examples. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/rust-toolchain.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/rust/basic_ethereum/rust-toolchain.toml b/rust/basic_ethereum/rust-toolchain.toml index b863946e6e..990104f055 100644 --- a/rust/basic_ethereum/rust-toolchain.toml +++ b/rust/basic_ethereum/rust-toolchain.toml @@ -1,4 +1,2 @@ [toolchain] -channel = "1.88" targets = ["wasm32-unknown-unknown"] -components = ["rustfmt", "clippy"] From 7e548c7a5caa30f24daddeabc68bb8dd85bd6b65 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 13:39:08 +0200 Subject: [PATCH 06/29] fix(rust/basic_ethereum): add export_candid!(); bump EVM RPC to v2.8.0 - ic_cdk::export_candid!() was missing, causing candid-extractor to fail with "get_candid_pointer not found" - Bump pre-built EVM RPC canister from v2.2.0 to v2.8.0 Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/backend/lib.rs | 2 ++ rust/basic_ethereum/icp.yaml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/basic_ethereum/backend/lib.rs b/rust/basic_ethereum/backend/lib.rs index b9e0473d4d..b57c9738f4 100644 --- a/rust/basic_ethereum/backend/lib.rs +++ b/rust/basic_ethereum/backend/lib.rs @@ -329,3 +329,5 @@ fn nat_to_u256(value: Nat) -> U256 { value_u256[32 - value_bytes.len()..].copy_from_slice(&value_bytes); U256::from_be_bytes(value_u256) } + +ic_cdk::export_candid!(); diff --git a/rust/basic_ethereum/icp.yaml b/rust/basic_ethereum/icp.yaml index fb1f73f89e..acff8a4aac 100644 --- a/rust/basic_ethereum/icp.yaml +++ b/rust/basic_ethereum/icp.yaml @@ -7,7 +7,7 @@ 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/v2.8.0/evm_rpc.wasm.gz init_args: "(record { nodesInSubnet = 28 })" # EVM_RPC_CANISTER_ID is injected as a canister environment variable at deploy time. From 2531cbb0080fa1e0606e8338b1cb45a02869771b Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 13:41:03 +0200 Subject: [PATCH 07/29] fix(rust/basic_ethereum): correct EVM RPC v2.8.0 download URL (tag is evm_rpc-v2.8.0) --- rust/basic_ethereum/icp.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/basic_ethereum/icp.yaml b/rust/basic_ethereum/icp.yaml index acff8a4aac..d779327b41 100644 --- a/rust/basic_ethereum/icp.yaml +++ b/rust/basic_ethereum/icp.yaml @@ -7,7 +7,7 @@ canisters: build: steps: - type: pre-built - url: https://github.com/dfinity/evm-rpc-canister/releases/download/v2.8.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 { nodesInSubnet = 28 })" # EVM_RPC_CANISTER_ID is injected as a canister environment variable at deploy time. From 0b059930a2245a70f2951e27396b9da626944a8e Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 13:58:15 +0200 Subject: [PATCH 08/29] improve(rust/basic_ethereum): HTTPS outcalls work locally; expand tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The local icp-cli network supports real HTTPS outcalls, so get_balance and transaction_count work against live Ethereum Sepolia data locally. test.sh: - Test 4: get_balance with canister's own Sepolia address — proves the EVM RPC canister's HTTPS outcall to PublicNode/Ankr works - Test 5: transaction_count_with_client — demonstrates EvmRpcClient high-level API vs raw inter-canister call in Test 4 README: - Remove "mainnet only" notes for get_balance and transaction_count - Explain local HTTPS outcall support and what test.sh covers - Document send_eth flow with Alchemy Sepolia faucet - Fix security links to specific anchors Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/README.md | 66 ++++++++++++++++------------------- rust/basic_ethereum/test.sh | 45 ++++++++++++++---------- 2 files changed, 58 insertions(+), 53 deletions(-) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index 763f03dba1..c52a714fad 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -6,16 +6,15 @@ This example demonstrates how to deploy a canister smart contract on the Interne This example internally leverages: - [Threshold ECDSA](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/encryption/t-ecdsa): Each user's Ethereum address is derived deterministically from the canister's master ECDSA key using a derivation path based on the user's IC principal. This means each user has a unique, stable Ethereum address controlled by the canister. -- [HTTPS outcalls](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/advanced-features/https-outcalls/https-outcalls-overview): The canister communicates with the Ethereum network via the [EVM RPC canister](https://github.com/internet-computer-protocol/evm-rpc-canister/tree/main) (canister ID `7hfb6-caaaa-aaaar-qadga-cai` on ICP mainnet). +- [HTTPS outcalls](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/advanced-features/https-outcalls/https-outcalls-overview): The canister communicates with the Ethereum network via the [EVM RPC canister](https://github.com/dfinity/evm-rpc-canister) (canister ID `7hfb6-caaaa-aaaar-qadga-cai` on ICP mainnet), which forwards requests to public Ethereum RPC providers such as `https://ethereum-sepolia-rpc.publicnode.com`. For a deeper understanding of the ICP ↔ ETH integration, see the [Ethereum integration overview](https://internetcomputer.org/docs/current/developer-docs/multi-chain/ethereum/overview). -> **Note:** `get_balance`, `transaction_count`, and `send_eth` require live HTTPS outcalls to the Ethereum network and work only when deployed on ICP mainnet. The `ethereum_address` function can be tested locally as it only uses threshold ECDSA, which is available in the local replica. - ## Build and deploy from the command line ### Prerequisites -- [Node.js](https://nodejs.org/) + +- Node.js - icp-cli: `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` ### Install @@ -27,6 +26,8 @@ cd examples/rust/basic_ethereum ### Deploy and test locally +The local icp-cli network supports real HTTPS outcalls, so `get_balance` and `transaction_count` work against live Ethereum Sepolia data without deploying to ICP mainnet. + ```bash icp network start -d icp deploy @@ -34,16 +35,18 @@ bash test.sh icp network stop ``` -### Deploy to ICP mainnet (Sepolia testnet) +`bash test.sh` verifies address derivation (threshold ECDSA), queries the canister's Sepolia balance via the raw EVM RPC canister interface, and queries the transaction count via the high-level `evm_rpc_client` — demonstrating both usage patterns. -To interact with the live Ethereum Sepolia testnet: +`send_eth` requires a funded canister wallet and is not covered in the automated tests. See [Sending ETH](#sending-eth) below. + +### Deploy to ICP mainnet (Sepolia testnet) ```bash icp deploy --network ic --argument '(opt record {ethereum_network = opt variant {Sepolia}; ecdsa_key_name = opt variant {TestKey1}})' ``` -- `ethereum_network = opt variant {Sepolia}`: uses the [Ethereum Testnet Sepolia](https://github.com/ethereum-lists/chains/blob/master/_data/chains/eip155-11155111.json). -- `ecdsa_key_name = opt variant {TestKey1}`: uses a test key for threshold ECDSA signing available on ICP mainnet. See [signing messages](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/encryption/t-ecdsa) for more details. +- `ethereum_network = opt variant {Sepolia}`: uses [Ethereum Testnet Sepolia](https://github.com/ethereum-lists/chains/blob/master/_data/chains/eip155-11155111.json). +- `ecdsa_key_name = opt variant {TestKey1}`: uses a test threshold ECDSA key available on ICP mainnet. For production use with real ETH on Ethereum mainnet: @@ -53,7 +56,7 @@ icp deploy --network ic --argument '(opt record {ethereum_network = opt variant ## Interacting with the deployed canister -### Step 1: Get your Ethereum address +### Get your Ethereum address An Ethereum address is derived from the caller's IC principal via the canister's threshold ECDSA key. The same principal always maps to the same Ethereum address: @@ -62,54 +65,47 @@ icp canister call backend ethereum_address '(null)' # Returns e.g. ("0x378a452B20d1f06008C06c581b1656BdC5313c0C") ``` -To get the Ethereum address for a different principal: +To get the Ethereum address for a specific principal: ```bash icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")' # Returns e.g. ("0x8d68f7B3cdb40A2E77071077658b01A9EA4B040F") ``` -You can view these addresses on any Ethereum block explorer such as [Etherscan](https://etherscan.io/). +### Check a balance -### Step 2: Receive ETH (mainnet only) +Query the ETH balance (in Wei) for any Ethereum address: -1. Get some Sepolia ETH from a faucet, for example [Alchemy's Sepolia faucet](https://www.alchemy.com/faucets/ethereum-sepolia). -2. Send Sepolia ETH to the address from Step 1 using any Ethereum wallet (e.g. MetaMask). -3. Once the transaction has at least one confirmation, verify the balance on [Sepolia Etherscan](https://sepolia.etherscan.io/). +```bash +icp canister call backend get_balance '(opt "0x378a452B20d1f06008C06c581b1656BdC5313c0C")' +``` -### Step 3: Check your balance (mainnet only) +### Sending ETH + +To send ETH the canister's wallet must be funded: + +1. Get your canister's Ethereum address (see above). +2. Get some Sepolia ETH from [Alchemy's Sepolia faucet](https://www.alchemy.com/faucets/ethereum-sepolia). +3. Send Sepolia ETH to the canister's address using any Ethereum wallet (e.g. MetaMask). +4. Once the transaction has at least one confirmation, verify the balance: ```bash icp canister call backend get_balance '(null)' ``` -### Step 4: Send ETH (mainnet only) - -Send 1 Wei to a destination address: +Then send ETH (amount in Wei): ```bash icp canister call backend send_eth '("0xdd2851Cdd40aE6536831558DD46db62fAc7A844d", 1)' ``` -The `send_eth` endpoint executes the following steps: - -1. Retrieves the transaction count (nonce) for the sender's address at `Latest` block height. Ethereum transactions are ordered by nonce, a monotonically increasing counter. -2. Estimates transaction fees. For simplicity, fees are hard-coded with a generous limit. A production application would dynamically fetch the latest fees via the [`eth_feeHistory`](https://github.com/internet-computer-protocol/evm-rpc-canister/blob/3cce151d4c1338d83e6741afa354ccf11dff41e8/candid/evm_rpc.did#L254) method. -3. Builds an [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) transaction. -4. Signs the transaction using the [sign_with_ecdsa API](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/encryption/t-ecdsa). -5. Sends the signed transaction to the Ethereum network via [`eth_sendRawTransaction`](https://github.com/internet-computer-protocol/evm-rpc-canister/blob/3cce151d4c1338d83e6741afa354ccf11dff41e8/candid/evm_rpc.did#L261) in the EVM RPC canister. - -Returns the transaction hash, which can be used to track the transaction on a block explorer. +Returns the transaction hash. Track it on [Sepolia Etherscan](https://sepolia.etherscan.io/). > **Note:** Due to the replicated nature of HTTPS outcalls, errors such as "transaction already known" or "nonce too low" may be reported even if the transaction was successfully broadcast. Verify by checking Etherscan or confirming that the transaction count for the address increased. ## Security considerations and best practices -If you base your application on this example, we recommend you familiarize yourself with and adhere to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for developing on the Internet Computer. This example may not implement all the best practices. - -For example, the following aspects are particularly relevant for this app: - -- [Certify query responses if they are relevant for security](https://docs.internetcomputer.org/guides/security/overview), since the app offers a method to read balances. -- [Use a decentralized governance system like SNS to make a canister have a decentralized controller](https://docs.internetcomputer.org/guides/security/overview), since decentralized control may be essential for canisters holding ETH on behalf of users. +Refer to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for information on security and best practices for your ICP app. For this example the following aspects are particularly relevant: -Additional examples for the ICP ↔ ETH integration can be found in the [ICP developer docs](https://internetcomputer.org/docs/current/developer-docs/multi-chain/ethereum/overview). +- [Certify query responses if they are relevant for security](https://docs.internetcomputer.org/guides/security/data-integrity-and-authenticity/#certified-variables): since the app offers a method to read balances. +- [Use a decentralized governance system like SNS to make a canister have a decentralized controller](https://docs.internetcomputer.org/guides/security/overview): decentralized control may be essential for canisters holding ETH on behalf of users. diff --git a/rust/basic_ethereum/test.sh b/rust/basic_ethereum/test.sh index 92048f6c14..7d506a3148 100755 --- a/rust/basic_ethereum/test.sh +++ b/rust/basic_ethereum/test.sh @@ -1,27 +1,36 @@ #!/usr/bin/env bash set -e -# Note: get_balance, transaction_count, and send_eth require live HTTPS outcalls -# to the Ethereum network and cannot be fully tested locally. The tests below -# verify canister deployment and the ethereum_address function, which derives -# an address using threshold ECDSA (available locally). -# For full end-to-end testing, deploy to ICP mainnet with Sepolia testnet. +# The local icp-cli network supports real HTTPS outcalls, so all read operations +# (get_balance, transaction_count) can be tested locally against Ethereum Sepolia. +# send_eth requires a funded canister wallet and is not covered here — see README. echo "=== Test 1: ethereum_address returns a valid 0x-prefixed Ethereum address ===" -result=$(icp canister call backend ethereum_address '(null)') && \ - echo "$result" && \ - echo "$result" | grep -qE '"0x[0-9a-fA-F]{40}"' && \ - echo "PASS" || (echo "FAIL" && exit 1) +result=$(icp canister call backend ethereum_address '(null)') +echo "$result" +echo "$result" | grep -qE '"0x[0-9a-fA-F]{40}"' && echo "PASS" || (echo "FAIL" && exit 1) +my_address=$(echo "$result" | grep -oE '0x[0-9a-fA-F]{40}') echo "=== Test 2: ethereum_address for a specific principal returns a valid address ===" -result=$(icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")') && \ - echo "$result" && \ - echo "$result" | grep -qE '"0x[0-9a-fA-F]{40}"' && \ - echo "PASS" || (echo "FAIL" && exit 1) +result=$(icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")') +echo "$result" +echo "$result" | grep -qE '"0x[0-9a-fA-F]{40}"' && echo "PASS" || (echo "FAIL" && exit 1) echo "=== Test 3: different principals derive different Ethereum addresses ===" -addr1=$(icp canister call backend ethereum_address '(null)' | grep -oE '0x[0-9a-fA-F]{40}') && \ - addr2=$(icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")' | grep -oE '0x[0-9a-fA-F]{40}') && \ - echo "addr1=$addr1 addr2=$addr2" && \ - [ "$addr1" != "$addr2" ] && \ - echo "PASS" || (echo "FAIL" && exit 1) +addr2=$(icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")' | grep -oE '0x[0-9a-fA-F]{40}') +echo " canister address: $my_address" +echo " principal address: $addr2" +[ "$my_address" != "$addr2" ] && echo "PASS" || (echo "FAIL" && exit 1) + +echo "=== Test 4: get_balance makes a real HTTPS outcall to Ethereum Sepolia and returns a valid Nat ===" +# Uses the canister's own Sepolia address. Balance may be 0 but the call must succeed, +# proving the EVM RPC canister's HTTPS outcall to PublicNode/Ankr works locally. +result=$(icp canister call backend get_balance "(opt \"$my_address\")") +echo "$result" +echo "$result" | grep -qE '\([0-9]+ : nat\)' && echo "PASS" || (echo "FAIL" && exit 1) + +echo "=== Test 5: transaction_count_with_client returns a valid Nat via EvmRpcClient ===" +# Demonstrates the high-level evm_rpc_client API — same HTTPS outcall, no manual cycle management. +result=$(icp canister call backend transaction_count_with_client "(null, null)") +echo "$result" +echo "$result" | grep -qE '\([0-9]+ : nat\)' && echo "PASS" || (echo "FAIL" && exit 1) From 0cd7b7e766b20bedd266995d21c09b487c72fb2e Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 14:11:36 +0200 Subject: [PATCH 09/29] fix(rust/basic_ethereum): use PublicNode by default; document API key setup evm_rpc_services() defaulted to None (all providers) which includes API-key-required providers like Alchemy (provider 6), breaking local tests. Switch to explicit PublicNode (free, no API key) so the example works out of the box. Add README section explaining how to configure API keys locally and switch to multi-provider mode for production. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/README.md | 21 +++++++++++++++++++++ rust/basic_ethereum/backend/state.rs | 16 ++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index c52a714fad..b493355a57 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -103,6 +103,27 @@ Returns the transaction hash. Track it on [Sepolia Etherscan](https://sepolia.et > **Note:** Due to the replicated nature of HTTPS outcalls, errors such as "transaction already known" or "nonce too low" may be reported even if the transaction was successfully broadcast. Verify by checking Etherscan or confirming that the transaction count for the address increased. +## RPC providers and API keys + +The example uses [PublicNode](https://ethereum-sepolia-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, the EVM RPC canister supports premium providers (Alchemy, Ankr, BlockPi) that offer higher rate limits and reliability when configured with API keys. To enable them locally: + +1. Register as a provider admin on the locally deployed EVM RPC canister: +```bash +icp canister call evm_rpc authorize '(record { auth = variant { RegisterProvider }; principal = principal "YOUR_PRINCIPAL" })' +``` + +2. Update the desired provider with your API key: +```bash +icp canister call evm_rpc updateProvider '(record { providerId = 6; apiKey = opt "YOUR_ALCHEMY_API_KEY" })' +``` + +3. In `backend/state.rs`, change `evm_rpc_services()` to use `None` to include all configured providers (better consensus across multiple providers): +```rust +EthereumNetwork::Sepolia => RpcServices::EthSepolia(None), +``` + ## Security considerations and best practices Refer to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for information on security and best practices for your ICP app. For this example the following aspects are particularly relevant: diff --git a/rust/basic_ethereum/backend/state.rs b/rust/basic_ethereum/backend/state.rs index 65b62ad2b0..81a8c479e1 100644 --- a/rust/basic_ethereum/backend/state.rs +++ b/rust/basic_ethereum/backend/state.rs @@ -40,10 +40,22 @@ impl State { self.ethereum_network } + // Returns the RPC services to use for multi-provider calls. + // Uses PublicNode by default (no API key required) so the example works + // out of the box locally and without credentials. + // + // For production, pass `None` to use all configured providers (including + // API-key-based ones like Alchemy/Ankr), or add multiple providers for + // better consensus. API keys are configured via the EVM RPC canister's + // `authorize` and `updateProvider` endpoints — see README for details. pub fn evm_rpc_services(&self) -> RpcServices { match self.ethereum_network { - EthereumNetwork::Mainnet => RpcServices::EthMainnet(None), - EthereumNetwork::Sepolia => RpcServices::EthSepolia(None), + EthereumNetwork::Mainnet => { + RpcServices::EthMainnet(Some(vec![EthMainnetService::PublicNode])) + } + EthereumNetwork::Sepolia => { + RpcServices::EthSepolia(Some(vec![EthSepoliaService::PublicNode])) + } } } From 0cbd26fc71a681dfc5f283ffb6bc4700e5c61f61 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 14:29:53 +0200 Subject: [PATCH 10/29] improve(rust/basic_ethereum): assert non-zero balance for known Sepolia address Test 4 now uses 0x378a452B20d1f06008C06c581b1656BdC5313c0C (from the original README) which has known Sepolia ETH, and asserts balance > 0. This is a stronger check proving real on-chain data is returned, not just that the call succeeds with 0. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/test.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/rust/basic_ethereum/test.sh b/rust/basic_ethereum/test.sh index 7d506a3148..e7fab2e92f 100755 --- a/rust/basic_ethereum/test.sh +++ b/rust/basic_ethereum/test.sh @@ -22,12 +22,15 @@ echo " canister address: $my_address" echo " principal address: $addr2" [ "$my_address" != "$addr2" ] && echo "PASS" || (echo "FAIL" && exit 1) -echo "=== Test 4: get_balance makes a real HTTPS outcall to Ethereum Sepolia and returns a valid Nat ===" -# Uses the canister's own Sepolia address. Balance may be 0 but the call must succeed, -# proving the EVM RPC canister's HTTPS outcall to PublicNode/Ankr works locally. -result=$(icp canister call backend get_balance "(opt \"$my_address\")") +echo "=== Test 4: get_balance returns non-zero balance for a known funded Sepolia address ===" +# 0x378a452B20d1f06008C06c581b1656BdC5313c0C is an address with known Sepolia ETH. +# Asserting > 0 proves the HTTPS outcall to the Ethereum RPC provider works and +# returns real on-chain data. +KNOWN_ADDRESS="0x378a452B20d1f06008C06c581b1656BdC5313c0C" +result=$(icp canister call backend get_balance "(opt \"$KNOWN_ADDRESS\")") echo "$result" -echo "$result" | grep -qE '\([0-9]+ : nat\)' && echo "PASS" || (echo "FAIL" && exit 1) +balance=$(echo "$result" | grep -oE '[0-9]+' | head -1) +[ "$balance" -gt 0 ] && echo "PASS" || (echo "FAIL: expected non-zero Sepolia ETH balance for $KNOWN_ADDRESS" && exit 1) echo "=== Test 5: transaction_count_with_client returns a valid Nat via EvmRpcClient ===" # Demonstrates the high-level evm_rpc_client API — same HTTPS outcall, no manual cycle management. From 11501616ddfb1005ac5a409b0d9e5e977a99e9e6 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 15:06:21 +0200 Subject: [PATCH 11/29] improve(rust/basic_ethereum): transaction_count_with_client accepts Ethereum address directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change the signature from Option to Option to mirror get_balance — any Ethereum address can be queried, not just ones derived from IC principals. Passing null still falls back to the canister's own derived address. This creates a clear educational contrast: - transaction_count: raw inter-canister call, derives address from principal - transaction_count_with_client: EvmRpcClient, accepts any ETH address Test 5 now uses the known funded Sepolia address and asserts count >= 3. README updated to show both use cases. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/README.md | 20 ++++++++++++++++---- rust/basic_ethereum/backend/lib.rs | 15 +++++---------- rust/basic_ethereum/test.sh | 11 +++++++---- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index b493355a57..f93c7e8acb 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -5,10 +5,10 @@ This example demonstrates how to deploy a canister smart contract on the Interne ## Architecture This example internally leverages: -- [Threshold ECDSA](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/encryption/t-ecdsa): Each user's Ethereum address is derived deterministically from the canister's master ECDSA key using a derivation path based on the user's IC principal. This means each user has a unique, stable Ethereum address controlled by the canister. -- [HTTPS outcalls](https://internetcomputer.org/docs/current/developer-docs/smart-contracts/advanced-features/https-outcalls/https-outcalls-overview): The canister communicates with the Ethereum network via the [EVM RPC canister](https://github.com/dfinity/evm-rpc-canister) (canister ID `7hfb6-caaaa-aaaar-qadga-cai` on ICP mainnet), which forwards requests to public Ethereum RPC providers such as `https://ethereum-sepolia-rpc.publicnode.com`. +- [Threshold ECDSA](https://docs.internetcomputer.org/concepts/chain-key-cryptography/#chain-key-signatures-threshold-ecdsa-and-schnorr): Each user's Ethereum address is derived deterministically from the canister's master ECDSA key using a derivation path based on the user's IC principal. This means each user has a unique, stable Ethereum address controlled by the canister. +- [HTTPS outcalls](https://docs.internetcomputer.org/concepts/https-outcalls): The canister communicates with the Ethereum network via the [EVM RPC canister](https://github.com/dfinity/evm-rpc-canister) (canister ID `7hfb6-caaaa-aaaar-qadga-cai` on ICP mainnet), which forwards requests to public Ethereum RPC providers such as `https://ethereum-sepolia-rpc.publicnode.com`. -For a deeper understanding of the ICP ↔ ETH integration, see the [Ethereum integration overview](https://internetcomputer.org/docs/current/developer-docs/multi-chain/ethereum/overview). +For a deeper understanding of the ICP ↔ ETH integration, see the [Ethereum integration](https://docs.internetcomputer.org/concepts/chain-fusion/ethereum). ## Build and deploy from the command line @@ -72,7 +72,7 @@ icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ek # Returns e.g. ("0x8d68f7B3cdb40A2E77071077658b01A9EA4B040F") ``` -### Check a balance +### Check a balance and transaction count Query the ETH balance (in Wei) for any Ethereum address: @@ -80,6 +80,18 @@ Query the ETH balance (in Wei) for any Ethereum address: icp canister call backend get_balance '(opt "0x378a452B20d1f06008C06c581b1656BdC5313c0C")' ``` +Query the transaction count (nonce) for any Ethereum address using the high-level `EvmRpcClient`: + +```bash +icp canister call backend transaction_count_with_client '(opt "0x378a452B20d1f06008C06c581b1656BdC5313c0C", null)' +``` + +To get the transaction count for the canister's own derived address, pass `null`: + +```bash +icp canister call backend transaction_count_with_client '(null, null)' +``` + ### Sending ETH To send ETH the canister's wallet must be funded: diff --git a/rust/basic_ethereum/backend/lib.rs b/rust/basic_ethereum/backend/lib.rs index b57c9738f4..f73a521ffa 100644 --- a/rust/basic_ethereum/backend/lib.rs +++ b/rust/basic_ethereum/backend/lib.rs @@ -132,19 +132,14 @@ pub async fn transaction_count(owner: Option, block: Option /// Demonstrates the high-level `EvmRpcClient` pattern: no manual cycle amounts, automatic /// consensus across providers, and a cleaner API surface compared to the raw inter-canister -/// call used in `transaction_count`. +/// call used in `transaction_count`. Accepts any Ethereum address directly (like `get_balance`), +/// rather than deriving an address from an IC principal. #[update] -pub async fn transaction_count_with_client(owner: Option, block: Option) -> Nat { - let caller = validate_caller_not_anonymous(); - let owner = owner.unwrap_or(caller); - let wallet = EthereumWallet::new(owner).await; +pub async fn transaction_count_with_client(address: Option, block: Option) -> Nat { + let address = address.unwrap_or(ethereum_address(None).await); let rpc_services = read_state(|s| s.evm_rpc_services()); - let address: Hex20 = wallet - .ethereum_address() - .to_string() - .parse() - .expect("failed to parse ethereum address"); + let address: Hex20 = address.parse().expect("failed to parse ethereum address"); let block_tag = block.unwrap_or(BlockTag::Finalized); let canister_id = evm_rpc_id(); diff --git a/rust/basic_ethereum/test.sh b/rust/basic_ethereum/test.sh index e7fab2e92f..2491678c61 100755 --- a/rust/basic_ethereum/test.sh +++ b/rust/basic_ethereum/test.sh @@ -32,8 +32,11 @@ echo "$result" balance=$(echo "$result" | grep -oE '[0-9]+' | head -1) [ "$balance" -gt 0 ] && echo "PASS" || (echo "FAIL: expected non-zero Sepolia ETH balance for $KNOWN_ADDRESS" && exit 1) -echo "=== Test 5: transaction_count_with_client returns a valid Nat via EvmRpcClient ===" -# Demonstrates the high-level evm_rpc_client API — same HTTPS outcall, no manual cycle management. -result=$(icp canister call backend transaction_count_with_client "(null, null)") +echo "=== Test 5: transaction_count_with_client returns >= 3 for the known Sepolia address ===" +# Demonstrates the high-level evm_rpc_client API — same Sepolia HTTPS outcall as Test 4 +# but with automatic cycle management and accepting an Ethereum address directly. +# The known address has sent at least 3 transactions on Sepolia. +result=$(icp canister call backend transaction_count_with_client "(opt \"$KNOWN_ADDRESS\", null)") echo "$result" -echo "$result" | grep -qE '\([0-9]+ : nat\)' && echo "PASS" || (echo "FAIL" && exit 1) +count=$(echo "$result" | grep -oE '[0-9]+' | head -1) +[ "$count" -ge 3 ] && echo "PASS" || (echo "FAIL: expected transaction count >= 3 for $KNOWN_ADDRESS, got $count" && exit 1) From db8ef92779f1a5fe58a2a13fd0aebefec336f4ac Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 15:12:05 +0200 Subject: [PATCH 12/29] improve(rust/basic_ethereum): 3-environment icp.yaml setup (local/staging/production) - local: deploys backend + evm_rpc pre-built locally; icp-cli auto-injects PUBLIC_CANISTER_ID:evm_rpc - staging: backend only on ICP mainnet, Sepolia + TestKey1, points to shared EVM RPC canister (7hfb6-caaaa-aaaar-qadga-cai) - production: backend only on ICP mainnet, Mainnet + ProductionKey1, same shared EVM RPC canister Also fixes env var name mismatch: production env was setting EVM_RPC_CANISTER_ID but lib.rs reads PUBLIC_CANISTER_ID:evm_rpc (auto-injected by icp-cli pattern). README updated to show icp deploy -e staging / -e production instead of manual --argument flags. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/README.md | 22 +++++++++++++++------- rust/basic_ethereum/icp.yaml | 25 ++++++++++++++++++++----- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index f93c7e8acb..ee62c241f5 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -39,19 +39,27 @@ icp network stop `send_eth` requires a funded canister wallet and is not covered in the automated tests. See [Sending ETH](#sending-eth) below. -### Deploy to ICP mainnet (Sepolia testnet) +### Deploy to ICP mainnet + +This example defines two mainnet environments in `icp.yaml`: + +| Environment | Ethereum network | ECDSA key | Use case | +|-------------|-----------------|-----------|----------| +| `staging` | Sepolia testnet | `test_key_1` | Development and testing on ICP mainnet with free Sepolia ETH | +| `production` | Ethereum mainnet | `key_1` | Production deployments with real ETH | + +Both environments deploy only the backend canister and point it to the [shared EVM RPC canister](https://github.com/dfinity/evm-rpc-canister) (`7hfb6-caaaa-aaaar-qadga-cai`) already running on ICP mainnet. + +**Deploy to staging (Sepolia):** ```bash -icp deploy --network ic --argument '(opt record {ethereum_network = opt variant {Sepolia}; ecdsa_key_name = opt variant {TestKey1}})' +icp deploy -e staging ``` -- `ethereum_network = opt variant {Sepolia}`: uses [Ethereum Testnet Sepolia](https://github.com/ethereum-lists/chains/blob/master/_data/chains/eip155-11155111.json). -- `ecdsa_key_name = opt variant {TestKey1}`: uses a test threshold ECDSA key available on ICP mainnet. - -For production use with real ETH on Ethereum mainnet: +**Deploy to production (Ethereum mainnet):** ```bash -icp deploy --network ic --argument '(opt record {ethereum_network = opt variant {Mainnet}; ecdsa_key_name = opt variant {ProductionKey1}})' +icp deploy -e production ``` ## Interacting with the deployed canister diff --git a/rust/basic_ethereum/icp.yaml b/rust/basic_ethereum/icp.yaml index d779327b41..1f523d42d9 100644 --- a/rust/basic_ethereum/icp.yaml +++ b/rust/basic_ethereum/icp.yaml @@ -10,17 +10,32 @@ canisters: url: https://github.com/dfinity/evm-rpc-canister/releases/download/evm_rpc-v2.8.0/evm_rpc.wasm.gz init_args: "(record { nodesInSubnet = 28 })" -# EVM_RPC_CANISTER_ID is injected as a canister environment variable at deploy time. -# Locally, icp-cli injects PUBLIC_CANISTER_ID:evm_rpc automatically after deploying the -# pre-built canister above. On mainnet, the production environment sets it to the -# shared EVM RPC canister (7hfb6-caaaa-aaaar-qadga-cai). environments: + # Local: deploys both backend 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 + # Staging: deploys only backend on ICP mainnet. + # Points to the shared EVM RPC canister on ICP mainnet, uses Sepolia and the test ECDSA key. + - name: staging + network: ic + canisters: [backend] + init_args: + backend: "(opt record {ethereum_network = opt variant {Sepolia}; ecdsa_key_name = opt variant {TestKey1}})" + settings: + backend: + environment_variables: + "PUBLIC_CANISTER_ID:evm_rpc": "7hfb6-caaaa-aaaar-qadga-cai" + + # Production: deploys only backend on ICP mainnet. + # Points to the shared EVM RPC canister on ICP mainnet, uses Ethereum mainnet and the production ECDSA key. - name: production network: ic + canisters: [backend] + init_args: + backend: "(opt record {ethereum_network = opt variant {Mainnet}; ecdsa_key_name = opt variant {ProductionKey1}})" settings: backend: environment_variables: - EVM_RPC_CANISTER_ID: "7hfb6-caaaa-aaaar-qadga-cai" + "PUBLIC_CANISTER_ID:evm_rpc": "7hfb6-caaaa-aaaar-qadga-cai" From 82df10303154bba88ad2c385da4b30f51a897574 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 15:17:12 +0200 Subject: [PATCH 13/29] fix(rust/basic_ethereum): clarify that null = caller's address, not the canister's The ethereum_address(null), get_balance(null), and transaction_count_with_client(null, ...) functions all derive the address from the *caller's* IC principal, not the canister itself. The canister manages one Ethereum address per user. Updated README and test.sh labels accordingly. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/README.md | 18 ++++++++++-------- rust/basic_ethereum/test.sh | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index ee62c241f5..d3bc4ae1e5 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -66,14 +66,16 @@ icp deploy -e production ### Get your Ethereum address -An Ethereum address is derived from the caller's IC principal via the canister's threshold ECDSA key. The same principal always maps to the same Ethereum address: +Each IC principal gets a unique, stable Ethereum address controlled by this canister. The address is derived deterministically from the principal using the canister's threshold ECDSA key — the same principal always maps to the same address. + +Passing `null` returns the address for your own IC principal (the identity you are calling with): ```bash icp canister call backend ethereum_address '(null)' -# Returns e.g. ("0x378a452B20d1f06008C06c581b1656BdC5313c0C") +# Returns your Ethereum address, e.g. ("0x378a452B20d1f06008C06c581b1656BdC5313c0C") ``` -To get the Ethereum address for a specific principal: +You can also look up the address for any other IC principal: ```bash icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")' @@ -94,7 +96,7 @@ Query the transaction count (nonce) for any Ethereum address using the high-leve icp canister call backend transaction_count_with_client '(opt "0x378a452B20d1f06008C06c581b1656BdC5313c0C", null)' ``` -To get the transaction count for the canister's own derived address, pass `null`: +Passing `null` uses the derived Ethereum address of your calling IC principal: ```bash icp canister call backend transaction_count_with_client '(null, null)' @@ -102,11 +104,11 @@ icp canister call backend transaction_count_with_client '(null, null)' ### Sending ETH -To send ETH the canister's wallet must be funded: +To send ETH, your derived Ethereum address must be funded first: -1. Get your canister's Ethereum address (see above). +1. Get your Ethereum address (see above) — this is the address managed by the canister for your IC principal. 2. Get some Sepolia ETH from [Alchemy's Sepolia faucet](https://www.alchemy.com/faucets/ethereum-sepolia). -3. Send Sepolia ETH to the canister's address using any Ethereum wallet (e.g. MetaMask). +3. Send Sepolia ETH to your address using any Ethereum wallet (e.g. MetaMask). 4. Once the transaction has at least one confirmation, verify the balance: ```bash @@ -149,4 +151,4 @@ EthereumNetwork::Sepolia => RpcServices::EthSepolia(None), Refer to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for information on security and best practices for your ICP app. For this example the following aspects are particularly relevant: - [Certify query responses if they are relevant for security](https://docs.internetcomputer.org/guides/security/data-integrity-and-authenticity/#certified-variables): since the app offers a method to read balances. -- [Use a decentralized governance system like SNS to make a canister have a decentralized controller](https://docs.internetcomputer.org/guides/security/overview): decentralized control may be essential for canisters holding ETH on behalf of users. +- [Use a decentralized governance system like SNS to make a canister have a decentralized controller](https://docs.internetcomputer.org/guides/security/canister-control/#use-a-governance-framework-such-as-the-sns-to-control-your-canisters): decentralized control may be essential for canisters holding ETH on behalf of users. diff --git a/rust/basic_ethereum/test.sh b/rust/basic_ethereum/test.sh index 2491678c61..01948d3f6b 100755 --- a/rust/basic_ethereum/test.sh +++ b/rust/basic_ethereum/test.sh @@ -18,7 +18,7 @@ echo "$result" | grep -qE '"0x[0-9a-fA-F]{40}"' && echo "PASS" || (echo "FAIL" & echo "=== Test 3: different principals derive different Ethereum addresses ===" addr2=$(icp canister call backend ethereum_address '(opt principal "hkroy-sm7vs-yyjs7-ekppe-qqnwx-hm4zf-n7ybs-titsi-k6e3k-ucuiu-uqe")' | grep -oE '0x[0-9a-fA-F]{40}') -echo " canister address: $my_address" +echo " my address: $my_address" echo " principal address: $addr2" [ "$my_address" != "$addr2" ] && echo "PASS" || (echo "FAIL" && exit 1) From 954cbc753b69a2778a52a8f9c0494747b7ef1a1f Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 15:31:42 +0200 Subject: [PATCH 14/29] simplify(rust/basic_ethereum): remove dfx_test_key, use test_key_1 as default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_key_1 works in icp-cli's PocketIC environment; dfx_test_key was a dfx-specific artifact that is no longer needed. TestKey1 is now the default EcdsaKeyName, used for both local and staging. This lets staging drop its init_args entirely — local and staging share identical defaults (Sepolia + test_key_1). Only production needs init_args. icp.yaml is now visibly simpler: two environment blocks with no init_args, one with. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/README.md | 5 +++-- rust/basic_ethereum/backend/lib.rs | 2 -- rust/basic_ethereum/icp.yaml | 10 ++++------ 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index d3bc4ae1e5..450d6abd3c 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -45,7 +45,8 @@ This example defines two mainnet environments in `icp.yaml`: | Environment | Ethereum network | ECDSA key | Use case | |-------------|-----------------|-----------|----------| -| `staging` | Sepolia testnet | `test_key_1` | Development and testing on ICP mainnet with free Sepolia ETH | +| `local` | Sepolia testnet | `test_key_1` | Local development with real HTTPS outcalls | +| `staging` | Sepolia testnet | `test_key_1` | Testing on ICP mainnet with free Sepolia ETH | | `production` | Ethereum mainnet | `key_1` | Production deployments with real ETH | Both environments deploy only the backend canister and point it to the [shared EVM RPC canister](https://github.com/dfinity/evm-rpc-canister) (`7hfb6-caaaa-aaaar-qadga-cai`) already running on ICP mainnet. @@ -151,4 +152,4 @@ EthereumNetwork::Sepolia => RpcServices::EthSepolia(None), Refer to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for information on security and best practices for your ICP app. For this example the following aspects are particularly relevant: - [Certify query responses if they are relevant for security](https://docs.internetcomputer.org/guides/security/data-integrity-and-authenticity/#certified-variables): since the app offers a method to read balances. -- [Use a decentralized governance system like SNS to make a canister have a decentralized controller](https://docs.internetcomputer.org/guides/security/canister-control/#use-a-governance-framework-such-as-the-sns-to-control-your-canisters): decentralized control may be essential for canisters holding ETH on behalf of users. +- [Use a governance framework like SNS to make a canister have a decentralized controller](https://docs.internetcomputer.org/guides/security/canister-control/#use-a-governance-framework-such-as-the-sns-to-control-your-canisters): decentralized control may be essential for canisters holding ETH on behalf of users. diff --git a/rust/basic_ethereum/backend/lib.rs b/rust/basic_ethereum/backend/lib.rs index f73a521ffa..8a31e3ebb0 100644 --- a/rust/basic_ethereum/backend/lib.rs +++ b/rust/basic_ethereum/backend/lib.rs @@ -279,7 +279,6 @@ impl EthereumNetwork { #[derive(CandidType, Deserialize, Debug, Default, PartialEq, Eq, Clone)] pub enum EcdsaKeyName { #[default] - TestKeyLocalDevelopment, TestKey1, ProductionKey1, } @@ -289,7 +288,6 @@ impl From<&EcdsaKeyName> for EcdsaKeyId { EcdsaKeyId { curve: EcdsaCurve::Secp256k1, name: match value { - EcdsaKeyName::TestKeyLocalDevelopment => "dfx_test_key", EcdsaKeyName::TestKey1 => "test_key_1", EcdsaKeyName::ProductionKey1 => "key_1", } diff --git a/rust/basic_ethereum/icp.yaml b/rust/basic_ethereum/icp.yaml index 1f523d42d9..77b1207c45 100644 --- a/rust/basic_ethereum/icp.yaml +++ b/rust/basic_ethereum/icp.yaml @@ -13,23 +13,21 @@ canisters: environments: # Local: deploys both backend and evm_rpc (pre-built). # icp-cli auto-injects PUBLIC_CANISTER_ID:evm_rpc into the backend after deploying evm_rpc. + # Defaults: Sepolia + test_key_1. - name: local network: local - # Staging: deploys only backend on ICP mainnet. - # Points to the shared EVM RPC canister on ICP mainnet, uses Sepolia and the test ECDSA key. + # Staging: backend only on ICP mainnet, Sepolia + test_key_1 (same as local defaults). + # Points to the shared EVM RPC canister already running on ICP mainnet. - name: staging network: ic canisters: [backend] - init_args: - backend: "(opt record {ethereum_network = opt variant {Sepolia}; ecdsa_key_name = opt variant {TestKey1}})" settings: backend: environment_variables: "PUBLIC_CANISTER_ID:evm_rpc": "7hfb6-caaaa-aaaar-qadga-cai" - # Production: deploys only backend on ICP mainnet. - # Points to the shared EVM RPC canister on ICP mainnet, uses Ethereum mainnet and the production ECDSA key. + # Production: backend only on ICP mainnet, Ethereum mainnet + production ECDSA key. - name: production network: ic canisters: [backend] From e77a9e161d4632ce539326c19d2c4602721021f6 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 16:39:00 +0200 Subject: [PATCH 15/29] fix(rust/basic_ethereum): assert nonce >= 2, clarify eth_getTransactionCount is outgoing-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit eth_getTransactionCount returns the nonce — only outgoing transactions increment it. The known Sepolia address has 2 outgoing txs (nonce = 2), not 3 total (1 incoming + 2 outgoing). Updated test assertion and comments accordingly. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/README.md | 2 +- rust/basic_ethereum/test.sh | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index 450d6abd3c..b6048ec554 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -91,7 +91,7 @@ Query the ETH balance (in Wei) for any Ethereum address: icp canister call backend get_balance '(opt "0x378a452B20d1f06008C06c581b1656BdC5313c0C")' ``` -Query the transaction count (nonce) for any Ethereum address using the high-level `EvmRpcClient`: +Query the transaction count for any Ethereum address using the high-level `EvmRpcClient`. This calls `eth_getTransactionCount`, which returns the **nonce** — the number of transactions sent *from* the address (outgoing only, not received): ```bash icp canister call backend transaction_count_with_client '(opt "0x378a452B20d1f06008C06c581b1656BdC5313c0C", null)' diff --git a/rust/basic_ethereum/test.sh b/rust/basic_ethereum/test.sh index 01948d3f6b..c3f693d092 100755 --- a/rust/basic_ethereum/test.sh +++ b/rust/basic_ethereum/test.sh @@ -32,11 +32,12 @@ echo "$result" balance=$(echo "$result" | grep -oE '[0-9]+' | head -1) [ "$balance" -gt 0 ] && echo "PASS" || (echo "FAIL: expected non-zero Sepolia ETH balance for $KNOWN_ADDRESS" && exit 1) -echo "=== Test 5: transaction_count_with_client returns >= 3 for the known Sepolia address ===" +echo "=== Test 5: transaction_count_with_client returns the nonce (outgoing tx count) via EvmRpcClient ===" # Demonstrates the high-level evm_rpc_client API — same Sepolia HTTPS outcall as Test 4 # but with automatic cycle management and accepting an Ethereum address directly. -# The known address has sent at least 3 transactions on Sepolia. +# eth_getTransactionCount returns the nonce: only outgoing transactions are counted. +# The known address has 2 outgoing transactions on Sepolia (nonce = 2). result=$(icp canister call backend transaction_count_with_client "(opt \"$KNOWN_ADDRESS\", null)") echo "$result" count=$(echo "$result" | grep -oE '[0-9]+' | head -1) -[ "$count" -ge 3 ] && echo "PASS" || (echo "FAIL: expected transaction count >= 3 for $KNOWN_ADDRESS, got $count" && exit 1) +[ "$count" -ge 2 ] && echo "PASS" || (echo "FAIL: expected nonce >= 2 for $KNOWN_ADDRESS, got $count" && exit 1) From 583d09c713d7528d69cd0445f19f196d71e9e35d Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 16:47:32 +0200 Subject: [PATCH 16/29] fix(rust/basic_ethereum): create plaintext identity before canister calls in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit icp canister call uses the anonymous principal when no identity exists, causing ethereum_address to trap. Create a named plaintext identity at test start — --storage-mode plaintext is required in CI containers where no keyring daemon is running. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/test.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rust/basic_ethereum/test.sh b/rust/basic_ethereum/test.sh index c3f693d092..dd3fbb9b12 100755 --- a/rust/basic_ethereum/test.sh +++ b/rust/basic_ethereum/test.sh @@ -5,6 +5,11 @@ set -e # (get_balance, transaction_count) can be tested locally against Ethereum Sepolia. # send_eth requires a funded canister wallet and is not covered here — see README. +# Create a non-anonymous identity for canister calls that reject the anonymous principal. +# --storage-mode plaintext is required in CI environments without a keyring daemon. +icp identity new test --storage-mode plaintext 2>/dev/null || true +icp identity default test + echo "=== Test 1: ethereum_address returns a valid 0x-prefixed Ethereum address ===" result=$(icp canister call backend ethereum_address '(null)') echo "$result" From 2e6b59ce6c171b7af214f39b3392df8b19b48c86 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 16:59:35 +0200 Subject: [PATCH 17/29] fix(rust/basic_ethereum): correct flag --storage-mode to --storage for icp identity new Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/basic_ethereum/test.sh b/rust/basic_ethereum/test.sh index dd3fbb9b12..1b901b0a43 100755 --- a/rust/basic_ethereum/test.sh +++ b/rust/basic_ethereum/test.sh @@ -7,7 +7,7 @@ set -e # Create a non-anonymous identity for canister calls that reject the anonymous principal. # --storage-mode plaintext is required in CI environments without a keyring daemon. -icp identity new test --storage-mode plaintext 2>/dev/null || true +icp identity new test --storage plaintext 2>/dev/null || true icp identity default test echo "=== Test 1: ethereum_address returns a valid 0x-prefixed Ethereum address ===" From 52645f3ce86f3f0c8cb6fabb289d2aa8c635763a Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 17:04:21 +0200 Subject: [PATCH 18/29] fix(rust/basic_ethereum): fix two small README inaccuracies - 'queries the canister's Sepolia balance' -> 'queries a known funded Sepolia address's balance' (test uses an external address, not any canister's balance) - 'funded canister wallet' -> 'funded Ethereum address' (consistent with the rest of the README which uses caller-centric language) - 'Both environments' -> 'Both mainnet environments' (local also deploys the evm_rpc canister, so this was ambiguous) Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index b6048ec554..148ddab4e4 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -35,9 +35,9 @@ bash test.sh icp network stop ``` -`bash test.sh` verifies address derivation (threshold ECDSA), queries the canister's Sepolia balance via the raw EVM RPC canister interface, and queries the transaction count via the high-level `evm_rpc_client` — demonstrating both usage patterns. +`bash test.sh` verifies address derivation (threshold ECDSA), queries a known funded Sepolia address's balance via the raw EVM RPC canister interface, and queries its transaction count (nonce) via the high-level `evm_rpc_client` — demonstrating both usage patterns side by side. -`send_eth` requires a funded canister wallet and is not covered in the automated tests. See [Sending ETH](#sending-eth) below. +`send_eth` requires a funded Ethereum address and is not covered in the automated tests. See [Sending ETH](#sending-eth) below. ### Deploy to ICP mainnet @@ -49,7 +49,7 @@ This example defines two mainnet environments in `icp.yaml`: | `staging` | Sepolia testnet | `test_key_1` | Testing on ICP mainnet with free Sepolia ETH | | `production` | Ethereum mainnet | `key_1` | Production deployments with real ETH | -Both environments deploy only the backend canister and point it to the [shared EVM RPC canister](https://github.com/dfinity/evm-rpc-canister) (`7hfb6-caaaa-aaaar-qadga-cai`) already running on ICP mainnet. +Both mainnet environments deploy only the backend canister and point it to the [shared EVM RPC canister](https://github.com/dfinity/evm-rpc-canister) (`7hfb6-caaaa-aaaar-qadga-cai`) already running on ICP mainnet. **Deploy to staging (Sepolia):** From 734a70bfe389e4707fd11a5968555f565d0f81ad Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 17:10:21 +0200 Subject: [PATCH 19/29] docs(rust/basic_ethereum): explain nodesInSubnet in icp.yaml comment Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/icp.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rust/basic_ethereum/icp.yaml b/rust/basic_ethereum/icp.yaml index 77b1207c45..fbbdaedb9a 100644 --- a/rust/basic_ethereum/icp.yaml +++ b/rust/basic_ethereum/icp.yaml @@ -8,6 +8,10 @@ canisters: steps: - type: pre-built url: https://github.com/dfinity/evm-rpc-canister/releases/download/evm_rpc-v2.8.0/evm_rpc.wasm.gz + # 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 PocketIC only runs 1 node. init_args: "(record { nodesInSubnet = 28 })" environments: From c72732b246042f729d350a40dbc8782e67db1ceb Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 17:16:25 +0200 Subject: [PATCH 20/29] fix(rust/basic_ethereum): fix stale --storage-mode comment to match actual --storage flag Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/basic_ethereum/test.sh b/rust/basic_ethereum/test.sh index 1b901b0a43..fbb19dbc52 100755 --- a/rust/basic_ethereum/test.sh +++ b/rust/basic_ethereum/test.sh @@ -6,7 +6,7 @@ set -e # send_eth requires a funded canister wallet and is not covered here — see README. # Create a non-anonymous identity for canister calls that reject the anonymous principal. -# --storage-mode plaintext is required in CI environments without a keyring daemon. +# --storage plaintext is required in CI environments without a keyring daemon. icp identity new test --storage plaintext 2>/dev/null || true icp identity default test From 92e4d23c5c55b08083cd71bd6ee8dbccf6dbd59b Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 17:17:36 +0200 Subject: [PATCH 21/29] fix(rust/basic_ethereum): align comment and expect message with actual env var name Both referenced EVM_RPC_CANISTER_ID (old name) instead of the actual env var PUBLIC_CANISTER_ID:evm_rpc read by the code. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/backend/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/basic_ethereum/backend/lib.rs b/rust/basic_ethereum/backend/lib.rs index 8a31e3ebb0..53e1f26dee 100644 --- a/rust/basic_ethereum/backend/lib.rs +++ b/rust/basic_ethereum/backend/lib.rs @@ -17,14 +17,14 @@ use ic_ethereum_types::Address; use num::{BigUint, Num}; use std::str::FromStr; -// The EVM RPC canister ID is configured as a canister environment variable: -// local: PUBLIC_CANISTER_ID:evm_rpc injected by icp-cli after deploying the pre-built canister -// production: EVM_RPC_CANISTER_ID = 7hfb6-caaaa-aaaar-qadga-cai (shared mainnet EVM RPC) +// The EVM RPC canister ID is injected as PUBLIC_CANISTER_ID:evm_rpc at deploy time: +// local: auto-injected by icp-cli after deploying the pre-built evm_rpc canister +// staging/production: set explicitly in icp.yaml to 7hfb6-caaaa-aaaar-qadga-cai (shared mainnet EVM RPC) // // See icp.yaml for the environment configuration. fn evm_rpc_id() -> Principal { let id = ic_cdk::api::env_var_value("PUBLIC_CANISTER_ID:evm_rpc"); - Principal::from_text(&id).expect("invalid EVM_RPC_CANISTER_ID") + Principal::from_text(&id).expect("invalid PUBLIC_CANISTER_ID:evm_rpc") } #[init] From ab670b86c266fda0a0818668e3c96d64b4047e10 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 18:20:45 +0200 Subject: [PATCH 22/29] simplify(rust/basic_ethereum): replace EcdsaKeyName enum with plain string, rename staging to ic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EcdsaKeyName enum added unnecessary indirection — callers now pass the key name directly ("test_key_1", "key_1") matching what the IC management canister expects, same pattern as other examples. icp.yaml: - Remove production environment (not needed for an example) - Rename staging -> ic (standard icp-cli environment name) - Explicit init_args on ic environment with string key format - Comment showing how to adapt init_args for production (Mainnet + key_1) README: simplified deploy section, removed environment table. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/README.md | 20 +++----------------- rust/basic_ethereum/backend/lib.rs | 26 ++++---------------------- rust/basic_ethereum/backend/state.rs | 27 ++++++++++++++++++++------- rust/basic_ethereum/icp.yaml | 24 ++++++++++-------------- 4 files changed, 37 insertions(+), 60 deletions(-) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index 148ddab4e4..0200d61570 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -41,27 +41,13 @@ icp network stop ### Deploy to ICP mainnet -This example defines two mainnet environments in `icp.yaml`: - -| Environment | Ethereum network | ECDSA key | Use case | -|-------------|-----------------|-----------|----------| -| `local` | Sepolia testnet | `test_key_1` | Local development with real HTTPS outcalls | -| `staging` | Sepolia testnet | `test_key_1` | Testing on ICP mainnet with free Sepolia ETH | -| `production` | Ethereum mainnet | `key_1` | Production deployments with real ETH | - -Both mainnet environments deploy only the backend canister and point it to the [shared EVM RPC canister](https://github.com/dfinity/evm-rpc-canister) (`7hfb6-caaaa-aaaar-qadga-cai`) already running on ICP mainnet. - -**Deploy to staging (Sepolia):** - ```bash -icp deploy -e staging +icp deploy -e ic ``` -**Deploy to production (Ethereum mainnet):** +This deploys only the backend canister and points it to the [shared EVM RPC canister](https://github.com/dfinity/evm-rpc-canister) (`7hfb6-caaaa-aaaar-qadga-cai`) already running on ICP mainnet. The default configuration uses Ethereum Sepolia testnet with `test_key_1` — suitable for testing with free Sepolia ETH from a faucet. -```bash -icp deploy -e production -``` +To deploy for production use on Ethereum mainnet, update the `init_args` in `icp.yaml` to use `variant {Mainnet}` and `"key_1"` — see the comment in `icp.yaml` for the exact value. ## Interacting with the deployed canister diff --git a/rust/basic_ethereum/backend/lib.rs b/rust/basic_ethereum/backend/lib.rs index 53e1f26dee..6d8d0fdadb 100644 --- a/rust/basic_ethereum/backend/lib.rs +++ b/rust/basic_ethereum/backend/lib.rs @@ -11,7 +11,6 @@ use evm_rpc_types::{ BlockTag, EthMainnetService, EthSepoliaService, GetTransactionCountArgs, Hex20, MultiRpcResult, Nat256, RpcService, }; -use ic_cdk_management_canister::{EcdsaCurve, EcdsaKeyId}; use ic_cdk::{init, update}; use ic_ethereum_types::Address; use num::{BigUint, Num}; @@ -257,7 +256,10 @@ fn estimate_transaction_fees() -> (u128, u128, u128) { #[derive(CandidType, Deserialize, Debug, Default, PartialEq, Eq)] pub struct InitArg { pub ethereum_network: Option, - pub ecdsa_key_name: Option, + /// ECDSA key name as used by the IC management canister: "dfx_test_key" (local dfx), + /// "test_key_1" (ICP mainnet testing), or "key_1" (ICP mainnet production). + /// Defaults to "test_key_1". + pub ecdsa_key_name: Option, } #[derive(CandidType, Deserialize, Debug, Default, PartialEq, Eq, Clone, Copy)] @@ -276,26 +278,6 @@ impl EthereumNetwork { } } -#[derive(CandidType, Deserialize, Debug, Default, PartialEq, Eq, Clone)] -pub enum EcdsaKeyName { - #[default] - TestKey1, - ProductionKey1, -} - -impl From<&EcdsaKeyName> for EcdsaKeyId { - fn from(value: &EcdsaKeyName) -> Self { - EcdsaKeyId { - curve: EcdsaCurve::Secp256k1, - name: match value { - EcdsaKeyName::TestKey1 => "test_key_1", - EcdsaKeyName::ProductionKey1 => "key_1", - } - .to_string(), - } - } -} - pub fn validate_caller_not_anonymous() -> Principal { let principal = ic_cdk::api::msg_caller(); if principal == Principal::anonymous() { diff --git a/rust/basic_ethereum/backend/state.rs b/rust/basic_ethereum/backend/state.rs index 81a8c479e1..abb8fb28a3 100644 --- a/rust/basic_ethereum/backend/state.rs +++ b/rust/basic_ethereum/backend/state.rs @@ -1,7 +1,7 @@ use crate::ecdsa::EcdsaPublicKey; -use crate::{EcdsaKeyName, EthereumNetwork, InitArg}; +use crate::{EthereumNetwork, InitArg}; use evm_rpc_types::{EthMainnetService, EthSepoliaService, RpcServices}; -use ic_cdk_management_canister::EcdsaKeyId; +use ic_cdk_management_canister::{EcdsaCurve, EcdsaKeyId}; use std::cell::RefCell; use std::ops::{Deref, DerefMut}; @@ -24,16 +24,29 @@ where STATE.with(|s| f(s.borrow_mut().deref_mut())) } -#[derive(Debug, Default, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct State { ethereum_network: EthereumNetwork, - ecdsa_key_name: EcdsaKeyName, + ecdsa_key_name: String, ecdsa_public_key: Option, } +impl Default for State { + fn default() -> Self { + Self { + ethereum_network: EthereumNetwork::default(), + ecdsa_key_name: "test_key_1".to_string(), + ecdsa_public_key: None, + } + } +} + impl State { pub fn ecdsa_key_id(&self) -> EcdsaKeyId { - EcdsaKeyId::from(&self.ecdsa_key_name) + EcdsaKeyId { + curve: EcdsaCurve::Secp256k1, + name: self.ecdsa_key_name.clone(), + } } pub fn ethereum_network(&self) -> EthereumNetwork { @@ -75,8 +88,8 @@ impl From for State { fn from(init_arg: InitArg) -> Self { State { ethereum_network: init_arg.ethereum_network.unwrap_or_default(), - ecdsa_key_name: init_arg.ecdsa_key_name.unwrap_or_default(), - ..Default::default() + ecdsa_key_name: init_arg.ecdsa_key_name.unwrap_or_else(|| "test_key_1".to_string()), + ecdsa_public_key: None, } } } diff --git a/rust/basic_ethereum/icp.yaml b/rust/basic_ethereum/icp.yaml index fbbdaedb9a..c36d8ceab0 100644 --- a/rust/basic_ethereum/icp.yaml +++ b/rust/basic_ethereum/icp.yaml @@ -17,26 +17,22 @@ canisters: environments: # Local: deploys both backend and evm_rpc (pre-built). # icp-cli auto-injects PUBLIC_CANISTER_ID:evm_rpc into the backend after deploying evm_rpc. - # Defaults: Sepolia + test_key_1. - name: local network: local - # Staging: backend only on ICP mainnet, Sepolia + test_key_1 (same as local defaults). - # Points to the shared EVM RPC canister already running on ICP mainnet. - - name: staging - network: ic - canisters: [backend] - settings: - backend: - environment_variables: - "PUBLIC_CANISTER_ID:evm_rpc": "7hfb6-caaaa-aaaar-qadga-cai" - - # Production: backend only on ICP mainnet, Ethereum mainnet + production ECDSA key. - - name: production + # IC mainnet: deploys only the backend; the shared EVM RPC canister is already running + # on ICP mainnet at 7hfb6-caaaa-aaaar-qadga-cai. + # + # Default configuration uses Sepolia testnet with test_key_1, suitable for development + # and testing on ICP mainnet with free Sepolia ETH from a faucet. + # + # For a production deployment on Ethereum mainnet, change init_args to: + # "(opt record {ethereum_network = opt variant {Mainnet}; ecdsa_key_name = opt \"key_1\"})" + - name: ic network: ic canisters: [backend] init_args: - backend: "(opt record {ethereum_network = opt variant {Mainnet}; ecdsa_key_name = opt variant {ProductionKey1}})" + backend: "(opt record {ethereum_network = opt variant {Sepolia}; ecdsa_key_name = opt \"test_key_1\"})" settings: backend: environment_variables: From f088cb1b42771f21473d1051f64242207e59a7f4 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 18:31:45 +0200 Subject: [PATCH 23/29] fix(rust/basic_ethereum): address review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Switch ic-secp256k1, ic-sha3, ic-ethereum-types from IC git repo to crates.io (0.3.0, 1.0.0, 1.0.0) — no need to pull the full IC repo - Pin getrandom from wildcard '*' to '0.2' - Fix grep pattern for Candid nat values: '[0-9][0-9_]*' + tr -d '_' to handle underscore digit separators (e.g. 1_000_000) Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/backend/Cargo.toml | 8 ++++---- rust/basic_ethereum/test.sh | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rust/basic_ethereum/backend/Cargo.toml b/rust/basic_ethereum/backend/Cargo.toml index fd2973326f..b120a1a537 100644 --- a/rust/basic_ethereum/backend/Cargo.toml +++ b/rust/basic_ethereum/backend/Cargo.toml @@ -17,12 +17,12 @@ evm_rpc_client = "0.4.0" ic-canister-runtime = "0.2" # transitive dependency: ic-crypto-ecdsa-secp256k1 -> k256 -> ecdsa -> elliptic-curve -> crypto-bigint -> rand_core -> getrandom # See https://forum.dfinity.org/t/module-imports-function-wbindgen-describe-from-wbindgen-placeholder-that-is-not-exported-by-the-runtime/11545/8 -getrandom = { version = "*", default-features = false, features = ["custom"] } +getrandom = { version = "0.2", default-features = false, features = ["custom"] } ic-cdk = "0.20" ic-cdk-management-canister = "0.1.1" -ic-secp256k1 = { git = "https://github.com/dfinity/ic", tag = "release-2025-07-03_03-27-base", package = "ic-secp256k1" } -ic-sha3 = { git = "https://github.com/dfinity/ic", tag = "release-2025-07-03_03-27-base", package = "ic-sha3" } -ic-ethereum-types = { git = "https://github.com/dfinity/ic", tag = "release-2025-07-03_03-27-base", package = "ic-ethereum-types" } +ic-secp256k1 = "0.3.0" +ic-sha3 = "1.0.0" +ic-ethereum-types = "1.0.0" serde = "1.0" serde_json = "1.0" serde_bytes = "0.11.15" diff --git a/rust/basic_ethereum/test.sh b/rust/basic_ethereum/test.sh index fbb19dbc52..df0849ca68 100755 --- a/rust/basic_ethereum/test.sh +++ b/rust/basic_ethereum/test.sh @@ -34,7 +34,7 @@ echo "=== Test 4: get_balance returns non-zero balance for a known funded Sepoli KNOWN_ADDRESS="0x378a452B20d1f06008C06c581b1656BdC5313c0C" result=$(icp canister call backend get_balance "(opt \"$KNOWN_ADDRESS\")") echo "$result" -balance=$(echo "$result" | grep -oE '[0-9]+' | head -1) +balance=$(echo "$result" | grep -oE '[0-9][0-9_]*' | head -1 | tr -d '_') [ "$balance" -gt 0 ] && echo "PASS" || (echo "FAIL: expected non-zero Sepolia ETH balance for $KNOWN_ADDRESS" && exit 1) echo "=== Test 5: transaction_count_with_client returns the nonce (outgoing tx count) via EvmRpcClient ===" @@ -44,5 +44,5 @@ echo "=== Test 5: transaction_count_with_client returns the nonce (outgoing tx c # The known address has 2 outgoing transactions on Sepolia (nonce = 2). result=$(icp canister call backend transaction_count_with_client "(opt \"$KNOWN_ADDRESS\", null)") echo "$result" -count=$(echo "$result" | grep -oE '[0-9]+' | head -1) +count=$(echo "$result" | grep -oE '[0-9][0-9_]*' | head -1 | tr -d '_') [ "$count" -ge 2 ] && echo "PASS" || (echo "FAIL: expected nonce >= 2 for $KNOWN_ADDRESS, got $count" && exit 1) From 7fb5ad79d4b004c899f698ba8bd31f64bbef76a4 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 18:48:24 +0200 Subject: [PATCH 24/29] docs(rust/basic_ethereum): clarify evm_rpc is local-only in icp.yaml comment Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/icp.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rust/basic_ethereum/icp.yaml b/rust/basic_ethereum/icp.yaml index c36d8ceab0..231dbd70f0 100644 --- a/rust/basic_ethereum/icp.yaml +++ b/rust/basic_ethereum/icp.yaml @@ -3,6 +3,9 @@ canisters: recipe: type: "@dfinity/rust@v3.3.0" + # 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] only and injects its principal via env vars. - name: evm_rpc build: steps: From 5035090c829eec6fca0e149b4557242632eac817 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 18:48:54 +0200 Subject: [PATCH 25/29] fix(rust/basic_ethereum): include key name in ECDSA trap message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes misconfiguration failures diagnosable — e.g. a typo in the ecdsa_key_name init arg now shows which key name was attempted. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/backend/state.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rust/basic_ethereum/backend/state.rs b/rust/basic_ethereum/backend/state.rs index abb8fb28a3..b490da50a0 100644 --- a/rust/basic_ethereum/backend/state.rs +++ b/rust/basic_ethereum/backend/state.rs @@ -101,6 +101,7 @@ pub async fn lazy_call_ecdsa_public_key() -> EcdsaPublicKey { return ecdsa_pk; } let key_id = read_state(|s| s.ecdsa_key_id()); + let key_name = key_id.name.clone(); let response = ecdsa_public_key(&EcdsaPublicKeyArgument { canister_id: None, derivation_path: vec![], @@ -109,8 +110,8 @@ pub async fn lazy_call_ecdsa_public_key() -> EcdsaPublicKey { .await .unwrap_or_else(|e| { ic_cdk::trap(&format!( - "failed to get canister's public key: {:?}", - e, + "failed to get ECDSA public key for key '{}': {:?}", + key_name, e, )) }); let pk = EcdsaPublicKey::from(response); From 0a059b0c76a6a1a1559183e77a17f241fc485c57 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 18:51:48 +0200 Subject: [PATCH 26/29] fix(rust/basic_ethereum): correct transaction_count reference in README Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index 0200d61570..349a41c92d 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -26,7 +26,7 @@ cd examples/rust/basic_ethereum ### Deploy and test locally -The local icp-cli network supports real HTTPS outcalls, so `get_balance` and `transaction_count` work against live Ethereum Sepolia data without deploying to ICP mainnet. +The local icp-cli network supports real HTTPS outcalls, so `get_balance` and `transaction_count_with_client` work against live Ethereum Sepolia data without deploying to ICP mainnet. ```bash icp network start -d From 771dc4cee8d8a416fbb13c50ca2b58adbf60dee0 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 18:52:22 +0200 Subject: [PATCH 27/29] fix(rust/basic_ethereum): mention all three Sepolia functions in README Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index 349a41c92d..dff3d6c12e 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -26,7 +26,7 @@ cd examples/rust/basic_ethereum ### Deploy and test locally -The local icp-cli network supports real HTTPS outcalls, so `get_balance` and `transaction_count_with_client` work against live Ethereum Sepolia data without deploying to ICP mainnet. +The local icp-cli network supports real HTTPS outcalls, so `get_balance`, `transaction_count`, and `transaction_count_with_client` work against live Ethereum Sepolia data without deploying to ICP mainnet. ```bash icp network start -d From 90610015fefc9021301a6853a284dfe9c1f1cd5e Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 18 Jun 2026 18:53:37 +0200 Subject: [PATCH 28/29] docs(rust/basic_ethereum): mention Ethereum mainnet option for local testing Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index dff3d6c12e..296c95a629 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -26,7 +26,7 @@ cd examples/rust/basic_ethereum ### Deploy and test locally -The local icp-cli network supports real HTTPS outcalls, so `get_balance`, `transaction_count`, and `transaction_count_with_client` work against live Ethereum Sepolia data without deploying to ICP mainnet. +The local icp-cli network supports real HTTPS outcalls, so `get_balance`, `transaction_count`, and `transaction_count_with_client` work against live Ethereum Sepolia data without deploying to ICP mainnet. To query Ethereum mainnet data instead, pass `--args '(opt record {ethereum_network = opt variant {Mainnet}})'` to `icp deploy`. ```bash icp network start -d From 3913eabfd157818e6d7205d9a18d369885c7ba21 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 19 Jun 2026 08:16:09 +0200 Subject: [PATCH 29/29] fix(rust/basic_ethereum): replace stale EVM RPC API key instructions with doc link authorize/updateProvider no longer exist in evm_rpc-v2.8.0. Rather than maintaining version-specific admin CLI commands in this README, point to the EVM RPC canister documentation instead. Co-Authored-By: Claude Sonnet 4.6 --- rust/basic_ethereum/README.md | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/rust/basic_ethereum/README.md b/rust/basic_ethereum/README.md index 296c95a629..25c4436b33 100644 --- a/rust/basic_ethereum/README.md +++ b/rust/basic_ethereum/README.md @@ -116,22 +116,7 @@ Returns the transaction hash. Track it on [Sepolia Etherscan](https://sepolia.et The example uses [PublicNode](https://ethereum-sepolia-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, the EVM RPC canister supports premium providers (Alchemy, Ankr, BlockPi) that offer higher rate limits and reliability when configured with API keys. To enable them locally: - -1. Register as a provider admin on the locally deployed EVM RPC canister: -```bash -icp canister call evm_rpc authorize '(record { auth = variant { RegisterProvider }; principal = principal "YOUR_PRINCIPAL" })' -``` - -2. Update the desired provider with your API key: -```bash -icp canister call evm_rpc updateProvider '(record { providerId = 6; apiKey = opt "YOUR_ALCHEMY_API_KEY" })' -``` - -3. In `backend/state.rs`, change `evm_rpc_services()` to use `None` to include all configured providers (better consensus across multiple providers): -```rust -EthereumNetwork::Sepolia => RpcServices::EthSepolia(None), -``` +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 `evm_rpc_services()` in `backend/state.rs` to pass `None` instead of an explicit provider list to use all configured providers for better consensus. ## Security considerations and best practices