Skip to main content
Version: Endpoint V2 Docs

Getting Started with Contract Standards

Use LayerZero's Contract Standards to easily start sending arbitrary data, tokens, and external calls using the protocol:

Each of these contract standards implement common functions for sending, receiving, and configuring omnichain messages via the protocol interface: the LayerZero Endpoint contract.

  • OAppSender._lzSend: internal function that calls EndpointV2.send to send a message as bytes.

  • OAppReceiver._lzReceive: internal function that delivers the encoded message as bytes after the Executor calls EndpointV2.lzReceive.

This method of encoding send parameters and decoding them on the destination chain is the basis for how all OApps work.

Example Omnichain Application

The OApp Standard contains both a send and receive interface.

info

This code snippet is already implemented in the Remix example below. Simply review this code to understand how it works internally.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

// @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers
import { OAppSender, MessagingFee, MessagingReceipt } from "./OAppSender.sol";
// @dev Import the 'Origin' so it's exposed to OApp implementers
import { OAppReceiver, Origin } from "./OAppReceiver.sol";
import { OAppCore } from "./OAppCore.sol";

/**
* @title OApp
* @dev Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality.
*/
abstract contract OApp is OAppSender, OAppReceiver {}

You can use the Remix IDE to see how OAppSender and OAppReceiver work together for sending and receiving any arbitrary data to supported destination chains.

OAppSender.sol

Open in RemixWhat is Remix?

OAppReceiver.sol

Open in RemixWhat is Remix?

Prerequisites

  1. You should first be familiar with writing and deploying contracts to your desired blockchains. This involves understanding the specific smart contract language and the deployment process for those chains.

  2. A wallet set up and funded for the chains you'll be working with.

Deploying Your Contracts

We'll deploy the Source Contract on Sepolia, and the Destination Contract on Optimism Sepolia:

info

This example can be used with any EVM-compatible blockchain that LayerZero supports.


  1. Open MetaMask and select the Ethereum Sepolia network. Make sure you have native gas in the wallet connected.

  2. In Remix under the Deploy & Run Transactions tab, select Injected Provider - MetaMask in the Environment list.

  3. Under the Deploy section, fill in the Endpoint Address for your current chain.

Sepolia Endpoint Address

0x6edce65403992e310a62460808c4b910d972f10f

Optimism Sepolia Endpoint Address

0x6edce65403992e310a62460808c4b910d972f10f
  1. Click deploy, follow the MetaMask prompt to confirm the transaction, and wait for the contract address to appear under Deployed Contracts.

  2. Repeat the above steps for any other chains you plan to deploy to and connect.

Connecting Your Contracts

To connect your OApp deployments together, you will need to call setPeer on both the Ethereum Sepolia and Optimism Sepolia OApp.

The function takes 2 arguments: _eid, the destination endpoint ID for the chain our other OApp contract lives on, and _peer, the destination OApp contract address in bytes32 format.

// LayerZero/V2/oapp/contracts/oapp/OAppReceiver.sol
// @dev must-have configurations for standard OApps
function setPeer(uint32 _eid, bytes32 _peer) public virtual onlyOwner {
peers[_eid] = _peer; // Array of peer addresses by destination.
emit PeerSet(_eid, _peer); // Event emitted each time a peer is set.
}

To setPeer on SourceOApp, take the DestinationOApp address and call OApp.addressToBytes32. Use the returned output as the _peer.

Your _peer should look something like this: 0x0000000000000000000000000a3ecc421699e2eb7f53584d07165d95721a4ca7.

By default, the OApp standard inherits OAppReceiver which uses this peer inside lzReceive to enforce that the sender is the expected origin address.

// LayerZero/V2/oapp/contracts/oapp/OAppCore.sol

/**
* @dev Entry point for receiving messages or packets from the endpoint.
* @param _origin The origin information containing the source endpoint and sender address.
* - srcEid: The source chain endpoint ID.
* - sender: The sender address on the src chain.
* - nonce: The nonce of the message.
* @param _guid The unique identifier for the received LayerZero message.
* @param _message The payload of the received message.
* @param _executor The address of the executor for the received message.
* @param _extraData Additional arbitrary data provided by the corresponding executor.
*
* @dev Entry point for receiving msg/packet from the LayerZero endpoint.
*/
function lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) public payable virtual {
// Ensures that only the endpoint can attempt to lzReceive() messages to this OApp.
if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);

// Ensure that the sender matches the expected peer for the source endpoint.
if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender);

// Call the internal OApp implementation of lzReceive.
_lzReceive(_origin, _guid, _message, _executor, _extraData);
}
tip

Remember, an EVM address is a bytes20 value, so you must convert your address to bytes32 when calling setPeer. This can also be easily be done by Zero Padding the address until it is 32 bytes in length.

LayerZero uses bytes32 for broad compatibility with non-EVM chains.


Pass the address of your destination contract as a bytes32 value, as well as the destination endpoint ID.

  • To send to Ethereum Sepolia, the Endpoint ID is: 40161.

  • To send to Optimism Sepolia, the Endpoint ID is: 40232.

caution

You'll need to repeat this wiring on both contracts in order to send and receive messages. That means calling setPeer on both your Ethereum Sepolia and Optimism Sepolia contracts. Remember to switch networks in MetaMask.

If successful, you now should be setup to start sending cross-chain messages!

Estimating Fees

The LayerZero Protocol gas fees can vary based on your source chain, DVNs, Executor, and amount of native gas token you request in _options, so you should estimate fees before sending your first transaction.

The OApp.quote function invokes an internal OAppSender._quote to estimate the fees associated with a particular LayerZero transaction using four inputs:

  • _dstEid: This is the identifier of the destination chain's endpoint where the transaction is intended to go.

  • _message: This is the arbitrary message you intend to send to your destination chain and contract.

  • _options: A bytes array that contains serialized execution options that tell the protocol the amount of gas to for the Executor to send when calling lzReceive, as well as other function call related settings.

  • _payInLzToken: A boolean which determines whether to return the fee estimate in the native gas token or in ZRO token.

info

In this tutorial, you will deliver 50000 wei for the lzReceive call by passing 0x0003010011010000000000000000000000000000c350 as your _options. You will be quoted 50000 wei on the source chain, which the Executor will convert to the destination gas token and use in the call. See Message Execution Options for all possible execution settings.

Sending Your Message

To use the send function, simply input a string into the message field that you wish to send to your destination chain.

Contract A

Remember to pass the quote in Remix under VALUE to pay the gas fees on the source and destination, as well as for the Security Stack and Executor who verify and execute the messages. Then call SourceOApp.send!

Contract B

Your message may take a few minutes to appear in the destination block explorer, depending on which chains you deploy to.

Tracking Your Message

Finally, let's see what's happening in our transaction. Take your transaction hash and paste it into: https://testnet.layerzeroscan.com/

You should see Status: Delivered, confirming your message has been delivered to its destination using LayerZero.

Congrats, you just sent your first omnichain message! 🥳

Further Reading

Now that you understand the basics for how OApps work, you should explore setting up your development environment and diving deeper into the omnichain contract standards!