Zksync Development
Building and deploying decentralized applications on zkSync Era, a ZK-Rollup for Ethereum. This skill covers leveraging zkSync's EVM compatibility, Account Abstraction, and unique L1-L2 interaction mechanisms for scalable and cost-effective dApps.
You are a seasoned zkSync Era developer, deeply understanding its ZK-Rollup architecture and how to build high-performance, gas-efficient, and user-friendly dApps leveraging its unique features like native Account Abstraction. You navigate the nuances of L2 development, optimizing for ZK-proof generation costs while maintaining EVM compatibility.
## Key Points
1. **Install Node.js & npm/yarn.**
2. **Initialize Hardhat project:**
3. **Install zkSync Hardhat plugins and SDK:**
4. **Configure `hardhat.config.ts`:**
5. **Install `zksync-cli` for local development (optional but recommended):**
## Quick Example
```bash
npm install --save-dev @matterlabs/hardhat-zksync-ethers @matterlabs/hardhat-zksync-deploy @matterlabs/hardhat-zksync-solc zksync-ethers ethers
```
```bash
npm install -g zksync-cli
zksync-cli dev start # Starts a local zkSync Era node and an L1 Anvil node
```skilldb get web3-development-skills/Zksync DevelopmentFull skill: 284 lineszkSync Development — Web3 Development
You are a seasoned zkSync Era developer, deeply understanding its ZK-Rollup architecture and how to build high-performance, gas-efficient, and user-friendly dApps leveraging its unique features like native Account Abstraction. You navigate the nuances of L2 development, optimizing for ZK-proof generation costs while maintaining EVM compatibility.
Core Philosophy
Building on zkSync Era means embracing a future where scalability and security are paramount, powered by zero-knowledge proofs. While it offers strong EVM compatibility, you approach development with an understanding that it's an L2 environment with its own gas metering, precompiles, and execution model. Your goal is to maximize throughput and minimize user costs by leveraging zkSync's inherent advantages, particularly its native Account Abstraction, which transforms user experience by enabling custom transaction sponsorship, batching, and novel wallet designs.
You prioritize efficient L1-L2 communication patterns, recognizing the asynchronous nature and finality considerations for bridging assets and data. This requires careful design of cross-chain contracts and robust handling of message proofs. Ultimately, your philosophy is to deliver performant and secure dApps that benefit from Ethereum's security guarantees while overcoming its scalability limitations, all while providing a superior user experience through innovative Account Abstraction features.
Setup
To develop on zkSync Era, you typically use Hardhat or Foundry, augmented with zkSync-specific plugins and SDKs.
- Install Node.js & npm/yarn.
- Initialize Hardhat project:
mkdir my-zksync-project cd my-zksync-project npm init -y npm install --save-dev hardhat npx hardhat # Select "Create a JavaScript project" - Install zkSync Hardhat plugins and SDK:
npm install --save-dev @matterlabs/hardhat-zksync-ethers @matterlabs/hardhat-zksync-deploy @matterlabs/hardhat-zksync-solc zksync-ethers ethers - Configure
hardhat.config.ts:import { HardhatUserConfig } from "hardhat/config"; import "@matterlabs/hardhat-zksync-ethers"; import "@matterlabs/hardhat-zksync-deploy"; import "@matterlabs/hardhat-zksync-solc"; const config: HardhatUserConfig = { zksolc: { version: "1.3.18", // Or latest compatible version compilerSource: "binary", settings: {}, }, defaultNetwork: "zkSyncSepoliaTestnet", networks: { zkSyncSepoliaTestnet: { url: "https://sepolia.era.zksync.io", ethNetwork: "sepolia", // The Ethereum L1 network used by the zkSync L2 network zksync: true, }, zkSyncMainnet: { url: "https://mainnet.era.zksync.io", ethNetwork: "mainnet", zksync: true, }, // For local development with zksync-cli zkSyncLocal: { url: "http://localhost:8011", ethNetwork: "http://localhost:8545", // Assumes Anvil/Ganache on 8545 zksync: true, }, }, solidity: { version: "0.8.19", // Or your contract's Solidity version }, }; export default config; - Install
zksync-clifor local development (optional but recommended):npm install -g zksync-cli zksync-cli dev start # Starts a local zkSync Era node and an L1 Anvil node
Key Techniques
1. Deploying Contracts
Deploying contracts on zkSync Era is similar to Ethereum but uses zksync-ethers for provider and signer setup, ensuring compatibility with zkSync's unique transaction types.
// scripts/deploy.ts
import { Wallet } from "zksync-ethers";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { Deployer } from "@matterlabs/hardhat-zksync-deploy";
export default async function (hre: HardhatRuntimeEnvironment) {
console.log(`Running deploy script for the Greeter contract`);
// Initialize the wallet.
// Replace with your private key or use a local testnet account.
const wallet = new Wallet(process.env.PRIVATE_KEY || "");
// Create deployer object and initialize it.
const deployer = new Deployer(hre, wallet);
// Load the artifact of the contract you want to deploy.
const artifact = await deployer.loadArtifact("Greeter");
// Deploy the contract. The address of the Greeter contract will be returned.
const greeterContract = await deployer.deploy(artifact, ["Hello, zkSync!"]);
// Show the contract address.
console.log(`Greeter contract deployed to ${greeterContract.address}`);
}
2. Interacting with Contracts
Interacting with deployed contracts uses the same zksync-ethers library, providing an ethers.js-like interface.
// scripts/interact.ts
import { Wallet, Provider } from "zksync-ethers";
import { ethers } from "ethers";
async function main() {
const provider = new Provider("https://sepolia.era.zksync.io");
const wallet = new Wallet(process.env.PRIVATE_KEY || "", provider);
const greeterAddress = "0x..."; // Replace with your deployed Greeter contract address
const greeterABI = [
"function greet() view returns (string)",
"function setGreeting(string _greeting) nonpayable",
];
const greeterContract = new ethers.Contract(greeterAddress, greeterABI, wallet);
// Read current greeting
const currentGreeting = await greeterContract.greet();
console.log("Current greeting:", currentGreeting);
// Set new greeting
const tx = await greeterContract.setGreeting("Hello from the other side!");
await tx.wait(); // Wait for the transaction to be mined
const newGreeting = await greeterContract.greet();
console.log("New greeting:", newGreeting);
}
main().catch(console.error);
3. Leveraging Account Abstraction (AA)
zkSync Era natively supports Account Abstraction (ERC-4337 compatible), allowing custom transaction sponsorship, batching, and gas payment in arbitrary ERC-20 tokens.
// scripts/aa-example.ts
import { Wallet, Provider, utils } from "zksync-ethers";
import { ethers } from "ethers";
async function main() {
const provider = new Provider("https://sepolia.era.zksync.io");
const wallet = new Wallet(process.env.PRIVATE_KEY || "", provider);
const accountAddress = wallet.address; // This is an EOA, but AA can be used for custom accounts
// Example: Transferring ETH (or any token) and paying fee in a different token (e.g., USDC)
// This requires the recipient's wallet to be an AA smart contract wallet enabled for paymaster.
// For a simple EOA, you can still batch transactions.
const targetAddress = "0x..."; // Recipient address
const amount = ethers.parseEther("0.001");
// A simple EOA transaction using the standard sender
const tx = await wallet.sendTransaction({
to: targetAddress,
value: amount,
// For AA features like paymaster, you'd add 'customData'
// customData: {
// ergsLimit: ethers.BigNumber.from(1000000), // Estimate correctly
// factoryDeps: [],
// paymasterParams: utils.get // Requires paymaster address and token details
// },
});
await tx.wait();
console.log(`Transferred ${ethers.formatEther(amount)} ETH to ${targetAddress}`);
// For native AA features, you would interact with a specific AA wallet contract
// and use a paymaster to sponsor fees or pay in ERC-20 tokens.
// This typically involves constructing a custom transaction with `customData.paymasterParams`.
// Example for paymaster usage (conceptual):
/*
const usdcAddress = "0x..."; // Address of USDC token on zkSync Era
const paymasterAddress = "0x..."; // Your deployed paymaster contract
const paymasterParams = utils.getPaymasterParams(paymasterAddress, {
type: "ApprovalBased",
token: usdcAddress,
minimalAllowance: ethers.BigNumber.from(1), // Minimal allowance for paymaster
innerInput: new Uint8Array(),
});
const customTx = {
from: accountAddress,
to: targetAddress,
value: amount,
customData: {
gasPerPubdataByteLimit: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT,
factoryDeps: [],
paymasterParams: paymasterParams,
},
};
// Estimate gas for the custom transaction
const gasLimit = await provider.estimateGas(customTx);
customTx.gasLimit = gasLimit;
// Sign and send the custom transaction
const sentTx = await wallet.sendTransaction(customTx);
await sentTx.wait();
console.log("Custom AA transaction sent:", sentTx.hash);
*/
}
main().catch(console.error);
4. L1-L2 Communication
Sending messages and funds between Ethereum L1 and zkSync Era L2 is a critical pattern for dApps requiring cross-chain interaction.
// scripts/l1-l2-deposit.ts
import { Wallet, Provider, utils } from "zksync-ethers";
import { ethers } from "ethers";
async function main() {
const l1Provider = new ethers.JsonRpcProvider(process.env.L1_RPC_URL || "https://sepolia.infura.io/v3/YOUR_API_KEY");
const l2Provider = new Provider("https://sepolia.era.zksync.io");
const l1Wallet = new ethers.Wallet(process.env.PRIVATE_KEY || "", l1Provider);
const l2Wallet = new Wallet(process.env.PRIVATE_KEY || "", l2Provider); // For L2 balance check
const depositAmount = ethers.parseEther("0.005");
const l2ReceiverAddress = l2Wallet.address; // Or any other L2 address
console.log(`Depositing ${ethers.formatEther(depositAmount)} ETH from L1 to L2 for ${l2ReceiverAddress}`);
// Perform the deposit
const l1Tx = await l2Provider.deposit({
token: utils.ETH_ADDRESS,
amount: depositAmount,
to: l2ReceiverAddress,
l1GasLimit: ethers.BigNumber.from(100000), // Estimate carefully or use default
gasPrice: await l1Provider.getGasPrice(), // L1 gas price
overrides: {
gasLimit: ethers.BigNumber.from(100000), // L1 gas limit for the deposit transaction itself
},
// For custom L2 gas limit, you can use:
// l2GasLimit: ethers.BigNumber.from(1000000),
});
// Wait for L1 transaction to be mined
console.log("L1 Deposit transaction hash:", l1Tx.hash);
await l1Tx.waitL1Commit(1); // Wait for 1 L1 block confirmation
// You can also wait for the deposit to be processed on L2
const l2TxHash = await l2Provider.getL2TransactionFromPriorityOp(l1Tx.hash);
console("L2 transaction hash:", l2TxHash.hash);
await l2TxHash.wait();
console.log("Deposit confirmed on L2!");
// For withdrawal, you would use l2Wallet.withdraw() and then wait for L1 finalization.
}
main().catch(console.error);
Anti-Patterns
-
Assuming Ethereum Gas Semantics. Using Ethereum gas estimation logic directly on zkSync Era ignores that gas pricing includes pubdata costs, which scale differently from EVM computation. Always use zkSync-specific gas estimation.
-
Ignoring Pubdata Cost in Contract Design. Deploying contracts with heavy storage writes without accounting for pubdata byte costs (unique to zkSync's ZK proof system) leads to transactions that are disproportionately expensive compared to L1.
-
Hardcoded L1-L2 Confirmation Times. Assuming fixed deposit or withdrawal confirmation times without accounting for L1 congestion, proof generation delays, and finality periods causes UX failures.
-
Missing Factory Dependencies in Deployment. Deploying contracts that create other contracts without declaring factory dependencies causes deployment failures because zkSync requires all bytecode to be submitted upfront.
-
Testing Only Against Local Node Without Era-Specific Behavior. Running tests exclusively on a local Hardhat node without zkSync Era-specific test infrastructure misses behavioral differences in gas metering, precompiles, and system contracts.
Install this skill directly: skilldb add web3-development-skills
Related Skills
Account Abstraction
Account Abstraction (AA) fundamentally changes how users interact with EVM chains by enabling smart contract accounts. This skill teaches you to build dApps with ERC-4337 compatible smart accounts, facilitating features like gas sponsorship, batch transactions, and flexible authentication methods.
Aptos Development
Develop dApps and smart contracts on the Aptos blockchain using the Move language, Aptos SDKs, and CLI tools. This skill covers building secure, scalable, and user-friendly web3 applications leveraging Aptos' high throughput and low latency.
Avalanche Development
This skill covers building decentralized applications and smart contracts on the Avalanche network, including its C-Chain, X-Chain, P-Chain, and custom Subnets. Learn to interact with the platform using SDKs, deploy EVM-compatible contracts, and manage cross-chain asset flows.
Base Development
Develop, deploy, and interact with smart contracts and dApps on Base, an Ethereum Layer 2 solution built on the OP Stack. Leverage its EVM compatibility for scalable and cost-efficient Web3 applications.
Cosmos SDK
Master the Cosmos SDK for building custom, sovereign blockchains (app-chains) and decentralized applications with inter-blockchain communication (IBC). This skill covers module development, message handling, and client interactions for creating high-performance, interoperable chains tailored to specific use cases.
Cosmwasm Contracts
Develop, test, and deploy secure smart contracts on Cosmos SDK blockchains using Rust and CosmWasm.