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>
305 lines
19 KiB
Markdown
305 lines
19 KiB
Markdown
# 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](https://blog.lamassu.is/updates-to-our-lamassu-software-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](../README.md#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](https://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
|
|
|
|
- [LNbits nostr-native-transport docs](https://git.atitlan.io/aiolabs/lnbits) — the wire spec for what's actually happening on kind-21000
|
|
- [NIP-01 Nostr basics](https://github.com/nostr-protocol/nips/blob/master/01.md)
|
|
- [NIP-44 v2 encryption](https://github.com/nostr-protocol/nips/blob/master/44.md)
|
|
- [LUD-03 LNURL-withdraw](https://github.com/lnurl/luds/blob/luds/03.md)
|
|
- [CLINK Protocol](./clink-protocol.md) — historical reference, dormant on `dev`
|
|
- [bitSpire deployment walkthrough](../deploy/nixos/README.md)
|
|
- [machine-installation.md](./machine-installation.md) — high-level deployment overview
|