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.
- package/.prettierignore +2 -0
- package/CHANGELOG.md +17 -0
- package/capacity/accept_capacity_change.js +3 -3
- package/capacity/ask_for_change_details.js +34 -1
- package/capacity/capacity_change_requests.js +9 -0
- package/capacity/change_channel_capacity.js +7 -1
- package/capacity/encode_change_request.js +8 -1
- package/capacity/fee_for_replacement.js +1 -1
- package/capacity/get_capacity_change_requests.js +2 -1
- package/capacity/get_capacity_replacement.js +1 -1
- package/capacity/initiate_capacity_change.js +5 -5
- package/capacity/parse_capacity_change_request.js +8 -1
- package/capacity/propose_capacity_change.js +4 -1
- package/p2p/service_peer_requests.js +10 -2
- package/package.json +10 -9
- package/service_types.json +16 -0
- package/services/response_for_relay.js +0 -1
- package/test/capacity/test_capacity_change_requests.js +1 -0
- package/test/capacity/test_encode_change_request.js +5 -0
- package/test/capacity/test_parse_capacity_change_request.js +1 -0
- package/test/integration/test_accept_capacity_change.js +50 -14
- package/test/integration/test_change_channel_capacity.js +7 -1
- package/test/integration/test_decrease_channel_capacity.js +16 -7
- package/test/integration/test_increase_channel_capacity.js +4 -1
- 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 +61 -0
- package/trades/create_trade.js +149 -37
- package/trades/decode_anchored_trade.js +56 -0
- 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_anchored_trade.js +35 -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 +123 -0
- package/trades/find_trade.js +339 -0
- package/trades/manage_trade.js +61 -39
- package/trades/manage_trades.js +209 -2
- 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 +95 -0
- package/trades/service_anchored_trades.js +181 -0
- package/trades/service_open_trade.js +172 -0
- package/trades/service_trade_requests.js +255 -0
- package/trades/trade_from_invoice.js +51 -0
- package/capacity/service_types.json +0 -6
package/trades/manage_trades.js
CHANGED
|
@@ -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
|
+
};
|