zkSync Era RPC: What's Different from Ethereum
zkSync Era is EVM-compatible, so you can point ethers or viem at it and eth_blockNumber, eth_call, and eth_getLogs just work. That lulls people into treating it like any other EVM chain — and then a contract deployment fails, a gas estimate is wildly off, or a transaction reverts for reasons that make no sense on Ethereum. zkSync is a zkEVM, not an EVM clone, and the differences show up exactly where you interact with it over RPC. Here's the map.
It's compatible, not equivalent
Ethereum L2s come in two flavors: EVM-equivalent (run standard Ethereum bytecode as-is, like the OP-stack chains) and EVM-compatible (support Solidity but compile to a different VM). zkSync Era is the latter — Solidity/Vyper compile through zksolc/zkvyper to zkSync's own bytecode. For reading state, this is invisible; for deploying and low-level work, it matters a lot.
Mainnet chain ID is 324. Standard read RPC works:
import { createPublicClient, http } from "viem";
import { zksync } from "viem/chains";
const client = createPublicClient({
chain: zksync,
transport: http("https://rpc.swiftnodes.io/rpc/zksync?key=YOUR_API_KEY"),
});
await client.getBlockNumber(); // just works
The four things that actually differ
1. Native account abstraction — every account is a smart account. On Ethereum, EOAs and contracts are different beasts. On zkSync, account abstraction is built into the protocol: accounts can carry validation logic, and transactions can be sponsored by paymasters. This introduces a zkSync-specific transaction type (EIP-712 / type 0x71) for AA and paymaster flows. Standard type-2 transactions still work for basic transfers, but if you want AA or gasless UX, you're building type-113 transactions — which the standard ethers sendTransaction won't construct for you. That's what the zksync-ethers SDK is for.
2. The zks_ RPC namespace. Alongside eth_*, zkSync exposes L2-specific methods under zks_. The ones you'll actually reach for:
| Method | What it gives you |
|---|---|
zks_L1BatchNumber |
current L1 batch (see finality below) |
zks_estimateFee |
accurate fee incl. pubdata (see gas below) |
zks_getL2ToL1LogProof |
proof for L2→L1 messages (withdrawals) |
zks_getAllAccountBalances |
all token balances for an address in one call |
zks_getBridgeContracts |
canonical bridge addresses |
zks_getBlockDetails |
L2 block + which L1 batch it's in |
If your provider only proxies eth_* and strips zks_, half of zkSync-native development breaks. A zkSync endpoint needs to pass the zks_ namespace through.
3. The gas model includes pubdata. This is the single biggest "why is my estimate wrong" gotcha. zkSync's cost has two parts: L2 execution gas and the cost of the pubdata it posts to Ethereum L1 (gasPerPubdataByteLimit). A plain eth_estimateGas doesn't capture the pubdata dimension well, so estimates can be off — sometimes a lot. Use zks_estimateFee (or let zksync-ethers handle it) for transactions that write meaningful state:
curl -s -X POST https://rpc.swiftnodes.io/rpc/zksync?key=YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"zks_estimateFee","params":[{"from":"0x…","to":"0x…","data":"0x"}],"id":1}'
4. Deployment is not standard. You can't take EVM bytecode and eth_sendRawTransaction a deployment the Ethereum way. Contracts are deployed through a system contract (ContractDeployer), the bytecode must be zksolc-compiled, and CREATE/CREATE2 address derivation differs from Ethereum — so an address you precompute with standard CREATE2 math will be wrong. Use hardhat-zksync / zksync-ethers for deploys; don't expect a vanilla Hardhat/Foundry deploy script to work unchanged.
Blocks, batches, and finality
zkSync has two levels you'll see over RPC:
- L2 blocks — produced by the sequencer every ~1 second.
eth_blockNumberreturns these. This is your fast, soft-confirmed view (see What Is a Sequencer?). - L1 batches — many L2 blocks are rolled into a batch that gets committed, proven, and executed on Ethereum.
zks_L1BatchNumberandzks_getBlockDetailstell you which batch a block belongs to and its status.
Finality mirrors the L2 pattern: the sequencer gives you near-instant soft confirmation, but hard finality only arrives once the batch's validity proof is verified and executed on L1 — which can take hours. If you're doing anything settlement-sensitive (large withdrawals, bridging), check batch execution status via zks_, don't trust the L2 block alone. This is the same soft-vs-hard distinction we covered in Soft vs Hard Finality on L2s — and the pubdata that gets posted is part of Ethereum's blob data-availability story.
What carries over unchanged
Don't overcorrect — most of your read-side tooling is fine:
eth_call,eth_getBalance,eth_getLogs,eth_getTransactionReceipt,eth_blockNumberall behave normally.- WebSocket subscriptions (
eth_subscribe) for new heads and logs work. - Event indexing works like any EVM chain (mind reorgs at the sequencer level — see Handling Chain Reorgs).
- Reading contract state via ABI +
eth_callis identical.
The rule of thumb: reading zkSync is standard EVM; writing and deploying to it is zkSync-specific. Reach for zksync-ethers and the zks_ methods the moment you go beyond reads.
The short version
zkSync Era gives you the familiar eth_* surface for reads, then diverges where it counts: native account abstraction (type-113 txs, paymasters), a zks_ namespace for L2 data and proofs, a two-part gas model where pubdata dominates (use zks_estimateFee), and non-standard deployment (zksolc, ContractDeployer, different CREATE2). Treat reads as normal EVM and writes as zkSync-native, and it stops surprising you.
For that you need an endpoint that passes through both eth_* and zks_* — many generic proxies drop the latter. Our zkSync RPC exposes the full namespace, flat-rate, alongside dozens of other chains under one key. Grab a free key and connect:
https://rpc.swiftnodes.io/rpc/zksync?key=YOUR_API_KEY
Related posts
- Scroll RPC: The zkEVM That Runs Ethereum Bytecode Unchanged
Scroll is a bytecode-equivalent zkEVM rollup (chain ID 534352) — it targets opcode-level equality with Ethereum, so your existing contracts, audits, and tooling work with zero changes, while ZK validity proofs give ~1-hour hard finality instead of the 7-day optimistic window. Standard eth_* (viem/ethers/foundry). Here's the developer map, including how it differs from zkSync Era.
- Manta Pacific RPC: An OP Stack L2 Whose Data Doesn't Live on Ethereum
Manta Pacific is an EVM OP Stack L2 (chain ID 169) that posts its data availability to Celestia instead of Ethereum — making it a validium, and making it cheap. It's standard EVM (viem/ethers/foundry work), with ETH gas and a ~7-day optimistic withdrawal window. Here's the developer map, including what modular DA changes about how you reason on security.
- Metis RPC: The Optimistic Rollup That Decentralized Its Sequencer
Metis is an EVM-equivalent optimistic rollup (chain ID 1088) notable for moving from a single sequencer to a sequencer pool — reducing the single-operator risk most L2s still carry. It's standard EVM (viem/ethers/foundry work), with METIS (not ETH) as gas. Here's the developer map, including what sequencer decentralization does and doesn't change for you.