balanceofsatoshis 12.7.1 → 12.8.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 +2 -2
- package/lnurl/get_lnurl_request.js +75 -0
- package/lnurl/get_pay_request.js +96 -0
- package/lnurl/get_pay_terms.js +139 -0
- package/lnurl/index.js +3 -1
- package/lnurl/pay.js +32 -158
- package/network/push_payment.js +80 -28
- package/package.json +5 -5
- package/peers/open_channels.js +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 12.8.2
|
|
4
|
+
|
|
5
|
+
- `telegram`: Add safeguards to deal with errors on /graph command
|
|
6
|
+
|
|
7
|
+
## 12.8.0
|
|
8
|
+
|
|
9
|
+
- `send`: Add support for specifying a LNURL or lightning.address to send to
|
|
10
|
+
|
|
3
11
|
## 12.7.1
|
|
4
12
|
|
|
5
13
|
- `swap`: Add keysend support to swap for pushing swap requests
|
package/bos
CHANGED
|
@@ -1129,7 +1129,7 @@ prog
|
|
|
1129
1129
|
})
|
|
1130
1130
|
|
|
1131
1131
|
// Open a balanced channel with a peer
|
|
1132
|
-
.command('open-balanced-channel', 'Open
|
|
1132
|
+
.command('open-balanced-channel', 'Open channel with equal start balances')
|
|
1133
1133
|
.help('Remote node must be prepared to receive this type of channel')
|
|
1134
1134
|
.help('The remote node should also run this same command after you propose')
|
|
1135
1135
|
.help('Channel details are negotiated with keysend so that is also required')
|
|
@@ -1506,7 +1506,7 @@ prog
|
|
|
1506
1506
|
.help('Formulas supported in amount, and N*USD or N*EUR')
|
|
1507
1507
|
.help('Also supported in formulas: LIQUIDITY, INBOUND, OUTBOUND (with peer)')
|
|
1508
1508
|
.help('OUT_INBOUND, OUT_OUTBOUND (when specifying outbound peer)')
|
|
1509
|
-
.argument('<to>', 'Send to
|
|
1509
|
+
.argument('<to>', 'Send to public key, zero pay request, lnurl, ln.address')
|
|
1510
1510
|
.option('--amount <amount>', 'Amount to send to destination', STRING, '1')
|
|
1511
1511
|
.option('--avoid <avoid>', 'Avoid forwarding via node/chan/tag', REPEATABLE)
|
|
1512
1512
|
.option('--dryrun', 'Avoid actually sending funds')
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {returnResult} = require('asyncjs-util');
|
|
3
|
+
|
|
4
|
+
const getPayRequest = require('./get_pay_request');
|
|
5
|
+
const getPayTerms = require('./get_pay_terms');
|
|
6
|
+
const parseUrl = require('./parse_url');
|
|
7
|
+
|
|
8
|
+
const tokensAsMtokens = tokens => (BigInt(tokens) * BigInt(1e3)).toString();
|
|
9
|
+
|
|
10
|
+
/** Get a LNURL request for a given amount
|
|
11
|
+
|
|
12
|
+
{
|
|
13
|
+
lnurl: <LNUrl String>
|
|
14
|
+
request: <Request Function>
|
|
15
|
+
tokens: <Tokens Payment Request String>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@returns via cbk or Promise
|
|
19
|
+
{
|
|
20
|
+
destination: <Destination Public Key Hex String>
|
|
21
|
+
request: <BOLT 11 Payment Request String>
|
|
22
|
+
}
|
|
23
|
+
*/
|
|
24
|
+
module.exports = ({lnurl, request, tokens}, cbk) => {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
return asyncAuto({
|
|
27
|
+
// Check arguments
|
|
28
|
+
validate: cbk => {
|
|
29
|
+
try {
|
|
30
|
+
parseUrl({url: lnurl});
|
|
31
|
+
} catch (err) {
|
|
32
|
+
return cbk([400, err.message]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!request) {
|
|
36
|
+
return cbk([400, 'ExpectedRequestFunctionToGetLnurlRequest']);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!tokens) {
|
|
40
|
+
return cbk([400, 'ExpectedTokensToGetLnurlRequest']);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return cbk();
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Parse the LNURL into a regular url
|
|
47
|
+
url: ['validate', ({}, cbk) => {
|
|
48
|
+
return cbk(null, parseUrl({url: lnurl}).url);
|
|
49
|
+
}],
|
|
50
|
+
|
|
51
|
+
// Get accepted terms from the encoded url
|
|
52
|
+
getTerms: ['url', ({url}, cbk) => getPayTerms({request, url}, cbk)],
|
|
53
|
+
|
|
54
|
+
// Get payment request
|
|
55
|
+
getRequest: ['getTerms', ({getTerms}, cbk) => {
|
|
56
|
+
if (tokens > getTerms.max) {
|
|
57
|
+
return cbk([400, 'PaymentAmountAboveMaximum', {max: getTerms.max}]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (tokens < getTerms.min) {
|
|
61
|
+
return cbk([400, 'PaymentAmountBelowMinimum', {min: getTerms.min}]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return getPayRequest({
|
|
65
|
+
request,
|
|
66
|
+
hash: getTerms.hash,
|
|
67
|
+
mtokens: tokensAsMtokens(tokens),
|
|
68
|
+
url: getTerms.url,
|
|
69
|
+
},
|
|
70
|
+
cbk);
|
|
71
|
+
}],
|
|
72
|
+
},
|
|
73
|
+
returnResult({reject, resolve, of: 'getRequest'}, cbk));
|
|
74
|
+
});
|
|
75
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {parsePaymentRequest} = require('ln-service');
|
|
3
|
+
const {returnResult} = require('asyncjs-util');
|
|
4
|
+
|
|
5
|
+
const errorStatus = 'ERROR';
|
|
6
|
+
|
|
7
|
+
/** Get a payment request for a LNURL
|
|
8
|
+
|
|
9
|
+
{
|
|
10
|
+
hash: <Hash Hex String>
|
|
11
|
+
mtokens: <Millitokens For Payment Request String>
|
|
12
|
+
request: <Request Function>
|
|
13
|
+
url: <URL String>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@returns via cbk or Promise
|
|
17
|
+
{
|
|
18
|
+
destination: <Destination Public Key Hex String>
|
|
19
|
+
request: <BOLT 11 Payment Request String>
|
|
20
|
+
}
|
|
21
|
+
*/
|
|
22
|
+
module.exports = ({hash, mtokens, request, url}, cbk) => {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
return asyncAuto({
|
|
25
|
+
// Check arguments
|
|
26
|
+
validate: cbk => {
|
|
27
|
+
if (!hash) {
|
|
28
|
+
return cbk([400, 'ExpectedDescriptionHashToGetLnurlPayRequest']);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!mtokens) {
|
|
32
|
+
return cbk([400, 'ExpectedMillitokensToGetLnurlPayRequest']);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!request) {
|
|
36
|
+
return cbk([400, 'ExpectedRequestFunctionToGetLnurlPayRequest']);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!url) {
|
|
40
|
+
return cbk([400, 'ExpectedUrlToGetLnurlPayRequest']);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return cbk();
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Get the payment request
|
|
47
|
+
getRequest: ['validate', ({}, cbk) => {
|
|
48
|
+
const qs = {amount: mtokens};
|
|
49
|
+
|
|
50
|
+
return request({qs, url, json: true}, (err, r, json) => {
|
|
51
|
+
if (!!err) {
|
|
52
|
+
return cbk([503, 'FailedToGetPaymentRequestFromService', {err}]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!json) {
|
|
56
|
+
return cbk([503, 'ServiceFailedToReturnPayReqJson']);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (json.status === errorStatus) {
|
|
60
|
+
return cbk([503, 'ServiceReturnedError', {err: json.reason}]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!json.pr) {
|
|
64
|
+
return cbk([503, 'ExpectedPaymentRequestFromService']);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
parsePaymentRequest({request: json.pr});
|
|
69
|
+
} catch (err) {
|
|
70
|
+
return cbk([503, 'FailedToParseReturnedPaymentRequest', {err}]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const request = parsePaymentRequest({request: json.pr});
|
|
74
|
+
|
|
75
|
+
if (request.description_hash !== hash) {
|
|
76
|
+
return cbk([503, 'ServiceReturnedInvalidPaymentDescriptionHash']);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (request.is_expired) {
|
|
80
|
+
return cbk([503, 'ServiceReturnedExpiredPaymentRequest']);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (request.mtokens !== mtokens) {
|
|
84
|
+
return cbk([503, 'ServiceReturnedIncorrectInvoiceAmount']);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return cbk(null, {
|
|
88
|
+
destination: request.destination,
|
|
89
|
+
request: json.pr,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}],
|
|
93
|
+
},
|
|
94
|
+
returnResult({reject, resolve, of: 'getRequest'}, cbk));
|
|
95
|
+
});
|
|
96
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const {createHash} = require('crypto');
|
|
2
|
+
|
|
3
|
+
const asyncAuto = require('async/auto');
|
|
4
|
+
const {returnResult} = require('asyncjs-util');
|
|
5
|
+
|
|
6
|
+
const {isArray} = Array;
|
|
7
|
+
const isNumber = n => !isNaN(n);
|
|
8
|
+
const lowestSendableValue = 1000;
|
|
9
|
+
const {max} = Math;
|
|
10
|
+
const minMaxSendable = 1000;
|
|
11
|
+
const minMinSendable = 1;
|
|
12
|
+
const mtokensAsTokens = n => Math.floor(n / 1000);
|
|
13
|
+
const {parse} = JSON;
|
|
14
|
+
const payRequestTag = 'payRequest';
|
|
15
|
+
const sha256 = n => createHash('sha256').update(n).digest().toString('hex');
|
|
16
|
+
const sslProtocol = 'https:';
|
|
17
|
+
const textPlain = 'text/plain';
|
|
18
|
+
const utf8AsBuffer = utf8 => Buffer.from(utf8, 'utf8');
|
|
19
|
+
|
|
20
|
+
/** Get payment terms
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
request: <Request Function>
|
|
24
|
+
url: <URL String>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@returns via cbk or Promise
|
|
28
|
+
{
|
|
29
|
+
description: <Payment Description String>
|
|
30
|
+
hash: <Expected Description Hash Hex String>
|
|
31
|
+
max: <Maximum Tokens Number>
|
|
32
|
+
min: <Minimum Tokens Number>
|
|
33
|
+
url: <Callback URL String>
|
|
34
|
+
}
|
|
35
|
+
*/
|
|
36
|
+
module.exports = ({request, url}, cbk) => {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
return asyncAuto({
|
|
39
|
+
// Check arguments
|
|
40
|
+
validate: cbk => {
|
|
41
|
+
if (!request) {
|
|
42
|
+
return cbk([400, 'ExpectedRequestFunctionToGetPayTerms']);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!url) {
|
|
46
|
+
return cbk([400, 'ExpectedUrlToGetPayTerms']);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return cbk();
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Get payment terms
|
|
53
|
+
getTerms: ['validate', ({}, cbk) => {
|
|
54
|
+
return request({url, json: true}, (err, r, json) => {
|
|
55
|
+
if (!!err) {
|
|
56
|
+
return cbk([503, 'FailureGettingLnUrlDataFromUrl', {err}]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!json) {
|
|
60
|
+
return cbk([503, 'ExpectedJsonObjectReturnedInLnurlResponse']);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!json.callback) {
|
|
64
|
+
return cbk([503, 'ExpectedCallbackInLnurlResponseJson']);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
new URL(json.callback);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
return cbk([503, 'ExpectedValidCallbackUrlInLnurlResponseJson']);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if ((new URL(json.callback)).protocol !== sslProtocol) {
|
|
74
|
+
return cbk([400, 'LnurlsThatSpecifyNonSslUrlsAreUnsupported']);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!isNumber(json.maxSendable)) {
|
|
78
|
+
return cbk([503, 'ExpectedNumericValueForMaxSendable']);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!json.maxSendable) {
|
|
82
|
+
return cbk([503, 'ExpectedNonZeroMaxSendableInLnurlResponse']);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (json.maxSendable < minMaxSendable) {
|
|
86
|
+
return cbk([400, 'MaxSendableValueIsLowerThanSupportedValue']);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!json.metadata) {
|
|
90
|
+
return cbk([503, 'ExpectedLnUrlMetadataInLnurlResponse']);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
parse(json.metadata);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
return cbk([503, 'ExpectedValidMetadataInLnurlResponse']);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!isArray(parse(json.metadata))) {
|
|
100
|
+
return cbk([503, 'ExpectedMetadataArrayInLnurlResponse', json]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const [, description] = parse(json.metadata)
|
|
104
|
+
.filter(isArray)
|
|
105
|
+
.find(([entry, text]) => entry === textPlain && !!text);
|
|
106
|
+
|
|
107
|
+
if (!description) {
|
|
108
|
+
return cbk([503, 'ExpectedTextPlainEntryInLnurlResponse']);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!isNumber(json.minSendable)) {
|
|
112
|
+
return cbk([503, 'ExpectedNumericValueForMinSendable']);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (json.minSendable < minMinSendable) {
|
|
116
|
+
return cbk([503, 'ExpectedHigherMinSendableValueInLnurlResponse']);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (json.minSendable > json.maxSendable) {
|
|
120
|
+
return cbk([503, 'ExpectedMaxSendableMoreThanMinSendable']);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (json.tag !== payRequestTag) {
|
|
124
|
+
return cbk([503, 'ExpectedPaymentRequestTagInLnurlResponse']);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return cbk(null, {
|
|
128
|
+
description,
|
|
129
|
+
hash: sha256(utf8AsBuffer(json.metadata)),
|
|
130
|
+
max: mtokensAsTokens(json.maxSendable),
|
|
131
|
+
min: mtokensAsTokens(max(lowestSendableValue, json.minSendable)),
|
|
132
|
+
url: json.callback,
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}],
|
|
136
|
+
},
|
|
137
|
+
returnResult({reject, resolve, of: 'getTerms'}, cbk));
|
|
138
|
+
});
|
|
139
|
+
};
|
package/lnurl/index.js
CHANGED
package/lnurl/pay.js
CHANGED
|
@@ -1,44 +1,35 @@
|
|
|
1
|
-
const {createHash} = require('crypto');
|
|
2
|
-
|
|
3
1
|
const asyncAuto = require('async/auto');
|
|
4
2
|
const {getNodeAlias} = require('ln-sync');
|
|
5
3
|
const moment = require('moment');
|
|
6
4
|
const {returnResult} = require('asyncjs-util');
|
|
7
5
|
const {parsePaymentRequest} = require('ln-service');
|
|
8
6
|
|
|
7
|
+
const getPayRequest = require('./get_pay_request');
|
|
8
|
+
const getPayTerms = require('./get_pay_terms');
|
|
9
9
|
const parseUrl = require('./parse_url');
|
|
10
10
|
const {pay} = require('./../network');
|
|
11
11
|
|
|
12
|
-
const errorStatus = 'ERROR';
|
|
13
12
|
const {isArray} = Array;
|
|
14
13
|
const isNumber = n => !isNaN(n);
|
|
15
|
-
const lowestSendableValue = 1000;
|
|
16
|
-
const {max} = Math;
|
|
17
|
-
const minMaxSendable = 1000;
|
|
18
|
-
const minMinSendable = 1;
|
|
19
|
-
const mtokensAsTokens = n => Math.floor(n / 1000);
|
|
20
|
-
const {parse} = JSON;
|
|
21
|
-
const payRequestTag = 'payRequest';
|
|
22
14
|
const {round} = Math;
|
|
23
|
-
const sha256 = n => createHash('sha256').update(n).digest().toString('hex');
|
|
24
|
-
const sslProtocol = 'https:';
|
|
25
|
-
const textPlain = 'text/plain';
|
|
26
15
|
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
27
16
|
const tokensAsMillitokens = n => n * 1000;
|
|
28
|
-
const utf8AsBuffer = utf8 => Buffer.from(utf8, 'utf8');
|
|
29
17
|
|
|
30
18
|
/** Pay to lnurl
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
ask: <Ask Function>
|
|
22
|
+
avoid: [<Avoid Forwarding Through String>]
|
|
23
|
+
lnd: <Authenticated LND API Object>
|
|
24
|
+
lnurl: <Lnurl String>
|
|
25
|
+
logger: <Winston Logger Object>
|
|
26
|
+
max_fee: <Max Fee Tokens Number>
|
|
27
|
+
max_paths: <Maximum Paths Number>
|
|
28
|
+
out: [<Out Through Peer With Public Key Hex String>]
|
|
29
|
+
request: <Request Function>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@returns via cbk or Promise
|
|
42
33
|
*/
|
|
43
34
|
module.exports = (args, cbk) => {
|
|
44
35
|
return new Promise((resolve, reject) => {
|
|
@@ -92,89 +83,11 @@ module.exports = (args, cbk) => {
|
|
|
92
83
|
|
|
93
84
|
// Get accepted terms from the encoded url
|
|
94
85
|
getTerms: ['validate', ({}, cbk) => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (!json) {
|
|
103
|
-
return cbk([503, 'ExpectedJsonObjectReturnedInLnurlResponse']);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (!json.callback) {
|
|
107
|
-
return cbk([503, 'ExpectedCallbackInLnurlResponseJson']);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
new URL(json.callback);
|
|
112
|
-
} catch (err) {
|
|
113
|
-
return cbk([503, 'ExpectedValidCallbackUrlInLnurlResponseJson']);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if ((new URL(json.callback)).protocol !== sslProtocol) {
|
|
117
|
-
return cbk([400, 'LnurlsThatSpecifyNonSslUrlsAreUnsupported']);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!isNumber(json.maxSendable)) {
|
|
121
|
-
return cbk([503, 'ExpectedNumericValueForMaxSendable']);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (!json.maxSendable) {
|
|
125
|
-
return cbk([503, 'ExpectedNonZeroMaxSendableInLnurlResponse']);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (json.maxSendable < minMaxSendable) {
|
|
129
|
-
return cbk([400, 'MaxSendableValueIsLowerThanSupportedValue']);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!json.metadata) {
|
|
133
|
-
return cbk([503, 'ExpectedLnUrlMetadataInLnurlResponse']);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
parse(json.metadata);
|
|
138
|
-
} catch (err) {
|
|
139
|
-
return cbk([503, 'ExpectedValidMetadataInLnurlResponse']);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (!isArray(parse(json.metadata))) {
|
|
143
|
-
return cbk([503, 'ExpectedMetadataArrayInLnurlResponse', json]);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const [, description] = parse(json.metadata)
|
|
147
|
-
.filter(isArray)
|
|
148
|
-
.find(([entry, text]) => entry === textPlain && !!text);
|
|
149
|
-
|
|
150
|
-
if (!description) {
|
|
151
|
-
return cbk([503, 'ExpectedTextPlainEntryInLnurlResponse']);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (!isNumber(json.minSendable)) {
|
|
155
|
-
return cbk([503, 'ExpectedNumericValueForMinSendable']);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (json.minSendable < minMinSendable) {
|
|
159
|
-
return cbk([503, 'ExpectedHigherMinSendableValueInLnurlResponse']);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (json.minSendable > json.maxSendable) {
|
|
163
|
-
return cbk([503, 'ExpectedMaxSendableMoreThanMinSendable']);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (json.tag !== payRequestTag) {
|
|
167
|
-
return cbk([503, 'ExpectedPaymentRequestTagInLnurlResponse']);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return cbk(null, {
|
|
171
|
-
description,
|
|
172
|
-
hash: sha256(utf8AsBuffer(json.metadata)),
|
|
173
|
-
max: mtokensAsTokens(json.maxSendable),
|
|
174
|
-
min: mtokensAsTokens(max(lowestSendableValue, json.minSendable)),
|
|
175
|
-
url: json.callback,
|
|
176
|
-
});
|
|
177
|
-
});
|
|
86
|
+
return getPayTerms({
|
|
87
|
+
request: args.request,
|
|
88
|
+
url: parseUrl({url: args.lnurl}).url,
|
|
89
|
+
},
|
|
90
|
+
cbk);
|
|
178
91
|
}],
|
|
179
92
|
|
|
180
93
|
// Ask the user for how much they want to send
|
|
@@ -217,57 +130,18 @@ module.exports = (args, cbk) => {
|
|
|
217
130
|
|
|
218
131
|
// Get payment request
|
|
219
132
|
getRequest: ['askAmount', 'getTerms', ({askAmount, getTerms}, cbk) => {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (!json) {
|
|
229
|
-
return cbk([503, 'ServiceFailedToReturnPayReqJson']);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (json.status === errorStatus) {
|
|
233
|
-
return cbk([503, 'ServiceReturnedError', {err: json.reason}]);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (!json.pr) {
|
|
237
|
-
return cbk([503, 'ExpectedPaymentRequestFromService']);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
try {
|
|
241
|
-
parsePaymentRequest({request: json.pr});
|
|
242
|
-
} catch (err) {
|
|
243
|
-
return cbk([503, 'FailedToParseReturnedPaymentRequest', {err}]);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const request = parsePaymentRequest({request: json.pr});
|
|
247
|
-
|
|
248
|
-
if (request.description_hash !== getTerms.hash) {
|
|
249
|
-
return cbk([503, 'ServiceReturnedInvalidPaymentDescriptionHash']);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (request.is_expired) {
|
|
253
|
-
return cbk([503, 'ServiceReturnedExpiredPaymentRequest']);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (request.mtokens !== askAmount.toString()) {
|
|
257
|
-
return cbk([503, 'ServiceReturnedIncorrectInvoiceAmount']);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return cbk(null, json.pr);
|
|
261
|
-
});
|
|
133
|
+
return getPayRequest({
|
|
134
|
+
hash: getTerms.hash,
|
|
135
|
+
mtokens: askAmount.toString(),
|
|
136
|
+
request: args.request,
|
|
137
|
+
url: getTerms.url,
|
|
138
|
+
},
|
|
139
|
+
cbk);
|
|
262
140
|
}],
|
|
263
141
|
|
|
264
142
|
// Get the destination node alias
|
|
265
143
|
getAlias: ['getRequest', ({getRequest}, cbk) => {
|
|
266
|
-
return getNodeAlias({
|
|
267
|
-
id: parsePaymentRequest({request: getRequest}).destination,
|
|
268
|
-
lnd: args.lnd,
|
|
269
|
-
},
|
|
270
|
-
cbk);
|
|
144
|
+
return getNodeAlias({id: getRequest.destination, lnd: args.lnd}, cbk);
|
|
271
145
|
}],
|
|
272
146
|
|
|
273
147
|
// Confirm payment
|
|
@@ -277,12 +151,12 @@ module.exports = (args, cbk) => {
|
|
|
277
151
|
'getTerms',
|
|
278
152
|
({getAlias, getRequest, getTerms}, cbk) =>
|
|
279
153
|
{
|
|
280
|
-
const details = parsePaymentRequest({request: getRequest});
|
|
154
|
+
const details = parsePaymentRequest({request: getRequest.request});
|
|
281
155
|
|
|
282
156
|
args.logger.info({
|
|
283
157
|
amount: details.safe_tokens,
|
|
284
158
|
description: getTerms.description,
|
|
285
|
-
payment_request: getRequest,
|
|
159
|
+
payment_request: getRequest.request,
|
|
286
160
|
expires: moment(details.expires_at).fromNow(),
|
|
287
161
|
});
|
|
288
162
|
|
|
@@ -312,7 +186,7 @@ module.exports = (args, cbk) => {
|
|
|
312
186
|
max_fee: args.max_fee,
|
|
313
187
|
max_paths: args.max_paths,
|
|
314
188
|
out: args.out,
|
|
315
|
-
request: getRequest,
|
|
189
|
+
request: getRequest.request,
|
|
316
190
|
},
|
|
317
191
|
cbk);
|
|
318
192
|
}],
|
package/network/push_payment.js
CHANGED
|
@@ -10,8 +10,10 @@ const {parsePaymentRequest} = require('ln-service');
|
|
|
10
10
|
const {returnResult} = require('asyncjs-util');
|
|
11
11
|
|
|
12
12
|
const {getIgnores} = require('./../routing');
|
|
13
|
+
const getLnurlRequest = require('./../lnurl/get_lnurl_request');
|
|
13
14
|
const {getTags} = require('./../tags');
|
|
14
15
|
const {parseAmount} = require('./../display');
|
|
16
|
+
const parseUrl = require('./../lnurl/parse_url');
|
|
15
17
|
const probeDestination = require('./probe_destination');
|
|
16
18
|
|
|
17
19
|
const coins = ['BTC', 'LTC'];
|
|
@@ -153,28 +155,39 @@ module.exports = (args, cbk) => {
|
|
|
153
155
|
// Payment details
|
|
154
156
|
payment: ['validate', ({}, cbk) => {
|
|
155
157
|
try {
|
|
156
|
-
const {
|
|
157
|
-
|
|
158
|
-
});
|
|
158
|
+
const {url} = parseUrl({url: args.destination});
|
|
159
|
+
|
|
160
|
+
return cbk(null, {lnurl: args.destination});
|
|
161
|
+
} catch (err) {
|
|
162
|
+
// Ignore errors, destination isn't a LNURL
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const details = parsePaymentRequest({request: args.destination});
|
|
167
|
+
|
|
168
|
+
const {destination, mtokens} = details;
|
|
159
169
|
|
|
160
170
|
if (!!BigInt(mtokens)) {
|
|
161
171
|
return cbk([400, 'ExpectedZeroAmountPayRequestToSendFunds']);
|
|
162
172
|
}
|
|
163
173
|
|
|
164
174
|
return cbk(null, {destination, request: args.destination});
|
|
165
|
-
} catch (
|
|
166
|
-
|
|
167
|
-
lnd: args.lnd,
|
|
168
|
-
query: args.destination,
|
|
169
|
-
},
|
|
170
|
-
(err, res) => {
|
|
171
|
-
if (!!err) {
|
|
172
|
-
return cbk(err);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return cbk(null, {destination: res.public_key, is_push: true});
|
|
176
|
-
});
|
|
175
|
+
} catch (err) {
|
|
176
|
+
// Ignore errors, destination isn't BOLT 11
|
|
177
177
|
}
|
|
178
|
+
|
|
179
|
+
// Find the key to send to
|
|
180
|
+
return findKey({
|
|
181
|
+
lnd: args.lnd,
|
|
182
|
+
query: args.destination,
|
|
183
|
+
},
|
|
184
|
+
(err, res) => {
|
|
185
|
+
if (!!err) {
|
|
186
|
+
return cbk(err);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return cbk(null, {destination: res.public_key, is_push: true});
|
|
190
|
+
});
|
|
178
191
|
}],
|
|
179
192
|
|
|
180
193
|
// Get ignores
|
|
@@ -366,22 +379,61 @@ module.exports = (args, cbk) => {
|
|
|
366
379
|
}
|
|
367
380
|
}],
|
|
368
381
|
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
'getIgnores',
|
|
372
|
-
'getInKey',
|
|
373
|
-
'getOutKey',
|
|
382
|
+
// Get LNURL payment request
|
|
383
|
+
getLnurlRequest: [
|
|
374
384
|
'parseAmount',
|
|
375
385
|
'payment',
|
|
376
|
-
({
|
|
386
|
+
({parseAmount, payment}, cbk) =>
|
|
377
387
|
{
|
|
378
388
|
if (parseAmount.tokens < minTokens) {
|
|
379
389
|
return cbk([400, 'ExpectedNonZeroAmountToPushPayment']);
|
|
380
390
|
}
|
|
381
391
|
|
|
392
|
+
// Exit early when there is no LNURL to send to
|
|
393
|
+
if (!payment.lnurl) {
|
|
394
|
+
return cbk(null, {});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return getLnurlRequest({
|
|
398
|
+
lnurl: payment.lnurl,
|
|
399
|
+
request: args.request,
|
|
400
|
+
tokens: parseAmount.tokens,
|
|
401
|
+
},
|
|
402
|
+
cbk);
|
|
403
|
+
}],
|
|
404
|
+
|
|
405
|
+
// Final payment details
|
|
406
|
+
send: [
|
|
407
|
+
'getLnurlRequest',
|
|
408
|
+
'parseAmount',
|
|
409
|
+
'payment',
|
|
410
|
+
({getLnurlRequest, parseAmount, payment}, cbk) =>
|
|
411
|
+
{
|
|
412
|
+
if (parseAmount.tokens < minTokens) {
|
|
413
|
+
return cbk([400, 'ExpectedNonZeroAmountToPushPayment']);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return cbk(null, {
|
|
417
|
+
destination: getLnurlRequest.destination || payment.destination,
|
|
418
|
+
is_push: payment.is_push,
|
|
419
|
+
max_fee: parseAmount.max_fee,
|
|
420
|
+
request: getLnurlRequest.request || payment.request,
|
|
421
|
+
tokens: !getLnurlRequest.request ? parseAmount.tokens : undefined,
|
|
422
|
+
});
|
|
423
|
+
}],
|
|
424
|
+
|
|
425
|
+
// Push the amount to the destination
|
|
426
|
+
push: [
|
|
427
|
+
'getIgnores',
|
|
428
|
+
'getInKey',
|
|
429
|
+
'getOutKey',
|
|
430
|
+
'send',
|
|
431
|
+
({getIgnores, getInKey, getOutKey, send}, cbk) =>
|
|
432
|
+
{
|
|
382
433
|
args.logger.info({
|
|
383
|
-
|
|
384
|
-
|
|
434
|
+
max_fee: send.max_fee,
|
|
435
|
+
paying: formatTokens({tokens: send.tokens}).display,
|
|
436
|
+
to: send.destination,
|
|
385
437
|
});
|
|
386
438
|
|
|
387
439
|
if (!!args.is_dry_run) {
|
|
@@ -389,25 +441,25 @@ module.exports = (args, cbk) => {
|
|
|
389
441
|
}
|
|
390
442
|
|
|
391
443
|
return probeDestination({
|
|
392
|
-
destination:
|
|
444
|
+
destination: send.destination,
|
|
393
445
|
fs: args.fs,
|
|
394
446
|
ignore: getIgnores.ignore,
|
|
395
447
|
lnd: args.lnd,
|
|
396
448
|
logger: args.logger,
|
|
397
449
|
in_through: getInKey,
|
|
398
450
|
is_omitting_message_from: args.is_omitting_message_from,
|
|
399
|
-
is_push:
|
|
451
|
+
is_push: send.is_push,
|
|
400
452
|
is_real_payment: true,
|
|
401
|
-
max_fee:
|
|
453
|
+
max_fee: send.max_fee,
|
|
402
454
|
message: args.message,
|
|
403
455
|
messages: args.quiz_answers.map((answer, i) => ({
|
|
404
456
|
type: (quizStart + i).toString(),
|
|
405
457
|
value: utf8AsHex(answer),
|
|
406
458
|
})),
|
|
407
459
|
out_through: getOutKey,
|
|
408
|
-
request:
|
|
460
|
+
request: send.request,
|
|
409
461
|
timeout_minutes: args.timeout_minutes,
|
|
410
|
-
tokens:
|
|
462
|
+
tokens: send.tokens,
|
|
411
463
|
},
|
|
412
464
|
cbk);
|
|
413
465
|
}],
|
package/package.json
CHANGED
|
@@ -30,15 +30,15 @@
|
|
|
30
30
|
"csv-parse": "5.0.4",
|
|
31
31
|
"ecpair": "2.0.1",
|
|
32
32
|
"goldengate": "11.2.1",
|
|
33
|
-
"grammy": "1.8.
|
|
33
|
+
"grammy": "1.8.3",
|
|
34
34
|
"hot-formula-parser": "4.0.0",
|
|
35
35
|
"import-lazy": "4.0.0",
|
|
36
36
|
"ini": "3.0.0",
|
|
37
37
|
"inquirer": "8.2.4",
|
|
38
38
|
"ln-accounting": "5.0.6",
|
|
39
|
-
"ln-service": "53.
|
|
39
|
+
"ln-service": "53.16.0",
|
|
40
40
|
"ln-sync": "3.12.0",
|
|
41
|
-
"ln-telegram": "3.21.
|
|
41
|
+
"ln-telegram": "3.21.5",
|
|
42
42
|
"moment": "2.29.3",
|
|
43
43
|
"paid-services": "3.16.0",
|
|
44
44
|
"probing": "2.0.5",
|
|
@@ -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.
|
|
58
|
+
"ln-docker-daemons": "2.2.10",
|
|
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.
|
|
87
|
+
"version": "12.8.2"
|
|
88
88
|
}
|
package/peers/open_channels.js
CHANGED
|
@@ -556,9 +556,15 @@ module.exports = (args, cbk) => {
|
|
|
556
556
|
// Detect funding transaction
|
|
557
557
|
detectFunding: [
|
|
558
558
|
'getNetwork',
|
|
559
|
+
'isExternal',
|
|
559
560
|
'openChannels',
|
|
560
|
-
({getNetwork, openChannels}, cbk) =>
|
|
561
|
+
({getNetwork, isExternal, openChannels}, cbk) =>
|
|
561
562
|
{
|
|
563
|
+
// Exit early when the funding is coming from the internal wallet
|
|
564
|
+
if (!isExternal) {
|
|
565
|
+
return cbk();
|
|
566
|
+
}
|
|
567
|
+
|
|
562
568
|
if (!detectNetworks.includes(getNetwork.network)) {
|
|
563
569
|
return cbk();
|
|
564
570
|
}
|