paid-services 3.15.2 → 3.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Versions
2
2
 
3
- ## Version 3.15.2
3
+ ## Version 3.16.0
4
+
5
+ - `manageSwap`: Add external funding, conf target setting, keysend pushes
6
+
7
+ ## Version 3.15.4
4
8
 
5
9
  - `manageSwap`: Add method to execute submarine swaps on testnet
6
10
 
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "ecpair": "2.0.1",
17
17
  "goldengate": "11.2.1",
18
18
  "invoices": "2.0.6",
19
- "ln-service": "53.14.1",
19
+ "ln-service": "53.15.0",
20
20
  "ln-sync": "3.12.0",
21
21
  "psbt": "2.0.1",
22
22
  "p2tr": "1.3.1",
@@ -47,5 +47,5 @@
47
47
  "integration-tests": "tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 180 test/integration/*.js",
48
48
  "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"
49
49
  },
50
- "version": "3.15.2"
50
+ "version": "3.16.0"
51
51
  }
@@ -6,6 +6,7 @@
6
6
  "serviceTypeReceiveChannelSale": "8050008",
7
7
  "serviceTypeRequestChannelSale": "8050007",
8
8
  "serviceTypeSignCapacityChange": "8050004",
9
+ "serviceTypeSwapResponse": "8050009",
9
10
  "serviceTypeWaitOnConfirmation": "8050003",
10
11
  "types": {
11
12
  "8050001": "serviceTypeChangeCapacity",
@@ -15,6 +16,7 @@
15
16
  "8050005": "serviceTypeRequestTrades",
16
17
  "8050006": "serviceTypeReceiveTrades",
17
18
  "8050007": "serviceTypeRequestChannelSale",
18
- "8050008": "serviceTypeReceiveChannelSale"
19
+ "8050008": "serviceTypeReceiveChannelSale",
20
+ "8050009": "serviceTypeSwapResponse"
19
21
  }
20
22
  }
@@ -81,6 +81,7 @@ const v1AddressWords = key => [].concat(1).concat(bech32m.toWords(key));
81
81
  {
82
82
  emitter: <Event Emitter Object>
83
83
  [is_avoiding_broadcast]: <Avoid Sweep Broadcast Bool>
84
+ [is_external_funding]: <Externally Fund Swap Bool>
84
85
  [is_uncooperative]: <Avoid Cooperative Signing Bool>
85
86
  lnd: <Autenticated LND API Object>
86
87
  max_fee_deposit: <Max Routing Fee Tokens For Deposit Number>
@@ -236,6 +237,12 @@ module.exports = (args, cbk) => {
236
237
  payToFund: ['responseDetails', asyncReflect(({responseDetails}, cbk) => {
237
238
  const request = responseDetails.request;
238
239
 
240
+ if (!!args.is_external_funding) {
241
+ args.emitter.emit('update', {pay_to_fund_swap_offchain: request});
242
+
243
+ return cbk();
244
+ }
245
+
239
246
  args.emitter.emit('update', {funding_swap_offchain: request});
240
247
 
241
248
  return pay({
@@ -906,9 +913,9 @@ module.exports = (args, cbk) => {
906
913
 
907
914
  return cbk(null, {
908
915
  paid_execution_fee: deposit.tokens,
909
- paid_execution_routing: deposit.fee,
916
+ paid_execution_routing: deposit.fee || undefined,
910
917
  swap_payment_sent: funding.tokens,
911
- swap_payment_routing_fee: funding.fee,
918
+ swap_payment_routing_fee: funding.fee || undefined,
912
919
  });
913
920
  }],
914
921
  },
@@ -68,9 +68,9 @@ const witnessLengthTimeoutSweep = 3;
68
68
  /** Complete the on-chain side of the swap by taking the off-chain funds
69
69
 
70
70
  {
71
+ [confirmation_target]: <Funding Confirmation Target Number>
71
72
  emitter: <Event Emitter Object>
72
73
  [is_ignoring_deposit]: <Ignore Held Deposit Funds Bool>
73
- [fund_fee_rate]: <Fund Chain Fee Rate Number>
74
74
  lnd: <Authenticated LND API Object>
75
75
  recovery: <Swap Recovery Hex String>
76
76
  [request]: <Request Function>
@@ -418,8 +418,7 @@ module.exports = (args, cbk) => {
418
418
  });
419
419
 
420
420
  return cbk(null, tx.toHex());
421
- },
422
- cbk);
421
+ });
423
422
  },
424
423
  cbk);
425
424
  }],
@@ -487,8 +486,8 @@ module.exports = (args, cbk) => {
487
486
 
488
487
  return fundPsbt({
489
488
  psbt,
490
- fee_tokens_per_vbyte: args.fund_fee_rate,
491
489
  lnd: args.lnd,
490
+ target_confirmations: args.confirmation_target,
492
491
  },
493
492
  cbk);
494
493
  }],
@@ -681,7 +680,7 @@ module.exports = (args, cbk) => {
681
680
  transaction,
682
681
  lnd: args.lnd,
683
682
  },
684
- err => {});
683
+ () => {});
685
684
  });
686
685
 
687
686
  subTimeout.on('error', err => done(err));
@@ -1,15 +1,32 @@
1
1
  const asyncAuto = require('async/auto');
2
- const {getNetwork} = require('ln-sync')
2
+ const asyncMap = require('async/map');
3
+ const {findKey} = require('ln-sync');
4
+ const {getMasterPublicKeys} = require('ln-service');
5
+ const {getAllInvoices} = require('ln-sync');
6
+ const {getInvoice} = require('ln-service');
7
+ const {getNetwork} = require('ln-sync');
8
+ const {getNodeAlias} = require('ln-sync');
3
9
  const {returnResult} = require('asyncjs-util');
4
10
 
11
+ const decodeOffToOnRequest = require('./decode_off_to_on_request');
12
+ const {decodeTrade} = require('./../trades');
5
13
  const recoverResponseToSwapOut = require('./recover_response_to_swap_out');
6
14
  const requestSwapOut = require('./request_swap_out');
7
15
  const respondToSwapOutRequest = require('./respond_to_swap_out_request');
8
16
 
17
+ const actionPushRequest = 'push-request';
9
18
  const allowedNetwork = 'btctestnet';
19
+ const bip86Path = `m/86'/0'/0'`;
20
+ const flatten = arr => [].concat(...arr);
21
+ const minConfirmations = {btc: 2, btcregtest: 3, btctestnet: 1};
22
+ const niceId = id => id.substring(0, 8);
23
+ const niceName = node => `${node.alias} ${node.id.substring(0, 8)}`.trim();
24
+ const pushesReceivedAfter = () => new Date(Date.now() - 1000 * 60 * 60 * 24);
10
25
  const recoverRespondAction = 'recover-response';
11
26
  const requestAction = 'request';
12
27
  const respondAction = 'respond';
28
+ const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
29
+ const typeKeySendTrade = '805805';
13
30
 
14
31
  /** Manage a swap
15
32
 
@@ -22,55 +39,174 @@ const respondAction = 'respond';
22
39
 
23
40
  @returns via cbk or Promise
24
41
  */
25
- module.exports = ({ask, lnd, logger, request}, cbk) => {
42
+ module.exports = (args, cbk) => {
26
43
  return new Promise((resolve, reject) => {
27
44
  return asyncAuto({
28
45
  // Check arguments
29
46
  validate: cbk => {
30
- if (!ask) {
47
+ if (!args.ask) {
31
48
  return cbk([400, 'ExpectedAskFunctionToManageSwap']);
32
49
  }
33
50
 
34
- if (!lnd) {
51
+ if (!args.lnd) {
35
52
  return cbk([400, 'ExpectedAuthenticatedLndToManageSwap']);
36
53
  }
37
54
 
38
- if (!logger) {
55
+ if (!args.logger) {
39
56
  return cbk([400, 'ExpectedWinstonLoggerToManageSwap']);
40
57
  }
41
58
 
42
- if (!request) {
59
+ if (!args.request) {
43
60
  return cbk([400, 'ExpectedRequestFunctionToManageSwap']);
44
61
  }
45
62
 
46
63
  return cbk();
47
64
  },
48
65
 
66
+ // Get invoices to find past swap requests
67
+ getInvoices: ['validate', ({}, cbk) => {
68
+ return getAllInvoices({
69
+ created_after: pushesReceivedAfter().toISOString(),
70
+ lnd: args.lnd,
71
+ },
72
+ cbk);
73
+ }],
74
+
75
+ // Look for a p2tr master public key to detect p2tr support
76
+ getMasterPublicKeys: ['validate', ({}, cbk) => {
77
+ return getMasterPublicKeys({lnd: args.lnd}, cbk);
78
+ }],
79
+
49
80
  // Get the network to make sure this is on testnet
50
- getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
81
+ getNetwork: ['validate', ({}, cbk) => getNetwork({lnd: args.lnd}, cbk)],
82
+
83
+ // Received swap pushes
84
+ swapPushes: ['getInvoices', ({getInvoices}, cbk) => {
85
+ const pushes = getInvoices.invoices.map(invoice => {
86
+ // A swap push is a KeySend payment
87
+ if (!invoice.is_confirmed || !invoice.is_push) {
88
+ return;
89
+ }
90
+
91
+ const messages = flatten(invoice.payments.map(n => n.messages));
92
+
93
+ // Convert messages to swaps
94
+ const swaps = messages.map(message => {
95
+ // Swaps have the trade type wrapper
96
+ if (message.type !== typeKeySendTrade) {
97
+ return;
98
+ }
99
+
100
+ // The trade type should be a swap
101
+ try {
102
+ const {swap} = decodeTrade({trade: message.value});
103
+
104
+ if (!swap) {
105
+ return;
106
+ }
107
+
108
+ const details = decodeOffToOnRequest({request: swap.request});
109
+
110
+ // Return relevant swap details for acceptance
111
+ return {
112
+ confirmed_at: invoice.confirmed_at,
113
+ id: details.hash,
114
+ tokens: details.tokens,
115
+ node: swap.node,
116
+ trade: message.value,
117
+ };
118
+ } catch (err) {
119
+ return;
120
+ }
121
+ });
122
+
123
+ const [swap] = swaps.filter(n => !!n);
124
+
125
+ return swap;
126
+ });
127
+
128
+ return cbk(null, pushes.filter(n => !!n));
129
+ }],
130
+
131
+ // Get node aliases for pushes
132
+ getAliases: ['swapPushes', ({swapPushes}, cbk) => {
133
+ return asyncMap(swapPushes, ({node}, cbk) => {
134
+ return getNodeAlias({id: node, lnd: args.lnd}, cbk);
135
+ },
136
+ cbk);
137
+ }],
138
+
139
+ // Get existing swaps
140
+ getExisting: ['swapPushes', ({swapPushes}, cbk) => {
141
+ return asyncMap(swapPushes, ({id}, cbk) => {
142
+ return getInvoice({id, lnd: args.lnd}, (err, res) => {
143
+ // Error indicates that the invoice could not be looked up
144
+ if (!!err) {
145
+ return cbk();
146
+ }
147
+
148
+ // Result indicates the invoice is known so the swap is in progress
149
+ return cbk(null, id);
150
+ });
151
+ },
152
+ cbk);
153
+ }],
154
+
155
+ // Determine if taproot is supported or not
156
+ hasTr: ['getMasterPublicKeys', ({getMasterPublicKeys}, cbk) => {
157
+ const {keys} = getMasterPublicKeys;
158
+
159
+ return cbk(null, !!keys.find(n => n.derivation_path === bip86Path));
160
+ }],
51
161
 
52
162
  // Select a swap action
53
- selectAction: ['getNetwork', ({getNetwork}, cbk) => {
163
+ selectAction: [
164
+ 'getAliases',
165
+ 'getExisting',
166
+ 'getNetwork',
167
+ 'hasTr',
168
+ 'swapPushes',
169
+ ({getAliases, getExisting, getNetwork, hasTr, swapPushes}, cbk) =>
170
+ {
54
171
  // Check to make sure the network is supported
55
- if (getNetwork.network !== allowedNetwork) {
172
+ if (!hasTr && getNetwork.network !== allowedNetwork) {
56
173
  return cbk([501, 'CurrentlyUnsupportedChainForSwap']);
57
174
  }
58
175
 
59
- return ask({
60
- choices: [
61
- {
62
- name: 'Make a new swap request',
63
- value: requestAction,
64
- },
65
- {
66
- name: 'Respond to a swap request',
67
- value: respondAction,
68
- },
69
- {
70
- name: 'Recovery for swap response',
71
- value: recoverRespondAction,
72
- },
73
- ],
176
+ const pushes = swapPushes
177
+ .filter(push => !getExisting.includes(push.id))
178
+ .map(push => {
179
+ const amount = tokensAsBigUnit(push.tokens);
180
+ const id = niceId(push.id);
181
+ const node = niceName(getAliases.find(n => n.id === push.node));
182
+
183
+ return {
184
+ name: `Accept request ${id} to swap ${amount} with ${node}`,
185
+ value: push.trade,
186
+ };
187
+ });
188
+
189
+ const choices = [
190
+ {
191
+ name: 'Make a new swap request',
192
+ value: requestAction,
193
+ },
194
+ {
195
+ name: 'Send a new swap request via KeySend',
196
+ value: actionPushRequest,
197
+ },
198
+ {
199
+ name: 'Respond to a swap request',
200
+ value: respondAction,
201
+ },
202
+ {
203
+ name: 'Recovery for swap response',
204
+ value: recoverRespondAction,
205
+ },
206
+ ];
207
+
208
+ return args.ask({
209
+ choices: choices.concat(pushes),
74
210
  message: 'Off-chain to on-chain swap:',
75
211
  name: 'action',
76
212
  type: 'list',
@@ -78,31 +214,146 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
78
214
  ({action}) => cbk(null, action));
79
215
  }],
80
216
 
217
+ // Ask for the remote to send to
218
+ askForRemote: ['selectAction', ({selectAction}, cbk) => {
219
+ // Exit early when the request is not KeySend
220
+ if (selectAction !== actionPushRequest) {
221
+ return cbk();
222
+ }
223
+
224
+ return args.ask({
225
+ message: 'Node key or channel peer alias to send request to?',
226
+ name: 'remote',
227
+ validate: input => !!input,
228
+ },
229
+ ({remote}) => cbk(null, remote));
230
+ }],
231
+
232
+ // Find the remote identity key when specified
233
+ findKey: ['askForRemote', ({askForRemote}, cbk) => {
234
+ if (!askForRemote) {
235
+ return cbk(null, {});
236
+ }
237
+
238
+ return findKey({lnd: args.lnd, query: askForRemote}, cbk);
239
+ }],
240
+
241
+ // Ask if the offchain funding should be external
242
+ askForExternal: [
243
+ 'askForRemote',
244
+ 'selectAction',
245
+ ({selectAction}, cbk) =>
246
+ {
247
+ switch (selectAction) {
248
+ case actionPushRequest:
249
+ case requestAction:
250
+ return args.ask({
251
+ message: 'Use internal funds to pay offchain payment request?',
252
+ name: 'internal',
253
+ type: 'confirm',
254
+ },
255
+ ({internal}) => cbk(null, !internal));
256
+
257
+ default:
258
+ // External funding not supported
259
+ return cbk();
260
+ }
261
+ }],
262
+
81
263
  // Make swap request
82
- makeRequest: ['selectAction', ({selectAction}, cbk) => {
83
- if (selectAction !== requestAction) {
264
+ makeRequest: [
265
+ 'askForExternal',
266
+ 'askForRemote',
267
+ 'findKey',
268
+ 'hasTr',
269
+ 'selectAction',
270
+ ({askForExternal, askForRemote, findKey, hasTr, selectAction}, cbk) =>
271
+ {
272
+ switch (selectAction) {
273
+ case actionPushRequest:
274
+ if (!findKey.public_key) {
275
+ return cbk([400, 'UnknownNodeToPushSwapRequestTo']);
276
+ }
277
+ break;
278
+
279
+ case requestAction:
280
+ break;
281
+
282
+ default:
84
283
  return cbk();
85
284
  }
86
285
 
87
- return requestSwapOut({ask, lnd, logger, request}, cbk);
286
+ return requestSwapOut({
287
+ ask: args.ask,
288
+ is_external_funding: askForExternal,
289
+ lnd: args.lnd,
290
+ logger: args.logger,
291
+ push_to: findKey.public_key,
292
+ min_confirmations: minConfirmations[getNetwork.network],
293
+ request: !hasTr ? args.request : undefined,
294
+ },
295
+ cbk);
88
296
  }],
89
297
 
90
- // Respond to swap request
91
- makeResponse: ['selectAction', ({selectAction}, cbk) => {
298
+ // Make a response for a swap request
299
+ makeResponse: ['hasTr', 'selectAction', ({hasTr, selectAction}, cbk) => {
92
300
  if (selectAction !== respondAction) {
93
301
  return cbk();
94
302
  }
95
303
 
96
- return respondToSwapOutRequest({ask, lnd, logger, request}, cbk);
304
+ return respondToSwapOutRequest({
305
+ ask: args.ask,
306
+ is_external_funding: true,
307
+ lnd: args.lnd,
308
+ logger: args.logger,
309
+ request: !hasTr ? args.request : undefined,
310
+ },
311
+ cbk);
312
+ }],
313
+
314
+ // Respond to a pushed swap request
315
+ respondToPush: [
316
+ 'hasTr',
317
+ 'selectAction',
318
+ ({hasTr, selectAction}, cbk) =>
319
+ {
320
+ // Exit early when not responding to a push
321
+ try {
322
+ decodeTrade({trade: selectAction});
323
+ } catch (err) {
324
+ return cbk();
325
+ }
326
+
327
+ const {swap} = decodeTrade({trade: selectAction});
328
+
329
+ return respondToSwapOutRequest({
330
+ ask: args.ask,
331
+ lnd: args.lnd,
332
+ logger: args.logger,
333
+ request: !hasTr ? args.request : undefined,
334
+ swap: swap.request,
335
+ to: swap.node,
336
+ },
337
+ cbk);
97
338
  }],
98
339
 
99
340
  // Recover responding to swap request
100
- recoverResponse: ['selectAction', ({selectAction}, cbk) => {
341
+ recoverResponse: [
342
+ 'hasTr',
343
+ 'selectAction',
344
+ ({hasTr, selectAction}, cbk) =>
345
+ {
101
346
  if (selectAction !== recoverRespondAction) {
102
347
  return cbk();
103
348
  }
104
349
 
105
- return recoverResponseToSwapOut({ask, lnd, logger, request}, cbk);
350
+ return recoverResponseToSwapOut({
351
+ ask: args.ask,
352
+ lnd: args.lnd,
353
+ logger: args.logger,
354
+ request: !hasTr ? args.request : undefined,
355
+ },
356
+ cbk);
106
357
  }],
107
358
 
108
359
  // Final result
@@ -110,9 +361,26 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
110
361
  'makeRequest',
111
362
  'makeResponse',
112
363
  'recoverResponse',
113
- ({makeRequest, makeResponse, recoverResponse}, cbk) =>
364
+ 'respondToPush',
365
+ ({makeRequest, makeResponse, recoverResponse, respondToPush}, cbk) =>
114
366
  {
115
- return cbk(null, makeRequest || makeResponse || recoverResponse);
367
+ if (!!makeRequest) {
368
+ return cbk(null, makeRequest);
369
+ }
370
+
371
+ if (!!makeResponse) {
372
+ return cbk(null, makeResponse);
373
+ }
374
+
375
+ if (!!recoverResponse) {
376
+ return cbk(null, recoverResponse);
377
+ }
378
+
379
+ if (!!respondToPush) {
380
+ return cbk(null, respondToPush);
381
+ }
382
+
383
+ return cbk();
116
384
  }],
117
385
  },
118
386
  returnResult({reject, resolve, of: 'result'}, cbk));
@@ -11,7 +11,7 @@ const completeOnToOffSwap = require('./complete_on_to_off_swap');
11
11
  ask: <Ask Function>
12
12
  lnd: <Authenticated LND API Object>
13
13
  logger: <Winston Logger Object>
14
- request: <Request Function>
14
+ [request]: <Request Function>
15
15
  }
16
16
 
17
17
  @returns via cbk or Promise
@@ -33,10 +33,6 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
33
33
  return cbk([400, 'ExpectedWinstonLoggerToRecoverResponseToSwapOut']);
34
34
  }
35
35
 
36
- if (!request) {
37
- return cbk([400, 'ExpectedRequestFunctionToRecoverResponseToSwap']);
38
- }
39
-
40
36
  return cbk();
41
37
  },
42
38