From b0e6e9b15a5f21d2ae04a3f336de27dfeeb55e79 Mon Sep 17 00:00:00 2001 From: candyburst Date: Sun, 31 May 2026 19:29:41 +0530 Subject: [PATCH 1/5] docs: add MetaMask integration guide with USDC token setup Adds comprehensive guide for integrating Arc Testnet with MetaMask, including the critical wallet_watchAsset call to register USDC. Without this step, users see no USDC balance in MetaMask after receiving tokens, causing confusion and making DApps appear broken. Includes: - Complete onboarding flow with wallet_addEthereumChain - wallet_watchAsset call for USDC token registration - Contract addresses and chain configuration - Why this matters section explaining the impact Fixes #97 Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 1 + docs/metamask-integration.md | 119 +++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 docs/metamask-integration.md diff --git a/README.md b/README.md index 98318c4..51f88da 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Arc is an open EVM-compatible layer 1 built on [Malachite](https://github.com/ci - 🚀 **[Execution](crates/node/README.md)** - Execution binary and configuration - 🗳️ **[Consensus](crates/malachite-app/README.md)** - Consensus binary and configuration +- 🦊 **[MetaMask Integration](docs/metamask-integration.md)** - Connect MetaMask and register USDC token - More: see Arc [developer docs](https://docs.arc.network/arc/concepts/welcome-to-arc) for guides, APIs, and specs ## Install and Run a Node diff --git a/docs/metamask-integration.md b/docs/metamask-integration.md new file mode 100644 index 0000000..5eaebd2 --- /dev/null +++ b/docs/metamask-integration.md @@ -0,0 +1,119 @@ +# MetaMask Integration Guide + +Guide for integrating Arc Testnet with MetaMask, including USDC token setup. + +## Add Arc Testnet to MetaMask + +Use `wallet_addEthereumChain` to add Arc Testnet: + +```typescript +await window.ethereum.request({ + method: "wallet_addEthereumChain", + params: [{ + chainId: "0x4CF252", // 5042002 in hex + chainName: "Arc Testnet", + nativeCurrency: { + name: "USDC", + symbol: "USDC", + decimals: 6, + }, + rpcUrls: ["https://rpc.arc.network"], + blockExplorerUrls: ["https://testnet.arcscan.app"], + }], +}); +``` + +## Register USDC Token + +**Important:** MetaMask does not automatically show USDC in the token list for custom chains. After adding the network, you must register USDC using `wallet_watchAsset`: + +```typescript +await window.ethereum.request({ + method: "wallet_watchAsset", + params: { + type: "ERC20", + options: { + address: "0x3600000000000000000000000000000000000000", + symbol: "USDC", + decimals: 6, + image: "https://cryptologos.cc/logos/usd-coin-usdc-logo.png", + }, + }, +}); +``` + +MetaMask will show a confirmation dialog. Once accepted, USDC will appear in the user's token list with the correct balance. + +## Complete Onboarding Flow + +Recommended sequence for DApp wallet connection: + +```typescript +async function connectWallet() { + try { + // 1. Request account access + const accounts = await window.ethereum.request({ + method: "eth_requestAccounts", + }); + + // 2. Add Arc Testnet network + await window.ethereum.request({ + method: "wallet_addEthereumChain", + params: [{ + chainId: "0x4CF252", + chainName: "Arc Testnet", + nativeCurrency: { + name: "USDC", + symbol: "USDC", + decimals: 6, + }, + rpcUrls: ["https://rpc.arc.network"], + blockExplorerUrls: ["https://testnet.arcscan.app"], + }], + }); + + // 3. Register USDC token + await window.ethereum.request({ + method: "wallet_watchAsset", + params: { + type: "ERC20", + options: { + address: "0x3600000000000000000000000000000000000000", + symbol: "USDC", + decimals: 6, + image: "https://cryptologos.cc/logos/usd-coin-usdc-logo.png", + }, + }, + }); + + console.log("Wallet connected:", accounts[0]); + return accounts[0]; + } catch (error) { + console.error("Wallet connection failed:", error); + throw error; + } +} +``` + +## Why This Matters + +Without the `wallet_watchAsset` call: +- Users see no USDC balance in MetaMask after receiving tokens +- Users assume transactions failed +- DApps appear broken + +Every DApp on Arc Testnet that involves USDC transfers should include this step in their onboarding flow. + +## Contract Addresses + +**Arc Testnet:** +- USDC: `0x3600000000000000000000000000000000000000` +- Chain ID: `5042002` (hex: `0x4CF252`) +- RPC: `https://rpc.arc.network` +- Explorer: `https://testnet.arcscan.app` + +## References + +- [MetaMask wallet_watchAsset documentation](https://docs.metamask.io/wallet/reference/wallet_watchasset/) +- [MetaMask wallet_addEthereumChain documentation](https://docs.metamask.io/wallet/reference/wallet_addethereumchain/) +- [Arc Network documentation](https://docs.arc.network/) From 832571e315028c274d7ce51c39d1bcd0589840a4 Mon Sep 17 00:00:00 2001 From: zkasuran Date: Tue, 9 Jun 2026 02:22:23 +0530 Subject: [PATCH 2/5] docs: fix Arc Testnet chain config and document switch-chain workaround Three corrections to the MetaMask guide, all verified against the repo config, the MetaMask spec and the live testnet RPC: - chainId was 0x4CF252 (5042002 transposed to 5042770). The testnet chain id is 5042002, which is 0x4CEF52 (hardhat.config.ts, defaults.rs). - nativeCurrency was USDC/6 decimals. MetaMask only supports 18-decimal native currencies and rejects decimals != 18, so the add call has to use ETH/18 even though gas is paid in USDC (issue #95). The USDC ERC-20 watchAsset call keeps decimals: 6, which is correct. - rpcUrls was https://rpc.arc.network, which does not resolve. Switched to https://rpc.drpc.testnet.arc.network, which returns chain id 0x4cef52 and Access-Control-Allow-Origin: * for browser DApps (#90). Also documents that wallet_switchEthereumChain fails silently or throws 4902 on Arc Testnet, and that wallet_addEthereumChain should be used as both add and switch (issue #89). --- docs/metamask-integration.md | 67 ++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/docs/metamask-integration.md b/docs/metamask-integration.md index 5eaebd2..b67a3e2 100644 --- a/docs/metamask-integration.md +++ b/docs/metamask-integration.md @@ -10,14 +10,51 @@ Use `wallet_addEthereumChain` to add Arc Testnet: await window.ethereum.request({ method: "wallet_addEthereumChain", params: [{ - chainId: "0x4CF252", // 5042002 in hex + chainId: "0x4CEF52", // 5042002 in hex chainName: "Arc Testnet", nativeCurrency: { - name: "USDC", - symbol: "USDC", - decimals: 6, + name: "ETH", + symbol: "ETH", + decimals: 18, }, - rpcUrls: ["https://rpc.arc.network"], + rpcUrls: ["https://rpc.drpc.testnet.arc.network"], + blockExplorerUrls: ["https://testnet.arcscan.app"], + }], +}); +``` + +### Why the native currency is "ETH" and not "USDC" + +Arc pays gas in USDC, but the `nativeCurrency` here still has to be `{ name: "ETH", symbol: "ETH", decimals: 18 }`. MetaMask only supports 18-decimal native currencies and validates that `decimals` equals 18, so a config with `decimals: 6` is rejected. If you pass `symbol: "USDC"` with `decimals: 18` the network is accepted but MetaMask shows the gas balance 10^12 times too high. + +The practical consequences: + +- MetaMask labels gas costs in "ETH" (for example "0.000000021 ETH") rather than USDC. Show the real USDC gas estimate in your own DApp UI if that matters to your users. +- This only affects the native gas display. The USDC ERC-20 token you register below keeps `decimals: 6` and shows the correct balance. + +See issue [#95](https://github.com/circlefin/arc-node/issues/95) for the full background. + +## Switching to Arc Testnet + +Do not use `wallet_switchEthereumChain` to move the user to Arc Testnet. On Arc Testnet it fails silently or throws a `4902` (chain not found) even after the network has already been added (issue [#89](https://github.com/circlefin/arc-node/issues/89)). + +Call `wallet_addEthereumChain` instead. It both adds the network if it is missing and switches to it if it is already present, so it is reliable in both cases: + +```typescript +// Unreliable on Arc Testnet, may resolve without switching or throw 4902 +// await window.ethereum.request({ +// method: "wallet_switchEthereumChain", +// params: [{ chainId: "0x4CEF52" }], +// }); + +// Reliable, works whether the network is already added or not +await window.ethereum.request({ + method: "wallet_addEthereumChain", + params: [{ + chainId: "0x4CEF52", + chainName: "Arc Testnet", + nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }, + rpcUrls: ["https://rpc.drpc.testnet.arc.network"], blockExplorerUrls: ["https://testnet.arcscan.app"], }], }); @@ -42,7 +79,7 @@ await window.ethereum.request({ }); ``` -MetaMask will show a confirmation dialog. Once accepted, USDC will appear in the user's token list with the correct balance. +MetaMask will show a confirmation dialog. Once accepted, USDC will appear in the user's token list with the correct balance. Note that USDC uses `decimals: 6` here, which is correct for the ERC-20 token and separate from the 18-decimal native currency above. ## Complete Onboarding Flow @@ -56,18 +93,18 @@ async function connectWallet() { method: "eth_requestAccounts", }); - // 2. Add Arc Testnet network + // 2. Add Arc Testnet network (also switches to it if already added) await window.ethereum.request({ method: "wallet_addEthereumChain", params: [{ - chainId: "0x4CF252", + chainId: "0x4CEF52", chainName: "Arc Testnet", nativeCurrency: { - name: "USDC", - symbol: "USDC", - decimals: 6, + name: "ETH", + symbol: "ETH", + decimals: 18, }, - rpcUrls: ["https://rpc.arc.network"], + rpcUrls: ["https://rpc.drpc.testnet.arc.network"], blockExplorerUrls: ["https://testnet.arcscan.app"], }], }); @@ -108,10 +145,12 @@ Every DApp on Arc Testnet that involves USDC transfers should include this step **Arc Testnet:** - USDC: `0x3600000000000000000000000000000000000000` -- Chain ID: `5042002` (hex: `0x4CF252`) -- RPC: `https://rpc.arc.network` +- Chain ID: `5042002` (hex: `0x4CEF52`) +- RPC: `https://rpc.drpc.testnet.arc.network` - Explorer: `https://testnet.arcscan.app` +The public RPC `https://rpc.testnet.arc.network` works too, but `rpc.drpc.testnet.arc.network` returns `Access-Control-Allow-Origin: *`, which is the safest choice for browser DApps calling the endpoint directly (see issue [#90](https://github.com/circlefin/arc-node/issues/90)). + ## References - [MetaMask wallet_watchAsset documentation](https://docs.metamask.io/wallet/reference/wallet_watchasset/) From a83f48dbc5bbd4f8986fc04eddcf3c3054499f1d Mon Sep 17 00:00:00 2001 From: Asuran Date: Wed, 10 Jun 2026 06:04:22 +0530 Subject: [PATCH 3/5] docs: note watchAsset ordering and dead explorer URLs (review) --- docs/metamask-integration.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/metamask-integration.md b/docs/metamask-integration.md index b67a3e2..ef08de4 100644 --- a/docs/metamask-integration.md +++ b/docs/metamask-integration.md @@ -81,6 +81,8 @@ await window.ethereum.request({ MetaMask will show a confirmation dialog. Once accepted, USDC will appear in the user's token list with the correct balance. Note that USDC uses `decimals: 6` here, which is correct for the ERC-20 token and separate from the 18-decimal native currency above. +**Order matters:** call `wallet_watchAsset` only after the `wallet_addEthereumChain` promise has resolved. If it fires while the user is still on a different network, MetaMask registers USDC against the wrong chain and the balance never shows up. The onboarding flow below awaits the chain add before registering the token for this reason. + ## Complete Onboarding Flow Recommended sequence for DApp wallet connection: @@ -151,6 +153,8 @@ Every DApp on Arc Testnet that involves USDC transfers should include this step The public RPC `https://rpc.testnet.arc.network` works too, but `rpc.drpc.testnet.arc.network` returns `Access-Control-Allow-Origin: *`, which is the safest choice for browser DApps calling the endpoint directly (see issue [#90](https://github.com/circlefin/arc-node/issues/90)). +`https://testnet.arcscan.app` is the only block explorer that currently resolves. The older `explorer.testnet.arc.network` and `explorer.arc.io` hosts are dead, so use `testnet.arcscan.app` in the `blockExplorerUrls` field and in any transaction-link examples. + ## References - [MetaMask wallet_watchAsset documentation](https://docs.metamask.io/wallet/reference/wallet_watchasset/) From b33caaac8b9e30046f87cf1c738bc3aed1e2816a Mon Sep 17 00:00:00 2001 From: Asuran Date: Wed, 10 Jun 2026 06:05:36 +0530 Subject: [PATCH 4/5] docs: correct explorer.arc.io status (gated, not dead) --- docs/metamask-integration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/metamask-integration.md b/docs/metamask-integration.md index ef08de4..c47f4e5 100644 --- a/docs/metamask-integration.md +++ b/docs/metamask-integration.md @@ -153,7 +153,7 @@ Every DApp on Arc Testnet that involves USDC transfers should include this step The public RPC `https://rpc.testnet.arc.network` works too, but `rpc.drpc.testnet.arc.network` returns `Access-Control-Allow-Origin: *`, which is the safest choice for browser DApps calling the endpoint directly (see issue [#90](https://github.com/circlefin/arc-node/issues/90)). -`https://testnet.arcscan.app` is the only block explorer that currently resolves. The older `explorer.testnet.arc.network` and `explorer.arc.io` hosts are dead, so use `testnet.arcscan.app` in the `blockExplorerUrls` field and in any transaction-link examples. +`https://testnet.arcscan.app` is the only public block explorer that currently works, so use it in the `blockExplorerUrls` field and in any transaction-link examples. `explorer.testnet.arc.network` no longer resolves, and `explorer.arc.io` resolves but sits behind Circle's internal Cloudflare Access login rather than serving a public explorer. ## References From b879bebc5fabb81298f36d83275ae974d7d41ed0 Mon Sep 17 00:00:00 2001 From: zkasuran Date: Thu, 11 Jun 2026 04:57:04 +0530 Subject: [PATCH 5/5] docs: wait for RPC transport after chain switch before sending tx (#130) --- docs/metamask-integration.md | 40 +++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/metamask-integration.md b/docs/metamask-integration.md index c47f4e5..41ca11f 100644 --- a/docs/metamask-integration.md +++ b/docs/metamask-integration.md @@ -60,6 +60,42 @@ await window.ethereum.request({ }); ``` +## Wait for the RPC Transport Before Sending Transactions + +`wallet_addEthereumChain` resolving does not mean transactions route to Arc Testnet yet. For a short window (roughly 200 to 500 ms) MetaMask already reports the new chain from `eth_chainId` while its internal JSON-RPC router still points at the previous endpoint. A transaction sent in that window lands on the old chain (issue [#130](https://github.com/circlefin/arc-node/issues/130)). The reported case was a CCTP `receiveMessage` meant for Arc Testnet that landed on Base Sepolia instead and reverted with `"Invalid destination domain"`. + +Polling `eth_chainId` does not close the gap because it flips before the routing does. Verify through `provider.getNetwork()` instead. It goes through the same transport as `eth_sendTransaction`, so once it returns the Arc Testnet chain ID, transactions route there too: + +```typescript +import { ethers } from "ethers"; + +// After wallet_addEthereumChain resolves, wait for the provider +// transport to catch up before sending any transaction. +async function waitForProviderChain( + expectedChainId: number, + retries = 6, +): Promise { + for (let i = 0; i < retries; i++) { + if (i > 0) await new Promise((r) => setTimeout(r, 500 * i)); + const provider = new ethers.BrowserProvider(window.ethereum); + const network = await provider.getNetwork(); + if (Number(network.chainId) === expectedChainId) return; + } + throw new Error("Network did not stabilize. Switch manually and retry."); +} + +await window.ethereum.request({ + method: "wallet_addEthereumChain", + params: [arcTestnetConfig], +}); +await waitForProviderChain(5042002); +// Now safe to send transactions +``` + +Construct a fresh `BrowserProvider` on each attempt. ethers caches the network per provider instance, so reusing one instance can keep returning the stale chain. + +The wait is only needed when a transaction follows the network switch in the same flow. Reading balances or registering tokens is not affected. + ## Register USDC Token **Important:** MetaMask does not automatically show USDC in the token list for custom chains. After adding the network, you must register USDC using `wallet_watchAsset`: @@ -134,6 +170,8 @@ async function connectWallet() { } ``` +If the flow continues straight into a transaction, run `waitForProviderChain` from the section above between steps 2 and 3. + ## Why This Matters Without the `wallet_watchAsset` call: @@ -153,7 +191,7 @@ Every DApp on Arc Testnet that involves USDC transfers should include this step The public RPC `https://rpc.testnet.arc.network` works too, but `rpc.drpc.testnet.arc.network` returns `Access-Control-Allow-Origin: *`, which is the safest choice for browser DApps calling the endpoint directly (see issue [#90](https://github.com/circlefin/arc-node/issues/90)). -`https://testnet.arcscan.app` is the only public block explorer that currently works, so use it in the `blockExplorerUrls` field and in any transaction-link examples. `explorer.testnet.arc.network` no longer resolves, and `explorer.arc.io` resolves but sits behind Circle's internal Cloudflare Access login rather than serving a public explorer. +`https://testnet.arcscan.app` is the only public block explorer that currently works, so use it in the `blockExplorerUrls` field and in any transaction-link examples. `explorer.testnet.arc.network` no longer resolves. `explorer.arc.io` resolves but sits behind Circle's internal Cloudflare Access login rather than serving a public explorer. ## References