paid-services 9.2.0 → 9.3.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
|
@@ -27,6 +27,25 @@ const {typeInvoiceError} = require('./constants');
|
|
|
27
27
|
id: <Payment Hash Hex String>
|
|
28
28
|
invoice: <BOLT 12 Invoice String>
|
|
29
29
|
mtokens: <Invoice Amount Millitokens String>
|
|
30
|
+
paths: [{
|
|
31
|
+
base_fee_mtokens: <Accumulated Base Fee Millitokens String>
|
|
32
|
+
cltv_delta: <Accumulated CLTV Expiry Delta Number>
|
|
33
|
+
features: [{
|
|
34
|
+
bit: <BOLT 09 Feature Bit Number>
|
|
35
|
+
is_required: <Feature Support is Required To Pay Bool>
|
|
36
|
+
type: <Feature Type String>
|
|
37
|
+
}]
|
|
38
|
+
fee_rate: <Accumulated Fee Rate Millitokens Per Million Number>
|
|
39
|
+
hops: [{
|
|
40
|
+
encrypted_data: <Encrypted Recipient Data Hex String>
|
|
41
|
+
relay_key: <Blinded Relaying Node Public Key Hex String>
|
|
42
|
+
}]
|
|
43
|
+
[introduction_edge]: <Introduction Node Edge Format Channel Id String>
|
|
44
|
+
[introduction_node]: <Introduction Node Public Key Hex String>
|
|
45
|
+
key: <First Hop Path Key Public Key Hex String>
|
|
46
|
+
max_htlc_mtokens: <Maximum HTLC Millitokens String>
|
|
47
|
+
min_htlc_mtokens: <Minimum HTLC Millitokens String>
|
|
48
|
+
}]
|
|
30
49
|
}
|
|
31
50
|
}
|
|
32
51
|
*/
|
|
@@ -77,6 +96,7 @@ module.exports = ({reply, request}) => {
|
|
|
77
96
|
id: invoice.id,
|
|
78
97
|
invoice: invoice.invoice,
|
|
79
98
|
mtokens: invoice.mtokens,
|
|
99
|
+
paths: invoice.paths,
|
|
80
100
|
},
|
|
81
101
|
};
|
|
82
102
|
};
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
-
const
|
|
2
|
+
const asyncReduce = require('async/reduce');
|
|
3
3
|
const {getNetwork} = require('ln-sync');
|
|
4
4
|
const {getPeers} = require('ln-service');
|
|
5
5
|
const {returnResult} = require('asyncjs-util');
|
|
6
6
|
|
|
7
|
-
const checkOfferInvoiceReply = require('./check_offer_invoice_reply');
|
|
8
7
|
const invoiceRequestForOffer = require('./invoice_request_for_offer');
|
|
9
8
|
const offerInvoiceFailure = require('./offer_invoice_failure');
|
|
10
|
-
const
|
|
11
|
-
const {requestNetworkReply} = require('./../network');
|
|
9
|
+
const requestOfferInvoice = require('./request_offer_invoice');
|
|
12
10
|
const {resolvePathIntroductions} = require('./../network');
|
|
13
|
-
const {typeInvoice} = require('./constants');
|
|
14
|
-
const {typeInvoiceError} = require('./constants');
|
|
15
|
-
const {typeInvoiceRequest} = require('./constants');
|
|
16
11
|
|
|
17
12
|
const {isSafeInteger} = Number;
|
|
18
13
|
const defaultTimeoutMs = 30000;
|
|
@@ -40,10 +35,24 @@ const isTimeout = n => isSafeInteger(n) && n > 0;
|
|
|
40
35
|
|
|
41
36
|
@returns via cbk or Promise
|
|
42
37
|
{
|
|
38
|
+
[description]: <Offer Description String>
|
|
43
39
|
expires_at: <Invoice Expires At ISO 8601 Date String>
|
|
44
40
|
id: <Payment Hash Hex String>
|
|
45
41
|
invoice: <BOLT 12 Invoice String>
|
|
46
42
|
mtokens: <Invoice Amount Millitokens String>
|
|
43
|
+
paths: [{
|
|
44
|
+
base_fee_mtokens: <Accumulated Base Fee Millitokens String>
|
|
45
|
+
cltv_delta: <Accumulated CLTV Expiry Delta Number>
|
|
46
|
+
fee_rate: <Accumulated Fee Rate Millitokens Per Million Number>
|
|
47
|
+
hops: [{
|
|
48
|
+
encrypted_data: <Encrypted Recipient Data Hex String>
|
|
49
|
+
relay_key: <Blinded Relaying Node Public Key Hex String>
|
|
50
|
+
}]
|
|
51
|
+
introduction_node: <Introduction Node Public Key Hex String>
|
|
52
|
+
key: <First Hop Path Key Public Key Hex String>
|
|
53
|
+
max_htlc_mtokens: <Maximum HTLC Millitokens String>
|
|
54
|
+
min_htlc_mtokens: <Minimum HTLC Millitokens String>
|
|
55
|
+
}]
|
|
47
56
|
payer_id: <Payer Public Key Hex String>
|
|
48
57
|
payer_secret: <Payer Secret Key Hex String>
|
|
49
58
|
}
|
|
@@ -95,95 +104,101 @@ module.exports = (args, cbk) => {
|
|
|
95
104
|
lnd: args.lnd,
|
|
96
105
|
paths: request.paths,
|
|
97
106
|
},
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return cbk(err);
|
|
101
|
-
}
|
|
107
|
+
cbk);
|
|
108
|
+
}],
|
|
102
109
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
// The offer paths to send the request over
|
|
111
|
+
offerPaths: ['getPaths', ({getPaths}, cbk) => {
|
|
112
|
+
// Exit early when no path has a known introduction node
|
|
113
|
+
if (!getPaths.paths.length) {
|
|
114
|
+
return cbk([503, 'FailedToFindIntroductionNodesOfOfferPaths']);
|
|
115
|
+
}
|
|
107
116
|
|
|
108
|
-
|
|
109
|
-
});
|
|
117
|
+
return cbk(null, getPaths.paths);
|
|
110
118
|
}],
|
|
111
119
|
|
|
112
|
-
// Send the request over each path in turn until
|
|
113
|
-
|
|
114
|
-
'getPaths',
|
|
120
|
+
// Send the request over each path in turn until the recipient answers
|
|
121
|
+
getReplies: [
|
|
115
122
|
'getPeers',
|
|
123
|
+
'offerPaths',
|
|
116
124
|
'request',
|
|
117
|
-
({
|
|
125
|
+
({getPeers, offerPaths, request}, cbk) =>
|
|
118
126
|
{
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// Make a task to send the request over a path and check the reply
|
|
122
|
-
const requestOver = path => cbk => {
|
|
123
|
-
const {introduction} = replyIntroduction({
|
|
124
|
-
path,
|
|
125
|
-
peers: getPeers.peers,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
return requestNetworkReply({
|
|
129
|
-
introduction,
|
|
127
|
+
return asyncReduce(offerPaths, [], (replies, path, cbk) => {
|
|
128
|
+
return requestOfferInvoice({
|
|
130
129
|
path,
|
|
130
|
+
replies,
|
|
131
|
+
request,
|
|
131
132
|
lnd: args.lnd,
|
|
132
|
-
|
|
133
|
+
peers: getPeers.peers,
|
|
133
134
|
timeout: args.timeout || defaultTimeoutMs,
|
|
134
|
-
types: [typeInvoice, typeInvoiceError],
|
|
135
135
|
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
136
|
+
cbk);
|
|
137
|
+
},
|
|
138
|
+
cbk);
|
|
139
|
+
}],
|
|
140
|
+
|
|
141
|
+
// The invoice the recipient answered with
|
|
142
|
+
getInvoice: ['getReplies', ({getReplies}, cbk) => {
|
|
143
|
+
const answer = getReplies.find(n => !!n.error || !!n.invoice);
|
|
144
|
+
|
|
145
|
+
// Exit early when no path gave back an answer from the recipient
|
|
146
|
+
if (!answer) {
|
|
147
|
+
const failures = getReplies.filter(n => !!n.err);
|
|
148
|
+
|
|
149
|
+
return cbk(offerInvoiceFailure({failures}).error);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Exit early when the recipient said why there is no invoice
|
|
153
|
+
if (!!answer.error) {
|
|
154
|
+
return cbk(answer.error);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return cbk(null, answer.invoice);
|
|
158
|
+
}],
|
|
159
|
+
|
|
160
|
+
// Find the introduction node of each invoice path, to pay over it
|
|
161
|
+
getPaymentPaths: ['getInvoice', ({getInvoice}, cbk) => {
|
|
162
|
+
return resolvePathIntroductions({
|
|
163
|
+
lnd: args.lnd,
|
|
164
|
+
paths: getInvoice.paths,
|
|
165
|
+
},
|
|
166
|
+
cbk);
|
|
167
|
+
}],
|
|
168
|
+
|
|
169
|
+
// The payment paths to pay the invoice over
|
|
170
|
+
paymentPaths: ['getPaymentPaths', ({getPaymentPaths}, cbk) => {
|
|
171
|
+
// Exit early when no payment path has a known introduction node
|
|
172
|
+
if (!getPaymentPaths.paths.length) {
|
|
173
|
+
return cbk([503, 'FailedToFindStartOfPaymentPathsForOfferInvoice']);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return cbk(null, getPaymentPaths.paths.map(path => ({
|
|
177
|
+
base_fee_mtokens: path.base_fee_mtokens,
|
|
178
|
+
cltv_delta: path.cltv_delta,
|
|
179
|
+
fee_rate: path.fee_rate,
|
|
180
|
+
hops: path.hops,
|
|
181
|
+
introduction_node: path.introduction_node,
|
|
182
|
+
key: path.key,
|
|
183
|
+
max_htlc_mtokens: path.max_htlc_mtokens,
|
|
184
|
+
min_htlc_mtokens: path.min_htlc_mtokens,
|
|
185
|
+
})));
|
|
178
186
|
}],
|
|
179
187
|
|
|
180
188
|
// The invoice for the request
|
|
181
|
-
invoice: [
|
|
189
|
+
invoice: [
|
|
190
|
+
'getInvoice',
|
|
191
|
+
'paymentPaths',
|
|
192
|
+
'request',
|
|
193
|
+
({getInvoice, paymentPaths, request}, cbk) =>
|
|
194
|
+
{
|
|
182
195
|
return cbk(null, {
|
|
196
|
+
description: request.description,
|
|
183
197
|
expires_at: getInvoice.expires_at,
|
|
184
198
|
id: getInvoice.id,
|
|
185
199
|
invoice: getInvoice.invoice,
|
|
186
200
|
mtokens: getInvoice.mtokens,
|
|
201
|
+
paths: paymentPaths,
|
|
187
202
|
payer_id: request.payer_id,
|
|
188
203
|
payer_secret: request.payer_secret,
|
|
189
204
|
});
|
|
@@ -32,6 +32,7 @@ const namePattern = /^[0-9a-zA-Z\-_.]+@[0-9a-zA-Z\-_.]+$/;
|
|
|
32
32
|
|
|
33
33
|
@returns
|
|
34
34
|
{
|
|
35
|
+
[description]: <Offer Description String>
|
|
35
36
|
encoded: <Signed Invoice Request TLV Stream Hex String>
|
|
36
37
|
mtokens: <Requested Millitokens String>
|
|
37
38
|
paths: [{
|
|
@@ -137,6 +138,7 @@ module.exports = args => {
|
|
|
137
138
|
});
|
|
138
139
|
|
|
139
140
|
return {
|
|
141
|
+
description: details.description,
|
|
140
142
|
encoded: signed.encoded,
|
|
141
143
|
mtokens: amount,
|
|
142
144
|
paths: details.paths,
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncReflect = require('async/reflect');
|
|
3
|
+
const {returnResult} = require('asyncjs-util');
|
|
4
|
+
|
|
5
|
+
const checkOfferInvoiceReply = require('./check_offer_invoice_reply');
|
|
6
|
+
const replyIntroduction = require('./../network/reply_introduction');
|
|
7
|
+
const {requestNetworkReply} = require('./../network');
|
|
8
|
+
const {typeInvoice} = require('./constants');
|
|
9
|
+
const {typeInvoiceError} = require('./constants');
|
|
10
|
+
const {typeInvoiceRequest} = require('./constants');
|
|
11
|
+
|
|
12
|
+
const {isArray} = Array;
|
|
13
|
+
const isAnswered = replies => replies.some(n => !!n.error || !!n.invoice);
|
|
14
|
+
|
|
15
|
+
/** Send an invoice request over an offer path and add the reply to replies
|
|
16
|
+
|
|
17
|
+
No request is sent when the recipient already answered over another path,
|
|
18
|
+
with an invoice, or with an invoice error that says why there is no invoice.
|
|
19
|
+
|
|
20
|
+
A path that gives no reply, or a reply that is not a good invoice, is added
|
|
21
|
+
as a failure, so that the request can be sent over the next path.
|
|
22
|
+
|
|
23
|
+
{
|
|
24
|
+
lnd: <Authenticated LND API Object>
|
|
25
|
+
path: {
|
|
26
|
+
hops: [{
|
|
27
|
+
encrypted_data: <Encrypted Recipient Data Hex String>
|
|
28
|
+
relay_key: <Blinded Relaying Node Public Key Hex String>
|
|
29
|
+
}]
|
|
30
|
+
introduction_node: <Introduction Node Public Key Hex String>
|
|
31
|
+
key: <First Hop Path Key Public Key Hex String>
|
|
32
|
+
}
|
|
33
|
+
peers: [{
|
|
34
|
+
features: [{
|
|
35
|
+
bit: <BOLT 09 Feature Bit Number>
|
|
36
|
+
}]
|
|
37
|
+
public_key: <Peer Public Key Hex String>
|
|
38
|
+
}]
|
|
39
|
+
replies: [<Reply Object>]
|
|
40
|
+
request: {
|
|
41
|
+
encoded: <Signed Invoice Request TLV Stream Hex String>
|
|
42
|
+
mtokens: <Requested Millitokens String>
|
|
43
|
+
request: <BOLT 12 Invoice Request String>
|
|
44
|
+
}
|
|
45
|
+
timeout: <Wait For Reply Milliseconds Number>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@returns via cbk or Promise
|
|
49
|
+
[{
|
|
50
|
+
[err]: [<Failure Code Number>, <Failure Message String>, <Details Object>]
|
|
51
|
+
[error]: [<Error Code Number>, <Error Message String>, <Details Object>]
|
|
52
|
+
[invoice]: {
|
|
53
|
+
expires_at: <Invoice Expires At ISO 8601 Date String>
|
|
54
|
+
id: <Payment Hash Hex String>
|
|
55
|
+
invoice: <BOLT 12 Invoice String>
|
|
56
|
+
mtokens: <Invoice Amount Millitokens String>
|
|
57
|
+
paths: [<Invoice Payment Path Object>]
|
|
58
|
+
}
|
|
59
|
+
[is_reply]: <Failure Is a Recipient Reply Bool>
|
|
60
|
+
}]
|
|
61
|
+
*/
|
|
62
|
+
module.exports = (args, cbk) => {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
return asyncAuto({
|
|
65
|
+
// Check arguments
|
|
66
|
+
validate: cbk => {
|
|
67
|
+
if (!args.lnd) {
|
|
68
|
+
return cbk([400, 'ExpectedLndToRequestOfferInvoice']);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!args.path) {
|
|
72
|
+
return cbk([400, 'ExpectedOfferPathToRequestOfferInvoice']);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!isArray(args.peers)) {
|
|
76
|
+
return cbk([400, 'ExpectedPeersToRequestOfferInvoice']);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!isArray(args.replies)) {
|
|
80
|
+
return cbk([400, 'ExpectedRepliesToRequestOfferInvoice']);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!args.request) {
|
|
84
|
+
return cbk([400, 'ExpectedInvoiceRequestToRequestOfferInvoice']);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!args.timeout) {
|
|
88
|
+
return cbk([400, 'ExpectedTimeoutToRequestOfferInvoice']);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return cbk();
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// Send the request over the path, unless the recipient already answered
|
|
95
|
+
getReply: ['validate', asyncReflect(({}, cbk) => {
|
|
96
|
+
// Exit early when there is no need to send the request again
|
|
97
|
+
if (isAnswered(args.replies)) {
|
|
98
|
+
return cbk();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const {introduction} = replyIntroduction({
|
|
102
|
+
path: args.path,
|
|
103
|
+
peers: args.peers,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return requestNetworkReply({
|
|
107
|
+
introduction,
|
|
108
|
+
lnd: args.lnd,
|
|
109
|
+
message: {type: typeInvoiceRequest, value: args.request.encoded},
|
|
110
|
+
path: args.path,
|
|
111
|
+
timeout: args.timeout,
|
|
112
|
+
types: [typeInvoice, typeInvoiceError],
|
|
113
|
+
},
|
|
114
|
+
cbk);
|
|
115
|
+
})],
|
|
116
|
+
|
|
117
|
+
// Add the reply over the path to the replies
|
|
118
|
+
replies: ['getReply', ({getReply}, cbk) => {
|
|
119
|
+
// Exit early when the request was not sent
|
|
120
|
+
if (!getReply.error && !getReply.value) {
|
|
121
|
+
return cbk(null, args.replies);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Exit early when there was no reply, so the next path is tried
|
|
125
|
+
if (!!getReply.error) {
|
|
126
|
+
return cbk(null, [].concat(args.replies, {err: getReply.error}));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const {reply} = getReply.value;
|
|
130
|
+
|
|
131
|
+
const {error, invoice} = checkOfferInvoiceReply({
|
|
132
|
+
reply,
|
|
133
|
+
request: args.request,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Exit early when the recipient said why there is no invoice
|
|
137
|
+
if (!!error && reply.type === typeInvoiceError) {
|
|
138
|
+
return cbk(null, [].concat(args.replies, {error}));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const failure = {err: error, is_reply: true};
|
|
142
|
+
|
|
143
|
+
// Exit early when the reply is not a good invoice
|
|
144
|
+
if (!!error) {
|
|
145
|
+
return cbk(null, [].concat(args.replies, failure));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return cbk(null, [].concat(args.replies, {invoice}));
|
|
149
|
+
}],
|
|
150
|
+
},
|
|
151
|
+
returnResult({reject, resolve, of: 'replies'}, cbk));
|
|
152
|
+
});
|
|
153
|
+
};
|
package/package.json
CHANGED
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"integration-tests": "node test/integration",
|
|
74
74
|
"test": "node --experimental-test-coverage --test test/actions/*.js test/balanced/*.js test/client/*.js test/config/*.js test/network/*.js test/offers/*.js test/p2p/*.js test/records/*.js test/respond/*.js test/server/*.js test/service_records/*.js test/swaps/*.js test/services/*.js test/support/*.js test/trades/*.js"
|
|
75
75
|
},
|
|
76
|
-
"version": "9.
|
|
76
|
+
"version": "9.3.0"
|
|
77
77
|
}
|