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 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 oepn trade-secret serving
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
@@ -6,7 +6,7 @@ Commands for working with LND balances.
6
6
 
7
7
  Supported LND versions:
8
8
 
9
- - v0.11.0-beta to v0.14.2-beta
9
+ - v0.11.0-beta to v0.14.3-beta
10
10
 
11
11
  ## Install
12
12
 
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 <public_key>', 'Fees paid on routes in node with public key')
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 <public_key>', 'Fees paid on routes out peer with public key')
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
- decode(asLnurl(args.lnurl), bech32CharLimit);
61
+ parseUrl({url: args.lnurl});
67
62
  } catch (err) {
68
- return cbk([400, 'FailedToDecodeLnurlToPay']);
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 {words} = decode(asLnurl(args.lnurl), bech32CharLimit);
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) {
@@ -43,4 +43,4 @@ module.exports = ({ecp, hostname, k1, seed}) => {
43
43
  public_key: bufferAsHex(linkingKey.publicKey),
44
44
  signature: derEncodeSignature({signature}).encoded,
45
45
  };
46
- };
46
+ };
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.11.0",
39
+ "ln-service": "53.13.0",
40
40
  "ln-sync": "3.12.0",
41
- "ln-telegram": "3.21.1",
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-beta.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.7",
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.3.0"
87
+ "version": "12.5.0"
88
88
  }
@@ -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: ['getPayments', 'start', ({getPayments, start}, cbk) => {
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 !== args.out) {
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 !== args.in) {
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: ['validate', async ({}) => {
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 ? null : await getNodeAlias({lnd, id: args.in});
382
- const out = !args.out ? null : await getNodeAlias({lnd, id: 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.alias || into.id}` : '';
385
- const outPeer = !!args.out ? `out ${out.alias || out.id}` : '';
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
  });