Smart Contract Deployment to zkSync

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Smart Contract Deployment to zkSync
Medium
from 4 hours to 2 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1043
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Deploying Smart Contracts to zkSync

zkSync Era is a ZK Rollup with EVM compatibility, but not EVM equivalence. Most Solidity contracts compile and deploy without changes, but there is zksolc compiler specifics and a number of opcodes that behave differently. You need to know this before deployment, otherwise the contract will deploy but work incorrectly.

zkSync Era Specifics

zksolc Compiler

zkSync uses its own compiler zksolc, which compiles Solidity to EraVM bytecode (not EVM bytecode). This is important difference:

  • Contract deployed on zkSync has different bytecode than the same contract on Ethereum
  • CREATE and CREATE2 work differently: before deployment bytecode must be declared in transaction (feature "factory dependencies")
  • SELFDESTRUCT — can deploy but opcode doesn't delete contract (changed in EIP-6049, zkSync follows this semantics)
  • PUSH0 — supported from certain zksolc versions; for old contracts with pragma solidity ^0.8.19 may be problem

Gas Model Differences

zkSync Era has two-dimensional gas model: gasLimit (computation) + gasPerPubdata (cost of publishing data to L1). When deploying contracts with large bytecode gasPerPubdata may dominate over computational gas.

Deployment via Hardhat

npm install -D @matterlabs/hardhat-zksync @matterlabs/zksync-contracts
// hardhat.config.ts
import { HardhatUserConfig } from 'hardhat/config'
import '@matterlabs/hardhat-zksync'

const config: HardhatUserConfig = {
  zksolc: {
    version: 'latest',
    settings: {
      optimizer: {
        enabled: true,
        mode: '3', // z (size), s (speed), 3 (balanced)
      },
    },
  },
  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

Deploy script:

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)
  
  // Load artifact (zksolc compiles to zk-specific format)
  const artifact = await deployer.loadArtifact('MyContract')
  
  // Estimate deployment cost
  const deploymentFee = await deployer.estimateDeployFee(artifact, [/* constructor args */])
  console.log(`Estimated deploy fee: ${ethers.formatEther(deploymentFee)} ETH`)
  
  const contract = await deployer.deploy(artifact, [/* constructor args */])
  await contract.waitForDeployment()
  
  console.log(`Deployed to: ${await contract.getAddress()}`)
}
npx hardhat deploy-zksync --script deploy.ts --network zkSyncMainnet

Deployment via Foundry (zkSync Fork)

Foundry officially supports zkSync via foundry-zksync — a fork adding --zksync flag:

# Install foundry-zksync
curl -L https://raw.githubusercontent.com/matter-labs/foundry-zksync/main/install-foundry-zksync | bash

# Deploy
forge create src/MyContract.sol:MyContract \
  --rpc-url https://mainnet.era.zksync.io \
  --private-key $PRIVATE_KEY \
  --zksync \
  --constructor-args "arg1" 123

# Or via script
forge script script/Deploy.s.sol \
  --rpc-url https://mainnet.era.zksync.io \
  --private-key $PRIVATE_KEY \
  --zksync \
  --broadcast

Contract Verification

# Via Hardhat
npx hardhat verify --network zkSyncMainnet CONTRACT_ADDRESS "constructor_arg1"

# Via zkSync Explorer API directly
curl -X POST https://zksync2-mainnet-explorer.zksync.io/contract_verification \
  -H "Content-Type: application/json" \
  -d '{
    "contractAddress": "0x...",
    "sourceCode": "...",
    "contractName": "MyContract",
    "compilerZksolcVersion": "v1.4.1",
    "compilerSolcVersion": "0.8.24",
    "optimizationUsed": true
  }'

Native Account Abstraction

Key advantage of zkSync Era — native AA at protocol level (unlike ERC-4337, which works via entrypoint contract). Every account can be a smart contract. To deploy AA account:

// Contract must implement 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 logic (multisig, session keys, etc.)
    }
    
    function executeTransaction(
        bytes32 _txHash,
        bytes32 _suggestedSignedHash,
        Transaction calldata _transaction
    ) external payable override {
        // Execution
    }
    // ...
}

Bridges and Depositing ETH

For deployment you need ETH on zkSync. Bridge via official zkSync bridge (~15–30 minutes L1 finalization). Programmatic bridge via L1 contract:

import { Provider, Wallet, utils } from 'zksync-ethers'
import { ethers } from 'ethers'

const l1Provider = new ethers.JsonRpcProvider(L1_RPC_URL)
const l2Provider = new Provider('https://mainnet.era.zksync.io')
const wallet = new Wallet(PRIVATE_KEY, l2Provider, l1Provider)

// Deposit 0.1 ETH from L1 to L2
const depositHandle = await wallet.deposit({
  token: utils.ETH_ADDRESS,
  amount: ethers.parseEther('0.1'),
})
await depositHandle.waitFinalize()

Deploying simple contract takes several hours (environment preparation + verification). Migrating existing Ethereum project to zkSync accounting for compiler specifics and testing — 1–2 days.