padreug opened issue aiolabs/lightning-pub#13

padreug
2026-04-01T16:56:20Z

13#Extension: Split Payments#

Summary

Implement a Nostr-native split payments extension for Lightning.Pub, replacing the LNbits splitpayments extension. Automatically distribute a percentage of every incoming payment to one or more recipients.

What LNbits splitpayments Does

Extremely simple extension (~400 lines of Python total):

1. User configures a list of targets for a source wallet — each target has a wallet/LNURL/Lightning Address and a percentage (must sum to ≤100%)

2. Background task listens for incoming payments on the source wallet

3. On each payment received, automatically splits the configured percentages to each target:

Internal wallets → feeless internal transfer

LNURL/Lightning Address → pays external invoice (deducting fee reserve)

4. Remaining percentage stays in the source wallet

5. Splits are tagged with splitted: true in payment extras to prevent infinite recursion

Data model — just one table:

Target {
  id: string
  wallet: string        // wallet ID, LNURL, or Lightning Address
  source: string        // source wallet ID
  percent: float        // 0-100
  alias: string | null  // display name
}

API — just 3 endpoints:

GET /api/v1/targets — list targets for wallet

PUT /api/v1/targets — set targets (replaces all)

DELETE /api/v1/targets — clear all targets

Lightning.Pub Implementation

ExtensionContext API Usage

API | Use
onPaymentReceived() | Listen for incoming payments to trigger splits
payInvoice() | Pay split targets (external recipients)
createInvoice() | Create internal invoices for LP-internal recipients
registerMethod() | Register RPC methods for target management
getDatabase() | Store target configurations
log() | Log split operations

RPC Methods

splitpay.setTargets      // Configure split targets for an app
splitpay.getTargets      // List current targets
splitpay.clearTargets    // Remove all targets
splitpay.getHistory      // View split payment history

Data Model

Target {
  id: string
  application_id: string    // which LP app this applies to
  recipient: string         // Nostr pubkey, LNURL, or Lightning Address
  percent: number           // 0-100
  alias: string | null
  creator_pubkey: string    // who configured this split
}

SplitRecord {
  id: string
  source_payment_hash: string
  target_id: string
  amount_sats: number
  payment_hash: string
  status: 'pending' | 'success' | 'failed'
  created_at: number
}

Core Logic

onPaymentReceived(payment):
  if payment.metadata?.splitted → return (prevent recursion)
  targets = db.getTargets(applicationId)
  if !targets or sum(percents) > 100 → return

  for each target:
    amount = payment.amountSats * target.percent / 100
    if target is nostr pubkey → payInvoice via CLINK/keysend
    if target is LNURL/address → resolve LNURL, payInvoice
    record split in history

Complexity

VERY LOW — ~300-500 lines of TypeScript. The simplest possible extension. Could be implemented in a few hours.

The entire LNbits extension is ~400 lines of Python across 4 files. The LP version would be comparable or smaller since the ExtensionContext handles payment plumbing.

Nostr-Native Advantages

LNbits | Lightning.Pub
Targets identified by wallet ID, LNURL, or Lightning Address | Targets can also be Nostr pubkeys — resolve via NIP-57 zap tag or CLINK
HTTP API with wallet admin key | Nostr RPC — configure splits from any Nostr client
No payment proof | Split receipts as signed events in history
Isolated to one LNbits instance | Works across any Lightning.Pub via Nostr

NIP Review

NIP-57: Zap Tags — NATIVE SPLIT STANDARD

NIP-57 Appendix G already defines a zap tag split mechanism on Nostr events:

{
  "tags": [
    ["zap", "<pubkey-1>", "wss://relay", "1"],  // 25%
    ["zap", "<pubkey-2>", "wss://relay", "1"],  // 25%
    ["zap", "<pubkey-3>", "wss://relay", "2"]   // 50%
  ]
}

Weights are relative (not percentages) — clients sum all weights and calculate each recipient's share. This is the existing Nostr standard for split payments.

Implication: The extension could optionally publish a kind 30078 event with zap tags representing the split configuration, making it interoperable with any NIP-57 compliant wallet. Wallets zapping the LP user would automatically split according to the tags.

Other Applicable NIPs

NIP | Use | Priority
NIP-57 (Zaps) | zap tag weights for split configuration; zap receipts as split proof | Core
NIP-78 (App Data) | Store split config as addressable event (kind 30078) | Recommended
NIP-47 (NWC) | pay_keysend for direct pubkey payments without invoice | Optional
NIP-44 (Encryption) | Encrypt split config if recipient list is sensitive | Optional
NIP-17 (DMs) | Notify recipients of incoming splits | Nice-to-have

Recommended Architecture

The simplest path: implement splits purely in the extension using onPaymentReceived() + payInvoice(). Optionally publish a NIP-57 zap tag event so external wallets also respect the splits when zapping.

Reference

LNbits splitpayments: https://github.com/lnbits/splitpayments

NIP-57 Appendix G (zap tag splits): nips/57.md

Existing withdraw extension as implementation reference: lightning-pub/withdraw/