balanceofsatoshis 24.0.1 → 24.1.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 +8 -0
- package/README.md +13 -16
- package/bos +8 -1
- package/chain/index.js +0 -2
- package/chain/recover_p2pk.js +85 -53
- package/display/constants.json +6 -0
- package/display/format_duration.js +34 -0
- package/display/index.js +2 -0
- package/package.json +6 -6
- package/peers/intercept_inbound_channels.js +1 -1
- package/routing/index.js +2 -0
- package/routing/manage_fees.js +129 -0
- package/services/open_balanced_channel.js +1 -1
- package/swaps/execute_rebalance.js +281 -0
- package/swaps/manage_rebalance.js +70 -178
- package/swaps/parse_interval_formula.js +72 -0
- package/chain/networks.json +0 -7
- package/chain/output_script_for_address.js +0 -88
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 24.1.0
|
|
4
|
+
|
|
5
|
+
- `fees`: Add `--repeat-interval-ms` to show or set the fees again each time an
|
|
6
|
+
interval elapses, such as `5*min` to keep formula fee rates up to date
|
|
7
|
+
- `rebalance`: Add `--repeat-interval-ms` to run the rebalance again each time
|
|
8
|
+
an interval elapses, such as `30*min`, `1*h * (0.5 + RAND())` for a random
|
|
9
|
+
interval, or `1*min * 2^FAILURES_COUNT` to back off on failures in a row
|
|
10
|
+
|
|
3
11
|
## 24.0.1
|
|
4
12
|
|
|
5
13
|
### Breaking Changes
|
package/README.md
CHANGED
|
@@ -327,18 +327,17 @@ expr $(bos balance --node=savedNode1) + $(bos balance --node=savedNode2)
|
|
|
327
327
|
|
|
328
328
|
### Auto Adjust Fees
|
|
329
329
|
|
|
330
|
-
|
|
331
|
-
# Cron every 5 minutes adjust fees
|
|
332
|
-
*/5 * * * * /bin/timeout -s 2 30 /home/ubuntu/update-fees.sh
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
update-fees.sh:
|
|
330
|
+
Repeat the `fees` command on an interval to keep a fee rate formula up to date
|
|
336
331
|
|
|
332
|
+
```shell
|
|
333
|
+
# Every 5 minutes, raise the outbound fees to a public key when inbound increases
|
|
334
|
+
bos fees --to PUBLIC_KEY --set-fee-rate="IF(INBOUND>10000000,1000,500)" --repeat-interval-ms="5*min"
|
|
337
335
|
```
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
336
|
+
|
|
337
|
+
The interval is a formula in milliseconds that can use `s`, `min`, `h`, `d` and
|
|
338
|
+
`w` units, so `"5*min * (0.5 + RAND())"` repeats at a random interval between
|
|
339
|
+
2.5 and 7.5 minutes. See "Persist Long-Running Commands" below to keep the
|
|
340
|
+
command running.
|
|
342
341
|
|
|
343
342
|
### Auto Balance Liquidity Between Two Nodes
|
|
344
343
|
|
|
@@ -431,13 +430,11 @@ Docker example:
|
|
|
431
430
|
docker run -d --restart always -v $HOME/.bos:/home/node/.bos alexbosworth/balanceofsatoshis telegram --connect CONNECT_CODE
|
|
432
431
|
```
|
|
433
432
|
|
|
434
|
-
|
|
433
|
+
Commands like `fees` and `rebalance` can also repeat themselves, with a delay
|
|
435
434
|
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
sleep 2000;
|
|
440
|
-
done
|
|
435
|
+
```shell
|
|
436
|
+
# Rebalance again 30 minutes after each attempt, or 1 hour when it failed
|
|
437
|
+
bos rebalance --repeat-interval-ms="IF(FAILURES_COUNT > 0, 1*h, 30*min)"
|
|
441
438
|
```
|
|
442
439
|
|
|
443
440
|
## Docker
|
package/bos
CHANGED
|
@@ -731,7 +731,9 @@ prog
|
|
|
731
731
|
.help('You can use INBOUND_FEE_RATE to mirror an inbound fee')
|
|
732
732
|
.help('You can use FEE_RATE_OF_<PUBKEY> to reference other node rates')
|
|
733
733
|
.help('--set-inbound-discount-rate is total discount on traffic from peer')
|
|
734
|
+
.help('--repeat-interval-ms vars: s, min, h, d, w: 1*h repeats hourly')
|
|
734
735
|
.option('--node <node_name>', 'Saved node (not peer to set fees on)')
|
|
736
|
+
.option('--repeat-interval-ms <interval>', 'Repeat fees after interval')
|
|
735
737
|
.option('--set-cltv-delta <count>', 'Set the number of blocks for CLTV', INT)
|
|
736
738
|
.option('--set-fee-rate <rate>', 'Fee in parts per million or use a formula')
|
|
737
739
|
.option('--set-inbound-rate-discount <rate>', 'Source discount rate in PPM')
|
|
@@ -739,13 +741,14 @@ prog
|
|
|
739
741
|
.action((args, options, logger) => {
|
|
740
742
|
return new Promise(async (resolve, reject) => {
|
|
741
743
|
try {
|
|
742
|
-
return routing.
|
|
744
|
+
return routing.manageFees({
|
|
743
745
|
logger,
|
|
744
746
|
cltv_delta: options.setCltvDelta,
|
|
745
747
|
fee_rate: options.setFeeRate,
|
|
746
748
|
fs: {getFile: readFile},
|
|
747
749
|
inbound_rate_discount: options.setInboundRateDiscount,
|
|
748
750
|
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
|
|
751
|
+
repeat_interval_ms: options.repeatIntervalMs || undefined,
|
|
749
752
|
to: flatten([options.to].filter(n => !!n)),
|
|
750
753
|
},
|
|
751
754
|
responses.returnObject({logger, reject, resolve, table: 'rows'}));
|
|
@@ -1693,6 +1696,8 @@ prog
|
|
|
1693
1696
|
.help('--in-filter/--out-filter vars: OUTBOUND_LIQUIDITY/PENDING_PAYMENTS')
|
|
1694
1697
|
.help('--in-filter/--out-filter vars: INBOUND_BASE_FEE/INBOUND_FEE_RATE')
|
|
1695
1698
|
.help('--out increases the inbound liquidity with a specific peer/tag')
|
|
1699
|
+
.help('--repeat-interval-ms vars: s, min, h, d, w: 1*h repeats hourly')
|
|
1700
|
+
.help('--repeat-interval-ms var FAILURES_COUNT: count of failures in a row')
|
|
1696
1701
|
.option('--amount <amount>', 'Maximum amount to rebalance')
|
|
1697
1702
|
.option('--avoid <pubkey_or_chanid>', 'Avoid forwarding through', REPEATABLE)
|
|
1698
1703
|
.option('--avoid-append', 'Formula for appending failing edges to list')
|
|
@@ -1709,6 +1714,7 @@ prog
|
|
|
1709
1714
|
.option('--out <pubkey_or_alias>', 'Route out through a specific peer')
|
|
1710
1715
|
.option('--out-filter <out_filter>', 'Filter outbound peer/tag', REPEATABLE)
|
|
1711
1716
|
.option('--out-target-inbound <amount>', 'Balance up to inbound amount')
|
|
1717
|
+
.option('--repeat-interval-ms <interval>', 'Repeat rebalance after interval')
|
|
1712
1718
|
.action((args, options, logger) => {
|
|
1713
1719
|
return new Promise(async (resolve, reject) => {
|
|
1714
1720
|
try {
|
|
@@ -1729,6 +1735,7 @@ prog
|
|
|
1729
1735
|
out_filters: flatten([options.outFilter].filter(n => !!n)),
|
|
1730
1736
|
out_inbound: options.outTargetInbound,
|
|
1731
1737
|
out_through: options.out || undefined,
|
|
1738
|
+
repeat_interval_ms: options.repeatIntervalMs || undefined,
|
|
1732
1739
|
timeout_minutes: options.minutes || undefined,
|
|
1733
1740
|
},
|
|
1734
1741
|
responses.returnObject({exit, logger, reject, resolve}));
|
package/chain/index.js
CHANGED
|
@@ -6,7 +6,6 @@ const getDepositAddress = require('./get_deposit_address');
|
|
|
6
6
|
const getMempoolSize = require('./get_mempool_size');
|
|
7
7
|
const getRawTransaction = require('./get_raw_transaction');
|
|
8
8
|
const getUtxos = require('./get_utxos');
|
|
9
|
-
const outputScriptForAddress = require('./output_script_for_address');
|
|
10
9
|
const recoverP2pk = require('./recover_p2pk');
|
|
11
10
|
const splitUtxos = require('./split_utxos');
|
|
12
11
|
|
|
@@ -19,7 +18,6 @@ module.exports = {
|
|
|
19
18
|
getMempoolSize,
|
|
20
19
|
getRawTransaction,
|
|
21
20
|
getUtxos,
|
|
22
|
-
outputScriptForAddress,
|
|
23
21
|
recoverP2pk,
|
|
24
22
|
splitUtxos,
|
|
25
23
|
};
|
package/chain/recover_p2pk.js
CHANGED
|
@@ -1,40 +1,39 @@
|
|
|
1
1
|
const {createHash} = require('crypto');
|
|
2
2
|
|
|
3
3
|
const asyncAuto = require('async/auto');
|
|
4
|
-
const bitcoinjsLib = require('bitcoinjs-lib');
|
|
5
4
|
const {broadcastChainTransaction} = require('ln-service');
|
|
5
|
+
const {componentsOfTransaction} = require('@alexbosworth/blockchain');
|
|
6
6
|
const {createChainAddress} = require('ln-service');
|
|
7
|
-
const {decode} = require('bip66');
|
|
8
7
|
const {getChainFeeRate} = require('ln-service');
|
|
9
8
|
const {getIdentity} = require('ln-service');
|
|
10
9
|
const {getNetwork} = require('ln-sync');
|
|
10
|
+
const {idForTransaction} = require('@alexbosworth/blockchain');
|
|
11
|
+
const {outputScriptForAddress} = require('@alexbosworth/blockchain');
|
|
11
12
|
const {returnResult} = require('asyncjs-util');
|
|
13
|
+
const {scriptElementsAsScript} = require('@alexbosworth/blockchain');
|
|
12
14
|
const {signBytes} = require('ln-service');
|
|
15
|
+
const {sizeOfTransaction} = require('@alexbosworth/blockchain');
|
|
16
|
+
const {transactionFromComponents} = require('@alexbosworth/blockchain');
|
|
13
17
|
|
|
14
18
|
const getRawTransaction = require('./get_raw_transaction');
|
|
15
19
|
|
|
16
20
|
const {ceil} = Math;
|
|
17
|
-
const {compile} = bitcoinjsLib.script;
|
|
18
21
|
const {concat} = Buffer;
|
|
19
22
|
const description = 'bos recover p2pk node identity key funds';
|
|
20
|
-
const {encode} = bitcoinjsLib.script.signature;
|
|
21
23
|
const estimatedSignatureSize = 73;
|
|
22
24
|
const format = 'p2wpkh';
|
|
23
|
-
const {fromHex} = bitcoinjsLib.Transaction;
|
|
24
25
|
const hashFlag = Buffer.from('01000000', 'hex');
|
|
25
26
|
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
26
|
-
const inputIndex = 0;
|
|
27
27
|
const inputSequence = 0;
|
|
28
28
|
const isHash = n => !!n && /^[0-9A-F]{64}$/i.test(n);
|
|
29
|
-
const
|
|
30
|
-
const {networks} = bitcoinjsLib;
|
|
29
|
+
const locktime = 0;
|
|
31
30
|
const nodeIdentityKeyFamily = 6;
|
|
32
31
|
const nodeIdentityKeyIndex = 0;
|
|
33
32
|
const OP_CHECKSIG = 172;
|
|
34
33
|
const sha256 = n => createHash('sha256').update(n).digest().toString('hex');
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const
|
|
34
|
+
const sigHashType = Buffer.from('01', 'hex');
|
|
35
|
+
const unsignedScript = '';
|
|
36
|
+
const version = 1;
|
|
38
37
|
|
|
39
38
|
/** Recover funds sent to a P2PK using the node identity key
|
|
40
39
|
|
|
@@ -97,14 +96,16 @@ module.exports = ({id, lnd, request, vout}, cbk) => {
|
|
|
97
96
|
|
|
98
97
|
// Derive the transaction details
|
|
99
98
|
output: ['getIdentity', 'getTx', ({getIdentity, getTx}, cbk) => {
|
|
100
|
-
const
|
|
99
|
+
const {transaction} = getTx;
|
|
101
100
|
|
|
102
101
|
// Make sure the tx data matches the input id
|
|
103
|
-
if (
|
|
102
|
+
if (idForTransaction({transaction}).id !== id) {
|
|
104
103
|
return cbk([503, 'ExpectedTransactionIdToMatchTxDataHashToRecover']);
|
|
105
104
|
}
|
|
106
105
|
|
|
107
|
-
const
|
|
106
|
+
const {outputs} = componentsOfTransaction({transaction});
|
|
107
|
+
|
|
108
|
+
const output = outputs[vout];
|
|
108
109
|
|
|
109
110
|
// Make sure the output exists
|
|
110
111
|
if (!output) {
|
|
@@ -113,14 +114,16 @@ module.exports = ({id, lnd, request, vout}, cbk) => {
|
|
|
113
114
|
|
|
114
115
|
const identityKey = hexAsBuffer(getIdentity.public_key);
|
|
115
116
|
|
|
116
|
-
const
|
|
117
|
+
const expected = scriptElementsAsScript({
|
|
118
|
+
elements: [identityKey, OP_CHECKSIG],
|
|
119
|
+
});
|
|
117
120
|
|
|
118
121
|
// Make sure that the output pays to the node identity key
|
|
119
|
-
if (
|
|
122
|
+
if (output.script !== expected.script) {
|
|
120
123
|
return cbk([503, 'ExpectedOutputPayingToNodeIdentityPublicKey']);
|
|
121
124
|
}
|
|
122
125
|
|
|
123
|
-
return cbk(null, {script: output.script, tokens: output.
|
|
126
|
+
return cbk(null, {script: output.script, tokens: output.tokens});
|
|
124
127
|
}],
|
|
125
128
|
|
|
126
129
|
// Create a recovery address
|
|
@@ -128,45 +131,73 @@ module.exports = ({id, lnd, request, vout}, cbk) => {
|
|
|
128
131
|
return createChainAddress({format, lnd}, cbk);
|
|
129
132
|
}],
|
|
130
133
|
|
|
131
|
-
//
|
|
132
|
-
|
|
134
|
+
// Derive the output script of the recovery address
|
|
135
|
+
recoveryScript: [
|
|
133
136
|
'createAddress',
|
|
134
|
-
'getFee',
|
|
135
137
|
'getNetwork',
|
|
136
|
-
|
|
137
|
-
({createAddress, getFee, getNetwork, output}, cbk) =>
|
|
138
|
+
({createAddress, getNetwork}, cbk) =>
|
|
138
139
|
{
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
try {
|
|
141
|
+
const {script} = outputScriptForAddress({
|
|
142
|
+
address: createAddress.address,
|
|
143
|
+
network: getNetwork.network,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return cbk(null, {script});
|
|
147
|
+
} catch (err) {
|
|
148
|
+
return cbk([503, 'FailedToParseRecoveryAddress', {err}]);
|
|
149
|
+
}
|
|
150
|
+
}],
|
|
143
151
|
|
|
144
|
-
|
|
145
|
-
|
|
152
|
+
// Derive the sweep output, less the amount needed to pay for chain fees
|
|
153
|
+
sweep: [
|
|
154
|
+
'getFee',
|
|
155
|
+
'output',
|
|
156
|
+
'recoveryScript',
|
|
157
|
+
({getFee, output, recoveryScript}, cbk) =>
|
|
158
|
+
{
|
|
159
|
+
const {script} = recoveryScript;
|
|
160
|
+
|
|
161
|
+
const unsigned = transactionFromComponents({
|
|
162
|
+
locktime,
|
|
163
|
+
version,
|
|
164
|
+
inputs: [{
|
|
165
|
+
id,
|
|
166
|
+
vout,
|
|
167
|
+
script: unsignedScript,
|
|
168
|
+
sequence: inputSequence,
|
|
169
|
+
}],
|
|
170
|
+
outputs: [{script, tokens: output.tokens}],
|
|
171
|
+
});
|
|
146
172
|
|
|
147
|
-
|
|
148
|
-
const [out] = tx.outs;
|
|
173
|
+
const {vsize} = sizeOfTransaction(unsigned);
|
|
149
174
|
|
|
150
175
|
// Include the prospective signature in the tx total weight
|
|
151
|
-
const vbytes =
|
|
176
|
+
const vbytes = vsize + estimatedSignatureSize;
|
|
152
177
|
|
|
153
178
|
// Reduce the sweep value by the amount needed to pay for chain fees
|
|
154
|
-
|
|
179
|
+
const tokens = output.tokens - ceil(vbytes * getFee.tokens_per_vbyte);
|
|
155
180
|
|
|
156
|
-
return cbk(null,
|
|
181
|
+
return cbk(null, {script, tokens});
|
|
157
182
|
}],
|
|
158
183
|
|
|
159
184
|
// Derive the preimage to use for signing
|
|
160
|
-
preimage: ['output', '
|
|
161
|
-
const cloneTx = txToSign.clone();
|
|
162
|
-
|
|
163
|
-
const [input] = cloneTx.ins;
|
|
164
|
-
|
|
185
|
+
preimage: ['output', 'sweep', ({output, sweep}, cbk) => {
|
|
165
186
|
// When signing, the input to sign is set to the previous output script
|
|
166
|
-
|
|
187
|
+
const {transaction} = transactionFromComponents({
|
|
188
|
+
locktime,
|
|
189
|
+
version,
|
|
190
|
+
inputs: [{
|
|
191
|
+
id,
|
|
192
|
+
vout,
|
|
193
|
+
script: output.script,
|
|
194
|
+
sequence: inputSequence,
|
|
195
|
+
}],
|
|
196
|
+
outputs: [sweep],
|
|
197
|
+
});
|
|
167
198
|
|
|
168
199
|
// The bytes to sign are the tx itself plus the signature hash flag
|
|
169
|
-
return cbk(null, concat([
|
|
200
|
+
return cbk(null, concat([hexAsBuffer(transaction), hashFlag]));
|
|
170
201
|
}],
|
|
171
202
|
|
|
172
203
|
// Give the preimage to signer, hashed once - signBytes does 2nd SHA hash
|
|
@@ -181,17 +212,18 @@ module.exports = ({id, lnd, request, vout}, cbk) => {
|
|
|
181
212
|
}],
|
|
182
213
|
|
|
183
214
|
// Put together the signature with the transaction
|
|
184
|
-
signedTx: ['getSig', '
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
215
|
+
signedTx: ['getSig', 'sweep', ({getSig, sweep}, cbk) => {
|
|
216
|
+
// A chain signature is the DER signature followed by the sighash type
|
|
217
|
+
const signature = concat([hexAsBuffer(getSig.signature), sigHashType]);
|
|
218
|
+
|
|
219
|
+
const {script} = scriptElementsAsScript({elements: [signature]});
|
|
220
|
+
|
|
221
|
+
return cbk(null, transactionFromComponents({
|
|
222
|
+
locktime,
|
|
223
|
+
version,
|
|
224
|
+
inputs: [{id, script, vout, sequence: inputSequence}],
|
|
225
|
+
outputs: [sweep],
|
|
226
|
+
}));
|
|
195
227
|
}],
|
|
196
228
|
|
|
197
229
|
// Broadcast the signed transaction
|
|
@@ -199,7 +231,7 @@ module.exports = ({id, lnd, request, vout}, cbk) => {
|
|
|
199
231
|
return broadcastChainTransaction({
|
|
200
232
|
description,
|
|
201
233
|
lnd,
|
|
202
|
-
transaction: signedTx.
|
|
234
|
+
transaction: signedTx.transaction,
|
|
203
235
|
},
|
|
204
236
|
cbk);
|
|
205
237
|
}],
|
|
@@ -214,7 +246,7 @@ module.exports = ({id, lnd, request, vout}, cbk) => {
|
|
|
214
246
|
return cbk(null, {
|
|
215
247
|
recovering: output.tokens,
|
|
216
248
|
recovering_to: createAddress.address,
|
|
217
|
-
transaction_id: signedTx.
|
|
249
|
+
transaction_id: idForTransaction(signedTx).id,
|
|
218
250
|
});
|
|
219
251
|
}],
|
|
220
252
|
},
|
package/display/constants.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
+
"durationUnits": [
|
|
3
|
+
{"ms": 86400000, "unit": "day"},
|
|
4
|
+
{"ms": 3600000, "unit": "hour"},
|
|
5
|
+
{"ms": 60000, "unit": "minute"},
|
|
6
|
+
{"ms": 1000, "unit": "second"}
|
|
7
|
+
],
|
|
2
8
|
"parseErrors": {
|
|
3
9
|
"#DIV/0!": "CannotDivideByZeroInFormula",
|
|
4
10
|
"#ERROR!": "FailedToParseFormula",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const {durationUnits} = require('./constants');
|
|
2
|
+
|
|
3
|
+
const {floor} = Math;
|
|
4
|
+
const maxUnits = 2;
|
|
5
|
+
const plural = (count, unit) => `${count} ${unit}${count === 1 ? '' : 's'}`;
|
|
6
|
+
|
|
7
|
+
/** Format a duration for display, using the two largest units of time
|
|
8
|
+
|
|
9
|
+
{
|
|
10
|
+
ms: <Duration Milliseconds Number>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@returns
|
|
14
|
+
{
|
|
15
|
+
display: <Display Formatted Duration String>
|
|
16
|
+
}
|
|
17
|
+
*/
|
|
18
|
+
module.exports = ({ms}) => {
|
|
19
|
+
// Take whole counts of each unit out of the duration, largest unit first
|
|
20
|
+
const {parts, remaining} = durationUnits.reduce((sum, unit) => {
|
|
21
|
+
const count = floor(sum.remaining / unit.ms);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
parts: !count ? sum.parts : sum.parts.concat(plural(count, unit.unit)),
|
|
25
|
+
remaining: sum.remaining - count * unit.ms,
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
{parts: [], remaining: ms});
|
|
29
|
+
|
|
30
|
+
// Leftover milliseconds are shown when present or when there is nothing else
|
|
31
|
+
const leftover = !remaining && !!parts.length ? [] : [`${remaining} ms`];
|
|
32
|
+
|
|
33
|
+
return {display: parts.concat(leftover).slice(0, maxUnits).join(' ')};
|
|
34
|
+
};
|
package/display/index.js
CHANGED
|
@@ -2,6 +2,7 @@ const chartAliasForPeer = require('./chart_alias_for_peer');
|
|
|
2
2
|
const describeConfidence = require('./describe_confidence');
|
|
3
3
|
const describeRoute = require('./describe_route');
|
|
4
4
|
const describeRoutingFailure = require('./describe_routing_failure');
|
|
5
|
+
const formatDuration = require('./format_duration');
|
|
5
6
|
const formatFeeRate = require('./format_fee_rate');
|
|
6
7
|
const getIcons = require('./get_icons');
|
|
7
8
|
const isMatchingFilters = require('./is_matching_filters');
|
|
@@ -13,6 +14,7 @@ module.exports = {
|
|
|
13
14
|
describeConfidence,
|
|
14
15
|
describeRoute,
|
|
15
16
|
describeRoutingFailure,
|
|
17
|
+
formatDuration,
|
|
16
18
|
formatFeeRate,
|
|
17
19
|
getIcons,
|
|
18
20
|
isMatchingFilters,
|
package/package.json
CHANGED
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
"url": "https://github.com/alexbosworth/balanceofsatoshis/issues"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@alexbosworth/blockchain": "4.
|
|
18
|
+
"@alexbosworth/blockchain": "4.9.0",
|
|
19
19
|
"@alexbosworth/caporal": "2.0.0",
|
|
20
20
|
"@alexbosworth/fiat": "2.0.0",
|
|
21
|
-
"@alexbosworth/formulas": "1.
|
|
21
|
+
"@alexbosworth/formulas": "1.5.0",
|
|
22
22
|
"@alexbosworth/html2unicode": "1.1.5",
|
|
23
23
|
"@alexbosworth/node-fetch": "2.6.2",
|
|
24
24
|
"abort-controller": "3.0.0",
|
|
@@ -33,18 +33,18 @@
|
|
|
33
33
|
"bolt07": "2.0.0",
|
|
34
34
|
"cbor": "10.0.12",
|
|
35
35
|
"colorette": "2.0.20",
|
|
36
|
-
"csv-parse": "7.0.
|
|
36
|
+
"csv-parse": "7.0.3",
|
|
37
37
|
"goldengate": "16.0.7",
|
|
38
38
|
"grammy": "1.46.0",
|
|
39
39
|
"import-lazy": "4.0.0",
|
|
40
40
|
"ini": "7.0.0",
|
|
41
41
|
"inquirer": "14.2.2",
|
|
42
42
|
"ln-accounting": "10.0.7",
|
|
43
|
-
"ln-service": "
|
|
43
|
+
"ln-service": "60.0.0",
|
|
44
44
|
"ln-sync": "8.0.6",
|
|
45
45
|
"ln-telegram": "8.0.6",
|
|
46
46
|
"moment": "2.31.0",
|
|
47
|
-
"paid-services": "
|
|
47
|
+
"paid-services": "9.0.0",
|
|
48
48
|
"probing": "7.0.0",
|
|
49
49
|
"qrcode-terminal": "0.12.0",
|
|
50
50
|
"sanitize-filename": "1.6.4",
|
|
@@ -107,5 +107,5 @@
|
|
|
107
107
|
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64 -t alexbosworth/balanceofsatoshis -t alexbosworth/balanceofsatoshis:$npm_package_version --push .",
|
|
108
108
|
"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"
|
|
109
109
|
},
|
|
110
|
-
"version": "24.0
|
|
110
|
+
"version": "24.1.0"
|
|
111
111
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
2
|
const {getIdentity} = require('ln-service');
|
|
3
3
|
const {getNetwork} = require('ln-sync');
|
|
4
|
+
const {outputScriptForAddress} = require('@alexbosworth/blockchain');
|
|
4
5
|
const {returnResult} = require('asyncjs-util');
|
|
5
6
|
const {subscribeToOpenRequests} = require('ln-service');
|
|
6
7
|
|
|
7
8
|
const detectOpenRuleViolation = require('./detect_open_rule_violation');
|
|
8
9
|
const openRequestViolation = require('./open_request_violation');
|
|
9
|
-
const {outputScriptForAddress} = require('./../chain');
|
|
10
10
|
|
|
11
11
|
const {isArray} = Array;
|
|
12
12
|
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
package/routing/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const getIgnores = require('./get_ignores');
|
|
|
8
8
|
const getPastForwards = require('./get_past_forwards');
|
|
9
9
|
const giftRoute = require('./gift_route');
|
|
10
10
|
const ignoreFromAvoid = require('./ignore_from_avoid');
|
|
11
|
+
const manageFees = require('./manage_fees');
|
|
11
12
|
|
|
12
13
|
module.exports = {
|
|
13
14
|
adjustFees,
|
|
@@ -20,4 +21,5 @@ module.exports = {
|
|
|
20
21
|
getPastForwards,
|
|
21
22
|
giftRoute,
|
|
22
23
|
ignoreFromAvoid,
|
|
24
|
+
manageFees,
|
|
23
25
|
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncForever = require('async/forever');
|
|
3
|
+
const {getBorderCharacters} = require('table');
|
|
4
|
+
const moment = require('moment');
|
|
5
|
+
const renderTable = require('table').table;
|
|
6
|
+
const {returnResult} = require('asyncjs-util');
|
|
7
|
+
|
|
8
|
+
const adjustFees = require('./adjust_fees');
|
|
9
|
+
const {formatDuration} = require('./../display');
|
|
10
|
+
const parseIntervalFormula = require('./../swaps/parse_interval_formula');
|
|
11
|
+
|
|
12
|
+
const border = getBorderCharacters('norc');
|
|
13
|
+
const calendarFormats = {sameElse: 'L [at] LT'};
|
|
14
|
+
const {isArray} = Array;
|
|
15
|
+
|
|
16
|
+
/** Manage routing fee adjustments
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
[cltv_delta]: <Set CLTV Delta Number>
|
|
20
|
+
[fee_rate]: <Fee Rate String>
|
|
21
|
+
fs: {
|
|
22
|
+
getFile: <Read File Contents Function> (path, cbk) => {}
|
|
23
|
+
}
|
|
24
|
+
[inbound_rate_discount]: <Discount Fee Rate Number>
|
|
25
|
+
lnd: <Authenticated LND API Object>
|
|
26
|
+
logger: <Winston Logger Object>
|
|
27
|
+
[repeat_interval_ms]: <Repeat After Interval Milliseconds Formula String>
|
|
28
|
+
to: [<Adjust Routing Fee To Peer Alias or Public Key or Tag String>]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@returns via cbk or Promise
|
|
32
|
+
{
|
|
33
|
+
rows: [[<Table Cell String>]]
|
|
34
|
+
}
|
|
35
|
+
*/
|
|
36
|
+
module.exports = (args, cbk) => {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
return asyncAuto({
|
|
39
|
+
// Check arguments
|
|
40
|
+
validate: cbk => {
|
|
41
|
+
if (!args.fs) {
|
|
42
|
+
return cbk([400, 'ExpectedFsMethodsToManageFees']);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!args.lnd) {
|
|
46
|
+
return cbk([400, 'ExpectedLndToManageFees']);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!args.logger) {
|
|
50
|
+
return cbk([400, 'ExpectedLoggerToManageFees']);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (isArray(args.repeat_interval_ms)) {
|
|
54
|
+
return cbk([400, 'ExpectedSingleRepeatIntervalValue']);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return cbk();
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
// Check that the repeat interval formula is valid before starting
|
|
61
|
+
interval: ['validate', ({}, cbk) => {
|
|
62
|
+
// Exit early when the fee adjustment is not repeated
|
|
63
|
+
if (args.repeat_interval_ms === undefined) {
|
|
64
|
+
return cbk();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const {failure} = parseIntervalFormula({
|
|
68
|
+
formula: args.repeat_interval_ms,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (!!failure) {
|
|
72
|
+
return cbk([400, 'FailedToParseRepeatIntervalFormula', {failure}]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return cbk();
|
|
76
|
+
}],
|
|
77
|
+
|
|
78
|
+
// Adjust the fees
|
|
79
|
+
adjust: ['interval', ({}, cbk) => {
|
|
80
|
+
const execute = cbk => adjustFees({
|
|
81
|
+
cltv_delta: args.cltv_delta,
|
|
82
|
+
fee_rate: args.fee_rate,
|
|
83
|
+
fs: args.fs,
|
|
84
|
+
inbound_rate_discount: args.inbound_rate_discount,
|
|
85
|
+
lnd: args.lnd,
|
|
86
|
+
logger: args.logger,
|
|
87
|
+
to: args.to,
|
|
88
|
+
},
|
|
89
|
+
cbk);
|
|
90
|
+
|
|
91
|
+
// Exit early when the fees are only adjusted once
|
|
92
|
+
if (args.repeat_interval_ms === undefined) {
|
|
93
|
+
return execute(cbk);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Adjust the fees again every time the interval elapses
|
|
97
|
+
return asyncForever(cbk => {
|
|
98
|
+
return execute((err, res) => {
|
|
99
|
+
if (!!err) {
|
|
100
|
+
args.logger.error({err});
|
|
101
|
+
} else {
|
|
102
|
+
args.logger.info(renderTable(res.rows, {border}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// The interval formula is evaluated again after every attempt
|
|
106
|
+
const {failure, ms} = parseIntervalFormula({
|
|
107
|
+
formula: args.repeat_interval_ms,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (!!failure) {
|
|
111
|
+
return cbk([400, 'FailedToEvaluateRepeatInterval', {failure}]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const next = moment().add(ms, 'ms');
|
|
115
|
+
|
|
116
|
+
args.logger.info({
|
|
117
|
+
repeat_interval: formatDuration({ms}).display,
|
|
118
|
+
repeating_fees_at: next.calendar(null, calendarFormats),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return setTimeout(cbk, ms);
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
cbk);
|
|
125
|
+
}],
|
|
126
|
+
},
|
|
127
|
+
returnResult({reject, resolve, of: 'adjust'}, cbk));
|
|
128
|
+
});
|
|
129
|
+
};
|
|
@@ -9,12 +9,12 @@ const {getNodeAlias} = require('ln-sync');
|
|
|
9
9
|
const {getPublicKey} = require('ln-service');
|
|
10
10
|
const moment = require('moment');
|
|
11
11
|
const {networks} = require('bitcoinjs-lib');
|
|
12
|
+
const {outputScriptForAddress} = require('@alexbosworth/blockchain');
|
|
12
13
|
const {returnResult} = require('asyncjs-util');
|
|
13
14
|
|
|
14
15
|
const acceptBalancedChannel = require('./accept_balanced_channel');
|
|
15
16
|
const getBalancedOpens = require('./get_balanced_opens');
|
|
16
17
|
const initiateBalancedChannel = require('./initiate_balanced_channel');
|
|
17
|
-
const {outputScriptForAddress} = require('./../chain');
|
|
18
18
|
const recoverTransitFunds = require('./recover_transit_funds');
|
|
19
19
|
|
|
20
20
|
const bufferAsHex = buffer => buffer.toString('hex');
|