miscellaneous files
This commit is contained in:
parent
3c26a15ffe
commit
2f0cc901eb
6 changed files with 1448 additions and 0 deletions
555
misc-aio/LNBITS_REPLACEMENT_PLAN.md
Normal file
555
misc-aio/LNBITS_REPLACEMENT_PLAN.md
Normal 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue