14 KiB
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-KEYheader - Core Functions:
newInvoice()- Create Lightning invoicesgetInvoiceStatus()- Check payment statussendCoins()- Pay Lightning invoicesbalance()- Check wallet balanceprobeLN()- Test payment routes
LNBits API Structure
- API Type: RESTful API with JSON responses
- Authentication: Admin/Invoice keys via
X-API-KEYheader - Endpoint:
POST /api/v1/payments(universal endpoint) - Documentation: Available at
/docsendpoint
API Mapping
1. Invoice Creation
Current Galoy:
// 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:
// 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:
const query = {
operationName: 'lnInvoicePaymentStatus',
query: `query lnInvoicePaymentStatus($input: LnInvoicePaymentStatusInput!) {
lnInvoicePaymentStatus(input: $input) {
status
}
}`,
variables: { input: { paymentRequest: address } }
}
LNBits Equivalent:
// REST API Call
GET /api/v1/payments/{payment_hash}
// Returns: { "paid": true/false, "amount": satoshis, "fee": fee_amount }
3. Outgoing Payments
Current Galoy:
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:
// REST API Call
POST /api/v1/payments
{
"out": true,
"bolt11": "lnbc...",
"amount": cryptoAtoms // For zero-amount invoices
}
4. Balance Inquiry
Current Galoy:
const balanceQuery = {
operationName: 'me',
query: `query me {
me {
defaultAccount {
wallets {
walletCurrency
balance
}
}
}
}`
}
LNBits Equivalent:
// 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
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
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
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
const lnbits = require('./lnbits')
module.exports = {
LNBits: lnbits
}
File: packages/server/lib/plugins/wallet/index.js (update)
module.exports = {
// ... existing plugins
LNBits: require('./lnbits')
}
Environment Variables
File: packages/server/.sample.env (add)
# 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
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
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
-
Development Environment
- Deploy LNBits instance for testing
- Configure test wallets with small amounts
- Run comprehensive integration tests
-
Staging Environment
- Mirror production configuration
- Test with realistic transaction volumes
- Validate error handling and edge cases
-
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
// 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
- Self-Hosted Control: Run your own Lightning infrastructure
- Better Integration: RESTful API easier to work with than GraphQL
- Enhanced Features: Native webhook support, better payment tracking
- Simplified Configuration: No separate walletId needed (admin key is wallet-specific)
- Cost Efficiency: No external service fees
- Privacy: Complete control over transaction data
- 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.