miscellaneous files

This commit is contained in:
padreug 2025-10-12 14:24:20 +02:00
parent 3c26a15ffe
commit 2f0cc901eb
6 changed files with 1448 additions and 0 deletions

86
CLAUDE.md Normal file
View file

@ -0,0 +1,86 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a Bitcoin ATM server implementation built as a monorepo with three main packages:
1. **packages/server** - Node.js backend with Express, GraphQL, PostgreSQL
2. **packages/admin-ui** - React frontend with Material-UI
3. **packages/typesafe-db** - TypeScript database layer using Kysely
## Common Commands
### Development Setup
- `pnpm install` - Install dependencies (Node.js 22+ required)
- `bash packages/server/tools/cert-gen.sh` - Generate SSL certificates
- `node packages/server/bin/lamassu-migrate` - Run database migrations
- `node packages/server/bin/lamassu-register admin@example.com superuser` - Create admin user
### Development
- `pnpm run dev` - Start development servers (both admin UI and server)
- `pnpm run build` - Build all packages using Turbo
- `pnpm run test` - Run tests
### TypeScript/Database
- `cd packages/typesafe-db && npm run generate-types` - Generate database types
- `cd packages/typesafe-db && npm run build` - Build TypeScript database layer
### Individual Package Commands
- Server: `npm run dev` in packages/server (runs both server and admin-server with --watch)
- Admin UI: `npm run dev` in packages/admin-ui (Vite dev server)
## Architecture
**Monorepo Structure**: Uses Turbo for build orchestration and PNPM workspaces.
### packages/server
- **bin/** - CLI tools and executables (lamassu-migrate, lamassu-register, etc.)
- **lib/** - Core business logic
- **blockchain/** - Cryptocurrency handling (bitcoin, ethereum, etc.)
- **cash-in/**, **cash-out/** - Transaction processing
- **compliance/** - KYC/AML and sanctions checking
- **new-admin/** - GraphQL API and admin server
- **plugins/** - Exchange, wallet, and service integrations
- **routes/** - Express API endpoints
- **migrations/** - Database schema migrations
- Express server with GraphQL API, PostgreSQL database
### packages/admin-ui
- React app built with Vite
- **src/pages/** - Main application screens
- **src/components/** - Reusable UI components
- Material-UI design system
- Apollo Client for GraphQL
### packages/typesafe-db
- Kysely for type-safe SQL queries
- Auto-generates TypeScript types from PostgreSQL schema
- Shared database layer across packages
## Key Configuration
### Environment Setup
- PostgreSQL database required
- Environment variables configured in `packages/server/.env`
- SSL certificates generated via `cert-gen.sh`
### Build System
- **turbo.json** - Defines build pipeline and caching
- **pnpm-workspace.yaml** - Workspace package definitions
- **eslint.config.mjs** - ESLint configuration with React, TypeScript, Vitest rules
- Husky pre-commit hooks with lint-staged for code quality
### Database
- PostgreSQL with extensive migration system
- Critical data: transactions, customers, machines, compliance records
- Use typesafe-db package for database operations
## Development Guidelines
### Code Quality
- TypeScript for database operations via typesafe-db package
- Follow existing patterns in GraphQL resolvers and Express routes
- Test database changes with migrations before deployment
- ESLint + Prettier enforced via pre-commit hooks

View file

@ -0,0 +1,169 @@
# Bitcoin Node and Wallet Configuration Guide
## Overview
The Lamassu server uses environment variables `BTC_NODE_LOCATION` and `BTC_WALLET_LOCATION` to control how it interacts with Bitcoin infrastructure. These variables determine whether Bitcoin node and wallet operations run locally or connect to remote services.
## Configuration Values
Both variables accept two values:
- **`local`** - Run Bitcoin Core node/wallet on the same server as Lamassu
- **`remote`** - Connect to external Bitcoin Core node/wallet services
## Valid Configuration Combinations
### 1. Local Node + Local Wallet ✅
```bash
BTC_NODE_LOCATION=local
BTC_WALLET_LOCATION=local
```
- **Use case**: Full local Bitcoin Core setup
- **Requirements**: Sufficient storage for blockchain, bandwidth for sync
- **Security**: Maximum control and security
### 2. Remote Node + Remote Wallet ✅
```bash
BTC_NODE_LOCATION=remote
BTC_WALLET_LOCATION=remote
```
- **Use case**: Fully hosted Bitcoin infrastructure
- **Requirements**: All remote connection variables (see below)
- **Security**: Depends on remote service trust
### 3. Remote Node + Local Wallet ✅
```bash
BTC_NODE_LOCATION=remote
BTC_WALLET_LOCATION=local
```
- **Use case**: Use remote blockchain data with local key management
- **Requirements**: Remote node connection variables
- **Security**: Local key control, remote blockchain dependency
### 4. Local Node + Remote Wallet ❌
```bash
BTC_NODE_LOCATION=local
BTC_WALLET_LOCATION=remote
```
- **Status**: **FORBIDDEN** by Lamassu validation
- **Error**: "It's not possible to use a remote wallet without using a remote node!"
- **Reason**: Remote wallet services need their own blockchain access
## Required Environment Variables
### For Remote Node Configurations
When `BTC_NODE_LOCATION=remote`:
```bash
BTC_NODE_HOST=<remote-node-ip>
BTC_NODE_PORT=<p2p-port> # Usually 8333 for mainnet, 18333 for testnet
```
### For Remote Wallet Configurations
When `BTC_WALLET_LOCATION=remote`:
```bash
BTC_NODE_RPC_HOST=<rpc-host>
BTC_NODE_RPC_PORT=<rpc-port> # Usually 8332 for mainnet, 18332 for testnet
BTC_NODE_USER=<rpc-username>
BTC_NODE_PASSWORD=<rpc-password>
```
## Environment Setup Scripts
### Development Environment
File: `packages/server/tools/build-dev-env.js`
```javascript
BTC_NODE_LOCATION=remote // Avoid full blockchain sync
BTC_WALLET_LOCATION=local // Local testing
```
### Production Environment
File: `packages/server/tools/build-prod-env.js`
```javascript
BTC_NODE_LOCATION=local // Full control
BTC_WALLET_LOCATION=local // Maximum security
```
## Validation Logic
The configuration is validated in `packages/server/lib/blockchain/install.js`:
```javascript
function isEnvironmentValid(crypto) {
// Prevent remote wallet + local node
if (isRemoteWallet(crypto) && !isRemoteNode(crypto))
throw new Error(
`Invalid environment setup for ${crypto.display}: It's not possible to use a remote wallet without using a remote node!`
)
// Validate required variables for each configuration...
}
```
### Logic Breakdown
For `BTC_NODE_LOCATION=remote` + `BTC_WALLET_LOCATION=local`:
- `isRemoteWallet(crypto)``false` (wallet is local)
- `!isRemoteNode(crypto)``false` (node is remote, so `!remote` = `false`)
- `false && false``false` (validation passes)
## Bitcoin Core Configuration Impact
The variables affect the generated `bitcoin.conf`:
### Local Node Configuration
```conf
rpcuser=lamassuserver
rpcpassword=<generated>
server=1
rpcport=8333
bind=0.0.0.0:8332
```
### Remote Node Configuration
```conf
rpcuser=lamassuserver
rpcpassword=<generated>
server=1
rpcport=8333
bind=0.0.0.0:8332
connect=<BTC_NODE_HOST>:<BTC_NODE_PORT> # Only connect to specified peer
```
## Multi-Cryptocurrency Support
The same pattern applies to all supported cryptocurrencies:
- `BCH_NODE_LOCATION` / `BCH_WALLET_LOCATION`
- `LTC_NODE_LOCATION` / `LTC_WALLET_LOCATION`
- `DASH_NODE_LOCATION` / `DASH_WALLET_LOCATION`
- `ZEC_NODE_LOCATION` / `ZEC_WALLET_LOCATION`
- `XMR_NODE_LOCATION` / `XMR_WALLET_LOCATION`
## Bitcoin Core Compatibility
These configurations align with standard Bitcoin Core functionality:
- **Local Node + Local Wallet**: Standard `bitcoind` with wallet enabled
- **Remote Node + Remote Wallet**: Client connecting to remote Bitcoin Core RPC
- **Remote Node + Local Wallet**: Local wallet connecting to remote node via RPC
The restriction on "Local Node + Remote Wallet" is a Lamassu design choice, not a Bitcoin protocol limitation.
## Security Considerations
| Configuration | Storage | Bandwidth | Security | Trust |
|--------------|---------|-----------|----------|-------|
| Local + Local | High | High | Maximum | Self |
| Remote + Remote | Low | Low | Depends on service | Remote service |
| Remote + Local | Low | Medium | Keys local, blockchain remote | Remote node |
| Local + Remote | High | High | ❌ Not allowed | N/A |
## Troubleshooting
### Common Errors
1. **"Environment variable BTC_NODE_LOCATION is not set!"**
- Solution: Set the required environment variable
2. **"It's not possible to use a remote wallet without using a remote node!"**
- Solution: Use a valid configuration combination
3. **Missing RPC connection variables**
- Solution: Set all required variables for remote configurations

View file

@ -0,0 +1,543 @@
# Lamassu Lightning Network Integration Analysis
## Overview
This document provides a comprehensive technical analysis of how the Lamassu Bitcoin ATM server integrates with the Lightning Network to process Bitcoin Lightning transactions. The analysis covers the complete flow from hardware interaction to Lightning invoice creation and payment processing.
## Lightning Network Architecture
Lamassu implements Lightning Network support through the **Galoy wallet plugin**, which acts as a Lightning service provider rather than running a local Lightning node.
**Key Architecture Decision**: Despite the presence of a `LIGHTNING_NETWORK_DAEMON` environment variable, Lightning functionality is actually handled via **Galoy's hosted GraphQL API**, not a local Lightning daemon.
### Core Components
- **Transaction Coordinator**: Routes Lightning transactions through the cash-in/cash-out pipeline
- **Galoy Wallet Plugin**: Interfaces with Galoy's Lightning infrastructure via GraphQL
- **Invoice Management**: Creates, monitors, and processes Lightning invoices
- **Payment Probing**: Tests Lightning payment routes and limits
## Lightning Invoice Creation Flow
When a customer wants to purchase $100 of Bitcoin Lightning, the following sequence occurs:
### 1. Machine Initiates Transaction Request
The ATM machine sends a POST request to the server:
```http
POST /tx
Content-Type: application/json
{
"direction": "cashIn",
"cryptoCode": "LN",
"fiat": 10000,
"cryptoAtoms": "546448",
"isLightning": true,
"deviceId": "<machine-id>",
"txVersion": 1
}
```
**Parameters Explained**:
- `fiat`: Amount in cents ($100.00 = 10000 cents)
- `cryptoAtoms`: Equivalent amount in satoshis
- `cryptoCode`: "LN" indicates Lightning Network
- `isLightning`: Boolean flag for Lightning transactions
### 2. Server Processing Pipeline
#### Route Handler
**File**: `packages/server/lib/routes/txRoutes.js:13-49`
```javascript
function postTx(req, res, next) {
const pi = plugins(req.settings, req.deviceId)
return Tx.post(_.set('deviceId', req.deviceId, req.body), pi)
.then(tx => res.json(tx))
.catch(next)
}
```
#### Transaction Coordinator
**File**: `packages/server/lib/tx.js:13-18`
```javascript
function process(tx, pi) {
const mtx = massage(tx)
if (mtx.direction === 'cashIn') return CashInTx.post(mtx, pi) // Lightning routing
if (mtx.direction === 'cashOut') return CashOutTx.post(mtx, pi)
return Promise.reject(new Error('No such tx direction: ' + mtx.direction))
}
```
#### Cash-In Handler
**File**: `packages/server/lib/cash-in/cash-in-tx.js:154-155`
```javascript
return pi
.newAddress(r.tx) // Creates Lightning invoice
.then(txObj => {
// Process the invoice response
if (txObj.batched) {
return txBatching.addUnconfirmedTx(r.tx)
}
// Handle immediate processing
})
```
### 3. Lightning Invoice Generation
#### Plugin System Routing
**File**: `packages/server/lib/plugins.js:427-437`
```javascript
function newAddress(tx) {
const info = {
cryptoCode: tx.cryptoCode, // "LN"
label: 'TX ' + Date.now(),
hdIndex: tx.hdIndex,
cryptoAtoms: tx.cryptoAtoms, // Amount in satoshis
isLightning: tx.isLightning // true
}
return wallet.newAddress(settings, info, tx) // Calls Galoy plugin
}
```
#### Galoy Lightning Plugin
**File**: `packages/server/lib/plugins/wallet/galoy/galoy.js:310-320`
```javascript
function newAddress(account, info, tx) {
const { cryptoAtoms, cryptoCode } = tx
return checkCryptoCode(cryptoCode).then(() =>
newInvoice(
account.walletId,
cryptoAtoms, // Amount in satoshis
account.apiSecret,
account.endpoint,
),
)
}
```
### 4. Galoy GraphQL Lightning Invoice Creation
#### Invoice Creation Function
**File**: `packages/server/lib/plugins/wallet/galoy/galoy.js:279-298`
```javascript
function newInvoice(walletId, cryptoAtoms, token, endpoint) {
const createInvoice = {
operationName: 'lnInvoiceCreate',
query: `mutation lnInvoiceCreate($input: LnInvoiceCreateInput!) {
lnInvoiceCreate(input: $input) {
errors {
message
path
}
invoice {
paymentRequest // Lightning invoice string
}
}
}`,
variables: {
input: {
walletId,
amount: cryptoAtoms.toString()
}
}
}
return request(createInvoice, token, endpoint).then(result => {
return result.data.lnInvoiceCreate.invoice.paymentRequest // Returns "lnbc..."
})
}
```
#### GraphQL Request Handler
**File**: `packages/server/lib/plugins/wallet/galoy/galoy.js:10-28`
```javascript
function request(graphqlQuery, token, endpoint) {
const headers = {
'content-type': 'application/json',
'X-API-KEY': token,
}
return axios({
method: 'post',
url: endpoint,
headers: headers,
data: graphqlQuery,
})
.then(r => {
if (r.error) throw r.error
return r.data
})
}
```
### 5. Response to Machine
The server returns the Lightning invoice to the ATM machine:
```json
{
"id": "tx-uuid-12345",
"toAddress": "lnbc5464480n1pn2s...", // Lightning invoice
"layer2Address": null,
"cryptoCode": "LN",
"direction": "cashIn",
"fiat": 10000,
"cryptoAtoms": "546448",
"status": "authorized"
}
```
## Complete Machine-Server Interaction Flow
```
[ATM Machine] [Lamassu Server] [Galoy API]
│ │ │
│ 1. POST /tx │ │
│ {direction:"cashIn", LN, $100}│ │
├──────────────────────────────►│ │
│ │ 2. Process transaction │
│ │ - Route to cash-in │
│ │ - Extract amount & type │
│ │ │
│ │ 3. GraphQL newInvoice │
│ ├───────────────────────────►│
│ │ lnInvoiceCreate mutation │
│ │ │
│ │ 4. Lightning invoice │
│ │◄───────────────────────────┤
│ │ "lnbc5464480n1..." │
│ │ │
│ 5. Transaction response │ │
│ {toAddress: "lnbc..."} │ │
│◄──────────────────────────────┤ │
│ │ │
│ 6. Display QR code to user │ │
│ Show Lightning invoice │ │
│ │ │
│ 7. Poll for payment status │ │
│ GET /tx/:id?status=confirmed │ │
├──────────────────────────────►│ │
│ │ 8. Check invoice status │
│ ├───────────────────────────►│
│ │ lnInvoicePaymentStatus │
│ │ │
│ │ 9. Payment status │
│ │◄───────────────────────────┤
│ 10. Confirmation response │ "PAID" or "PENDING" │
│ {status: "confirmed"} │ │
│◄──────────────────────────────┤ │
│ │ │
│ 11. Dispense cash to user │ │
```
## Lightning Payment Detection and Processing
### Address Type Detection
**File**: `packages/server/lib/plugins/wallet/galoy/galoy.js:94-100`
```javascript
function isLnInvoice(address) {
return address.toLowerCase().startsWith('lnbc') // Lightning mainnet invoices
}
function isLnurl(address) {
return address.toLowerCase().startsWith('lnurl') // Lightning URL format
}
```
### Payment Status Monitoring
**File**: `packages/server/lib/plugins/wallet/galoy/galoy.js:322-339`
```javascript
function getInvoiceStatus(token, endpoint, address) {
const query = {
operationName: 'lnInvoicePaymentStatus',
query: `query lnInvoicePaymentStatus($input: LnInvoicePaymentStatusInput!) {
lnInvoicePaymentStatus(input: $input) {
status // Returns "PENDING" or "PAID"
}
}`,
variables: { input: { paymentRequest: address } }
}
return request(query, token, endpoint)
.then(r => r?.data?.lnInvoicePaymentStatus?.status)
.catch(err => {
throw new Error(err)
})
}
```
### Status Polling Implementation
The machine continuously polls the server to check if the Lightning payment has been received:
**Route**: `/tx/:id?status=confirmed`
**Handler**: `packages/server/lib/routes/txRoutes.js:51-65`
```javascript
function getTx(req, res, next) {
if (req.query.status) {
return helpers
.fetchStatusTx(req.params.id, req.query.status)
.then(r => res.json(r))
.catch(err => {
if (err.name === 'HTTPError') {
return res.status(err.code).send(err.message)
}
next(err)
})
}
return next(httpError('Not Found', 404))
}
```
## Lightning Transaction Capabilities
### Supported Operations
1. **Invoice Creation** (`newAddress`)
- Generates Lightning invoices for incoming payments
- Specifies exact amount in satoshis
- Returns BOLT11 payment request string
2. **Payment Sending** (`sendCoins`)
- Pays existing Lightning invoices
- Handles both amount-specified and no-amount invoices
- Supports LNURL payments
3. **Status Checking** (`getStatus`)
- Monitors payment confirmation status
- Provides real-time payment updates
- Handles payment timeouts and failures
4. **Balance Queries** (`balance`)
- Checks Lightning wallet balance
- Returns available satoshi amounts
- Accounts for pending transactions
5. **Payment Probing** (`probeLN`)
- Tests Lightning payment routes
- Validates payment feasibility
- Determines supported amounts
### Payment Route Probing
**File**: `packages/server/lib/plugins/wallet/galoy/galoy.js:244-254`
```javascript
function probeLN(account, cryptoCode, invoice) {
const probeHardLimits = [200000, 1000000, 2000000] // Test limits in satoshis
const promises = probeHardLimits.map(limit => {
return sendProbeRequest(
account.walletId,
invoice,
limit,
account.apiSecret,
account.endpoint,
).then(r => _.isEmpty(r.errors)) // Check if amount is routable
})
return Promise.all(promises) // Return array of supported limits
}
```
**Probe Request Implementation**:
```javascript
function sendProbeRequest(walletId, invoice, cryptoAtoms, token, endpoint) {
const sendProbeNoAmount = {
operationName: 'lnNoAmountInvoiceFeeProbe',
query: `mutation lnNoAmountInvoiceFeeProbe($input: LnNoAmountInvoiceFeeProbeInput!) {
lnNoAmountInvoiceFeeProbe(input: $input) {
amount
errors {
message
path
}
}
}`,
variables: {
input: {
paymentRequest: invoice,
walletId,
amount: cryptoAtoms.toString(),
},
},
}
return request(sendProbeNoAmount, token, endpoint)
}
```
## Lightning Cash-Out (Payment) Flow
For outgoing Lightning payments (when user wants to send Lightning Bitcoin):
### Payment Processing
**File**: `packages/server/lib/plugins/wallet/galoy/galoy.js:196-242`
```javascript
function sendCoins(account, tx) {
const { toAddress, cryptoAtoms, cryptoCode } = tx
return checkCryptoCode(cryptoCode)
.then(() => {
if (isLnInvoice(toAddress)) {
return sendFundsLN(
account.walletId,
toAddress, // Lightning invoice to pay
cryptoAtoms,
account.apiSecret,
account.endpoint,
)
}
if (isLnurl(toAddress)) {
return sendFundsLNURL(
account.walletId,
toAddress, // LNURL to pay
cryptoAtoms,
account.apiSecret,
account.endpoint,
)
}
// Handle on-chain addresses
return sendFundsOnChain(...)
})
}
```
### Lightning Payment Execution
```javascript
function sendFundsLN(walletId, invoice, cryptoAtoms, token, endpoint) {
const sendLnNoAmount = {
operationName: 'lnNoAmountInvoicePaymentSend',
query: `mutation lnNoAmountInvoicePaymentSend($input: LnNoAmountInvoicePaymentInput!) {
lnNoAmountInvoicePaymentSend(input: $input) {
errors {
message
path
}
status // Payment result status
}
}`,
variables: {
input: {
paymentRequest: invoice,
walletId,
amount: cryptoAtoms.toString(),
},
},
}
return request(sendLnNoAmount, token, endpoint)
}
```
## Configuration and Environment
### Environment Variables
**File**: `packages/server/.sample.env:38`
```bash
LIGHTNING_NETWORK_DAEMON=
```
**Note**: This variable exists but is not used. Lightning functionality is provided through Galoy's API configuration in the admin interface.
### Galoy Account Configuration
Required configuration parameters for Galoy integration:
- `walletId`: Galoy wallet identifier
- `apiSecret`: Authentication token for Galoy API
- `endpoint`: Galoy GraphQL API endpoint URL
### Supported Cryptocurrency Codes
**File**: `packages/server/lib/plugins/wallet/galoy/galoy.js:5-6`
```javascript
const NAME = 'LN'
const SUPPORTED_COINS = ['LN']
```
## Error Handling and Edge Cases
### Lightning-Specific Error Handling
1. **Invoice Validation**
- Validates Lightning invoice format (starts with "lnbc")
- Checks invoice expiration
- Verifies payment request integrity
2. **Payment Routing Failures**
- Handles insufficient Lightning liquidity
- Manages routing failures
- Provides fallback mechanisms
3. **Network Connectivity Issues**
- Implements retry logic for Galoy API calls
- Handles temporary network failures
- Maintains transaction state during outages
### Transaction State Management
Lightning transactions follow these states:
- `pending`: Invoice created, awaiting payment
- `authorized`: Payment detected but not confirmed
- `confirmed`: Payment fully confirmed
- `expired`: Invoice expired without payment
- `failed`: Payment attempt failed
## Security Considerations
### API Security
- All Galoy API requests use authenticated connections
- API keys are securely stored and transmitted
- GraphQL queries are parameterized to prevent injection
### Lightning Network Security
- Invoices have built-in expiration times
- Payment amounts are validated before processing
- Route probing prevents overpayment attempts
### Transaction Integrity
- Database transactions ensure atomicity
- Serializable isolation prevents race conditions
- Transaction versioning prevents double-processing
## Performance Characteristics
### Lightning Advantages
- **Instant Payments**: Lightning payments confirm in seconds
- **Low Fees**: Minimal network fees compared to on-chain
- **Scalability**: High transaction throughput capability
### System Performance
- **GraphQL Efficiency**: Single API calls for complex operations
- **Caching**: Invoice status results cached for performance
- **Batching**: Multiple probe requests processed in parallel
### Monitoring and Observability
- Transaction logs include Lightning-specific metadata
- Payment routing information tracked for debugging
- API response times monitored for performance optimization
## Conclusion
The Lamassu Lightning Network integration provides a complete solution for instant Bitcoin transactions through ATMs. By leveraging Galoy's hosted Lightning infrastructure, the system achieves the benefits of Lightning Network payments without the operational complexity of running local Lightning nodes.
Key strengths of this implementation:
- **Reliable Infrastructure**: Galoy provides enterprise-grade Lightning services
- **Simple Integration**: GraphQL API provides clean, documented interfaces
- **Real-time Processing**: Instant payment detection and confirmation
- **Comprehensive Features**: Supports full Lightning payment lifecycle
- **Robust Error Handling**: Graceful failure modes and recovery mechanisms
This architecture enables Lamassu ATMs to offer Lightning Network functionality while maintaining the reliability and security required for financial hardware deployment.

View file

@ -0,0 +1,95 @@
# LNBits Configuration Clarification
## Key Finding: walletId is NOT Required
After reviewing the LNBits codebase, we can confirm that **walletId is not necessary** for the Lamassu-LNBits integration because **the adminKey is already wallet-specific**.
## Evidence from LNBits Source Code
### 1. Wallet Model Structure
```python
# packages/lnbits/lnbits/core/models/wallets.py
class Wallet(BaseModel):
id: str
user: str
name: str
adminkey: str # Each wallet has unique adminkey
inkey: str # Each wallet has unique inkey
```
### 2. Key-to-Wallet Lookup
```sql
-- packages/lnbits/lnbits/core/crud/wallets.py:194
SELECT *, COALESCE((
SELECT balance FROM balances WHERE wallet_id = wallets.id
), 0)
AS balance_msat FROM wallets
WHERE (adminkey = :key OR inkey = :key) AND deleted = false
```
**Key Point**: LNBits looks up wallets directly by the adminkey/inkey - no walletId parameter needed.
### 3. Authentication Flow
```python
# packages/lnbits/lnbits/decorators.py:94
wallet = await get_wallet_for_key(key_value)
```
The authentication system uses only the key to identify and authorize access to the specific wallet.
### 4. Unique Key Generation
```python
# packages/lnbits/lnbits/core/crud/wallets.py:24-25
adminkey=uuid4().hex, # Unique UUID for each wallet
inkey=uuid4().hex, # Unique UUID for each wallet
```
Each wallet gets unique keys generated as UUIDs, ensuring 1:1 key-to-wallet relationship.
## Impact on Lamassu Integration
### Simplified Configuration
**Before (Incorrect Assumption)**:
```javascript
const config = {
endpoint: 'https://lnbits.example.com',
adminKey: 'abc123...',
walletId: 'wallet_xyz' // ❌ Not needed!
}
```
**After (Correct Implementation)**:
```javascript
const config = {
endpoint: 'https://lnbits.example.com',
adminKey: 'abc123...' // ✅ Admin key identifies wallet
}
```
### API Calls Simplified
All LNBits API calls use the adminKey in the `X-API-KEY` header, which automatically identifies the target wallet:
```javascript
// Create invoice - no walletId needed
POST /api/v1/payments
Headers: { 'X-API-KEY': 'abc123...' }
Body: { "out": false, "amount": 1000, "memo": "Purchase" }
// Check balance - no walletId needed
GET /api/v1/wallet
Headers: { 'X-API-KEY': 'abc123...' }
```
## Updated Migration Benefits
1. **Fewer Configuration Parameters** - Reduces setup complexity
2. **Eliminates Mismatch Errors** - No risk of wrong walletId/adminKey pairs
3. **Consistent with LNBits Design** - Follows intended architecture
4. **Simpler Admin UI** - Fewer fields in configuration forms
5. **Reduced Validation Logic** - Less error-prone configuration
## Conclusion
The walletId parameter should be **removed from the LNBits integration plan**. The adminKey serves as both authentication credential and wallet identifier, following LNBits' design principle of wallet-specific API keys.

View file

@ -0,0 +1,555 @@
# LNBits API Integration Plan
## Overview
This document outlines the complete plan to replace the current Galoy (Blink API) Lightning Network integration with LNBits API in the Lamassu Bitcoin ATM server.
## Current State Analysis
### Galoy Plugin Structure
- **Location**: `packages/server/lib/plugins/wallet/galoy/galoy.js`
- **API Type**: GraphQL mutations and queries
- **Authentication**: API secret token via `X-API-KEY` header
- **Core Functions**:
- `newInvoice()` - Create Lightning invoices
- `getInvoiceStatus()` - Check payment status
- `sendCoins()` - Pay Lightning invoices
- `balance()` - Check wallet balance
- `probeLN()` - Test payment routes
### LNBits API Structure
- **API Type**: RESTful API with JSON responses
- **Authentication**: Admin/Invoice keys via `X-API-KEY` header
- **Endpoint**: `POST /api/v1/payments` (universal endpoint)
- **Documentation**: Available at `/docs` endpoint
## API Mapping
### 1. Invoice Creation
**Current Galoy**:
```javascript
// GraphQL Mutation
const createInvoice = {
operationName: 'lnInvoiceCreate',
query: `mutation lnInvoiceCreate($input: LnInvoiceCreateInput!) {
lnInvoiceCreate(input: $input) {
errors { message path }
invoice { paymentRequest }
}
}`,
variables: { input: { walletId, amount: cryptoAtoms.toString() } }
}
```
**LNBits Equivalent**:
```javascript
// REST API Call
POST /api/v1/payments
{
"out": false,
"amount": cryptoAtoms,
"unit": "sat",
"memo": `Lamassu ATM Purchase - ${Date.now()}`,
"webhook": "https://your-server.com/webhook/lnbits"
}
```
### 2. Payment Status Check
**Current Galoy**:
```javascript
const query = {
operationName: 'lnInvoicePaymentStatus',
query: `query lnInvoicePaymentStatus($input: LnInvoicePaymentStatusInput!) {
lnInvoicePaymentStatus(input: $input) {
status
}
}`,
variables: { input: { paymentRequest: address } }
}
```
**LNBits Equivalent**:
```javascript
// REST API Call
GET /api/v1/payments/{payment_hash}
// Returns: { "paid": true/false, "amount": satoshis, "fee": fee_amount }
```
### 3. Outgoing Payments
**Current Galoy**:
```javascript
const sendLnNoAmount = {
operationName: 'lnNoAmountInvoicePaymentSend',
query: `mutation lnNoAmountInvoicePaymentSend($input: LnNoAmountInvoicePaymentInput!) {
lnNoAmountInvoicePaymentSend(input: $input) {
errors { message path }
status
}
}`,
variables: { input: { paymentRequest: invoice, walletId, amount: cryptoAtoms.toString() } }
}
```
**LNBits Equivalent**:
```javascript
// REST API Call
POST /api/v1/payments
{
"out": true,
"bolt11": "lnbc...",
"amount": cryptoAtoms // For zero-amount invoices
}
```
### 4. Balance Inquiry
**Current Galoy**:
```javascript
const balanceQuery = {
operationName: 'me',
query: `query me {
me {
defaultAccount {
wallets {
walletCurrency
balance
}
}
}
}`
}
```
**LNBits Equivalent**:
```javascript
// REST API Call
GET /api/v1/wallet
// Returns: { "name": "wallet_name", "balance": balance_msat }
```
## Implementation Strategy
### Phase 1: Create LNBits Plugin
**File**: `packages/server/lib/plugins/wallet/lnbits/lnbits.js`
```javascript
const axios = require('axios')
const { URL } = require('url')
const BN = require('../../../bn')
const NAME = 'LNBits'
const SUPPORTED_COINS = ['LN']
// Configuration validation
function validateConfig(account) {
const required = ['endpoint', 'adminKey'] // No walletId needed
for (const field of required) {
if (!account[field]) {
throw new Error(`LNBits configuration missing: ${field}`)
}
}
}
// HTTP request wrapper
async function request(endpoint, method = 'GET', data = null, apiKey) {
const config = {
method,
url: endpoint,
headers: {
'Content-Type': 'application/json',
'X-API-KEY': apiKey
}
}
if (data) config.data = data
try {
const response = await axios(config)
return response.data
} catch (error) {
throw new Error(`LNBits API Error: ${error.response?.data?.detail || error.message}`)
}
}
// Create Lightning invoice
async function newAddress(account, info, tx) {
validateConfig(account)
const { cryptoAtoms } = tx
const invoiceData = {
out: false,
amount: parseInt(cryptoAtoms.toString()),
unit: 'sat',
memo: `Lamassu ATM Purchase - ${Date.now()}`,
expiry: 3600 // 1 hour expiration
}
const endpoint = `${account.endpoint}/api/v1/payments`
const result = await request(endpoint, 'POST', invoiceData, account.adminKey)
return result.payment_request // Returns BOLT11 invoice string
}
// Check payment status
async function getStatus(account, tx) {
validateConfig(account)
const { toAddress } = tx
// Extract payment hash from BOLT11 invoice
const paymentHash = extractPaymentHash(toAddress)
const endpoint = `${account.endpoint}/api/v1/payments/${paymentHash}`
const result = await request(endpoint, 'GET', null, account.adminKey)
return { status: result.paid ? 'confirmed' : 'pending' }
}
// Send Lightning payment
async function sendCoins(account, tx) {
validateConfig(account)
const { toAddress, cryptoAtoms } = tx
const paymentData = {
out: true,
bolt11: toAddress,
amount: parseInt(cryptoAtoms.toString()) // For zero-amount invoices
}
const endpoint = `${account.endpoint}/api/v1/payments`
const result = await request(endpoint, 'POST', paymentData, account.adminKey)
return {
txid: result.payment_hash,
fee: result.fee || 0
}
}
// Get wallet balance
async function balance(account) {
validateConfig(account)
const endpoint = `${account.endpoint}/api/v1/wallet`
const result = await request(endpoint, 'GET', null, account.adminKey)
return new BN(Math.floor(result.balance / 1000)) // Convert msat to sat
}
// Payment route probing
async function probeLN(account, cryptoCode, invoice) {
validateConfig(account)
// Decode invoice to get payment info
const endpoint = `${account.endpoint}/api/v1/payments/decode`
const decodeData = { data: invoice }
const result = await request(endpoint, 'POST', decodeData, account.adminKey)
// Test different amounts for route feasibility
const limits = [200000, 1000000, 2000000]
return limits.map(limit => result.amount_msat <= limit * 1000)
}
// Utility functions
function extractPaymentHash(bolt11) {
// Use bolt11 decoder library to extract payment hash
const decoded = require('bolt11').decode(bolt11)
return decoded.paymentHash
}
function checkCryptoCode(cryptoCode) {
if (!SUPPORTED_COINS.includes(cryptoCode)) {
throw new Error(`Unsupported crypto: ${cryptoCode}`)
}
return Promise.resolve()
}
module.exports = {
NAME,
balance,
sendCoins,
newAddress,
getStatus,
probeLN,
isLnInvoice: (address) => address.toLowerCase().startsWith('lnbc'),
cryptoNetwork: () => 'main'
}
```
### Phase 2: Configuration Changes
#### Admin UI Configuration Panel
**File**: `packages/admin-ui/src/pages/Wallet/LNBitsConfig.jsx`
```javascript
const LNBitsConfig = () => {
const [config, setConfig] = useState({
endpoint: '',
adminKey: '' // Admin key is wallet-specific, no walletId needed
})
const fields = [
{
name: 'endpoint',
label: 'LNBits Server URL',
placeholder: 'https://your-lnbits.com'
},
{
name: 'adminKey',
label: 'Admin API Key',
type: 'password',
help: 'Wallet-specific key for all operations (no separate walletId needed)'
}
]
return <WalletConfigForm fields={fields} config={config} />
}
```
#### Database Schema Updates
**Migration File**: `packages/server/migrations/xxx-add-lnbits-config.js`
```javascript
const up = `
INSERT INTO user_config (name, display_name, data_type, config_type, secret)
VALUES
('lnbitsEndpoint', 'LNBits Server URL', 'string', 'wallets', false),
('lnbitsAdminKey', 'LNBits Admin Key', 'string', 'wallets', true);
-- Note: No lnbitsWalletId needed - adminKey is wallet-specific
`
```
### Phase 3: Integration Points
#### Plugin Registration
**File**: `packages/server/lib/plugins/wallet/lnbits/index.js`
```javascript
const lnbits = require('./lnbits')
module.exports = {
LNBits: lnbits
}
```
**File**: `packages/server/lib/plugins/wallet/index.js` (update)
```javascript
module.exports = {
// ... existing plugins
LNBits: require('./lnbits')
}
```
#### Environment Variables
**File**: `packages/server/.sample.env` (add)
```bash
# LNBits Configuration
LNBITS_ENDPOINT=https://demo.lnbits.com
LNBITS_ADMIN_KEY=your_admin_key_here
# Note: No LNBITS_WALLET_ID needed - admin key identifies the wallet
```
### Phase 4: Testing Strategy
#### Unit Tests
**File**: `packages/server/tests/unit/test_lnbits_plugin.js`
```javascript
const test = require('tape')
const lnbits = require('../../lib/plugins/wallet/lnbits/lnbits')
const mockAccount = {
endpoint: 'https://demo.lnbits.com',
adminKey: 'test_admin_key' // No walletId needed
}
test('LNBits invoice creation', async t => {
const tx = { cryptoAtoms: new BN(100000) } // 100k sats
const invoice = await lnbits.newAddress(mockAccount, { cryptoCode: 'LN' }, tx)
t.ok(invoice.startsWith('lnbc'), 'Generated valid BOLT11 invoice')
t.end()
})
test('LNBits balance check', async t => {
const balance = await lnbits.balance(mockAccount, 'LN')
t.ok(balance instanceof BN, 'Returns BigNumber balance')
t.ok(balance.gte(0), 'Balance is non-negative')
t.end()
})
```
#### Integration Tests
**File**: `packages/server/tests/integration/test_lnbits_integration.js`
```javascript
const test = require('tape')
const request = require('supertest')
const app = require('../../lib/app')
test('Full Lightning transaction flow', async t => {
// 1. Create transaction
const createTx = await request(app)
.post('/tx')
.send({
direction: 'cashIn',
cryptoCode: 'LN',
fiat: 1000, // $10
cryptoAtoms: '39216',
isLightning: true
})
t.equal(createTx.status, 200, 'Transaction created successfully')
t.ok(createTx.body.toAddress.startsWith('lnbc'), 'Valid Lightning invoice')
// 2. Check transaction status
const statusCheck = await request(app)
.get(`/tx/${createTx.body.id}?status=confirmed`)
t.equal(statusCheck.status, 200, 'Status check successful')
t.end()
})
```
### Phase 5: Deployment Strategy
#### Rollout Plan
1. **Development Environment**
- Deploy LNBits instance for testing
- Configure test wallets with small amounts
- Run comprehensive integration tests
2. **Staging Environment**
- Mirror production configuration
- Test with realistic transaction volumes
- Validate error handling and edge cases
3. **Production Migration**
- Feature flag to switch between Galoy and LNBits
- Gradual rollout to subset of machines
- Monitor transaction success rates
- Full cutover after validation
#### Configuration Migration
**File**: `packages/server/tools/migrate-galoy-to-lnbits.js`
```javascript
// Migration script to convert existing Galoy configurations
const migrateWalletConfig = async () => {
const galoyConfigs = await db.query(`
SELECT * FROM user_config
WHERE name IN ('galoyEndpoint', 'galoyApiSecret', 'galoyWalletId')
`)
// Convert to LNBits format
const lnbitsConfigs = galoyConfigs.map(config => ({
...config,
name: config.name.replace('galoy', 'lnbits'),
// Additional mapping logic
}))
// Insert new configurations
await db.query(INSERT_LNBITS_CONFIG_SQL, lnbitsConfigs)
}
```
## Migration Checklist
### Pre-Migration
- [ ] LNBits server deployed and configured
- [ ] Test wallets created with sufficient balance
- [ ] All unit tests passing
- [ ] Integration tests validated
- [ ] Staging environment fully tested
### During Migration
- [ ] Feature flag enabled for LNBits
- [ ] Monitor transaction success rates
- [ ] Validate Lightning invoice generation
- [ ] Test payment status polling
- [ ] Confirm balance reporting accuracy
### Post-Migration
- [ ] All machines successfully switched to LNBits
- [ ] Transaction monitoring shows normal patterns
- [ ] Error rates within acceptable bounds
- [ ] Remove Galoy plugin code
- [ ] Update documentation
## Key Differences & Considerations
### API Architecture
- **Galoy**: Single GraphQL endpoint with mutations/queries
- **LNBits**: RESTful endpoints with standard HTTP verbs
### Authentication
- **Galoy**: Single API secret for all operations
- **LNBits**: Wallet-specific admin key (no separate walletId needed)
### Error Handling
- **Galoy**: GraphQL errors array in response
- **LNBits**: HTTP status codes with JSON error details
### Webhook Support
- **LNBits**: Native webhook support for payment notifications
- **Galoy**: Requires polling for status updates
### Payment Hash Handling
- **LNBits**: Exposes payment hashes directly
- **Galoy**: Uses internal payment request tracking
### Key Management
- **LNBits**: Admin key is wallet-specific (1:1 relationship)
- **Galoy**: API secret can access multiple wallets within account
## Benefits of Migration
1. **Self-Hosted Control**: Run your own Lightning infrastructure
2. **Better Integration**: RESTful API easier to work with than GraphQL
3. **Enhanced Features**: Native webhook support, better payment tracking
4. **Simplified Configuration**: No separate walletId needed (admin key is wallet-specific)
5. **Cost Efficiency**: No external service fees
6. **Privacy**: Complete control over transaction data
7. **Extensibility**: Easy to add custom Lightning functionality
## Risks & Mitigation
### Technical Risks
- **API Differences**: Thorough testing and validation required
- **Payment Failures**: Comprehensive error handling needed
- **Performance Issues**: Load testing with realistic volumes
### Business Risks
- **Service Interruption**: Feature flag enables quick rollback
- **Transaction Loss**: Extensive logging and monitoring
- **Customer Impact**: Staged rollout minimizes exposure
### Mitigation Strategies
- Feature flags for safe deployment
- Comprehensive monitoring and alerting
- Automated testing pipeline
- Quick rollback procedures
- 24/7 monitoring during migration
## Success Metrics
- **Transaction Success Rate**: >99.5%
- **Payment Confirmation Time**: <30 seconds average
- **API Response Time**: <2 seconds for all endpoints
- **Error Rate**: <0.5%
- **Uptime**: >99.9%
This migration plan provides a comprehensive path from Galoy's hosted Lightning service to a self-managed LNBits infrastructure, giving Lamassu operators full control over their Lightning Network integration.

Binary file not shown.