@layerzerolabs/protocol-stellar-v2 0.2.115 → 0.2.117

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -277,6 +277,113 @@ pub struct MyCustomOApp;
277
277
  // Implement each trait manually...
278
278
  ```
279
279
 
280
+ ## Debugging and recovering messages
281
+
282
+ An inbound path is tracked by `(receiver, src_eid, sender)` and guarded by an Endpoint nonce.
283
+ A message can only be delivered — via `lz_receive` (on the OApp) or `clear` (on the Endpoint) — once its nonce is at or below
284
+ `inbound_nonce`, and `inbound_nonce` only advances across the gapless prefix of verified, skipped,
285
+ or nilified nonces. Two very different situations are worth separating:
286
+
287
+ - **A verification gap truly stalls the channel.** If a lower nonce is never verified, `inbound_nonce`
288
+ stops before it, so every nonce at or above the gap becomes undeliverable until the gap is resolved
289
+ (for example, verified or skipped).
290
+ - **A reverting `lz_receive` does not, by itself, block later nonces.** The payload hash stays stored,
291
+ so that nonce can be retried or cleared. Whether a stuck nonce holds up higher ones depends on
292
+ whether the OApp enforces ordered delivery via `next_nonce` (the default returns `0`, i.e. unordered).
293
+
294
+ The Endpoint exposes four channel-management operations for an OApp to recover from these situations.
295
+ They are the on-chain equivalents of the skip / clear / nilify / burn flow described for other VMs.
296
+
297
+ ### Authorization
298
+
299
+ All four operations are gated by `require_oapp_auth`: the `caller` must be **the OApp contract
300
+ itself or its registered delegate**, and `caller.require_auth()` is enforced. The `receiver`
301
+ argument is always the OApp address. Two calling patterns are available:
302
+
303
+ - **Delegate calls the endpoint directly** — the delegate set via `set_delegate` (typically the
304
+ owner or an admin) invokes the endpoint, passing itself as `caller` and the OApp as `receiver`.
305
+ - **OApp wraps the call** — add an admin-gated method on the OApp that reaches the endpoint through
306
+ the shared client and passes the OApp as both `caller` and `receiver`:
307
+
308
+ ```rust
309
+ use oapp::oapp_core::endpoint_client;
310
+
311
+ #[contract_impl]
312
+ impl MyOApp {
313
+ #[only_role(operator, MESSAGE_ADMIN_ROLE)]
314
+ pub fn skip_message(env: &Env, src_eid: u32, sender: &BytesN<32>, nonce: u64, operator: &Address) {
315
+ let oapp = env.current_contract_address();
316
+ endpoint_client::<Self>(env).skip(&oapp, &oapp, &src_eid, sender, &nonce);
317
+ }
318
+ }
319
+ ```
320
+
321
+ `endpoint_client::<Self>(env)` returns the endpoint client, which exposes `skip`, `clear`, `nilify`,
322
+ and `burn` alongside a read-only `inbound_payload_hash` for inspecting stored payloads.
323
+
324
+ ### Skip
325
+
326
+ Skips the next expected inbound nonce **before it is verified**. Use this to bypass a message you
327
+ never want delivered (e.g. flagged by a precrime alert) so the channel keeps advancing.
328
+
329
+ ```rust
330
+ endpoint.skip(&caller, &receiver, src_eid, &sender, nonce);
331
+ ```
332
+
333
+ - `nonce` **must** be the next expected nonce (`inbound_nonce + 1`), otherwise the call fails with
334
+ `EndpointError::InvalidNonce`.
335
+ - The skipped nonce counts as "verified" for ordering purposes, so subsequent nonces can proceed.
336
+ - Emits `InboundNonceSkipped`.
337
+
338
+ ### Clear
339
+
340
+ Clears a **verified** message from the channel without running your `__lz_receive` logic. This is the
341
+ PULL-mode counterpart to `lz_receive`: use it to accept-and-drop a payload that can never execute
342
+ successfully but that you want to move past.
343
+
344
+ ```rust
345
+ endpoint.clear(&caller, &origin, &receiver, &guid, &message);
346
+ ```
347
+
348
+ - Requires the reconstructed payload (`guid` + `message`) to match the stored payload hash for the
349
+ nonce carried in `origin`; a missing or mismatched hash fails with
350
+ `EndpointError::PayloadHashNotFound`.
351
+ - The nonce must be at or below `inbound_nonce`, otherwise the call fails with
352
+ `EndpointError::InvalidNonce`.
353
+ - Removes the stored payload hash and emits `PacketDelivered`, but does **not** advance
354
+ `inbound_nonce`; nonce advancement happens during `verify`, `skip`, or `nilify`.
355
+
356
+ ### Nilify
357
+
358
+ Marks a message as nil so it **cannot execute until it is re-verified**. Unlike `burn`, the message
359
+ can be verified again later, making this the recoverable option for temporarily blocking a nonce.
360
+
361
+ ```rust
362
+ endpoint.nilify(&caller, &receiver, src_eid, &sender, nonce, &payload_hash);
363
+ ```
364
+
365
+ - `payload_hash` must match the currently stored hash; a mismatch fails with
366
+ `EndpointError::PayloadHashNotFound`.
367
+ - Pass `None` only when no hash is stored yet, and only for a future nonce inside the pending window
368
+ (`inbound_nonce < nonce <= inbound_nonce + 256`).
369
+ - Sets the stored hash to the NIL sentinel; a fresh `verify` can restore it.
370
+ - Emits `PacketNilified`.
371
+
372
+ ### Burn
373
+
374
+ Permanently marks a nonce as **unexecutable and un-verifiable** — it can never be re-verified or
375
+ executed. Use this as the terminal action for a message you have decided to discard for good.
376
+
377
+ ```rust
378
+ endpoint.burn(&caller, &receiver, src_eid, &sender, nonce, &payload_hash);
379
+ ```
380
+
381
+ - There must be a matching stored payload hash — possibly the NIL sentinel left by `nilify` — at the
382
+ target nonce. A missing or mismatched hash fails with `EndpointError::PayloadHashNotFound`.
383
+ - `nonce` must be at or below `inbound_nonce`; otherwise the call fails with
384
+ `EndpointError::InvalidNonce`.
385
+ - Removes the payload hash from storage and emits `PacketBurnt`.
386
+
280
387
  ## Example: Counter OApp
281
388
 
282
389
  See `contracts/oapps/counter/` for a complete example demonstrating:
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@layerzerolabs/protocol-stellar-v2",
3
- "version": "0.2.115",
3
+ "version": "0.2.117",
4
4
  "private": false,
5
5
  "license": "LZBL-1.3",
6
6
  "devDependencies": {
7
7
  "@types/node": "^22.18.6",
8
8
  "tsx": "^4.21.0",
9
9
  "typescript": "^5.8.2",
10
- "@layerzerolabs/vm-tooling-stellar": "0.2.115",
11
- "@layerzerolabs/common-node-utils": "0.2.115",
12
- "@layerzerolabs/stellar-ts-bindings-gen": "0.2.115"
10
+ "@layerzerolabs/common-node-utils": "0.2.117",
11
+ "@layerzerolabs/stellar-ts-bindings-gen": "0.2.117",
12
+ "@layerzerolabs/vm-tooling-stellar": "0.2.117"
13
13
  },
14
14
  "publishConfig": {
15
15
  "access": "public",
package/sdk/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layerzerolabs/lz-v2-stellar-sdk",
3
- "version": "0.2.115",
3
+ "version": "0.2.117",
4
4
  "private": false,
5
5
  "description": "TypeScript SDK for endpoint-v2 Stellar contract",
6
6
  "repository": {