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.
@@ -14,21 +14,22 @@ 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
+ [channel]: <Sell Channel With Capacity Tokens Number>
19
18
  channels: [{
20
19
  id: <Standard Format Channel Id String>
21
20
  partner_public_key: <Node Public Key Hex String>
22
21
  }]
23
- description: <Trade Description String>
22
+ [description]: <Trade Description String>
24
23
  expires_at: <Expires At ISO 8601 Date String>
25
24
  id: <Trade Anchor Id Hex String>
26
25
  lnd: <Authenticated LND API Object>
27
26
  logger: <Winston Logger Object>
28
27
  network: <Network Name String>
28
+ [price]: <Price Expression String>
29
29
  public_key: <Identity Public Key Hex String>
30
- secret: <Secret to Sell String>
31
- tokens: <Trade Price Tokens Number>
30
+ request: <Request Function>
31
+ [secret]: <Secret to Sell String>
32
+ [tokens]: <Trade Price Tokens Number>
32
33
  uris: [<Node URI String>]
33
34
  }
34
35
 
@@ -39,12 +40,16 @@ module.exports = (args, cbk) => {
39
40
  return asyncAuto({
40
41
  // Check arguments
41
42
  validate: cbk => {
43
+ if (!args.channel && !args.secret) {
44
+ return cbk([400, 'ExpectedSecretOrChannelToSellToServiceTrade']);
45
+ }
46
+
42
47
  if (!isArray(args.channels)) {
43
48
  return cbk([400, 'ExpectedArrayOfChannelsToServiceTrade']);
44
49
  }
45
50
 
46
- if (!args.description) {
47
- return cbk([400, 'ExpectedDescriptionToServiceTrade']);
51
+ if (!args.channel && !args.description) {
52
+ return cbk([400, 'ExpectedChannelOrDescriptionToServiceTrade']);
48
53
  }
49
54
 
50
55
  if (!args.expires_at) {
@@ -67,16 +72,16 @@ module.exports = (args, cbk) => {
67
72
  return cbk([400, 'ExpectedNetworkNameToServiceTrade']);
68
73
  }
69
74
 
70
- if (!args.public_key) {
71
- return cbk([400, 'ExpectedIdentityPublicKeyToServiceTrade']);
75
+ if (!args.price && !args.tokens) {
76
+ return cbk([400, 'ExpectedTokensOrPriceExpressionToServiceTrade']);
72
77
  }
73
78
 
74
- if (!args.secret) {
75
- return cbk([400, 'ExpectedSecretToSellToServiceTrade']);
79
+ if (!args.public_key) {
80
+ return cbk([400, 'ExpectedIdentityPublicKeyToServiceTrade']);
76
81
  }
77
82
 
78
- if (!args.tokens) {
79
- return cbk([400, 'ExpectedTokensPriceToServiceTrade']);
83
+ if (!args.request) {
84
+ return cbk([400, 'ExpectedRequestFunctionToServiceTrade']);
80
85
  }
81
86
 
82
87
  if (!isArray(args.uris)) {
@@ -104,15 +109,15 @@ module.exports = (args, cbk) => {
104
109
  args.logger.info({trade_expiry: new Date(args.expires_at)});
105
110
 
106
111
  const sub = serviceTradeRequests({
107
- action: args.action,
108
- capacity: args.capacity,
112
+ channel: args.channel,
109
113
  description: args.description,
110
114
  expires_at: args.expires_at,
111
115
  id: args.id,
112
116
  lnd: args.lnd,
113
- logger: args.logger,
117
+ price: args.price,
118
+ request: args.request,
114
119
  secret: args.secret,
115
- tokens: asNumber(args.tokens),
120
+ tokens: args.tokens,
116
121
  });
117
122
 
118
123
  // Someone asked for trade details
@@ -130,6 +135,8 @@ module.exports = (args, cbk) => {
130
135
 
131
136
  sub.on('failure', failure => args.logger.error({failure}));
132
137
 
138
+ sub.on('opening_channel', opening => args.logger.info({opening}));
139
+
133
140
  sub.on('settled', ({to}) => settled.push(to));
134
141
 
135
142
  sub.on('trade', async ({to}) => {
@@ -6,7 +6,7 @@ const {getInvoice} = require('ln-service');
6
6
  const {subscribeToInvoice} = require('ln-service');
7
7
 
8
8
  const acceptTrade = require('./accept_trade');
9
- const finalizeTradeSecret = require('./finalize_trade_secret');
9
+ const finalizeTrade = require('./finalize_trade');
10
10
  const {makePeerRequest} = require('./../p2p');
11
11
  const {servicePeerRequests} = require('./../p2p');
12
12
  const {serviceTypeReceiveChannelSale} = require('./../service_types');
@@ -14,16 +14,15 @@ const {serviceTypeRequestChannelSale} = require('./../service_types');
14
14
  const {serviceTypeRequestTrades} = require('./../service_types');
15
15
  const {serviceTypeReceiveTrades} = require('./../service_types');
16
16
 
17
+ const chanDescription = capacity => `${(capacity / 1e8).toFixed(8)} channel`;
17
18
  const descRecord = description => ({type: '2', value: description});
18
- const events = ['details', 'end', 'failure', 'settled', 'trade'];
19
+ const events = 'details end failure opening_channel settled trade'.split(' ');
19
20
  const findIdRecord = records => records.find(n => n.type === '0');
20
21
  const findRequestIdRecord = records => records.find(n => n.type === '1');
21
22
  const hashHexLength = 64;
22
23
  const idBackType = '0';
23
24
  const idRecord = id => ({type: '1', value: id});
24
- const msRemainingToTime = time => time - Date.now();
25
25
  const postOpenTradeTimeoutMs = 1000 * 30;
26
- const sellAction = 'sell';
27
26
  const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
28
27
  const tradeSecretType = '1';
29
28
  const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
@@ -32,15 +31,19 @@ const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
32
31
 
33
32
  The maximum expiration date is three weeks
34
33
 
34
+ Either a channel or secret to sell is required
35
+ Either a direct price in tokens or a price expression is required
36
+
35
37
  {
36
- action: <Trade Action String>
37
- description: <Trade Description String>
38
+ [channel]: <Sell Channel With Capacity Tokens Number>
39
+ [description]: <Trade Description String>
38
40
  expires_at: <Trade Expires At ISO 8601 Date String>
39
41
  id: <Trade Id Hex String>
40
42
  lnd: <Authenticated LND API Object>
41
- logger: <Winston Logger Object>
42
- secret: <Secret Payload String>
43
- tokens: <Tokens Number>
43
+ [price]: <Trade Price String>
44
+ request: <Request Function>
45
+ [secret]: <Secret Payload String>
46
+ [tokens]: <Trade Price Tokens Number>
44
47
  }
45
48
 
46
49
  // Return details
@@ -56,6 +59,14 @@ const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
56
59
  @event 'failure'
57
60
  [<Failure Code Number>, <Failure Code String>]
58
61
 
62
+ // Traded a channel open
63
+ @event 'opening_channel'
64
+ {
65
+ fee_tokens_per_vbyte: <Chain Fee Rate Number>
66
+ transaction_id: <Channel Funding Transaction Id Hex String>
67
+ transaction_vout: <Channel Funding Transaction Output Index Number>
68
+ }
69
+
59
70
  // Received payment
60
71
  @event 'settled'
61
72
  {
@@ -72,7 +83,11 @@ const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
72
83
  <Service Event Emitter Object>
73
84
  */
74
85
  module.exports = args => {
75
- if (!args.description) {
86
+ if (!args.channel && !args.secret) {
87
+ throw new Error('ExpectedChannelOrSecretToEncryptToServiceTradeRequests');
88
+ }
89
+
90
+ if (!args.channel && !args.description) {
76
91
  throw new Error('ExpectedTradeDescriptionToServiceTradeRequests');
77
92
  }
78
93
 
@@ -88,16 +103,15 @@ module.exports = args => {
88
103
  throw new Error('ExpectedAuthenticatedLndToServiceTradeRequests');
89
104
  }
90
105
 
91
- if (!args.secret) {
92
- throw new Error('ExpectedSecretToEncryptToServiceTradeRequests');
106
+ if (!args.price && !args.tokens) {
107
+ throw new Error('ExpectedTokensOrPriceForTradeToServiceTradeRequests');
93
108
  }
94
109
 
95
- if (!args.tokens) {
96
- throw new Error('ExpectedTokensPriceForTradeToServiceTradeRequests');
110
+ if (!args.request) {
111
+ throw new Error('ExpectedRequestFunctionToServiceTradeRequests');
97
112
  }
98
113
 
99
114
  const emitter = new EventEmitter();
100
-
101
115
  const holds = [];
102
116
  const service = servicePeerRequests({lnd: args.lnd});
103
117
 
@@ -129,10 +143,10 @@ module.exports = args => {
129
143
  // Basic trade description
130
144
  const basicTradeRecords = [
131
145
  idRecord(args.id),
132
- descRecord(utf8AsHex(args.description)),
146
+ descRecord(utf8AsHex(args.description || chanDescription(args.channel))),
133
147
  ];
134
148
 
135
- const requestType = !!args.action ?
149
+ const requestType = !!args.channel ?
136
150
  serviceTypeRequestChannelSale :
137
151
  serviceTypeRequestTrades;
138
152
 
@@ -173,7 +187,7 @@ module.exports = args => {
173
187
  return;
174
188
  }
175
189
 
176
- const receiveType = args.action === sellAction ?
190
+ const receiveType = !!args.channel ?
177
191
  serviceTypeReceiveChannelSale :
178
192
  serviceTypeReceiveTrades;
179
193
 
@@ -204,13 +218,15 @@ module.exports = args => {
204
218
  emitter.emit('trade', ({to: req.from}));
205
219
 
206
220
  // Create a trade secret for the requesting peer and return that
207
- return finalizeTradeSecret({
208
- action: args.action,
221
+ return finalizeTrade({
222
+ channel: args.channel,
209
223
  description: args.description,
210
224
  expires_at: args.expires_at,
225
+ id: args.id,
211
226
  is_hold: true,
212
227
  lnd: args.lnd,
213
- logger: args.logger,
228
+ price: args.price,
229
+ request: args.request,
214
230
  secret: args.secret,
215
231
  to: req.from,
216
232
  tokens: args.tokens,
@@ -219,7 +235,7 @@ module.exports = args => {
219
235
  if (!!err) {
220
236
  emitter.emit('failure', err);
221
237
 
222
- return failure([503, 'FailedToFinalizeTradeSecret']);
238
+ return failure([503, 'FailedToFinalizeTrade']);
223
239
  }
224
240
 
225
241
  // Create a record for the encoded trade
@@ -246,36 +262,40 @@ module.exports = args => {
246
262
 
247
263
  // Settle the held invoice with the preimage
248
264
  return acceptTrade({
249
- action: args.action,
250
- capacity: args.capacity,
251
265
  cancel: holds.filter(n => n.id !== updated.id).map(n => n.id),
266
+ channel: args.channel,
267
+ from: req.from,
252
268
  id: args.id,
253
269
  lnd: args.lnd,
254
- logger: args.logger,
255
- partner_public_key: req.from,
256
270
  secret: res.secret,
257
271
  },
258
- err => {
272
+ (err, res) => {
259
273
  if (!!err) {
260
274
  return emitter.emit('failure', err);
261
275
  }
262
276
 
263
277
  emitter.emit('settled', {to: req.from});
264
278
 
279
+ if (!!res.opening_channel) {
280
+ emitter.emit('opening_channel', res.opening_channel);
281
+ }
282
+
265
283
  return emitter.emit('end', {});
266
284
  });
267
285
  });
268
286
 
269
- // Exit early on channel sale, confirming acceptance
270
- if (args.action === sellAction) {
287
+ // Exit early on channel sale, make sure a channel can be opened first
288
+ if (!!args.channel) {
271
289
  return acceptsChannelOpen({
272
- capacity: args.capacity,
290
+ capacity: args.channel,
273
291
  lnd: args.lnd,
274
292
  partner_public_key: req.from,
275
293
  },
276
294
  err => {
277
295
  if (!!err) {
278
- return failure([503, 'FailedToAcceptChannelOpen', {err}]);
296
+ emitter.emit('failure', [503, 'FailedOpenAttempt', {err}]);
297
+
298
+ return failure([503, 'CannotOpenChannel']);
279
299
  }
280
300
 
281
301
  // Return details about the trade
@@ -16,12 +16,14 @@ const decodeAnchoredTrade = require('./decode_anchored_trade');
16
16
  @returns
17
17
  {
18
18
  [trade]: {
19
+ [channel]: <Channel Sale Tokens Number>
19
20
  created_at: <Open Trade Created At ISO 8601 Date String>
20
- description: <Trade Description String>
21
+ [description]: <Trade Description String>
21
22
  expires_at: <Trade Expires at ISO 8601 Date String>
22
23
  id: <Trade Id Hex String>
23
- secret: <Secret Payload String>
24
- tokens: <Tokens Number>
24
+ [price]: <Price Expression String>
25
+ [secret]: <Secret Payload String>
26
+ [tokens]: <Tokens Number>
25
27
  }
26
28
  }
27
29
  */
@@ -30,7 +32,7 @@ module.exports = args => {
30
32
  return {};
31
33
  }
32
34
 
33
- if (!!args.is_push || !args.tokens) {
35
+ if (!!args.is_push) {
34
36
  return {};
35
37
  }
36
38
 
@@ -43,12 +45,14 @@ module.exports = args => {
43
45
 
44
46
  return {
45
47
  trade: {
48
+ channel: trade.channel || undefined,
46
49
  created_at: args.created_at,
47
- description: trade.description,
50
+ description: trade.description || undefined,
48
51
  expires_at: args.expires_at,
49
52
  id: args.id,
50
- secret: trade.secret,
51
- tokens: args.tokens,
53
+ price: trade.price || undefined,
54
+ secret: trade.secret || undefined,
55
+ tokens: args.tokens || undefined,
52
56
  },
53
57
  };
54
58
  };
@@ -1,158 +0,0 @@
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 {getChainFeeRate} = require('ln-service');
7
- const {returnResult} = require('asyncjs-util');
8
-
9
- const encodeTradeSecret = require('./encode_trade_secret');
10
- const encryptTradeSecret = require('./encrypt_trade_secret');
11
-
12
- const bufferAsHex = buffer => buffer.toString('hex');
13
- const {ceil} = Math;
14
- const hexAsBuffer = hex => Buffer.from(hex, 'hex');
15
- const openSizeVbytes = 250;
16
- const sellAction = 'sell';
17
- const sha256 = preimage => createHash('sha256').update(preimage).digest();
18
- const slowConf = 144;
19
- const utf8AsHex = utf8 => Buffer.from(utf8, 'utf8').toString('hex');
20
-
21
- /** Create a trade secret for a node
22
-
23
- {
24
- action: <Trade Action String>
25
- description: <Trade Description String>
26
- expires_at: <Trade Expires At ISO 8601 Date String>
27
- is_hold: <Create Hold Invoice Bool>
28
- lnd: <Authenticated LND API Object>
29
- logger: <Winston Logger Object>
30
- secret: <Clear Secret String>
31
- to: <To Public Key Hex Encoded String>
32
- tokens: <Price Tokens Number>
33
- }
34
-
35
- @returns via cbk or Promise
36
- {
37
- id: <Payment Hash Hex String>
38
- secret: <Hex Encoded Payment Secret String>
39
- trade: <Hex Encoded Trade String>
40
- }
41
- */
42
- module.exports = (args, cbk) => {
43
- return new Promise((resolve, reject) => {
44
- return asyncAuto({
45
- // Check arguments
46
- validate: cbk => {
47
- if (!args.description) {
48
- return cbk([400, 'ExpectedDescriptionToFinalizeTradeSecret']);
49
- }
50
-
51
- if (!args.expires_at) {
52
- return cbk([400, 'ExpectedExpiresAtDateToFinalizeTradeSecret']);
53
- }
54
-
55
- if (!args.lnd) {
56
- return cbk([400, 'ExpectedAuthenticatedLndToFinalizeTradeSecret']);
57
- }
58
-
59
- if (!args.secret) {
60
- return cbk([400, 'ExpectedSecretValueToFinalizeTradeSecret']);
61
- }
62
-
63
- if (!args.to) {
64
- return cbk([400, 'ExpectedToPublicKeyToFinalizeTradeSecret']);
65
- }
66
-
67
- if (!args.tokens) {
68
- return cbk([400, 'ExpectedTokensPriceToFinalizeTradeSecret']);
69
- }
70
-
71
- return cbk();
72
- },
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
-
102
- // Encrypt the secret data
103
- encrypt: ['validate', ({}, cbk) => {
104
- return encryptTradeSecret({
105
- lnd: args.lnd,
106
- secret: utf8AsHex(args.secret),
107
- to: args.to,
108
- },
109
- cbk);
110
- }],
111
-
112
- // Create the invoice to purchase the unlocking secret
113
- createInvoice: ['encrypt', 'salePrice', ({encrypt, salePrice}, cbk) => {
114
- // Exit early when this is a hold invoice
115
- if (!!args.is_hold) {
116
- return createHodlInvoice({
117
- description: args.description,
118
- expires_at: args.expires_at,
119
- id: bufferAsHex(sha256(hexAsBuffer(encrypt.payment_secret))),
120
- lnd: args.lnd,
121
- secret: encrypt.payment_secret,
122
- tokens: salePrice.price || args.tokens,
123
- },
124
- cbk);
125
- }
126
-
127
- return createInvoice({
128
- description: args.description,
129
- expires_at: args.expires_at,
130
- lnd: args.lnd,
131
- secret: encrypt.payment_secret,
132
- tokens: args.tokens,
133
- },
134
- cbk);
135
- }],
136
-
137
- // Encode all the trade data into wire format
138
- encode: ['createInvoice', 'encrypt', ({createInvoice, encrypt}, cbk) => {
139
- try {
140
- const {trade} = encodeTradeSecret({
141
- auth: encrypt.trade_auth_tag,
142
- payload: encrypt.trade_cipher,
143
- request: createInvoice.request,
144
- });
145
-
146
- return cbk(null, {
147
- trade,
148
- id: createInvoice.id,
149
- secret: encrypt.payment_secret,
150
- });
151
- } catch (err) {
152
- return cbk([500, err.message]);
153
- }
154
- }],
155
- },
156
- returnResult({reject, resolve, of: 'encode'}, cbk));
157
- });
158
- };
@@ -1,60 +0,0 @@
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
- };