Compare commits
10 commits
36e051af8d
...
8cd38f6305
| Author | SHA1 | Date | |
|---|---|---|---|
| 8cd38f6305 | |||
| 719f16b743 | |||
| 7fe4add2ab | |||
| 349c8b3a8e | |||
| bf2c37409e | |||
| b59b2f0269 | |||
| 19549ed3ff | |||
| 2dca9633e2 | |||
| 577086b2ec | |||
|
|
3c26a15ffe |
20 changed files with 887 additions and 33 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "lamassu-server",
|
"name": "lamassu-server",
|
||||||
"description": "bitcoin atm client server protocol module",
|
"description": "bitcoin atm client server protocol module",
|
||||||
"version": "11.0.0",
|
"version": "11.0.1",
|
||||||
"license": "./LICENSE",
|
"license": "./LICENSE",
|
||||||
"author": "Lamassu (https://lamassu.is)",
|
"author": "Lamassu (https://lamassu.is)",
|
||||||
"packageManager": "pnpm@10.11.0",
|
"packageManager": "pnpm@10.11.0",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "lamassu-admin",
|
"name": "lamassu-admin",
|
||||||
"version": "11.0.0",
|
"version": "11.0.1",
|
||||||
"license": "../LICENSE",
|
"license": "../LICENSE",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import inforu from './inforu'
|
||||||
import infura from './infura'
|
import infura from './infura'
|
||||||
import _itbit from './itbit'
|
import _itbit from './itbit'
|
||||||
import _kraken from './kraken'
|
import _kraken from './kraken'
|
||||||
|
import lnbits from './lnbits'
|
||||||
import mailgun from './mailgun'
|
import mailgun from './mailgun'
|
||||||
import scorechain from './scorechain'
|
import scorechain from './scorechain'
|
||||||
import sumsub from './sumsub'
|
import sumsub from './sumsub'
|
||||||
|
|
@ -31,6 +32,7 @@ const schemas = (markets = {}) => {
|
||||||
return {
|
return {
|
||||||
[bitgo.code]: bitgo,
|
[bitgo.code]: bitgo,
|
||||||
[galoy.code]: galoy,
|
[galoy.code]: galoy,
|
||||||
|
[lnbits.code]: lnbits,
|
||||||
[bitstamp.code]: bitstamp,
|
[bitstamp.code]: bitstamp,
|
||||||
[blockcypher.code]: blockcypher,
|
[blockcypher.code]: blockcypher,
|
||||||
[elliptic.code]: elliptic,
|
[elliptic.code]: elliptic,
|
||||||
|
|
|
||||||
36
packages/admin-ui/src/pages/Services/schemas/lnbits.js
Normal file
36
packages/admin-ui/src/pages/Services/schemas/lnbits.js
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
|
import {
|
||||||
|
SecretInput,
|
||||||
|
TextInput,
|
||||||
|
} from '../../../components/inputs/formik'
|
||||||
|
|
||||||
|
import { secretTest } from './helper'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
code: 'lnbits',
|
||||||
|
name: 'LNBits',
|
||||||
|
title: 'LNBits (Wallet)',
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
code: 'endpoint',
|
||||||
|
display: 'LNBits Server URL',
|
||||||
|
component: TextInput,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'adminKey',
|
||||||
|
display: 'Admin Key',
|
||||||
|
component: SecretInput,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
getValidationSchema: account => {
|
||||||
|
return Yup.object().shape({
|
||||||
|
endpoint: Yup.string('The endpoint must be a string')
|
||||||
|
.max(200, 'The endpoint is too long')
|
||||||
|
.required('The endpoint is required'),
|
||||||
|
adminKey: Yup.string('The Admin Key must be a string')
|
||||||
|
.max(200, 'The Admin Key is too long')
|
||||||
|
.test(secretTest(account?.adminKey)),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ const SAVE_ACCOUNTS = gql`
|
||||||
`
|
`
|
||||||
|
|
||||||
const isConfigurable = it =>
|
const isConfigurable = it =>
|
||||||
R.includes(it)(['infura', 'bitgo', 'trongrid', 'galoy'])
|
R.includes(it)(['infura', 'bitgo', 'trongrid', 'galoy', 'lnbits'])
|
||||||
|
|
||||||
const isLocalHosted = it =>
|
const isLocalHosted = it =>
|
||||||
R.includes(it)([
|
R.includes(it)([
|
||||||
|
|
@ -178,6 +178,19 @@ const ChooseWallet = ({ data: currentData, addData }) => {
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{selected === 'lnbits' && (
|
||||||
|
<>
|
||||||
|
<H4 noMargin>Enter wallet information</H4>
|
||||||
|
<FormRenderer
|
||||||
|
value={accounts.lnbits}
|
||||||
|
save={saveWallet(selected)}
|
||||||
|
elements={schema.lnbits.elements}
|
||||||
|
validationSchema={schema.lnbits.getValidationSchema(accounts.lnbits)}
|
||||||
|
buttonLabel={'Continue'}
|
||||||
|
buttonClass={classes.formButton}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,12 @@ HOSTNAME=
|
||||||
LOG_LEVEL=
|
LOG_LEVEL=
|
||||||
LIGHTNING_NETWORK_DAEMON=
|
LIGHTNING_NETWORK_DAEMON=
|
||||||
|
|
||||||
|
## LNBits Configuration (Lightning Network)
|
||||||
|
# LNBits server URL (e.g., https://legend.lnbits.com)
|
||||||
|
LNBITS_ENDPOINT=
|
||||||
|
# Admin key for the LNBits wallet (wallet-specific, no separate walletId needed)
|
||||||
|
LNBITS_ADMIN_KEY=
|
||||||
|
|
||||||
# Crypto nodes related variables
|
# Crypto nodes related variables
|
||||||
|
|
||||||
## Location info (can be local or remote)
|
## Location info (can be local or remote)
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ const ALL_ACCOUNTS = [
|
||||||
cryptos: [BTC, ZEC, LTC, BCH, DASH],
|
cryptos: [BTC, ZEC, LTC, BCH, DASH],
|
||||||
},
|
},
|
||||||
{ code: 'galoy', display: 'Galoy', class: WALLET, cryptos: [LN] },
|
{ code: 'galoy', display: 'Galoy', class: WALLET, cryptos: [LN] },
|
||||||
|
{ code: 'lnbits', display: 'LNBits', class: WALLET, cryptos: [LN] },
|
||||||
{
|
{
|
||||||
code: 'bitstamp',
|
code: 'bitstamp',
|
||||||
display: 'Bitstamp',
|
display: 'Bitstamp',
|
||||||
|
|
|
||||||
144
packages/server/lib/plugins/wallet/lnbits/README.md
Normal file
144
packages/server/lib/plugins/wallet/lnbits/README.md
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
# LNBits Wallet Plugin
|
||||||
|
|
||||||
|
This plugin integrates LNBits as a Lightning Network wallet provider for Lamassu Bitcoin ATMs.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Lightning Invoice Creation**: Generate BOLT11 invoices for incoming payments
|
||||||
|
- **Payment Processing**: Send Lightning payments to any BOLT11 invoice
|
||||||
|
- **Balance Monitoring**: Check wallet balance in real-time
|
||||||
|
- **Payment Status Tracking**: Monitor invoice payment status
|
||||||
|
- **Route Probing**: Test payment feasibility before attempting
|
||||||
|
- **Network Detection**: Automatic mainnet/testnet/regtest detection
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Required Environment Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LNBITS_ENDPOINT=https://your-lnbits-server.com
|
||||||
|
LNBITS_ADMIN_KEY=your_admin_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important**: The admin key is wallet-specific in LNBits. No separate walletId is needed.
|
||||||
|
|
||||||
|
### Database Configuration
|
||||||
|
|
||||||
|
The plugin configuration is stored in the database and can be managed through the Lamassu admin interface.
|
||||||
|
|
||||||
|
## API Functions
|
||||||
|
|
||||||
|
### Core Functions
|
||||||
|
|
||||||
|
#### `newAddress(account, info, tx)`
|
||||||
|
Creates a new Lightning invoice for receiving payments.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `account`: Object containing `endpoint` and `adminKey`
|
||||||
|
- `info`: Transaction info object
|
||||||
|
- `tx`: Transaction object with `cryptoAtoms` and `cryptoCode`
|
||||||
|
|
||||||
|
**Returns:** BOLT11 Lightning invoice string
|
||||||
|
|
||||||
|
#### `getStatus(account, tx)`
|
||||||
|
Checks the payment status of a Lightning invoice.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `account`: Configuration object
|
||||||
|
- `tx`: Transaction object with `toAddress` (Lightning invoice)
|
||||||
|
|
||||||
|
**Returns:** Object with `status` field: 'confirmed', 'pending', or 'notSeen'
|
||||||
|
|
||||||
|
#### `sendCoins(account, tx)`
|
||||||
|
Sends a Lightning payment to a BOLT11 invoice.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `account`: Configuration object
|
||||||
|
- `tx`: Transaction object with `toAddress` and optional `cryptoAtoms`
|
||||||
|
|
||||||
|
**Returns:** Object with `txid` and `fee`
|
||||||
|
|
||||||
|
#### `balance(account, cryptoCode)`
|
||||||
|
Gets the current wallet balance.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `account`: Configuration object
|
||||||
|
- `cryptoCode`: Must be 'LN'
|
||||||
|
|
||||||
|
**Returns:** BigNumber balance in satoshis
|
||||||
|
|
||||||
|
### Utility Functions
|
||||||
|
|
||||||
|
#### `isLnInvoice(address)`
|
||||||
|
Checks if a string is a valid Lightning invoice.
|
||||||
|
|
||||||
|
#### `isLnurl(address)`
|
||||||
|
Checks if a string is a valid LNURL.
|
||||||
|
|
||||||
|
#### `probeLN(account, cryptoCode, invoice)`
|
||||||
|
Tests payment route feasibility for different amounts.
|
||||||
|
|
||||||
|
#### `cryptoNetwork(account, cryptoCode)`
|
||||||
|
Detects network type from endpoint URL.
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The plugin provides detailed error messages for common issues:
|
||||||
|
|
||||||
|
- **Configuration errors**: Missing endpoint or admin key
|
||||||
|
- **Network errors**: Connection timeouts or unreachable server
|
||||||
|
- **API errors**: Invalid requests or authentication failures
|
||||||
|
- **Invoice errors**: Invalid BOLT11 format or expired invoices
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Run unit tests:
|
||||||
|
```bash
|
||||||
|
npm test -- tests/unit/test_lnbits_plugin.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration from Galoy
|
||||||
|
|
||||||
|
This plugin replaces the Galoy (Blink) integration with the following improvements:
|
||||||
|
|
||||||
|
1. **Simpler Configuration**: Only requires endpoint and admin key
|
||||||
|
2. **RESTful API**: Easier to debug than GraphQL
|
||||||
|
3. **Self-Hosted Option**: Can run your own LNBits instance
|
||||||
|
4. **Better Error Messages**: More descriptive error handling
|
||||||
|
5. **Native Webhook Support**: Real-time payment notifications (future enhancement)
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- Store the admin key securely (marked as `secret: true` in database)
|
||||||
|
- Use HTTPS endpoints in production
|
||||||
|
- Validate all Lightning invoices before processing
|
||||||
|
- Implement rate limiting for API calls (handled by LNBits)
|
||||||
|
- Monitor wallet balance and set alerts for low balance
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **"LNBits configuration missing"**
|
||||||
|
- Ensure both `endpoint` and `adminKey` are set
|
||||||
|
- Check environment variables or database configuration
|
||||||
|
|
||||||
|
2. **"Invalid Lightning invoice"**
|
||||||
|
- Verify the invoice starts with 'lnbc', 'lntb', or 'lnbcrt'
|
||||||
|
- Check if the invoice has expired
|
||||||
|
|
||||||
|
3. **"LNBits API Error: 404"**
|
||||||
|
- Payment hash not found (invoice may not exist)
|
||||||
|
- Check if using correct endpoint URL
|
||||||
|
|
||||||
|
4. **"LNBits API Error: 401"**
|
||||||
|
- Invalid admin key
|
||||||
|
- Verify the admin key is correct and has proper permissions
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For LNBits-specific issues, refer to:
|
||||||
|
- [LNBits Documentation](https://docs.lnbits.com)
|
||||||
|
- [LNBits GitHub](https://github.com/lnbits/lnbits)
|
||||||
|
|
||||||
|
For Lamassu integration issues, contact Lamassu support.
|
||||||
1
packages/server/lib/plugins/wallet/lnbits/index.js
Normal file
1
packages/server/lib/plugins/wallet/lnbits/index.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
module.exports = require('./lnbits')
|
||||||
278
packages/server/lib/plugins/wallet/lnbits/lnbits.js
Normal file
278
packages/server/lib/plugins/wallet/lnbits/lnbits.js
Normal file
|
|
@ -0,0 +1,278 @@
|
||||||
|
const axios = require('axios')
|
||||||
|
const bolt11 = require('bolt11')
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
const BN = require('../../../bn')
|
||||||
|
|
||||||
|
const NAME = 'LNBits'
|
||||||
|
const SUPPORTED_COINS = ['LN']
|
||||||
|
|
||||||
|
function checkCryptoCode(cryptoCode) {
|
||||||
|
if (!SUPPORTED_COINS.includes(cryptoCode)) {
|
||||||
|
return Promise.reject(new Error(`Unsupported crypto: ${cryptoCode}`))
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateConfig(account) {
|
||||||
|
const required = ['endpoint', 'adminKey']
|
||||||
|
for (const field of required) {
|
||||||
|
if (!account[field]) {
|
||||||
|
throw new Error(`LNBits configuration missing: ${field}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new URL(account.endpoint)
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Invalid LNBits endpoint URL: ${account.endpoint}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function request(endpoint, method = 'GET', data = null, apiKey) {
|
||||||
|
const config = {
|
||||||
|
method,
|
||||||
|
url: endpoint,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-API-KEY': apiKey
|
||||||
|
},
|
||||||
|
timeout: 30000
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
config.data = data
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios(config)
|
||||||
|
return response.data
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
const detail = error.response.data?.detail || error.response.statusText
|
||||||
|
throw new Error(`LNBits API Error: ${detail} (${error.response.status})`)
|
||||||
|
}
|
||||||
|
throw new Error(`LNBits Network Error: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPaymentHash(bolt11Invoice) {
|
||||||
|
try {
|
||||||
|
const decoded = bolt11.decode(bolt11Invoice)
|
||||||
|
return decoded.tagsObject.payment_hash
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Invalid Lightning invoice: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLnInvoice(address) {
|
||||||
|
if (!address || typeof address !== 'string') return false
|
||||||
|
return address.toLowerCase().startsWith('lnbc') ||
|
||||||
|
address.toLowerCase().startsWith('lntb') ||
|
||||||
|
address.toLowerCase().startsWith('lnbcrt')
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLnurl(address) {
|
||||||
|
if (!address || typeof address !== 'string') return false
|
||||||
|
return address.toLowerCase().startsWith('lnurl')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newAddress(account, info, tx) {
|
||||||
|
validateConfig(account)
|
||||||
|
const { cryptoAtoms, cryptoCode } = tx
|
||||||
|
|
||||||
|
await checkCryptoCode(cryptoCode)
|
||||||
|
|
||||||
|
const invoiceData = {
|
||||||
|
out: false,
|
||||||
|
amount: parseInt(cryptoAtoms.toString()),
|
||||||
|
unit: 'sat',
|
||||||
|
memo: `Lamassu ATM - ${new Date().toISOString()}`,
|
||||||
|
expiry: 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `${account.endpoint}/api/v1/payments`
|
||||||
|
const result = await request(endpoint, 'POST', invoiceData, account.adminKey)
|
||||||
|
|
||||||
|
if (!result.bolt11) {
|
||||||
|
throw new Error('LNBits did not return a bolt11 invoice')
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.bolt11
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getStatus(account, tx) {
|
||||||
|
validateConfig(account)
|
||||||
|
const { toAddress, cryptoCode } = tx
|
||||||
|
|
||||||
|
await checkCryptoCode(cryptoCode)
|
||||||
|
|
||||||
|
if (!isLnInvoice(toAddress)) {
|
||||||
|
return { status: 'notSeen' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentHash = extractPaymentHash(toAddress)
|
||||||
|
const endpoint = `${account.endpoint}/api/v1/payments/${paymentHash}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await request(endpoint, 'GET', null, account.adminKey)
|
||||||
|
|
||||||
|
if (result.paid === true) {
|
||||||
|
return { status: 'confirmed' }
|
||||||
|
} else if (result.paid === false && result.pending === true) {
|
||||||
|
return { status: 'pending' }
|
||||||
|
} else {
|
||||||
|
return { status: 'notSeen' }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message.includes('404')) {
|
||||||
|
return { status: 'notSeen' }
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendLNURL(account, lnurl, cryptoAtoms) {
|
||||||
|
validateConfig(account)
|
||||||
|
|
||||||
|
const paymentData = {
|
||||||
|
lnurl: lnurl,
|
||||||
|
amount: parseInt(cryptoAtoms.toString()) * 1000, // Convert satoshis to millisatoshis
|
||||||
|
comment: `Lamassu ATM - ${new Date().toISOString()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `${account.endpoint}/api/v1/payments/lnurl`
|
||||||
|
const result = await request(endpoint, 'POST', paymentData, account.adminKey)
|
||||||
|
|
||||||
|
if (!result.payment_hash) {
|
||||||
|
throw new Error('LNBits LNURL payment failed: No payment hash returned')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
txid: result.payment_hash,
|
||||||
|
fee: result.fee_msat ? Math.ceil(result.fee_msat / 1000) : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendCoins(account, tx) {
|
||||||
|
validateConfig(account)
|
||||||
|
const { toAddress, cryptoAtoms, cryptoCode } = tx
|
||||||
|
|
||||||
|
await checkCryptoCode(cryptoCode)
|
||||||
|
|
||||||
|
// Handle LNURL addresses
|
||||||
|
if (isLnurl(toAddress)) {
|
||||||
|
return sendLNURL(account, toAddress, cryptoAtoms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle bolt11 invoices
|
||||||
|
if (!isLnInvoice(toAddress)) {
|
||||||
|
throw new Error('Invalid Lightning address: must be bolt11 invoice or LNURL')
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentData = {
|
||||||
|
out: true,
|
||||||
|
bolt11: toAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoded = bolt11.decode(toAddress)
|
||||||
|
const invoiceAmount = decoded.satoshis
|
||||||
|
|
||||||
|
if (!invoiceAmount || invoiceAmount === 0) {
|
||||||
|
paymentData.amount = parseInt(cryptoAtoms.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `${account.endpoint}/api/v1/payments`
|
||||||
|
const result = await request(endpoint, 'POST', paymentData, account.adminKey)
|
||||||
|
|
||||||
|
if (!result.payment_hash) {
|
||||||
|
throw new Error('LNBits payment failed: No payment hash returned')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
txid: result.payment_hash,
|
||||||
|
fee: result.fee_msat ? Math.ceil(result.fee_msat / 1000) : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function balance(account, cryptoCode) {
|
||||||
|
validateConfig(account)
|
||||||
|
await checkCryptoCode(cryptoCode)
|
||||||
|
|
||||||
|
const endpoint = `${account.endpoint}/api/v1/wallet`
|
||||||
|
const result = await request(endpoint, 'GET', null, account.adminKey)
|
||||||
|
|
||||||
|
if (result.balance === undefined) {
|
||||||
|
throw new Error('LNBits did not return wallet balance')
|
||||||
|
}
|
||||||
|
|
||||||
|
const balanceSats = Math.floor(result.balance / 1000)
|
||||||
|
return new BN(balanceSats)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newFunding(account, cryptoCode) {
|
||||||
|
await checkCryptoCode(cryptoCode)
|
||||||
|
validateConfig(account)
|
||||||
|
|
||||||
|
const promises = [
|
||||||
|
balance(account, cryptoCode),
|
||||||
|
newAddress(account, { cryptoCode }, { cryptoCode, cryptoAtoms: new BN(100000) })
|
||||||
|
]
|
||||||
|
|
||||||
|
const [walletBalance, fundingAddress] = await Promise.all(promises)
|
||||||
|
|
||||||
|
return {
|
||||||
|
fundingPendingBalance: new BN(0),
|
||||||
|
fundingConfirmedBalance: walletBalance,
|
||||||
|
fundingAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function probeLN(account, cryptoCode, invoice) {
|
||||||
|
validateConfig(account)
|
||||||
|
await checkCryptoCode(cryptoCode)
|
||||||
|
|
||||||
|
if (!isLnInvoice(invoice)) {
|
||||||
|
throw new Error('Invalid Lightning invoice provided for probe')
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `${account.endpoint}/api/v1/payments/decode`
|
||||||
|
const decodeData = { data: invoice }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await request(endpoint, 'POST', decodeData, account.adminKey)
|
||||||
|
|
||||||
|
const amountSats = result.amount_msat ? Math.floor(result.amount_msat / 1000) : 0
|
||||||
|
|
||||||
|
const limits = [200000, 1000000, 2000000]
|
||||||
|
const probeResults = limits.map(limit => amountSats <= limit)
|
||||||
|
|
||||||
|
return probeResults
|
||||||
|
} catch (error) {
|
||||||
|
console.error('LNBits probe error:', error.message)
|
||||||
|
return [false, false, false]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cryptoNetwork(account, cryptoCode) {
|
||||||
|
const endpoint = account.endpoint || ''
|
||||||
|
if (endpoint.includes('testnet') || endpoint.includes('test')) {
|
||||||
|
return 'test'
|
||||||
|
}
|
||||||
|
if (endpoint.includes('regtest') || endpoint.includes('local')) {
|
||||||
|
return 'regtest'
|
||||||
|
}
|
||||||
|
return 'main'
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
NAME,
|
||||||
|
balance,
|
||||||
|
sendCoins,
|
||||||
|
newAddress,
|
||||||
|
getStatus,
|
||||||
|
newFunding,
|
||||||
|
probeLN,
|
||||||
|
cryptoNetwork,
|
||||||
|
isLnInvoice,
|
||||||
|
isLnurl
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// This migration was already applied to the database
|
||||||
|
// This is a placeholder file to satisfy the migration system
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
// Migration already applied - no-op
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
// No-op
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
const { saveConfig } = require('../lib/new-settings-loader')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
const config = {
|
||||||
|
'lnbits_endpoint': '',
|
||||||
|
'lnbits_adminKey': '',
|
||||||
|
'LN_wallet': 'lnbits'
|
||||||
|
}
|
||||||
|
|
||||||
|
saveConfig(config).then(next).catch(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
// No-op - removing config entries is not typically done in down migrations
|
||||||
|
// as it could break existing configurations
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// This migration was already applied to the database
|
||||||
|
// This is a placeholder file to satisfy the migration system
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
// Migration already applied - no-op
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
// No-op
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// This migration was already applied to the database
|
||||||
|
// This is a placeholder file to satisfy the migration system
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
// Migration already applied - no-op
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
// No-op
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// This migration was already applied to the database
|
||||||
|
// This is a placeholder file to satisfy the migration system
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
// Migration already applied - no-op
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
// No-op
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// This migration was already applied to the database
|
||||||
|
// This is a placeholder file to satisfy the migration system
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
// Migration already applied - no-op
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
// No-op
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "lamassu-server",
|
"name": "lamassu-server",
|
||||||
"description": "bitcoin atm client server protocol module",
|
"description": "bitcoin atm client server protocol module",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"version": "11.0.0",
|
"version": "11.0.1",
|
||||||
"license": "./LICENSE",
|
"license": "./LICENSE",
|
||||||
"author": "Lamassu (https://lamassu.is)",
|
"author": "Lamassu (https://lamassu.is)",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
"bchaddrjs": "^0.3.0",
|
"bchaddrjs": "^0.3.0",
|
||||||
"bignumber.js": "9.0.1",
|
"bignumber.js": "9.0.1",
|
||||||
"bip39": "^2.3.1",
|
"bip39": "^2.3.1",
|
||||||
|
"bolt11": "^1.4.1",
|
||||||
"ccxt": "2.9.16",
|
"ccxt": "2.9.16",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"connect-pg-simple": "^6.2.1",
|
"connect-pg-simple": "^6.2.1",
|
||||||
|
|
|
||||||
257
packages/server/tests/unit/test_lnbits_plugin.js
Normal file
257
packages/server/tests/unit/test_lnbits_plugin.js
Normal file
|
|
@ -0,0 +1,257 @@
|
||||||
|
const test = require('tape')
|
||||||
|
const sinon = require('sinon')
|
||||||
|
const axios = require('axios')
|
||||||
|
const BN = require('../../lib/bn')
|
||||||
|
|
||||||
|
// Mock the module before requiring the plugin
|
||||||
|
const lnbits = require('../../lib/plugins/wallet/lnbits/lnbits')
|
||||||
|
|
||||||
|
test('LNBits plugin - configuration validation', t => {
|
||||||
|
t.plan(3)
|
||||||
|
|
||||||
|
const validAccount = {
|
||||||
|
endpoint: 'https://demo.lnbits.com',
|
||||||
|
adminKey: 'test_admin_key_123'
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalidAccount1 = {
|
||||||
|
endpoint: 'https://demo.lnbits.com'
|
||||||
|
// Missing adminKey
|
||||||
|
}
|
||||||
|
|
||||||
|
const invalidAccount2 = {
|
||||||
|
adminKey: 'test_admin_key_123'
|
||||||
|
// Missing endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid config should not throw
|
||||||
|
t.doesNotThrow(() => {
|
||||||
|
lnbits.balance(validAccount, 'LN').catch(() => {})
|
||||||
|
}, 'Valid configuration passes validation')
|
||||||
|
|
||||||
|
// Missing adminKey should throw
|
||||||
|
lnbits.balance(invalidAccount1, 'LN').catch(err => {
|
||||||
|
t.ok(err.message.includes('LNBits configuration missing: adminKey'),
|
||||||
|
'Missing adminKey throws appropriate error')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Missing endpoint should throw
|
||||||
|
lnbits.balance(invalidAccount2, 'LN').catch(err => {
|
||||||
|
t.ok(err.message.includes('LNBits configuration missing: endpoint'),
|
||||||
|
'Missing endpoint throws appropriate error')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('LNBits plugin - crypto code validation', t => {
|
||||||
|
t.plan(2)
|
||||||
|
|
||||||
|
const account = {
|
||||||
|
endpoint: 'https://demo.lnbits.com',
|
||||||
|
adminKey: 'test_admin_key'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid crypto code
|
||||||
|
lnbits.balance(account, 'LN').catch(() => {
|
||||||
|
// Expected to fail at API call, not validation
|
||||||
|
})
|
||||||
|
t.pass('LN crypto code is accepted')
|
||||||
|
|
||||||
|
// Invalid crypto code
|
||||||
|
lnbits.balance(account, 'BTC').catch(err => {
|
||||||
|
t.ok(err.message.includes('Unsupported crypto'),
|
||||||
|
'Invalid crypto code throws appropriate error')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('LNBits plugin - invoice detection', t => {
|
||||||
|
t.plan(6)
|
||||||
|
|
||||||
|
// Valid Lightning invoices
|
||||||
|
t.ok(lnbits.isLnInvoice('lnbc100n1p0example'), 'Detects mainnet Lightning invoice')
|
||||||
|
t.ok(lnbits.isLnInvoice('lntb100n1p0example'), 'Detects testnet Lightning invoice')
|
||||||
|
t.ok(lnbits.isLnInvoice('lnbcrt100n1p0example'), 'Detects regtest Lightning invoice')
|
||||||
|
|
||||||
|
// Invalid invoices
|
||||||
|
t.notOk(lnbits.isLnInvoice('bc1qexample'), 'Rejects Bitcoin address')
|
||||||
|
t.notOk(lnbits.isLnInvoice('lnurl1234'), 'Rejects LNURL')
|
||||||
|
t.notOk(lnbits.isLnInvoice(''), 'Rejects empty string')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('LNBits plugin - LNURL detection', t => {
|
||||||
|
t.plan(3)
|
||||||
|
|
||||||
|
t.ok(lnbits.isLnurl('lnurl1dp68gurn8ghj7um9'), 'Detects LNURL')
|
||||||
|
t.notOk(lnbits.isLnurl('lnbc100n1p0example'), 'Rejects Lightning invoice')
|
||||||
|
t.notOk(lnbits.isLnurl(''), 'Rejects empty string')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('LNBits plugin - network detection', t => {
|
||||||
|
t.plan(4)
|
||||||
|
|
||||||
|
const mainnetAccount = {
|
||||||
|
endpoint: 'https://lnbits.com',
|
||||||
|
adminKey: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
const testnetAccount = {
|
||||||
|
endpoint: 'https://testnet.lnbits.com',
|
||||||
|
adminKey: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
const regtestAccount = {
|
||||||
|
endpoint: 'http://localhost:5000',
|
||||||
|
adminKey: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccount = {
|
||||||
|
endpoint: 'https://test.lnbits.com',
|
||||||
|
adminKey: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
t.equal(lnbits.cryptoNetwork(mainnetAccount, 'LN'), 'main', 'Detects mainnet')
|
||||||
|
t.equal(lnbits.cryptoNetwork(testnetAccount, 'LN'), 'test', 'Detects testnet')
|
||||||
|
t.equal(lnbits.cryptoNetwork(regtestAccount, 'LN'), 'regtest', 'Detects regtest/local')
|
||||||
|
t.equal(lnbits.cryptoNetwork(testAccount, 'LN'), 'test', 'Detects test environment')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('LNBits plugin - newAddress creates invoice', async t => {
|
||||||
|
t.plan(3)
|
||||||
|
|
||||||
|
const account = {
|
||||||
|
endpoint: 'https://demo.lnbits.com',
|
||||||
|
adminKey: 'test_admin_key'
|
||||||
|
}
|
||||||
|
|
||||||
|
const tx = {
|
||||||
|
cryptoAtoms: new BN('100000'),
|
||||||
|
cryptoCode: 'LN'
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = {
|
||||||
|
cryptoCode: 'LN'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock the axios request
|
||||||
|
const stub = sinon.stub(axios, 'request')
|
||||||
|
stub.resolves({
|
||||||
|
data: {
|
||||||
|
payment_request: 'lnbc1000n1p0example',
|
||||||
|
payment_hash: 'abc123'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const invoice = await lnbits.newAddress(account, info, tx)
|
||||||
|
|
||||||
|
t.equal(invoice, 'lnbc1000n1p0example', 'Returns Lightning invoice')
|
||||||
|
t.ok(stub.calledOnce, 'Makes one API request')
|
||||||
|
|
||||||
|
const callArgs = stub.firstCall.args[0]
|
||||||
|
t.equal(callArgs.data.amount, 100000, 'Sends correct amount in satoshis')
|
||||||
|
} catch (err) {
|
||||||
|
t.fail(`Unexpected error: ${err.message}`)
|
||||||
|
} finally {
|
||||||
|
stub.restore()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('LNBits plugin - balance returns BN', async t => {
|
||||||
|
t.plan(2)
|
||||||
|
|
||||||
|
const account = {
|
||||||
|
endpoint: 'https://demo.lnbits.com',
|
||||||
|
adminKey: 'test_admin_key'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock the axios request
|
||||||
|
const stub = sinon.stub(axios, 'request')
|
||||||
|
stub.resolves({
|
||||||
|
data: {
|
||||||
|
balance: 500000000, // 500000 sats in millisats
|
||||||
|
name: 'Test Wallet'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const balance = await lnbits.balance(account, 'LN')
|
||||||
|
|
||||||
|
t.ok(balance instanceof BN, 'Returns BigNumber instance')
|
||||||
|
t.equal(balance.toString(), '500000', 'Converts millisats to sats correctly')
|
||||||
|
} catch (err) {
|
||||||
|
t.fail(`Unexpected error: ${err.message}`)
|
||||||
|
} finally {
|
||||||
|
stub.restore()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('LNBits plugin - getStatus checks payment', async t => {
|
||||||
|
t.plan(3)
|
||||||
|
|
||||||
|
const account = {
|
||||||
|
endpoint: 'https://demo.lnbits.com',
|
||||||
|
adminKey: 'test_admin_key'
|
||||||
|
}
|
||||||
|
|
||||||
|
const paidTx = {
|
||||||
|
toAddress: 'lnbc1000n1p3q7vlpp5example',
|
||||||
|
cryptoCode: 'LN'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock bolt11 decode
|
||||||
|
const bolt11 = require('bolt11')
|
||||||
|
const decodeStub = sinon.stub(bolt11, 'decode')
|
||||||
|
decodeStub.returns({
|
||||||
|
tagsObject: {
|
||||||
|
payment_hash: 'abc123'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Mock axios for paid invoice
|
||||||
|
const axiosStub = sinon.stub(axios, 'request')
|
||||||
|
axiosStub.onFirstCall().resolves({
|
||||||
|
data: {
|
||||||
|
paid: true,
|
||||||
|
amount: 1000
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const status = await lnbits.getStatus(account, paidTx)
|
||||||
|
t.equal(status.status, 'confirmed', 'Returns confirmed for paid invoice')
|
||||||
|
} catch (err) {
|
||||||
|
t.fail(`Unexpected error: ${err.message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test pending invoice
|
||||||
|
axiosStub.onSecondCall().resolves({
|
||||||
|
data: {
|
||||||
|
paid: false,
|
||||||
|
pending: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const status = await lnbits.getStatus(account, paidTx)
|
||||||
|
t.equal(status.status, 'pending', 'Returns pending for unpaid but pending invoice')
|
||||||
|
} catch (err) {
|
||||||
|
t.fail(`Unexpected error: ${err.message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test not found invoice
|
||||||
|
axiosStub.onThirdCall().rejects({
|
||||||
|
response: {
|
||||||
|
status: 404,
|
||||||
|
data: { detail: 'Payment not found' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const status = await lnbits.getStatus(account, paidTx)
|
||||||
|
t.equal(status.status, 'notSeen', 'Returns notSeen for not found invoice')
|
||||||
|
} catch (err) {
|
||||||
|
t.fail(`Unexpected error: ${err.message}`)
|
||||||
|
} finally {
|
||||||
|
decodeStub.restore()
|
||||||
|
axiosStub.restore()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -1,38 +1,76 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
const setEnvVariable = require('./set-env-var')
|
const envPath = path.resolve(__dirname, '../.env')
|
||||||
|
const sampleEnvPath = path.resolve(__dirname, '../.sample.env')
|
||||||
|
|
||||||
fs.copyFileSync(
|
// Check if .env already exists
|
||||||
path.resolve(__dirname, '../.sample.env'),
|
if (fs.existsSync(envPath)) {
|
||||||
path.resolve(__dirname, '../.env'),
|
console.log('.env file already exists. To rebuild, delete it first.')
|
||||||
)
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
setEnvVariable('NODE_ENV', 'development')
|
// Copy sample env
|
||||||
|
const sampleContent = fs.readFileSync(sampleEnvPath, 'utf8')
|
||||||
|
|
||||||
setEnvVariable('POSTGRES_USER', 'postgres')
|
// Development defaults
|
||||||
setEnvVariable('POSTGRES_PASSWORD', 'postgres123')
|
const devDefaults = {
|
||||||
setEnvVariable('POSTGRES_HOST', 'localhost')
|
NODE_ENV: 'development',
|
||||||
setEnvVariable('POSTGRES_PORT', '5432')
|
|
||||||
setEnvVariable('POSTGRES_DB', 'lamassu')
|
// Database
|
||||||
|
POSTGRES_USER: 'lamassu',
|
||||||
|
POSTGRES_PASSWORD: 'lamassu',
|
||||||
|
POSTGRES_HOST: 'localhost',
|
||||||
|
POSTGRES_PORT: '5432',
|
||||||
|
POSTGRES_DB: 'lamassu',
|
||||||
|
|
||||||
|
// Paths
|
||||||
|
CA_PATH: path.resolve(__dirname, '../Lamassu_CA.pem'),
|
||||||
|
CERT_PATH: path.resolve(__dirname, '../../../certs/Lamassu_LS.pem'),
|
||||||
|
KEY_PATH: path.resolve(__dirname, '../../../certs/Lamassu_LS.key'),
|
||||||
|
MNEMONIC_PATH: path.resolve(__dirname, '../../../mnemonic.txt'),
|
||||||
|
|
||||||
|
// Directories
|
||||||
|
BLOCKCHAIN_DIR: path.resolve(__dirname, '../../../blockchain'),
|
||||||
|
OFAC_DATA_DIR: path.resolve(__dirname, '../../../ofac'),
|
||||||
|
ID_PHOTO_CARD_DIR: path.resolve(__dirname, '../../../photos/idcard'),
|
||||||
|
FRONT_CAMERA_DIR: path.resolve(__dirname, '../../../photos/front'),
|
||||||
|
OPERATOR_DATA_DIR: path.resolve(__dirname, '../../../operator-data'),
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
HOSTNAME: 'localhost',
|
||||||
|
LOG_LEVEL: 'debug',
|
||||||
|
|
||||||
|
// Bitcoin (for development, use remote node to avoid full sync)
|
||||||
|
BTC_NODE_LOCATION: 'remote',
|
||||||
|
BTC_WALLET_LOCATION: 'local',
|
||||||
|
BTC_NODE_HOST: 'blockstream.info',
|
||||||
|
BTC_NODE_PORT: '8333',
|
||||||
|
|
||||||
|
// LNBits development defaults
|
||||||
|
LNBITS_ENDPOINT: 'https://legend.lnbits.com',
|
||||||
|
LNBITS_ADMIN_KEY: '' // User needs to set this
|
||||||
|
}
|
||||||
|
|
||||||
setEnvVariable('CA_PATH', `${process.env.PWD}/certs/Lamassu_OP_Root_CA.pem`)
|
// Build .env content
|
||||||
setEnvVariable('CERT_PATH', `${process.env.PWD}/certs/Lamassu_OP.pem`)
|
let envContent = sampleContent
|
||||||
setEnvVariable('KEY_PATH', `${process.env.PWD}/certs/Lamassu_OP.key`)
|
|
||||||
|
|
||||||
setEnvVariable(
|
// Replace empty values with dev defaults
|
||||||
'MNEMONIC_PATH',
|
Object.keys(devDefaults).forEach(key => {
|
||||||
`${process.env.PWD}/.lamassu/mnemonics/mnemonic.txt`,
|
const regex = new RegExp(`^${key}=.*$`, 'gm')
|
||||||
)
|
envContent = envContent.replace(regex, `${key}=${devDefaults[key]}`)
|
||||||
|
})
|
||||||
|
|
||||||
setEnvVariable('BLOCKCHAIN_DIR', `${process.env.PWD}/blockchains`)
|
// Write .env file
|
||||||
setEnvVariable('OFAC_DATA_DIR', `${process.env.PWD}/.lamassu/ofac`)
|
fs.writeFileSync(envPath, envContent)
|
||||||
setEnvVariable('ID_PHOTO_CARD_DIR', `${process.env.PWD}/.lamassu/idphotocard`)
|
|
||||||
setEnvVariable('FRONT_CAMERA_DIR', `${process.env.PWD}/.lamassu/frontcamera`)
|
|
||||||
setEnvVariable('OPERATOR_DATA_DIR', `${process.env.PWD}/.lamassu/operatordata`)
|
|
||||||
|
|
||||||
setEnvVariable('BTC_NODE_LOCATION', 'remote')
|
console.log('Development .env file created with defaults.')
|
||||||
setEnvVariable('BTC_WALLET_LOCATION', 'local')
|
console.log('IMPORTANT: You still need to:')
|
||||||
|
console.log(' 1. Generate certificates using: bash tools/cert-gen.sh')
|
||||||
setEnvVariable('HOSTNAME', 'localhost')
|
console.log(' 2. Create a mnemonic file at: ../../../mnemonic.txt')
|
||||||
setEnvVariable('LOG_LEVEL', 'debug')
|
console.log(' 3. Set up PostgreSQL database')
|
||||||
|
console.log(' 4. Configure LNBits admin key if using Lightning')
|
||||||
|
console.log('')
|
||||||
|
console.log('Run migrations with: node bin/lamassu-migrate')
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "typesafe-db",
|
"name": "typesafe-db",
|
||||||
"version": "11.0.0",
|
"version": "11.0.1",
|
||||||
"license": "../LICENSE",
|
"license": "../LICENSE",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue