James Bachini

LayerZero Example | How To Send Cross-Chain Messages In Solidity

LayerZero Example

In this article we are going to look at how to send messages or data from one chain to another. In this LayerZero example we will be sending a string from Ethereum’s Goerli Testnet to Optimisms Layer 2 Goerli Testnet.

  1. Cross-Chain Tutorial Video
  2. How LayerZero Works
  3. What Chains Is Layer Zero On
  4. LayerZero Messaging Example
  5. Cross-Chain Tokens Example
  6. Conclusion

Cross-Chain Tutorial Video

James On YouTube

How LayerZero Works

LayerZero comprises of a network of nodes which pass communications between different EVM blockchains.

LayerZero Example

A user will call a transaction and pay a fee in the native asset on one chain. This will fire an event which will be tracked by a p2p network of external nodes. These nodes form the LayerZero network and relay the data on to a receiver contract on the destination blockchain.


What Chains Is Layer Zero On

As of time of publishing in December 2022 LayerZero is currently deployed to the following chains:

MainnetsTestnets
EthereumGoerli Testnet
BNB ChainBNB Testnet
AvalancheFuji Testnet
PolygonMumbai Testnet
Optimism L2Goerli Optimism
Arbitrum L2Goerli Arbitrum
AptosAptos Testnet
FantomFantom Tesntet
Swimmer
DFK
HarmonyHarmony Testnet
MoonbeamMoonbeam Testnet
CeloCelo Testnet
DexalotDexalot Testnet
Fuse
GnosisGnosis Tesnet
KlaytnKlaytn Testnet
MetisMetis Testnet
Intain
zkSync Testnet
CoreDao Testnet
Portal Fantasy

LayerZero Messaging Example

This LayerZero example Solidity code and full instructions are available on Github:
https://github.com/jamesbachini/LayerZero-Example

Thank you to @Gangstarr60 for updating it for the latest OpenZeppelin libraries.

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.17;

import "https://github.com/LayerZero-Labs/solidity-examples/blob/main/contracts/lzApp/NonblockingLzApp.sol";

contract LayerZeroTest is NonblockingLzApp {
    string public data = "Nothing received yet";
    uint16 destChainId;
    
    constructor(address _lzEndpoint) NonblockingLzApp(_lzEndpoint) Ownable(msg.sender) {
        if (_lzEndpoint == 0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1) destChainId = 10121;
        if (_lzEndpoint == 0xbfD2135BFfbb0B5378b56643c2Df8a87552Bfa23) destChainId = 10132;
    }

    function _nonblockingLzReceive(uint16, bytes memory, uint64, bytes memory _payload) internal override {
       data = abi.decode(_payload, (string));
    }

    function send(string memory _message) public payable {
        bytes memory payload = abi.encode(_message);
        _lzSend(destChainId, payload, payable(msg.sender), address(0x0), bytes(""), msg.value);
    }

    function trustAddress(address _otherContract) public onlyOwner {
        trustedRemoteLookup[destChainId] = abi.encodePacked(_otherContract, address(this));   
    }
}

I ran this on Remix you’ll need to add Optimism Goerli testnet to Metamask using Chainlist.

Note that the chainId’s that LayerZero uses are different from the real chain ID’s that you would use to connect to RPC nodes etc.

Instructions

Copy the code into remix

Deploy on both networks using the lzEndpoints from here in the constructor argument.

Then use the trustAddress(address _otherContract) function to approve the other contract address on both deployed contracts.

Once that’s done you can send messages both ways using send(“Hello World”). It can take a few minutes for the data to be relayed from one chain to the other and the state to be updated on the destination chain.

Note that you will need to call the send function with some funds by putting a value amount in. I sent 12345678 gwei which seemed to work on testnet and any surplus is refunded back to your wallet. There’s an option for this near the gas limit in the top left panel on Remix.


Cross-Chain Tokens Example

This LayerZero Example contract is also available in my LayerZero Example Repository, note that there is a more sophisticated OFT example here.

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.17;

import "https://github.com/LayerZero-Labs/solidity-examples/blob/main/contracts/lzApp/NonblockingLzApp.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract CrossChainToken is NonblockingLzApp, ERC20 {
    uint16 destChainId;
    
    constructor(address _lzEndpoint) NonblockingLzApp(_lzEndpoint) ERC20("Cross Chain Token", "CCT") Ownable(msg.sender) {
        if (_lzEndpoint == 0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1) destChainId = 10121;
        if (_lzEndpoint == 0xbfD2135BFfbb0B5378b56643c2Df8a87552Bfa23) destChainId = 10132;
        _mint(msg.sender, 1000000 * 10 ** decimals());
    }

    function _nonblockingLzReceive(uint16, bytes memory, uint64, bytes memory _payload) internal override {
       (address toAddress, uint amount) = abi.decode(_payload, (address,uint));
       _mint(toAddress, amount);
    }

    function bridge(uint _amount) public payable {
        _burn(msg.sender, _amount);
        bytes memory payload = abi.encode(msg.sender, _amount);
        _lzSend(destChainId, payload, payable(msg.sender), address(0x0), bytes(""), msg.value);
    }

    function trustAddress(address _otherContract) public onlyOwner {
        trustedRemoteLookup[destChainId] = abi.encodePacked(_otherContract, address(this));   
    }
}

Remember to add some ETH to the value field when calling the bridge() function. You can check it’s gone through by checking the balanceOf() for the owner address.


Conclusion

LayerZero makes cross-chain communications relatively simple from within Solidity. There are security concerns given the number of exploits we’ve seen with bridges over the last 12 months but the future is on L2s and this could become the go to solution for interchain communications.

I hope that eventually Ethereum will add a data layer for coms between mainnet and L2s which will add a lot of value to the “chain of chain” narrative. Until then we are reliant on 3rd party networks to relay data between chains and LayerZero does that well.

LayerZero

Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

Thank you.

James Bachini

Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.