Skip to content

NWC Improvements#42

Open
ntheile wants to merge 2 commits into
masterfrom
nwc-improvments
Open

NWC Improvements#42
ntheile wants to merge 2 commits into
masterfrom
nwc-improvments

Conversation

@ntheile

@ntheile ntheile commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added Lightning Address support for NWC nodes
    • Introduced getLightningAddress() method to retrieve Lightning Address from NWC configuration
    • Implemented LNURL-verify support for validating Lightning Address payment requests
  • Improvements

    • Enhanced NWC error reporting with structured error codes and detailed error messages
  • Tests

    • Added comprehensive test coverage for Lightning Address resolution and LNURL verification workflows

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ntheile, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 48 minutes and 47 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

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.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ec0957c2-a7d4-47a4-ab0c-5eabfb2302c6

📥 Commits

Reviewing files that changed from the base of the PR and between 26b40eb and 80376ff.

⛔ Files ignored due to path filters (4)
  • bindings/typescript-arkade/package-lock.json is excluded by !**/package-lock.json
  • bindings/typescript-spark/examples/spark-expo-go/package-lock.json is excluded by !**/package-lock.json
  • bindings/typescript-spark/package-lock.json is excluded by !**/package-lock.json
  • bindings/typescript/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • bindings/typescript-arkade/package.json
  • bindings/typescript-spark/package.json
  • bindings/typescript/package.json
📝 Walkthrough

Walkthrough

This PR adds Lightning Address support to NWC nodes with structured NIP-47 error handling. It introduces LNURL-verify validation for payment requests, centralizes error translation to preserve wallet error codes, and exposes Lightning Address metadata via getNwcLightningAddress in both TypeScript and Rust.

Changes

NWC Lightning Address and Error Handling

Layer / File(s) Summary
Error type contracts and NWC error code translation
bindings/typescript/src/errors.ts, crates/lni/lib.rs, bindings/typescript/src/__tests__/nwc.test.ts
LniErrorCode is extended with 'NwcError' variant. NwcError class in TypeScript carries nwcCode, nwcMessage, and optional operation fields. Rust ApiError gains Nwc variant with code and message. Test mock defines Nip47Error class.
LNURL-verify flow and verification helpers
bindings/typescript/src/lnurl.ts, crates/lni/lnurl/mod.rs
LnurlPayResponse is exported. New types for verify responses and error payloads. LnurlVerifyUnsupportedError added. Centralized error handling via handleLnurlErrorResponse replaces ad-hoc status checks. verifyLightningAddressPayRequest (TS) and verify_lightning_address_pay_request (Rust) implement full verify flow: resolve Lightning Address → fetch pay callback with amount → validate pr/verify fields → follow verify endpoint → enforce OK status.
Lightning Address data types and contracts
bindings/typescript/src/types.ts, crates/lni/nwc/lib.rs, crates/lni/lib.rs
LightningAddressInfo interface in TypeScript with lightningAddress and lnurlVerifySupported flag. NwcLightningAddress struct in Rust with corresponding fields. Rust module re-exports NwcLightningAddress.
NWC error translation and mapping helpers
bindings/typescript/src/nodes/nwc.ts, crates/lni/nwc/api.rs
toNwcError and throwNwcOrApiError helpers in TypeScript convert Nip47Error to NwcError with operation context. nwc_error_code_to_string and map_nwc_error in Rust translate NIP-47 error codes to ApiError::Nwc.
NWC Lightning Address extraction and verification
bindings/typescript/src/nodes/nwc.ts, crates/lni/nwc/api.rs, crates/lni/nwc/lib.rs
extractLightningAddressFromNwcUri helper parses lud16 from NWC wallet-connect URIs with fallbacks. NwcNode constructor stores NodeRequestOptions to enable custom fetch. getLightningAddress extracts lud16, validates it as Lightning Address, and verifies LNURL support via verifyLightningAddressPayRequest. Rust functions lightning_address_from_nwc_uri and get_lightning_address perform URI parsing, validation, and LNURL verification.
Error mapping across NWC operations
bindings/typescript/src/nodes/nwc.ts, crates/lni/nwc/api.rs
createInvoice, payInvoice, lookupInvoice, listTransactions (TS) and get_permissions, create_invoice, pay_invoice, lookup_invoice, list_transactions (Rust) updated to use centralized error translation (throwNwcOrApiError/map_nwc_error) instead of ad-hoc formatting, preserving NWC error codes.
Test coverage for error propagation and Lightning Address resolution
bindings/typescript/src/__tests__/nwc.test.ts, crates/lni/nwc/api.rs, crates/lni/nwc/lib.rs
Test scaffolding adds Nip47Error mock export, makeJsonResponse helper, and NWC_URI_WITH_LUD16 constant. payInvoice test validates NwcError instance and detailed fields (nwcCode, nwcMessage, operation). getLightningAddress test suite covers LNURL verify success with URL assertions, omitted verify endpoint behavior, and missing lud16 error cases. Rust tests validate error code mapping and lightning address parsing with/without lud16.

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant NwcNode
  participant URIParse as URI Parser
  participant LnurlVerify as LNURL Verify
  participant LNURL as LNURL-pay
  participant Callback
  participant Verify as LNURL-verify
  App->>NwcNode: getLightningAddress()
  NwcNode->>URIParse: extract lud16 from nwcUri
  URIParse-->>NwcNode: lightningAddress
  NwcNode->>LnurlVerify: verifyLightningAddressPayRequest
  LnurlVerify->>LNURL: resolve Lightning Address
  LNURL-->>LnurlVerify: pay endpoint + amount range
  LnurlVerify->>Callback: request invoice with amount
  Callback-->>LnurlVerify: pr + verify endpoint
  alt has verify endpoint
    LnurlVerify->>Verify: follow verify URL
    Verify-->>LnurlVerify: status OK
    LnurlVerify-->>NwcNode: verified
  else no verify endpoint
    LnurlVerify-->>NwcNode: throw LnurlVerifyUnsupportedError
  end
  NwcNode-->>App: LightningAddressInfo with flag
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • lightning-node-interface/lni#11: Extends Rust NWC integration with NWC/NIP-47 error mapping and Lightning Address parsing in crates/lni/nwc/api.rs, directly foundational to this PR's error translation and address extraction logic.
  • lightning-node-interface/lni#27: Implements the LNURL verification plumbing in crates/lni/lnurl/mod.rs that this PR builds on for Lightning Address LNURL-verify validation in both Rust and TypeScript.
  • lightning-node-interface/lni#35: Adds NwcNode.getPermissions() that calls getInfo and rethrows errors—directly affected by this PR's shared error handling refactor in getInfo and error propagation layer.

Poem

🐰 With NWC URIs parsed and lud16 gleaned,
The rabbit verifies where Lightning Address gleams,
Through LNURL callbacks and verify endpoints clean,
NipErrors now translate to NwcErrors supreme—
Error codes preserved through each operation's dream! 🌙✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'NWC Improvements' is vague and generic, using a non-descriptive term that doesn't convey meaningful information about the specific changes made. Replace with a more specific title that describes the primary change, such as 'Add Lightning Address support and NWC error handling to NwcNode' or 'Implement LNURL-verify and NIP47 error translation for NWC'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nwc-improvments

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 26b40ebacd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +177 to +180
}).then(
() => true,
() => false,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject malformed lud16 values before probing LNURL

When the NWC URI contains a malformed lud16 such as lud16=notaddress, verifyLightningAddressPayRequest rejects while parsing the Lightning Address, but this rejection handler converts every failure into lnurlVerifySupported: false. That makes getLightningAddress() return { lightningAddress: 'notaddress', lnurlVerifySupported: false } instead of surfacing the invalid URI, so callers cannot distinguish an unsupported verify endpoint from bad NWC metadata. Validate the parsed lud16 before swallowing only the expected LNURL-verify unsupported/probe failures.

Useful? React with 👍 / 👎.

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​expo-crypto@​56.0.47710084100100
Addednpm/​@​getalby/​sdk@​7.0.07710010093100
Updatednpm/​@​types/​node@​25.6.0 ⏵ 22.19.9100 +110081 +196100

View full report

@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @expo/cli is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/expo-crypto@56.0.4npm/@expo/cli@56.1.14

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@expo/cli@56.1.14. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @react-native/debugger-frontend is 96.0% likely obfuscated

Confidence: 0.96

Location: Package overview

From: ?npm/expo-crypto@56.0.4npm/@react-native/debugger-frontend@0.74.89

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@react-native/debugger-frontend@0.74.89. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @react-native/debugger-frontend is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/expo-crypto@56.0.4npm/@react-native/debugger-frontend@0.85.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@react-native/debugger-frontend@0.85.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @react-native/debugger-frontend is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/expo-crypto@56.0.4npm/@react-native/debugger-frontend@0.85.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@react-native/debugger-frontend@0.85.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm commander is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/expo-crypto@56.0.4npm/commander@9.5.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/commander@9.5.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm fast-xml-parser is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/expo-crypto@56.0.4npm/fast-xml-parser@4.5.6

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/fast-xml-parser@4.5.6. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm js-yaml is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/expo-crypto@56.0.4npm/js-yaml@4.2.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/js-yaml@4.2.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm kind-of is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/expo-crypto@56.0.4npm/kind-of@6.0.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/kind-of@6.0.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm nostr-wasm is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: bindings/typescript/package-lock.jsonnpm/@getalby/sdk@7.0.0npm/nostr-wasm@0.1.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/nostr-wasm@0.1.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm nostr-wasm is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: bindings/typescript/package-lock.jsonnpm/@getalby/sdk@7.0.0npm/nostr-wasm@0.1.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/nostr-wasm@0.1.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@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: 3

🧹 Nitpick comments (1)
bindings/typescript/src/__tests__/nwc.test.ts (1)

127-140: 💤 Low value

Consider consolidating duplicate payInvoice calls.

The test makes two separate calls to makeNode().payInvoice() with different assertions. This could be refactored to capture the rejection once:

-    await expect(makeNode().payInvoice({ invoice: BOLT11_INVOICE })).rejects.toMatchObject({
-      name: 'NwcError',
-      code: 'NwcError',
-      nwcCode: 'QUOTA_EXCEEDED',
-      nwcMessage: 'quota spent',
-      operation: 'pay_invoice',
-      message: 'quota spent',
-    });
-
-    await expect(makeNode().payInvoice({ invoice: BOLT11_INVOICE })).rejects.toBeInstanceOf(NwcError);
+    const error = await makeNode().payInvoice({ invoice: BOLT11_INVOICE }).catch(e => e);
+    
+    expect(error).toMatchObject({
+      name: 'NwcError',
+      code: 'NwcError',
+      nwcCode: 'QUOTA_EXCEEDED',
+      nwcMessage: 'quota spent',
+      operation: 'pay_invoice',
+      message: 'quota spent',
+    });
+    expect(error).toBeInstanceOf(NwcError);
🤖 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 `@bindings/typescript/src/__tests__/nwc.test.ts` around lines 127 - 140, Call
makeNode().payInvoice(...) only once and reuse that rejected promise for both
assertions: assign the result of makeNode().payInvoice({ invoice: BOLT11_INVOICE
}) to a variable (e.g., rejectedPromise) after configuring nwcMocks.payInvoice
to throw the Nip47Error, then await
expect(rejectedPromise).rejects.toMatchObject({...}) and await
expect(rejectedPromise).rejects.toBeInstanceOf(NwcError). Keep references to
makeNode, payInvoice, NwcError, nwcMocks.payInvoice, and nwcMocks.Nip47Error to
locate the code to change.
🤖 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 `@bindings/typescript/src/lnurl.ts`:
- Around line 397-400: The current check treats empty strings for maybeVerify.pr
and maybeVerify.verify as valid because it only tests typeof, leading to
downstream URL parsing errors instead of throwing LnurlVerifyUnsupportedError;
update the validation in the handler that casts callbackResponse to
Partial<LnurlVerifyInvoiceResponse> (the maybeVerify variable) to additionally
ensure both maybeVerify.pr and maybeVerify.verify are non-empty (e.g., check
they are strings with length > 0 or trim() !== '') and throw
LnurlVerifyUnsupportedError() when either is missing/empty so empty values are
normalized as unsupported verify responses.

In `@bindings/typescript/src/nodes/nwc.ts`:
- Around line 169-186: The current getLightningAddress implementation swallows
all errors from verifyLightningAddressPayRequest and returns
lnurlVerifySupported: false; change it to only treat LnurlVerifyUnsupportedError
as "unsupported" while letting genuine failures propagate: call
verifyLightningAddressPayRequest (from bindings/typescript/src/lnurl.ts) and
catch exceptions only of type LnurlVerifyUnsupportedError to set
lnurlVerifySupported = false, but rethrow any other errors (e.g. instances of
LniError or unknown errors) so they surface to the caller; keep the existing
extraction via extractLightningAddressFromNwcUri and preserve passing
this.options.fetch to the verify call.

In `@crates/lni/lnurl/mod.rs`:
- Around line 266-290: The code in verify_lightning_address_pay_request
currently fetches URLs derived from untrusted LNURL data (via
lightning_address_to_url, well_known.callback and verify_response.verify) using
fetch_lnurl_pay and fetch_lnurl_json_value without validating them first; add
strict URL validation before any outbound fetch: parse each URL (the URL
produced by lightning_address_to_url, the callback URL built by
callback_url_with_amount(&well_known.callback, …), and verify_response.verify),
ensure the scheme is https, reject IP-literal hosts or resolve the hostname and
verify the resolved addresses are not loopback/private/RFC1918/cloud metadata
ranges, or apply a safe-host allowlist; perform these checks in
verify_lightning_address_pay_request immediately before calling fetch_lnurl_pay
and before each fetch_lnurl_json_value, returning ApiError::InvalidInput on
failure.

---

Nitpick comments:
In `@bindings/typescript/src/__tests__/nwc.test.ts`:
- Around line 127-140: Call makeNode().payInvoice(...) only once and reuse that
rejected promise for both assertions: assign the result of
makeNode().payInvoice({ invoice: BOLT11_INVOICE }) to a variable (e.g.,
rejectedPromise) after configuring nwcMocks.payInvoice to throw the Nip47Error,
then await expect(rejectedPromise).rejects.toMatchObject({...}) and await
expect(rejectedPromise).rejects.toBeInstanceOf(NwcError). Keep references to
makeNode, payInvoice, NwcError, nwcMocks.payInvoice, and nwcMocks.Nip47Error to
locate the code to change.
🪄 Autofix (Beta)

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

Run ID: 633a7b89-7db4-4e44-ae1b-be6f8a9fa097

📥 Commits

Reviewing files that changed from the base of the PR and between 45a1d91 and 26b40eb.

📒 Files selected for processing (9)
  • bindings/typescript/src/__tests__/nwc.test.ts
  • bindings/typescript/src/errors.ts
  • bindings/typescript/src/lnurl.ts
  • bindings/typescript/src/nodes/nwc.ts
  • bindings/typescript/src/types.ts
  • crates/lni/lib.rs
  • crates/lni/lnurl/mod.rs
  • crates/lni/nwc/api.rs
  • crates/lni/nwc/lib.rs

Comment on lines +397 to +400
const maybeVerify = callbackResponse as Partial<LnurlVerifyInvoiceResponse>;
if (typeof maybeVerify.pr !== 'string' || typeof maybeVerify.verify !== 'string') {
throw new LnurlVerifyUnsupportedError();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize empty pr/verify as unsupported verify responses.

Line [398] only checks typeof, so empty strings pass and then fail later as generic URL parsing errors instead of LnurlVerifyUnsupportedError.

Proposed fix
-  if (typeof maybeVerify.pr !== 'string' || typeof maybeVerify.verify !== 'string') {
+  if (
+    typeof maybeVerify.pr !== 'string' ||
+    maybeVerify.pr.trim() === '' ||
+    typeof maybeVerify.verify !== 'string' ||
+    maybeVerify.verify.trim() === ''
+  ) {
     throw new LnurlVerifyUnsupportedError();
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const maybeVerify = callbackResponse as Partial<LnurlVerifyInvoiceResponse>;
if (typeof maybeVerify.pr !== 'string' || typeof maybeVerify.verify !== 'string') {
throw new LnurlVerifyUnsupportedError();
}
const maybeVerify = callbackResponse as Partial<LnurlVerifyInvoiceResponse>;
if (
typeof maybeVerify.pr !== 'string' ||
maybeVerify.pr.trim() === '' ||
typeof maybeVerify.verify !== 'string' ||
maybeVerify.verify.trim() === ''
) {
throw new LnurlVerifyUnsupportedError();
}
🤖 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 `@bindings/typescript/src/lnurl.ts` around lines 397 - 400, The current check
treats empty strings for maybeVerify.pr and maybeVerify.verify as valid because
it only tests typeof, leading to downstream URL parsing errors instead of
throwing LnurlVerifyUnsupportedError; update the validation in the handler that
casts callbackResponse to Partial<LnurlVerifyInvoiceResponse> (the maybeVerify
variable) to additionally ensure both maybeVerify.pr and maybeVerify.verify are
non-empty (e.g., check they are strings with length > 0 or trim() !== '') and
throw LnurlVerifyUnsupportedError() when either is missing/empty so empty values
are normalized as unsupported verify responses.

Comment on lines +169 to +186
async getLightningAddress(): Promise<LightningAddressInfo> {
const lightningAddress = extractLightningAddressFromNwcUri(this.config.nwcUri);
if (!lightningAddress) {
throw new LniError('InvalidInput', 'NWC URI does not include a lud16 Lightning Address.');
}

const lnurlVerifySupported = await verifyLightningAddressPayRequest(lightningAddress, {
fetch: this.options.fetch,
}).then(
() => true,
() => false,
);

return {
lightningAddress,
lnurlVerifySupported,
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Error handling may hide genuine failures.

The current implementation catches all errors from verifyLightningAddressPayRequest and returns lnurlVerifySupported: false. This could mask real failures like network errors, invalid Lightning Address format, or HTTP issues.

Based on the LNURL helper (see bindings/typescript/src/lnurl.ts:282-416), verifyLightningAddressPayRequest can throw:

  • LnurlVerifyUnsupportedError when the verify endpoint is missing (expected)
  • LniError for network failures, invalid formats, or other issues (should propagate)

Consider catching only the expected error:

+import { LnurlVerifyUnsupportedError } from '../lnurl.js';

 async getLightningAddress(): Promise<LightningAddressInfo> {
   const lightningAddress = extractLightningAddressFromNwcUri(this.config.nwcUri);
   if (!lightningAddress) {
     throw new LniError('InvalidInput', 'NWC URI does not include a lud16 Lightning Address.');
   }

-  const lnurlVerifySupported = await verifyLightningAddressPayRequest(lightningAddress, {
-    fetch: this.options.fetch,
-  }).then(
-    () => true,
-    () => false,
-  );
+  let lnurlVerifySupported = false;
+  try {
+    await verifyLightningAddressPayRequest(lightningAddress, {
+      fetch: this.options.fetch,
+    });
+    lnurlVerifySupported = true;
+  } catch (error) {
+    if (error instanceof LnurlVerifyUnsupportedError) {
+      lnurlVerifySupported = false;
+    } else {
+      throw error;
+    }
+  }

   return {
     lightningAddress,
     lnurlVerifySupported,
   };
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async getLightningAddress(): Promise<LightningAddressInfo> {
const lightningAddress = extractLightningAddressFromNwcUri(this.config.nwcUri);
if (!lightningAddress) {
throw new LniError('InvalidInput', 'NWC URI does not include a lud16 Lightning Address.');
}
const lnurlVerifySupported = await verifyLightningAddressPayRequest(lightningAddress, {
fetch: this.options.fetch,
}).then(
() => true,
() => false,
);
return {
lightningAddress,
lnurlVerifySupported,
};
}
import { LnurlVerifyUnsupportedError } from '../lnurl.js';
async getLightningAddress(): Promise<LightningAddressInfo> {
const lightningAddress = extractLightningAddressFromNwcUri(this.config.nwcUri);
if (!lightningAddress) {
throw new LniError('InvalidInput', 'NWC URI does not include a lud16 Lightning Address.');
}
let lnurlVerifySupported = false;
try {
await verifyLightningAddressPayRequest(lightningAddress, {
fetch: this.options.fetch,
});
lnurlVerifySupported = true;
} catch (error) {
if (error instanceof LnurlVerifyUnsupportedError) {
lnurlVerifySupported = false;
} else {
throw error;
}
}
return {
lightningAddress,
lnurlVerifySupported,
};
}
🤖 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 `@bindings/typescript/src/nodes/nwc.ts` around lines 169 - 186, The current
getLightningAddress implementation swallows all errors from
verifyLightningAddressPayRequest and returns lnurlVerifySupported: false; change
it to only treat LnurlVerifyUnsupportedError as "unsupported" while letting
genuine failures propagate: call verifyLightningAddressPayRequest (from
bindings/typescript/src/lnurl.ts) and catch exceptions only of type
LnurlVerifyUnsupportedError to set lnurlVerifySupported = false, but rethrow any
other errors (e.g. instances of LniError or unknown errors) so they surface to
the caller; keep the existing extraction via extractLightningAddressFromNwcUri
and preserve passing this.options.fetch to the verify call.

Comment thread crates/lni/lnurl/mod.rs
Comment on lines +266 to +290
pub async fn verify_lightning_address_pay_request(lightning_address: &str) -> Result<(), ApiError> {
let PaymentDestination::LightningAddress { user, domain } = PaymentDestination::parse(lightning_address)? else {
return Err(ApiError::InvalidInput("Expected Lightning Address".to_string()));
};
let well_known = fetch_lnurl_pay(&lightning_address_to_url(&user, &domain)).await?;
let amount_msats = std::cmp::min(
std::cmp::max(100_000, well_known.min_sendable),
well_known.max_sendable,
);

if amount_msats < well_known.min_sendable || amount_msats > well_known.max_sendable {
return Err(ApiError::InvalidInput("Invalid LNURL sendable amount range".to_string()));
}

let callback_response = fetch_lnurl_json_value(&callback_url_with_amount(&well_known.callback, amount_msats)?).await?;
let verify_response: LnurlVerifyPayResponse = serde_json::from_value(callback_response)
.map_err(|_| ApiError::InvalidInput("LNURL-verify endpoint is not supported".to_string()))?;

if verify_response.pr.is_empty() || verify_response.verify.is_empty() {
return Err(ApiError::InvalidInput("LNURL-verify endpoint is not supported".to_string()));
}

let verify_result = fetch_lnurl_json_value(&verify_response.verify).await?;
handle_lnurl_ok_value(&verify_result, "LNURL verify")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Validate LNURL callback/verify URLs before fetching (SSRF risk).

Lines [270], [280], and [288] perform outbound GETs to URLs derived from untrusted remote data without HTTPS/public-host validation. A malicious Lightning Address endpoint can return callback/verify URLs to internal targets (e.g., RFC1918 or metadata services), and this code will fetch them.

Suggested hardening direction
+fn validate_public_https_url(raw: &str) -> Result<reqwest::Url, ApiError> {
+    let url = reqwest::Url::parse(raw)
+        .map_err(|e| ApiError::InvalidInput(format!("Invalid LNURL URL: {}", e)))?;
+    if url.scheme() != "https" {
+        return Err(ApiError::InvalidInput("LNURL endpoints must use HTTPS".to_string()));
+    }
+    // Reject credentials + private/local hosts/IP ranges here.
+    Ok(url)
+}
...
- let callback_response = fetch_lnurl_json_value(&callback_url_with_amount(&well_known.callback, amount_msats)?).await?;
+ let callback_url = callback_url_with_amount(&well_known.callback, amount_msats)?;
+ let callback_url = validate_public_https_url(&callback_url)?;
+ let callback_response = fetch_lnurl_json_value(callback_url.as_str()).await?;
...
- let verify_result = fetch_lnurl_json_value(&verify_response.verify).await?;
+ let verify_url = validate_public_https_url(&verify_response.verify)?;
+ let verify_result = fetch_lnurl_json_value(verify_url.as_str()).await?;
🤖 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 `@crates/lni/lnurl/mod.rs` around lines 266 - 290, The code in
verify_lightning_address_pay_request currently fetches URLs derived from
untrusted LNURL data (via lightning_address_to_url, well_known.callback and
verify_response.verify) using fetch_lnurl_pay and fetch_lnurl_json_value without
validating them first; add strict URL validation before any outbound fetch:
parse each URL (the URL produced by lightning_address_to_url, the callback URL
built by callback_url_with_amount(&well_known.callback, …), and
verify_response.verify), ensure the scheme is https, reject IP-literal hosts or
resolve the hostname and verify the resolved addresses are not
loopback/private/RFC1918/cloud metadata ranges, or apply a safe-host allowlist;
perform these checks in verify_lightning_address_pay_request immediately before
calling fetch_lnurl_pay and before each fetch_lnurl_json_value, returning
ApiError::InvalidInput on failure.

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