paid-services 3.5.1 → 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/CHANGELOG.md +8 -0
- package/capacity/get_capacity_replacement.js +0 -1
- package/capacity/initiate_capacity_change.js +0 -1
- package/package.json +7 -7
- package/services/response_for_relay.js +0 -1
- package/test/integration/test_accept_capacity_change.js +0 -1
- package/test/integration/test_change_channel_capacity.js +0 -1
- package/test/integration/test_decrease_channel_capacity.js +0 -1
- package/test/integration/test_increase_channel_capacity.js +0 -1
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## Version 3.6.0
|
|
4
|
+
|
|
5
|
+
- `manageTrades`: Add support for persistent open trades
|
|
6
|
+
- `manageTrades`: Add expiration dates to open trades
|
|
7
|
+
- `manageTrades`: Support longer-lived open trade scenarios
|
|
8
|
+
- `manageTrades`: Show final encoded trade when requesting open trade
|
|
9
|
+
- `manageTrades`: Check for RPC signer support before trading
|
|
10
|
+
|
|
3
11
|
## Version 3.5.1
|
|
4
12
|
|
|
5
13
|
- `changeChannelCapacity`: Allow changing private/public status of channel
|
|
@@ -266,7 +266,6 @@ module.exports = (args, cbk) => {
|
|
|
266
266
|
give_tokens: pendingChannel.remote_balance,
|
|
267
267
|
is_private: args.is_private,
|
|
268
268
|
partner_public_key: pendingChannel.partner_public_key,
|
|
269
|
-
is_private: args.is_private,
|
|
270
269
|
}],
|
|
271
270
|
is_avoiding_broadcast: true,
|
|
272
271
|
lnd: args.lnd,
|
|
@@ -47,7 +47,6 @@ const rebroadcastTimeMs = 1000 * 5;
|
|
|
47
47
|
const recordsAsObject = a => a.reduce((sum, n) => sum[n.type] = n.value, {});
|
|
48
48
|
const requestCapacityChangeIntervalMs = 2000;
|
|
49
49
|
const requestCapacityChangeTimeoutMs = 1000 * 60 * 60 * 6;
|
|
50
|
-
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
|
|
51
50
|
const testMessage = '00';
|
|
52
51
|
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
53
52
|
const uniq = arr => Array.from(new Set(arr));
|
package/package.json
CHANGED
|
@@ -7,20 +7,20 @@
|
|
|
7
7
|
"url": "https://github.com/alexbosworth/paid-services/issues"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"async": "3.2.
|
|
11
|
-
"asyncjs-util": "1.2.
|
|
10
|
+
"async": "3.2.3",
|
|
11
|
+
"asyncjs-util": "1.2.8",
|
|
12
12
|
"bolt01": "1.2.3",
|
|
13
13
|
"bolt07": "1.8.0",
|
|
14
|
-
"invoices": "2.0.
|
|
15
|
-
"ln-service": "53.
|
|
14
|
+
"invoices": "2.0.3",
|
|
15
|
+
"ln-service": "53.4.0",
|
|
16
16
|
"ln-sync": "3.6.1"
|
|
17
17
|
},
|
|
18
18
|
"description": "Lightning Paid Services library",
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@alexbosworth/tap": "15.0.10",
|
|
21
|
-
"ln-docker-daemons": "2.
|
|
21
|
+
"ln-docker-daemons": "2.2.0",
|
|
22
22
|
"mock-lnd": "1.4.1",
|
|
23
|
-
"secp256k1": "4.0.
|
|
23
|
+
"secp256k1": "4.0.3"
|
|
24
24
|
},
|
|
25
25
|
"engines": {
|
|
26
26
|
"node": ">=12.20"
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"integration-tests": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 120 test/integration/*.js",
|
|
41
41
|
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/actions/*.js test/capacity/*.js test/client/*.js test/config/*.js test/p2p/*.js test/records/*.js test/respond/*.js test/server/*.js test/server/*.js test/services/*.js test/trades/*.js"
|
|
42
42
|
},
|
|
43
|
-
"version": "3.
|
|
43
|
+
"version": "3.6.0"
|
|
44
44
|
}
|
|
@@ -18,7 +18,6 @@ const defaultProbeTimeoutMs = 1000 * 60 * 5;
|
|
|
18
18
|
const expiresAt = () => new Date(Date.now() + 1000 * 60 * 30).toISOString();
|
|
19
19
|
const interval = 1000 * 60 * 10;
|
|
20
20
|
const rateDivisor = BigInt(1e6);
|
|
21
|
-
const sumOf = arr => arr.reduce((sum, n) => BigInt(n) + n, BigInt(0));
|
|
22
21
|
const text = 'Relay payment request created';
|
|
23
22
|
const times = 12;
|
|
24
23
|
|
|
@@ -32,7 +32,6 @@ const maturity = 100;
|
|
|
32
32
|
const proposeCapacityChange = propose;
|
|
33
33
|
const signCapacityReplacement = method;
|
|
34
34
|
const size = 2;
|
|
35
|
-
const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
|
|
36
35
|
const times = 2000;
|
|
37
36
|
const {toOutputScript} = address;
|
|
38
37
|
const weightAsVBytes = n => Math.ceil(n / 4);
|
|
@@ -27,7 +27,6 @@ const logger = {error: () => {}, info: () => {}};
|
|
|
27
27
|
const maturity = 100;
|
|
28
28
|
const size = 2;
|
|
29
29
|
const slow = 3000;
|
|
30
|
-
const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
|
|
31
30
|
const times = 2000;
|
|
32
31
|
const {toOutputScript} = address;
|
|
33
32
|
const weightAsVBytes = n => Math.ceil(n / 4);
|
|
@@ -26,7 +26,6 @@ const interval = 10;
|
|
|
26
26
|
const logger = {error: () => {}, info: () => {}};
|
|
27
27
|
const maturity = 100;
|
|
28
28
|
const size = 2;
|
|
29
|
-
const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
|
|
30
29
|
const times = 2000;
|
|
31
30
|
const {toOutputScript} = address;
|
|
32
31
|
const weightAsVBytes = n => Math.ceil(n / 4);
|
package/trades/accept_trade.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
2
|
const asyncEach = require('async/each');
|
|
3
3
|
const {cancelHodlInvoice} = require('ln-service');
|
|
4
|
+
const {getInvoice} = require('ln-service');
|
|
4
5
|
const {returnResult} = require('asyncjs-util');
|
|
5
6
|
const {settleHodlInvoice} = require('ln-service');
|
|
6
7
|
|
|
@@ -10,13 +11,14 @@ const {isArray} = Array;
|
|
|
10
11
|
|
|
11
12
|
{
|
|
12
13
|
cancel: [<Alternative Invoice Id Hex String>]
|
|
14
|
+
id: <Trade Id Hex String>
|
|
13
15
|
lnd: <Authenticated LND API Object>
|
|
14
16
|
secret: <Invoice to Settle Preimage Hex String>
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
@returns via cbk or Promise
|
|
18
20
|
*/
|
|
19
|
-
module.exports = ({cancel, lnd, secret}, cbk) => {
|
|
21
|
+
module.exports = ({cancel, id, lnd, secret}, cbk) => {
|
|
20
22
|
return new Promise((resolve, reject) => {
|
|
21
23
|
return asyncAuto({
|
|
22
24
|
// Check arguments
|
|
@@ -25,6 +27,10 @@ module.exports = ({cancel, lnd, secret}, cbk) => {
|
|
|
25
27
|
return cbk([400, 'ExpectedArrayOfIdsToCancelToAcceptTrade']);
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
if (!id) {
|
|
31
|
+
return cbk([400, 'ExpectedAnchorTradeIdToAcceptTrade']);
|
|
32
|
+
}
|
|
33
|
+
|
|
28
34
|
if (!lnd) {
|
|
29
35
|
return cbk([400, 'ExpectedAuthenticatedLndToAcceptTrade']);
|
|
30
36
|
}
|
|
@@ -36,9 +42,12 @@ module.exports = ({cancel, lnd, secret}, cbk) => {
|
|
|
36
42
|
return cbk();
|
|
37
43
|
},
|
|
38
44
|
|
|
45
|
+
// Fetch the anchor invoice to make sure it's still open
|
|
46
|
+
getAnchor: ['validate', ({}, cbk) => getInvoice({id, lnd}, cbk)],
|
|
47
|
+
|
|
39
48
|
// Cancel alternative invoices so that only one resolves as settled
|
|
40
|
-
cancel: ['
|
|
41
|
-
return asyncEach(cancel, (id, cbk) => {
|
|
49
|
+
cancel: ['getAnchor', ({}, cbk) => {
|
|
50
|
+
return asyncEach([].concat(cancel).concat(id), (id, cbk) => {
|
|
42
51
|
return cancelHodlInvoice({id, lnd}, cbk);
|
|
43
52
|
},
|
|
44
53
|
cbk);
|
package/trades/create_trade.js
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
+
const {createInvoice} = require('ln-service');
|
|
2
3
|
const {getChannels} = require('ln-service');
|
|
3
4
|
const {getNetwork} = require('ln-sync');
|
|
4
|
-
const {getNodeAlias} = require('ln-sync');
|
|
5
5
|
const {getWalletInfo} = require('ln-service');
|
|
6
6
|
const {returnResult} = require('asyncjs-util');
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const encodeAnchoredTrade = require('./encode_anchored_trade');
|
|
9
9
|
const finalizeTradeSecret = require('./finalize_trade_secret');
|
|
10
|
-
const
|
|
10
|
+
const serviceOpenTrade = require('./service_open_trade');
|
|
11
11
|
|
|
12
12
|
const asNumber = n => parseFloat(n, 10);
|
|
13
|
+
const daysAsMs = days => Number(days) * 1000 * 60 * 60 * 24;
|
|
14
|
+
const defaultExpirationDays = 14;
|
|
13
15
|
const {floor} = Math;
|
|
16
|
+
const futureDate = ms => new Date(Date.now() + ms).toISOString();
|
|
14
17
|
const isNumber = n => !isNaN(n);
|
|
15
18
|
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
16
19
|
const maxDescriptionLength = 100;
|
|
17
20
|
const maxSecretLength = 100;
|
|
18
|
-
const nodeName = (alias, id) => `${alias} ${id}`;
|
|
19
21
|
const uriAsSocket = n => n.substring(67);
|
|
20
22
|
const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
|
|
21
23
|
|
|
@@ -164,20 +166,76 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
164
166
|
cbk);
|
|
165
167
|
}],
|
|
166
168
|
|
|
169
|
+
// Ask for the expiration of the trade
|
|
170
|
+
askForExpiration: ['askForPrice', ({}, cbk) => {
|
|
171
|
+
return ask({
|
|
172
|
+
default: defaultExpirationDays,
|
|
173
|
+
name: 'days',
|
|
174
|
+
message: 'Days to offer this for?',
|
|
175
|
+
validate: input => {
|
|
176
|
+
if (!isNumber(input) || !Number(input)) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return true;
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
cbk);
|
|
184
|
+
}],
|
|
185
|
+
|
|
186
|
+
// Create an anchor invoice for the trade
|
|
187
|
+
createAnchor: [
|
|
188
|
+
'askForDescription',
|
|
189
|
+
'askForExpiration',
|
|
190
|
+
'askForNodeId',
|
|
191
|
+
'askForPrice',
|
|
192
|
+
'askForSecret',
|
|
193
|
+
({
|
|
194
|
+
askForDescription,
|
|
195
|
+
askForExpiration,
|
|
196
|
+
askForNodeId,
|
|
197
|
+
askForPrice,
|
|
198
|
+
askForSecret,
|
|
199
|
+
},
|
|
200
|
+
cbk) =>
|
|
201
|
+
{
|
|
202
|
+
// Exit early if this is a closed trade
|
|
203
|
+
if (!!askForNodeId.id) {
|
|
204
|
+
return cbk();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const {encoded} = encodeAnchoredTrade({
|
|
208
|
+
description: askForDescription.description,
|
|
209
|
+
secret: askForSecret.secret,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return createInvoice({
|
|
213
|
+
lnd,
|
|
214
|
+
description: encoded,
|
|
215
|
+
expires_at: futureDate(daysAsMs(askForExpiration.days)),
|
|
216
|
+
tokens: asNumber(askForPrice.tokens),
|
|
217
|
+
},
|
|
218
|
+
cbk);
|
|
219
|
+
}],
|
|
220
|
+
|
|
167
221
|
// Wait for a peer to connect and ask for the trade details
|
|
168
222
|
serviceTradeRequests: [
|
|
169
223
|
'askForDescription',
|
|
224
|
+
'askForExpiration',
|
|
170
225
|
'askForNodeId',
|
|
171
226
|
'askForPrice',
|
|
172
227
|
'askForSecret',
|
|
228
|
+
'createAnchor',
|
|
173
229
|
'getChannels',
|
|
174
230
|
'getIdentity',
|
|
175
231
|
'getNetwork',
|
|
176
232
|
({
|
|
177
233
|
askForDescription,
|
|
234
|
+
askForExpiration,
|
|
178
235
|
askForNodeId,
|
|
179
236
|
askForPrice,
|
|
180
237
|
askForSecret,
|
|
238
|
+
createAnchor,
|
|
181
239
|
getChannels,
|
|
182
240
|
getIdentity,
|
|
183
241
|
getNetwork,
|
|
@@ -189,64 +247,37 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
189
247
|
return cbk();
|
|
190
248
|
}
|
|
191
249
|
|
|
192
|
-
|
|
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({
|
|
250
|
+
return serviceOpenTrade({
|
|
205
251
|
lnd,
|
|
252
|
+
logger,
|
|
253
|
+
channels: getChannels.channels,
|
|
206
254
|
description: askForDescription.description,
|
|
255
|
+
expires_at: futureDate(daysAsMs(askForExpiration.days)),
|
|
256
|
+
id: createAnchor.id,
|
|
257
|
+
network: getNetwork.network,
|
|
258
|
+
public_key: getIdentity.public_key,
|
|
207
259
|
secret: askForSecret.secret,
|
|
208
260
|
tokens: asNumber(askForPrice.tokens),
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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});
|
|
261
|
+
uris: (getIdentity.uris || []),
|
|
262
|
+
},
|
|
263
|
+
cbk);
|
|
241
264
|
}],
|
|
242
265
|
|
|
243
266
|
// Finalize the trade with an encrypted secret and an invoice
|
|
244
267
|
finalize: [
|
|
245
268
|
'askForDescription',
|
|
269
|
+
'askForExpiration',
|
|
246
270
|
'askForNodeId',
|
|
247
271
|
'askForPrice',
|
|
248
272
|
'askForSecret',
|
|
249
|
-
({
|
|
273
|
+
({
|
|
274
|
+
askForDescription,
|
|
275
|
+
askForExpiration,
|
|
276
|
+
askForNodeId,
|
|
277
|
+
askForPrice,
|
|
278
|
+
askForSecret,
|
|
279
|
+
},
|
|
280
|
+
cbk) =>
|
|
250
281
|
{
|
|
251
282
|
// Exit early when there is no node to finalize the trade to
|
|
252
283
|
if (!askForNodeId.id) {
|
|
@@ -256,6 +287,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
256
287
|
return finalizeTradeSecret({
|
|
257
288
|
lnd,
|
|
258
289
|
description: askForDescription.description,
|
|
290
|
+
expires_at: futureDate(daysAsMs(askForExpiration.days)),
|
|
259
291
|
secret: askForSecret.secret,
|
|
260
292
|
to: askForNodeId.id,
|
|
261
293
|
tokens: asNumber(askForPrice.tokens),
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const {decodeTlvStream} = require('bolt01');
|
|
2
|
+
|
|
3
|
+
const anchorPrefix = 'anchor-trade-secret:';
|
|
4
|
+
const base64AsHex = base64 => Buffer.from(base64, 'base64').toString('hex');
|
|
5
|
+
const findDescription = records => records.find(n => n.type === '1');
|
|
6
|
+
const findSecret = records => records.find(n => n.type === '0');
|
|
7
|
+
const hexAsUtf = n => !n ? '' : Buffer.from(n.value, 'hex').toString('utf8');
|
|
8
|
+
|
|
9
|
+
/** Decode an anchored trade data blob
|
|
10
|
+
|
|
11
|
+
Data blobs look like anchored-trade-secret:<base64-encoded-data>
|
|
12
|
+
|
|
13
|
+
Inside the base64 is encoded TLV data:
|
|
14
|
+
0: <Trade Secret>
|
|
15
|
+
[1]: <Trade Description>
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
encoded: <Encoded Trade Data String>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@returns
|
|
22
|
+
{
|
|
23
|
+
[trade]: {
|
|
24
|
+
description: <Description of Trade String>
|
|
25
|
+
secret: <Trade Secret String>
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
*/
|
|
29
|
+
module.exports = ({encoded}) => {
|
|
30
|
+
if (!encoded.startsWith(anchorPrefix)) {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const data = encoded.slice(anchorPrefix.length);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
decodeTlvStream({encoded: base64AsHex(data)});
|
|
38
|
+
} catch (err) {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const {records} = decodeTlvStream({encoded: base64AsHex(data)});
|
|
43
|
+
|
|
44
|
+
const secret = findSecret(records);
|
|
45
|
+
|
|
46
|
+
if (!secret.value) {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
trade: {
|
|
52
|
+
description: hexAsUtf(findDescription(records)),
|
|
53
|
+
secret: hexAsUtf(secret),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const {encodeTlvStream} = require('bolt01');
|
|
2
|
+
|
|
3
|
+
const anchorPrefix = 'anchor-trade-secret:';
|
|
4
|
+
const descriptionRecord = value => !!value ? ({value, type: '1'}) : undefined;
|
|
5
|
+
const secretRecord = value => ({value, type: '0'});
|
|
6
|
+
const utf8AsHex = utf8 => Buffer.from(utf8, 'utf8').toString('hex');
|
|
7
|
+
const withPrefix = (pre, v) => pre + Buffer.from(v, 'hex').toString('base64');
|
|
8
|
+
|
|
9
|
+
/** Encode a trade into an anchor blob
|
|
10
|
+
|
|
11
|
+
This will look like anchored-trade-secret:<base64-encoded-data>
|
|
12
|
+
|
|
13
|
+
The data encodes a TLV:
|
|
14
|
+
0: <Trade Secret>
|
|
15
|
+
[1]: <Trade Description>
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
[description]: <Open Trade Description String>
|
|
19
|
+
secret: <Open Trade Secret String>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@returns
|
|
23
|
+
{
|
|
24
|
+
encoded: <Encoded Anchor Trade Data Blob String>
|
|
25
|
+
}
|
|
26
|
+
*/
|
|
27
|
+
module.exports = ({description, secret}) => {
|
|
28
|
+
const records = [secretRecord(secret), descriptionRecord(description)]
|
|
29
|
+
.filter(n => !!n)
|
|
30
|
+
.map(({type, value}) => ({type, value: utf8AsHex(value)}));
|
|
31
|
+
|
|
32
|
+
const {encoded} = encodeTlvStream({records});
|
|
33
|
+
|
|
34
|
+
return {encoded: withPrefix(anchorPrefix, encoded)};
|
|
35
|
+
};
|
|
@@ -17,6 +17,7 @@ const utf8AsHex = utf8 => Buffer.from(utf8, 'utf8').toString('hex');
|
|
|
17
17
|
|
|
18
18
|
{
|
|
19
19
|
description: <Trade Description String>
|
|
20
|
+
expires_at: <Trade Expires At ISO 8601 Date String>
|
|
20
21
|
is_hold: <Create Hold Invoice Bool>
|
|
21
22
|
lnd: <Authenticated LND API Object>
|
|
22
23
|
secret: <Clear Secret String>
|
|
@@ -40,6 +41,10 @@ module.exports = (args, cbk) => {
|
|
|
40
41
|
return cbk([400, 'ExpectedDescriptionToFinalizeTradeSecret']);
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
if (!args.expires_at) {
|
|
45
|
+
return cbk([400, 'ExpectedExpiresAtDateToFinalizeTradeSecret']);
|
|
46
|
+
}
|
|
47
|
+
|
|
43
48
|
if (!args.lnd) {
|
|
44
49
|
return cbk([400, 'ExpectedAuthenticatedLndToFinalizeTradeSecret']);
|
|
45
50
|
}
|
|
@@ -75,6 +80,7 @@ module.exports = (args, cbk) => {
|
|
|
75
80
|
if (!!args.is_hold) {
|
|
76
81
|
return createHodlInvoice({
|
|
77
82
|
description: args.description,
|
|
83
|
+
expires_at: args.expires_at,
|
|
78
84
|
id: bufferAsHex(sha256(hexAsBuffer(encrypt.payment_secret))),
|
|
79
85
|
lnd: args.lnd,
|
|
80
86
|
secret: encrypt.payment_secret,
|
|
@@ -85,6 +91,7 @@ module.exports = (args, cbk) => {
|
|
|
85
91
|
|
|
86
92
|
return createInvoice({
|
|
87
93
|
description: args.description,
|
|
94
|
+
expires_at: args.expires_at,
|
|
88
95
|
lnd: args.lnd,
|
|
89
96
|
secret: encrypt.payment_secret,
|
|
90
97
|
tokens: args.tokens,
|
package/trades/find_trade.js
CHANGED
|
@@ -24,6 +24,8 @@ const makeRequestId = () => randomBytes(32).toString('hex');
|
|
|
24
24
|
const requestTradeType = '805005';
|
|
25
25
|
const requestTradesTimeoutMs = 1000 * 30;
|
|
26
26
|
const tradesRequestIdType = '1';
|
|
27
|
+
const uniq = arr => Array.from(new Set(arr));
|
|
28
|
+
const uniqBy = (a,b) => a.filter((e,i) => a.findIndex(n => n[b] == e[b]) == i);
|
|
27
29
|
const waitForTradesMs = 1000 * 5;
|
|
28
30
|
|
|
29
31
|
/** Find a trade to purchase
|
|
@@ -49,6 +51,7 @@ const waitForTradesMs = 1000 * 5;
|
|
|
49
51
|
auth: <Encrypted Payload Auth Hex String>
|
|
50
52
|
payload: <Preimage Encrypted Payload Hex String>
|
|
51
53
|
request: <BOLT 11 Payment Request String>
|
|
54
|
+
trade: <Encoded Trade String>
|
|
52
55
|
}
|
|
53
56
|
*/
|
|
54
57
|
module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
@@ -89,7 +92,23 @@ module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
|
89
92
|
|
|
90
93
|
// Exit early when the node id and sockets is specified directly
|
|
91
94
|
if (!!connect.node) {
|
|
92
|
-
return
|
|
95
|
+
return getNode({
|
|
96
|
+
lnd,
|
|
97
|
+
is_omitting_channels: true,
|
|
98
|
+
public_key: connect.node.id,
|
|
99
|
+
},
|
|
100
|
+
(err, res) => {
|
|
101
|
+
if (!!err) {
|
|
102
|
+
return cbk(null, connect.node);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const sockets = connect.node.sockets.map(n => n.socket);
|
|
106
|
+
|
|
107
|
+
return cbk(null, {
|
|
108
|
+
id: connect.node.id,
|
|
109
|
+
sockets: uniq([].concat(sockets).concat(connect.node.sockets)),
|
|
110
|
+
});
|
|
111
|
+
});
|
|
93
112
|
}
|
|
94
113
|
|
|
95
114
|
// Exit early when there is no landmark channel
|
|
@@ -156,6 +175,8 @@ module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
|
156
175
|
|
|
157
176
|
// Try and connect to a referenced node
|
|
158
177
|
return asyncDetect(getNodes.filter(n => !!n), (node, cbk) => {
|
|
178
|
+
logger.info({connecting_to: node.id});
|
|
179
|
+
|
|
159
180
|
// Attempt referenced sockets to establish p2p connection
|
|
160
181
|
return asyncDetectSeries(node.sockets, (socket, cbk) => {
|
|
161
182
|
return addPeer({lnd, socket, public_key: node.id}, err => {
|
|
@@ -238,6 +259,8 @@ module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
|
238
259
|
return res.success({});
|
|
239
260
|
});
|
|
240
261
|
|
|
262
|
+
logger.info({requesting_trade_details: true});
|
|
263
|
+
|
|
241
264
|
// Once connected, ask for the trade details
|
|
242
265
|
return makePeerRequest({
|
|
243
266
|
lnd,
|
|
@@ -270,7 +293,9 @@ module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
|
270
293
|
return cbk([404, 'NoTradesFound']);
|
|
271
294
|
}
|
|
272
295
|
|
|
273
|
-
const
|
|
296
|
+
const uniqueTrades = uniqBy(requestTrades.trades, 'description');
|
|
297
|
+
|
|
298
|
+
const [trade, other] = uniqueTrades;
|
|
274
299
|
|
|
275
300
|
// Exit early when there is only a single trade
|
|
276
301
|
if (!other) {
|
|
@@ -279,7 +304,7 @@ module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
|
279
304
|
|
|
280
305
|
// Present the possible choices
|
|
281
306
|
return ask({
|
|
282
|
-
choices:
|
|
307
|
+
choices: uniqueTrades.map(trade => ({
|
|
283
308
|
name: trade.description,
|
|
284
309
|
value: trade.id,
|
|
285
310
|
})),
|
package/trades/manage_trade.js
CHANGED
|
@@ -16,6 +16,7 @@ const findTrade = require('./find_trade');
|
|
|
16
16
|
const hexAsUtf8 = hex => Buffer.from(hex, 'hex').toString();
|
|
17
17
|
const {isArray} = Array;
|
|
18
18
|
const isHexPreimage = n => !!n && /^[0-9A-F]{64}$/i.test(n);
|
|
19
|
+
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
19
20
|
|
|
20
21
|
/** Manage an individual trade
|
|
21
22
|
|
|
@@ -141,7 +142,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
141
142
|
logger.info({
|
|
142
143
|
id: requestDetails.id,
|
|
143
144
|
offering: requestDetails.description,
|
|
144
|
-
price: requestDetails.tokens,
|
|
145
|
+
price: tokensAsBigUnit(requestDetails.tokens),
|
|
145
146
|
is_canceled: invoice.is_canceled || undefined,
|
|
146
147
|
is_purchased: invoice.is_confirmed || undefined,
|
|
147
148
|
});
|
|
@@ -169,7 +170,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
169
170
|
logger.info({
|
|
170
171
|
id: requestDetails.id,
|
|
171
172
|
offering: requestDetails.description,
|
|
172
|
-
price: requestDetails.tokens,
|
|
173
|
+
price: tokensAsBigUnit(requestDetails.tokens),
|
|
173
174
|
});
|
|
174
175
|
|
|
175
176
|
return ask({
|
|
@@ -195,7 +196,15 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
195
196
|
return cbk();
|
|
196
197
|
}
|
|
197
198
|
|
|
198
|
-
return cancelHodlInvoice({lnd, id: requestDetails.id},
|
|
199
|
+
return cancelHodlInvoice({lnd, id: requestDetails.id}, err => {
|
|
200
|
+
if (!!err) {
|
|
201
|
+
return cbk(err);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
logger.info({trade_canceled: true});
|
|
205
|
+
|
|
206
|
+
return cbk();
|
|
207
|
+
});
|
|
199
208
|
}],
|
|
200
209
|
|
|
201
210
|
// Select a purchase option for the trade
|
|
@@ -216,10 +225,12 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
216
225
|
return cbk();
|
|
217
226
|
}
|
|
218
227
|
|
|
228
|
+
logger.info({trade: findTrade.trade});
|
|
229
|
+
|
|
219
230
|
logger.info({
|
|
220
231
|
request: findTrade.request,
|
|
221
232
|
purchase: requestDetails.description,
|
|
222
|
-
price: requestDetails.tokens,
|
|
233
|
+
price: tokensAsBigUnit(requestDetails.tokens),
|
|
223
234
|
to: requestDetails.destination,
|
|
224
235
|
});
|
|
225
236
|
|