paid-services 3.3.0 → 3.4.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/capacity/accept_capacity_change.js +3 -3
  3. package/capacity/get_capacity_change_requests.js +1 -1
  4. package/capacity/get_capacity_replacement.js +1 -1
  5. package/capacity/initiate_capacity_change.js +5 -4
  6. package/capacity/propose_capacity_change.js +3 -1
  7. package/p2p/service_peer_requests.js +10 -2
  8. package/package.json +3 -2
  9. package/service_types.json +16 -0
  10. package/test/integration/test_accept_capacity_change.js +50 -13
  11. package/test/integration/test_change_channel_capacity.js +3 -0
  12. package/test/trades/test_decode_basic_trade.js +40 -0
  13. package/test/trades/test_decode_open_trade.js +50 -0
  14. package/test/trades/test_encode_open_trade.js +35 -0
  15. package/test/trades/test_encode_trade.js +37 -0
  16. package/trades/accept_trade.js +52 -0
  17. package/trades/create_trade.js +124 -44
  18. package/trades/decode_basic_trade.js +45 -0
  19. package/trades/decode_open_trade.js +69 -0
  20. package/trades/decode_trade.js +48 -66
  21. package/trades/decode_trade_secret.js +89 -0
  22. package/trades/encode_open_trade.js +89 -0
  23. package/trades/encode_trade.js +48 -33
  24. package/trades/encode_trade_secret.js +50 -0
  25. package/trades/finalize_trade_secret.js +116 -0
  26. package/trades/find_trade.js +314 -0
  27. package/trades/manage_trade.js +46 -35
  28. package/trades/network_from_network_record.js +3 -3
  29. package/trades/network_record_from_network.js +36 -0
  30. package/trades/network_record_from_request.js +2 -1
  31. package/trades/node_as_record.js +98 -0
  32. package/trades/record_as_node.js +113 -0
  33. package/trades/request_trade_by_id.js +89 -0
  34. package/trades/service_trade_requests.js +184 -0
  35. package/capacity/service_types.json +0 -6
@@ -11,6 +11,7 @@ const {returnResult} = require('asyncjs-util');
11
11
  const buyPreimage = require('./buy_preimage');
12
12
  const decodeTrade = require('./decode_trade');
13
13
  const decryptTradeSecret = require('./decrypt_trade_secret');
14
+ const findTrade = require('./find_trade');
14
15
 
15
16
  const hexAsUtf8 = hex => Buffer.from(hex, 'hex').toString();
16
17
  const {isArray} = Array;
@@ -52,13 +53,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
52
53
  name: 'trade',
53
54
  message: 'Enter encoded trade',
54
55
  type: 'input',
55
- validate: input => {
56
- if (!input) {
57
- return false;
58
- }
59
-
60
- return true;
61
- },
56
+ validate: input => !!input,
62
57
  },
63
58
  cbk);
64
59
  }],
@@ -71,22 +66,43 @@ module.exports = ({ask, lnd, logger}, cbk) => {
71
66
  try {
72
67
  const details = decodeTrade({trade: askForTrade.trade});
73
68
 
74
- return cbk(null, {
75
- auth: details.auth,
76
- payload: details.payload,
77
- request: details.request,
78
- });
69
+ return cbk(null, details);
79
70
  } catch (err) {
80
71
  return cbk([400, err.message]);
81
72
  }
82
73
  }],
83
74
 
84
- // Parse the trade payment request
85
- requestDetails: ['decodeDetails', ({decodeDetails}, cbk) => {
86
- const {request} = decodeDetails;
75
+ // Get details about an open ended trade
76
+ findTrade: [
77
+ 'decodeDetails',
78
+ 'getIdentity',
79
+ ({decodeDetails, getIdentity}, cbk) =>
80
+ {
81
+ // Exit early when there is a known trade
82
+ if (!!decodeDetails.secret) {
83
+ return cbk(null, {
84
+ auth: decodeDetails.secret.auth,
85
+ payload: decodeDetails.secret.payload,
86
+ request: decodeDetails.secret.request,
87
+ });
88
+ }
89
+
90
+ // Get the trade details interactively
91
+ return findTrade({
92
+ ask,
93
+ lnd,
94
+ logger,
95
+ id: decodeDetails.connect.id,
96
+ identity: getIdentity.public_key,
97
+ nodes: decodeDetails.connect.nodes,
98
+ },
99
+ cbk);
100
+ }],
87
101
 
102
+ // Parse the trade payment request
103
+ requestDetails: ['findTrade', ({findTrade}, cbk) => {
88
104
  try {
89
- return cbk(null, parsePaymentRequest({request}));
105
+ return cbk(null, parsePaymentRequest({request: findTrade.request}));
90
106
  } catch (err) {
91
107
  return cbk([400, err.message]);
92
108
  }
@@ -184,11 +200,11 @@ module.exports = ({ask, lnd, logger}, cbk) => {
184
200
 
185
201
  // Select a purchase option for the trade
186
202
  selectPurchaseAction: [
187
- 'decodeDetails',
203
+ 'findTrade',
188
204
  'getPurchase',
189
205
  'getRequest',
190
206
  'requestDetails',
191
- ({decodeDetails, getPurchase, getRequest, requestDetails}, cbk) =>
207
+ ({findTrade, getPurchase, getRequest, requestDetails}, cbk) =>
192
208
  {
193
209
  // Exit early when the trade was already purchased
194
210
  if (!!getPurchase.value && !!getPurchase.value.is_confirmed) {
@@ -201,7 +217,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
201
217
  }
202
218
 
203
219
  logger.info({
204
- request: decodeDetails.request,
220
+ request: findTrade.request,
205
221
  purchase: requestDetails.description,
206
222
  price: requestDetails.tokens,
207
223
  to: requestDetails.destination,
@@ -257,9 +273,9 @@ module.exports = ({ask, lnd, logger}, cbk) => {
257
273
 
258
274
  // Pay and get the preimage through paying for it
259
275
  payForPreimage: [
260
- 'decodeDetails',
276
+ 'findTrade',
261
277
  'selectPurchaseAction',
262
- ({decodeDetails, selectPurchaseAction}, cbk) =>
278
+ ({findTrade, selectPurchaseAction}, cbk) =>
263
279
  {
264
280
  // Exit early when this is an offer
265
281
  if (!selectPurchaseAction) {
@@ -271,32 +287,25 @@ module.exports = ({ask, lnd, logger}, cbk) => {
271
287
  return cbk();
272
288
  }
273
289
 
290
+ const {request} = findTrade;
291
+
274
292
  return asyncRetry({
275
293
  errorFilter: err => !!isArray(err) && err.slice().shift() >= 500,
276
294
  },
277
- cbk => {
278
- return buyPreimage({
279
- ask,
280
- lnd,
281
- logger,
282
- request: decodeDetails.request,
283
- },
284
- cbk);
285
- },
286
- cbk);
295
+ cbk => buyPreimage({ask, lnd, logger, request}, cbk), cbk);
287
296
  }],
288
297
 
289
298
  // Use the preimage to decrypt the trade secret
290
299
  decryptSecret: [
291
300
  'askForPreimage',
292
- 'decodeDetails',
301
+ 'findTrade',
293
302
  'getPurchase',
294
303
  'payForPreimage',
295
304
  'requestDetails',
296
305
  'selectPurchaseAction',
297
306
  ({
298
307
  askForPreimage,
299
- decodeDetails,
308
+ findTrade,
300
309
  getPurchase,
301
310
  payForPreimage,
302
311
  requestDetails,
@@ -327,11 +336,13 @@ module.exports = ({ask, lnd, logger}, cbk) => {
327
336
 
328
337
  const paid = payForPreimage || askForPreimage || payment;
329
338
 
339
+ const {auth, payload} = findTrade;
340
+
330
341
  return decryptTradeSecret({
342
+ auth,
331
343
  lnd,
332
- auth: decodeDetails.auth,
344
+ payload,
333
345
  from: requestDetails.destination,
334
- payload: decodeDetails.payload,
335
346
  secret: paid.secret,
336
347
  },
337
348
  cbk);
@@ -17,15 +17,15 @@ const testnetNetworkType = '01';
17
17
  */
18
18
  module.exports = ({value}) => {
19
19
  if (!value) {
20
- return defaultNetwork;
20
+ return {network: defaultNetwork};
21
21
  }
22
22
 
23
23
  if (value === regtestNetworkType) {
24
- return regtestNetwork;
24
+ return {network: regtestNetwork};
25
25
  }
26
26
 
27
27
  if (value === testnetNetworkType) {
28
- return testnetNetwork;
28
+ return {network: testnetNetwork};
29
29
  }
30
30
 
31
31
  throw new Error('UnknownNetworkType');
@@ -0,0 +1,36 @@
1
+ const defaultNetwork = 'btc';
2
+ const regtestNetwork = 'btcregtest';
3
+ const regtestNetworkType = '02';
4
+ const testnetNetwork = 'btctestnet';
5
+ const testnetNetworkType = '01';
6
+
7
+ /** Derive a network record name from a network name
8
+
9
+ {
10
+ network: <Network Name String>
11
+ }
12
+
13
+ @returns
14
+ {
15
+ [value]: <Network Record Value Hex String>
16
+ }
17
+ */
18
+ module.exports = ({network}) => {
19
+ if (!network) {
20
+ throw new Error('ExpectedNetworkNameToDeriveNetworkRecord');
21
+ }
22
+
23
+ switch (network) {
24
+ case defaultNetwork:
25
+ return {};
26
+
27
+ case regtestNetwork:
28
+ return {value: regestNetworkType};
29
+
30
+ case testnetNetwork:
31
+ return {value: testnetNetworkType};
32
+
33
+ default:
34
+ throw new Error('UnknownNetworkNameToDeriveNetworkRecordFor');
35
+ }
36
+ };
@@ -1,5 +1,6 @@
1
1
  const defaultNetwork = 'lnbc';
2
2
  const regtestNetwork = 'lnbcrt';
3
+ const regtestNetworkType = '02';
3
4
  const testnetNetwork = 'lntb';
4
5
  const testnetNetworkType = '01';
5
6
 
@@ -24,7 +25,7 @@ module.exports = ({request}) => {
24
25
  }
25
26
 
26
27
  if (request.startsWith(regtestNetwork)) {
27
- return {};
28
+ return {value: regtestNetworkType};
28
29
  }
29
30
 
30
31
  if (request.startsWith(testnetNetwork)) {
@@ -0,0 +1,98 @@
1
+ const {decodeChanId} = require('bolt07');
2
+ const {encodeSocket} = require('bolt07');
3
+ const {encodeTlvStream} = require('bolt01');
4
+ const {rawChanId} = require('bolt07');
5
+
6
+ const highOrderKeyType = '1';
7
+ const ipV4SocketsType = '3';
8
+ const ipV6SocketsType = '4';
9
+ const torV3SocketsType = '5';
10
+ const {isArray} = Array;
11
+ const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
12
+ const join = arr => arr.join('');
13
+ const lowOrderKeyType = '0';
14
+ const nodeIdType = '2';
15
+
16
+ /** Convert node details to a node record
17
+
18
+ A node record looks like:
19
+
20
+ [0]: low order key channel id
21
+ [1]: high order key channel id
22
+ [2]: node identity public key
23
+ [3]: node ipv4 sockets (6 byte chunks)
24
+ [4]: node ipv6 sockets (18 byte chunks)
25
+ [5]: node torV3 addresses (37 byte chunks)
26
+
27
+ {
28
+ channels: [{
29
+ id: <Standard Format Channel Id String>
30
+ partner_public_key: <Node Public Key Hex String>
31
+ }]
32
+ id: <Node Public Key Id Hex String>
33
+ sockets: [<Peer Socket String>]
34
+ }
35
+
36
+ @returns
37
+ {
38
+ encoded: <Encoded Node Details Hex String>
39
+ }
40
+ */
41
+ module.exports = ({channels, id, sockets}) => {
42
+ if (!isArray(channels)) {
43
+ throw new Error('ExpectedArrayOfChannelsToMapNodeToRecord');
44
+ }
45
+
46
+ if (!isPublicKey(id)) {
47
+ throw new Error('ExpectedNodePublicKeyToMapNodeToRecord');
48
+ }
49
+
50
+ if (!isArray(sockets)) {
51
+ throw new Error('ExpectedArrayOfSocketsToMapNodeToRecord');
52
+ }
53
+
54
+ // Sort the channels by their height ascending, use the oldest as a landmark
55
+ const [channel] = channels.slice().sort((a, b) => {
56
+ const aHeight = decodeChanId({channel: a.id}).block_height;
57
+ const bHeight = decodeChanId({channel: b.id}).block_height;
58
+
59
+ return aHeight - bHeight;
60
+ });
61
+
62
+ // Exit early when there is a channel to reference
63
+ if (!!channel) {
64
+ const value = rawChanId({channel: channel.id}).id;
65
+
66
+ // Sort the keys by lexicographical order
67
+ const [key1, key2] = [channel.partner_public_key, id].sort();
68
+
69
+ // When the id is the first key in order, it is a low record
70
+ const type = key1 === id ? lowOrderKeyType : highOrderKeyType;
71
+
72
+ return encodeTlvStream({records: [{type, value}]});
73
+ }
74
+
75
+ // When there is no channel to reference, use the node identity key instead
76
+ const records = [{type: nodeIdType, value: id}];
77
+
78
+ const encodedSockets = sockets.map(socket => encodeSocket({socket}));
79
+
80
+ const ipV4 = join(encodedSockets.filter(n => !!n.ip4).map(n => n.ip4));
81
+ const ipV6 = join(encodedSockets.filter(n => !!n.ip6).map(n => n.ip6));
82
+ const torV3 = join(encodedSockets.filter(n => !!n.tor3).map(n => n.tor3));
83
+
84
+ if (!!ipV4) {
85
+ records.push({type: ipV4SocketsType, value: ipV4});
86
+ }
87
+
88
+ if (!!ipV6) {
89
+ records.push({type: ipV6SocketsType, value: ipV6});
90
+ }
91
+
92
+ if (!!torV3) {
93
+ records.push({type: torV3SocketsType, value: ipV6});
94
+ }
95
+
96
+ // Return the node identity key and network sockets
97
+ return encodeTlvStream({records});
98
+ };
@@ -0,0 +1,113 @@
1
+ const {chanFormat} = require('bolt07');
2
+ const {decodeSocket} = require('bolt07');
3
+ const {decodeTlvStream} = require('bolt01');
4
+
5
+ const channelHexLength = 16;
6
+ const chunkIpV4 = data => !!data ? data.value.match(/.{1,12}/g) : [];
7
+ const chunkIpV6 = data => !!data ? data.value.match(/.{1,36}/g) : [];
8
+ const chunkTorV3 = data => !!data ? data.value.match(/.{1,74}/g) : [];
9
+
10
+ const findHighKeyChannelRecord = records => records.find(n => n.type === '1');
11
+ const findIdRecord = records => records.find(n => n.type === '2');
12
+ const findIpV4Record = records => records.find(n => n.type === '3');
13
+ const findIpV6Record = records => records.find(n => n.type === '4');
14
+ const findLowKeyChannelRecord = records => records.find(n => n.type === '0');
15
+ const findTorV3Record = records => records.find(n => n.type === '5');
16
+ const ipV4HexLength = 12;
17
+ const ipV6HexLength = 36;
18
+ const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
19
+ const torV3HexLength = 74;
20
+
21
+ /** Decode a node record
22
+
23
+ {
24
+ encoded: <Encoded Node Record>
25
+ }
26
+
27
+ @throws
28
+ <Error>
29
+
30
+ @returns
31
+ {
32
+ [high_channel]: <High Key Channel Id String>
33
+ [low_channel]: <Low Key Channel Id String>
34
+ [node]: {
35
+ id: <Node Public Key Id Hex String>
36
+ sockets: [<Peer Socket String>]
37
+ }
38
+ }
39
+ */
40
+ module.exports = ({encoded}) => {
41
+ if (!encoded) {
42
+ throw new Error('ExpectedEncodedNodeRecordToGetNodePointer');
43
+ }
44
+
45
+ const {records} = decodeTlvStream({encoded});
46
+
47
+ const highKeyRecord = findHighKeyChannelRecord(records);
48
+
49
+ if (!!highKeyRecord && highKeyRecord.value.length !== channelHexLength) {
50
+ throw new Error('ExpectedChannelIdInHighKeyRecord');
51
+ }
52
+
53
+ if (!!highKeyRecord) {
54
+ return {high_channel: chanFormat({id: highKeyRecord.value}).channel};
55
+ }
56
+
57
+ const lowKeyRecord = findLowKeyChannelRecord(records);
58
+
59
+ if (!!lowKeyRecord && lowKeyRecord.value.length !== channelHexLength) {
60
+ throw new Error('ExpectedChannelIdInLowKeyRecord');
61
+ }
62
+
63
+ if (!!lowKeyRecord) {
64
+ return {low_channel: chanFormat({id: lowKeyRecord.value}).channel};
65
+ }
66
+
67
+ const idRecord = findIdRecord(records);
68
+
69
+ if (!idRecord) {
70
+ throw new Error('ExpectedNodeIdRecordToMapNodeRecordToNodePointer');
71
+ }
72
+
73
+ if (!isPublicKey(idRecord.value)) {
74
+ throw new Error('ExpectedNodeIdPublicKeyToMapNodeRecordToNodePointer');
75
+ }
76
+
77
+ const ipV4SocketsRecord = findIpV4Record(records);
78
+
79
+ if (!!ipV4SocketsRecord && !!(ipV4SocketsRecord % ipV4HexLength)) {
80
+ throw new Error('ExpectedListOfIpV4SocketsToMapRecordToNode');
81
+ }
82
+
83
+ const ipV4Sockets = chunkIpV4(ipV4SocketsRecord).map(ip4 => {
84
+ return decodeSocket({ip4}).socket;
85
+ });
86
+
87
+ const ipV6SocketsRecord = findIpV6Record(records);
88
+
89
+ if (!!ipV6SocketsRecord && !!(ipV6SocketsRecord % ipV6HexLength)) {
90
+ throw new Error('ExpectedListOfIpV6SocketsToMapRecordToNode');
91
+ }
92
+
93
+ const ipV6Sockets = chunkIpV6(ipV6SocketsRecord).map(ip6 => {
94
+ return decodeSocket({ip6}).socket;
95
+ });
96
+
97
+ const torV3SocketsRecord = findTorV3Record(records);
98
+
99
+ if (!!torV3SocketsRecord && !!(torV3SocketsRecord % torV3HexLength)) {
100
+ throw new Error('ExpectedListOfTorV3SocketsToMapRecordToNode');
101
+ }
102
+
103
+ const torV3Sockets = chunkTorV3(torV3SocketsRecord).map(tor3 => {
104
+ return decodeSocket({tor3}).socket;
105
+ });
106
+
107
+ return {
108
+ node: {
109
+ id: idRecord.value,
110
+ sockets: [].concat(ipV4Sockets).concat(ipV6Sockets).concat(torV3Sockets),
111
+ },
112
+ };
113
+ };
@@ -0,0 +1,89 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {returnResult} = require('asyncjs-util');
3
+
4
+ const decodeTrade = require('./decode_trade');
5
+ const {makePeerRequest} = require('./../p2p');
6
+
7
+ const findTradeRecord = records => records.find(n => n.type === '1');
8
+ const requestTradeTimeoutMs = 1000 * 30;
9
+ const {serviceTypeRequestTrades} = require('./../service_types');
10
+ const tradeIdRecordType = '0';
11
+
12
+ /** Request a specific trade by its id
13
+
14
+ {
15
+ id: <Trade Identifier Hex String>
16
+ lnd: <Authenticated LND API Object>
17
+ to: <Make Request To Public Key Hex Encoded String>
18
+ }
19
+
20
+ @returns via cbk or Promise
21
+ {
22
+ auth: <Encrypted Payload Auth Hex String>
23
+ payload: <Preimage Encrypted Payload Hex String>
24
+ request: <BOLT 11 Payment Request String>
25
+ }
26
+ */
27
+ module.exports = ({id, lnd, to}, cbk) => {
28
+ return new Promise((resolve, reject) => {
29
+ return asyncAuto({
30
+ // Check arguments
31
+ validate: cbk => {
32
+ if (!id) {
33
+ return cbk([400, 'ExpectedTradeIdentifierToRequestTradeById']);
34
+ }
35
+
36
+ if (!lnd) {
37
+ return cbk([400, 'ExpectedAuthenticatedLndToRequestTradeById']);
38
+ }
39
+
40
+ if (!to) {
41
+ return cbk([400, 'ExpectedRemotePublicKeyToRequestTradeById']);
42
+ }
43
+
44
+ return cbk();
45
+ },
46
+
47
+ // Get trade
48
+ getTrade: ['validate', ({}, cbk) => {
49
+ return makePeerRequest({
50
+ lnd,
51
+ to,
52
+ records: [{type: tradeIdRecordType, value: id}],
53
+ timeout: requestTradeTimeoutMs,
54
+ type: serviceTypeRequestTrades,
55
+ },
56
+ (err, res) => {
57
+ if (!!err) {
58
+ return cbk(err);
59
+ }
60
+
61
+ const tradeRecord = findTradeRecord(res.records);
62
+
63
+ if (!tradeRecord) {
64
+ return cbk([503, 'ExpectedTradeRecordFromPeer']);
65
+ }
66
+
67
+ try {
68
+ decodeTrade({trade: tradeRecord.value});
69
+ } catch (err) {
70
+ return cbk([503, err.message]);
71
+ }
72
+
73
+ const {secret} = decodeTrade({trade: tradeRecord.value});
74
+
75
+ if (!secret) {
76
+ return cbk([503, 'ExpectedTradeSecretInResponseForTradeById']);
77
+ }
78
+
79
+ try {
80
+ return cbk(null, secret);
81
+ } catch (err) {
82
+ return cbk([503, 'ExpectedValidTradeRecordFromPeer', {err}]);
83
+ }
84
+ });
85
+ }],
86
+ },
87
+ returnResult({reject, resolve, of: 'getTrade'}, cbk));
88
+ });
89
+ };