Skip to content

feat(extensions): add LNURL-withdraw extension (LUD-03)#905

Merged
shocknet-justin merged 9 commits into
shocknet:pr/feat-extension-loaderfrom
PatMulligan:feat/lnurl-withdraw
Mar 5, 2026
Merged

feat(extensions): add LNURL-withdraw extension (LUD-03)#905
shocknet-justin merged 9 commits into
shocknet:pr/feat-extension-loaderfrom
PatMulligan:feat/lnurl-withdraw

Conversation

@PatMulligan

@PatMulligan PatMulligan commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Full LUD-03 LNURL-withdraw implementation built on the extension loader (feat(extensions): add modular extension loader infrastructure #904 )
  • Extension HTTP server on port+1 serves LNURL endpoints with CORS
  • Nostr RPC routing forwards withdraw.* methods to the extension
  • Supports single-use and multi-use links with rate limiting, unique per-use hashes, and webhook notifications
  • Per-user balance tracking — withdraw links debit the creator's balance via PayAppUserInvoice
  • Allows LND self-payments for local wallet withdrawals
  • RPC methods: createLink, createVouchers, getLink, listLinks, updateLink, deleteLink, listWithdrawals, getStats

Test plan

  • Create single-use withdraw link, scan with wallet, funds received
  • Multi-use link with rate limiting enforces wait_time between claims
  • Creator pubkey balance debited correctly (not app owner)
  • HTTP API creates links with Bearer token auth
  • Deployed and tested in production with ATM cash-out flow

Dependencies

This PR depends on the following PRs being merged first:

@PatMulligan PatMulligan force-pushed the feat/lnurl-withdraw branch from d4602da to 12a7b1d Compare March 4, 2026 19:04
@socket-security

socket-security Bot commented Mar 4, 2026

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
Added@​types/​better-sqlite3@​7.6.131001007181100

View full report

@PatMulligan PatMulligan force-pushed the feat/lnurl-withdraw branch from 12a7b1d to e2eb5de Compare March 4, 2026 19:18
PatMulligan and others added 9 commits March 4, 2026 15:14
Implements LUD-03 (LNURL-withdraw) for creating withdraw links
that allow anyone to pull funds from a Lightning wallet.

Features:
- Create withdraw links with min/max amounts
- Quick vouchers: batch creation of single-use codes
- Multi-use links with wait time between uses
- Unique QR codes per use (prevents sharing exploits)
- Webhook notifications on successful withdrawals
- Full LNURL protocol compliance for wallet compatibility

Use cases:
- Faucets
- Gift cards / prepaid cards
- Tips / donations
- User onboarding

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add extension loader initialization to startup
- Create mainHandlerAdapter to bridge mainHandler with extension context
- Mount extension HTTP routes on separate port (main port + 1)
- Configure EXTENSION_SERVICE_URL for LNURL link generation

The withdraw extension provides LUD-03 LNURL-withdraw support for
creating withdraw links that allow users to pull funds.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Enable CORS on the extension HTTP server to allow cross-origin requests
from ATM apps and other web-based clients.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add POST /api/v1/withdraw/create endpoint to allow external apps (ATM,
web clients) to create LNURL-withdraw links via HTTP instead of RPC.

Changes:
- Add handleCreateWithdrawLink HTTP handler
- Fix route ordering: callback routes before wildcard :unique_hash
- Extract app_id from Authorization header (Bearer app_<id>)
- Use is_unique=false for simple single-use ATM links

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Initialize extension system before nostrMiddleware so registered
RPC methods are available. Extension methods (e.g. withdraw.createLink)
are intercepted and routed to the extension loader before falling
through to the standard nostrTransport.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Store the Nostr pubkey of the user who creates a withdraw link so the
LNURL callback debits the correct user's balance instead of the app
owner's. Pass userPubkey through from RPC handler to WithdrawManager.

- Add creator_pubkey column (migration v4)
- Store creatorPubkey on link creation
- Pass creator_pubkey to payInvoice on LNURL callback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When userPubkey is provided, resolve the ApplicationUser and call
applicationManager.PayAppUserInvoice instead of paymentManager.PayInvoice
directly. This ensures notifyAppUserPayment fires, sending
LiveUserOperation events via Nostr for real-time balance updates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the user's wallet (e.g. Zeus) is connected to the same LND node
that LP uses, LNURL-withdraw fails because LND rejects the payment
with "no self-payments allowed". This is safe because LP always
decrements the user's balance before paying and refunds on failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
notifyAppUserPayment was sending the stale cached balance from the
entity loaded before PayInvoice decremented it. Update the entity's
balance_sats from the PayInvoice response so LiveUserOperation events
contain the correct post-payment balance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@PatMulligan PatMulligan force-pushed the feat/lnurl-withdraw branch from e2eb5de to 66e14d6 Compare March 4, 2026 20:22
@shocknet-justin shocknet-justin requested a review from boufni95 March 5, 2026 03:15
@shocknet-justin shocknet-justin changed the base branch from master to pr/feat-extension-loader March 5, 2026 04:04
@shocknet-justin shocknet-justin marked this pull request as ready for review March 5, 2026 04:05
@shocknet-justin shocknet-justin merged commit a5c8b2d into shocknet:pr/feat-extension-loader Mar 5, 2026
2 checks passed
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.

2 participants