balanceofsatoshis 11.3.0 → 11.6.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,23 @@
1
1
  # Versions
2
2
 
3
+ ## Version 11.6.0
4
+
5
+ - `clean-failed-payments`: Add method to clean out failed past payments
6
+
7
+ ## Version 11.5.1
8
+
9
+ - `--avoid`: Correct naming`OUT_FEE_RATE`/`IN_FEE_RATE` to `OPPOSITE_FEE_RATE`
10
+
11
+ ## Version 11.5.0
12
+
13
+ - `--avoid` Add `IN_FEE_RATE` and `OUT_FEE_RATE` to formulas
14
+ - `find`: Correct socket output for peer node lookups
15
+ - `telegram`: Correct week summary chain fee costs in `/costs`
16
+
17
+ ## Version 11.4.0
18
+
19
+ - `call`: Add `--param` flag to pass arguments directly instead of interactively
20
+
3
21
  ## Version 11.3.0
4
22
 
5
23
  - `credentials`: Allow specifying specific methods to allow in a credential
package/README.md CHANGED
@@ -533,6 +533,22 @@ functions and reference variables.
533
533
  There is a dynamic playground here where you can play with expressions:
534
534
  https://formulajs.info/functions/
535
535
 
536
+ ### `--avoid`
537
+
538
+ In `--avoid` flag commands like rebalance, a formula can be applied directionally:
539
+
540
+ `--avoid "fee_rate < 100/<PUBKEY>"` to avoid channels forwarding to the public key that
541
+ charge a fee rate under 100 PPM.
542
+
543
+ Available variables:
544
+
545
+ - `age`: Age of the channel vs the current height
546
+ - `base_fee`: Base fee to be charged to route
547
+ - `capacity`: Capacity of the channel
548
+ - `fee_rate`: PPM fee to be charged to route
549
+ - `height`: Absolute height of the channel
550
+ - `opposite_fee_rate`: PPM fee that is charged in the non-routing direction
551
+
536
552
  ### `amount`
537
553
 
538
554
  Formula amounts are supported in the following commands:
package/bos CHANGED
@@ -212,6 +212,7 @@ prog
212
212
  .help('If you do not specify a method it will list the supported methods')
213
213
  .argument('[method]', 'Method to call')
214
214
  .option('--node <node_name>', 'Saved node to use for call')
215
+ .option('--param <param>', 'query encoded name=value', REPEATABLE)
215
216
  .action((args, options, logger) => {
216
217
  return new Promise(async (resolve, reject) => {
217
218
  try {
@@ -220,6 +221,7 @@ prog
220
221
  ask: (n, cbk) => inquirer.prompt([n]).then(n => cbk(null, n)),
221
222
  lnd: (await lndForNode(logger, options.node)).lnd,
222
223
  method: args.method,
224
+ params: flatten([options.param].filter(n => !!n)),
223
225
  },
224
226
  responses.returnObject({logger, reject, resolve}));
225
227
  } catch (err) {
@@ -393,6 +395,26 @@ prog
393
395
  });
394
396
  })
395
397
 
398
+ // Clean out failed payments
399
+ .command('clean-failed-payments', 'Clean out past failed payment data')
400
+ .help('Remove old failed payment data for probes and other failed payments')
401
+ .option('--dryrun', 'Avoid actually deleting the failed payment record')
402
+ .option('--node <node_name>', 'Clean failed payments on a saved node')
403
+ .action((args, options, logger) => {
404
+ return new Promise(async (resolve, reject) => {
405
+ try {
406
+ return wallets.cleanFailedPayments({
407
+ logger,
408
+ is_dry_run: !!options.dryrun,
409
+ lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
410
+ },
411
+ responses.returnObject({logger, reject, resolve}));
412
+ } catch (err) {
413
+ return reject(err);
414
+ }
415
+ });
416
+ })
417
+
396
418
  // Determine the outcomes of channel closings
397
419
  .command('closed', 'Get the status of a channel closings')
398
420
  .help('Channel closes with chain-transaction derived resolution details')
@@ -1191,7 +1213,7 @@ prog
1191
1213
  .option('--max-fee-rate <max_fee_rate>', 'Max fee rate to pay')
1192
1214
  .option('--minutes <minutes>', 'Time-out route search after N minutes', INT)
1193
1215
  .option('--no-color', 'Mute all colors')
1194
- .option('--node <node_name>', 'Node to use for rebalance')
1216
+ .option('--node <node_name>', 'Saved node to use for rebalance')
1195
1217
  .option('--out <pubkey_or_alias>', 'Route out through a specific peer')
1196
1218
  .option('--out-filter <out_filter>', 'Filter outbound tag nodes', REPEATABLE)
1197
1219
  .option('--out-target-inbound <amount>', 'Balance up to inbound amount')
package/commands/api.json CHANGED
@@ -287,6 +287,22 @@
287
287
  {
288
288
  "method": "getConnectedWatchtowers"
289
289
  },
290
+ {
291
+ "arguments": [
292
+ {
293
+ "description": "Results limit",
294
+ "named": "limit",
295
+ "optional": true,
296
+ "type": "number"
297
+ },
298
+ {
299
+ "description": "Paging token",
300
+ "named": "token",
301
+ "optional": true
302
+ }
303
+ ],
304
+ "method": "getFailedPayments"
305
+ },
290
306
  {
291
307
  "method": "getFeeRates"
292
308
  },
@@ -1,3 +1,5 @@
1
+ const {parse} = require('querystring');
2
+
1
3
  const asyncAuto = require('async/auto');
2
4
  const asyncMapSeries = require('async/mapSeries');
3
5
  const lnService = require('ln-service');
@@ -5,6 +7,7 @@ const {returnResult} = require('asyncjs-util');
5
7
 
6
8
  const {calls} = require('./api');
7
9
 
10
+ const {assign} = Object;
8
11
  const isChannel = n => !!n && /^\d*x\d*x\d*$/.test(n);
9
12
  const isHash = n => !!n && /^[0-9A-F]{64}$/i.test(n);
10
13
  const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
@@ -19,12 +22,13 @@ const methodDetails = (calls, method) => calls.find(n => n.method === method);
19
22
  lnd: <Authenticated LND API Object>
20
23
  logger: <Winston Logger Object>
21
24
  [method]: <Method to Call String>
25
+ [params]: [<Querystring Encoded Parameter String>]
22
26
  }
23
27
 
24
28
  @returns via cbk or Promise
25
29
  <Result Object>
26
30
  */
27
- module.exports = ({ask, lnd, logger, method}, cbk) => {
31
+ module.exports = ({ask, lnd, logger, method, params}, cbk) => {
28
32
  return new Promise((resolve, reject) => {
29
33
  return asyncAuto({
30
34
  // Check arguments
@@ -76,12 +80,36 @@ module.exports = ({ask, lnd, logger, method}, cbk) => {
76
80
  return cbk(null, []);
77
81
  }
78
82
 
83
+ const parameters = (params || [])
84
+ .map(encoded => parse(encoded))
85
+ .reduce((sum, n) => assign(sum, n), {});
86
+
87
+ // Parameters must all match method parameters
88
+ const unknown = keys(parameters).find(param => {
89
+ return !arguments.find(({named}) => named === param);
90
+ });
91
+
92
+ if (!!unknown) {
93
+ return cbk([400, 'UnknownParameterProvided', {unknown}]);
94
+ }
95
+
79
96
  return asyncMapSeries(arguments, (argument, cbk) => {
97
+ const {named} = argument;
98
+
99
+ if (!!parameters[named] && argument.type === 'boolean') {
100
+ return cbk(null, {[named]: parameters[named] === 'true'});
101
+ }
102
+
103
+ if (parameters[named] !== undefined) {
104
+ return cbk(null, {[named]: parameters[named]});
105
+ }
106
+
80
107
  if (argument.type === 'boolean') {
81
108
  return ask({
82
109
  default: false,
83
- name: argument.named,
110
+ name: named,
84
111
  message: argument.description,
112
+ prefix: `[${named}]`,
85
113
  type: 'confirm',
86
114
  },
87
115
  cbk);
@@ -90,7 +118,8 @@ module.exports = ({ask, lnd, logger, method}, cbk) => {
90
118
  return ask({
91
119
  default: () => !!argument.optional ? String() : undefined,
92
120
  message: argument.description,
93
- name: argument.named,
121
+ name: named,
122
+ prefix: `[${named}]`,
94
123
  suffix: !!argument.optional ? ' (Optional)' : String(),
95
124
  type: argument.type || 'input',
96
125
  validate: input => {
@@ -114,7 +114,7 @@ module.exports = ({lnd, query}, cbk) => {
114
114
  color: res.color,
115
115
  features: res.features,
116
116
  public_key: getKey.value.public_key,
117
- sockets: res.sockets,
117
+ sockets: res.sockets.map(n => n.socket),
118
118
  updated_at: res.last_updated,
119
119
  }],
120
120
  });
package/package.json CHANGED
@@ -25,19 +25,19 @@
25
25
  "caporal": "1.4.0",
26
26
  "cbor": "8.0.2",
27
27
  "cert-info": "1.5.1",
28
- "colorette": "2.0.14",
28
+ "colorette": "2.0.16",
29
29
  "crypto-js": "4.1.1",
30
30
  "csv-parse": "4.16.3",
31
31
  "goldengate": "10.4.0",
32
32
  "hot-formula-parser": "4.0.0",
33
33
  "import-lazy": "4.0.0",
34
34
  "ini": "2.0.0",
35
- "inquirer": "8.1.5",
35
+ "inquirer": "8.2.0",
36
36
  "invoices": "2.0.0",
37
37
  "ln-accounting": "5.0.3",
38
- "ln-service": "52.10.2",
38
+ "ln-service": "52.12.0",
39
39
  "ln-sync": "2.0.2",
40
- "ln-telegram": "3.3.0",
40
+ "ln-telegram": "3.3.1",
41
41
  "moment": "2.29.1",
42
42
  "paid-services": "3.0.0",
43
43
  "probing": "1.3.6",
@@ -80,5 +80,5 @@
80
80
  "postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
81
81
  "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/fiat/*.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/wallets/*.js"
82
82
  },
83
- "version": "11.3.0"
83
+ "version": "11.6.0"
84
84
  }
@@ -223,6 +223,7 @@ module.exports = (args, cbk) => {
223
223
  age: getHeight.current_block_height - height,
224
224
  base_fee: Number(outPolicy.base_fee_mtokens) || Number(),
225
225
  fee_rate: outPolicy.fee_rate || Number(),
226
+ opposite_fee_rate: peerPolicy.fee_rate || Number(),
226
227
  });
227
228
 
228
229
  keys(variables).forEach(key => {
@@ -283,8 +284,9 @@ module.exports = (args, cbk) => {
283
284
  .map(({capacity, id, policies}) => {
284
285
  const height = heightFromId(id);
285
286
  const inPolicy = policies.find(n => n.public_key !== key);
287
+ const outPolicy = policies.find(n => n.public_key === key);
286
288
 
287
- if (!inPolicy) {
289
+ if (!inPolicy || !outPolicy) {
288
290
  return;
289
291
  }
290
292
 
@@ -299,6 +301,7 @@ module.exports = (args, cbk) => {
299
301
  age: getHeight.current_block_height - height,
300
302
  base_fee: Number(inPolicy.base_fee_mtokens) || Number(),
301
303
  fee_rate: inPolicy.fee_rate || Number(),
304
+ opposite_fee_rate: outPolicy.fee_rate || Number(),
302
305
  });
303
306
 
304
307
  keys(variables).forEach(key => {
@@ -323,7 +323,7 @@ module.exports = ({fs, id, limits, lnds, logger, payments, request}, cbk) => {
323
323
  });
324
324
  });
325
325
 
326
- bot.command('liquidity', async ({message, reply}) => {
326
+ bot.command('liquidity', async ctx => {
327
327
  try {
328
328
  await asyncRetry({
329
329
  errorFilter: err => {
@@ -335,13 +335,14 @@ module.exports = ({fs, id, limits, lnds, logger, payments, request}, cbk) => {
335
335
  },
336
336
  }, async () => {
337
337
  await handleLiquidityCommand({
338
- reply,
339
338
  request,
340
- from: message.from.id,
339
+ from: ctx.message.from.id,
341
340
  id: connectedId,
342
341
  key: apiKey.key,
343
342
  nodes: allNodes,
344
- text: message.text,
343
+ reply: ctx.reply,
344
+ text: ctx.message.text,
345
+ working: () => ctx.replyWithChatAction('typing'),
345
346
  });
346
347
  });
347
348
  } catch (err) {
@@ -392,6 +393,7 @@ module.exports = ({fs, id, limits, lnds, logger, payments, request}, cbk) => {
392
393
  id: connectedId,
393
394
  nodes: allNodes,
394
395
  reply: n => ctx.reply(n),
396
+ working: () => ctx.replyWithChatAction('typing'),
395
397
  });
396
398
  } catch (err) {
397
399
  logger.error({err});
@@ -0,0 +1,153 @@
1
+ const asyncAuto = require('async/auto');
2
+ const asyncEachSeries = require('async/eachSeries');
3
+ const asyncWhilst = require('async/whilst');
4
+ const {deletePayment} = require('ln-service');
5
+ const {getFailedPayments} = require('ln-service');
6
+ const {getWalletVersion} = require('ln-service');
7
+ const {returnResult} = require('asyncjs-util');
8
+
9
+ const defaultPagingLimit = 500;
10
+ const {isArray} = Array;
11
+ const unsupported = 501;
12
+
13
+ /** Clean out failed payments from the wallet
14
+
15
+ {
16
+ is_dry_run: <Avoid Actually Deleting Payments>
17
+ lnd: <Authenticated LND API Object>
18
+ logger: <Winston Logger Object>
19
+ }
20
+
21
+ @returns via cbk or Promise
22
+ */
23
+ module.exports = (args, cbk) => {
24
+ return new Promise((resolve, reject) => {
25
+ return asyncAuto({
26
+ // Check arguments
27
+ validate: cbk => {
28
+ if (!args.lnd) {
29
+ return cbk([400, 'ExpectedAuthenticatedLndApiToCleanPayments']);
30
+ }
31
+
32
+ if (!args.logger) {
33
+ return cbk([400, 'ExpectedWinstonLoggerToCleanFailedPayments']);
34
+ }
35
+
36
+ return cbk();
37
+ },
38
+
39
+ // Try and determine what wallet version this is
40
+ getWalletVersion: ['validate', ({}, cbk) => {
41
+ return getWalletVersion({lnd: args.lnd}, cbk);
42
+ }],
43
+
44
+ // Deny known unsupported versions
45
+ checkWalletVersion: ['getWalletVersion', ({getWalletVersion}, cbk) => {
46
+ // Exit early when not actually deleting
47
+ if (!!args.is_dry_run) {
48
+ return cbk();
49
+ }
50
+
51
+ switch (getWalletVersion.version) {
52
+ case '0.11.0-beta':
53
+ case '0.11.1-beta':
54
+ case '0.12.0-beta':
55
+ case '0.12.1-beta':
56
+ case '0.13.0-beta':
57
+ case '0.13.1-beta':
58
+ case '0.13.2-beta':
59
+ case '0.13.3-beta':
60
+ return cbk([501, 'CleanFailedPaymentsUnsupportedOnThisLndVersion']);
61
+
62
+ default:
63
+ return cbk();
64
+ }
65
+ }],
66
+
67
+ // Delete failed payments
68
+ fetchAndDelete: ['checkWalletVersion', ({}, cbk) => {
69
+ const ids = [];
70
+ let token;
71
+
72
+ if (!!args.is_dry_run) {
73
+ args.logger.info({finding_failed_payments: true});
74
+ } else {
75
+ args.logger.info({deleting_failed_payments: true});
76
+ }
77
+
78
+ return asyncWhilst(
79
+ cbk => cbk(null, token !== false),
80
+ cbk => {
81
+ return getFailedPayments({
82
+ token,
83
+ limit: !token ? defaultPagingLimit : undefined,
84
+ lnd: args.lnd,
85
+ },
86
+ (err, res) => {
87
+ if (!!err) {
88
+ return cbk([503, 'UnexpectedErrorGettingPayFailures', {err}]);
89
+ }
90
+
91
+ // Setting token to false will signal the end of paging
92
+ token = res.next || false;
93
+
94
+ // Exit early when there
95
+ if (!res.payments.length) {
96
+ const date = new Date().toISOString();
97
+
98
+ args.logger.info({searching_for_failed_payments: date});
99
+
100
+ return cbk();
101
+ }
102
+
103
+ res.payments.forEach(({id}) => ids.push(id));
104
+
105
+ // Delete each payment
106
+ return asyncEachSeries(res.payments, ({id}, cbk) => {
107
+ // Exit early when doing a dry run
108
+ if (!!args.is_dry_run) {
109
+ return cbk();
110
+ }
111
+
112
+ return deletePayment({id, lnd: args.lnd}, err => {
113
+ // LND 0.13.3 and below do not support deleting payments
114
+ if (!!isArray(err) && err.slice().shift() === unsupported) {
115
+ return cbk([501, 'LndVersionDoesNotSupportFailDeletion']);
116
+ }
117
+
118
+ if (!!err) {
119
+ return cbk([503, 'UnexpectedErrorDeletingPayment', {err}]);
120
+ }
121
+
122
+ return cbk();
123
+ });
124
+ },
125
+ err => {
126
+ if (!!err) {
127
+ return cbk(err);
128
+ }
129
+
130
+ args.logger.info({failed_payments: ids.length});
131
+
132
+ return cbk();
133
+ });
134
+ });
135
+ },
136
+ err => {
137
+ if (!!err) {
138
+ return cbk(err);
139
+ }
140
+
141
+ // Exit early when this is a test
142
+ if (!!args.is_dry_run) {
143
+ return cbk(null, {total_failed_payments_found: ids.length});
144
+ }
145
+
146
+ return cbk(null, {total_failed_payments_deleted: ids.length});
147
+ }
148
+ );
149
+ }],
150
+ },
151
+ returnResult({reject, resolve, of: 'fetchAndDelete'}, cbk));
152
+ });
153
+ };
package/wallets/index.js CHANGED
@@ -1,5 +1,11 @@
1
+ const cleanFailedPayments = require('./clean_failed_payments');
1
2
  const getReceivedChart = require('./get_received_chart');
2
3
  const getReport = require('./get_report');
3
4
  const unlockWallet = require('./unlock_wallet');
4
5
 
5
- module.exports = {getReceivedChart, getReport, unlockWallet};
6
+ module.exports = {
7
+ cleanFailedPayments,
8
+ getReceivedChart,
9
+ getReport,
10
+ unlockWallet,
11
+ };