Skip to main content
This page provides a deep technical dive into the LayerZero V2 protocol implementation on IOTA, documenting the complete message lifecycle with actual contract code, function signatures, and transaction analysis. What you’ll find:
  • Complete send workflow (7 steps from OApp to MessagingReceipt)
  • DVN verification and commit process with storage management
  • Executor delivery and OApp receive handling
  • Real transaction analysis from mainnet
  • Event emissions and monitoring
  • Recovery operations (skip, clear, nilify, burn)
Target audience: Developers who understand IOTA basics and want to deeply understand the protocol implementation.

Prerequisites

Before reading this page, familiarize yourself with IOTA fundamentals in Technical Overview. For SDK usage and practical implementation, see OFT SDK or implementation guides for OApp and OFT.

This page documents the complete message lifecycle with contract-level implementation details:
  • Send Workflow: Message initiation, fee calculation, nonce management, and packet dispatch
  • Verification Workflow: DVN submission, threshold checking, and verification commitment
  • Receive Workflow: Executor delivery, payload clearing, and OApp processing

Send Overview

When a user sends a crosschain message, the following high-level steps occur within a single Programmable Transaction Block (PTB):
  1. OApp Initiates Send: User calls the OApp module’s send() function, which creates a Call<SendParam, MessagingReceipt> object
  2. Endpoint Processes: The Endpoint increments the outbound nonce, constructs a packet with GUID, and routes to the send library
  3. ULN302 Assigns Jobs: The message library creates child Call objects for the executor and each DVN
  4. Workers Calculate Fees: Executor and DVNs estimate their fees and return FeeRecipient results
  5. Confirmation Chain: Results flow back through confirm functions, aggregating fees and emitting events
  6. OApp Finalizes: The OApp confirms the send call to extract the MessagingReceipt

Endpoint Send

The EndpointV2 module orchestrates message sending through its shared object.

EndpointV2 Shared Object

MessagingChannel per OApp

Each OApp gets a dedicated MessagingChannel shared object for parallel execution:

Step 1: OApp Creates Send Call

The OApp module creates a Call object targeting the Endpoint:
Key Points:
  • Returns a Call object (hot potato—must be consumed)
  • The Call has no drop or store abilities
  • PTB must route this Call to the Endpoint module
  • oapp_cap validates ownership via ID comparison

Step 2: Endpoint Processes Send

The Endpoint receives the Call, manages state, and delegates to the send library:
Inside messaging_channel.send():
GUID Generation: Uses keccak256 hashing with BCS-encoded parameters:

Step 3: ULN302 Assigns Jobs to Workers

The ULN302 message library creates child Call objects for the executor and DVNs:
Inside send_uln::send():

Step 4: Workers Process Job Assignments

Executor Assignment:
DVN Assignment (similar pattern):

Step 5: ULN302 Confirms Send

The ULN302 destroys worker Call objects and aggregates fees:
Inside send_uln::confirm_send():

Step 6: Endpoint Finalizes Send

Inside messaging_channel.confirm_send():

Step 7: OApp Extracts Receipt

The OApp confirms the send call to extract the receipt:

Example PTB for Send (from actual transaction)

Based on transaction HXZqH1RdANEkstz3MTFGMuLQ74CfgAkwQCq1YW8TMHHH:

Send Limitations

Max Message Size

The maxMessageSize is configured per executor and OApp:
Default is typically 10,000 bytes, but OApps can configure custom limits.

Fee Payment Model

Unlike EVM’s direct fee transfer, IOTA uses Coin object splitting:

Verification Workflow

After the PacketSentEvent is emitted on the source chain, DVNs independently verify the message on the destination chain.

DVN Verification Process

Step 1: DVN Monitors Source Chain

DVNs watch for PacketSentEvent and wait for the configured number of block confirmations (finality).

Step 2: DVN Submits Verification

Each DVN calls the verify() function on the ULN302:
Inside receive_uln::verify():
Verification Storage:

Commit Verification

After sufficient DVNs have verified (meeting the X of Y of N threshold), anyone can call commit_verification():
Inside receive_uln::verify_and_reclaim_storage():

Endpoint Verify

The Endpoint inserts the verified payload hash into the messaging channel:
Inside messaging_channel::verify():
Message State Transition:

Receive Workflow

After verification is committed, the Executor can deliver the message to the destination OApp.

Executor Delivery

The Executor initiates message delivery by constructing a PTB with all required objects.

Step 1: Executor Queries OApp Metadata

The Executor queries the OApp’s execution metadata to determine which objects are needed:

Step 2: Executor Creates PTB

Based on the transaction 9fqmkJYFQyQs6u1vVmMSuqhZyobpSW7P4i7MaNVzbSFg, the PTB contains:

Step 3: Executor Calls execute_lz_receive

Step 4: Endpoint Creates lz_receive Call

Inside messaging_channel::clear():
Key Security Features:
  1. Lazy nonce validation: Ensures all prior messages have been verified
  2. Payload hash verification: Confirms executor provided the correct message
  3. Storage cleanup: Removes hash to prevent double execution
  4. Event emission: Signals successful delivery

Step 5: OApp Processes Message

The OApp’s lz_receive() function is invoked via the Call object:
OApp Responsibilities:
    • Validate Call came from authorized Endpoint
    • Validate sender matches configured peer
    • Process message and update state
    • No need to call clear() (done by Endpoint before Call creation)

Example PTB for Receive (from actual transaction)

Based on transaction 9fqmkJYFQyQs6u1vVmMSuqhZyobpSW7P4i7MaNVzbSFg:

Key IOTA-Specific Patterns

Object Ownership in Message Flow

Call Pattern vs EVM/Solana

Nonce Management

EVM:
Solana:
IOTA:

Fee Payment Model

EVM:
Solana:
IOTA:

Configuration Management

Send Library Configuration

OApps can set custom send libraries per destination:
Default Fallback:

DVN Configuration

OApps configure DVN sets through the ULN:
Setting Configuration (via Endpoint):

Recovery Operations

The Endpoint provides several recovery mechanisms for stuck or problematic messages.

Skip

Increments the lazy nonce without executing the message:
Inside messaging_channel::skip():

Nilify

Removes verification but keeps nonce ordering:

Burn

Permanently blocks a nonce (irreversible):

Comparison with EVM and Solana

Architecture Comparison

Send Flow Comparison

Receive Flow Comparison


Event Monitoring

Events Emitted During Send

  1. ExecutorFeePaidEvent (from ULN):
  1. DVNFeePaidEvent (from ULN):
  1. PacketSentEvent (from MessagingChannel):
  1. OFTSentEvent (from OFT, if applicable):

Events Emitted During Verification

  1. PayloadVerifiedEvent (per DVN):
  1. PacketVerifiedEvent (after commit):

Events Emitted During Receive

  1. PacketDeliveredEvent:
  1. OFTReceivedEvent (from OFT, if applicable):

Capabilities and Authorization

CallCap Pattern

CallCap is IOTA’s capability-based authorization for creating Call objects:
Usage in Validation:

AdminCap Pattern

AdminCap authorizes administrative operations:
Ownership Transfer:

PTB Construction Patterns

Simple Send PTB

Receive PTB


IOTA-Specific Considerations

Object Abilities

IOTA’s ability system controls what can be done with types: Call Object Abilities:
This enforces the hot potato pattern—Call objects must be handled.

Phantom Type Parameters

IOTA uses phantom type parameters for type safety without storage:
The phantom keyword means T is for type safety only—not stored directly.

Table vs Vector

IOTA uses Table<K, V> for dynamic key-value storage:
Trade-offs:
  • Table: Dynamic size, gas per access, better for sparse data
  • vector: Fixed size, cheaper access, better for dense data

Summary

LayerZero on IOTA achieves crosschain messaging through:
  1. Object-Based State: Shared objects (EndpointV2, MessagingChannel, OApp) enable parallel execution
  2. Capability Authorization: CallCap and AdminCap replace msg.sender checks
  3. Call Pattern: Call<Param, Result> objects enable dynamic routing without delegatecall
  4. PTB Composition: Atomic multi-step workflows ensure message integrity
  5. Type Safety: Move’s ability system and phantom types provide compile-time guarantees
Key Differences from Other VMs:
  • No inheritance (explicit capability validation)
  • No dynamic dispatch (Call pattern workaround)
  • Object ownership model (shared vs owned vs immutable)
  • Coin object model (split/merge instead of balance transfer)
  • Table-based storage (not mappings or PDAs)
For implementation guides and code examples, see: