Automatically distributes a percentage of every incoming payment to
one or more recipients. Supports Nostr pubkeys, LNURL, and Lightning
Addresses as targets.
Dual-layer architecture:
- NIP-57 zap tags (preferred): Nostr wallets split at sender side
- Internal splits (fallback): onPaymentReceived for non-Nostr payments
- Recursion guard (metadata.splitted) prevents double-splitting
RPC methods:
- splitpay.setTargets: configure split recipients and percentages
- splitpay.getTargets: list current configuration
- splitpay.clearTargets: remove all targets
- splitpay.getHistory: view split payment audit trail
Closes#13
Co-Authored-By: Claude Opus 4.6 (1M context) <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>
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>
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>
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>
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>
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 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>
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>
When the payment index advances (e.g. after an LND restart or external
payment), update the cached offset instead of immediately locking.
Only lock if both a history mismatch AND a balance discrepancy are
detected — indicating a real security concern rather than a benign
LND restart.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Warmup() previously only checked that LND responded to GetInfo(), but
did not verify syncedToChain/syncedToGraph. This caused LP to accept
requests while LND was still syncing, leading to "not synced" errors
on every Health() check. Now waits for full sync with a 10min timeout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each sendEvent() call created a new SimplePool() but never closed it,
causing relay WebSocket connections to accumulate indefinitely (~20/min).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update all NostrSend call sites to properly handle the async nature
of the function now that it returns Promise<void>.
Changes:
- handler.ts: Add async to sendResponse, await nostrSend calls
- debitManager.ts: Add logging for Kind 21002 response sending
- nostrMiddleware.ts: Update nostrSend signature
- tlvFilesStorageProcessor.ts: Update nostrSend signature
- webRTC/index.ts: Add async/await for nostrSend calls
This ensures Kind 21002 (ndebit) responses are properly sent to
wallet clients, fixing the "Debit request failed" issue in ShockWallet.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The NostrSend type was incorrectly typed as returning void when it actually
returns Promise<void>. This caused async errors to be silently swallowed.
Changes:
- Update NostrSend type signature to return Promise<void>
- Make NostrSender._nostrSend default to async function
- Add .catch() error handling in NostrSender.Send() to log failures
- Add logging to track event publishing status
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>