Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
688798024c | ||
|
|
66a54570c5 | ||
|
|
e42ef3a85e | ||
|
|
600556f84c | ||
|
|
a01a8c6e14 | ||
|
|
e9c6f3abde | ||
|
|
7f9463bbbc | ||
|
|
1b1fe8f08b | ||
|
|
28dd3a847a | ||
|
|
a28eead0ab | ||
|
|
87afa20019 | ||
|
|
067f6d6667 | ||
|
|
957d6c5951 | ||
|
|
d3df6aab05 | ||
|
|
4e8ce5e46f | ||
|
|
1fd24b640e | ||
|
|
c47211f984 | ||
|
|
5223edc2b7 | ||
|
|
92e626508c | ||
|
|
3e2c31f429 | ||
|
|
376d61f81d | ||
|
|
1bb781270c | ||
|
|
c0716bc6e9 | ||
|
|
358f858ad8 | ||
|
|
7a6e8a2940 | ||
|
|
96e82ad20d | ||
|
|
406afef7f7 | ||
|
|
3ebe1938ab | ||
|
|
ee2398f247 | ||
|
|
bc84429b61 | ||
|
|
ec3039d91d | ||
|
|
1483449529 | ||
|
|
e0f037ea95 | ||
|
|
b4f880b35d | ||
|
|
35fe816d0a | ||
|
|
4a4daccecd | ||
|
|
66b9d3ab92 | ||
|
|
87fc5d6ff7 | ||
|
|
e6b1f950f3 | ||
|
|
eab49fde2f | ||
|
|
aa11daf608 | ||
|
|
4e7731ff54 | ||
|
|
1c8232d1ec | ||
|
|
fd6caf859c | ||
|
|
df6412a525 | ||
|
|
c46c1385c6 | ||
|
|
dd5d96aa56 | ||
|
|
d6d563039e | ||
|
|
f5eb5d0c78 | ||
|
|
9d17ceb23f | ||
|
|
d0406afd31 | ||
|
|
6411e9bd28 | ||
|
|
db218ef440 | ||
|
|
735b65dc0f | ||
|
|
c73d762028 | ||
|
|
4f3dff6022 | ||
|
|
a792432700 | ||
|
|
590bccd4c0 | ||
|
|
d90881771c | ||
|
|
d9d75a0b29 | ||
|
|
34ffd59c1b | ||
|
|
7dce6b848e | ||
|
|
0d337bdc7b | ||
|
|
f0bf066cfd | ||
|
|
f0ad86f1b9 | ||
|
|
12d1e9560f | ||
|
|
1eaf6c5a47 | ||
|
|
dfcaac2cfa | ||
|
|
f11c02d2f2 | ||
|
|
a4eedde3fb | ||
|
|
597615209b | ||
|
|
5fc7b074f9 | ||
|
|
df9b9a71d9 | ||
|
|
ede2359e8a | ||
|
|
b9a55f01d7 | ||
|
|
1c4eb6d83a | ||
|
|
d4c2771f4a | ||
|
|
f64a8c8102 | ||
|
|
170bf0d120 | ||
|
|
049ae5de5c | ||
|
|
877d7762ff | ||
|
|
e0a0eed038 | ||
|
|
26692d6c92 | ||
|
|
68fd866894 | ||
|
|
d7216e1506 | ||
|
|
30c8bb2d94 | ||
|
|
17e9bd30c8 | ||
|
|
d47f03501d | ||
|
|
3d1abda05b | ||
|
|
de64f1c55a | ||
|
|
bcf87de2b4 | ||
|
|
ad1b1d3da4 | ||
|
|
a6f354600c | ||
|
|
647632c914 |
9
.github/workflows/push.yml
vendored
9
.github/workflows/push.yml
vendored
@@ -12,12 +12,7 @@ jobs:
|
||||
build:
|
||||
name: Build image
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@v2
|
||||
@@ -49,6 +44,6 @@ jobs:
|
||||
docker buildx build \
|
||||
--cache-from "type=local,src=/tmp/.buildx-cache" \
|
||||
--cache-to "type=local,dest=/tmp/.buildx-cache" \
|
||||
--platform ${{matrix.platform}} \
|
||||
--platform linux/arm64,linux/amd64 \
|
||||
--tag ${{ secrets.DOCKER_CONTAINER_USERNAME }}/lndhub:$BRANCH \
|
||||
--output "type=registry" ./
|
||||
11
.github/workflows/tag.yml
vendored
11
.github/workflows/tag.yml
vendored
@@ -13,12 +13,7 @@ jobs:
|
||||
build:
|
||||
name: Build image
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@v2
|
||||
@@ -49,7 +44,7 @@ jobs:
|
||||
docker buildx build \
|
||||
--cache-from "type=local,src=/tmp/.buildx-cache" \
|
||||
--cache-to "type=local,dest=/tmp/.buildx-cache" \
|
||||
--platform ${{matrix.platform}} \
|
||||
--platform linux/arm64,linux/amd64 \
|
||||
--tag ${{ secrets.DOCKER_CONTAINER_USERNAME }}/lndhub:$TAG \
|
||||
--output "type=registry" ./
|
||||
- name: Run Docker buildx
|
||||
@@ -57,6 +52,6 @@ jobs:
|
||||
docker buildx build \
|
||||
--cache-from "type=local,src=/tmp/.buildx-cache" \
|
||||
--cache-to "type=local,dest=/tmp/.buildx-cache" \
|
||||
--platform ${{matrix.platform}} \
|
||||
--platform linux/arm64,linux/amd64 \
|
||||
--tag ${{ secrets.DOCKER_CONTAINER_USERNAME }}/lndhub:latest \
|
||||
--output "type=registry" ./
|
||||
|
||||
26
README.md
26
README.md
@@ -1,7 +1,7 @@
|
||||
LndHub
|
||||
======
|
||||
|
||||
Wrapper for Lightning Network Daemon. It provides separate accounts with minimum trust for end users
|
||||
Wrapper for Lightning Network Daemon (lnd). It provides separate accounts with minimum trust for end users.
|
||||
|
||||
INSTALLATION
|
||||
------------
|
||||
@@ -17,12 +17,17 @@ cd LndHub
|
||||
npm i
|
||||
```
|
||||
|
||||
Install `bitcoind`, `lnd` and `redis`. Edit `config.js` and set it up correctly.
|
||||
Copy `admin.macaroon` and `tls.cert` in root folder of LndHub.
|
||||
Install `bitcoind`, `lnd`, and `redis`. Edit LndHub's `config.js` to set it up correctly.
|
||||
Copy the files `admin.macaroon` (for Bitcoin mainnet, usually stored in `~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon`)
|
||||
and `tls.cert` (usually stored in `~/.lnd/tls.cert`) into the 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`.
|
||||
LndHub expects LND's wallet to be unlocked, if not — it will attempt to unlock it with the password stored in `config.lnd.password`.
|
||||
Don't forget to configure disk-persistence for `redis` (e.g., you may want to set `appendonly` to `yes` in `redis.conf` (see
|
||||
http://redis.io/topics/persistence for more information).
|
||||
|
||||
If you have no `bitcoind` instance, for example if you use neutrino, or you have no bitcoind wallet,
|
||||
for example if you use LND for wallet managment, you can remove the bitcoind settings from `config.js`.
|
||||
Please note that this feature is limited to Bitcoin, so you can't use it if you use any other cryptocurrency with LND (e.g., Litecoin).
|
||||
|
||||
### Deploy to Heroku
|
||||
|
||||
@@ -31,11 +36,18 @@ Add config vars :
|
||||
* `MACAROON`: hex-encoded `admin.macaroon`
|
||||
* `TLSCERT`: hex-encoded `tls.cert`
|
||||
|
||||
### Run in docker
|
||||
|
||||
LndHub is available on Docker Hub as [`bluewalletorganization/lndhub`](https://hub.docker.com/r/bluewalletorganization/lndhub).
|
||||
Please note that this requires a separate instance of redis and LND and optionally, bitcoind.
|
||||
You can also view Umbrel's implementation using docker-compose [here](https://github.com/getumbrel/umbrel/blob/280c87f0f323666b1b0552aeb24f60df94d1e43c/apps/lndhub/docker-compose.yml).
|
||||
|
||||
### Reference client implementation
|
||||
|
||||
Can be used in ReactNative or Nodejs environment
|
||||
|
||||
* https://github.com/BlueWallet/BlueWallet/blob/master/class/lightning-custodian-wallet.js
|
||||
* https://github.com/BlueWallet/BlueWallet/blob/master/class/wallets/lightning-custodian-wallet.js
|
||||
|
||||
|
||||
|
||||
### Tests
|
||||
@@ -48,5 +60,5 @@ Acceptance tests are in https://github.com/BlueWallet/BlueWallet/blob/master/Lig
|
||||
|
||||
## Responsible disclosure
|
||||
|
||||
Found critical bugs/vulnerabilities? Please email them bluewallet@bluewallet.io
|
||||
Found critical bugs/vulnerabilities? Please email them to bluewallet@bluewallet.io
|
||||
Thanks!
|
||||
|
||||
@@ -12,10 +12,6 @@ export class Paym {
|
||||
this._isPaid = null;
|
||||
}
|
||||
|
||||
static get fee() {
|
||||
return 0.003;
|
||||
}
|
||||
|
||||
setInvoice(bolt11) {
|
||||
this._bolt11 = bolt11;
|
||||
}
|
||||
@@ -39,7 +35,7 @@ export class Paym {
|
||||
pub_key: this._decoded.destination,
|
||||
amt: this._decoded.num_satoshis,
|
||||
final_cltv_delta: 144,
|
||||
fee_limit: { fixed: Math.floor(this._decoded.num_satoshis * 0.01) + 1 },
|
||||
fee_limit: { fixed: Math.floor(this._decoded.num_satoshis * forwardFee) + 1 },
|
||||
};
|
||||
let that = this;
|
||||
return new Promise(function (resolve, reject) {
|
||||
@@ -74,7 +70,7 @@ export class Paym {
|
||||
if (payment && payment.payment_route && payment.payment_route.total_amt_msat) {
|
||||
// paid just now
|
||||
this._isPaid = true;
|
||||
payment.payment_route.total_fees = +payment.payment_route.total_fees + Math.floor(+payment.payment_route.total_amt * Paym.fee);
|
||||
payment.payment_route.total_fees = +payment.payment_route.total_fees + Math.floor(+payment.payment_route.total_amt * internalFee);
|
||||
if (this._bolt11) payment.pay_req = this._bolt11;
|
||||
if (this._decoded) payment.decoded = this._decoded;
|
||||
}
|
||||
@@ -87,7 +83,7 @@ export class Paym {
|
||||
if (this._bolt11) payment.pay_req = this._bolt11;
|
||||
// trying to guess the fee
|
||||
payment.payment_route = payment.payment_route || {};
|
||||
payment.payment_route.total_fees = Math.floor(this._decoded.num_satoshis * 0.01); // we dont know the exact fee, so we use max (same as fee_limit)
|
||||
payment.payment_route.total_fees = Math.floor(this._decoded.num_satoshis * forwardFee); // we dont know the exact fee, so we use max (same as fee_limit)
|
||||
payment.payment_route.total_amt = this._decoded.num_satoshis;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ export class User {
|
||||
let lockedPayments = await this.getLockedPayments();
|
||||
for (let paym of lockedPayments) {
|
||||
// locked payments are processed in scripts/process-locked-payments.js
|
||||
calculatedBalance -= +paym.amount + /* feelimit */ Math.floor(paym.amount * 0.01);
|
||||
calculatedBalance -= +paym.amount + /* feelimit */ Math.floor(paym.amount * forwardFee);
|
||||
}
|
||||
|
||||
return calculatedBalance;
|
||||
@@ -412,7 +412,7 @@ export class User {
|
||||
amount: tx.amount,
|
||||
confirmations: tx.confirmations,
|
||||
address: tx.address,
|
||||
time: tx.time,
|
||||
time: tx.blocktime || tx.time,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
let config = {
|
||||
enableUpdateDescribeGraph: false,
|
||||
postRateLimit: 100,
|
||||
rateLimit: 200,
|
||||
forwardReserveFee: 0.01, // default 0.01
|
||||
intraHubFee: 0.003, // default 0.003
|
||||
bitcoind: {
|
||||
rpc: 'http://login:password@1.1.1.1:8332/wallet/wallet.dat',
|
||||
},
|
||||
|
||||
@@ -15,6 +15,12 @@ redis.monitor(function (err, monitor) {
|
||||
});
|
||||
});
|
||||
|
||||
/****** START SET FEES FROM CONFIG AT STARTUP ******/
|
||||
/** GLOBALS */
|
||||
global.forwardFee = config.forwardReserveFee || 0.01;
|
||||
global.internalFee = config.intraHubFee || 0.003;
|
||||
/****** END SET FEES FROM CONFIG AT STARTUP ******/
|
||||
|
||||
let bitcoinclient = require('../bitcoin');
|
||||
let lightning = require('../lightning');
|
||||
let identity_pubkey = false;
|
||||
@@ -27,6 +33,7 @@ if (config.bitcoind) {
|
||||
console.error('bitcoind is not caught up');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('bitcoind getblockchaininfo:', info);
|
||||
} else {
|
||||
console.error('bitcoind failure:', err, info);
|
||||
process.exit(2);
|
||||
@@ -41,7 +48,7 @@ lightning.getInfo({}, function (err, info) {
|
||||
process.exit(3);
|
||||
}
|
||||
if (info) {
|
||||
console.info(info);
|
||||
console.info('lnd getinfo:', info);
|
||||
if (!info.synced_to_chain && !config.forceStart) {
|
||||
console.error('lnd not synced');
|
||||
// process.exit(4);
|
||||
@@ -109,17 +116,36 @@ subscribeInvoicesCall.on('end', function () {
|
||||
// The server has closed the stream.
|
||||
});
|
||||
|
||||
let lightningDescribeGraph = {};
|
||||
function updateDescribeGraph() {
|
||||
console.log('updateDescribeGraph()');
|
||||
lightning.describeGraph({ include_unannounced: true }, function (err, response) {
|
||||
if (!err) lightningDescribeGraph = response;
|
||||
console.log('updated graph');
|
||||
});
|
||||
}
|
||||
if (config.enableUpdateDescribeGraph) {
|
||||
updateDescribeGraph();
|
||||
setInterval(updateDescribeGraph, 120000);
|
||||
}
|
||||
|
||||
// ######################## ROUTES ########################
|
||||
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const postLimiter = rateLimit({
|
||||
windowMs: 30 * 60 * 1000,
|
||||
max: 100,
|
||||
max: config.postRateLimit || 100,
|
||||
});
|
||||
|
||||
router.post('/create', postLimiter, async function (req, res) {
|
||||
logger.log('/create', [req.id]);
|
||||
if (!(req.body.partnerid && req.body.partnerid === 'bluewallet' && req.body.accounttype)) return errorBadArguments(res);
|
||||
// Valid if the partnerid isn't there or is a string (same with accounttype)
|
||||
if (! (
|
||||
(!req.body.partnerid || (typeof req.body.partnerid === 'string' || req.body.partnerid instanceof String))
|
||||
&& (!req.body.accounttype || (typeof req.body.accounttype === 'string' || req.body.accounttype instanceof String))
|
||||
) ) return errorBadArguments(res);
|
||||
|
||||
if (config.sunset) return errorSunset(res);
|
||||
|
||||
let u = new User(redis, bitcoinclient, lightning);
|
||||
await u.create();
|
||||
@@ -158,6 +184,8 @@ router.post('/addinvoice', postLimiter, async function (req, res) {
|
||||
|
||||
if (!req.body.amt || /*stupid NaN*/ !(req.body.amt > 0)) return errorBadArguments(res);
|
||||
|
||||
if (config.sunset) return errorSunsetAddInvoice(res);
|
||||
|
||||
const invoice = new Invo(redis, bitcoinclient, lightning);
|
||||
const r_preimage = invoice.makePreimageHex();
|
||||
lightning.addInvoice(
|
||||
@@ -217,7 +245,7 @@ router.post('/payinvoice', async function (req, res) {
|
||||
|
||||
logger.log('/payinvoice', [req.id, 'userBalance: ' + userBalance, 'num_satoshis: ' + info.num_satoshis]);
|
||||
|
||||
if (userBalance >= +info.num_satoshis + Math.floor(info.num_satoshis * 0.01)) {
|
||||
if (userBalance >= +info.num_satoshis + Math.floor(info.num_satoshis * forwardFee)) {
|
||||
// got enough balance, including 1% of payment amount - reserve for fees
|
||||
|
||||
if (identity_pubkey === info.destination) {
|
||||
@@ -244,8 +272,8 @@ router.post('/payinvoice', async function (req, res) {
|
||||
await u.savePaidLndInvoice({
|
||||
timestamp: parseInt(+new Date() / 1000),
|
||||
type: 'paid_invoice',
|
||||
value: +info.num_satoshis + Math.floor(info.num_satoshis * Paym.fee),
|
||||
fee: Math.floor(info.num_satoshis * Paym.fee),
|
||||
value: +info.num_satoshis + Math.floor(info.num_satoshis * internalFee),
|
||||
fee: Math.floor(info.num_satoshis * internalFee),
|
||||
memo: decodeURIComponent(info.description),
|
||||
pay_req: req.body.invoice,
|
||||
});
|
||||
@@ -298,7 +326,7 @@ router.post('/payinvoice', async function (req, res) {
|
||||
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) + 1 },
|
||||
fee_limit: { fixed: Math.floor(info.num_satoshis * forwardFee) + 1 },
|
||||
};
|
||||
try {
|
||||
await u.lockFunds(req.body.invoice, info);
|
||||
@@ -323,6 +351,8 @@ router.get('/getbtc', async function (req, res) {
|
||||
return errorBadAuth(res);
|
||||
}
|
||||
|
||||
if (config.sunset) return errorSunsetAddInvoice(res);
|
||||
|
||||
let address = await u.getAddress();
|
||||
if (!address) {
|
||||
await u.generateAddress();
|
||||
@@ -399,8 +429,8 @@ router.get('/gettxs', async function (req, res) {
|
||||
for (let locked of lockedPayments) {
|
||||
txs.push({
|
||||
type: 'paid_invoice',
|
||||
fee: Math.floor(locked.amount * 0.01) /* feelimit */,
|
||||
value: locked.amount + Math.floor(locked.amount * 0.01) /* feelimit */,
|
||||
fee: Math.floor(locked.amount * forwardFee) /* feelimit */,
|
||||
value: locked.amount + Math.floor(locked.amount * forwardFee) /* feelimit */,
|
||||
timestamp: locked.timestamp,
|
||||
memo: 'Payment in transition',
|
||||
});
|
||||
@@ -480,6 +510,7 @@ router.get('/queryroutes/:source/:dest/:amt', async function (req, res) {
|
||||
|
||||
let request = {
|
||||
pub_key: req.params.dest,
|
||||
use_mission_control: true,
|
||||
amt: req.params.amt,
|
||||
source_pub_key: req.params.source,
|
||||
};
|
||||
@@ -489,6 +520,19 @@ router.get('/queryroutes/:source/:dest/:amt', async function (req, res) {
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/getchaninfo/:chanid', async function (req, res) {
|
||||
logger.log('/getchaninfo', [req.id]);
|
||||
|
||||
if (lightningDescribeGraph && lightningDescribeGraph.edges) {
|
||||
for (const edge of lightningDescribeGraph.edges) {
|
||||
if (edge.channel_id == req.params.chanid) {
|
||||
return res.send(JSON.stringify(edge, null, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
res.send('');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
// ################# HELPERS ###########################
|
||||
@@ -556,3 +600,19 @@ function errorPaymentFailed(res) {
|
||||
message: 'Payment failed. Does the receiver have enough inbound capacity?',
|
||||
});
|
||||
}
|
||||
|
||||
function errorSunset(res) {
|
||||
return res.send({
|
||||
error: true,
|
||||
code: 11,
|
||||
message: 'This LNDHub instance is not accepting any more users',
|
||||
});
|
||||
}
|
||||
|
||||
function errorSunsetAddInvoice(res) {
|
||||
return res.send({
|
||||
error: true,
|
||||
code: 11,
|
||||
message: 'This LNDHub instance is scheduled to shut down. Withdraw any remaining funds',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ function updateLightning() {
|
||||
lightning.getInfo({}, function (err, info) {
|
||||
if (err) {
|
||||
console.error('lnd failure:', err);
|
||||
process.exit(4);
|
||||
return;
|
||||
}
|
||||
lightningGetInfo = info;
|
||||
});
|
||||
@@ -21,8 +23,10 @@ function updateLightning() {
|
||||
lightning.listChannels({}, function (err, response) {
|
||||
if (err) {
|
||||
console.error('lnd failure:', err);
|
||||
process.exit(4);
|
||||
return;
|
||||
}
|
||||
console.log('updated');
|
||||
lightningListChannels = response;
|
||||
let channels = [];
|
||||
let max_chan_capacity = -1;
|
||||
@@ -47,7 +51,6 @@ function updateLightning() {
|
||||
} catch (Err) {
|
||||
console.log(Err);
|
||||
}
|
||||
console.log('updated');
|
||||
}
|
||||
updateLightning();
|
||||
setInterval(updateLightning, 60000);
|
||||
@@ -59,6 +62,7 @@ const pubkey2name = {
|
||||
'030c3f19d742ca294a55c00376b3b355c3c90d61c6b6b39554dbc7ac19b141c14f': 'bitrefill.com',
|
||||
'03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f': 'ACINQ',
|
||||
'03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e': 'OpenNode',
|
||||
'028d98b9969fbed53784a36617eb489a59ab6dc9b9d77fcdca9ff55307cd98e3c4': 'OpenNode 2',
|
||||
'0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3': 'coingate.com',
|
||||
'0279c22ed7a068d10dc1a38ae66d2d6461e269226c60258c021b1ddcdfe4b00bc4': 'ln1.satoshilabs.com',
|
||||
'02c91d6aa51aa940608b497b6beebcb1aec05be3c47704b682b3889424679ca490': 'lnd-21.LNBIG.com',
|
||||
@@ -69,6 +73,7 @@ const pubkey2name = {
|
||||
'026c7d28784791a4b31a64eb34d9ab01552055b795919165e6ae886de637632efb': 'LivingRoomOfSatoshi',
|
||||
'02816caed43171d3c9854e3b0ab2cf0c42be086ff1bd4005acc2a5f7db70d83774': 'ln.pizza',
|
||||
'0254ff808f53b2f8c45e74b70430f336c6c76ba2f4af289f48d6086ae6e60462d3': 'bitrefill thor',
|
||||
'03d607f3e69fd032524a867b288216bfab263b6eaee4e07783799a6fe69bb84fac': 'bitrefill 3',
|
||||
'02a0bc43557fae6af7be8e3a29fdebda819e439bea9c0f8eb8ed6a0201f3471ca9': 'LightningPeachHub',
|
||||
'02d4531a2f2e6e5a9033d37d548cff4834a3898e74c3abe1985b493c42ebbd707d': 'coinfinity.co',
|
||||
'02d23fa6794d8fd056c757f3c8f4877782138dafffedc831fc570cab572620dc61': 'paywithmoon.com',
|
||||
@@ -78,6 +83,10 @@ const pubkey2name = {
|
||||
'033d8656219478701227199cbd6f670335c8d408a92ae88b962c49d4dc0e83e025': 'bfx-lnd0',
|
||||
'03021c5f5f57322740e4ee6936452add19dc7ea7ccf90635f95119ab82a62ae268': 'lnd1.bluewallet.io',
|
||||
'037cc5f9f1da20ac0d60e83989729a204a33cc2d8e80438969fadf35c1c5f1233b': 'lnd2.bluewallet.io',
|
||||
'036b53093df5a932deac828cca6d663472dbc88322b05eec1d42b26ab9b16caa1c': 'okcoin',
|
||||
'038f8f113c580048d847d6949371726653e02b928196bad310e3eda39ff61723f6': 'magnetron',
|
||||
'03829249ef39746fd534a196510232df08b83db0967804ec71bf4120930864ff97': 'blokada.org',
|
||||
'02ce691b2e321954644514db708ba2a72769a6f9142ac63e65dd87964e9cf2add9': 'Satoshis.Games',
|
||||
};
|
||||
|
||||
router.get('/', function (req, res) {
|
||||
@@ -96,7 +105,8 @@ router.get('/qr', function (req, res) {
|
||||
if (process.env.TOR_URL) {
|
||||
host = process.env.TOR_URL;
|
||||
}
|
||||
const url = 'bluewallet:setlndhuburl?url=' + encodeURIComponent(req.protocol + '://' + host);
|
||||
const customPath = req.url.replace('/qr', '');
|
||||
const url = 'bluewallet:setlndhuburl?url=' + encodeURIComponent(req.protocol + '://' + host + customPath);
|
||||
var code = qr.image(url, { type: 'png' });
|
||||
res.setHeader('Content-type', 'image/png');
|
||||
code.pipe(res);
|
||||
|
||||
@@ -512,25 +512,3 @@ Oauth2 process consists of such stages as:
|
||||
- Token service checks user id and secret and sends token data with refresh token to Authorization service which sends it to Client
|
||||
- Client uses token to access protected resources (GET ?access_token=XXXXXXXXXXXXXX)
|
||||
- When token expires or needs to refresh token for security issues Client sends refresh_token to Token service (POST /auth?type=refresh_token), which sends new token data with refresh_token and disables to access old
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```
|
||||
Добавил POST метод /addinvoice для того, чтобы пользователь мог создать свой инвойс
|
||||
в боди передаются параметы:
|
||||
{
|
||||
"amt": "string" обязательный
|
||||
"memo":"string" не обязательный
|
||||
"receipt":"string" не обязательный
|
||||
"preimage": "string" не обязательный
|
||||
"fallbackAddr": "string" не обязательны
|
||||
"expiry": "string" не обязательны
|
||||
"private": "string" не обязательны
|
||||
}
|
||||
|
||||
информация по инвойсам, которые оплатили пользователям досутпна через метод GET /getuserinvoices , полученные коины учитываются в балансе (settled в unconfirmed balance, остальной в confirmed balance)
|
||||
|
||||
```
|
||||
|
||||
@@ -28,7 +28,7 @@ User storage schema
|
||||
* metadata_for_{userid}= {serialized json}
|
||||
* userinvoices_for_{userid} = []
|
||||
* payment_hash_{payment_hash} = {userid}
|
||||
* ispaid_{payment_hash} = 1
|
||||
* ispaid_{payment_hash} = {settleAmountSat}
|
||||
|
||||
|
||||
####cleanup test user
|
||||
@@ -36,4 +36,5 @@ User storage schema
|
||||
* del locked_payments_for_666
|
||||
* del txs_for_666
|
||||
* del invoice_paying_for_666
|
||||
* del userinvoices_for_666
|
||||
* del userinvoices_for_666
|
||||
* del balance_for_666
|
||||
14
index.js
14
index.js
@@ -5,9 +5,11 @@ process.on('uncaughtException', function (err) {
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
let express = require('express');
|
||||
const helmet = require('helmet');
|
||||
let morgan = require('morgan');
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
let logger = require('./utils/logger');
|
||||
const config = require('./config');
|
||||
|
||||
morgan.token('id', function getId(req) {
|
||||
return req.id;
|
||||
@@ -15,11 +17,13 @@ morgan.token('id', function getId(req) {
|
||||
|
||||
let app = express();
|
||||
app.enable('trust proxy');
|
||||
app.use(helmet.hsts());
|
||||
app.use(helmet.hidePoweredBy());
|
||||
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 200,
|
||||
max: config.rateLimit || 200,
|
||||
});
|
||||
app.use(limiter);
|
||||
|
||||
@@ -35,7 +39,6 @@ app.use(
|
||||
);
|
||||
|
||||
let bodyParser = require('body-parser');
|
||||
let config = require('./config');
|
||||
|
||||
app.use(bodyParser.urlencoded({ extended: false })); // parse application/x-www-form-urlencoded
|
||||
app.use(bodyParser.json(null)); // parse application/json
|
||||
@@ -44,7 +47,10 @@ app.use('/static', express.static('static'));
|
||||
app.use(require('./controllers/api'));
|
||||
app.use(require('./controllers/website'));
|
||||
|
||||
let server = app.listen(process.env.PORT || 3000, function () {
|
||||
logger.log('BOOTING UP', 'Listening on port ' + (process.env.PORT || 3000));
|
||||
const bindHost = process.env.HOST || '0.0.0.0';
|
||||
const bindPort = process.env.PORT || 3000;
|
||||
|
||||
let server = app.listen(bindPort, bindHost, function () {
|
||||
logger.log('BOOTING UP', 'Listening on ' + bindHost + ':' + bindPort);
|
||||
});
|
||||
module.exports = server;
|
||||
|
||||
9668
package-lock.json
generated
9668
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lndhub",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -12,29 +12,29 @@
|
||||
"author": "Igor Korsakov <overtorment@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/cli": "^7.13.0",
|
||||
"@babel/core": "^7.13.1",
|
||||
"@babel/eslint-parser": "^7.13.4",
|
||||
"@babel/node": "^7.13.0",
|
||||
"@babel/preset-env": "^7.13.5",
|
||||
"@babel/register": "^7.13.0",
|
||||
"@grpc/grpc-js": "^1.2.8",
|
||||
"@grpc/proto-loader": "^0.5.6",
|
||||
"@babel/cli": "^7.14.8",
|
||||
"@babel/core": "^7.15.0",
|
||||
"@babel/eslint-parser": "^7.14.2",
|
||||
"@babel/node": "^7.14.9",
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@babel/register": "^7.14.5",
|
||||
"@grpc/grpc-js": "^1.3.7",
|
||||
"@grpc/proto-loader": "^0.6.4",
|
||||
"bignumber.js": "^9.0.1",
|
||||
"bitcoinjs-lib": "^5.2.0",
|
||||
"bolt11": "^1.2.7",
|
||||
"core-js": "^3.9.0",
|
||||
"eslint": "^7.20.0",
|
||||
"eslint-config-prettier": "^8.0.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"bolt11": "^1.3.2",
|
||||
"eslint": "^7.24.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"express": "^4.17.1",
|
||||
"express-rate-limit": "^5.2.6",
|
||||
"express-rate-limit": "^5.3.0",
|
||||
"frisbee": "^3.1.4",
|
||||
"ioredis": "^4.22.0",
|
||||
"jayson": "^3.4.4",
|
||||
"helmet": "^4.6.0",
|
||||
"ioredis": "^4.27.8",
|
||||
"jayson": "^3.6.4",
|
||||
"morgan": "^1.10.0",
|
||||
"mustache": "^4.1.0",
|
||||
"prettier": "^2.2.1",
|
||||
"prettier": "^2.3.0",
|
||||
"qr-image": "3.2.0",
|
||||
"request": "^2.88.2",
|
||||
"request-promise": "^4.2.6",
|
||||
|
||||
@@ -9,18 +9,28 @@ const important_channels = {
|
||||
uri: '03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e@18.221.23.28:9735',
|
||||
wumbo: 1,
|
||||
},
|
||||
'0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3': {
|
||||
name: 'coingate.com',
|
||||
uri: '0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3@3.124.63.44:9735',
|
||||
'028d98b9969fbed53784a36617eb489a59ab6dc9b9d77fcdca9ff55307cd98e3c4': {
|
||||
name: 'OpenNode 2',
|
||||
uri: '028d98b9969fbed53784a36617eb489a59ab6dc9b9d77fcdca9ff55307cd98e3c4@18.222.70.85:9735',
|
||||
wumbo: 1,
|
||||
},
|
||||
// '0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3': {
|
||||
// name: 'coingate.com',
|
||||
// uri: '0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3@3.124.63.44:9735',
|
||||
// },
|
||||
// '0254ff808f53b2f8c45e74b70430f336c6c76ba2f4af289f48d6086ae6e60462d3': {
|
||||
// name: 'bitrefill thor',
|
||||
// uri: '0254ff808f53b2f8c45e74b70430f336c6c76ba2f4af289f48d6086ae6e60462d3@52.30.63.2:9735',
|
||||
// wumbo: 1,
|
||||
// },
|
||||
'030c3f19d742ca294a55c00376b3b355c3c90d61c6b6b39554dbc7ac19b141c14f': {
|
||||
name: 'bitrefill 2',
|
||||
uri: '030c3f19d742ca294a55c00376b3b355c3c90d61c6b6b39554dbc7ac19b141c14f@52.50.244.44:9735',
|
||||
// '030c3f19d742ca294a55c00376b3b355c3c90d61c6b6b39554dbc7ac19b141c14f': {
|
||||
// name: 'bitrefill 2',
|
||||
// uri: '030c3f19d742ca294a55c00376b3b355c3c90d61c6b6b39554dbc7ac19b141c14f@52.50.244.44:9735',
|
||||
// wumbo: 1,
|
||||
// },
|
||||
'03d607f3e69fd032524a867b288216bfab263b6eaee4e07783799a6fe69bb84fac': {
|
||||
name: 'bitrefill 3',
|
||||
uri: '03d607f3e69fd032524a867b288216bfab263b6eaee4e07783799a6fe69bb84fac@3.237.23.179:9735',
|
||||
wumbo: 1,
|
||||
},
|
||||
// '025f1456582e70c4c06b61d5c8ed3ce229e6d0db538be337a2dc6d163b0ebc05a5': {
|
||||
@@ -40,6 +50,11 @@ const important_channels = {
|
||||
uri: '02816caed43171d3c9854e3b0ab2cf0c42be086ff1bd4005acc2a5f7db70d83774@35.238.153.25:9735',
|
||||
wumbo: 1,
|
||||
},
|
||||
'036b53093df5a932deac828cca6d663472dbc88322b05eec1d42b26ab9b16caa1c': {
|
||||
name: 'okcoin',
|
||||
uri: '036b53093df5a932deac828cca6d663472dbc88322b05eec1d42b26ab9b16caa1c@47.243.25.4:26658',
|
||||
wumbo: 1,
|
||||
},
|
||||
// '0331f80652fb840239df8dc99205792bba2e559a05469915804c08420230e23c7c': {
|
||||
// name: 'LightningPowerUsers.com',
|
||||
// uri: '0331f80652fb840239df8dc99205792bba2e559a05469915804c08420230e23c7c@34.200.181.109:9735',
|
||||
@@ -52,10 +67,10 @@ const important_channels = {
|
||||
// name: 'fixedfloat.com',
|
||||
// uri: '037f990e61acee8a7697966afd29dd88f3b1f8a7b14d625c4f8742bd952003a590@185.5.53.91:9735',
|
||||
// },
|
||||
'03c2abfa93eacec04721c019644584424aab2ba4dff3ac9bdab4e9c97007491dda': {
|
||||
name: 'tippin.me',
|
||||
uri: '03c2abfa93eacec04721c019644584424aab2ba4dff3ac9bdab4e9c97007491dda@157.245.68.47:9735',
|
||||
},
|
||||
// '03c2abfa93eacec04721c019644584424aab2ba4dff3ac9bdab4e9c97007491dda': {
|
||||
// name: 'tippin.me',
|
||||
// uri: '03c2abfa93eacec04721c019644584424aab2ba4dff3ac9bdab4e9c97007491dda@157.245.68.47:9735',
|
||||
// },
|
||||
};
|
||||
|
||||
let lightning = require('../lightning');
|
||||
@@ -68,7 +83,7 @@ lightning.listChannels({}, function (err, response) {
|
||||
}
|
||||
let lightningListChannels = response;
|
||||
for (let channel of lightningListChannels.channels) {
|
||||
if (channel.capacity < 0.05 / 100000000) {
|
||||
if (channel.capacity <= 1000000) {
|
||||
console.log(
|
||||
'lncli closechannel',
|
||||
channel.channel_point.replace(':', ' '),
|
||||
@@ -143,7 +158,7 @@ lightning.listChannels({}, function (err, response) {
|
||||
'--connect',
|
||||
important_channels[important].uri.split('@')[1],
|
||||
'--local_amt',
|
||||
important_channels[important].wumbo ? '50000000' : '16777215',
|
||||
important_channels[important].wumbo ? '100000000' : '16777215',
|
||||
'--remote_csv_delay 144',
|
||||
'--sat_per_byte 10',
|
||||
'#',
|
||||
|
||||
25
scripts/migrate_addresses_to_other_bitcoind.sh
Normal file
25
scripts/migrate_addresses_to_other_bitcoind.sh
Normal file
@@ -0,0 +1,25 @@
|
||||
# this script should be used if youre retiring one bitcoind in favor of new one
|
||||
# it exports all addresses from the old one and prepares script to import them on a new node
|
||||
#
|
||||
echo export 1...
|
||||
./bitcoin-0.21.0/bin/bitcoin-cli -rpcwallet="" -rpcconnect=1.1.1.1 -rpcuser=user -rpcpassword=oldPassword listreceivedbyaddress 0 true true > addresses.txt
|
||||
echo export 2...
|
||||
./bitcoin-0.21.0/bin/bitcoin-cli -rpcwallet="wallet.dat" -rpcconnect=1.1.1.1 -rpcuser=user -rpcpassword=oldPassword listreceivedbyaddress 0 true true >> addresses.txt
|
||||
|
||||
echo clean...
|
||||
cat addresses.txt | grep address | sort -u | awk '{print $2}' | sed 's/"//g' | sed 's/,//g' > addresses_clean.txt
|
||||
|
||||
echo "got addresses:"
|
||||
wc -l < addresses_clean.txt
|
||||
|
||||
|
||||
echo writing import_on_other_node.sh ...
|
||||
>import_on_other_node.sh
|
||||
chmod +x import_on_other_node.sh
|
||||
|
||||
while read in; do
|
||||
echo "./bitcoin-0.21.0/bin/bitcoin-cli -rpcconnect=2.2.2.2 -rpcuser=user -rpcpassword=newPassword importaddress $in $in false" >> import_on_other_node.sh
|
||||
done < addresses_clean.txt
|
||||
|
||||
echo 'done. dont forget to run ./import_on_other_node.sh and then ./bitcoin-0.21.0/bin/bitcoin-cli -rpcconnect=2.2.2.2 -rpcwallet="wallet.dat" -rpcuser=user -rpcpassword=newPassword rescanblockchain 459491'
|
||||
|
||||
@@ -50,9 +50,10 @@ let lightning = require('../lightning');
|
||||
|
||||
let locked = await U.getLockedPayments();
|
||||
for (let loc of locked) {
|
||||
console.log('-', loc.amount + /* fee limit */ Math.floor(loc.amount * 0.01), new Date(loc.timestamp * 1000).toString(), '[locked]');
|
||||
console.log('-', loc.amount + /* fee limit */ Math.floor(loc.amount * config.forwardReserveFee), new Date(loc.timestamp * 1000).toString(), '[locked]');
|
||||
}
|
||||
|
||||
console.log('\ncalculatedBalance\n================\n', calculatedBalance, await U.getCalculatedBalance());
|
||||
console.log('txs:', txs.length, 'userinvoices:', userinvoices.length);
|
||||
process.exit();
|
||||
})();
|
||||
|
||||
@@ -112,8 +112,10 @@ body {
|
||||
appearance: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-width : 100%;
|
||||
max-width: 100%;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#progressbar[max]::-webkit-progress-value {
|
||||
border-radius: 8px 4px 4px 8px;
|
||||
|
||||
Reference in New Issue
Block a user