balanceofsatoshis 24.0.0 → 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 CHANGED
@@ -1,6 +1,14 @@
1
1
  # Versions
2
2
 
3
- ## 24.0.0
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
+
11
+ ## 24.0.1
4
12
 
5
13
  ### Breaking Changes
6
14
 
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
- #!/bin/bash
339
- # Raise the outbound fees to a public key when inbound increases
340
- /home/ubuntu/.npm-global/bin/bos fees --to PUBLIC_KEY --set-fee-rate="IF(INBOUND>10000000,1000,500)"
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
- You can also create a shell-script.sh to run a command repeatedly, with a delay
433
+ Commands like `fees` and `rebalance` can also repeat themselves, with a delay
435
434
 
436
- ```bash
437
- while true;
438
- do bos rebalance;
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.adjustFees({
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
  };
@@ -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 networkNames = {btc: 'bitcoin', btctestnet: 'testnet'};
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 slicePoint = r => r.length === 33 ? r.slice(1) : r;
36
- const {toOutputScript} = bitcoinjsLib.address;
37
- const {Transaction} = bitcoinjsLib;
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 tx = fromHex(getTx.transaction);
99
+ const {transaction} = getTx;
101
100
 
102
101
  // Make sure the tx data matches the input id
103
- if (tx.getId() !== id) {
102
+ if (idForTransaction({transaction}).id !== id) {
104
103
  return cbk([503, 'ExpectedTransactionIdToMatchTxDataHashToRecover']);
105
104
  }
106
105
 
107
- const output = tx.outs[vout];
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 expectedScript = compile([identityKey, OP_CHECKSIG]);
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 (!output.script.equals(expectedScript)) {
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.value});
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
- // Make the transaction to sign
132
- txToSign: [
134
+ // Derive the output script of the recovery address
135
+ recoveryScript: [
133
136
  'createAddress',
134
- 'getFee',
135
137
  'getNetwork',
136
- 'output',
137
- ({createAddress, getFee, getNetwork, output}, cbk) =>
138
+ ({createAddress, getNetwork}, cbk) =>
138
139
  {
139
- const network = networks[networkNames[getNetwork.network]];
140
- const tx = new Transaction();
141
-
142
- const scriptPubKey = toOutputScript(createAddress.address, network);
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
- tx.addInput(hexAsBuffer(id).reverse(), vout, inputSequence);
145
- tx.addOutput(scriptPubKey, output.tokens);
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
- // There is only one output on the sweep transaction
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 = tx.virtualSize() + estimatedSignatureSize;
176
+ const vbytes = vsize + estimatedSignatureSize;
152
177
 
153
178
  // Reduce the sweep value by the amount needed to pay for chain fees
154
- out.value -= ceil(vbytes * getFee.tokens_per_vbyte);
179
+ const tokens = output.tokens - ceil(vbytes * getFee.tokens_per_vbyte);
155
180
 
156
- return cbk(null, tx);
181
+ return cbk(null, {script, tokens});
157
182
  }],
158
183
 
159
184
  // Derive the preimage to use for signing
160
- preimage: ['output', 'txToSign', ({output, txToSign}, cbk) => {
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
- input.script = output.script;
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([cloneTx.toBuffer(), hashFlag]));
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', 'txToSign', ({getSig, txToSign}, cbk) => {
185
- const {r, s} = decode(hexAsBuffer(getSig.signature));
186
-
187
- const rValue = slicePoint(r);
188
-
189
- // Convert the signature from sign bytes to a chain signature
190
- const scriptSig = encode(concat([rValue, s]), Transaction.SIGHASH_ALL);
191
-
192
- txToSign.setInputScript(inputIndex, compile([scriptSig]));
193
-
194
- return cbk(null, txToSign);
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.toHex(),
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.getId(),
249
+ transaction_id: idForTransaction(signedTx).id,
218
250
  });
219
251
  }],
220
252
  },
@@ -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.8.0",
18
+ "@alexbosworth/blockchain": "4.9.0",
19
19
  "@alexbosworth/caporal": "2.0.0",
20
20
  "@alexbosworth/fiat": "2.0.0",
21
- "@alexbosworth/formulas": "1.4.0",
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.2",
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": "59.7.0",
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": "8.0.7",
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.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');