ln-service 53.4.2 → 53.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +44 -0
- package/index.js +2 -0
- package/package.json +15 -5
- package/test/integration/test_delete_pending_channel.js +26 -10
- package/test/integration/test_get_network_centrality.js +42 -34
- package/test/integration/test_get_pending_force.js +8 -1
- package/test/tower_clientrpc-integration/test_disconnect_watchtower.js +2 -2
- package/test/walletrpc-integration/test_partially_sign_psbt.js +209 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 53.6.0
|
|
4
|
+
|
|
5
|
+
- `partiallySignPsbt`: Add method to add a partial signature to a PSBT
|
|
6
|
+
|
|
7
|
+
## 53.5.2
|
|
8
|
+
|
|
9
|
+
- `getPayments`: Correct paging issue that prevented paging through all results
|
|
10
|
+
|
|
11
|
+
## 53.5.0
|
|
12
|
+
|
|
13
|
+
- `createWallet`: Add support for returning the admin `macaroon`
|
|
14
|
+
|
|
3
15
|
## 53.4.2
|
|
4
16
|
|
|
5
17
|
- `pay`, `payViaPaymentRequest`: Fix support for `outgoing_channels` constraint
|
package/README.md
CHANGED
|
@@ -187,6 +187,7 @@ for `unlocker` methods.
|
|
|
187
187
|
- [openChannel](#openchannel) - Open a new channel
|
|
188
188
|
- [openChannels](#openchannels) - Open channels with external funding
|
|
189
189
|
- [parsePaymentRequest](#parsepaymentrequest) - Parse a BOLT11 Payment Request
|
|
190
|
+
- [partiallySignPsbt](#partiallysignpsbt) - Sign a PSBT without finalizing it
|
|
190
191
|
- [pay](#pay) - Send a payment
|
|
191
192
|
- [payViaPaymentDetails](#payviapaymentdetails) - Pay using decomposed details
|
|
192
193
|
- [payViaPaymentRequest](#payviapaymentrequest) - Pay using a payment request
|
|
@@ -762,6 +763,9 @@ Requires unlocked lnd and unauthenticated LND API Object
|
|
|
762
763
|
}
|
|
763
764
|
|
|
764
765
|
@returns via cbk or Promise
|
|
766
|
+
{
|
|
767
|
+
macaroon: <Base64 Encoded Admin Macaroon String>
|
|
768
|
+
}
|
|
765
769
|
|
|
766
770
|
Example:
|
|
767
771
|
|
|
@@ -3389,6 +3393,46 @@ const {parsePaymentRequest} = require('ln-service');
|
|
|
3389
3393
|
const requestDetails = parsePaymentRequest({request: 'paymentRequestString'});
|
|
3390
3394
|
```
|
|
3391
3395
|
|
|
3396
|
+
### partiallySignPsbt
|
|
3397
|
+
|
|
3398
|
+
Sign a PSBT to produce a partially signed PSBT
|
|
3399
|
+
|
|
3400
|
+
Requires `onchain:write` permission
|
|
3401
|
+
|
|
3402
|
+
Requires LND built with `walletrpc` tag
|
|
3403
|
+
|
|
3404
|
+
This method is not supported in LND 0.14.1 and below
|
|
3405
|
+
|
|
3406
|
+
{
|
|
3407
|
+
lnd: <Authenticated LND API Object>
|
|
3408
|
+
psbt: <Funded PSBT Hex String>
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
@returns via cbk or Promise
|
|
3412
|
+
{
|
|
3413
|
+
psbt: <Partially Signed PSBT Hex String>
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3416
|
+
|
|
3417
|
+
Example:
|
|
3418
|
+
|
|
3419
|
+
```node
|
|
3420
|
+
const {broadcastChainTransaction} = require('ln-service');
|
|
3421
|
+
const {fundPsbt, partiallySignPsbt} = require('ln-service');
|
|
3422
|
+
const {extractTransaction, finalizePsbt} = require('psbt');
|
|
3423
|
+
|
|
3424
|
+
const funding = await fundPsbt({
|
|
3425
|
+
lnd,
|
|
3426
|
+
outputs: [{address: chainAddress, tokens: 100000}]
|
|
3427
|
+
})
|
|
3428
|
+
|
|
3429
|
+
const finalize = finalizePsbt({psbt: funding.psbt});
|
|
3430
|
+
|
|
3431
|
+
const {transaction} = extractTransaction({psbt: finalize.psbt});
|
|
3432
|
+
|
|
3433
|
+
await broadcastChainTransaction({lnd, transaction});
|
|
3434
|
+
```
|
|
3435
|
+
|
|
3392
3436
|
### pay
|
|
3393
3437
|
|
|
3394
3438
|
Make a payment.
|
package/index.js
CHANGED
|
@@ -76,6 +76,7 @@ const {lockUtxo} = require('lightning');
|
|
|
76
76
|
const {openChannel} = require('lightning');
|
|
77
77
|
const {openChannels} = require('lightning');
|
|
78
78
|
const {parsePaymentRequest} = require('invoices');
|
|
79
|
+
const {partiallySignPsbt} = require('lightning');
|
|
79
80
|
const {pay} = require('lightning');
|
|
80
81
|
const {payViaPaymentDetails} = require('lightning');
|
|
81
82
|
const {payViaPaymentRequest} = require('lightning');
|
|
@@ -215,6 +216,7 @@ module.exports = {
|
|
|
215
216
|
openChannel,
|
|
216
217
|
openChannels,
|
|
217
218
|
parsePaymentRequest,
|
|
219
|
+
partiallySignPsbt,
|
|
218
220
|
pay,
|
|
219
221
|
payViaPaymentDetails,
|
|
220
222
|
payViaPaymentRequest,
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"cors": "2.8.5",
|
|
12
12
|
"express": "4.17.2",
|
|
13
13
|
"invoices": "2.0.3",
|
|
14
|
-
"lightning": "5.
|
|
14
|
+
"lightning": "5.4.1",
|
|
15
15
|
"macaroon": "3.0.4",
|
|
16
16
|
"morgan": "1.10.0",
|
|
17
17
|
"ws": "8.4.2"
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"bitcoinjs-lib": "6.0.1",
|
|
27
27
|
"bn.js": "5.2.0",
|
|
28
28
|
"ecpair": "2.0.1",
|
|
29
|
-
"ln-docker-daemons": "2.2.
|
|
29
|
+
"ln-docker-daemons": "2.2.1",
|
|
30
30
|
"portfinder": "1.0.28",
|
|
31
|
-
"psbt": "1.1.
|
|
31
|
+
"psbt": "1.1.11",
|
|
32
32
|
"rimraf": "3.0.2",
|
|
33
33
|
"secp256k1": "4.0.3",
|
|
34
|
-
"tiny-secp256k1": "2.
|
|
34
|
+
"tiny-secp256k1": "2.2.0",
|
|
35
35
|
"uuid": "8.3.2",
|
|
36
36
|
"varuint-bitcoin": "1.1.2"
|
|
37
37
|
},
|
|
@@ -53,7 +53,17 @@
|
|
|
53
53
|
"url": "https://github.com/alexbosworth/ln-service.git"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
|
+
"integration-test-daily-lnd-build": "DOCKER_LND_VERSION=daily-testing-only npm run test",
|
|
57
|
+
"integration-test-0.14.1": "DOCKER_LND_VERSION=v0.14.1-beta npm run test",
|
|
58
|
+
"integration-test-0.14.0": "DOCKER_LND_VERSION=v0.14.0-beta npm run test",
|
|
59
|
+
"integration-test-0.13.4": "DOCKER_LND_VERSION=v0.13.4-beta npm run test",
|
|
60
|
+
"integration-test-0.13.3": "DOCKER_LND_VERSION=v0.13.3-beta npm run test",
|
|
61
|
+
"integration-test-0.13.2": "DOCKER_LND_VERSION=v0.13.2-beta npm run test",
|
|
62
|
+
"integration-test-0.13.1": "DOCKER_LND_VERSION=v0.13.1-beta npm run test",
|
|
63
|
+
"integration-test-0.13.0": "DOCKER_LND_VERSION=v0.13.0-beta npm run test",
|
|
64
|
+
"integration-test-0.12.1": "DOCKER_LND_VERSION=v0.12.1-beta npm run test",
|
|
65
|
+
"integration-test-0.12.0": "DOCKER_LND_VERSION=v0.12.0-beta npm run test",
|
|
56
66
|
"test": "echo $DOCKER_LND_VERSION && tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 200 test/autopilotrpc-integration/*.js test/chainrpc-integration/*.js test/integration/*.js test/invoicesrpc-integration/*.js test/routerrpc-integration/*.js test/signerrpc-integration/*.js test/tower_clientrpc-integration/*.js test/tower_serverrpc-integration/*.js test/walletrpc-integration/*.js"
|
|
57
67
|
},
|
|
58
|
-
"version": "53.
|
|
68
|
+
"version": "53.6.0"
|
|
59
69
|
}
|
|
@@ -8,6 +8,7 @@ const {test} = require('@alexbosworth/tap');
|
|
|
8
8
|
|
|
9
9
|
const {addPeer} = require('./../../');
|
|
10
10
|
const {broadcastChainTransaction} = require('./../../');
|
|
11
|
+
const {cancelPendingChannel} = require('./../../');
|
|
11
12
|
const {createCluster} = require('./../macros');
|
|
12
13
|
const {delay} = require('./../macros');
|
|
13
14
|
const {deletePendingChannel} = require('./../../');
|
|
@@ -30,7 +31,7 @@ const timeout = 1000 * 5;
|
|
|
30
31
|
const times = 200;
|
|
31
32
|
|
|
32
33
|
// Forfeiting a pending channel should remove the pending channel
|
|
33
|
-
test(`Forfeit pending channel`, async ({end, equal}) => {
|
|
34
|
+
test(`Forfeit pending channel`, async ({end, equal, strictSame}) => {
|
|
34
35
|
const {kill, nodes} = await spawnLightningCluster({size});
|
|
35
36
|
|
|
36
37
|
const [control, target, remote] = nodes;
|
|
@@ -60,7 +61,7 @@ test(`Forfeit pending channel`, async ({end, equal}) => {
|
|
|
60
61
|
// Sign the funding to the target
|
|
61
62
|
const signTarget = await signPsbt({lnd, psbt: fundTarget.psbt});
|
|
62
63
|
|
|
63
|
-
// Fund the target channel
|
|
64
|
+
// Fund the target channel that will get stuck
|
|
64
65
|
await fundPendingChannels({
|
|
65
66
|
lnd,
|
|
66
67
|
channels: proposeToTarget.pending.map(n => n.id),
|
|
@@ -124,16 +125,31 @@ test(`Forfeit pending channel`, async ({end, equal}) => {
|
|
|
124
125
|
|
|
125
126
|
const stuckTx = extractTransaction({psbt: signTarget.psbt});
|
|
126
127
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
})
|
|
128
|
+
const [stuckPending] = proposeToTarget.pending;
|
|
129
|
+
|
|
130
|
+
// Try to cancel the pending channel, it will fail
|
|
131
|
+
try {
|
|
132
|
+
await cancelPendingChannel({lnd, id: stuckPending.id});
|
|
133
|
+
} catch (err) {
|
|
134
|
+
const [code] = err;
|
|
135
|
+
|
|
136
|
+
equal(code, 503, 'Pending channel cannot be canceled');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
await deletePendingChannel({
|
|
141
|
+
lnd,
|
|
142
|
+
confirmed_transaction: transaction,
|
|
143
|
+
pending_transaction: stuckTx.transaction,
|
|
144
|
+
pending_transaction_vout: pending.transaction_vout,
|
|
145
|
+
});
|
|
133
146
|
|
|
134
|
-
|
|
147
|
+
const [notPending] = (await getPendingChannels({lnd})).pending_channels;
|
|
135
148
|
|
|
136
|
-
|
|
149
|
+
equal(notPending, undefined, 'Conflicting pending channel deleted');
|
|
150
|
+
} catch (err) {
|
|
151
|
+
strictSame(err, [501, 'DeletePendingChannelMethodNotSupported']);
|
|
152
|
+
}
|
|
137
153
|
} catch (err) {
|
|
138
154
|
equal(err, null, 'No error is expected');
|
|
139
155
|
} finally {
|
|
@@ -9,7 +9,7 @@ const {setupChannel} = require('./../macros');
|
|
|
9
9
|
|
|
10
10
|
const interval = 100;
|
|
11
11
|
const size = 3;
|
|
12
|
-
const times =
|
|
12
|
+
const times = 800;
|
|
13
13
|
|
|
14
14
|
// Getting the network centrality should return the centrality scores
|
|
15
15
|
test(`Get network centrality`, async ({end, equal, strictSame}) => {
|
|
@@ -19,42 +19,50 @@ test(`Get network centrality`, async ({end, equal, strictSame}) => {
|
|
|
19
19
|
|
|
20
20
|
const {lnd} = control;
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
try {
|
|
23
|
+
await control.generate({count: 100});
|
|
23
24
|
|
|
24
|
-
await setupChannel({
|
|
25
|
-
generate: target.generate,
|
|
26
|
-
lnd: target.lnd,
|
|
27
|
-
to: remote,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
await asyncRetry({interval, times}, async () => {
|
|
31
25
|
await addPeer({lnd, public_key: remote.id, socket: remote.socket});
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
27
|
+
await setupChannel({lnd, generate: control.generate, to: target});
|
|
28
|
+
|
|
29
|
+
await setupChannel({
|
|
30
|
+
generate: target.generate,
|
|
31
|
+
lnd: target.lnd,
|
|
32
|
+
to: remote,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await asyncRetry({interval, times}, async () => {
|
|
36
|
+
await addPeer({lnd, public_key: remote.id, socket: remote.socket});
|
|
37
|
+
|
|
38
|
+
const {nodes} = await getNetworkCentrality({lnd});
|
|
39
|
+
|
|
40
|
+
const controlScore = nodes.find(n => n.public_key === control.id);
|
|
41
|
+
const remoteScore = nodes.find(n => n.public_key === remote.id);
|
|
42
|
+
const targetScore = nodes.find(n => n.public_key === target.id);
|
|
43
|
+
|
|
44
|
+
if (!targetScore.betweenness || !targetScore.betweenness_normalized) {
|
|
45
|
+
throw new Error('UnexpectedValueForTargetScoreBetweenness');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (targetScore.betweenness !== 1e6) {
|
|
49
|
+
throw new Error('WrongBetweennessScore');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
equal(controlScore.betweenness, 0, 'No centrality on control');
|
|
53
|
+
equal(controlScore.betweenness_normalized, 0, 'No control centrality');
|
|
54
|
+
equal(remoteScore.betweenness, 0, 'No centrality on remote');
|
|
55
|
+
equal(remoteScore.betweenness_normalized, 0, 'No centrality on remote');
|
|
56
|
+
equal(targetScore.betweenness, 1e6, 'Centrality around target');
|
|
57
|
+
equal(targetScore.betweenness_normalized, 1e6, 'Centrality at target');
|
|
58
|
+
|
|
59
|
+
return;
|
|
60
|
+
});
|
|
61
|
+
} catch (err) {
|
|
62
|
+
equal(err, null, 'Expected no error');
|
|
63
|
+
} finally {
|
|
64
|
+
await kill({});
|
|
65
|
+
}
|
|
58
66
|
|
|
59
67
|
return end();
|
|
60
68
|
});
|
|
@@ -177,7 +177,14 @@ test(`Get pending channels`, async ({end, equal}) => {
|
|
|
177
177
|
equal(forceClose.partner_public_key, target.id, 'pk');
|
|
178
178
|
equal(forceClose.received, 0, 'No receive amount');
|
|
179
179
|
equal(forceClose.recovered_tokens, undefined, 'No recovered amount');
|
|
180
|
-
|
|
180
|
+
|
|
181
|
+
// LND 0.14.1 and below do not support remote balance info
|
|
182
|
+
if (!!forceClose.remote_balance) {
|
|
183
|
+
equal(forceClose.remote_balance, giftTokens, 'Got gift remote balance');
|
|
184
|
+
} else {
|
|
185
|
+
equal(forceClose.remote_balance, 0, 'No remote balance');
|
|
186
|
+
}
|
|
187
|
+
|
|
181
188
|
equal(forceClose.sent, 0, 'No sent amount');
|
|
182
189
|
equal(!!forceClose.timelock_blocks, true, 'Timelock blocks set');
|
|
183
190
|
equal(forceClose.timelock_expiration, startHeight + 265, 'Funds timelock');
|
|
@@ -9,9 +9,9 @@ const {spawnLnd} = require('./../macros');
|
|
|
9
9
|
const {waitForTermination} = require('./../macros');
|
|
10
10
|
|
|
11
11
|
const all = promise => Promise.all(promise);
|
|
12
|
-
const interval =
|
|
12
|
+
const interval = 200;
|
|
13
13
|
const nodes = [{watchers: true}, {tower: true}];
|
|
14
|
-
const times =
|
|
14
|
+
const times = 1000;
|
|
15
15
|
|
|
16
16
|
// Disconnecting a watchtower should remove a watchtower
|
|
17
17
|
test(`Disconnect watchtower`, async ({end, equal, match}) => {
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
const asyncEach = require('async/each');
|
|
2
|
+
const asyncMap = require('async/map');
|
|
3
|
+
const asyncRetry = require('async/retry');
|
|
4
|
+
const {combinePsbts} = require('psbt');
|
|
5
|
+
const {createPsbt} = require('psbt');
|
|
6
|
+
const {decodePsbt} = require('psbt');
|
|
7
|
+
const {extractTransaction} = require('psbt');
|
|
8
|
+
const {finalizePsbt} = require('psbt');
|
|
9
|
+
const {spawnLightningCluster} = require('ln-docker-daemons');
|
|
10
|
+
const {test} = require('@alexbosworth/tap');
|
|
11
|
+
const {Transaction} = require('bitcoinjs-lib');
|
|
12
|
+
const {updatePsbt} = require('psbt');
|
|
13
|
+
|
|
14
|
+
const {broadcastChainTransaction} = require('./../../');
|
|
15
|
+
const {createChainAddress} = require('./../../');
|
|
16
|
+
const {fundPsbt} = require('./../../');
|
|
17
|
+
const {getChainBalance} = require('./../../');
|
|
18
|
+
const {getChainTransactions} = require('./../../');
|
|
19
|
+
const {getUtxos} = require('./../../');
|
|
20
|
+
const {partiallySignPsbt} = require('./../../');
|
|
21
|
+
const {sendToChainAddresses} = require('./../../');
|
|
22
|
+
|
|
23
|
+
const count = 150;
|
|
24
|
+
const flatten = arr => [].concat(...arr);
|
|
25
|
+
const interval = 10;
|
|
26
|
+
const size = 3;
|
|
27
|
+
const startingFunds = 1e7;
|
|
28
|
+
const times = 1000;
|
|
29
|
+
const tokens = 1e6;
|
|
30
|
+
|
|
31
|
+
// Partially signing a PSBT should result in a partially signed PSBT
|
|
32
|
+
test(`Partially sign PSBT`, async ({end, equal, strictSame}) => {
|
|
33
|
+
const {kill, nodes} = await spawnLightningCluster({size});
|
|
34
|
+
|
|
35
|
+
const [control, target, remote] = nodes;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
// Generate some funds for the control
|
|
39
|
+
await control.generate({count});
|
|
40
|
+
|
|
41
|
+
// Send starting coins to target and remote
|
|
42
|
+
await sendToChainAddresses({
|
|
43
|
+
lnd: control.lnd,
|
|
44
|
+
send_to: await asyncMap([target, remote], async ({lnd}) => {
|
|
45
|
+
return {
|
|
46
|
+
address: (await createChainAddress({lnd})).address,
|
|
47
|
+
tokens: startingFunds,
|
|
48
|
+
};
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Make sure that the nodes have funds
|
|
53
|
+
await asyncRetry({interval, times}, async () => {
|
|
54
|
+
await asyncEach([control, target, remote], async ({lnd}) => {
|
|
55
|
+
await control.generate({});
|
|
56
|
+
|
|
57
|
+
if (!(await getChainBalance({lnd})).chain_balance) {
|
|
58
|
+
throw new Error('WaitingForChainBalance');
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Get UTXOs to spend
|
|
64
|
+
const [controlUtxo] = (await getUtxos({lnd: control.lnd})).utxos.reverse();
|
|
65
|
+
const [targetUtxo] = (await getUtxos({lnd: target.lnd})).utxos;
|
|
66
|
+
const [remoteUtxo] = (await getUtxos({lnd: remote.lnd})).utxos;
|
|
67
|
+
|
|
68
|
+
// Make addresses to spend to
|
|
69
|
+
const addresses = await asyncMap(nodes, async ({lnd}) => {
|
|
70
|
+
return await createChainAddress({lnd});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const [controlAddress, targetAddress, remoteAddress] = addresses;
|
|
74
|
+
|
|
75
|
+
// Fund the addresses using the selected UTXOs
|
|
76
|
+
const controlFund = await fundPsbt({
|
|
77
|
+
inputs: [{
|
|
78
|
+
transaction_id: controlUtxo.transaction_id,
|
|
79
|
+
transaction_vout: controlUtxo.transaction_vout,
|
|
80
|
+
}],
|
|
81
|
+
lnd: control.lnd,
|
|
82
|
+
outputs: [{tokens, address: controlAddress.address}],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const targetFund = await fundPsbt({
|
|
86
|
+
inputs: [{
|
|
87
|
+
transaction_id: targetUtxo.transaction_id,
|
|
88
|
+
transaction_vout: targetUtxo.transaction_vout,
|
|
89
|
+
}],
|
|
90
|
+
lnd: target.lnd,
|
|
91
|
+
outputs: [{tokens, address: targetAddress.address}],
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const remoteFund = await fundPsbt({
|
|
95
|
+
inputs: [{
|
|
96
|
+
transaction_id: remoteUtxo.transaction_id,
|
|
97
|
+
transaction_vout: remoteUtxo.transaction_vout,
|
|
98
|
+
}],
|
|
99
|
+
lnd: remote.lnd,
|
|
100
|
+
outputs: [{tokens, address: remoteAddress.address}],
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Collect all inputs that are spending
|
|
104
|
+
const inputs = []
|
|
105
|
+
.concat(controlFund.inputs)
|
|
106
|
+
.concat(targetFund.inputs)
|
|
107
|
+
.concat(remoteFund.inputs);
|
|
108
|
+
|
|
109
|
+
// Collect all outputs being sent to
|
|
110
|
+
const outputs = []
|
|
111
|
+
.concat(controlFund.outputs)
|
|
112
|
+
.concat(targetFund.outputs)
|
|
113
|
+
.concat(remoteFund.outputs);
|
|
114
|
+
|
|
115
|
+
// Make a new PSBT that includes all the outputs and inputs
|
|
116
|
+
const base = createPsbt({
|
|
117
|
+
outputs: outputs.map(output => ({
|
|
118
|
+
script: output.output_script,
|
|
119
|
+
tokens: output.tokens,
|
|
120
|
+
})),
|
|
121
|
+
utxos: inputs.map(input => ({
|
|
122
|
+
id: input.transaction_id,
|
|
123
|
+
vout: input.transaction_vout,
|
|
124
|
+
})),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Decode the PSBTs to get signing key details
|
|
128
|
+
const controlDecoded = decodePsbt({psbt: controlFund.psbt});
|
|
129
|
+
const targetDecoded = decodePsbt({psbt: targetFund.psbt});
|
|
130
|
+
const remoteDecoded = decodePsbt({psbt: remoteFund.psbt});
|
|
131
|
+
|
|
132
|
+
// Collect all the BIP32 derivations
|
|
133
|
+
const controlBip32Derivations = controlDecoded.inputs
|
|
134
|
+
.map(n => n.bip32_derivations);
|
|
135
|
+
|
|
136
|
+
const targetBip32Derivations = targetDecoded.inputs
|
|
137
|
+
.map(n => n.bip32_derivations);
|
|
138
|
+
|
|
139
|
+
const remoteBip32Derivations = remoteDecoded.inputs
|
|
140
|
+
.map(n => n.bip32_derivations);
|
|
141
|
+
|
|
142
|
+
const controlTransactions = controlDecoded.inputs
|
|
143
|
+
.map(n => n.non_witness_utxo);
|
|
144
|
+
|
|
145
|
+
const targetTransactions = targetDecoded.inputs
|
|
146
|
+
.map(n => n.non_witness_utxo);
|
|
147
|
+
|
|
148
|
+
const remoteTransactions = remoteDecoded.inputs
|
|
149
|
+
.map(n => n.non_witness_utxo);
|
|
150
|
+
|
|
151
|
+
const allDerivations = []
|
|
152
|
+
.concat(controlBip32Derivations)
|
|
153
|
+
.concat(targetBip32Derivations)
|
|
154
|
+
.concat(remoteBip32Derivations);
|
|
155
|
+
|
|
156
|
+
const bip32Derivations = flatten(allDerivations);
|
|
157
|
+
|
|
158
|
+
// Update the PSBT so that it has the consolidated details
|
|
159
|
+
const updated = updatePsbt({
|
|
160
|
+
bip32_derivations: bip32Derivations,
|
|
161
|
+
psbt: base.psbt,
|
|
162
|
+
sighashes: inputs.map(input => ({
|
|
163
|
+
id: input.transaction_id,
|
|
164
|
+
sighash: Transaction.SIGHASH_ALL,
|
|
165
|
+
vout: input.transaction_vout,
|
|
166
|
+
})),
|
|
167
|
+
transactions: controlTransactions.concat(targetTransactions),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await partiallySignPsbt({lnd: control.lnd, psbt: updated.psbt});
|
|
171
|
+
|
|
172
|
+
// Sign the PSBT
|
|
173
|
+
const psbts = await asyncMap(nodes, async ({lnd}) => {
|
|
174
|
+
return (await partiallySignPsbt({lnd, psbt: updated.psbt})).psbt;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Finalize the signatures
|
|
178
|
+
const finalize = finalizePsbt({psbt: combinePsbts({psbts}).psbt});
|
|
179
|
+
|
|
180
|
+
// Pull out the transaction
|
|
181
|
+
const {transaction} = extractTransaction({psbt: finalize.psbt});
|
|
182
|
+
|
|
183
|
+
// Publish the transaction
|
|
184
|
+
await broadcastChainTransaction({lnd: control.lnd, transaction});
|
|
185
|
+
|
|
186
|
+
// Make sure that the transaction confirms into a block
|
|
187
|
+
await asyncEach(nodes, async ({lnd}) => {
|
|
188
|
+
return asyncRetry({interval, times}, async () => {
|
|
189
|
+
await control.generate({});
|
|
190
|
+
|
|
191
|
+
const {utxos} = await getUtxos({lnd});
|
|
192
|
+
|
|
193
|
+
const [confirmed] = utxos.filter(n => n.tokens === tokens);
|
|
194
|
+
|
|
195
|
+
if (!confirmed || !confirmed.confirmation_count) {
|
|
196
|
+
throw new Error('ExpectedConfirmedCoin');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
equal(confirmed.tokens, tokens, 'Got confirmed funds');
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
} catch (err) {
|
|
203
|
+
strictSame(err, [501, 'PartiallySignPsbtMethodNotSupported'], 'NoSupport');
|
|
204
|
+
} finally {
|
|
205
|
+
await kill({});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return end();
|
|
209
|
+
});
|