Skip to content

Comments

chore: add example for wrapped invoices#529

Merged
rolznz merged 2 commits intomasterfrom
chore/wrapped-invoice-example
Feb 13, 2026
Merged

chore: add example for wrapped invoices#529
rolznz merged 2 commits intomasterfrom
chore/wrapped-invoice-example

Conversation

@rolznz
Copy link
Contributor

@rolznz rolznz commented Feb 13, 2026

Summary by CodeRabbit

  • New Features

    • Added a new example demonstrating the complete Nostr Wallet Connect hold invoice workflow, including invoice creation, notification listening, upstream payment, and settlement.
  • Refactor

    • Enhanced type annotations across examples for improved type safety and better developer experience.

@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Two example files in the NWC client directory are modified. The first receives TypeScript type annotations on function parameters. The second introduces a new example script demonstrating hold-invoice workflow, including user input collection, invoice creation, notification listening, upstream payment, and settlement.

Changes

Cohort / File(s) Summary
Type Annotations
examples/nwc/client/hold-invoice.ts
Added TypeScript type annotations: Nip47Notification for onNotification parameter and Uint8Array<ArrayBuffer> for toHexString parameter. Imported Nip47Notification from "@getalby/sdk/nwc".
New Example Script
examples/nwc/client/wrapped-invoice.ts
New 80-line example demonstrating NWC hold-invoice workflow: initializes WebSocket polyfill and readline, collects user input (NWC URL, upstream invoice, extra amount), derives payment_hash, creates hold invoice, subscribes to notifications, and upon acceptance pays upstream and settles hold invoice with preimage.

Sequence Diagram

sequenceDiagram
    actor User
    participant Script
    participant NWCClient
    participant WalletServer as Wallet Server<br/>(NWC)
    
    User->>Script: Provides NWC URL, upstream invoice, extra amount
    Script->>Script: Derives payment_hash from invoice
    Script->>NWCClient: Initialize with NWC URL
    Script->>WalletServer: Request hold_invoice creation
    WalletServer->>Script: Return hold invoice
    Script->>User: Display hold invoice
    Script->>WalletServer: Subscribe to notifications
    WalletServer->>Script: Emit hold_invoice_accepted notification
    Script->>WalletServer: Pay upstream invoice
    Script->>WalletServer: Settle hold invoice with preimage
    Script->>User: Process complete, exit
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hop, hop, type annotations dance,
Hold invoices in their rightful stance,
NWC clients now type-safe and bright,
Notifications flowing through the night,
A wallet connect example, pure delight!

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a new example for wrapped invoices, which corresponds to the new wrapped-invoice.ts file being introduced in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into master

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/wrapped-invoice-example

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rolznz rolznz merged commit 96a9a68 into master Feb 13, 2026
3 of 4 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@examples/nwc/client/wrapped-invoice.ts`:
- Around line 57-65: The pay+settle sequence can leave the hold invoice
unresolved if client.payInvoice throws; wrap the call to client.payInvoice({...
upstreamInvoice ...}) and the subsequent client.settleHoldInvoice({ preimage })
in a try/catch, capture the thrown error, and in the catch call
client.cancelHoldInvoice({ preimage }) (or cancel using the hold invoice id if
no preimage) before rethrowing or exiting; ensure the final process.exit() runs
only after settle or cancel completes and log the error while canceling to aid
debugging.
- Around line 24-27: The hold invoice `amount` is currently set only from the
user's extra-fee input and must instead be upstream_amount + extra_amount;
locate the user-facing `amount` variable and where `makeHoldInvoice` is called,
extract the upstream invoice amount (e.g., `upstream.amount`, `upstreamAmount`,
or the parsed msat/sat value from the fetched upstream invoice) convert units
consistently with the extra fee, add it to the parsed extra fee, and pass that
sum into `makeHoldInvoice` so the hold invoice covers both the upstream payment
and the extra fee.

Comment on lines +24 to +27
const amount =
parseInt(
(await rl.question("Extra amount in sats (default 1 sat): ")) || "1",
) * 1000;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the actual file to see the full context
head -80 examples/nwc/client/wrapped-invoice.ts

Repository: getAlby/js-sdk

Length of output: 2268


🏁 Script executed:

# Search for Invoice class usage and imports
rg -n "Invoice" examples/nwc/client/wrapped-invoice.ts -B2 -A2

Repository: getAlby/js-sdk

Length of output: 948


🏁 Script executed:

# Check package.json to understand `@getalby/lightning-tools` dependency
fd package.json | xargs cat

Repository: getAlby/js-sdk

Length of output: 3766


🏁 Script executed:

# Look at hold-invoice.ts for comparison (mentioned in original comment)
fd "hold-invoice.ts" --type f

Repository: getAlby/js-sdk

Length of output: 93


🏁 Script executed:

# Search for any type definitions or imports from `@getalby/lightning-tools`
rg "@getalby/lightning-tools" --type=ts -A3

Repository: getAlby/js-sdk

Length of output: 2243


🏁 Script executed:

# Look for Invoice class definition or type in the SDK source
rg -n "class Invoice|interface Invoice" --type=ts -A 10

Repository: getAlby/js-sdk

Length of output: 40


🏁 Script executed:

# Check if there's any documentation or usage of Invoice.satoshi
rg "satoshi|\.amount" examples/nwc/client/ --type=ts -B2 -A2

Repository: getAlby/js-sdk

Length of output: 384


🏁 Script executed:

# Look at hold-invoice.ts to compare the pattern
cat examples/nwc/client/hold-invoice.ts

Repository: getAlby/js-sdk

Length of output: 2447


🏁 Script executed:

# Search for any comments or documentation about wrapped invoices
rg -i "wrapped.*invoice|wrap" --type=ts -B2 -A2

Repository: getAlby/js-sdk

Length of output: 1654


🏁 Script executed:

# Search for node_modules or type definitions for `@getalby/lightning-tools`
find . -name "lightning-tools" -o -name "*lightning-tools*" | head -20

Repository: getAlby/js-sdk

Length of output: 40


🏁 Script executed:

# Check if there's a dist/types directory with type definitions
find . -path "*/node_modules/@getalby/lightning-tools" -type d

Repository: getAlby/js-sdk

Length of output: 40


🏁 Script executed:

# Try to look at what properties Invoice instance uses in the code
rg "Invoice.*\{.*pr:" --type=ts -A 15

Repository: getAlby/js-sdk

Length of output: 3180


🏁 Script executed:

# Search for any .d.ts files or interfaces that might define Invoice
fd "\.d\.ts$" | xargs rg "Invoice" -A 10 2>/dev/null | head -100

Repository: getAlby/js-sdk

Length of output: 4501


🏁 Script executed:

# Check if there are any test files that use Invoice
fd "test|spec" --type f | xargs rg "Invoice" -A 5 -B 5 2>/dev/null | head -50

Repository: getAlby/js-sdk

Length of output: 40


🏁 Script executed:

# Search for any usage of Invoice properties beyond paymentHash
rg "Invoice.*\(" --type=ts -A 10 | grep -E "\.(paymentHash|satoshi|amount|msat)" | head -20

Repository: getAlby/js-sdk

Length of output: 602


🌐 Web query:

@getalby/lightning-tools Invoice class satoshi amount property documentation

💡 Result:

In @getalby/lightning-tools, the Invoice class exposes the invoice amount as a satoshi: number property (there isn’t an amount field on the class). It’s set when the constructor decodes the BOLT11 pr and assigns this.satoshi = decodedInvoice.satoshi. (raw.githubusercontent.com)

Usage-wise, the README shows both:

  • requesting an invoice by passing { satoshi: 1000 }, and
  • decoding an invoice and reading { paymentHash, satoshi, description, createdDate, expiryDate } from the Invoice instance. (github.com)

Citations:


🏁 Script executed:

# Let's check if there are any other usages or patterns with Invoice.satoshi in the codebase
rg "\.satoshi" --type=ts -B 2 -A 2

Repository: getAlby/js-sdk

Length of output: 2466


🏁 Script executed:

# Verify the wrapped invoice logic one more time - check what the intended behavior should be
cat -n examples/nwc/client/wrapped-invoice.ts | head -70

Repository: getAlby/js-sdk

Length of output: 2575


🏁 Script executed:

# Check if there's any documentation in README about wrapped invoices
fd "README|readme" | xargs grep -i "wrap" -B 2 -A 2 2>/dev/null

Repository: getAlby/js-sdk

Length of output: 42


🏁 Script executed:

# One final check - verify the exact property name and confirm satoshi vs amount
rg "Invoice" examples/lnclient/splitter.ts -A 5 -B 5

Repository: getAlby/js-sdk

Length of output: 40


Hold invoice amount must include the upstream invoice amount plus the extra fee.

The amount variable (lines 24-27) is calculated only from user input for the extra fee and passed directly to makeHoldInvoice (line 35). For a wrapped invoice to function correctly, the hold invoice must be created for upstream_amount + extra_amount so that paying it provides sufficient funds to cover the upstream payment. Currently, if the upstream invoice is 10,000 sats and the extra is 1 sat (default), the hold invoice will only be 1 sat—insufficient to pay upstream.

Extract the upstream amount and include it in the calculation:

Proposed fix
+const upstreamAmount = new Invoice({ pr: upstreamInvoice.trim() }).satoshi;
+
 const amount =
   parseInt(
     (await rl.question("Extra amount in sats (default 1 sat): ")) || "1",
-  ) * 1000;
+  ) * 1000 +
+  upstreamAmount * 1000;

Also applies to: lines 35-41

🤖 Prompt for AI Agents
In `@examples/nwc/client/wrapped-invoice.ts` around lines 24 - 27, The hold
invoice `amount` is currently set only from the user's extra-fee input and must
instead be upstream_amount + extra_amount; locate the user-facing `amount`
variable and where `makeHoldInvoice` is called, extract the upstream invoice
amount (e.g., `upstream.amount`, `upstreamAmount`, or the parsed msat/sat value
from the fetched upstream invoice) convert units consistently with the extra
fee, add it to the parsed extra fee, and pass that sum into `makeHoldInvoice` so
the hold invoice covers both the upstream payment and the extra fee.

Comment on lines +57 to +65
const { preimage } = await client.payInvoice({
invoice: upstreamInvoice,
});

console.info("Paid upstream invoice", preimage);

await client.settleHoldInvoice({ preimage });

process.exit();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing error handling: upstream payment failure leaves hold invoice in limbo.

If client.payInvoice fails (e.g., no route, insufficient balance), the hold invoice is never settled or canceled, locking the payer's funds until the settle deadline expires. Wrap the pay+settle in a try/catch and cancel the hold invoice on failure.

Proposed fix
-  const { preimage } = await client.payInvoice({
-    invoice: upstreamInvoice,
-  });
-
-  console.info("Paid upstream invoice", preimage);
-
-  await client.settleHoldInvoice({ preimage });
+  try {
+    const { preimage } = await client.payInvoice({
+      invoice: upstreamInvoice,
+    });
+
+    console.info("Paid upstream invoice", preimage);
+
+    await client.settleHoldInvoice({ preimage });
+  } catch (e) {
+    console.error("Failed to pay upstream invoice, canceling hold invoice", e);
+    await client.cancelHoldInvoice({ payment_hash: paymentHash });
+  }
🤖 Prompt for AI Agents
In `@examples/nwc/client/wrapped-invoice.ts` around lines 57 - 65, The pay+settle
sequence can leave the hold invoice unresolved if client.payInvoice throws; wrap
the call to client.payInvoice({... upstreamInvoice ...}) and the subsequent
client.settleHoldInvoice({ preimage }) in a try/catch, capture the thrown error,
and in the catch call client.cancelHoldInvoice({ preimage }) (or cancel using
the hold invoice id if no preimage) before rethrowing or exiting; ensure the
final process.exit() runs only after settle or cancel completes and log the
error while canceling to aid debugging.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant