Skip to main content
Version: Endpoint V1

Messaging Properties

Message State

Messages are sent from the User Application (UA) at source srcUA to the UA at the destination dstUA. Once the message is received by dstUA, the message is considered delivered (transitioning from INFLIGHT to either SUCCESS or STORED)

Message StateCases
INFLIGHTAfter a message is sent
SUCCESSA1: dstUA success OK()
A2: dstUA fails with caught error/exception
STOREDB1: dstUA fails with uncaught error/exception
// message handling at destination chain
try ILayerZeroReceiver(_dstAddress).lzReceive{gas: _gasLimit}(_srcChainId, _srcAddress, _nonce, _payload) {
// message state becomes SUCCESS
} catch {
// message state becomes STORED
emit PayloadStored(_srcChainId, _srcAddress, _dstAddress, _payload);
}

Case A2: dstUA is expected to store the message in their contract to be retried (LayerZero will not store any successfully delivered messages). dstUA is expected to monitor and retry STORED messages on behalf of its users.

Case B1: dstUA is expected to gracefully handle all errors/exceptions when receiving a message, and any uncaught errors/exceptions (including out-of-gas) will cause the message to transition into STORED. A STORED message will block the delivery of any future message from srcUA to all dstUA on the same destination chain and can be retried until the message becomes SUCCESS. dstUA should implement a handler to transition the stored message from STORED to SUCCESS. If a bug in dstUA contract results in an unrecoverable error/exception, LayerZero provides a last-resort interface to force resume message delivery, only by the dstUA contract.

Message Ordering

LayerZero provides ordered delivery of messages from a given sender to a destination chain, i.e. srcUA -> dstChain. In other words, the message order nonce is shared by all dstUA on the same dstChain. That's why a STORED message blocks the message pathway from srcUA to all dstUA on the same destination chain. If it isn't necessary to preserve the sequential nonce property for a particular dstUA the sender must add the nonce into the payload and handle it end-to-end within the UA. UAs can implement a non-blocking pattern in their contract code.

Extensibility

Message Adapter Parameters

LayerZero allows UAs to add arbitrary transaction params in the send() function, providing a high level of flexibility and opening up opportunities for a diverse set of 3rd party plugins This is implemented as an unreserved byte array parameter to the send() function, with UAs allowed to write any additional data necessary into that parameter. We recommend that UAs leave some degree of configurability for the extra parameters to allow for feature extensions.

One great feature of _adapterParams is performing an Airdrop.

Patterns

Non-Reentrancy

LayerZero Endpoint has a non-reentrancy guard for both the send() and receive(), respectively. In other words, both send() and receive() can not call themselves on the same chain. UAs should not rely on LayerZero to perform the non-reentrancy check. However, UAs can query the endpoint to see if the endpoint isSendingPayload() or isReceivingPayload() for finer-grained reentrancy control.

Message Chaining

UAs can call send() in the receive() calls on the same chain. Example applications for calling send() in the receive() include (e.g. Ping Pong):

  • the UA at the source chain wants a message receipt (Chain A -> Chain B -> Chain A)
  • the UA at the destination reroutes the message (Chain A -> Chain B -> Chain C)
function lzReceive(uint16 _srcChainId, bytes memory _fromAddress, uint64, /*_nonce*/ bytes memory _payload) external override {
...
// message chaining
endpoint.send{value: messageFee}(
...
);
}

However, the fee for sending messages on another chain is not observable on-chain. UAs would need to create some fee estimate heuristics. Optionally, user apps can store the chained message and then resend them with another transaction.

Multi-Send

UAs can send multiple messages in one transaction at the source chain. The endpoint non-reentrancy will not block this pattern.

function sendFirstMessage(
uint gasAmountForDst, uint16[] calldata chainIds, bytes[] calldata dstAddresses) external payable {
...
for(uint i = 0; i < chainIds.length; i++){
endpoint.send{value: fee}(chainIds[i], dstAddresses[i], messageString, msg.sender, address(0x0), _relayerParams);
}
}