guide10 min read

zkSync Era Mainnet RPC for Wallet Builders

What to check before a wallet points at zkSync Era mainnet: chain ID 324 / 0x144, EraVM vs Ethereum and OP Stack, native account abstraction, EIP-712 0x71 transactions, pubdata gas, and public vs dedicated endpoints.

By RPC Directory•
#zkSync#ETH#RPC#Wallet#Chain ID#zkEVM#Account Abstraction
$zksync-era-mainnet-rpc.md

zkSync Era (the official docs now write it ZKsync Era) is a ZK rollup on Ethereum that runs its own virtual machine, EraVM. It is not "Ethereum with a different RPC URL", it is not an OP Stack chain like Base or Optimism, and it does not behave exactly like the other zkEVMs in your network list. Accounts, transaction types, gas estimation, and contract deployment all have Era-specific rules. Wallet builders who paste chain ID 1, reuse the Sepolia testnet key, or carry Ethereum gas and address assumptions into Era ship stuck sends, wrong fee quotes, or funds bugs. Before you wire MetaMask, Rabby, Rainbow, or your own wallet_addEthereumChain flow, prove chain ID 324, learn the handful of EraVM differences that reach the wallet layer, and probe the URL you intend to ship.

This is not a ranking of providers. Use the zkSync Era Mainnet hub to compare endpoints and paid rows, then run the checks below on the URL you configure. If you only need the network in a browser wallet, the add zkSync Era to MetaMask page has the fields prefilled.

Chain ID 324 is not Ethereum and not zkSync Sepolia

Mainnet chain ID is 324, hex 0x144. The current testnet, ZKsync Era Sepolia, is 300 / 0x12c. Ethereum mainnet is 1. The old zkSync Era Goerli testnet (280) is deprecated; if it still appears in a config file, delete it rather than repurpose it. A wallet that already has Ethereum added still needs a separate zkSync Era network entry.

Wallet-add fields for mainnet, as published in the ZKsync docs:

  • Network name: ZKsync Era Mainnet (or zkSync Era if the product already scopes the list)
  • Chain ID: 324 (or 0x144 if the form wants hex)
  • Currency symbol: ETH
  • RPC URL: a JSON-RPC HTTPS endpoint you have already probed
  • Block explorer: https://explorer.zksync.io

Native gas is ETH with 18 decimals, the same symbol as Ethereum L1 and most Ethereum L2s. Show the network name and chain ID in the UI, not the symbol alone, or users will not know which "ETH" balance they are looking at.

Call eth_chainId on the exact RPC URL you will ship. Expect 0x144. A gateway that returns 0x1 is Ethereum. A gateway that returns 0x12c is Sepolia. A timeout or an HTML error page is not zkSync Era mainnet for that transport. Run the check after any load balancer or fallback so you are testing the backend production actually hits. The official public RPC is https://mainnet.era.zksync.io (WebSocket wss://mainnet.era.zksync.io/ws), already listed on the hub; treat it as a probe target, not as proof that every branded gateway forwards to Era mainnet.

EraVM quirks that reach the wallet layer

zkSync Era is EVM-compatible, not EVM-equivalent. Contracts compiled for EraVM (with zksolc or zkvyper) run natively, and standard EVM bytecode runs through an EVM Bytecode Interpreter on top of EraVM. Most dapp calls look like Ethereum from the RPC side. The differences below are the ones a wallet or its backend actually hits:

  • Native account abstraction. On Era, smart accounts and paymasters are protocol-level features, not an ERC-4337 add-on. The docs state that under the hood all accounts, even EOAs, behave like smart contract accounts and all accounts support paymasters, with one mempool for both. A wallet that only understands "EOA signs, EOA pays gas in ETH" will misread sponsored transactions and smart-account senders.
  • EIP-712 transaction type 0x71. Legacy (0x0), EIP-2930 (0x1), and EIP-1559 (0x2) transactions work for plain sends. Paymaster flows, smart-account signatures (customSignature), and native contract deployment (factoryDeps) use type 0x71 (113), where the user signs an EIP-712 typed structure instead of the RLP payload. L1-originated priority transactions show up as 0xff. Your transaction decoder, history view, and signing flow should not reject or mislabel these types.
  • No priority fee market. The docs recommend maxPriorityFeePerGas of 0 and state that Era chains do not have a concept of priority fees. A tip slider copied from Ethereum does nothing useful here.
  • Pubdata is part of the gas bill. Era is a state-diff rollup, so pubdata cost is only known after execution and is post-charged per byte via gas_per_pubdata. If the remaining gas cannot cover pubdata, the transaction reverts and the user still pays for computation. Storage writes are also precharged at the cold cost with a refund later. Always take the gas limit from eth_estimateGas on an Era endpoint and add headroom; do not reuse a hard-coded 21000 or an estimate from Ethereum or an OP Stack L2.
  • Presigned Ethereum transactions may not replay. Gas values signed in an Era transaction are native EraVM gas, and the docs warn that presigned or keyless transactions created for Ethereum may be incompatible. Deterministic deployer tricks that depend on a keyless Ethereum transaction need an Era-specific path.
  • Contract addresses are not portable. Native EraVM deployment goes through the ContractDeployer system contract, and Era splits nonces into a transaction nonce and a deployment nonce, so hash(RLP[address, nonce]) does not predict an EOA-deployed address. The docs recommend reading the ContractDeployed event (or using create2 via ContractDeployer). Contracts deployed through the EVM interpreter follow Ethereum create / create2 derivation. Either way, never hard-code an Ethereum token or router address into an Era wallet path.
  • Finality is batch- and proof-based. Blocks are grouped into L1 batches; a batch is committed to Ethereum, proven, and then executed after a time lock that governance has set to a 3 hour minimum. The docs put full finality at around 3 hours, sometimes longer. zks_getTransactionDetails reports Pending, Included, Verified, or Failed. Decide which of latest, safe, or finalized "confirmed" means, and keep withdrawal UX honest about the delay.
  • zks_ namespace. Era nodes expose methods such as zks_L1BatchNumber, zks_getTransactionDetails, zks_getL1BatchDetails, zks_gasPerPubdata, and zks_getL2ToL1LogProof. Some older ones (zks_estimateFee, zks_L1ChainId) are marked deprecated. Generic multichain gateways may only forward eth_, so if your SDK or withdrawal flow calls zks_, test it on the exact URL.
  • Not OP Stack. Era is not Base or Optimism. There are no Flashblocks, no OP Stack sequencer or derivation assumptions, and withdrawals are not governed by an optimistic fault-proof challenge window. Do not import a Base pending matrix or an Optimism withdrawal timer into an Era wallet path.

The ecosystem around Era is broader than one chain. ZKsync now calls its family of chains the Elastic Network, and the docs say new ZKsync chains use ZKsync OS, which is EVM-equivalent and does not support EraVM native account abstraction. Era itself still runs EraVM today. If you support several ZKsync chains, key each one by chain ID and do not assume Era behavior carries over.

Wallet-add gotchas that show up in QA

The failures that reach support are usually config, not cryptography:

  1. Wrong chain ID - Ethereum 1, Sepolia 300, or the deprecated Goerli 280 pasted into a mainnet form.
  2. Ethereum RPC on a zkSync Era row - eth_chainId will not return 0x144.
  3. Explorer mismatch - sending users to etherscan.io for Era hashes, or to sepolia.explorer.zksync.io for mainnet hashes.
  4. One shared "zksync" RPC bucket - keep separate keys for zksync mainnet and zksync-sepolia so a fallback cannot silently swap networks. Do the same for other Elastic Network chains, which have their own chain IDs.
  5. Ethereum gas habits - fixed gas limits, a priority fee slider, or a fee quote copied from L1. Estimate on Era and budget for pubdata.
  6. Type 0x71 blind spots - history or signing code that drops EIP-712 transactions, which breaks paymaster and smart-account flows.
  7. Missing parent Ethereum - bridge and withdraw flows need chain ID 1 configured even when the product lives on Era.
  8. OP Stack assumptions - treating Era like Base or Optimism for pending tags, sequencer URLs, or withdrawal timing.

wallet_addEthereumChain vs wallet_switchEthereumChain

When you implement wallet_addEthereumChain, send a full EIP-3085 payload:

{

"chainId": "0x144",

"chainName": "ZKsync Era Mainnet",

"nativeCurrency": { "name": "Ether", "symbol": "ETH", "decimals": 18 },

"rpcUrls": ["https://your-probed-endpoint.example"],

"blockExplorerUrls": ["https://explorer.zksync.io"]

}

Wallets that receive only symbol in nativeCurrency can reject the request. The chain ID must be the hex string 0x144, not the number 324.

For wallet_switchEthereumChain, send only { "chainId": "0x144" }. Metadata fields belong on add, not switch. Prefer one primary RPC in the wallet payload; put failover in your own client, not as a list of public URLs the wallet may pick at random.

Public vs dedicated for wallet traffic

Public endpoints are fine for:

  • Local development and wallet QA
  • Low-volume eth_chainId / eth_blockNumber probes
  • One-off scripts and docs examples

They are a poor primary path when you:

  • Run production wallet RPC for many users (balance, nonce, estimate, send, receipt polls)
  • Depend on zks_* methods for withdrawals, batch status, or fee data
  • Need predictable latency and a support channel when the gateway returns 429
  • Depend on historical state, wide eth_getLogs, or sustained WebSocket traffic
  • Cannot absorb shared-node brownouts during an airdrop, a mint, or a market spike

Probe the candidate URL before you call it ready:

  1. eth_chainId returns 0x144.
  2. The head advances when you poll eth_blockNumber twice a few seconds apart. An unchanged balance is not proof the node is live.
  3. One balance or receipt read against a known zkSync Era mainnet address (not an Ethereum or Sepolia address).
  4. eth_estimateGas for a simple transfer returns a value, so you know estimation, and therefore pubdata-aware gas limits, works on this URL.
  5. If you need push updates, a WebSocket path that survives a disconnect (many public sockets do not).
  6. Optional: if your wallet or SDK uses the zks_ namespace, call zks_L1BatchNumber and zks_getTransactionDetails on a known hash. A -32601 "Method not found" means this URL cannot serve that flow.

Public rows on the hub are shared and rate-limited. Use them for the probe. For production wallet traffic, compare the paid rows on the same hub for method coverage (including zks_*), archive or historical access if you need it, and quota. Dwellir is one of those rows, with HTTPS and WebSocket zkSync Era mainnet endpoints and a flat response-credit model that is easier to budget when your mix of estimates, reads, and sends is not stable. It is not the only option. A cheaper URL that fails the probe above is not a savings. The public vs paid guide covers when a shared endpoint stops being enough, and the pricing calculator helps with the rest. Log-heavy indexers should also read the eth_getLogs caps guide before assuming a wide filter will pass on every URL.

Practical split for a wallet stack

  • User-facing zkSync Era row - chain ID 324 / 0x144, ETH symbol, explorer.zksync.io, an RPC you probed today.
  • zkSync Era Sepolia row - chain ID 300 / 0x12c, a separate config key, never reused as the mainnet RPC. Remove any Goerli (280) leftovers.
  • Parent Ethereum row - chain ID 1, kept for bridge and withdraw flows, never reused as the Era RPC.
  • Signing and history - support EIP-712 type 0x71 alongside legacy and EIP-1559, and render paymaster-sponsored transactions correctly.
  • Fee logic - gas limits from eth_estimateGas on Era plus headroom, priority fee 0, no fixed limits carried over from Ethereum.
  • Backend / indexer - dedicated or paid endpoint with explicit eth_, zks_, and archive needs; do not share the browser wallet's public URL.
  • Receipt policy - decide whether "confirmed" means latest, safe, or finalized on Era, remember that L1 finality follows batch proofs and the execution delay, and store that choice next to the RPC URL.

Ship the hub comparison and the probe first. Marketing copy about "zkSync Era RPC" does not replace eth_chainId returning 0x144 and a gas estimate that came from Era.