Skip to content

docs: Ethereum integration guide - #39

Merged
marc0olo merged 4 commits into
mainfrom
docs/guides-chain-fusion-ethereum
Apr 15, 2026
Merged

docs: Ethereum integration guide#39
marc0olo merged 4 commits into
mainfrom
docs/guides-chain-fusion-ethereum

Conversation

@marc0olo

@marc0olo marc0olo commented Mar 20, 2026

Copy link
Copy Markdown
Member

Summary

  • EVM RPC canister architecture: how ICP canisters call Ethereum via multi-provider consensus
  • Supported chains table (Ethereum, Arbitrum, Base, Optimism) with provider details
  • Reading onchain data: typed API (eth_getBlockByNumber, eth_getTransactionCount) and raw JSON-RPC
  • ERC-20 balance example with ABI-encoded calls (Motoko/Rust tabs)
  • Signing and sending transactions via threshold ECDSA + EVM RPC
  • Cycle cost table for all RPC methods
  • Development setup with local EVM RPC canister
  • Common mistakes table (5 pitfalls)

Sync recommendation

Informed by dfinity/portaldocs/building-apps/chain-fusion/ethereum/*; dfinity/icskillsskills/evm-rpc/SKILL.md; dfinity/cdk-rsic-cdk/src/management_canister.rs (ECDSA management canister API)

@marc0olo

Copy link
Copy Markdown
Member Author

Review: Ethereum integration guide

Must fix

  • --with-cycles flag does not exist for icp canister call (lines 635-648): The icp-cli reference shows --cycles <CYCLES> only when --proxy is specified. There is no general --with-cycles flag. The entire "Testing via icp-cli" section needs the correct approach documented or a {/* Needs human verification */} flag.
  • icp deps pull/init/deploy commands don't exist (lines 619-621): There is no deps subcommand in icp-cli at all. The local deployment section needs a correct approach for deploying the EVM RPC canister locally.
  • Motoko canister:evm_rpc import syntax: Per project note "Motoko named imports in flux", canister:name syntax doesn't work with icp-cli. All Motoko examples use import EvmRpc "canister:evm_rpc". At minimum add a {/* Needs human verification: canister:name import syntax may not work with icp-cli */} flag, or explain the alternative (importing by canister ID).
  • dfx_test_key in comments (lines 382, 408): Comments say // Use "dfx_test_key" locally. While this is a protocol-level key name, mentioning dfx in a comment violates the spirit of the no-dfx rule. Consider rephrasing to e.g. // Use "dfx_test_key" for local testing// Use the test key "dfx_test_key" locally (this is a protocol-level key name).

Suggestions

  • Content brief gap — "Compare direct RPC vs HTTPS outcalls approach": The stub asks for this comparison. The page mentions HTTPS outcalls briefly in "How it works" but doesn't provide an explicit comparison of when to use EVM RPC canister vs. direct HTTPS outcalls. A short subsection would satisfy the brief.
  • Missing sign_with_ecdsa call: The signing step is deferred to external examples. Showing the call signature (~10 lines) inline would make the signing flow more complete without exceeding the 30-line limit.
  • Motoko ERC-20 example is ~63 lines (lines 233-296): Well over the 30-line guideline. Could be trimmed (e.g., elide the long list of null fields with a comment like // ... all optional fields null).
  • -e ic vs -n ic: Using -e ic implies an environment named "ic" in icp.yaml. For calling canisters by principal ID directly, -n ic (--network) may be more appropriate. Need to verify which approach is correct for mainnet targeting.
  • Collateral cycles "currently fully refunded" claim (lines 585-587): This is time-sensitive information. Consider adding a verification date or flag.

Verified

  • Canister ID 7hfb6-caaaa-aaaar-qadga-cai matches evm-rpc icskill
  • Supported chains and providers table matches icskill exactly
  • Cycle cost formula matches icskill: (5_912_000 + 60_000 * nodes + 2400 * request_bytes + 800 * max_response_bytes) * nodes * rpc_count
  • Rust CDK API usage verified: Call::unbounded_wait, with_args, with_cycles, candid_tuple all match .sources/cdk-rs
  • Rust ECDSA types verified: EcdsaPublicKeyArgument, EcdsaKeyId, EcdsaCurve::Secp256k1
  • Motoko core library used throughout (never base)
  • persistent actor syntax correct
  • MultiResult handling correct (Consistent(Ok), Consistent(Err), Inconsistent)
  • ERC-20 balanceOf selector 0x70a08231 correct
  • Old .md stub deleted, .mdx format correct with proper imports
  • Tab order (Motoko first), syncKey="lang", no headings in TabItem — all correct
  • Upstream comment present in correct JSX format
  • Two {/* Needs human verification */} flags present (Keccak library gap, EVM RPC WASM version)
  • All internal links resolve
  • Build passes
  • Diataxis how-to compliance confirmed

@marc0olo

Copy link
Copy Markdown
Member Author

icskills was bumped to 1d125a9. The following content on this PR may be outdated:

  1. Rust implementation should use evm_rpc_client crate. The EVM RPC skill was fully rewritten to use the evm_rpc_client crate, which provides a typed client API and re-exports all Candid types from evm_rpc_types. The PR currently uses raw Call::unbounded_wait — this still works but drifts from upstream guidance and requires manual type definitions that can diverge from the canister interface.

  2. Consensus strategy. The new skill recommends ConsensusStrategy::Threshold { total: Some(3), min: 2 } (2-of-3 agreement) instead of the default Equality strategy for most queries (e.g., eth_getBlockByNumber(Latest) fails with Equality since providers are often 1-2 blocks apart). Check whether the PR's examples use the default strategy and add a note if so.

  3. icp-cli compatibility bumped to >= 0.2.2. If the page references an icp-cli version requirement, update it.

Please review before merging.

@marc0olo
marc0olo force-pushed the docs/guides-chain-fusion-ethereum branch from bffdf77 to 1945174 Compare April 15, 2026 10:21
@marc0olo
marc0olo force-pushed the docs/guides-chain-fusion-ethereum branch from 1945174 to 3795697 Compare April 15, 2026 11:27
@marc0olo

Copy link
Copy Markdown
Member Author

Feedback addressed:

  • Fixed --with-cycles flag (doesn't exist as a general flag; --cycles requires --proxy); added verification flag on testing section
  • Removed icp deps pull/init/deploy (subcommand doesn't exist); added verification flag on local deployment
  • Added verification flag on Motoko canister:evm_rpc import syntax
  • Rephrased dfx_test_key comments to clarify it's a protocol-level key name
  • Added note recommending evm_rpc_client crate for Rust implementation
  • Added tip about ConsensusStrategy::Threshold for Latest block queries

…I guidance

- Fix deprecated Rust ECDSA import: ic_cdk::api::management_canister::ecdsa ->
  ic_cdk::management_canister; EcdsaPublicKeyArgument -> EcdsaPublicKeyArgs
- Replace dfx_test_key with test_key_1 as default (both Motoko and Rust);
  mention key_1 for production
- MultiResult/RpcResult -> MultiRpcResult with correct Ok/Err arms throughout
  all Rust snippets; use evm_rpc_types imports
- Update prose reference: MultiResult -> MultiRpcResult
- CLI testing: replace unresolved verification comment with clear guidance —
  query calls work directly, update calls require going through backend wrapper
- Remove local deployment verification comment (resolved by icp.yaml approach)
- Cargo.toml note: remove unused evm_rpc_client and ic-canister-runtime;
  keep only evm_rpc_types and ic-cdk which the examples actually use
@marc0olo

Copy link
Copy Markdown
Member Author

Review complete. All findings addressed in commit 6faab6b:

Fixed:

  • Deprecated Rust ECDSA import: ic_cdk::api::management_canister::ecdsa::*ic_cdk::management_canister::*; EcdsaPublicKeyArgumentEcdsaPublicKeyArgs
  • Key names: dfx_test_key removed; examples now use test_key_1 (default, works locally and on mainnet), with key_1 noted for production
  • MultiResult/RpcResultMultiRpcResult with correct Ok/Err arms throughout all Rust snippets; imports updated to evm_rpc_types
  • CLI testing section: removed unresolved verification comment; replaced with clear guidance — query methods work directly, update calls (which require cycles) should be tested through your backend canister's wrapper functions
  • Removed resolved local deployment verification comment
  • Cargo.toml example: removed unused evm_rpc_client and ic-canister-runtime; kept only evm_rpc_types and ic-cdk which the examples actually use

Verified:

  • All internal links valid (.md.mdx resolution confirmed); EVM RPC canister ID correct; cycle cost formula verified; Motoko/Rust API usage verified against .sources/icskills/skills/evm-rpc/ and .sources/cdk-rs/; build passes

@marc0olo

Copy link
Copy Markdown
Member Author

Feedback addressed:

  • Fixed ecdsa_public_key call: pass argument by reference (&request) and remove incorrect tuple destructuring ((response,)response) — function returns CallResult<EcdsaPublicKeyResult>, not a tuple
  • Added missing imports to get_eth_balance raw JSON-RPC snippet: replaced unused candid::{CandidType, Deserialize} with evm_rpc_types::{EthMainnetService, RpcError, RpcService}
  • Added missing imports to get_erc20_balance snippet: evm_rpc_types::{EthMainnetService, RpcError, RpcService}
  • Added missing imports to "Querying other EVM chains" snippet: evm_rpc_types::{Block, BlockTag, CustomRpcService, MultiRpcResult, RpcError, RpcService, RpcServices}
  • Updated Upstream: comment to include dfinity/cdk-rs — ic-cdk/src/management_canister.rs

@marc0olo
marc0olo merged commit 4c9ee08 into main Apr 15, 2026
1 check passed
@marc0olo
marc0olo deleted the docs/guides-chain-fusion-ethereum branch April 15, 2026 14:06
aterga added a commit that referenced this pull request Sep 3, 2026
## Summary

Follow-up to #376. That PR correctly retargeted the opt-in caution from
the old single terms of service to the [App Operator
Terms](https://internetcomputer.org/icp-mcp/app-operator-terms/), but it
inherited the ambiguity those Terms carried: it presented registration
as the acceptance step while also treating manifest publication as
acceptance by conduct, so a reader could not tell what they must do
before their app can be acted on.

dfinity/internetcomputer-org#96 resolved that in the Terms themselves.
This states the same model here, which matters because the Terms point
at this page for how acceptance happens (and say that where the two
differ, the Terms prevail).

## What the caution now says

- **Publishing the Layer 1 manifest at `/.well-known/ic-architecture`
opts the app in and constitutes the operator&#39;s acceptance** of the
App Operator Terms. It is sufficient to make the app&#39;s declared
canisters eligible for everything ICP MCP does with them, queries and
state-changing calls alike; no separate registration is required.
- **Publication is the condition ICP MCP applies to every call that
reaches a canister.** It reads and calls only canisters a manifest
declares, so an app that publishes none is still resolved and described,
but none of its canisters is read or called. Removing the manifest stops
new calls of every kind.
- **Registration is optional** and is not a condition of participation.
It is how an operator tells DFINITY who they are, so they can be reached
with the notices the terms provide for, and so there is a record of who
accepted and which version; it can carry the URL of the app&#39;s
**own** privacy policy so ICP MCP can present it to users.
- The privacy sentence describes what the [ICP MCP Privacy
Policy](https://internetcomputer.org/icp-mcp/privacy-policy/) actually
covers (what is disclosed to a participating app, and what registration
processes), and states that the operator remains responsible for
handling that data lawfully and for keeping the app&#39;s own privacy
notice accurate — rather than asking operators to &#34;comply with&#34;
a notice that describes DFINITY&#39;s processing and expressly does not
govern theirs.
- The **caution title** now names the trigger (&#34;Publishing the Layer
1 manifest opts your app in&#34;), which review caught contradicting the
body: the old title read as though any layer opts an app in, while the
body pins opt-in to Layer 1 and notes the layers are otherwise
independently adoptable.

The five-layer content below the caution is unchanged.

## Revision history

This PR went through three models of what publication gates, because the
code moved under it:

1. An early revision gated state-changing calls on DFINITY confirming
the operator&#39;s registration. That was the wrong branch of the two
the review offered; publication really was sufficient, so registration
became optional instead.
2. Two review suggestions then narrowed what publication gates to
**state-changing calls only**, on the correct ground that
`crates/imcp2-core/src/discoverability.rs` gated writes only and its
`READS_ARE_FINE` refusal text told agents that reads worked regardless.
Both were applied verbatim in `60a73a0` and `8ec933f`.
3. **`e4ce4be`** undoes that narrowing, because dfinity/imcp2#184
(`64c48be`, "Gate every canister-reaching tool on the discoverability
manifest") has since made reads gated the same way.
`get_canister_candid`, `get_canister_api_doc`,
`get_canister_oql_schema`, `canister_query` on both paths and
`canister_update_call` are all restricted to declared canisters; per the
module docs, "Reads and writes share one mechanism and differ only in
what a refusal SAYS", `the_rule()` now says the server "**reads and
calls** ONLY canisters an app declares", and `READS_ARE_FINE` is gone.

So the two suggested sentences were accurate when written and are not
now. Step 3 keeps their substance where it still holds (publication is
sufficient, registration is optional) and moves the line to where the
code now draws it: not reads versus writes, but **calls that reach a
canister versus the surface that does not**. `open_app`, the static
guides and the identity tools reach no canister and stay ungated, which
is why the caution still says an app publishing no manifest is resolved
and described.

@sea-snake, your approval was on `8ec933f`, so this last commit is newer
than what you read.

## Verification

- `npm run validate` — **210 files, all checks passed** on each
revision, including `e4ce4be` (the repo&#39;s no-em-dash rule caught new
prose in the first draft, fixed before pushing).
- Rendered preview checked, not just the source, on the earlier
revisions: the caution reads as intended and the page links only
`app-operator-terms/` and `privacy-policy/`, with no `/icp-mcp/terms/`
link that would send operators to the User Terms.
- The claims about the gate are quoted from
`crates/imcp2-core/src/discoverability.rs` at imcp2 `main` (`64c48be`),
not from memory.

## Companion

dfinity/internetcomputer-org#97 makes the matching correction to App
Operator Terms §3, §4 and §7. Merge that with or before this, since the
Terms are the authority this page defers to.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01Xpg83AWtkQo34jbMxpX8WB

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant