paid-services 3.13.2 → 3.14.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/p2p/encode_peer_response.js +1 -2
- package/package.json +2 -1
- package/test/trades/test_encode_open_trade.js +48 -0
- package/test/trades/test_encode_trade.js +24 -1
- package/trades/accept_trade.js +83 -33
- package/trades/buy_channel.js +251 -0
- package/trades/buy_preimage.js +3 -2
- package/trades/connect_to_seller.js +183 -0
- package/trades/convert_fiat.js +112 -0
- package/trades/create_anchored_trade.js +22 -13
- package/trades/create_channel_sale.js +181 -77
- package/trades/create_trade.js +115 -20
- package/trades/decode_anchored_trade.js +39 -11
- package/trades/decode_open_trade.js +6 -0
- package/trades/decode_trade.js +16 -4
- package/trades/decode_trade_payment.js +45 -0
- package/trades/encode_anchored_trade.js +25 -5
- package/trades/encode_trade.js +1 -1
- package/trades/encode_trade_payment.js +38 -0
- package/trades/encode_trade_secret.js +1 -1
- package/trades/finalize_trade.js +244 -0
- package/trades/find_trade.js +14 -175
- package/trades/get_anchored_trade.js +10 -6
- package/trades/manage_trade.js +2 -8
- package/trades/manage_trades.js +44 -37
- package/trades/request_trade_by_id.js +3 -18
- package/trades/service_anchored_trades.js +5 -1
- package/trades/service_open_trade.js +24 -17
- package/trades/service_trade_requests.js +51 -31
- package/trades/trade_from_invoice.js +11 -7
- package/trades/finalize_trade_secret.js +0 -158
- package/trades/open_channel.js +0 -60
|
@@ -1,56 +1,50 @@
|
|
|
1
|
-
const {randomBytes} = require('crypto');
|
|
2
|
-
|
|
3
1
|
const asyncAuto = require('async/auto');
|
|
4
|
-
const
|
|
5
|
-
const {getChainFeeRate} = require('ln-service');
|
|
2
|
+
const {getChainBalance} = require('ln-service');
|
|
6
3
|
const {getChannels} = require('ln-service');
|
|
7
4
|
const {getNetwork} = require('ln-sync');
|
|
8
5
|
const {getWalletInfo} = require('ln-service');
|
|
9
|
-
const {signMessage} = require('ln-service');
|
|
10
6
|
const {returnResult} = require('asyncjs-util');
|
|
11
7
|
|
|
8
|
+
const convertFiat = require('./convert_fiat');
|
|
12
9
|
const createAnchoredTrade = require('./create_anchored_trade');
|
|
13
10
|
const serviceOpenTrade = require('./service_open_trade');
|
|
14
11
|
|
|
15
12
|
const asNumber = n => parseFloat(n, 10);
|
|
16
13
|
const daysAsMs = days => Number(days) * 1000 * 60 * 60 * 24;
|
|
14
|
+
const defaultChannelSize = 5000000;
|
|
17
15
|
const defaultExpirationDays = 1;
|
|
16
|
+
const {floor} = Math;
|
|
18
17
|
const futureDate = ms => new Date(Date.now() + ms).toISOString();
|
|
19
18
|
const isNumber = n => !isNaN(n);
|
|
19
|
+
const {min} = Math;
|
|
20
|
+
const minChannelSize = 20000;
|
|
21
|
+
const ppmCost = (amount, rate) => Math.ceil(amount * rate / 1000000);
|
|
22
|
+
const priceFiat = 'price-in-fiat';
|
|
23
|
+
const pricePpm = 'price-in-ppm';
|
|
24
|
+
const priceTokens = 'price-in-tokens';
|
|
20
25
|
const query = 'How much would you like to sell?';
|
|
21
|
-
const
|
|
22
|
-
const saleSecret = randomBytes(48).toString('hex');
|
|
23
|
-
const tradeDescription = (alias, tokens) => `channelsale:${alias}-${tokens}`;
|
|
26
|
+
const usdAsPrice = usd => `${usd}*USD`;
|
|
24
27
|
|
|
25
28
|
/** Create a new channel sale
|
|
26
29
|
|
|
27
30
|
{
|
|
28
|
-
action: <Channel Sale Action String>
|
|
29
31
|
ask: <Ask Function>
|
|
30
|
-
balance: <Total Available Chain Confirmed Balance Tokens Number>
|
|
31
32
|
lnd: <Authenticated LND API Object>
|
|
32
33
|
logger: <Winston Logger Object>
|
|
34
|
+
request: <Request Function>
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
@returns via cbk or Promise
|
|
36
38
|
*/
|
|
37
|
-
module.exports = ({
|
|
39
|
+
module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
38
40
|
return new Promise((resolve, reject) => {
|
|
39
41
|
return asyncAuto({
|
|
40
42
|
// Check arguments
|
|
41
43
|
validate: cbk => {
|
|
42
|
-
if (!action) {
|
|
43
|
-
return cbk([400, 'ExpectedActionTypeToCreateChannelSale']);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
44
|
if (!ask) {
|
|
47
45
|
return cbk([400, 'ExpectedAskFunctionToCreateChannelSale']);
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
if (balance === undefined) {
|
|
51
|
-
return cbk([400, 'ExpectedOnChainBalanceToCreateChannelSale']);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
48
|
if (!lnd) {
|
|
55
49
|
return cbk([400, 'ExpectedLndToCreateChannelSale']);
|
|
56
50
|
}
|
|
@@ -59,9 +53,16 @@ module.exports = ({action, ask, balance, lnd, logger}, cbk) => {
|
|
|
59
53
|
return cbk([400, 'ExpectedLoggerToCreateChannelSale']);
|
|
60
54
|
}
|
|
61
55
|
|
|
56
|
+
if (!request) {
|
|
57
|
+
return cbk([400, 'ExpectedRequestFunctionToCreateChannelSale']);
|
|
58
|
+
}
|
|
59
|
+
|
|
62
60
|
return cbk();
|
|
63
61
|
},
|
|
64
62
|
|
|
63
|
+
// Get the current chain balance to set the limit for a channel sale
|
|
64
|
+
getBalance: ['validate', ({}, cbk) => getChainBalance({lnd}, cbk)],
|
|
65
|
+
|
|
65
66
|
// Get the public channels to use for an open trade
|
|
66
67
|
getChannels: ['validate', ({}, cbk) => {
|
|
67
68
|
return getChannels({lnd, is_public: true}, cbk);
|
|
@@ -73,10 +74,15 @@ module.exports = ({action, ask, balance, lnd, logger}, cbk) => {
|
|
|
73
74
|
// Get the network name to use for an open trade
|
|
74
75
|
getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
|
|
75
76
|
|
|
76
|
-
// Ask for
|
|
77
|
-
askForAmount: ['
|
|
77
|
+
// Ask for channel capacity to sell
|
|
78
|
+
askForAmount: ['getBalance', ({getBalance}, cbk) => {
|
|
79
|
+
if (getBalance.chain_balance < minChannelSize) {
|
|
80
|
+
return cbk([400, 'InsufficientOnChainFundsToSellChannel']);
|
|
81
|
+
}
|
|
82
|
+
|
|
78
83
|
return ask({
|
|
79
|
-
|
|
84
|
+
default: min(defaultChannelSize, getBalance.chain_balance),
|
|
85
|
+
message: `${query} (Available balance: ${getBalance.chain_balance})`,
|
|
80
86
|
name: 'amount',
|
|
81
87
|
type: 'input',
|
|
82
88
|
validate: input => {
|
|
@@ -86,31 +92,56 @@ module.exports = ({action, ask, balance, lnd, logger}, cbk) => {
|
|
|
86
92
|
|
|
87
93
|
// The token amount should be numeric
|
|
88
94
|
if (!isNumber(input)) {
|
|
89
|
-
return 'Expected numeric amount for
|
|
95
|
+
return 'Expected numeric amount for channel capacity';
|
|
90
96
|
}
|
|
91
97
|
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
// Avoid listing more coins than are present on-chain
|
|
99
|
+
if (Number(input) > getBalance.chain_balance) {
|
|
100
|
+
return 'Capacity cannot be more than available balance';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (Number(input) < minChannelSize) {
|
|
104
|
+
return `Channel capacity must be larger than ${minChannelSize}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Disallow fractional values
|
|
108
|
+
if (!!isNumber(input) && floor(input) !== asNumber(input)) {
|
|
109
|
+
return 'Decimal capacities are not supported';
|
|
94
110
|
}
|
|
95
111
|
|
|
96
112
|
return true;
|
|
97
113
|
},
|
|
98
114
|
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return cbk([503, 'UnexpectedErrorAskingForSaleAmount', {err}]);
|
|
102
|
-
}
|
|
115
|
+
cbk);
|
|
116
|
+
}],
|
|
103
117
|
|
|
104
|
-
|
|
105
|
-
|
|
118
|
+
// Select method of pricing
|
|
119
|
+
selectPricing: ['askForAmount', ({}, cbk) => {
|
|
120
|
+
return ask({
|
|
121
|
+
choices: [
|
|
122
|
+
{name: 'Set absolute price', value: priceTokens},
|
|
123
|
+
{name: 'Set price by PPM (parts per million)', value: pricePpm},
|
|
124
|
+
{name: 'Set price in fiat (USD)', value: priceFiat},
|
|
125
|
+
],
|
|
126
|
+
default: pricePpm,
|
|
127
|
+
message: '?',
|
|
128
|
+
name: 'action',
|
|
129
|
+
type: 'list',
|
|
130
|
+
},
|
|
131
|
+
cbk);
|
|
106
132
|
}],
|
|
107
133
|
|
|
108
|
-
// Ask for
|
|
109
|
-
|
|
134
|
+
// Ask for fiat based cost
|
|
135
|
+
askForFiat: ['selectPricing', ({selectPricing}, cbk) => {
|
|
136
|
+
// Exit early when the pricing is not in fiat
|
|
137
|
+
if (selectPricing.action !== priceFiat) {
|
|
138
|
+
return cbk();
|
|
139
|
+
}
|
|
140
|
+
|
|
110
141
|
return ask({
|
|
111
|
-
default: '
|
|
112
|
-
message: '
|
|
113
|
-
name: '
|
|
142
|
+
default: '0.01',
|
|
143
|
+
message: 'Dollars USD to charge?',
|
|
144
|
+
name: 'usd',
|
|
114
145
|
type: 'input',
|
|
115
146
|
validate: input => {
|
|
116
147
|
if (!input) {
|
|
@@ -119,23 +150,83 @@ module.exports = ({action, ask, balance, lnd, logger}, cbk) => {
|
|
|
119
150
|
|
|
120
151
|
// Price of sale should be numeric
|
|
121
152
|
if (!isNumber(input)) {
|
|
122
|
-
return '
|
|
153
|
+
return 'Not a number of dollars, try a number?';
|
|
123
154
|
}
|
|
124
155
|
|
|
125
156
|
return true;
|
|
126
157
|
},
|
|
127
158
|
},
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
159
|
+
cbk);
|
|
160
|
+
}],
|
|
161
|
+
|
|
162
|
+
// Ask for absolute cost
|
|
163
|
+
askForPrice: ['selectPricing', ({selectPricing}, cbk) => {
|
|
164
|
+
// Exit early when the pricing is not absolute
|
|
165
|
+
if (selectPricing.action !== priceTokens) {
|
|
166
|
+
return cbk();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return ask({
|
|
170
|
+
default: '1337',
|
|
171
|
+
message: 'Amount to charge?',
|
|
172
|
+
name: 'tokens',
|
|
173
|
+
type: 'input',
|
|
174
|
+
validate: input => {
|
|
175
|
+
if (!input) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Price of sale should be numeric
|
|
180
|
+
if (!isNumber(input)) {
|
|
181
|
+
return 'Not a number, try a number?';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Disallow fractional values
|
|
185
|
+
if (!!isNumber(input) && floor(input) !== asNumber(input)) {
|
|
186
|
+
return 'Specified precision not supported';
|
|
187
|
+
}
|
|
132
188
|
|
|
133
|
-
|
|
134
|
-
|
|
189
|
+
return true;
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
cbk);
|
|
193
|
+
}],
|
|
194
|
+
|
|
195
|
+
// Ask for rate-based cost
|
|
196
|
+
askForRate: ['askForAmount', 'selectPricing', ({selectPricing}, cbk) => {
|
|
197
|
+
// Exit early when the pricing is not in ppm
|
|
198
|
+
if (selectPricing.action !== pricePpm) {
|
|
199
|
+
return cbk();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return ask({
|
|
203
|
+
default: '350',
|
|
204
|
+
message: 'Price in PPM?',
|
|
205
|
+
name: 'rate',
|
|
206
|
+
type: 'input',
|
|
207
|
+
validate: input => {
|
|
208
|
+
if (!input) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Price of sale should be in numeric ppm
|
|
213
|
+
if (!isNumber(input)) {
|
|
214
|
+
return 'Unable to parse rate, try a numeric ppm rate?';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Disallow fractional values
|
|
218
|
+
if (!!isNumber(input) && floor(input) !== asNumber(input)) {
|
|
219
|
+
return 'Specified precision not supported';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return true;
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
cbk);
|
|
135
226
|
}],
|
|
136
227
|
|
|
137
228
|
// Ask for the expiration of the channel sale
|
|
138
|
-
|
|
229
|
+
askForDays: ['askForFiat', 'askForPrice', 'askForRate', ({}, cbk) => {
|
|
139
230
|
return ask({
|
|
140
231
|
default: defaultExpirationDays,
|
|
141
232
|
name: 'days',
|
|
@@ -151,40 +242,54 @@ module.exports = ({action, ask, balance, lnd, logger}, cbk) => {
|
|
|
151
242
|
cbk);
|
|
152
243
|
}],
|
|
153
244
|
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
245
|
+
// Check ability to get fiat prices
|
|
246
|
+
checkCostLookup: ['askForFiat', ({askForFiat}, cbk) => {
|
|
247
|
+
// Exit early when no fiat is referenced
|
|
248
|
+
if (!askForFiat) {
|
|
249
|
+
return cbk();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return convertFiat({request, price: usdAsPrice(askForFiat.usd)}, cbk);
|
|
161
253
|
}],
|
|
162
254
|
|
|
163
|
-
//
|
|
164
|
-
|
|
255
|
+
// Determine the tokens value of the trade
|
|
256
|
+
cost: [
|
|
165
257
|
'askForAmount',
|
|
166
|
-
'
|
|
167
|
-
|
|
258
|
+
'askForFiat',
|
|
259
|
+
'askForPrice',
|
|
260
|
+
'askForRate',
|
|
261
|
+
({askForAmount, askForFiat, askForPrice, askForRate}, cbk) =>
|
|
168
262
|
{
|
|
169
|
-
|
|
263
|
+
// Exit early when price is in fiat, not tokens
|
|
264
|
+
if (!!askForFiat) {
|
|
265
|
+
return cbk(null, {price: usdAsPrice(askForFiat.usd)});
|
|
266
|
+
}
|
|
170
267
|
|
|
171
|
-
|
|
268
|
+
// Exit early when the price is specified directly
|
|
269
|
+
if (!!askForPrice) {
|
|
270
|
+
return cbk(null, {tokens: askForPrice.tokens});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Calculate the rate that was specified in PPM
|
|
274
|
+
const tokens = ppmCost(Number(askForAmount.amount), askForRate.rate);
|
|
275
|
+
|
|
276
|
+
return cbk(null, {tokens});
|
|
172
277
|
}],
|
|
173
278
|
|
|
174
279
|
// Create an anchor invoice for the channel sale
|
|
175
280
|
createAnchor: [
|
|
176
|
-
'
|
|
177
|
-
'
|
|
178
|
-
'
|
|
179
|
-
'
|
|
180
|
-
({
|
|
281
|
+
'askForAmount',
|
|
282
|
+
'askForDays',
|
|
283
|
+
'checkCostLookup',
|
|
284
|
+
'cost',
|
|
285
|
+
({askForAmount, askForDays, askForRate, cost}, cbk) =>
|
|
181
286
|
{
|
|
182
287
|
return createAnchoredTrade({
|
|
183
|
-
description,
|
|
184
288
|
lnd,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
289
|
+
channel: Number(askForAmount.amount),
|
|
290
|
+
expires_at: futureDate(daysAsMs(askForDays.days)),
|
|
291
|
+
price: cost.price,
|
|
292
|
+
tokens: cost.tokens,
|
|
188
293
|
},
|
|
189
294
|
cbk);
|
|
190
295
|
}],
|
|
@@ -192,37 +297,36 @@ module.exports = ({action, ask, balance, lnd, logger}, cbk) => {
|
|
|
192
297
|
// Wait for a peer to connect and ask for the channel sale details
|
|
193
298
|
serviceSaleRequests: [
|
|
194
299
|
'askForAmount',
|
|
195
|
-
'
|
|
300
|
+
'askForDays',
|
|
301
|
+
'cost',
|
|
196
302
|
'createAnchor',
|
|
197
|
-
'description',
|
|
198
303
|
'getChannels',
|
|
199
304
|
'getNetwork',
|
|
200
305
|
'getIdentity',
|
|
201
306
|
({
|
|
202
307
|
askForAmount,
|
|
203
|
-
|
|
308
|
+
askForDays,
|
|
309
|
+
askForRate,
|
|
310
|
+
cost,
|
|
204
311
|
createAnchor,
|
|
205
|
-
description,
|
|
206
312
|
getChannels,
|
|
207
313
|
getNetwork,
|
|
208
314
|
getIdentity,
|
|
209
|
-
saleCost,
|
|
210
315
|
},
|
|
211
316
|
cbk) =>
|
|
212
317
|
{
|
|
213
318
|
return serviceOpenTrade({
|
|
214
|
-
action,
|
|
215
|
-
description,
|
|
216
319
|
lnd,
|
|
217
320
|
logger,
|
|
218
|
-
|
|
321
|
+
request,
|
|
322
|
+
channel: Number(askForAmount.amount),
|
|
219
323
|
channels: getChannels.channels,
|
|
220
|
-
expires_at: futureDate(daysAsMs(
|
|
324
|
+
expires_at: futureDate(daysAsMs(askForDays.days)),
|
|
221
325
|
id: createAnchor.id,
|
|
222
326
|
network: getNetwork.network,
|
|
327
|
+
price: cost.price,
|
|
223
328
|
public_key: getIdentity.public_key,
|
|
224
|
-
|
|
225
|
-
tokens: asNumber(saleCost),
|
|
329
|
+
tokens: cost.tokens,
|
|
226
330
|
uris: (getIdentity.uris || []),
|
|
227
331
|
},
|
|
228
332
|
cbk);
|
package/trades/create_trade.js
CHANGED
|
@@ -4,20 +4,25 @@ const {getNetwork} = require('ln-sync');
|
|
|
4
4
|
const {getWalletInfo} = require('ln-service');
|
|
5
5
|
const {returnResult} = require('asyncjs-util');
|
|
6
6
|
|
|
7
|
+
const convertFiat = require('./convert_fiat');
|
|
7
8
|
const createAnchoredTrade = require('./create_anchored_trade');
|
|
8
|
-
const
|
|
9
|
+
const finalizeTrade = require('./finalize_trade');
|
|
9
10
|
const serviceOpenTrade = require('./service_open_trade');
|
|
10
11
|
|
|
11
12
|
const asNumber = n => parseFloat(n, 10);
|
|
12
13
|
const daysAsMs = days => Number(days) * 1000 * 60 * 60 * 24;
|
|
13
|
-
const defaultExpirationDays =
|
|
14
|
+
const defaultExpirationDays = 1;
|
|
15
|
+
const defaultFiatInvoiceExpiryMs = 1000 * 60 * 30;
|
|
14
16
|
const {floor} = Math;
|
|
15
17
|
const futureDate = ms => new Date(Date.now() + ms).toISOString();
|
|
16
18
|
const isNumber = n => !isNaN(n);
|
|
17
19
|
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
18
20
|
const maxDescriptionLength = 100;
|
|
19
21
|
const maxSecretLength = 100;
|
|
22
|
+
const priceFiat = 'set-price-fiat';
|
|
23
|
+
const priceTokens = 'set-price-tokens';
|
|
20
24
|
const uriAsSocket = n => n.substring(67);
|
|
25
|
+
const usdAsPrice = ({usd}) => `${usd}*USD`;
|
|
21
26
|
const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
|
|
22
27
|
|
|
23
28
|
/** Create a new trade
|
|
@@ -26,6 +31,7 @@ const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
|
|
|
26
31
|
ask: <Ask Function>
|
|
27
32
|
lnd: <Authenticated LND API Object>
|
|
28
33
|
logger: <Winston Logger Object>
|
|
34
|
+
request: <Request Function>
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
@returns via cbk or Promise
|
|
@@ -33,7 +39,7 @@ const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
|
|
|
33
39
|
trade: <Hex Encoded Trade String>
|
|
34
40
|
}
|
|
35
41
|
*/
|
|
36
|
-
module.exports = ({ask, lnd, logger}, cbk) => {
|
|
42
|
+
module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
37
43
|
return new Promise((resolve, reject) => {
|
|
38
44
|
return asyncAuto({
|
|
39
45
|
// Check arguments
|
|
@@ -50,6 +56,10 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
50
56
|
return cbk([400, 'ExpectedWinstonLoggerToCreateTrade']);
|
|
51
57
|
}
|
|
52
58
|
|
|
59
|
+
if (!request) {
|
|
60
|
+
return cbk([400, 'ExpectedRequestFunctionToCreateTrade']);
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
return cbk();
|
|
54
64
|
},
|
|
55
65
|
|
|
@@ -126,7 +136,7 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
126
136
|
askForSecret: ['askForDescription', ({askForDescription}, cbk) => {
|
|
127
137
|
return ask({
|
|
128
138
|
name: 'secret',
|
|
129
|
-
message: '
|
|
139
|
+
message: 'What is the secret you want to sell?',
|
|
130
140
|
type: 'input',
|
|
131
141
|
validate: input => {
|
|
132
142
|
if (!input) {
|
|
@@ -143,19 +153,81 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
143
153
|
cbk);
|
|
144
154
|
}],
|
|
145
155
|
|
|
156
|
+
// Select pricing type
|
|
157
|
+
selectPriceType: [
|
|
158
|
+
'askForNodeId',
|
|
159
|
+
'askForSecret',
|
|
160
|
+
({askForNodeId}, cbk) =>
|
|
161
|
+
{
|
|
162
|
+
// Exit early and force absolute price when this is a direct trade
|
|
163
|
+
if (!!askForNodeId.id) {
|
|
164
|
+
return cbk(null, {type: priceTokens});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return ask({
|
|
168
|
+
choices: [
|
|
169
|
+
{name: 'Set absolute price', value: priceTokens},
|
|
170
|
+
{name: 'Set dynamic price in fiat terms (USD)', value: priceFiat},
|
|
171
|
+
],
|
|
172
|
+
message: '?',
|
|
173
|
+
name: 'type',
|
|
174
|
+
type: 'list',
|
|
175
|
+
},
|
|
176
|
+
cbk);
|
|
177
|
+
}],
|
|
178
|
+
|
|
179
|
+
// Ask for the fiat price of the secret
|
|
180
|
+
askForFiatPrice: ['selectPriceType', ({selectPriceType}, cbk) => {
|
|
181
|
+
// Exit early when not setting a fiat price
|
|
182
|
+
if (selectPriceType.type !== priceFiat) {
|
|
183
|
+
return cbk();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return ask({
|
|
187
|
+
default: '0.01',
|
|
188
|
+
message: 'Dollars USD to charge?',
|
|
189
|
+
name: 'usd',
|
|
190
|
+
type: 'input',
|
|
191
|
+
validate: input => {
|
|
192
|
+
if (!input) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Price of sale should be numeric
|
|
197
|
+
if (!isNumber(input)) {
|
|
198
|
+
return 'Not a number of dollars, try a number?';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return true;
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
cbk);
|
|
205
|
+
}],
|
|
206
|
+
|
|
146
207
|
// Ask for the price of the secret
|
|
147
|
-
|
|
208
|
+
askForTokensPrice: ['selectPriceType', ({selectPriceType}, cbk) => {
|
|
209
|
+
// Exit early when not asking for an absolute price
|
|
210
|
+
if (selectPriceType.type !== priceTokens) {
|
|
211
|
+
return cbk();
|
|
212
|
+
}
|
|
213
|
+
|
|
148
214
|
return ask({
|
|
215
|
+
default: '1',
|
|
216
|
+
message: 'Amount to charge?',
|
|
149
217
|
name: 'tokens',
|
|
150
|
-
|
|
218
|
+
type: 'input',
|
|
151
219
|
validate: input => {
|
|
152
|
-
|
|
153
|
-
if (!isNumber(input)) {
|
|
220
|
+
if (!input) {
|
|
154
221
|
return false;
|
|
155
222
|
}
|
|
156
223
|
|
|
224
|
+
// Price of sale should be numeric
|
|
225
|
+
if (!isNumber(input)) {
|
|
226
|
+
return 'Not a number, try a number?';
|
|
227
|
+
}
|
|
228
|
+
|
|
157
229
|
// Disallow fractional values
|
|
158
|
-
if (floor(input) !== asNumber(input)) {
|
|
230
|
+
if (!!isNumber(input) && floor(input) !== asNumber(input)) {
|
|
159
231
|
return 'Specified precision not supported';
|
|
160
232
|
}
|
|
161
233
|
|
|
@@ -166,12 +238,13 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
166
238
|
}],
|
|
167
239
|
|
|
168
240
|
// Ask for the expiration of the trade
|
|
169
|
-
askForExpiration: ['
|
|
241
|
+
askForExpiration: ['askForFiatPrice', 'askForTokensPrice', ({}, cbk) => {
|
|
170
242
|
return ask({
|
|
171
243
|
default: defaultExpirationDays,
|
|
172
244
|
name: 'days',
|
|
173
245
|
message: 'Days to offer this for?',
|
|
174
246
|
validate: input => {
|
|
247
|
+
// Days must be a number
|
|
175
248
|
if (!isNumber(input) || !Number(input)) {
|
|
176
249
|
return false;
|
|
177
250
|
}
|
|
@@ -182,19 +255,33 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
182
255
|
cbk);
|
|
183
256
|
}],
|
|
184
257
|
|
|
258
|
+
// Get the conversion to tokens for fiat
|
|
259
|
+
getTokens: ['askForFiatPrice', ({askForFiatPrice}, cbk) => {
|
|
260
|
+
// Exit early if not using fiat
|
|
261
|
+
if (!askForFiatPrice) {
|
|
262
|
+
return cbk();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Check that the fiat price can be converted
|
|
266
|
+
return convertFiat({request, price: usdAsPrice(askForFiatPrice)}, cbk);
|
|
267
|
+
}],
|
|
268
|
+
|
|
185
269
|
// Create an anchor invoice for the trade
|
|
186
270
|
createAnchor: [
|
|
187
271
|
'askForDescription',
|
|
188
272
|
'askForExpiration',
|
|
273
|
+
'askForFiatPrice',
|
|
189
274
|
'askForNodeId',
|
|
190
|
-
'askForPrice',
|
|
191
275
|
'askForSecret',
|
|
276
|
+
'askForTokensPrice',
|
|
277
|
+
'getTokens',
|
|
192
278
|
({
|
|
193
279
|
askForDescription,
|
|
194
280
|
askForExpiration,
|
|
281
|
+
askForFiatPrice,
|
|
195
282
|
askForNodeId,
|
|
196
|
-
askForPrice,
|
|
197
283
|
askForSecret,
|
|
284
|
+
askForTokensPrice,
|
|
198
285
|
},
|
|
199
286
|
cbk) =>
|
|
200
287
|
{
|
|
@@ -203,12 +290,16 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
203
290
|
return cbk();
|
|
204
291
|
}
|
|
205
292
|
|
|
293
|
+
// Tokens may be undefined when fiat price is set
|
|
294
|
+
const {tokens} = !!askForTokensPrice ? askForTokensPrice : {};
|
|
295
|
+
|
|
206
296
|
return createAnchoredTrade({
|
|
207
297
|
lnd,
|
|
298
|
+
tokens,
|
|
208
299
|
description: askForDescription.description,
|
|
209
300
|
expires_at: futureDate(daysAsMs(askForExpiration.days)),
|
|
301
|
+
price: !!askForFiatPrice ? usdAsPrice(askForFiatPrice) : undefined,
|
|
210
302
|
secret: askForSecret.secret,
|
|
211
|
-
tokens: asNumber(askForPrice.tokens),
|
|
212
303
|
},
|
|
213
304
|
cbk);
|
|
214
305
|
}],
|
|
@@ -217,9 +308,10 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
217
308
|
serviceTradeRequests: [
|
|
218
309
|
'askForDescription',
|
|
219
310
|
'askForExpiration',
|
|
311
|
+
'askForFiatPrice',
|
|
220
312
|
'askForNodeId',
|
|
221
|
-
'askForPrice',
|
|
222
313
|
'askForSecret',
|
|
314
|
+
'askForTokensPrice',
|
|
223
315
|
'createAnchor',
|
|
224
316
|
'getChannels',
|
|
225
317
|
'getIdentity',
|
|
@@ -227,9 +319,10 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
227
319
|
({
|
|
228
320
|
askForDescription,
|
|
229
321
|
askForExpiration,
|
|
322
|
+
askForFiatPrice,
|
|
230
323
|
askForNodeId,
|
|
231
|
-
askForPrice,
|
|
232
324
|
askForSecret,
|
|
325
|
+
askForTokensPrice,
|
|
233
326
|
createAnchor,
|
|
234
327
|
getChannels,
|
|
235
328
|
getIdentity,
|
|
@@ -245,14 +338,16 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
245
338
|
return serviceOpenTrade({
|
|
246
339
|
lnd,
|
|
247
340
|
logger,
|
|
341
|
+
request,
|
|
248
342
|
channels: getChannels.channels,
|
|
249
343
|
description: askForDescription.description,
|
|
250
344
|
expires_at: futureDate(daysAsMs(askForExpiration.days)),
|
|
251
345
|
id: createAnchor.id,
|
|
252
346
|
network: getNetwork.network,
|
|
347
|
+
price: !!askForFiatPrice ? usdAsPrice(askForFiatPrice) : undefined,
|
|
253
348
|
public_key: getIdentity.public_key,
|
|
254
349
|
secret: askForSecret.secret,
|
|
255
|
-
tokens:
|
|
350
|
+
tokens: (!!askForTokensPrice ? askForTokensPrice : {}).tokens,
|
|
256
351
|
uris: (getIdentity.uris || []),
|
|
257
352
|
},
|
|
258
353
|
cbk);
|
|
@@ -263,14 +358,14 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
263
358
|
'askForDescription',
|
|
264
359
|
'askForExpiration',
|
|
265
360
|
'askForNodeId',
|
|
266
|
-
'askForPrice',
|
|
267
361
|
'askForSecret',
|
|
362
|
+
'askForTokensPrice',
|
|
268
363
|
({
|
|
269
364
|
askForDescription,
|
|
270
365
|
askForExpiration,
|
|
271
366
|
askForNodeId,
|
|
272
|
-
askForPrice,
|
|
273
367
|
askForSecret,
|
|
368
|
+
askForTokensPrice,
|
|
274
369
|
},
|
|
275
370
|
cbk) =>
|
|
276
371
|
{
|
|
@@ -279,13 +374,13 @@ module.exports = ({ask, lnd, logger}, cbk) => {
|
|
|
279
374
|
return cbk();
|
|
280
375
|
}
|
|
281
376
|
|
|
282
|
-
return
|
|
377
|
+
return finalizeTrade({
|
|
283
378
|
lnd,
|
|
284
379
|
description: askForDescription.description,
|
|
285
380
|
expires_at: futureDate(daysAsMs(askForExpiration.days)),
|
|
286
381
|
secret: askForSecret.secret,
|
|
287
382
|
to: askForNodeId.id,
|
|
288
|
-
tokens: asNumber(
|
|
383
|
+
tokens: asNumber(askForTokensPrice.tokens),
|
|
289
384
|
},
|
|
290
385
|
cbk);
|
|
291
386
|
}],
|