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
@@ -1,10 +1,13 @@
1
1
  const asyncAuto = require('async/auto');
2
- const {createInvoice} = require('ln-service');
3
- const {getIdentity} = require('ln-service');
2
+ const {getChannels} = require('ln-service');
3
+ const {getNetwork} = require('ln-sync');
4
+ const {getNodeAlias} = require('ln-sync');
5
+ const {getWalletInfo} = require('ln-service');
4
6
  const {returnResult} = require('asyncjs-util');
5
7
 
6
- const encodeTrade = require('./encode_trade');
7
- const encryptTradeSecret = require('./encrypt_trade_secret');
8
+ const encodeOpenTrade = require('./encode_open_trade');
9
+ const finalizeTradeSecret = require('./finalize_trade_secret');
10
+ const serviceTradeRequests = require('./service_trade_requests');
8
11
 
9
12
  const asNumber = n => parseFloat(n, 10);
10
13
  const {floor} = Math;
@@ -12,6 +15,8 @@ const isNumber = n => !isNaN(n);
12
15
  const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
13
16
  const maxDescriptionLength = 100;
14
17
  const maxSecretLength = 100;
18
+ const nodeName = (alias, id) => `${alias} ${id}`;
19
+ const uriAsSocket = n => n.substring(67);
15
20
  const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
16
21
 
17
22
  /** Create a new trade
@@ -19,6 +24,7 @@ const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
19
24
  {
20
25
  ask: <Ask Function>
21
26
  lnd: <Authenticated LND API Object>
27
+ logger: <Winston Logger Object>
22
28
  }
23
29
 
24
30
  @returns via cbk or Promise
@@ -26,7 +32,7 @@ const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
26
32
  trade: <Hex Encoded Trade String>
27
33
  }
28
34
  */
29
- module.exports = ({ask, lnd}, cbk) => {
35
+ module.exports = ({ask, lnd, logger}, cbk) => {
30
36
  return new Promise((resolve, reject) => {
31
37
  return asyncAuto({
32
38
  // Check arguments
@@ -39,28 +45,32 @@ module.exports = ({ask, lnd}, cbk) => {
39
45
  return cbk([400, 'ExpectedAuthenticatedLndToCreateTrade']);
40
46
  }
41
47
 
48
+ if (!logger) {
49
+ return cbk([400, 'ExpectedWinstonLoggerToCreateTrade']);
50
+ }
51
+
42
52
  return cbk();
43
53
  },
44
54
 
45
55
  // Get the node identity key
46
- getNodeId: ['validate', ({}, cbk) => getIdentity({lnd}, cbk)],
56
+ getIdentity: ['validate', ({}, cbk) => getWalletInfo({lnd}, cbk)],
47
57
 
48
58
  // Ask for the public key of the node to trade with
49
- askForNodeId: ['getNodeId', ({getNodeId}, cbk) => {
59
+ askForNodeId: ['getIdentity', ({getIdentity}, cbk) => {
50
60
  return ask({
51
61
  name: 'id',
52
- message: 'Public key of node you are trading with?',
62
+ message: 'Public key of node you are trading with? (Optional)',
53
63
  type: 'input',
54
64
  validate: input => {
55
65
  if (!input) {
56
- return false;
66
+ return true;
57
67
  }
58
68
 
59
69
  if (!isPublicKey(input)) {
60
70
  return 'Expected public key of node to trade with';
61
71
  }
62
72
 
63
- if (input === getNodeId.public_key) {
73
+ if (input === getIdentity.public_key) {
64
74
  return 'Expected public key of other node';
65
75
  }
66
76
 
@@ -70,6 +80,26 @@ module.exports = ({ask, lnd}, cbk) => {
70
80
  cbk);
71
81
  }],
72
82
 
83
+ // Get the public channels to use for an open trade
84
+ getChannels: ['askForNodeId', ({askForNodeId}, cbk) => {
85
+ // Exit early when there is a specific node
86
+ if (!!askForNodeId.id) {
87
+ return cbk();
88
+ }
89
+
90
+ return getChannels({lnd, is_public: true}, cbk);
91
+ }],
92
+
93
+ // Get the network name to use for an open trade
94
+ getNetwork: ['askForNodeId', ({askForNodeId}, cbk) => {
95
+ // Exit early when there is a specific node
96
+ if (!!askForNodeId.id) {
97
+ return cbk();
98
+ }
99
+
100
+ return getNetwork({lnd}, cbk);
101
+ }],
102
+
73
103
  // Ask for how to describe the payload
74
104
  askForDescription: ['askForNodeId', ({}, cbk) => {
75
105
  return ask({
@@ -134,55 +164,105 @@ module.exports = ({ask, lnd}, cbk) => {
134
164
  cbk);
135
165
  }],
136
166
 
137
- // Encrypt the secret data
138
- encryptPayload: [
167
+ // Wait for a peer to connect and ask for the trade details
168
+ serviceTradeRequests: [
169
+ 'askForDescription',
139
170
  'askForNodeId',
171
+ 'askForPrice',
140
172
  'askForSecret',
141
- ({askForNodeId, askForSecret}, cbk) =>
173
+ 'getChannels',
174
+ 'getIdentity',
175
+ 'getNetwork',
176
+ ({
177
+ askForDescription,
178
+ askForNodeId,
179
+ askForPrice,
180
+ askForSecret,
181
+ getChannels,
182
+ getIdentity,
183
+ getNetwork,
184
+ },
185
+ cbk) =>
142
186
  {
143
- return encryptTradeSecret({
187
+ // Exit early if this is a closed trade
188
+ if (!!askForNodeId.id) {
189
+ return cbk();
190
+ }
191
+
192
+ // Encode the open trade details to give out
193
+ const openTrade = encodeOpenTrade({
194
+ network: getNetwork.network,
195
+ nodes: [{
196
+ channels: getChannels.channels,
197
+ id: getIdentity.public_key,
198
+ sockets: (getIdentity.uris || []).map(uriAsSocket),
199
+ }],
200
+ });
201
+
202
+ const settled = [];
203
+
204
+ const sub = serviceTradeRequests({
144
205
  lnd,
145
- secret: utf8AsHex(askForSecret.secret),
146
- to: askForNodeId.id,
147
- },
148
- cbk);
206
+ description: askForDescription.description,
207
+ secret: askForSecret.secret,
208
+ tokens: asNumber(askForPrice.tokens),
209
+ });
210
+
211
+ sub.on('details', async ({to}) => {
212
+ const {alias, id} = await getNodeAlias({lnd, id: to});
213
+
214
+ return logger.info({return_trade_info_to: nodeName(alias, id)});
215
+ });
216
+
217
+ sub.once('end', async () => {
218
+ const [to] = settled;
219
+
220
+ if (!!to) {
221
+ const {alias, id} = await getNodeAlias({lnd, id: to});
222
+
223
+ return logger.info({finished_trade_with: nodeName(alias, id)});
224
+ }
225
+
226
+ return cbk();
227
+ });
228
+
229
+ sub.on('failure', failure => logger.error({failure}));
230
+
231
+ sub.on('settled', ({to}) => settled.push(to));
232
+
233
+ sub.on('trade', async ({to}) => {
234
+ const {alias, id} = await getNodeAlias({lnd, id: to});
235
+
236
+ return logger.info({make_trade_invoice_for: nodeName(alias, id)});
237
+ });
238
+
239
+ // Show the trade details blob
240
+ return logger.info({waiting_for_trade_request_to: openTrade.trade});
149
241
  }],
150
242
 
151
- // Create the invoice to purchase the unlocking secret
152
- createPurchaseInvoice: [
243
+ // Finalize the trade with an encrypted secret and an invoice
244
+ finalize: [
153
245
  'askForDescription',
246
+ 'askForNodeId',
154
247
  'askForPrice',
155
- 'encryptPayload',
156
- ({askForDescription, askForPrice, encryptPayload}, cbk) =>
248
+ 'askForSecret',
249
+ ({askForDescription, askForNodeId, askForPrice, askForSecret}, cbk) =>
157
250
  {
158
- return createInvoice({
251
+ // Exit early when there is no node to finalize the trade to
252
+ if (!askForNodeId.id) {
253
+ return cbk();
254
+ }
255
+
256
+ return finalizeTradeSecret({
159
257
  lnd,
160
258
  description: askForDescription.description,
161
- secret: encryptPayload.payment_secret,
259
+ secret: askForSecret.secret,
260
+ to: askForNodeId.id,
162
261
  tokens: asNumber(askForPrice.tokens),
163
262
  },
164
263
  cbk);
165
264
  }],
166
-
167
- // Encode all the trade data into wire format
168
- encodeTradeToWireFormat: [
169
- 'createPurchaseInvoice',
170
- 'encryptPayload',
171
- ({createPurchaseInvoice, encryptPayload}, cbk) =>
172
- {
173
- try {
174
- const {trade} = encodeTrade({
175
- auth: encryptPayload.trade_auth_tag,
176
- payload: encryptPayload.trade_cipher,
177
- request: createPurchaseInvoice.request,
178
- });
179
-
180
- return cbk(null, {trade});
181
- } catch (err) {
182
- return cbk([500, err.message]);
183
- }
184
- }],
185
265
  },
186
- returnResult({reject, resolve, of: 'encodeTradeToWireFormat'}, cbk));
266
+ returnResult({reject, resolve, of: 'finalize'}, cbk));
187
267
  });
188
268
  };
@@ -0,0 +1,45 @@
1
+ const findDescriptionRecord = records => records.find(n => n.type === '2');
2
+ const findIdRecord = records => records.find(n => n.type === '1');
3
+ const hexAsUtf8 = hex => Buffer.from(hex, 'hex').toString('utf8');
4
+ const {isArray} = Array;
5
+
6
+ /** Decode a basic trade
7
+
8
+ 1: <Trade Id>
9
+ 2: <Trade Description>
10
+
11
+ {
12
+ records: [{
13
+ type: <Basic Trade Record Type Number String>
14
+ value: <Value Hex Encoded String>
15
+ }]
16
+ }
17
+
18
+ @returns
19
+ {
20
+ description: <Trade Description String>
21
+ id: <Trade Id Hex String>
22
+ }
23
+ */
24
+ module.exports = ({records}) => {
25
+ if (!isArray(records)) {
26
+ throw new Error('ExpectedArrayOfRecordsToDecodeBasicTrade');
27
+ }
28
+
29
+ const descriptionRecord = findDescriptionRecord(records);
30
+
31
+ if (!descriptionRecord) {
32
+ throw new Error('ExpectedDescriptionRecordToDecodeBasicTrade');
33
+ }
34
+
35
+ const idRecord = findIdRecord(records);
36
+
37
+ if (!idRecord) {
38
+ throw new Error('ExpectedIdRecordToDecodeBasicTradeDetails');
39
+ }
40
+
41
+ return {
42
+ description: hexAsUtf8(descriptionRecord.value),
43
+ id: idRecord.value,
44
+ };
45
+ };
@@ -0,0 +1,69 @@
1
+ const {decodeTlvStream} = require('bolt01');
2
+
3
+ const recordAsNode = require('./record_as_node');
4
+
5
+ const findIdRecord = records => records.find(n => n.type === '5');
6
+ const findNodesRecord = records => records.find(n => n.type === '4');
7
+ const {isArray} = Array;
8
+
9
+ /** Decode open trade records
10
+
11
+ An open ended trade is a pointer to a node or nodes that can create trade
12
+ invoices.
13
+
14
+ 4: <Nodes Records>
15
+ [5]: <Trade Identifier>
16
+
17
+ {
18
+ network: <Network Name String>
19
+ records: [{
20
+ type: <Type Number String>
21
+ value: <Value Hex Encoded String>
22
+ }]
23
+ }
24
+
25
+ @throws
26
+ <Error>
27
+
28
+ @returns
29
+ {
30
+ [id]: <Trade Identifier Hex String>
31
+ network: <Network Name String>
32
+ nodes: [{
33
+ [high_channel]: <High Key Channel Id String>
34
+ [low_channel]: <Low Key Channel Id String>
35
+ [node]: {
36
+ id: <Node Public Key Id Hex String>
37
+ sockets: [<Peer Socket String>]
38
+ }
39
+ }]
40
+ }
41
+ */
42
+ module.exports = ({network, records}) => {
43
+ if (!network) {
44
+ throw new Error('ExpectedNetworkNameToDecodeOpenTrade');
45
+ }
46
+
47
+ if (!isArray(records)) {
48
+ throw new Error('ExpectedArrayOfRecordsToDecodeOpenTrade');
49
+ }
50
+
51
+ const idRecord = findIdRecord(records);
52
+ const nodesRecord = findNodesRecord(records);
53
+
54
+ if (!nodesRecord) {
55
+ throw new Error('ExpectedNodesRecordToDecodeOpenTradeDetails');
56
+ }
57
+
58
+ const nodeRecords = decodeTlvStream({encoded: nodesRecord.value}).records;
59
+
60
+ if (!nodeRecords.length) {
61
+ throw new Error('ExpectedNodeRecordsForOpenTrade');
62
+ }
63
+
64
+ return {
65
+ network,
66
+ id: !!idRecord ? idRecord.value : undefined,
67
+ nodes: nodeRecords.map(({value}) => recordAsNode({encoded: value})),
68
+ };
69
+ };
@@ -1,32 +1,49 @@
1
+ const {decodeBigSize} = require('bolt01');
1
2
  const {decodeTlvStream} = require('bolt01');
2
3
 
4
+ const decodeOpenTrade = require('./decode_open_trade');
5
+ const decodeTradeSecret = require('./decode_trade_secret');
3
6
  const networkFromNetworkRecord = require('./network_from_network_record');
4
- const {requestRecordsAsRequest} = require('./../records');
5
7
 
6
- const findAuth = records => records.find(n => n.type === '1');
7
- const findCipher = records => records.find(n => n.type === '0');
8
- const findDetails = records => records.find(n => n.type === '3');
9
- const findEncrypt = records => records.find(n => n.type === '0');
10
8
  const findNetwork = records => records.find(n => n.type === '1');
11
- const findRequest = records => records.find(n => n.type === '2');
12
- const findVersion = records => records.find(n => n.type === '0');
9
+ const findVer = records => records.find(n => n.type === '0') || {value: '00'};
10
+ const findRequestRecord = records => records.find(n => n.type === '2');
13
11
  const isTrade = trade => trade.toLowerCase().startsWith('626f73ff');
12
+ const isValidVersion = version => BigInt(version) <= BigInt(1);
14
13
  const tradeData = trade => trade.slice('626f73ff'.length);
15
14
 
16
- /** Encode a trade
15
+ /** Decode a trade record
16
+
17
+ [0]: <Version Record>
18
+ [1]: <Network Name Record>
19
+ [2]: <Payment Request Record>
20
+ [3]: <Trade Details Record
21
+ [4]: <Nodes Records>
22
+ [5]: <Trade Identifier>
17
23
 
18
24
  {
19
- trade: <Hex Encoded Trade String>
25
+ trade: <Trade Record Hex String>
20
26
  }
21
27
 
22
- @throws
23
- <Error>
24
-
25
28
  @returns
26
29
  {
27
- auth: <Encrypted Payload Auth Hex String>
28
- payload: <Preimage Encrypted Payload Hex String>
29
- request: <BOLT 11 Payment Request String>
30
+ [connect]: {
31
+ [id]: <Reference Trade Id Hex String>
32
+ network: <Network Name String>
33
+ nodes: [{
34
+ [high_channel]: <High Key Channel Id String>
35
+ [low_channel]: <Low Key Channel Id String>
36
+ [node]: {
37
+ id: <Node Public Key Id Hex String>
38
+ sockets: [<Peer Socket String>]
39
+ }
40
+ }]
41
+ }
42
+ [secret]: {
43
+ auth: <Encrypted Payload Auth Hex String>
44
+ payload: <Preimage Encrypted Payload Hex String>
45
+ request: <BOLT 11 Payment Request String>
46
+ }
30
47
  }
31
48
  */
32
49
  module.exports = ({trade}) => {
@@ -45,64 +62,29 @@ module.exports = ({trade}) => {
45
62
  }
46
63
 
47
64
  // Decode the overall packet
48
- const tradeRecords = decodeTlvStream({encoded: tradeData(trade)}).records;
65
+ const {records} = decodeTlvStream({encoded: tradeData(trade)});
49
66
 
50
67
  // Get the trade version
51
- const version = findVersion(tradeRecords);
52
-
53
- if (!!version) {
54
- throw new Error('UnexpectedVersionOfTradeData');
55
- }
56
-
57
- // Get the network record
58
- const networkRecord = findNetwork(tradeRecords) || {};
59
-
60
- // Get the payment request record
61
- const requestRecord = findRequest(tradeRecords);
62
-
63
- if (!requestRecord) {
64
- throw new Error('ExpectedRequestRecordInTradeData');
65
- }
66
-
67
- const {request} = requestRecordsAsRequest({
68
- network: networkFromNetworkRecord({value: networkRecord.value}),
69
- encoded: requestRecord.value,
70
- });
68
+ const versionRecord = findVer(records);
71
69
 
72
- // Get the trade details
73
- const detailsRecord = findDetails(tradeRecords);
70
+ // Decode the version number
71
+ const version = decodeBigSize({encoded: versionRecord.value}).decoded;
74
72
 
75
- if (!detailsRecord) {
76
- throw new Error('ExpctedDetailsRecordToDecodeTrade');
73
+ // Too-high versions cannot be parsed
74
+ if (!isValidVersion(version)) {
75
+ throw new Error('ExpectedKnownVersionToDecodeTrade');
77
76
  }
78
77
 
79
- // Trade details
80
- const details = decodeTlvStream({encoded: detailsRecord.value}).records;
81
-
82
- // Encode the encrypted data
83
- const encryptedRecord = findEncrypt(details);
84
-
85
- if (!encryptedRecord) {
86
- throw new Error('ExpectedEncryptedRecordToDecodeTrade');
87
- }
88
-
89
- const encrypted = decodeTlvStream({encoded: encryptedRecord.value}).records;
90
-
91
- const encryptedDataRecord = findCipher(encrypted);
92
-
93
- if (!encryptedDataRecord) {
94
- throw new Error('ExpectedEncryptedDataRecordToDecodeTrade');
95
- }
78
+ // Get the network record
79
+ const networkRecord = findNetwork(records) || {};
96
80
 
97
- const authDataRecord = findAuth(encrypted);
81
+ // Decode the network record
82
+ const {network} = networkFromNetworkRecord({value: networkRecord.value});
98
83
 
99
- if (!authDataRecord) {
100
- throw new Error('ExpectedAuthDataRecordToDecodeTrade');
84
+ // A trade is either a trade secret to someone or an open trade offer
85
+ if (!!findRequestRecord(records)) {
86
+ return {secret: decodeTradeSecret({network, records})};
87
+ } else {
88
+ return {connect: decodeOpenTrade({network, records})};
101
89
  }
102
-
103
- return {
104
- request,
105
- auth: authDataRecord.value,
106
- payload: encryptedDataRecord.value,
107
- };
108
90
  };
@@ -0,0 +1,89 @@
1
+ const {decodeTlvStream} = require('bolt01');
2
+
3
+ const {requestRecordsAsRequest} = require('./../records');
4
+
5
+ const findAuth = records => records.find(n => n.type === '1');
6
+ const findCipher = records => records.find(n => n.type === '0');
7
+ const findDetails = records => records.find(n => n.type === '3');
8
+ const findEncrypt = records => records.find(n => n.type === '0');
9
+ const findRequest = records => records.find(n => n.type === '2');
10
+ const {isArray} = Array;
11
+
12
+ /** Decode a trade secret
13
+
14
+ 2: <Payment Request Record>
15
+ 3: <Trade Details Record
16
+
17
+ {
18
+ network: <Network Name String>
19
+ records: [{
20
+ type: <Type Number String>
21
+ value: <Hex Encoded Value String>
22
+ }]
23
+ }
24
+
25
+ @throws
26
+ <Error>
27
+
28
+ @returns
29
+ {
30
+ auth: <Encrypted Payload Auth Hex String>
31
+ payload: <Preimage Encrypted Payload Hex String>
32
+ request: <BOLT 11 Payment Request String>
33
+ }
34
+ */
35
+ module.exports = ({network, records}) => {
36
+ if (!network) {
37
+ throw new Error('ExpectedNetworkNameToDecodeTradeSecret');
38
+ }
39
+
40
+ if (!isArray(records)) {
41
+ throw new Error('ExpectedArrayOfRecordsToDecodeTradeSecret');
42
+ }
43
+
44
+ // Get the payment request record
45
+ const requestRecord = findRequest(records);
46
+
47
+ if (!requestRecord) {
48
+ throw new Error('ExpectedRequestRecordInTradeData');
49
+ }
50
+
51
+ const encoded = requestRecord.value;
52
+
53
+ // Get the trade details
54
+ const detailsRecord = findDetails(records);
55
+
56
+ if (!detailsRecord) {
57
+ throw new Error('ExpctedDetailsRecordToDecodeTrade');
58
+ }
59
+
60
+ // Trade details
61
+ const details = decodeTlvStream({encoded: detailsRecord.value}).records;
62
+
63
+ // Encode the encrypted data
64
+ const encryptedRecord = findEncrypt(details);
65
+
66
+ if (!encryptedRecord) {
67
+ throw new Error('ExpectedEncryptedRecordToDecodeTrade');
68
+ }
69
+
70
+ const encrypted = decodeTlvStream({encoded: encryptedRecord.value}).records;
71
+
72
+ const encryptedDataRecord = findCipher(encrypted);
73
+
74
+ if (!encryptedDataRecord) {
75
+ throw new Error('ExpectedEncryptedDataRecordToDecodeTrade');
76
+ }
77
+
78
+ const authDataRecord = findAuth(encrypted);
79
+
80
+ if (!authDataRecord) {
81
+ throw new Error('ExpectedAuthDataRecordToDecodeTrade');
82
+ }
83
+
84
+ return {
85
+ request: requestRecordsAsRequest({encoded, network}).request,
86
+ auth: authDataRecord.value,
87
+ payload: encryptedDataRecord.value,
88
+ };
89
+ };
@@ -0,0 +1,89 @@
1
+ const {encodeTlvStream} = require('bolt01');
2
+
3
+ const networkRecordFromNetwork = require('./network_record_from_network');
4
+ const nodeAsRecord = require('./node_as_record');
5
+
6
+ const asTrade = hex => `626f73ff${hex}`;
7
+ const idType = '5';
8
+ const {isArray} = Array;
9
+ const networkRecordType = '1';
10
+ const nodeType = '4';
11
+ const versionRecord = '01';
12
+ const versionType = '0';
13
+
14
+ /** Encode records for an open ended trade
15
+
16
+ An open ended trade is a pointer to a node or nodes that can create trade
17
+ invoices. At the minimum it has a pointer to a node.
18
+
19
+ 0: version (v1)
20
+ [1]: network name
21
+ 4: nodes records
22
+ [5]: trade identifier
23
+
24
+ {
25
+ [id]: <Trade Identifier Hex String>
26
+ network: <Network Name String>
27
+ nodes: [{
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
+
37
+ @throws
38
+ <Error>
39
+
40
+ @returns
41
+ {
42
+ trade: <Encoded Open Ended Trade Hex String>
43
+ }
44
+ */
45
+ module.exports = ({id, network, nodes}) => {
46
+ if (!network) {
47
+ throw new Error('ExpectedNetworkNameToEncodeOpenTrade');
48
+ }
49
+
50
+ if (!isArray(nodes)) {
51
+ throw new Error('ExpectedArrayOfNodesToEncodeOpenTrade');
52
+ }
53
+
54
+ if (!nodes.length) {
55
+ throw new Error('ExpectedNodeToReferToInOpenTrade');
56
+ }
57
+
58
+ // Add trade version record
59
+ const records = [{type: versionType, value: versionRecord}];
60
+
61
+ // Add a network name record if required
62
+ if (!!networkRecordFromNetwork({network}).value) {
63
+ records.push({
64
+ type: networkRecordType,
65
+ value: networkRecordFromNetwork({network}).value,
66
+ });
67
+ }
68
+
69
+ // A trade id refers to a specific trade
70
+ if (!!id) {
71
+ records.push({type: idType, value: id});
72
+ }
73
+
74
+ // Map the nodes to node records
75
+ const nodeRecords = nodes.map(({channels, id, sockets}, type) => {
76
+ return {
77
+ type: type.toString(),
78
+ value: nodeAsRecord({channels, id, sockets}).encoded,
79
+ };
80
+ });
81
+
82
+ // Add the node records to the open trade
83
+ records.push({
84
+ type: nodeType,
85
+ value: encodeTlvStream({records: nodeRecords}).encoded,
86
+ });
87
+
88
+ return {trade: asTrade(encodeTlvStream({records}).encoded)};
89
+ };