feat: add NWC-321 pay and receive methods - #576
Conversation
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>
|
Warning Review limit reached
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 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. 📝 WalkthroughWalkthroughThe 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. ChangesNWC-321 Operations
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
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
examples/nwc/client/pay.tsexamples/nwc/client/receive.tssrc/nwc/NWCClient.tssrc/nwc/types.tssrc/webln/NostrWeblnProvider.ts
| // payer_note: "a message from the payer", | ||
| }); | ||
|
|
||
| console.info(response); |
There was a problem hiding this comment.
📐 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
| const amount = | ||
| parseInt((await rl.question("Amount in sats (default 1 sat): ")) || "1") * | ||
| 1000; |
There was a problem hiding this comment.
🗄️ 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:
- 1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/parseInt
- 2: https://stackoverflow.com/questions/49696476/why-parseint-in-javascript-converting-1abc-to-1
- 3: https://github.com/mdn/content/blob/main/files/en-us/web/javascript/reference/global_objects/parseint/index.md
- 4: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FJSON%2Fstringify
- 5: https://fixjson.org/blog/how-to-stringify-json
- 6: https://github.com/mdn/content/blob/main/files/en-us/web/javascript/reference/global_objects/json/stringify/index.md
🏁 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 -240Repository: 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 examplesRepository: 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";
})(),
}));
}
JSRepository: 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.
| async pay(request: Nip47Bip321PayRequest): Promise<Nip47Bip321PayResponse> { | ||
| try { | ||
| const result = await this.executeNip47Request<Nip47Bip321PayResponse>( | ||
| "pay", | ||
| request, | ||
| (result) => !!result.state, | ||
| ); |
There was a problem hiding this comment.
🎯 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 requiredpayfields and accepted enum values, not only truthystate.src/nwc/NWCClient.ts#L574-L582: validate thatbip321is 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.
| | "pay" | ||
| | "receive" |
There was a problem hiding this comment.
🎯 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>
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 paymentNip47Bip321PayRequest/Response,Nip47Bip321ReceiveRequest/Response(theBip321prefix avoids clashing with the existingNip47PayResponseused bypay_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 changespay/receiveadded toNip47SingleMethodand excluded from the WebLN method map (no WebLN equivalent, same as the hold invoice methods)examples/nwc/client/pay.tsandexamples/nwc/client/receive.ts🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Compatibility