diff --git a/lamassu-next/CLAUDE.md b/lamassu-next/CLAUDE.md index 4aacac4..fb37e96 100644 --- a/lamassu-next/CLAUDE.md +++ b/lamassu-next/CLAUDE.md @@ -81,9 +81,19 @@ infra-logs # Follow service logs # Bitcoin/Lightning (regtest) btccli # Bitcoin CLI -lncli # LND CLI +lncli # LND CLI (Lightning.Pub's node) +lncli-alice # LND CLI (Alice's node for testing payments) mine-blocks # Mine regtest blocks (default: 1) +setup-channel # Setup channel between Alice and LND +alice-pay # Pay invoice from Alice's node relay-test # Test Nostr relay connection + +# Testing (E2E) +test-setup # Validate test environment (services, channels, payments) +test-payment # Quick e2e payment test (ATM → customer) +fund-atm # Fund ATM account (default: 100k sats) +alice-invoice # Create invoice on Alice's node +node-info # Show node pubkeys and channel info ``` ## Development Infrastructure @@ -282,7 +292,12 @@ When porting: ## Related Documentation -- `../docs/implementation-plan.md` - Full implementation roadmap -- `../docs/nostr-native-architecture.md` - Architecture details -- `../docs/architecture-review.md` - KYC-free vision -- `.claude/skills/*.md` - Skill documentation +- `packages/lightning/TROUBLESHOOTING.md` - Lightning.Pub integration gotchas (must read!) +- `.claude/skills/*.md` - Custom skill documentation + +## External Resources + +- [CLINK Protocol Spec](https://github.com/shocknet/clink) +- [Lightning.Pub](https://github.com/shocknet/Lightning.Pub) +- [NIP-44 Encryption](https://github.com/nostr-protocol/nips/blob/master/44.md) +- [LND Hold Invoices](https://docs.lightning.engineering/lightning-network-tools/lnd/hold-invoices) diff --git a/lamassu-next/docs/future-features.md b/lamassu-next/docs/future-features.md deleted file mode 100644 index 0bc7a59..0000000 --- a/lamassu-next/docs/future-features.md +++ /dev/null @@ -1,352 +0,0 @@ -# Future Features Roadmap - -This document outlines advanced features planned for Lamassu Next that leverage the Nostr-native architecture. - -## 1. Public Cash Availability Display - -**Status**: Fully Achievable -**Complexity**: Low -**Dependencies**: Service beacon (kind 30078) - -### Overview - -A public website or app can display real-time cash availability for ATMs without requiring any special authentication. - -### Technical Approach - -1. **Machine publishes beacon** (replaceable event kind 30078): - - ```json - { - "kind": 30078, - "tags": [ - ["d", "machine-status"], - ["location", "40.7128,-74.0060"], - ["currency", "USD"] - ], - "content": { - "cassettes": [ - { "denomination": 20, "count": 45 }, - { "denomination": 50, "count": 20 } - ], - "totalCash": 1900, - "status": "online", - "lastDispense": "2026-01-24T10:30:00Z" - } - } - ``` - -2. **Website subscribes** to relay: - - ```javascript - ["REQ", "availability", {"kinds": [30078], "authors": []}] - ``` - -3. **Real-time updates**: Replaceable events update in place, website gets live data. - -### Privacy Considerations - -- Location precision can be reduced (city-level vs exact coordinates) -- Operators choose what to publish (can omit exact counts) -- Machine identity is pseudonymous (pubkey only) - -### Implementation Notes - -- No encryption needed (public data) -- Works with any public relay -- Multiple machines can publish to same relay for fleet view -- Dashboard aggregates for operator, public website shows customer view - ---- - -## 2. Remote Initiation, Local Redemption - -**Status**: Fully Achievable -**Complexity**: Medium -**Dependencies**: CLINK protocol (kinds 21001-21003), signed claims - -### Overview - -Users can initiate a cash-out transaction remotely (from their phone/wallet) and redeem it physically at the ATM later. - -### User Flow - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ REMOTE INITIATION │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. User opens wallet app │ -│ └─> Selects "Cash Out at ATM" │ -│ │ -│ 2. Wallet shows available ATMs (from beacons) │ -│ └─> User selects ATM and amount ($100) │ -│ │ -│ 3. Wallet sends CLINK request to ATM via relay │ -│ └─> kind 21002 (debit request) │ -│ │ -│ 4. ATM responds with signed claim │ -│ └─> "Bearer of this claim can redeem $100" │ -│ └─> Includes: amount, expiry, ATM signature, claim_id │ -│ │ -│ 5. User pays Lightning invoice (from wallet balance) │ -│ └─> Payment settles instantly │ -│ │ -│ 6. Wallet stores claim locally │ -│ └─> Claim is valid for 24 hours │ -│ │ -└─────────────────────────────────────────────────────────────────┘ - -┌─────────────────────────────────────────────────────────────────┐ -│ LOCAL REDEMPTION │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ 7. User arrives at ATM (minutes to hours later) │ -│ └─> Taps "Redeem" on ATM screen │ -│ │ -│ 8. ATM displays QR code for claim submission │ -│ └─> Wallet scans and sends signed claim │ -│ │ -│ 9. ATM verifies claim │ -│ └─> Checks signature (did I sign this?) │ -│ └─> Checks expiry (still valid?) │ -│ └─> Checks claim_id (not already redeemed?) │ -│ │ -│ 10. ATM dispenses cash │ -│ └─> Marks claim as redeemed (local DB + Nostr) │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### Claim Structure - -```typescript -interface RedemptionClaim { - version: 1 - claim_id: string // Unique identifier - machine_pubkey: string // ATM that will honor this - amount_cents: number // Fiat amount to dispense - currency: string // "USD" - created_at: number // Unix timestamp - expires_at: number // Unix timestamp (24h default) - payment_preimage: string // Proof of payment - user_pubkey?: string // Optional: restrict to specific user - signature: string // ATM's signature over above fields -} -``` - -### Security Model - -1. **Claim is bearer token**: Whoever presents valid claim gets cash -2. **Single-use**: claim_id tracked to prevent double-redemption -3. **Time-limited**: Expires after configurable window -4. **User-binding** (optional): Can restrict to specific npub -5. **Offline-capable**: ATM can verify signature without network - -### State Sync - -- Redeemed claims published to relay (for dashboard visibility) -- Claims stored locally with SQLite for offline redemption -- Periodic sync ensures consistency - -### Implementation Notes - -- Claim fits in QR code (< 2KB when base64 encoded) -- Works offline after initial payment (ATM doesn't need network to verify) -- Natural fit with Cashu tokens (future enhancement) - ---- - -## 3. Hold Invoices for Safe Dispensing - -**Status**: Achievable with Hybrid Approach -**Complexity**: High -**Dependencies**: LND hold invoices, Lightning.Pub extension or direct LND access - -### Overview - -For cash-out, the current flow has a risk window: user pays invoice, but if dispenser jams, user loses funds. Hold invoices solve this by delaying settlement until cash is physically dispensed. - -### Current Flow (Risky) - -``` -User pays invoice ──> Invoice settles ──> Dispense attempted ──> JAM! - │ - └── User lost funds, ATM owes them -``` - -### Hold Invoice Flow (Safe) - -``` -User pays invoice ──> Invoice HELD ──> Dispense attempted ──> Success ──> SETTLE - │ │ - │ └── JAM! ──> CANCEL (funds returned) - │ - └── Funds locked but not settled -``` - -### Technical Background - -LND supports hold invoices via: - -- `AddHoldInvoice`: Create invoice with known preimage hash -- `SettleInvoice`: Release funds (ATM provides preimage) -- `CancelInvoice`: Return funds to payer - -### Lightning.Pub Status - -Lightning.Pub's codebase includes LND protobuf definitions for hold invoices: - -```typescript -// From Lightning.Pub/proto/lnd/invoices.ts -interface AddHoldInvoiceRequest { - hash: Uint8Array // SHA256 of preimage we choose - value: string // Amount in sats - memo: string - expiry: string - // ... -} - -interface SettleInvoiceMsg { - preimage: Uint8Array // Reveal to settle -} - -interface CancelInvoiceMsg { - payment_hash: Uint8Array -} -``` - -**However**: These are not currently exposed in Lightning.Pub's HTTP/Nostr API. - -### Implementation Options - -#### Option A: Lightning.Pub Extension (Recommended) - -Contribute hold invoice support to Lightning.Pub: - -```typescript -// New RPC methods needed -interface LightningPubExtension { - // Create hold invoice (doesn't settle automatically) - createHoldInvoice(params: { - amount_sats: number - memo: string - hash: string // We provide the hash - }): Promise<{ invoice: string }> - - // Settle after successful dispense - settleHoldInvoice(params: { preimage: string }): Promise - - // Cancel if dispense fails - cancelHoldInvoice(params: { hash: string }): Promise -} -``` - -**Pros**: Clean integration, benefits entire ecosystem -**Cons**: Requires upstream contribution, timeline uncertain - -#### Option B: Hybrid Approach - -ATM uses Lightning.Pub for accounting but connects to LND directly for hold invoices: - -``` -┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ ATM Machine │────>│ Lightning.Pub │────>│ LND │ -│ │ │ (accounting) │ │ (payments) │ -│ │─────────────────────────────>│ │ -│ │ direct gRPC for hold │ │ -└─────────────────┘ invoices └─────────────────┘ -``` - -**Pros**: Works today, no upstream changes needed -**Cons**: More complex, two connections to manage - -#### Option C: Escrow Account - -Use Lightning.Pub's internal accounts as escrow: - -1. Create "escrow" account in Lightning.Pub -2. User pays to escrow account (instant settlement) -3. On successful dispense: transfer escrow → operator -4. On failed dispense: transfer escrow → user refund - -**Pros**: Works within Lightning.Pub model -**Cons**: Requires user to have Lightning.Pub account for refund - -### Chosen Approach: Lightning.Pub Contribution - -We will contribute hold invoice support directly to Lightning.Pub (Option A). This: - -- Benefits the entire Nostr+Lightning ecosystem -- Keeps our architecture clean (single payment backend) -- Aligns with the open-source-first philosophy of this project - -**Action Item**: Open PR to Lightning.Pub exposing hold invoice methods via Nostr RPC (kind 21000). - -### Cash-Out Flow with Hold Invoices - -```typescript -async function cashOutWithHoldInvoice(amount: number) { - // 1. Generate preimage and hash - const preimage = crypto.randomBytes(32) - const hash = sha256(preimage) - - // 2. Create hold invoice via LND - const invoice = await lnd.addHoldInvoice({ - hash, - value: amount, - memo: `ATM Cash-Out $${amount / 100}`, - expiry: 600, // 10 minutes - }) - - // 3. Display invoice, wait for payment - displayQR(invoice) - await waitForHtlcAccepted(hash) // Payment received but not settled - - // 4. Attempt to dispense - try { - await dispenser.dispense(calculateBills(amount)) - - // 5a. Success - settle the invoice - await lnd.settleInvoice({ preimage }) - return { success: true } - } catch (error) { - // 5b. Failure - cancel the invoice, funds return to user - await lnd.cancelInvoice({ payment_hash: hash }) - return { success: false, error: 'Dispense failed, payment cancelled' } - } -} -``` - -### Timeout Handling - -- Hold invoices have expiry (default 10 minutes) -- If ATM crashes mid-transaction, invoice eventually expires -- User's funds return automatically after timeout -- No manual intervention needed - ---- - -## Implementation Priority - -| Feature | Priority | Effort | Value | -| ------------------------ | -------- | ------ | ------------------- | -| Public Cash Availability | P1 | Low | High | -| Remote Initiation | P2 | Medium | High | -| Hold Invoices | P2 | High | Critical for safety | - -### Suggested Order - -1. **Phase 1**: Basic ATM flows (cash-in, cash-out with standard invoices) -2. **Phase 2**: Public availability beacon + simple dashboard -3. **Phase 3**: Remote initiation with signed claims -4. **Phase 4**: Hold invoices for production safety - ---- - -## Related Documentation - -- [CLINK Protocol Spec](https://github.com/shocknet/clink) -- [Lightning.Pub](https://github.com/shocknet/Lightning.Pub) -- [LND Hold Invoices](https://docs.lightning.engineering/lightning-network-tools/lnd/hold-invoices) -- [NIP-44 Encryption](https://github.com/nostr-protocol/nips/blob/master/44.md)