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
- Avalanche RPC: C-Chain, Subnets, and Endpoints
Avalanche isn't one chain — it's three (X, P, C) plus a growing set of L1 subnets, and that trips up everyone wiring up 'Avalanche RPC' for the first time. Here's which endpoint you actually need, why the URLs look weird, and how to connect.
- What Are Blobs? EIP-4844 and L2 Data Availability Explained
If your L2 fees dropped through the floor in 2024, blobs are why. Here's what EIP-4844 blobs actually are, why they're cheap and temporary, how data availability works, and what changes for you as a developer reading the chain.