paid-services 3.15.0 → 3.15.3
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 +1 -1
- package/package.json +4 -3
- package/swaps/complete_off_to_on_swap.js +11 -1
- package/swaps/complete_on_to_off_swap.js +3 -4
- package/swaps/manage_swap.js +45 -9
- package/swaps/recover_response_to_swap_out.js +1 -5
- package/swaps/request_swap_out.js +20 -16
- package/swaps/respond_to_swap_out_request.js +2 -6
- package/test/integration/test_request_swap_out.js +151 -0
- package/test/integration/test_swap_with_claim_path.js +152 -0
- package/test/integration/test_swap_with_refund_path.js +164 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
"@alexbosworth/fiat": "1.0.2",
|
|
11
11
|
"async": "3.2.3",
|
|
12
12
|
"asyncjs-util": "1.2.9",
|
|
13
|
+
"bech32": "2.0.0",
|
|
13
14
|
"bolt01": "1.2.4",
|
|
14
15
|
"bolt07": "1.8.1",
|
|
15
16
|
"ecpair": "2.0.1",
|
|
16
17
|
"goldengate": "11.2.1",
|
|
17
18
|
"invoices": "2.0.6",
|
|
18
|
-
"ln-service": "53.
|
|
19
|
+
"ln-service": "53.14.1",
|
|
19
20
|
"ln-sync": "3.12.0",
|
|
20
21
|
"psbt": "2.0.1",
|
|
21
22
|
"p2tr": "1.3.1",
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
"description": "Lightning Paid Services library",
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"@alexbosworth/tap": "15.0.11",
|
|
27
|
-
"ln-docker-daemons": "2.2.
|
|
28
|
+
"ln-docker-daemons": "2.2.9",
|
|
28
29
|
"mock-lnd": "1.4.1",
|
|
29
30
|
"secp256k1": "4.0.3"
|
|
30
31
|
},
|
|
@@ -46,5 +47,5 @@
|
|
|
46
47
|
"integration-tests": "tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 180 test/integration/*.js",
|
|
47
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"
|
|
48
49
|
},
|
|
49
|
-
"version": "3.15.
|
|
50
|
+
"version": "3.15.3"
|
|
50
51
|
}
|
|
@@ -5,6 +5,7 @@ const asyncAuto = require('async/auto');
|
|
|
5
5
|
const asyncMap = require('async/map');
|
|
6
6
|
const asyncRace = require('async/race');
|
|
7
7
|
const asyncReflect = require('async/reflect');
|
|
8
|
+
const {bech32m} = require('bech32');
|
|
8
9
|
const {broadcastChainTransaction} = require('ln-service');
|
|
9
10
|
const {confirmationFee} = require('goldengate');
|
|
10
11
|
const {controlBlock} = require('p2tr');
|
|
@@ -19,12 +20,14 @@ const {getNetwork} = require('ln-sync');
|
|
|
19
20
|
const {getPayment} = require('ln-service');
|
|
20
21
|
const {getPublicKey} = require('ln-service');
|
|
21
22
|
const {hashForTree} = require('p2tr');
|
|
23
|
+
const {networks} = require('bitcoinjs-lib');
|
|
22
24
|
const {parsePaymentRequest} = require('ln-service');
|
|
23
25
|
const {pay} = require('ln-service');
|
|
24
26
|
const {payViaPaymentDetails} = require('ln-service');
|
|
25
27
|
const {pointAdd} = require('tiny-secp256k1');
|
|
26
28
|
const {privateAdd} = require('tiny-secp256k1');
|
|
27
29
|
const {returnResult} = require('asyncjs-util');
|
|
30
|
+
const {script} = require('bitcoinjs-lib');
|
|
28
31
|
const {signTransaction} = require('ln-service');
|
|
29
32
|
const {subscribeToBlocks} = require('ln-service');
|
|
30
33
|
const {subscribeToPastPayment} = require('ln-service');
|
|
@@ -42,12 +45,14 @@ const {typePayMetadata} = require('./swap_field_types');
|
|
|
42
45
|
|
|
43
46
|
const bufferAsHex = buffer => buffer.toString('hex');
|
|
44
47
|
const {ceil} = Math;
|
|
48
|
+
const decompileOutputScript = hex => script.decompile(Buffer.from(hex, 'hex'));
|
|
45
49
|
const defaultConfsCount = 1;
|
|
46
50
|
const defaultDepositSettleTimeoutMs = 1000 * 60 * 10;
|
|
47
51
|
const defaultMaxFeeMultiplier = 1000;
|
|
48
52
|
const defaultMaxPreimagePushFee = 10;
|
|
49
53
|
const defaultMinSweepBlocks = 20;
|
|
50
54
|
const defaultWaitForChainFundingMs = 1000 * 60 * 60 * 3;
|
|
55
|
+
const encodeAddress = (prefix, data) => bech32m.encode(prefix, data);
|
|
51
56
|
const family = 805;
|
|
52
57
|
const {floor} = Math;
|
|
53
58
|
const {from} = Buffer;
|
|
@@ -69,6 +74,7 @@ const sweepInputIndex = 0;
|
|
|
69
74
|
const times = n => Array(n).fill(null).map((_, i) => i);
|
|
70
75
|
const tokensForPushPreimage = 1;
|
|
71
76
|
const uniqBy = (a,b) => a.filter((e,i) => a.findIndex(n => n[b] == e[b]) == i);
|
|
77
|
+
const v1AddressWords = key => [].concat(1).concat(bech32m.toWords(key));
|
|
72
78
|
|
|
73
79
|
/** Complete the off to on swap
|
|
74
80
|
|
|
@@ -393,9 +399,13 @@ module.exports = (args, cbk) => {
|
|
|
393
399
|
'swap',
|
|
394
400
|
({deadline, getNetwork, requestDetails, startHeight, swap}, cbk) =>
|
|
395
401
|
{
|
|
402
|
+
const [, key] = decompileOutputScript(swap.output_script);
|
|
396
403
|
const outputScript = swap.output_script;
|
|
404
|
+
const prefix = networks[getNetwork.bitcoinjs].bech32;
|
|
397
405
|
|
|
398
|
-
|
|
406
|
+
const address = encodeAddress(prefix, v1AddressWords(key));
|
|
407
|
+
|
|
408
|
+
args.emitter.emit('update', {waiting_for_chain_funding: address});
|
|
399
409
|
|
|
400
410
|
if (!!args.request) {
|
|
401
411
|
return findDeposit({
|
|
@@ -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
|
}],
|
|
@@ -681,7 +680,7 @@ module.exports = (args, cbk) => {
|
|
|
681
680
|
transaction,
|
|
682
681
|
lnd: args.lnd,
|
|
683
682
|
},
|
|
684
|
-
|
|
683
|
+
() => {});
|
|
685
684
|
});
|
|
686
685
|
|
|
687
686
|
subTimeout.on('error', err => done(err));
|
|
@@ -718,7 +717,7 @@ module.exports = (args, cbk) => {
|
|
|
718
717
|
return cbk();
|
|
719
718
|
}
|
|
720
719
|
|
|
721
|
-
args.emitter.emit('update', {receiving_funds:
|
|
720
|
+
args.emitter.emit('update', {receiving_funds: getSettlement.tokens});
|
|
722
721
|
|
|
723
722
|
return settleHodlInvoice({
|
|
724
723
|
lnd: args.lnd,
|
package/swaps/manage_swap.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
-
const {
|
|
2
|
+
const {getMasterPublicKeys} = require('ln-service');
|
|
3
|
+
const {getNetwork} = require('ln-sync');
|
|
3
4
|
const {returnResult} = require('asyncjs-util');
|
|
4
5
|
|
|
5
6
|
const recoverResponseToSwapOut = require('./recover_response_to_swap_out');
|
|
@@ -7,6 +8,7 @@ const requestSwapOut = require('./request_swap_out');
|
|
|
7
8
|
const respondToSwapOutRequest = require('./respond_to_swap_out_request');
|
|
8
9
|
|
|
9
10
|
const allowedNetwork = 'btctestnet';
|
|
11
|
+
const bip86Path = `m/86'/0'/0'`;
|
|
10
12
|
const recoverRespondAction = 'recover-response';
|
|
11
13
|
const requestAction = 'request';
|
|
12
14
|
const respondAction = 'respond';
|
|
@@ -46,13 +48,25 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
46
48
|
return cbk();
|
|
47
49
|
},
|
|
48
50
|
|
|
51
|
+
// Look for a p2tr master public key to detect p2tr support
|
|
52
|
+
getMasterPublicKeys: ['validate', ({}, cbk) => {
|
|
53
|
+
return getMasterPublicKeys({lnd}, cbk);
|
|
54
|
+
}],
|
|
55
|
+
|
|
49
56
|
// Get the network to make sure this is on testnet
|
|
50
57
|
getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
|
|
51
58
|
|
|
59
|
+
// Determine if taproot is supported or not
|
|
60
|
+
hasTr: ['getMasterPublicKeys', ({getMasterPublicKeys}, cbk) => {
|
|
61
|
+
const {keys} = getMasterPublicKeys;
|
|
62
|
+
|
|
63
|
+
return cbk(null, !!keys.find(n => n.derivation_path === bip86Path));
|
|
64
|
+
}],
|
|
65
|
+
|
|
52
66
|
// Select a swap action
|
|
53
|
-
selectAction: ['getNetwork', ({getNetwork}, cbk) => {
|
|
67
|
+
selectAction: ['getNetwork', 'hasTr', ({getNetwork, hasTr}, cbk) => {
|
|
54
68
|
// Check to make sure the network is supported
|
|
55
|
-
if (getNetwork.network !== allowedNetwork) {
|
|
69
|
+
if (!hasTr && getNetwork.network !== allowedNetwork) {
|
|
56
70
|
return cbk([501, 'CurrentlyUnsupportedChainForSwap']);
|
|
57
71
|
}
|
|
58
72
|
|
|
@@ -79,30 +93,52 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
79
93
|
}],
|
|
80
94
|
|
|
81
95
|
// Make swap request
|
|
82
|
-
makeRequest: ['selectAction', ({selectAction}, cbk) => {
|
|
96
|
+
makeRequest: ['hasTr', 'selectAction', ({hasTr, selectAction}, cbk) => {
|
|
83
97
|
if (selectAction !== requestAction) {
|
|
84
98
|
return cbk();
|
|
85
99
|
}
|
|
86
100
|
|
|
87
|
-
return requestSwapOut({
|
|
101
|
+
return requestSwapOut({
|
|
102
|
+
ask,
|
|
103
|
+
lnd,
|
|
104
|
+
logger,
|
|
105
|
+
request: !hasTr ? request : undefined,
|
|
106
|
+
},
|
|
107
|
+
cbk);
|
|
88
108
|
}],
|
|
89
109
|
|
|
90
110
|
// Respond to swap request
|
|
91
|
-
makeResponse: ['selectAction', ({selectAction}, cbk) => {
|
|
111
|
+
makeResponse: ['hasTr', 'selectAction', ({hasTr, selectAction}, cbk) => {
|
|
92
112
|
if (selectAction !== respondAction) {
|
|
93
113
|
return cbk();
|
|
94
114
|
}
|
|
95
115
|
|
|
96
|
-
return respondToSwapOutRequest({
|
|
116
|
+
return respondToSwapOutRequest({
|
|
117
|
+
ask,
|
|
118
|
+
lnd,
|
|
119
|
+
logger,
|
|
120
|
+
request: !hasTr ? request : undefined,
|
|
121
|
+
},
|
|
122
|
+
cbk);
|
|
97
123
|
}],
|
|
98
124
|
|
|
99
125
|
// Recover responding to swap request
|
|
100
|
-
recoverResponse: [
|
|
126
|
+
recoverResponse: [
|
|
127
|
+
'hasTr',
|
|
128
|
+
'selectAction',
|
|
129
|
+
({hasTr, selectAction}, cbk) =>
|
|
130
|
+
{
|
|
101
131
|
if (selectAction !== recoverRespondAction) {
|
|
102
132
|
return cbk();
|
|
103
133
|
}
|
|
104
134
|
|
|
105
|
-
return recoverResponseToSwapOut({
|
|
135
|
+
return recoverResponseToSwapOut({
|
|
136
|
+
ask,
|
|
137
|
+
lnd,
|
|
138
|
+
logger,
|
|
139
|
+
request: !hasTr ? request : undefined,
|
|
140
|
+
},
|
|
141
|
+
cbk);
|
|
106
142
|
}],
|
|
107
143
|
|
|
108
144
|
// Final result
|
|
@@ -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
|
|
|
@@ -22,27 +22,29 @@ const rateDenominator = 1e6;
|
|
|
22
22
|
|
|
23
23
|
{
|
|
24
24
|
ask: <Ask Function>
|
|
25
|
+
[is_avoiding_broadcast]: <Avoid Broadcasting Bool>
|
|
26
|
+
[is_uncooperative]: <Avoid Cooperative Resolution Bool>
|
|
25
27
|
lnd: <Authenticated LND API Object>
|
|
26
28
|
logger: <Winston Logger Object>
|
|
27
|
-
request: <Request Function>
|
|
29
|
+
[request]: <Request Function>
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
@returns via cbk or Promise
|
|
31
33
|
*/
|
|
32
|
-
module.exports = (
|
|
34
|
+
module.exports = (args, cbk) => {
|
|
33
35
|
return new Promise((resolve, reject) => {
|
|
34
36
|
return asyncAuto({
|
|
35
37
|
// Check arguments
|
|
36
38
|
validate: cbk => {
|
|
37
|
-
if (!ask) {
|
|
39
|
+
if (!args.ask) {
|
|
38
40
|
return cbk([400, 'ExpectedAskFunctionToRequestSwapOut']);
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
if (!lnd) {
|
|
43
|
+
if (!args.lnd) {
|
|
42
44
|
return cbk([400, 'ExpectedAuthenticatedLndToRequestSwapOut']);
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
if (!logger) {
|
|
47
|
+
if (!args.logger) {
|
|
46
48
|
return cbk([400, 'ExpectedWinstonLoggerToRequestSwapOut']);
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -51,7 +53,7 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
51
53
|
|
|
52
54
|
// Ask for amount
|
|
53
55
|
askForTokens: ['validate', ({}, cbk) => {
|
|
54
|
-
return ask({
|
|
56
|
+
return args.ask({
|
|
55
57
|
default: defaultSwapAmount,
|
|
56
58
|
message: 'Amount to swap?',
|
|
57
59
|
name: 'tokens',
|
|
@@ -71,11 +73,11 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
71
73
|
}],
|
|
72
74
|
|
|
73
75
|
// Get the network name
|
|
74
|
-
getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
|
|
76
|
+
getNetwork: ['validate', ({}, cbk) => getNetwork({lnd: args.lnd}, cbk)],
|
|
75
77
|
|
|
76
78
|
// Ask for routing fee rate
|
|
77
79
|
askForRate: ['askForTokens', ({}, cbk) => {
|
|
78
|
-
return ask({
|
|
80
|
+
return args.ask({
|
|
79
81
|
default: defaultFeeRate,
|
|
80
82
|
message: 'Max routing fee rate for swap funds in parts per million?',
|
|
81
83
|
name: 'rate',
|
|
@@ -97,8 +99,8 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
97
99
|
// Make a swap request
|
|
98
100
|
makeRequest: ['askForTokens', ({askForTokens}, cbk) => {
|
|
99
101
|
return makeRequest({
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
is_external_solo_key: !!args.request,
|
|
103
|
+
lnd: args.lnd,
|
|
102
104
|
tokens: askForTokens,
|
|
103
105
|
},
|
|
104
106
|
cbk);
|
|
@@ -111,9 +113,9 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
111
113
|
'makeRequest',
|
|
112
114
|
({getNetwork, makeRequest}, cbk) =>
|
|
113
115
|
{
|
|
114
|
-
logger.info({swap_request: makeRequest.request});
|
|
116
|
+
args.logger.info({swap_request: makeRequest.request});
|
|
115
117
|
|
|
116
|
-
return ask({
|
|
118
|
+
return args.ask({
|
|
117
119
|
message: 'Response to swap request?',
|
|
118
120
|
name: 'response',
|
|
119
121
|
validate: response => {
|
|
@@ -152,7 +154,7 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
152
154
|
const fee = tokens - askForTokens;
|
|
153
155
|
const pricing = `Execution cost ${deposit}, plus liquidity fee ${fee}`;
|
|
154
156
|
|
|
155
|
-
return ask({
|
|
157
|
+
return args.ask({
|
|
156
158
|
default: true,
|
|
157
159
|
message: `Start swap ${timeout}? ${pricing}?`,
|
|
158
160
|
name: 'ok',
|
|
@@ -176,15 +178,17 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
176
178
|
|
|
177
179
|
const emitter = new EventEmitter();
|
|
178
180
|
|
|
179
|
-
emitter.on('update', update => logger.info(update));
|
|
181
|
+
emitter.on('update', update => args.logger.info(update));
|
|
180
182
|
|
|
181
183
|
return completeOffToOnSwap({
|
|
182
184
|
emitter,
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
is_avoiding_broadcast: args.is_avoiding_broadcast,
|
|
186
|
+
is_uncooperative: args.is_uncooperative,
|
|
187
|
+
lnd: args.lnd,
|
|
185
188
|
max_fee_deposit: defaultMaxFeeForDeposit,
|
|
186
189
|
max_fee_funding: askForTokens * askForRate / rateDenominator,
|
|
187
190
|
recovery: makeRequest.recovery,
|
|
191
|
+
request: args.request,
|
|
188
192
|
response: getResponse,
|
|
189
193
|
},
|
|
190
194
|
cbk);
|
|
@@ -23,7 +23,7 @@ const rateDenominator = 1e6;
|
|
|
23
23
|
ask: <Ask Function>
|
|
24
24
|
lnd: <Authenticated LND API Object>
|
|
25
25
|
logger: <Winston Logger Object>
|
|
26
|
-
request: <Request Function>
|
|
26
|
+
[request]: <Request Function>
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
@returns via cbk or Promise
|
|
@@ -45,10 +45,6 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
45
45
|
return cbk([400, 'ExpectedLoggerToRespondToSwapOutRequest']);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
if (!request) {
|
|
49
|
-
return cbk([400, 'ExpectedRequestFunctionToRespondToSwapOutReq']);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
48
|
return cbk();
|
|
53
49
|
},
|
|
54
50
|
|
|
@@ -115,7 +111,7 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
115
111
|
lnd,
|
|
116
112
|
delta: defaultCltvDelta,
|
|
117
113
|
deposit: ceil(getRate.tokens_per_vbyte * estimatedVirtualSize),
|
|
118
|
-
is_external_solo_key:
|
|
114
|
+
is_external_solo_key: !!request,
|
|
119
115
|
price: floor(tokens * askForRate / rateDenominator),
|
|
120
116
|
request: askForRequest,
|
|
121
117
|
},
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const asyncRetry = require('async/retry');
|
|
2
|
+
const {createChainAddress} = require('ln-service');
|
|
3
|
+
const {createInvoice} = require('ln-service');
|
|
4
|
+
const {getChannelBalance} = require('ln-service');
|
|
5
|
+
const {getChannels} = require('ln-service');
|
|
6
|
+
const {getMasterPublicKeys} = require('ln-service');
|
|
7
|
+
const {openChannel} = require('ln-service');
|
|
8
|
+
const {pay} = require('ln-service');
|
|
9
|
+
const {sendToChainAddress} = require('ln-service');
|
|
10
|
+
const {spawnLightningCluster} = require('ln-docker-daemons');
|
|
11
|
+
const {test} = require('@alexbosworth/tap');
|
|
12
|
+
|
|
13
|
+
const requestSwapOut = require('./../../swaps/request_swap_out');
|
|
14
|
+
const respondToSwapOut = require('./../../swaps/respond_to_swap_out_request');
|
|
15
|
+
|
|
16
|
+
const capacity = 1e6;
|
|
17
|
+
const count = 50;
|
|
18
|
+
const interval = 100;
|
|
19
|
+
const maturity = 100;
|
|
20
|
+
const size = 2;
|
|
21
|
+
const taprootDerivationPath = `m/86'/0'/0'`;
|
|
22
|
+
const times = 3000;
|
|
23
|
+
const tokens = 1e5;
|
|
24
|
+
|
|
25
|
+
// Start an offchain swap
|
|
26
|
+
test(`Start offchain swap`, async ({end, equal, strictSame}) => {
|
|
27
|
+
const {kill, nodes} = await spawnLightningCluster({size});
|
|
28
|
+
|
|
29
|
+
const [{generate, id, lnd}, target] = nodes;
|
|
30
|
+
|
|
31
|
+
const {keys} = await getMasterPublicKeys({lnd});
|
|
32
|
+
|
|
33
|
+
// Exit early when taproot is not supported
|
|
34
|
+
if (!keys.find(n => n.derivation_path === taprootDerivationPath)) {
|
|
35
|
+
await kill({});
|
|
36
|
+
|
|
37
|
+
return end();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
await generate({count: maturity});
|
|
42
|
+
|
|
43
|
+
// Setup a channel between the nodes
|
|
44
|
+
{
|
|
45
|
+
// Make a channel
|
|
46
|
+
await asyncRetry({interval, times}, async () => {
|
|
47
|
+
await openChannel({
|
|
48
|
+
lnd,
|
|
49
|
+
local_tokens: capacity,
|
|
50
|
+
partner_public_key: target.id,
|
|
51
|
+
partner_socket: target.socket,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await asyncRetry({interval, times}, async () => {
|
|
56
|
+
const {channels} = await getChannels({lnd, is_actve: true});
|
|
57
|
+
|
|
58
|
+
if (!!channels.length) {
|
|
59
|
+
return channels;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await generate({});
|
|
63
|
+
|
|
64
|
+
await sendToChainAddress({
|
|
65
|
+
lnd,
|
|
66
|
+
address: (await createChainAddress({lnd: target.lnd})).address,
|
|
67
|
+
tokens: capacity,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const balance = await getChannelBalance({lnd});
|
|
71
|
+
|
|
72
|
+
if (!balance.channel_balance) {
|
|
73
|
+
throw new Error('WaitingForChannelBalance');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw new Error('WaitingForChannelOpen');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await target.generate({count: maturity});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Collect request messages
|
|
83
|
+
const messages = [];
|
|
84
|
+
|
|
85
|
+
// Make a swap out request
|
|
86
|
+
await requestSwapOut({
|
|
87
|
+
lnd,
|
|
88
|
+
ask: async (args, cbk) => {
|
|
89
|
+
if (args.name === 'tokens') {
|
|
90
|
+
return cbk({tokens: '10000'});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (args.name === 'rate') {
|
|
94
|
+
return cbk({rate: '10'});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const swapRequest = messages.find(n => !!n.swap_request);
|
|
98
|
+
|
|
99
|
+
if (args.name === 'response') {
|
|
100
|
+
return await respondToSwapOut({
|
|
101
|
+
ask: (args, cbk) => {
|
|
102
|
+
if (args.name === 'rate') {
|
|
103
|
+
return cbk({rate: '100'});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (args.name === 'req') {
|
|
107
|
+
return cbk({req: swapRequest.swap_request});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
throw new Error('UnrecognizedQueryForResponse');
|
|
111
|
+
},
|
|
112
|
+
lnd: target.lnd,
|
|
113
|
+
logger: {
|
|
114
|
+
info: async message => {
|
|
115
|
+
if (!!message.response) {
|
|
116
|
+
return cbk({response: message.response});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Transaction is funded, generate funding into a block
|
|
120
|
+
if (!!message.funding_transaction_id) {
|
|
121
|
+
return await target.generate({count});
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (args.name === 'ok') {
|
|
129
|
+
return cbk({ok: true});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
throw new Error('UnrecognizedQueryForRequest');
|
|
133
|
+
},
|
|
134
|
+
logger: {
|
|
135
|
+
info: async message => {
|
|
136
|
+
if (message.broadcasting_tx_to_resolve_swap) {
|
|
137
|
+
await generate({count});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return messages.push(message);
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
} catch (err) {
|
|
145
|
+
strictSame(err, null, 'Expected no failure');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await kill({});
|
|
149
|
+
|
|
150
|
+
return end();
|
|
151
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
const asyncRetry = require('async/retry');
|
|
2
|
+
const {createChainAddress} = require('ln-service');
|
|
3
|
+
const {createInvoice} = require('ln-service');
|
|
4
|
+
const {getChannelBalance} = require('ln-service');
|
|
5
|
+
const {getChannels} = require('ln-service');
|
|
6
|
+
const {getMasterPublicKeys} = require('ln-service');
|
|
7
|
+
const {openChannel} = require('ln-service');
|
|
8
|
+
const {pay} = require('ln-service');
|
|
9
|
+
const {sendToChainAddress} = require('ln-service');
|
|
10
|
+
const {spawnLightningCluster} = require('ln-docker-daemons');
|
|
11
|
+
const {test} = require('@alexbosworth/tap');
|
|
12
|
+
|
|
13
|
+
const requestSwapOut = require('./../../swaps/request_swap_out');
|
|
14
|
+
const respondToSwapOut = require('./../../swaps/respond_to_swap_out_request');
|
|
15
|
+
|
|
16
|
+
const capacity = 1e6;
|
|
17
|
+
const count = 50;
|
|
18
|
+
const interval = 100;
|
|
19
|
+
const maturity = 100;
|
|
20
|
+
const size = 2;
|
|
21
|
+
const taprootDerivationPath = `m/86'/0'/0'`;
|
|
22
|
+
const times = 3000;
|
|
23
|
+
const tokens = 1e5;
|
|
24
|
+
|
|
25
|
+
// Start an offchain swap but use the claim path to sweep
|
|
26
|
+
test(`Swap with claim path`, async ({end, equal, strictSame}) => {
|
|
27
|
+
const {kill, nodes} = await spawnLightningCluster({size});
|
|
28
|
+
|
|
29
|
+
const [{generate, id, lnd}, target] = nodes;
|
|
30
|
+
|
|
31
|
+
const {keys} = await getMasterPublicKeys({lnd});
|
|
32
|
+
|
|
33
|
+
// Exit early when taproot is not supported
|
|
34
|
+
if (!keys.find(n => n.derivation_path === taprootDerivationPath)) {
|
|
35
|
+
await kill({});
|
|
36
|
+
|
|
37
|
+
return end();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
await generate({count: maturity});
|
|
42
|
+
|
|
43
|
+
// Setup a channel between the nodes
|
|
44
|
+
{
|
|
45
|
+
// Make a channel
|
|
46
|
+
await asyncRetry({interval, times}, async () => {
|
|
47
|
+
await openChannel({
|
|
48
|
+
lnd,
|
|
49
|
+
local_tokens: capacity,
|
|
50
|
+
partner_public_key: target.id,
|
|
51
|
+
partner_socket: target.socket,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await asyncRetry({interval, times}, async () => {
|
|
56
|
+
const {channels} = await getChannels({lnd, is_actve: true});
|
|
57
|
+
|
|
58
|
+
if (!!channels.length) {
|
|
59
|
+
return channels;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await generate({});
|
|
63
|
+
|
|
64
|
+
await sendToChainAddress({
|
|
65
|
+
lnd,
|
|
66
|
+
address: (await createChainAddress({lnd: target.lnd})).address,
|
|
67
|
+
tokens: capacity,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const balance = await getChannelBalance({lnd});
|
|
71
|
+
|
|
72
|
+
if (!balance.channel_balance) {
|
|
73
|
+
throw new Error('WaitingForChannelBalance');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw new Error('WaitingForChannelOpen');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await target.generate({count: maturity});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Collect request messages
|
|
83
|
+
const messages = [];
|
|
84
|
+
|
|
85
|
+
// Make a swap out request
|
|
86
|
+
await requestSwapOut({
|
|
87
|
+
lnd,
|
|
88
|
+
ask: async (args, cbk) => {
|
|
89
|
+
if (args.name === 'tokens') {
|
|
90
|
+
return cbk({tokens: '10000'});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (args.name === 'rate') {
|
|
94
|
+
return cbk({rate: '10'});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const swapRequest = messages.find(n => !!n.swap_request);
|
|
98
|
+
|
|
99
|
+
if (args.name === 'response') {
|
|
100
|
+
return await respondToSwapOut({
|
|
101
|
+
ask: (args, cbk) => {
|
|
102
|
+
if (args.name === 'rate') {
|
|
103
|
+
return cbk({rate: '100'});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (args.name === 'req') {
|
|
107
|
+
return cbk({req: swapRequest.swap_request});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
throw new Error('UnrecognizedQueryForResponse');
|
|
111
|
+
},
|
|
112
|
+
lnd: target.lnd,
|
|
113
|
+
logger: {
|
|
114
|
+
info: async message => {
|
|
115
|
+
if (!!message.response) {
|
|
116
|
+
return cbk({response: message.response});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Transaction is funded, generate funding into a block
|
|
120
|
+
if (!!message.funding_transaction_id) {
|
|
121
|
+
return await target.generate({count});
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (args.name === 'ok') {
|
|
129
|
+
return cbk({ok: true});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
throw new Error('UnrecognizedQueryForRequest');
|
|
133
|
+
},
|
|
134
|
+
is_uncooperative: true,
|
|
135
|
+
logger: {
|
|
136
|
+
info: async message => {
|
|
137
|
+
if (message.broadcasting_tx_to_resolve_swap) {
|
|
138
|
+
await generate({count});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return messages.push(message);
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
} catch (err) {
|
|
146
|
+
strictSame(err, null, 'Expected no failure');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await kill({});
|
|
150
|
+
|
|
151
|
+
return end();
|
|
152
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
const asyncRetry = require('async/retry');
|
|
2
|
+
const {createChainAddress} = require('ln-service');
|
|
3
|
+
const {createInvoice} = require('ln-service');
|
|
4
|
+
const {getChannelBalance} = require('ln-service');
|
|
5
|
+
const {getChannels} = require('ln-service');
|
|
6
|
+
const {getMasterPublicKeys} = require('ln-service');
|
|
7
|
+
const {openChannel} = require('ln-service');
|
|
8
|
+
const {pay} = require('ln-service');
|
|
9
|
+
const {sendToChainAddress} = require('ln-service');
|
|
10
|
+
const {spawnLightningCluster} = require('ln-docker-daemons');
|
|
11
|
+
const {test} = require('@alexbosworth/tap');
|
|
12
|
+
|
|
13
|
+
const requestSwapOut = require('./../../swaps/request_swap_out');
|
|
14
|
+
const respondToSwapOut = require('./../../swaps/respond_to_swap_out_request');
|
|
15
|
+
|
|
16
|
+
const capacity = 1e6;
|
|
17
|
+
const count = 50;
|
|
18
|
+
const interval = 100;
|
|
19
|
+
const maturity = 100;
|
|
20
|
+
const size = 2;
|
|
21
|
+
const taprootDerivationPath = `m/86'/0'/0'`;
|
|
22
|
+
const times = 3000;
|
|
23
|
+
const tokens = 1e5;
|
|
24
|
+
|
|
25
|
+
// Start an offchain swap but do not cooperate and eventually force a refund
|
|
26
|
+
test(`Timeout a swap`, async ({end, equal, strictSame}) => {
|
|
27
|
+
const {kill, nodes} = await spawnLightningCluster({size});
|
|
28
|
+
|
|
29
|
+
const [{generate, id, lnd}, target] = nodes;
|
|
30
|
+
|
|
31
|
+
const {keys} = await getMasterPublicKeys({lnd});
|
|
32
|
+
|
|
33
|
+
// Exit early when taproot is not supported
|
|
34
|
+
if (!keys.find(n => n.derivation_path === taprootDerivationPath)) {
|
|
35
|
+
await kill({});
|
|
36
|
+
|
|
37
|
+
return end();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
await generate({count: maturity});
|
|
42
|
+
|
|
43
|
+
// Setup a channel between the nodes
|
|
44
|
+
{
|
|
45
|
+
// Make a channel
|
|
46
|
+
await asyncRetry({interval, times}, async () => {
|
|
47
|
+
await openChannel({
|
|
48
|
+
lnd,
|
|
49
|
+
local_tokens: capacity,
|
|
50
|
+
partner_public_key: target.id,
|
|
51
|
+
partner_socket: target.socket,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await asyncRetry({interval, times}, async () => {
|
|
56
|
+
const {channels} = await getChannels({lnd, is_actve: true});
|
|
57
|
+
|
|
58
|
+
if (!!channels.length) {
|
|
59
|
+
return channels;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await generate({});
|
|
63
|
+
|
|
64
|
+
await sendToChainAddress({
|
|
65
|
+
lnd,
|
|
66
|
+
address: (await createChainAddress({lnd: target.lnd})).address,
|
|
67
|
+
tokens: capacity,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const balance = await getChannelBalance({lnd});
|
|
71
|
+
|
|
72
|
+
if (!balance.channel_balance) {
|
|
73
|
+
throw new Error('WaitingForChannelBalance');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
throw new Error('WaitingForChannelOpen');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await target.generate({count: maturity});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Collect request messages
|
|
83
|
+
const messages = [];
|
|
84
|
+
|
|
85
|
+
// Make a swap out request
|
|
86
|
+
await requestSwapOut({
|
|
87
|
+
lnd,
|
|
88
|
+
ask: async (args, cbk) => {
|
|
89
|
+
if (args.name === 'tokens') {
|
|
90
|
+
return cbk({tokens: '10000'});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (args.name === 'rate') {
|
|
94
|
+
return cbk({rate: '10'});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const swapRequest = messages.find(n => !!n.swap_request);
|
|
98
|
+
|
|
99
|
+
if (args.name === 'response') {
|
|
100
|
+
try {
|
|
101
|
+
return await respondToSwapOut({
|
|
102
|
+
ask: (args, cbk) => {
|
|
103
|
+
if (args.name === 'rate') {
|
|
104
|
+
return cbk({rate: '100'});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (args.name === 'req') {
|
|
108
|
+
return cbk({req: swapRequest.swap_request});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
throw new Error('UnrecognizedQueryForResponse');
|
|
112
|
+
},
|
|
113
|
+
lnd: target.lnd,
|
|
114
|
+
logger: {
|
|
115
|
+
info: async message => {
|
|
116
|
+
// Push chain forward to timeout
|
|
117
|
+
if (!!message.blocks_until_timeout) {
|
|
118
|
+
return await target.generate({});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!!message.response) {
|
|
122
|
+
return cbk({response: message.response});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Transaction is funded, generate funding into a block
|
|
126
|
+
if (!!message.funding_transaction_id) {
|
|
127
|
+
return await target.generate({count});
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
} catch (err) {
|
|
133
|
+
strictSame(err, [503, 'SwapFailedViaTimeout']);
|
|
134
|
+
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (args.name === 'ok') {
|
|
140
|
+
return cbk({ok: true});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
throw new Error('UnrecognizedQueryForRequest');
|
|
144
|
+
},
|
|
145
|
+
is_avoiding_broadcast: true,
|
|
146
|
+
is_uncooperative: true,
|
|
147
|
+
logger: {
|
|
148
|
+
info: async message => {
|
|
149
|
+
if (message.broadcasting_tx_to_resolve_swap) {
|
|
150
|
+
await generate({count});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return messages.push(message);
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
} catch (err) {
|
|
158
|
+
strictSame(err, null, 'Expected no failure');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await kill({});
|
|
162
|
+
|
|
163
|
+
return end();
|
|
164
|
+
});
|