Skip to main content
Version: Endpoint V2 Docs

OApp Technical Reference

LayerZero’s Omnichain Application (OApp) standard defines a common set of patterns and interfaces for any smart contract that needs to send and receive messages across multiple blockchains. By inheriting OApp’s core functionality, higher-level primitives (such as OFT, ONFT, or any custom cross-chain logic) can rely on a unified, secure messaging layer.

All OApp implementations must handle:

  • Message sending: Encode and dispatch outbound messages
  • Message receiving: Decode and process inbound messages
  • Fee handling: Quote, collect, and refund native & ZRO fees
  • Peer management: Maintain trusted mappings between chains
  • Channel management and security: Control security and execution settings between chains

Deployment

Every OApp needs to be deployed on each chain where it will operate. Initialization involves two steps:

1. Integrate with the local Endpoint

  1. Pass the local Endpoint V2 address into your constructor or initializer.
  2. The Endpoint’s delegate authority is set to your OApp and the address initializing unless overridden.
  3. As a delegate, your OApp can call any endpoint.* security method (setSendLibrary, setConfig, etc.) in a secure, authorized manner.

2. Configure peers (directional peering)

  1. On each chain, the owner calls setPeer(eid, peerAddress) to register the remote OApp’s address for a given Endpoint ID.
  2. Repeat on the destination chain: register the source chain’s OApp address under its Endpoint ID.
  3. Because trust is directional, the receiving OApp checks peers[srcEid] == origin.sender before processing inbound messages.
info

For guidelines on channel security, see Message Channel Security. For an example implementation, see the OFT Technical Reference.

Core Message Flow

OApps follow a three-step life cycle. Developers focus on local state changes and message encoding; LayerZero handles secure routing and final delivery.

PhaseActorsResponsibility
1. send(...)OAppPerform local state change and encode the message
3. TransportLayerZero, DVNs, & ExecutorsBuild, verify, and route the packet to the destination chain
4. lzReceive(...)OAppValidate origin, decode message, apply state change

1. send(...) Entrypoint

  • Developer-defined logic
    1. Perform a local state change (e.g., burn or lock tokens, record intent).
    2. Encode all necessary data (addresses, amounts, or arbitrary instructions) into a byte array.
    3. Optionally accept execution options (gas limits, native gas transfers, or LayerZero Executor services).
  • Key points
    • Your public send(...) handles only local logic and message construction.
    • All packet assembly, peer lookup, and fee handling occur inside the internal call to endpoint.send(...).

2. Transport and Routing

  • Fee payment and validation
    1. Ensure the caller has supplied exactly the required native or ZRO fee.
    2. When endpoint.send(...) executes, the Endpoint verifies that the fees match the quote from the chosen messaging library. Underpayment causes a revert.
  • Packet construction and dispatch
    1. The Endpoint computes the next outbound nonce for (sender, dstEid, receiver) and builds a Packet struct with nonce, srcEid, sender, dstEid, receiver, GUID, and the raw message.
    2. It looks up which send library to use, either a per-OApp override or a default, for (sender, dstEid).
    3. The send library serializes the Packet into an encodedPacket and returns a MessagingFee struct.
    4. The Endpoint emits a PacketSent(...) event so DVNs and Executors know which packet to process.
  • DVNs & Executors
    • Paid DVNs pick up the packet, verify its integrity, and relay it to the destination chain’s Endpoint V2.
    • The destination library enforces DVN verification and block-confirmation requirements based on your receive config.
  • Destination Endpoint validation
    1. Verify that the packet’s srcEid has a registered peer.
    2. Confirm that origin.sender matches peers[srcEid].
  • Invoke lzReceive(...)
    • If validation succeeds, the destination Endpoint calls your OApp’s public lzReceive(origin, guid, message, executor, extraData).

3. lzReceive(...) Entrypoint

  • Access control and peer check
    • Only the Endpoint may call lzReceive.
    • Immediately validate that _origin.sender == peers[_origin.srcEid].
  • Internal _lzReceive(...) logic
    1. Decode the byte array into original data types (addresses, amounts, or instructions).
    2. Execute the intended on-chain business logic (e.g., mint tokens, unlock collateral, update balances).
    3. If there’s a composable hook, your OApp can invoke sendCompose(...) to bundle further cross-chain calls.
  • Outcome
    • Upon completion, the destination chain’s state reflects the source chain’s intent. Any post-processing (events, composable calls) occurs here.

This clear separation between local state updates in send(...) versus remote updates in _lzReceive(...) lets you focus on business logic while LayerZero’s Endpoint V2 manages transport intricacies.

Security and Channel Management

Whether you’re using Solidity, Rust, or Move, these foundational patterns ensure consistent security, extensibility, and developer ergonomics.

Security and roles

  • Owner
    • Manages delegates, peers, and enforced gas settings
    • setPeer(...): update trust mappings
    • setDelegate(...): assign a new delegate for Endpoint configurations
    • setEnforcedOptions(...): define per-chain minimum gas for inbound execution
  • Delegate
    • Manages Endpoint settings and message-channel controls
    • setSendLibrary(oappAddress, eid, newLibrary): override send library for (oappAddress, eid).
    • setReceiveLibrary(oappAddress, eid, newLibrary, gracePeriod): override receive library; gracePeriod lets the previous library handle retries.
    • setReceiveLibraryTimeout(oappAddress, eid, library, newTimeout): update how long an old receive library remains valid.
    • setConfig(oappAddress, libraryAddress, params[]): adjust per-library settings (DVNs, Executors, confirmations).
    • skip(oappAddress, srcEid, srcSender, nonce): advance the inbound nonce without processing when verification fails.
    • nilify(oappAddress, srcEid, srcSender, nonce, payloadHash): treat the payload as empty and advance the inbound nonce.
    • burn(oappAddress, srcEid, srcSender, nonce, payloadHash): permanently discard a malicious or irrecoverable payload.
      tip

      Use multisigs or your preferred governance to manage Owner and Delegate roles.

Peering and Trust Management

  • peers mapping
    • Store a mapping from eid → bytes32 peerAddress. Using bytes32 lets you store addresses for various chains.
    • setPeer(eid, peerAddress) updates that mapping. Passing bytes32(0) disables the pathway.
  • Directional trust
    • Registering on Chain A → Chain B does not register the reverse. Each side must call setPeer for the other.
    • On receipt, enforce peers[origin.srcEid] == origin.sender to confirm the message is from the expected contract.
  • Updating peers
    • If you redeploy or upgrade an OApp, call setPeer on both old and new deployments to maintain continuity.

Further Reading

  • Message Channel Security
    Deep dive into Endpoint V2's cryptographic guarantees, signature verification, and relayer/oracle incentives.
  • OFT Technical Reference
    A concrete OApp example for fungible token transfers, illustrating how to use OApp's core patterns without platform-specific code.
  • Omnichain Composability
    Patterns for building advanced cross-chain primitives (AMM routers, multi-chain staking, governance) on top of OApp's hooks and the Executor model.