paid-services 3.18.0 → 3.19.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 +5 -0
- package/package.json +4 -4
- package/swaps/complete_off_to_on_swap.js +17 -2
- package/swaps/complete_on_to_off_swap.js +65 -3
- package/swaps/decode_off_to_on_response.js +23 -1
- package/swaps/decode_on_to_off_recovery.js +19 -2
- package/swaps/encode_off_to_on_response.js +22 -2
- package/swaps/manage_swap.js +48 -1
- package/swaps/request_swap_out.js +6 -1
- package/swaps/respond_to_swap_out_request.js +26 -1
- package/swaps/start_on_to_off_swap.js +2 -0
- package/swaps/swap_field_types.json +3 -1
- package/test/integration/test_request_swap_out.js +4 -0
- package/test/integration/test_swap_with_claim_path.js +4 -0
- package/test/integration/test_swap_with_refund_path.js +4 -0
- package/trades/connect_to_seller.js +57 -4
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"ecpair": "2.0.1",
|
|
17
17
|
"goldengate": "11.2.3",
|
|
18
18
|
"invoices": "2.1.0",
|
|
19
|
-
"ln-service": "53.17.
|
|
20
|
-
"ln-sync": "3.13.
|
|
21
|
-
"psbt": "2.7.
|
|
19
|
+
"ln-service": "53.17.5",
|
|
20
|
+
"ln-sync": "3.13.1",
|
|
21
|
+
"psbt": "2.7.1",
|
|
22
22
|
"p2tr": "1.3.1",
|
|
23
23
|
"tiny-secp256k1": "2.2.1"
|
|
24
24
|
},
|
|
@@ -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.
|
|
50
|
+
"version": "3.19.2"
|
|
51
51
|
}
|
|
@@ -90,6 +90,7 @@ const v1AddressWords = key => [].concat(1).concat(bech32m.toWords(key));
|
|
|
90
90
|
recovery: <Swap Request Recovery Hex String>
|
|
91
91
|
[request]: <Request Function>
|
|
92
92
|
response: <Swap Response Hex String>
|
|
93
|
+
[sweep_address]: <Sweep Chain Address String>
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
@returns via cbk or Promise
|
|
@@ -134,6 +135,11 @@ module.exports = (args, cbk) => {
|
|
|
134
135
|
|
|
135
136
|
// Create a sweep address
|
|
136
137
|
createAddress: ['validate', ({}, cbk) => {
|
|
138
|
+
// Exit early when there is no need to create a sweep address
|
|
139
|
+
if (!!args.sweep_address) {
|
|
140
|
+
return cbk(null, {address: args.sweep_address});
|
|
141
|
+
}
|
|
142
|
+
|
|
137
143
|
return createChainAddress({lnd: args.lnd}, cbk);
|
|
138
144
|
}],
|
|
139
145
|
|
|
@@ -204,6 +210,7 @@ module.exports = (args, cbk) => {
|
|
|
204
210
|
coop_public_key: decoded.coop_public_key,
|
|
205
211
|
deposit_mtokens: decoded.deposit_mtokens,
|
|
206
212
|
deposit_payment: decoded.deposit_payment,
|
|
213
|
+
incoming_peer: decoded.incoming_peer,
|
|
207
214
|
push: decoded.push,
|
|
208
215
|
refund_public_key: decoded.refund_public_key,
|
|
209
216
|
request: decoded.request,
|
|
@@ -238,7 +245,10 @@ module.exports = (args, cbk) => {
|
|
|
238
245
|
const request = responseDetails.request;
|
|
239
246
|
|
|
240
247
|
if (!!args.is_external_funding) {
|
|
241
|
-
args.emitter.emit('update', {
|
|
248
|
+
args.emitter.emit('update', {
|
|
249
|
+
external_funding_pay_to_fund_swap_offchain: request,
|
|
250
|
+
must_be_in_through: responseDetails.incoming_peer || undefined,
|
|
251
|
+
});
|
|
242
252
|
|
|
243
253
|
return cbk();
|
|
244
254
|
}
|
|
@@ -247,6 +257,7 @@ module.exports = (args, cbk) => {
|
|
|
247
257
|
|
|
248
258
|
return pay({
|
|
249
259
|
request,
|
|
260
|
+
incoming_peer: responseDetails.incoming_peer || undefined,
|
|
250
261
|
lnd: args.lnd,
|
|
251
262
|
max_fee: args.max_fee_funding,
|
|
252
263
|
},
|
|
@@ -381,6 +392,7 @@ module.exports = (args, cbk) => {
|
|
|
381
392
|
cltv_delta: to.cltv_delta,
|
|
382
393
|
destination: to.destination,
|
|
383
394
|
features: to.features,
|
|
395
|
+
incoming_peer: requestDetails.incoming_peer || undefined,
|
|
384
396
|
lnd: args.lnd,
|
|
385
397
|
max_fee: args.max_fee_deposit,
|
|
386
398
|
messages: [{
|
|
@@ -412,7 +424,10 @@ module.exports = (args, cbk) => {
|
|
|
412
424
|
|
|
413
425
|
const address = encodeAddress(prefix, v1AddressWords(key));
|
|
414
426
|
|
|
415
|
-
args.emitter.emit('update', {
|
|
427
|
+
args.emitter.emit('update', {
|
|
428
|
+
waiting_for_chain_funding: address,
|
|
429
|
+
required_confirmations: args.min_confirmations || defaultConfsCount,
|
|
430
|
+
});
|
|
416
431
|
|
|
417
432
|
if (!!args.request) {
|
|
418
433
|
return findDeposit({
|
|
@@ -14,6 +14,7 @@ const {diffieHellmanComputeSecret} = require('ln-service');
|
|
|
14
14
|
const {fundPsbt} = require('ln-service');
|
|
15
15
|
const {getChainFeeRate} = require('ln-service');
|
|
16
16
|
const {getChainTransactions} = require('ln-service');
|
|
17
|
+
const {getChannels} = require('ln-service');
|
|
17
18
|
const {getHeight} = require('ln-service');
|
|
18
19
|
const {getIdentity} = require('ln-service');
|
|
19
20
|
const {getInvoice} = require('ln-service');
|
|
@@ -159,6 +160,7 @@ module.exports = (args, cbk) => {
|
|
|
159
160
|
claim_solo_public_key: details.claim_solo_public_key,
|
|
160
161
|
hash: details.hash,
|
|
161
162
|
key_index: details.key_index,
|
|
163
|
+
incoming_peer: details.incoming_peer || undefined,
|
|
162
164
|
refund_coop_private_key: details.refund_coop_private_key,
|
|
163
165
|
refund_coop_private_key_hash: details.refund_coop_private_key_hash,
|
|
164
166
|
solo_private_key: details.refund_solo_private_key,
|
|
@@ -170,6 +172,19 @@ module.exports = (args, cbk) => {
|
|
|
170
172
|
}
|
|
171
173
|
}],
|
|
172
174
|
|
|
175
|
+
// Lookup channel ids with the incoming peer when applicable
|
|
176
|
+
getChannels: ['recoveryDetails', ({recoveryDetails}, cbk) => {
|
|
177
|
+
if (!recoveryDetails.incoming_peer) {
|
|
178
|
+
return cbk();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return getChannels({
|
|
182
|
+
lnd: args.lnd,
|
|
183
|
+
partner_public_key: recoveryDetails.incoming_peer,
|
|
184
|
+
},
|
|
185
|
+
cbk);
|
|
186
|
+
}],
|
|
187
|
+
|
|
173
188
|
// Get the refund key
|
|
174
189
|
getRefundKey: [
|
|
175
190
|
'ecp',
|
|
@@ -213,6 +228,7 @@ module.exports = (args, cbk) => {
|
|
|
213
228
|
return;
|
|
214
229
|
}
|
|
215
230
|
|
|
231
|
+
const channels = invoice.payments.map(n => n.in_channel);
|
|
216
232
|
const hash = recoveryDetails.claim_coop_public_key_hash;
|
|
217
233
|
const timeout = min(...invoice.payments.map(n => n.timeout));
|
|
218
234
|
|
|
@@ -231,7 +247,11 @@ module.exports = (args, cbk) => {
|
|
|
231
247
|
|
|
232
248
|
args.emitter.emit('update', {execution_payment_held: id});
|
|
233
249
|
|
|
234
|
-
return cbk(null, {
|
|
250
|
+
return cbk(null, {
|
|
251
|
+
channels,
|
|
252
|
+
delta,
|
|
253
|
+
claim_coop_public_key: message.value,
|
|
254
|
+
});
|
|
235
255
|
});
|
|
236
256
|
}],
|
|
237
257
|
|
|
@@ -257,13 +277,14 @@ module.exports = (args, cbk) => {
|
|
|
257
277
|
|
|
258
278
|
args.emitter.emit('update', {offchain_funding_held: id});
|
|
259
279
|
|
|
280
|
+
const channels = invoice.payments.map(n => n.in_channel);
|
|
260
281
|
const timeout = min(...invoice.payments.map(n => n.timeout));
|
|
261
282
|
|
|
262
283
|
const delta = timeout - recoveryDetails.timeout;
|
|
263
284
|
|
|
264
285
|
sub.removeAllListeners();
|
|
265
286
|
|
|
266
|
-
return cbk(null, {delta});
|
|
287
|
+
return cbk(null, {channels, delta});
|
|
267
288
|
});
|
|
268
289
|
|
|
269
290
|
return;
|
|
@@ -423,12 +444,52 @@ module.exports = (args, cbk) => {
|
|
|
423
444
|
cbk);
|
|
424
445
|
}],
|
|
425
446
|
|
|
447
|
+
// Check incoming peer constraints
|
|
448
|
+
checkIncomingPeer: [
|
|
449
|
+
'getChannels',
|
|
450
|
+
'waitForDepositHold',
|
|
451
|
+
'waitForFundHold',
|
|
452
|
+
({getChannels, waitForDepositHold, waitForFundHold}, cbk) =>
|
|
453
|
+
{
|
|
454
|
+
// Exit early when there are no incoming peer constraints
|
|
455
|
+
if (!args.incoming_peer) {
|
|
456
|
+
return cbk();
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const channels = getChannels.channels.map(n => n.id);
|
|
460
|
+
|
|
461
|
+
// The deposit must come in under a channel with the incoming peer
|
|
462
|
+
const invalidDeposit = waitForDepositHold.channels.find(id => {
|
|
463
|
+
return !channels.includes(id);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
if (!!invalidDeposit) {
|
|
467
|
+
return cbk([503, 'UnexpectedInboundChannelForHeldDeposit']);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// The funding must come in under a channel with the incoming peer
|
|
471
|
+
const invalidFunding = waitForFundHold.channels.find(id => {
|
|
472
|
+
return !channels.includes(id);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
if (!!invalidFunding) {
|
|
476
|
+
return cbk([503, 'UnexpectedInboundChannelForHeldFunding']);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return cbk();
|
|
480
|
+
}],
|
|
481
|
+
|
|
426
482
|
// Check that there are sufficient remaining blocks
|
|
427
483
|
checkTimeRemaining: [
|
|
428
484
|
'recoveryDetails',
|
|
429
485
|
'waitForDepositHold',
|
|
430
486
|
'waitForFundHold',
|
|
431
|
-
({
|
|
487
|
+
({
|
|
488
|
+
recoveryDetails,
|
|
489
|
+
waitForDepositHold,
|
|
490
|
+
waitForFundHold,
|
|
491
|
+
},
|
|
492
|
+
cbk) =>
|
|
432
493
|
{
|
|
433
494
|
if (waitForDepositHold.delta < defaultMinDelta) {
|
|
434
495
|
return cbk([503, 'InsufficientDepositDeltaBlocksToFundSwap']);
|
|
@@ -458,6 +519,7 @@ module.exports = (args, cbk) => {
|
|
|
458
519
|
|
|
459
520
|
// Lock chain funds to fund the swap with
|
|
460
521
|
lockFunding: [
|
|
522
|
+
'checkIncomingPeer',
|
|
461
523
|
'checkTimeRemaining',
|
|
462
524
|
'getTransactions',
|
|
463
525
|
'swap',
|
|
@@ -3,6 +3,7 @@ const {decodeTlvStream} = require('bolt01');
|
|
|
3
3
|
|
|
4
4
|
const {requestRecordsAsRequest} = require('./../records');
|
|
5
5
|
const {publicTypes} = require('./swap_field_types');
|
|
6
|
+
const {swapVersion} = require('./swap_field_types');
|
|
6
7
|
|
|
7
8
|
const decodeNumber = encoded => decodeBigSize({encoded}).decoded;
|
|
8
9
|
const findRecord = (records, type) => records.find(n => n.type === type);
|
|
@@ -13,6 +14,7 @@ const paymentNonceLength = 64;
|
|
|
13
14
|
const startIndex = 0;
|
|
14
15
|
const typeCoopPrivateKeyHash = publicTypes.typeRefundCoopPrivateKeyHash;
|
|
15
16
|
const {typeDeposit} = publicTypes;
|
|
17
|
+
const {typeInboundPeer} = publicTypes;
|
|
16
18
|
const {typePush} = publicTypes;
|
|
17
19
|
const {typeRefundCoopPublicKey} = publicTypes;
|
|
18
20
|
const {typeRefundSoloPublicKey} = publicTypes;
|
|
@@ -33,6 +35,7 @@ const {typeVersion} = publicTypes;
|
|
|
33
35
|
coop_public_key: <Refund Cooperative Key Hex String>
|
|
34
36
|
deposit_mtokens: <Deposit Amount Millitokens Number String>
|
|
35
37
|
deposit_payment: <Deposit Payment Nonce Hex String>
|
|
38
|
+
[incoming_peer]: <Constrained to Inbound Peer Public Key Id Hex String>
|
|
36
39
|
push: <Push Payment Nonce Hex String>
|
|
37
40
|
refund_public_key: <Refund Unilateral Public Key Hex String>
|
|
38
41
|
request: <BOLT 11 Encoded Funding Request String>
|
|
@@ -56,10 +59,22 @@ module.exports = ({network, response}) => {
|
|
|
56
59
|
|
|
57
60
|
const {records} = decodeTlvStream({encoded: response});
|
|
58
61
|
|
|
59
|
-
if (
|
|
62
|
+
if (!findRecord(records, typeVersion)) {
|
|
60
63
|
throw new Error('UnexpectedVersionOfOffToOnResponse');
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
const versionRecord = findRecord(records, typeVersion);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
decodeNumber(versionRecord.value);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
throw new Error('ExpectedValidVersionRecordForOffToOnResponse');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (Number(decodeNumber(versionRecord.value)) !== swapVersion) {
|
|
75
|
+
throw new Error('UnsupportedSwapVersionNumberForOffchainToOnchainSwap');
|
|
76
|
+
}
|
|
77
|
+
|
|
63
78
|
const coopPrivateKeyHashRecord = findRecord(records, typeCoopPrivateKeyHash);
|
|
64
79
|
|
|
65
80
|
if (!coopPrivateKeyHashRecord) {
|
|
@@ -106,6 +121,12 @@ module.exports = ({network, response}) => {
|
|
|
106
121
|
throw new Error('ExpectedSmallerDepositAmountInOffToOnResponse');
|
|
107
122
|
}
|
|
108
123
|
|
|
124
|
+
const inboundRecord = findRecord(records, typeInboundPeer);
|
|
125
|
+
|
|
126
|
+
if (!!inboundRecord && !isPublicKey(inboundRecord.value)) {
|
|
127
|
+
throw new Error('ExpectectedPublicKeyForInboundPeerConstraint');
|
|
128
|
+
}
|
|
129
|
+
|
|
109
130
|
const pushRecord = findRecord(records, typePush);
|
|
110
131
|
|
|
111
132
|
if (!pushRecord) {
|
|
@@ -150,6 +171,7 @@ module.exports = ({network, response}) => {
|
|
|
150
171
|
coop_public_key: coopPublicKeyRecord.value,
|
|
151
172
|
deposit_mtokens: decodeBigSize({encoded: depositAmount}).decoded,
|
|
152
173
|
deposit_payment: depositRecord.value.slice(startIndex, paymentNonceLength),
|
|
174
|
+
incoming_peer: !!inboundRecord ? inboundRecord.value : undefined,
|
|
153
175
|
push: pushRecord.value,
|
|
154
176
|
refund_public_key: soloPublicKeyRecord.value,
|
|
155
177
|
request: funding.request,
|
|
@@ -5,6 +5,7 @@ const {decodeTlvStream} = require('bolt01');
|
|
|
5
5
|
|
|
6
6
|
const decodeSwapSecrets = require('./decode_swap_secrets');
|
|
7
7
|
const {publicTypes} = require('./swap_field_types');
|
|
8
|
+
const {swapVersion} = require('./swap_field_types');
|
|
8
9
|
|
|
9
10
|
const decodeNumber = encoded => decodeBigSize({encoded}).decoded;
|
|
10
11
|
const findRecord = (records, type) => records.find(n => n.type === type);
|
|
@@ -16,6 +17,7 @@ const sha256 = preimage => createHash('sha256').update(preimage).digest('hex');
|
|
|
16
17
|
const {typeClaimCoopPublicKeyHash} = publicTypes;
|
|
17
18
|
const {typeClaimSoloPublicKey} = publicTypes;
|
|
18
19
|
const {typeHash} = publicTypes;
|
|
20
|
+
const {typeInboundPeer} = publicTypes;
|
|
19
21
|
const {typePrivateRefundDetails} = publicTypes;
|
|
20
22
|
const {typeRefundCoopPrivateKeyHash} = publicTypes;
|
|
21
23
|
const {typeTimeout} = publicTypes;
|
|
@@ -60,8 +62,17 @@ module.exports = ({decrypt, recovery}) => {
|
|
|
60
62
|
|
|
61
63
|
const {records} = decodeTlvStream({encoded: recovery});
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
// Make sure the recovery records are for the known swap version
|
|
66
|
+
const versionRecord = findRecord(records, typeVersion);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
decodeNumber(versionRecord.value);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
throw new Error('ExpectedValidVersionRecordForOffToOnRecovery');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (Number(decodeNumber(versionRecord.value)) !== swapVersion) {
|
|
75
|
+
throw new Error('UnsupportedSwapVersionNumberForOffToOnRecovery');
|
|
65
76
|
}
|
|
66
77
|
|
|
67
78
|
const claimCoopPubKeyHash = findRecord(records, typeClaimCoopPublicKeyHash);
|
|
@@ -74,6 +85,12 @@ module.exports = ({decrypt, recovery}) => {
|
|
|
74
85
|
throw new Error('ExpectedValidClaimCoopPublicKeyHash');
|
|
75
86
|
}
|
|
76
87
|
|
|
88
|
+
const inboundRecord = findRecord(records, typeInboundPeer);
|
|
89
|
+
|
|
90
|
+
if (!!inboundRecord && !isPublicKey(inboundRecord.value)) {
|
|
91
|
+
throw new Error('ExpectectedPublicKeyForInboundPeerConstraintInRecovery');
|
|
92
|
+
}
|
|
93
|
+
|
|
77
94
|
const claimSoloPublicKeyRecord = findRecord(records, typeClaimSoloPublicKey);
|
|
78
95
|
|
|
79
96
|
if (!claimSoloPublicKeyRecord) {
|
|
@@ -5,6 +5,7 @@ const {parsePaymentRequest} = require('ln-service');
|
|
|
5
5
|
const encodeSwapSecrets = require('./encode_swap_secrets');
|
|
6
6
|
const {requestAsRequestRecords} = require('./../records');
|
|
7
7
|
const {publicTypes} = require('./swap_field_types');
|
|
8
|
+
const {swapVersion} = require('./swap_field_types');
|
|
8
9
|
|
|
9
10
|
const encode = records => encodeTlvStream({records}).encoded;
|
|
10
11
|
const encodeNumber = n => encodeBigSize({number: n.toString()}).encoded;
|
|
@@ -13,6 +14,7 @@ const {typeClaimCoopPublicKeyHash} = publicTypes;
|
|
|
13
14
|
const {typeClaimSoloPublicKey} = publicTypes;
|
|
14
15
|
const {typeDeposit} = publicTypes;
|
|
15
16
|
const {typeHash} = publicTypes;
|
|
17
|
+
const {typeInboundPeer} = publicTypes;
|
|
16
18
|
const {typePush} = publicTypes;
|
|
17
19
|
const {typePrivateRefundDetails} = publicTypes;
|
|
18
20
|
const {typeRefundCoopPrivateKeyHash} = publicTypes;
|
|
@@ -21,6 +23,7 @@ const {typeRefundSoloPublicKey} = publicTypes;
|
|
|
21
23
|
const {typeRequest} = publicTypes;
|
|
22
24
|
const {typeTimeout} = publicTypes;
|
|
23
25
|
const {typeTokens} = publicTypes;
|
|
26
|
+
const {typeVersion} = publicTypes;
|
|
24
27
|
|
|
25
28
|
/** Serialize off to on swap records
|
|
26
29
|
|
|
@@ -32,6 +35,7 @@ const {typeTokens} = publicTypes;
|
|
|
32
35
|
deposit: <Deposit BOLT 11 Request String>
|
|
33
36
|
encrypt: <Encrypt Secrets Base Encryption Key Hex String>
|
|
34
37
|
hash: <Swap Hash Hex String>
|
|
38
|
+
[incoming_peer]: <Constrained to Inbound Peer Public Key Id Hex String>
|
|
35
39
|
[key_index]: <Refund Unilateral Key Id Number>
|
|
36
40
|
push: <BOLT 11 Encoded Push Request String>
|
|
37
41
|
refund_public_key: <Refund Unilateral Public Key Hex String>
|
|
@@ -61,6 +65,10 @@ module.exports = args => {
|
|
|
61
65
|
type: typeDeposit,
|
|
62
66
|
value: deposit.payment + depositAmount,
|
|
63
67
|
},
|
|
68
|
+
{
|
|
69
|
+
type: typeInboundPeer,
|
|
70
|
+
value: args.incoming_peer,
|
|
71
|
+
},
|
|
64
72
|
{
|
|
65
73
|
type: typeRefundCoopPrivateKeyHash,
|
|
66
74
|
value: privateCoopKeyHash,
|
|
@@ -85,6 +93,10 @@ module.exports = args => {
|
|
|
85
93
|
type: typeTimeout,
|
|
86
94
|
value: timeout,
|
|
87
95
|
},
|
|
96
|
+
{
|
|
97
|
+
type: typeVersion,
|
|
98
|
+
value: encodeNumber(swapVersion),
|
|
99
|
+
},
|
|
88
100
|
];
|
|
89
101
|
|
|
90
102
|
const {encoded} = encodeSwapSecrets({
|
|
@@ -108,6 +120,10 @@ module.exports = args => {
|
|
|
108
120
|
type: typeHash,
|
|
109
121
|
value: swapHash,
|
|
110
122
|
},
|
|
123
|
+
{
|
|
124
|
+
type: typeInboundPeer,
|
|
125
|
+
value: args.incoming_peer,
|
|
126
|
+
},
|
|
111
127
|
{
|
|
112
128
|
type: typePrivateRefundDetails,
|
|
113
129
|
value: encoded,
|
|
@@ -124,10 +140,14 @@ module.exports = args => {
|
|
|
124
140
|
type: typeTokens,
|
|
125
141
|
value: encodeNumber(args.tokens),
|
|
126
142
|
},
|
|
143
|
+
{
|
|
144
|
+
type: typeVersion,
|
|
145
|
+
value: encodeNumber(swapVersion),
|
|
146
|
+
},
|
|
127
147
|
];
|
|
128
148
|
|
|
129
149
|
return {
|
|
130
|
-
recovery: encode(recoveryRecords),
|
|
131
|
-
response: encode(responseRecords),
|
|
150
|
+
recovery: encode(recoveryRecords.filter(n => !!n.value)),
|
|
151
|
+
response: encode(responseRecords.filter(n => !!n.value)),
|
|
132
152
|
};
|
|
133
153
|
};
|
package/swaps/manage_swap.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const {address} = require('bitcoinjs-lib');
|
|
1
2
|
const asyncAuto = require('async/auto');
|
|
2
3
|
const asyncMap = require('async/map');
|
|
3
4
|
const {findKey} = require('ln-sync');
|
|
@@ -6,6 +7,7 @@ const {getAllInvoices} = require('ln-sync');
|
|
|
6
7
|
const {getInvoice} = require('ln-service');
|
|
7
8
|
const {getNetwork} = require('ln-sync');
|
|
8
9
|
const {getNodeAlias} = require('ln-sync');
|
|
10
|
+
const {networks} = require('bitcoinjs-lib');
|
|
9
11
|
const {returnResult} = require('asyncjs-util');
|
|
10
12
|
|
|
11
13
|
const decodeOffToOnRequest = require('./decode_off_to_on_request');
|
|
@@ -25,6 +27,7 @@ const pushesReceivedAfter = () => new Date(Date.now() - 1000 * 60 * 60 * 24);
|
|
|
25
27
|
const recoverRespondAction = 'recover-response';
|
|
26
28
|
const requestAction = 'request';
|
|
27
29
|
const respondAction = 'respond';
|
|
30
|
+
const {toOutputScript} = address;
|
|
28
31
|
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
29
32
|
const typeKeySendTrade = '805805';
|
|
30
33
|
|
|
@@ -260,14 +263,57 @@ module.exports = (args, cbk) => {
|
|
|
260
263
|
}
|
|
261
264
|
}],
|
|
262
265
|
|
|
266
|
+
// Ask if the sweep address should be custom
|
|
267
|
+
askForSweepAddress: [
|
|
268
|
+
'askForExternal',
|
|
269
|
+
'getNetwork',
|
|
270
|
+
'selectAction',
|
|
271
|
+
({getNetwork, selectAction}, cbk) =>
|
|
272
|
+
{
|
|
273
|
+
switch (selectAction) {
|
|
274
|
+
case actionPushRequest:
|
|
275
|
+
case requestAction:
|
|
276
|
+
return args.ask({
|
|
277
|
+
message: 'Send on-chain funds to an external address? (Optional)',
|
|
278
|
+
name: 'sweep',
|
|
279
|
+
type: 'input',
|
|
280
|
+
validate: input => {
|
|
281
|
+
if (!input) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
return !!toOutputScript(input, networks[getNetwork.bitcoinjs]);
|
|
287
|
+
} catch (err) {
|
|
288
|
+
return 'Unsupported on-chain address format';
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
({sweep}) => cbk(null, sweep));
|
|
293
|
+
|
|
294
|
+
default:
|
|
295
|
+
// Custom sweep address not supported
|
|
296
|
+
return cbk();
|
|
297
|
+
}
|
|
298
|
+
}],
|
|
299
|
+
|
|
263
300
|
// Make swap request
|
|
264
301
|
makeRequest: [
|
|
265
302
|
'askForExternal',
|
|
266
303
|
'askForRemote',
|
|
304
|
+
'askForSweepAddress',
|
|
267
305
|
'findKey',
|
|
268
306
|
'hasTr',
|
|
269
307
|
'selectAction',
|
|
270
|
-
({
|
|
308
|
+
({
|
|
309
|
+
askForExternal,
|
|
310
|
+
askForRemote,
|
|
311
|
+
askForSweepAddress,
|
|
312
|
+
findKey,
|
|
313
|
+
hasTr,
|
|
314
|
+
selectAction,
|
|
315
|
+
},
|
|
316
|
+
cbk) =>
|
|
271
317
|
{
|
|
272
318
|
switch (selectAction) {
|
|
273
319
|
case actionPushRequest:
|
|
@@ -291,6 +337,7 @@ module.exports = (args, cbk) => {
|
|
|
291
337
|
push_to: findKey.public_key,
|
|
292
338
|
min_confirmations: minConfirmations[getNetwork.network],
|
|
293
339
|
request: !hasTr ? args.request : undefined,
|
|
340
|
+
sweep_address: askForSweepAddress || undefined,
|
|
294
341
|
},
|
|
295
342
|
cbk);
|
|
296
343
|
}],
|
|
@@ -51,6 +51,7 @@ const typeSwapResponse = serviceTypes.serviceTypeSwapResponse;
|
|
|
51
51
|
[min_confirmations]: <Minimum Confirmations to Wait Number>
|
|
52
52
|
[push_to]: <Push Swap Request to Node with Identity Public Key Hex String>
|
|
53
53
|
[request]: <Request Function>
|
|
54
|
+
[sweep_address]: <Sweep Chain Address String>
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
@returns via cbk or Promise
|
|
@@ -329,6 +330,7 @@ module.exports = (args, cbk) => {
|
|
|
329
330
|
});
|
|
330
331
|
|
|
331
332
|
const deposit = mtokensAsTokens(response.deposit_mtokens);
|
|
333
|
+
const inbound = response.incoming_peer;
|
|
332
334
|
const timeout = `that times out at ${response.timeout}`;
|
|
333
335
|
const {tokens} = parsePaymentRequest({request: response.request});
|
|
334
336
|
|
|
@@ -340,9 +342,11 @@ module.exports = (args, cbk) => {
|
|
|
340
342
|
return cbk(null, true)
|
|
341
343
|
}
|
|
342
344
|
|
|
345
|
+
const inPeer = !!args.is_external_funding ? ` in via ${inbound}` : '';
|
|
346
|
+
|
|
343
347
|
return args.ask({
|
|
344
348
|
default: true,
|
|
345
|
-
message: `Start swap ${timeout}? ${pricing}?`,
|
|
349
|
+
message: `Start swap ${timeout}? ${pricing}${inPeer}?`,
|
|
346
350
|
name: 'ok',
|
|
347
351
|
type: 'confirm',
|
|
348
352
|
},
|
|
@@ -378,6 +382,7 @@ module.exports = (args, cbk) => {
|
|
|
378
382
|
recovery: makeRequest.recovery,
|
|
379
383
|
request: args.request,
|
|
380
384
|
response: getResponse,
|
|
385
|
+
sweep_address: args.sweep_address,
|
|
381
386
|
},
|
|
382
387
|
cbk);
|
|
383
388
|
}],
|
|
@@ -2,6 +2,7 @@ const {createHash} = require('crypto');
|
|
|
2
2
|
const EventEmitter = require('events');
|
|
3
3
|
|
|
4
4
|
const asyncAuto = require('async/auto');
|
|
5
|
+
const {findKey} = require('ln-sync');
|
|
5
6
|
const {getChainFeeRate} = require('ln-service');
|
|
6
7
|
const {returnResult} = require('asyncjs-util');
|
|
7
8
|
|
|
@@ -158,6 +159,24 @@ module.exports = ({ask, lnd, logger, request, swap, to}, cbk) => {
|
|
|
158
159
|
({target}) => cbk(null, Number(target)));
|
|
159
160
|
}],
|
|
160
161
|
|
|
162
|
+
// Ask for an incoming peer to constrain the swap to
|
|
163
|
+
askForIncoming: ['askForTarget', ({}, cbk) => {
|
|
164
|
+
return ask({
|
|
165
|
+
message: 'Require off-chain through a specific peer? (Optional)',
|
|
166
|
+
name: 'incoming',
|
|
167
|
+
},
|
|
168
|
+
({incoming}) => cbk(null, incoming));
|
|
169
|
+
}],
|
|
170
|
+
|
|
171
|
+
// Find the incoming identity key when specified
|
|
172
|
+
findIncoming: ['askForIncoming', ({askForIncoming}, cbk) => {
|
|
173
|
+
if (!askForIncoming) {
|
|
174
|
+
return cbk(null, {});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return findKey({lnd, query: askForIncoming}, cbk);
|
|
178
|
+
}],
|
|
179
|
+
|
|
161
180
|
// Get the chain fee rate
|
|
162
181
|
getRate: ['askForTarget', ({askForTarget}, cbk) => {
|
|
163
182
|
return getChainFeeRate({lnd, confirmation_target: askForTarget}, cbk);
|
|
@@ -167,15 +186,21 @@ module.exports = ({ask, lnd, logger, request, swap, to}, cbk) => {
|
|
|
167
186
|
makeResponse: [
|
|
168
187
|
'askForRate',
|
|
169
188
|
'askForRequest',
|
|
189
|
+
'findIncoming',
|
|
170
190
|
'getRate',
|
|
171
|
-
({askForRate, askForRequest, getRate}, cbk) =>
|
|
191
|
+
({askForRate, askForRequest, findIncoming, getRate}, cbk) =>
|
|
172
192
|
{
|
|
173
193
|
const {tokens} = decodeOffToOnRequest({request: askForRequest});
|
|
174
194
|
|
|
195
|
+
if (!!findIncoming.public_key) {
|
|
196
|
+
logger.info({incoming_peer_constraint: findIncoming.public_key});
|
|
197
|
+
}
|
|
198
|
+
|
|
175
199
|
return startOnToOffSwap({
|
|
176
200
|
lnd,
|
|
177
201
|
delta: defaultCltvDelta,
|
|
178
202
|
deposit: ceil(getRate.tokens_per_vbyte * estimatedVirtualSize),
|
|
203
|
+
incoming_peer: findIncoming.public_key || undefined,
|
|
179
204
|
is_external_solo_key: !!request,
|
|
180
205
|
price: floor(tokens * askForRate / rateDenominator),
|
|
181
206
|
request: askForRequest,
|
|
@@ -29,6 +29,7 @@ const sha256 = preimage => createHash('sha256').update(preimage).digest('hex');
|
|
|
29
29
|
{
|
|
30
30
|
delta: <Swap CLTV Delta Number>
|
|
31
31
|
deposit: <Unilateral Deposit Tokens Number>
|
|
32
|
+
[incoming_peer]: <Constrained to Inbound Peer Public Key Id Hex String>
|
|
32
33
|
[is_external_solo_key]: <Use External Unilateral Refund Key Bool>
|
|
33
34
|
lnd: <Authenticated LND API Object>
|
|
34
35
|
price: <Swap Price Tokens Number>
|
|
@@ -227,6 +228,7 @@ module.exports = (args, cbk) => {
|
|
|
227
228
|
deposit: createExecInvoice.request,
|
|
228
229
|
encrypt: getEncrypt.secret,
|
|
229
230
|
hash: requestDetails.hash,
|
|
231
|
+
incoming_peer: args.incoming_peer || undefined,
|
|
230
232
|
key_index: getRefundKey.index,
|
|
231
233
|
push: createPushInvoice.request,
|
|
232
234
|
refund_public_key: getRefundKey.public_key,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"typeSoloPrivateKey": "6"
|
|
9
9
|
},
|
|
10
10
|
"publicTypes": {
|
|
11
|
+
"typeVersion": "0",
|
|
11
12
|
"typeClaimCoopPublicKeyHash": "1",
|
|
12
13
|
"typeClaimSoloPublicKey": "2",
|
|
13
14
|
"typeDeposit": "3",
|
|
@@ -21,11 +22,12 @@
|
|
|
21
22
|
"typeRequest": "11",
|
|
22
23
|
"typeTimeout": "12",
|
|
23
24
|
"typeTokens": "13",
|
|
24
|
-
"
|
|
25
|
+
"typeInboundPeer": "14"
|
|
25
26
|
},
|
|
26
27
|
"pushTypes": {
|
|
27
28
|
"typeSwapId": "0",
|
|
28
29
|
"typeSwapResponse": "1"
|
|
29
30
|
},
|
|
31
|
+
"swapVersion": 1,
|
|
30
32
|
"typePayMetadata": "805001"
|
|
31
33
|
}
|
|
@@ -103,6 +103,10 @@ test(`Start offchain swap`, async ({end, equal, strictSame}) => {
|
|
|
103
103
|
return cbk({[args.name]: args.default});
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
if (args.name === 'incoming') {
|
|
107
|
+
return cbk({incoming: false});
|
|
108
|
+
}
|
|
109
|
+
|
|
106
110
|
if (args.name === 'req') {
|
|
107
111
|
return cbk({req: swapRequest.swap_request});
|
|
108
112
|
}
|
|
@@ -103,6 +103,10 @@ test(`Swap with claim path`, async ({end, equal, strictSame}) => {
|
|
|
103
103
|
return cbk({[args.name]: args.default});
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
if (args.name === 'incoming') {
|
|
107
|
+
return cbk({incoming: false});
|
|
108
|
+
}
|
|
109
|
+
|
|
106
110
|
if (args.name === 'req') {
|
|
107
111
|
return cbk({req: swapRequest.swap_request});
|
|
108
112
|
}
|
|
@@ -104,6 +104,10 @@ test(`Timeout a swap`, async ({end, equal, strictSame}) => {
|
|
|
104
104
|
return cbk({[args.name]: args.default});
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
if (args.name === 'incoming') {
|
|
108
|
+
return cbk({incoming: false});
|
|
109
|
+
}
|
|
110
|
+
|
|
107
111
|
if (args.name === 'req') {
|
|
108
112
|
return cbk({req: swapRequest.swap_request});
|
|
109
113
|
}
|
|
@@ -2,12 +2,15 @@ const {addPeer} = require('ln-service');
|
|
|
2
2
|
const asyncAuto = require('async/auto');
|
|
3
3
|
const asyncDetect = require('async/detect');
|
|
4
4
|
const asyncDetectSeries = require('async/detectSeries');
|
|
5
|
+
const asyncEach = require('async/each');
|
|
5
6
|
const asyncMap = require('async/map');
|
|
6
7
|
const asyncRetry = require('async/retry');
|
|
7
8
|
const {getChannel} = require('ln-service');
|
|
9
|
+
const {getChannels} = require('ln-service');
|
|
8
10
|
const {getIdentity} = require('ln-service');
|
|
9
11
|
const {getNode} = require('ln-service');
|
|
10
12
|
const {getPeers} = require('ln-service');
|
|
13
|
+
const {removePeer} = require('ln-service');
|
|
11
14
|
const {returnResult} = require('asyncjs-util');
|
|
12
15
|
|
|
13
16
|
const interval = 1000;
|
|
@@ -55,12 +58,14 @@ module.exports = ({lnd, logger, nodes}, cbk) => {
|
|
|
55
58
|
return cbk();
|
|
56
59
|
},
|
|
57
60
|
|
|
61
|
+
// Get the channels to see if this is a channel peer
|
|
62
|
+
getChannels: ['validate', ({}, cbk) => {
|
|
63
|
+
return getChannels({lnd, is_active: true}, cbk);
|
|
64
|
+
}],
|
|
65
|
+
|
|
58
66
|
// Derive the self public key
|
|
59
67
|
getIdentity: ['validate', ({}, cbk) => getIdentity({lnd}, cbk)],
|
|
60
68
|
|
|
61
|
-
// Get the list of connected peers
|
|
62
|
-
getPeers: ['validate', ({}, cbk) => getPeers({lnd}, cbk)],
|
|
63
|
-
|
|
64
69
|
// Find node connect info to connect to
|
|
65
70
|
getNodes: ['getIdentity', ({getIdentity}, cbk) => {
|
|
66
71
|
return asyncMap(nodes, (connect, cbk) => {
|
|
@@ -135,8 +140,56 @@ module.exports = ({lnd, logger, nodes}, cbk) => {
|
|
|
135
140
|
cbk);
|
|
136
141
|
}],
|
|
137
142
|
|
|
143
|
+
// Remove the peer if there is no channel peer to avoid stale peers
|
|
144
|
+
removePeer: [
|
|
145
|
+
'getChannels',
|
|
146
|
+
'getNodes',
|
|
147
|
+
({getChannels, getNodes}, cbk) =>
|
|
148
|
+
{
|
|
149
|
+
const ids = getChannels.channels.map(n => n.partner_public_key);
|
|
150
|
+
|
|
151
|
+
return asyncEach(getNodes, (node, cbk) => {
|
|
152
|
+
// Exit early when there is no node id
|
|
153
|
+
if (!node || !node.id || ids.includes(node.id)) {
|
|
154
|
+
return cbk();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return removePeer({lnd, public_key: node.id}, (err, res) => {
|
|
158
|
+
if (!!err) {
|
|
159
|
+
return cbk(err);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return asyncRetry({interval, times}, cbk => {
|
|
163
|
+
return getPeers({lnd}, (err, res) => {
|
|
164
|
+
if (!!err) {
|
|
165
|
+
return cbk(err);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const peers = res.peers.map(n => n.public_key);
|
|
169
|
+
|
|
170
|
+
if (!!peers.includes(node.id)) {
|
|
171
|
+
return cbk([503, 'FailedToDisconnectSellerPeer']);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return cbk();
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
cbk);
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
cbk);
|
|
181
|
+
}],
|
|
182
|
+
|
|
183
|
+
// Get the list of connected peers
|
|
184
|
+
getPeers: ['removePeer', ({}, cbk) => getPeers({lnd}, cbk)],
|
|
185
|
+
|
|
138
186
|
// Try and connect to a node in order to do p2p messaging
|
|
139
|
-
connect: [
|
|
187
|
+
connect: [
|
|
188
|
+
'getNodes',
|
|
189
|
+
'getPeers',
|
|
190
|
+
'removePeer',
|
|
191
|
+
({getNodes, getPeers}, cbk) =>
|
|
192
|
+
{
|
|
140
193
|
const connected = getPeers.peers.map(n => n.public_key);
|
|
141
194
|
|
|
142
195
|
// Look for a node that is already a connected peer
|