Smart Contracts

Deploy smart contracts to the ZKsync Era network

The Web3.js plugin for ZKsync provides a ContractFactory class that can be used to deploy smart contracts to the ZKsync Era network. The ContractFactory class can be used with all four deployment types supported by the ZKsync Era network:

  • create: Deploy a regular smart contract with a non-deterministic address. (default deployment type)
  • create2: Deploy a regular smart contract with a deterministic address.
  • createAccount: Deploy a smart contract for a smart account with a non-deterministic address.
  • create2Account: Deploy a smart contract for a smart account with a deterministic address.
Learn about using smart accounts with the Web3.js plugin for ZKsync!

Create a contract factory

The following code sample demonstrates creating a new ContractFactory and using it to deploy a simple smart contract (keep reading to learn about more deployment options):

import { Bytes, Contract, ContractAbi, Web3 } from "web3";
import {
  ContractFactory,
  types,
  Web3ZKsyncL2,
  ZKsyncPlugin,
  ZKsyncWallet,
} from "web3-plugin-zksync";

async function main() {
  const web3: Web3 = new Web3(/* optional L1 provider */);
  web3.registerPlugin(
    new ZKsyncPlugin(
      Web3ZKsyncL2.initWithDefaultProvider(types.Network.Sepolia),
    ),
  );
  const zksync: ZKsyncPlugin = web3.ZKsync;

  const PRIVATE_KEY: string = "<PRIVATE_KEY>";
  const wallet: ZKsyncWallet = new zksync.Wallet(PRIVATE_KEY);

  // replace with actual values
  const contractAbi: ContractAbi = [];
  const contractByteCode: Bytes = "";

  // create a ContractFactory that uses the default create deployment type
  const contractFactory: ContractFactory<ContractAbi> = new ContractFactory(
    contractAbi,
    contractByteCode,
    wallet,
  );

  // or specify the deployment type
  // const contractFactory: ContractFactory<ContractAbi> = new ContractFactory(
  //   contractAbi,
  //   contractByteCode,
  //   wallet,
  //   "createAccount",
  // );

  const contract: Contract<ContractAbi> = await contractFactory.deploy();
  console.log("Contract address:", contract.options.address);
  console.log("Contract methods:", contract.methods);
}

main()
  .then(() => console.log("✅ Script executed successfully"))
  .catch((error) => console.error(`❌ Error executing script: ${error}`));

Deploy a smart contract

Some smart contracts require constructor parameters to be supplied when they are deployed. The following code snippet demonstrates deploying a smart contract with constructor parameters:

// deploy a smart contract with an array of constructor parameters
const contract: Contract<ContractAbi> = await contractFactory.deploy([
  arg1,
  arg2,
]);

Smart contracts that are deployed using a ContractFactory that was created with the create2 or create2Account deployment type must specify a "salt" value when they are deployed. The following code snippets demonstrates deploying a smart contract with a provided salt value:

// deploy a smart contract with a salt value
const contract: Contract<ContractAbi> = await contractFactory.deploy(
  [
    /* empty array or constructor parameters */
  ],
  {
    customData: { salt: "salt" },
  },
);

Interact with a smart contract

The return type of the ContractFactory.deploy method is the Web3.js Contract class. The smart contract's methods can be accessed with the Contract.methods property, as demonstrated by the following code snippet:

const returnValue = await contract.methods
  .contractMethod(/* method parameters, if any */)
  .call();

Interact with an existing smart contract

The example above demonstrates interacting with a new smart contract that was deployed with a ContractFactory. The following examples demonstrate instantiating and interacting with an existing smart contract.

To instantiate an existing smart contract from a server-side environment (e.g. Node.js), use a ZKsyncWallet and its provider property to construct a new Contract object as demonstrated in the following code sample:

import { Contract, ContractAbi, Web3 } from "web3";
import {
  types,
  Web3ZKsyncL2,
  ZKsyncPlugin,
  ZKsyncWallet,
} from "web3-plugin-zksync";

async function main() {
  const web3: Web3 = new Web3(/* optional L1 provider */);
  web3.registerPlugin(
    new ZKsyncPlugin(
      Web3ZKsyncL2.initWithDefaultProvider(types.Network.Sepolia),
    ),
  );
  const zksync: ZKsyncPlugin = web3.ZKsync;

  const PRIVATE_KEY: string = "<PRIVATE_KEY>";
  const wallet: ZKsyncWallet = new zksync.Wallet(PRIVATE_KEY);

  // replace with actual values
  const contractAbi: ContractAbi = [];
  const contractAddress: string = "<CONTRACT_ADDRESS>";

  // use the wallet and its provider to instantiate the contract
  const contract: Contract<ContractAbi> = new wallet.provider!.eth.Contract(
    contractAbi,
    contractAddress,
  );

  const returnValue = await contract.methods
    .contractMethod(/* method parameters, if any */)
    .call();
}

main()
  .then(() => console.log("✅ Script executed successfully"))
  .catch((error) => console.error(`❌ Error executing script: ${error}`));

The following React component demonstrates instantiating and interacting with an existing smart contract using an injected provider (e.g. MetaMask):

import { useEffect, useState } from "react";

import { Contract, ContractAbi } from "web3";
import { ZKsyncPlugin } from "web3-plugin-zksync";

function App() {
  const [zksync, setZKsync] = useState<ZKsyncPlugin | null>(null);
  useEffect(() => {
    if (window.ethereum) {
      setZKsync(new ZKsyncPlugin(window.ethereum));
    } else {
      console.error("No injected providers");
    }
  }, []);

  useEffect(() => {
    if (!zksync) {
      return;
    }

    // replace with actual values
    const contractAbi: ContractAbi = [];
    const contractAddress: string = "<CONTRACT_ADDRESS>";

    // use the plugin and its provider to instantiate the contract
    const contract: Contract<ContractAbi> = new zksync.L2.eth.Contract(
      contractAbi,
      contractAddress,
    );

    const callContract = async () => {
      // use an injected account to interact with the smart contract
      const allAccounts = await zksync.L2.eth.requestAccounts();
      // send a transaction that updates the state of the smart contract
      const transactionReceipt = await contract.methods
        .contractMethod(/* method parameters, if any */)
        .send({ from: allAccounts[0] });
      // call a smart contract to inspect its state
      const returnValue = await contract.methods
        .contractMethod(/* method parameters, if any */)
        .call();
    };

    callContract();
  }, [zksync]);
  return <div className="App"></div>;
}

export default App;

Made with ❤️ by the ZKsync Community