Compare commits

..

1 Commits

Author SHA1 Message Date
Marcos Rodriguez Vélez
deefc3a112 FIX: API Error Wording 2021-07-14 18:35:11 +01:00
16 changed files with 3042 additions and 5201 deletions

View File

@ -1,15 +0,0 @@
*
!build/
!class/
!controllers/
!scripts/
!static/
!templates/
!utils/
!*.js
!.babelrc
!.eslint*
!admin.macaroon
!package*.json
!rpc.proto
!tls.cert

1
.gitignore vendored
View File

@ -2,7 +2,6 @@
admin.macaroon
tls.cert
build/
logs/
# dependencies

View File

@ -6,24 +6,26 @@ RUN adduser --disabled-password \
--gecos "" \
"lndhub"
FROM node:16-bullseye-slim AS builder
FROM node:12-buster-slim AS builder
# These packages are required for building LNDHub
RUN apt-get update && apt-get -y install python3
WORKDIR /lndhub
# Copy project files and folders to the current working directory
COPY . .
# Copy 'package-lock.json' and 'package.json'
COPY package.json package-lock.json ./
# Install dependencies
RUN npm i
RUN npm run dockerbuild
# Copy project files and folders to the current working directory
COPY . .
# Delete git data as it's not needed inside the container
RUN rm -rf .git
FROM node:16-alpine
FROM node:12-buster-slim
# Create a specific user so LNDHub doesn't run as root
COPY --from=perms /etc/group /etc/passwd /etc/shadow /etc/
@ -32,11 +34,10 @@ COPY --from=perms /etc/group /etc/passwd /etc/shadow /etc/
COPY --from=builder /lndhub /lndhub
# Create logs folder and ensure permissions are set correctly
RUN mkdir -p /lndhub/logs && chown -R lndhub:lndhub /lndhub
RUN mkdir /lndhub/logs && chown -R lndhub:lndhub /lndhub
USER lndhub
ENV PORT=3000
EXPOSE 3000
WORKDIR /lndhub
CMD cp $LND_CERT_FILE /lndhub/ && cp $LND_ADMIN_MACAROON_FILE /lndhub/ && cd /lndhub && npm start

View File

@ -1,7 +1,7 @@
LndHub
======
Wrapper for Lightning Network Daemon (lnd). It provides separate accounts with minimum trust for end users.
Wrapper for Lightning Network Daemon. It provides separate accounts with minimum trust for end users
INSTALLATION
------------
@ -17,17 +17,15 @@ cd LndHub
npm i
```
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.
Install `bitcoind`, `lnd` and `redis`. Edit `config.js` and set it up correctly.
Copy `admin.macaroon` and `tls.cert` in root folder of LndHub.
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).
`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`.
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).
Please not that this feature is limited to Bitcoin, so you can't use this feature if you use any other cryptocurrency with LND.
### Deploy to Heroku
@ -46,13 +44,12 @@ You can also view Umbrel's implementation using docker-compose [here](https://gi
Can be used in ReactNative or Nodejs environment
* https://github.com/BlueWallet/BlueWallet/blob/master/class/wallets/lightning-custodian-wallet.js
* https://github.com/BlueWallet/BlueWallet/blob/master/class/lightning-custodian-wallet.js
### Tests
Acceptance tests are in https://github.com/BlueWallet/BlueWallet/blob/master/tests/integration/lightning-custodian-wallet.test.js
Acceptance tests are in https://github.com/BlueWallet/BlueWallet/blob/master/LightningCustodianWallet.test.js
![image](https://user-images.githubusercontent.com/1913337/52418916-f30beb00-2ae6-11e9-9d63-17189dc1ae8c.png)
@ -60,5 +57,5 @@ Acceptance tests are in https://github.com/BlueWallet/BlueWallet/blob/master/tes
## Responsible disclosure
Found critical bugs/vulnerabilities? Please email them to bluewallet@bluewallet.io
Found critical bugs/vulnerabilities? Please email them bluewallet@bluewallet.io
Thanks!

View File

@ -12,6 +12,10 @@ export class Paym {
this._isPaid = null;
}
static get fee() {
return 0.003;
}
setInvoice(bolt11) {
this._bolt11 = bolt11;
}
@ -35,7 +39,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 * forwardFee) + 1 },
fee_limit: { fixed: Math.floor(this._decoded.num_satoshis * 0.01) + 1 },
};
let that = this;
return new Promise(function (resolve, reject) {
@ -70,7 +74,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 * internalFee);
payment.payment_route.total_fees = +payment.payment_route.total_fees + Math.floor(+payment.payment_route.total_amt * Paym.fee);
if (this._bolt11) payment.pay_req = this._bolt11;
if (this._decoded) payment.decoded = this._decoded;
}
@ -83,7 +87,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 * forwardFee); // 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 * 0.01); // we dont know the exact fee, so we use max (same as fee_limit)
payment.payment_route.total_amt = this._decoded.num_satoshis;
}
}

View File

@ -119,12 +119,6 @@ export class User {
return new Promise(function (resolve, reject) {
self._lightning.newAddress({ type: 0 }, async function (err, response) {
if (err) return reject('LND failure when trying to generate new address');
const addressAlreadyExists = await self.getAddress();
if (addressAlreadyExists) {
// one last final check, for a case of really long race condition
resolve();
return;
}
await self.addAddress(response.address);
if (config.bitcoind) self._bitcoindrpc.request('importaddress', [response.address, response.address, false]);
resolve();
@ -181,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 * forwardFee);
calculatedBalance -= +paym.amount + /* feelimit */ Math.floor(paym.amount * 0.01);
}
return calculatedBalance;

View File

@ -2,8 +2,6 @@ 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',
},

View File

@ -1,13 +1,11 @@
import { User, Lock, Paym, Invo } from '../class/';
import fetch from 'node-fetch';
import Frisbee from 'frisbee';
const config = require('../config');
let express = require('express');
let router = express.Router();
let logger = require('../utils/logger');
const MIN_BTC_BLOCK = 670000;
if (process.env.NODE_ENV !== 'prod') {
console.log('using config', JSON.stringify(config));
}
console.log('using config', JSON.stringify(config));
var Redis = require('ioredis');
var redis = new Redis(config.redis);
@ -17,12 +15,6 @@ 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;
@ -92,16 +84,21 @@ const subscribeInvoicesCallCallback = async function (response) {
console.log('payment', LightningInvoiceSettledNotification.hash, 'was paid, posting to GroundControl...');
const baseURI = process.env.GROUNDCONTROL;
if (!baseURI) return;
const apiResponse = await fetch(`${baseURI}/lightningInvoiceGotSettled`, {
method: 'POST',
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
},
body: JSON.stringify(LightningInvoiceSettledNotification),
});
console.log('Groundcontrol apiResponse=', apiResponse);
const _api = new Frisbee({ baseURI: baseURI });
const apiResponse = await _api.post(
'/lightningInvoiceGotSettled',
Object.assign(
{},
{
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
},
body: LightningInvoiceSettledNotification,
},
),
);
console.log('GroundControl:', apiResponse.originalResponse.status);
}
};
let subscribeInvoicesCall = lightning.subscribeInvoices({});
@ -136,11 +133,7 @@ const postLimiter = rateLimit({
router.post('/create', postLimiter, async function (req, res) {
logger.log('/create', [req.id]);
// 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 (!(req.body.partnerid && req.body.partnerid === 'bluewallet' && req.body.accounttype)) return errorBadArguments(res);
if (config.sunset) return errorSunset(res);
@ -199,7 +192,7 @@ router.post('/addinvoice', postLimiter, async function (req, res) {
);
});
router.post('/payinvoice', postLimiter, async function (req, res) {
router.post('/payinvoice', async function (req, res) {
let u = new User(redis, bitcoinclient, lightning);
if (!(await u.loadByAuthorization(req.headers.authorization))) {
return errorBadAuth(res);
@ -242,7 +235,7 @@ router.post('/payinvoice', postLimiter, 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 * forwardFee) + 1) {
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) {
@ -269,8 +262,8 @@ router.post('/payinvoice', postLimiter, async function (req, res) {
await u.savePaidLndInvoice({
timestamp: parseInt(+new Date() / 1000),
type: 'paid_invoice',
value: +info.num_satoshis + Math.floor(info.num_satoshis * internalFee),
fee: Math.floor(info.num_satoshis * internalFee),
value: +info.num_satoshis + Math.floor(info.num_satoshis * Paym.fee),
fee: Math.floor(info.num_satoshis * Paym.fee),
memo: decodeURIComponent(info.description),
pay_req: req.body.invoice,
});
@ -323,7 +316,7 @@ router.post('/payinvoice', postLimiter, 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 * forwardFee) + 1 },
fee_limit: { fixed: Math.floor(info.num_satoshis * 0.005) + 1 },
};
try {
await u.lockFunds(req.body.invoice, info);
@ -410,7 +403,7 @@ router.get('/getinfo', postLimiter, async function (req, res) {
});
});
router.get('/gettxs', postLimiter, async function (req, res) {
router.get('/gettxs', async function (req, res) {
logger.log('/gettxs', [req.id]);
let u = new User(redis, bitcoinclient, lightning);
if (!(await u.loadByAuthorization(req.headers.authorization))) {
@ -426,8 +419,8 @@ router.get('/gettxs', postLimiter, async function (req, res) {
for (let locked of lockedPayments) {
txs.push({
type: 'paid_invoice',
fee: Math.floor(locked.amount * forwardFee) /* feelimit */,
value: locked.amount + Math.floor(locked.amount * forwardFee) /* feelimit */,
fee: Math.floor(locked.amount * 0.01) /* feelimit */,
value: locked.amount + Math.floor(locked.amount * 0.01) /* feelimit */,
timestamp: locked.timestamp,
memo: 'Payment in transition',
});
@ -456,7 +449,7 @@ router.get('/getuserinvoices', postLimiter, async function (req, res) {
}
});
router.get('/getpending', postLimiter, async function (req, res) {
router.get('/getpending', async function (req, res) {
logger.log('/getpending', [req.id]);
let u = new User(redis, bitcoinclient, lightning);
if (!(await u.loadByAuthorization(req.headers.authorization))) {
@ -470,7 +463,7 @@ router.get('/getpending', postLimiter, async function (req, res) {
res.send(txs);
});
router.get('/decodeinvoice', postLimiter, async function (req, res) {
router.get('/decodeinvoice', async function (req, res) {
logger.log('/decodeinvoice', [req.id]);
let u = new User(redis, bitcoinclient, lightning);
if (!(await u.loadByAuthorization(req.headers.authorization))) {
@ -602,7 +595,7 @@ function errorSunset(res) {
return res.send({
error: true,
code: 11,
message: 'This LNDHub instance is not accepting any more users',
message: 'This LNDHub instance is not accepting new users.',
});
}

View File

@ -62,7 +62,6 @@ const pubkey2name = {
'030c3f19d742ca294a55c00376b3b355c3c90d61c6b6b39554dbc7ac19b141c14f': 'bitrefill.com',
'03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f': 'ACINQ',
'03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e': 'OpenNode',
'028d98b9969fbed53784a36617eb489a59ab6dc9b9d77fcdca9ff55307cd98e3c4': 'OpenNode 2',
'0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3': 'coingate.com',
'0279c22ed7a068d10dc1a38ae66d2d6461e269226c60258c021b1ddcdfe4b00bc4': 'ln1.satoshilabs.com',
'02c91d6aa51aa940608b497b6beebcb1aec05be3c47704b682b3889424679ca490': 'lnd-21.LNBIG.com',
@ -83,10 +82,6 @@ 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) {
@ -105,8 +100,7 @@ router.get('/qr', function (req, res) {
if (process.env.TOR_URL) {
host = process.env.TOR_URL;
}
const customPath = req.url.replace('/qr', '');
const url = 'bluewallet:setlndhuburl?url=' + encodeURIComponent(req.protocol + '://' + host + customPath);
const url = 'bluewallet:setlndhuburl?url=' + encodeURIComponent(req.protocol + '://' + host);
var code = qr.image(url, { type: 'png' });
res.setHeader('Content-type', 'image/png');
code.pipe(res);

View File

@ -33,7 +33,7 @@ associated with corresponding user id.
| Call | Method | Handler | Params | Return | Description |
| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- |
| Create Account | POST | /create | {none} | JSON Auth Data | Create new user account and get credentials |
| Authorize | POST | /auth | auth params (login/password or refresh_token) | JSON token data | Authorize user with Oauth. When user use refresh_token to auth, then this refresh_token not available for access once again. Use new refresh_token |
| Authorize | POST | /auth | auth params (login/password of refresh_token) | JSON token data | Authorize user with Oauth. When user use refresh_token to auth, then this refresh_token not available for access once again. Use new refresh_token |
| Get token | POST | /oauth2/token | user id, secret, grant_type and scope | token data | Get token data from user id, secret, grant_type and scope |
| Get BTC Addr | GET | /getbtc | {none} | Text address | Get user's BTC address to top-up his account |
| New BTC Addr | POST | /newbtc | {none} | Text address | Create new BTC address for user. Old addresses should remain valid, so if user accidentaly sends money to old address transaction will be assigned to his account |
@ -70,9 +70,9 @@ Response is always JSON.
`error:true` should be always present.
{
"error" : true, // boolean
"code" : 1, // int
"message": "..." // string
"error" : true, // boolean
"code" : 1, // int
"message": "..." // string
}
Error code | Error message
@ -95,15 +95,15 @@ Create new user account and get credentials. Not whitelisted partners should ret
Request:
{
"partnerid" : "bluewallet" // string, not mandatory parameter
"accounttype" : "..." // string, not mandatory, default is common, also can be test or core
"partnerid" : "bluewallet" // string, not mandatory parameter
"accounttype" : "..." // string, not mandatory, default is common, also can be test or core
}
Response:
{
"login":"...", // string
"password":"...", // string
"login":"...", // srting
"password":"...", // srting
}
## POST /auth?type=auth
@ -113,16 +113,16 @@ Authorize user with Oauth user and login
Request:
{
"login": "...", // string
"password": "..." // string
"login": "...", //string
"password": "..." //string
}
Response:
{
"access_token": "...", // string
"token_type": "...", // string
"refresh_token": "...", // string
"access_token": "...", //string
"token_type": "...", //string
"refresh_token": "...", //string
"expiry": "0001-01-01T00:00:00Z" // datetime
}
@ -135,16 +135,16 @@ Authorize user with Oauth user and login
Request:
{
"refresh_token": "...", // string
"refresh_token": "...", //string
}
Response:
{
"access_token": "...", // string
"token_type": "...", // string
"refresh_token": "...", // string
"expiry": "0001-01-01T00:00:00Z" // datetime
"access_token": "...", //string
"token_type": "...", //string
"refresh_token": "...", //string
"expiry": "0001-01-01T00:00:00Z" // datetime
}
## POST /oauth2/token
@ -154,17 +154,17 @@ Authorize user with Oauth user and login
Request:
{
"grant_type": "client_credentials", // string
"client_id": "...", // string
"grant_type": "client_credentials", //string
"client_id": "...", //string
"client_secret": "..." // string
}
Response:
{
"access_token": "...", // string
"token_type": "...", // string
"refresh_token": "...", // string
"access_token": "...", //string
"token_type": "...", //string
"refresh_token": "...", //string
"expiry": "0001-01-01T00:00:00Z" // datetime
}
@ -229,34 +229,34 @@ Request:
Response:
{
"destination": "...", // string, lnd node address
"payment_hash": "...", // string
"num_satoshis": "78497", // string, satoshis
"timestamp": "1534430501", // string, unixtime
"expiry": "3600", // string, seconds
"description": "...", // string
"description_hash": "", // string
"fallback_addr": "...", // string, fallback on-chain address
"cltv_expiry": "...", // string, delta to use for the time-lock of the CLTV extended to the final hop
"destination": "...", //string, lnd node address
"payment_hash": "...", //string
"num_satoshis": "78497", //string, satoshis
"timestamp": "1534430501", //string, unixtime
"expiry": "3600", //string, seconds
"description": "...", //string
"description_hash": "", //string
"fallback_addr": "...", //string, fallback on-chain address
"cltv_expiry": "...", //string, delta to use for the time-lock of the CLTV extended to the final hop
"route_hints": [
{
"hop_hints" : [
{
"node_id": "..", // string, the public key of the node at the start of the
"node_id": "..", //string, the public key of the node at the start of the
// channel.
"chan_id": ..., // int, the unique identifier of the channel.
"chan_id": ..., //int, the unique identifier of the channel.
"fee_base_msat": ..., // int, The base fee of the channel denominated in
"fee_base_msat": ..., //int, The base fee of the channel denominated in
// millisatoshis.
"fee_proportional_millionths": ...,
// int, the fee rate of the channel
//int, the fee rate of the channel
// for sending one satoshi across it denominated
// in millionths of a satoshi
"cltv_expiry_delta": ...
// int, the fee rate of the channel for sending one satoshi
//int, the fee rate of the channel for sending one satoshi
// across it denominated in millionths of a satoshi
}, ...
]
@ -288,7 +288,7 @@ Request:
{
"destination" : "..." // string, destination lnd node address
"amt": "..." // string,
"amt": "..." // string,
}
Response:
@ -311,26 +311,26 @@ Request:
Response:
{
"payment_error": "..." // string
"payment_preimage": "..." // string
"payment_route": {
"total_time_lock": ... , // int
"total_fees": ... , // int
"total_amt": ... , // int
"total_fees_msat": ... , // int
"total_amt_msat": ... , // int
"hops": [
{
"chan_id": ... , // int
"chan_capacity": ... , // int
"amt_to_forward": ... , // int
"fee": ... , // int
"expiry": ... , // int
"amt_to_forward_msat": ... , // int
"fee_msat": ... , // int
},
]
}
"payment_error": "..." //string
"payment_preimage": "..." //string
"payment_route": {
"total_time_lock": ... , //int
"total_fees": ... , //int
"total_amt": ... , //int
"total_fees_msat": ... , //int
"total_amt_msat": ... , //int
"hops": [
{
"chan_id": ... , //int
"chan_capacity": ... , //int
"amt_to_forward": ... , //int
"fee": ... , //int
"expiry": ... , //int
"amt_to_forward_msat": ... , //int
"fee_msat": ... , //int
},
]
}
}
## POST /sendcoins
@ -356,14 +356,14 @@ Get successful lightning and btc transactions user made. Order newest to oldest.
Request:
{
"limit" : 10, // INT
"offset": 0, // INT
"limit" : 10, // INT
"offset": 0, // INT
}
Response:
{
[ // array of Transaction object (see below)
[ // array of Transaction object (see below)
{
...
}
@ -398,10 +398,10 @@ Request:
Response:
{
"BTC": { // string, currency
"TotalBalance": 109388, // int, satoshis
"BTC": { //string, currency
"TotalBalance": 109388, //int, satoshis
"AvailableBalance": 109388, // int, satoshis
"UncomfirmedBalance": 0 // int, satoshis
"UncomfirmedBalance": 0 //int, satoshis
}, ...
//now available only btc balance
@ -422,52 +422,50 @@ Response:
"fee": 0, // int, in cents of percent, i.e. 100 for 1%, 50 for 0.5%, 1 for 0.01%
"identity_pubkey": "...", // string, lnd node identity pubkey
"alias": "...", // string, lnd node alias
"num_pending_channels": 0, // int
"num_active_channels": 3, // int
"num_peers": 6, // int
"block_height": 542389, // int
"block_hash": "...", // string
"synced_to_chain": true, // bool
"testnet": false,
"chains": [
"bitcoin" // string, available chans to operate by lnd
],
"uris": [
"...", // string, uris of lnd node
],
"best_header_timestamp": "...", // string, unixtime
"version": "..." // string, lnd version
"identity_pubkey": "...", //string, lnd node identity pubkey
"alias": "...", //string, lnd node alias
"num_pending_channels": 0, //int
"num_active_channels": 3, //int
"num_peers": 6, //int
"block_height": 542389, //int
"block_hash": "...", //string
"synced_to_chain": true, //bool
"testnet": false,
"chains": [
"bitcoin" //string, available chans to operate by lnd
],
"uris": [
"...", //string, uris of lnd node
],
"best_header_timestamp": "...", //string, unixtime
"version": "..." // string, lnd version
}
## GET /getinvoice
## GET /getaddinvoice
Returns fees user pays for payments, status of the system, etc.
Request:
{
"amt": "...", // string
"memo":"...", // string
"receipt":"...", // string, not mandatory parameter
"preimage": "...", // string, not mandatory parameter
"fallbackAddr": "...", // string, not mandatory parameter
"expiry": "...", // string, not mandatory parameter
"private": "..." // string, not mandatory parameter
"amt": "...", //string
"memo":"...", //string
"receipt":"...", //string, not mandatory parameter
"preimage": "...", //string, not mandatory parameter
"fallbackAddr": "...", //string, not mandatory parameter
"expiry": "...", //string, not mandatory parameter
"private": "..." //string, not mandatory parameter
}
Response:
{
"r_hash": "...", // string,
"pay_req": "...", // string, a bare-bones invoice for a payment within the Lightning Network
"add_index": ... // int, The “add” index of this invoice. Each newly created invoice will
"r_hash": "...", //string,
"pay_req": "...", //string, a bare-bones invoice for a payment within the Lightning Network
"add_index": ... //int, The “add” index of this invoice. Each newly created invoice will
// increment this index making it monotonically increasing.
// Callers to the SubscribeInvoices call can use this to instantly
// get notified of all added invoices with an add_index greater than this one.
}
## GET /getuserinvoices
Returns fees user pays for payments, status of the system, etc.
@ -477,35 +475,33 @@ Request:
none
Response:
{
"r_hash": "...", // string
"payment_request": "...", // string
"add_index": "...", // string
"description": "...", // string
"amt": ... , // int
"ispaid": ... // bool
}
{
"r_hash": "...", //string
"payment_request": "...", //string
"add_index": "...", //string
"description": "...", //string
"amt": ... , //int
"ispaid": ... //bool
}
# Data structures
## Transaction object
{
"type": "...", // string, type of txs. Types:
// bitcoind_internal_tx - moves to user btc address or account
// bitcoind_tx - received by address or account
// paid_invoice - user paid someone's invoice
// sent_coins - user sent coins by lnd to someone's btc account
// received_invoice_payments - user received payments by invoice
"txid": "...", // string, internal tx id. not related to onchain transaction id
"amt": 666, // satoshi, int
"fee": 11, // satoshi, int
"timestamp": 1234567, // int, unixtime
"from": "...", // string
"to": "...", // string
"description": "...", // string, user-defined text
"invoice": "...", // string, original bolt11-format invoice
// bitcoind_internal_tx - moves to user btc address or account
// bitcoind_tx - received by address or account
// paid_invoice - user paid someone's invoice
// sent_coins - user sent coins by lnd to someone's btc account
// received_invoice_payments - user received payments by invoice
"txid": "...", // string, internal tx id. not related to onchain transaction id
"amt": 666, // satoshi, int
"fee": 11, // satoshi, int
"timestamp": 1234567, // int, unixtime
"from": "...", // string
"to": "...", // string
"description": "...", // string, user-defined text
"invoice": "...", // string, original bolt11-format invoice
}
# Explaining oauth2 mechanism

View File

@ -47,11 +47,7 @@ app.use('/static', express.static('static'));
app.use(require('./controllers/api'));
app.use(require('./controllers/website'));
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);
logger.log('using GroundControl', process.env.GROUNDCONTROL);
let server = app.listen(process.env.PORT || 3000, function () {
logger.log('BOOTING UP', 'Listening on port ' + (process.env.PORT || 3000));
});
module.exports = server;

7748
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,10 @@
{
"name": "lndhub",
"version": "1.4.3",
"version": "1.3.5",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dockerbuild": "./node_modules/.bin/babel ./ --ignore node_modules/ --ignore *.spec.js --out-dir ./build",
"dev": "nodemon node_modules/.bin/babel-node index.js",
"start": "node_modules/.bin/babel-node index.js",
"lint": "./node_modules/.bin/eslint ./ controllers/ class/ --fix"
@ -13,28 +12,29 @@
"author": "Igor Korsakov <overtorment@gmail.com>",
"license": "MIT",
"dependencies": {
"@babel/cli": "^7.14.8",
"@babel/core": "^7.15.0",
"@babel/cli": "^7.14.5",
"@babel/core": "^7.14.5",
"@babel/eslint-parser": "^7.14.2",
"@babel/node": "^7.14.9",
"@babel/preset-env": "^7.22.0",
"@babel/node": "^7.14.5",
"@babel/preset-env": "^7.14.5",
"@babel/register": "^7.14.5",
"@grpc/grpc-js": "^1.3.7",
"@grpc/proto-loader": "^0.6.5",
"@grpc/grpc-js": "^1.3.2",
"@grpc/proto-loader": "^0.6.2",
"bignumber.js": "^9.0.1",
"bitcoinjs-lib": "^5.2.0",
"bolt11": "^1.3.2",
"bolt11": "^1.3.1",
"core-js": "^3.14.0",
"eslint": "^7.24.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"express": "^4.17.1",
"express-rate-limit": "^5.4.1",
"express-rate-limit": "^5.2.6",
"frisbee": "^3.1.4",
"helmet": "^4.6.0",
"ioredis": "^4.27.10",
"jayson": "^3.6.4",
"ioredis": "^4.27.2",
"jayson": "^3.6.3",
"morgan": "^1.10.0",
"mustache": "^4.1.0",
"node-fetch": "^2.6.1",
"prettier": "^2.3.0",
"qr-image": "3.2.0",
"request": "^2.88.2",

View File

@ -9,11 +9,6 @@ const important_channels = {
uri: '03abf6f44c355dec0d5aa155bdbdd6e0c8fefe318eff402de65c6eb2e1be55dc3e@18.221.23.28:9735',
wumbo: 1,
},
'028d98b9969fbed53784a36617eb489a59ab6dc9b9d77fcdca9ff55307cd98e3c4': {
name: 'OpenNode 2',
uri: '028d98b9969fbed53784a36617eb489a59ab6dc9b9d77fcdca9ff55307cd98e3c4@18.222.70.85:9735',
wumbo: 1,
},
// '0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3': {
// name: 'coingate.com',
// uri: '0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3@3.124.63.44:9735',
@ -83,7 +78,7 @@ lightning.listChannels({}, function (err, response) {
}
let lightningListChannels = response;
for (let channel of lightningListChannels.channels) {
if (channel.capacity <= 1000000) {
if (channel.capacity < 0.05 / 100000000) {
console.log(
'lncli closechannel',
channel.channel_point.replace(':', ' '),

View File

@ -3,15 +3,10 @@
* sentout payments from LND. If locked payment is in there we moe locked payment to array of real payments for the user
* (it is effectively spent coins by user), if not - we attempt to pay it again (if it is not too old).
*/
import { User, Paym } from '../class/';
import { User, Lock, Paym } from '../class/';
const config = require('../config');
/****** 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 ******/
const fs = require('fs');
var Redis = require('ioredis');
var redis = new Redis(config.redis);
@ -25,8 +20,8 @@ let lightning = require('../lightning');
console.log('fetching listPayments...');
let tempPaym = new Paym(redis, bitcoinclient, lightning);
let listPayments = await tempPaym.listPayments();
// DEBUG let listPayments = JSON.parse(fs.readFileSync('listpayments.txt').toString('ascii'));
console.log('done', 'got', listPayments['payments'].length, 'payments');
fs.writeFileSync('listPayments.json', JSON.stringify(listPayments['payments'], null, 2));
for (let key of keys) {
const userid = key.replace('locked_payments_for_', '');
@ -35,7 +30,7 @@ let lightning = require('../lightning');
let user = new User(redis, bitcoinclient, lightning);
user._userid = userid;
let lockedPayments = await user.getLockedPayments();
// DEBUG let lockedPayments = [{ pay_req : 'lnbc108130n1pshdaeupp58kw9djt9vcdx26wkdxl07tgncdmxz2w7s9hzul45tf8gfplme94sdqqcqzzgxqrrssrzjqw8c7yfutqqy3kz8662fxutjvef7q2ujsxtt45csu0k688lkzu3ld93gutl3k6wauyqqqqryqqqqthqqpysp5jcmk82hypuud0lhpf66dg3w5ta6aumc4w9g9sxljazglq9wkwstq9qypqsqnw8hwwauvzrala3g4yrkgazk2l2fh582j9ytz7le46gmsgglvmrknx842ej9z4c63en5866l8tpevm8cwul8g94kf2nepppn256unucp43jnsw', amount: 10813, timestamp: 1635186606 }];
// lockedPayments = [{pay_req : 'lnbc2m1pwgd4tdpp5vjz80mm8murdkskrnre6w4kphzy3d6gap5jyffr93u02ruaj0wtsdq2xgcrqvpsxqcqzysk34zva4h9ce9jdf08nfdm2sh2ek4y4hjse8ww9jputneltjl24krkv50sene4jh0wpull6ujgrg632u2qt3lkva74vpkqr5e5tuuljspasqfhx'}];
for (let lockedPayment of lockedPayments) {
let daysPassed = (+new Date() / 1000 - lockedPayment.timestamp) / 3600 / 24;
@ -43,33 +38,52 @@ let lightning = require('../lightning');
let payment = new Paym(redis, bitcoinclient, lightning);
payment.setInvoice(lockedPayment.pay_req);
// first things first:
// trying to lookup this stuck payment in an array of delivered payments
let isPaid = false;
for (let sentPayment of listPayments['payments']) {
if ((await payment.getPaymentHash()) == sentPayment.payment_hash) {
console.log('found this payment in listPayments array, so it is paid successfully');
let sendResult = payment.processSendPaymentResponse({ payment_error: 'already paid' } /* hacky */); // adds fees
if (sendResult.decoded.num_satoshis == 0) {
// zero sat invoice, get corect sat number from the lockedPayment info
sendResult.decoded.num_satoshis = lockedPayment.amount + Math.ceil(lockedPayment.amount * forwardFee);
sendResult.decoded.num_msat = sendResult.decoded.num_satoshis * 1000;
sendResult.payment_route.total_fees = 0;
sendResult.payment_route.total_amt = sendResult.decoded.num_satoshis;
}
if (daysPassed > 1 / 24 && daysPassed <= 1) {
// if (!await payment.isExpired()) {
let sendResult;
console.log('attempting to pay to route');
try {
sendResult = await payment.attemptPayToRoute();
} catch (_) {
console.log(_);
console.log('evict lock');
await user.unlockFunds(lockedPayment.pay_req);
continue;
}
console.log('sendResult=', sendResult);
console.log('payment.getIsPaid() = ', payment.getIsPaid());
if (payment.getIsPaid() === true) {
console.log('paid successfully');
sendResult = payment.processSendPaymentResponse(sendResult); // adds fees
console.log('saving paid invoice:', sendResult);
await user.savePaidLndInvoice(sendResult);
await user.unlockFunds(lockedPayment.pay_req);
isPaid = true;
console.log('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!', await payment.getPaymentHash(), sentPayment.payment_hash);
break;
} else if (payment.getIsPaid() === false) {
console.log('not paid, just evict the lock');
await user.unlockFunds(lockedPayment.pay_req);
} else {
console.log('payment is in unknown state');
}
console.log('sleeping 5 sec...');
console.log('-----------------------------------------------------------------------------------');
await User._sleep(0);
} else if (daysPassed > 1) {
// trying to lookup this stuck payment in an array of delivered payments
let isPaid = false;
for (let sentPayment of listPayments['payments']) {
if ((await payment.getPaymentHash()) == sentPayment.payment_hash) {
console.log('found this payment in listPayments array, so it is paid successfully');
let sendResult = payment.processSendPaymentResponse({ payment_error: 'already paid' } /* hacky */); // adds fees
console.log('saving paid invoice:', sendResult);
await user.savePaidLndInvoice(sendResult);
await user.unlockFunds(lockedPayment.pay_req);
isPaid = true;
console.log('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!', await payment.getPaymentHash(), sentPayment.payment_hash);
process.exit();
break;
}
}
}
// could not find...
if (daysPassed > 1) {
// could not find in listpayments array; too late to retry
if (!isPaid) {
console.log('very old payment, evict the lock');
await user.unlockFunds(lockedPayment.pay_req);

View File

@ -50,10 +50,9 @@ let lightning = require('../lightning');
let locked = await U.getLockedPayments();
for (let loc of locked) {
console.log('-', loc.amount + /* fee limit */ Math.floor(loc.amount * config.forwardReserveFee), new Date(loc.timestamp * 1000).toString(), '[locked]');
console.log('-', loc.amount + /* fee limit */ Math.floor(loc.amount * 0.01), 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();
})();