Skip to main content
Each design pattern functions as a distinct omnichain building block, capable of being used independently or in conjunction with others.
This modularity allows for the seamless integration and combination of patterns to suit specific developer requirements.

ABA

AB messaging refers to a one way send call from a source to destination blockchain. Diagram showing simple AB messaging pattern: a one-way send call from source chain A to destination chain B via LayerZero In the Getting Started Guide, we use this design pattern to send a string from Chain A to store on Chain B (A -> B). The ABA message pattern extends this simple logic by nesting another _lzSend call within the destination contract’s _lzReceive function. You can think of this as a ping-pong style call, pinging a destination chain and ponging back to the original source (A -> B -> A). Diagram showing ABA messaging pattern: a ping-pong style call where Chain A sends to Chain B, which then sends back to Chain A (A → B → A)
This is particularly useful when actions on one blockchain depend on the state or confirmation of another, such as:
  • Conditional Execution of Contracts: A smart contract on chain A will only execute a function if a condition on chain B is met. It sends a message to chain B to check the condition and then receives a confirmation back before proceeding.
  • Omnichain Data Feeds: A contract on Chain A can fetch data from the destination (Chain B) to complete a process back on the source.
  • Crosschain Authentication: A user or contract might authenticate on chain A, ping chain B to process something that requires this authentication, and then receive back a token or confirmation that the process was successful.

Code Example

This pattern demonstrates vertical composability, where the nested message contains both handling for the message receipt, as well as additional logic for a subsequent call that must all happen within one atomic transaction.
This message pattern can also be considered an ABC type call (A -> B -> C), as the nested _lzSend can send to any new destination chain.

Batch Send

The Batch Send design pattern, where a single transaction can initiate multiple _lzSend calls to various destination chains, is highly efficient for operations that need to propagate an action across several blockchains simultaneously. Diagram showing Batch Send pattern: a single transaction from Chain A initiating multiple _lzSend calls to Chains B, C, and D simultaneously This can significantly reduce the operational overhead associated with performing the same action multiple times on different blockchains. It streamlines omnichain interactions by bundling them into a single transaction, making processes more efficient and easier to manage for example:
  • Simultaneous Omnichain Updates: When a system needs to update the same information across multiple chains (such as a change in governance parameters or updating oracle data), Batch Send can propagate the updates in one go.
  • DeFi Strategies: For DeFi protocols that operate on multiple chains, rebalancing liquidity pools or executing yield farming strategies can be done in batch to maintain parity across ecosystems.
  • Aggregated Omnichain Data Posting: Oracles or data providers that supply information to smart contracts on multiple chains can use Batch Send to post data such as price feeds, event outcomes, or other updates in a single transaction.

Code Example

Composed

A composed message refers to an application that invokes the Endpoint method, sendCompose, to deliver a composed call to a destination contract via lzCompose. Diagram showing Composed messaging pattern: message sent from Chain A to contract B1 on Chain B, which then calls sendCompose to deliver a composed call to contract B2 via lzCompose (A → B1 → B2) This pattern demonstrates horizontal composability, which differs from vertical composability in that the external call is now containerized as a new message packet; enabling the application to ensure that certain receipt logic remains separate from the external call itself.
Since each composable call is created as a separate message packet via lzCompose, this pattern can be extended for as many steps as your application needs (B1 -> B2 -> B3, etc).

This pattern can be particularly powerful for orchestrating complex interactions and processes on the destination chain that need contract logic to be handled in independent steps, such as:
  • Omnichain DeFi Strategies: A smart contract could trigger a token transfer on the destination chain and then automatically interact with a DeFi protocol to lend, borrow, provide liquidity, stake, etc. executing a series of financial strategies across chains.
  • NFT Interactions: An NFT could be transferred to another chain, and upon arrival, it could trigger a contract to issue a license, register a domain, or initiate a subscription service linked to the NFT’s ownership.
  • DAO Coordination: A DAO could send funds to another chain’s contract and compose a message to execute specific DAO-agreed upon investments or funding of public goods.

Composing an OApp

There are 3 relevant contract interactions when composing an OApp:
  1. Source OApp: the OApp sending a crosschain message via _lzSend to a destination.
  2. Destination OApp(s): the OApp receiving a crosschain message via _lzReceive and calling sendCompose.
  3. Composed Receiver(s): the contract interface implementing business logic to handle receiving a composed message via lzCompose.

Sending Message

The sending OApp is required to pass specific Composed Message Execution Options (more on this below) for the sendCompose call, but is not required to pass any input parameters for the call itself (however this pattern may be useful depending on what arbitrary action you wish to trigger when composing). For example, this send function packs the destination _composedAddress for the destination OApp to decode and use for the actual composed call.

Sending Compose

The receiving OApp invokes the LayerZero Endpoint’s sendCompose method as part of your OApp’s _lzReceive business logic. The sendCompose method takes the following inputs:
  1. _to: the contract implementing the ILayerZeroComposer receive interface.
  2. _guid: the global unique identifier of the source message (provided standard by lzReceive).
  3. _index: the index of the composed message (used for pricing different gas execution amounts along different composed legs of the transaction).
This means that when a packet is received (_lzReceive) by the Destination OApp, it will send (sendCompose) a new composed packet via the destination LayerZero Endpoint.
The above sendCompose call hardcodes _index to 0 and simply forwards the same payload as _lzReceive to lzCompose, however these inputs can also be dynamically adjusted depending on the number and type of composed calls you wish to make.

Composed Message Execution Options

You can decide both the _gas and msg.value that should be used for the composed call(s), depending on the type and quantity of messages you intend to send. Your configured Executor will use the _options provided in the original _lzSend call to determine the gas limit and amount of msg.value to include per message _index:
It’s important to remember that gas costs may vary depending on the destination chain. For example, all new Ethereum transactions cost 21000 wei, but other chains may have lower or higher opcode costs, or entirely different gas mechanisms. You can read more about generating _options and the role of _index in Message Execution Options.

Receiving Compose

The destination must implement the ILayerZeroComposer interface to handle receiving the composed message. From there, you can decide any additional composed business logic to execute within lzCompose, as shown below:

Further Reading

For more advanced implementations of sendCompose and lzCompose:
  • Review the OmniCounter.sol for sending composed messages to the same OApp implementation.
  • Read the OFT Composing section to see how to implement composed business logic into your OFTs.

Composed ABA

The Composed ABA design pattern enables sophisticated omnichain communication by allowing for an operation to be performed as part of the receive logic on the destination chain (B1), a follow-up action or call containerized as an independent step within lzCompose (B2), which then sends back to the source chain (A). Diagram showing Composed ABA pattern: Chain A sends to B1, B1 calls lzCompose to B2, and B2 sends back to Chain A (A → B1 → B2 → A), enabling multi-step crosschain workflows
This message pattern can also be considered a Composed ABC type call (A -> B1 -> B2 -> C), as the nested _lzSend can send to any new destination chain.

This pattern demonstrates a complex, multi-step, process across blockchains where each step requires its own atomic logic to execute without depending on separate execution logic. Here are some use cases that could benefit from a Composed ABA design pattern:
  • Omnichain Data Verification: Chain A sends a request to chain B to verify a set of data. Once verified, a contract on chain B executes an action based on this data and sends a signal back to chain A to either proceed with the next step or record the verification.
  • Omnichain Collateral Management: When collateral on chain A is locked or released, a corresponding contract on chain B could be called to issue a loan or unlock additional funds. Confirmation of the action is then sent back to chain A to complete the process.
  • Multi-Step Contract Interaction for Games and Collectibles: In a gaming scenario, an asset (like an NFT) could be sent from chain A to B, triggering a contract on B that could unlock a new level or feature in a game, with a confirmation or reward then sent back to chain A.

Message Ordering

See Message Ordering under Core Concepts for details.

Rate Limiting

The RateLimiter.sol is used to control the number of crosschain messages that can be sent within a certain time window, ensuring that the OApp is not spammed by too many transactions at once. It’s particularly useful for:
  • Preventing Denial of Service Attacks: By setting thresholds on the number of messages that can be processed within a given timeframe, the RateLimiter acts as a safeguard against DoS attacks, where malicious actors might attempt to overload an OApp with a flood of transactions. This protection ensures that the OApp remains accessible and functional for legitimate users, even under attempted attacks.
  • Regulatory Compliance: Some applications may need to enforce limits to comply with legal or regulatory requirements.
The RateLimiter is only useful under specific application use cases. It will not be necessary for most OApps and can even be counterproductive for more generic applications:
  • Low Traffic Applications: If your application doesn’t expect high volumes of traffic, implementing a rate limiter might be unnecessary overhead.
  • Critical Systems Requiring Immediate Transactions: For systems where transactions need to be processed immediately without delay, rate limiting could hinder performance.

Installation

To begin working with LayerZero contracts, you can install the OApp npm package to an existing project:

Usage

Import the RateLimiter.sol contract into your OApp contract file and inherit the contract:

Initializing Rate Limits

In the constructor of your contract, initialize the rate limits using _setRateLimits with an array of RateLimitConfig structs. Example:
RateLimitConfig Struct:

Setting Rate Limits

Provide functions to set or update rate limits dynamically. This can include a function to adjust individual or multiple rate limits and a mechanism to authorize who can make these changes (typically restricted to the contract owner or a specific role).

Checking Rate Limits During Send Calls

Before processing transactions, use _outflow to ensure the transaction doesn’t exceed the set limits. This function should be called in any transactional functions, such as message passing or token transfers.

Message Passing

Token Transfers