Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Network & Protocol Guides11 min read

ZKsync Era: How the zkEVM Rollup Works and How to Build on It

A practical guide to ZKsync Era's zkEVM architecture, transaction lifecycle, RPC integration, gas handling, and verification workflow for Ethereum developers.

TL;DR

ZKsync Era is a Layer 2 zk-rollup that executes EVM-compatible transactions off Ethereum and posts compressed state commitments back to Ethereum, secured by zero-knowledge proofs rather than a fraud-proof challenge window. For developers, the practical consequence is that most Ethereum tooling works with familiar JSON-RPC calls, but gas accounting, finality stages, and a few Era-specific methods behave differently from an L1 or an optimistic rollup. This guide explains the mental model, then walks through a reproducible integration and verification procedure using your own RPC endpoint. It separates documented protocol behavior from recommendations and from measurements you should run against your own infrastructure.

What ZKsync Era Is and What This Guide Covers

ZKsync Era is a Layer 2 rollup on Ethereum built around a zero-knowledge EVM (zkEVM). Transactions are executed by an off-chain sequencer, batched, and committed to Ethereum along with validity proofs. Because correctness is enforced cryptographically by the proof rather than by a challenge period, withdrawals and state transitions do not depend on an honest watcher staying online for a dispute window.

The name is used in two overlapping ways. It refers to the Era mainnet chain that developers interact with today, and it also refers to the open-source matter-labs/zksync-era codebase, which has since been generalized into the ZK Stack framework for launching custom chains. When you read documentation, check whether a page is describing the Era chain specifically or the broader stack.

This article covers the Era chain as an integration target: the execution model, the JSON-RPC surface, a runnable read-and-write procedure, how to verify results, and where the common failure modes are. It does not cover tokenomics or price, and it does not recommend a specific public endpoint URL; endpoint selection is a separate decision covered by the RPC Assistant.

  • Execution: EVM-compatible bytecode runs on an off-chain sequencer.
  • Security: validity proofs posted to Ethereum, not fraud proofs.
  • Data: transaction data and state diffs are compressed and published to Ethereum.
  • Tooling: standard Ethereum JSON-RPC plus a small set of Era-specific methods.

Prerequisites and Assumptions

The procedures below assume you have Node.js 18 or newer, curl, and a funded account on ZKsync Era if you intend to send a transaction. You need an RPC endpoint that serves the Era chain; keep it in an environment variable named RPC_URL so the same scripts work against any provider. For write operations, keep the key in PRIVATE_KEY and never commit it.

You should already be comfortable with Ethereum concepts: accounts, nonces, gas, and the difference between a transaction hash and a receipt. If you are new to node access patterns, the guide to blockchain node hosting explains what a dedicated node gives you that a shared endpoint does not.

All numeric outputs in this article are placeholders. Chain state changes constantly, so any value you see is a snapshot of your own call, not a benchmark.

The Mental Model: Execution, Batching, and Proofs

Think of ZKsync Era as three layers of activity. First, the sequencer orders and executes transactions, producing new state. Second, a prover generates a zero-knowledge proof that the state transition followed the rules. Third, a commit transaction on Ethereum publishes the compressed data and the proof so anyone can verify the transition without re-executing it.

This differs from optimistic rollups in a way that matters operationally. On an optimistic rollup, a transaction is accepted optimistically and can be challenged for a window; on Era, the security argument rests on the proof. In practice both feel similar to an application developer, but the timing of when a state root is considered final on Ethereum differs, and you should not treat a sequencer-included transaction as Ethereum-final.

Era also compresses calldata before publishing. That is why fees on Era are typically much lower than L1, and why gas estimation can look unfamiliar: the cost of publishing data is a large component, and it is priced in a way that reflects byte-level compression rather than raw calldata length.

How ZKsync Era Differs from a Plain EVM Chain

Most contracts deploy and run unchanged, but a handful of behaviors are Era-specific and worth knowing before you debug. Gas is split into computation and pubdata components, and the gasPerPubdataByte field appears in transaction types. Some precompiles and opcodes behave differently or are unavailable, and account abstraction is native rather than bolted on, so accounts can pay fees in ERC-20 tokens and can carry custom validation logic.

For integration work, the most important consequence is that you should not assume an Ethereum mainnet gas estimator will produce a correct limit. Use the node's own estimation and add a margin. Similarly, do not assume that eth_getTransactionReceipt returning a receipt means the batch is proven on Ethereum; it means the sequencer has included the transaction.

  • Native account abstraction: smart accounts and paymasters are first-class.
  • Two-part gas: computation plus pubdata, exposed via Era-specific fields.
  • Proof-based finality: inclusion, batch commit, and proof verification are distinct stages.
  • Era-specific RPC methods supplement the standard Ethereum set.

Reading Chain State over JSON-RPC

Start with read-only calls to confirm your endpoint is serving the Era chain and that standard methods behave as expected. The script below uses curl and prints the chain ID, the latest block number, and the balance of an address you supply. Replace RPC_URL and ADDRESS before running it.

A correct run returns a chain ID that matches the Era network you intended, a monotonically increasing block number, and a balance in wei as a hex string. If the chain ID does not match, you are pointed at the wrong network and every subsequent result is suspect.

export RPC_URL="https://your-era-endpoint.example"
export ADDRESS="0xYourAddress"

curl -s -X POST "$RPC_URL" \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'

curl -s -X POST "$RPC_URL" \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"eth_blockNumber","params":[]}'

curl -s -X POST "$RPC_URL" \
  -H 'Content-Type: application/json' \
  -d "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"eth_getBalance\",\"params\":[\"$ADDRESS\",\"latest\"]}"

Sending a Transaction and Tracking Its Stages

The JavaScript example below uses ethers to send a small transfer and then poll the receipt. It reads the endpoint and key from environment variables, estimates gas with the node, and applies a margin. It also prints the block number at which the receipt appears so you can correlate it with batch activity later.

Run it against a test account first. The expected verification result is a receipt with status equal to 1 and a blockNumber that is greater than the block number you recorded before sending. If the receipt never appears, the transaction may still be pending in the sequencer's mempool; check the hash with eth_getTransactionByHash before resending.

import { JsonRpcProvider, Wallet, parseEther } from "ethers";

const provider = new JsonRpcProvider(process.env.RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY, provider);

const before = await provider.getBlockNumber();
const fee = await provider.getFeeData();
const gas = await provider.estimateGas({
  from: wallet.address,
  to: process.env.ADDRESS,
  value: parseEther("0.0001"),
});

const tx = await wallet.sendTransaction({
  to: process.env.ADDRESS,
  value: parseEther("0.0001"),
  gasLimit: (gas * 120n) / 100n,
  maxFeePerGas: fee.maxFeePerGas,
  maxPriorityFeePerGas: fee.maxPriorityFeePerGas,
});

console.log("sent", tx.hash, "from block", before);
const receipt = await tx.wait();
console.log("status", receipt.status, "in block", receipt.blockNumber);

A Reproducible Measurement Procedure

If you need to compare endpoints or confirm latency for your own application, run a controlled procedure rather than trusting a single call. The method below is designed so that another engineer can repeat it and get comparable structure, even if the absolute numbers differ.

Record the following table for each endpoint you test, using at least 100 iterations of each call and reporting the median and 95th percentile. Do not fill in numbers from this article; the table is a template for your own run.

  • Warm up with 10 calls and discard them.
  • Time eth_blockNumber and eth_getBalance separately.
  • Run from the same machine and network path for every endpoint.
  • Log failures and timeouts as their own category, not as slow successes.
  • Repeat at a different time of day to expose load-dependent variance.

Verifying Results and Understanding Finality

Verification has two levels. The first is inclusion: the sequencer has executed your transaction and a receipt exists. The second is Ethereum-level settlement: the batch containing your transaction has been committed and its proof verified on Ethereum. A receipt alone does not prove the second.

To check settlement, look up the batch that includes your transaction using Era-specific methods such as zks_getTransactionDetails, which returns batch numbers and related metadata, then query the batch with zks_getL1BatchDetails. Compare the batch's commit and execute transaction hashes against Ethereum. If those hashes are absent, the batch is not yet settled.

For most application logic, inclusion is sufficient and you should design around it. For bridges, custody, and anything that must survive a sequencer failure, wait for the proof to be verified on Ethereum. The tradeoff is latency: settlement takes longer than inclusion, and the gap depends on prover throughput and Ethereum block timing.

  • Inclusion: receipt exists, status is 1.
  • Commit: batch data is posted to Ethereum.
  • Execute: the validity proof is verified on Ethereum.
  • Application rule: choose the stage that matches your risk tolerance.

Troubleshooting Common Integration Failures

Most failures fall into a few buckets. A wrong chain ID means the endpoint is serving a different network. A gas estimation revert usually means the transaction would fail on execution, not that the endpoint is broken; simulate with eth_call first. A missing receipt after a long wait often means the transaction was dropped or the nonce is stale.

If you see repeated timeouts under load, the problem may be your endpoint's rate limits rather than the chain. Compare against a second endpoint using the measurement procedure above. For persistent throughput needs, a dedicated node removes shared rate limits, and the RPC pricing page explains how plans map to request volume.

If a contract behaves differently than on Ethereum mainnet, check Era-specific opcode and precompile support before assuming a node bug. Reproduce the call with eth_call against a known-good block to isolate whether the issue is state-dependent.

  • Chain ID mismatch: confirm the endpoint's network before debugging further.
  • Estimation revert: simulate with eth_call and inspect the revert reason.
  • Stale nonce: query the pending nonce and replace the transaction.
  • Rate limiting: compare endpoints and check response headers.

Limitations and Tradeoffs

ZKsync Era is not a drop-in replacement for Ethereum in every respect. Account abstraction changes how fees can be paid and how validation works, which affects wallet and paymaster integration. Pubdata pricing means gas costs are sensitive to calldata size in ways that differ from L1. And because settlement depends on proof generation, the time to Ethereum finality is not fixed.

There is also an operational tradeoff in how you access the chain. Shared public endpoints are convenient but subject to rate limits and variable latency; dedicated infrastructure costs more but gives predictable performance. Neither choice changes the protocol, but both change the experience your users have.

Finally, the ecosystem is broader than the Era chain alone. The ZK Stack allows teams to launch custom chains that interoperate with Era, so documentation you read may describe behavior that applies to the stack rather than to Era specifically. Always confirm which target a page addresses.

Next Steps for Building on ZKsync Era

With reads, writes, and verification working, the next step is to harden your integration. Add retries with backoff around RPC calls, cache immutable data such as chain ID, and log the batch number alongside each transaction so settlement checks are easy later. If you are deploying contracts, test against a local node or a testnet before mainnet.

For endpoint selection, use the RPC Assistant rather than hardcoding a URL from a tutorial. It can point you to a suitable Era endpoint and explain the tradeoffs. To see which networks are supported, browse the network directory, and for managed access options review the API service. If you are still deciding between shared and dedicated access, the multi-chain RPC endpoints guide covers the decision in more depth.

  • Add retry and backoff logic around every RPC call.
  • Record batch numbers for transactions that require settlement checks.
  • Test contract deployments on a testnet before mainnet.
  • Re-run the measurement procedure after any infrastructure change.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started