Builders
Dapp Developers
Bridging Domains
Communicating Between OP Mainnet and Ethereum Using Solidity

Communicating between contracts on OP Mainnet and Ethereum Using Solidity

This tutorial teaches you how to use Solidity for interlayer communication between contracts to send and receive messages. We'll go over the L1 contract that controls Greeter on L2, FromL1_ControlL2Greeter.sol, and the contract going the other direction is identical, FromL2_ControlL1Greeter.sol, except for addresses. For more details about this process, see the Messaging Guide.

Before You Begin

To complete this tutorial, you'll need the contract details for FromL1_ControlL2Greeter.sol and FromL2_ControlL1Greeter.sol, which can be found below.

Working with Greeters on Solidity

Import Interface to Send Messages

//SPDX-License-Identifier: Unlicense
// This contracts runs on L1, and controls a Greeter on L2.
pragma solidity ^0.8.0;
 
import { ICrossDomainMessenger } from 
    "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol";

This line imports the interface to send messages, ICrossDomainMessenger.sol (opens in a new tab).

Set Cross Domain Messenger Address

contract FromL1_ControlL2Greeter {
    address crossDomainMessengerAddr = 0x5086d1eEF304eb5284A0f6720f79403b4e9bE294;

This is the address of Proxy_OVM_L1CrossDomainMessenger (opens in a new tab) on Goerli. To call L2 from L1 on mainnet, you need to use this address (opens in a new tab). To call L1 from L2, on either mainnet or Goerli, use the address of L2CrossDomainMessenger, 0x4200000000000000000000000000000000000007.

Install Greeter on Optimism Goerli

    address greeterL2Addr = 0xE8B462EEF7Cbd4C855Ea4B65De65a5c5Bab650A9;

This is the address on which Greeter is installed on Optimistic Goerli.

Set the New Greeting

    function setGreeting(string calldata _greeting) public {

This function sets the new greeting. Note that the string is stored in calldata. This saves us some gas, because when we are called from an externally owned account or a different contract there is no need to copy the input string to memory. The downside is that we cannot call setGreeting from within this contract, because contracts cannot modify their own calldata.

Store the Message

        bytes memory message;

This is where we'll store the message to send to L2.

Create the Message

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

Here we create the message, the calldata to be sent on L2. The Solidity abi.encodeWithSignature (opens in a new tab) function creates this calldata. As specified in the ABI (opens in a new tab), it is four bytes of signature for the function being called followed by the parameters, in this case a string for the new greeting, and an address for the original sender.

We don't need the original sender for the tutorial itself. We sent it here to make it easier to see how many people went through the tutorial.

Send the Message

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

This call actually sends the message. It gets three parameters:

  • The address on L2 of the contract being contacted.
  • The calldata to send that contract.
  • The gas limit. As long as the gas limit is below the enqueueL2GasPrepaid (opens in a new tab) value, there is no extra cost. Note that this parameter is also required on messages from L2 to L1, but there it does not affect anything.

Close the Functions

    }      // function setGreeting 
}          // contract FromL1_ControlL2Greeter

Getting the Source Address

If you look at Etherscan, for either the L1 Greeter (opens in a new tab) or the L2 Greeter (opens in a new tab), you will see events with the source address on the other layer. The way this works is that the cross domain messenger that calls the target contract has a method, xDomainMessageSender(), that returns the source address. It is used by the getXsource function in Greeter.

Get Cross Domain Origin

  // Get the cross domain origin, if any
  function getXorig() private view returns (address) {
    address cdmAddr = address(0);    

Calculate Address of Cross Domain Messenger

It might look like it would be more efficient to calculate the address of the cross domain messenger just once, but that would involve changing the state, which is an expensive operation. Unless we are going to run this code thousands of times, it is more efficient to just have a few if statements.

    // Mainnet
    if (block.chainid == 1)
      cdmAddr = 0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1;
 
    // Goerli
    if (block.chainid == 5)
      cdmAddr = 0x5086d1eEF304eb5284A0f6720f79403b4e9bE294;
 
    // L2 (same address on every network)
    if (block.chainid == 10 || block.chainid == 420)
      cdmAddr = 0x4200000000000000000000000000000000000007;
      

There are two possibilities for the cross domain messenger's address on L1 because the address is not determined by The Optimism Foundation. On L2, The Optimism Foundation has full control of the genesis block, so we can put all of our contracts on convenient addresses.

Check Sender of Cross Domain Message

    // If this isn't a cross domain message
    if (msg.sender != cdmAddr)
      return address(0);

If the sender isn't the cross domain messenger, then this isn't a cross domain message. Just return zero.

Get Original Source Address for Cross Domain Messenger

    // If it is a cross domain message, find out where it is from
    return ICrossDomainMessenger(cdmAddr).xDomainMessageSender();
  }    // getXorig()

If it is the cross domain messenger, call xDomainMessageSender() to get the original source address.

Conclusion

You should now be able to control contracts on OP Mainnet from Ethereum or the other way around. This is useful, for example, if you want to hold cheap DAO votes on OP Mainnet to manage an Ethereum treasury (see rollcall (opens in a new tab)) or offload a complicated calculation, which must be done in a traceable manner, to OP Mainnet where gas is cheap.