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
|
@@ -0,0 +1,123 @@
|
|
|
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
|
+
expires_at: <Trade Expires At ISO 8601 Date String>
|
|
21
|
+
is_hold: <Create Hold Invoice Bool>
|
|
22
|
+
lnd: <Authenticated LND API Object>
|
|
23
|
+
secret: <Clear Secret String>
|
|
24
|
+
to: <To Public Key Hex Encoded String>
|
|
25
|
+
tokens: <Price Tokens Number>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@returns via cbk or Promise
|
|
29
|
+
{
|
|
30
|
+
id: <Payment Hash Hex String>
|
|
31
|
+
secret: <Hex Encoded Payment Secret String>
|
|
32
|
+
trade: <Hex Encoded Trade String>
|
|
33
|
+
}
|
|
34
|
+
*/
|
|
35
|
+
module.exports = (args, cbk) => {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
return asyncAuto({
|
|
38
|
+
// Check arguments
|
|
39
|
+
validate: cbk => {
|
|
40
|
+
if (!args.description) {
|
|
41
|
+
return cbk([400, 'ExpectedDescriptionToFinalizeTradeSecret']);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!args.expires_at) {
|
|
45
|
+
return cbk([400, 'ExpectedExpiresAtDateToFinalizeTradeSecret']);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!args.lnd) {
|
|
49
|
+
return cbk([400, 'ExpectedAuthenticatedLndToFinalizeTradeSecret']);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!args.secret) {
|
|
53
|
+
return cbk([400, 'ExpectedSecretValueToFinalizeTradeSecret']);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!args.to) {
|
|
57
|
+
return cbk([400, 'ExpectedToPublicKeyToFinalizeTradeSecret']);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!args.tokens) {
|
|
61
|
+
return cbk([400, 'ExpectedTokensPriceToFinalizeTradeSecret']);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return cbk();
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// Encrypt the secret data
|
|
68
|
+
encrypt: ['validate', ({}, cbk) => {
|
|
69
|
+
return encryptTradeSecret({
|
|
70
|
+
lnd: args.lnd,
|
|
71
|
+
secret: utf8AsHex(args.secret),
|
|
72
|
+
to: args.to,
|
|
73
|
+
},
|
|
74
|
+
cbk);
|
|
75
|
+
}],
|
|
76
|
+
|
|
77
|
+
// Create the invoice to purchase the unlocking secret
|
|
78
|
+
createInvoice: ['encrypt', ({encrypt}, cbk) => {
|
|
79
|
+
// Exit early when this is a hold invoice
|
|
80
|
+
if (!!args.is_hold) {
|
|
81
|
+
return createHodlInvoice({
|
|
82
|
+
description: args.description,
|
|
83
|
+
expires_at: args.expires_at,
|
|
84
|
+
id: bufferAsHex(sha256(hexAsBuffer(encrypt.payment_secret))),
|
|
85
|
+
lnd: args.lnd,
|
|
86
|
+
secret: encrypt.payment_secret,
|
|
87
|
+
tokens: args.tokens,
|
|
88
|
+
},
|
|
89
|
+
cbk);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return createInvoice({
|
|
93
|
+
description: args.description,
|
|
94
|
+
expires_at: args.expires_at,
|
|
95
|
+
lnd: args.lnd,
|
|
96
|
+
secret: encrypt.payment_secret,
|
|
97
|
+
tokens: args.tokens,
|
|
98
|
+
},
|
|
99
|
+
cbk);
|
|
100
|
+
}],
|
|
101
|
+
|
|
102
|
+
// Encode all the trade data into wire format
|
|
103
|
+
encode: ['createInvoice', 'encrypt', ({createInvoice, encrypt}, cbk) => {
|
|
104
|
+
try {
|
|
105
|
+
const {trade} = encodeTradeSecret({
|
|
106
|
+
auth: encrypt.trade_auth_tag,
|
|
107
|
+
payload: encrypt.trade_cipher,
|
|
108
|
+
request: createInvoice.request,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return cbk(null, {
|
|
112
|
+
trade,
|
|
113
|
+
id: createInvoice.id,
|
|
114
|
+
secret: encrypt.payment_secret,
|
|
115
|
+
});
|
|
116
|
+
} catch (err) {
|
|
117
|
+
return cbk([500, err.message]);
|
|
118
|
+
}
|
|
119
|
+
}],
|
|
120
|
+
},
|
|
121
|
+
returnResult({reject, resolve, of: 'encode'}, cbk));
|
|
122
|
+
});
|
|
123
|
+
};
|
|
@@ -0,0 +1,339 @@
|
|
|
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 uniq = arr => Array.from(new Set(arr));
|
|
28
|
+
const uniqBy = (a,b) => a.filter((e,i) => a.findIndex(n => n[b] == e[b]) == i);
|
|
29
|
+
const waitForTradesMs = 1000 * 5;
|
|
30
|
+
|
|
31
|
+
/** Find a trade to purchase
|
|
32
|
+
|
|
33
|
+
{
|
|
34
|
+
ask: <Inquirer Ask Function>
|
|
35
|
+
[id]: <Trade Id Encoded Hex String>
|
|
36
|
+
identity: <Own Node Identity Public Key Hex String>
|
|
37
|
+
lnd: <Authenticated LND API Object>
|
|
38
|
+
logger: <Winston Logger Object>
|
|
39
|
+
nodes: [{
|
|
40
|
+
[high_channel]: <High Key Channel Id String>
|
|
41
|
+
[low_channel]: <Low Key Channel Id String>
|
|
42
|
+
[node]: {
|
|
43
|
+
id: <Node Public Key Id Hex String>
|
|
44
|
+
sockets: [<Peer Socket String>]
|
|
45
|
+
}
|
|
46
|
+
}]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@returns via cbk or Promise
|
|
50
|
+
{
|
|
51
|
+
auth: <Encrypted Payload Auth Hex String>
|
|
52
|
+
payload: <Preimage Encrypted Payload Hex String>
|
|
53
|
+
request: <BOLT 11 Payment Request String>
|
|
54
|
+
trade: <Encoded Trade String>
|
|
55
|
+
}
|
|
56
|
+
*/
|
|
57
|
+
module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
return asyncAuto({
|
|
60
|
+
// Check arguments
|
|
61
|
+
validate: cbk => {
|
|
62
|
+
if (!ask) {
|
|
63
|
+
return cbk([400, 'ExpectedAskMethodToFindTrade']);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!identity) {
|
|
67
|
+
return cbk([400, 'ExpectedIdentityToFindTrade']);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!lnd) {
|
|
71
|
+
return cbk([400, 'ExpectedLndApiToFindTrade']);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!logger) {
|
|
75
|
+
return cbk([400, 'ExpectedLoggerObjectToFindTrade']);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!isArray(nodes)) {
|
|
79
|
+
return cbk([400, 'ExpectedNodesArrayToFindTrade']);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return cbk();
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
// Find node connect info to connect to
|
|
86
|
+
getNodes: ['validate', ({}, cbk) => {
|
|
87
|
+
return asyncMap(nodes, (connect, cbk) => {
|
|
88
|
+
// Exit early when the node is self
|
|
89
|
+
if (!!connect.node && connect.node.id === identity) {
|
|
90
|
+
return cbk();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Exit early when the node id and sockets is specified directly
|
|
94
|
+
if (!!connect.node) {
|
|
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
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Exit early when there is no landmark channel
|
|
115
|
+
if (!connect.high_channel && !connect.low_channel) {
|
|
116
|
+
return cbk();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const channelId = connect.high_channel || connect.low_channel;
|
|
120
|
+
|
|
121
|
+
// Get the public keys of the landmark channel
|
|
122
|
+
return getChannel({lnd, id: channelId}, (err, res) => {
|
|
123
|
+
// Exit early when the channel cannot be fetched
|
|
124
|
+
if (!!err) {
|
|
125
|
+
return cbk();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const [low, high] = res.policies.map(n => n.public_key);
|
|
129
|
+
|
|
130
|
+
// The node key is either the lower or the higher public key
|
|
131
|
+
const nodeId = !!connect.low_channel ? low : high;
|
|
132
|
+
|
|
133
|
+
// Exit early when the node is self
|
|
134
|
+
if (nodeId === identity) {
|
|
135
|
+
return cbk();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Find the sockets for the node
|
|
139
|
+
return getNode({
|
|
140
|
+
lnd,
|
|
141
|
+
is_omitting_channels: true,
|
|
142
|
+
public_key: nodeId,
|
|
143
|
+
},
|
|
144
|
+
(err, res) => {
|
|
145
|
+
if (!!err) {
|
|
146
|
+
return cbk();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return cbk(null, {
|
|
150
|
+
id: nodeId,
|
|
151
|
+
sockets: res.sockets.map(n => n.socket),
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
cbk);
|
|
157
|
+
}],
|
|
158
|
+
|
|
159
|
+
// Get the list of connected peers
|
|
160
|
+
getPeers: ['validate', ({}, cbk) => getPeers({lnd}, cbk)],
|
|
161
|
+
|
|
162
|
+
// Try and connect to a node in order to do p2p messaging
|
|
163
|
+
connect: ['getNodes', 'getPeers', ({getNodes, getPeers}, cbk) => {
|
|
164
|
+
const connected = getPeers.peers.map(n => n.public_key);
|
|
165
|
+
|
|
166
|
+
// Look for a node that is already a connected peer
|
|
167
|
+
const [alreadyConnected] = getNodes.filter(node => {
|
|
168
|
+
return !!node && connected.includes(node.id);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Exit early when already connected to a node
|
|
172
|
+
if (!!alreadyConnected) {
|
|
173
|
+
return cbk(null, alreadyConnected);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Try and connect to a referenced node
|
|
177
|
+
return asyncDetect(getNodes.filter(n => !!n), (node, cbk) => {
|
|
178
|
+
logger.info({connecting_to: node.id});
|
|
179
|
+
|
|
180
|
+
// Attempt referenced sockets to establish p2p connection
|
|
181
|
+
return asyncDetectSeries(node.sockets, (socket, cbk) => {
|
|
182
|
+
return addPeer({lnd, socket, public_key: node.id}, err => {
|
|
183
|
+
// Stop trying sockets when there is no error
|
|
184
|
+
return cbk(null, !err);
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
(err, socket) => {
|
|
188
|
+
if (!!err) {
|
|
189
|
+
return cbk(err);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// The node is connected if one of the sockets worked
|
|
193
|
+
return cbk(null, !!socket);
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
cbk);
|
|
197
|
+
}],
|
|
198
|
+
|
|
199
|
+
// Node to request trades from
|
|
200
|
+
to: ['connect', ({connect}, cbk) => {
|
|
201
|
+
if (!connect) {
|
|
202
|
+
return cbk([503, 'FailedToConnectToNode']);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return cbk(null, connect.id);
|
|
206
|
+
}],
|
|
207
|
+
|
|
208
|
+
// Request a specific trade
|
|
209
|
+
requestTrade: ['to', ({to}, cbk) => {
|
|
210
|
+
// Exit early when this is an open ended trade request with no id
|
|
211
|
+
if (!id) {
|
|
212
|
+
return cbk();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return requestTradeById({id, lnd, to}, cbk);
|
|
216
|
+
}],
|
|
217
|
+
|
|
218
|
+
// Request an inventory of trades
|
|
219
|
+
requestTrades: ['to', ({to}, cbk) => {
|
|
220
|
+
// Exit early when this is a trade request for a specific id
|
|
221
|
+
if (!!id) {
|
|
222
|
+
return cbk();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const requestId = makeRequestId();
|
|
226
|
+
const service = servicePeerRequests({lnd});
|
|
227
|
+
const trades = [];
|
|
228
|
+
|
|
229
|
+
// When the requesting period is over, return the received trades
|
|
230
|
+
const finished = err => {
|
|
231
|
+
service.stop({});
|
|
232
|
+
|
|
233
|
+
return !!err ? cbk(err) : cbk(null, {trades});
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Listen for trades
|
|
237
|
+
service.request({type: serviceTypeReceiveTrades}, (req, res) => {
|
|
238
|
+
const requestIdRecord = findIdRecord(req.records);
|
|
239
|
+
|
|
240
|
+
// Exit early when this is a request for something else
|
|
241
|
+
if (!requestIdRecord || requestIdRecord.value !== requestId) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Make sure there is a basic trade record in the records
|
|
246
|
+
if (!findBasicRecord(req.records)) {
|
|
247
|
+
return res.failure([400, 'ExpectedBasicTradeRecord']);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Make sure the basic trade record is valid
|
|
251
|
+
try {
|
|
252
|
+
decodeBasicTrade({records: req.records});
|
|
253
|
+
} catch (err) {
|
|
254
|
+
return res.failure([400, 'ExpectedValidBasicTradeRecord']);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
trades.push(decodeBasicTrade({records: req.records}));
|
|
258
|
+
|
|
259
|
+
return res.success({});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
logger.info({requesting_trade_details: true});
|
|
263
|
+
|
|
264
|
+
// Once connected, ask for the trade details
|
|
265
|
+
return makePeerRequest({
|
|
266
|
+
lnd,
|
|
267
|
+
to,
|
|
268
|
+
records: [{type: tradesRequestIdType, value: requestId}],
|
|
269
|
+
timeout: requestTradesTimeoutMs,
|
|
270
|
+
type: serviceTypeRequestTrades,
|
|
271
|
+
},
|
|
272
|
+
(err, res) => {
|
|
273
|
+
if (!!err) {
|
|
274
|
+
return finished(err);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return setTimeout(finished, waitForTradesMs);
|
|
278
|
+
});
|
|
279
|
+
}],
|
|
280
|
+
|
|
281
|
+
// Select the desired basic trade
|
|
282
|
+
selectTrade: [
|
|
283
|
+
'requestTrade',
|
|
284
|
+
'requestTrades',
|
|
285
|
+
({requestTrade, requestTrades}, cbk) =>
|
|
286
|
+
{
|
|
287
|
+
// Exit early when the trade was already selected
|
|
288
|
+
if (!!requestTrade) {
|
|
289
|
+
return cbk();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (!requestTrades.trades.length) {
|
|
293
|
+
return cbk([404, 'NoTradesFound']);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const uniqueTrades = uniqBy(requestTrades.trades, 'description');
|
|
297
|
+
|
|
298
|
+
const [trade, other] = uniqueTrades;
|
|
299
|
+
|
|
300
|
+
// Exit early when there is only a single trade
|
|
301
|
+
if (!other) {
|
|
302
|
+
return cbk(null, trade);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Present the possible choices
|
|
306
|
+
return ask({
|
|
307
|
+
choices: uniqueTrades.map(trade => ({
|
|
308
|
+
name: trade.description,
|
|
309
|
+
value: trade.id,
|
|
310
|
+
})),
|
|
311
|
+
message: 'What would you like to buy?',
|
|
312
|
+
name: 'id',
|
|
313
|
+
type: 'list',
|
|
314
|
+
},
|
|
315
|
+
cbk);
|
|
316
|
+
}],
|
|
317
|
+
|
|
318
|
+
// Fetch the full trade-secret that was selected
|
|
319
|
+
fetchSelectedTrade: ['selectTrade', 'to', ({selectTrade, to}, cbk) => {
|
|
320
|
+
// Exit early when trade is already known
|
|
321
|
+
if (!selectTrade) {
|
|
322
|
+
return cbk();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return requestTradeById({lnd, to, id: selectTrade.id}, cbk);
|
|
326
|
+
}],
|
|
327
|
+
|
|
328
|
+
// Final trade details
|
|
329
|
+
trade: [
|
|
330
|
+
'fetchSelectedTrade',
|
|
331
|
+
'requestTrade',
|
|
332
|
+
({fetchSelectedTrade, requestTrade}, cbk) =>
|
|
333
|
+
{
|
|
334
|
+
return cbk(null, fetchSelectedTrade || requestTrade);
|
|
335
|
+
}],
|
|
336
|
+
},
|
|
337
|
+
returnResult({reject, resolve, of: 'trade'}, cbk));
|
|
338
|
+
});
|
|
339
|
+
};
|
package/trades/manage_trade.js
CHANGED
|
@@ -11,10 +11,12 @@ const {returnResult} = require('asyncjs-util');
|
|
|
11
11
|
const buyPreimage = require('./buy_preimage');
|
|
12
12
|
const decodeTrade = require('./decode_trade');
|
|
13
13
|
const decryptTradeSecret = require('./decrypt_trade_secret');
|
|
14
|
+
const findTrade = require('./find_trade');
|
|
14
15
|
|
|
15
16
|
const hexAsUtf8 = hex => Buffer.from(hex, 'hex').toString();
|
|
16
17
|
const {isArray} = Array;
|
|
17
18
|
const isHexPreimage = n => !!n && /^[0-9A-F]{64}$/i.test(n);
|
|
19
|
+
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
18
20
|
|
|
19
21
|
/** Manage an individual trade
|
|
20
22
|
|
|
@@ -52,13 +54,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
52
54
|
name: 'trade',
|
|
53
55
|
message: 'Enter encoded trade',
|
|
54
56
|
type: 'input',
|
|
55
|
-
validate: input =>
|
|
56
|
-
if (!input) {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return true;
|
|
61
|
-
},
|
|
57
|
+
validate: input => !!input,
|
|
62
58
|
},
|
|
63
59
|
cbk);
|
|
64
60
|
}],
|
|
@@ -71,22 +67,43 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
71
67
|
try {
|
|
72
68
|
const details = decodeTrade({trade: askForTrade.trade});
|
|
73
69
|
|
|
74
|
-
return cbk(null,
|
|
75
|
-
auth: details.auth,
|
|
76
|
-
payload: details.payload,
|
|
77
|
-
request: details.request,
|
|
78
|
-
});
|
|
70
|
+
return cbk(null, details);
|
|
79
71
|
} catch (err) {
|
|
80
72
|
return cbk([400, err.message]);
|
|
81
73
|
}
|
|
82
74
|
}],
|
|
83
75
|
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
76
|
+
// Get details about an open ended trade
|
|
77
|
+
findTrade: [
|
|
78
|
+
'decodeDetails',
|
|
79
|
+
'getIdentity',
|
|
80
|
+
({decodeDetails, getIdentity}, cbk) =>
|
|
81
|
+
{
|
|
82
|
+
// Exit early when there is a known trade
|
|
83
|
+
if (!!decodeDetails.secret) {
|
|
84
|
+
return cbk(null, {
|
|
85
|
+
auth: decodeDetails.secret.auth,
|
|
86
|
+
payload: decodeDetails.secret.payload,
|
|
87
|
+
request: decodeDetails.secret.request,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
87
90
|
|
|
91
|
+
// Get the trade details interactively
|
|
92
|
+
return findTrade({
|
|
93
|
+
ask,
|
|
94
|
+
lnd,
|
|
95
|
+
logger,
|
|
96
|
+
id: decodeDetails.connect.id,
|
|
97
|
+
identity: getIdentity.public_key,
|
|
98
|
+
nodes: decodeDetails.connect.nodes,
|
|
99
|
+
},
|
|
100
|
+
cbk);
|
|
101
|
+
}],
|
|
102
|
+
|
|
103
|
+
// Parse the trade payment request
|
|
104
|
+
requestDetails: ['findTrade', ({findTrade}, cbk) => {
|
|
88
105
|
try {
|
|
89
|
-
return cbk(null, parsePaymentRequest({request}));
|
|
106
|
+
return cbk(null, parsePaymentRequest({request: findTrade.request}));
|
|
90
107
|
} catch (err) {
|
|
91
108
|
return cbk([400, err.message]);
|
|
92
109
|
}
|
|
@@ -125,7 +142,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
125
142
|
logger.info({
|
|
126
143
|
id: requestDetails.id,
|
|
127
144
|
offering: requestDetails.description,
|
|
128
|
-
price: requestDetails.tokens,
|
|
145
|
+
price: tokensAsBigUnit(requestDetails.tokens),
|
|
129
146
|
is_canceled: invoice.is_canceled || undefined,
|
|
130
147
|
is_purchased: invoice.is_confirmed || undefined,
|
|
131
148
|
});
|
|
@@ -153,7 +170,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
153
170
|
logger.info({
|
|
154
171
|
id: requestDetails.id,
|
|
155
172
|
offering: requestDetails.description,
|
|
156
|
-
price: requestDetails.tokens,
|
|
173
|
+
price: tokensAsBigUnit(requestDetails.tokens),
|
|
157
174
|
});
|
|
158
175
|
|
|
159
176
|
return ask({
|
|
@@ -179,16 +196,24 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
179
196
|
return cbk();
|
|
180
197
|
}
|
|
181
198
|
|
|
182
|
-
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
|
+
});
|
|
183
208
|
}],
|
|
184
209
|
|
|
185
210
|
// Select a purchase option for the trade
|
|
186
211
|
selectPurchaseAction: [
|
|
187
|
-
'
|
|
212
|
+
'findTrade',
|
|
188
213
|
'getPurchase',
|
|
189
214
|
'getRequest',
|
|
190
215
|
'requestDetails',
|
|
191
|
-
({
|
|
216
|
+
({findTrade, getPurchase, getRequest, requestDetails}, cbk) =>
|
|
192
217
|
{
|
|
193
218
|
// Exit early when the trade was already purchased
|
|
194
219
|
if (!!getPurchase.value && !!getPurchase.value.is_confirmed) {
|
|
@@ -200,10 +225,12 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
200
225
|
return cbk();
|
|
201
226
|
}
|
|
202
227
|
|
|
228
|
+
logger.info({trade: findTrade.trade});
|
|
229
|
+
|
|
203
230
|
logger.info({
|
|
204
|
-
request:
|
|
231
|
+
request: findTrade.request,
|
|
205
232
|
purchase: requestDetails.description,
|
|
206
|
-
price: requestDetails.tokens,
|
|
233
|
+
price: tokensAsBigUnit(requestDetails.tokens),
|
|
207
234
|
to: requestDetails.destination,
|
|
208
235
|
});
|
|
209
236
|
|
|
@@ -257,9 +284,9 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
257
284
|
|
|
258
285
|
// Pay and get the preimage through paying for it
|
|
259
286
|
payForPreimage: [
|
|
260
|
-
'
|
|
287
|
+
'findTrade',
|
|
261
288
|
'selectPurchaseAction',
|
|
262
|
-
({
|
|
289
|
+
({findTrade, selectPurchaseAction}, cbk) =>
|
|
263
290
|
{
|
|
264
291
|
// Exit early when this is an offer
|
|
265
292
|
if (!selectPurchaseAction) {
|
|
@@ -271,32 +298,25 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
271
298
|
return cbk();
|
|
272
299
|
}
|
|
273
300
|
|
|
301
|
+
const {request} = findTrade;
|
|
302
|
+
|
|
274
303
|
return asyncRetry({
|
|
275
304
|
errorFilter: err => !!isArray(err) && err.slice().shift() >= 500,
|
|
276
305
|
},
|
|
277
|
-
cbk => {
|
|
278
|
-
return buyPreimage({
|
|
279
|
-
ask,
|
|
280
|
-
lnd,
|
|
281
|
-
logger,
|
|
282
|
-
request: decodeDetails.request,
|
|
283
|
-
},
|
|
284
|
-
cbk);
|
|
285
|
-
},
|
|
286
|
-
cbk);
|
|
306
|
+
cbk => buyPreimage({ask, lnd, logger, request}, cbk), cbk);
|
|
287
307
|
}],
|
|
288
308
|
|
|
289
309
|
// Use the preimage to decrypt the trade secret
|
|
290
310
|
decryptSecret: [
|
|
291
311
|
'askForPreimage',
|
|
292
|
-
'
|
|
312
|
+
'findTrade',
|
|
293
313
|
'getPurchase',
|
|
294
314
|
'payForPreimage',
|
|
295
315
|
'requestDetails',
|
|
296
316
|
'selectPurchaseAction',
|
|
297
317
|
({
|
|
298
318
|
askForPreimage,
|
|
299
|
-
|
|
319
|
+
findTrade,
|
|
300
320
|
getPurchase,
|
|
301
321
|
payForPreimage,
|
|
302
322
|
requestDetails,
|
|
@@ -327,11 +347,13 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
327
347
|
|
|
328
348
|
const paid = payForPreimage || askForPreimage || payment;
|
|
329
349
|
|
|
350
|
+
const {auth, payload} = findTrade;
|
|
351
|
+
|
|
330
352
|
return decryptTradeSecret({
|
|
353
|
+
auth,
|
|
331
354
|
lnd,
|
|
332
|
-
|
|
355
|
+
payload,
|
|
333
356
|
from: requestDetails.destination,
|
|
334
|
-
payload: decodeDetails.payload,
|
|
335
357
|
secret: paid.secret,
|
|
336
358
|
},
|
|
337
359
|
cbk);
|