paid-services 3.3.0 → 3.6.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 (54) hide show
  1. package/.prettierignore +2 -0
  2. package/CHANGELOG.md +17 -0
  3. package/capacity/accept_capacity_change.js +3 -3
  4. package/capacity/ask_for_change_details.js +34 -1
  5. package/capacity/capacity_change_requests.js +9 -0
  6. package/capacity/change_channel_capacity.js +7 -1
  7. package/capacity/encode_change_request.js +8 -1
  8. package/capacity/fee_for_replacement.js +1 -1
  9. package/capacity/get_capacity_change_requests.js +2 -1
  10. package/capacity/get_capacity_replacement.js +1 -1
  11. package/capacity/initiate_capacity_change.js +5 -5
  12. package/capacity/parse_capacity_change_request.js +8 -1
  13. package/capacity/propose_capacity_change.js +4 -1
  14. package/p2p/service_peer_requests.js +10 -2
  15. package/package.json +10 -9
  16. package/service_types.json +16 -0
  17. package/services/response_for_relay.js +0 -1
  18. package/test/capacity/test_capacity_change_requests.js +1 -0
  19. package/test/capacity/test_encode_change_request.js +5 -0
  20. package/test/capacity/test_parse_capacity_change_request.js +1 -0
  21. package/test/integration/test_accept_capacity_change.js +50 -14
  22. package/test/integration/test_change_channel_capacity.js +7 -1
  23. package/test/integration/test_decrease_channel_capacity.js +16 -7
  24. package/test/integration/test_increase_channel_capacity.js +4 -1
  25. package/test/trades/test_decode_basic_trade.js +40 -0
  26. package/test/trades/test_decode_open_trade.js +50 -0
  27. package/test/trades/test_encode_open_trade.js +35 -0
  28. package/test/trades/test_encode_trade.js +37 -0
  29. package/trades/accept_trade.js +61 -0
  30. package/trades/create_trade.js +149 -37
  31. package/trades/decode_anchored_trade.js +56 -0
  32. package/trades/decode_basic_trade.js +45 -0
  33. package/trades/decode_open_trade.js +69 -0
  34. package/trades/decode_trade.js +48 -66
  35. package/trades/decode_trade_secret.js +89 -0
  36. package/trades/encode_anchored_trade.js +35 -0
  37. package/trades/encode_open_trade.js +89 -0
  38. package/trades/encode_trade.js +48 -33
  39. package/trades/encode_trade_secret.js +50 -0
  40. package/trades/finalize_trade_secret.js +123 -0
  41. package/trades/find_trade.js +339 -0
  42. package/trades/manage_trade.js +61 -39
  43. package/trades/manage_trades.js +209 -2
  44. package/trades/network_from_network_record.js +3 -3
  45. package/trades/network_record_from_network.js +36 -0
  46. package/trades/network_record_from_request.js +2 -1
  47. package/trades/node_as_record.js +98 -0
  48. package/trades/record_as_node.js +113 -0
  49. package/trades/request_trade_by_id.js +95 -0
  50. package/trades/service_anchored_trades.js +181 -0
  51. package/trades/service_open_trade.js +172 -0
  52. package/trades/service_trade_requests.js +255 -0
  53. package/trades/trade_from_invoice.js +51 -0
  54. package/capacity/service_types.json +0 -6
@@ -1,11 +1,29 @@
1
1
  const asyncAuto = require('async/auto');
2
+ const asyncUntil = require('async/until');
3
+ const {cancelHodlInvoice} = require('ln-service');
4
+ const {diffieHellmanComputeSecret} = require('ln-service');
5
+ const {getIdentity} = require('ln-service');
6
+ const {getInvoices} = require('ln-service');
7
+ const {getNetwork} = require('ln-sync');
8
+ const {getNodeAlias} = require('ln-sync');
2
9
  const {returnResult} = require('asyncjs-util');
3
10
 
4
11
  const createTrade = require('./create_trade');
12
+ const encodeTrade = require('./encode_trade');
5
13
  const manageTrade = require('./manage_trade');
14
+ const serviceAnchoredTrades = require('./service_anchored_trades');
15
+ const serviceOpenTrade = require('./service_open_trade');
16
+ const tradeFromInvoice = require('./trade_from_invoice');
6
17
 
18
+ const cancelAction = 'cancel-';
7
19
  const createAction = 'create';
8
20
  const decodeAction = 'decode';
21
+ const defaultInvoicesLimit = 100;
22
+ const listAction = 'list';
23
+ const serveAction = 'serve';
24
+ const serveTradeAction = 'serve-trade-';
25
+ const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
26
+ const viewAction = 'view';
9
27
 
10
28
  /** Create, view, and accept trades
11
29
 
@@ -13,11 +31,12 @@ const decodeAction = 'decode';
13
31
  ask: <Ask Function>
14
32
  lnd: <Authenticated LND API Object>
15
33
  logger: <Winston Logger Object>
34
+ separator: <Create Separator Function>
16
35
  }
17
36
 
18
37
  @returns via cbk or Promise
19
38
  */
20
- module.exports = ({ask, lnd, logger}, cbk) => {
39
+ module.exports = ({ask, lnd, logger, separator}, cbk) => {
21
40
  return new Promise((resolve, reject) => {
22
41
  return asyncAuto({
23
42
  // Check arguments
@@ -37,12 +56,21 @@ module.exports = ({ask, lnd, logger}, cbk) => {
37
56
  return cbk();
38
57
  },
39
58
 
59
+ // Get node identity
60
+ getIdentity: ['validate', ({}, cbk) => getIdentity({lnd}, cbk)],
61
+
62
+ // Get the network name
63
+ getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
64
+
40
65
  // Select a trade option
41
66
  select: ['validate', ({}, cbk) => {
42
67
  return ask({
43
68
  choices: [
44
69
  {name: 'Create Trade', value: createAction},
45
70
  {name: 'Decode Trade', value: decodeAction},
71
+ separator(),
72
+ {name: 'Open Trades', value: listAction},
73
+ {name: 'Serve Trades', value: serveAction},
46
74
  ],
47
75
  message: 'Trade?',
48
76
  name: 'action',
@@ -51,6 +79,15 @@ module.exports = ({ask, lnd, logger}, cbk) => {
51
79
  cbk);
52
80
  }],
53
81
 
82
+ // Confirm that signer RPC is enabled
83
+ checkSigner: ['getIdentity', ({getIdentity}, cbk) => {
84
+ return diffieHellmanComputeSecret({
85
+ lnd,
86
+ partner_public_key: getIdentity.public_key,
87
+ },
88
+ cbk);
89
+ }],
90
+
54
91
  // Create a new trade
55
92
  create: ['select', ({select}, cbk) => {
56
93
  // Exit early when not creating a new trade
@@ -74,7 +111,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
74
111
  }],
75
112
 
76
113
  // View an existing trade
77
- view: ['select', ({select}, cbk) => {
114
+ view: ['checkSigner', 'select', ({checkSigner, select}, cbk) => {
78
115
  // Exit early when not decoding a trade
79
116
  if (select.action !== decodeAction) {
80
117
  return cbk();
@@ -82,6 +119,176 @@ module.exports = ({ask, lnd, logger}, cbk) => {
82
119
 
83
120
  return manageTrade({ask, lnd, logger}, cbk)
84
121
  }],
122
+
123
+ // Get open trades
124
+ getTrades: ['validate', ({}, cbk) => {
125
+ const paging = {trades: []};
126
+
127
+ // Find open anchored trades
128
+ asyncUntil(
129
+ cbk => cbk(null, paging.token === false),
130
+ cbk => {
131
+ return getInvoices({
132
+ lnd,
133
+ is_unconfirmed: true,
134
+ limit: !paging.token ? defaultInvoicesLimit : undefined,
135
+ token: paging.token,
136
+ },
137
+ (err, res) => {
138
+ if (!!err) {
139
+ return cbk(err);
140
+ }
141
+
142
+ paging.token = res.next || false;
143
+
144
+ res.invoices
145
+ .map(tradeFromInvoice)
146
+ .map(n => n.trade)
147
+ .filter(n => !!n)
148
+ .forEach(n => paging.trades.push(n));
149
+
150
+ return cbk();
151
+ });
152
+ },
153
+ err => {
154
+ if (!!err) {
155
+ return cbk(err);
156
+ }
157
+
158
+ return cbk(null, paging.trades);
159
+ }
160
+ );
161
+ }],
162
+
163
+ // List open trades
164
+ list: ['getTrades', 'select', ({getTrades, select}, cbk) => {
165
+ // Exit early when not viewing open trades
166
+ if (select.action !== listAction) {
167
+ return cbk();
168
+ }
169
+
170
+ if (!getTrades.length) {
171
+ return cbk([404, 'NoTradesCurrentlyOpen']);
172
+ }
173
+
174
+ return ask({
175
+ choices: getTrades.map(trade => ({
176
+ name: `${tokensAsBigUnit(trade.tokens)} ${trade.description}`,
177
+ value: trade.id,
178
+ })),
179
+ message: 'Trade?',
180
+ name: 'manage',
181
+ type: 'list',
182
+ },
183
+ cbk);
184
+ }],
185
+
186
+ // Manage an open trades
187
+ manage: ['getTrades', 'list', ({getTrades, list}, cbk) => {
188
+ // Exit early when not viewing open trades
189
+ if (!list) {
190
+ return cbk();
191
+ }
192
+
193
+ return ask({
194
+ choices: [
195
+ {name: 'Serve Trade', value: `${serveTradeAction}${list.manage}`},
196
+ separator(),
197
+ {name: 'Cancel Trade', value: `${cancelAction}${list.manage}`},
198
+ ],
199
+ message: 'Trade?',
200
+ name: 'trade',
201
+ type: 'list',
202
+ },
203
+ cbk);
204
+ }],
205
+
206
+ // Service an open trade
207
+ serviceTrade: [
208
+ 'getIdentity',
209
+ 'getNetwork',
210
+ 'getTrades',
211
+ 'manage',
212
+ ({getIdentity, getNetwork, getTrades, manage}, cbk) =>
213
+ {
214
+ // Exit early when this is not cancel of an open trade
215
+ if (!manage || !manage.trade.startsWith(serveTradeAction)) {
216
+ return cbk();
217
+ }
218
+
219
+ const id = manage.trade.slice(serveTradeAction.length);
220
+
221
+ const trade = getTrades.find(n => n.id === id);
222
+
223
+ return serviceOpenTrade({
224
+ lnd,
225
+ logger,
226
+ channels: [],
227
+ description: trade.description,
228
+ expires_at: trade.expires_at,
229
+ id: trade.id,
230
+ network: getNetwork.network,
231
+ public_key: getIdentity.public_key,
232
+ secret: trade.secret,
233
+ tokens: trade.tokens,
234
+ uris: [],
235
+ },
236
+ cbk);
237
+ }],
238
+
239
+ // Cancel an open trade
240
+ cancelTrade: ['manage', ({manage}, cbk) => {
241
+ // Exit early when this is not cancel of an open trade
242
+ if (!manage || !manage.trade.startsWith(cancelAction)) {
243
+ return cbk();
244
+ }
245
+
246
+ return cancelHodlInvoice({
247
+ lnd,
248
+ id: manage.trade.slice(cancelAction.length),
249
+ },
250
+ cbk);
251
+ }],
252
+
253
+ // Serve open trades
254
+ service: [
255
+ 'getIdentity',
256
+ 'getNetwork',
257
+ 'select',
258
+ ({getIdentity, getNetwork, select}, cbk) =>
259
+ {
260
+ // Exit early when not serving trades
261
+ if (select.action !== serveAction) {
262
+ return cbk();
263
+ }
264
+
265
+ const sub = serviceAnchoredTrades({lnd});
266
+
267
+ const {trade} = encodeTrade({
268
+ connect: {
269
+ network: getNetwork.network,
270
+ nodes: [{channels: [], id: getIdentity.public_key, sockets: []}],
271
+ },
272
+ });
273
+
274
+ logger.info({servicing_all_open_trades: trade});
275
+
276
+ sub.on('settled', async ({description, to, tokens}) => {
277
+ const {alias, id} = await getNodeAlias({lnd, id: to});
278
+
279
+ return logger.info(`sold: ${tokensAsBigUnit(tokens)} ${description} to ${alias} ${to}`);
280
+ });
281
+
282
+ sub.on('start', ({description, tokens}) => {
283
+ return logger.info(`open: ${tokensAsBigUnit(tokens)} ${description}`);
284
+ });
285
+
286
+ sub.on('error', err => {
287
+ sub.removeAllListeners();
288
+
289
+ return cbk(err);
290
+ });
291
+ }],
85
292
  },
86
293
  returnResult({reject, resolve}, cbk));
87
294
  });
@@ -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,95 @@
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
+ trade: <Encoded Trade Record String>
26
+ }
27
+ */
28
+ module.exports = ({id, lnd, to}, cbk) => {
29
+ return new Promise((resolve, reject) => {
30
+ return asyncAuto({
31
+ // Check arguments
32
+ validate: cbk => {
33
+ if (!id) {
34
+ return cbk([400, 'ExpectedTradeIdentifierToRequestTradeById']);
35
+ }
36
+
37
+ if (!lnd) {
38
+ return cbk([400, 'ExpectedAuthenticatedLndToRequestTradeById']);
39
+ }
40
+
41
+ if (!to) {
42
+ return cbk([400, 'ExpectedRemotePublicKeyToRequestTradeById']);
43
+ }
44
+
45
+ return cbk();
46
+ },
47
+
48
+ // Get trade
49
+ getTrade: ['validate', ({}, cbk) => {
50
+ return makePeerRequest({
51
+ lnd,
52
+ to,
53
+ records: [{type: tradeIdRecordType, value: id}],
54
+ timeout: requestTradeTimeoutMs,
55
+ type: serviceTypeRequestTrades,
56
+ },
57
+ (err, res) => {
58
+ if (!!err) {
59
+ return cbk(err);
60
+ }
61
+
62
+ const tradeRecord = findTradeRecord(res.records);
63
+
64
+ if (!tradeRecord) {
65
+ return cbk([503, 'ExpectedTradeRecordFromPeer']);
66
+ }
67
+
68
+ try {
69
+ decodeTrade({trade: tradeRecord.value});
70
+ } catch (err) {
71
+ return cbk([503, err.message]);
72
+ }
73
+
74
+ const {secret} = decodeTrade({trade: tradeRecord.value});
75
+
76
+ if (!secret) {
77
+ return cbk([503, 'ExpectedTradeSecretInResponseForTradeById']);
78
+ }
79
+
80
+ try {
81
+ return cbk(null, {
82
+ auth: secret.auth,
83
+ payload: secret.payload,
84
+ request: secret.request,
85
+ trade: tradeRecord.value,
86
+ });
87
+ } catch (err) {
88
+ return cbk([503, 'ExpectedValidTradeRecordFromPeer', {err}]);
89
+ }
90
+ });
91
+ }],
92
+ },
93
+ returnResult({reject, resolve, of: 'getTrade'}, cbk));
94
+ });
95
+ };