Cross Chain Tutorial Intro
Complete Project
Download the complete project on GitHub.
Project Setup
Open a terminal window, create a new folder for the project tutorial, e.g. mkdir cross-chain-tutorial
, and cd
into
the folder.
Now create separate folders to store contracts and scripts on L1 and L2. For now we will start with L1-governance folder.
mkdir L1-governance
The
L1-governance
code is a default Hardhat project used to deploy a contract on L1.
The L2-counter
code includes all ZKsync dependencies and configurations for L2.L1 Governance
cd
into theL1-governance
folder.
cd L1-governance
- Initialize and set up the L1 project.
npx hardhat --init
Select the option to use hardhat-2
with typescript
and mocha-ethers
.
Select yes to install the default dependencies.
Hardhat v3 is not fully supported for
@matterlabs/hardhat-zksync
.To interact with the ZKsync bridge contract using Solidity, you need
the ZKsync contract interface. There are two ways to get it:
- Import it from the
@matterlabs/zksync-contracts
npm package (preferred). - Download it from the contracts repo.
- Install the following dependencies:
npm i -D typescript ts-node @openzeppelin/contracts @matterlabs/zksync-contracts@beta @nomicfoundation/hardhat-ethers @typechain/ethers-v6 @typechain/hardhat typechain ethers dotenv
Create L1 Governance Contract
Make sure you're still in the
L1-governance
folder.The following Solidity code defines the Governance smart contract.
The constructor sets the contract creator as the single governor.
The callZkSync
function calls a transaction on L2 which can only be called by the governor.
- Remove the existing
/test
directory and any contracts that exist in/contracts
. - Create a new file called
Governance.sol
inside the emptycontracts
folder.
touch contracts/Governance.sol
- Copy/paste the code below into it.
L1-governance/contracts/Governance.sol
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.13;
import { IBridgehub, L2TransactionRequestDirect } from '@matterlabs/zksync-contracts/contracts/l1-contracts/bridgehub/IBridgehub.sol';
contract Governance {
address public governor;
constructor() {
governor = msg.sender;
}
function callZkSync(
uint256 chainId,
address bridgeHubAddress,
address contractAddr,
bytes memory data,
uint256 gasLimit,
uint256 gasPerPubdataByteLimit,
uint256 cost
) external payable {
require(msg.sender == governor, 'Only governor is allowed');
IBridgehub zksyncBridgeHub = IBridgehub(bridgeHubAddress);
L2TransactionRequestDirect memory requestInput = L2TransactionRequestDirect(
chainId,
cost,
contractAddr,
0,
data,
gasLimit,
gasPerPubdataByteLimit,
new bytes[](0),
msg.sender
);
zksyncBridgeHub.requestL2TransactionDirect{ value: msg.value }(requestInput);
}
}