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
|
@@ -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);
|
|
@@ -47,11 +46,13 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
47
46
|
|
|
48
47
|
try {
|
|
49
48
|
// Open up a new channel
|
|
50
|
-
const channelOpen = await
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
const channelOpen = await asyncRetry({interval, times}, async () => {
|
|
50
|
+
return await openChannel({
|
|
51
|
+
lnd,
|
|
52
|
+
local_tokens: capacity,
|
|
53
|
+
partner_public_key: target.id,
|
|
54
|
+
partner_socket: target.socket,
|
|
55
|
+
});
|
|
55
56
|
});
|
|
56
57
|
|
|
57
58
|
// Wait for the channel to be active
|
|
@@ -116,6 +117,10 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
116
117
|
return cbk({query: target.id});
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
if (args.name === 'type') {
|
|
121
|
+
return cbk({type: 'Private'});
|
|
122
|
+
}
|
|
123
|
+
|
|
119
124
|
throw new Error('UnexpectedQueryNameForProposingSide');
|
|
120
125
|
},
|
|
121
126
|
});
|
|
@@ -159,7 +164,9 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
159
164
|
interval: slow,
|
|
160
165
|
},
|
|
161
166
|
async () => {
|
|
162
|
-
const
|
|
167
|
+
const {channels} = await getChannels({lnd, is_active: true});
|
|
168
|
+
|
|
169
|
+
const [channel] = channels.filter(n => n.capacity < capacity);
|
|
163
170
|
|
|
164
171
|
await generate({});
|
|
165
172
|
|
|
@@ -167,6 +174,8 @@ test(`Decrease capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
167
174
|
throw new Error('ExpectedChannelActivation');
|
|
168
175
|
}
|
|
169
176
|
|
|
177
|
+
equal(channel.is_private, true, 'Channel is changed to private');
|
|
178
|
+
|
|
170
179
|
{
|
|
171
180
|
const {policies} = await getChannel({lnd, id: channel.id});
|
|
172
181
|
|
|
@@ -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);
|
|
@@ -111,6 +110,10 @@ test(`Accept capacity replacement`, async ({end, equal, strictSame}) => {
|
|
|
111
110
|
return cbk({query: target.id});
|
|
112
111
|
}
|
|
113
112
|
|
|
113
|
+
if (args.name === 'type') {
|
|
114
|
+
return cbk({type: args.default});
|
|
115
|
+
}
|
|
116
|
+
|
|
114
117
|
throw new Error('UnexpectedQueryNameForProposingSide');
|
|
115
118
|
},
|
|
116
119
|
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const {test} = require('@alexbosworth/tap');
|
|
2
|
+
|
|
3
|
+
const method = require('./../../trades/decode_basic_trade');
|
|
4
|
+
|
|
5
|
+
const tests = [
|
|
6
|
+
{
|
|
7
|
+
args: {},
|
|
8
|
+
description: 'Records are expected',
|
|
9
|
+
error: 'ExpectedArrayOfRecordsToDecodeBasicTrade',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
args: {records: []},
|
|
13
|
+
description: 'A description record is expected',
|
|
14
|
+
error: 'ExpectedDescriptionRecordToDecodeBasicTrade',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
args: {records: [{type: '2', value: '00'}]},
|
|
18
|
+
description: 'An id record is expected',
|
|
19
|
+
error: 'ExpectedIdRecordToDecodeBasicTradeDetails',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
args: {records: [{type: '1', value: '00'}, {type: '2', value: '00'}]},
|
|
23
|
+
description: 'Basic trade is encoded',
|
|
24
|
+
expected: {description: '\u0000', id: '00'},
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
tests.forEach(({args, description, error, expected}) => {
|
|
29
|
+
return test(description, async ({end, strictSame, throws}) => {
|
|
30
|
+
if (!!error) {
|
|
31
|
+
throws(() => method(args), new Error(error), 'Got error');
|
|
32
|
+
} else {
|
|
33
|
+
const res = method(args);
|
|
34
|
+
|
|
35
|
+
strictSame(res, expected, 'Got expected result');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return end();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const {test} = require('@alexbosworth/tap');
|
|
2
|
+
|
|
3
|
+
const method = require('./../../trades/decode_open_trade');
|
|
4
|
+
|
|
5
|
+
const tests = [
|
|
6
|
+
{
|
|
7
|
+
args: {},
|
|
8
|
+
description: 'A network name is expected',
|
|
9
|
+
error: 'ExpectedNetworkNameToDecodeOpenTrade',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
args: {network: 'btc'},
|
|
13
|
+
description: 'Records are expected',
|
|
14
|
+
error: 'ExpectedArrayOfRecordsToDecodeOpenTrade',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
args: {network: 'btc', records: []},
|
|
18
|
+
description: 'Nodes are expected',
|
|
19
|
+
error: 'ExpectedNodesRecordToDecodeOpenTradeDetails',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
args: {
|
|
23
|
+
network: 'btc',
|
|
24
|
+
records: [
|
|
25
|
+
{type: '0', value: '01'},
|
|
26
|
+
{type: '4', value: '000a01080000010000020003'},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
description: 'Open trade is decoded',
|
|
30
|
+
expected: {
|
|
31
|
+
id: undefined,
|
|
32
|
+
network: 'btc',
|
|
33
|
+
nodes: [{high_channel: '1x2x3'}],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
tests.forEach(({args, description, error, expected}) => {
|
|
39
|
+
return test(description, async ({end, strictSame, throws}) => {
|
|
40
|
+
if (!!error) {
|
|
41
|
+
throws(() => method(args), new Error(error), 'Got error');
|
|
42
|
+
} else {
|
|
43
|
+
const res = method(args);
|
|
44
|
+
|
|
45
|
+
strictSame(res, expected, 'Got expected result');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return end();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const {test} = require('@alexbosworth/tap');
|
|
2
|
+
|
|
3
|
+
const method = require('./../../trades/encode_open_trade');
|
|
4
|
+
|
|
5
|
+
const tests = [
|
|
6
|
+
{
|
|
7
|
+
args: {
|
|
8
|
+
network: 'btc',
|
|
9
|
+
nodes: [{
|
|
10
|
+
channels: [{
|
|
11
|
+
id: '1x2x3',
|
|
12
|
+
partner_public_key: Buffer.alloc(33, 2).toString('hex'),
|
|
13
|
+
}],
|
|
14
|
+
id: Buffer.alloc(33, 3).toString('hex'),
|
|
15
|
+
sockets: [],
|
|
16
|
+
}],
|
|
17
|
+
},
|
|
18
|
+
description: 'Open trade is encoded',
|
|
19
|
+
expected: {trade: '626f73ff000101040c000a01080000010000020003'},
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
tests.forEach(({args, description, error, expected}) => {
|
|
24
|
+
return test(description, async ({end, strictSame, throws}) => {
|
|
25
|
+
if (!!error) {
|
|
26
|
+
throws(() => method(args), new Error(error), 'Got error');
|
|
27
|
+
} else {
|
|
28
|
+
const res = method(args);
|
|
29
|
+
|
|
30
|
+
strictSame(res, expected, 'Got expected result');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return end();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const {test} = require('@alexbosworth/tap');
|
|
2
|
+
|
|
3
|
+
const method = require('./../../trades/encode_trade');
|
|
4
|
+
|
|
5
|
+
const tests = [
|
|
6
|
+
{
|
|
7
|
+
args: {
|
|
8
|
+
connect: {
|
|
9
|
+
network: 'btc',
|
|
10
|
+
nodes: [{
|
|
11
|
+
channels: [{
|
|
12
|
+
id: '1x2x3',
|
|
13
|
+
partner_public_key: Buffer.alloc(33, 2).toString('hex'),
|
|
14
|
+
}],
|
|
15
|
+
id: Buffer.alloc(33, 3).toString('hex'),
|
|
16
|
+
sockets: [],
|
|
17
|
+
}],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
description: 'Trade is encoded',
|
|
21
|
+
expected: {trade: '626f73ff000101040c000a01080000010000020003'},
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
tests.forEach(({args, description, error, expected}) => {
|
|
26
|
+
return test(description, async ({end, strictSame, throws}) => {
|
|
27
|
+
if (!!error) {
|
|
28
|
+
throws(() => method(args), new Error(error), 'Got error');
|
|
29
|
+
} else {
|
|
30
|
+
const res = method(args);
|
|
31
|
+
|
|
32
|
+
strictSame(res, expected, 'Got expected result');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return end();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncEach = require('async/each');
|
|
3
|
+
const {cancelHodlInvoice} = require('ln-service');
|
|
4
|
+
const {getInvoice} = require('ln-service');
|
|
5
|
+
const {returnResult} = require('asyncjs-util');
|
|
6
|
+
const {settleHodlInvoice} = require('ln-service');
|
|
7
|
+
|
|
8
|
+
const {isArray} = Array;
|
|
9
|
+
|
|
10
|
+
/** Accept an open ended trade
|
|
11
|
+
|
|
12
|
+
{
|
|
13
|
+
cancel: [<Alternative Invoice Id Hex String>]
|
|
14
|
+
id: <Trade Id Hex String>
|
|
15
|
+
lnd: <Authenticated LND API Object>
|
|
16
|
+
secret: <Invoice to Settle Preimage Hex String>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@returns via cbk or Promise
|
|
20
|
+
*/
|
|
21
|
+
module.exports = ({cancel, id, lnd, secret}, cbk) => {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
return asyncAuto({
|
|
24
|
+
// Check arguments
|
|
25
|
+
validate: cbk => {
|
|
26
|
+
if (!isArray(cancel)) {
|
|
27
|
+
return cbk([400, 'ExpectedArrayOfIdsToCancelToAcceptTrade']);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!id) {
|
|
31
|
+
return cbk([400, 'ExpectedAnchorTradeIdToAcceptTrade']);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!lnd) {
|
|
35
|
+
return cbk([400, 'ExpectedAuthenticatedLndToAcceptTrade']);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!secret) {
|
|
39
|
+
return cbk([400, 'ExpectedSettlementPreimageToAcceptTrade']);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return cbk();
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// Fetch the anchor invoice to make sure it's still open
|
|
46
|
+
getAnchor: ['validate', ({}, cbk) => getInvoice({id, lnd}, cbk)],
|
|
47
|
+
|
|
48
|
+
// Cancel alternative invoices so that only one resolves as settled
|
|
49
|
+
cancel: ['getAnchor', ({}, cbk) => {
|
|
50
|
+
return asyncEach([].concat(cancel).concat(id), (id, cbk) => {
|
|
51
|
+
return cancelHodlInvoice({id, lnd}, cbk);
|
|
52
|
+
},
|
|
53
|
+
cbk);
|
|
54
|
+
}],
|
|
55
|
+
|
|
56
|
+
// Settle the held invoice with the preimage
|
|
57
|
+
settle: ['cancel', ({}, cbk) => settleHodlInvoice({lnd, secret}, cbk)],
|
|
58
|
+
},
|
|
59
|
+
returnResult({reject, resolve}, cbk));
|
|
60
|
+
});
|
|
61
|
+
};
|
package/trades/create_trade.js
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
2
|
const {createInvoice} = require('ln-service');
|
|
3
|
-
const {
|
|
3
|
+
const {getChannels} = require('ln-service');
|
|
4
|
+
const {getNetwork} = require('ln-sync');
|
|
5
|
+
const {getWalletInfo} = require('ln-service');
|
|
4
6
|
const {returnResult} = require('asyncjs-util');
|
|
5
7
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
+
const encodeAnchoredTrade = require('./encode_anchored_trade');
|
|
9
|
+
const finalizeTradeSecret = require('./finalize_trade_secret');
|
|
10
|
+
const serviceOpenTrade = require('./service_open_trade');
|
|
8
11
|
|
|
9
12
|
const asNumber = n => parseFloat(n, 10);
|
|
13
|
+
const daysAsMs = days => Number(days) * 1000 * 60 * 60 * 24;
|
|
14
|
+
const defaultExpirationDays = 14;
|
|
10
15
|
const {floor} = Math;
|
|
16
|
+
const futureDate = ms => new Date(Date.now() + ms).toISOString();
|
|
11
17
|
const isNumber = n => !isNaN(n);
|
|
12
18
|
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
13
19
|
const maxDescriptionLength = 100;
|
|
14
20
|
const maxSecretLength = 100;
|
|
21
|
+
const uriAsSocket = n => n.substring(67);
|
|
15
22
|
const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
|
|
16
23
|
|
|
17
24
|
/** Create a new trade
|
|
@@ -19,6 +26,7 @@ const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
|
|
|
19
26
|
{
|
|
20
27
|
ask: <Ask Function>
|
|
21
28
|
lnd: <Authenticated LND API Object>
|
|
29
|
+
logger: <Winston Logger Object>
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
@returns via cbk or Promise
|
|
@@ -26,7 +34,7 @@ const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
|
|
|
26
34
|
trade: <Hex Encoded Trade String>
|
|
27
35
|
}
|
|
28
36
|
*/
|
|
29
|
-
module.exports = ({ask, lnd}, cbk) => {
|
|
37
|
+
module.exports = ({ask, lnd, logger}, cbk) => {
|
|
30
38
|
return new Promise((resolve, reject) => {
|
|
31
39
|
return asyncAuto({
|
|
32
40
|
// Check arguments
|
|
@@ -39,28 +47,32 @@ module.exports = ({ask, lnd}, cbk) => {
|
|
|
39
47
|
return cbk([400, 'ExpectedAuthenticatedLndToCreateTrade']);
|
|
40
48
|
}
|
|
41
49
|
|
|
50
|
+
if (!logger) {
|
|
51
|
+
return cbk([400, 'ExpectedWinstonLoggerToCreateTrade']);
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
return cbk();
|
|
43
55
|
},
|
|
44
56
|
|
|
45
57
|
// Get the node identity key
|
|
46
|
-
|
|
58
|
+
getIdentity: ['validate', ({}, cbk) => getWalletInfo({lnd}, cbk)],
|
|
47
59
|
|
|
48
60
|
// Ask for the public key of the node to trade with
|
|
49
|
-
askForNodeId: ['
|
|
61
|
+
askForNodeId: ['getIdentity', ({getIdentity}, cbk) => {
|
|
50
62
|
return ask({
|
|
51
63
|
name: 'id',
|
|
52
|
-
message: 'Public key of node you are trading with?',
|
|
64
|
+
message: 'Public key of node you are trading with? (Optional)',
|
|
53
65
|
type: 'input',
|
|
54
66
|
validate: input => {
|
|
55
67
|
if (!input) {
|
|
56
|
-
return
|
|
68
|
+
return true;
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
if (!isPublicKey(input)) {
|
|
60
72
|
return 'Expected public key of node to trade with';
|
|
61
73
|
}
|
|
62
74
|
|
|
63
|
-
if (input ===
|
|
75
|
+
if (input === getIdentity.public_key) {
|
|
64
76
|
return 'Expected public key of other node';
|
|
65
77
|
}
|
|
66
78
|
|
|
@@ -70,6 +82,26 @@ module.exports = ({ask, lnd}, cbk) => {
|
|
|
70
82
|
cbk);
|
|
71
83
|
}],
|
|
72
84
|
|
|
85
|
+
// Get the public channels to use for an open trade
|
|
86
|
+
getChannels: ['askForNodeId', ({askForNodeId}, cbk) => {
|
|
87
|
+
// Exit early when there is a specific node
|
|
88
|
+
if (!!askForNodeId.id) {
|
|
89
|
+
return cbk();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return getChannels({lnd, is_public: true}, cbk);
|
|
93
|
+
}],
|
|
94
|
+
|
|
95
|
+
// Get the network name to use for an open trade
|
|
96
|
+
getNetwork: ['askForNodeId', ({askForNodeId}, cbk) => {
|
|
97
|
+
// Exit early when there is a specific node
|
|
98
|
+
if (!!askForNodeId.id) {
|
|
99
|
+
return cbk();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return getNetwork({lnd}, cbk);
|
|
103
|
+
}],
|
|
104
|
+
|
|
73
105
|
// Ask for how to describe the payload
|
|
74
106
|
askForDescription: ['askForNodeId', ({}, cbk) => {
|
|
75
107
|
return ask({
|
|
@@ -134,55 +166,135 @@ module.exports = ({ask, lnd}, cbk) => {
|
|
|
134
166
|
cbk);
|
|
135
167
|
}],
|
|
136
168
|
|
|
137
|
-
//
|
|
138
|
-
|
|
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',
|
|
139
190
|
'askForNodeId',
|
|
191
|
+
'askForPrice',
|
|
140
192
|
'askForSecret',
|
|
141
|
-
({
|
|
193
|
+
({
|
|
194
|
+
askForDescription,
|
|
195
|
+
askForExpiration,
|
|
196
|
+
askForNodeId,
|
|
197
|
+
askForPrice,
|
|
198
|
+
askForSecret,
|
|
199
|
+
},
|
|
200
|
+
cbk) =>
|
|
142
201
|
{
|
|
143
|
-
|
|
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({
|
|
144
213
|
lnd,
|
|
145
|
-
|
|
146
|
-
|
|
214
|
+
description: encoded,
|
|
215
|
+
expires_at: futureDate(daysAsMs(askForExpiration.days)),
|
|
216
|
+
tokens: asNumber(askForPrice.tokens),
|
|
147
217
|
},
|
|
148
218
|
cbk);
|
|
149
219
|
}],
|
|
150
220
|
|
|
151
|
-
//
|
|
152
|
-
|
|
221
|
+
// Wait for a peer to connect and ask for the trade details
|
|
222
|
+
serviceTradeRequests: [
|
|
153
223
|
'askForDescription',
|
|
224
|
+
'askForExpiration',
|
|
225
|
+
'askForNodeId',
|
|
154
226
|
'askForPrice',
|
|
155
|
-
'
|
|
156
|
-
|
|
227
|
+
'askForSecret',
|
|
228
|
+
'createAnchor',
|
|
229
|
+
'getChannels',
|
|
230
|
+
'getIdentity',
|
|
231
|
+
'getNetwork',
|
|
232
|
+
({
|
|
233
|
+
askForDescription,
|
|
234
|
+
askForExpiration,
|
|
235
|
+
askForNodeId,
|
|
236
|
+
askForPrice,
|
|
237
|
+
askForSecret,
|
|
238
|
+
createAnchor,
|
|
239
|
+
getChannels,
|
|
240
|
+
getIdentity,
|
|
241
|
+
getNetwork,
|
|
242
|
+
},
|
|
243
|
+
cbk) =>
|
|
157
244
|
{
|
|
158
|
-
|
|
245
|
+
// Exit early if this is a closed trade
|
|
246
|
+
if (!!askForNodeId.id) {
|
|
247
|
+
return cbk();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return serviceOpenTrade({
|
|
159
251
|
lnd,
|
|
252
|
+
logger,
|
|
253
|
+
channels: getChannels.channels,
|
|
160
254
|
description: askForDescription.description,
|
|
161
|
-
|
|
255
|
+
expires_at: futureDate(daysAsMs(askForExpiration.days)),
|
|
256
|
+
id: createAnchor.id,
|
|
257
|
+
network: getNetwork.network,
|
|
258
|
+
public_key: getIdentity.public_key,
|
|
259
|
+
secret: askForSecret.secret,
|
|
162
260
|
tokens: asNumber(askForPrice.tokens),
|
|
261
|
+
uris: (getIdentity.uris || []),
|
|
163
262
|
},
|
|
164
263
|
cbk);
|
|
165
264
|
}],
|
|
166
265
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
'
|
|
170
|
-
'
|
|
171
|
-
|
|
266
|
+
// Finalize the trade with an encrypted secret and an invoice
|
|
267
|
+
finalize: [
|
|
268
|
+
'askForDescription',
|
|
269
|
+
'askForExpiration',
|
|
270
|
+
'askForNodeId',
|
|
271
|
+
'askForPrice',
|
|
272
|
+
'askForSecret',
|
|
273
|
+
({
|
|
274
|
+
askForDescription,
|
|
275
|
+
askForExpiration,
|
|
276
|
+
askForNodeId,
|
|
277
|
+
askForPrice,
|
|
278
|
+
askForSecret,
|
|
279
|
+
},
|
|
280
|
+
cbk) =>
|
|
172
281
|
{
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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]);
|
|
282
|
+
// Exit early when there is no node to finalize the trade to
|
|
283
|
+
if (!askForNodeId.id) {
|
|
284
|
+
return cbk();
|
|
183
285
|
}
|
|
286
|
+
|
|
287
|
+
return finalizeTradeSecret({
|
|
288
|
+
lnd,
|
|
289
|
+
description: askForDescription.description,
|
|
290
|
+
expires_at: futureDate(daysAsMs(askForExpiration.days)),
|
|
291
|
+
secret: askForSecret.secret,
|
|
292
|
+
to: askForNodeId.id,
|
|
293
|
+
tokens: asNumber(askForPrice.tokens),
|
|
294
|
+
},
|
|
295
|
+
cbk);
|
|
184
296
|
}],
|
|
185
297
|
},
|
|
186
|
-
returnResult({reject, resolve, of: '
|
|
298
|
+
returnResult({reject, resolve, of: 'finalize'}, cbk));
|
|
187
299
|
});
|
|
188
300
|
};
|
|
@@ -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,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
|
+
};
|