balanceofsatoshis 12.3.0 → 12.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/README.md +1 -1
- package/bos +2 -2
- package/lnurl/parse_url.js +59 -0
- package/lnurl/pay.js +4 -15
- package/lnurl/sign_auth_challenge.js +1 -1
- package/package.json +5 -5
- package/routing/get_fees_paid.js +114 -8
- package/telegram/start_telegram_bot.js +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 12.5.0
|
|
4
|
+
|
|
5
|
+
- `lnurl`: Add support for paying to https://lightningaddress.com/ type urls
|
|
6
|
+
|
|
7
|
+
## 12.4.1
|
|
8
|
+
|
|
9
|
+
- Add support for LND 0.14.3-beta
|
|
10
|
+
- `telegram`: Disallow non connect user id queries for /mempool and /version
|
|
11
|
+
|
|
12
|
+
## 12.4.0
|
|
13
|
+
|
|
14
|
+
- `chart-fees-paid`: Allow using aliases when specifying --in and --out peers
|
|
15
|
+
|
|
3
16
|
## 12.3.0
|
|
4
17
|
|
|
5
18
|
- `lnurl`: Add support for the `auth` function to authenticate with a node key
|
|
@@ -67,7 +80,7 @@ For `peers` and `remove-peer` commands:
|
|
|
67
80
|
|
|
68
81
|
## 11.59.4
|
|
69
82
|
|
|
70
|
-
- `telegram`: Fix
|
|
83
|
+
- `telegram`: Fix open trade-secret serving
|
|
71
84
|
- `telegram`: Fix `/stop` command to require confirmation before termination
|
|
72
85
|
|
|
73
86
|
## 11.59.2
|
package/README.md
CHANGED
package/bos
CHANGED
|
@@ -398,12 +398,12 @@ prog
|
|
|
398
398
|
.help('Show the routing fees paid to forwarding nodes')
|
|
399
399
|
.help('--rebalances can return results much more quickly')
|
|
400
400
|
.option('--days <days>', 'Chart fees over the past number of days', INT, 60)
|
|
401
|
-
.option('--in <
|
|
401
|
+
.option('--in <key_or_alias>', 'Fees paid on routes in node with peer')
|
|
402
402
|
.option('--most-fees', 'View table of fees paid per node')
|
|
403
403
|
.option('--most-forwarded', 'View table of forwarded per node')
|
|
404
404
|
.option('--network', 'Show only non-peers in table view')
|
|
405
405
|
.option('--node <node_name>', 'Get fees chart for saved node(s)', REPEATABLE)
|
|
406
|
-
.option('--out <
|
|
406
|
+
.option('--out <key_or_alias>', 'Fees paid on routes out node with peer')
|
|
407
407
|
.option('--peers', 'Show only peers in table view')
|
|
408
408
|
.option('--rebalances', 'Only consider fees paid in self-to-self transfers')
|
|
409
409
|
.action((args, options, logger) => {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const {bech32} = require('bech32');
|
|
2
|
+
|
|
3
|
+
const asLnurl = n => n.substring(n.startsWith('lightning:') ? 10 : 0);
|
|
4
|
+
const bech32CharLimit = 2000;
|
|
5
|
+
const {decode} = bech32;
|
|
6
|
+
const isEmail = n => /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/.test(n);
|
|
7
|
+
const isOnion = n => /.onion$/.test(n);
|
|
8
|
+
const isUsername = n => /^[a-z0-9_.]*$/.test(n);
|
|
9
|
+
const join = arr => arr.join('');
|
|
10
|
+
const parseEmail = email => email.split('@');
|
|
11
|
+
const prefix = 'lnurl';
|
|
12
|
+
const sslProtocol = 'https://';
|
|
13
|
+
const urlString = '/.well-known/lnurlp/';
|
|
14
|
+
const wordsAsUtf8 = n => Buffer.from(bech32.fromWords(n)).toString('utf8');
|
|
15
|
+
|
|
16
|
+
/** Parse lnurl or LUD-16 lightning address
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
url: <Lnurl or Lightning Address String>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@throws
|
|
23
|
+
<Error>
|
|
24
|
+
|
|
25
|
+
@returns
|
|
26
|
+
{
|
|
27
|
+
url: <Callback Url String>
|
|
28
|
+
}
|
|
29
|
+
*/
|
|
30
|
+
module.exports = ({url}) => {
|
|
31
|
+
if (!url) {
|
|
32
|
+
throw new Error('ExpectedLnurlOrLightningAddressToParse');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Exit early when the URL looks like an email, indicating lightning address
|
|
36
|
+
if (!!isEmail(url)) {
|
|
37
|
+
const [username, domain] = parseEmail(url);
|
|
38
|
+
|
|
39
|
+
// Check if the user name is valid
|
|
40
|
+
if (!isUsername(username)) {
|
|
41
|
+
throw new Error('ExpectedValidUsernameInLightningAddress');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Because of restrictions on the HTTP request library, disallow onion URLs
|
|
45
|
+
if (!!isOnion(domain)) {
|
|
46
|
+
throw new Error('LnurlOnionUrlsCurrentlyUnsupported');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {url: join([sslProtocol, domain, urlString, username])};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (decode(asLnurl(url), bech32CharLimit).prefix !== prefix) {
|
|
53
|
+
throw new Error('ExpectedLnurlPrefix');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const {words} = decode(asLnurl(url), bech32CharLimit);
|
|
57
|
+
|
|
58
|
+
return {url: wordsAsUtf8(words)};
|
|
59
|
+
};
|
package/lnurl/pay.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
const {createHash} = require('crypto');
|
|
2
2
|
|
|
3
3
|
const asyncAuto = require('async/auto');
|
|
4
|
-
const {bech32} = require('bech32');
|
|
5
4
|
const {getNodeAlias} = require('ln-sync');
|
|
6
5
|
const moment = require('moment');
|
|
7
6
|
const {returnResult} = require('asyncjs-util');
|
|
8
7
|
const {parsePaymentRequest} = require('ln-service');
|
|
9
8
|
|
|
9
|
+
const parseUrl = require('./parse_url');
|
|
10
10
|
const {pay} = require('./../network');
|
|
11
11
|
|
|
12
|
-
const asLnurl = n => n.substring(n.startsWith('lightning:') ? 10 : 0);
|
|
13
|
-
const bech32CharLimit = 2000;
|
|
14
12
|
const errorStatus = 'ERROR';
|
|
15
|
-
const {decode} = bech32;
|
|
16
13
|
const {isArray} = Array;
|
|
17
14
|
const isNumber = n => !isNaN(n);
|
|
18
15
|
const lowestSendableValue = 1000;
|
|
@@ -22,7 +19,6 @@ const minMinSendable = 1;
|
|
|
22
19
|
const mtokensAsTokens = n => Math.floor(n / 1000);
|
|
23
20
|
const {parse} = JSON;
|
|
24
21
|
const payRequestTag = 'payRequest';
|
|
25
|
-
const prefix = 'lnurl';
|
|
26
22
|
const {round} = Math;
|
|
27
23
|
const sha256 = n => createHash('sha256').update(n).digest().toString('hex');
|
|
28
24
|
const sslProtocol = 'https:';
|
|
@@ -30,7 +26,6 @@ const textPlain = 'text/plain';
|
|
|
30
26
|
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
31
27
|
const tokensAsMillitokens = n => n * 1000;
|
|
32
28
|
const utf8AsBuffer = utf8 => Buffer.from(utf8, 'utf8');
|
|
33
|
-
const wordsAsUtf8 = n => Buffer.from(bech32.fromWords(n)).toString('utf8');
|
|
34
29
|
|
|
35
30
|
/** Pay to lnurl
|
|
36
31
|
{
|
|
@@ -63,13 +58,9 @@ module.exports = (args, cbk) => {
|
|
|
63
58
|
}
|
|
64
59
|
|
|
65
60
|
try {
|
|
66
|
-
|
|
61
|
+
parseUrl({url: args.lnurl});
|
|
67
62
|
} catch (err) {
|
|
68
|
-
return cbk([400,
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (decode(asLnurl(args.lnurl), bech32CharLimit).prefix !== prefix) {
|
|
72
|
-
return cbk([400, 'ExpectedLnUrlPrefixToPay']);
|
|
63
|
+
return cbk([400, err.message]);
|
|
73
64
|
}
|
|
74
65
|
|
|
75
66
|
if (!args.lnd) {
|
|
@@ -101,9 +92,7 @@ module.exports = (args, cbk) => {
|
|
|
101
92
|
|
|
102
93
|
// Get accepted terms from the encoded url
|
|
103
94
|
getTerms: ['validate', ({}, cbk) => {
|
|
104
|
-
const {
|
|
105
|
-
|
|
106
|
-
const url = wordsAsUtf8(words);
|
|
95
|
+
const {url} = parseUrl({url: args.lnurl});
|
|
107
96
|
|
|
108
97
|
return args.request({url, json: true}, (err, r, json) => {
|
|
109
98
|
if (!!err) {
|
package/package.json
CHANGED
|
@@ -36,16 +36,16 @@
|
|
|
36
36
|
"ini": "3.0.0",
|
|
37
37
|
"inquirer": "8.2.2",
|
|
38
38
|
"ln-accounting": "5.0.6",
|
|
39
|
-
"ln-service": "53.
|
|
39
|
+
"ln-service": "53.13.0",
|
|
40
40
|
"ln-sync": "3.12.0",
|
|
41
|
-
"ln-telegram": "3.21.
|
|
41
|
+
"ln-telegram": "3.21.2",
|
|
42
42
|
"moment": "2.29.3",
|
|
43
43
|
"paid-services": "3.15.1",
|
|
44
44
|
"probing": "2.0.5",
|
|
45
45
|
"psbt": "2.0.1",
|
|
46
46
|
"qrcode-terminal": "0.12.0",
|
|
47
47
|
"sanitize-filename": "1.6.3",
|
|
48
|
-
"socks-proxy-agent": "6.2.0
|
|
48
|
+
"socks-proxy-agent": "6.2.0",
|
|
49
49
|
"table": "6.8.0",
|
|
50
50
|
"tiny-secp256k1": "2.2.1",
|
|
51
51
|
"update-notifier": "5.1.0",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@alexbosworth/tap": "15.0.11",
|
|
57
57
|
"invoices": "2.0.6",
|
|
58
|
-
"ln-docker-daemons": "2.2.
|
|
58
|
+
"ln-docker-daemons": "2.2.9",
|
|
59
59
|
"mock-lnd": "1.4.1",
|
|
60
60
|
"tiny-secp256k1": "2.2.1"
|
|
61
61
|
},
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
|
|
85
85
|
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/arrays/*.js test/balances/*.js test/chain/*.js test/display/*.js test/encryption/*.js test/lnd/*.js test/network/*.js test/nodes/*.js test/peers/*.js test/responses/*.js test/routing/*.js test/services/*.js test/swaps/*.js test/tags/*.js test/telegram/*.js test/wallets/*.js"
|
|
86
86
|
},
|
|
87
|
-
"version": "12.
|
|
87
|
+
"version": "12.5.0"
|
|
88
88
|
}
|
package/routing/get_fees_paid.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
2
|
const asyncMap = require('async/map');
|
|
3
|
+
const {findKey} = require('ln-sync');
|
|
3
4
|
const {getNode} = require('ln-service');
|
|
4
5
|
const {getNodeAlias} = require('ln-sync');
|
|
5
6
|
const {getChannels} = require('ln-service');
|
|
@@ -19,14 +20,17 @@ const flatten = arr => [].concat(...arr);
|
|
|
19
20
|
const {floor} = Math;
|
|
20
21
|
const heading = [['Node', 'Public Key', 'Fees Paid', 'Forwarded']];
|
|
21
22
|
const hoursPerDay = 24;
|
|
23
|
+
const isAmbiguous = n => n[1] === 'AmbiguousAliasSpecified';
|
|
22
24
|
const {isArray} = Array;
|
|
23
25
|
const {keys} = Object;
|
|
24
26
|
const minChartDays = 4;
|
|
25
27
|
const maxChartDays = 90;
|
|
26
28
|
const mtokensAsBigUnit = n => (Number(n / BigInt(1e3)) / 1e8).toFixed(8);
|
|
27
29
|
const mtokensAsTokens = mtokens => Number(mtokens / BigInt(1e3));
|
|
30
|
+
const niceAlias = n => `${(n.alias || n.id).trim()} ${n.id.substring(0, 8)}`;
|
|
28
31
|
const title = 'Routing fees paid';
|
|
29
32
|
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
33
|
+
const uniq = arr => Array.from(new Set(arr));
|
|
30
34
|
|
|
31
35
|
/** Get routing fees paid
|
|
32
36
|
|
|
@@ -35,11 +39,13 @@ const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
|
35
39
|
fs: {
|
|
36
40
|
getFile: <Read File Contents Function> (path, cbk) => {}
|
|
37
41
|
}
|
|
42
|
+
[in]: <In Node Public Key or Alias String>
|
|
38
43
|
[is_most_fees_table]: <Is Most Fees Table Bool>
|
|
39
44
|
[is_most_forwarded_table]: <Is Most Forwarded Bool>
|
|
40
45
|
[is_network]: <Show Only Non-Peers In Table Bool>
|
|
41
46
|
[is_peer]: <Show Only Peers In Table Bool>
|
|
42
47
|
lnds: [<Authenticated LND API Object>]
|
|
48
|
+
[out]: <Out Node Public Key or Alias String>
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
@returns via cbk or Promise
|
|
@@ -90,6 +96,88 @@ module.exports = (args, cbk) => {
|
|
|
90
96
|
// Get node icons
|
|
91
97
|
getIcons: ['validate', ({}, cbk) => getIcons({fs: args.fs}, cbk)],
|
|
92
98
|
|
|
99
|
+
// Determine the in public key to use
|
|
100
|
+
getInKey: ['validate', ({}, cbk) => {
|
|
101
|
+
// Exit early when no in query is specified
|
|
102
|
+
if (!args.in) {
|
|
103
|
+
return cbk();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return asyncMap(args.lnds, (lnd, cbk) => {
|
|
107
|
+
return findKey({lnd, query: args.in}, (err, res) => {
|
|
108
|
+
// Exit for ambiguous queries
|
|
109
|
+
if (!!err && isAmbiguous(err)) {
|
|
110
|
+
return cbk(err);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Ignore all other errors, since a peer may not exist on all nodes
|
|
114
|
+
if (!!err) {
|
|
115
|
+
return cbk();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return cbk(null, res.public_key);
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
(err, res) => {
|
|
122
|
+
if (!!err) {
|
|
123
|
+
return cbk(err);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const [key, otherKey] = uniq(res.filter(n => !!n));
|
|
127
|
+
|
|
128
|
+
if (!key) {
|
|
129
|
+
return cbk([400, 'FailedToFindMatchesForInQueryAlias']);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!!otherKey) {
|
|
133
|
+
return cbk([400, 'MultipleMatchesForInQueryAlias']);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return cbk(null, key);
|
|
137
|
+
});
|
|
138
|
+
}],
|
|
139
|
+
|
|
140
|
+
// Determine the out public key to use
|
|
141
|
+
getOutKey: ['validate', ({}, cbk) => {
|
|
142
|
+
// Exit early when no out query is specified
|
|
143
|
+
if (!args.out) {
|
|
144
|
+
return cbk();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return asyncMap(args.lnds, (lnd, cbk) => {
|
|
148
|
+
return findKey({lnd, query: args.out}, (err, res) => {
|
|
149
|
+
// Exit for ambiguous queries
|
|
150
|
+
if (!!err && isAmbiguous(err)) {
|
|
151
|
+
return cbk(err);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Ignore all other errors, since a peer may not exist on all nodes
|
|
155
|
+
if (!!err) {
|
|
156
|
+
return cbk();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return cbk(null, res.public_key);
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
(err, res) => {
|
|
163
|
+
if (!!err) {
|
|
164
|
+
return cbk(err);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const [key, otherKey] = uniq(res.filter(n => !!n));
|
|
168
|
+
|
|
169
|
+
if (!key) {
|
|
170
|
+
return cbk([400, 'FailedToFindMatchesForOutQueryAlias']);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!!otherKey) {
|
|
174
|
+
return cbk([400, 'MultipleMatchesForOutQueryAlias']);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return cbk(null, key);
|
|
178
|
+
});
|
|
179
|
+
}],
|
|
180
|
+
|
|
93
181
|
// Segment measure
|
|
94
182
|
measure: ['validate', ({}, cbk) => {
|
|
95
183
|
if (args.days > maxChartDays) {
|
|
@@ -141,7 +229,13 @@ module.exports = (args, cbk) => {
|
|
|
141
229
|
}],
|
|
142
230
|
|
|
143
231
|
// Filter the payments
|
|
144
|
-
forwards: [
|
|
232
|
+
forwards: [
|
|
233
|
+
'getInKey',
|
|
234
|
+
'getOutKey',
|
|
235
|
+
'getPayments',
|
|
236
|
+
'start',
|
|
237
|
+
({getInKey, getOutKey, getPayments, start}, cbk) =>
|
|
238
|
+
{
|
|
145
239
|
const payments = getPayments
|
|
146
240
|
.filter(payment => payment.is_confirmed !== false)
|
|
147
241
|
.filter(payment => payment.confirmed_at > start.toISOString())
|
|
@@ -163,7 +257,7 @@ module.exports = (args, cbk) => {
|
|
|
163
257
|
}
|
|
164
258
|
|
|
165
259
|
// Ignore attempts that do not include the specified out hop
|
|
166
|
-
if (!!args.out && outHop !==
|
|
260
|
+
if (!!args.out && outHop !== getOutKey) {
|
|
167
261
|
return false;
|
|
168
262
|
}
|
|
169
263
|
|
|
@@ -172,7 +266,7 @@ module.exports = (args, cbk) => {
|
|
|
172
266
|
}
|
|
173
267
|
|
|
174
268
|
// Ignore attempts that do not include the specified in hop
|
|
175
|
-
if (!!args.in && inHop !==
|
|
269
|
+
if (!!args.in && inHop !== getInKey) {
|
|
176
270
|
return false;
|
|
177
271
|
}
|
|
178
272
|
|
|
@@ -375,14 +469,19 @@ module.exports = (args, cbk) => {
|
|
|
375
469
|
}],
|
|
376
470
|
|
|
377
471
|
// Title for fees paid
|
|
378
|
-
title: [
|
|
472
|
+
title: [
|
|
473
|
+
'validate',
|
|
474
|
+
'getInKey',
|
|
475
|
+
'getOutKey',
|
|
476
|
+
async ({getInKey, getOutKey}) =>
|
|
477
|
+
{
|
|
379
478
|
const [lnd] = args.lnds;
|
|
380
479
|
|
|
381
|
-
const into = !args.in ?
|
|
382
|
-
const out = !args.out ?
|
|
480
|
+
const into = !args.in ? {} : await getNodeAlias({lnd, id: getInKey});
|
|
481
|
+
const out = !args.out ? {} : await getNodeAlias({lnd, id: getOutKey});
|
|
383
482
|
|
|
384
|
-
const inPeer = !!args.in ? `in ${into
|
|
385
|
-
const outPeer = !!args.out ? `out ${out
|
|
483
|
+
const inPeer = !!args.in ? `in ${niceAlias(into)}` : '';
|
|
484
|
+
const outPeer = !!args.out ? `out ${niceAlias(out)}` : '';
|
|
386
485
|
|
|
387
486
|
return [title, outPeer, inPeer].filter(n => !!n).join(' ');
|
|
388
487
|
}],
|
|
@@ -395,6 +494,13 @@ module.exports = (args, cbk) => {
|
|
|
395
494
|
'title',
|
|
396
495
|
({description, rows, sum, title}, cbk) =>
|
|
397
496
|
{
|
|
497
|
+
const isRows = args.is_most_fees_table || args.is_most_forwarded_table;
|
|
498
|
+
|
|
499
|
+
// Add a title row when there is a restriction involved
|
|
500
|
+
if (!!isRows && (!!args.in || !!args.out)) {
|
|
501
|
+
rows.unshift([String(), title, String(), String()]);
|
|
502
|
+
}
|
|
503
|
+
|
|
398
504
|
return cbk(null, {description, rows, title, data: sum.fees});
|
|
399
505
|
}],
|
|
400
506
|
},
|
|
@@ -302,6 +302,8 @@ module.exports = (args, cbk) => {
|
|
|
302
302
|
args.bot.command('mempool', async ctx => {
|
|
303
303
|
try {
|
|
304
304
|
return await handleMempoolCommand({
|
|
305
|
+
from: ctx.message.from.id,
|
|
306
|
+
id: connectedId,
|
|
305
307
|
reply: n => ctx.reply(n, markdown),
|
|
306
308
|
request: args.request,
|
|
307
309
|
});
|
|
@@ -409,6 +411,8 @@ module.exports = (args, cbk) => {
|
|
|
409
411
|
await handleVersionCommand({
|
|
410
412
|
named,
|
|
411
413
|
version,
|
|
414
|
+
from: ctx.message.from.id,
|
|
415
|
+
id: connectedId,
|
|
412
416
|
request: args.request,
|
|
413
417
|
reply: n => ctx.reply(n, markdown),
|
|
414
418
|
});
|