paid-services 3.12.2 → 3.13.2
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/package.json +1 -1
- package/service_types.json +5 -1
- package/trades/accept_trade.js +43 -9
- package/trades/create_channel_sale.js +233 -0
- package/trades/finalize_trade_secret.js +37 -2
- package/trades/find_trade.js +40 -8
- package/trades/manage_trade.js +7 -1
- package/trades/manage_trades.js +44 -3
- package/trades/network_record_from_request.js +22 -13
- package/trades/open_channel.js +60 -0
- package/trades/request_trade_by_id.js +19 -3
- package/trades/service_open_trade.js +5 -0
- package/trades/service_trade_requests.js +39 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## Version 3.13.2
|
|
4
|
+
|
|
5
|
+
- `acceptTrade`: Fix `logger` to be documented as an arg and optional
|
|
6
|
+
|
|
7
|
+
## Version 3.13.0
|
|
8
|
+
|
|
9
|
+
- `manageTrades`: Add support for experimental channel trades
|
|
10
|
+
|
|
3
11
|
## Version 3.12.2
|
|
4
12
|
|
|
5
13
|
- `serviceOpenTrade`: Fix regtest network support
|
package/package.json
CHANGED
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"integration-tests": "tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 180 test/integration/*.js",
|
|
43
43
|
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/actions/*.js test/balanced/*.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"
|
|
44
44
|
},
|
|
45
|
-
"version": "3.
|
|
45
|
+
"version": "3.13.2"
|
|
46
46
|
}
|
package/service_types.json
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
"serviceTypeChangeCapacity": "8050001",
|
|
4
4
|
"serviceTypeReceiveTrades": "8050006",
|
|
5
5
|
"serviceTypeRequestTrades": "8050005",
|
|
6
|
+
"serviceTypeReceiveChannelSale": "8050008",
|
|
7
|
+
"serviceTypeRequestChannelSale": "8050007",
|
|
6
8
|
"serviceTypeSignCapacityChange": "8050004",
|
|
7
9
|
"serviceTypeWaitOnConfirmation": "8050003",
|
|
8
10
|
"types": {
|
|
@@ -11,6 +13,8 @@
|
|
|
11
13
|
"8050003": "serviceTypeWaitOnConfirmation",
|
|
12
14
|
"8050004": "serviceTypeSignCapacityChange",
|
|
13
15
|
"8050005": "serviceTypeRequestTrades",
|
|
14
|
-
"8050006": "serviceTypeReceiveTrades"
|
|
16
|
+
"8050006": "serviceTypeReceiveTrades",
|
|
17
|
+
"8050007": "serviceTypeRequestChannelSale",
|
|
18
|
+
"8050008": "serviceTypeReceiveChannelSale"
|
|
15
19
|
}
|
|
16
20
|
}
|
package/trades/accept_trade.js
CHANGED
|
@@ -5,7 +5,10 @@ const {getInvoice} = require('ln-service');
|
|
|
5
5
|
const {returnResult} = require('asyncjs-util');
|
|
6
6
|
const {settleHodlInvoice} = require('ln-service');
|
|
7
7
|
|
|
8
|
+
const openChannel = require('./open_channel');
|
|
9
|
+
|
|
8
10
|
const {isArray} = Array;
|
|
11
|
+
const sellAction = 'sell';
|
|
9
12
|
|
|
10
13
|
/** Accept an open ended trade
|
|
11
14
|
|
|
@@ -13,29 +16,30 @@ const {isArray} = Array;
|
|
|
13
16
|
cancel: [<Alternative Invoice Id Hex String>]
|
|
14
17
|
id: <Trade Id Hex String>
|
|
15
18
|
lnd: <Authenticated LND API Object>
|
|
19
|
+
[logger]: <Winston Logger Object>
|
|
16
20
|
secret: <Invoice to Settle Preimage Hex String>
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
@returns via cbk or Promise
|
|
20
24
|
*/
|
|
21
|
-
module.exports = (
|
|
25
|
+
module.exports = (args, cbk) => {
|
|
22
26
|
return new Promise((resolve, reject) => {
|
|
23
27
|
return asyncAuto({
|
|
24
28
|
// Check arguments
|
|
25
29
|
validate: cbk => {
|
|
26
|
-
if (!isArray(cancel)) {
|
|
30
|
+
if (!isArray(args.cancel)) {
|
|
27
31
|
return cbk([400, 'ExpectedArrayOfIdsToCancelToAcceptTrade']);
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
if (!id) {
|
|
34
|
+
if (!args.id) {
|
|
31
35
|
return cbk([400, 'ExpectedAnchorTradeIdToAcceptTrade']);
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
if (!lnd) {
|
|
38
|
+
if (!args.lnd) {
|
|
35
39
|
return cbk([400, 'ExpectedAuthenticatedLndToAcceptTrade']);
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
if (!secret) {
|
|
42
|
+
if (!args.secret) {
|
|
39
43
|
return cbk([400, 'ExpectedSettlementPreimageToAcceptTrade']);
|
|
40
44
|
}
|
|
41
45
|
|
|
@@ -43,18 +47,48 @@ module.exports = ({cancel, id, lnd, secret}, cbk) => {
|
|
|
43
47
|
},
|
|
44
48
|
|
|
45
49
|
// Fetch the anchor invoice to make sure it's still open
|
|
46
|
-
getAnchor: ['validate', ({}, cbk) =>
|
|
50
|
+
getAnchor: ['validate', ({}, cbk) => {
|
|
51
|
+
return getInvoice({id: args.id, lnd: args.lnd}, cbk);
|
|
52
|
+
}],
|
|
47
53
|
|
|
48
54
|
// Cancel alternative invoices so that only one resolves as settled
|
|
49
55
|
cancel: ['getAnchor', ({}, cbk) => {
|
|
50
|
-
return asyncEach([].concat(cancel).concat(id), (id, cbk) => {
|
|
51
|
-
return cancelHodlInvoice({id, lnd}, cbk);
|
|
56
|
+
return asyncEach([].concat(args.cancel).concat(args.id), (id, cbk) => {
|
|
57
|
+
return cancelHodlInvoice({id, lnd: args.lnd}, cbk);
|
|
52
58
|
},
|
|
53
59
|
cbk);
|
|
54
60
|
}],
|
|
55
61
|
|
|
56
62
|
// Settle the held invoice with the preimage
|
|
57
|
-
settle: ['cancel', ({}, cbk) =>
|
|
63
|
+
settle: ['cancel', ({}, cbk) => {
|
|
64
|
+
return settleHodlInvoice({lnd: args.lnd, secret: args.secret}, cbk);
|
|
65
|
+
}],
|
|
66
|
+
|
|
67
|
+
// Open the channel
|
|
68
|
+
openChannel: ['settle', ({}, cbk) => {
|
|
69
|
+
// Exit early when the channel action is not a sell action
|
|
70
|
+
if (args.action !== sellAction) {
|
|
71
|
+
return cbk();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return openChannel({
|
|
75
|
+
id: args.partner_public_key,
|
|
76
|
+
lnd: args.lnd,
|
|
77
|
+
tokens: args.capacity,
|
|
78
|
+
},
|
|
79
|
+
(err, res) => {
|
|
80
|
+
if (!!err) {
|
|
81
|
+
return cbk(err);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!!args.logger) {
|
|
85
|
+
args.logger.info({channel_opened: res});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return cbk();
|
|
89
|
+
},
|
|
90
|
+
cbk);
|
|
91
|
+
}],
|
|
58
92
|
},
|
|
59
93
|
returnResult({reject, resolve}, cbk));
|
|
60
94
|
});
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
const {randomBytes} = require('crypto');
|
|
2
|
+
|
|
3
|
+
const asyncAuto = require('async/auto');
|
|
4
|
+
const asyncReflect = require('async/reflect');
|
|
5
|
+
const {getChainFeeRate} = require('ln-service');
|
|
6
|
+
const {getChannels} = require('ln-service');
|
|
7
|
+
const {getNetwork} = require('ln-sync');
|
|
8
|
+
const {getWalletInfo} = require('ln-service');
|
|
9
|
+
const {signMessage} = require('ln-service');
|
|
10
|
+
const {returnResult} = require('asyncjs-util');
|
|
11
|
+
|
|
12
|
+
const createAnchoredTrade = require('./create_anchored_trade');
|
|
13
|
+
const serviceOpenTrade = require('./service_open_trade');
|
|
14
|
+
|
|
15
|
+
const asNumber = n => parseFloat(n, 10);
|
|
16
|
+
const daysAsMs = days => Number(days) * 1000 * 60 * 60 * 24;
|
|
17
|
+
const defaultExpirationDays = 1;
|
|
18
|
+
const futureDate = ms => new Date(Date.now() + ms).toISOString();
|
|
19
|
+
const isNumber = n => !isNaN(n);
|
|
20
|
+
const query = 'How much would you like to sell?';
|
|
21
|
+
const saleCost = (amount, rate) => (amount * rate / 1000000).toFixed(0);
|
|
22
|
+
const saleSecret = randomBytes(48).toString('hex');
|
|
23
|
+
const tradeDescription = (alias, tokens) => `channelsale:${alias}-${tokens}`;
|
|
24
|
+
|
|
25
|
+
/** Create a new channel sale
|
|
26
|
+
|
|
27
|
+
{
|
|
28
|
+
action: <Channel Sale Action String>
|
|
29
|
+
ask: <Ask Function>
|
|
30
|
+
balance: <Total Available Chain Confirmed Balance Tokens Number>
|
|
31
|
+
lnd: <Authenticated LND API Object>
|
|
32
|
+
logger: <Winston Logger Object>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@returns via cbk or Promise
|
|
36
|
+
*/
|
|
37
|
+
module.exports = ({action, ask, balance, lnd, logger}, cbk) => {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
return asyncAuto({
|
|
40
|
+
// Check arguments
|
|
41
|
+
validate: cbk => {
|
|
42
|
+
if (!action) {
|
|
43
|
+
return cbk([400, 'ExpectedActionTypeToCreateChannelSale']);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!ask) {
|
|
47
|
+
return cbk([400, 'ExpectedAskFunctionToCreateChannelSale']);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (balance === undefined) {
|
|
51
|
+
return cbk([400, 'ExpectedOnChainBalanceToCreateChannelSale']);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!lnd) {
|
|
55
|
+
return cbk([400, 'ExpectedLndToCreateChannelSale']);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!logger) {
|
|
59
|
+
return cbk([400, 'ExpectedLoggerToCreateChannelSale']);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return cbk();
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Get the public channels to use for an open trade
|
|
66
|
+
getChannels: ['validate', ({}, cbk) => {
|
|
67
|
+
return getChannels({lnd, is_public: true}, cbk);
|
|
68
|
+
}],
|
|
69
|
+
|
|
70
|
+
// Get self identity, including alias
|
|
71
|
+
getIdentity: ['validate', ({}, cbk) => getWalletInfo({lnd}, cbk)],
|
|
72
|
+
|
|
73
|
+
// Get the network name to use for an open trade
|
|
74
|
+
getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
|
|
75
|
+
|
|
76
|
+
// Ask for sale amount
|
|
77
|
+
askForAmount: ['validate', ({}, cbk) => {
|
|
78
|
+
return ask({
|
|
79
|
+
message: `${query} (Available balance: ${balance})`,
|
|
80
|
+
name: 'amount',
|
|
81
|
+
type: 'input',
|
|
82
|
+
validate: input => {
|
|
83
|
+
if (!input) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The token amount should be numeric
|
|
88
|
+
if (!isNumber(input)) {
|
|
89
|
+
return 'Expected numeric amount for sale';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (input > balance) {
|
|
93
|
+
return 'Sale amount cannot be more than available balance';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return true;
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
(err, result) => {
|
|
100
|
+
if (!!err) {
|
|
101
|
+
return cbk([503, 'UnexpectedErrorAskingForSaleAmount', {err}]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return cbk(null, result);
|
|
105
|
+
});
|
|
106
|
+
}],
|
|
107
|
+
|
|
108
|
+
// Ask for the rate
|
|
109
|
+
askForRate: ['askForAmount', ({}, cbk) => {
|
|
110
|
+
return ask({
|
|
111
|
+
default: '1',
|
|
112
|
+
message: 'Price of channel (in ppm)?',
|
|
113
|
+
name: 'rate',
|
|
114
|
+
type: 'input',
|
|
115
|
+
validate: input => {
|
|
116
|
+
if (!input) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Price of sale should be numeric
|
|
121
|
+
if (!isNumber(input)) {
|
|
122
|
+
return 'Expected numeric fee rate for sale';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return true;
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
(err, result) => {
|
|
129
|
+
if (!!err) {
|
|
130
|
+
return cbk([503, 'UnexpectedErrorAskingForRate', err]);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return cbk(null, result);
|
|
134
|
+
});
|
|
135
|
+
}],
|
|
136
|
+
|
|
137
|
+
// Ask for the expiration of the channel sale
|
|
138
|
+
askForExpiration: ['askForRate', ({}, cbk) => {
|
|
139
|
+
return ask({
|
|
140
|
+
default: defaultExpirationDays,
|
|
141
|
+
name: 'days',
|
|
142
|
+
message: 'Days to offer this for?',
|
|
143
|
+
validate: input => {
|
|
144
|
+
if (!isNumber(input) || !Number(input)) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return true;
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
cbk);
|
|
152
|
+
}],
|
|
153
|
+
|
|
154
|
+
// Calculate sale cost
|
|
155
|
+
saleCost: [
|
|
156
|
+
'askForAmount',
|
|
157
|
+
'askForRate',
|
|
158
|
+
({askForRate, askForAmount}, cbk) =>
|
|
159
|
+
{
|
|
160
|
+
return cbk(null, saleCost(askForAmount.amount, askForRate.rate));
|
|
161
|
+
}],
|
|
162
|
+
|
|
163
|
+
// Description of sale
|
|
164
|
+
description: [
|
|
165
|
+
'askForAmount',
|
|
166
|
+
'getIdentity',
|
|
167
|
+
({askForAmount, getIdentity}, cbk) =>
|
|
168
|
+
{
|
|
169
|
+
const alias = getIdentity.alias || getIdentity.public_key;
|
|
170
|
+
|
|
171
|
+
return cbk(null, tradeDescription(alias, askForAmount.amount));
|
|
172
|
+
}],
|
|
173
|
+
|
|
174
|
+
// Create an anchor invoice for the channel sale
|
|
175
|
+
createAnchor: [
|
|
176
|
+
'askForExpiration',
|
|
177
|
+
'askForRate',
|
|
178
|
+
'description',
|
|
179
|
+
'saleCost',
|
|
180
|
+
({askForExpiration, description, saleCost}, cbk) =>
|
|
181
|
+
{
|
|
182
|
+
return createAnchoredTrade({
|
|
183
|
+
description,
|
|
184
|
+
lnd,
|
|
185
|
+
expires_at: futureDate(daysAsMs(askForExpiration.days)),
|
|
186
|
+
secret: saleSecret,
|
|
187
|
+
tokens: asNumber(saleCost),
|
|
188
|
+
},
|
|
189
|
+
cbk);
|
|
190
|
+
}],
|
|
191
|
+
|
|
192
|
+
// Wait for a peer to connect and ask for the channel sale details
|
|
193
|
+
serviceSaleRequests: [
|
|
194
|
+
'askForAmount',
|
|
195
|
+
'askForExpiration',
|
|
196
|
+
'createAnchor',
|
|
197
|
+
'description',
|
|
198
|
+
'getChannels',
|
|
199
|
+
'getNetwork',
|
|
200
|
+
'getIdentity',
|
|
201
|
+
({
|
|
202
|
+
askForAmount,
|
|
203
|
+
askForExpiration,
|
|
204
|
+
createAnchor,
|
|
205
|
+
description,
|
|
206
|
+
getChannels,
|
|
207
|
+
getNetwork,
|
|
208
|
+
getIdentity,
|
|
209
|
+
saleCost,
|
|
210
|
+
},
|
|
211
|
+
cbk) =>
|
|
212
|
+
{
|
|
213
|
+
return serviceOpenTrade({
|
|
214
|
+
action,
|
|
215
|
+
description,
|
|
216
|
+
lnd,
|
|
217
|
+
logger,
|
|
218
|
+
capacity: askForAmount.amount,
|
|
219
|
+
channels: getChannels.channels,
|
|
220
|
+
expires_at: futureDate(daysAsMs(askForExpiration.days)),
|
|
221
|
+
id: createAnchor.id,
|
|
222
|
+
network: getNetwork.network,
|
|
223
|
+
public_key: getIdentity.public_key,
|
|
224
|
+
secret: saleSecret,
|
|
225
|
+
tokens: asNumber(saleCost),
|
|
226
|
+
uris: (getIdentity.uris || []),
|
|
227
|
+
},
|
|
228
|
+
cbk);
|
|
229
|
+
}],
|
|
230
|
+
},
|
|
231
|
+
returnResult({reject, resolve}, cbk));
|
|
232
|
+
});
|
|
233
|
+
};
|
|
@@ -3,23 +3,30 @@ const {createHash} = require('crypto');
|
|
|
3
3
|
const asyncAuto = require('async/auto');
|
|
4
4
|
const {createHodlInvoice} = require('ln-service');
|
|
5
5
|
const {createInvoice} = require('ln-service');
|
|
6
|
+
const {getChainFeeRate} = require('ln-service');
|
|
6
7
|
const {returnResult} = require('asyncjs-util');
|
|
7
8
|
|
|
8
9
|
const encodeTradeSecret = require('./encode_trade_secret');
|
|
9
10
|
const encryptTradeSecret = require('./encrypt_trade_secret');
|
|
10
11
|
|
|
11
12
|
const bufferAsHex = buffer => buffer.toString('hex');
|
|
13
|
+
const {ceil} = Math;
|
|
12
14
|
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
15
|
+
const openSizeVbytes = 250;
|
|
16
|
+
const sellAction = 'sell';
|
|
13
17
|
const sha256 = preimage => createHash('sha256').update(preimage).digest();
|
|
18
|
+
const slowConf = 144;
|
|
14
19
|
const utf8AsHex = utf8 => Buffer.from(utf8, 'utf8').toString('hex');
|
|
15
20
|
|
|
16
21
|
/** Create a trade secret for a node
|
|
17
22
|
|
|
18
23
|
{
|
|
24
|
+
action: <Trade Action String>
|
|
19
25
|
description: <Trade Description String>
|
|
20
26
|
expires_at: <Trade Expires At ISO 8601 Date String>
|
|
21
27
|
is_hold: <Create Hold Invoice Bool>
|
|
22
28
|
lnd: <Authenticated LND API Object>
|
|
29
|
+
logger: <Winston Logger Object>
|
|
23
30
|
secret: <Clear Secret String>
|
|
24
31
|
to: <To Public Key Hex Encoded String>
|
|
25
32
|
tokens: <Price Tokens Number>
|
|
@@ -64,6 +71,34 @@ module.exports = (args, cbk) => {
|
|
|
64
71
|
return cbk();
|
|
65
72
|
},
|
|
66
73
|
|
|
74
|
+
// Calculate dynamic sale price for channel sales
|
|
75
|
+
salePrice: ['validate', ({}, cbk) => {
|
|
76
|
+
// Exit early when this is not a channel sale
|
|
77
|
+
if (args.action !== sellAction) {
|
|
78
|
+
return cbk(null, {});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Get a chain fee rate to dynamically price vs the chain cost
|
|
82
|
+
return getChainFeeRate({
|
|
83
|
+
confirmation_target: slowConf,
|
|
84
|
+
lnd: args.lnd,
|
|
85
|
+
},
|
|
86
|
+
(err, res) => {
|
|
87
|
+
if (!!err) {
|
|
88
|
+
return cbk(err);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const chainSurcharge = ceil(res.tokens_per_vbyte * openSizeVbytes);
|
|
92
|
+
|
|
93
|
+
const price = chainSurcharge + args.tokens;
|
|
94
|
+
|
|
95
|
+
args.logger.info({total_channel_sale_cost: price});
|
|
96
|
+
|
|
97
|
+
return cbk(null, {price});
|
|
98
|
+
},
|
|
99
|
+
cbk);
|
|
100
|
+
}],
|
|
101
|
+
|
|
67
102
|
// Encrypt the secret data
|
|
68
103
|
encrypt: ['validate', ({}, cbk) => {
|
|
69
104
|
return encryptTradeSecret({
|
|
@@ -75,7 +110,7 @@ module.exports = (args, cbk) => {
|
|
|
75
110
|
}],
|
|
76
111
|
|
|
77
112
|
// Create the invoice to purchase the unlocking secret
|
|
78
|
-
createInvoice: ['encrypt', ({encrypt}, cbk) => {
|
|
113
|
+
createInvoice: ['encrypt', 'salePrice', ({encrypt, salePrice}, cbk) => {
|
|
79
114
|
// Exit early when this is a hold invoice
|
|
80
115
|
if (!!args.is_hold) {
|
|
81
116
|
return createHodlInvoice({
|
|
@@ -84,7 +119,7 @@ module.exports = (args, cbk) => {
|
|
|
84
119
|
id: bufferAsHex(sha256(hexAsBuffer(encrypt.payment_secret))),
|
|
85
120
|
lnd: args.lnd,
|
|
86
121
|
secret: encrypt.payment_secret,
|
|
87
|
-
tokens: args.tokens,
|
|
122
|
+
tokens: salePrice.price || args.tokens,
|
|
88
123
|
},
|
|
89
124
|
cbk);
|
|
90
125
|
}
|
package/trades/find_trade.js
CHANGED
|
@@ -15,9 +15,12 @@ const decodeBasicTrade = require('./decode_basic_trade');
|
|
|
15
15
|
const {makePeerRequest} = require('./../p2p');
|
|
16
16
|
const requestTradeById = require('./request_trade_by_id');
|
|
17
17
|
const {servicePeerRequests} = require('./../p2p');
|
|
18
|
+
const {serviceTypeReceiveChannelSale} = require('./../service_types');
|
|
19
|
+
const {serviceTypeRequestChannelSale} = require('./../service_types');
|
|
18
20
|
const {serviceTypeReceiveTrades} = require('./../service_types');
|
|
19
21
|
const {serviceTypeRequestTrades} = require('./../service_types');
|
|
20
22
|
|
|
23
|
+
const buyChannelAction = 'buy';
|
|
21
24
|
const findBasicRecord = records => records.find(n => n.type === '1');
|
|
22
25
|
const findIdRecord = records => records.find(n => n.type === '0');
|
|
23
26
|
const {isArray} = Array;
|
|
@@ -55,11 +58,15 @@ const waitForTradesMs = 1000 * 5;
|
|
|
55
58
|
trade: <Encoded Trade String>
|
|
56
59
|
}
|
|
57
60
|
*/
|
|
58
|
-
module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
61
|
+
module.exports = ({action, ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
59
62
|
return new Promise((resolve, reject) => {
|
|
60
63
|
return asyncAuto({
|
|
61
64
|
// Check arguments
|
|
62
65
|
validate: cbk => {
|
|
66
|
+
if (!action) {
|
|
67
|
+
return cbk([400, 'ExpectedActionTypeToFindTrade']);
|
|
68
|
+
}
|
|
69
|
+
|
|
63
70
|
if (!ask) {
|
|
64
71
|
return cbk([400, 'ExpectedAskMethodToFindTrade']);
|
|
65
72
|
}
|
|
@@ -103,7 +110,7 @@ module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
|
103
110
|
return cbk(null, connect.node);
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
const sockets =
|
|
113
|
+
const sockets = res.sockets.map(n => n.socket);
|
|
107
114
|
|
|
108
115
|
return cbk(null, {
|
|
109
116
|
id: connect.node.id,
|
|
@@ -160,6 +167,26 @@ module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
|
160
167
|
// Get the list of connected peers
|
|
161
168
|
getPeers: ['validate', ({}, cbk) => getPeers({lnd}, cbk)],
|
|
162
169
|
|
|
170
|
+
// Determine the receive type to listen for
|
|
171
|
+
receiveType: ['validate', ({}, cbk) => {
|
|
172
|
+
// Exit early when the action is to buy a channel
|
|
173
|
+
if (action === buyChannelAction) {
|
|
174
|
+
return cbk(null, serviceTypeReceiveChannelSale);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return cbk(null, serviceTypeReceiveTrades);
|
|
178
|
+
}],
|
|
179
|
+
|
|
180
|
+
// Determine the request type to send
|
|
181
|
+
requestType: ['validate', ({}, cbk) => {
|
|
182
|
+
// Exit early when the action is to buy a channel
|
|
183
|
+
if (action === buyChannelAction) {
|
|
184
|
+
return cbk(null, serviceTypeRequestChannelSale);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return cbk(null, serviceTypeRequestTrades);
|
|
188
|
+
}],
|
|
189
|
+
|
|
163
190
|
// Try and connect to a node in order to do p2p messaging
|
|
164
191
|
connect: ['getNodes', 'getPeers', ({getNodes, getPeers}, cbk) => {
|
|
165
192
|
const connected = getPeers.peers.map(n => n.public_key);
|
|
@@ -213,11 +240,17 @@ module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
|
213
240
|
return cbk();
|
|
214
241
|
}
|
|
215
242
|
|
|
216
|
-
return requestTradeById({id, lnd, to}, cbk);
|
|
243
|
+
return requestTradeById({action, id, lnd, to}, cbk);
|
|
217
244
|
})],
|
|
218
245
|
|
|
219
246
|
// Request an inventory of trades
|
|
220
|
-
requestTrades: [
|
|
247
|
+
requestTrades: [
|
|
248
|
+
'receiveType',
|
|
249
|
+
'requestTrade',
|
|
250
|
+
'requestType',
|
|
251
|
+
'to',
|
|
252
|
+
({receiveType, requestTrade, requestType, to}, cbk) =>
|
|
253
|
+
{
|
|
221
254
|
// Exit early when there was a successful request for a specific id
|
|
222
255
|
if (!!requestTrade.value) {
|
|
223
256
|
return cbk();
|
|
@@ -234,8 +267,7 @@ module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
|
234
267
|
return !!err ? cbk(err) : cbk(null, {trades});
|
|
235
268
|
};
|
|
236
269
|
|
|
237
|
-
|
|
238
|
-
service.request({type: serviceTypeReceiveTrades}, (req, res) => {
|
|
270
|
+
service.request({type: receiveType}, (req, res) => {
|
|
239
271
|
const requestIdRecord = findIdRecord(req.records);
|
|
240
272
|
|
|
241
273
|
// Exit early when this is a request for something else
|
|
@@ -268,7 +300,7 @@ module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
|
268
300
|
to,
|
|
269
301
|
records: [{type: tradesRequestIdType, value: requestId}],
|
|
270
302
|
timeout: requestTradesTimeoutMs,
|
|
271
|
-
type:
|
|
303
|
+
type: requestType,
|
|
272
304
|
},
|
|
273
305
|
(err, res) => {
|
|
274
306
|
if (!!err) {
|
|
@@ -323,7 +355,7 @@ module.exports = ({ask, id, identity, lnd, logger, nodes}, cbk) => {
|
|
|
323
355
|
return cbk();
|
|
324
356
|
}
|
|
325
357
|
|
|
326
|
-
return requestTradeById({lnd, to, id: selectTrade.id}, cbk);
|
|
358
|
+
return requestTradeById({action, lnd, to, id: selectTrade.id}, cbk);
|
|
327
359
|
}],
|
|
328
360
|
|
|
329
361
|
// Final trade details
|
package/trades/manage_trade.js
CHANGED
|
@@ -21,6 +21,7 @@ const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
|
21
21
|
/** Manage an individual trade
|
|
22
22
|
|
|
23
23
|
{
|
|
24
|
+
action: <Action Type String>
|
|
24
25
|
ask: <Ask Function>
|
|
25
26
|
lnd: <Authenticated LND API Object>
|
|
26
27
|
logger: <Winston Logger Object>
|
|
@@ -28,11 +29,15 @@ const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
|
28
29
|
|
|
29
30
|
@returns via cbk or Promise
|
|
30
31
|
*/
|
|
31
|
-
module.exports = ({ask, lnd, logger}, cbk) => {
|
|
32
|
+
module.exports = ({action, ask, lnd, logger}, cbk) => {
|
|
32
33
|
return new Promise((resolve, reject) => {
|
|
33
34
|
return asyncAuto({
|
|
34
35
|
// Check arguments
|
|
35
36
|
validate: cbk => {
|
|
37
|
+
if (!action) {
|
|
38
|
+
return cbk([400, 'ExpectedActionTypeToManageTrade']);
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
if (!ask) {
|
|
37
42
|
return cbk([400, 'ExpectedAskFunctionToManageTrade']);
|
|
38
43
|
}
|
|
@@ -90,6 +95,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
90
95
|
|
|
91
96
|
// Get the trade details interactively
|
|
92
97
|
return findTrade({
|
|
98
|
+
action,
|
|
93
99
|
ask,
|
|
94
100
|
lnd,
|
|
95
101
|
logger,
|
package/trades/manage_trades.js
CHANGED
|
@@ -2,12 +2,14 @@ const asyncAuto = require('async/auto');
|
|
|
2
2
|
const asyncUntil = require('async/until');
|
|
3
3
|
const {cancelHodlInvoice} = require('ln-service');
|
|
4
4
|
const {diffieHellmanComputeSecret} = require('ln-service');
|
|
5
|
+
const {getChainBalance} = require('ln-service');
|
|
5
6
|
const {getIdentity} = require('ln-service');
|
|
6
7
|
const {getInvoices} = require('ln-service');
|
|
7
8
|
const {getNetwork} = require('ln-sync');
|
|
8
9
|
const {getNodeAlias} = require('ln-sync');
|
|
9
10
|
const {returnResult} = require('asyncjs-util');
|
|
10
11
|
|
|
12
|
+
const createChannelSale = require('./create_channel_sale');
|
|
11
13
|
const createTrade = require('./create_trade');
|
|
12
14
|
const encodeTrade = require('./encode_trade');
|
|
13
15
|
const manageTrade = require('./manage_trade');
|
|
@@ -15,11 +17,13 @@ const serviceAnchoredTrades = require('./service_anchored_trades');
|
|
|
15
17
|
const serviceOpenTrade = require('./service_open_trade');
|
|
16
18
|
const tradeFromInvoice = require('./trade_from_invoice');
|
|
17
19
|
|
|
20
|
+
const buyAction = 'buy';
|
|
18
21
|
const cancelAction = 'cancel-';
|
|
19
22
|
const createAction = 'create';
|
|
20
23
|
const decodeAction = 'decode';
|
|
21
24
|
const defaultInvoicesLimit = 100;
|
|
22
25
|
const listAction = 'list';
|
|
26
|
+
const sellAction = 'sell';
|
|
23
27
|
const serveAction = 'serve';
|
|
24
28
|
const serveTradeAction = 'serve-trade-';
|
|
25
29
|
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
@@ -29,6 +33,7 @@ const viewAction = 'view';
|
|
|
29
33
|
|
|
30
34
|
{
|
|
31
35
|
ask: <Ask Function>
|
|
36
|
+
experimental: <Experiment Mode Bool>
|
|
32
37
|
lnd: <Authenticated LND API Object>
|
|
33
38
|
logger: <Winston Logger Object>
|
|
34
39
|
separator: <Create Separator Function>
|
|
@@ -36,7 +41,7 @@ const viewAction = 'view';
|
|
|
36
41
|
|
|
37
42
|
@returns via cbk or Promise
|
|
38
43
|
*/
|
|
39
|
-
module.exports = ({ask, lnd, logger, separator}, cbk) => {
|
|
44
|
+
module.exports = ({ask, experimental, lnd, logger, separator}, cbk) => {
|
|
40
45
|
return new Promise((resolve, reject) => {
|
|
41
46
|
return asyncAuto({
|
|
42
47
|
// Check arguments
|
|
@@ -66,12 +71,25 @@ module.exports = ({ask, lnd, logger, separator}, cbk) => {
|
|
|
66
71
|
select: ['validate', ({}, cbk) => {
|
|
67
72
|
return ask({
|
|
68
73
|
choices: [
|
|
74
|
+
separator(),
|
|
69
75
|
{name: 'Create Trade', value: createAction},
|
|
70
76
|
{name: 'Decode Trade', value: decodeAction},
|
|
71
77
|
separator(),
|
|
78
|
+
{
|
|
79
|
+
name: 'Buy Channel (experimental)',
|
|
80
|
+
value: buyAction,
|
|
81
|
+
disabled: !experimental,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'Sell Channel (experimental)',
|
|
85
|
+
value: sellAction,
|
|
86
|
+
disabled: !experimental,
|
|
87
|
+
},
|
|
88
|
+
separator(),
|
|
72
89
|
{name: 'Open Trades', value: listAction},
|
|
73
90
|
{name: 'Serve Trades', value: serveAction},
|
|
74
91
|
],
|
|
92
|
+
loop: false,
|
|
75
93
|
message: 'Trade?',
|
|
76
94
|
name: 'action',
|
|
77
95
|
type: 'list',
|
|
@@ -113,11 +131,11 @@ module.exports = ({ask, lnd, logger, separator}, cbk) => {
|
|
|
113
131
|
// View an existing trade
|
|
114
132
|
view: ['checkSigner', 'select', ({checkSigner, select}, cbk) => {
|
|
115
133
|
// Exit early when not decoding a trade
|
|
116
|
-
if (select.action !== decodeAction) {
|
|
134
|
+
if (select.action !== decodeAction && select.action !== buyAction) {
|
|
117
135
|
return cbk();
|
|
118
136
|
}
|
|
119
137
|
|
|
120
|
-
return manageTrade({ask, lnd, logger}, cbk)
|
|
138
|
+
return manageTrade({ask, lnd, logger, action: select.action}, cbk)
|
|
121
139
|
}],
|
|
122
140
|
|
|
123
141
|
// Get open trades
|
|
@@ -289,6 +307,29 @@ module.exports = ({ask, lnd, logger, separator}, cbk) => {
|
|
|
289
307
|
return cbk(err);
|
|
290
308
|
});
|
|
291
309
|
}],
|
|
310
|
+
|
|
311
|
+
// Sell a channel
|
|
312
|
+
sellChannel: ['select', ({select}, cbk) => {
|
|
313
|
+
// Exit early when not selling a channel
|
|
314
|
+
if (select.action !== sellAction) {
|
|
315
|
+
return cbk();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return getChainBalance({lnd}, (err, res) => {
|
|
319
|
+
if (!!err) {
|
|
320
|
+
return cbk(err);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return createChannelSale({
|
|
324
|
+
ask,
|
|
325
|
+
lnd,
|
|
326
|
+
logger,
|
|
327
|
+
action: select.action,
|
|
328
|
+
balance: res.chain_balance,
|
|
329
|
+
},
|
|
330
|
+
cbk);
|
|
331
|
+
});
|
|
332
|
+
}],
|
|
292
333
|
},
|
|
293
334
|
returnResult({reject, resolve}, cbk));
|
|
294
335
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
1
|
+
const {parsePaymentRequest} = require('ln-service');
|
|
2
|
+
|
|
3
|
+
const defaultNetwork = 'bitcoin';
|
|
4
|
+
const regtestNetwork = 'regtest';
|
|
3
5
|
const regtestNetworkType = '02';
|
|
4
|
-
const testnetNetwork = '
|
|
6
|
+
const testnetNetwork = 'testnet';
|
|
5
7
|
const testnetNetworkType = '01';
|
|
6
8
|
|
|
7
9
|
/** Derive a network record name from a payment request
|
|
@@ -20,17 +22,24 @@ module.exports = ({request}) => {
|
|
|
20
22
|
throw new Error('ExpectedPaymentRequestToDeriveNetworkRecord');
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
25
|
+
try {
|
|
26
|
+
const {network} = parsePaymentRequest({request});
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
if (network === defaultNetwork) {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
if (network === regtestNetwork) {
|
|
33
|
+
return {value: regtestNetworkType};
|
|
34
|
+
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
if (network === testnetNetwork) {
|
|
37
|
+
return {value: testnetNetworkType};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
throw new Error('UnknownNetworkToDeriveNetworkRecordFor');
|
|
41
|
+
|
|
42
|
+
} catch (err) {
|
|
43
|
+
throw new Error('FailedToParsePaymentRequestToDeriveNetworkRecord');
|
|
44
|
+
}
|
|
36
45
|
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {getPeers} = require('ln-service');
|
|
3
|
+
const {getChainFeeRate} = require('ln-service');
|
|
4
|
+
const {openChannel} = require('ln-service');
|
|
5
|
+
const {returnResult} = require('asyncjs-util');
|
|
6
|
+
|
|
7
|
+
const slowConf = 144;
|
|
8
|
+
|
|
9
|
+
/** Opens channel on invoice payment
|
|
10
|
+
{
|
|
11
|
+
id: <Partner Public Key Hex String>
|
|
12
|
+
lnd: <Authenticated LND API Object>
|
|
13
|
+
tokens: <Capacity of Channel To Open Tokens Number>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@returns via cbk or Promise
|
|
17
|
+
{
|
|
18
|
+
transaction_id: <Funding Transaction Id String>
|
|
19
|
+
transaction_vout: <Funding Transaction Output Index Number>
|
|
20
|
+
}
|
|
21
|
+
*/
|
|
22
|
+
module.exports = ({id, lnd, tokens}, cbk) => {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
return asyncAuto({
|
|
25
|
+
// Check arguments
|
|
26
|
+
validate: cbk => {
|
|
27
|
+
if (!id) {
|
|
28
|
+
return cbk([400, 'ExpectedPartnerPublicKeyToOpenNewChannel']);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!lnd) {
|
|
32
|
+
return cbk([400, 'ExpectedLndObjectToOpenNewChannel']);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!tokens) {
|
|
36
|
+
return cbk([400, 'ExpectedCapacityToOpenNewChannel']);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return cbk();
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
// Get low fee rate
|
|
43
|
+
getSlowFee: ['validate', ({}, cbk) => {
|
|
44
|
+
return getChainFeeRate({lnd, confirmation_target: slowConf}, cbk);
|
|
45
|
+
}],
|
|
46
|
+
|
|
47
|
+
// Select a peer and open a channel
|
|
48
|
+
openChannel: ['getSlowFee', ({getSlowFee}, cbk) => {
|
|
49
|
+
return openChannel({
|
|
50
|
+
lnd,
|
|
51
|
+
chain_fee_rate: getSlowFee.tokens_per_vbyte,
|
|
52
|
+
local_tokens: tokens,
|
|
53
|
+
partner_public_key: id,
|
|
54
|
+
},
|
|
55
|
+
cbk);
|
|
56
|
+
}],
|
|
57
|
+
},
|
|
58
|
+
returnResult({reject, resolve, of: 'openChannel'}, cbk));
|
|
59
|
+
});
|
|
60
|
+
};
|
|
@@ -4,14 +4,17 @@ const {returnResult} = require('asyncjs-util');
|
|
|
4
4
|
const decodeTrade = require('./decode_trade');
|
|
5
5
|
const {makePeerRequest} = require('./../p2p');
|
|
6
6
|
|
|
7
|
+
const buyChannelAction = 'buy';
|
|
7
8
|
const findTradeRecord = records => records.find(n => n.type === '1');
|
|
8
9
|
const requestTradeTimeoutMs = 1000 * 30;
|
|
10
|
+
const {serviceTypeRequestChannelSale} = require('./../service_types');
|
|
9
11
|
const {serviceTypeRequestTrades} = require('./../service_types');
|
|
10
12
|
const tradeIdRecordType = '0';
|
|
11
13
|
|
|
12
14
|
/** Request a specific trade by its id
|
|
13
15
|
|
|
14
16
|
{
|
|
17
|
+
action: <Purchase Action String>
|
|
15
18
|
id: <Trade Identifier Hex String>
|
|
16
19
|
lnd: <Authenticated LND API Object>
|
|
17
20
|
to: <Make Request To Public Key Hex Encoded String>
|
|
@@ -25,11 +28,15 @@ const tradeIdRecordType = '0';
|
|
|
25
28
|
trade: <Encoded Trade Record String>
|
|
26
29
|
}
|
|
27
30
|
*/
|
|
28
|
-
module.exports = ({id, lnd, to}, cbk) => {
|
|
31
|
+
module.exports = ({action, id, lnd, to}, cbk) => {
|
|
29
32
|
return new Promise((resolve, reject) => {
|
|
30
33
|
return asyncAuto({
|
|
31
34
|
// Check arguments
|
|
32
35
|
validate: cbk => {
|
|
36
|
+
if (!action) {
|
|
37
|
+
return cbk([400, 'ExpectedActionTypeToRequestTradeById']);
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
if (!id) {
|
|
34
41
|
return cbk([400, 'ExpectedTradeIdentifierToRequestTradeById']);
|
|
35
42
|
}
|
|
@@ -45,14 +52,23 @@ module.exports = ({id, lnd, to}, cbk) => {
|
|
|
45
52
|
return cbk();
|
|
46
53
|
},
|
|
47
54
|
|
|
55
|
+
// Derive request type
|
|
56
|
+
requestType: ['validate', ({}, cbk) => {
|
|
57
|
+
if (action === buyChannelAction) {
|
|
58
|
+
return cbk(null, serviceTypeRequestChannelSale);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return cbk(null, serviceTypeRequestTrades);
|
|
62
|
+
}],
|
|
63
|
+
|
|
48
64
|
// Get trade
|
|
49
|
-
getTrade: ['
|
|
65
|
+
getTrade: ['requestType', ({requestType}, cbk) => {
|
|
50
66
|
return makePeerRequest({
|
|
51
67
|
lnd,
|
|
52
68
|
to,
|
|
53
69
|
records: [{type: tradeIdRecordType, value: id}],
|
|
54
70
|
timeout: requestTradeTimeoutMs,
|
|
55
|
-
type:
|
|
71
|
+
type: requestType,
|
|
56
72
|
},
|
|
57
73
|
(err, res) => {
|
|
58
74
|
if (!!err) {
|
|
@@ -14,6 +14,8 @@ const uriAsSocket = n => n.substring(67);
|
|
|
14
14
|
/** Service an individual trade
|
|
15
15
|
|
|
16
16
|
{
|
|
17
|
+
[action]: <Trade Action String>
|
|
18
|
+
[capacity]: <Channel Open Capacity Tokens Number>
|
|
17
19
|
channels: [{
|
|
18
20
|
id: <Standard Format Channel Id String>
|
|
19
21
|
partner_public_key: <Node Public Key Hex String>
|
|
@@ -102,10 +104,13 @@ module.exports = (args, cbk) => {
|
|
|
102
104
|
args.logger.info({trade_expiry: new Date(args.expires_at)});
|
|
103
105
|
|
|
104
106
|
const sub = serviceTradeRequests({
|
|
107
|
+
action: args.action,
|
|
108
|
+
capacity: args.capacity,
|
|
105
109
|
description: args.description,
|
|
106
110
|
expires_at: args.expires_at,
|
|
107
111
|
id: args.id,
|
|
108
112
|
lnd: args.lnd,
|
|
113
|
+
logger: args.logger,
|
|
109
114
|
secret: args.secret,
|
|
110
115
|
tokens: asNumber(args.tokens),
|
|
111
116
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const EventEmitter = require('events');
|
|
2
2
|
const {randomBytes} = require('crypto');
|
|
3
3
|
|
|
4
|
+
const {acceptsChannelOpen} = require('ln-sync');
|
|
4
5
|
const {getInvoice} = require('ln-service');
|
|
5
6
|
const {subscribeToInvoice} = require('ln-service');
|
|
6
7
|
|
|
@@ -8,6 +9,8 @@ const acceptTrade = require('./accept_trade');
|
|
|
8
9
|
const finalizeTradeSecret = require('./finalize_trade_secret');
|
|
9
10
|
const {makePeerRequest} = require('./../p2p');
|
|
10
11
|
const {servicePeerRequests} = require('./../p2p');
|
|
12
|
+
const {serviceTypeReceiveChannelSale} = require('./../service_types');
|
|
13
|
+
const {serviceTypeRequestChannelSale} = require('./../service_types');
|
|
11
14
|
const {serviceTypeRequestTrades} = require('./../service_types');
|
|
12
15
|
const {serviceTypeReceiveTrades} = require('./../service_types');
|
|
13
16
|
|
|
@@ -20,6 +23,7 @@ const idBackType = '0';
|
|
|
20
23
|
const idRecord = id => ({type: '1', value: id});
|
|
21
24
|
const msRemainingToTime = time => time - Date.now();
|
|
22
25
|
const postOpenTradeTimeoutMs = 1000 * 30;
|
|
26
|
+
const sellAction = 'sell';
|
|
23
27
|
const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
|
|
24
28
|
const tradeSecretType = '1';
|
|
25
29
|
const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
|
|
@@ -29,10 +33,12 @@ const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
|
|
|
29
33
|
The maximum expiration date is three weeks
|
|
30
34
|
|
|
31
35
|
{
|
|
36
|
+
action: <Trade Action String>
|
|
32
37
|
description: <Trade Description String>
|
|
33
38
|
expires_at: <Trade Expires At ISO 8601 Date String>
|
|
34
39
|
id: <Trade Id Hex String>
|
|
35
40
|
lnd: <Authenticated LND API Object>
|
|
41
|
+
logger: <Winston Logger Object>
|
|
36
42
|
secret: <Secret Payload String>
|
|
37
43
|
tokens: <Tokens Number>
|
|
38
44
|
}
|
|
@@ -126,8 +132,12 @@ module.exports = args => {
|
|
|
126
132
|
descRecord(utf8AsHex(args.description)),
|
|
127
133
|
];
|
|
128
134
|
|
|
135
|
+
const requestType = !!args.action ?
|
|
136
|
+
serviceTypeRequestChannelSale :
|
|
137
|
+
serviceTypeRequestTrades;
|
|
138
|
+
|
|
129
139
|
// Wait for a request for the open trade
|
|
130
|
-
service.request({type:
|
|
140
|
+
service.request({type: requestType}, (req, res) => {
|
|
131
141
|
const {failure, success} = res;
|
|
132
142
|
const requestTradeId = findIdRecord(req.records);
|
|
133
143
|
|
|
@@ -163,13 +173,17 @@ module.exports = args => {
|
|
|
163
173
|
return;
|
|
164
174
|
}
|
|
165
175
|
|
|
176
|
+
const receiveType = args.action === sellAction ?
|
|
177
|
+
serviceTypeReceiveChannelSale :
|
|
178
|
+
serviceTypeReceiveTrades;
|
|
179
|
+
|
|
166
180
|
// Ping the node with trade details
|
|
167
181
|
return makePeerRequest({
|
|
168
182
|
records,
|
|
169
183
|
lnd: args.lnd,
|
|
170
184
|
timeout: postOpenTradeTimeoutMs,
|
|
171
185
|
to: req.from,
|
|
172
|
-
type:
|
|
186
|
+
type: receiveType,
|
|
173
187
|
},
|
|
174
188
|
err => {
|
|
175
189
|
if (!!err) {
|
|
@@ -191,10 +205,12 @@ module.exports = args => {
|
|
|
191
205
|
|
|
192
206
|
// Create a trade secret for the requesting peer and return that
|
|
193
207
|
return finalizeTradeSecret({
|
|
208
|
+
action: args.action,
|
|
194
209
|
description: args.description,
|
|
195
210
|
expires_at: args.expires_at,
|
|
196
211
|
is_hold: true,
|
|
197
212
|
lnd: args.lnd,
|
|
213
|
+
logger: args.logger,
|
|
198
214
|
secret: args.secret,
|
|
199
215
|
to: req.from,
|
|
200
216
|
tokens: args.tokens,
|
|
@@ -230,9 +246,13 @@ module.exports = args => {
|
|
|
230
246
|
|
|
231
247
|
// Settle the held invoice with the preimage
|
|
232
248
|
return acceptTrade({
|
|
249
|
+
action: args.action,
|
|
250
|
+
capacity: args.capacity,
|
|
233
251
|
cancel: holds.filter(n => n.id !== updated.id).map(n => n.id),
|
|
234
252
|
id: args.id,
|
|
235
253
|
lnd: args.lnd,
|
|
254
|
+
logger: args.logger,
|
|
255
|
+
partner_public_key: req.from,
|
|
236
256
|
secret: res.secret,
|
|
237
257
|
},
|
|
238
258
|
err => {
|
|
@@ -246,6 +266,23 @@ module.exports = args => {
|
|
|
246
266
|
});
|
|
247
267
|
});
|
|
248
268
|
|
|
269
|
+
// Exit early on channel sale, confirming acceptance
|
|
270
|
+
if (args.action === sellAction) {
|
|
271
|
+
return acceptsChannelOpen({
|
|
272
|
+
capacity: args.capacity,
|
|
273
|
+
lnd: args.lnd,
|
|
274
|
+
partner_public_key: req.from,
|
|
275
|
+
},
|
|
276
|
+
err => {
|
|
277
|
+
if (!!err) {
|
|
278
|
+
return failure([503, 'FailedToAcceptChannelOpen', {err}]);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Return details about the trade
|
|
282
|
+
return success({records: [record]});
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
249
286
|
// Return details about the trade
|
|
250
287
|
return success({records: [record]});
|
|
251
288
|
});
|