balanceofsatoshis 12.0.1 → 12.2.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,17 @@
1
1
  # Versions
2
2
 
3
+ ## 12.2.0
4
+
5
+ - `swap`: Add utility command for testing submarine swaps
6
+
7
+ ## 12.1.0
8
+
9
+ - `lnurl`: Add support for the `withdraw` function to send a payment request
10
+
11
+ ## 12.0.2
12
+
13
+ - `open-balanced-channel`: Fix display issue where `undefined` was printed
14
+
3
15
  ## 12.0.1
4
16
 
5
17
  - `peers`: Add `capacity` as a variable for the `--filter` option
package/bos CHANGED
@@ -1023,6 +1023,8 @@ prog
1023
1023
  .command('lnurl', 'Collection of lnurl features')
1024
1024
  .argument('<function>', 'lnurl function', keys(lnurlFunctions))
1025
1025
  .help(`Functions: ${keys(lnurlFunctions).join(', ')}`)
1026
+ .help('lnurl pay will request a payment request from a service')
1027
+ .help('lnurl withdraw will create an invoice and send it to a service')
1026
1028
  .option('--avoid <avoid>', 'Avoid forwarding via node/chan/tag', REPEATABLE)
1027
1029
  .option('--max-fee <max_fee>', 'Maximum fee to pay', INT, 1337)
1028
1030
  .option('--max-paths <max_paths>', 'Maximum paths to use', INT, 1)
@@ -1032,7 +1034,7 @@ prog
1032
1034
  .action((args, options, logger) => {
1033
1035
  return new Promise(async (resolve, reject) => {
1034
1036
  try {
1035
- return lnurl.pay({
1037
+ return lnurl.manageLnurl({
1036
1038
  logger,
1037
1039
  ask: (n, cbk) => inquirer.prompt(n).then(n => cbk(n)),
1038
1040
  avoid: flatten([options.avoid].filter(n => !!n)),
@@ -1624,6 +1626,26 @@ prog
1624
1626
  });
1625
1627
  })
1626
1628
 
1629
+ // Submarine swap
1630
+ .command('swap', 'Trade off-chain coins for on-chain via submarine swap')
1631
+ .visible(false)
1632
+ .option('--node <node_name>', 'Node to use for swap')
1633
+ .action((args, options, logger) => {
1634
+ return new Promise(async (resolve, reject) => {
1635
+ try {
1636
+ return paidServices.manageSwap({
1637
+ logger,
1638
+ ask: (n, cbk) => inquirer.prompt([n]).then(res => cbk(res)),
1639
+ lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
1640
+ request: commands.simpleRequest,
1641
+ },
1642
+ responses.returnObject({exit, logger, reject, resolve}));
1643
+ } catch (err) {
1644
+ return logger.error({err}) && reject();
1645
+ }
1646
+ });
1647
+ })
1648
+
1627
1649
  // Create submarine swap to convert on-chain funds to off-chain
1628
1650
  .command('swap-in', 'Trade on-chain coins for off-chain via submarine swap')
1629
1651
  .argument('[amount]', 'Amount to receive', INT, 1e6)
@@ -8,7 +8,8 @@
8
8
  "payments": "payments"
9
9
  },
10
10
  "lnurlFunctions": {
11
- "pay": "pay"
11
+ "pay": "pay",
12
+ "withdraw": "withdraw"
12
13
  },
13
14
  "peerSortOptions": [
14
15
  "alias",
package/lnurl/index.js CHANGED
@@ -1,3 +1,3 @@
1
- const pay = require('./pay');
1
+ const manageLnurl = require('./manage_lnurl');
2
2
 
3
- module.exports = {pay};
3
+ module.exports = {manageLnurl};
@@ -0,0 +1,108 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {returnResult} = require('asyncjs-util');
3
+
4
+ const pay = require('./pay');
5
+ const withdraw = require('./withdraw');
6
+
7
+ const functionPay = 'pay';
8
+ const functionWithdraw = 'withdraw';
9
+ const {isArray} = Array;
10
+
11
+ /** Manage Lnurl functions
12
+
13
+ {
14
+ ask: <Ask Function>
15
+ avoid: [<Avoid Forwarding Through String>]
16
+ function: <Lnurl Function String>
17
+ request: <Request Function>
18
+ lnd: <Authenticated LND API Object>
19
+ lnurl: <Lnurl String>
20
+ logger: <Winston Logger Object>
21
+ [max_fee]: <Max Fee Tokens Number>
22
+ [max_paths]: <Maximum Paths Number>
23
+ out: [<Out Through Peer With Public Key Hex String>]
24
+ }
25
+
26
+ @returns via cbk or Promise
27
+ */
28
+ module.exports = (args, cbk) => {
29
+ return new Promise((resolve, reject) => {
30
+ return asyncAuto({
31
+ // Check arguments
32
+ validate: cbk => {
33
+ if (!args.ask) {
34
+ return cbk([400, 'ExpectedAskFunctionToManageLnurl']);
35
+ }
36
+
37
+ if (!isArray(args.avoid)) {
38
+ return cbk([400, 'ExpectedArrayOfAvoidsToManageLnurl']);
39
+ }
40
+
41
+ if (![functionPay, functionWithdraw].includes(args.function)) {
42
+ return cbk([400, 'ExpectedLnurlFunctionToManageLnurl']);
43
+ }
44
+
45
+ if (!args.lnd) {
46
+ return cbk([400, 'ExpectedLndToManageLnurl']);
47
+ }
48
+
49
+ if (!args.lnurl) {
50
+ return cbk([400, 'ExpectedUrlStringToManageLnurl']);
51
+ }
52
+
53
+ if (!args.logger) {
54
+ return cbk([400, 'ExpectedLoggerToManageLnurl']);
55
+ }
56
+
57
+ if (!isArray(args.out)) {
58
+ return cbk([400, 'ExpectedArrayOfOutPeersToManageLnurl']);
59
+ }
60
+
61
+ if (!args.request) {
62
+ return cbk([400, 'ExpectedRequestFunctionToManageLnurl']);
63
+ }
64
+
65
+ return cbk();
66
+ },
67
+
68
+ // Pay to lnurl
69
+ pay: ['validate', ({}, cbk) => {
70
+ // Exit early if not lnurl pay
71
+ if (args.function !== functionPay) {
72
+ return cbk();
73
+ }
74
+
75
+ return pay({
76
+ ask: args.ask,
77
+ avoid: args.avoid,
78
+ lnd: args.lnd,
79
+ lnurl: args.lnurl,
80
+ logger: args.logger,
81
+ max_fee: args.max_fee,
82
+ max_paths: args.max_paths,
83
+ out: args.out,
84
+ request: args.request,
85
+ },
86
+ cbk);
87
+ }],
88
+
89
+ // Withdraw from lnurl
90
+ withdraw: ['validate', ({}, cbk) => {
91
+ // Exit early if not lnurl withdraw
92
+ if (args.function !== functionWithdraw) {
93
+ return cbk();
94
+ }
95
+
96
+ return withdraw({
97
+ ask: args.ask,
98
+ lnd: args.lnd,
99
+ lnurl: args.lnurl,
100
+ logger: args.logger,
101
+ request: args.request,
102
+ },
103
+ cbk);
104
+ }],
105
+ },
106
+ returnResult({reject, resolve}, cbk));
107
+ });
108
+ };
package/lnurl/pay.js CHANGED
@@ -36,13 +36,13 @@ const wordsAsUtf8 = n => Buffer.from(bech32.fromWords(n)).toString('utf8');
36
36
  {
37
37
  ask: <Ask Function>
38
38
  avoid: [<Avoid Forwarding Through String>]
39
- fetch: <Fetch Function>
40
39
  lnd: <Authenticated LND API Object>
41
40
  lnurl: <Lnurl String>
42
41
  logger: <Winston Logger Object>
43
42
  max_fee: <Max Fee Tokens Number>
44
43
  max_paths: <Maximum Paths Number>
45
44
  out: [<Out Through Peer With Public Key Hex String>]
45
+ request: <Request Function>
46
46
  }
47
47
  */
48
48
  module.exports = (args, cbk) => {
@@ -58,10 +58,6 @@ module.exports = (args, cbk) => {
58
58
  return cbk([400, 'ExpectedAvoidArrayToGetPaymentRequestFromLnurl']);
59
59
  }
60
60
 
61
- if (!args.request) {
62
- return cbk([400, 'ExpectedRequestFunctionToGetLnurlData']);
63
- }
64
-
65
61
  if (!args.lnurl) {
66
62
  return cbk([400, 'ExpectedUrlToGetPaymentRequestFromLnurl']);
67
63
  }
@@ -96,6 +92,10 @@ module.exports = (args, cbk) => {
96
92
  return cbk([400, 'ExpectedArrayOfOutPeersToPayViaLnurl']);
97
93
  }
98
94
 
95
+ if (!args.request) {
96
+ return cbk([400, 'ExpectedRequestFunctionToGetLnurlData']);
97
+ }
98
+
99
99
  return cbk();
100
100
  },
101
101
 
@@ -0,0 +1,226 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {bech32} = require('bech32');
3
+ const {createInvoice} = require('ln-service');
4
+ const {returnResult} = require('asyncjs-util');
5
+
6
+ const {decode} = bech32;
7
+ const asLnurl = n => n.substring(n.startsWith('lightning:') ? 10 : 0);
8
+ const bech32CharLimit = 2000;
9
+ const errorStatus = 'ERROR';
10
+ const isNumber = n => !isNaN(n);
11
+ const minWithdrawable = 1;
12
+ const mtokensAsTokens = n => Math.floor(n / 1000);
13
+ const prefix = 'lnurl';
14
+ const {round} = Math;
15
+ const sslProtocol = 'https:';
16
+ const tag = 'withdrawRequest';
17
+ const tokensAsMillitokens = n => n * 1000;
18
+ const wordsAsUtf8 = n => Buffer.from(bech32.fromWords(n)).toString('utf8');
19
+
20
+ /** Withdraw from lnurl
21
+
22
+ {
23
+ ask: <Ask Function>
24
+ request: <Request Function>
25
+ lnd: <Authenticated LND API Object>
26
+ lnurl: <Lnurl String>
27
+ logger: <Winston Logger Object>
28
+ }
29
+
30
+ @returns via cbk or Promise
31
+ */
32
+ module.exports = (args, cbk) => {
33
+ return new Promise((resolve, reject) => {
34
+ return asyncAuto({
35
+ // Check arguments
36
+ validate: cbk => {
37
+ if (!args.ask) {
38
+ return cbk([400, 'ExpectedAskFunctionToWithdrawFromLnurl']);
39
+ }
40
+
41
+ if (!args.lnurl) {
42
+ return cbk([400, 'ExpectedUrlToWithdrawFromLnurl']);
43
+ }
44
+
45
+ try {
46
+ decode(asLnurl(args.lnurl), bech32CharLimit);
47
+ } catch (err) {
48
+ return cbk([400, 'FailedToDecodeLnurlToWithdraw', {err}]);
49
+ }
50
+
51
+ if (decode(asLnurl(args.lnurl), bech32CharLimit).prefix !== prefix) {
52
+ return cbk([400, 'ExpectedLnUrlPrefixToWithdraw']);
53
+ }
54
+
55
+ if (!args.lnd) {
56
+ return cbk([400, 'ExpectedLndToWithdrawFromLnurl']);
57
+ }
58
+
59
+ if (!args.logger) {
60
+ return cbk([400, 'ExpectedLoggerToWithdrawFromLnurl']);
61
+ }
62
+
63
+ if (!args.request) {
64
+ return cbk([400, 'ExpectedRequestFunctionToGetLnurlWithdrawData']);
65
+ }
66
+
67
+ return cbk();
68
+ },
69
+
70
+ // Get accepted terms from the encoded url
71
+ getTerms: ['validate', ({}, cbk) => {
72
+ const {words} = decode(asLnurl(args.lnurl), bech32CharLimit);
73
+
74
+ const url = wordsAsUtf8(words);
75
+
76
+ return args.request({url, json: true}, (err, r, json) => {
77
+ if (!!err) {
78
+ return cbk([503, 'FailureGettingLnUrlDataFromUrl', {err}]);
79
+ }
80
+
81
+ if (!json) {
82
+ return cbk([503, 'ExpectedJsonObjectReturnedInLnurlResponse']);
83
+ }
84
+
85
+ if (json.status === errorStatus) {
86
+ return cbk([503, 'LnurlWithdrawReturnedErr', {err: json.reason}]);
87
+ }
88
+
89
+ if (!json.callback) {
90
+ return cbk([503, 'ExpectedCallbackInLnurlResponseJson']);
91
+ }
92
+
93
+ try {
94
+ new URL(json.callback);
95
+ } catch (err) {
96
+ return cbk([503, 'ExpectedValidCallbackUrlInLnurlResponseJson']);
97
+ }
98
+
99
+ if ((new URL(json.callback)).protocol !== sslProtocol) {
100
+ return cbk([400, 'LnurlsThatSpecifyNonSslUrlsAreUnsupported']);
101
+ }
102
+
103
+ if (!json.k1) {
104
+ return cbk([503, 'ExpectedK1InLnurlResponseJson']);
105
+ }
106
+
107
+ if (!json.tag) {
108
+ return cbk([503, 'ExpectedTagInLnurlResponseJson']);
109
+ }
110
+
111
+ if (json.tag !== tag) {
112
+ return cbk([503, 'ExpectedTagToBeWithdrawRequestInLnurlResponse']);
113
+ }
114
+
115
+ if (!isNumber(json.minWithdrawable)) {
116
+ return cbk([503, 'ExpectedNumericValueForMinWithdrawable']);
117
+ }
118
+
119
+ if (!isNumber(json.maxWithdrawable)) {
120
+ return cbk([503, 'ExpectedNumericValueForMaxWithdrawable']);
121
+ }
122
+
123
+ if (json.minWithdrawable < minWithdrawable) {
124
+ return cbk([400, 'MinWithdrawableIsLowerThanSupportedValue']);
125
+ }
126
+
127
+ if (json.minWithdrawable > json.maxWithdrawable) {
128
+ return cbk([400, 'MinWithdrawableIsHigherThanMaxWithdrawable']);
129
+ }
130
+
131
+ return cbk(null, {
132
+ description: json.defaultDescription,
133
+ k1: json.k1,
134
+ max: mtokensAsTokens(json.maxWithdrawable),
135
+ min: mtokensAsTokens(json.minWithdrawable),
136
+ url: json.callback,
137
+ });
138
+ });
139
+ }],
140
+
141
+ // Ask amount for invoice
142
+ askAmount: ['getTerms', ({getTerms}, cbk) => {
143
+ const {max} = getTerms;
144
+ const {min} = getTerms;
145
+
146
+ return args.ask({
147
+ default: min,
148
+ message: `Amount to withdraw? (min: ${min}, max: ${max})`,
149
+ name: 'amount',
150
+ type: 'input',
151
+ validate: input => {
152
+ if (!input) {
153
+ return false;
154
+ }
155
+
156
+ // The amount should be numeric
157
+ if (!isNumber(input)) {
158
+ return false;
159
+ }
160
+
161
+ if (round(input) !== Number(input)) {
162
+ return 'Fractional amounts are not supported';
163
+ }
164
+
165
+ if (Number(input) > max) {
166
+ return `Service max withdrawable is ${max}, try a lower amount?`;
167
+ }
168
+
169
+ if (Number(input) < min) {
170
+ return `Service min withdrawable is ${min}, try higher amount?`;
171
+ }
172
+
173
+ return true;
174
+ },
175
+ },
176
+ ({amount}) => cbk(null, tokensAsMillitokens(Number(amount))));
177
+ }],
178
+
179
+ // Create a new payment request for withdrawl
180
+ createInvoice: ['askAmount', ({askAmount}, cbk) => {
181
+ return createInvoice({lnd: args.lnd, mtokens: askAmount}, cbk);
182
+ }],
183
+
184
+ // Send the withdraw request
185
+ withdraw: [
186
+ 'createInvoice',
187
+ 'getTerms',
188
+ ({createInvoice, getTerms}, cbk) =>
189
+ {
190
+ args.logger.info({invoice: createInvoice.request});
191
+
192
+ const {url} = getTerms;
193
+ const {k1} = getTerms;
194
+
195
+ const qs = {k1, pr: createInvoice.request};
196
+
197
+ return args.request({url, qs, json: true}, (err, r, json) => {
198
+ if (!!err) {
199
+ return cbk([503, 'UnexpectedErrorRequestingLnurlWithdraw', {err}]);
200
+ }
201
+
202
+ if (!json) {
203
+ return cbk([503, 'ExpectedJsonObjectReturnedInWithdrawResponse']);
204
+ }
205
+
206
+ if (!json.status) {
207
+ return cbk([503, 'ExpectedStatusInLnurlWithdrawResponseJson']);
208
+ }
209
+
210
+ if (json.status === errorStatus) {
211
+ return cbk([503, 'LnurlWithdrawReqFailed', {err: json.reason}]);
212
+ }
213
+
214
+ if (json.status !== 'OK') {
215
+ return cbk([503, 'ExpectedStatusToBeOkInLnurlResponseJson']);
216
+ }
217
+
218
+ args.logger.info({withdrawal_request_sent: true});
219
+
220
+ return cbk();
221
+ });
222
+ }],
223
+ },
224
+ returnResult({reject, resolve}, cbk));
225
+ });
226
+ };
@@ -7,7 +7,7 @@ const {getChannels} = require('ln-service');
7
7
  const {getIdentity} = require('ln-service');
8
8
  const {getNode} = require('ln-service');
9
9
  const moment = require('moment');
10
- const {parsePaymentRequest} = require('invoices');
10
+ const {parsePaymentRequest} = require('ln-service');
11
11
  const {payViaRoutes} = require('ln-service');
12
12
  const {returnResult} = require('asyncjs-util');
13
13
  const {signBytes} = require('ln-service');
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "url": "https://github.com/alexbosworth/balanceofsatoshis/issues"
11
11
  },
12
12
  "dependencies": {
13
- "@alexbosworth/caporal": "1.4.3",
13
+ "@alexbosworth/caporal": "1.4.4",
14
14
  "@alexbosworth/fiat": "1.0.2",
15
15
  "@alexbosworth/html2unicode": "1.1.5",
16
16
  "@alexbosworth/node-fetch": "2.6.2",
@@ -28,19 +28,18 @@
28
28
  "colorette": "2.0.16",
29
29
  "crypto-js": "4.1.1",
30
30
  "csv-parse": "5.0.4",
31
- "goldengate": "11.1.1",
31
+ "goldengate": "11.2.1",
32
32
  "grammy": "1.7.3",
33
33
  "hot-formula-parser": "4.0.0",
34
34
  "import-lazy": "4.0.0",
35
35
  "ini": "3.0.0",
36
36
  "inquirer": "8.2.2",
37
- "invoices": "2.0.5",
38
37
  "ln-accounting": "5.0.6",
39
38
  "ln-service": "53.11.0",
40
39
  "ln-sync": "3.12.0",
41
40
  "ln-telegram": "3.21.1",
42
41
  "moment": "2.29.2",
43
- "paid-services": "3.14.5",
42
+ "paid-services": "3.15.0",
44
43
  "probing": "2.0.5",
45
44
  "psbt": "2.0.1",
46
45
  "qrcode-terminal": "0.12.0",
@@ -53,6 +52,7 @@
53
52
  "description": "Lightning balance CLI",
54
53
  "devDependencies": {
55
54
  "@alexbosworth/tap": "15.0.11",
55
+ "invoices": "2.0.6",
56
56
  "ln-docker-daemons": "2.2.7",
57
57
  "mock-lnd": "1.4.1",
58
58
  "tiny-secp256k1": "2.2.1"
@@ -82,5 +82,5 @@
82
82
  "postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
83
83
  "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"
84
84
  },
85
- "version": "12.0.1"
85
+ "version": "12.2.0"
86
86
  }
@@ -3,7 +3,7 @@ const asyncFilter = require('async/filter');
3
3
  const {balancedOpenRequest} = require('paid-services');
4
4
  const {getInvoices} = require('ln-service');
5
5
  const {getPayment} = require('ln-service');
6
- const {parsePaymentRequest} = require('invoices');
6
+ const {parsePaymentRequest} = require('ln-service');
7
7
  const {returnResult} = require('asyncjs-util');
8
8
 
9
9
  const {balancedChannelKeyTypes} = require('./service_key_types');
@@ -2,7 +2,7 @@ const asyncAuto = require('async/auto');
2
2
  const {formatTokens} = require('ln-sync');
3
3
  const {getNodeAlias} = require('ln-sync');
4
4
  const {getPayment} = require('ln-service');
5
- const {parsePaymentRequest} = require('invoices');
5
+ const {parsePaymentRequest} = require('ln-service');
6
6
  const {payViaPaymentRequest} = require('ln-service');
7
7
  const {returnResult} = require('asyncjs-util');
8
8
 
@@ -8,7 +8,7 @@ const {getSwapOutTerms} = require('goldengate');
8
8
  const {lightningLabsSwapAuth} = require('goldengate');
9
9
  const {lightningLabsSwapService} = require('goldengate');
10
10
  const {paidMacaroon} = require('goldengate');
11
- const {parsePaymentRequest} = require('invoices');
11
+ const {parsePaymentRequest} = require('ln-service');
12
12
  const {payViaPaymentRequest} = require('ln-service');
13
13
  const {returnResult} = require('asyncjs-util');
14
14
  const {swapUserId} = require('goldengate');