Skip to content

feat: add NWC-321 pay and receive methods - #576

Open
rolznz wants to merge 2 commits into
masterfrom
feat/nwc-321
Open

feat: add NWC-321 pay and receive methods#576
rolznz wants to merge 2 commits into
masterfrom
feat/nwc-321

Conversation

@rolznz

@rolznz rolznz commented Aug 12, 2026

Copy link
Copy Markdown
Member

Description

Adds basic client support for NWC-321 BIP-321 Lightning Payments, matching the BOLT-11-only implementation being added to Alby Hub.

  • NWCClient.pay() — pays a Lightning payment instruction from a BIP-321 URI (bitcoin:?lightning=lnbc...)
  • NWCClient.receive() — creates a BIP-321 URI for receiving a payment
  • New types: Nip47Bip321PayRequest/Response, Nip47Bip321ReceiveRequest/Response (the Bip321 prefix avoids clashing with the existing Nip47PayResponse used by pay_invoice/pay_keysend). The pay response includes the full spec surface (instruction_type, payer_proof, txid, ...) so BOLT-12-capable wallets work later without type changes
  • pay/receive added to Nip47SingleMethod and excluded from the WebLN method map (no WebLN equivalent, same as the hold invoice methods)
  • Example files: examples/nwc/client/pay.ts and examples/nwc/client/receive.ts

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for BIP-321 payments through Nostr Wallet Connect.
    • Added support for requesting payments through Nostr Wallet Connect.
    • Added command-line examples for sending and receiving payments.
    • Payment responses include transaction details, fees, proofs, and failure information where available.
  • Compatibility

    • Updated WebLN integration to avoid exposing these operations through unsupported methods.

Adds basic (BOLT-11 only) client support for NWC-321 BIP-321
Lightning payments, matching the Alby Hub implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rolznz, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 98feec49-23ea-426c-a155-4b29f15312f2

📥 Commits

Reviewing files that changed from the base of the PR and between c0fa0ba and 72c9469.

📒 Files selected for processing (1)
  • src/nwc/types.ts
📝 Walkthrough

Walkthrough

The SDK adds NWC-321 payment and receive types and client methods. It adds interactive Node.js examples and excludes these methods from the WebLN request map.

Changes

NWC-321 Operations

Layer / File(s) Summary
NWC-321 request and response contracts
src/nwc/types.ts
The NWC method union adds pay and receive. New types describe BIP-321 payment and receive requests and responses.
Client operations and method mapping
src/nwc/NWCClient.ts, src/webln/NostrWeblnProvider.ts
NWCClient sends payment and receive requests, validates response fields, logs failures, and rethrows errors. The WebLN provider excludes both methods.
Interactive payment and receive examples
examples/nwc/client/pay.ts, examples/nwc/client/receive.ts
The examples collect input, call the new client methods, print responses, and close the client.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant NWCClient
  participant NWCWallet
  CLI->>NWCClient: call pay or receive
  NWCClient->>NWCWallet: send NIP-47 request
  NWCWallet-->>NWCClient: return NWC-321 response
  NWCClient-->>CLI: return validated response
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding NWC-321 payment and receiving methods.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/nwc-321

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/nwc/client/pay.ts`:
- Line 26: Resolve the no-console lint errors for the required CLI output by
applying the project-approved output mechanism, or adding targeted suppressions
with a CLI-output reason, at examples/nwc/client/pay.ts lines 26-26 and
examples/nwc/client/receive.ts lines 27-27; apply the same handling to both
console.info calls.

In `@examples/nwc/client/receive.ts`:
- Around line 12-14: Validate the parsed amount in the receive flow before
constructing the NWC request: require a positive safe integer representing sats
and reject malformed or non-numeric input such as “1abc” or NaN. Only after
validation should the value be converted to millisats and assigned to the
request’s amount, preserving null exclusively for intentional variable-amount
requests.

In `@src/nwc/NWCClient.ts`:
- Around line 554-560: Strengthen response validation in NWCClient.pay
(src/nwc/NWCClient.ts:554-560) to require every mandatory pay response field and
verify each enum-valued field against its accepted values, rather than checking
only truthy state. Also update the receive response validation in
NWCClient.receive (src/nwc/NWCClient.ts:574-582) to require bip321 to be a
valid, non-empty BIP-321 URI before returning it.

In `@src/webln/NostrWeblnProvider.ts`:
- Around line 72-73: Update getInfo() to filter out NIP-47 methods such as “pay”
and “receive” when their WebLN mapping is undefined, so the returned methods
array contains only mapped capabilities.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e39d094e-6166-43ee-b21e-2fb8c979285c

📥 Commits

Reviewing files that changed from the base of the PR and between 07ed0f4 and c0fa0ba.

📒 Files selected for processing (5)
  • examples/nwc/client/pay.ts
  • examples/nwc/client/receive.ts
  • src/nwc/NWCClient.ts
  • src/nwc/types.ts
  • src/webln/NostrWeblnProvider.ts

// payer_note: "a message from the payer",
});

console.info(response);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Resolve the no-console lint errors in both examples. ESLint reports no-console errors for the required CLI output.

  • examples/nwc/client/pay.ts#L26-L26: add a targeted lint suppression with a CLI-output reason, or use the project-approved output mechanism.
  • examples/nwc/client/receive.ts#L27-L27: apply the same approved output handling.
🧰 Tools
🪛 ESLint

[error] 26-26: Unexpected console statement.

(no-console)

📍 Affects 2 files
  • examples/nwc/client/pay.ts#L26-L26 (this comment)
  • examples/nwc/client/receive.ts#L27-L27
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/nwc/client/pay.ts` at line 26, Resolve the no-console lint errors
for the required CLI output by applying the project-approved output mechanism,
or adding targeted suppressions with a CLI-output reason, at
examples/nwc/client/pay.ts lines 26-26 and examples/nwc/client/receive.ts lines
27-27; apply the same handling to both console.info calls.

Source: Linters/SAST tools

Comment on lines +12 to +14
const amount =
parseInt((await rl.question("Amount in sats (default 1 sat): ")) || "1") *
1000;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

According to the MDN JavaScript reference, how do parseInt("1abc"), parseInt("abc"), and JSON.stringify({ amount: NaN }) behave?

💡 Result:

According to the MDN JavaScript reference, these operations behave as follows: parseInt("1abc") The function returns the integer 1 [1][2]. When parseInt encounters a character that is not a valid numeral in the specified radix (or decimal, if no radix is provided), it ignores that character and all succeeding characters, returning the integer value parsed up to that point [1][3]. parseInt("abc") The function returns NaN [1][2]. If the first character of the string cannot be converted to a number in the specified radix, parseInt returns NaN [1][3]. JSON.stringify({ amount: NaN }) The result is the string '{"amount":null}' [4][5]. During JSON serialization, the value NaN is considered null and is converted to null [4][6]. In the context of an object property, the NaN value is thus represented as null within the resulting JSON string [4][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(receive\.ts|.*nwc.*)$' | head -80

printf '%s\n' '--- receive.ts ---'
cat -n examples/nwc/client/receive.ts

printf '%s\n' '--- NWCClient request serialization and receive definitions ---'
rg -n -C 5 'JSON\.stringify|receive|amount' examples packages src 2>/dev/null | head -240

Repository: getAlby/js-sdk

Length of output: 17524


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- NWCClient outline ---'
ast-grep outline src/nwc/NWCClient.ts

printf '%s\n' '--- receive and request implementation ---'
rg -n -C 12 'async receive|receive\(|JSON\.stringify|params' src/nwc/NWCClient.ts src/nwc/types.ts

printf '%s\n' '--- receive request types and wallet-service handling ---'
rg -n -C 10 'Nip47Receive|ReceiveRequest|receive.*amount|amount.*receive|variable amount|variable-amount' src/nwc docs examples

Repository: getAlby/js-sdk

Length of output: 20678


🏁 Script executed:

#!/bin/bash
set -eu

node - <<'JS'
const inputs = ["", "1", "1abc", "abc", "0", " 2 ", "9007199254740"];
for (const raw of inputs) {
  const sats = parseInt(raw || "1");
  const amount = sats * 1000;
  const command = { method: "receive", params: { amount, description: "NWC Client example" } };
  console.log(JSON.stringify({
    raw,
    sats,
    amount,
    serializedParams: JSON.stringify(command.params),
    proposedValidation: (() => {
      const value = raw.trim() || "1";
      if (!/^[1-9]\d*$/.test(value)) return "invalid";
      const millisats = Number(value) * 1000;
      return Number.isSafeInteger(millisats) ? millisats : "too large";
    })(),
  }));
}
JS

Repository: getAlby/js-sdk

Length of output: 1203


Reject invalid satoshi input before creating the request.

parseInt accepts "1abc" as 1. Non-numeric input becomes NaN, which JSON serialization converts to null. The NWC receive type defines amount: null as a variable-amount request. Validate a positive safe integer before converting sats to millisats.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/nwc/client/receive.ts` around lines 12 - 14, Validate the parsed
amount in the receive flow before constructing the NWC request: require a
positive safe integer representing sats and reject malformed or non-numeric
input such as “1abc” or NaN. Only after validation should the value be converted
to millisats and assigned to the request’s amount, preserving null exclusively
for intentional variable-amount requests.

Comment thread src/nwc/NWCClient.ts
Comment on lines +554 to +560
async pay(request: Nip47Bip321PayRequest): Promise<Nip47Bip321PayResponse> {
try {
const result = await this.executeNip47Request<Nip47Bip321PayResponse>(
"pay",
request,
(result) => !!result.state,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate complete NWC-321 responses at the client boundary. Both validators accept partial or invalid payloads from decrypted JSON and return them as fully typed responses.

  • src/nwc/NWCClient.ts#L554-L560: validate required pay fields and accepted enum values, not only truthy state.
  • src/nwc/NWCClient.ts#L574-L582: validate that bip321 is a valid non-empty BIP-321 URI before returning the receive response.
📍 Affects 1 file
  • src/nwc/NWCClient.ts#L554-L560 (this comment)
  • src/nwc/NWCClient.ts#L574-L582
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/nwc/NWCClient.ts` around lines 554 - 560, Strengthen response validation
in NWCClient.pay (src/nwc/NWCClient.ts:554-560) to require every mandatory pay
response field and verify each enum-valued field against its accepted values,
rather than checking only truthy state. Also update the receive response
validation in NWCClient.receive (src/nwc/NWCClient.ts:574-582) to require bip321
to be a valid, non-empty BIP-321 URI before returning it.

Comment on lines +72 to +73
| "pay"
| "receive"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Filter methods that have no WebLN mapping.

When a wallet advertises pay or receive, getInfo() maps the method at src/webln/NostrWeblnProvider.ts Line 160 and returns undefined in methods. Omit unmapped NIP-47 methods before returning the WebLN response.

Proposed fix
-        methods: nip47Result.methods.map(
-          (key) =>
-            nip47ToWeblnRequestMap[key as keyof typeof nip47ToWeblnRequestMap],
-        ),
+        methods: nip47Result.methods.flatMap((key) => {
+          const method =
+            nip47ToWeblnRequestMap[
+              key as keyof typeof nip47ToWeblnRequestMap
+            ];
+          return method === undefined ? [] : [method];
+        }),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/webln/NostrWeblnProvider.ts` around lines 72 - 73, Update getInfo() to
filter out NIP-47 methods such as “pay” and “receive” when their WebLN mapping
is undefined, so the returned methods array contains only mapped capabilities.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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