balanceofsatoshis 12.5.1 → 12.6.2
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 +8 -0
- package/bos +1 -0
- package/commands/constants.json +1 -0
- package/lnurl/channel.js +295 -0
- package/lnurl/manage_lnurl.js +21 -1
- package/package.json +3 -3
- package/telegram/start_telegram_bot.js +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 12.6.2
|
|
4
|
+
|
|
5
|
+
- `telegram`: Avoid bot crash when receiving an AMP push payment
|
|
6
|
+
|
|
7
|
+
## 12.6.1
|
|
8
|
+
|
|
9
|
+
- `lnurl`: Add support for `channel` to request an inbound channel
|
|
10
|
+
|
|
3
11
|
## 12.5.1
|
|
4
12
|
|
|
5
13
|
- `open-balanced-channel`: Correct potential future regression in signing
|
package/bos
CHANGED
|
@@ -1024,6 +1024,7 @@ prog
|
|
|
1024
1024
|
.argument('<function>', 'lnurl function', keys(lnurlFunctions))
|
|
1025
1025
|
.help(`Functions: ${keys(lnurlFunctions).join(', ')}`)
|
|
1026
1026
|
.help('lnurl auth will request authorization')
|
|
1027
|
+
.help('lnurl channel will request an incoming payment channel')
|
|
1027
1028
|
.help('lnurl pay will request a payment request from a service')
|
|
1028
1029
|
.help('lnurl withdraw will create an invoice and send it to a service')
|
|
1029
1030
|
.option('--avoid <avoid>', 'Avoid forwarding via node/chan/tag', REPEATABLE)
|
package/commands/constants.json
CHANGED
package/lnurl/channel.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
const {isIP} = require('net');
|
|
2
|
+
|
|
3
|
+
const asyncAuto = require('async/auto');
|
|
4
|
+
const {bech32} = require('bech32');
|
|
5
|
+
const {addPeer} = require('ln-service');
|
|
6
|
+
const {getIdentity} = require('ln-service');
|
|
7
|
+
const {getNodeAlias} = require('ln-sync');
|
|
8
|
+
const {getPeers} = require('ln-service');
|
|
9
|
+
const {returnResult} = require('asyncjs-util');
|
|
10
|
+
|
|
11
|
+
const asLnurl = n => n.substring(n.startsWith('lightning:') ? 10 : 0);
|
|
12
|
+
const bech32CharLimit = 2000;
|
|
13
|
+
const {decode} = bech32;
|
|
14
|
+
const errorStatus = 'ERROR';
|
|
15
|
+
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
16
|
+
const okStatus = 'OK';
|
|
17
|
+
const parseUri = n => n.split('@');
|
|
18
|
+
const prefix = 'lnurl';
|
|
19
|
+
const sslProtocol = 'https:';
|
|
20
|
+
const tag = 'channelRequest';
|
|
21
|
+
const typeDefault = '0';
|
|
22
|
+
const types = [{name: 'Public', value: '0'}, {name: 'Private', value: '1'}];
|
|
23
|
+
const wordsAsUtf8 = n => Buffer.from(bech32.fromWords(n)).toString('utf8');
|
|
24
|
+
|
|
25
|
+
/** Request inbound channel from lnurl
|
|
26
|
+
|
|
27
|
+
{
|
|
28
|
+
ask: <Ask Function>
|
|
29
|
+
lnd: <Authenticated LND API Object>
|
|
30
|
+
lnurl: <Lnurl String>
|
|
31
|
+
logger: <Winston Logger Object>
|
|
32
|
+
request: <Request Function>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@returns via cbk or Promise
|
|
36
|
+
*/
|
|
37
|
+
module.exports = (args, cbk) => {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
return asyncAuto({
|
|
40
|
+
// Check arguments
|
|
41
|
+
validate: cbk => {
|
|
42
|
+
if (!args.ask) {
|
|
43
|
+
return cbk([400, 'ExpectedAskFunctionToRequestChannelFromLnurl']);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!args.lnurl) {
|
|
47
|
+
return cbk([400, 'ExpectedUrlToRequestChannelFromLnurl']);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
decode(asLnurl(args.lnurl), bech32CharLimit);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
return cbk([400, 'FailedToDecodeLnurlToRequestChannel', {err}]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (decode(asLnurl(args.lnurl), bech32CharLimit).prefix !== prefix) {
|
|
57
|
+
return cbk([400, 'ExpectedLnUrlPrefixToRequestChannel']);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!args.lnd) {
|
|
61
|
+
return cbk([400, 'ExpectedLndToRequestChannelFromLnurl']);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!args.logger) {
|
|
65
|
+
return cbk([400, 'ExpectedLoggerToRequestChannelFromLnurl']);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!args.request) {
|
|
69
|
+
return cbk([400, 'ExpectedRequestFunctionToGetLnurlRequestChannel']);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return cbk();
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// Get node identity public key
|
|
76
|
+
getIdentity: ['validate', ({}, cbk) => {
|
|
77
|
+
return getIdentity({lnd: args.lnd}, cbk);
|
|
78
|
+
}],
|
|
79
|
+
|
|
80
|
+
// Get the list of connected peers to determine if connection is needed
|
|
81
|
+
getPeers: ['validate', ({}, cbk) => getPeers({lnd: args.lnd}, cbk)],
|
|
82
|
+
|
|
83
|
+
// Get accepted terms from the encoded url
|
|
84
|
+
getTerms: ['validate', ({}, cbk) => {
|
|
85
|
+
const {words} = decode(asLnurl(args.lnurl), bech32CharLimit);
|
|
86
|
+
|
|
87
|
+
const url = wordsAsUtf8(words);
|
|
88
|
+
|
|
89
|
+
return args.request({url, json: true}, (err, r, json) => {
|
|
90
|
+
if (!!err) {
|
|
91
|
+
return cbk([503, 'FailureGettingLnurlDataFromUrl', {err}]);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!json) {
|
|
95
|
+
return cbk([503, 'ExpectedJsonObjectReturnedInLnurlResponse']);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (json.status === errorStatus) {
|
|
99
|
+
return cbk([503, 'UnexpectedServiceError', {err: json.reason}]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!json.callback) {
|
|
103
|
+
return cbk([503, 'ExpectedCallbackInLnurlResponseJson']);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
new URL(json.callback);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
return cbk([503, 'ExpectedValidLnurlResponseCallbackUrl', {err}]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if ((new URL(json.callback)).protocol !== sslProtocol) {
|
|
113
|
+
return cbk([400, 'LnurlsThatSpecifyNonSslUrlsAreUnsupported']);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!json.k1) {
|
|
117
|
+
return cbk([503, 'ExpectedK1InLnurlChannelResponseJson']);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!json.tag) {
|
|
121
|
+
return cbk([503, 'ExpectedTagInLnurlChannelResponseJson']);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (json.tag !== tag) {
|
|
125
|
+
return cbk([503, 'ExpectedTagToBeChannelRequestInLnurlResponse']);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!json.uri) {
|
|
129
|
+
return cbk([503, 'ExpectedUriInLnurlResponseJson']);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// uri: remote node address of form node_key@ip_address:port_number
|
|
133
|
+
const [id, socket] = parseUri(json.uri);
|
|
134
|
+
|
|
135
|
+
if (!isPublicKey(id)) {
|
|
136
|
+
return cbk([503, 'ExpectedValidPublicKeyIdInLnurlResponseJson']);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!socket) {
|
|
140
|
+
return cbk([503, 'ExpectedNetworkSocketAddressInLnurlResponse']);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return cbk(null, {id, socket, k1: json.k1, url: json.callback});
|
|
144
|
+
});
|
|
145
|
+
}],
|
|
146
|
+
|
|
147
|
+
// Get the node alias
|
|
148
|
+
getAlias: ['getTerms', ({getTerms}, cbk) => {
|
|
149
|
+
return getNodeAlias({id: getTerms.id, lnd: args.lnd}, cbk);
|
|
150
|
+
}],
|
|
151
|
+
|
|
152
|
+
// Connect to the peer returned in the lnurl response
|
|
153
|
+
connect: [
|
|
154
|
+
'getAlias',
|
|
155
|
+
'getPeers',
|
|
156
|
+
'getTerms',
|
|
157
|
+
({getAlias, getPeers, getTerms}, cbk) =>
|
|
158
|
+
{
|
|
159
|
+
// Exit early when the node is already connected
|
|
160
|
+
if (getPeers.peers.map(n => n.public_key).includes(getTerms.id)) {
|
|
161
|
+
return cbk();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
args.logger.info({
|
|
165
|
+
connecting_to: {
|
|
166
|
+
alias: getAlias.alias || undefined,
|
|
167
|
+
public_key: getTerms.id,
|
|
168
|
+
socket: getTerms.socket,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return addPeer({
|
|
173
|
+
lnd: args.lnd,
|
|
174
|
+
public_key: getTerms.id,
|
|
175
|
+
socket: getTerms.socket,
|
|
176
|
+
},
|
|
177
|
+
cbk);
|
|
178
|
+
}],
|
|
179
|
+
|
|
180
|
+
// Select private or public mode for the channel
|
|
181
|
+
askPrivate: ['connect', 'getTerms', ({getTerms}, cbk) => {
|
|
182
|
+
return args.ask({
|
|
183
|
+
choices: types,
|
|
184
|
+
default: typeDefault,
|
|
185
|
+
message: 'Channel type?',
|
|
186
|
+
name: 'priv',
|
|
187
|
+
type: 'list',
|
|
188
|
+
},
|
|
189
|
+
({priv}) => cbk(null, priv));
|
|
190
|
+
}],
|
|
191
|
+
|
|
192
|
+
// Confirm that an inbound channel should be requested
|
|
193
|
+
ok: ['askPrivate', 'getAlias', ({askPrivate, getAlias}, cbk) => {
|
|
194
|
+
const node = getAlias.alias || getAlias.id;
|
|
195
|
+
const type = !!askPrivate ? 'a private' : 'an';
|
|
196
|
+
|
|
197
|
+
return args.ask({
|
|
198
|
+
default: true,
|
|
199
|
+
message: `Request ${type} inbound channel from ${node}?`,
|
|
200
|
+
name: 'ok',
|
|
201
|
+
type: 'confirm',
|
|
202
|
+
},
|
|
203
|
+
({ok}) => cbk(null, ok));
|
|
204
|
+
}],
|
|
205
|
+
|
|
206
|
+
// Send a signal to cancel the channel request
|
|
207
|
+
sendCancelation: [
|
|
208
|
+
'getIdentity',
|
|
209
|
+
'getTerms',
|
|
210
|
+
'ok',
|
|
211
|
+
({channel, getTerms, ok}, cbk) =>
|
|
212
|
+
{
|
|
213
|
+
// Exit early when user wants to proceed with the channel request
|
|
214
|
+
if (!!ok) {
|
|
215
|
+
return cbk();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return args.request({
|
|
219
|
+
json: true,
|
|
220
|
+
qs: {
|
|
221
|
+
cancel: Number(!ok),
|
|
222
|
+
k1: getTerms.k1,
|
|
223
|
+
remoteid: getIdentity.public_key,
|
|
224
|
+
},
|
|
225
|
+
url: getTerms.url,
|
|
226
|
+
},
|
|
227
|
+
(err, r, json) => {
|
|
228
|
+
if (!!err) {
|
|
229
|
+
return cbk([503, 'UnexpectedErrorCancelingChannelRequest', {err}]);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!json) {
|
|
233
|
+
return cbk([503, 'ExpectedJsonObjectInCancelChannelResponse']);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (json.status === errorStatus) {
|
|
237
|
+
return cbk([503, 'ChannelCancelReturnedErr', {err: json.reason}]);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (json.status !== okStatus) {
|
|
241
|
+
return cbk([503, 'ExpectedOkStatusInCancelChannelResponse']);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return cbk([400, 'CanceledRequestForInboundChannel']);
|
|
245
|
+
});
|
|
246
|
+
}],
|
|
247
|
+
|
|
248
|
+
// Make the request to confirm a request for an inbound channel
|
|
249
|
+
sendConfirmation: [
|
|
250
|
+
'askPrivate',
|
|
251
|
+
'getIdentity',
|
|
252
|
+
'getTerms',
|
|
253
|
+
'ok',
|
|
254
|
+
({askPrivate, getIdentity, getTerms, ok}, cbk) =>
|
|
255
|
+
{
|
|
256
|
+
// Exit early when the user decides to cancel
|
|
257
|
+
if (!ok) {
|
|
258
|
+
return cbk();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return args.request({
|
|
262
|
+
json: true,
|
|
263
|
+
qs: {
|
|
264
|
+
k1: getTerms.k1,
|
|
265
|
+
private: askPrivate,
|
|
266
|
+
remoteid: getIdentity.public_key,
|
|
267
|
+
},
|
|
268
|
+
url: getTerms.url,
|
|
269
|
+
},
|
|
270
|
+
(err, r, json) => {
|
|
271
|
+
if (!!err) {
|
|
272
|
+
return cbk([503, 'UnexpectedErrorRequestingLnurlChannel', {err}]);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!json) {
|
|
276
|
+
return cbk([503, 'ExpectedJsonObjectReturnedInChannelResponse']);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (json.status === errorStatus) {
|
|
280
|
+
return cbk([503, 'ChannelRequestReturnedErr', {err: json.reason}]);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (json.status !== okStatus) {
|
|
284
|
+
return cbk([503, 'ExpectedOkStatusInChannelRequestResponse']);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
args.logger.info({requested_channel_open: true});
|
|
288
|
+
|
|
289
|
+
return cbk();
|
|
290
|
+
});
|
|
291
|
+
}],
|
|
292
|
+
},
|
|
293
|
+
returnResult({reject, resolve}, cbk));
|
|
294
|
+
});
|
|
295
|
+
};
|
package/lnurl/manage_lnurl.js
CHANGED
|
@@ -2,13 +2,16 @@ const asyncAuto = require('async/auto');
|
|
|
2
2
|
const {returnResult} = require('asyncjs-util');
|
|
3
3
|
|
|
4
4
|
const auth = require('./auth');
|
|
5
|
+
const channel = require('./channel');
|
|
5
6
|
const pay = require('./pay');
|
|
6
7
|
const withdraw = require('./withdraw');
|
|
7
8
|
|
|
8
9
|
const functionAuth = 'auth';
|
|
10
|
+
const functionChannel = 'channel';
|
|
9
11
|
const functionPay = 'pay';
|
|
10
12
|
const functionWithdraw = 'withdraw';
|
|
11
13
|
const {isArray} = Array;
|
|
14
|
+
const supportedFunctions = ['auth', 'channel', 'pay', 'withdraw'];
|
|
12
15
|
|
|
13
16
|
/** Manage Lnurl functions
|
|
14
17
|
|
|
@@ -40,7 +43,7 @@ module.exports = (args, cbk) => {
|
|
|
40
43
|
return cbk([400, 'ExpectedArrayOfAvoidsToManageLnurl']);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
if (!
|
|
46
|
+
if (!supportedFunctions.includes(args.function)) {
|
|
44
47
|
return cbk([400, 'ExpectedLnurlFunctionToManageLnurl']);
|
|
45
48
|
}
|
|
46
49
|
|
|
@@ -84,6 +87,23 @@ module.exports = (args, cbk) => {
|
|
|
84
87
|
cbk);
|
|
85
88
|
}],
|
|
86
89
|
|
|
90
|
+
// Request inbound channel
|
|
91
|
+
channel: ['validate', ({}, cbk) => {
|
|
92
|
+
// Exit early if not lnurl channel
|
|
93
|
+
if (args.function !== functionChannel) {
|
|
94
|
+
return cbk();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return channel({
|
|
98
|
+
ask: args.ask,
|
|
99
|
+
lnd: args.lnd,
|
|
100
|
+
lnurl: args.lnurl,
|
|
101
|
+
logger: args.logger,
|
|
102
|
+
request: args.request,
|
|
103
|
+
},
|
|
104
|
+
cbk);
|
|
105
|
+
}],
|
|
106
|
+
|
|
87
107
|
// Pay to lnurl
|
|
88
108
|
pay: ['validate', ({}, cbk) => {
|
|
89
109
|
// Exit early if not lnurl pay
|
package/package.json
CHANGED
|
@@ -36,11 +36,11 @@
|
|
|
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.15.0",
|
|
40
40
|
"ln-sync": "3.12.0",
|
|
41
41
|
"ln-telegram": "3.21.2",
|
|
42
42
|
"moment": "2.29.3",
|
|
43
|
-
"paid-services": "3.15.
|
|
43
|
+
"paid-services": "3.15.4",
|
|
44
44
|
"probing": "2.0.5",
|
|
45
45
|
"psbt": "2.0.1",
|
|
46
46
|
"qrcode-terminal": "0.12.0",
|
|
@@ -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.6.2"
|
|
88
88
|
}
|
|
@@ -58,6 +58,7 @@ const ask = (n, cbk) => inquirer.prompt(n).then(n => cbk(n));
|
|
|
58
58
|
const fileAsDoc = file => new InputFile(file.source, file.filename);
|
|
59
59
|
const fromName = node => `${node.alias} ${node.public_key.substring(0, 8)}`;
|
|
60
60
|
const {isArray} = Array;
|
|
61
|
+
const isHash = n => /^[0-9A-F]{64}$/i.test(n);
|
|
61
62
|
let isBotInit = false;
|
|
62
63
|
const isNumber = n => !isNaN(n);
|
|
63
64
|
const limit = 99999;
|
|
@@ -694,6 +695,11 @@ module.exports = (args, cbk) => {
|
|
|
694
695
|
subscriptions.push(sub);
|
|
695
696
|
|
|
696
697
|
sub.on('invoice_updated', invoice => {
|
|
698
|
+
// Exit early when an invoice has no associated hash
|
|
699
|
+
if (!isHash(invoice.id)) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
|
|
697
703
|
return postSettledInvoice({
|
|
698
704
|
from: node.from,
|
|
699
705
|
id: connectedId,
|