Skip to content

fix(cli): unblock transaction signing on Arc - #124

Closed
zebastieneth wants to merge 1 commit into
mainfrom
fix/arc-rpc-fallback
Closed

zebastieneth wants to merge 1 commit into
mainfrom
fix/arc-rpc-fallback

Conversation

@zebastieneth

Copy link
Copy Markdown
Collaborator

Problem

Arc mainnet went public today. Reads work — positions, history, pnl, and off-chain signing (sign-message, sign-typed-data) are all fine, because 1.9.x resolves chains from the live /v1/chains/ catalog.

zerion send --chain arc does not, and it fails before anything gets signed:

$ zerion send USDC 0.001 --to 0x… --chain arc
{"error":{"code":"send_error","message":"HTTP request failed.\n\nStatus: 403\nURL: https://rpc.zerion.io/v1/arc\nRequest body: {\"method\":\"eth_getBlockByNumber\",…"}}

send is built and broadcast client-side, so it needs a real Arc node for the nonce and gas estimate. The CLI gets its endpoints from attributes.rpc.public_servers_url, and Arc's list has exactly one entry — https://rpc.zerion.io/v1/arc, our own proxy, which is behind a Cloudflare managed challenge and 403s anything that isn't a browser. With a single URL buildTransport degrades to a plain http() transport, so there is nothing to fall back to.

This won't fix itself:

  • rpc.public in chain-storage is overwritten daily by the chainlist sync cronjob.
  • chainid.network lists Arc mainnet (5042) with an empty rpc array — Circle hasn't published there yet — so the sync has nothing to pick up.
  • Arc is one of 8 sending-enabled chains with no reachable public RPC (also degen, polynomial, rari, redstone, swellchain, wonder, zkcandy). Nothing validates liveness today.

Change

Both changes live in the catalog's URL resolution, so every consumer benefits — send, swap, consolidate, and the signature-verification client in sign-message.

1. RPC_SEED — last-resort endpoints, appended after the catalog's own URLs.

Seeded with Circle's four official Arc endpoints from docs.arc.io, all verified open and unauthenticated as of this morning. The catalog still wins: viem's fallback transport only advances on error, so the seed is skipped entirely on a healthy chain and goes cold the moment chain-storage publishes real endpoints. No code change needed to retire it.

2. ZERION_RPC_URL_<CHAIN> — per-chain override.

Mirrors the existing SOLANA_RPC_URL escape hatch. Chain id upper-cased, dashes to underscores (binance-smart-chain → ZERION_RPC_URL_BINANCE_SMART_CHAIN). Tried first; catalog URLs stay on as fallbacks. This is the general way out of a dead catalog entry, for the other seven chains and whatever breaks next.

Verification

Against live Arc mainnet, with the patch applied:

chainIdNum: 5042
rpcHttpUrls: [
  'https://rpc.zerion.io/v1/arc',          ← still first, still 403s
  'https://rpc.mainnet.arc.io',            ← fallback takes over
  'https://rpc.blockdaemon.mainnet.arc.io',
  'https://rpc.drpc.mainnet.arc.io',
  'https://rpc.quicknode.mainnet.arc.io'
]
block=21140223 gasPrice=60719758243 in 175ms
nonce: 0

That's everything send needs before it reaches the signer.

8 new unit tests in cli/tests/unit/cli/utils/chain/catalog.test.mjs cover ordering, the wss:// filter, seed append, override precedence, dash mapping, malformed overrides and de-duplication. Full unit suite: 44 failures before and after this change — all pre-existing on main, none introduced here.

Out of scope

  • Swap and bridge on Arc are a separate matter — supports_trading and supports_bridge are both false in the catalog, and the CLI correctly returns chain_capability_missing. That's a trading-backend question, not a client one.
  • The better fix is server-side: add Circle's endpoints to rpc.public_pinned for arc in chain-storage (pinned survives the daily chainlist sync, and is emitted first). That fixes every client at once with no release — including the extension's dapp RPC passthrough, which reads rpc_url_public[0] and hits the same 403. Failing that, exempting /v1/arc from the Cloudflare challenge the way /v1/zero already is would also do it. This PR is the client-side unblock that doesn't need either.

🤖 Generated with Claude Code

`zerion send --chain arc` fails before it can build a transaction. The CLI
takes its RPC endpoints from `attributes.rpc.public_servers_url` on
`/v1/chains/`, and Arc's only entry is `https://rpc.zerion.io/v1/arc` —
our own proxy, which sits behind a Cloudflare managed challenge and 403s
any client that isn't a browser. The send path dies on its first
`eth_getBlockByNumber`, so nothing ever reaches the signer.

chain-storage can't self-heal here: `rpc.public` is overwritten daily by
the chainlist sync, and chainid.network lists Arc mainnet (5042) with an
empty `rpc` array, so there is nothing upstream to pick up.

Two changes, both in the catalog's URL resolution so every consumer
(send, swap, consolidate, signature verification) benefits:

- `RPC_SEED`: last-resort endpoints appended after the catalog's own
  URLs, seeded with Circle's four official Arc endpoints. The catalog
  still wins — viem's fallback transport only advances on error — so
  these go cold the moment chain-storage publishes real endpoints.
- `ZERION_RPC_URL_<CHAIN>`: per-chain override, mirroring the existing
  SOLANA_RPC_URL escape hatch. Tried first, catalog URLs stay on as
  fallbacks. A general way out of a dead catalog entry — eight
  sending-enabled chains currently have no reachable public RPC.

Verified against Arc mainnet: the client routes around the 403 in ~175ms
and returns block number, gas price and nonce.

Co-Authored-By: Claude Opus 5 (1M context) <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