-
Notifications
You must be signed in to change notification settings - Fork 45
feat: add NWC-321 pay and receive methods #576
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import * as readline from "node:readline/promises"; | ||
| import { stdin as input, stdout as output } from "node:process"; | ||
|
|
||
| import { NWCClient } from "@getalby/sdk/nwc"; | ||
|
|
||
| const rl = readline.createInterface({ input, output }); | ||
|
|
||
| const nwcUrl = | ||
| process.env.NWC_URL || | ||
| (await rl.question("Nostr Wallet Connect URL (nostr+walletconnect://...): ")); | ||
| const payment = await rl.question( | ||
| "BIP-321 payment URI (bitcoin:?lightning=lnbc...): ", | ||
| ); | ||
| rl.close(); | ||
|
|
||
| const client = new NWCClient({ | ||
| nostrWalletConnectUrl: nwcUrl, | ||
| }); | ||
|
|
||
| const response = await client.pay({ | ||
| payment, | ||
| // amount: 1000, // in millisats, required if the selected payment instruction has no amount | ||
| // payer_note: "a message from the payer", | ||
| }); | ||
|
|
||
| console.info(response); | ||
|
|
||
| client.close(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import * as readline from "node:readline/promises"; | ||
| import { stdin as input, stdout as output } from "node:process"; | ||
|
|
||
| import { NWCClient } from "@getalby/sdk/nwc"; | ||
|
|
||
| const rl = readline.createInterface({ input, output }); | ||
|
|
||
| const nwcUrl = | ||
| process.env.NWC_URL || | ||
| (await rl.question("Nostr Wallet Connect URL (nostr+walletconnect://...): ")); | ||
|
|
||
| const amount = | ||
| parseInt((await rl.question("Amount in sats (default 1 sat): ")) || "1") * | ||
| 1000; | ||
|
Comment on lines
+12
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 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 -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.
🤖 Prompt for AI Agents |
||
|
|
||
| rl.close(); | ||
|
|
||
| const client = new NWCClient({ | ||
| nostrWalletConnectUrl: nwcUrl, | ||
| }); | ||
|
|
||
| const response = await client.receive({ | ||
| amount, // in millisats; omit for a variable amount (if supported by the wallet) | ||
| description: "NWC Client example", | ||
| }); | ||
|
|
||
| console.info(response); | ||
|
|
||
| client.close(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,6 +53,10 @@ import { | |
| Nip47SettleHoldInvoiceResponse, | ||
| Nip47CancelHoldInvoiceRequest, | ||
| Nip47CancelHoldInvoiceResponse, | ||
| Nip47Bip321PayRequest, | ||
| Nip47Bip321PayResponse, | ||
| Nip47Bip321ReceiveRequest, | ||
| Nip47Bip321ReceiveResponse, | ||
| Nip47NetworkError, | ||
| } from "./types"; | ||
| import { ReconnectingPool } from "./ReconnectingPool"; | ||
|
|
@@ -539,6 +543,50 @@ export class NWCClient { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Pays one Lightning payment instruction from a BIP-321 URI (NWC-321). | ||
| * | ||
| * The wallet service selects and pays a single supported instruction | ||
| * (e.g. a BOLT-11 invoice or BOLT-12 offer) from the URI. | ||
| * | ||
| * @see https://github.com/nostr-wallet-connect/nwc/blob/main/321.md | ||
| */ | ||
| async pay(request: Nip47Bip321PayRequest): Promise<Nip47Bip321PayResponse> { | ||
| try { | ||
| const result = await this.executeNip47Request<Nip47Bip321PayResponse>( | ||
| "pay", | ||
| request, | ||
| (result) => !!result.state, | ||
| ); | ||
|
Comment on lines
+554
to
+560
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
📍 Affects 1 file
🤖 Prompt for AI Agents |
||
| return result; | ||
| } catch (error) { | ||
| console.error("Failed to request pay", error); | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Creates a BIP-321 URI containing one or more wallet-selected Lightning | ||
| * receive instructions that can be given to a payer (NWC-321). | ||
| * | ||
| * @see https://github.com/nostr-wallet-connect/nwc/blob/main/321.md | ||
| */ | ||
| async receive( | ||
| request: Nip47Bip321ReceiveRequest, | ||
| ): Promise<Nip47Bip321ReceiveResponse> { | ||
| try { | ||
| const result = await this.executeNip47Request<Nip47Bip321ReceiveResponse>( | ||
| "receive", | ||
| request, | ||
| (result) => !!result.bip321, | ||
| ); | ||
| return result; | ||
| } catch (error) { | ||
| console.error("Failed to request receive", error); | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
| async signMessage( | ||
| request: Nip47SignMessageRequest, | ||
| ): Promise<Nip47SignMessageResponse> { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -69,6 +69,8 @@ const nip47ToWeblnRequestMap: Record< | |
| | "make_hold_invoice" | ||
| | "settle_hold_invoice" | ||
| | "cancel_hold_invoice" | ||
| | "pay" | ||
| | "receive" | ||
|
Comment on lines
+72
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 |
||
| >, | ||
| WebLNMethod | ||
| > = { | ||
|
|
||
There was a problem hiding this comment.
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-consolelint errors in both examples. ESLint reportsno-consoleerrors 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
Source: Linters/SAST tools