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_blockNumber returns 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_L1BatchNumber and zks_getBlockDetails tell 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_blockNumber all 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_call is 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.

Try SwiftNodes free — multi-chain RPC across 75+ networks, flat-rate pricing, pay by card or crypto, no KYC. Get an API key in 30 seconds →