Using Truffle L2 boxes to bridge blockchain networks

It is well known that many blockchains have problems with scalability and congestion. These issues have far-reaching effects from slow transaction times to increased transaction fees and degraded user experience.

One solution is that web3 should be multi chain using L2 (layer two) chains. Ethereum L2s, such as Optimism, Arbitrum, and Polygon, build on top of the Ethereum network, but are faster and cheaper than Ethereum.

However, as a trade-off, they are often less secure than Ethereum. That’s why L2s handle the day-to-day user activities while still relying on Ethereum L1 as a behind-the-scenes basis for a secure and decentralized settlement and data availability layer.

This is a great solution — but there are a lot of L2s on Ethereum alone; each an independent network with its own nuances and experiences.

Building and using dapps that interact and move between these networks and Ethereum L1 can be tedious, difficult and a bad experience for users and developers.

What we need is for web3 to become one multi chain experience, where consumers don’t need to know which chain they’re using (and frankly, don’t care) and where developers can rely on the network that best supports their dapp’s needs.

By moving to this multichain The Internet of Blockchainsbecomes web3 a better experience for everyone involved.

Unfortunately, allowing dapps to move between chains is a difficult technical challenge. In this article, we’ll look at one solution – using Infura RPC endpoints and truffle boxes to build on, and bridge these networks seamlessly.

Specifically, we will use the Optimism Bridge Truffle Box to create a project on the Ethereum Goerli testnet and bridge to Optimism Goerli.

Running a multi-chain Dapp using Infura and truffle boxes

Truffle boxes

As the core of our example solution, we will rely on truffle boxes – “shortcut” boilerplates (such as contracts, libraries, modules and even fully functional dapps) from ConsenSys that you can use to build your dapp.

For multi-chain solutions, they build on top of Infura RPC nodes for many of the L2 networks.

As mentioned above, we will specifically rely on Optimism Bridge Truffle box. This box has all the contracts needed to interact with the Optimism bridge from both L1 and L2, and a set of migrations to deploy, call functions, and pass messages/values ​​between the layers.

It even has a helper script that does everything we need to see all of this in action. We just need to unpack it to get everything we need! According to Trufflesuite.com, the box contains:

  • “An L1 contract that sends a message over the Optimism bridge
  • A migration that sends a message from Ethereum to Optimisme
  • An L2 contract that sends a message over the Optimism bridge
  • A migration that sends a message from Optimism to Ethereum
  • A script to automate compiling contracts, running migrations and sending messages
  • A script to automate sending ETH and DAO across the bridge”

Note: a bridge is a tool that allows independent blockchains to communicate with each other, send tokens, NFTs, etc.

Prerequisites

Before we begin, we need the following prerequisites:

  • Node.js and the package manager NPM.
  • Verify that we have Node.js installed by using the following terminal command:

node -v && npm -v

Step 1 — Create an Infura account to access the network

Once you’ve met the prerequisites, visit the Infura website to log in (or register for a new account).

After successful registration, the page redirects to the Infura dashboard where we can create a new API key, as shown below.

Click the “Create a new key” button and fill in the required information.

After creating your API key, your project ID will be visible on the dashboard under the API KEY section, as shown below. Copy and store it somewhere; you will need it later in this tutorial.

Step 2 — Setup and Installation

Then we set one up Truffle Optimism Bridge Box. We can run the unbox command in any directory you want by using the following command.

npx truffle unbox optimism-bridge <DIRECTORY_NAME>

Replace with the directory name you want. Alternatively, you can install Truffle globally and run the unbox command.

npm install -g truffle
truffle unbox optimism-bridge <DIRECTORY_NAME>

The command should download and run npm install as part of the checkout process.

Now run the following command to change the directory to the new one we just created.

cd truffle-bridge-demo

Note: truffle-bridge-demo is the name of our directory that was created.

We should have something similar to what is shown below.

The .dotenv The npm package is installed, but we need to add some information to the .env file created after unpacking.

The truffle-config.ovm.js file expects a GOERLI_MNEMONIC value to exist in the .env file to run commands on the Ethereum Goerli and Optimism Goerli testnets and an INFURA_KEY to connect to the network.

GOERLI_MNEMONIC="<your-wallet-mnemonic>"
INFURA_KEY="<your-infura-key>"

Replace with the information we got earlier from our Infura dashboard. (Note: Never share your private keys (mnemonic) with anyone and keep them safe). And replace with mnemonic as shown below:

To retrieve the mnemonic from Metamask, click the icon shown below on your Metamask.

Then click on Export private key button to copy the mnemonic.

Git ignores the .env file in this project to protect your private data. It is good security practice to avoid disclosing your private keys to GitHub.

Step 3 — build a bridge using truffle L2 boxes

When we unpacked the project, all the project’s necessary contracts and scripts were created for us. In this next step, let’s go through the individual contracts and migrations to understand how bridging and interactions happen between the networks.

The contract contract/ethereum/GreeterL1.sol shows you how to send a message over the Optimism bridge from L1 to L2.

//SPDX-License-Identifier: Unlicense
// This contract runs on L1, and controls a Greeter on L2.
pragma solidity ^0.8.0;

import { ICrossDomainMessenger } from
    "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol";

contract GreeterL1 {
    address crossDomainMessengerAddr = 0x5086d1eEF304eb5284A0f6720f79403b4e9bE294;


    address greeterL2Addr = 0xC0836cCc8FBa87637e782Dde6e6572aD624fb984;


    function setGreeting(string calldata _greeting) public {
        bytes memory message;


        message = abi.encodeWithSignature("setGreeting(string)",
            _greeting);


        ICrossDomainMessenger(crossDomainMessengerAddr).sendMessage(
            greeterL2Addr,
            message,
            1000000   // within the free gas limit amount
        );
    }      // function setGreeting


}          // contract GreeterL1

The migration migrations/3_set_L2_greeting.js uses the above contract to send a message from Ethereum to Optimism.

var Greeter = artifacts.require("GreeterL1");


/**
 * Set L2 Greeting
 * Run this migration on L1 to update the L1 greeting.
 */
module.exports = async function (deployer) {
  console.log("Updating the L2 Greetings contract from L1! 👋👋");


  const instance = await Greeter.deployed();
  const tx = await instance.setGreeting("👋 Greetings from Truffle!");


  console.log(`🙌 Greeter txn confirmed on L1! ${tx.receipt.transactionHash}`);
  console.log(`🛣️  Bridging message to L2 Greeter contract...`);
  console.log(
    `🕐 In about 1 minute, check the Greeter contract "read" function: 
  );
};

Next, the contracts/optimism/GreeterL2.sol contract sends a message in the other direction (L2->L1) over the Optimism bridge.

//SPDX-License-Identifier: Unlicense
// This contract runs on L2, and controls a Greeter on L1.
pragma solidity ^0.8.0;


import { ICrossDomainMessenger } from
    "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol";


contract GreeterL2 {
    address crossDomainMessengerAddr = 0x4200000000000000000000000000000000000007;

    address greeterL1Addr = 0x7fA4D972bB15B71358da2D937E4A830A9084cf2e;


    function setGreeting(string calldata _greeting) public {
        bytes memory message;



        message = abi.encodeWithSignature("setGreeting(string)",
            _greeting);


        ICrossDomainMessenger(crossDomainMessengerAddr).sendMessage(
            greeterL1Addr,
            message,
            1000000   // irrelevant here
        );
    }      // function setGreeting


}          // contract GreeterL2

The migration migrations/4_set_L1_greeting.js uses the above contract to send a message from Optimism to Ethereum.

require("dotenv").config();
const sdk = require("@eth-optimism/sdk");
const ethers = require("ethers");
const Greeter = artifacts.require("GreeterL2");
const goerliMnemonic = process.env["GOERLI_MNEMONIC"];
const infuraKey = process.env["INFURA_KEY"];

const sleep = (milliseconds) => {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
};


/**
 * Set L1 Greeting
 * Run this migration on L1 to update the L1 greeting.
 */
module.exports = async function (deployer) {
  const newGreeting = "👋 Greetings from Truffle!"; //<---- CHANGE THIS VALUE TO YOUR NAME!!!
  const instance = await Greeter.deployed();


  console.log("Updating the L1 Greetings contract from L2! 👋");

  const tx = await instance.setGreeting(newGreeting);
  const txHash = tx.receipt.transactionHash;



  console.log(`🙌🙌 Greeter txn confirmed on L2! ${txHash}`);
  console.log(
    `🛣️  Bridging message to L1 Greeter contract.\n 🕐 This will take at least 1-5 min...`
  );


  // Set providers for Optimism sdk
  const l1Provider = new ethers.providers.JsonRpcProvider(
    " + infuraKey
  );
  const l2Provider = new ethers.providers.JsonRpcProvider(
    " + infuraKey
  );


  // Connect an L1 signer
  const wallet = ethers.Wallet.fromMnemonic(goerliMnemonic);
  const l1Signer = wallet.connect(l1Provider);


  // Initialize sdk messenger
  const crossChainMessenger = new sdk.CrossChainMessenger({
    l1ChainId: 5,
    l2ChainId: 420,
    l1SignerOrProvider: l1Signer,
    l2SignerOrProvider: l2Provider,
  });

  let statusReady = false;

  // Sleep for 1 min during L2 -> L1 bridging
  await sleep(60000); // 60 seconds

  // Poll the L1 msg status
  while (!statusReady) {
    let status = null;
    status = await crossChainMessenger.getMessageStatus(txHash);
    statusReady = status == sdk.MessageStatus.READY_FOR_RELAY;
    if (!statusReady) {
      console.log(
        "Message not yet received on L1.\n 🕐 Retrying in 10 seconds..."
      );
      await sleep(10000); // 10 seconds
    }
  }



  console.log("📬 Message received! Finalizing...");

  // Open the message on L1
  finalize = await crossChainMessenger.finalizeMessage(txHash);
  console.log(
    `🎉 Message finalized. Check the L1 Greeter contract "read" function: 
  );
};

In the script directory we also have goerli_bridge_message.mjs and goerli_bridge_value.js to automate the process of compiling contracts, running migrations and sending messages.

Step 4 — Complete compilation, migration and bridging of a contract between Ethereum Goerli and Optimism Goerli

Then we will actually distribute our contract to Goerli. The helper script facilitates the compilation, migration and bridging of messages between Ethereum Goerli and Optimism Goerli.

On these networks we need testnet ETH to use it. To receive someone, use a faucet. We also need to add the Optimism addon to your Infura account.

Then we run the following command to start the project.

npm run deploy

Below is a URL to verify (through Etherscan) the bridged message after complete migration.

A link to verify the bridged message via Etherscan will be provided once the fourth migration is complete.

Step 5 — Verify project success on Goerli Testnet with Block Explore

We have set up, installed, built, deployed and walked through the project we unpacked earlier. Next, we will verify the project on the Goerli Ethereum testnet.

Go to the Goerli Etherscan block explorer and paste the txn address 0xbcc1746a9ebbfcfb71665225c1a353a8c8dc9a1aa528a3babcb5b046d615a353 that was displayed on our CLI upon deployment.

https://goerli-optimism.etherscan.io/tx/0xbcc1746a9ebbfcfb71665225c1a353a8c8dc9a1aa528a3babcb5b046d615a353

Conclusion

A multi-chain web3 world is essential if we want the user and developer experience to continue to improve. And to achieve that, we need ways for dapps to communicate between chains quickly and seamlessly.

Hopefully the example we went through with the Optimism Bridge Truffle Box showed you a relatively easy and quick way to get started. To learn more, check out the official documentation.

Have a really great day!

LOAD
. . . comments & more!

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *