paid-services 3.4.0 → 3.7.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.
Files changed (38) hide show
  1. package/.prettierignore +2 -0
  2. package/CHANGELOG.md +16 -0
  3. package/capacity/accept_capacity_change.js +2 -0
  4. package/capacity/ask_for_change_details.js +41 -9
  5. package/capacity/ask_for_decrease.js +167 -49
  6. package/capacity/capacity_change_requests.js +9 -0
  7. package/capacity/change_channel_capacity.js +7 -1
  8. package/capacity/encode_change_request.js +8 -1
  9. package/capacity/fee_for_replacement.js +3 -7
  10. package/capacity/get_capacity_change_requests.js +1 -0
  11. package/capacity/get_capacity_replacement.js +56 -7
  12. package/capacity/initiate_capacity_change.js +1 -2
  13. package/capacity/parse_capacity_change_request.js +8 -1
  14. package/capacity/propose_capacity_change.js +208 -92
  15. package/capacity/propose_channel_open.js +54 -0
  16. package/package.json +9 -9
  17. package/services/response_for_relay.js +0 -1
  18. package/test/capacity/test_capacity_change_requests.js +1 -0
  19. package/test/capacity/test_encode_change_request.js +5 -0
  20. package/test/capacity/test_parse_capacity_change_request.js +1 -0
  21. package/test/integration/test_accept_capacity_change.js +3 -4
  22. package/test/integration/test_change_channel_capacity.js +8 -1
  23. package/test/integration/test_decrease_channel_capacity.js +23 -8
  24. package/test/integration/test_increase_channel_capacity.js +4 -1
  25. package/test/integration/test_split_channel_capacity.js +241 -0
  26. package/trades/accept_trade.js +12 -3
  27. package/trades/create_trade.js +82 -50
  28. package/trades/decode_anchored_trade.js +56 -0
  29. package/trades/encode_anchored_trade.js +35 -0
  30. package/trades/finalize_trade_secret.js +7 -0
  31. package/trades/find_trade.js +28 -3
  32. package/trades/manage_trade.js +15 -4
  33. package/trades/manage_trades.js +209 -2
  34. package/trades/request_trade_by_id.js +7 -1
  35. package/trades/service_anchored_trades.js +181 -0
  36. package/trades/service_open_trade.js +172 -0
  37. package/trades/service_trade_requests.js +106 -35
  38. package/trades/trade_from_invoice.js +51 -0
@@ -1,6 +1,7 @@
1
1
  const EventEmitter = require('events');
2
2
  const {randomBytes} = require('crypto');
3
3
 
4
+ const {getInvoice} = require('ln-service');
4
5
  const {subscribeToInvoice} = require('ln-service');
5
6
 
6
7
  const acceptTrade = require('./accept_trade');
@@ -11,24 +12,37 @@ const {serviceTypeRequestTrades} = require('./../service_types');
11
12
  const {serviceTypeReceiveTrades} = require('./../service_types');
12
13
 
13
14
  const descRecord = description => ({type: '2', value: description});
15
+ const events = ['details', 'end', 'failure', 'settled', 'trade'];
14
16
  const findIdRecord = records => records.find(n => n.type === '0');
15
17
  const findRequestIdRecord = records => records.find(n => n.type === '1');
18
+ const hashHexLength = 64;
16
19
  const idBackType = '0';
17
20
  const idRecord = id => ({type: '1', value: id});
18
- const makeEphemeralTradeId = () => randomBytes(32).toString('hex');
21
+ const msRemainingToTime = time => time - Date.now();
19
22
  const postOpenTradeTimeoutMs = 1000 * 30;
23
+ const sumOf = arr => arr.reduce((sum, n) => sum + n, 0);
20
24
  const tradeSecretType = '1';
21
25
  const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
22
26
 
23
27
  /** Service requests for an open trade
24
28
 
29
+ The maximum expiration date is three weeks
30
+
25
31
  {
26
32
  description: <Trade Description String>
33
+ expires_at: <Trade Expires At ISO 8601 Date String>
34
+ id: <Trade Id Hex String>
27
35
  lnd: <Authenticated LND API Object>
28
36
  secret: <Secret Payload String>
29
37
  tokens: <Tokens Number>
30
38
  }
31
39
 
40
+ // Return details
41
+ @event 'details'
42
+ {
43
+ to: <Public Key Id Hex String>
44
+ }
45
+
32
46
  // Service ended
33
47
  @event 'end'
34
48
 
@@ -36,12 +50,6 @@ const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
36
50
  @event 'failure'
37
51
  [<Failure Code Number>, <Failure Code String>]
38
52
 
39
- // Return details
40
- @event 'details'
41
- {
42
- to: <Public Key Id Hex String>
43
- }
44
-
45
53
  // Received payment
46
54
  @event 'settled'
47
55
  {
@@ -57,18 +65,66 @@ const utf8AsHex = utf8 => Buffer.from(utf8).toString('hex');
57
65
  @returns
58
66
  <Service Event Emitter Object>
59
67
  */
60
- module.exports = ({description, lnd, secret, tokens}) => {
68
+ module.exports = args => {
69
+ if (!args.description) {
70
+ throw new Error('ExpectedTradeDescriptionToServiceTradeRequests');
71
+ }
72
+
73
+ if (!args.id) {
74
+ throw new Error('ExpectedTradeIdToServiceTradeRequests');
75
+ }
76
+
77
+ if (args.id.length !== hashHexLength) {
78
+ throw new Error('ExpectedShorterRequestIdValueToServiceTradeRequests');
79
+ }
80
+
81
+ if (!args.lnd) {
82
+ throw new Error('ExpectedAuthenticatedLndToServiceTradeRequests');
83
+ }
84
+
85
+ if (!args.secret) {
86
+ throw new Error('ExpectedSecretToEncryptToServiceTradeRequests');
87
+ }
88
+
89
+ if (!args.tokens) {
90
+ throw new Error('ExpectedTokensPriceForTradeToServiceTradeRequests');
91
+ }
92
+
61
93
  const emitter = new EventEmitter();
62
94
 
63
95
  const holds = [];
64
- const id = makeEphemeralTradeId();
65
- const service = servicePeerRequests({lnd});
96
+ const service = servicePeerRequests({lnd: args.lnd});
97
+
98
+ // Stop the service when there are no more listeners
99
+ emitter.on('removeListener', () => {
100
+ // Exit early when there are still listeners on an event
101
+ if (!!sumOf(events.map(n => emitter.listenerCount(n)))) {
102
+ return;
103
+ }
104
+
105
+ return service.stop({});
106
+ });
107
+
108
+ const sub = subscribeToInvoice({id: args.id, lnd: args.lnd});
109
+
110
+ sub.on('invoice_updated', invoice => {
111
+ if (!invoice.is_canceled) {
112
+ return;
113
+ }
114
+
115
+ holds.forEach(n => n.sub.removeAllListeners());
116
+
117
+ return service.stop({});
118
+ });
66
119
 
67
120
  // Notify when the service ends
68
121
  service.end(() => emitter.emit('end', {}));
69
122
 
70
123
  // Basic trade description
71
- const basicTradeRecords = [idRecord(id), descRecord(utf8AsHex(description))];
124
+ const basicTradeRecords = [
125
+ idRecord(args.id),
126
+ descRecord(utf8AsHex(args.description)),
127
+ ];
72
128
 
73
129
  // Wait for a request for the open trade
74
130
  service.request({type: serviceTypeRequestTrades}, (req, res) => {
@@ -76,7 +132,7 @@ module.exports = ({description, lnd, secret, tokens}) => {
76
132
  const requestTradeId = findIdRecord(req.records);
77
133
 
78
134
  // Exit early when the trade requested is unrelated
79
- if (!!requestTradeId && requestTradeId.value !== id) {
135
+ if (!!requestTradeId && requestTradeId.value !== args.id) {
80
136
  return;
81
137
  }
82
138
 
@@ -87,6 +143,13 @@ module.exports = ({description, lnd, secret, tokens}) => {
87
143
  return emitter.emit('failure', [400, 'ExpectedRequestTradeOrRequestId']);
88
144
  }
89
145
 
146
+ // Exit early when the trade is expired
147
+ if (args.expires_at < new Date().toISOString()) {
148
+ service.stop({});
149
+
150
+ return;
151
+ }
152
+
90
153
  // Exit early and ping peer with trade when this is an open ended query
91
154
  if (!requestTradeId) {
92
155
  emitter.emit('details', {to: req.from});
@@ -95,25 +158,31 @@ module.exports = ({description, lnd, secret, tokens}) => {
95
158
 
96
159
  const records = [].concat(basicTradeRecords).concat(reqIdRecord);
97
160
 
98
- // Ping the node with trade details
99
- return makePeerRequest({
100
- lnd,
101
- records,
102
- timeout: postOpenTradeTimeoutMs,
103
- to: req.from,
104
- type: serviceTypeReceiveTrades,
105
- },
106
- err => {
107
- if (!!err) {
108
- failure([503, 'FailedToDeliverTradeInfo']);
109
-
110
- return emitter.emit(
111
- 'failure',
112
- [503, 'FailedToDeliverTradeInfo', {err}]
113
- );
161
+ return getInvoice({id: args.id, lnd: args.lnd}, (err, res) => {
162
+ if (!!err || !!res.is_canceled) {
163
+ return;
114
164
  }
115
165
 
116
- return success({});
166
+ // Ping the node with trade details
167
+ return makePeerRequest({
168
+ records,
169
+ lnd: args.lnd,
170
+ timeout: postOpenTradeTimeoutMs,
171
+ to: req.from,
172
+ type: serviceTypeReceiveTrades,
173
+ },
174
+ err => {
175
+ if (!!err) {
176
+ failure([503, 'FailedToDeliverTradeInfo']);
177
+
178
+ return emitter.emit(
179
+ 'failure',
180
+ [503, 'FailedToDeliverTradeInfo', {err}]
181
+ );
182
+ }
183
+
184
+ return success({});
185
+ });
117
186
  });
118
187
  }
119
188
 
@@ -122,12 +191,13 @@ module.exports = ({description, lnd, secret, tokens}) => {
122
191
 
123
192
  // Create a trade secret for the requesting peer and return that
124
193
  return finalizeTradeSecret({
125
- description,
126
- lnd,
127
- secret,
128
- tokens,
194
+ description: args.description,
195
+ expires_at: args.expires_at,
129
196
  is_hold: true,
197
+ lnd: args.lnd,
198
+ secret: args.secret,
130
199
  to: req.from,
200
+ tokens: args.tokens,
131
201
  },
132
202
  (err, res) => {
133
203
  if (!!err) {
@@ -140,7 +210,7 @@ module.exports = ({description, lnd, secret, tokens}) => {
140
210
  const record = {type: tradeSecretType, value: res.trade};
141
211
 
142
212
  // Add hold invoice to set of held invoices, only one should settle
143
- const sub = subscribeToInvoice({lnd, id: res.id});
213
+ const sub = subscribeToInvoice({id: res.id, lnd: args.lnd});
144
214
 
145
215
  // Add sub to list of holds
146
216
  holds.push({sub, id: res.id});
@@ -160,8 +230,9 @@ module.exports = ({description, lnd, secret, tokens}) => {
160
230
 
161
231
  // Settle the held invoice with the preimage
162
232
  return acceptTrade({
163
- lnd,
164
233
  cancel: holds.filter(n => n.id !== updated.id).map(n => n.id),
234
+ id: args.id,
235
+ lnd: args.lnd,
165
236
  secret: res.secret,
166
237
  },
167
238
  err => {
@@ -0,0 +1,51 @@
1
+ const decodeAnchoredTrade = require('./decode_anchored_trade');
2
+
3
+ /** Derive open trade details from an invoice
4
+
5
+ {
6
+ description: <Invoice Description String>
7
+ id: <Invoice Id Hex String>
8
+ expires_at: <Invoice Expiration ISO 8601 Date String>
9
+ [is_canceled]: <Invoice is Canceled Bool>
10
+ is_confirmed: <Invoice is Confirmed Bool>
11
+ [is_push]: <Invoice is Push Payment Bool>
12
+ tokens: <Invoiced Amount Number>
13
+ }
14
+
15
+ @returns
16
+ {
17
+ [trade]: {
18
+ description: <Trade Description String>
19
+ expires_at: <Trade Expires at ISO 8601 Date String>
20
+ id: <Trade Id Hex String>
21
+ secret: <Secret Payload String>
22
+ tokens: <Tokens Number>
23
+ }
24
+ }
25
+ */
26
+ module.exports = args => {
27
+ if (!args.description || !!args.is_canceled || !!args.is_confirmed) {
28
+ return {};
29
+ }
30
+
31
+ if (!!args.is_push || !args.tokens) {
32
+ return {};
33
+ }
34
+
35
+ const {trade} = decodeAnchoredTrade({encoded: args.description});
36
+
37
+ // Exit early when the invoice doesn't match an anchored trade
38
+ if (!trade) {
39
+ return {};
40
+ }
41
+
42
+ return {
43
+ trade: {
44
+ description: trade.description,
45
+ expires_at: args.expires_at,
46
+ id: args.id,
47
+ secret: trade.secret,
48
+ tokens: args.tokens,
49
+ },
50
+ };
51
+ };