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

  1. cd into the L1-governance folder.
cd L1-governance
  1. Initialise and set up the L1 project.
npx hardhat init

Select the option Create a Typescript project and accept the defaults for everything else.

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.
  1. 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.

  1. Remove the existing /test directory and any contracts that exist in /contracts.
  2. Create a new file called Governance.sol inside the empty contracts folder.
touch contracts/Governance.sol
  1. 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);
  }
}

Made with ❤️ by the ZKsync Community