> ## 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.

# Debugging Messages

> Intervene on inbound LayerZero messages on Stellar using the Endpoint's skip, nilify, burn, and clear methods -- preconditions, authorization, and SDK examples.

LayerZero V2 processes messages in two distinct phases:

**`Verified`**: the destination chain has received verification from all configured [DVNs](../../../concepts/modular-security/security-stack-dvns) and the message nonce has been committed to the [Endpoint](../../../concepts/protocol/layerzero-endpoint)'s messaging channel.

**`Delivered`**: the message has been successfully executed by the [Executor](../../../concepts/permissionless-execution/executors).

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](../technical-overview#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](../../../concepts/troubleshooting/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](/v2/developers/stellar/oapp/overview#step-4-change-delegate-optional) 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.

```typescript wrap theme={null}
import { endpoint } from '@layerzerolabs/lz-v2-stellar-sdk';
import { Keypair, Networks } from '@stellar/stellar-sdk';
import { basicNodeSigner } from '@stellar/stellar-sdk/contract';

const keypair = Keypair.fromSecret('S...');

const endpointClient = new endpoint.Client({
  contractId: 'C...ENDPOINT_CONTRACT_ID',
  networkPassphrase: Networks.TESTNET,
  rpcUrl: 'https://soroban-testnet.stellar.org',
  publicKey: keypair.publicKey(),
  ...basicNodeSigner(keypair, Networks.TESTNET),
});
```

## 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.

```typescript wrap theme={null}
const tx = await endpointClient.skip({
  caller: keypair.publicKey(),   // registered delegate keypair (signs the call)
  receiver: 'C...RECEIVER_OAPP',  // the receiving OApp on Stellar
  src_eid: 40161,                 // source endpoint ID
  sender: senderBytes32,          // 32-byte source sender (Buffer)
  nonce: 3n,                      // must equal inbound_nonce + 1
});
await tx.signAndSend();
```

<Warning>
  A skipped nonce counts as verified and can never be delivered. Once skipped, the payload cannot be recovered.
</Warning>

## 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`.

```typescript wrap theme={null}
// Read the current on-chain hash (the current SDK returns null if nothing is stored yet)
const { result: currentPayloadHash } = await endpointClient.inbound_payload_hash({
  receiver: 'C...RECEIVER_OAPP',
  src_eid: 40161,
  sender: senderBytes32,
  nonce: 3n,
});

const tx = await endpointClient.nilify({
  caller: keypair.publicKey(),
  receiver: 'C...RECEIVER_OAPP',
  src_eid: 40161,
  sender: senderBytes32,
  nonce: 3n,
  payload_hash: currentPayloadHash, // must match exactly; null nilifies a future nonce
});
await tx.signAndSend();
```

## 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.

```typescript wrap theme={null}
const { result: currentPayloadHash } = await endpointClient.inbound_payload_hash({
  receiver: 'C...RECEIVER_OAPP',
  src_eid: 40161,
  sender: senderBytes32,
  nonce: 2n,
});

if (currentPayloadHash == null) {
  throw new Error('No payload hash is stored for this nonce');
}

const tx = await endpointClient.burn({
  caller: keypair.publicKey(),
  receiver: 'C...RECEIVER_OAPP',
  src_eid: 40161,
  sender: senderBytes32,
  nonce: 2n,                        // must be <= inbound_nonce
  payload_hash: currentPayloadHash, // narrowed to Buffer; must match the stored hash exactly
});
await tx.signAndSend();
```

<Warning>
  Burning a nonce is irreversible. The message can never be recovered, verified, or executed.
</Warning>

## 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`.

```typescript wrap theme={null}
const tx = await endpointClient.clear({
  caller: keypair.publicKey(),
  origin: {
    nonce: 3n,
    sender: senderBytes32,  // 32-byte source sender (Buffer)
    src_eid: 40161,
  },
  receiver: 'C...RECEIVER_OAPP',
  guid: guidBytes32,          // 32-byte message GUID (Buffer)
  message: messageBytes,      // the exact message payload (Buffer)
});
await tx.signAndSend();
```
