Replace LNbits backend with Lightning.Pub #16

Open
opened 2026-02-23 13:50:10 +00:00 by padreug · 0 comments
Owner

Summary

Migrate the payment backend from LNbits to Lightning.Pub, matching how ShockWallet integrates. This enables Nostr-native authentication (keypair instead of username/password) and aligns the app with the same Lightning infrastructure used by the ATM and other aiolabs projects.

Motivation

  • Unified backend: The ATM (lamassu-next) already uses Lightning.Pub. Running LNbits as a separate service adds operational complexity.
  • Nostr-native auth: Lightning.Pub authenticates via Nostr keypairs — no passwords to manage, and identity is portable across the Nostr ecosystem.
  • Feature parity with ShockWallet: Lightning.Pub supports noffer (Nostr offers), ndebit (Nostr debit authorizations), and programmatic app user management — none of which are available in LNbits.

Current Architecture (LNbits)

Layer Implementation
Auth Username/password → JWT via POST /api/v1/auth
Create invoice POST /api/v1/payments with X-Api-Key: {adminkey}
Pay invoice POST /api/v1/payments with { out: true }
Balance WebSocket wss://lnbits/api/v1/ws/{inkey}
Pay links LNURLP extension POST /lnurlp/api/v1/links

Target Architecture (Lightning.Pub)

Layer Implementation
Auth Nostr keypair — pubkey is identity, JWT from POST /api/user/auth
Create invoice POST /api/user/invoice/new{ amountSats, memo }
Pay invoice POST /api/user/invoice/pay{ invoice, amount }
Balance GET /api/user/info{ balance, max_withdrawable }
Receive address noffer (Nostr offer) replaces LNURLP pay links

Lightning.Pub also supports Nostr-based RPC (NIP-44 encrypted events) as a transport, but the HTTP API is sufficient for the initial migration.

Implementation Plan

Phase 1: API Client + Auth (foundation)

New file: src/lib/api/lightning-pub.ts

  • HTTP client for Lightning.Pub REST API
  • Reference: shock-wallet/main/src/Api/pub/autogenerated/ts/http_client.ts
  • Methods: newInvoice(), payInvoice(), getUserInfo(), payAddress()
  • Auth: Bearer token in Authorization header

Modify: src/modules/base/auth/auth-service.ts

  • Replace username/password login with Nostr keypair generation/import
  • Generate keypair on first launch, store in localStorage
  • Login flow: send pubkey to Lightning.Pub → receive JWT
  • nostr-tools is already a dependency — use generateSecretKey() / getPublicKey()

Modify: src/app.config.ts

  • Replace VITE_LNBITS_BASE_URLVITE_LIGHTNING_PUB_API_URL
  • Add VITE_APP_ID (Lightning.Pub app identifier)
  • Add VITE_LIGHTNING_PUB_PUBKEY (server's Nostr pubkey for encryption)

Phase 2: Wallet Service (payments)

Rewrite: src/modules/wallet/services/WalletService.ts

  • createInvoice(amount, memo)client.newInvoice({ amountSats, memo })
  • sendPayment(invoice)client.payInvoice({ invoice, amount })
  • getBalance()client.getUserInfo().balance
  • Drop LNURLP extension calls (replaced by noffer in future)
  • Transaction history: GET /api/user/operations

Rewrite: src/modules/wallet/services/WalletWebSocketService.ts

  • Lightning.Pub has no LNbits-style WebSocket
  • Option A: Poll getUserInfo() on interval (the existing fallbackToPolling path)
  • Option B: Subscribe to Nostr relay for payment notification events
  • The existing VisibilityService integration and reconnection logic stays

Phase 3: Clean up

  • Remove src/lib/api/lnbits.ts and src/lib/config/lnbits.ts
  • Update PaymentService.ts if any LNbits-specific logic leaked in
  • Update .env.example with new env vars
  • Update wallet UI components if field names changed (e.g., balance_msatbalance in sats)

What stays the same

  • All Vue components/views (they use services via DI, don't reference LNbits directly)
  • PaymentService orchestration layer (delegates to WalletService)
  • QR code generation, BOLT11 parsing (light-bolt11-decoder)
  • Nostr modules (chat, feed, marketplace) — already Nostr-native
  • Electron/PWA setup
  • Shadcn/UI components, i18n, routing

API Reference

Lightning.Pub HTTP endpoints (from protobuf definitions):

POST /api/user/auth                    - Authenticate with Nostr keypair
POST /api/user/invoice/new             - Create BOLT11 invoice
POST /api/user/invoice/pay             - Pay BOLT11 invoice
POST /api/user/address/pay             - Pay Lightning address
GET  /api/user/info                    - Get balance, max_withdrawable, noffer, etc.
GET  /api/user/operations              - Transaction history
POST /api/user/invoice/decode          - Decode BOLT11 invoice
GET  /api/user/lnurl/pay               - LNURL-pay info
POST /api/user/lnurl/withdraw/handle   - Handle LNURL-withdraw

Estimate

~500 lines of new code across 5 files. The modular DI architecture makes this a clean swap — service implementations change but interfaces stay the same.

Risks

  • Auth UX change: Moving from username/password to Nostr keypair changes the login experience. Need to handle key backup/export clearly.
  • No WebSocket: Balance updates will be polling-based unless we add Nostr relay subscriptions. Acceptable for now — the existing polling fallback path already works.
  • Lightning.Pub API stability: The HTTP API is auto-generated from protobuf — verify endpoint paths against the running instance before implementing.
## Summary Migrate the payment backend from LNbits to Lightning.Pub, matching how ShockWallet integrates. This enables Nostr-native authentication (keypair instead of username/password) and aligns the app with the same Lightning infrastructure used by the ATM and other aiolabs projects. ## Motivation - **Unified backend**: The ATM (`lamassu-next`) already uses Lightning.Pub. Running LNbits as a separate service adds operational complexity. - **Nostr-native auth**: Lightning.Pub authenticates via Nostr keypairs — no passwords to manage, and identity is portable across the Nostr ecosystem. - **Feature parity with ShockWallet**: Lightning.Pub supports `noffer` (Nostr offers), `ndebit` (Nostr debit authorizations), and programmatic app user management — none of which are available in LNbits. ## Current Architecture (LNbits) | Layer | Implementation | |-------|---------------| | Auth | Username/password → JWT via `POST /api/v1/auth` | | Create invoice | `POST /api/v1/payments` with `X-Api-Key: {adminkey}` | | Pay invoice | `POST /api/v1/payments` with `{ out: true }` | | Balance | WebSocket `wss://lnbits/api/v1/ws/{inkey}` | | Pay links | LNURLP extension `POST /lnurlp/api/v1/links` | ## Target Architecture (Lightning.Pub) | Layer | Implementation | |-------|---------------| | Auth | Nostr keypair — pubkey is identity, JWT from `POST /api/user/auth` | | Create invoice | `POST /api/user/invoice/new` → `{ amountSats, memo }` | | Pay invoice | `POST /api/user/invoice/pay` → `{ invoice, amount }` | | Balance | `GET /api/user/info` → `{ balance, max_withdrawable }` | | Receive address | `noffer` (Nostr offer) replaces LNURLP pay links | Lightning.Pub also supports Nostr-based RPC (NIP-44 encrypted events) as a transport, but the HTTP API is sufficient for the initial migration. ## Implementation Plan ### Phase 1: API Client + Auth (foundation) **New file: `src/lib/api/lightning-pub.ts`** - HTTP client for Lightning.Pub REST API - Reference: `shock-wallet/main/src/Api/pub/autogenerated/ts/http_client.ts` - Methods: `newInvoice()`, `payInvoice()`, `getUserInfo()`, `payAddress()` - Auth: Bearer token in `Authorization` header **Modify: `src/modules/base/auth/auth-service.ts`** - Replace username/password login with Nostr keypair generation/import - Generate keypair on first launch, store in localStorage - Login flow: send pubkey to Lightning.Pub → receive JWT - `nostr-tools` is already a dependency — use `generateSecretKey()` / `getPublicKey()` **Modify: `src/app.config.ts`** - Replace `VITE_LNBITS_BASE_URL` → `VITE_LIGHTNING_PUB_API_URL` - Add `VITE_APP_ID` (Lightning.Pub app identifier) - Add `VITE_LIGHTNING_PUB_PUBKEY` (server's Nostr pubkey for encryption) ### Phase 2: Wallet Service (payments) **Rewrite: `src/modules/wallet/services/WalletService.ts`** - `createInvoice(amount, memo)` → `client.newInvoice({ amountSats, memo })` - `sendPayment(invoice)` → `client.payInvoice({ invoice, amount })` - `getBalance()` → `client.getUserInfo().balance` - Drop LNURLP extension calls (replaced by `noffer` in future) - Transaction history: `GET /api/user/operations` **Rewrite: `src/modules/wallet/services/WalletWebSocketService.ts`** - Lightning.Pub has no LNbits-style WebSocket - Option A: Poll `getUserInfo()` on interval (the existing `fallbackToPolling` path) - Option B: Subscribe to Nostr relay for payment notification events - The existing VisibilityService integration and reconnection logic stays ### Phase 3: Clean up - Remove `src/lib/api/lnbits.ts` and `src/lib/config/lnbits.ts` - Update `PaymentService.ts` if any LNbits-specific logic leaked in - Update `.env.example` with new env vars - Update wallet UI components if field names changed (e.g., `balance_msat` → `balance` in sats) ## What stays the same - All Vue components/views (they use services via DI, don't reference LNbits directly) - `PaymentService` orchestration layer (delegates to WalletService) - QR code generation, BOLT11 parsing (`light-bolt11-decoder`) - Nostr modules (chat, feed, marketplace) — already Nostr-native - Electron/PWA setup - Shadcn/UI components, i18n, routing ## API Reference **Lightning.Pub HTTP endpoints** (from protobuf definitions): ``` POST /api/user/auth - Authenticate with Nostr keypair POST /api/user/invoice/new - Create BOLT11 invoice POST /api/user/invoice/pay - Pay BOLT11 invoice POST /api/user/address/pay - Pay Lightning address GET /api/user/info - Get balance, max_withdrawable, noffer, etc. GET /api/user/operations - Transaction history POST /api/user/invoice/decode - Decode BOLT11 invoice GET /api/user/lnurl/pay - LNURL-pay info POST /api/user/lnurl/withdraw/handle - Handle LNURL-withdraw ``` ## Estimate ~500 lines of new code across 5 files. The modular DI architecture makes this a clean swap — service implementations change but interfaces stay the same. ### Risks - **Auth UX change**: Moving from username/password to Nostr keypair changes the login experience. Need to handle key backup/export clearly. - **No WebSocket**: Balance updates will be polling-based unless we add Nostr relay subscriptions. Acceptable for now — the existing polling fallback path already works. - **Lightning.Pub API stability**: The HTTP API is auto-generated from protobuf — verify endpoint paths against the running instance before implementing.
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/webapp#16
No description provided.