LayerZero V2 processes messages in two distinct phases:
Verified: the destination chain has received verification from all configured DVNs and the message nonce has been committed to the Endpoint’s messaging channel.
Delivered: the message has been successfully executed by the Executor.
Unlike EVM, where the Endpoint pushes a verified message to the OApp, Stellar uses pull mode: the Executor calls OApp.lz_receive() directly, and the OApp calls endpoint.clear() internally to verify and clear the payload. See Reentrancy Prohibition for why Soroban requires this model.
Because verification and execution are independent, the receiving OApp or its delegate can intervene when an inbound message is stuck, has been verified but not executed, or must be abandoned. The Endpoint provides four methods for handling these cases:
skip — skip the next expected inbound nonce without verifying it.
nilify — set an existing or future nonce’s payload hash to NIL, blocking execution while still allowing recovery.
burn — permanently mark a nonce as unexecutable and un-verifiable.
clear — clear a verified-but-unexecuted message’s payload without executing it.
For the general debugging workflow, see Debugging Messages.
Authorization
All four methods run the same on-chain authorization check (require_oapp_auth): the caller must be the receiver OApp itself or the OApp’s registered delegate, and it must authorize the call.
When you call these methods off-chain with the SDK (as shown below), the signer is a keypair, so caller must be a registered delegate — a keypair-backed account that can sign the transaction. The OApp-as-caller path only applies when the OApp contract itself invokes the Endpoint on-chain; it does not apply to a plain SDK call, because a contract address cannot sign an off-chain transaction. See Change Delegate to register a delegate.
Setup
Every example below reuses a single endpointClient. Because these are off-chain SDK calls, the signer (caller) must be a keypair registered as the OApp’s delegate.
skip
endpointClient.skip({ caller, receiver, src_eid, sender, nonce })
When to use: Skip the next expected inbound nonce without verifying it, to unblock subsequent messages when a message is stuck, invalid, or must be abandoned.
Preconditions:
nonce must equal inbound_nonce + 1 (the next expected nonce); otherwise the call reverts with InvalidNonce.
caller must be the receiver OApp or its delegate.
A skipped nonce counts as verified and can never be delivered. Once skipped, the payload cannot be recovered.
nilify
endpointClient.nilify({ caller, receiver, src_eid, sender, nonce, payload_hash })
When to use: Set an inbound nonce’s payload hash to NIL, which blocks execution while still allowing recovery — the packet can be re-verified through the MessageLib. It is a precautionary measure against a malicious DVN. There are two cases:
- Existing hash: for a nonce whose payload was verified but not executed, pass the current on-chain payload hash.
- Future nonce: for a
nonce greater than inbound_nonce that has no stored hash yet, read the current value with inbound_payload_hash and pass the returned null value unchanged to nilify it pre-emptively.
Preconditions:
payload_hash represents an Option<Buffer> and must exactly equal the payload hash currently stored on-chain for that nonce; otherwise the call reverts with PayloadHashNotFound. Although generated TypeScript declarations may model the empty option as undefined, the current Stellar SDK decodes the on-chain empty option as null. Read the value with inbound_payload_hash and pass it through unchanged.
nonce > inbound_nonce, or the current payload hash exists; otherwise the call reverts with InvalidNonce. A future nonce must also be no greater than inbound_nonce + 256, the maximum pending-nonce window.
caller must be the receiver OApp or its delegate.
- Effect: the payload hash for that nonce is set to NIL (
0xff...ff); the packet cannot execute until it is re-verified. If nonce > inbound_nonce, the nonce is added to the pending list and consecutive nonces may advance inbound_nonce.
burn
endpointClient.burn({ caller, receiver, src_eid, sender, nonce, payload_hash })
When to use: Permanently mark a verified nonce as unexecutable and un-verifiable — it can never be verified or executed again. Unlike clear, burn needs only the nonce’s on-chain payload hash, not the full message, so it can eject a nonce even when the original message is unavailable (for example, withheld by a malicious DVN). It still requires that a matching payload hash is currently stored for that nonce.
Preconditions:
- A payload hash must currently be stored for that nonce, and your
payload_hash (a required Buffer) must exactly equal it; otherwise the call reverts with PayloadHashNotFound.
nonce <= inbound_nonce; otherwise the call reverts with InvalidNonce.
caller must be the receiver OApp or its delegate.
- Effect: the stored payload hash is removed permanently. Because the nonce is at or below
inbound_nonce and no longer has a stored hash, it can never be re-verified.
Burning a nonce is irreversible. The message can never be recovered, verified, or executed.
clear
endpointClient.clear({ caller, origin, receiver, guid, message })
When to use: PULL-mode manual acknowledgement — settle an already-verified message from the Endpoint without push execution. Use it to eject a message that cannot or should not be executed. This is the Stellar equivalent of the EVM clear.
Preconditions:
- Provide the full
origin ({ nonce, sender, src_eid }), the guid, and the exact message. The Endpoint rebuilds the payload from guid + message and checks it against what was verified; a mismatch reverts with PayloadHashNotFound.
nonce <= inbound_nonce; otherwise the call reverts with InvalidNonce.
caller must be the receiver OApp or its delegate.
- Effect: removes the stored payload hash for that nonce and emits
PacketDelivered. It does not advance inbound_nonce — on Stellar the inbound nonce is advanced only by verification, skip, or nilify.