ln-service 53.5.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
CHANGED
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
|
|
@@ -3392,6 +3393,46 @@ const {parsePaymentRequest} = require('ln-service');
|
|
|
3392
3393
|
const requestDetails = parsePaymentRequest({request: 'paymentRequestString'});
|
|
3393
3394
|
```
|
|
3394
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
|
+
|
|
3395
3436
|
### pay
|
|
3396
3437
|
|
|
3397
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.4.
|
|
14
|
+
"lightning": "5.4.1",
|
|
15
15
|
"macaroon": "3.0.4",
|
|
16
16
|
"morgan": "1.10.0",
|
|
17
17
|
"ws": "8.4.2"
|
|
@@ -26,9 +26,9 @@
|
|
|
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
34
|
"tiny-secp256k1": "2.2.0",
|
|
@@ -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
|
}
|
|
@@ -31,7 +31,7 @@ const timeout = 1000 * 5;
|
|
|
31
31
|
const times = 200;
|
|
32
32
|
|
|
33
33
|
// Forfeiting a pending channel should remove the pending channel
|
|
34
|
-
test(`Forfeit pending channel`, async ({end, equal}) => {
|
|
34
|
+
test(`Forfeit pending channel`, async ({end, equal, strictSame}) => {
|
|
35
35
|
const {kill, nodes} = await spawnLightningCluster({size});
|
|
36
36
|
|
|
37
37
|
const [control, target, remote] = nodes;
|
|
@@ -136,16 +136,20 @@ test(`Forfeit pending channel`, async ({end, equal}) => {
|
|
|
136
136
|
equal(code, 503, 'Pending channel cannot be canceled');
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
try {
|
|
140
|
+
await deletePendingChannel({
|
|
141
|
+
lnd,
|
|
142
|
+
confirmed_transaction: transaction,
|
|
143
|
+
pending_transaction: stuckTx.transaction,
|
|
144
|
+
pending_transaction_vout: pending.transaction_vout,
|
|
145
|
+
});
|
|
145
146
|
|
|
146
|
-
|
|
147
|
+
const [notPending] = (await getPendingChannels({lnd})).pending_channels;
|
|
147
148
|
|
|
148
|
-
|
|
149
|
+
equal(notPending, undefined, 'Conflicting pending channel deleted');
|
|
150
|
+
} catch (err) {
|
|
151
|
+
strictSame(err, [501, 'DeletePendingChannelMethodNotSupported']);
|
|
152
|
+
}
|
|
149
153
|
} catch (err) {
|
|
150
154
|
equal(err, null, 'No error is expected');
|
|
151
155
|
} finally {
|
|
@@ -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');
|
|
@@ -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
|
+
});
|