- 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)
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):- OApp Initiates Send: User calls the OApp module’s
send()function, which creates aCall<SendParam, MessagingReceipt>object - Endpoint Processes: The Endpoint increments the outbound nonce, constructs a packet with GUID, and routes to the send library
- ULN302 Assigns Jobs: The message library creates child
Callobjects for the executor and each DVN - Workers Calculate Fees: Executor and DVNs estimate their fees and return
FeeRecipientresults - Confirmation Chain: Results flow back through confirm functions, aggregating fees and emitting events
- OApp Finalizes: The OApp confirms the send call to extract the
MessagingReceipt
Endpoint Send
TheEndpointV2 module orchestrates message sending through its shared object.
EndpointV2 Shared Object
MessagingChannel per OApp
Each OApp gets a dedicatedMessagingChannel shared object for parallel execution:
Step 1: OApp Creates Send Call
The OApp module creates aCall object targeting the Endpoint:
- Returns a
Callobject (hot potato—must be consumed) - The
Callhas nodroporstoreabilities - PTB must route this
Callto the Endpoint module oapp_capvalidates ownership via ID comparison
Step 2: Endpoint Processes Send
The Endpoint receives theCall, manages state, and delegates to the send library:
messaging_channel.send():
Step 3: ULN302 Assigns Jobs to Workers
The ULN302 message library creates childCall objects for the executor and DVNs:
send_uln::send():
Step 4: Workers Process Job Assignments
Executor Assignment:Step 5: ULN302 Confirms Send
The ULN302 destroys workerCall objects and aggregates fees:
send_uln::confirm_send():
Step 6: Endpoint Finalizes Send
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 transactionHXZqH1RdANEkstz3MTFGMuLQ74CfgAkwQCq1YW8TMHHH:
Send Limitations
Max Message Size
ThemaxMessageSize is configured per executor and OApp:
Fee Payment Model
Unlike EVM’s direct fee transfer, Sui usesCoin object splitting:
Verification Workflow
After thePacketSentEvent 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 forPacketSentEvent and wait for the configured number of block confirmations (finality).
Step 2: DVN Submits Verification
Each DVN calls theverify() function on the ULN302:
receive_uln::verify():
Commit Verification
After sufficient DVNs have verified (meeting the X of Y of N threshold), anyone can callcommit_verification():
receive_uln::verify_and_reclaim_storage():
Endpoint Verify
The Endpoint inserts the verified payload hash into the messaging channel:messaging_channel::verify():
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 transaction9fqmkJYFQyQs6u1vVmMSuqhZyobpSW7P4i7MaNVzbSFg, the PTB contains:
Step 3: Executor Calls execute_lz_receive
Step 4: Endpoint Creates lz_receive Call
messaging_channel::clear():
- Lazy nonce validation: Ensures all prior messages have been verified
- Payload hash verification: Confirms executor provided the correct message
- Storage cleanup: Removes hash to prevent double execution
- Event emission: Signals successful delivery
Step 5: OApp Processes Message
The OApp’slz_receive() function is invoked via the Call object:
-
- Validate
Callcame from authorized Endpoint
- Validate
-
- Validate sender matches configured peer
-
- Process message and update state
-
- No need to call
clear()(done by Endpoint before Call creation)
- No need to call
Example PTB for Receive (from actual transaction)
Based on transaction9fqmkJYFQyQs6u1vVmMSuqhZyobpSW7P4i7MaNVzbSFg:
Key Sui-Specific Patterns
Object Ownership in Message Flow
| Object Type | Ownership | Access Pattern | Example |
|---|---|---|---|
EndpointV2 | Shared | Anyone reads, admin writes | &EndpointV2 or &mut EndpointV2 |
MessagingChannel | Shared | Anyone reads, owner writes | &mut MessagingChannel |
OApp | Shared | Anyone reads, admin writes | &mut OApp |
CallCap | Owned | Must own to use | Owned by OApp module or user |
AdminCap | Owned | Must own to use | Owned by admin address |
Call<P, R> | Neither | Must be consumed in PTB | Created and destroyed in same TX |
Call Pattern vs EVM/Solana
| Aspect | EVM | Solana | Sui |
|---|---|---|---|
| Cross-Contract Calls | delegatecall | CPI (Cross-Program Invocation) | Call<Param, Result> objects |
| Authorization | msg.sender | Signer checks + PDAs | CallCap validation |
| Return Values | Function returns | CPI returns | Call.complete() sets result |
| Call Hierarchy | Call stack (implicit) | CPI depth limit (4) | Call parent/child relationships |
| Atomicity | Transaction revert | Transaction revert | PTB revert |
Nonce Management
EVM:Fee Payment Model
EVM:Configuration Management
Send Library Configuration
OApps can set custom send libraries per destination:DVN Configuration
OApps configure DVN sets through the ULN:Recovery Operations
The Endpoint provides several recovery mechanisms for stuck or problematic messages.Skip
Increments the lazy nonce without executing the message:messaging_channel::skip():
Nilify
Removes verification but keeps nonce ordering:Burn
Permanently blocks a nonce (irreversible):Comparison with EVM and Solana
Architecture Comparison
| Component | EVM | Solana | Sui |
|---|---|---|---|
| Code Organization | Solidity contracts | Rust programs | Move packages/modules |
| State Storage | Contract storage slots | PDA accounts | Shared/owned objects |
| Nonce Management | Nested mappings | PDA per pathway | Table in MessagingChannel |
| Message Channel | Contract storage | PDA accounts | MessagingChannel shared object |
| Call Pattern | delegatecall | CPI | Call<Param, Result> objects |
| Authorization | msg.sender | Signers + PDA derivation | CallCap validation |
| Fee Payment | msg.value transfer | Token account ops | Coin object splitting |
| Atomicity | Transaction revert | Transaction revert | PTB revert |
Send Flow Comparison
| Step | EVM | Solana | Sui |
|---|---|---|---|
| Initiate | OApp.send() internal | OApp CPI to Endpoint | OApp creates Call object |
| Nonce | Mapping increment | PDA account write | Table field increment |
| Library Call | Direct function call | CPI to SendUln302 | Child Call creation |
| Worker Calls | Direct function calls | CPI to each worker | Child Call per worker |
| Fee Collection | Transfer to library | Record in library | Coin splitting |
| Event | emit PacketSent | emit_cpi!(PacketSent) | event::emit(PacketSent) |
Receive Flow Comparison
| Step | EVM | Solana | Sui |
|---|---|---|---|
| Entry Point | Executor calls Endpoint.lzReceive | Executor invokes with all accounts | Executor calls execute_lz_receive |
| Clear Payload | Endpoint clears before calling OApp | OApp CPIs back to Endpoint.clear | Endpoint clears before creating Call |
| OApp Invocation | delegatecall to OApp.lzReceive | Instruction with account list | Call object to OApp module |
| Validation | Modifier checks | Account constraints + CPI auth | Call validation + peer check |
| Processing | Override _lzReceive | Implement lz_receive instruction | Implement lz_receive function |
Event Monitoring
Events Emitted During Send
- ExecutorFeePaidEvent (from ULN):
- DVNFeePaidEvent (from ULN):
- PacketSentEvent (from MessagingChannel):
- OFTSentEvent (from OFT, if applicable):
Events Emitted During Verification
- PayloadVerifiedEvent (per DVN):
- PacketVerifiedEvent (after commit):
Events Emitted During Receive
- PacketDeliveredEvent:
- OFTReceivedEvent (from OFT, if applicable):
Capabilities and Authorization
CallCap Pattern
CallCap is Sui’s capability-based authorization for creating Call objects:
AdminCap Pattern
AdminCap authorizes administrative operations:
PTB Construction Patterns
Simple Send PTB
Receive PTB
Sui-Specific Considerations
Object Abilities
Sui’s ability system controls what can be done with types:| Ability | Meaning | LayerZero Usage |
|---|---|---|
key | Can be stored at top-level | OApp, EndpointV2, AdminCap |
store | Can be stored in other structs | Peer, Channel, UlnConfig |
copy | Can be copied | ChannelKey, MessagingFee |
drop | Can be ignored/discarded | One-time witnesses, config structs |
Call objects must be handled.
Phantom Type Parameters
Sui uses phantom type parameters for type safety without storage:phantom keyword means T is for type safety only—not stored directly.
Table vs Vector
Sui usesTable<K, V> for dynamic key-value storage:
Table: Dynamic size, gas per access, better for sparse datavector: Fixed size, cheaper access, better for dense data
Summary
LayerZero on Sui achieves crosschain messaging through:- Object-Based State: Shared objects (
EndpointV2,MessagingChannel,OApp) enable parallel execution - Capability Authorization:
CallCapandAdminCapreplacemsg.senderchecks - Call Pattern:
Call<Param, Result>objects enable dynamic routing withoutdelegatecall - PTB Composition: Atomic multi-step workflows ensure message integrity
- Type Safety: Move’s ability system and phantom types provide compile-time guarantees
- 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)
- OApp Implementation - Build custom crosschain applications
- OFT Implementation - Deploy crosschain tokens
- OFT SDK - Complete SDK methods and patterns
- Configuration Guide - DVN, executor, and gas configuration
- Technical Overview - Sui fundamentals and architecture