diff --git a/lamassu-next/apps/machine/src/services/lightning.ts b/lamassu-next/apps/machine/src/services/lightning.ts index f4b68ef..f8a6dec 100644 --- a/lamassu-next/apps/machine/src/services/lightning.ts +++ b/lamassu-next/apps/machine/src/services/lightning.ts @@ -140,8 +140,9 @@ export async function initializeLightningServices(): Promise console.log('[CLINK] Invoice created:', invoice.paymentRequest.slice(0, 32) + '...') - // Watch for payment - lightningPub.watchInvoice(invoice.paymentHash, (paidInvoice) => { + // Watch for payment using the full invoice string (not payment hash) + // Lightning.Pub sends LiveUserOperation events when invoices are paid + lightningPub.watchInvoice(invoice.paymentRequest, (paidInvoice) => { console.log('[CLINK] Payment received! Preimage:', paidInvoice.preimage) if (paidInvoice.preimage && paymentReceivedCallback) { paymentReceivedCallback(paidInvoice.preimage) @@ -285,6 +286,7 @@ function createATMServices( console.log('[ATM Service] Generating invoice for', amountMsat, 'msats') const amountSats = Math.floor(amountMsat / 1000) + console.log('[ATM Service] Amount in sats:', amountSats) try { const invoice = await lightningPub.createInvoice({ @@ -292,6 +294,12 @@ function createATMServices( description: 'Lamassu ATM - Cash Out', }) + console.log('[ATM Service] createInvoice response:', invoice) + + if (!invoice || !invoice.paymentRequest) { + throw new Error('Invoice creation returned empty response') + } + console.log('[ATM Service] Generated invoice:', invoice.paymentRequest.slice(0, 32) + '...') return invoice.paymentRequest } catch (error) { @@ -438,6 +446,56 @@ function createATMServices( console.log('[ATM Service] Validated amount:', fiatCents, 'cents') return fiatCents }, + + /** + * Watch an invoice for payment (cash-out flow) + * + * Uses Lightning.Pub's GetLiveUserOperations subscription to detect payment. + * Returns a cleanup function to stop watching. + * + * Note: Lightning.Pub sends LiveUserOperation events when invoices are paid. + * We subscribe to kind 21000 events and filter for INCOMING_INVOICE operations. + */ + watchInvoice: (invoice: string, callback: (preimage: string) => void): (() => void) => { + console.log('[ATM Service] Watching invoice for payment:', invoice.slice(0, 32) + '...') + + // Validate it looks like a BOLT11 invoice + if (!invoice.toLowerCase().startsWith('ln')) { + console.error('[ATM Service] Invalid invoice format - expected BOLT11') + return () => {} + } + + // Lightning.Pub's watchInvoice subscribes to LiveUserOperation events + // and returns a cleanup function + const cleanup = lightningPub.watchInvoice(invoice, (paidInvoice) => { + console.log('[ATM Service] Invoice paid!') + // LiveUserOperation doesn't include preimage, so we use a placeholder + // In a real scenario, we'd need to get preimage from another source + callback(paidInvoice.preimage || 'payment-confirmed') + }) + + // Return the cleanup function from Lightning.Pub + return cleanup + }, + + /** + * Get available inventory (denominations and counts) + * + * In production, this would query the actual dispenser hardware. + * For Phase 1, returns mock inventory. + */ + getInventory: async (): Promise> => { + console.log('[ATM Service] Getting inventory') + + // Mock inventory for Phase 1: only $20 bills + // In production, this would interface with the Rust HAL + const inventory: Record = { + 20: 50, // 50 x $20 bills = $1000 capacity + } + + console.log('[ATM Service] Inventory:', inventory) + return inventory + }, } } diff --git a/lamassu-next/apps/machine/src/stores/atm.ts b/lamassu-next/apps/machine/src/stores/atm.ts index 9428c3f..7b2de95 100644 --- a/lamassu-next/apps/machine/src/stores/atm.ts +++ b/lamassu-next/apps/machine/src/stores/atm.ts @@ -71,6 +71,19 @@ const mockServices: ATMServices = { } return fiatCents }, + + watchInvoice: (paymentHash, _callback) => { + console.log('[Mock] Watching invoice for payment:', paymentHash.slice(0, 20) + '...') + // Note: Auto-pay disabled for testing real invoice monitoring + // Use the "Simulate Payment Now" button in the UI to manually trigger payment + return () => {} + }, + + getInventory: async () => { + console.log('[Mock] Getting inventory') + // Mock inventory: only $20 bills for Phase 1 + return { 20: 50 } + }, } export const useAtmStore = defineStore('atm', () => { @@ -343,6 +356,23 @@ export const useAtmStore = defineStore('atm', () => { send({ type: 'CASH_DISPENSED' }) } + // Cash-out selection methods + function addDenomination(denomination: number) { + send({ type: 'ADD_DENOMINATION', denomination }) + } + + function removeDenomination(denomination: number) { + send({ type: 'REMOVE_DENOMINATION', denomination }) + } + + function clearSelection() { + send({ type: 'CLEAR_SELECTION' }) + } + + function confirmAmount() { + send({ type: 'CONFIRM_AMOUNT' }) + } + function toggleDebug() { debugMode.value = !debugMode.value } @@ -381,6 +411,10 @@ export const useAtmStore = defineStore('atm', () => { requestDebit, skipReceipt, cashTaken, + addDenomination, + removeDenomination, + clearSelection, + confirmAmount, toggleDebug, } }) diff --git a/lamassu-next/apps/machine/src/views/CashOutView.vue b/lamassu-next/apps/machine/src/views/CashOutView.vue index 5d4eb70..a586be6 100644 --- a/lamassu-next/apps/machine/src/views/CashOutView.vue +++ b/lamassu-next/apps/machine/src/views/CashOutView.vue @@ -1,5 +1,5 @@