paid-services 3.4.0 → 3.7.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 +16 -0
- package/capacity/accept_capacity_change.js +2 -0
- package/capacity/ask_for_change_details.js +41 -9
- package/capacity/ask_for_decrease.js +167 -49
- 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 +3 -7
- package/capacity/get_capacity_change_requests.js +1 -0
- package/capacity/get_capacity_replacement.js +56 -7
- package/capacity/initiate_capacity_change.js +1 -2
- package/capacity/parse_capacity_change_request.js +8 -1
- package/capacity/propose_capacity_change.js +208 -92
- package/capacity/propose_channel_open.js +54 -0
- package/package.json +9 -9
- 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 +3 -4
- package/test/integration/test_change_channel_capacity.js +8 -1
- package/test/integration/test_decrease_channel_capacity.js +23 -8
- package/test/integration/test_increase_channel_capacity.js +4 -1
- package/test/integration/test_split_channel_capacity.js +241 -0
- package/trades/accept_trade.js +12 -3
- package/trades/create_trade.js +82 -50
- package/trades/decode_anchored_trade.js +56 -0
- package/trades/encode_anchored_trade.js +35 -0
- package/trades/finalize_trade_secret.js +7 -0
- package/trades/find_trade.js +28 -3
- package/trades/manage_trade.js +15 -4
- package/trades/manage_trades.js +209 -2
- package/trades/request_trade_by_id.js +7 -1
- package/trades/service_anchored_trades.js +181 -0
- package/trades/service_open_trade.js +172 -0
- package/trades/service_trade_requests.js +106 -35
- package/trades/trade_from_invoice.js +51 -0
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
|
});
|
|
@@ -22,6 +22,7 @@ const tradeIdRecordType = '0';
|
|
|
22
22
|
auth: <Encrypted Payload Auth Hex String>
|
|
23
23
|
payload: <Preimage Encrypted Payload Hex String>
|
|
24
24
|
request: <BOLT 11 Payment Request String>
|
|
25
|
+
trade: <Encoded Trade Record String>
|
|
25
26
|
}
|
|
26
27
|
*/
|
|
27
28
|
module.exports = ({id, lnd, to}, cbk) => {
|
|
@@ -77,7 +78,12 @@ module.exports = ({id, lnd, to}, cbk) => {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
try {
|
|
80
|
-
return cbk(null,
|
|
81
|
+
return cbk(null, {
|
|
82
|
+
auth: secret.auth,
|
|
83
|
+
payload: secret.payload,
|
|
84
|
+
request: secret.request,
|
|
85
|
+
trade: tradeRecord.value,
|
|
86
|
+
});
|
|
81
87
|
} catch (err) {
|
|
82
88
|
return cbk([503, 'ExpectedValidTradeRecordFromPeer', {err}]);
|
|
83
89
|
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
const EventEmitter = require('events');
|
|
2
|
+
|
|
3
|
+
const asyncUntil = require('async/until');
|
|
4
|
+
const {getInvoices} = require('ln-service');
|
|
5
|
+
const {subscribeToInvoice} = require('ln-service');
|
|
6
|
+
const {subscribeToInvoices} = require('ln-service');
|
|
7
|
+
|
|
8
|
+
const decodeAnchoredTrade = require('./decode_anchored_trade');
|
|
9
|
+
const serviceTradeRequests = require('./service_trade_requests');
|
|
10
|
+
const tradeFromInvoice = require('./trade_from_invoice');
|
|
11
|
+
|
|
12
|
+
const defaultInvoicesLimit = 100;
|
|
13
|
+
const events = ['details', 'error', 'failure', 'settled', 'trade'];
|
|
14
|
+
const fromNow = date => new Date(date) - new Date();
|
|
15
|
+
const {keys} = Object;
|
|
16
|
+
|
|
17
|
+
/** Service trades represented by anchored invoices
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
lnd: <Authenticated LND API Object>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Returned details
|
|
24
|
+
@event 'details'
|
|
25
|
+
{
|
|
26
|
+
to: <Public Key Id Hex String>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Terminal failure
|
|
30
|
+
@event 'error'
|
|
31
|
+
<Error Object>
|
|
32
|
+
|
|
33
|
+
// Encountered a failure
|
|
34
|
+
@event 'failure'
|
|
35
|
+
[<Failure Code Number>, <Failure Code String>]
|
|
36
|
+
|
|
37
|
+
// Received payment
|
|
38
|
+
@event 'settled'
|
|
39
|
+
{
|
|
40
|
+
description: <Trade Description String>
|
|
41
|
+
id: <Trade Id Hex String>
|
|
42
|
+
secret: <Secret Payload String>
|
|
43
|
+
to: <Public Key Id Hex String>
|
|
44
|
+
tokens: <Tokens Number>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Starting service for trade
|
|
48
|
+
@event 'start'
|
|
49
|
+
{
|
|
50
|
+
description: <Trade Description String>
|
|
51
|
+
expires_at: <Trade Expires at ISO 8601 Date String>
|
|
52
|
+
id: <Trade Id Hex String>
|
|
53
|
+
secret: <Secret Payload String>
|
|
54
|
+
tokens: <Tokens Number>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Return finalized trade
|
|
58
|
+
@event 'trade'
|
|
59
|
+
{
|
|
60
|
+
to: <Public Key Id Hex String>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@returns
|
|
64
|
+
<EventEmitter Object>
|
|
65
|
+
*/
|
|
66
|
+
module.exports = ({lnd}) => {
|
|
67
|
+
const emitter = new EventEmitter();
|
|
68
|
+
const listening = {};
|
|
69
|
+
const paging = {};
|
|
70
|
+
|
|
71
|
+
// Service a trade until it's paid or canceled
|
|
72
|
+
const serviceTrade = trade => {
|
|
73
|
+
// Exit early when already listening to this trade
|
|
74
|
+
if (!!listening[trade.id]) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const sub = serviceTradeRequests({
|
|
79
|
+
lnd,
|
|
80
|
+
description: trade.description,
|
|
81
|
+
expires_at: trade.expires_at,
|
|
82
|
+
id: trade.id,
|
|
83
|
+
secret: trade.secret,
|
|
84
|
+
tokens: trade.tokens,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Pick up the trade to service
|
|
88
|
+
emitter.emit('start', {
|
|
89
|
+
description: trade.description,
|
|
90
|
+
expires_at: trade.expires_at,
|
|
91
|
+
id: trade.id,
|
|
92
|
+
secret: trade.secret,
|
|
93
|
+
tokens: trade.tokens,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Settled a trade
|
|
97
|
+
sub.on('settled', ({to}) => {
|
|
98
|
+
return emitter.emit('settled', {
|
|
99
|
+
to,
|
|
100
|
+
id: trade.id,
|
|
101
|
+
description: trade.description,
|
|
102
|
+
secret: trade.secret,
|
|
103
|
+
tokens: trade.tokens,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Remember this listener to avoid double-listening to the same trade
|
|
108
|
+
listening[trade.id] = sub;
|
|
109
|
+
|
|
110
|
+
sub.on('details', ({to}) => emitter.emit('details', {to, id: trade.id}));
|
|
111
|
+
sub.on('failure', err => emitter.emit('failure', {err, id: trade.id}));
|
|
112
|
+
sub.on('trade', ({to}) => emitter.emit('trade', {to, id: trade.id}));
|
|
113
|
+
|
|
114
|
+
sub.once('end', () => sub.removeAllListeners());
|
|
115
|
+
|
|
116
|
+
const invoiceUpdates = subscribeToInvoice({lnd, id: trade.id});
|
|
117
|
+
|
|
118
|
+
invoiceUpdates.on('invoice_updated', updated => {
|
|
119
|
+
if (!updated.is_canceled) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return invoiceUpdates.removeAllListeners();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Listen for any new invoices that represent anchored trades
|
|
130
|
+
const sub = subscribeToInvoices({lnd});
|
|
131
|
+
|
|
132
|
+
// Listen for new trades to appear
|
|
133
|
+
sub.on('invoice_updated', invoice => {
|
|
134
|
+
const {trade} = tradeFromInvoice(invoice);
|
|
135
|
+
|
|
136
|
+
// Exit early when there is no anchored trade found in the description
|
|
137
|
+
if (!trade) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return serviceTrade(trade);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Find open anchored trades
|
|
145
|
+
asyncUntil(
|
|
146
|
+
cbk => cbk(null, paging.token === false),
|
|
147
|
+
cbk => {
|
|
148
|
+
return getInvoices({
|
|
149
|
+
lnd,
|
|
150
|
+
is_unconfirmed: true,
|
|
151
|
+
limit: !paging.token ? defaultInvoicesLimit : undefined,
|
|
152
|
+
token: paging.token,
|
|
153
|
+
},
|
|
154
|
+
(err, res) => {
|
|
155
|
+
if (!!err) {
|
|
156
|
+
emitter.emit(err);
|
|
157
|
+
|
|
158
|
+
// Stop listening to all invoices
|
|
159
|
+
sub.removeAllListeners();
|
|
160
|
+
|
|
161
|
+
// Stop listening to all anchor invoices
|
|
162
|
+
keys(listening).forEach(id => listening[id].removeAllListeners());
|
|
163
|
+
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
paging.token = res.next || false;
|
|
168
|
+
|
|
169
|
+
res.invoices
|
|
170
|
+
.map(tradeFromInvoice)
|
|
171
|
+
.map(n => n.trade)
|
|
172
|
+
.filter(n => !!n)
|
|
173
|
+
.forEach(serviceTrade);
|
|
174
|
+
|
|
175
|
+
return cbk();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
return emitter;
|
|
181
|
+
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {getNodeAlias} = require('ln-sync');
|
|
3
|
+
const {returnResult} = require('asyncjs-util');
|
|
4
|
+
|
|
5
|
+
const encodeOpenTrade = require('./encode_open_trade');
|
|
6
|
+
const serviceTradeRequests = require('./service_trade_requests');
|
|
7
|
+
|
|
8
|
+
const asNumber = n => parseFloat(n, 10);
|
|
9
|
+
const {isArray} = Array;
|
|
10
|
+
const nodeName = (alias, id) => `${alias} ${id}`;
|
|
11
|
+
const slowTradeDate = () => new Date(Date.now() + (86400000*15)).toISOString();
|
|
12
|
+
const uriAsSocket = n => n.substring(67);
|
|
13
|
+
|
|
14
|
+
/** Service an individual trade
|
|
15
|
+
|
|
16
|
+
{
|
|
17
|
+
channels: [{
|
|
18
|
+
id: <Standard Format Channel Id String>
|
|
19
|
+
partner_public_key: <Node Public Key Hex String>
|
|
20
|
+
}]
|
|
21
|
+
description: <Trade Description String>
|
|
22
|
+
expires_at: <Expires At ISO 8601 Date String>
|
|
23
|
+
id: <Trade Anchor Id Hex String>
|
|
24
|
+
lnd: <Authenticated LND API Object>
|
|
25
|
+
logger: <Winston Logger Object>
|
|
26
|
+
network: <Network Name String>
|
|
27
|
+
public_key: <Identity Public Key Hex String>
|
|
28
|
+
secret: <Secret to Sell String>
|
|
29
|
+
tokens: <Trade Price Tokens Number>
|
|
30
|
+
uris: [<Node URI String>]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@returns via cbk or Promise
|
|
34
|
+
*/
|
|
35
|
+
module.exports = (args, cbk) => {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
return asyncAuto({
|
|
38
|
+
// Check arguments
|
|
39
|
+
validate: cbk => {
|
|
40
|
+
if (!isArray(args.channels)) {
|
|
41
|
+
return cbk([400, 'ExpectedArrayOfChannelsToServiceTrade']);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!args.description) {
|
|
45
|
+
return cbk([400, 'ExpectedDescriptionToServiceTrade']);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!args.expires_at) {
|
|
49
|
+
return cbk([400, 'ExpectedExpiresAtDateToServiceTrade']);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!args.id) {
|
|
53
|
+
return cbk([400, 'ExpectedTradeAnchorIdToServiceTrade']);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!args.lnd) {
|
|
57
|
+
return cbk([400, 'ExpectedAuthenticatedLndToServiceTrade']);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!args.logger) {
|
|
61
|
+
return cbk([400, 'ExpectedWinstonLoggerToServiceTrade']);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!args.network) {
|
|
65
|
+
return cbk([400, 'ExpectedNetworkNameToServiceTrade']);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!args.public_key) {
|
|
69
|
+
return cbk([400, 'ExpectedIdentityPublicKeyToServiceTrade']);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!args.secret) {
|
|
73
|
+
return cbk([400, 'ExpectedSecretToSellToServiceTrade']);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!args.tokens) {
|
|
77
|
+
return cbk([400, 'ExpectedTokensPriceToServiceTrade']);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!isArray(args.uris)) {
|
|
81
|
+
return cbk([400, 'ExpectedArrayOfNodeSocketUrisToServiceTrade']);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return cbk();
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
// Serve the trade
|
|
88
|
+
serve: ['validate', ({}, cbk) => {
|
|
89
|
+
const isSlowTrade = args.expires_at > slowTradeDate();
|
|
90
|
+
const settled = [];
|
|
91
|
+
|
|
92
|
+
// Encode the open trade details to give out
|
|
93
|
+
const openTrade = encodeOpenTrade({
|
|
94
|
+
network: args.network,
|
|
95
|
+
nodes: [{
|
|
96
|
+
channels: isSlowTrade ? [] : args.channels,
|
|
97
|
+
id: args.public_key,
|
|
98
|
+
sockets: !args.channels.length ? args.uris.map(uriAsSocket) : [],
|
|
99
|
+
}],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
args.logger.info({trade_expiry: new Date(args.expires_at)});
|
|
103
|
+
|
|
104
|
+
const sub = serviceTradeRequests({
|
|
105
|
+
description: args.description,
|
|
106
|
+
expires_at: args.expires_at,
|
|
107
|
+
id: args.id,
|
|
108
|
+
lnd: args.lnd,
|
|
109
|
+
secret: args.secret,
|
|
110
|
+
tokens: asNumber(args.tokens),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Someone asked for trade details
|
|
114
|
+
sub.on('details', async ({to}) => {
|
|
115
|
+
try {
|
|
116
|
+
const {alias, id} = await getNodeAlias({id: to, lnd: args.lnd});
|
|
117
|
+
|
|
118
|
+
const toName = nodeName(alias, id);
|
|
119
|
+
|
|
120
|
+
return args.logger.info({return_trade_info_to: toName});
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return args.logger.error({err});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
sub.on('failure', failure => args.logger.error({failure}));
|
|
127
|
+
|
|
128
|
+
sub.on('settled', ({to}) => settled.push(to));
|
|
129
|
+
|
|
130
|
+
sub.on('trade', async ({to}) => {
|
|
131
|
+
try {
|
|
132
|
+
const {alias, id} = await getNodeAlias({id: to, lnd: args.lnd});
|
|
133
|
+
|
|
134
|
+
const toName = nodeName(alias, id);
|
|
135
|
+
|
|
136
|
+
return args.logger.info({make_trade_invoice_for: toName});
|
|
137
|
+
} catch (err) {
|
|
138
|
+
return args.logger.error({err});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Show the trade details blob
|
|
143
|
+
return args.logger.info({
|
|
144
|
+
waiting_for_trade_request_to: openTrade.trade,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Wait for the trade to end
|
|
148
|
+
sub.once('end', async () => {
|
|
149
|
+
try {
|
|
150
|
+
const [to] = settled;
|
|
151
|
+
|
|
152
|
+
// Exit early when there was no settlement
|
|
153
|
+
if (!to) {
|
|
154
|
+
args.logger.info({trade_ended: true});
|
|
155
|
+
|
|
156
|
+
return cbk();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const {alias, id} = await getNodeAlias({id: to, lnd: args.lnd});
|
|
160
|
+
|
|
161
|
+
args.logger.info({finished_trade_with: nodeName(alias, id)});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
args.logger.error({err});
|
|
164
|
+
} finally {
|
|
165
|
+
return cbk();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}],
|
|
169
|
+
},
|
|
170
|
+
returnResult({reject, resolve}, cbk));
|
|
171
|
+
});
|
|
172
|
+
};
|