Design Pattern Categories
OApp design patterns help teams decide and implement their contract business logic for crosschain coordination:- Architecture Patterns: How contracts coordinate and interact with one another across multiple chains (hub-spoke or symmetric business logic)
- Message Flow Patterns: How messages travel between chains (one-way push, round-trip ping-pong, batch distribution, compose workflows)
- Message Processing Patterns: How to control message processing (ordered delivery, rate limiting, conditional handling)
- Data Access Patterns: How to retrieve information from other chains (pull messaging, crosschain queries)
1. Architecture Patterns
How contracts coordinate and interact with one another across multiple chains.Hub-Spoke Architecture (Asymmetric Coordination)
One chain acts as the central “hub” with coordination logic, while other chains act as “spokes” with execution logic. The hub makes decisions, aggregates data, and distributes commands. Spokes report to the hub and execute received instructions.Use Cases: Crosschain governance, vault deposits (deep liquidity), oracle aggregation
Point-to-Point Architecture (Symmetric Business Logic)
All contracts have identical business logic and operate as equal peers. Each contract can initiate communication with any other contract, and all contracts handle messages the same way. No central coordinator - each contract maintains its own state while staying synchronized with peers.Use Cases: Token transfers (OFT), peer-to-peer protocols
2. Message Flow Patterns
How messages travel between chains. Push messaging sends data from source to destination chains, with the source initiating and destination processing.Batch Send
Send one message to multiple destination chains simultaneously:Fee Distribution Logic: Batch send requires overriding the base OApp fee logic since you’re summing multiple quotes into one payment and distributing to each send call:
Ping-Pong (ABA Pattern)
Chain A sends to Chain B, which calls LayerZero Endpoint within its_lzReceive business logic to send back to Chain A:
Conditional Message Handling: The
_lzReceive business logic can support conditional handling based on message contents. If your application requires it, you can encode conditional identifiers in your message to determine what type of processing should occur:
Key Insight: The Executor uses the
msg.value from your lzReceiveOption to fund the B→A return message. You must calculate this cost off-chain before sending the initial A→B message.
Use Cases: Crosschain authentication, conditional execution, data verification
Call Composer
Two-step, non-atomic process where the primary message stores a compose message for later execution:Key Insight: The OApp calls
endpoint.sendCompose() which stores a compose message tied to the LayerZero message GUID. The composer contract is called in a separate transaction, making this a non-atomic, fault-isolated process.
Use Cases: Token transfers with automated actions, multi-step DeFi operations
3. Message Processing Patterns
How to control message processing.Ordered Delivery
OApp enforces strict sequence order by comparing protocol nonce with local nonce tracking:Key Insight: The LayerZero Endpoint has its own nonce tracking but delivers messages unordered by default. To implement ordered delivery, the OApp must compare the protocol nonce (from message origin) with its own local nonce tracking and enforce sequence requirements. Use Cases: Financial transactions, workflow dependencies, state machines
Rate Limiting
Control in-flight capacity per channel over time windows to prevent spam and ensure controlled interactions. Rate limiters track consumed capacity that decays over time. LayerZero’s default rate limiter implementation tracks “in-flight” capacity that decays linearly over time. Unlike simple counters that reset at fixed intervals, this approach provides smooth capacity recovery.How It Works: When a message/token transfer occurs, the rate limiter adds the amount to “in-flight” capacity. This capacity decays linearly over the configured time window. If adding a new request would exceed the limit, the request is rejected. This provides smooth capacity recovery rather than sudden resets. Linear Decay Visualization:
Example: With a 100-unit limit over 60 seconds, if the limit is reached at T=0, capacity decays at ~1.67 units/second. After 30 seconds, 50 units of capacity are available for new requests. Units can either be the number of individual OApp messages, or a specific value such as amount transferred per channel.
Outbound Rate Limiting (Source Chain)
Rate check occurs before sending crosschain message. Clean failure mode with no partial states.Transaction fails immediately on source chain - user retains funds, no crosschain state changes.
Inbound Rate Limiting (Destination Chain)
Rate check occurs after crosschain message arrives. Can create partial states requiring retry handling.Rate Limiter Configuration
Definition: Per channel configuration with a limit (number of messages or token value) over a time window. Capacity decays linearly over the window duration, allowing gradual recovery. Examples:- Message limiting: 10 messages per 60 seconds per channel
- Token limiting: 1000 USDC per 24 hours per channel
- Combined limiting: Both message count and token amount restrictions per channel
4. Data Access Patterns
How to retrieve information from other chains. Pull messaging requests data from other chains and returns responses to the requesting chain.Data Queries (lzRead)
Request and retrieve state data from contracts on other chains:Use Cases: Price feeds, state verification, crosschain calculations For comprehensive pull messaging patterns and implementation details, see Omnichain Queries (lzRead).
Exit Criteria
Before proceeding to Module 5, you should be able to:- Explain what makes an app “omnichain” vs “multi-chain”
- Identify which pattern fits your use case
- Design a message schema for your application
- Implement idempotent message handling
Further Reading
Official Documentation
- OApp Extensions & Design Patterns - Comprehensive pattern details and advanced examples
- OApp Quickstart - Step-by-step implementation guide
Code References
- OApp.sol - Core OApp interface and implementation
- OptionsBuilder.sol - Execution options helpers
- Test Mocks - Working pattern implementations
Related Modules
- Module 3: LayerZero as Master Interface - The interface OApps build on
- Module 6: Value Transfer Implementations - Specialized OApp patterns for tokens