balanceofsatoshis 11.40.0 → 11.44.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 +20 -0
- package/bos +11 -0
- package/commands/api.json +28 -0
- package/lnd/find_record.js +2 -0
- package/network/push_payment.js +16 -2
- package/network/remove_peer.js +113 -6
- package/package.json +3 -3
- package/peers/limit_forwarding.js +31 -3
- package/services/initiate_balanced_channel.js +3 -2
- package/swaps/rebalance.js +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 11.44.0
|
|
4
|
+
|
|
5
|
+
- `send`: Add support for `--max-fee-rate` to limit fees paid via PPM measure
|
|
6
|
+
|
|
7
|
+
## 11.43.0
|
|
8
|
+
|
|
9
|
+
- `limit-forwards`: Add `--min-channel-confirmations` for custom channel ages
|
|
10
|
+
- `limit-forwards`: Add `--only-allow` to restrict forwards to allowed edges
|
|
11
|
+
- `open-balanced-channel`: Disallow fractional fee rate entry
|
|
12
|
+
|
|
13
|
+
## 11.42.0
|
|
14
|
+
|
|
15
|
+
- `call`: Add support for `fundPsbt` to create a funded PSBT ready to sign
|
|
16
|
+
- `call`: Add support for `partiallySignPsbt` to add a partial sig to a PSBT
|
|
17
|
+
- `call`: Add support for `signPsbt` to sign and finalize a PSBT for broadcast
|
|
18
|
+
|
|
19
|
+
## 11.41.0
|
|
20
|
+
|
|
21
|
+
- `remove-peer`: Add interactive mode to select channels to close
|
|
22
|
+
|
|
3
23
|
## 11.40.0
|
|
4
24
|
|
|
5
25
|
- `peers`: Add `DISK_USAGE_MB` filter to `--filter` formulas for est disk usage
|
package/bos
CHANGED
|
@@ -826,10 +826,14 @@ prog
|
|
|
826
826
|
|
|
827
827
|
// Intercept forwarding requests, enforce requirements on acceptance
|
|
828
828
|
.command('limit-forwarding', 'Enforce rules for routing payments')
|
|
829
|
+
.help('Setting --only-allow will disable all forwards except only allowed')
|
|
830
|
+
.help('--only-allow option can be repeated for multiple forwards')
|
|
829
831
|
.option('--node <node_name>', 'Saved node to enforce rules on')
|
|
830
832
|
.option('--disable-forwards', 'Disable all forwards')
|
|
831
833
|
.option('--max-hours-since-last-block <h>', 'Require fresh blocks', INT, 5)
|
|
832
834
|
.option('--max-new-pending-per-hour <h>', 'Limit held HTLCs', INT)
|
|
835
|
+
.option('--min-channel-confirmations <confs>', 'Minimum channel confs', INT)
|
|
836
|
+
.option('--only-allow <pair>', 'only forward fromKey/toKey', REPEATABLE)
|
|
833
837
|
.action((args, options, logger) => {
|
|
834
838
|
return new Promise(async (resolve, reject) => {
|
|
835
839
|
try {
|
|
@@ -839,6 +843,8 @@ prog
|
|
|
839
843
|
lnd: (await lndForNode(logger, options.node)).lnd,
|
|
840
844
|
max_hours_since_last_block: options.maxHoursSinceLastBlock,
|
|
841
845
|
max_new_pending_per_hour: options.maxNewPendingPerHour,
|
|
846
|
+
min_channel_confirmations: options.minChannelConfirmations,
|
|
847
|
+
only_allow: flatten([options.onlyAllow].filter(n => !!n)),
|
|
842
848
|
});
|
|
843
849
|
} catch (err) {
|
|
844
850
|
return reject(logger.error({err}));
|
|
@@ -1396,6 +1402,7 @@ prog
|
|
|
1396
1402
|
.option('--outpoint', 'Only remove specific channel with funding txid:vout')
|
|
1397
1403
|
.option('--private', 'Peer is privately connected')
|
|
1398
1404
|
.option('--public', 'Peer is publicly connected')
|
|
1405
|
+
.option('--select-channels', 'Select channels to remove interactively')
|
|
1399
1406
|
.action((args, options, logger) => {
|
|
1400
1407
|
return new Promise(async (resolve, reject) => {
|
|
1401
1408
|
try {
|
|
@@ -1405,6 +1412,7 @@ prog
|
|
|
1405
1412
|
lnd,
|
|
1406
1413
|
logger,
|
|
1407
1414
|
address: options.address,
|
|
1415
|
+
ask: (n, cbk) => inquirer.prompt([n]).then(n => cbk(n)),
|
|
1408
1416
|
chain_fee_rate: options.feeRate || undefined,
|
|
1409
1417
|
fs: {getFile: readFile},
|
|
1410
1418
|
idle_days: options.idleDays,
|
|
@@ -1415,6 +1423,7 @@ prog
|
|
|
1415
1423
|
is_offline: !!options.offline,
|
|
1416
1424
|
is_private: !!options.private,
|
|
1417
1425
|
is_public: !!options.public,
|
|
1426
|
+
is_selecting_channels: options.selectChannels || undefined,
|
|
1418
1427
|
omit: flatten([options.omit].filter(n => !!n)),
|
|
1419
1428
|
outbound_liquidity_below: options.outboundBelow,
|
|
1420
1429
|
outpoints: flatten([options.outpoint].filter(n => !!n)),
|
|
@@ -1455,6 +1464,7 @@ prog
|
|
|
1455
1464
|
.option('--dryrun', 'Avoid actually sending funds')
|
|
1456
1465
|
.option('--in <pubkey_or_alias>', 'Route in through a specific node')
|
|
1457
1466
|
.option('--max-fee <fee>', 'Maximum fee tokens', INT, 1337)
|
|
1467
|
+
.option('--max-fee-rate <max_fee_rate>', 'Max fee rate in PPM to pay', INT)
|
|
1458
1468
|
.option('--message <message>', 'Message to include with payment')
|
|
1459
1469
|
.option('--message-omit-from-key', 'Leave out the from key on messages')
|
|
1460
1470
|
.option('--no-color', 'Mute all colors')
|
|
@@ -1475,6 +1485,7 @@ prog
|
|
|
1475
1485
|
is_omitting_message_from: options.messageOmitFromKey,
|
|
1476
1486
|
lnd: (await lndForNode(logger, options.node)).lnd,
|
|
1477
1487
|
max_fee: options.maxFee,
|
|
1488
|
+
max_fee_rate: options.maxFeeRate,
|
|
1478
1489
|
message: options.message,
|
|
1479
1490
|
quiz_answers: flatten([options.quiz].filter(n => !!n)),
|
|
1480
1491
|
out_through: options.out,
|
package/commands/api.json
CHANGED
|
@@ -242,6 +242,20 @@
|
|
|
242
242
|
],
|
|
243
243
|
"method": "enableChannel"
|
|
244
244
|
},
|
|
245
|
+
{
|
|
246
|
+
"arguments": [
|
|
247
|
+
{
|
|
248
|
+
"description": "Base PSBT to fund",
|
|
249
|
+
"named": "psbt"
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"description": "Fee rate per vbyte to use for funding",
|
|
253
|
+
"named": "fee_tokens_per_vbyte",
|
|
254
|
+
"optional": true
|
|
255
|
+
}
|
|
256
|
+
],
|
|
257
|
+
"method": "fundPsbt"
|
|
258
|
+
},
|
|
245
259
|
{
|
|
246
260
|
"method": "getAccessIds"
|
|
247
261
|
},
|
|
@@ -582,6 +596,13 @@
|
|
|
582
596
|
],
|
|
583
597
|
"method": "openChannel"
|
|
584
598
|
},
|
|
599
|
+
{
|
|
600
|
+
"arguments": [{
|
|
601
|
+
"description": "Funded PSBT Hex String",
|
|
602
|
+
"named": "psbt"
|
|
603
|
+
}],
|
|
604
|
+
"method": "partiallySignPsbt"
|
|
605
|
+
},
|
|
585
606
|
{
|
|
586
607
|
"arguments": [
|
|
587
608
|
{
|
|
@@ -689,6 +710,13 @@
|
|
|
689
710
|
}],
|
|
690
711
|
"method": "signMessage"
|
|
691
712
|
},
|
|
713
|
+
{
|
|
714
|
+
"arguments": [{
|
|
715
|
+
"description": "Funded PSBT to sign and finalize",
|
|
716
|
+
"named": "psbt"
|
|
717
|
+
}],
|
|
718
|
+
"method": "signPsbt"
|
|
719
|
+
},
|
|
692
720
|
{
|
|
693
721
|
"method": "stopDaemon"
|
|
694
722
|
},
|
package/lnd/find_record.js
CHANGED
|
@@ -236,6 +236,7 @@ module.exports = ({lnd, query}, cbk) => {
|
|
|
236
236
|
.map(chan => {
|
|
237
237
|
const [height] = chan.id.split('x');
|
|
238
238
|
const local = formatTokens({tokens: chan.local_balance});
|
|
239
|
+
const pending = chan.pending_payments;
|
|
239
240
|
const remote = formatTokens({tokens: chan.remote_balance});
|
|
240
241
|
|
|
241
242
|
const inbound = `in: ${balance(remote)}`;
|
|
@@ -248,6 +249,7 @@ module.exports = ({lnd, query}, cbk) => {
|
|
|
248
249
|
capacity: formatTokens({tokens: chan.capacity}).display,
|
|
249
250
|
funding: `${chan.transaction_id} ${chan.transaction_vout}`,
|
|
250
251
|
peer_created: chan.is_partner_initiated || undefined,
|
|
252
|
+
pending_payments: !!pending.length ? pending : undefined,
|
|
251
253
|
};
|
|
252
254
|
}),
|
|
253
255
|
}
|
package/network/push_payment.js
CHANGED
|
@@ -16,6 +16,7 @@ const probeDestination = require('./probe_destination');
|
|
|
16
16
|
|
|
17
17
|
const coins = ['BTC', 'LTC'];
|
|
18
18
|
const defaultFiatRateProvider = 'coingecko';
|
|
19
|
+
const feeTokensForFeeRate = (tokens, rate) => Math.floor(rate * tokens / 1e6);
|
|
19
20
|
const fiats = ['EUR', 'USD'];
|
|
20
21
|
const hasFiat = n => /(eur|usd)/gim.test(n);
|
|
21
22
|
const {isArray} = Array;
|
|
@@ -23,6 +24,7 @@ const isPublicKey = n => /^[0-9A-F]{66}$/i.test(n);
|
|
|
23
24
|
const maxQuizLength = 10;
|
|
24
25
|
const rateAsTokens = rate => 1e8 / rate;
|
|
25
26
|
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
|
|
27
|
+
const {min} = Math;
|
|
26
28
|
const minQuiz = 2;
|
|
27
29
|
const minTokens = 1;
|
|
28
30
|
const networks = {btc: 'BTC', btctestnet: 'BTC', ltc: 'LTC'};
|
|
@@ -45,6 +47,7 @@ const utf8AsHex = n => Buffer.from(n, 'utf8').toString('hex');
|
|
|
45
47
|
lnd: <Authenticated LND API Object>
|
|
46
48
|
logger: <Winston Logger Object>
|
|
47
49
|
max_fee: <Maximum Fee Tokens Number>
|
|
50
|
+
[max_fee_rate]: <Max Fee Rate Tokens Per Million Number>
|
|
48
51
|
[message]: <Message to Include With Payment String>
|
|
49
52
|
[out_through]: <Pay Out Through Peer String>
|
|
50
53
|
quiz_answers: [<Quiz Answer String>]
|
|
@@ -346,7 +349,18 @@ module.exports = (args, cbk) => {
|
|
|
346
349
|
};
|
|
347
350
|
|
|
348
351
|
try {
|
|
349
|
-
|
|
352
|
+
const {tokens} = parseAmount({variables, amount: args.amount});
|
|
353
|
+
|
|
354
|
+
// Exit early when there is no max fee rate to compute max fee for
|
|
355
|
+
if (!!args.max_fee_rate === undefined) {
|
|
356
|
+
return cbk(null, {tokens, max_fee: args.mage_fee});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Compute the max potential fee given the max fee rate constraint
|
|
360
|
+
const maxFeeByRate = feeTokensForFeeRate(tokens, args.max_fee_rate);
|
|
361
|
+
|
|
362
|
+
// The maximum fee to pay is the lower of the fee limit, fee by rate
|
|
363
|
+
return cbk(null, {tokens, max_fee: min(args.max_fee, maxFeeByRate)});
|
|
350
364
|
} catch (err) {
|
|
351
365
|
return cbk([400, 'FailedToParsePushAmount', err]);
|
|
352
366
|
}
|
|
@@ -384,7 +398,7 @@ module.exports = (args, cbk) => {
|
|
|
384
398
|
is_omitting_message_from: args.is_omitting_message_from,
|
|
385
399
|
is_push: payment.is_push,
|
|
386
400
|
is_real_payment: true,
|
|
387
|
-
max_fee:
|
|
401
|
+
max_fee: parseAmount.max_fee,
|
|
388
402
|
message: args.message,
|
|
389
403
|
messages: args.quiz_answers.map((answer, i) => ({
|
|
390
404
|
type: (quizStart + i).toString(),
|
package/network/remove_peer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
2
|
const asyncEachSeries = require('async/eachSeries');
|
|
3
3
|
const {closeChannel} = require('ln-service');
|
|
4
|
+
const {decodeChanId} = require('bolt07');
|
|
4
5
|
const {getChainFeeRate} = require('ln-service');
|
|
5
6
|
const {getChannels} = require('ln-service');
|
|
6
7
|
const {getNetwork} = require('ln-sync');
|
|
@@ -11,20 +12,25 @@ const getPeers = require('./get_peers');
|
|
|
11
12
|
|
|
12
13
|
const arrayWithEntries = arr => !!arr.length ? arr : undefined;
|
|
13
14
|
const asOutpoint = n => `${n.transaction_id}:${n.transaction_vout}`;
|
|
15
|
+
const estimateDisk = n => Math.round(n * 500 / 1e6 * 10) / 10;
|
|
14
16
|
const fastConf = 6;
|
|
15
17
|
const {floor} = Math;
|
|
16
18
|
const defaultDays = 365 * 2;
|
|
17
19
|
const getMempoolRetries = 10;
|
|
20
|
+
const iconDisabled = channel => !channel.is_active ? '💀 ' : '';
|
|
21
|
+
const iconPending = channel => channel.pending_payments.length ? '💸 ' : ''
|
|
18
22
|
const {isArray} = Array;
|
|
19
23
|
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
20
24
|
const maxMempoolSize = 2e6;
|
|
21
25
|
const regularConf = 72;
|
|
22
26
|
const slowConf = 144;
|
|
27
|
+
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
23
28
|
|
|
24
29
|
/** Close out channels with a peer and disconnect them
|
|
25
30
|
|
|
26
31
|
{
|
|
27
32
|
[address]: <Close Out Funds to On-Chain Address String>
|
|
33
|
+
ask: <Ask Function>
|
|
28
34
|
[chain_fee_rate]: <Chain Fee Per VByte Number>
|
|
29
35
|
fs: {
|
|
30
36
|
getFile: <Read File Contents Function> (path, cbk) => {}
|
|
@@ -37,6 +43,7 @@ const slowConf = 144;
|
|
|
37
43
|
[is_offline]: <Peer Is Disconnected Bool>
|
|
38
44
|
[is_private]: <Peer is Privately Connected Bool>
|
|
39
45
|
[is_public]: <Peer is Publicly Connected Bool>
|
|
46
|
+
[is_selecting_channels]: <Interactively Select Channels to Remove Bool>
|
|
40
47
|
lnd: <Authenticated LND API Object>
|
|
41
48
|
logger: <Winston Logger Object>
|
|
42
49
|
[omit]: [<Avoid Peer With Public Key String>]
|
|
@@ -53,10 +60,18 @@ module.exports = (args, cbk) => {
|
|
|
53
60
|
return asyncAuto({
|
|
54
61
|
// Check arguments
|
|
55
62
|
validate: cbk => {
|
|
63
|
+
if (!args.ask) {
|
|
64
|
+
return cbk([400, 'ExpectedAskFunctionToRemovePeer']);
|
|
65
|
+
}
|
|
66
|
+
|
|
56
67
|
if (!args.fs) {
|
|
57
68
|
return cbk([400, 'ExpectedFsMethodsToRemovePeer']);
|
|
58
69
|
}
|
|
59
70
|
|
|
71
|
+
if (!!args.is_selecting_channels && !args.public_key) {
|
|
72
|
+
return cbk([400, 'ExpectedPeerToRemoveWhenSelectingChannels']);
|
|
73
|
+
}
|
|
74
|
+
|
|
60
75
|
if (!args.lnd) {
|
|
61
76
|
return cbk([400, 'LndIsRequiredToRemovePeer']);
|
|
62
77
|
}
|
|
@@ -69,6 +84,10 @@ module.exports = (args, cbk) => {
|
|
|
69
84
|
return cbk([400, 'ExpectedSpecificOutpointsToRemoveFromPeer']);
|
|
70
85
|
}
|
|
71
86
|
|
|
87
|
+
if (!!args.outpoints.length && !args.public_key) {
|
|
88
|
+
return cbk([400, 'ExpectedPeerToRemoteWhenOutpointsSpecified']);
|
|
89
|
+
}
|
|
90
|
+
|
|
72
91
|
if (!!args.public_key && !isPublicKey(args.public_key)) {
|
|
73
92
|
return cbk([400, 'ExpectedPublicKeyOfPeerToRemove']);
|
|
74
93
|
}
|
|
@@ -137,8 +156,94 @@ module.exports = (args, cbk) => {
|
|
|
137
156
|
cbk);
|
|
138
157
|
}],
|
|
139
158
|
|
|
159
|
+
// Determine outpoints to use
|
|
160
|
+
outpoints: ['getChannels', ({getChannels}, cbk) => {
|
|
161
|
+
// Exit early when a peer is not specified
|
|
162
|
+
if (!args.public_key) {
|
|
163
|
+
return cbk(null, []);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const channelsWithPeer = getChannels.channels
|
|
167
|
+
.filter(channel => {
|
|
168
|
+
// Ignore channels that are not the specified public key
|
|
169
|
+
if (channel.partner_public_key !== args.public_key) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
//Return channels with the peer
|
|
174
|
+
return true;
|
|
175
|
+
})
|
|
176
|
+
.sort((a, b) => {
|
|
177
|
+
const heightA = decodeChanId({channel: a.id}).block_height;
|
|
178
|
+
const heightB = decodeChanId({channel: b.id}).block_height;
|
|
179
|
+
|
|
180
|
+
// Sort channels by oldest to newest
|
|
181
|
+
return heightA - heightB;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Exit early when no channels are available
|
|
185
|
+
if (!channelsWithPeer.length) {
|
|
186
|
+
return cbk([404, 'NoChannelsToCloseWithSpecifiedPeer']);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Collect any outpoints that are unable to be cooperatively closed
|
|
190
|
+
const blocked = channelsWithPeer
|
|
191
|
+
.filter(channel => {
|
|
192
|
+
// Channels that are inactive or have HTLCs cannot be coop-closed
|
|
193
|
+
return !channel.is_active || !!channel.pending_payments.length;
|
|
194
|
+
})
|
|
195
|
+
.map(channel => asOutpoint(channel));
|
|
196
|
+
|
|
197
|
+
// Find a directly referenced outpoint that is in the blocked list
|
|
198
|
+
const blockedOutpoint = args.outpoints.find(n => blocked.includes(n));
|
|
199
|
+
|
|
200
|
+
// Make sure we aren't trying to coop close a channel that can't be
|
|
201
|
+
if (!args.is_forced && !!blockedOutpoint) {
|
|
202
|
+
return cbk([400, 'CannotCoopClose', {outpoint: blockedOutpoint}]);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Exit early if not selecting a channel
|
|
206
|
+
if (!args.is_selecting_channels) {
|
|
207
|
+
return cbk(null, args.outpoints);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Interactively select outpoints to close
|
|
211
|
+
return args.ask({
|
|
212
|
+
choices: channelsWithPeer.map(channel => {
|
|
213
|
+
// In closing, channels are identified by their funding outpoint
|
|
214
|
+
const value = asOutpoint(channel);
|
|
215
|
+
|
|
216
|
+
// Channels that are inactive or have HTLCs cannot be coop-closed
|
|
217
|
+
const isBlocked = blocked.includes(value);
|
|
218
|
+
|
|
219
|
+
const disk = `Est disk mb: ${estimateDisk(channel.past_states)}`;
|
|
220
|
+
const icon = iconDisabled(channel) || iconPending(channel);
|
|
221
|
+
const {id} = channel;
|
|
222
|
+
const inbound = `in: ${tokensAsBigUnit(channel.remote_balance)}`;
|
|
223
|
+
const outbound = `out: ${tokensAsBigUnit(channel.local_balance)}`;
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
value,
|
|
227
|
+
checked: args.outpoints.includes(value),
|
|
228
|
+
disabled: !args.is_forced ? isBlocked : false,
|
|
229
|
+
name: `${icon}${id}: ${inbound} | ${outbound}. ${disk}.`,
|
|
230
|
+
};
|
|
231
|
+
}),
|
|
232
|
+
loop: false,
|
|
233
|
+
message: `Channels to ${!!args.is_forced ? 'force ' : ''}close?`,
|
|
234
|
+
name: 'outpoints',
|
|
235
|
+
type: 'checkbox',
|
|
236
|
+
validate: input => !!input.length,
|
|
237
|
+
},
|
|
238
|
+
({outpoints}) => cbk(null, outpoints));
|
|
239
|
+
}],
|
|
240
|
+
|
|
140
241
|
// Check channels for peer to make sure that they can be cleanly closed
|
|
141
|
-
checkChannels: [
|
|
242
|
+
checkChannels: [
|
|
243
|
+
'getChannels',
|
|
244
|
+
'outpoints',
|
|
245
|
+
({getChannels, outpoints}, cbk) =>
|
|
246
|
+
{
|
|
142
247
|
// Exit early when a peer is not specified or force closing is OK
|
|
143
248
|
if (!args.public_key || !!args.is_forced) {
|
|
144
249
|
return cbk();
|
|
@@ -151,12 +256,12 @@ module.exports = (args, cbk) => {
|
|
|
151
256
|
}
|
|
152
257
|
|
|
153
258
|
// Exit early when there are no outpoints, consider all peer channels
|
|
154
|
-
if (!
|
|
259
|
+
if (!outpoints.length) {
|
|
155
260
|
return true;
|
|
156
261
|
}
|
|
157
262
|
|
|
158
263
|
// Only include selected channels
|
|
159
|
-
return
|
|
264
|
+
return outpoints.includes(asOutpoint(channel));
|
|
160
265
|
});
|
|
161
266
|
|
|
162
267
|
const costToClose = selectedChannels
|
|
@@ -279,8 +384,9 @@ module.exports = (args, cbk) => {
|
|
|
279
384
|
'checkChannels',
|
|
280
385
|
'getChannels',
|
|
281
386
|
'getNormalFee',
|
|
387
|
+
'outpoints',
|
|
282
388
|
'selectPeer',
|
|
283
|
-
({getChannels, getNormalFee, selectPeer}, cbk) =>
|
|
389
|
+
({getChannels, getNormalFee, outpoints, selectPeer}, cbk) =>
|
|
284
390
|
{
|
|
285
391
|
// Exit early when there is no peer to close out with
|
|
286
392
|
if (!selectPeer) {
|
|
@@ -292,11 +398,12 @@ module.exports = (args, cbk) => {
|
|
|
292
398
|
const toClose = getChannels.channels
|
|
293
399
|
.filter(chan => chan.partner_public_key === selectPeer.public_key)
|
|
294
400
|
.filter(chan => {
|
|
295
|
-
|
|
401
|
+
// When no outpoints are specified, all channels should be closed
|
|
402
|
+
if (!outpoints.length) {
|
|
296
403
|
return true;
|
|
297
404
|
}
|
|
298
405
|
|
|
299
|
-
return
|
|
406
|
+
return !!outpoints.includes(asOutpoint(chan));
|
|
300
407
|
});
|
|
301
408
|
|
|
302
409
|
// Exit early when there are no channels to close
|
package/package.json
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"invoices": "2.0.3",
|
|
37
37
|
"ln-accounting": "5.0.5",
|
|
38
38
|
"ln-service": "53.6.0",
|
|
39
|
-
"ln-sync": "3.
|
|
39
|
+
"ln-sync": "3.9.0",
|
|
40
40
|
"ln-telegram": "3.12.0",
|
|
41
41
|
"moment": "2.29.1",
|
|
42
42
|
"paid-services": "3.11.0",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"description": "Lightning balance CLI",
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@alexbosworth/tap": "15.0.10",
|
|
55
|
-
"ln-docker-daemons": "2.2.
|
|
55
|
+
"ln-docker-daemons": "2.2.2",
|
|
56
56
|
"mock-lnd": "1.4.1",
|
|
57
57
|
"secp256k1": "4.0.3"
|
|
58
58
|
},
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
|
|
82
82
|
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/arrays/*.js test/balances/*.js test/chain/*.js test/display/*.js test/encryption/*.js test/lnd/*.js test/network/*.js test/nodes/*.js test/peers/*.js test/responses/*.js test/routing/*.js test/services/*.js test/swaps/*.js test/tags/*.js test/wallets/*.js"
|
|
83
83
|
},
|
|
84
|
-
"version": "11.
|
|
84
|
+
"version": "11.44.0"
|
|
85
85
|
}
|
|
@@ -4,6 +4,8 @@ const {returnResult} = require('asyncjs-util');
|
|
|
4
4
|
|
|
5
5
|
const disableAllForwards = 0;
|
|
6
6
|
const hoursAsSeconds = hours => hours * 60 * 60;
|
|
7
|
+
const {isArray} = Array;
|
|
8
|
+
const isEdge = n => !!n && /^[0-9A-F]{66}\/[0-9A-F]{66}$/i.test(n);
|
|
7
9
|
|
|
8
10
|
/** Limit forwarding requests
|
|
9
11
|
|
|
@@ -11,8 +13,10 @@ const hoursAsSeconds = hours => hours * 60 * 60;
|
|
|
11
13
|
lnd: (await lndForNode(logger, options.node)).lnd,
|
|
12
14
|
logger: <Winston Logger Object>
|
|
13
15
|
[is_disabling_all_forwards]: <All Forwards Are Disabled Bool>
|
|
14
|
-
[max_hours_since_last_block]:
|
|
15
|
-
[max_new_pending_per_hour]:
|
|
16
|
+
[max_hours_since_last_block]: <Maximum Hours Since Last Block Number>
|
|
17
|
+
[max_new_pending_per_hour]: <Maximum Outstanding New HTLCs Per Hour Number>
|
|
18
|
+
[min_channel_confirmations]: <Minimum Required Channel Confs Number>
|
|
19
|
+
only_allow: [<In Public Key / Out Public Key String>]
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
@returns via cbk or Promise
|
|
@@ -22,6 +26,14 @@ module.exports = (args, cbk) => {
|
|
|
22
26
|
return asyncAuto({
|
|
23
27
|
// Check arguments
|
|
24
28
|
validate: cbk => {
|
|
29
|
+
if (!isArray(args.only_allow)) {
|
|
30
|
+
return cbk([400, 'ExpectedOnlyAllowArrayToLimitForwarding']);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!!args.only_allow.filter(n => !isEdge(n)).length) {
|
|
34
|
+
return cbk([400, 'ExpectedOnlyAllowAsPublicKeyPairs']);
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
if (!args.logger) {
|
|
26
38
|
return cbk([400, 'ExpectedWinstonLoggerToLimitForwarding']);
|
|
27
39
|
}
|
|
@@ -55,11 +67,25 @@ module.exports = (args, cbk) => {
|
|
|
55
67
|
return cbk(null, hoursAsSeconds(args.max_hours_since_last_block));
|
|
56
68
|
}],
|
|
57
69
|
|
|
70
|
+
// Only allow pairs
|
|
71
|
+
onlyAllow: ['validate', ({}, cbk) => {
|
|
72
|
+
if (!args.only_allow.length) {
|
|
73
|
+
return cbk();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const allow = args.only_allow.map(([inKey, outKey]) => {
|
|
77
|
+
return {inbound_peer: inKey, outbound_peer: outKey};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return cbk(null, allow);
|
|
81
|
+
}],
|
|
82
|
+
|
|
58
83
|
// Limit forward requests
|
|
59
84
|
limit: [
|
|
60
85
|
'maxPendingPerHour',
|
|
61
86
|
'maxSecondsSinceLastBlock',
|
|
62
|
-
|
|
87
|
+
'onlyAllow',
|
|
88
|
+
({maxPendingPerHour, maxSecondsSinceLastBlock, onlyAllow}, cbk) =>
|
|
63
89
|
{
|
|
64
90
|
args.logger.info({limiting_forwards: true});
|
|
65
91
|
|
|
@@ -67,6 +93,8 @@ module.exports = (args, cbk) => {
|
|
|
67
93
|
lnd: args.lnd,
|
|
68
94
|
max_new_pending_per_hour: maxPendingPerHour,
|
|
69
95
|
max_seconds_since_last_block: maxSecondsSinceLastBlock,
|
|
96
|
+
min_activation_age: args.min_channel_confirmations || undefined,
|
|
97
|
+
only_allow: onlyAllow,
|
|
70
98
|
});
|
|
71
99
|
|
|
72
100
|
sub.on('error', err => {
|
|
@@ -41,6 +41,7 @@ const giveTokens = capacity => Math.ceil(capacity / 2);
|
|
|
41
41
|
const hasInbound = channels => !!channels.find(n => !!n.remote_balance);
|
|
42
42
|
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
43
43
|
const idAsHash = id => Buffer.from(id, 'hex').reverse();
|
|
44
|
+
const {isInteger} = Number;
|
|
44
45
|
const isNumber = n => !isNaN(n);
|
|
45
46
|
const isOdd = n => n % 2;
|
|
46
47
|
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
@@ -237,11 +238,11 @@ module.exports = (args, cbk) => {
|
|
|
237
238
|
default: round(getChainFee.tokens_per_vbyte),
|
|
238
239
|
message: 'Fee rate per vbyte for the joint funding transaction?',
|
|
239
240
|
name: 'rate',
|
|
240
|
-
|
|
241
|
+
validate: n => !!isNumber(n) && !!Number(n) && isInteger(Number(n)),
|
|
241
242
|
};
|
|
242
243
|
|
|
243
244
|
return args.ask(feeRate, ({rate}) => {
|
|
244
|
-
if (!isNumber(rate)) {
|
|
245
|
+
if (!isNumber(Number(rate))) {
|
|
245
246
|
return cbk([400, 'ExpectedFeeRatePerVirtualByteToProposeChannel']);
|
|
246
247
|
}
|
|
247
248
|
|
package/swaps/rebalance.js
CHANGED
|
@@ -43,7 +43,6 @@ const isPublicKey = n => /^[0-9A-F]{66}$/i.test(n);
|
|
|
43
43
|
const legacyMaxRebalanceTokens = 4294967;
|
|
44
44
|
const {max} = Math;
|
|
45
45
|
const maxPaymentSize = 4294967;
|
|
46
|
-
const maxRebalanceTokens = 16777215;
|
|
47
46
|
const {min} = Math;
|
|
48
47
|
const minInboundBalance = 4294967 * 2;
|
|
49
48
|
const minRebalanceAmount = 5e4;
|
|
@@ -632,7 +631,7 @@ module.exports = (args, cbk) => {
|
|
|
632
631
|
lnd,
|
|
633
632
|
cltv_delta: defaultCltvDelta,
|
|
634
633
|
description: 'Rebalance',
|
|
635
|
-
tokens:
|
|
634
|
+
tokens: findRoute.route_maximum,
|
|
636
635
|
},
|
|
637
636
|
cbk);
|
|
638
637
|
}],
|