Extension: Satoshi Machine (DCA Distribution) #11

Open
opened 2026-04-01 16:39:53 +00:00 by padreug · 2 comments
Owner

Summary

Implement a Nostr-native DCA (Dollar Cost Averaging) distribution extension for Lightning.Pub, replacing the LNbits satmachineadmin + satmachineclient extensions with a single extension that uses Nostr events instead of SSH database polling.

Motivation

The current LNbits DCA system works by SSH-tunneling into a Lamassu ATM's PostgreSQL database to poll for cash-out transactions, then distributing BTC proportionally to registered clients. This is fragile (SSH tunnels, credential management, hourly polling) and fundamentally HTTP-dependent.

In the lamassu-next architecture, the ATM already communicates via Nostr (kind 30079 transaction records). A Lightning.Pub extension can subscribe to these events in real-time instead of polling a remote database.

Architecture Comparison

LNbits Approach Lightning.Pub Approach
SSH tunnel to Lamassu PostgreSQL Subscribe to Nostr kind 30079 transaction events
HTTP API with wallet keys Nostr RPC methods
Separate admin + client extensions Single extension, role by pubkey
Hourly polling task Real-time Nostr event subscription via onNostrEvent()
LNbits internal payments payInvoice() through ExtensionContext

Scope

RPC Methods:

satmachine.registerClient       satmachine.listClients
satmachine.createDeposit        satmachine.confirmDeposit
satmachine.getBalance           satmachine.getTransactions
satmachine.triggerDistribution  satmachine.getConfig
satmachine.updateConfig         satmachine.exportHistory

Core Logic (ports from LNbits):

  • Commission calculation engine: base_amount = total_amount / (1 + effective_commission)
  • Flow mode: proportional distribution based on client balance ratios
  • Fixed mode: daily limit-based allocation
  • Complete audit trail of all transactions and distributions

Data Model:

  • DcaClient — pubkey, identifier, mode (flow/fixed), daily_limit, status
  • DcaDeposit — client_id, fiat_amount, fiat_currency, status (pending/confirmed), confirmed_at
  • DcaPayment — client_id, amount_sats, payment_hash, triggered_by (transaction event ID)
  • DcaConfig — commission_percentage, discount, distribution_mode, source settings
  • ProcessedTransaction — nostr_event_id, crypto_atoms, fiat_amount, processed_at

ExtensionContext API usage:

  • onNostrEvent() to subscribe to ATM transaction events (kind 30079)
  • payInvoice() for distributing sats to clients
  • createInvoice() for deposit collection
  • onPaymentReceived() to confirm deposits
  • sendEncryptedDM() for notifying clients of distributions
  • registerMethod() for all operations

Complexity

MEDIUM — ~2000-3000 lines of TypeScript. 3-5 days estimated.

The commission math and distribution logic are non-trivial but well-defined. The biggest win is replacing SSH polling with Nostr event subscription.

Strategic Value

This closes the loop on the ATM ecosystem: lamassu-next ATM → Nostr → Lightning.Pub → DCA extension → client wallets. No LNbits, no SSH tunnels, no HTTP APIs in the critical path.

Reference

  • LNbits satmachineadmin: lnbits/extensions/satmachineadmin/
  • LNbits satmachineclient: lnbits/extensions/satmachineclient/
  • lamassu-next transaction records: kind 30079 events
  • Commission calculation: satmachineadmin/transaction_processor.py
## Summary Implement a Nostr-native DCA (Dollar Cost Averaging) distribution extension for Lightning.Pub, replacing the LNbits `satmachineadmin` + `satmachineclient` extensions with a single extension that uses Nostr events instead of SSH database polling. ## Motivation The current LNbits DCA system works by SSH-tunneling into a Lamassu ATM's PostgreSQL database to poll for cash-out transactions, then distributing BTC proportionally to registered clients. This is fragile (SSH tunnels, credential management, hourly polling) and fundamentally HTTP-dependent. In the lamassu-next architecture, the ATM already communicates via Nostr (kind 30079 transaction records). A Lightning.Pub extension can subscribe to these events in real-time instead of polling a remote database. ## Architecture Comparison | LNbits Approach | Lightning.Pub Approach | |----------------|----------------------| | SSH tunnel to Lamassu PostgreSQL | Subscribe to Nostr kind 30079 transaction events | | HTTP API with wallet keys | Nostr RPC methods | | Separate admin + client extensions | Single extension, role by pubkey | | Hourly polling task | Real-time Nostr event subscription via `onNostrEvent()` | | LNbits internal payments | `payInvoice()` through ExtensionContext | ## Scope **RPC Methods:** ``` satmachine.registerClient satmachine.listClients satmachine.createDeposit satmachine.confirmDeposit satmachine.getBalance satmachine.getTransactions satmachine.triggerDistribution satmachine.getConfig satmachine.updateConfig satmachine.exportHistory ``` **Core Logic (ports from LNbits):** - Commission calculation engine: `base_amount = total_amount / (1 + effective_commission)` - Flow mode: proportional distribution based on client balance ratios - Fixed mode: daily limit-based allocation - Complete audit trail of all transactions and distributions **Data Model:** - `DcaClient` — pubkey, identifier, mode (flow/fixed), daily_limit, status - `DcaDeposit` — client_id, fiat_amount, fiat_currency, status (pending/confirmed), confirmed_at - `DcaPayment` — client_id, amount_sats, payment_hash, triggered_by (transaction event ID) - `DcaConfig` — commission_percentage, discount, distribution_mode, source settings - `ProcessedTransaction` — nostr_event_id, crypto_atoms, fiat_amount, processed_at **ExtensionContext API usage:** - `onNostrEvent()` to subscribe to ATM transaction events (kind 30079) - `payInvoice()` for distributing sats to clients - `createInvoice()` for deposit collection - `onPaymentReceived()` to confirm deposits - `sendEncryptedDM()` for notifying clients of distributions - `registerMethod()` for all operations ## Complexity **MEDIUM** — ~2000-3000 lines of TypeScript. 3-5 days estimated. The commission math and distribution logic are non-trivial but well-defined. The biggest win is replacing SSH polling with Nostr event subscription. ## Strategic Value This closes the loop on the ATM ecosystem: lamassu-next ATM → Nostr → Lightning.Pub → DCA extension → client wallets. No LNbits, no SSH tunnels, no HTTP APIs in the critical path. ## Reference - LNbits satmachineadmin: `lnbits/extensions/satmachineadmin/` - LNbits satmachineclient: `lnbits/extensions/satmachineclient/` - lamassu-next transaction records: kind 30079 events - Commission calculation: `satmachineadmin/transaction_processor.py`
Author
Owner

NIP Review: Applicable Standards for Satoshi Machine Extension

Core NIPs to Implement

NIP-47: Nostr Wallet Connect (NWC) — PAYMENT ENGINE

Kinds: 13194 (info), 23194 (request), 23195 (response), 23197 (notification)

The backbone for DCA distribution payments. Relevant methods:

NWC Method DCA Use
pay_invoice Execute distribution payments to clients
make_invoice Generate invoices for fiat deposit collection
get_balance Verify sufficient funds before distribution
list_transactions Audit trail of all distributions
pay_keysend Direct pubkey payment with TLV records (no invoice needed)

pay_keysend is particularly interesting — DCA distributions to registered clients (known pubkeys) could bypass invoice generation entirely.

The metadata field (up to 4KB) stores distribution context: client ID, deposit reference, commission breakdown, batch ID.

Key insight: NWC provides the authorization layer. The DCA extension can be a lightweight orchestrator that issues NWC requests for each distribution payment, with Lightning.Pub handling the actual settlement.

NIP-78: Application-Specific Data — CONFIG & STATE

Kind: 30078 (addressable, replaceable)

Store DCA configuration as encrypted addressable events:

{
  "kind": 30078,
  "tags": [["d", "satmachine-config"]],
  "content": "nip44_encrypt({
    'commission_percentage': 0.03,
    'discount': 0,
    'distribution_mode': 'flow',
    'atm_pubkey': '<lamassu-next-pubkey>',
    'poll_interval': 3600
  })"
}

Also use for client registry, distribution history snapshots, and batch state recovery after restart.

NIP-44: Versioned Encryption — REQUIRED

All financial data (configs, balances, distribution records) encrypted with NIP-44 (ChaCha20 + HMAC-SHA256 + HKDF). Non-negotiable for DCA where amounts and client identities are sensitive.

Supplementary NIPs

NIP Kind Use Case Priority
NIP-17 (Private DMs) 14, 1059, 13 Notify clients of distributions via sealed gift-wrapped messages Recommended
NIP-57 (Zaps) 9735 Zap receipts as secondary payment proof / audit trail Optional
NIP-51 (Lists) 30000 Manage recipient cohorts as named follow sets Optional
NIP-40 (Expiration) Time-limited DCA campaigns with auto-expiry Nice-to-have
NIP-75 (Zap Goals) 9041 DCA savings targets with progress tracking Low priority
NIP-60 (Cashu) 7375, 7376 Offline ecash distribution alternative Future exploration

Architecture Using NIPs

DCA Distribution Flow:

  1. ATM publishes transaction event (kind 30079) via Nostr
  2. Extension receives via onNostrEvent() subscription
  3. Extension loads config from kind 30078 event
  4. Commission calculated, client shares determined
  5. For each client: issue NWC pay_keysend (kind 23194) or pay_invoice
  6. On NWC response (kind 23195): record in local DB, update client balance
  7. Notify client via NIP-17 sealed DM (kind 14 → 13 → 1059)
  8. Update config/state event (kind 30078) with last processed transaction

What's NOT in any NIP:

  • No standard for scheduled/recurring payments — the extension must implement its own scheduler
  • No standard financial ledger event kind — recommend a custom kind or use NIP-78 with structured content
  • No subscription/recurring authorization model beyond NIP-47's per-request approval

Recommendation: Use NWC (NIP-47) as the payment primitive, NIP-78 for state, NIP-17 for notifications, NIP-44 for encryption. The DCA scheduling logic lives entirely in the extension — NIPs provide the communication and payment rails.

## NIP Review: Applicable Standards for Satoshi Machine Extension ### Core NIPs to Implement #### NIP-47: Nostr Wallet Connect (NWC) — PAYMENT ENGINE **Kinds:** 13194 (info), 23194 (request), 23195 (response), 23197 (notification) The backbone for DCA distribution payments. Relevant methods: | NWC Method | DCA Use | |-----------|---------| | `pay_invoice` | Execute distribution payments to clients | | `make_invoice` | Generate invoices for fiat deposit collection | | `get_balance` | Verify sufficient funds before distribution | | `list_transactions` | Audit trail of all distributions | | `pay_keysend` | Direct pubkey payment with TLV records (no invoice needed) | `pay_keysend` is particularly interesting — DCA distributions to registered clients (known pubkeys) could bypass invoice generation entirely. The `metadata` field (up to 4KB) stores distribution context: client ID, deposit reference, commission breakdown, batch ID. **Key insight:** NWC provides the authorization layer. The DCA extension can be a lightweight orchestrator that issues NWC requests for each distribution payment, with Lightning.Pub handling the actual settlement. #### NIP-78: Application-Specific Data — CONFIG & STATE **Kind:** 30078 (addressable, replaceable) Store DCA configuration as encrypted addressable events: ```json { "kind": 30078, "tags": [["d", "satmachine-config"]], "content": "nip44_encrypt({ 'commission_percentage': 0.03, 'discount': 0, 'distribution_mode': 'flow', 'atm_pubkey': '<lamassu-next-pubkey>', 'poll_interval': 3600 })" } ``` Also use for client registry, distribution history snapshots, and batch state recovery after restart. #### NIP-44: Versioned Encryption — REQUIRED All financial data (configs, balances, distribution records) encrypted with NIP-44 (ChaCha20 + HMAC-SHA256 + HKDF). Non-negotiable for DCA where amounts and client identities are sensitive. ### Supplementary NIPs | NIP | Kind | Use Case | Priority | |-----|------|----------|----------| | **NIP-17** (Private DMs) | 14, 1059, 13 | Notify clients of distributions via sealed gift-wrapped messages | Recommended | | **NIP-57** (Zaps) | 9735 | Zap receipts as secondary payment proof / audit trail | Optional | | **NIP-51** (Lists) | 30000 | Manage recipient cohorts as named follow sets | Optional | | **NIP-40** (Expiration) | — | Time-limited DCA campaigns with auto-expiry | Nice-to-have | | **NIP-75** (Zap Goals) | 9041 | DCA savings targets with progress tracking | Low priority | | **NIP-60** (Cashu) | 7375, 7376 | Offline ecash distribution alternative | Future exploration | ### Architecture Using NIPs **DCA Distribution Flow:** 1. ATM publishes transaction event (kind 30079) via Nostr 2. Extension receives via `onNostrEvent()` subscription 3. Extension loads config from kind 30078 event 4. Commission calculated, client shares determined 5. For each client: issue NWC `pay_keysend` (kind 23194) or `pay_invoice` 6. On NWC response (kind 23195): record in local DB, update client balance 7. Notify client via NIP-17 sealed DM (kind 14 → 13 → 1059) 8. Update config/state event (kind 30078) with last processed transaction **What's NOT in any NIP:** - No standard for scheduled/recurring payments — the extension must implement its own scheduler - No standard financial ledger event kind — recommend a custom kind or use NIP-78 with structured content - No subscription/recurring authorization model beyond NIP-47's per-request approval **Recommendation:** Use NWC (NIP-47) as the payment primitive, NIP-78 for state, NIP-17 for notifications, NIP-44 for encryption. The DCA scheduling logic lives entirely in the extension — NIPs provide the communication and payment rails.
Author
Owner

Superseded by #15

The original DCA approach (SSH polling Lamassu PostgreSQL, hourly cron, separate admin/client extensions) has been redesigned as a Nostr-native Cash Provider system in #15.

Key differences:

  • Providers fund ATMs with fiat and receive BTC as customers do cash-out — replacing the deposit/distribution model
  • Real-time Nostr events (kind 30079) replace SSH tunnel polling
  • Commission splits tie into the Split Payments extension (#13) using NIP-57 weights
  • Single extension with role-based access (provider vs operator) instead of two separate extensions
  • Provider dashboard via encrypted RPC queries from any Nostr client

The commission calculation logic and proportional distribution math from the original satmachineadmin still apply — they're carried forward into #15's settlement engine.

Keeping this issue open as reference for the original LNbits implementation details and the NIP review comment.

## Superseded by #15 The original DCA approach (SSH polling Lamassu PostgreSQL, hourly cron, separate admin/client extensions) has been redesigned as a Nostr-native **Cash Provider** system in #15. Key differences: - **Providers fund ATMs with fiat** and receive BTC as customers do cash-out — replacing the deposit/distribution model - **Real-time Nostr events** (kind 30079) replace SSH tunnel polling - **Commission splits** tie into the Split Payments extension (#13) using NIP-57 weights - **Single extension** with role-based access (provider vs operator) instead of two separate extensions - **Provider dashboard** via encrypted RPC queries from any Nostr client The commission calculation logic and proportional distribution math from the original satmachineadmin still apply — they're carried forward into #15's settlement engine. Keeping this issue open as reference for the original LNbits implementation details and the NIP review comment.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: aiolabs/lightning-pub#11
No description provided.