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.
- package/CHANGELOG.md +5 -0
- package/capacity/accept_capacity_change.js +3 -3
- package/capacity/get_capacity_change_requests.js +1 -1
- package/capacity/get_capacity_replacement.js +1 -1
- package/capacity/initiate_capacity_change.js +5 -4
- package/capacity/propose_capacity_change.js +3 -1
- package/p2p/service_peer_requests.js +10 -2
- package/package.json +3 -2
- package/service_types.json +16 -0
- package/test/integration/test_accept_capacity_change.js +50 -13
- package/test/integration/test_change_channel_capacity.js +3 -0
- package/test/trades/test_decode_basic_trade.js +40 -0
- package/test/trades/test_decode_open_trade.js +50 -0
- package/test/trades/test_encode_open_trade.js +35 -0
- package/test/trades/test_encode_trade.js +37 -0
- package/trades/accept_trade.js +52 -0
- package/trades/create_trade.js +124 -44
- package/trades/decode_basic_trade.js +45 -0
- package/trades/decode_open_trade.js +69 -0
- package/trades/decode_trade.js +48 -66
- package/trades/decode_trade_secret.js +89 -0
- package/trades/encode_open_trade.js +89 -0
- package/trades/encode_trade.js +48 -33
- package/trades/encode_trade_secret.js +50 -0
- package/trades/finalize_trade_secret.js +116 -0
- package/trades/find_trade.js +314 -0
- package/trades/manage_trade.js +46 -35
- package/trades/network_from_network_record.js +3 -3
- package/trades/network_record_from_network.js +36 -0
- package/trades/network_record_from_request.js +2 -1
- package/trades/node_as_record.js +98 -0
- package/trades/record_as_node.js +113 -0
- package/trades/request_trade_by_id.js +89 -0
- package/trades/service_trade_requests.js +184 -0
- package/capacity/service_types.json +0 -6
package/trades/create_trade.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
-
const {
|
|
3
|
-
const {
|
|
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
|
|
7
|
-
const
|
|
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
|
-
|
|
56
|
+
getIdentity: ['validate', ({}, cbk) => getWalletInfo({lnd}, cbk)],
|
|
47
57
|
|
|
48
58
|
// Ask for the public key of the node to trade with
|
|
49
|
-
askForNodeId: ['
|
|
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
|
|
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 ===
|
|
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
|
-
//
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
//
|
|
152
|
-
|
|
243
|
+
// Finalize the trade with an encrypted secret and an invoice
|
|
244
|
+
finalize: [
|
|
153
245
|
'askForDescription',
|
|
246
|
+
'askForNodeId',
|
|
154
247
|
'askForPrice',
|
|
155
|
-
'
|
|
156
|
-
({askForDescription, askForPrice,
|
|
248
|
+
'askForSecret',
|
|
249
|
+
({askForDescription, askForNodeId, askForPrice, askForSecret}, cbk) =>
|
|
157
250
|
{
|
|
158
|
-
|
|
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:
|
|
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: '
|
|
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
|
+
};
|
package/trades/decode_trade.js
CHANGED
|
@@ -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
|
|
12
|
-
const
|
|
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
|
-
/**
|
|
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: <
|
|
25
|
+
trade: <Trade Record Hex String>
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
@throws
|
|
23
|
-
<Error>
|
|
24
|
-
|
|
25
28
|
@returns
|
|
26
29
|
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
65
|
+
const {records} = decodeTlvStream({encoded: tradeData(trade)});
|
|
49
66
|
|
|
50
67
|
// Get the trade version
|
|
51
|
-
const
|
|
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
|
-
//
|
|
73
|
-
const
|
|
70
|
+
// Decode the version number
|
|
71
|
+
const version = decodeBigSize({encoded: versionRecord.value}).decoded;
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
// Too-high versions cannot be parsed
|
|
74
|
+
if (!isValidVersion(version)) {
|
|
75
|
+
throw new Error('ExpectedKnownVersionToDecodeTrade');
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
//
|
|
80
|
-
const
|
|
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
|
-
|
|
81
|
+
// Decode the network record
|
|
82
|
+
const {network} = networkFromNetworkRecord({value: networkRecord.value});
|
|
98
83
|
|
|
99
|
-
|
|
100
|
-
|
|
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
|
+
};
|