Skip to main content
Version: Endpoint V1

Send Messages

Use LayerZero to send a bytes payload from one chain to another.

To send a message, call the Endpoint's send() function.

Initiate the send() function in your contracts (similar to the CounterMock) to send a cross chain message.

// an endpoint is the contract which has the send() function
ILayerZeroEndpoint public endpoint;
// remote address concated with local address packed into 40 bytes
bytes memory remoteAndLocalAddresses = abi.encodePacked(remoteAddress, localAddress);
// call send() to send a message/payload to another chain
10001, // destination LayerZero chainId
remoteAndLocalAddresses, // send to this address on the destination
bytes("hello"), // bytes payload
msg.sender, // refund address
address(0x0), // future parameter
bytes("") // adapterParams (see "Advanced Features")

Here is an explanation of the endpoint.send() interface:

// @notice send a LayerZero message to the specified address at a LayerZero endpoint specified by our chainId.
// @param _dstChainId - the destination chain identifier
// @param _remoteAndLocalAddresses - remote address concated with local address packed into 40 bytes
// @param _payload - a custom bytes payload to send to the destination contract
// @param _refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address
// @param _zroPaymentAddress - the address of the ZRO token holder who would pay for the transaction
// @param _adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination
function send(
uint16 _dstChainId,
bytes calldata _remoteAndLocalAddresses,
bytes calldata _payload,
address payable _refundAddress,
address _zroPaymentAddress,
bytes calldata _adapterParams
) external payable;

You will note in the topmost example we call send() with {value: msg.value} this is because send() requires a bit of native gas token so the relayer can complete the message delivery on the destination chain. If you don't set this value you might get this error when calling endpoint.send()

Putting it into a more complete example, your User Application contract may look something like this:

pragma solidity 0.8.4;
pragma abicoder v2;

import "../lzApp/NonblockingLzApp.sol";

/// @title A LayerZero example sending a cross chain message from a source chain to a destination chain to increment a counter
contract OmniCounter is NonblockingLzApp {
uint public counter;

constructor(address _lzEndpoint) NonblockingLzApp(_lzEndpoint) {}

function _nonblockingLzReceive(uint16, bytes memory, uint64, bytes memory) internal override {
// _nonblockingLzReceive is how we provide custom logic to lzReceive()
// in this case, increment a counter when a message is received.
counter += 1;

function incrementCounter(uint16 _dstChainId) public payable {
// _lzSend calls endpoint.send()
_lzSend(_dstChainId, bytes(""), payable(msg.sender), address(0x0), bytes(""));

There you have it! Call incrementCounter() to send a LayerZero message to another chain.


Use LayerZero Scan to track your omnichain message in-flight after calling send!

See the Receive Messages section for how to handle receiving the message by implementing lzReceive() and also see how to execute any smart contract logic on the destination.

Putting together a full User Application contract simply means implementing a way to call endpoint.send() and ensuring lzReceive() is overridden to handle receiving messages (see ILayerZeroReceiver.sol for the lzReceive() interface)

OmniChainToken is a slightly more complex example of a omnichain contract.

Estimating Message Fees

If you want to know how much {value: xxx} to give to the send() function to pay for you message please refer to this section on estimating fees.

Adapter Parameters

Also see advanced message features using Adapter Parameters (aka: _adapterParameters)