> ## Documentation Index
> Fetch the complete documentation index at: https://docs.layerzero.network/llms.txt
> Use this file to discover all available pages before exploring further.

# LayerZero V2 Starknet Protocol Overview

> Deep technical dive into the LayerZero V2 protocol implementation on Starknet covering message lifecycle, DVN verification, and recovery operations.

This page provides a deep technical dive into the LayerZero V2 protocol implementation on Starknet, documenting the complete message lifecycle with contract-level code samples and function signatures.

**What you'll find**:

* Complete send workflow (quote, approve, send, endpoint processing)
* DVN verification process and verification status checks
* Executor delivery and OApp receive handling
* Recovery operations (skip, clear, nilify, burn)
* Security considerations for payload hashes and reentrancy

**Target audience**: Developers who understand Starknet basics and want to deeply understand the protocol implementation.

<Tip>
  **Prerequisites**

  Before reading this page, familiarize yourself with Starknet fundamentals in [Technical Overview](/v2/developers/starknet/technical-overview). For SDK usage and practical implementation, see [OApp](/v2/developers/starknet/oapp/overview) or [OFT](/v2/developers/starknet/oft/overview) guides.
</Tip>

***

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, verification checks, and execution readiness
* **Receive Workflow:** Executor delivery, payload clearing, and OApp processing

## Send Overview

When an OApp initiates a crosschain message, the following high-level steps occur on the source chain.

### Message Lifecycle Overview

The LayerZero protocol enables secure crosschain messaging through a three-phase process:

```mermaid theme={null}
sequenceDiagram
    participant User
    participant OApp_Src as OApp (Source)
    participant Endpoint_Src as Endpoint (Source)
    participant DVN
    participant ReceiveLib as Receive Library (ULN)
    participant Executor
    participant Endpoint_Dst as Endpoint (Dest)
    participant OApp_Dst as OApp (Dest)

    User->>OApp_Src: invoke send()
    OApp_Src->>Endpoint_Src: quote() + send()
    Endpoint_Src-->>DVN: PacketSent event
    DVN->>DVN: verify packet
    DVN->>ReceiveLib: submit verification
    ReceiveLib->>Endpoint_Dst: verify()
    Executor->>Endpoint_Dst: lz_receive()
    Endpoint_Dst->>OApp_Dst: lz_receive callback
```

### Core Data Structures

#### Packet

The `Packet` structure represents a crosschain message:

```rust wrap theme={null}
#[derive(Clone, Drop, Serde)]
pub struct Packet {
    pub nonce: u64,              // Sequential message number
    pub src_eid: u32,            // Source Endpoint ID
    pub sender: ContractAddress, // Source OApp address
    pub dst_eid: u32,            // Destination Endpoint ID
    pub receiver: Bytes32,       // Destination OApp (bytes32 for cross-VM compatibility)
    pub guid: Bytes32,           // Globally Unique Identifier
    pub message: ByteArray,      // Application payload
}
```

#### Origin

The `Origin` structure identifies the source of an incoming message:

```rust wrap theme={null}
#[derive(Clone, Drop, Serde, Debug, PartialEq, Default)]
pub struct Origin {
    pub src_eid: u32,     // Source chain Endpoint ID
    pub sender: Bytes32,  // Source OApp address (bytes32)
    pub nonce: u64,       // Message nonce for ordering
}
```

#### MessagingParams

Parameters for sending a message:

```rust wrap theme={null}
pub struct MessagingParams {
    pub dst_eid: u32,           // Destination Endpoint ID
    pub receiver: Bytes32,      // Recipient OApp address
    pub message: ByteArray,     // Application payload
    pub options: ByteArray,     // Execution options (gas, etc.)
    pub pay_in_lz_token: bool,  // Pay fees in ZRO token
}
```

#### MessagingFee

Fee structure returned by quote operations:

```rust wrap theme={null}
pub struct MessagingFee {
    pub native_fee: u256,    // Fee in native token (STRK/ETH)
    pub lz_token_fee: u256,  // Fee in ZRO token (if applicable)
}
```

### Send Workflow

When an OApp initiates a crosschain message, the following steps occur:

#### Step 1: Quote the Fee

Before sending, get a fee estimate:

```rust wrap theme={null}
// In your OApp or client code
fn quote_send(
    self: @ContractState,
    dst_eid: u32,
    message: ByteArray,
    options: ByteArray,
) -> MessagingFee {
    let params = MessagingParams {
        dst_eid,
        receiver: self.peers.read(dst_eid),
        message,
        options,
        pay_in_lz_token: false,
    };

    let endpoint = IEndpointV2Dispatcher {
        contract_address: self.endpoint.read()
    };

    endpoint.quote(params, get_contract_address())
}
```

#### Step 2: Approve Fees

The caller must approve the Endpoint to spend their tokens:

```rust wrap theme={null}
// Approve native token for fee payment
let native_token = IERC20Dispatcher { contract_address: native_token_address };
native_token.approve(endpoint_address, fee.native_fee);
```

#### Step 3: Send the Message

The OApp calls the Endpoint's `send` function:

```rust wrap theme={null}
fn send(
    ref self: ContractState,
    dst_eid: u32,
    message: ByteArray,
    options: ByteArray,
    fee: MessagingFee,
    refund_address: ContractAddress,
) -> MessageReceipt {
    let params = MessagingParams {
        dst_eid,
        receiver: self.peers.read(dst_eid),
        message,
        options,
        pay_in_lz_token: false,
    };

    let endpoint = IEndpointV2Dispatcher {
        contract_address: self.endpoint.read()
    };

    endpoint.send(params, refund_address)
}
```

#### Step 4: Endpoint Processing

The Endpoint performs the following:

1. **Creates the Packet** with a unique GUID and incremented nonce
2. **Looks up the Send Library** for this OApp/destination pair
3. **Routes to Message Library** (ULN302) for worker fee calculation
4. **Pays Workers** (DVNs, Executor) via ERC20 transfers
5. **Emits PacketSent event** with encoded packet and options

```rust wrap theme={null}
// Internal Endpoint logic (simplified)
fn send(ref self: ContractState, params: MessagingParams, refund_address: ContractAddress) -> MessageReceipt {
    let sender = get_caller_address();

    // Create packet with new nonce
    let nonce = self.outbound_nonce(sender, params.dst_eid, params.receiver) + 1;
    let packet = Packet {
        nonce,
        src_eid: self.eid.read(),
        sender,
        dst_eid: params.dst_eid,
        receiver: params.receiver,
        guid: GUID::generate(nonce, src_eid, sender, dst_eid, receiver),
        message: params.message,
    };

    // Send through message library and pay workers
    let result = message_lib.send(packet, params.options, params.pay_in_lz_token);
    self._pay_workers(sender, result.receipt, refund_address, params.pay_in_lz_token);

    // Emit event for off-chain listeners
    self.emit(PacketSent {
        encoded_packet: result.encoded_packet,
        options: params.options,
        send_library
    });

    result.message_receipt
}
```

#### Events Emitted During Send

| Event        | Description                                                |
| ------------ | ---------------------------------------------------------- |
| `PacketSent` | Contains encoded packet, options, and send library address |

***

## Verification Workflow

After a `PacketSent` event is emitted, DVNs verify the message and the destination Endpoint records verification data.

### DVN Verification Process

#### Step 1: DVN Monitors Source Chain

DVNs monitor the source chain for `PacketSent` events and extract the packet data.

#### Step 2: DVN Submits Verification

Once a DVN has verified the packet (e.g., confirmed finality), it submits the verification to the receive library. The receive library then calls `verify` on the destination Endpoint.

```rust wrap theme={null}
// Called by receive library on destination chain after DVN quorum
fn verify(
    ref self: ContractState,
    origin: Origin,
    receiver: ContractAddress,
    payload_hash: Bytes32,
) {
    // Verify caller is a valid receive library
    self._assert_only_receive_library(receiver, origin.src_eid);

    // Store the payload hash
    self.inbound_payload_hash.write(
        (receiver, origin.src_eid, origin.sender, origin.nonce),
        payload_hash
    );

    self.emit(PacketVerified { origin, receiver, payload_hash });
}
```

#### Step 3: Check Verification Status

The Executor (or anyone) can check if a message is ready for execution:

```rust wrap theme={null}
fn executable(self: @ContractState, origin: Origin, receiver: ContractAddress) -> ExecutionState {
    let payload_hash = self.inbound_payload_hash(receiver, origin.src_eid, origin.sender, origin.nonce);

    if payload_hash == EMPTY_PAYLOAD_HASH && nonce <= lazy_inbound_nonce {
        return ExecutionState::Executed;  // Already executed
    }

    if payload_hash != NIL_PAYLOAD_HASH && nonce <= inbound_nonce {
        return ExecutionState::Executable;  // Ready to execute
    }

    if payload_hash != EMPTY_PAYLOAD_HASH && payload_hash != NIL_PAYLOAD_HASH {
        return ExecutionState::VerifiedButNotExecutable;  // Verified but blocked
    }

    ExecutionState::NotExecutable
}
```

### Events Emitted During Verification

| Event            | Description                                 |
| ---------------- | ------------------------------------------- |
| `PacketVerified` | Contains origin, receiver, and payload hash |

***

## Receive Workflow

Once verified, the Executor delivers the message to the destination OApp.

### Executor Delivery

#### Step 1: Executor Calls lz\_receive

```rust wrap theme={null}
// Called by Executor on destination chain
fn lz_receive(
    ref self: ContractState,
    origin: Origin,
    receiver: ContractAddress,
    guid: Bytes32,
    message: ByteArray,
    extra_data: ByteArray,
    value: u256,  // Native token value to forward
) {
    // Clear payload hash first (prevents reentrancy)
    let payload = self._create_payload(guid, @message);
    self._clear_payload(receiver, @origin, @payload);

    // Transfer value to receiver (if any)
    if value > 0 {
        native_token.transfer_from(executor, receiver, value);
    }

    // Call receiver's lz_receive
    let receiver_dispatcher = ILayerZeroReceiverDispatcher { contract_address: receiver };
    receiver_dispatcher.lz_receive(origin, guid, message, executor, extra_data, value);

    self.emit(PacketDelivered { origin, receiver });
}
```

#### Step 2: OApp Handles the Message

Your OApp implements the `ILayerZeroReceiver` interface:

```rust wrap theme={null}
impl OAppHooks of OAppCoreComponent::OAppHooks<ContractState> {
    fn _lz_receive(
        ref self: OAppCoreComponent::ComponentState<ContractState>,
        origin: Origin,
        guid: Bytes32,
        message: ByteArray,
        executor: ContractAddress,
        extra_data: ByteArray,
        value: u256,
    ) {
        // Decode and process the message
        // The OApp has access to:
        // - origin.src_eid: source chain
        // - origin.sender: source OApp (bytes32)
        // - message: application payload
        // - value: native tokens forwarded

        // Your custom logic here
    }
}
```

### Events Emitted During Receive

| Event             | Description                              |
| ----------------- | ---------------------------------------- |
| `PacketDelivered` | Confirms successful delivery to receiver |
| `LzReceiveAlert`  | Emitted if lz\_receive execution fails   |

***

## Recovery Operations

LayerZero provides mechanisms for handling stuck or problematic messages:

### Skip

Skip an unverified message (before DVN verification):

```rust wrap theme={null}
fn skip(ref self: ContractState, oapp: ContractAddress, src_eid: u32, sender: Bytes32, nonce: u64) {
    // Only callable by OApp owner/delegate
    // Marks nonce as processed without verification
}
```

**Use Case**: Skip a message that will never be verified (e.g., source chain reorg).

### Clear

Clear a verified but unexecuted message:

```rust wrap theme={null}
fn clear(
    ref self: ContractState,
    origin: Origin,
    receiver: ContractAddress,
    guid: Bytes32,
    message: ByteArray,
) {
    self._assert_authorized(receiver);

    let payload = self._create_payload(guid, @message);
    self._clear_payload(receiver, @origin, @payload);

    self.emit(PacketDelivered { origin, receiver });
}
```

**Use Case**: Clear a message that's blocking subsequent messages due to ordering.

### Nilify

Reset a verification to allow re-verification:

```rust wrap theme={null}
fn nilify(ref self: ContractState, oapp: ContractAddress, src_eid: u32, sender: Bytes32, nonce: u64, payload_hash: Bytes32) {
    // Sets payload hash to NIL_PAYLOAD_HASH
    // Message must be re-verified before execution
}
```

**Use Case**: Dispute a verification or handle DVN misbehavior.

### Burn

Permanently block a message:

```rust wrap theme={null}
fn burn(ref self: ContractState, oapp: ContractAddress, src_eid: u32, sender: Bytes32, nonce: u64, payload_hash: Bytes32) {
    // Permanently marks message as non-executable
    // Cannot be reversed
}
```

**Use Case**: Permanently reject a malicious or invalid message.

| Operation | Reversible | When to Use                |
| --------- | ---------- | -------------------------- |
| `skip`    | Yes        | Message won't be verified  |
| `clear`   | No         | Unblock message ordering   |
| `nilify`  | Yes        | Dispute verification       |
| `burn`    | No         | Permanently reject message |

***

## Security Considerations

### Payload Hash Verification

The Endpoint stores only the hash of the payload, not the full message. This:

* Saves storage costs
* Prevents spam attacks
* Requires Executor to provide correct message data

### Reentrancy Protection

The Endpoint uses OpenZeppelin's `ReentrancyGuard` component:

```rust wrap theme={null}
fn send(ref self: ContractState, params: MessagingParams, refund_address: ContractAddress) -> MessageReceipt {
    self.reentrancy_guard.start();  // Lock
    // ... send logic ...
    self.reentrancy_guard.end();    // Unlock
    message_receipt
}
```

### Clear Before Execute

The `lz_receive` function clears the payload hash before calling the receiver's handler:

```rust wrap theme={null}
// Clear first (prevents reentrancy attacks)
self._clear_payload(receiver, @origin, @payload);

// Then execute (safe even if receiver calls back)
receiver_dispatcher.lz_receive(...);
```

This "clear-then-execute" pattern prevents reentrancy attacks where a malicious receiver could attempt to re-execute the same message.

***

## Next Steps

* [Technical Overview](/v2/developers/starknet/technical-overview) - Starknet architecture details
* [OApp Overview](/v2/developers/starknet/oapp/overview) - Building custom OApps
* [OFT Overview](/v2/developers/starknet/oft/overview) - Token transfer patterns
* [Configuration Guide](/v2/developers/starknet/configuration/dvn-executor-config) - DVN and Executor setup
