Add input validation to hal:dispense IPC handler:
- Reject non-array or empty amounts
- Validate denomination and count are numbers
- Reject non-positive or non-integer counts
- Verify denomination exists in loaded cassettes
- Verify requested count does not exceed available inventory
Prevents a compromised renderer from sending crafted dispense
requests (negative counts, unknown denominations, over-capacity).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move atmPrivateKey and adminToken out of the general get-config IPC
handler into a dedicated one-shot get-atm-secrets handler that returns
secrets only once per app lifecycle. Subsequent calls return empty
strings. This prevents XSS or DevTools from repeatedly querying
getConfig() to steal the ATM's Nostr private key.
TODO: Move signing/encryption to main process entirely (Phase 2)
so the private key never crosses the IPC boundary.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The hal:dispense IPC handler in main.ts did not return the result of
dispenseCash(), causing the state machine guard to crash on undefined
output. This left the UI stuck on "Dispensing cash..." after successful
dispense.
- hal-service.ts: return DispenseResult instead of void/throwing
- main.ts: add missing return in IPC handler
- machine.ts: defensive guard (?. instead of .) as safety net
Bug found with the aid of Seoyoung at Trece Cielos.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dispenseCash now always resolves with a DispenseCashResult (per-bill
dispensed/rejected counts, overall success flag, optional error) instead
of throwing. dispenseError is a 30s timed state that auto-returns to
idle, matching brain.js _timedState('outOfCash'). The dead-end retry
loop (which the UI never exposed) is removed.
The Vue dispenseError screen now shows partial dispense info, the
transaction ID as a QR code, and a 30s countdown.
Closes#30
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove infinite box-shadow glow animations from Buy/Sell buttons
on idle screen (expensive on CPU without GPU)
- Reduce BTC price polling from 10s to 30s
- Reduce LNURL-withdraw polling from 2s to 5s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When VITE_FORCE_MOCK=true, initialize with mock services directly
instead of requiring Electron or a live Lightning.Pub connection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
loadTheme() and loadColorMode() were called during module evaluation
before the isElectron const was declared, causing a ReferenceError in
Firefox's strict TDZ enforcement. Move isElectron above the ref() calls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The kiosk UI was optimized for 1920px touchscreens, breaking mobile web.
Uses Tailwind lg: breakpoints (mobile-first) so mobile works naturally
while kiosk sizing applies at 1024px+. No JS branching — pure CSS.
- Viewport: width=1920 → width=device-width
- Kiosk-only: overflow:hidden and cursor:none behind @media (min-width: 1024px)
- IdleView: stack buttons vertically on mobile, justify-around for even spacing
- CashInView/CashOutView: stack split panels on mobile (flex-col-reverse lg:flex-row)
- QRCode: SVG scales down on small screens via max-w-full
- App.vue: hide verbose badges on mobile, debug bar collapses theme/color selectors
- Connection/network badges always visible (not gated by isIdle)
- Reduce zone-glow animation intensity by half (20px/4px vs 40px/8px)
- All lg: values verified to match original kiosk rendering
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The debug panel already has full theme/color mode controls, so the
floating light/dark toggle is redundant in dev. Keep it for production.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The fixed-position BTC price, balance, and connection badges
overlapped with the CashIn/CashOut headers. Now only visible on idle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move light/dark toggle with Lucide Sun/Moon icons from IdleView to
App.vue so it's visible on all screens. Detect Bitcoin network
(mainnet/testnet/regtest) from BOLT-11 invoice prefix and persist
in localStorage. Hide network badge on mainnet (implied).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split-screen for displayingInvoice (info left, QR right), full-width
for selectingAmount. Add StepIndicator with warning flow accent header.
Replace Skeleton with BounceDots, remove ScrollArea and Card wrappers,
wrap states in Transition for fade animations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split-screen for insertingBills (amounts left, bill visual right) and
displayingQR (info left, QR right). Add StepIndicator with success
flow accent header. Replace Skeleton with BounceDots, remove ScrollArea
and Card wrappers, wrap states in Transition for fade animations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full-canvas layout: brand zone with logo/badges/balance at top,
two circular touch zones (Buy=bitcoin orange, Sell=green) with
zone-glow animation. Show per-flow commission rates (Buy/Sell).
Move light/dark toggle to App.vue for omnipresence.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap QrcodeVue in animated scanning frame with 4 pulsing corner
brackets and a sweep line. Increase default size from 300 to 380
for split-screen contexts. Use font-mono-code for URI preview.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BounceDots: three animated dots replacing Skeleton shimmer loaders.
StepIndicator: dot-line-dot progress with active/complete states,
split into separate dots and labels rows for proper alignment.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scale up button sizes (kiosk, kiosk-lg, kiosk-icon variants), add
active:scale feedback, set viewport to 1920px fixed width, hide cursor,
and bump card title/description sizes for touch-screen readability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cassette inventory was never initialized in SQLite, so
recordTransaction's UPDATE decrements were no-ops against an empty
table. Now the Electron main process seeds cassettes from
VITE_LAMASSU_CASSETTES or the model preset on first boot.
Also adds an atm-transactions CLI script (with --summary, --inventory,
--type, --today, --last, --since filters) and sqlite to the NixOS
system packages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Subtle sun/moon button in bottom-left corner lets users switch
between light and dark mode. Persists via localStorage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Debug toggle button only shown when allowMockFallback is true
- Electron defaults to Catppuccin Latte (light) instead of Gruvbox dark
- Browser dev mode keeps Gruvbox dark as default
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When VITE_ALLOW_MOCK_FALLBACK is unset (production default), the ATM
now shows a maintenance screen instead of silently falling back to mock
services when hardware or Lightning initialization fails. Also disables
ndebit/CLINK in production since the static ndebit pointer is replayable
— cash-in uses LNURL-withdraw only (single-use by design).
- Add allowMockFallback config field (Electron IPC + types)
- Add strict config validation (no localhost, require private key)
- Gate all catch-block fallbacks behind allowMockFallback
- Disable debit approval service and ndebit generation in production
- Add maintenance screen in App.vue when initError is set
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The displayingQR state showed "Waiting for payment..." with an hourglass,
which confused users into thinking they needed to pay. This is an
LNURL-withdraw flow — the user scans to *receive* sats.
- "Preparing payment code" → "Preparing your withdraw code"
- Add prominent "Scan to receive your sats" heading above QR
- "Waiting for payment..." → "Open your wallet and scan to claim"
- "Manual payment options" → "Manual withdraw options"
- "Payment Sent!" → "Sats Sent!"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace bg-orange-500 lightning bolt overlay with bg-bitcoin, bg-white
receipt placeholders with bg-qr/text-qr-foreground, and remove redundant
bg-white QR wrappers in IdleView overlays (QRCode.vue already has bg-qr).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded Tailwind colors (bg-white/5, text-green-400, bg-black/30,
etc.) with theme-aware semantic classes (bg-card, text-success, text-bitcoin,
bg-destructive/20) across CashInView and CashOutView. Add colorMode support
to useTheme composable with localStorage persistence and system preference
detection. QRCode component now reacts to theme/mode changes via
MutationObserver. Debug panel includes Light/Dark/System toggle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cancel button is now always visible during the cash-in flow. The state
machine routes CANCEL to confirmAbandon when bills are present, so the
user always has an exit path with appropriate warnings. Also adds a
5-minute auto-timeout on displayingQR and allows cancel during
generatingNdebit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The cancel button was still accessible during insertingBills after a bill
had been stacked (physically irreversible). Now CANCEL in insertingBills
is guarded: no bills → idle, bills present → confirmAbandon warning.
The UI also hides the cancel button once bills are detected.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Hide header cancel button after bills are in cash box
- Add spinning hourglass to displayingQR waiting indicator
- Hide keyboard-dependent UI in Electron kiosk mode:
QR mode selector, manual payment options, copy buttons
These remain available in browser (web-ui) mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add confirmAbandon state UI: warns user cash can't be returned,
offers "Show QR Code Again" or "Abandon (lose cash)"
- Error state shows "Try Again" button when bills are inserted
- Replace Skeleton loading placeholders with hourglass emoji
- Add animate-hourglass: 45-degree step rotation (Tailwind v4 @utility)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Poll LNbits /api/v1/rate/ every 10s for live price display
- Show local currency rate + USD reference (e.g. GTQ/BTC: Q525,034 ($67,960))
- Reduce rate cache from 5min to 10s for responsive display
- Use simpler /api/v1/rate/ endpoint instead of /conversion POST
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix cassette denominations: Douro uses Q100/Q200, not Q20
- Add hal:get-inventory IPC so renderer can read HAL cassette inventory
- Add balance fetch/display to HAL+IPC init path and idle screen
- Enable/disable bill validator via watch on nested state transitions
- Pass fiatCode to state machine context (was hardcoded to USD)
- Preserve currency across state machine resetContext
- Add CANCEL handler to dispenseError state (was stuck)
- Fix remaining hardcoded $ symbols in CashInView
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HAL hardware drivers (serialport) run in the main process since they
need Node.js. The renderer communicates via IPC for all hardware ops.
- hal-service.ts: bridge between HAL drivers and Electron IPC
- main.ts: HAL IPC handlers (init, dispense, validator stack/reject)
- preload.ts: expose HAL API to renderer via contextBridge
- atm.ts: IPC-based production init with validator event wiring
- hal.ts: add 'hold' mode for escrow (async stack/reject decision)
- electron.d.ts: HAL type declarations for window.electronAPI
Bills go to escrow first; the renderer checks balance before accepting.
Falls back to Lightning-only mock mode if HAL init fails.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- App.vue detects Electron and calls initializeForProduction() to start
real hardware drivers instead of Lightning-only mode
- Auto-send CASH_DISPENSED when HAL is active since dispenseCash already
waits for bills to be removed before resolving
- Add GTQ (Guatemalan Quetzal) bill lengths to F56 and Puloon dispensers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Port the Puloon RS232 dispenser protocol from lamassu-machine to
the lamassu-next HAL package. Adds Douro machine preset with Puloon
dispenser on /dev/ttyS1 and ID003 validator on /dev/ttyS0.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enable debugMode by default (simulated bill buttons) until real
hardware validator is connected. Fix live.nix to include native
node_modules (better-sqlite3, bindings) from pnpm virtual store,
add LD_LIBRARY_PATH for NixOS, bake aiolabs.dev endpoints into
env template, and enable Electron renderer logging in journalctl.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use h-dvh instead of h-screen for mobile browser viewport
- Add ScrollArea component (shadcn-vue) to replace native scrollbars
- Move Hide Debug into debug bar row, show Show Debug only when off
- Responsive cash-out denomination grid (single col on mobile)
- Add safe-area padding for mobile gesture bar
- Add business model documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Debug bar is now a normal flex child (shrink-0) instead of fixed
overlay, so content naturally fits above it without padding hacks
- Replace bulky dashed bill acceptor box with a single text line
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Without min-h-0, flex children won't shrink below their content size,
so the parent's pb-28 padding is ignored and the debug bar overlaps.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Push header below fixed status badges on mobile (pt-12)
- Use w-full max-w-* instead of fixed widths so cards fit mobile screens
- Make main content scrollable with overflow-auto
- Reduce padding on mobile (p-4 vs p-8)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents the fixed debug overlay from covering content (QR links)
on mobile by reserving space at the bottom of the main container.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- IdleView: stack cards vertically on mobile, side-by-side on sm+
- Scale logo, cards, text, and badges down for small screens
- QR overlays use smaller codes on mobile (280px vs 400px)
- Status badges wrap and use smaller text on mobile
- Hide subtitle text on mobile cards to save space
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>