paid-services 3.15.3 → 3.16.1

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.3
3
+ ## Version 3.16.1
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
@@ -8,24 +8,24 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@alexbosworth/fiat": "1.0.2",
11
- "async": "3.2.3",
11
+ "async": "3.2.4",
12
12
  "asyncjs-util": "1.2.9",
13
13
  "bech32": "2.0.0",
14
- "bolt01": "1.2.4",
15
- "bolt07": "1.8.1",
14
+ "bolt01": "1.2.5",
15
+ "bolt07": "1.8.2",
16
16
  "ecpair": "2.0.1",
17
- "goldengate": "11.2.1",
18
- "invoices": "2.0.6",
19
- "ln-service": "53.14.1",
20
- "ln-sync": "3.12.0",
21
- "psbt": "2.0.1",
17
+ "goldengate": "11.2.2",
18
+ "invoices": "2.0.7",
19
+ "ln-service": "53.17.2",
20
+ "ln-sync": "3.12.1",
21
+ "psbt": "2.6.0",
22
22
  "p2tr": "1.3.1",
23
23
  "tiny-secp256k1": "2.2.1"
24
24
  },
25
25
  "description": "Lightning Paid Services library",
26
26
  "devDependencies": {
27
27
  "@alexbosworth/tap": "15.0.11",
28
- "ln-docker-daemons": "2.2.9",
28
+ "ln-docker-daemons": "2.3.0",
29
29
  "mock-lnd": "1.4.1",
30
30
  "secp256k1": "4.0.3"
31
31
  },
@@ -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.3"
50
+ "version": "3.16.1"
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>
@@ -486,8 +486,8 @@ module.exports = (args, cbk) => {
486
486
 
487
487
  return fundPsbt({
488
488
  psbt,
489
- fee_tokens_per_vbyte: args.fund_fee_rate,
490
489
  lnd: args.lnd,
490
+ target_confirmations: args.confirmation_target,
491
491
  },
492
492
  cbk);
493
493
  }],
@@ -1,17 +1,32 @@
1
1
  const asyncAuto = require('async/auto');
2
+ const asyncMap = require('async/map');
3
+ const {findKey} = require('ln-sync');
2
4
  const {getMasterPublicKeys} = require('ln-service');
5
+ const {getAllInvoices} = require('ln-sync');
6
+ const {getInvoice} = require('ln-service');
3
7
  const {getNetwork} = require('ln-sync');
8
+ const {getNodeAlias} = require('ln-sync');
4
9
  const {returnResult} = require('asyncjs-util');
5
10
 
11
+ const decodeOffToOnRequest = require('./decode_off_to_on_request');
12
+ const {decodeTrade} = require('./../trades');
6
13
  const recoverResponseToSwapOut = require('./recover_response_to_swap_out');
7
14
  const requestSwapOut = require('./request_swap_out');
8
15
  const respondToSwapOutRequest = require('./respond_to_swap_out_request');
9
16
 
17
+ const actionPushRequest = 'push-request';
10
18
  const allowedNetwork = 'btctestnet';
11
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);
12
25
  const recoverRespondAction = 'recover-response';
13
26
  const requestAction = 'request';
14
27
  const respondAction = 'respond';
28
+ const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
29
+ const typeKeySendTrade = '805805';
15
30
 
16
31
  /** Manage a swap
17
32
 
@@ -24,37 +39,118 @@ const respondAction = 'respond';
24
39
 
25
40
  @returns via cbk or Promise
26
41
  */
27
- module.exports = ({ask, lnd, logger, request}, cbk) => {
42
+ module.exports = (args, cbk) => {
28
43
  return new Promise((resolve, reject) => {
29
44
  return asyncAuto({
30
45
  // Check arguments
31
46
  validate: cbk => {
32
- if (!ask) {
47
+ if (!args.ask) {
33
48
  return cbk([400, 'ExpectedAskFunctionToManageSwap']);
34
49
  }
35
50
 
36
- if (!lnd) {
51
+ if (!args.lnd) {
37
52
  return cbk([400, 'ExpectedAuthenticatedLndToManageSwap']);
38
53
  }
39
54
 
40
- if (!logger) {
55
+ if (!args.logger) {
41
56
  return cbk([400, 'ExpectedWinstonLoggerToManageSwap']);
42
57
  }
43
58
 
44
- if (!request) {
59
+ if (!args.request) {
45
60
  return cbk([400, 'ExpectedRequestFunctionToManageSwap']);
46
61
  }
47
62
 
48
63
  return cbk();
49
64
  },
50
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
+
51
75
  // Look for a p2tr master public key to detect p2tr support
52
76
  getMasterPublicKeys: ['validate', ({}, cbk) => {
53
- return getMasterPublicKeys({lnd}, cbk);
77
+ return getMasterPublicKeys({lnd: args.lnd}, cbk);
54
78
  }],
55
79
 
56
80
  // Get the network to make sure this is on testnet
57
- 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
+ }],
58
154
 
59
155
  // Determine if taproot is supported or not
60
156
  hasTr: ['getMasterPublicKeys', ({getMasterPublicKeys}, cbk) => {
@@ -64,27 +160,53 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
64
160
  }],
65
161
 
66
162
  // Select a swap action
67
- selectAction: ['getNetwork', 'hasTr', ({getNetwork, hasTr}, cbk) => {
163
+ selectAction: [
164
+ 'getAliases',
165
+ 'getExisting',
166
+ 'getNetwork',
167
+ 'hasTr',
168
+ 'swapPushes',
169
+ ({getAliases, getExisting, getNetwork, hasTr, swapPushes}, cbk) =>
170
+ {
68
171
  // Check to make sure the network is supported
69
172
  if (!hasTr && getNetwork.network !== allowedNetwork) {
70
173
  return cbk([501, 'CurrentlyUnsupportedChainForSwap']);
71
174
  }
72
175
 
73
- return ask({
74
- choices: [
75
- {
76
- name: 'Make a new swap request',
77
- value: requestAction,
78
- },
79
- {
80
- name: 'Respond to a swap request',
81
- value: respondAction,
82
- },
83
- {
84
- name: 'Recovery for swap response',
85
- value: recoverRespondAction,
86
- },
87
- ],
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),
88
210
  message: 'Off-chain to on-chain swap:',
89
211
  name: 'action',
90
212
  type: 'list',
@@ -92,32 +214,125 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
92
214
  ({action}) => cbk(null, action));
93
215
  }],
94
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
+
95
263
  // Make swap request
96
- makeRequest: ['hasTr', 'selectAction', ({hasTr, selectAction}, cbk) => {
97
- 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:
98
283
  return cbk();
99
284
  }
100
285
 
101
286
  return requestSwapOut({
102
- ask,
103
- lnd,
104
- logger,
105
- request: !hasTr ? request : undefined,
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,
106
294
  },
107
295
  cbk);
108
296
  }],
109
297
 
110
- // Respond to swap request
298
+ // Make a response for a swap request
111
299
  makeResponse: ['hasTr', 'selectAction', ({hasTr, selectAction}, cbk) => {
112
300
  if (selectAction !== respondAction) {
113
301
  return cbk();
114
302
  }
115
303
 
116
304
  return respondToSwapOutRequest({
117
- ask,
118
- lnd,
119
- logger,
120
- request: !hasTr ? request : undefined,
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,
121
336
  },
122
337
  cbk);
123
338
  }],
@@ -133,10 +348,10 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
133
348
  }
134
349
 
135
350
  return recoverResponseToSwapOut({
136
- ask,
137
- lnd,
138
- logger,
139
- request: !hasTr ? request : undefined,
351
+ ask: args.ask,
352
+ lnd: args.lnd,
353
+ logger: args.logger,
354
+ request: !hasTr ? args.request : undefined,
140
355
  },
141
356
  cbk);
142
357
  }],
@@ -146,9 +361,26 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
146
361
  'makeRequest',
147
362
  'makeResponse',
148
363
  'recoverResponse',
149
- ({makeRequest, makeResponse, recoverResponse}, cbk) =>
364
+ 'respondToPush',
365
+ ({makeRequest, makeResponse, recoverResponse, respondToPush}, cbk) =>
150
366
  {
151
- 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();
152
384
  }],
153
385
  },
154
386
  returnResult({reject, resolve, of: 'result'}, cbk));
@@ -1,31 +1,55 @@
1
+ const {createHash} = require('crypto');
1
2
  const EventEmitter = require('events');
3
+ const {randomBytes} = require('crypto');
2
4
 
3
5
  const asyncAuto = require('async/auto');
6
+ const asyncReflect = require('async/reflect');
7
+ const {connectPeer} = require('ln-sync');
8
+ const {getIdentity} = require('ln-service');
4
9
  const {getNetwork} = require('ln-sync');
5
10
  const {parsePaymentRequest} = require('ln-service');
11
+ const {payViaPaymentDetails} = require('ln-service');
6
12
  const {returnResult} = require('asyncjs-util');
7
13
 
8
14
  const completeOffToOnSwap = require('./complete_off_to_on_swap');
9
15
  const decodeOffToOnResponse = require('./decode_off_to_on_response');
16
+ const {encodeTrade} = require('./../trades');
10
17
  const makeRequest = require('./start_off_to_on_swap');
18
+ const {pushTypes} = require('./swap_field_types');
19
+ const {servicePeerRequests} = require('./../p2p');
20
+ const serviceTypes = require('./../service_types');
11
21
 
22
+ const bufferAsHex = buffer => buffer.toString('hex');
12
23
  const defaultFeeRate = 5000;
13
24
  const defaultMaxFeeForDeposit = 1337;
14
25
  const defaultSwapAmount = 2500000;
26
+ const findRecord = (r, type) => (r.find(n => n.type === type) || {}).value;
27
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
15
28
  const isNumber = n => !isNaN(n) && !isNaN(parseFloat(n));
29
+ const makeSecret = () => randomBytes(32);
16
30
  const minAmount = 10000;
17
31
  const minRate = 0;
18
32
  const mtokensAsTokens = n => Number(BigInt(n) / BigInt(1e3));
33
+ const pushSwapRequestMaxFee = 10;
34
+ const pushSwapRequestTokens = 10;
19
35
  const rateDenominator = 1e6;
36
+ const rateForFee = (tokens, fee) => fee * 1e6 / tokens;
37
+ const sha256 = preimage => createHash('sha256').update(preimage).digest();
38
+ const typeKeySendPreimage = '5482373484';
39
+ const typeKeySendTrade = '805805';
40
+ const typeSwapResponse = serviceTypes.serviceTypeSwapResponse;
20
41
 
21
42
  /** Request a swap out
22
43
 
23
44
  {
24
45
  ask: <Ask Function>
25
46
  [is_avoiding_broadcast]: <Avoid Broadcasting Bool>
47
+ [is_external_funding]: <Externally Fund Swap Bool>
26
48
  [is_uncooperative]: <Avoid Cooperative Resolution Bool>
27
49
  lnd: <Authenticated LND API Object>
28
50
  logger: <Winston Logger Object>
51
+ [min_confirmations]: <Minimum Confirmations to Wait Number>
52
+ [push_to]: <Push Swap Request to Node with Identity Public Key Hex String>
29
53
  [request]: <Request Function>
30
54
  }
31
55
 
@@ -72,11 +96,36 @@ module.exports = (args, cbk) => {
72
96
  ({tokens}) => cbk(null, Number(tokens)));
73
97
  }],
74
98
 
75
- // Get the network name
99
+ // Attempt to connect to the push peer
100
+ connect: ['validate', asyncReflect(({}, cbk) => {
101
+ // Exit early when there is no push
102
+ if (!args.push_to) {
103
+ return cbk();
104
+ }
105
+
106
+ return connectPeer({id: args.push_to, lnd: args.lnd}, cbk);
107
+ })],
108
+
109
+ // Get the public key for node identity attachment
110
+ getIdentity: ['validate', ({}, cbk) => {
111
+ // Exit early when not pushing a request
112
+ if (!args.push_to) {
113
+ return cbk();
114
+ }
115
+
116
+ return getIdentity({lnd: args.lnd}, cbk);
117
+ }],
118
+
119
+ // Get the network name to decode the encoded swap payment request
76
120
  getNetwork: ['validate', ({}, cbk) => getNetwork({lnd: args.lnd}, cbk)],
77
121
 
78
122
  // Ask for routing fee rate
79
123
  askForRate: ['askForTokens', ({}, cbk) => {
124
+ // Exit earlyw hen there is no internal routing
125
+ if (!!args.is_external_funding) {
126
+ return cbk();
127
+ }
128
+
80
129
  return args.ask({
81
130
  default: defaultFeeRate,
82
131
  message: 'Max routing fee rate for swap funds in parts per million?',
@@ -96,6 +145,32 @@ module.exports = (args, cbk) => {
96
145
  ({rate}) => cbk(null, Number(rate)));
97
146
  }],
98
147
 
148
+ // Ask for the max swap fee rate that will be paid automatically
149
+ askForSwapRate: ['askForRate', ({}, cbk) => {
150
+ // Exit early when not pushing a swap request
151
+ if (!args.push_to) {
152
+ return cbk();
153
+ }
154
+
155
+ return args.ask({
156
+ default: defaultFeeRate,
157
+ message: 'Max auto-accept fee rate for funds in parts per million?',
158
+ name: 'max',
159
+ validate: input => {
160
+ if (!isNumber(input)) {
161
+ return false;
162
+ }
163
+
164
+ if (Number(input) < minRate) {
165
+ return `A larger rate is required, minimum: ${minRate}`;
166
+ }
167
+
168
+ return true;
169
+ },
170
+ },
171
+ ({max}) => cbk(null, Number(max)));
172
+ }],
173
+
99
174
  // Make a swap request
100
175
  makeRequest: ['askForTokens', ({askForTokens}, cbk) => {
101
176
  return makeRequest({
@@ -106,13 +181,118 @@ module.exports = (args, cbk) => {
106
181
  cbk);
107
182
  }],
108
183
 
184
+ // Listen on p2p for a response to the swap request
185
+ listenForResponse: [
186
+ 'getNetwork',
187
+ 'makeRequest',
188
+ ({getNetwork, makeRequest}, cbk) =>
189
+ {
190
+ // Exit early when not pushing a request
191
+ if (!args.push_to) {
192
+ return cbk();
193
+ }
194
+
195
+ const service = servicePeerRequests({lnd: args.lnd});
196
+
197
+ // The identifier of the swap is the hash over the swap request
198
+ const id = bufferAsHex(sha256(hexAsBuffer(makeRequest.request)));
199
+
200
+ // Listen for an accepted swap request
201
+ service.request({type: typeSwapResponse}, (req, res) => {
202
+ // Exit early when the request is from a different peer
203
+ if (req.from !== args.push_to) {
204
+ return;
205
+ }
206
+
207
+ // Exit early when the request is for a different swap
208
+ if (findRecord(req.records, pushTypes.typeSwapId) !== id) {
209
+ return;
210
+ }
211
+
212
+ // The peer will message the response to the swap in a record
213
+ const response = findRecord(req.records, pushTypes.typeSwapResponse);
214
+
215
+ // Check the swap response is valid
216
+ try {
217
+ decodeOffToOnResponse({response, network: getNetwork.bitcoinjs});
218
+ } catch (err) {
219
+ return res.failure([400, err.message]);
220
+ }
221
+
222
+ args.logger.info({got_response: true});
223
+
224
+ // Tell the peer that the swap response was received
225
+ res.success({});
226
+
227
+ // Don't listen for additional results
228
+ service.stop({});
229
+
230
+ return cbk(null, response);
231
+ });
232
+ }],
233
+
234
+ // Push the request to the destination
235
+ pushRequest: [
236
+ 'askForSwapRate',
237
+ 'getIdentity',
238
+ 'makeRequest',
239
+ ({getIdentity, makeRequest}, cbk) =>
240
+ {
241
+ // Exit early when this is not a push request
242
+ if (!args.push_to) {
243
+ return cbk();
244
+ }
245
+
246
+ const secret = makeSecret();
247
+
248
+ const id = bufferAsHex(sha256(secret));
249
+
250
+ // Include local peer details so the node can p2p message the response
251
+ const {trade} = encodeTrade({
252
+ swap: {node: getIdentity.public_key, request: makeRequest.request},
253
+ });
254
+
255
+ args.logger.info({
256
+ sending_swap_request_to: args.push_to,
257
+ push_request_id: id,
258
+ });
259
+
260
+ // Push the swap request
261
+ return payViaPaymentDetails({
262
+ id,
263
+ destination: args.push_to,
264
+ lnd: args.lnd,
265
+ max_fee: pushSwapRequestMaxFee,
266
+ messages: [
267
+ {type: typeKeySendPreimage, value: bufferAsHex(secret)},
268
+ {type: typeKeySendTrade, value: trade},
269
+ ],
270
+ tokens: pushSwapRequestTokens,
271
+ },
272
+ (err, res) => {
273
+ if (!!err) {
274
+ return cbk(err);
275
+ }
276
+
277
+ args.logger.info({sent_swap_request_waiting_for_response: true});
278
+
279
+ return cbk();
280
+ });
281
+ }],
282
+
109
283
  // Wait for a response
110
284
  getResponse: [
111
285
  'askForRate',
112
286
  'getNetwork',
287
+ 'listenForResponse',
113
288
  'makeRequest',
114
- ({getNetwork, makeRequest}, cbk) =>
289
+ ({getNetwork, listenForResponse, makeRequest}, cbk) =>
115
290
  {
291
+ // Exit early when there is a p2p request
292
+ if (!!listenForResponse) {
293
+ return cbk(null, listenForResponse);
294
+ }
295
+
116
296
  args.logger.info({swap_request: makeRequest.request});
117
297
 
118
298
  return args.ask({
@@ -137,10 +317,11 @@ module.exports = (args, cbk) => {
137
317
 
138
318
  // Confirm the swap details
139
319
  okSwap: [
320
+ 'askForSwapRate',
140
321
  'askForTokens',
141
322
  'getNetwork',
142
323
  'getResponse',
143
- ({askForTokens, getNetwork, getResponse}, cbk) =>
324
+ ({askForSwapRate, askForTokens, getNetwork, getResponse}, cbk) =>
144
325
  {
145
326
  const response = decodeOffToOnResponse({
146
327
  network: getNetwork.bitcoinjs,
@@ -154,6 +335,11 @@ module.exports = (args, cbk) => {
154
335
  const fee = tokens - askForTokens;
155
336
  const pricing = `Execution cost ${deposit}, plus liquidity fee ${fee}`;
156
337
 
338
+ // Exit early when swap fee is below the pre-specified tolerance
339
+ if (rateForFee(askForTokens, deposit + fee) <= askForSwapRate) {
340
+ return cbk(null, true)
341
+ }
342
+
157
343
  return args.ask({
158
344
  default: true,
159
345
  message: `Start swap ${timeout}? ${pricing}?`,
@@ -183,10 +369,12 @@ module.exports = (args, cbk) => {
183
369
  return completeOffToOnSwap({
184
370
  emitter,
185
371
  is_avoiding_broadcast: args.is_avoiding_broadcast,
372
+ is_external_funding: args.is_external_funding,
186
373
  is_uncooperative: args.is_uncooperative,
187
374
  lnd: args.lnd,
188
375
  max_fee_deposit: defaultMaxFeeForDeposit,
189
376
  max_fee_funding: askForTokens * askForRate / rateDenominator,
377
+ min_confirmations: args.min_confirmations,
190
378
  recovery: makeRequest.recovery,
191
379
  request: args.request,
192
380
  response: getResponse,
@@ -1,3 +1,4 @@
1
+ const {createHash} = require('crypto');
1
2
  const EventEmitter = require('events');
2
3
 
3
4
  const asyncAuto = require('async/auto');
@@ -5,17 +6,30 @@ const {getChainFeeRate} = require('ln-service');
5
6
  const {returnResult} = require('asyncjs-util');
6
7
 
7
8
  const completeOnToOffSwap = require('./complete_on_to_off_swap');
9
+ const {connectPeer} = require('ln-sync');
8
10
  const decodeOffToOnRequest = require('./decode_off_to_on_request');
11
+ const {makePeerRequest} = require('./../p2p');
12
+ const serviceTypes = require('./../service_types');
9
13
  const startOnToOffSwap = require('./start_on_to_off_swap');
10
14
 
15
+ const bufferAsHex = buffer => buffer.toString('hex');
11
16
  const {ceil} = Math;
17
+ const defaultConfirmationTarget = 6;
12
18
  const {floor} = Math;
13
19
  const defaultCltvDelta = 400;
14
- const defaultFeeRate = 5000;
20
+ const defaultFeeRate = 2500;
15
21
  const estimatedVirtualSize = 300;
22
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
16
23
  const isNumber = n => !isNaN(n) && !isNaN(parseFloat(n));
24
+ const maxTarget = 144;
17
25
  const minRate = 1;
26
+ const minTarget = 2;
27
+ const peerRequestTimeoutMs = 1000 * 60 * 3;
18
28
  const rateDenominator = 1e6;
29
+ const sha256 = preimage => createHash('sha256').update(preimage).digest();
30
+ const typeSwapId = '0';
31
+ const typeSwapReply = '1';
32
+ const typeSwapResponse = serviceTypes.serviceTypeSwapResponse;
19
33
 
20
34
  /** Respond to a swap out request
21
35
 
@@ -24,11 +38,13 @@ const rateDenominator = 1e6;
24
38
  lnd: <Authenticated LND API Object>
25
39
  logger: <Winston Logger Object>
26
40
  [request]: <Request Function>
41
+ [swap]: <Swap Hex String>
42
+ [to]: <Send Swap Response To Public Key Id Hex String>
27
43
  }
28
44
 
29
45
  @returns via cbk or Promise
30
46
  */
31
- module.exports = ({ask, lnd, logger, request}, cbk) => {
47
+ module.exports = ({ask, lnd, logger, request, swap, to}, cbk) => {
32
48
  return new Promise((resolve, reject) => {
33
49
  return asyncAuto({
34
50
  // Check arguments
@@ -45,11 +61,24 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
45
61
  return cbk([400, 'ExpectedLoggerToRespondToSwapOutRequest']);
46
62
  }
47
63
 
64
+ if (!!swap) {
65
+ try {
66
+ decodeOffToOnRequest({request: swap});
67
+ } catch (err) {
68
+ return cbk([400, err.message]);
69
+ }
70
+ }
71
+
48
72
  return cbk();
49
73
  },
50
74
 
51
75
  // Ask for a request
52
76
  askForRequest: ['validate', ({}, cbk) => {
77
+ // Exit early when a swap request is defined
78
+ if (!!swap) {
79
+ return cbk(null, swap);
80
+ }
81
+
53
82
  return ask({
54
83
  message: 'Swap request?',
55
84
  name: 'req',
@@ -70,8 +99,14 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
70
99
  ({req}) => cbk(null, req));
71
100
  }],
72
101
 
73
- // Get the chain fee rate
74
- getRate: ['validate', ({}, cbk) => getChainFeeRate({lnd}, cbk)],
102
+ // Connect to the peer when a swap is pushed
103
+ connect: ['validate', ({}, cbk) => {
104
+ if (!to) {
105
+ return cbk();
106
+ }
107
+
108
+ return connectPeer({lnd, id: to}, cbk);
109
+ }],
75
110
 
76
111
  // Ask for pricing
77
112
  askForRate: ['askForRequest', ({askForRequest}, cbk) => {
@@ -98,6 +133,36 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
98
133
  ({rate}) => cbk(null, Number(rate)));
99
134
  }],
100
135
 
136
+ // Ask for chain fee target
137
+ askForTarget: ['askForRate', ({}, cbk) => {
138
+ return ask({
139
+ default: defaultConfirmationTarget,
140
+ message: 'Confirm on-chain funding within how many blocks?',
141
+ name: 'target',
142
+ validate: input => {
143
+ if (!isNumber(input)) {
144
+ return false;
145
+ }
146
+
147
+ if (Number(input) > maxTarget) {
148
+ return `A smaller number is required, maximum: ${maxTarget}`;
149
+ }
150
+
151
+ if (Number(input) < minTarget) {
152
+ return `A larger number is required, minimum: ${minTarget}`;
153
+ }
154
+
155
+ return true;
156
+ },
157
+ },
158
+ ({target}) => cbk(null, Number(target)));
159
+ }],
160
+
161
+ // Get the chain fee rate
162
+ getRate: ['askForTarget', ({askForTarget}, cbk) => {
163
+ return getChainFeeRate({lnd, confirmation_target: askForTarget}, cbk);
164
+ }],
165
+
101
166
  // Make a response
102
167
  makeResponse: [
103
168
  'askForRate',
@@ -119,10 +184,14 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
119
184
  }],
120
185
 
121
186
  // Complete the swap
122
- completeSwap: ['makeResponse', ({makeResponse}, cbk) => {
187
+ completeSwap: [
188
+ 'askForTarget',
189
+ 'makeResponse',
190
+ ({askForTarget, makeResponse}, cbk) =>
191
+ {
123
192
  logger.info({
124
193
  recovery: makeResponse.recovery,
125
- response: makeResponse.response,
194
+ response: !to ? makeResponse.response : undefined,
126
195
  });
127
196
 
128
197
  const emitter = new EventEmitter();
@@ -133,10 +202,31 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
133
202
  emitter,
134
203
  lnd,
135
204
  request,
205
+ confirmation_target: askForTarget,
136
206
  recovery: makeResponse.recovery,
137
207
  },
138
208
  cbk);
139
209
  }],
210
+
211
+ // Send swap response over p2p so the peer can start the payment
212
+ sendResponse: ['connect', 'makeResponse', ({makeResponse}, cbk) => {
213
+ // Exit early when a swap response destination is not defined
214
+ if (!swap || !to) {
215
+ return cbk();
216
+ }
217
+
218
+ return makePeerRequest({
219
+ lnd,
220
+ to,
221
+ records: [
222
+ {type: typeSwapId, value: bufferAsHex(sha256(hexAsBuffer(swap)))},
223
+ {type: typeSwapReply, value: makeResponse.response},
224
+ ],
225
+ timeout: peerRequestTimeoutMs,
226
+ type: typeSwapResponse,
227
+ },
228
+ cbk);
229
+ }],
140
230
  },
141
231
  returnResult({reject, resolve, of: 'completeSwap'}, cbk));
142
232
  });
@@ -23,5 +23,9 @@
23
23
  "typeTokens": "13",
24
24
  "typeVersion": "0"
25
25
  },
26
+ "pushTypes": {
27
+ "typeSwapId": "0",
28
+ "typeSwapResponse": "1"
29
+ },
26
30
  "typePayMetadata": "805001"
27
31
  }
@@ -99,8 +99,8 @@ test(`Start offchain swap`, async ({end, equal, strictSame}) => {
99
99
  if (args.name === 'response') {
100
100
  return await respondToSwapOut({
101
101
  ask: (args, cbk) => {
102
- if (args.name === 'rate') {
103
- return cbk({rate: '100'});
102
+ if (args.default) {
103
+ return cbk({[args.name]: args.default});
104
104
  }
105
105
 
106
106
  if (args.name === 'req') {
@@ -140,6 +140,7 @@ test(`Start offchain swap`, async ({end, equal, strictSame}) => {
140
140
  return messages.push(message);
141
141
  },
142
142
  },
143
+ min_confirmations: 3,
143
144
  });
144
145
  } catch (err) {
145
146
  strictSame(err, null, 'Expected no failure');
@@ -99,8 +99,8 @@ test(`Swap with claim path`, async ({end, equal, strictSame}) => {
99
99
  if (args.name === 'response') {
100
100
  return await respondToSwapOut({
101
101
  ask: (args, cbk) => {
102
- if (args.name === 'rate') {
103
- return cbk({rate: '100'});
102
+ if (args.default) {
103
+ return cbk({[args.name]: args.default});
104
104
  }
105
105
 
106
106
  if (args.name === 'req') {
@@ -100,8 +100,8 @@ test(`Timeout a swap`, async ({end, equal, strictSame}) => {
100
100
  try {
101
101
  return await respondToSwapOut({
102
102
  ask: (args, cbk) => {
103
- if (args.name === 'rate') {
104
- return cbk({rate: '100'});
103
+ if (args.default) {
104
+ return cbk({[args.name]: args.default});
105
105
  }
106
106
 
107
107
  if (args.name === 'req') {
@@ -5,13 +5,8 @@ const method = require('./../../trades/encode_trade');
5
5
  const tests = [
6
6
  {
7
7
  args: {},
8
- description: 'Either connect or secret is required',
9
- error: 'ExpectedEitherConnectDetailsOrTradeSecret',
10
- },
11
- {
12
- args: {connect: true, secret: true},
13
- description: 'Cannot encode both connect and secret',
14
- error: 'ExpectedOnlyOneConnectDetailsOrTradeSecretNotBoth',
8
+ description: 'Trade details are required',
9
+ error: 'ExpectedTradeDetailsToEncode',
15
10
  },
16
11
  {
17
12
  args: {
@@ -0,0 +1,55 @@
1
+ const {decodeTlvStream} = require('bolt01');
2
+
3
+ const recordAsNode = require('./record_as_node');
4
+
5
+ const findSwapRecord = records => records.find(n => n.type === '6');
6
+ const {isArray} = Array;
7
+ const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
8
+ const nodeForSwap = record => record.substring(0, 66);
9
+ const requestForSwap = record => record.substring(66);
10
+
11
+ /** Decode swap trade records
12
+
13
+ 6: <Swap Request>
14
+
15
+ {
16
+ records: [{
17
+ type: <Type Number String>
18
+ value: <Value Hex Encoded String>
19
+ }]
20
+ }
21
+
22
+ @throws
23
+ <Error>
24
+
25
+ @returns
26
+ {
27
+ node: <Node Public Key Id Hex String>
28
+ request: <Swap Request Hex String>
29
+ }
30
+ */
31
+ module.exports = ({records}) => {
32
+ if (!isArray(records)) {
33
+ throw new Error('ExpectedArrayOfRecordsToDecodeSwapTrade');
34
+ }
35
+
36
+ const swapRecord = findSwapRecord(records);
37
+
38
+ if (!swapRecord) {
39
+ throw new Error('ExpectedSwapRecordInSwapTrade');
40
+ }
41
+
42
+ const node = nodeForSwap(swapRecord.value)
43
+
44
+ if (!isPublicKey(node)) {
45
+ throw new Error('ExpectedIdentityPublicKeyInSwapTrade');
46
+ }
47
+
48
+ const request = requestForSwap(swapRecord.value);
49
+
50
+ if (!request) {
51
+ throw new Error('ExpectedSwapRequestInSwapTrade');
52
+ }
53
+
54
+ return {node, request};
55
+ };
@@ -2,6 +2,7 @@ const {decodeBigSize} = require('bolt01');
2
2
  const {decodeTlvStream} = require('bolt01');
3
3
 
4
4
  const decodeOpenTrade = require('./decode_open_trade');
5
+ const decodeSwapTrade = require('./decode_swap_trade');
5
6
  const decodeTradePayment = require('./decode_trade_payment');
6
7
  const decodeTradeSecret = require('./decode_trade_secret');
7
8
  const networkFromNetworkRecord = require('./network_from_network_record');
@@ -10,8 +11,9 @@ const findDetailsRecord = records => records.find(n => n.type === '3');
10
11
  const findNetwork = records => records.find(n => n.type === '1');
11
12
  const findVer = records => records.find(n => n.type === '0') || {value: '00'};
12
13
  const findRequestRecord = records => records.find(n => n.type === '2');
14
+ const findSwapRecord = records => records.find(n => n.type === '6');
13
15
  const isTrade = trade => trade.toLowerCase().startsWith('626f73ff');
14
- const isValidVersion = version => BigInt(version) <= BigInt(1);
16
+ const isValidVersion = version => BigInt(version) <= BigInt(2);
15
17
  const tradeData = trade => trade.slice('626f73ff'.length);
16
18
 
17
19
  /** Decode a trade record
@@ -22,6 +24,7 @@ const tradeData = trade => trade.slice('626f73ff'.length);
22
24
  [3]: <Trade Details Record
23
25
  [4]: <Nodes Records>
24
26
  [5]: <Trade Identifier>
27
+ [6]: <Swap Request>
25
28
 
26
29
  {
27
30
  trade: <Trade Record Hex String>
@@ -49,6 +52,10 @@ const tradeData = trade => trade.slice('626f73ff'.length);
49
52
  payload: <Preimage Encrypted Payload Hex String>
50
53
  request: <BOLT 11 Payment Request String>
51
54
  }
55
+ [swap]: {
56
+ node: <Node Public Key Id Hex String>
57
+ request: <Swap Request Hex String>
58
+ }
52
59
  }
53
60
  */
54
61
  module.exports = ({trade}) => {
@@ -98,5 +105,10 @@ module.exports = ({trade}) => {
98
105
  return {payment: decodeTradePayment({network, records})};
99
106
  }
100
107
 
108
+ // Exit early when there is a swap encoded
109
+ if (!!findSwapRecord(records)) {
110
+ return {swap: decodeSwapTrade({records})};
111
+ }
112
+
101
113
  return {connect: decodeOpenTrade({network, records})};
102
114
  };
@@ -0,0 +1,38 @@
1
+ const {encodeTlvStream} = require('bolt01');
2
+
3
+ const networkRecordFromNetwork = require('./network_record_from_network');
4
+ const nodeAsRecord = require('./node_as_record');
5
+
6
+ const asTrade = hex => `626f73ff${hex}`;
7
+ const typeSwap = '6';
8
+ const typeVersion = '0';
9
+ const versionRecord = '02';
10
+
11
+ /** Encode records for a swap trade request
12
+
13
+ A swap trade request
14
+
15
+ 0: version (v2)
16
+ 6: swap request
17
+
18
+ {
19
+ node: <Swap Node Identity Public Key Hex String>
20
+ request: <Swap Request Hex String>
21
+ }
22
+
23
+ @throws
24
+ <Error>
25
+
26
+ @returns
27
+ {
28
+ trade: <Encoded Swap Trade Request Hex String>
29
+ }
30
+ */
31
+ module.exports = ({node, request}) => {
32
+ const records = [
33
+ {type: typeSwap, value: node + request},
34
+ {type: typeVersion, value: versionRecord},
35
+ ];
36
+
37
+ return {trade: asTrade(encodeTlvStream({records}).encoded)};
38
+ };
@@ -1,4 +1,5 @@
1
1
  const encodeOpenTrade = require('./encode_open_trade');
2
+ const encodeSwapTrade = require('./encode_swap_trade');
2
3
  const encodeTradeSecret = require('./encode_trade_secret');
3
4
 
4
5
  /** Encode v1 trade records
@@ -12,6 +13,7 @@ const encodeTradeSecret = require('./encode_trade_secret');
12
13
  [3]: trade details
13
14
  [4]: nodes records
14
15
  [5]: trade identifier
16
+ [6]: swap request
15
17
 
16
18
  {
17
19
  [connect]: {
@@ -28,6 +30,10 @@ const encodeTradeSecret = require('./encode_trade_secret');
28
30
  payload: <Preimage Encrypted Payload Hex String>
29
31
  request: <BOLT 11 Payment Request String>
30
32
  }
33
+ [swap]: {
34
+ node: <Node Public Key Id Hex String>
35
+ request: <Swap Request Hex String>
36
+ }
31
37
  }
32
38
 
33
39
  @throws
@@ -38,16 +44,11 @@ const encodeTradeSecret = require('./encode_trade_secret');
38
44
  trade: <Hex Encoded Trade String>
39
45
  }
40
46
  */
41
- module.exports = ({connect, secret}) => {
42
- if (!connect && !secret) {
43
- throw new Error('ExpectedEitherConnectDetailsOrTradeSecret');
44
- }
45
-
46
- if (!!connect && !!secret) {
47
- throw new Error('ExpectedOnlyOneConnectDetailsOrTradeSecretNotBoth');
47
+ module.exports = ({connect, secret, swap}) => {
48
+ if (!connect && !secret && !swap) {
49
+ throw new Error('ExpectedTradeDetailsToEncode');
48
50
  }
49
51
 
50
- // Exit early when this is an open ended trade
51
52
  if (!!connect) {
52
53
  return encodeOpenTrade({
53
54
  id: connect.id,
@@ -56,10 +57,17 @@ module.exports = ({connect, secret}) => {
56
57
  });
57
58
  }
58
59
 
59
- // Encode the trade secret
60
- return encodeTradeSecret({
61
- auth: secret.auth,
62
- payload: secret.payload,
63
- request: secret.request,
64
- });
60
+ if (!!secret) {
61
+ return encodeTradeSecret({
62
+ auth: secret.auth,
63
+ payload: secret.payload,
64
+ request: secret.request,
65
+ });
66
+ }
67
+
68
+ if (!!swap) {
69
+ return encodeSwapTrade({node: swap.node, request: swap.request});
70
+ }
71
+
72
+ throw new Error('ExpectedTradeTypeToEncode');
65
73
  };