Merge branch 'master' of github.com:BlueWallet/LndHub
This commit is contained in:
commit
27ebe36b4b
22
README.md
22
README.md
@ -6,17 +6,24 @@ Wrapper for Lightning Network Daemon. It provides separate accounts with minimum
|
|||||||
INSTALLATION
|
INSTALLATION
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
You can use those guides or follow instructions below:
|
||||||
|
|
||||||
|
* https://github.com/dangeross/guides/blob/master/raspibolt/raspibolt_6B_lndhub.md
|
||||||
|
* https://medium.com/@jpthor/running-lndhub-on-mac-osx-5be6671b2e0c
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone git@github.com:BlueWallet/LndHub.git
|
git clone git@github.com:BlueWallet/LndHub.git
|
||||||
cd LndHub
|
cd LndHub
|
||||||
npm i
|
npm i
|
||||||
```
|
```
|
||||||
|
|
||||||
Install `bitcoind`, `lnd` and `redis`.
|
Install `bitcoind`, `lnd` and `redis`. Edit `config.js` and set it up correctly.
|
||||||
|
|
||||||
Edit `config.js` and set it up correctly.
|
|
||||||
Copy `admin.macaroon` and `tls.cert` in root folder of LndHub.
|
Copy `admin.macaroon` and `tls.cert` in root folder of LndHub.
|
||||||
|
|
||||||
|
`bitcoind` should run with `-deprecatedrpc=accounts`, for now. Lndhub expects Lnd's wallet to be unlocked, if not - it will attempt to unlock it with password stored in `config.lnd.password`.
|
||||||
|
Don't forget to enable disk-persistance for `redis`.
|
||||||
|
|
||||||
|
|
||||||
### Deploy to Heroku
|
### Deploy to Heroku
|
||||||
|
|
||||||
Add config vars :
|
Add config vars :
|
||||||
@ -28,3 +35,12 @@ Add config vars :
|
|||||||
### Tests
|
### Tests
|
||||||
|
|
||||||
Acceptance tests are in https://github.com/BlueWallet/BlueWallet/blob/master/LightningCustodianWallet.test.js
|
Acceptance tests are in https://github.com/BlueWallet/BlueWallet/blob/master/LightningCustodianWallet.test.js
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Responsible disclosure
|
||||||
|
|
||||||
|
Found critical bugs/vulnerabilities? Please email them bluewallet@bluewallet.io
|
||||||
|
Thanks!
|
||||||
|
@ -24,8 +24,8 @@ export class Lock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// success - got lock
|
// success - got lock
|
||||||
await this._redis.expire(this._lock_key, 2 * 60);
|
await this._redis.expire(this._lock_key, 3600);
|
||||||
// lock expires in 2 mins just for any case
|
// lock expires in 5 mins just for any case
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
25
class/Paym.js
Normal file
25
class/Paym.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
var crypto = require('crypto');
|
||||||
|
var lightningPayReq = require('bolt11');
|
||||||
|
import { BigNumber } from 'bignumber.js';
|
||||||
|
|
||||||
|
export class Payment {
|
||||||
|
constructor(redis, bitcoindrpc, lightning) {
|
||||||
|
this._redis = redis;
|
||||||
|
this._bitcoindrpc = bitcoindrpc;
|
||||||
|
this._lightning = lightning;
|
||||||
|
this._decoded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async decodePayReqViaRpc(invoice) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
this._lightning.decodePayReq({ pay_req: invoice }, function(err, info) {
|
||||||
|
if (err) return reject(err);
|
||||||
|
return resolve(info);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
decodePayReq(payReq) {
|
||||||
|
this._decoded = lightningPayReq.decode(payReq);
|
||||||
|
}
|
||||||
|
}
|
134
class/User.js
134
class/User.js
@ -113,12 +113,67 @@ export class User {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LndHub no longer relies on redis balance as source of truth, this is
|
||||||
|
* more a cache now. See `this.getCalculatedBalance()` to get correct balance.
|
||||||
|
*
|
||||||
|
* @returns {Promise<number>} Balance available to spend
|
||||||
|
*/
|
||||||
async getBalance() {
|
async getBalance() {
|
||||||
return (await this._redis.get('balance_for_' + this._userid)) * 1;
|
let balance = (await this._redis.get('balance_for_' + this._userid)) * 1;
|
||||||
|
if (!balance) {
|
||||||
|
balance = await this.getCalculatedBalance();
|
||||||
|
await this.saveBalance(balance);
|
||||||
|
}
|
||||||
|
return balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accounts for all possible transactions in user's account and
|
||||||
|
* sums their amounts.
|
||||||
|
*
|
||||||
|
* @returns {Promise<number>} Balance available to spend
|
||||||
|
*/
|
||||||
|
async getCalculatedBalance() {
|
||||||
|
let calculatedBalance = 0;
|
||||||
|
let userinvoices = await this.getUserInvoices();
|
||||||
|
|
||||||
|
for (let invo of userinvoices) {
|
||||||
|
if (invo && invo.ispaid) {
|
||||||
|
calculatedBalance += +invo.amt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let txs = await this.getTxs();
|
||||||
|
for (let tx of txs) {
|
||||||
|
if (tx.type === 'bitcoind_tx') {
|
||||||
|
// topup
|
||||||
|
calculatedBalance += new BigNumber(tx.amount).multipliedBy(100000000).toNumber();
|
||||||
|
} else {
|
||||||
|
calculatedBalance -= +tx.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let lockedPayments = await this.getLockedPayments();
|
||||||
|
for (let paym of lockedPayments) {
|
||||||
|
// TODO: check if payment in determined state and actually evict it from this list
|
||||||
|
calculatedBalance -= +paym.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LndHub no longer relies on redis balance as source of truth, this is
|
||||||
|
* more a cache now. See `this.getCalculatedBalance()` to get correct balance.
|
||||||
|
*
|
||||||
|
* @param balance
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
async saveBalance(balance) {
|
async saveBalance(balance) {
|
||||||
return await this._redis.set('balance_for_' + this._userid, balance);
|
const key = 'balance_for_' + this._userid;
|
||||||
|
await this._redis.set(key, balance);
|
||||||
|
await this._redis.expire(key, 3600 * 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
async savePaidLndInvoice(doc) {
|
async savePaidLndInvoice(doc) {
|
||||||
@ -192,7 +247,7 @@ export class User {
|
|||||||
if (invoice.ispaid) {
|
if (invoice.ispaid) {
|
||||||
// so invoice was paid after all
|
// so invoice was paid after all
|
||||||
await this.setPaymentHashPaid(invoice.payment_hash);
|
await this.setPaymentHashPaid(invoice.payment_hash);
|
||||||
await this.saveBalance((await this.getBalance()) + decoded.satoshis);
|
await this.saveBalance(await this.getCalculatedBalance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,11 +268,16 @@ export class User {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* User's onchain txs that are >= 3 confs
|
* User's onchain txs that are >= 3 confs
|
||||||
|
* Queries bitcoind RPC.
|
||||||
*
|
*
|
||||||
* @returns {Promise<Array>}
|
* @returns {Promise<Array>}
|
||||||
*/
|
*/
|
||||||
async getTxs() {
|
async getTxs() {
|
||||||
let addr = await this.getAddress();
|
let addr = await this.getAddress();
|
||||||
|
if (!addr) {
|
||||||
|
await this.generateAddress();
|
||||||
|
addr = await this.getAddress();
|
||||||
|
}
|
||||||
if (!addr) throw new Error('cannot get transactions: no onchain address assigned to user');
|
if (!addr) throw new Error('cannot get transactions: no onchain address assigned to user');
|
||||||
let txs = await this._bitcoindrpc.request('listtransactions', [addr, 100500, 0, true]);
|
let txs = await this._bitcoindrpc.request('listtransactions', [addr, 100500, 0, true]);
|
||||||
txs = txs.result;
|
txs = txs.result;
|
||||||
@ -245,6 +305,12 @@ export class User {
|
|||||||
invoice.timestamp = invoice.decoded.timestamp;
|
invoice.timestamp = invoice.decoded.timestamp;
|
||||||
invoice.memo = invoice.decoded.description;
|
invoice.memo = invoice.decoded.description;
|
||||||
}
|
}
|
||||||
|
// removing unsued by client fields to reduce size
|
||||||
|
delete invoice.payment_error;
|
||||||
|
delete invoice.payment_preimage;
|
||||||
|
delete invoice.payment_route;
|
||||||
|
delete invoice.pay_req;
|
||||||
|
delete invoice.decoded;
|
||||||
result.push(invoice);
|
result.push(invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,6 +324,10 @@ export class User {
|
|||||||
*/
|
*/
|
||||||
async getPendingTxs() {
|
async getPendingTxs() {
|
||||||
let addr = await this.getAddress();
|
let addr = await this.getAddress();
|
||||||
|
if (!addr) {
|
||||||
|
await this.generateAddress();
|
||||||
|
addr = await this.getAddress();
|
||||||
|
}
|
||||||
if (!addr) throw new Error('cannot get transactions: no onchain address assigned to user');
|
if (!addr) throw new Error('cannot get transactions: no onchain address assigned to user');
|
||||||
let txs = await this._bitcoindrpc.request('listtransactions', [addr, 100500, 0, true]);
|
let txs = await this._bitcoindrpc.request('listtransactions', [addr, 100500, 0, true]);
|
||||||
txs = txs.result;
|
txs = txs.result;
|
||||||
@ -313,8 +383,9 @@ export class User {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let userBalance = await this.getBalance();
|
let userBalance = await this.getCalculatedBalance();
|
||||||
userBalance += new BigNumber(tx.amount).multipliedBy(100000000).toNumber();
|
// userBalance += new BigNumber(tx.amount).multipliedBy(100000000).toNumber();
|
||||||
|
// no need to add since it was accounted for in `this.getCalculatedBalance()`
|
||||||
await this.saveBalance(userBalance);
|
await this.saveBalance(userBalance);
|
||||||
await this._redis.rpush('imported_txids_for_' + this._userid, tx.txid);
|
await this._redis.rpush('imported_txids_for_' + this._userid, tx.txid);
|
||||||
await lock.releaseLock();
|
await lock.releaseLock();
|
||||||
@ -322,6 +393,59 @@ export class User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds invoice to a list of user's locked payments.
|
||||||
|
* Used to calculate balance till the lock is lifted (payment is in
|
||||||
|
* determined state - succeded or failed).
|
||||||
|
*
|
||||||
|
* @param {String} pay_req
|
||||||
|
* @param {Object} decodedInvoice
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async lockFunds(pay_req, decodedInvoice) {
|
||||||
|
let doc = {
|
||||||
|
pay_req,
|
||||||
|
amount: +decodedInvoice.num_satoshis,
|
||||||
|
timestamp: Math.floor(+new Date() / 1000),
|
||||||
|
};
|
||||||
|
|
||||||
|
return this._redis.rpush('locked_payments_for_' + this._userid, JSON.stringify(doc));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strips specific payreq from the list of locked payments
|
||||||
|
* @param pay_req
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async unlockFunds(pay_req) {
|
||||||
|
let payments = await this.getLockedPayments();
|
||||||
|
let saveBack = [];
|
||||||
|
for (let paym of payments) {
|
||||||
|
if (paym.pay_req !== pay_req) {
|
||||||
|
saveBack.push(paym);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this._redis.del('locked_payments_for_' + this._userid);
|
||||||
|
for (let doc of saveBack) {
|
||||||
|
await this._redis.rpush('locked_payments_for_' + this._userid, JSON.stringify(doc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLockedPayments() {
|
||||||
|
let payments = await this._redis.lrange('locked_payments_for_' + this._userid, 0, -1);
|
||||||
|
let result = [];
|
||||||
|
for (let paym of payments) {
|
||||||
|
let json;
|
||||||
|
try {
|
||||||
|
json = JSON.parse(paym);
|
||||||
|
result.push(json);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
_hash(string) {
|
_hash(string) {
|
||||||
return crypto
|
return crypto
|
||||||
.createHash('sha256')
|
.createHash('sha256')
|
||||||
|
@ -9,7 +9,7 @@ var Redis = require('ioredis');
|
|||||||
var redis = new Redis(config.redis);
|
var redis = new Redis(config.redis);
|
||||||
redis.monitor(function(err, monitor) {
|
redis.monitor(function(err, monitor) {
|
||||||
monitor.on('monitor', function(time, args, source, database) {
|
monitor.on('monitor', function(time, args, source, database) {
|
||||||
console.log('REDIS', JSON.stringify(args));
|
// console.log('REDIS', JSON.stringify(args));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ router.post('/create', async function(req, res) {
|
|||||||
logger.log('/create', [req.id]);
|
logger.log('/create', [req.id]);
|
||||||
if (!(req.body.partnerid && req.body.partnerid === 'bluewallet' && req.body.accounttype)) return errorBadArguments(res);
|
if (!(req.body.partnerid && req.body.partnerid === 'bluewallet' && req.body.accounttype)) return errorBadArguments(res);
|
||||||
|
|
||||||
let u = new User(redis);
|
let u = new User(redis, bitcoinclient, lightning);
|
||||||
await u.create();
|
await u.create();
|
||||||
await u.saveMetadata({ partnerid: req.body.partnerid, accounttype: req.body.accounttype, created_at: new Date().toISOString() });
|
await u.saveMetadata({ partnerid: req.body.partnerid, accounttype: req.body.accounttype, created_at: new Date().toISOString() });
|
||||||
res.send({ login: u.getLogin(), password: u.getPassword() });
|
res.send({ login: u.getLogin(), password: u.getPassword() });
|
||||||
@ -68,7 +68,7 @@ router.post('/auth', async function(req, res) {
|
|||||||
logger.log('/auth', [req.id]);
|
logger.log('/auth', [req.id]);
|
||||||
if (!((req.body.login && req.body.password) || req.body.refresh_token)) return errorBadArguments(res);
|
if (!((req.body.login && req.body.password) || req.body.refresh_token)) return errorBadArguments(res);
|
||||||
|
|
||||||
let u = new User(redis);
|
let u = new User(redis, bitcoinclient, lightning);
|
||||||
|
|
||||||
if (req.body.refresh_token) {
|
if (req.body.refresh_token) {
|
||||||
// need to refresh token
|
// need to refresh token
|
||||||
@ -87,7 +87,7 @@ router.post('/auth', async function(req, res) {
|
|||||||
|
|
||||||
router.post('/addinvoice', async function(req, res) {
|
router.post('/addinvoice', async function(req, res) {
|
||||||
logger.log('/addinvoice', [req.id]);
|
logger.log('/addinvoice', [req.id]);
|
||||||
let u = new User(redis);
|
let u = new User(redis, bitcoinclient, lightning);
|
||||||
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
return errorBadAuth(res);
|
return errorBadAuth(res);
|
||||||
}
|
}
|
||||||
@ -105,15 +105,19 @@ router.post('/addinvoice', async function(req, res) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post('/payinvoice', async function(req, res) {
|
router.post('/payinvoice', async function(req, res) {
|
||||||
logger.log('/payinvoice', [req.id]);
|
let u = new User(redis, bitcoinclient, lightning);
|
||||||
let u = new User(redis);
|
|
||||||
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
return errorBadAuth(res);
|
return errorBadAuth(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.log('/payinvoice', [req.id, 'userid: ' + u.getUserId(), 'invoice: ' + req.body.invoice]);
|
||||||
|
|
||||||
if (!req.body.invoice) return errorBadArguments(res);
|
if (!req.body.invoice) return errorBadArguments(res);
|
||||||
let freeAmount = false;
|
let freeAmount = false;
|
||||||
if (req.body.amount) freeAmount = parseInt(req.body.amount);
|
if (req.body.amount) {
|
||||||
|
freeAmount = parseInt(req.body.amount);
|
||||||
|
if (freeAmount <= 0) return errorBadArguments(res);
|
||||||
|
}
|
||||||
|
|
||||||
// obtaining a lock
|
// obtaining a lock
|
||||||
let lock = new Lock(redis, 'invoice_paying_for_' + u.getUserId());
|
let lock = new Lock(redis, 'invoice_paying_for_' + u.getUserId());
|
||||||
@ -121,7 +125,7 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
return errorTryAgainLater(res);
|
return errorTryAgainLater(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
let userBalance = await u.getBalance();
|
let userBalance = await u.getCalculatedBalance();
|
||||||
|
|
||||||
lightning.decodePayReq({ pay_req: req.body.invoice }, async function(err, info) {
|
lightning.decodePayReq({ pay_req: req.body.invoice }, async function(err, info) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -134,8 +138,10 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
info.num_satoshis = freeAmount;
|
info.num_satoshis = freeAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userBalance >= info.num_satoshis) {
|
logger.log('/payinvoice', [req.id, 'userBalance: ' + userBalance, 'num_satoshis: ' + info.num_satoshis]);
|
||||||
// got enough balance
|
|
||||||
|
if (userBalance >= +info.num_satoshis + Math.floor(info.num_satoshis * 0.01)) {
|
||||||
|
// got enough balance, including 1% of payment amount - reserve for fees
|
||||||
|
|
||||||
if (identity_pubkey === info.destination) {
|
if (identity_pubkey === info.destination) {
|
||||||
// this is internal invoice
|
// this is internal invoice
|
||||||
@ -152,7 +158,7 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
return errorLnd(res);
|
return errorLnd(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
let UserPayee = new User(redis);
|
let UserPayee = new User(redis, bitcoinclient, lightning);
|
||||||
UserPayee._userid = userid_payee; // hacky, fixme
|
UserPayee._userid = userid_payee; // hacky, fixme
|
||||||
let payee_balance = await UserPayee.getBalance();
|
let payee_balance = await UserPayee.getBalance();
|
||||||
payee_balance += info.num_satoshis * 1;
|
payee_balance += info.num_satoshis * 1;
|
||||||
@ -164,8 +170,8 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
await u.savePaidLndInvoice({
|
await u.savePaidLndInvoice({
|
||||||
timestamp: parseInt(+new Date() / 1000),
|
timestamp: parseInt(+new Date() / 1000),
|
||||||
type: 'paid_invoice',
|
type: 'paid_invoice',
|
||||||
value: info.num_satoshis * 1,
|
value: +info.num_satoshis + Math.floor(info.num_satoshis * 0.01),
|
||||||
fee: 0, // internal invoices are free
|
fee: Math.floor(info.num_satoshis * 0.01),
|
||||||
memo: decodeURIComponent(info.description),
|
memo: decodeURIComponent(info.description),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -178,14 +184,16 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
// else - regular lightning network payment:
|
// else - regular lightning network payment:
|
||||||
|
|
||||||
var call = lightning.sendPayment();
|
var call = lightning.sendPayment();
|
||||||
call.on('data', function(payment) {
|
call.on('data', async function(payment) {
|
||||||
// payment callback
|
// payment callback
|
||||||
|
await u.unlockFunds(req.body.invoice);
|
||||||
if (payment && payment.payment_route && payment.payment_route.total_amt_msat) {
|
if (payment && payment.payment_route && payment.payment_route.total_amt_msat) {
|
||||||
|
payment.payment_route.total_fees += Math.floor(+payment.payment_route.total_amt * 0.01);
|
||||||
userBalance -= +payment.payment_route.total_fees + +payment.payment_route.total_amt;
|
userBalance -= +payment.payment_route.total_fees + +payment.payment_route.total_amt;
|
||||||
u.saveBalance(userBalance);
|
u.saveBalance(userBalance);
|
||||||
payment.pay_req = req.body.invoice;
|
payment.pay_req = req.body.invoice;
|
||||||
payment.decoded = info;
|
payment.decoded = info;
|
||||||
u.savePaidLndInvoice(payment);
|
await u.savePaidLndInvoice(payment);
|
||||||
lock.releaseLock();
|
lock.releaseLock();
|
||||||
res.send(payment);
|
res.send(payment);
|
||||||
} else {
|
} else {
|
||||||
@ -199,12 +207,15 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
await lock.releaseLock();
|
await lock.releaseLock();
|
||||||
return errorBadArguments(res);
|
return errorBadArguments(res);
|
||||||
}
|
}
|
||||||
let inv = { payment_request: req.body.invoice, amt: info.num_satoshis }; // amt is used only for 'tip' invoices
|
let inv = {
|
||||||
|
payment_request: req.body.invoice,
|
||||||
|
amt: info.num_satoshis, // amt is used only for 'tip' invoices
|
||||||
|
fee_limit: { fixed: Math.floor(info.num_satoshis * 0.005) },
|
||||||
|
};
|
||||||
try {
|
try {
|
||||||
logger.log('/payinvoice', [req.id, 'before write', JSON.stringify(inv)]);
|
await u.lockFunds(req.body.invoice, info);
|
||||||
call.write(inv);
|
call.write(inv);
|
||||||
} catch (Err) {
|
} catch (Err) {
|
||||||
logger.log('/payinvoice', [req.id, 'exception', JSON.stringify(Err)]);
|
|
||||||
await lock.releaseLock();
|
await lock.releaseLock();
|
||||||
return errorLnd(res);
|
return errorLnd(res);
|
||||||
}
|
}
|
||||||
@ -243,12 +254,13 @@ router.get('/balance', async function(req, res) {
|
|||||||
if (!(await u.getAddress())) await u.generateAddress(); // onchain address needed further
|
if (!(await u.getAddress())) await u.generateAddress(); // onchain address needed further
|
||||||
u.accountForPosibleTxids();
|
u.accountForPosibleTxids();
|
||||||
let balance = await u.getBalance();
|
let balance = await u.getBalance();
|
||||||
|
if (balance < 0) balance = 0;
|
||||||
res.send({ BTC: { AvailableBalance: balance } });
|
res.send({ BTC: { AvailableBalance: balance } });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/getinfo', async function(req, res) {
|
router.get('/getinfo', async function(req, res) {
|
||||||
logger.log('/getinfo', [req.id]);
|
logger.log('/getinfo', [req.id]);
|
||||||
let u = new User(redis);
|
let u = new User(redis, bitcoinclient, lightning);
|
||||||
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
return errorBadAuth(res);
|
return errorBadAuth(res);
|
||||||
}
|
}
|
||||||
@ -272,7 +284,7 @@ router.get('/gettxs', async function(req, res) {
|
|||||||
let txs = await u.getTxs();
|
let txs = await u.getTxs();
|
||||||
res.send(txs);
|
res.send(txs);
|
||||||
} catch (Err) {
|
} catch (Err) {
|
||||||
console.log(Err);
|
logger.log('', [req.id, 'error:', Err]);
|
||||||
res.send([]);
|
res.send([]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -292,7 +304,7 @@ router.get('/getuserinvoices', async function(req, res) {
|
|||||||
res.send(invoices);
|
res.send(invoices);
|
||||||
}
|
}
|
||||||
} catch (Err) {
|
} catch (Err) {
|
||||||
console.log(Err);
|
logger.log('', [req.id, 'error:', Err]);
|
||||||
res.send([]);
|
res.send([]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -312,7 +324,7 @@ router.get('/getpending', async function(req, res) {
|
|||||||
|
|
||||||
router.get('/decodeinvoice', async function(req, res) {
|
router.get('/decodeinvoice', async function(req, res) {
|
||||||
logger.log('/decodeinvoice', [req.id]);
|
logger.log('/decodeinvoice', [req.id]);
|
||||||
let u = new User(redis, bitcoinclient);
|
let u = new User(redis, bitcoinclient, lightning);
|
||||||
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
return errorBadAuth(res);
|
return errorBadAuth(res);
|
||||||
}
|
}
|
||||||
@ -327,7 +339,7 @@ router.get('/decodeinvoice', async function(req, res) {
|
|||||||
|
|
||||||
router.get('/checkrouteinvoice', async function(req, res) {
|
router.get('/checkrouteinvoice', async function(req, res) {
|
||||||
logger.log('/checkrouteinvoice', [req.id]);
|
logger.log('/checkrouteinvoice', [req.id]);
|
||||||
let u = new User(redis, bitcoinclient);
|
let u = new User(redis, bitcoinclient, lightning);
|
||||||
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
return errorBadAuth(res);
|
return errorBadAuth(res);
|
||||||
}
|
}
|
||||||
@ -398,6 +410,6 @@ function errorTryAgainLater(res) {
|
|||||||
return res.send({
|
return res.send({
|
||||||
error: true,
|
error: true,
|
||||||
code: 9,
|
code: 9,
|
||||||
message: 'Try again later',
|
message: 'Your previous payment is in transit. Try again in 5 minutes',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,15 @@ function updateLightning() {
|
|||||||
try {
|
try {
|
||||||
lightning.getInfo({}, function(err, info) {
|
lightning.getInfo({}, function(err, info) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('lnd failure');
|
console.error('lnd failure:', err);
|
||||||
}
|
}
|
||||||
lightningGetInfo = info;
|
lightningGetInfo = info;
|
||||||
});
|
});
|
||||||
|
|
||||||
lightning.listChannels({}, function(err, response) {
|
lightning.listChannels({}, function(err, response) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('lnd failure');
|
console.error('lnd failure:', err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
lightningListChannels = response;
|
lightningListChannels = response;
|
||||||
let channels = [];
|
let channels = [];
|
||||||
@ -44,6 +45,7 @@ function updateLightning() {
|
|||||||
} catch (Err) {
|
} catch (Err) {
|
||||||
console.log(Err);
|
console.log(Err);
|
||||||
}
|
}
|
||||||
|
console.log('updated');
|
||||||
}
|
}
|
||||||
updateLightning();
|
updateLightning();
|
||||||
setInterval(updateLightning, 60000);
|
setInterval(updateLightning, 60000);
|
||||||
@ -61,6 +63,11 @@ const pubkey2name = {
|
|||||||
'024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca': 'satoshis.place',
|
'024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca': 'satoshis.place',
|
||||||
'03c2abfa93eacec04721c019644584424aab2ba4dff3ac9bdab4e9c97007491dda': 'tippin.me',
|
'03c2abfa93eacec04721c019644584424aab2ba4dff3ac9bdab4e9c97007491dda': 'tippin.me',
|
||||||
'022c699df736064b51a33017abfc4d577d133f7124ac117d3d9f9633b6297a3b6a': 'globee.com',
|
'022c699df736064b51a33017abfc4d577d133f7124ac117d3d9f9633b6297a3b6a': 'globee.com',
|
||||||
|
'0237fefbe8626bf888de0cad8c73630e32746a22a2c4faa91c1d9877a3826e1174': '1.ln.aantonop.com',
|
||||||
|
'036a54f02d2186de192e4bcec3f7b47adb43b1fa965793387cd2471990ce1d236b': 'capacity.network',
|
||||||
|
'026c7d28784791a4b31a64eb34d9ab01552055b795919165e6ae886de637632efb': 'LivingRoomOfSatoshi.com_LND_1',
|
||||||
|
'02816caed43171d3c9854e3b0ab2cf0c42be086ff1bd4005acc2a5f7db70d83774': 'ln.pizza',
|
||||||
|
'024a2e265cd66066b78a788ae615acdc84b5b0dec9efac36d7ac87513015eaf6ed': 'Bitrefill.com/lightning',
|
||||||
};
|
};
|
||||||
|
|
||||||
router.get('/', function(req, res) {
|
router.get('/', function(req, res) {
|
||||||
|
@ -20,6 +20,8 @@ User storage schema
|
|||||||
* bitcoin_address_for_{userid} = {address}
|
* bitcoin_address_for_{userid} = {address}
|
||||||
* balance_for_{userid} = {int}
|
* balance_for_{userid} = {int}
|
||||||
* txs_for_{userid} = [] `serialized paid lnd invoices in a list`
|
* txs_for_{userid} = [] `serialized paid lnd invoices in a list`
|
||||||
|
* locked_invoices_for_{userod} = [] `serialized attempts to pay invoice. used in calculating user's balance`
|
||||||
|
: {pay_req:..., amount:666, timestamp:666}
|
||||||
* imported_txids_for_{userid} = [] `list of txids processed for this user`
|
* imported_txids_for_{userid} = [] `list of txids processed for this user`
|
||||||
* metadata_for_{userid}= {serialized json}
|
* metadata_for_{userid}= {serialized json}
|
||||||
* userinvoices_for_{userid} = []
|
* userinvoices_for_{userid} = []
|
||||||
|
19
lightning.js
19
lightning.js
@ -25,4 +25,23 @@ let macaroonCreds = grpc.credentials.createFromMetadataGenerator(function(args,
|
|||||||
callback(null, metadata);
|
callback(null, metadata);
|
||||||
});
|
});
|
||||||
let creds = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds);
|
let creds = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds);
|
||||||
|
|
||||||
|
// trying to unlock the wallet:
|
||||||
|
if (config.lnd.password) {
|
||||||
|
console.log('trying to unlock the wallet');
|
||||||
|
var walletUnlocker = new lnrpc.WalletUnlocker(config.lnd.url, creds);
|
||||||
|
walletUnlocker.unlockWallet(
|
||||||
|
{
|
||||||
|
wallet_password: config.lnd.password,
|
||||||
|
},
|
||||||
|
function(err, response) {
|
||||||
|
if (err) {
|
||||||
|
console.log('unlockWallet failed, probably because its been aleady unlocked');
|
||||||
|
} else {
|
||||||
|
console.log('unlockWallet:', response);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = new lnrpc.Lightning(config.lnd.url, creds);
|
module.exports = new lnrpc.Lightning(config.lnd.url, creds);
|
||||||
|
@ -23,13 +23,10 @@ const logger = createLogger({
|
|||||||
level: 'info',
|
level: 'info',
|
||||||
format: combine(timestamp(), logFormat),
|
format: combine(timestamp(), logFormat),
|
||||||
transports: [
|
transports: [
|
||||||
new transports.File({
|
new transports.Console({
|
||||||
filename: './logs/error.log',
|
|
||||||
level: 'error',
|
level: 'error',
|
||||||
}),
|
}),
|
||||||
new transports.File({
|
new transports.Console(),
|
||||||
filename: './logs/out.log',
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -45,7 +42,6 @@ if (!fs.existsSync('logs')) {
|
|||||||
* @param {string} message log message
|
* @param {string} message log message
|
||||||
*/
|
*/
|
||||||
function log(label, message) {
|
function log(label, message) {
|
||||||
console.log(new Date(), label, message);
|
|
||||||
logger.log({
|
logger.log({
|
||||||
level: 'info',
|
level: 'info',
|
||||||
label: label,
|
label: label,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user