feat: #2230 add Arc mainnet and testnet to wallet with USDC - #2228
feat: #2230 add Arc mainnet and testnet to wallet with USDC#2228Dami904 wants to merge 12 commits into
Conversation
Wires up Circle's Arc testnet (chain 5042002) as a new supported chain: RPC/WSS config, chain + Blockscout explorer seed entries, and the native USDC gas-token precompile as a supported stablecoin. USDC metadata verified on-chain (symbol/name/decimals) via scripts/verify-token.ts.
|
Resolved. References accepted issue #2230. |
About the
|
suisuss
left a comment
There was a problem hiding this comment.
What this changes
Registers chain 5042002 across the three chain-onboarding files: PUBLIC_RPCS and a CHAIN_CONFIG entry in lib/rpc/rpc-config.ts; a DEFAULT_CHAINS row, a Blockscout EXPLORER_CONFIG_TEMPLATES entry and a chainToDefaultIdMap line in scripts/seed/seed-chains.ts; and a TOKEN_CONFIGS row for 0x3600...0000 in scripts/seed/seed-tokens.ts. Additive, 64 lines, no deletions.
I checked the data against the live chain rather than the docs. eth_chainId returns 0x4cef52, which is 5042002, on both RPC hosts. The WSS host completes a real 101 Switching Protocols handshake. testnet.arcscan.app is a CNAME to arc-testnet.cname.blockscout.com and both /api and /api/v2/stats answer. 0x3600...0000 returns symbol() USDC, name() USDC, decimals() 6. Every value in the diff is correct.
Field for field it also matches precedent exactly: the DEFAULT_CHAINS entry carries all thirteen fields 0G Galileo carries, the explorer entry all seven Robinhood's Blockscout entry carries, and the token row all five Plasma's carries. Measured against .claude/commands/add-chain.md, whose success criteria name these three files, it is complete. The findings below are all in surfaces that checklist does not cover, because Arc is the first chain of its kind here.
Does it match the description
Matches. The description is accurate about what was verified and honest about what was not.
Single unit - the three files cannot be split. scripts/seed/seed-chains.ts dereferences CHAIN_CONFIG[5_042_002].jsonKey directly and unguarded, so reverting the rpc-config.ts hunk throws at module load, and the token row targets a chain with no chains row. One line is independently revertible: aliases, and see below.
Blocking
-
lib/wallet/build-withdrawable-assets.ts:43andcomponents/overlays/wallet/chain-balance-item.tsx:443- Arc's native balance and its supported-token row are the same money, and nothing dedups them. I checked one live account:eth_getBalancereturns83134536318811116andbalanceOf(0x3600...)returns83134, which is 0.083134 USDC at 18 decimals and at 6.buildWithdrawableAssetsskips the native asset only forTEMPO_CHAIN_IDS(:30), then concatenates natives and supported tokens with no symbol or value dedup, so Arc yields twoWithdrawableAssetentries both labelled USDC. The wallet rendersUSDC - Native - 21.7512853968directly aboveUSDC - 21.751285, with a Withdraw button on each. A user reads roughly double their balance, and a withdraw-everything flow driven off that list attempts both. Generalise the Tempo skip into "the native token is a registered ERC-20 on this chain" keyed on 5042002, and suppress the native row and its button the wayisTempodoes. -
scripts/seed/seed-chains.ts-aliases: ["arc"]on a testnet row. No other testnet in the file carries aliases; the bare chain name goes on the mainnet row for Plasma, 0G and Robinhood.classifyChainTag(lib/agentic-wallet/workflow-binding.ts:296-300) resolves an alias withrows.findoverloadEnabledChains(), whose query at:167-175has noORDER BY. Todaychain: "arc"resolves to the testnet and returns a harmlessunrecognised. When Arc mainnet lands withaliases: ["arc"]per mainnet precedent, two rows share the alias and the unordered find can pick either. That is the Tempo incident, recorded in this file atworkflow-binding.ts:225-230: "seed-chains.ts stamps aliases ["tempo"] on both Tempo rows ...chain: "tempo"collapsed to unrecognised and 403'd every Tempo payment." Dropaliaseshere, or use["arc-testnet"]and reserve"arc"for the mainnet row.
Mechanical - actionable as-is
-
components/overlays/wallet/chain-utils.ts:9-11andapp/api/supported-tokens/route.ts:15- Arc is missing from both copies ofINDEPENDENT_TOKEN_LIST_CHAIN_IDS, so its wallet card renders the Ethereum master list and shows USDT and USDS as "Not available" on a chain where they do not exist. That is the output the Plasma carve-out was written to prevent (route.ts:11-14). Add5_042_002to both; they are duplicated. -
plugins/blockscout/chains.ts:14-24- Arc's explorer is a hosted Blockscout instance, so5_042_002: "https://testnet.arcscan.app"belongs inBLOCKSCOUT_INSTANCESalongside Robinhood's4663. Without it, Blockscout steps on Arc need a hand-suppliedBLOCKSCOUT_API_URL. -
docs/platform-reference.md:165-168- the experimental-chains table lists 0G and Galileo and would list Arc. No CI check covers that table, so it is a convention rather than a gate.
With the team
-
The daily native value cap is denominated in wei with no chain dimension.
reserveOrgValue(lib/execute/value-ledger.ts:242-254) takes an organization and a wei amount, and the default is20000000000000000(lib/execute/spend-cap-defaults.ts:52), around 0.02 ETH. On Arc, 1e18 wei is one dollar, so moving 20 USDC reserves a thousand times the org's entire daily cross-chain budget and starves every other native transfer that day. It fails closed, so this is unusable rather than unsafe. I'm weighing making the cap per-chain against converting to a USD-denominated budget at reservation time - the first is a smaller change, the second stops the same problem recurring on the next stablecoin-gas chain. I'm taking it to the core team and will come back. It does not block this PR; it decides whether Arc should ship withisEnabled: true. -
#2230is not labelledacceptedyet, socheck-issue-linkis red. That is mine to clear, not yours. The reference is also missing from the title, so once the label lands, retitle tofeat: #2230 add Arc testnet to wallet with USDC.
Verdict
Changes requested: Arc's native gas token and its supported-token row are the same balance, and the wallet currently lists both.
|
@Dami904 Please raise and link your issue please |
…lias collision Arc's native gas token and its ERC-20 supported-token row are the same underlying USDC balance at two decimal precisions (18 vs 6), so the wallet was listing it twice with two Withdraw buttons. Generalized the existing Tempo native-row suppression into a native-mirrors-token check that also covers Arc, in both the wallet UI and the withdrawable-assets builder. Also dropped the arc alias from the testnet seed row -- reserved for the mainnet row per existing precedent (Plasma/0G/Robinhood), avoiding the same alias-collision class that previously broke Tempo payment routing. Mechanical follow-ups from review: added Arc to INDEPENDENT_TOKEN_LIST_CHAIN_IDS (both copies) so USDT/USDS don't render as false "Not available" entries, registered Arc's Blockscout instance, and added it to the experimental chains table in platform-reference.md.
fundedAssets() in use-wallet-digest.ts had the same unguarded native-plus-token push as build-withdrawable-assets.ts and chain-balance-item.tsx, feeding wallet-digest-menu.tsx's asset list with a duplicate USDC row for Arc. Applies the same native-mirrors-token skip.
suisuss
left a comment
There was a problem hiding this comment.
All four items from the last round are addressed, and you found a fifth site I had missed.
- Duplicate asset in the wallet: fixed.
lib/wallet/build-withdrawable-assets.ts:36-39addsNATIVE_MIRRORS_TOKEN_CHAIN_IDSand:52skips the native asset for it. The UI follows throughnativeMirrorsSupportedToken(components/overlays/wallet/chain-utils.ts:29-31), used atchain-balance-item.tsx:389for the row and both Withdraw buttons. The per-token Withdraw is untouched, so the balance stays movable through the USDC row. aliases: ["arc"]: removed. The Arc row ends atdefaultPrivateRpcUrlwith noaliases, so the unorderedrows.findinclassifyChainTagcan no longer bind the bare name to a testnet row when the mainnet arrives.INDEPENDENT_TOKEN_LIST_CHAIN_IDS: added to both copies,chain-utils.ts:19-21andapp/api/supported-tokens/route.ts:16.BLOCKSCOUT_INSTANCES: added atplugins/blockscout/chains.ts:24, andSUPPORTED_BLOCKSCOUT_CHAIN_IDSpicks it up fromObject.keys.
The fifth is the one worth calling out: lib/wallet/use-wallet-digest.ts:158-161 double-counted Arc in the wallet menu and its USD total, and nothing in my review pointed at it. You found it and shipped the follow-up.
What this changes since the last round
Three dedup sites, both independent-token-list copies, the Blockscout instance, the alias removal, and a docs/platform-reference.md:168 row. specs/api-coverage.json carries no reference to that file, so no doc-drift check fires.
Blocking
lib/wallet/build-withdrawable-assets.ts:52- the native asset is suppressed on chain id alone, not on a supported-token row actually being present.scripts/seed/seed-tokens.ts:439-445catches per-token errors and continues, so an RPC failure while seeding this chain leaves it enabled with no USDC row. The wallet then shows Arc with no native row, no token row and "No tokens on this chain", andbuildWithdrawableAssetsreturns nothing for it, so a real balance is both invisible and unwithdrawable. Suppress the native row only when a supported-token row for that chain is in the feed.
Mechanical - actionable as-is
- The same chain-id set now exists three times, and the copy at
lib/wallet/use-wallet-digest.ts:49-53re-hardcodes42_431, 4217rather than spreadingTEMPO_CHAIN_IDS. This diff is its own evidence: the first commit updated two of the three and shipped a menu that still double-counted, and the second was needed to patch the third. Export one set fromlib/wallet/and havechain-utils.tsre-export it, sonativeMirrorsSupportedTokenis the only reader. tests/unit/build-withdrawable-assets.test.ts:169covers the Tempo skip and has no parallel case for5_042_002, and the test file is untouched here. Dropping the id from any of the three sets would ship silently.use-wallet-digest.ts:50-51puts the// Tempo testnet/mainnetcomment after4217, so it reads as annotating only that id.
With the team
- Settled: Arc ships with
isEnabled: true. The daily native value cap is a single chain-agnostic wei budget (lib/execute/value-ledger.ts, default inlib/execute/spend-cap-defaults.ts), so on a chain where 1e18 wei is one dollar a 20 USDC transfer reserves about a thousand times an organisation's whole daily budget. That is real, but it fails closed, nothing is over-spent, and Arc is an experimental testnet - so it is not a reason to hold your work. It is tracked as KEEP-1309 and is ours. Nothing on this pull request is blocked on it.
Verdict
Changes requested: one item only, the native row being suppressed on chain id rather than on a supported-token row existing, which can strand a balance in the wallet if the token seed partially fails.
|
Pushed fixes for all review items:
Not done: the Everything else above is pushed to this branch. |
The native-row suppression for Arc/Tempo was keyed on chain id only. If a chain's token seed partially failed, the native balance disappeared from the wallet with no supported-token row to replace it -- invisible and unwithdrawable. nativeMirrorsSupportedToken now also requires a matching supported-token row to be present in the feed before suppressing native. Consolidated the chain-id set into lib/wallet/build-withdrawable-assets.ts as the single source of truth (it existed independently in three files; one of the earlier fixes only touched two of them). chain-utils.ts now re-exports it instead of re-declaring it. Added test coverage for Arc specifically (previously only Tempo was covered) and a regression test for the partial-seed-failure case.
suisuss
left a comment
There was a problem hiding this comment.
Previously raised, one line each: the existence-test suppressing native on a "0" placeholder - addressed, nativeMirrorsSupportedToken now requires hasFundedMirrorRow (lib/wallet/build-withdrawable-assets.ts:69-73,86-96) with a regression test. The digest re-declaring the predicate - addressed, the shape-agnostic helper is exported and used at use-wallet-digest.ts:159. Tempo's categorical rule merged into Arc's arithmetic one - addressed, isTempoChain is separate at :38 and composed at the four call sites. The missing USDC column in platform-reference.md - addressed at :161-176. The single hidesNativeRow(chainId, rows) helper - not addressed, and now hand-assembled at four sites, which is how surface four came to be missed. A fundedAssets test in use-wallet-digest.ts - not addressed.
Your claims check out, including the fourth surface: staging's use-account-assets.ts has no isTempo or mirror reference at all, so the native push really was unguarded there. I re-verified the chain facts live this round rather than carrying them forward - eth_chainId is 0x4cef52 on both RPCs, 0x3600...0000 reports symbol USDC, name USDC and 6 decimals, and the Blockscout v2 instance agrees. The WSS endpoint, which I marked UNSURE last time, now checks out: it completes the handshake and answers eth_chainId over the socket. Runbook coverage is complete, including the two surfaces beyond it (plugins/blockscout/chains.ts:24 and app/api/supported-tokens/route.ts:16), and isTestnet: true with status: "experimental" is right.
The auth label is a false positive from the filename regex matching seed-tokens.ts and supported-tokens/route.ts. I read the only permission-adjacent expression in the diff in full - isAdmin at chain-balance-item.tsx:485 and :536 - and it is unchanged; the new left operand only narrows what renders.
Blocking
lib/wallet/build-withdrawable-assets.ts:50-52,86-96- the mirror is keyed on chainId rather than on the mirroring token, so it re-creates the bug this round fixed as soon as a second token is seeded.nativeMirrorsSupportedTokensuppresses the native row when any supported-token row on that chain is funded. Arc testnet already carries EURC at0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a- live on the Blockscout token list, 6 decimals - and for a Circle chain seeding it is the obvious next step. -> A user holding EURC and USDC gets their native USDC row suppressed by the EURC row, and if the USDCbalanceOfthen fails (app/api/user/wallet/balances/route.ts:301-311pushes"0"), the balance is invisible and unwithdrawable again. Same at the other three sites. -> Make it aMap<number, string>of chainId to mirror-token address and require a funded row at that address.
Mechanical - actionable as-is
-
Fold the four hand-composed call sites into one
hidesNativeRow(chainId, rows).use-wallet-digest.ts:158reading the set directly is fine - its rows carry nochainId- but the other three are the same two-liner written out three times, and the fourth surface was missed precisely because there was no single place to add it. -
A
fundedAssetstest inuse-wallet-digest.ts. It is the only changed surface with no coverage, and the only one whose Tempo behaviour changes silently.
With the team
- Whether Tempo's native row should disappear from three surfaces. The diff removes it from the wallet chain card (
chain-balance-item.tsx:450, where staging:443renders it unconditionally), fromfundedAssetsand therefore the toolbar wallet menu and the prices request (use-wallet-digest.ts:157), and from the Settings assets table (use-account-assets.ts:60). Staging has no Tempo handling on any of the three. That is arguably right - it matcheschain-utils.ts:1's long-standing "stablecoins only, no native token" and matches the withdraw path, which has always refused Tempo natives - but it is a display change to a live mainnet chain inside a PR titled for an Arc testnet, and I would rather decide it than let it land unstated. I'll come back. Please add it to the PR body either way.
Verdict
Changes requested - on the chainId-keyed mirror, which re-creates the fixed bug on the next token seed.
No migration and no operator step: pnpm db:seed runs in the Helm pre-upgrade hook on both environments, so the chain and token land on deploy, and a flaky Arc RPC at seed time cannot block it because seed-tokens.ts:439-445 catches per token and exits 0. I confirmed that rather than assuming, since a missed seed step is the usual way a chain addition half-lands.
Screenshots
The Arc half of the ask is discharged and I have taken requested-evidence down to one item. Covered: the chain card settled and funded, the digest in both states, the placeholder state - which I did not ask for and which is the most valuable frame here, since it is the exact regression the funded gate exists to prevent - and the fourth surface before and after.
Still outstanding: a Tempo card with tokens seeded. It was one of the four things asked for, and the decision above has made it the highest-value shot, because Tempo is now the only surface where this PR removes a row that staging renders, on a live mainnet chain, with no test covering it.
Not blocking, but worth knowing: all five are mocked rather than a live Arc wallet - image 03 shows the unit-test fixture address rather than the seeded precompile, which you labelled. They prove the render gate, which is what I asked for. They do not exercise the seeded data path from seed-tokens.ts through /api/supported-tokens to the card, so that remains unverified end to end. The loading frame I asked for is moot - I retracted the premise last round, since useWalletBalances seeds natives at "0" so no button renders.
…ain id nativeMirrorsSupportedToken suppressed the native row whenever any supported-token row on the chain was funded, so a second seeded token (e.g. EURC on Arc) would suppress the native USDC row even with USDC's own balance unfunded or failing to load. Key the dedup on a chainId -> mirror token address map instead, and require the funded row to be that specific token. Also consolidates the isTempoChain || nativeMirrorsSupportedToken check into a single hidesNativeRow helper used at the three call sites that carry chainId-tagged rows, and adds test coverage for lib/wallet/use-wallet-digest.ts, which previously had none.
suisuss
left a comment
There was a problem hiding this comment.
What this changes
One commit of your own, 0380a723, plus a merge of staging - the compare spans 332 commits but 331 of them are the merge, and the eight files that move are all yours.
NATIVE_MIRRORS_TOKEN_CHAIN_IDS becomes NATIVE_MIRROR_TOKEN_ADDRESS, a ReadonlyMap<number,string> pinning Arc to the USDC precompile; hasFundedMirrorRow takes that address and matches tokenAddress ?? address case-insensitively with a positive balance; nativeMirrorsSupportedToken returns false for unmapped chains; a new exported hidesNativeRow(chainId, rows) composes it with isTempoChain and is consumed at build-withdrawable-assets.ts:146, use-account-assets.ts:53 and chain-balance-item.tsx:389. Plus 213 lines of tests, including a new use-wallet-digest.test.ts.
Every item from the last round lands. The mirror is keyed on the token address rather than the chain id, so the EURC case is closed and regression-tested on all three surfaces; three of the four call sites are folded into the helper, with use-wallet-digest.ts:156-160 documenting why the fourth cannot be; and fundedAssets now has its own tests.
I traced the address end to end rather than taking the map on trust: app/api/user/wallet/balances/route.ts:282,302 populates tokenAddress on both the success and the caught-failure push, lib/wallet/use-wallet-balances.ts:183 and use-wallet-digest.ts:259 carry it through, the seeded address is lowercase and the helper lowercases both sides. It holds.
Does it match the description
Matches. No dependency, no CI or config file, no migration, no network call, no changed default. The auth flag on this diff is the filename regex hitting seed-tokens.ts and supported-tokens/route.ts; I read both changed guards in full - chain-balance-item.tsx:483 and :534 keep isAdmin untouched in position and value, and the operand that changed only ever narrows what renders.
Mechanical - actionable as-is
-
lib/wallet/build-withdrawable-assets.ts:77-79- the row type is{ tokenAddress?: string; address?: string; balance: string }, sohasFundedMirrorRow([{ balance: "1" }], addr)type-checks and returnsfalsewhere before the increment it returnedtrue. -> A future caller that loses its address field fails silently rather than at compile time. The failure direction is the safe one - both rows render, so a double-count rather than a stranded balance - which is why this is here and not above. Requiring at least one address field, or taking agetAddressaccessor, makes it a type error. -
lib/wallet/build-withdrawable-assets.ts:68- the docstring still opens "Shape-agnostic" while the body now requires one of two named address fields. The sentence after it is accurate; the lead is not. -
components/overlays/wallet/chain-utils.ts:7-nativeMirrorsSupportedTokenis now a dead re-export. Its only remaining caller ishidesNativeRow, inside the defining module. -
lib/wallet/use-wallet-digest.ts:162- the localconst hidesNativeRowshadows the name of the exported helper it deliberately does not use. Correct today, but this is the one file that must not silently start importing the helper, and a distinct local name - the waychain-balance-item.tsx:389chosehidesNativeBalanceRow- makes that impossible rather than merely unlikely.
Screenshots
The funded, placeholder, EURC and Tempo captures cover what they claim, and the before-fix double-count shot is the useful one. What is still missing is the loading frame: useWalletBalances seeds balances with loading: true while supportedTokenBalances is empty, so hidesNativeRow is false for that frame and the Arc native row renders with a spinner before disappearing. One capture of the Arc card in that frame closes the request.
With the team
- Whether Tempo's native row should disappear from the chain card, the settings assets table and the wallet digest at once.
hidesNativeRow:136still composesisTempoChain, so all three suppress a rowstagingrenders. It is now pinned by tests on all three surfaces, which is an improvement over last round either way. I'm weighing consistency with the mirror rule against the fact that Tempo's suppression is categorical rather than derived from a funded row - the cost of getting it wrong is a hidden balance on one side and a double-count on the other. I'm settling it with the core team and will come back. Nothing here is blocked on you.
Verdict
Changes requested on the four mechanical items only - the mirror rewrite is correct on every surface I could reach, and the one open question is mine.
hasFundedMirrorRow now requires a row carry one of its two address fields at compile time instead of silently reading as unfunded when both are absent. Drops the unused nativeMirrorsSupportedToken re-export from chain-utils.ts, and renames the local hides-native-row calculation in use-wallet-digest.ts so it can't collide with the exported helper of the same name it deliberately doesn't call.
|
@Dami904 We are going to setup this PR to setup both mainnet (when it goes live on the 16th of Sep) and testnet. |
Registers Circle's Arc mainnet alongside the testnet this PR already adds. Same USDC-as-gas model and the same USDC precompile at 0x3600000000000000000000000000000000000000, verified at 6 decimals with eth_call against https://rpc.arc-scan.org. Two Arc mainnet limitations shape the diff. No public WebSocket endpoint. CHAIN_CONFIG[5042] omits publicWssDefault, so getWssUrl returns undefined and seed-chains writes NULL into chains.defaultPrimaryWss - the sanctioned path for a WSS-less chain. Event triggers are then skipped per workflow by the event-tracker's workflow-mapper and the block dispatcher's chain monitor throws and is dropped from the liveness map. Both log, neither crashes, and neither falls back to HTTP polling, so event and block triggers do not fire on Arc mainnet until an operator supplies a WSS URL through chain-config. No reachable block explorer. arcscan.app has no DNS record, arc-scan.org serves a Next.js app rather than a Blockscout API and returns 403 on /tx and /address, and explorer.arc.io is behind Circle's Cloudflare Access. The chain is seeded with no EXPLORER_CONFIG_TEMPLATES entry and no chainToDefaultIdMap line, so no explorer_configs row is written. The seed warns and skips rather than throwing, /api/chains returns null explorer fields, the ABI route answers 404, and every wallet explorer link is guarded on null. Arc mainnet is left on the global gas defaults deliberately. Its base fee is a flat 20 gwei across recent blocks with zero observed priority tips and a gasUsedRatio of 0.0, so the 2.0x limit multiplier and 0.1 gwei priority floor are safe; no multiplier was invented for a chain nobody has benchmarked.
|
ok then, sure |


Summary
Adds Circle's Arc as a supported chain in the wallet -- testnet (
5042002) and mainnet (5042) -- following the same onboarding pattern used for Robinhood Chain, Plasma, and 0G (RPC config -> chain seed -> explorer config -> supported-token seed).Arc is Circle's EVM-compatible L1 purpose-built for stablecoin finance. Its defining quirk relative to every other chain in this repo: USDC is the native gas token, not ETH. Circle also exposes a fixed ERC-20-interface precompile at
0x3600000000000000000000000000000000000000for programmatic balance/transfer access to that same native asset (6 decimals, distinct from the 18-decimal native-currency gas accounting) -- this repo'ssupported_tokensmodel expects an ERC-20 address, so that precompile is what's registered as the stablecoin entry.50420025042USDCUSDCexperimentalexperimentalrpc.testnet.arc.iorpc.arc-scan.orgtestnet.arcscan.app)Config-side entries for both chains are already merged in
chain-config(#46), keyedarc-testnet/arc-mainnet, so the overrides land as soon as this merges.Read this before approving the mainnet half
Mainnet is being onboarded over two known, unresolved gaps. They are not oversights and they are not fixed here.
1. No websocket, so event and block triggers will never fire on Arc mainnet. No public WSS endpoint exists for chain 5042: Circle's
rpc.mainnet.arc.iois Cloudflare-gated (403), dRPC answersUnknown networkforarcandarc-mainnet, andrpc.arc-scan.orgreturns 405 to an upgrade request. Both trigger services are WebSocket-only with no HTTP polling fallback, and the resulting failure is silent:chain-monitor.ts:440-452throwsNo WSS URLs configured,index.ts:174-188catches it and deletes the monitor fromthis.monitors, andhealth.ts:31-38computes liveness only over that map -- so the dispatcher reportshealthy: true, status: "ok"while Arc has no monitor. The only machine-readable trace is a stuckis_alive=0gauge.workflow-mapper.ts:70-82returnsnullon an emptydefaultPrimaryWss; the skip lands in a local counter that reaches a pass-summary log line but not Prometheus and not/healthz.Arc mainnet will also be the only chain in the fleet with a null
defaultPrimaryWss-- all other entries have one -- so this code path has never run in a deployed environment.chain-config#47 disables Arc mainnet on staging as well as production for that reason; merging that first means mainnet seeds disabled everywhere and the untested path stays dormant until someone deliberately enables it.2. No explorer.
arcscan.appdoes not resolve,arc-scan.orgis a Next.js app rather than Blockscout (/api/v2/statsreturns 404 HTML) and 403s on/tx/and/address/, andexplorer.arc.ioredirects to Circle SSO. Every consumer guards on null so nothing breaks, but the consequences are real:/api/chains/[chainId]/abireturns404 Explorer not configured for this chain, so automatic ABI fetching is unavailable and users must paste ABIs manually;enrich-explorer-linksearly-returns so executions carry notransactionLink/addressLink; wallet rows render without links.Both are documented for users in
docs/platform-reference.mdin this PR.Changes
Testnet (
5042002):lib/rpc/rpc-config.ts:PUBLIC_RPCS.ARC_TESTNET/_FALLBACK/_WSSand aCHAIN_CONFIG[5042002]entry (primary RPCrpc.testnet.arc.io, fallback via dRPC).scripts/seed/seed-chains.ts:DEFAULT_CHAINSentry,chainToDefaultIdMap["Arc Testnet"], and a BlockscoutEXPLORER_CONFIG_TEMPLATES[5042002]entry.scripts/seed/seed-tokens.ts:TOKEN_CONFIGSentry for the USDC precompile address, verified on-chain (see Testing).Mainnet (
5042), added by a maintainer on top of the original testnet-only diff:lib/rpc/rpc-config.ts:PUBLIC_RPCS.ARC_MAINNETandCHAIN_CONFIG[5042]withjsonKey: "arc-mainnet".publicFallbackand the WSS fields are deliberately absent -- no second RPC and no websocket exist, andgetWssUrlreturningundefinedis the sanctioned path for a chain with no reliable public WSS. Comments at both sites record why each field is missing.scripts/seed/seed-chains.ts:DEFAULT_CHAINSrow (name: "Arc",aliases: ["arc"],isTestnet: false). No explorer template and nochainToDefaultIdMapentry, since there is no explorer -- the seed handles the miss by warning and skipping, verified.scripts/seed/seed-tokens.ts: the same0x3600...0000precompile on 5042.lib/wallet/build-withdrawable-assets.ts:NATIVE_MIRROR_TOKEN_ADDRESSgains 5042, without which mainnet double-counts native + token exactly as the testnet did.app/api/supported-tokens/route.tsandcomponents/overlays/wallet/chain-utils.ts: 5042 added to both copies ofINDEPENDENT_TOKEN_LIST_CHAIN_IDS.docs/platform-reference.md: Arc mainnet row plus a plain-language note that event and block triggers do not fire there and ABIs must be supplied manually.Deliberately not changed, with reasons:
eth_getCodeon Arc mainnet shows the Safe proxy factory and L2 singleton deployed, but the Allowance Module is absent (0xCFbFaC74C26F8647cBDb8c5caf80BB5b32E43134returns0x).lib/safe/contracts.ts:50-59requires it forSUPPORTED_SAFE_CHAIN_IDS, so Arc cannot enter that set and can never reachdeployableChainIds. Adding it todeploy-safe-card.tsx'sMAINNET_CHAIN_IDSwould be unreachable code.baseFeePerGasflat at 20 gwei across six consecutive blocks,gasUsedRatio0.0in every one,eth_maxPriorityFeePerGasand alleth_feeHistoryreward percentiles0x0, latest block 76,787 gas of a 30M limit. On a chain 0.25% full the global default (gasLimitMultiplier: 2.0,minPriorityFeeGwei: 0.1) cannot cause non-inclusion; the floor is an inert ~0.5% overpay (~0.00042 USDC per simple transfer). Worth settingminPriorityFeeGwei: 0once someone benchmarks real inclusion.lib/rpc/types.tsSUPPORTED_CHAIN_IDS. Arc is absent, sogetNetworkName(5042)returns"Chain 5042"and EIP-712 signing is refused -- identical to Plasma, Robinhood, 0G, Polygon, Arbitrum, Optimism and Avalanche, all of which are also absent. That gap deserves its own ticket covering all eight, not a partial fix smuggled in here.Testing
Testnet, by the original author:
pnpm tsx scripts/verify-token.ts 5042002 0x3600000000000000000000000000000000000000against the real Arc testnet RPC confirmedsymbol: USDC,name: USDC,decimals: 6.pnpm dev:bootstrap,pnpm tsx scripts/seed/seed-chains.ts, andpnpm tsx scripts/seed/seed-tokens.tsagainst it. Arc Testnet and its explorer config landed correctly inchains/explorer_configs; the USDC token seed matched the standalone verification exactly./api/supported-tokens's actual master-list-overlay query logic against the seeded data. Arc's USDC correctly resolvesavailable: truewith the right decimals and explorer URL; USDT/USDS (mainnet-only symbols that don't exist on Arc) correctly resolveavailable: false-- same behavior as every other single-asset testnet (Sepolia, Base Sepolia, etc.).pnpm devfails to boot in that environment on an unrelated pre-existing Windows/Node bug (ERR_UNSUPPORTED_ESM_URL_SCHEMEin the plugin/protocol discovery step, unrelated to this diff).pnpm vitest run tests/unit/rpc-config.test.ts: 112/112 pass, including the newARC_TESTNET*entries automatically covered by the existingPUBLIC_RPCS contains no KeeperHub-operated endpointparameterized check.Mainnet, for the maintainer commit:
eth_chainIdreturns0x13b2andnet_versionreturns5042, at block0x131fbc7. Block time measured at ~0.506s over 10,000 blocks.0x3600...0000precompile independently confirmed on mainnet viaeth_call:decimals()= 6,symbol()=USDC,name()=USDC.tsc --noEmit: 0 errors. Fullvitest run tests/unit: 595/595 files, 21,995/21,995 tests passing, exit 0.biome checkclean on the changed files.seed-chains.ts:1072warnsNo explorer config template for chain Arc (5042), skipping, returnsnull, and the entry is filtered out. Nothing throws.No new test file was added -- consistent with every prior chain addition (Robinhood, Plasma, 0G, Avalanche, BNB), none of which added coverage for
seed-chains.ts/seed-tokens.ts/ thesupported-tokensroute either.Follow-ups (not blocking)
minPriorityFeeGwei: 0for Arc once real inclusion behaviour is benchmarked.Tempo display change (intentional)
This PR's native/token dedup refactor (
hidesNativeRowinlib/wallet/build-withdrawable-assets.ts) also changes what renders for Tempo (chain ID 4217, live mainnet) on three surfaces: the wallet chain card (components/overlays/wallet/chain-balance-item.tsx), the toolbar wallet digest and USD total (lib/wallet/use-wallet-digest.ts), and the Settings assets table (components/settings/hub/wallets/use-account-assets.ts). Staging currently renders Tempo's native row unconditionally on all three; this PR removes it.This is intentional, not incidental. Tempo uses stablecoins for gas and has no native gas token to withdraw -- that's the long-standing rule in
chain-utils.ts(isTempoChain), and the withdraw path has always refused Tempo native withdrawals. This PR just makes the display consistent with that rule across all three surfaces instead of only some of them.