paid-services 3.3.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/capacity/accept_capacity_change.js +3 -3
- package/capacity/get_capacity_change_requests.js +1 -1
- package/capacity/get_capacity_replacement.js +1 -1
- package/capacity/initiate_capacity_change.js +5 -4
- package/capacity/propose_capacity_change.js +3 -1
- package/p2p/service_peer_requests.js +10 -2
- package/package.json +3 -2
- package/service_types.json +16 -0
- package/test/integration/test_accept_capacity_change.js +50 -13
- package/test/integration/test_change_channel_capacity.js +3 -0
- package/test/trades/test_decode_basic_trade.js +40 -0
- package/test/trades/test_decode_open_trade.js +50 -0
- package/test/trades/test_encode_open_trade.js +35 -0
- package/test/trades/test_encode_trade.js +37 -0
- package/trades/accept_trade.js +52 -0
- package/trades/create_trade.js +124 -44
- package/trades/decode_basic_trade.js +45 -0
- package/trades/decode_open_trade.js +69 -0
- package/trades/decode_trade.js +48 -66
- package/trades/decode_trade_secret.js +89 -0
- package/trades/encode_open_trade.js +89 -0
- package/trades/encode_trade.js +48 -33
- package/trades/encode_trade_secret.js +50 -0
- package/trades/finalize_trade_secret.js +116 -0
- package/trades/find_trade.js +314 -0
- package/trades/manage_trade.js +46 -35
- package/trades/network_from_network_record.js +3 -3
- package/trades/network_record_from_network.js +36 -0
- package/trades/network_record_from_request.js +2 -1
- package/trades/node_as_record.js +98 -0
- package/trades/record_as_node.js +113 -0
- package/trades/request_trade_by_id.js +89 -0
- package/trades/service_trade_requests.js +184 -0
- package/capacity/service_types.json +0 -6
package/trades/encode_trade.js
CHANGED
|
@@ -1,50 +1,65 @@
|
|
|
1
|
-
const
|
|
1
|
+
const encodeOpenTrade = require('./encode_open_trade');
|
|
2
|
+
const encodeTradeSecret = require('./encode_trade_secret');
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
const {requestAsRequestRecords} = require('./../records');
|
|
4
|
+
/** Encode v1 trade records
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
A v1 trade is either a qualified trade with a request, or an open ended trade
|
|
7
|
+
with connection details.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
0: version (v1)
|
|
10
|
+
[1]: network
|
|
11
|
+
[2]: payment request
|
|
12
|
+
[3]: trade details
|
|
13
|
+
[4]: nodes records
|
|
14
|
+
[5]: trade identifier
|
|
9
15
|
|
|
10
16
|
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
[connect]: {
|
|
18
|
+
[id]: <Reference Trade Id Hex String>
|
|
19
|
+
network: <Network Name String>
|
|
20
|
+
nodes: [{
|
|
21
|
+
channels: [<Standard Format Channel Id String>]
|
|
22
|
+
id: <Node Public Key Id Hex String>
|
|
23
|
+
[sockets]: [<Peer Socket String>]
|
|
24
|
+
}]
|
|
25
|
+
}
|
|
26
|
+
[secret]: {
|
|
27
|
+
auth: <Encrypted Payload Auth Hex String>
|
|
28
|
+
payload: <Preimage Encrypted Payload Hex String>
|
|
29
|
+
request: <BOLT 11 Payment Request String>
|
|
30
|
+
}
|
|
14
31
|
}
|
|
15
32
|
|
|
33
|
+
@throws
|
|
34
|
+
<Error>
|
|
35
|
+
|
|
16
36
|
@returns
|
|
17
37
|
{
|
|
18
38
|
trade: <Hex Encoded Trade String>
|
|
19
39
|
}
|
|
20
40
|
*/
|
|
21
|
-
module.exports = ({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
type: '2',
|
|
25
|
-
value: requestAsRequestRecords({request}).encoded,
|
|
26
|
-
}];
|
|
27
|
-
|
|
28
|
-
// Add a network name record if required
|
|
29
|
-
if (!!networkRecordFromRequest({request}).value) {
|
|
30
|
-
tradeRecords.push({
|
|
31
|
-
type: '1',
|
|
32
|
-
value: networkRecordFromRequest({request}).value,
|
|
33
|
-
});
|
|
41
|
+
module.exports = ({connect, secret}) => {
|
|
42
|
+
if (!connect && !secret) {
|
|
43
|
+
throw new Error('ExpectedEitherConnectDetailsOrTradeSecret');
|
|
34
44
|
}
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// Encode the details of the trade
|
|
42
|
-
const detailsRecords = encodeTlvStream({
|
|
43
|
-
records: [{type: '0', value: encryptionRecords.encoded}],
|
|
44
|
-
});
|
|
46
|
+
if (!!connect && !!secret) {
|
|
47
|
+
throw new Error('ExpectedEitherConnectDetailsOrTradeSecretNotBoth');
|
|
48
|
+
}
|
|
45
49
|
|
|
46
|
-
//
|
|
47
|
-
|
|
50
|
+
// Exit early when this is an open ended trade
|
|
51
|
+
if (!!connect) {
|
|
52
|
+
return encodeOpenTrade({
|
|
53
|
+
id: connect.id,
|
|
54
|
+
network: connect.network,
|
|
55
|
+
nodes: connect.nodes,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
48
58
|
|
|
49
|
-
|
|
59
|
+
// Encode the trade secret
|
|
60
|
+
return encodeTradeSecret({
|
|
61
|
+
auth: secret.auth,
|
|
62
|
+
payload: secret.payload,
|
|
63
|
+
request: secret.request,
|
|
64
|
+
});
|
|
50
65
|
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const {encodeTlvStream} = require('bolt01');
|
|
2
|
+
|
|
3
|
+
const networkRecordFromRequest = require('./network_record_from_request');
|
|
4
|
+
const {requestAsRequestRecords} = require('./../records');
|
|
5
|
+
|
|
6
|
+
const asTrade = hex => `626f73ff${hex}`;
|
|
7
|
+
|
|
8
|
+
/** Encode a trade
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
auth: <Encrypted Payload Auth Hex String>
|
|
12
|
+
payload: <Preimage Encrypted Payload Hex String>
|
|
13
|
+
request: <BOLT 11 Payment Request String>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@returns
|
|
17
|
+
{
|
|
18
|
+
trade: <Hex Encoded Trade String>
|
|
19
|
+
}
|
|
20
|
+
*/
|
|
21
|
+
module.exports = ({auth, payload, request}, cbk) => {
|
|
22
|
+
// Encode the trade record
|
|
23
|
+
const tradeRecords = [{
|
|
24
|
+
type: '2',
|
|
25
|
+
value: requestAsRequestRecords({request}).encoded,
|
|
26
|
+
}];
|
|
27
|
+
|
|
28
|
+
// Add a network name record if required
|
|
29
|
+
if (!!networkRecordFromRequest({request}).value) {
|
|
30
|
+
tradeRecords.push({
|
|
31
|
+
type: '1',
|
|
32
|
+
value: networkRecordFromRequest({request}).value,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Encode the encrypted data
|
|
37
|
+
const encryptionRecords = encodeTlvStream({
|
|
38
|
+
records: [{type: '0', value: payload}, {type: '1', value: auth}],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Encode the details of the trade
|
|
42
|
+
const detailsRecords = encodeTlvStream({
|
|
43
|
+
records: [{type: '0', value: encryptionRecords.encoded}],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Add the trade details to the trade
|
|
47
|
+
tradeRecords.push({type: '3', value: detailsRecords.encoded});
|
|
48
|
+
|
|
49
|
+
return {trade: asTrade(encodeTlvStream({records: tradeRecords}).encoded)};
|
|
50
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const {createHash} = require('crypto');
|
|
2
|
+
|
|
3
|
+
const asyncAuto = require('async/auto');
|
|
4
|
+
const {createHodlInvoice} = require('ln-service');
|
|
5
|
+
const {createInvoice} = require('ln-service');
|
|
6
|
+
const {returnResult} = require('asyncjs-util');
|
|
7
|
+
|
|
8
|
+
const encodeTradeSecret = require('./encode_trade_secret');
|
|
9
|
+
const encryptTradeSecret = require('./encrypt_trade_secret');
|
|
10
|
+
|
|
11
|
+
const bufferAsHex = buffer => buffer.toString('hex');
|
|
12
|
+
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
13
|
+
const sha256 = preimage => createHash('sha256').update(preimage).digest();
|
|
14
|
+
const utf8AsHex = utf8 => Buffer.from(utf8, 'utf8').toString('hex');
|
|
15
|
+
|
|
16
|
+
/** Create a trade secret for a node
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
description: <Trade Description String>
|
|
20
|
+
is_hold: <Create Hold Invoice Bool>
|
|
21
|
+
lnd: <Authenticated LND API Object>
|
|
22
|
+
secret: <Clear Secret String>
|
|
23
|
+
to: <To Public Key Hex Encoded String>
|
|
24
|
+
tokens: <Price Tokens Number>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@returns via cbk or Promise
|
|
28
|
+
{
|
|
29
|
+
id: <Payment Hash Hex String>
|
|
30
|
+
secret: <Hex Encoded Payment Secret String>
|
|
31
|
+
trade: <Hex Encoded Trade String>
|
|
32
|
+
}
|
|
33
|
+
*/
|
|
34
|
+
module.exports = (args, cbk) => {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
return asyncAuto({
|
|
37
|
+
// Check arguments
|
|
38
|
+
validate: cbk => {
|
|
39
|
+
if (!args.description) {
|
|
40
|
+
return cbk([400, 'ExpectedDescriptionToFinalizeTradeSecret']);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!args.lnd) {
|
|
44
|
+
return cbk([400, 'ExpectedAuthenticatedLndToFinalizeTradeSecret']);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!args.secret) {
|
|
48
|
+
return cbk([400, 'ExpectedSecretValueToFinalizeTradeSecret']);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!args.to) {
|
|
52
|
+
return cbk([400, 'ExpectedToPublicKeyToFinalizeTradeSecret']);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!args.tokens) {
|
|
56
|
+
return cbk([400, 'ExpectedTokensPriceToFinalizeTradeSecret']);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return cbk();
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Encrypt the secret data
|
|
63
|
+
encrypt: ['validate', ({}, cbk) => {
|
|
64
|
+
return encryptTradeSecret({
|
|
65
|
+
lnd: args.lnd,
|
|
66
|
+
secret: utf8AsHex(args.secret),
|
|
67
|
+
to: args.to,
|
|
68
|
+
},
|
|
69
|
+
cbk);
|
|
70
|
+
}],
|
|
71
|
+
|
|
72
|
+
// Create the invoice to purchase the unlocking secret
|
|
73
|
+
createInvoice: ['encrypt', ({encrypt}, cbk) => {
|
|
74
|
+
// Exit early when this is a hold invoice
|
|
75
|
+
if (!!args.is_hold) {
|
|
76
|
+
return createHodlInvoice({
|
|
77
|
+
description: args.description,
|
|
78
|
+
id: bufferAsHex(sha256(hexAsBuffer(encrypt.payment_secret))),
|
|
79
|
+
lnd: args.lnd,
|
|
80
|
+
secret: encrypt.payment_secret,
|
|
81
|
+
tokens: args.tokens,
|
|
82
|
+
},
|
|
83
|
+
cbk);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return createInvoice({
|
|
87
|
+
description: args.description,
|
|
88
|
+
lnd: args.lnd,
|
|
89
|
+
secret: encrypt.payment_secret,
|
|
90
|
+
tokens: args.tokens,
|
|
91
|
+
},
|
|
92
|
+
cbk);
|
|
93
|
+
}],
|
|
94
|
+
|
|
95
|
+
// Encode all the trade data into wire format
|
|
96
|
+
encode: ['createInvoice', 'encrypt', ({createInvoice, encrypt}, cbk) => {
|
|
97
|
+
try {
|
|
98
|
+
const {trade} = encodeTradeSecret({
|
|
99
|
+
auth: encrypt.trade_auth_tag,
|
|
100
|
+
payload: encrypt.trade_cipher,
|
|
101
|
+
request: createInvoice.request,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return cbk(null, {
|
|
105
|
+
trade,
|
|
106
|
+
id: createInvoice.id,
|
|
107
|
+
secret: encrypt.payment_secret,
|
|
108
|
+
});
|
|
109
|
+
} catch (err) {
|
|
110
|
+
return cbk([500, err.message]);
|
|
111
|
+
}
|
|
112
|
+
}],
|
|
113
|
+
},
|
|
114
|
+
returnResult({reject, resolve, of: 'encode'}, cbk));
|
|
115
|
+
});
|
|
116
|
+
};
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
const {randomBytes} = require('crypto');
|
|
2
|
+
|
|
3
|
+
const {addPeer} = require('ln-service');
|
|
4
|
+
const asyncAuto = require('async/auto');
|
|
5
|
+
const asyncDetect = require('async/detect');
|
|
6
|
+
const asyncDetectSeries = require('async/detectSeries');
|
|
7
|
+
const asyncMap = require('async/map');
|
|
8
|
+
const {getChannel} = require('ln-service');
|
|
9
|
+
const {getNode} = require('ln-service');
|
|
10
|
+
const {getPeers} = require('ln-service');
|
|
11
|
+
const {returnResult} = require('asyncjs-util');
|
|
12
|
+
|
|
13
|
+
const decodeBasicTrade = require('./decode_basic_trade');
|
|
14
|
+
const {makePeerRequest} = require('./../p2p');
|
|
15
|
+
const requestTradeById = require('./request_trade_by_id');
|
|
16
|
+
const {servicePeerRequests} = require('./../p2p');
|
|
17
|
+
const {serviceTypeReceiveTrades} = require('./../service_types');
|
|
18
|
+
const {serviceTypeRequestTrades} = require('./../service_types');
|
|
19
|
+
|
|
20
|
+
const findBasicRecord = records => records.find(n => n.type === '1');
|
|
21
|
+
const findIdRecord = records => records.find(n => n.type === '0');
|
|
22
|
+
const {isArray} = Array;
|
|
23
|
+
const makeRequestId = () => randomBytes(32).toString('hex');
|
|
24
|
+
const requestTradeType = '805005';
|
|
25
|
+
const requestTradesTimeoutMs = 1000 * 30;
|
|
26
|
+
const tradesRequestIdType = '1';
|
|
27
|
+
const waitForTradesMs = 1000 * 5;
|
|
28
|
+
|
|
29
|
+
/** Find a trade to purchase
|
|
30
|
+
|
|
31
|
+
{
|
|
32
|
+
ask: <Inquirer Ask Function>
|
|
33
|
+
[id]: <Trade Id Encoded Hex String>
|
|
34
|
+
identity: <Own Node Identity Public Key Hex String>
|
|
35
|
+
lnd: <Authenticated LND API Object>
|
|
36
|
+
logger: <Winston Logger Object>
|
|
37
|
+
nodes: [{
|
|
38
|
+
[high_channel]: <High Key Channel Id String>
|
|
39
|
+
[low_channel]: <Low Key Channel Id String>
|
|
40
|
+
[node]: {
|
|
41
|
+
id: <Node Public Key Id Hex String>
|
|
42
|
+
sockets: [<Peer Socket String>]
|
|
43
|
+
}
|
|
44
|
+
}]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@returns via cbk or Promise
|
|
48
|
+
{
|
|
49
|
+
auth: <Encrypted Payload Auth Hex String>
|
|
50
|
+
payload: <Preimage Encrypted Payload Hex String>
|
|
51
|
+
request: <BOLT 11 Payment Request String>
|
|
52
|
+
}
|
|
53
|
+
*/
|
|
54
|
+
module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
return asyncAuto({
|
|
57
|
+
// Check arguments
|
|
58
|
+
validate: cbk => {
|
|
59
|
+
if (!ask) {
|
|
60
|
+
return cbk([400, 'ExpectedAskMethodToFindTrade']);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!identity) {
|
|
64
|
+
return cbk([400, 'ExpectedIdentityToFindTrade']);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!lnd) {
|
|
68
|
+
return cbk([400, 'ExpectedLndApiToFindTrade']);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!logger) {
|
|
72
|
+
return cbk([400, 'ExpectedLoggerObjectToFindTrade']);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!isArray(nodes)) {
|
|
76
|
+
return cbk([400, 'ExpectedNodesArrayToFindTrade']);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return cbk();
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// Find node connect info to connect to
|
|
83
|
+
getNodes: ['validate', ({}, cbk) => {
|
|
84
|
+
return asyncMap(nodes, (connect, cbk) => {
|
|
85
|
+
// Exit early when the node is self
|
|
86
|
+
if (!!connect.node && connect.node.id === identity) {
|
|
87
|
+
return cbk();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Exit early when the node id and sockets is specified directly
|
|
91
|
+
if (!!connect.node) {
|
|
92
|
+
return cbk(null, connect.node);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Exit early when there is no landmark channel
|
|
96
|
+
if (!connect.high_channel && !connect.low_channel) {
|
|
97
|
+
return cbk();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const channelId = connect.high_channel || connect.low_channel;
|
|
101
|
+
|
|
102
|
+
// Get the public keys of the landmark channel
|
|
103
|
+
return getChannel({lnd, id: channelId}, (err, res) => {
|
|
104
|
+
// Exit early when the channel cannot be fetched
|
|
105
|
+
if (!!err) {
|
|
106
|
+
return cbk();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const [low, high] = res.policies.map(n => n.public_key);
|
|
110
|
+
|
|
111
|
+
// The node key is either the lower or the higher public key
|
|
112
|
+
const nodeId = !!connect.low_channel ? low : high;
|
|
113
|
+
|
|
114
|
+
// Exit early when the node is self
|
|
115
|
+
if (nodeId === identity) {
|
|
116
|
+
return cbk();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Find the sockets for the node
|
|
120
|
+
return getNode({
|
|
121
|
+
lnd,
|
|
122
|
+
is_omitting_channels: true,
|
|
123
|
+
public_key: nodeId,
|
|
124
|
+
},
|
|
125
|
+
(err, res) => {
|
|
126
|
+
if (!!err) {
|
|
127
|
+
return cbk();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return cbk(null, {
|
|
131
|
+
id: nodeId,
|
|
132
|
+
sockets: res.sockets.map(n => n.socket),
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
cbk);
|
|
138
|
+
}],
|
|
139
|
+
|
|
140
|
+
// Get the list of connected peers
|
|
141
|
+
getPeers: ['validate', ({}, cbk) => getPeers({lnd}, cbk)],
|
|
142
|
+
|
|
143
|
+
// Try and connect to a node in order to do p2p messaging
|
|
144
|
+
connect: ['getNodes', 'getPeers', ({getNodes, getPeers}, cbk) => {
|
|
145
|
+
const connected = getPeers.peers.map(n => n.public_key);
|
|
146
|
+
|
|
147
|
+
// Look for a node that is already a connected peer
|
|
148
|
+
const [alreadyConnected] = getNodes.filter(node => {
|
|
149
|
+
return !!node && connected.includes(node.id);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Exit early when already connected to a node
|
|
153
|
+
if (!!alreadyConnected) {
|
|
154
|
+
return cbk(null, alreadyConnected);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Try and connect to a referenced node
|
|
158
|
+
return asyncDetect(getNodes.filter(n => !!n), (node, cbk) => {
|
|
159
|
+
// Attempt referenced sockets to establish p2p connection
|
|
160
|
+
return asyncDetectSeries(node.sockets, (socket, cbk) => {
|
|
161
|
+
return addPeer({lnd, socket, public_key: node.id}, err => {
|
|
162
|
+
// Stop trying sockets when there is no error
|
|
163
|
+
return cbk(null, !err);
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
(err, socket) => {
|
|
167
|
+
if (!!err) {
|
|
168
|
+
return cbk(err);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// The node is connected if one of the sockets worked
|
|
172
|
+
return cbk(null, !!socket);
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
cbk);
|
|
176
|
+
}],
|
|
177
|
+
|
|
178
|
+
// Node to request trades from
|
|
179
|
+
to: ['connect', ({connect}, cbk) => {
|
|
180
|
+
if (!connect) {
|
|
181
|
+
return cbk([503, 'FailedToConnectToNode']);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return cbk(null, connect.id);
|
|
185
|
+
}],
|
|
186
|
+
|
|
187
|
+
// Request a specific trade
|
|
188
|
+
requestTrade: ['to', ({to}, cbk) => {
|
|
189
|
+
// Exit early when this is an open ended trade request with no id
|
|
190
|
+
if (!id) {
|
|
191
|
+
return cbk();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return requestTradeById({id, lnd, to}, cbk);
|
|
195
|
+
}],
|
|
196
|
+
|
|
197
|
+
// Request an inventory of trades
|
|
198
|
+
requestTrades: ['to', ({to}, cbk) => {
|
|
199
|
+
// Exit early when this is a trade request for a specific id
|
|
200
|
+
if (!!id) {
|
|
201
|
+
return cbk();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const requestId = makeRequestId();
|
|
205
|
+
const service = servicePeerRequests({lnd});
|
|
206
|
+
const trades = [];
|
|
207
|
+
|
|
208
|
+
// When the requesting period is over, return the received trades
|
|
209
|
+
const finished = err => {
|
|
210
|
+
service.stop({});
|
|
211
|
+
|
|
212
|
+
return !!err ? cbk(err) : cbk(null, {trades});
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Listen for trades
|
|
216
|
+
service.request({type: serviceTypeReceiveTrades}, (req, res) => {
|
|
217
|
+
const requestIdRecord = findIdRecord(req.records);
|
|
218
|
+
|
|
219
|
+
// Exit early when this is a request for something else
|
|
220
|
+
if (!requestIdRecord || requestIdRecord.value !== requestId) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Make sure there is a basic trade record in the records
|
|
225
|
+
if (!findBasicRecord(req.records)) {
|
|
226
|
+
return res.failure([400, 'ExpectedBasicTradeRecord']);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Make sure the basic trade record is valid
|
|
230
|
+
try {
|
|
231
|
+
decodeBasicTrade({records: req.records});
|
|
232
|
+
} catch (err) {
|
|
233
|
+
return res.failure([400, 'ExpectedValidBasicTradeRecord']);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
trades.push(decodeBasicTrade({records: req.records}));
|
|
237
|
+
|
|
238
|
+
return res.success({});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Once connected, ask for the trade details
|
|
242
|
+
return makePeerRequest({
|
|
243
|
+
lnd,
|
|
244
|
+
to,
|
|
245
|
+
records: [{type: tradesRequestIdType, value: requestId}],
|
|
246
|
+
timeout: requestTradesTimeoutMs,
|
|
247
|
+
type: serviceTypeRequestTrades,
|
|
248
|
+
},
|
|
249
|
+
(err, res) => {
|
|
250
|
+
if (!!err) {
|
|
251
|
+
return finished(err);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return setTimeout(finished, waitForTradesMs);
|
|
255
|
+
});
|
|
256
|
+
}],
|
|
257
|
+
|
|
258
|
+
// Select the desired basic trade
|
|
259
|
+
selectTrade: [
|
|
260
|
+
'requestTrade',
|
|
261
|
+
'requestTrades',
|
|
262
|
+
({requestTrade, requestTrades}, cbk) =>
|
|
263
|
+
{
|
|
264
|
+
// Exit early when the trade was already selected
|
|
265
|
+
if (!!requestTrade) {
|
|
266
|
+
return cbk();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!requestTrades.trades.length) {
|
|
270
|
+
return cbk([404, 'NoTradesFound']);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const [trade, other] = requestTrades.trades;
|
|
274
|
+
|
|
275
|
+
// Exit early when there is only a single trade
|
|
276
|
+
if (!other) {
|
|
277
|
+
return cbk(null, trade);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Present the possible choices
|
|
281
|
+
return ask({
|
|
282
|
+
choices: requestTrades.trades.map(trade => ({
|
|
283
|
+
name: trade.description,
|
|
284
|
+
value: trade.id,
|
|
285
|
+
})),
|
|
286
|
+
message: 'What would you like to buy?',
|
|
287
|
+
name: 'id',
|
|
288
|
+
type: 'list',
|
|
289
|
+
},
|
|
290
|
+
cbk);
|
|
291
|
+
}],
|
|
292
|
+
|
|
293
|
+
// Fetch the full trade-secret that was selected
|
|
294
|
+
fetchSelectedTrade: ['selectTrade', 'to', ({selectTrade, to}, cbk) => {
|
|
295
|
+
// Exit early when trade is already known
|
|
296
|
+
if (!selectTrade) {
|
|
297
|
+
return cbk();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return requestTradeById({lnd, to, id: selectTrade.id}, cbk);
|
|
301
|
+
}],
|
|
302
|
+
|
|
303
|
+
// Final trade details
|
|
304
|
+
trade: [
|
|
305
|
+
'fetchSelectedTrade',
|
|
306
|
+
'requestTrade',
|
|
307
|
+
({fetchSelectedTrade, requestTrade}, cbk) =>
|
|
308
|
+
{
|
|
309
|
+
return cbk(null, fetchSelectedTrade || requestTrade);
|
|
310
|
+
}],
|
|
311
|
+
},
|
|
312
|
+
returnResult({reject, resolve, of: 'trade'}, cbk));
|
|
313
|
+
});
|
|
314
|
+
};
|