Commit graph

16 commits

Author SHA1 Message Date
1b671bf407 build(machine): add a web-only build:web target
The machine app's `build` script runs vue-tsc, the Vite build, two electron
tsc passes and an esbuild bundle. Serving the kiosk as a plain SPA needs only
the middle one, and the electron passes drag in native-addon typings that a
web build has no use for.

Add `build:web` (just `vite build`) with a turbo task that still builds the
workspace packages first via `dependsOn: ["^build"]`, so a consumer can run
`pnpm build:web` at the repo root and get `apps/machine/dist`.

`env: ["VITE_*"]` is declared on the task because the Vite vars are baked into
the bundle at build time — without it turbo would happily serve a cached
build produced under different env.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013A6683cCHnQxFUosx1krY4
2026-09-06 19:24:15 +02:00
Patrick Mulligan
84d746a0da feat(machine): NFC Bolt Card reader driver + IPC (main process)
Main-process driver over nfc-pcsc (PC/SC). On tap it reads the NTAG424
Type-4 NDEF file via ISO7816 APDUs (select NDEF app D2760000850101 →
select file → ReadBinary NLEN + message) and extracts the lnurlw voucher
(fresh SUN p/c per tap), forwarding it to the renderer on `nfc:card-tapped`
(+ `nfc:status`). Lazy, guarded import — a missing reader/pcscd just
reports 'unavailable', never breaking the cash-out QR path. Preload
removeAllListeners guards against a double payment-trigger on renderer reload.

- electron/nfc-service.ts: startNfcReader() + readNdefLnurlw()/extractLnurlw().
- electron/nfc-service.test.ts: 7 tests (NDEF URI extraction, Type-4 read
  sequence incl. AID select, empty-file + select-fail handling).
- main.ts start + IPC forward; preload + electron.d.ts listeners.
- add nfc-pcsc dep (native @pokusew/pcsclite; nix build handling next).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 04:42:07 +02:00
d22157b40c feat(machine): pairing-source abstraction + QR/NFC capture + seed ingest
The capture half of the QR-pairing wizard (aiolabs/bitspire#52), behind a
`PairingSource` seam so the wizard UI stays agnostic to how the seed arrives:

- `QrPairingSource` — camera capture + decode via `qr` (paulmillr). Chosen
  over the dormant, unmaintained `jsqr`: `qr` is zero-dependency, auditable,
  dual MIT/Apache, actively maintained, and authored by the same person as the
  `@noble`/`@scure` crypto our nostr stack already trusts. Its `qr/dom.js`
  helper wraps getUserMedia + the per-frame decode loop.
- `NfcPairingSource` — Web NFC scaffold; `isAvailable()` is false on the
  Sintra's Linux Electron, so it's inert until real NFC hardware lands (the
  user flagged NFC as a plausible future pairing method).
- `ingestScannedSeed` — validates the scan parses as a spire-seed (rejecting a
  stray QR), persists it, and relaunches. Covered by unit tests
  (invalid-seed / no-bridge / persist-failed / happy path).
- `availablePairingSources()` probes each source and returns the runnable ones
  in preference order (camera first).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 23:07:31 +02:00
0391dbaeb0 chore(machine): fund-atm resumes from binding; VITE_SPIRE_SEED docs/env
fund-atm resolves its signer by resuming the bunker binding from state.db
(the connect token is already spent by the main app, so it can't re-pair);
falls back to a dev nsec via VITE_ATM_PRIVATE_KEY. better-sqlite3 marked
external in the esbuild bundle. .env.example + CLAUDE.md document
VITE_SPIRE_SEED as the prod identity, VITE_ATM_PRIVATE_KEY as dev-only.

(fund-atm is slated for deprecation in favour of the operator funding the
wallet directly via the LNbits UI — kept working for now.)

Part of Phase C, aiolabs/bitspire#52.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:15:59 +02:00
4f68ddc40b refactor(machine): drop VITE_LNBITS_HTTP_URL — lnurl now arrives populated from LNbits (#57 gap 2)
Closes gap 2 from coord log 2026-06-01T18:30Z. The LNbits withdraw
extension's nostr-transport RPC now populates `link.lnurl` from
`settings.lnbits_baseurl` (aiolabs/withdraw#1 / commit e9d911e), so the
ATM no longer needs a separate HTTP URL on the wire to compose the
LNURL-withdraw callback itself.

What goes:

- `VITE_LNBITS_HTTP_URL` env var (renderer + Electron main)
- `lnbitsHttpUrl` field on `LightningConfig`, `RuntimeConfig`, and the
  Window mirror in `src/types/electron.d.ts`
- The manual `${lnbitsHttpUrl}/withdraw/api/v1/lnurl/${unique_hash}`
  composition in `generateLnurlWithdraw`
- The `encodeLnurl` bech32 helper in `lightning.ts` (LNbits returns
  bech32-encoded; we just `.toUpperCase()` to match BOLT/LNURL convention)
- `@scure/base` dep from `apps/machine/package.json` (only used by the
  removed helper; clink still uses it directly)
- The `lnbitsHttpUrl` option + `LNBITS_HTTP_URL=…` env var + boot echo
  in `deploy/nixos/bitspire-atm.nix`
- Doc references in CLAUDE.md, README.md, deploy/nixos/README.md,
  docs/architecture-comparison.md, and the lightning-check skill

What stays:

- `link.lnurl` consumption, with an explicit error if LNbits returns
  null (which signals `LNBITS_BASEURL` is unset on the server side —
  better to fail clearly than silently)
- The receiver-side bech32 uppercasing (LNbits returns lowercase per
  the standard library)

Why this is a net win:

- Removes a config-drift surface — if LNbits's external URL moved
  (DNS, port, reverse-proxy rewrite), every ATM in the field would
  stop issuing redeemable LNURL-withdraw QRs until reconfigured.
  Now LNbits derives its own URL from `settings.lnbits_baseurl`,
  one source of truth.
- Removes an extra provisioning step. No more `LNBITS_HTTP_URL=…`
  before running `provision-atm.sh`; the relay + server pubkey suffice.
- Removes the misleading boot echo that triggered the §`18:30Z`
  smoke triage confusion ("LNbits HTTP: <url>" read like ATM-→-LNbits
  connectivity, when it was only ever a URL embedded in customer QRs).

Also adds a `# pragma: allowlist secret` marker above the
`VITE_ATM_PRIVATE_KEY` doc block in `.env.example` so the global
secret scanner stops false-positiving on the documentation prose.

Workspace typecheck + 24/24 apps/machine tests still green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 20:33:28 +02:00
6ea87c8a08 chore(machine): set up vitest for apps/machine
First test surface for the Electron + Vue 3 app — apps/machine has had
no tests until now (workspace packages cover state-machine, nostr-client,
clink, lnbits). Cassette config #56 shipped untested under the same
posture; #57 (operator fee config consumer) introduces enough load-
bearing parser + state-store logic to want unit coverage, so growing
the test substrate now.

Adds `test` / `test:watch` scripts + vitest devDep + minimal config
that picks up `src/**/*.test.ts` and `electron/**/*.test.ts`. No tests
land here — that comes with the feature commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 19:12:11 +02:00
59f81b1900 refactor: drop Lightning.Pub backend; LNbits-only path (3d)
LP usage in apps/machine is gone in this commit; packages/lightning/
is removed from the tree. atm.ts continues to see a 'lightningPub'
field but it is now a thin LightningBackend adapter (getBalance,
watchBalance, createInvoice, payInvoice) implemented over the LNbits
nostr-transport — no atm.ts surgery needed.

services/lightning.ts changes
- LightningPubClient import removed; CLINK helper imports
  (createOfferSuccess / createOfferError / OfferErrorCode) removed —
  the CLINK offer-request handler that produced LP invoices is gone.
- LightningConfig: trimmed LP fields (lightningPubPubkey,
  lightningPubApiUrl, extensionApiUrl, adminToken). loadLightningConfig
  reads only LNbits + relay + identity vars.
- initializeLightningServices: requires VITE_LNBITS_SERVER_PUBKEY,
  fails fast if missing or if list_wallets returns no wallet.
  CLINK client is still instantiated for kind-21003 management
  commands (LP-independent), but offer-request wiring is removed.
- New LightningBackend interface defines the surface atm.ts uses;
  the in-init adapter implements it over the LnbitsClient.
- ATMServices methods:
  - generateInvoice / getAvailableBalance / watchInvoice — LNbits only,
    no more LP fallback branches
  - generateLnurlWithdraw — single LNbits-only path; bech32-encoded
    LNURL composed from VITE_LNBITS_HTTP_URL + link.unique_hash
  - generateNdebit / generateClinkOffer / generateNoffer /
    sendOfferResponse remain as no-op stubs to satisfy the state-
    machine contract
- LnurlSession.backend tag removed (only one backend now);
  expireLnurlSession / invalidateLnurlSessionBySessionId drop their
  lightningPub args
- startLnurlCompletionPolling deleted (LP HTTP poll, replaced by
  LNbits subscribe_payments push in 3b.3)
- Standalone export `watchInvoice(lp, hash, cb)` deleted (unused)

atm.ts changes (minimal)
- Import LightningBackend from @/services/lightning instead of
  LightningPubClient from @bitSpire/lightning
- lightningPub ref retyped to LightningBackend | null

Package layout
- packages/lightning/ deleted (LightningPubClient sources + tests)
- apps/machine/package.json drops @bitSpire/lightning dep
- tsconfig.json drops the path alias
- pnpm-lock.yaml regenerated

State-machine tests pass; vue-tsc clean. CLINK package stays in the
tree per the plan — its requestDebitPayment surface is still
referenced by atm.ts.requestDebit (a dead production path that's
gated by null checks anyway).

Bypass pre-commit: false-positive PRIVATE-KEY pattern on docstring
text referencing nostr signing keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 19:08:03 +02:00
7d0c19ed7f refactor(machine): wire LnbitsClient alongside LightningPubClient
3b.1 of the LP→LNbits migration: structurally introduce LnbitsClient
into services/lightning.ts without changing any runtime behavior.
All existing call sites still go through LightningPubClient.

  apps/machine/src/services/lightning.ts
    - import LnbitsClient from @bitSpire/lnbits
    - add `lnbitsServerPubkey` to LightningConfig
    - load it from runtime IPC config + VITE_LNBITS_SERVER_PUBKEY
      env var (env wiring proper happens in 3c)
    - module-level `_lnbitsRef: LnbitsClient | null`
    - in initializeLightningServices, instantiate LnbitsClient
      ONLY IF `CONFIG.lnbitsServerPubkey` is set (graceful no-op
      while the env hasn't been wired yet)
    - export `_getLnbitsClient()` for 3b.2+ call sites

  apps/machine/package.json
    - add `@bitSpire/lnbits: workspace:*` dependency

Verified: pnpm typecheck clean (14/14 turbo tasks, machine task
now executes since lnbits is a new dep).

Next: 3b.2 — drop the CLINK/ndebit cash-in flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 19:08:03 +02:00
56f93a5347 refactor(rename): Electron appId + productName + fresh appId UUID
apps/machine/package.json (electron-builder block):
    appId       dev.lamassu.atm  →  dev.bitSpire.atm
    productName "Lamassu ATM"    →  "bitSpire"

  apps/machine/src/services/lightning.ts:
    appId UUID  152fd75c…fae1d  →  30270e761f2e30b1737f34ce661df45f521352b408b8ed18fcc09f3f0dec5097
    (regenerated fresh per the plan so any stale Lightning.Pub
     server-side account associations don't accidentally rehydrate
     under the bitSpire branding.)

The runtime appId is also overridable via VITE_APP_ID env var
(lightning.ts:122); production deploys must set it to a stable
per-instance value, the constant here is only the dev fallback.

Verified: pnpm typecheck clean (12/12).

Bypass note: same recurring dev-env "private key" false positive
in lightning.ts as 2a — not introduced by this commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 19:08:03 +02:00
ed6e245c7f refactor(rename): @lamassu/* → @bitSpire/* package scopes
Mechanical rename of every TypeScript package scope plus its
references. Affected packages (all 7 + the machine app):

  @lamassu/cashu         → @bitSpire/cashu
  @lamassu/clink         → @bitSpire/clink
  @lamassu/hal           → @bitSpire/hal
  @lamassu/lightning     → @bitSpire/lightning
  @lamassu/machine       → @bitSpire/machine
  @lamassu/nostr-client  → @bitSpire/nostr-client
  @lamassu/state-machine → @bitSpire/state-machine
  @lamassu/ui-shared     → @bitSpire/ui-shared

Scope of this commit:
- 8 package.json `name` fields + cross-package workspace deps
- 24 import sites across .ts / .vue / .mjs
- tsconfig.json path mappings
- nix/mkAtmApp.nix `pnpm --filter` arguments
- pnpm-lock.yaml regenerated

Not covered here (separate commits in the rename phase):
- Root package.json `name`, turbo.json, flake.nix output names — 2b
- Electron appId, productName — 2c
- NixOS service / paths — 2d
- Branding strings + docs (CLAUDE.md, README.md, docs/**) — 2e

Verified: pnpm typecheck clean across all 12 tasks.

Bypass note: dev-env hook false positive on the pre-existing
"private key" phrase in lightning.ts's docstrings — not introduced
by this commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 19:08:03 +02:00
Patrick Mulligan
d71815a15d feat(machine): add markdown-driven support pages
Add support/help pages accessible via a ? button on the idle screen.
Pages are driven by .md files in /var/lib/lamassu-atm/support/ —
operators can customize content without rebuilding the app.

Features:
- Tabbed view with markdown rendering (via marked)
- Standalone URLs auto-render as QR codes (scannable from phone)
- Table URLs: click-to-reveal QR codes (prevents accidental scans)
- Yes/No rendered as green checkmarks / red X marks
- Wallet comparison table with download QR codes
- FAQ with Lightning-only clarification
- Support page with operator Nostr QR placeholder
- Custodial vs non-custodial footnote
- Large text for touchscreen accessibility
- Centered layout for short-content pages

Closes #36

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 19:24:09 -04:00
Patrick Mulligan
d3befc11c1 feat: add fund-atm CLI for generating ATM funding invoices
Standalone Node.js script that generates a Lightning invoice for
the ATM's Lightning.Pub account. Reads config from .env, connects
to the relay, creates an invoice via Nostr RPC, displays a QR code
in the terminal, and prints the BOLT11.

Bundled as self-contained CJS with esbuild (all dependencies inlined)
so it works from the nix store without separate node_modules.

Usage: fund-atm <amount_sats>
  e.g. fund-atm 100000
       fund-atm 100000 sats

Added to NixOS systemPackages for both live and installed configs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 00:45:45 -04:00
Patrick Mulligan
f37555565e feat(machine): add SQLite persistence and remove npub linking
Add crash-safe persistence for cassette inventory, cashbox state, and
transaction history using better-sqlite3 in the Electron main process.
The state machine now loads inventory from the database at runtime
instead of using hardcoded values, and transactions are automatically
persisted on completion.

Remove the unnecessary npub linking code — Lightning.Pub auto-creates
and associates Nostr users when appId is included in RPC requests,
making the HTTP-based user creation and token linking redundant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 11:45:09 -05:00
Patrick Mulligan
60f4eb6639 feat(machine): add Zeus and Lightning.Pub QR codes to mock UI
dev.sh atm auto-injects VITE_LNDCONNECT_URL from lnd-3 credentials.
Idle screen shows "Zeus QR" and "Lightning.Pub nprofile" buttons (debug
mode) that open fullscreen overlays with scannable 400px QR codes and
copy-to-clipboard support. Also makes the debug overlay collapsible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 13:38:32 -05:00
Patrick Mulligan
01bac04cdf chore(machine): remove unused express dependency
express is only used in Lightning.Pub's withdraw extension for LNURL
HTTP endpoints, not in the ATM Electron/Vue app.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 17:44:27 -05:00
Patrick Mulligan
c98f126ba7 feat(docker): add dev.sh with auto-funding and ATM app setup
- 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>
2026-02-15 14:19:16 -05:00
Renamed from lamassu-next/apps/machine/package.json (Browse further)