balanceofsatoshis 24.2.0 → 24.2.1
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 +4 -0
- package/package.json +4 -4
- package/peers/open_channels.js +23 -10
- package/swaps/channel_for_receive.js +91 -0
- package/swaps/rebalance.js +108 -5
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"url": "https://github.com/alexbosworth/balanceofsatoshis/issues"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@alexbosworth/blockchain": "4.
|
|
18
|
+
"@alexbosworth/blockchain": "4.10.0",
|
|
19
19
|
"@alexbosworth/caporal": "2.0.0",
|
|
20
20
|
"@alexbosworth/fiat": "2.0.0",
|
|
21
21
|
"@alexbosworth/formulas": "1.5.0",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"ini": "7.0.0",
|
|
39
39
|
"inquirer": "14.2.2",
|
|
40
40
|
"ln-accounting": "10.0.7",
|
|
41
|
-
"ln-service": "60.1.
|
|
41
|
+
"ln-service": "60.1.2",
|
|
42
42
|
"ln-sync": "8.0.6",
|
|
43
43
|
"ln-telegram": "8.0.6",
|
|
44
44
|
"moment": "2.31.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"description": "Lightning balance CLI",
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"invoices": "6.2.0",
|
|
57
|
-
"ln-docker-daemons": "8.0
|
|
57
|
+
"ln-docker-daemons": "8.1.0"
|
|
58
58
|
},
|
|
59
59
|
"engines": {
|
|
60
60
|
"node": ">=22"
|
|
@@ -105,5 +105,5 @@
|
|
|
105
105
|
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64 -t alexbosworth/balanceofsatoshis -t alexbosworth/balanceofsatoshis:$npm_package_version --push .",
|
|
106
106
|
"test": "node --experimental-test-coverage --test 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/offchain/*.js test/peers/*.js test/responses/*.js test/routing/*.js test/services/*.js test/swaps/*.js test/tags/*.js test/telegram/*.js test/wallets/*.js"
|
|
107
107
|
},
|
|
108
|
-
"version": "24.2.
|
|
108
|
+
"version": "24.2.1"
|
|
109
109
|
}
|
package/peers/open_channels.js
CHANGED
|
@@ -2,7 +2,6 @@ const {randomBytes} = require('crypto');
|
|
|
2
2
|
|
|
3
3
|
const {acceptsChannelOpen} = require('ln-sync');
|
|
4
4
|
const {addPeer} = require('ln-service');
|
|
5
|
-
const {address} = require('bitcoinjs-lib');
|
|
6
5
|
const {askForFeeRate} = require('ln-sync');
|
|
7
6
|
const asyncAuto = require('async/auto');
|
|
8
7
|
const asyncEach = require('async/each');
|
|
@@ -14,6 +13,7 @@ const asyncReflect = require('async/reflect');
|
|
|
14
13
|
const asyncRetry = require('async/retry');
|
|
15
14
|
const {broadcastChainTransaction} = require('ln-service');
|
|
16
15
|
const {cancelPendingChannel} = require('ln-service');
|
|
16
|
+
const {decodeBech32Address} = require('@alexbosworth/blockchain');
|
|
17
17
|
const {fundPendingChannels} = require('ln-service');
|
|
18
18
|
const {getChainBalance} = require('ln-service');
|
|
19
19
|
const {getChannels} = require('ln-service');
|
|
@@ -23,12 +23,13 @@ const {getNode} = require('ln-service');
|
|
|
23
23
|
const {getPeers} = require('ln-service');
|
|
24
24
|
const {getPsbtFromTransaction} = require('goldengate');
|
|
25
25
|
const {getWalletVersion} = require('ln-service');
|
|
26
|
+
const {idForTransaction} = require('@alexbosworth/blockchain');
|
|
26
27
|
const {openChannels} = require('ln-service');
|
|
27
28
|
const {maintainUtxoLocks} = require('ln-sync');
|
|
28
29
|
const moment = require('moment');
|
|
29
30
|
const {parseAmount} = require('ln-accounting');
|
|
30
31
|
const {returnResult} = require('asyncjs-util');
|
|
31
|
-
const {
|
|
32
|
+
const {scriptElementsAsScript} = require('@alexbosworth/blockchain');
|
|
32
33
|
const {unlockUtxo} = require('ln-service');
|
|
33
34
|
|
|
34
35
|
const adjustFees = require('./../routing/adjust_fees');
|
|
@@ -38,13 +39,14 @@ const {getAddressUtxo} = require('./../chain');
|
|
|
38
39
|
const getChannelOutpoints = require('./get_channel_outpoints');
|
|
39
40
|
const getPeersForNodes = require('./get_peers_for_nodes');
|
|
40
41
|
|
|
41
|
-
const bech32AsData = bech32 => address.fromBech32(bech32).data;
|
|
42
42
|
const description = 'bos open';
|
|
43
43
|
const detectNetworks = ['btc', 'btctestnet'];
|
|
44
|
+
const encodeVersion = version => 80 + version;
|
|
44
45
|
const featureAnchors = 'anchor_zero_fee_htlc_tx';
|
|
45
46
|
const flatten = arr => [].concat(...arr);
|
|
46
47
|
const format = 'p2wpkh';
|
|
47
|
-
const
|
|
48
|
+
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
49
|
+
const idOf = transaction => idForTransaction({transaction}).id;
|
|
48
50
|
const interval = 1000;
|
|
49
51
|
const {isArray} = Array;
|
|
50
52
|
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
@@ -623,15 +625,28 @@ module.exports = (args, cbk) => {
|
|
|
623
625
|
return pending.map(n => ({address: n.address, tokens: n.tokens}));
|
|
624
626
|
}));
|
|
625
627
|
|
|
628
|
+
// Map a bech32 address to its output script: OP_n then a data push
|
|
629
|
+
const scriptOf = address => {
|
|
630
|
+
const {program, version} = decodeBech32Address({address});
|
|
631
|
+
|
|
632
|
+
// Encode the version as a script number
|
|
633
|
+
const op = !version ? version : encodeVersion(version);
|
|
634
|
+
|
|
635
|
+
const {script} = scriptElementsAsScript({elements: [op, program]});
|
|
636
|
+
|
|
637
|
+
return hexAsBuffer(script);
|
|
638
|
+
};
|
|
639
|
+
|
|
626
640
|
// Sort all the outputs using BIP 69
|
|
627
641
|
try {
|
|
628
|
-
|
|
642
|
+
pending.sort((a, b) => {
|
|
629
643
|
// Sort by tokens ascending when no tie breaker needed
|
|
630
644
|
if (a.tokens !== b.tokens) {
|
|
631
645
|
return a.tokens - b.tokens;
|
|
632
646
|
}
|
|
633
647
|
|
|
634
|
-
|
|
648
|
+
// Break ties by comparing the output scripts as bytes
|
|
649
|
+
return scriptOf(a.address).compare(scriptOf(b.address));
|
|
635
650
|
});
|
|
636
651
|
} catch (err) {}
|
|
637
652
|
|
|
@@ -690,9 +705,7 @@ module.exports = (args, cbk) => {
|
|
|
690
705
|
return cbk();
|
|
691
706
|
}
|
|
692
707
|
|
|
693
|
-
args.logger.info({
|
|
694
|
-
funding_detected: Transaction.fromHex(foundTx).getId(),
|
|
695
|
-
});
|
|
708
|
+
args.logger.info({funding_detected: idOf(foundTx)});
|
|
696
709
|
|
|
697
710
|
return asyncEach(openChannels, ({lnd, node, pending}, cbk) => {
|
|
698
711
|
return fundPendingChannels({
|
|
@@ -817,7 +830,7 @@ module.exports = (args, cbk) => {
|
|
|
817
830
|
}
|
|
818
831
|
|
|
819
832
|
const toOpen = flatten(openChannels.map(n => n.pending));
|
|
820
|
-
const txId =
|
|
833
|
+
const txId = idOf(getFunding.value.transaction);
|
|
821
834
|
|
|
822
835
|
args.logger.info({confirming_pending_open: true});
|
|
823
836
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const {sortBy} = require('./../arrays');
|
|
2
|
+
|
|
3
|
+
const attribute = 'receivable';
|
|
4
|
+
const feeBuffer = fees => fees * 2;
|
|
5
|
+
const {floor} = Math;
|
|
6
|
+
const htlcOutputSize = 43;
|
|
7
|
+
const {min} = Math;
|
|
8
|
+
const mtokensAsTokens = mtokens => Number(BigInt(mtokens) / BigInt(1e3));
|
|
9
|
+
const rate = n => n.commit_transaction_fee / (n.commit_transaction_weight / 4);
|
|
10
|
+
|
|
11
|
+
/** Choose a channel with a peer to receive a payment through
|
|
12
|
+
|
|
13
|
+
The peer forwards the payment to us over the channel, so the peer needs the
|
|
14
|
+
balance on their side to send it and their policy towards us must allow it.
|
|
15
|
+
|
|
16
|
+
The preferred channel is kept when it can receive the full amount, otherwise
|
|
17
|
+
the channel that can receive the most is chosen.
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
channels: [{
|
|
21
|
+
commit_transaction_fee: <Commit Transaction Fee Number>
|
|
22
|
+
commit_transaction_weight: <Commit Transaction Weight Number>
|
|
23
|
+
id: <Standard Format Channel Id String>
|
|
24
|
+
is_active: <Channel Active Bool>
|
|
25
|
+
is_partner_initiated: <Channel Partner Opened Channel Bool>
|
|
26
|
+
partner_public_key: <Peer Public Key Hex String>
|
|
27
|
+
policies: [{
|
|
28
|
+
[is_disabled]: <Channel Is Disabled Bool>
|
|
29
|
+
[max_htlc_mtokens]: <Maximum HTLC Millitokens Value String>
|
|
30
|
+
public_key: <Node Public Key Hex String>
|
|
31
|
+
}]
|
|
32
|
+
remote_balance: <Remote Balance Tokens Number>
|
|
33
|
+
remote_reserve: <Remote Reserved Tokens Number>
|
|
34
|
+
}]
|
|
35
|
+
[id]: <Preferred Channel Id String>
|
|
36
|
+
peer: <Peer Public Key Hex String>
|
|
37
|
+
tokens: <Tokens To Receive Number>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@returns
|
|
41
|
+
{
|
|
42
|
+
[id]: <Channel Id String>
|
|
43
|
+
[receivable]: <Receivable Tokens Number>
|
|
44
|
+
}
|
|
45
|
+
*/
|
|
46
|
+
module.exports = ({channels, id, peer, tokens}) => {
|
|
47
|
+
const candidates = channels
|
|
48
|
+
.filter(n => n.partner_public_key === peer)
|
|
49
|
+
.filter(n => !!n.is_active)
|
|
50
|
+
.map(channel => {
|
|
51
|
+
const policy = channel.policies.find(n => n.public_key === peer);
|
|
52
|
+
|
|
53
|
+
// Exit early when the peer does not forward to us over this channel
|
|
54
|
+
if (!policy || !!policy.is_disabled) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The partner needs to keep a buffer for fees when they pay for commits
|
|
59
|
+
const fees = !channel.is_partner_initiated ? Number() :
|
|
60
|
+
channel.commit_transaction_fee + htlcOutputSize * rate(channel);
|
|
61
|
+
|
|
62
|
+
const balance = channel.remote_balance - channel.remote_reserve;
|
|
63
|
+
|
|
64
|
+
const spendable = balance - feeBuffer(fees);
|
|
65
|
+
|
|
66
|
+
// The peer limits the size of HTLCs that they will forward to us
|
|
67
|
+
const maxHtlc = !policy.max_htlc_mtokens ? spendable :
|
|
68
|
+
mtokensAsTokens(policy.max_htlc_mtokens);
|
|
69
|
+
|
|
70
|
+
return {id: channel.id, receivable: floor(min(spendable, maxHtlc))};
|
|
71
|
+
})
|
|
72
|
+
.filter(n => !!n && n.receivable > Number());
|
|
73
|
+
|
|
74
|
+
const preferred = candidates.find(n => n.id === id);
|
|
75
|
+
|
|
76
|
+
// Exit early when the preferred channel can receive the full amount
|
|
77
|
+
if (!!preferred && preferred.receivable >= tokens) {
|
|
78
|
+
return {id, receivable: preferred.receivable};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Exit early when no channel with the peer can receive anything
|
|
82
|
+
if (!candidates.length) {
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const {sorted} = sortBy({attribute, array: candidates});
|
|
87
|
+
|
|
88
|
+
const [best] = sorted.slice().reverse();
|
|
89
|
+
|
|
90
|
+
return {id: best.id, receivable: best.receivable};
|
|
91
|
+
};
|
package/swaps/rebalance.js
CHANGED
|
@@ -16,6 +16,7 @@ const {payViaRoutes} = require('ln-service');
|
|
|
16
16
|
const {returnResult} = require('asyncjs-util');
|
|
17
17
|
const {routeFromChannels} = require('ln-service');
|
|
18
18
|
|
|
19
|
+
const channelForReceive = require('./channel_for_receive');
|
|
19
20
|
const findRebalancePeer = require('./find_rebalance_peer');
|
|
20
21
|
const {formatFeeRate} = require('./../display');
|
|
21
22
|
const {getIgnores} = require('./../routing');
|
|
@@ -599,10 +600,9 @@ module.exports = (args, cbk) => {
|
|
|
599
600
|
cbk);
|
|
600
601
|
}],
|
|
601
602
|
|
|
602
|
-
// Get channels for the rebalance route
|
|
603
|
-
|
|
603
|
+
// Get channels for the probed rebalance route
|
|
604
|
+
getRouteChannels: [
|
|
604
605
|
'findRoute',
|
|
605
|
-
'getInitialLiquidity',
|
|
606
606
|
'getPublicKey',
|
|
607
607
|
'lnd',
|
|
608
608
|
({findRoute, getPublicKey, lnd}, cbk) =>
|
|
@@ -633,13 +633,116 @@ module.exports = (args, cbk) => {
|
|
|
633
633
|
cbk);
|
|
634
634
|
}],
|
|
635
635
|
|
|
636
|
+
// Get the current channels with the inbound peer
|
|
637
|
+
getInboundChannels: [
|
|
638
|
+
'getInbound',
|
|
639
|
+
'getRouteChannels',
|
|
640
|
+
'lnd',
|
|
641
|
+
({getInbound, lnd}, cbk) =>
|
|
642
|
+
{
|
|
643
|
+
return getChannels({
|
|
644
|
+
lnd,
|
|
645
|
+
is_active: true,
|
|
646
|
+
partner_public_key: getInbound.public_key,
|
|
647
|
+
},
|
|
648
|
+
cbk);
|
|
649
|
+
}],
|
|
650
|
+
|
|
651
|
+
// Get the inbound peer channels together with their routing policies
|
|
652
|
+
inboundChannels: [
|
|
653
|
+
'getInboundChannels',
|
|
654
|
+
'lnd',
|
|
655
|
+
({getInboundChannels, lnd}, cbk) =>
|
|
656
|
+
{
|
|
657
|
+
return asyncMap(getInboundChannels.channels, (channel, cbk) => {
|
|
658
|
+
return getChannel({lnd, id: channel.id}, (err, res) => {
|
|
659
|
+
// Policies are unknown when the channel is not found in the graph
|
|
660
|
+
const policies = !!err ? [] : res.policies;
|
|
661
|
+
|
|
662
|
+
return cbk(null, {
|
|
663
|
+
policies,
|
|
664
|
+
capacity: channel.capacity,
|
|
665
|
+
commit_transaction_fee: channel.commit_transaction_fee,
|
|
666
|
+
commit_transaction_weight: channel.commit_transaction_weight,
|
|
667
|
+
id: channel.id,
|
|
668
|
+
is_active: channel.is_active,
|
|
669
|
+
is_partner_initiated: channel.is_partner_initiated,
|
|
670
|
+
partner_public_key: channel.partner_public_key,
|
|
671
|
+
remote_balance: channel.remote_balance,
|
|
672
|
+
remote_reserve: channel.remote_reserve,
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
},
|
|
676
|
+
cbk);
|
|
677
|
+
}],
|
|
678
|
+
|
|
679
|
+
// Choose the final hop channel that has the inbound to receive the amount
|
|
680
|
+
finalHop: [
|
|
681
|
+
'findRoute',
|
|
682
|
+
'getInbound',
|
|
683
|
+
'getPublicKey',
|
|
684
|
+
'getRouteChannels',
|
|
685
|
+
'inboundChannels',
|
|
686
|
+
({
|
|
687
|
+
findRoute,
|
|
688
|
+
getInbound,
|
|
689
|
+
getPublicKey,
|
|
690
|
+
getRouteChannels,
|
|
691
|
+
inboundChannels,
|
|
692
|
+
},
|
|
693
|
+
cbk) =>
|
|
694
|
+
{
|
|
695
|
+
const [probed] = getRouteChannels.slice().reverse();
|
|
696
|
+
const inPeer = getInbound.public_key;
|
|
697
|
+
const maximum = findRoute.route_maximum;
|
|
698
|
+
|
|
699
|
+
const fromInPeer = probed.policies.find(n => n.public_key === inPeer);
|
|
700
|
+
|
|
701
|
+
// Exit early when the route does not end with the inbound peer to self
|
|
702
|
+
if (!fromInPeer || probed.destination !== getPublicKey.public_key) {
|
|
703
|
+
return cbk(null, {maximum, channel: probed});
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// LND picks the final hop channel without regard to the peer balance
|
|
707
|
+
const {id, receivable} = channelForReceive({
|
|
708
|
+
channels: inboundChannels,
|
|
709
|
+
id: probed.id,
|
|
710
|
+
peer: inPeer,
|
|
711
|
+
tokens: maximum,
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
// Exit early when the probed final hop channel is used to receive
|
|
715
|
+
if (!id || id === probed.id) {
|
|
716
|
+
return cbk(null, {maximum, channel: probed});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const {capacity, policies} = inboundChannels.find(n => n.id === id);
|
|
720
|
+
|
|
721
|
+
// The amount is limited to what the switched channel can receive
|
|
722
|
+
return cbk(null, {
|
|
723
|
+
channel: {capacity, id, policies, destination: probed.destination},
|
|
724
|
+
maximum: min(maximum, receivable),
|
|
725
|
+
});
|
|
726
|
+
}],
|
|
727
|
+
|
|
728
|
+
// Channels for the rebalance route, ending with the chosen final hop
|
|
729
|
+
channels: [
|
|
730
|
+
'finalHop',
|
|
731
|
+
'getRouteChannels',
|
|
732
|
+
({finalHop, getRouteChannels}, cbk) =>
|
|
733
|
+
{
|
|
734
|
+
const hops = getRouteChannels.slice(Number(), -1);
|
|
735
|
+
|
|
736
|
+
return cbk(null, hops.concat(finalHop.channel));
|
|
737
|
+
}],
|
|
738
|
+
|
|
636
739
|
// Create local invoice
|
|
637
|
-
invoice: ['channels', '
|
|
740
|
+
invoice: ['channels', 'finalHop', 'lnd', ({finalHop, lnd}, cbk) => {
|
|
638
741
|
return createInvoice({
|
|
639
742
|
lnd,
|
|
640
743
|
cltv_delta: defaultCltvDelta,
|
|
641
744
|
description: 'Rebalance',
|
|
642
|
-
tokens:
|
|
745
|
+
tokens: finalHop.maximum,
|
|
643
746
|
},
|
|
644
747
|
cbk);
|
|
645
748
|
}],
|