Deploying Smart Contracts on zkSync: Specifics, Tools, Tips

Our engineers often see developers porting EVM contracts to zkSync without changes — and get surprises: CREATE2 doesn't work, gas goes 3x higher than expected, verification fails due to bytecode incompatibility. In this article, we break down how to correctly deploy zkSync smart contracts on zkSync

Blockchain Development Services

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1441
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    998
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1267
  • image_logo-advance_0.webp
    B2B Advance company logo design
    713
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    1003

Our engineers often see developers porting EVM contracts to zkSync without changes — and get surprises: CREATE2 doesn't work, gas goes 3x higher than expected, verification fails due to bytecode incompatibility. In this article, we break down how to correctly deploy zkSync smart contracts on zkSync Era: with zksolc, EraVM, Native Account Abstraction, and the two-dimensional gas model. Deploying smart contracts zkSync requires understanding these differences.

Problems We Solve

Bytecode incompatibility. zksolc generates EraVM bytecode, not EVM. If you don't specify factory dependencies, CREATE (and CREATE2) simply won't work — you need to declare all contracts created from code in advance. Gas model. zkSync uses two-dimensional gas: regular gas for execution and gasPerPubdata for publishing data to L1. When deploying a heavy contract (e.g., with large constructors), the final cost can be 2–3 times higher than on Ethereum Mainnet if you don't optimize the payload. Lack of documentation. Many don't know about Native Account Abstraction zkSync and miss the chance to implement smart accounts with custom validation directly at the protocol level, without a separate entrypoint.

If you've faced these issues, contact us — we'll help.

How We Do It

We use a proven stack: Hardhat zkSync plugin (for complex projects) or Foundry zkSync (for quick prototypes). We configure the optimizer settings (size/speed/balanced) based on project specifics — they affect contract size by up to 40% and gas usage by up to 50%. Below is a tool comparison:

Tool Compilation Verification Flexibility Ideal for
Hardhat zksolc, full support Built-in via plugin High, many modules Production contracts with many dependencies
Foundry zkSync forge with --zksync Via API Medium, fewer plugins Quick prototypes and tests

Foundry zkSync, by our measurements, deploys contracts 2–3 times faster due to direct transaction submission, but Hardhat gives more detailed control over factory dependencies and verification.

zksolc Optimization Mode Contract Size Execution Gas Where to Apply
size -40% +20% Lightweight contracts with few calls
speed +10% -30% Frequently called contracts (tokens, DEX)
balanced baseline baseline Universal option

The size mode reduces contract size by 40% compared to balanced mode, but increases execution gas by 20%, making it ideal for rarely-called contracts.

zksolc and Its Importance

The zksolc compiler converts Solidity into bytecode for the custom EraVM. This means:

  • Standard patterns (CREATE2 for deterministic deployment) require explicit factory dependencies. Without them, deployment fails with ContractDeployedWithoutFactoryDep.
  • The SELFDESTRUCT opcode does not destroy the contract — zkSync follows EIP-6049 and returns an error.
  • PUSH0 may not be supported in older zksolc versions, breaking contracts with pragma ^0.8.19. In such cases, use pragma ^0.8.13 or add a fallback.
ZkSync Developer Guide states: 'Factory dependencies must be declared for all contracts created via CREATE.'

Deploying via Hardhat

Install the plugin and configure the config:

// hardhat.config.ts import { HardhatUserConfig } from 'hardhat/config' import '@matterlabs/hardhat-zksync' const config: HardhatUserConfig = { zksolc: { version: 'latest', settings: { optimizer: { enabled: true, mode: '3', }, }, }, networks: { zkSyncMainnet: { url: 'https://mainnet.era.zksync.io', ethNetwork: 'mainnet', zksync: true, verifyURL: 'https://zksync2-mainnet-explorer.zksync.io/contract_verification', }, zkSyncTestnet: { url: 'https://sepolia.era.zksync.dev', ethNetwork: 'sepolia', zksync: true, verifyURL: 'https://explorer.sepolia.era.zksync.dev/contract_verification', }, }, solidity: '0.8.24', } export default config 

Deployment script using Deployer:

import { Wallet, Provider } from 'zksync-ethers' import { Deployer } from '@matterlabs/hardhat-zksync' import { HardhatRuntimeEnvironment } from 'hardhat/types' export default async function (hre: HardhatRuntimeEnvironment) { const provider = new Provider(hre.network.config.url) const wallet = new Wallet(process.env.DEPLOYER_PRIVATE_KEY!, provider) const deployer = new Deployer(hre, wallet) const artifact = await deployer.loadArtifact('MyContract') const deploymentFee = await deployer.estimateDeployFee(artifact, []) console.log(`Estimated deploy fee: ${ethers.formatEther(deploymentFee)} ETH`) const contract = await deployer.deploy(artifact, []) await contract.waitForDeployment() console.log(`Deployed to: ${await contract.getAddress()}`) } 

Run: npx hardhat deploy-zksync --script deploy.ts --network zkSyncMainnet

Deploying via Foundry

Install Foundry zkSync and run:

curl -L https://raw.githubusercontent.com/matter-labs/foundry-zksync/main/install-foundry-zksync | bash forge create src/MyContract.sol:MyContract \ --rpc-url https://mainnet.era.zksync.io \ --private-key $PRIVATE_KEY \ --zksync \ --constructor-args "arg1" 123 

Verification via API is done by sending a POST request to https://zksync2-mainnet-explorer.zksync.io/contract_verification with a JSON body.

Native Account Abstraction in zkSync

Native AA works at the protocol level — every account can be a smart contract. To implement it, you need to implement the IAccount interface:

import "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IAccount.sol"; contract MyAccount is IAccount { function validateTransaction( bytes32 _txHash, bytes32 _suggestedSignedHash, Transaction calldata _transaction ) external payable override returns (bytes4 magic) { // custom validation } function executeTransaction( bytes32 _txHash, bytes32 _suggestedSignedHash, Transaction calldata _transaction ) external payable override { // execution } } 

This saves up to 30% gas compared to ERC-4337 due to the absence of a separate entrypoint contract.

Why zkSync Requires Special Gas Optimization

Two-dimensional gas is a key feature. gasPerPubdata can account for up to 40% of transaction cost. Optimize calldata: avoid long string arguments and arrays in constructors. Use the 'size' optimization mode to save space if the contract is deployed once and rarely called. Understanding the gas model zkSync is crucial for cost optimization.

Typical Mistakes When Deploying on zkSync

  1. Missing factory dependencies — the most common cause of deployment failure. For every contract created via CREATE/CREATE2, you must add its address to the dependencies array during deployment.
  2. Ignoring two-dimensional gas — gas for publishing data (gasPerPubdata) can account for up to 40% of transaction cost.
  3. Using unsupported opcodes — SELFDESTRUCT, PUSH0, some versions of DELEGATECALL may not work or work differently. Always test on testnet.
Checklist for zkSync compatibility
  • Check factory dependencies for all dynamic contracts
  • Ensure unsupported opcodes (SELFDESTRUCT, PUSH0) are not used
  • Configure zksolc optimizer (size/speed/balanced mode)
  • Test on zkSync Sepolia testnet
  • Verify the contract after deployment

What's Included in Deployment Work?

Step 1. Analysis and audit of existing contracts for zkSync compatibility. We check on-chain and off-chain security, identify problematic opcodes. Step 2. Setting up the zksolc compiler and optimizing the gas model. We select the optimization mode and configure factory dependencies. Step 3. Preparing deployment scripts (Hardhat/Foundry). Includes gas estimation, factory dependencies, network configuration. Step 4. Contract verification zkSync on the block explorer. We use the hardhat-zksync-verify plugin or direct API calls. Step 5. Writing deployment documentation and operational recommendations. We record all parameters and dependencies. Step 6. Technical support during launch (2 weeks). We help integrate the contract into interfaces.

With 7+ years of blockchain experience and over 200 smart contracts deployed, our team ensures a 100% success rate on over 50 L2 deployments. For complex projects, we add formal verification using solc-verify or Certora.

Deployment cost for a typical ERC-20 contract starts at $199. Our optimization can save you up to $300 per month on gas fees.

Timeline: 1 to 5 days depending on complexity.

Get a consultation — contact us for a free audit of your contract's zkSync compatibility. Turnkey contract deployment with compatibility guarantee.