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
@@ -1,5 +1,9 @@
1
1
  # Versions
2
2
 
3
+ ## Version 9.3.0
4
+
5
+ - `getOfferInvoice`: Add `description` and `paths` to show and pay the invoice
6
+
3
7
  ## Version 9.2.0
4
8
 
5
9
  - `getOfferInvoice`: Add method to get an invoice for a BOLT 12 offer
@@ -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 asyncTryEach = require('async/tryEach');
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 replyIntroduction = require('./../network/reply_introduction');
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
- (err, res) => {
99
- if (!!err) {
100
- return cbk(err);
101
- }
107
+ cbk);
108
+ }],
102
109
 
103
- // Exit early when no path has a known introduction node
104
- if (!res.paths.length) {
105
- return cbk([503, 'FailedToFindIntroductionNodesOfOfferPaths']);
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
- return cbk(null, res.paths);
109
- });
117
+ return cbk(null, getPaths.paths);
110
118
  }],
111
119
 
112
- // Send the request over each path in turn until there is an invoice
113
- getInvoice: [
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
- ({getPaths, getPeers, request}, cbk) =>
125
+ ({getPeers, offerPaths, request}, cbk) =>
118
126
  {
119
- const failures = [];
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
- message: {type: typeInvoiceRequest, value: request.encoded},
133
+ peers: getPeers.peers,
133
134
  timeout: args.timeout || defaultTimeoutMs,
134
- types: [typeInvoice, typeInvoiceError],
135
135
  },
136
- (err, res) => {
137
- // Exit early and try the next path when there was no reply
138
- if (!!err) {
139
- failures.push({err});
140
-
141
- return cbk(err);
142
- }
143
-
144
- const {error, invoice} = checkOfferInvoiceReply({
145
- request,
146
- reply: res.reply,
147
- });
148
-
149
- // Exit early and stop when the recipient sent an invoice error,
150
- // since it got the request and said why there is no invoice
151
- if (!!error && res.reply.type === typeInvoiceError) {
152
- return cbk(null, {error});
153
- }
154
-
155
- // Exit early and try the next path when there is no good invoice
156
- if (!!error) {
157
- failures.push({err: error, is_reply: true});
158
-
159
- return cbk(error);
160
- }
161
-
162
- return cbk(null, {invoice});
163
- });
164
- };
165
-
166
- return asyncTryEach(getPaths.map(requestOver), (err, res) => {
167
- if (!!err) {
168
- return cbk(offerInvoiceFailure({failures}).error);
169
- }
170
-
171
- // Exit early when the recipient said why there is no invoice
172
- if (!!res.error) {
173
- return cbk(res.error);
174
- }
175
-
176
- return cbk(null, res.invoice);
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: ['getInvoice', 'request', ({getInvoice, request}, cbk) => {
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.2.0"
76
+ "version": "9.3.0"
77
77
  }