Smart Contracts
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.
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;