Skip to content

gl-sdk: Add LNURL pay, withdraw, and address resolution#701

Draft
cdecker wants to merge 9 commits intomainfrom
2026w15-lnurl
Draft

gl-sdk: Add LNURL pay, withdraw, and address resolution#701
cdecker wants to merge 9 commits intomainfrom
2026w15-lnurl

Conversation

@cdecker
Copy link
Copy Markdown
Collaborator

@cdecker cdecker commented Apr 21, 2026

Summary

Adds LNURL support to gl-sdk (and the underlying gl-client), exposed through UniFFI and NAPI bindings. The change is purely additive — no existing APIs were removed or had their signatures changed.

New top-level types (11 classes)

  • ResolvedLnUrl — result of resolving a bare LNURL / Lightning Address
  • LnUrlPayRequest, LnUrlPayRequestData, LnUrlPayResult, LnUrlPaySuccessData
  • LnUrlWithdrawRequest, LnUrlWithdrawRequestData, LnUrlWithdrawResult, LnUrlWithdrawSuccessData
  • LnUrlErrorData, SuccessActionProcessed

New methods on Node

  • resolve_lnurl(input: str) -> ResolvedLnUrl
  • lnurl_pay(request: LnUrlPayRequest) -> LnUrlPayResult
  • lnurl_withdraw(request: LnUrlWithdrawRequest) -> LnUrlWithdrawResult

InputType extensions

parse_input() now also recognizes bare LNURLs and Lightning Addresses; InputType gained is_ln_url() / is_ln_url_address() variant checks alongside the existing bolt11 / node_id ones.

Supporting work

  • gl-client: LNURL protocol primitives covering LUD-01/03/06/09/10/16
  • gl-sdk-napi: matching TypeScript/Node bindings for the new types and methods
  • gl-testing: CLN-backed LNURL server fixture plus integration tests for pay/withdraw flows
  • Refactor: fetch_invoice() and build_withdraw_callback_url() extracted as free functions in gl-client for reuse across callers

Test plan

  • task sdk:test passes (UniFFI Python bindings + integration tests)
  • cargo test -p gl-client passes (LNURL protocol unit tests)
  • cargo test -p gl-sdk passes
  • NAPI bindings build and tests pass (libs/gl-sdk-napi)
  • task testing:check passes (LNURL server fixture + test_lnurl.py, test_lnurl_server.py)

🤖 Generated with Claude Code

cdecker and others added 9 commits April 13, 2026 16:15
Build out gl-client's lnurl module as a complete LNURL protocol
library. This lays the foundation for exposing LNURL support through
gl-sdk in subsequent commits.

Changes:
- Make lnurl sub-modules public so gl-sdk can access the types
- Add lnurl_encode() for bech32 LNURL encoding (LUD-01)
- Add SuccessAction enum with Message/Url/Aes variants (LUD-09/10)
- Add ProcessedSuccessAction and SuccessAction::process() for
  AES decryption using the payment preimage
- Add LnUrlResponse enum and LNURL::resolve() for tag-based dispatch
- Add comment_allowed to PayRequestResponse (LUD-12 prep)
- Add success_action to PayRequestCallbackResponse
- Refactor pay/withdraw to method-based API on the response types:
  PayRequestResponse::validate(), .description(), .get_invoice()
  WithdrawRequestResponse::build_callback_url()
- Add extract_description_from_metadata() utility
- Add get_json() to LnUrlHttpClient trait for generic resolution
- Add aes/cbc dependencies for LUD-10 AES-256-CBC decryption
- 29 tests (up from 12)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Thin binding layer that wraps gl-client's LNURL protocol types with
UniFFI annotations for cross-language export. No protocol logic here,
only type definitions and From conversions.

New types: ResolvedLnUrl, LnUrlPayRequestData, LnUrlPayRequest,
LnUrlPayResult, LnUrlPaySuccessData, LnUrlWithdrawRequestData,
LnUrlWithdrawRequest, LnUrlWithdrawResult, LnUrlWithdrawSuccessData,
LnUrlErrorData, SuccessActionProcessed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add LnUrl and LnUrlAddress variants to InputType. parse_input() now
recognizes bech32 LNURL strings (lnurl1...) and Lightning Addresses
(user@domain.com) in addition to BOLT11 invoices and node IDs.

Detection is offline only -- no HTTP calls. The caller should use
Node::resolve_lnurl() to resolve LnUrl/LnUrlAddress inputs to their
typed endpoint data (pay or withdraw).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire up the LNURL flows as methods on Node, following the two-phase
pattern: resolve first to inspect metadata, then pay or withdraw.

- resolve_lnurl(): accepts LNURL bech32, lightning address, or raw
  URL. Single HTTP GET with tag-based dispatch via gl-client.
- lnurl_pay(): validates, fetches invoice, pays it, processes any
  success action (message/url/aes decryption).
- lnurl_withdraw(): creates invoice via receive(), submits it to the
  service's callback URL.

All three are pure orchestration -- protocol logic is in gl-client.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add TypeScript/Node.js wrappers for the LNURL functionality:
- resolve_lnurl(), lnurl_pay(), lnurl_withdraw() on Node
- All LNURL types: ResolvedLnUrl, LnUrlPayRequest, LnUrlPayResult,
  LnUrlWithdrawRequest, LnUrlWithdrawResult, SuccessActionProcessed
- Enums represented as discriminated unions with string `type` field
- Millisatoshi amounts as i64 for JS number compatibility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a test LNURL server backed by a real CLN node, implementing
LUD-01/03/06/09/16. The server issues real BOLT11 invoices and pays
real invoices for withdraw, enabling full end-to-end testing.

New files:
- gltesting/lnurl_server.py: LnurlServer class with pay, withdraw,
  and lightning address endpoints
- tests/test_lnurl_server.py: 6 tests for the server itself (HTTP
  responses, invoice generation, k1 management)
- tests/test_lnurl.py: 5 integration tests using the full Greenlight
  stack (scheduler, signer, SDK node, channels) against the LNURL
  server. Includes end-to-end LNURL-pay with actual Lightning
  payments and success action verification.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ignore glsdk.py, libglsdk.so, __pycache__, and bindings/ since these
are regenerated by uniffi-bindgen from the compiled Rust library.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SDK's lnurl_pay() was reconstructing a PayRequestResponse struct
(a server response type) on the client side just to call its
get_invoice() method. Fix by extracting the logic into a public
fetch_invoice() free function that takes callback/amount/metadata
directly. The method on PayRequestResponse now delegates to it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same fix as the previous commit but for the withdraw side: the SDK
was reconstructing a WithdrawRequestResponse just to call
build_callback_url(). Extract a free function that takes callback,
k1, and invoice directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cdecker cdecker marked this pull request as draft April 21, 2026 14:45
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