bitspire/docs/architecture-comparison.md
Padreug 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

19 KiB

Architecture Comparison: bitSpire vs Traditional Lamassu

This document compares the bitSpire architecture — a Nostr-native Lightning ATM talking to LNbits over the nostr-native-transport — with the traditional lamassu-server + lamassu-machine architecture. The goal is to help an operator evaluating an open-source Lightning ATM stack understand what they're choosing between.

A note on prior art: bitSpire's hardware drivers and cash-flow state machine derive from Lamassu Industries AG's open-source lamassu-machine and lamassu-server projects up to and including the v8.1.5 release line (the last published under a fully-open license). Lamassu transitioned to a proprietary source-available license on 2024-01-26; bitSpire does not incorporate code from v8.1.6 or later and is not affiliated with Lamassu Industries AG. See the Acknowledgements section of the top-level README.

Executive summary

Aspect Traditional (lamassu-server ≤ v8.1.5) bitSpire
Software license AGPL-3.0 (v8.1.5); v8.1.6+ is proprietary SLA AGPL-3.0
Communication Custom WebSocket + GraphQL over HTTPS Nostr kind-21000 events (NIP-01, NIP-44 v2) over a relay
Identity Server-issued client certs Nostr keypairs (nsec/npub)
Account system PostgreSQL + custom auth LNbits wallet auto-created from ATM's nostr pubkey
Payment protocol Direct LND RPC BOLT11 (cash-out) + LNURL-withdraw (cash-in)
Server infrastructure App server + DB + Admin UI A relay + an LNbits instance (both can be one container)
Wallet compatibility Any Lightning wallet (cash-out); custom flow for cash-in Any Lightning wallet (BOLT11 + LNURL-withdraw — both widely supported)
State location Centralized in PostgreSQL Distributed: LNbits holds Lightning state, ATM holds session state

Traditional architecture (lamassu-server / lamassu-machine, v8.1.5)

┌──────────────────┐         WebSocket           ┌─────────────────────┐
│ ATM (kiosk)      │◄──────────────────────────►│ lamassu-server      │
│ lamassu-machine  │       client-cert auth      │                     │
└──────────────────┘                             │  ┌───────────────┐  │
                                                 │  │  PostgreSQL   │  │
┌──────────────────┐         HTTPS               │  └───────────────┘  │
│ Admin UI         │◄──────────────────────────►│                     │
│ (React SPA)      │       GraphQL              │  ┌───────────────┐  │
└──────────────────┘                             │  │     LND       │  │
                                                 │  └───────────────┘  │
┌──────────────────┐                             │                     │
│ Customer Wallet  │◄── Lightning Invoice ──────│  ┌───────────────┐  │
│ (any LN wallet)  │                             │  │  Compliance   │  │
└──────────────────┘                             │  │   Services    │  │
                                                 │  └───────────────┘  │
                                                 └─────────────────────┘

Components

Component Description
lamassu-server Node.js (Express + Apollo GraphQL) backend
lamassu-machine Node.js kiosk app with hardware drivers
PostgreSQL Central DB for transactions, users, config
Admin UI React dashboard for fleet management
LND Lightning Network Daemon for payments

Cash-out flow

  1. Machine boots, authenticates via client cert
  2. Server pushes config + state over WebSocket
  3. Customer initiates cash-out at machine
  4. Machine asks server for an invoice
  5. Server creates invoice via LND, stores in PostgreSQL
  6. Customer pays the invoice
  7. Server polls LND, detects payment, updates DB
  8. Server notifies machine to dispense
  9. Compliance services log the transaction

Characteristics

  • Centralized: all state in lamassu-server's PostgreSQL
  • Operator-managed: requires real infrastructure (server, DB, certs, monitoring)
  • Tightly coupled: machine doesn't transact if server is unreachable
  • Compliance-ready: KYC/AML hooks built in (relevant in regulated jurisdictions)

bitSpire architecture

┌──────────────────┐                                ┌──────────────────┐
│ ATM (kiosk)      │                                │ Customer wallet  │
│ npub_atm         │                                │ (any LN wallet)  │
└────────┬─────────┘                                └────────┬─────────┘
         │ kind-21000                                        │
         │ NIP-44 v2 encrypted                               │ BOLT11
         ▼                                                   │ LNURL-withdraw
   ┌───────────────────────────────┐                         │
   │     Nostr Relay               │                         │
   │  (LNbits' bundled             │                         │
   │   nostrrelay extension, or    │                         │
   │   any standalone relay)       │                         │
   └────────────────┬──────────────┘                         │
                    │                                        │
                    │ kind-21000 RPC + push                  │
                    ▼                                        │
            ┌───────────────────────┐ ◄──────────────────────┘
            │ LNbits                │   BOLT11 settle / LNURLw redeem
            │ ┌──────────────────┐  │
            │ │ FakeWallet (dev) │  │
            │ │ or LND/CLN/etc.  │  │
            │ └──────────────────┘  │
            └───────────────────────┘

Components

Component Description
Nostr relay Message broker. In the dev compose, this is LNbits's bundled nostrrelay extension at ws://<host>:5001/nostrrelay/test. Production can use any standalone relay both peers subscribe to
LNbits Lightning wallet platform; runs the nostr-native-transport branch. Auto-creates a wallet on first contact from any nostr pubkey
ATM machine Electron + Vue 3 kiosk identified by its npub. Signing key IS the credential to LNbits
Customer wallet Any LN wallet that handles BOLT11 (cash-out) and LNURL-withdraw (cash-in) — most do

Cash-out flow (customer pays ATM, gets cash)

  1. ATM publishes a kind-21000 event to LNbits: {rpc_name: "create_invoice", body: {amount: <sats>, memo: "..."}}
  2. LNbits replies on the same relay with the BOLT11 invoice
  3. ATM displays the BOLT11 as a QR
  4. Customer scans, pays from any LN wallet
  5. LNbits's Lightning backend (FakeWallet / LND / CLN / etc.) settles the payment
  6. LNbits pushes a kind-21000 settlement event to the ATM, filtered by payment_hash
  7. ATM dispenses cash

Cash-in flow (customer hands ATM cash, gets sats)

  1. ATM publishes lnurlw_create_link (kind-21000) to LNbits with {uses: 1, max_withdrawable: <sats>}
  2. LNbits replies with a WithdrawLink carrying a lnurl field already populated from settings.lnbits_baseurl (per aiolabs/withdraw#1 / e9d911e)
  3. ATM displays the LNURL as a QR (uppercased per BOLT/LNURL convention)
  4. Customer scans with any LNURL-withdraw-capable wallet, redeems it
  5. LNbits settles the withdrawal via its Lightning backend
  6. LNbits pushes a kind-21000 settlement event to the ATM, filtered by tag="withdraw" + link_id
  7. ATM marks the session complete (cash already physically accepted earlier in the flow)

Characteristics

  • Decentralized message layer: any nostr relay works, no proprietary protocol
  • No admin tokens on the kiosk: the ATM's signing key IS the credential — there are no HTTP API tokens or client certs to leak. LNbits derives the calling identity from the event signature.
  • Loose coupling: ATM and LNbits don't need to share a network segment; only a relay both reach
  • Privacy-first: no central transaction logs unless the operator builds them. LNbits stores its own ledger, but bitSpire writes no user-identifying data anywhere on its side.

Detailed comparison

1. Infrastructure requirements

Requirement Traditional bitSpire
Server hardware Dedicated server (4+ GB RAM, SSD) An LNbits instance (1 GB RAM is plenty in production)
Database PostgreSQL (operator managed) LNbits's SQLite (or its configured DB; LNbits manages it)
Client TLS Required (client certs per machine) Not required (signature-based auth over a nostr relay)
Domain Required Optional (only if exposing LNbits HTTP for LNURL redemption from the public internet)
Static IP Recommended Not required
Firewall Multiple inbound ports Outbound WebSocket to a relay; inbound only on the LNbits HTTP port for LNURL callbacks

Edge: bitSpire. A single-container LNbits with FakeWallet is enough for an operator to evaluate the whole stack end-to-end.

2. Security model

Aspect Traditional bitSpire
Machine auth Client certificates Nostr signing key
Transport security TLS NIP-44 v2 (XChaCha20 + HMAC-SHA256)
Compromise scope Server breach exposes all machines One machine's nsec compromises only that machine
Key storage Server manages Machine manages its own keys (/var/lib/bitspire/.env, mode 0600)
Revocation CA-style cert revocation Operator can stop honoring a pubkey at the LNbits layer; no fleet-wide blast radius

Edge: bitSpire. No central authority that, if compromised, exposes the whole fleet. Per-machine compromise stays contained.

3. Payment flow

Aspect Traditional bitSpire
Invoice creation Server creates via LND LNbits creates via its Lightning backend, returned over kind-21000
Payment detection Server polls LND LNbits pushes a settlement event over kind-21000 (subscribe_payments filtered by payment_hash)
Cash-in detection Custom flow LNbits pushes a tag="withdraw" + link_id event when the LNURL is redeemed
Wallet compatibility Any BOLT11 wallet Any BOLT11 wallet (cash-out) + any LNURL-withdraw wallet (cash-in)
Offline capability None Cashu ecash (placeholder package, not yet wired)

Tied. Both architectures end up using BOLT11 + LNURL-withdraw at the customer-wallet boundary. The difference is on the ATM side: traditional polls a server it owns; bitSpire subscribes to a push channel on a wallet it shares with anyone who can reach the relay.

4. Operator experience

Aspect Traditional bitSpire
Setup time Hours (provision server, DB, TLS, cert each machine) Minutes (run an LNbits instance + a relay; flash + provision each ATM)
Maintenance DB backups, server updates, monitoring Update LNbits + the bitSpire ATMs (the latter auto-upgrades from the dev branch nightly)
Fleet management Mature Admin UI Per-ATM journalctl; centralized dashboard is planned, not built
Transaction history PostgreSQL queries Per-ATM /var/lib/bitspire/state.db (SQLite); operator-side aggregation is on them to build
Configuration Server-pushed Per-ATM .env written via provision-atm.sh

Mixed. Traditional has mature operator tooling. bitSpire is simpler to stand up but the fleet-management story is currently "ssh + scripts" rather than a UI.

5. Customer experience

Aspect Traditional bitSpire
Cash-out Scan BOLT11, pay Scan BOLT11, pay
Cash-in Varies by deployment (LNURL-withdraw common, sometimes custom) Scan LNURL-withdraw QR, redeem
Wallet requirements Any LN wallet for cash-out Any LN wallet for cash-out + any LNURL-w wallet for cash-in
Account required Sometimes (compliance) Never
Transaction speed ~10 seconds ~3-5 seconds (push-based; no server polling delay)

Tied on protocol; slight edge to bitSpire on speed because LNbits push events skip the polling cycle that traditional setups have between LND and the application server.

6. Compliance & regulation

Aspect Traditional bitSpire
KYC integration Built-in Not included
Transaction logs PostgreSQL (server) Per-ATM SQLite + whatever LNbits records on its side
Audit trail Comprehensive Operator-defined; LNbits's ledger is the canonical Lightning-side record
Reporting Admin UI exports Custom tooling needed
Regulatory fit Designed for KYC/AML jurisdictions Privacy-first design; suits jurisdictions without identity-collection requirements

Edge: Traditional if you need built-in compliance. bitSpire targets KYC-free operation; operators in regulated jurisdictions would need to add compliance hooks themselves.

7. Resilience & availability

Aspect Traditional bitSpire
Server down All machines stop transacting Machines stop transacting if LNbits is down; relay outage means RPCs hang
Network partition Transactions fail Can connect to multiple relays for failover
Database corruption Catastrophic (cascading impact across fleet) LNbits-side issue affects only the affected operator's wallet; per-ATM SQLite is local and independent
Recovery Restore from backup LNbits-side restore is its concern; ATMs come back up clean by re-reading .env
Geographic distribution Single server LNbits can be regional; relays can be anywhere

Edge: bitSpire for blast-radius reasons. LNbits-side failures are no worse than lamassu-server-side failures, but bitSpire's per-ATM state is genuinely isolated.

8. Development & extensibility

Aspect Traditional bitSpire
Codebase Large monolith (lamassu-server is hundreds of files) Modular workspace; ATM, transport client, HAL, state machine are separate packages
Protocol Custom WebSocket schema NIP-01 + NIP-44 v2 + a thin documented kind-21000 envelope
Third-party integration Custom API needed Any nostr/Lightning client can talk to LNbits over the transport
Community Lamassu operators (and ex-operators after the license change) Nostr + Bitcoin ecosystem; LNbits community
Forkability Complex (server + machine are coupled) Straightforward (replace any one package without touching the others)

Edge: bitSpire. Open protocols mean a wider ecosystem of compatible clients, wallets, and tools.


When to choose what

Choose bitSpire if

  • You operate in a jurisdiction without mandatory KYC/AML
  • You want minimal infrastructure
  • You value customer privacy (no PII collection, no central server-side ledger of who-paid-what)
  • You're comfortable with emerging tooling (no admin UI yet)
  • You want a license that stays open (AGPL-3.0)

Stick with lamassu-server v8.1.5 if

  • You need built-in compliance features
  • You need a mature admin dashboard today
  • You have existing infrastructure investment in PostgreSQL + LND tooling
  • You're comfortable being on a code line that no longer receives upstream updates (v8.1.5 is the last open release; bug fixes in v8.1.6+ are paid-license-only)

Choose Lamassu's current commercial offering if

  • You want vendor support, official updates, and a commercial SLA
  • You're willing to pay the per-machine subscription
  • You operate in a regulated jurisdiction where KYC tooling matters

This last option is outside the scope of this document — see lamassu.is for their current commercial terms.


Trade-offs & honest assessment

Where bitSpire excels

  1. Simplicity — LNbits + a relay is a much smaller surface than a stateful application server + DB + admin tooling
  2. Privacy — no central transaction logs by design
  3. Resilience — no single point of failure on the ATM side; LNbits is replaceable
  4. Interoperability — works with any BOLT11 / LNURL-withdraw wallet
  5. Speed — push-based settlement detection skips the LND-poll cycle
  6. Cost — no Lamassu license fees; no commercial admin-server hosting

Where traditional (lamassu-server ≤ v8.1.5) is still better

  1. Compliance — built-in KYC/AML if your jurisdiction requires it
  2. Tooling — mature admin dashboard, fleet management, transaction reporting
  3. Maturity — battle-tested in production over many years
  4. Documentation — established support channels for operators (community-maintained for v8.1.5; commercial for v8.1.6+)

Current limitations of bitSpire

Limitation Status Mitigation
No fleet dashboard Planned, not built Per-ATM journalctl + custom scripts
Limited transaction reporting SQLite per ATM Aggregate via operator-side ETL if needed
No offline mode @bitSpire/cashu is a placeholder Requires LNbits connectivity currently
Smaller operator community Growing This doc + the README walkthrough

Further reading