- Add dev.sh script for managing regtest development environment
- Implement cmd_fund to fund ATM app owner via Lightning.Pub API
- Add --fund flag to cmd_up for automatic funding on startup
- Update setup_atm_app to write VITE_APP_ID to machine .env
- Fix Electron IPC to pass appId and extensionApiUrl to renderer
- Restructure repo from nested lamassu-next/ to root
The dev.sh script now supports:
- ./dev.sh up --fund # Start regtest and auto-fund ATM
- ./dev.sh fund # Fund existing ATM app
- ./dev.sh status # Show environment status
- ./dev.sh reset # Clean restart
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Redesign cash-in QR display with cleaner UI inspired by payment gateways:
- Unified QR encodes both lightning:LNURL and clink:ndebit on separate lines
- Pill buttons to select Universal/LNURL/CLINK mode
- Generate LNURL-withdraw immediately when entering QR state
- QR code with lightning bolt overlay
- Truncated URI with copy button
- Amount display with sats and fiat
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The polling was checking for `data.used > 0` but the LNURL endpoint
returns `{ status: 'ERROR', reason: 'Withdraw link is spent.' }` when
the link has been claimed. Check for this error response instead.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The onPaymentSuccess callback was passed to createATMServices but never
used, causing LNURL-withdraw claims to go undetected. Store the callback
so startLnurlCompletionPolling can trigger it when a withdrawal completes.
Refs: #24
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add LNURL-withdraw (LUD-03) as an alternative to ndebit for the cash-in
flow. Users can now choose between:
- ndebit (CLINK) - for ShockWallet and compatible apps
- LNURL-withdraw - for any LNURL-compatible wallet (Zeus, Phoenix, etc.)
Changes:
- CashInView.vue: Add tab UI to switch between ndebit and LNURL QR codes
- atm.ts: Add generateLnurlWithdraw store action
- lightning.ts: Implement LNURL-withdraw service calling extension API
- Add @scure/base dependency for LNURL encoding
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix state machine to convert bill denominations from dollars to cents
(e.g., $20 bill → 2000 cents in fiatAmount)
- Use fixed 1,000 sats/$ rate for development (~$100k BTC)
- Update test to expect correct cents value
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace brand-specific text with generic "Bitcoin ATM" heading
and simplified thank you messages.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implement the complete ATM-side ndebit (cash-in) flow with single-use
protection to prevent double-spending from the same QR code.
Key features:
- Debit approval service subscribes to GetLiveDebitRequests from Lightning.Pub
- Session-based validation: each ndebit QR is valid for one use only
- Amount decoded from BOLT11 invoice when not provided in message
- Atomic session marking prevents race conditions
- Triple protection: event ID tracking, invoice tracking, session status
Changes:
- apps/machine/src/services/lightning.ts: Add debit approval service with
session management, BOLT11 amount decoding, and RespondToDebit approval
- packages/state-machine: Add cashInSessionId to context and generation
- docs/ndebit-cash-in-flow.md: Comprehensive documentation of the flow,
architecture diagrams, and troubleshooting guide
Infrastructure improvements:
- Auto-miner service for regtest (keeps LND synced during testing)
- Electron .env file loading for runtime configuration
- Preload script compilation for Electron IPC
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Reviewed the CLINK spec from the shocknet/clink repository and fixed
several compliance issues:
## noffer TLV encoding (NIP-19 style)
- Fixed TLV numbers: pubkey=0, relay=1, offerId=2, priceType=3, amount=4, currency=5
- Added offerId (TLV 2) and currency (TLV 5) fields to noffer encoding
- Updated CLINKOffer interface to use offerId instead of description
## Error codes
- Split error codes into OfferErrorCode (Kind 21001) and GFYCode (Kind 21002/21003)
- Added all error codes per CLINK spec with proper numeric values
- Deprecated CLINKErrorCode alias for backward compatibility
## CLINK events
- Added mandatory clink_version tag ["clink_version", "1"] to all CLINK events
- Validate clink_version tag when receiving events
- Switched to NIP-44 v2 encryption for all CLINK events (21001-21003)
## Debit requests (Kind 21002)
- Fixed DebitRequest to include bolt11 field for payment requests
- Split into DebitPaymentRequest (with bolt11) and DebitBudgetRequest
- Added isDebitPaymentRequest type guard
## Client API changes
- Renamed requestDebit to requestDebitPayment (requires bolt11 invoice)
- Added requestDebitBudget for recurring budget authorization
- Updated createOffer signature to use offerId instead of description
## apps/machine updates
- Updated lightning.ts to use OfferErrorCode enum values
- Updated atm.ts to generate invoice before debit request
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comprehensive documentation for the CLINK protocol including:
- Kind 21001 (Offer), 21002 (Debit), 21003 (Manage) event types
- noffer and ndebit bech32 encoding formats with TLV fields
- Flow diagrams for ATM cash-out and cash-in scenarios
- JSON examples for all event types including error responses
- ATM-specific code examples using @lamassu/clink library
- Security considerations and related NIPs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add .env.example with all configuration options documented
- Add get-config IPC handler in Electron main process
- Expose getConfig() in preload for renderer access
- Update lightning.ts to load config at runtime in Electron
- Fall back to Vite build-time env vars for browser dev mode
- Add TypeScript declarations for electronAPI
This allows the same build to be deployed to different machines
with different configurations (relay URL, Lightning.Pub pubkey, etc.)
set via environment variables at runtime.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Python with setuptools to devenv.nix for node-gyp compatibility
- Remove Tauri dependencies from devenv (replaced by Electron)
- Add author, homepage, license to package.json for electron-builder
- Simplify Linux target to AppImage only
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create electron/main.ts with BrowserWindow (1080x1920, kiosk mode)
- Create electron/preload.ts with contextBridge API
- Add electron/tsconfig.json for main process compilation
- Update package.json: remove Tauri deps, add Electron + electron-builder
- Update vite.config.ts: remove Tauri-specific build config
- Update .gitignore: remove Tauri entries, add Electron build outputs
- Delete src-tauri/ directory and root Cargo.toml
- Update CLAUDE.md to reflect Electron architecture
The Vue frontend is unchanged - works identically in Electron renderer.
HAL drivers can now run directly in Electron main process (Node.js).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create apps/machine/src/services/hal.ts bridging @lamassu/hal
EventEmitter-based drivers to ATMServices interface
- Add initializeWithHal() to ATM store for hardware + Lightning init
- Merge HAL services (dispenseCash, getInventory) with Lightning services
- Wire validator events to state machine (BILL_INSERTED, BILL_REJECTED)
- Exclude @lamassu/hal from Vite browser bundle (Node.js only)
- Add @lamassu/hal as dependency to apps/machine
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Documents the decision to:
- Use TypeScript HAL drivers extracted from lamassu-machine
- Use Electron instead of Tauri for the kiosk app
- Avoid Rust rewrite (adds complexity without benefit)
Related issues: #18, #19, #20, #21
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extract bill validator and dispenser drivers from lamassu-machine:
- ID003 driver for JCM iVIZION bill validators (Sintra)
- F56 driver for Fujitsu F53/F56 bill dispensers (Sintra)
Converted to TypeScript with full type definitions. Uses existing
battle-tested protocol implementations from lamassu-machine.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comprehensive document comparing Nostr-native approach with traditional
lamassu-server/machine for operators evaluating the transition. Covers
infrastructure, security, payments, compliance, and migration path.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Lightning.Pub's GetPaymentState RPC is for checking OUTGOING payments
(invoices you've paid), not incoming payments (invoices you've created).
This caused invoice payment detection to fail with "invoice not found"
even when payments succeeded.
Solution: Subscribe to GetLiveUserOperations events which Lightning.Pub
sends in real-time when invoices are paid. These events have:
- requestId: "GetLiveUserOperations"
- operation.type: "INCOMING_INVOICE"
- operation.identifier: the full invoice string
Key changes:
- packages/lightning/src/client.ts: Rewrote watchInvoice() to use
subscription-based detection instead of GetPaymentState polling
- apps/machine/src/services/lightning.ts: Updated to use full invoice
string instead of payment hash
- packages/lightning/TROUBLESHOOTING.md: Added Issue #9 documenting
this gotcha (GetPaymentState vs GetLiveUserOperations)
Also includes ATM-driven cash-out flow implementation with:
- Invoice generation via NewInvoice RPC
- Real-time payment detection
- State machine updates for cash-out states
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The ndebit must encode Lightning.Pub's pubkey (not the ATM's pubkey)
so that Kind 21002 debit requests are received by Lightning.Pub.
Also added the 'atm' pointer to identify which Lightning.Pub user
account should pay the invoice.
Fixes:
- Changed pubkey from ATM identity to Lightning.Pub pubkey
- Added pointer: 'atm' to ndebit encoding
- Updated hardcoded pubkey to match current Lightning.Pub instance
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace LNURL-withdraw with CLINK ndebit for cash-in transactions:
- Add ndebit.ts with TLV-based bech32 encoding/decoding
- Generate clink:ndebit1...?amount=<sats> URIs
- Display QR code with copyable URI in CashInView
- Update state machine flow: generatingNdebit → displayingQR
- Fix shadcn-vue Input binding with :model-value prop
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Redesign cash-out view for noffer (CLINK Offers) workflow
- Add states: generatingNoffer, displayingNoffer, validatingRequest,
sendingInvoice, awaitingPayment
- Remove old selectingAmount state (user specifies amount in wallet)
- Add debug controls to simulate offer requests for testing
- Wire up offer request callback in ATM store to state machine
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace direct invoice generation with CLINK Offers (Kind 21001) for
cash-out. This provides a wallet-native flow where:
1. ATM displays noffer QR code
2. User's wallet sends Kind 21001 request with amount
3. ATM validates amount and responds with invoice
4. User pays invoice
5. ATM dispenses cash
Changes:
- packages/state-machine: Update cash-out flow with new states
- displayingNoffer: Show noffer QR, subscribe to requests
- validatingRequest: Check amount is dispensable
- sendingInvoice: Send Kind 21001 response
- awaitingPayment: Wait for payment confirmation
- packages/state-machine/types: Add OfferRequestEvent, new services
- apps/machine: Add service implementations and mocks
Benefits:
- Variable amount support (user enters in wallet)
- Standard CLINK protocol compliance
- Better UX (wallet-native flow)
- Static QR codes for multiple transactions
Closes#9
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds mock ATM machine and infrastructure for testing the ndebit
cash-in flow where users scan a QR code to withdraw sats.
Key changes:
- Add mock-machine.mjs: displays ndebit QR with clink: scheme
- Add ndebit-cash-in-flow.md: comprehensive implementation guide
- Update docker-compose: configure relay for browser access
- Use clink: URI scheme (protocol-agnostic, supports future Cashu/Fedimint)
Flow: ATM displays clink:ndebit1...?amount=X → User scans with wallet
→ Wallet creates invoice → Sends Kind 21002 debit request → ATM pays
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Mine a block before running checks to wake up LND sync status.
On regtest, LND reports "synced_to_chain: false" when no blocks
have been mined recently, even though it's actually synced.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Complete getting started guide for new developers:
- Prerequisites (Nix, devenv, Docker)
- Quick start from scratch (5 commands to working env)
- All available development commands
- Architecture overview
- Infrastructure services reference
- E2E testing instructions
- Troubleshooting section
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New devenv.nix scripts:
- test-setup: Validates entire test environment (services, connectivity,
blockchain state, channels, and runs a payment test)
- fund-atm: Funds ATM Lightning.Pub account via Alice
- test-payment: Quick e2e payment test (ATM → customer)
- alice-invoice: Create invoice on Alice's node
- node-info: Display node pubkeys and channel information
Script improvements:
- fund-dev.mjs: Use FUND_AMOUNT env var instead of hardcoded value
- test-pay.mjs: Use NOSTR_RELAY_URL env var with localhost fallback
- Both scripts now use localhost:7777 by default instead of hardcoded IP
Updated shell hook to document new testing commands.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Major fixes:
- Implement NIP-44 v1 encryption (Lightning.Pub requirement)
- Fix RPC request format (rpcName, params, query, body, authIdentifier, requestId)
- Fix PayInvoice amount field (always required, use 0 for invoices with amounts)
- Fix NostrClient subscriptions to use direct Relay instead of SimplePool
- Fix race condition by subscribing before publishing requests
- Remove #p/#e tag filters and match manually (relay compatibility)
New features:
- Add CLINK Debit support to cash-in flow
- Add invoice paste input for manual payments
- Add Input UI component
Documentation:
- Add TROUBLESHOOTING.md with 6 non-obvious integration gotchas
- Update CLAUDE.md with reference to troubleshooting doc
Test scripts:
- fund-dev.mjs - Create invoices for testing
- test-pay.mjs - Test PayInvoice RPC directly
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace CLINK offer with LNURL-withdraw for the "buy bitcoin" flow.
LNURL-withdraw is the correct protocol for customers to receive sats:
- ATM displays LNURL-withdraw QR code
- Customer scans with their Lightning wallet
- Wallet automatically creates invoice and sends it to ATM
- ATM pays the invoice, sending sats to customer
This provides a much better UX than manual invoice entry, especially
for a kiosk where users can't paste text.
Changes:
- Add LnurlWithdrawServer class that implements LUD-03 protocol
- Add LNURL HTTP server (port 3333) for wallet callbacks
- Update state machine: generatingOffer → generatingLnurlWithdraw
- Add lnurlWithdraw context field and generateLnurlWithdraw service
- Update CashInView to display LNURL-withdraw QR
- Add @scure/base dependency for bech32 encoding
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add --qr and --qr-foreground CSS variables to theme
- Light mode: standard gruvbox bg/fg for high contrast
- Dark mode: warm cream background, easier on eyes
- QRCode component now uses bg-qr and text-qr-foreground classes
- Reads CSS variables for qrcode.vue library props
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Warm cream background (#f5f0e6) instead of harsh white
- Gruvbox dark brown foreground (#3c3836) for good contrast
- Maintains ~10:1 contrast ratio for reliable phone scanning
- Easier on eyes in dark ATM environments
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add qrcode.vue dependency for QR code rendering
- Create QRCode.vue component for displaying payment codes
- Update CashInView and CashOutView to use real QR codes
- Add CLINK offer request handler to respond when wallets scan noffers
- Wire payment received callback to state machine
- Auto-complete transaction when payment is detected
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add lightning.ts services module with real LightningPubClient and CLINKClient
- Connect to local relay (ws://localhost:7777) and Lightning.Pub on init
- Generate real CLINK noffers for cash-in flow
- Generate real Lightning invoices for cash-out flow
- Add connection status indicator (Live/Connecting/Offline badge)
- Add toggle between live and mock services in debug panel
- Fall back to mock services on connection failure
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- docs/future-features.md: Document planned features
- Public cash availability display via kind 30078 beacons
- Remote initiation with local redemption using signed claims
- Hold invoices for safe dispensing (Lightning.Pub contribution)
- scripts/test-clink.ts: Test script for CLINK protocol verification
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Document Alice LND node for payment testing
- Add testing payment workflow examples
- Update service ports table (Lightning.Pub: 1776)
- Add gitignore for vite temp files
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add lncli-alice for Alice's LND node
- Add setup-channel script to create channel between Alice and LND
- Add alice-pay script for paying invoices from Alice
- Update infra-up to wait for Alice's LND
- Update LIGHTNING_PUB_URL to correct port 1776
- Add mine-blocks, relay-test and other utility scripts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add second LND node (Alice) for payment testing
- Fix Lightning.Pub port mapping (1776:1776)
- Add ADMIN_TOKEN for HTTP API access
- Configure NOSTR_RELAYS with host.docker.internal for proper nprofile generation
- Fix strfry nofiles limit (set to 0 to skip limit configuration)
- Add extra_hosts for Linux host.docker.internal support
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Implementation Status section with completed/placeholder/planned breakdown
- Mark architecture tree with completion status (✅, planned, placeholder)
- Add Kind 21000 (Lightning.Pub RPC) to Nostr Event Kinds table
- Fix Kind 30078 description: "Service Beacon" not "Machine Status"
- Update Key Technologies table with current state
- Remove NIP-17 (not currently used)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix noffer.ts encodeUint64BE to use division instead of bit shifts
(JS bitwise ops only work on 32-bit integers)
- Update noffer.test.ts to use amountSats instead of amountMsat
- Update client.test.ts to remove serviceUrl (no longer in config)
- Add decodeInvoice tests for different BOLT11 amount formats
- Add placeholder tests for @lamassu/cashu package
- Add placeholder tests for @lamassu/ui-shared package
All 43 tests now pass across 6 packages.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- nostr-client: Fix Relay.auth() callback to use verifyEvent and VerifiedEvent type
- nostr-client: Add @ts-expect-error for subscribeMany/querySync Filter[] types
- clink: Fix finalizeEvent to not pass pubkey (derived from secret key)
- clink: Fix bech32.decode to cast input as template literal type
- lightning: Fix finalizeEvent to not pass pubkey (derived from secret key)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove useWebSocketImplementation (not needed in newer versions)
- Fix Relay.auth() callback signature to match nostr-tools API
- Fix filter type casting for subscribeMany and querySync
- Remove ws dependency (not needed for browser/node compatible builds)
- Mark connections as authenticated when no auth required
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- ui-shared: Add Vite library config and minimal entry point
- cashu: Add TypeScript config and minimal entry point
These packages are placeholders that will be implemented as needed.
The scaffolding allows the monorepo build to complete.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>