balanceofsatoshis 11.41.0 → 11.45.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/CONTRIBUTING.md +72 -0
- package/bos +13 -4
- package/commands/api.json +28 -0
- package/network/push_payment.js +16 -2
- package/package.json +4 -4
- package/peers/limit_forwarding.js +31 -3
- package/services/initiate_balanced_channel.js +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 11.45.0
|
|
4
|
+
|
|
5
|
+
- `increase-inbound-liquidity`: Add support for formulas in `--amount`
|
|
6
|
+
|
|
7
|
+
## 11.44.0
|
|
8
|
+
|
|
9
|
+
- `send`: Add support for `--max-fee-rate` to limit fees paid via PPM measure
|
|
10
|
+
|
|
11
|
+
## 11.43.0
|
|
12
|
+
|
|
13
|
+
- `limit-forwards`: Add `--min-channel-confirmations` for custom channel ages
|
|
14
|
+
- `limit-forwards`: Add `--only-allow` to restrict forwards to allowed edges
|
|
15
|
+
- `open-balanced-channel`: Disallow fractional fee rate entry
|
|
16
|
+
|
|
17
|
+
## 11.42.0
|
|
18
|
+
|
|
19
|
+
- `call`: Add support for `fundPsbt` to create a funded PSBT ready to sign
|
|
20
|
+
- `call`: Add support for `partiallySignPsbt` to add a partial sig to a PSBT
|
|
21
|
+
- `call`: Add support for `signPsbt` to sign and finalize a PSBT for broadcast
|
|
22
|
+
|
|
3
23
|
## 11.41.0
|
|
4
24
|
|
|
5
25
|
- `remove-peer`: Add interactive mode to select channels to close
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Contribution Guidelines
|
|
2
|
+
|
|
3
|
+
- Feel free to open issues or pull requests
|
|
4
|
+
- They may not be addressed or merged
|
|
5
|
+
- You can ignore coding styles if you want
|
|
6
|
+
|
|
7
|
+
## Coding Style
|
|
8
|
+
|
|
9
|
+
If you want to help with style, here are some rough guidelines on style ideas:
|
|
10
|
+
|
|
11
|
+
### Formatting
|
|
12
|
+
|
|
13
|
+
- Spaces not tabs, 2 spaces
|
|
14
|
+
- Arguments to methods are snake_case
|
|
15
|
+
- Regular variables are camelCase
|
|
16
|
+
- Returned attributes are snake_case
|
|
17
|
+
- Minimize function nesting, make new files if nesting is required
|
|
18
|
+
- No extraneous whitespace
|
|
19
|
+
- A single newline should appear at the end of a file
|
|
20
|
+
- Don't bother with () in functions when not needed: `const a = b => c`
|
|
21
|
+
- If there are multiple things together, alphabetize them
|
|
22
|
+
- Don't split up long strings over multiple lines
|
|
23
|
+
- Lines should be terminated by explicit semicolons
|
|
24
|
+
- Logic like ternary operators should not extend beyond a single line
|
|
25
|
+
- Don't let any lines linger when they don't do anything
|
|
26
|
+
- Tightly space objects, like `{attribute: value}` not `{ attribute : value }`
|
|
27
|
+
- Use single '' quotes not double "" quotes, except when `` is required
|
|
28
|
+
- If conditions should avoid spanning multiple lines
|
|
29
|
+
- Avoid double specifying an attribute and value, `{type: type}` vs `{type}`
|
|
30
|
+
|
|
31
|
+
### Control Flow
|
|
32
|
+
|
|
33
|
+
- Async functions should support both cbk and Promise style
|
|
34
|
+
- Use async.js methods for asynchronous control flow
|
|
35
|
+
- Try to exit early from functions when possible, and note this exit in comment
|
|
36
|
+
- Prefer cbk over Promise style, aside from in tests or in Promise libs
|
|
37
|
+
- Use `asyncAuto` for asynchronous control flow dependency management
|
|
38
|
+
- Callbacks should generally be named cbk even when redefinining in inner scope
|
|
39
|
+
- Avoid mixing non-async complex logic and async control flows in the same file
|
|
40
|
+
- Minimize async nesting in returned attributes and in method arguments
|
|
41
|
+
- Methods should always document their arguments and their output
|
|
42
|
+
|
|
43
|
+
### Variables
|
|
44
|
+
|
|
45
|
+
- Generally use undefined rather than null when defining nil types
|
|
46
|
+
- Prefer variable assignments on new lines rather than on a single line
|
|
47
|
+
- Reduce the usage of . property access, like isArray instead of Array.isArray
|
|
48
|
+
- When there is a newline in an object, always put a comma at the end of a line
|
|
49
|
+
- Short properties always go first in objects: `{short, longer: type}`
|
|
50
|
+
- If a statement relies on a statement above it, it should have a newline above
|
|
51
|
+
- Avoid including scalar values such as strings or numbers in the code itself
|
|
52
|
+
|
|
53
|
+
### JS Features
|
|
54
|
+
|
|
55
|
+
- Limit usage of `let` and never use `var`, prefer `const`
|
|
56
|
+
- Limit use of npm dependencies when possible, only use good dependencies
|
|
57
|
+
- Target support of the oldest node.js LTS release still being supported
|
|
58
|
+
- Never use class or prototype
|
|
59
|
+
- Do not import more methods from an import than you actually use
|
|
60
|
+
- Try to avoid passing objects in arguments as much as possible
|
|
61
|
+
- Prefer using function iteration like map and forEach over for and while
|
|
62
|
+
- Use arrow functions and not `function` functions whenever possible
|
|
63
|
+
|
|
64
|
+
### Errors
|
|
65
|
+
|
|
66
|
+
- Never ignore an error case, always deal with it as soon as possible
|
|
67
|
+
- In the case of errors, do include the error string in the code
|
|
68
|
+
- Add simple validations to help target simple calling mistakes
|
|
69
|
+
- When throwing or returning error messages, use PascalCase for the message
|
|
70
|
+
- Use HTTP status codes as a guideline: 4** is a local issue, 5** is remote
|
|
71
|
+
- Return async errors as arrays: `[typeNumber, errorMsgString, extraDetails]`
|
|
72
|
+
- Try to be very specific with error messages and try to not repeat one
|
package/bos
CHANGED
|
@@ -25,6 +25,7 @@ const {accountingCategories} = commandConstants;
|
|
|
25
25
|
const balances = importLazy('./balances');
|
|
26
26
|
const chain = importLazy('./chain');
|
|
27
27
|
const commands = importLazy('./commands');
|
|
28
|
+
const display = importLazy('./display');
|
|
28
29
|
const encryption = importLazy('./encryption');
|
|
29
30
|
const lnd = importLazy('./lnd');
|
|
30
31
|
const network = importLazy('./network');
|
|
@@ -54,7 +55,6 @@ const months = [...Array(12).keys()].map(n => ++n);
|
|
|
54
55
|
const {REPEATABLE} = prog;
|
|
55
56
|
const {STRING} = prog;
|
|
56
57
|
const yearMatch = /^\d{4}$/;
|
|
57
|
-
|
|
58
58
|
prog
|
|
59
59
|
.version(version)
|
|
60
60
|
|
|
@@ -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}));
|
|
@@ -878,10 +884,11 @@ prog
|
|
|
878
884
|
.command('increase-inbound-liquidity', 'Increase node inbound liquidity')
|
|
879
885
|
.help('Spend down a channel to get inbound. Fee is an estimate, may be more')
|
|
880
886
|
.help('If you want to control chain fee increases, use show-raw-recoveries')
|
|
887
|
+
.help('Formulas supported for --amount like 5*m or 0.05*BTC for 5 million')
|
|
881
888
|
.option('--address <out_address>', 'Out chain address to send funds out to')
|
|
882
889
|
.option('--api-key <api_key>', 'Pre-paid API key to use')
|
|
890
|
+
.option('--amount <amount>', 'Amount to increase inbound', STRING, '500*k')
|
|
883
891
|
.option('--avoid <pubkey/chan/tag>', 'Avoid forwarding through', REPEATABLE)
|
|
884
|
-
.option('--amount <amount>', 'Amount to increase liquidity', INT, 5e5)
|
|
885
892
|
.option('--confs <confs>', 'Confs to consider reorg safe', INT, 1)
|
|
886
893
|
.option('--dryrun', 'Only show cost estimate for increase')
|
|
887
894
|
.option('--fast', 'Request swap server avoid batching delay')
|
|
@@ -924,11 +931,11 @@ prog
|
|
|
924
931
|
spend_address: options.spendAddress || undefined,
|
|
925
932
|
spend_tokens: options.spendAmount || undefined,
|
|
926
933
|
timeout: 1000 * 60 * 60 * 10,
|
|
927
|
-
tokens: options.amount,
|
|
934
|
+
tokens: display.parseAmount({amount: options.amount}).tokens,
|
|
928
935
|
},
|
|
929
936
|
responses.returnObject({exit, logger, reject, resolve}));
|
|
930
937
|
} catch (err) {
|
|
931
|
-
return reject(err);
|
|
938
|
+
return reject(logger.error({err}));
|
|
932
939
|
}
|
|
933
940
|
});
|
|
934
941
|
})
|
|
@@ -1458,6 +1465,7 @@ prog
|
|
|
1458
1465
|
.option('--dryrun', 'Avoid actually sending funds')
|
|
1459
1466
|
.option('--in <pubkey_or_alias>', 'Route in through a specific node')
|
|
1460
1467
|
.option('--max-fee <fee>', 'Maximum fee tokens', INT, 1337)
|
|
1468
|
+
.option('--max-fee-rate <max_fee_rate>', 'Max fee rate in PPM to pay', INT)
|
|
1461
1469
|
.option('--message <message>', 'Message to include with payment')
|
|
1462
1470
|
.option('--message-omit-from-key', 'Leave out the from key on messages')
|
|
1463
1471
|
.option('--no-color', 'Mute all colors')
|
|
@@ -1478,6 +1486,7 @@ prog
|
|
|
1478
1486
|
is_omitting_message_from: options.messageOmitFromKey,
|
|
1479
1487
|
lnd: (await lndForNode(logger, options.node)).lnd,
|
|
1480
1488
|
max_fee: options.maxFee,
|
|
1489
|
+
max_fee_rate: options.maxFeeRate,
|
|
1481
1490
|
message: options.message,
|
|
1482
1491
|
quiz_answers: flatten([options.quiz].filter(n => !!n)),
|
|
1483
1492
|
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/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/package.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"crypto-js": "4.1.1",
|
|
29
29
|
"csv-parse": "5.0.4",
|
|
30
30
|
"goldengate": "11.0.0",
|
|
31
|
-
"grammy": "1.
|
|
31
|
+
"grammy": "1.7.0",
|
|
32
32
|
"hot-formula-parser": "4.0.0",
|
|
33
33
|
"import-lazy": "4.0.0",
|
|
34
34
|
"ini": "2.0.0",
|
|
@@ -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.3",
|
|
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.45.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
|
|