Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98ba940342 | ||
|
|
6851769be6 | ||
|
|
d3100a1390 | ||
|
|
6a57559b16 | ||
|
|
4f345b20a8 | ||
|
|
a42d331e38 | ||
|
|
130a8dc773 | ||
|
|
b3c0873e4c | ||
|
|
6a3de700f4 | ||
|
|
51b0f89fd1 | ||
|
|
55ac146f1e |
@@ -294,8 +294,8 @@ export class User {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async accountForPosibleTxids() {
|
async accountForPosibleTxids() {
|
||||||
let imported_txids = await this._redis.lrange('imported_txids_for_' + this._userid, 0, -1);
|
|
||||||
let onchain_txs = await this.getTxs();
|
let onchain_txs = await this.getTxs();
|
||||||
|
let imported_txids = await this._redis.lrange('imported_txids_for_' + this._userid, 0, -1);
|
||||||
for (let tx of onchain_txs) {
|
for (let tx of onchain_txs) {
|
||||||
if (tx.type !== 'bitcoind_tx') continue;
|
if (tx.type !== 'bitcoind_tx') continue;
|
||||||
let already_imported = false;
|
let already_imported = false;
|
||||||
@@ -304,6 +304,16 @@ export class User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!already_imported && tx.category === 'receive') {
|
if (!already_imported && tx.category === 'receive') {
|
||||||
|
let locked = await this._redis.get('importing_' + tx.txid);
|
||||||
|
if (locked) {
|
||||||
|
// race condition, someone's already importing this tx
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// locking...
|
||||||
|
await this._redis.set('importing_' + tx.txid, 1);
|
||||||
|
await this._redis.expire('importing_' + tx.txid, 3600);
|
||||||
|
|
||||||
let userBalance = await this.getBalance();
|
let userBalance = await this.getBalance();
|
||||||
userBalance += new BigNumber(tx.amount).multipliedBy(100000000).toNumber();
|
userBalance += new BigNumber(tx.amount).multipliedBy(100000000).toNumber();
|
||||||
await this.saveBalance(userBalance);
|
await this.saveBalance(userBalance);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { User } from '../class/User';
|
|||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
let express = require('express');
|
let express = require('express');
|
||||||
let router = express.Router();
|
let router = express.Router();
|
||||||
|
let logger = require('../utils/logger');
|
||||||
console.log('using config', JSON.stringify(config));
|
console.log('using config', JSON.stringify(config));
|
||||||
|
|
||||||
var Redis = require('ioredis');
|
var Redis = require('ioredis');
|
||||||
@@ -53,6 +54,7 @@ redis.info(function(err, info) {
|
|||||||
// ######################## ROUTES ########################
|
// ######################## ROUTES ########################
|
||||||
|
|
||||||
router.post('/create', async function(req, res) {
|
router.post('/create', async function(req, res) {
|
||||||
|
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);
|
||||||
@@ -62,6 +64,7 @@ router.post('/create', async function(req, res) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post('/auth', async function(req, res) {
|
router.post('/auth', async function(req, res) {
|
||||||
|
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);
|
||||||
@@ -82,6 +85,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]);
|
||||||
let u = new User(redis);
|
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);
|
||||||
@@ -100,6 +104,7 @@ 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);
|
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);
|
||||||
@@ -108,6 +113,7 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
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);
|
||||||
|
const lock_key = 'invoice_paying_for_' + u.getUserId();
|
||||||
|
|
||||||
let userBalance = await u.getBalance();
|
let userBalance = await u.getBalance();
|
||||||
|
|
||||||
@@ -128,6 +134,12 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
let userid_payee = await u.getUseridByPaymentHash(info.payment_hash);
|
let userid_payee = await u.getUseridByPaymentHash(info.payment_hash);
|
||||||
if (!userid_payee) return errorGeneralServerError(res);
|
if (!userid_payee) return errorGeneralServerError(res);
|
||||||
|
|
||||||
|
if (await redis.get(lock_key)) {
|
||||||
|
return errorTryAgainLater(res);
|
||||||
|
}
|
||||||
|
await redis.set(lock_key, 1);
|
||||||
|
await redis.expire(lock_key, 2 * 60);
|
||||||
|
|
||||||
let UserPayee = new User(redis);
|
let UserPayee = new User(redis);
|
||||||
UserPayee._userid = userid_payee; // hacky, fixme
|
UserPayee._userid = userid_payee; // hacky, fixme
|
||||||
let payee_balance = await UserPayee.getBalance();
|
let payee_balance = await UserPayee.getBalance();
|
||||||
@@ -147,6 +159,7 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
|
|
||||||
await UserPayee.setPaymentHashPaid(info.payment_hash);
|
await UserPayee.setPaymentHashPaid(info.payment_hash);
|
||||||
|
|
||||||
|
await redis.del(lock_key, 1);
|
||||||
return res.send(info);
|
return res.send(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,14 +172,31 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
payment.pay_req = req.body.invoice;
|
payment.pay_req = req.body.invoice;
|
||||||
payment.decoded = info;
|
payment.decoded = info;
|
||||||
u.savePaidLndInvoice(payment);
|
u.savePaidLndInvoice(payment);
|
||||||
|
redis.del(lock_key);
|
||||||
res.send(payment);
|
res.send(payment);
|
||||||
} else {
|
} else {
|
||||||
// payment failed
|
// payment failed
|
||||||
|
redis.del(lock_key);
|
||||||
return errorLnd(res);
|
return errorLnd(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (!info.num_satoshis && !info.num_satoshis) {
|
||||||
|
// tip invoice, but someone forgot to specify amount
|
||||||
|
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
|
||||||
call.write(inv);
|
try {
|
||||||
|
if (await redis.get(lock_key)) {
|
||||||
|
return errorTryAgainLater(res);
|
||||||
|
}
|
||||||
|
await redis.set(lock_key, 1);
|
||||||
|
await redis.expire(lock_key, 2 * 60);
|
||||||
|
logger.log('/payinvoice', [req.id, 'before write', JSON.stringify(inv)]);
|
||||||
|
call.write(inv);
|
||||||
|
} catch (Err) {
|
||||||
|
logger.log('/payinvoice', [req.id, 'exception', JSON.stringify(Err)]);
|
||||||
|
return errorLnd(res);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return errorNotEnougBalance(res);
|
return errorNotEnougBalance(res);
|
||||||
}
|
}
|
||||||
@@ -174,6 +204,7 @@ router.post('/payinvoice', async function(req, res) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/getbtc', async function(req, res) {
|
router.get('/getbtc', async function(req, res) {
|
||||||
|
logger.log('/getbtc', [req.id]);
|
||||||
let u = new User(redis, bitcoinclient, lightning);
|
let u = new User(redis, bitcoinclient, lightning);
|
||||||
await u.loadByAuthorization(req.headers.authorization);
|
await u.loadByAuthorization(req.headers.authorization);
|
||||||
|
|
||||||
@@ -191,6 +222,7 @@ router.get('/getbtc', async function(req, res) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/balance', async function(req, res) {
|
router.get('/balance', async function(req, res) {
|
||||||
|
logger.log('/balance', [req.id]);
|
||||||
let u = new User(redis, bitcoinclient, lightning);
|
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);
|
||||||
@@ -203,6 +235,7 @@ router.get('/balance', async function(req, res) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/getinfo', async function(req, res) {
|
router.get('/getinfo', async function(req, res) {
|
||||||
|
logger.log('/getinfo', [req.id]);
|
||||||
let u = new User(redis);
|
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);
|
||||||
@@ -215,6 +248,7 @@ router.get('/getinfo', async function(req, res) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/gettxs', async function(req, res) {
|
router.get('/gettxs', async function(req, res) {
|
||||||
|
logger.log('/gettxs', [req.id]);
|
||||||
let u = new User(redis, bitcoinclient, lightning);
|
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);
|
||||||
@@ -232,16 +266,23 @@ router.get('/gettxs', async function(req, res) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/getuserinvoices', async function(req, res) {
|
router.get('/getuserinvoices', async function(req, res) {
|
||||||
|
logger.log('/getuserinvoices', [req.id]);
|
||||||
let u = new User(redis, bitcoinclient, lightning);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
let invoices = await u.getUserInvoices();
|
try {
|
||||||
res.send(invoices);
|
let invoices = await u.getUserInvoices();
|
||||||
|
res.send(invoices);
|
||||||
|
} catch (Err) {
|
||||||
|
console.log(Err);
|
||||||
|
res.send([]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/getpending', async function(req, res) {
|
router.get('/getpending', async function(req, res) {
|
||||||
|
logger.log('/getpending', [req.id]);
|
||||||
let u = new User(redis, bitcoinclient, lightning);
|
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);
|
||||||
@@ -254,6 +295,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]);
|
||||||
let u = new User(redis, bitcoinclient);
|
let u = new User(redis, bitcoinclient);
|
||||||
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
return errorBadAuth(res);
|
return errorBadAuth(res);
|
||||||
@@ -268,6 +310,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]);
|
||||||
let u = new User(redis, bitcoinclient);
|
let u = new User(redis, bitcoinclient);
|
||||||
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
return errorBadAuth(res);
|
return errorBadAuth(res);
|
||||||
@@ -334,3 +377,11 @@ function errorBadArguments(res) {
|
|||||||
message: 'Bad arguments',
|
message: 'Bad arguments',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function errorTryAgainLater(res) {
|
||||||
|
return res.send({
|
||||||
|
error: true,
|
||||||
|
code: 9,
|
||||||
|
message: 'Try again later',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,39 +3,47 @@ let router = express.Router();
|
|||||||
let fs = require('fs');
|
let fs = require('fs');
|
||||||
let mustache = require('mustache');
|
let mustache = require('mustache');
|
||||||
let lightning = require('../lightning');
|
let lightning = require('../lightning');
|
||||||
|
let logger = require('../utils/logger');
|
||||||
|
|
||||||
let lightningGetInfo = {};
|
let lightningGetInfo = {};
|
||||||
let lightningListChannels = {};
|
let lightningListChannels = {};
|
||||||
function updateLightning() {
|
function updateLightning() {
|
||||||
lightning.getInfo({}, function(err, info) {
|
console.log('updateLightning()');
|
||||||
if (err) {
|
try {
|
||||||
console.error('lnd failure');
|
lightning.getInfo({}, function(err, info) {
|
||||||
process.exit(3);
|
if (err) {
|
||||||
}
|
console.error('lnd failure');
|
||||||
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');
|
||||||
process.exit(3);
|
}
|
||||||
}
|
lightningListChannels = response;
|
||||||
lightningListChannels = response;
|
let channels = [];
|
||||||
let channels = [];
|
for (let channel of lightningListChannels.channels) {
|
||||||
for (let channel of lightningListChannels.channels) {
|
let divider = 524287;
|
||||||
let divider = 524287;
|
let ascii_length1 = channel.local_balance / divider;
|
||||||
let ascii_length1 = channel.local_balance / divider;
|
let ascii_length2 = channel.remote_balance / divider;
|
||||||
let ascii_length2 = channel.remote_balance / divider;
|
channel.ascii = '[';
|
||||||
channel.ascii = '[';
|
channel.ascii += '-'.repeat(Math.round(ascii_length1));
|
||||||
channel.ascii += '-'.repeat(Math.round(ascii_length1));
|
channel.ascii += '/' + '-'.repeat(Math.round(ascii_length2));
|
||||||
channel.ascii += '/' + '-'.repeat(Math.round(ascii_length2));
|
channel.ascii += ']';
|
||||||
channel.ascii += ']';
|
channel.capacity_btc = channel.capacity / 100000000;
|
||||||
channel.capacity_btc = channel.capacity / 100000000;
|
channel.name = pubkey2name[channel.remote_pubkey];
|
||||||
channel.name = pubkey2name[channel.remote_pubkey];
|
if (channel.name) {
|
||||||
channels.push(channel);
|
channels.unshift(channel);
|
||||||
}
|
} else {
|
||||||
lightningListChannels.channels = channels;
|
channels.push(channel);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
lightningListChannels.channels = channels;
|
||||||
|
});
|
||||||
|
} catch (Err) {
|
||||||
|
console.log(Err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateLightning();
|
updateLightning();
|
||||||
setInterval(updateLightning, 60000);
|
setInterval(updateLightning, 60000);
|
||||||
@@ -51,9 +59,11 @@ const pubkey2name = {
|
|||||||
'0279c22ed7a068d10dc1a38ae66d2d6461e269226c60258c021b1ddcdfe4b00bc4': 'ln1.satoshilabs.com',
|
'0279c22ed7a068d10dc1a38ae66d2d6461e269226c60258c021b1ddcdfe4b00bc4': 'ln1.satoshilabs.com',
|
||||||
'02c91d6aa51aa940608b497b6beebcb1aec05be3c47704b682b3889424679ca490': 'lnd-21.LNBIG.com',
|
'02c91d6aa51aa940608b497b6beebcb1aec05be3c47704b682b3889424679ca490': 'lnd-21.LNBIG.com',
|
||||||
'024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca': 'satoshis.place',
|
'024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca': 'satoshis.place',
|
||||||
|
'03c2abfa93eacec04721c019644584424aab2ba4dff3ac9bdab4e9c97007491dda': 'tippin.me',
|
||||||
};
|
};
|
||||||
|
|
||||||
router.get('/', function(req, res) {
|
router.get('/', function(req, res) {
|
||||||
|
logger.log('/', [req.id]);
|
||||||
if (!lightningGetInfo) {
|
if (!lightningGetInfo) {
|
||||||
console.error('lnd failure');
|
console.error('lnd failure');
|
||||||
process.exit(3);
|
process.exit(3);
|
||||||
@@ -64,6 +74,7 @@ router.get('/', function(req, res) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/about', function(req, res) {
|
router.get('/about', function(req, res) {
|
||||||
|
logger.log('/about', [req.id]);
|
||||||
let html = fs.readFileSync('./templates/about.html').toString('utf8');
|
let html = fs.readFileSync('./templates/about.html').toString('utf8');
|
||||||
res.setHeader('Content-Type', 'text/html');
|
res.setHeader('Content-Type', 'text/html');
|
||||||
return res.status(200).send(mustache.render(html, {}));
|
return res.status(200).send(mustache.render(html, {}));
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ User storage schema
|
|||||||
* access_token_for_{userid} = {access_token}
|
* access_token_for_{userid} = {access_token}
|
||||||
* userid_for_{refresh_token} = {userid}
|
* userid_for_{refresh_token} = {userid}
|
||||||
* refresh_token_for_{userid} = {access_token}
|
* refresh_token_for_{userid} = {access_token}
|
||||||
|
* importing_{txid} = 1 `atomic lock when processing topup tx`
|
||||||
|
* invoice_paying_for_{userid} = 1 `lock for when payinvoice is in progress`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
5
index.js
5
index.js
@@ -1,3 +1,8 @@
|
|||||||
|
process.on('uncaughtException', function(err) {
|
||||||
|
console.error(err);
|
||||||
|
console.log('Node NOT Exiting...');
|
||||||
|
});
|
||||||
|
|
||||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||||
let express = require('express');
|
let express = require('express');
|
||||||
let morgan = require('morgan');
|
let morgan = require('morgan');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "LndHub",
|
"name": "LndHub",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><pre class="line">{{ascii}}</pre></td>
|
<td><pre class="line">{{ascii}}</pre></td>
|
||||||
<td><pre class="line">{{capacity_btc}} BTC </pre></td>
|
<td><pre class="line">{{capacity_btc}} BTC </pre></td>
|
||||||
<td><pre class="line"><a href="https://1ml.com/node/{{remote_pubkey}}" target="_blank">{{remote_pubkey}}</a> {{name}} </pre></td>
|
<td><pre class="line"><a href="https://1ml.com/node/{{remote_pubkey}}" target="_blank">{{remote_pubkey}}</a> {{name}} {{^active}}<span class="dyer-orange">[INACTIVE]</span>{{/active}} </pre></td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/channels}}
|
{{/channels}}
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
Reference in New Issue
Block a user