balanceofsatoshis 11.2.1 → 11.5.1

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.5.1
4
+
5
+ - `--avoid`: Correct naming`OUT_FEE_RATE`/`IN_FEE_RATE` to `OPPOSITE_FEE_RATE`
6
+
7
+ ## Version 11.5.0
8
+
9
+ - `--avoid` Add `IN_FEE_RATE` and `OUT_FEE_RATE` to formulas
10
+ - `find`: Correct socket output for peer node lookups
11
+ - `telegram`: Correct week summary chain fee costs in `/costs`
12
+
13
+ ## Version 11.4.0
14
+
15
+ - `call`: Add `--param` flag to pass arguments directly instead of interactively
16
+
17
+ ## Version 11.3.0
18
+
19
+ - `credentials`: Allow specifying specific methods to allow in a credential
20
+
3
21
  ## Version 11.2.1
4
22
 
5
23
  - Improve support for LND v0.13.3
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) {
@@ -420,6 +422,7 @@ prog
420
422
  .help('Output encrypted remote access credentials. Use with "nodes --add"')
421
423
  .option('--cleartext', 'Output remote access credentials without encryption')
422
424
  .option('--days <days>', 'Expiration days for credentials', INT, 365)
425
+ .option('--method <method_name>', 'White-list specific method', REPEATABLE)
423
426
  .option('--node <node_name>', 'Get credentials for a saved node')
424
427
  .option('--nospend', 'Credentials do not include spending privileges')
425
428
  .option('--readonly', 'Credentials only include read permissions')
@@ -432,6 +435,7 @@ prog
432
435
  is_cleartext: options.cleartext,
433
436
  is_nospend: options.nospend,
434
437
  is_readonly: options.readonly,
438
+ methods: flatten([options.method].filter(n => !!n)),
435
439
  node: options.node,
436
440
  },
437
441
  responses.returnObject({logger, reject, resolve}));
@@ -1189,7 +1193,7 @@ prog
1189
1193
  .option('--max-fee-rate <max_fee_rate>', 'Max fee rate to pay')
1190
1194
  .option('--minutes <minutes>', 'Time-out route search after N minutes', INT)
1191
1195
  .option('--no-color', 'Mute all colors')
1192
- .option('--node <node_name>', 'Node to use for rebalance')
1196
+ .option('--node <node_name>', 'Saved node to use for rebalance')
1193
1197
  .option('--out <pubkey_or_alias>', 'Route out through a specific peer')
1194
1198
  .option('--out-filter <out_filter>', 'Filter outbound tag nodes', REPEATABLE)
1195
1199
  .option('--out-target-inbound <amount>', 'Balance up to inbound amount')
@@ -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 => {
@@ -0,0 +1,41 @@
1
+ const {noSpendPerms} = require('./constants');
2
+ const {permissionEntities} = require('./constants');
3
+
4
+ const readPerms = permissionEntities.map(entity => `${entity}:read`);
5
+
6
+ /** Derive restrictions for macaroon
7
+
8
+ {
9
+ [is_nospend]: <Restrict Credentials To Non-Spending Permissions Bool>
10
+ [is_readonly]: <Restrict Credentials To Read-Only Permissions Bool>
11
+ [methods]: [<Allow Specific Method String>]
12
+ }
13
+
14
+ @returns
15
+ {
16
+ [allow]: {
17
+ methods: [<Allow Specific Method String>]
18
+ permissions: [<Entity:Action String>]
19
+ }
20
+ }
21
+ */
22
+ module.exports = args => {
23
+ const methods = args.methods || [];
24
+
25
+ // Exit early when specific credentials are not requested
26
+ if (!args.is_readonly && !args.is_nospend && !methods.length) {
27
+ return {};
28
+ }
29
+
30
+ const permissions = [];
31
+
32
+ if (!!args.is_readonly) {
33
+ readPerms.forEach(n => permissions.push(n));
34
+ }
35
+
36
+ if (!!args.is_nospend) {
37
+ noSpendPerms.forEach(n => permissions.push(n));
38
+ }
39
+
40
+ return {allow: {methods, permissions}};
41
+ };
@@ -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
  });
@@ -16,6 +16,7 @@ const {pemAsDer} = require('./../encryption');
16
16
  is_nospend: <Restrict Credentials To Non-Spending Permissions Bool>
17
17
  is_readonly: <Restrict Credentials To Read-Only Permissions Bool>
18
18
  logger: <Winston Logger Object> ({info}) => ()
19
+ [methods]: [<Allow Specific Method String>]
19
20
  [node]: <Node Name String>
20
21
  }
21
22
 
@@ -81,6 +82,7 @@ module.exports = (args, cbk) => {
81
82
  is_nospend: args.is_nospend,
82
83
  is_readonly: args.is_readonly,
83
84
  logger: args.logger,
85
+ methods: args.methods,
84
86
  node: args.node,
85
87
  },
86
88
  cbk);
@@ -12,6 +12,7 @@ const {grantAccess} = require('ln-service');
12
12
  const {restrictMacaroon} = require('ln-service');
13
13
  const {returnResult} = require('asyncjs-util');
14
14
 
15
+ const credentialRestrictions = require('./credential_restrictions');
15
16
  const {decryptCiphertext} = require('./../encryption');
16
17
  const {derAsPem} = require('./../encryption');
17
18
  const getCert = require('./get_cert');
@@ -38,6 +39,7 @@ const socket = 'localhost:10009';
38
39
  [is_readonly]: <Restrict Credentials To Read-Only Permissions Bool>
39
40
  [key]: <Encrypt to Public Key DER Hex String>
40
41
  [logger]: <Winston Logger Object>
42
+ [methods]: [<Allow Specific Method String>]
41
43
  [node]: <Node Name String> // Defaults to default local mainnet node creds
42
44
  }
43
45
 
@@ -199,8 +201,14 @@ module.exports = (args, cbk) => {
199
201
  'macaroon',
200
202
  ({credentials, macaroon}, cbk) =>
201
203
  {
204
+ const {allow} = credentialRestrictions({
205
+ is_nospend: args.is_nospend,
206
+ is_readonly: args.is_readonly,
207
+ methods: args.methods,
208
+ });
209
+
202
210
  // Exit early when readonly credentials are not requested
203
- if (!args.is_readonly && !args.is_nospend) {
211
+ if (!allow) {
204
212
  return cbk(null, {macaroon});
205
213
  }
206
214
 
@@ -210,9 +218,12 @@ module.exports = (args, cbk) => {
210
218
  socket: credentials.socket,
211
219
  });
212
220
 
213
- const permissions = !!args.is_readonly ? readPerms : noSpendPerms;
214
-
215
- return grantAccess({lnd, permissions}, cbk);
221
+ return grantAccess({
222
+ lnd,
223
+ methods: allow.methods,
224
+ permissions: allow.permissions,
225
+ },
226
+ cbk);
216
227
  }],
217
228
 
218
229
  // Final credentials with encryption applied
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.1",
38
+ "ln-service": "52.11.1",
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.2.1"
83
+ "version": "11.5.1"
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,74 @@
1
+ const {test} = require('@alexbosworth/tap');
2
+
3
+ const method = require('./../../lnd/credential_restrictions');
4
+
5
+ const tests = [
6
+ {
7
+ args: {},
8
+ description: 'No restrictions results in no allow elements',
9
+ expected: {},
10
+ },
11
+ {
12
+ args: {is_nospend: true},
13
+ description: 'No spend results in nospend permissions',
14
+ expected: {
15
+ allow: {
16
+ methods: [],
17
+ permissions: [
18
+ 'address:read',
19
+ 'address:write',
20
+ 'info:read',
21
+ 'info:write',
22
+ 'invoices:read',
23
+ 'invoices:write',
24
+ 'macaroon:read',
25
+ 'message:read',
26
+ 'offchain:read',
27
+ 'onchain:read',
28
+ 'peers:read',
29
+ 'peers:write',
30
+ 'signer:read',
31
+ ],
32
+ },
33
+ },
34
+ },
35
+ {
36
+ args: {is_readonly: true},
37
+ description: 'Readonly results in read permissions',
38
+ expected: {
39
+ allow: {
40
+ methods: [],
41
+ permissions: [
42
+ 'address:read',
43
+ 'info:read',
44
+ 'invoices:read',
45
+ 'macaroon:read',
46
+ 'message:read',
47
+ 'offchain:read',
48
+ 'onchain:read',
49
+ 'peers:read',
50
+ 'signer:read',
51
+ ],
52
+ },
53
+ },
54
+ },
55
+ {
56
+ args: {methods: ['getWalletInfo']},
57
+ description: 'Readonly results in read permissions',
58
+ expected: {allow: {methods: ['getWalletInfo'], permissions: []}},
59
+ },
60
+ ];
61
+
62
+ tests.forEach(({args, description, error, expected}) => {
63
+ return test(description, async ({end, strictSame, throws}) => {
64
+ if (!!error) {
65
+ throws(() => method(args), new Error(error), 'Got expected error');
66
+ } else {
67
+ const res = method(args);
68
+
69
+ strictSame(res, expected, 'Got expected result');
70
+ }
71
+
72
+ return end();
73
+ });
74
+ });