balanceofsatoshis 11.0.0 → 11.3.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 +21 -0
- package/README.md +131 -0
- package/bos +8 -4
- package/commands/api.json +11 -0
- package/commands/call_raw_api.js +10 -0
- package/fiat/index.js +0 -2
- package/lnd/credential_restrictions.js +41 -0
- package/lnd/find_record.js +45 -1
- package/lnd/get_credentials.js +2 -0
- package/lnd/lnd_credentials.js +15 -4
- package/network/transfer_funds.js +40 -1
- package/package.json +5 -5
- package/peers/find_tag_match.js +4 -1
- package/routing/get_ignores.js +106 -5
- package/services/get_balanced_opens.js +26 -2
- package/swaps/manage_rebalance.js +10 -2
- package/test/lnd/test_credential_restrictions.js +74 -0
- package/test/services/test_get_balanced_opens.js +11 -0
- package/fiat/get_price_chart.js +0 -194
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## Version 11.3.0
|
|
4
|
+
|
|
5
|
+
- `credentials`: Allow specifying specific methods to allow in a credential
|
|
6
|
+
|
|
7
|
+
## Version 11.2.1
|
|
8
|
+
|
|
9
|
+
- Improve support for LND v0.13.3
|
|
10
|
+
|
|
11
|
+
## Version 11.2.0
|
|
12
|
+
|
|
13
|
+
- `rebalance`: Add support for `key/formula` expressions in `--avoid`
|
|
14
|
+
|
|
15
|
+
## Version 11.1.0
|
|
16
|
+
|
|
17
|
+
- `avoid`: Add `capacity` to reference channel capacity amount
|
|
18
|
+
- `call`: Add support for force closing a channel
|
|
19
|
+
- `find`: Improve lookup speed when querying a tx id or peer
|
|
20
|
+
- `open-balanced-channel`: Avoid showing incoming requests that were accepted
|
|
21
|
+
- `rebalance`: Add `capacity` variable to `--in-filter` and `--out-filter`
|
|
22
|
+
- `transfer`: Add `in_inbound` and `in_outbound` variables to amount formulas
|
|
23
|
+
|
|
3
24
|
## Version 11.0.0
|
|
4
25
|
|
|
5
26
|
- `rebalance`: Add `--in-filter` to filter inbound tagged nodes
|
package/README.md
CHANGED
|
@@ -524,3 +524,134 @@ You can also create an alias to run a command in the background
|
|
|
524
524
|
```shell
|
|
525
525
|
alias bosd="docker run -d --rm -v $HOME/.bos:/home/node/.bos alexbosworth/balanceofsatoshis"
|
|
526
526
|
```
|
|
527
|
+
|
|
528
|
+
## Formulas
|
|
529
|
+
|
|
530
|
+
Some commands take formula arguments. Formulas are expressions that allow you to perform
|
|
531
|
+
functions and reference variables.
|
|
532
|
+
|
|
533
|
+
There is a dynamic playground here where you can play with expressions:
|
|
534
|
+
https://formulajs.info/functions/
|
|
535
|
+
|
|
536
|
+
### `amount`
|
|
537
|
+
|
|
538
|
+
Formula amounts are supported in the following commands:
|
|
539
|
+
|
|
540
|
+
- `fund`
|
|
541
|
+
- `inbound-channel-rules`
|
|
542
|
+
- `open`
|
|
543
|
+
- `probe`
|
|
544
|
+
- `rebalance`
|
|
545
|
+
- `send`
|
|
546
|
+
|
|
547
|
+
When passing an amount you can pass a formula expression, and the following variables are
|
|
548
|
+
defined:
|
|
549
|
+
|
|
550
|
+
- `k`: 1,000
|
|
551
|
+
- `m`: 1,000,000
|
|
552
|
+
|
|
553
|
+
Examples:
|
|
554
|
+
|
|
555
|
+
```
|
|
556
|
+
bos fund <address> 7*m
|
|
557
|
+
// Fund address with value 7,000,000
|
|
558
|
+
|
|
559
|
+
bos probe <key> 100*k
|
|
560
|
+
// Probe to key amount 100,000
|
|
561
|
+
|
|
562
|
+
bos send <key> m/2
|
|
563
|
+
// Push 500,000 to key
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
#### `rebalance`
|
|
567
|
+
|
|
568
|
+
Rebalance defines additional variables for `--amount`:
|
|
569
|
+
|
|
570
|
+
- `capacity`: The total of inbound and outbound
|
|
571
|
+
|
|
572
|
+
And for `--in-filter` and `--out-filter`:
|
|
573
|
+
|
|
574
|
+
- `capacity`: The total capacity with the peer
|
|
575
|
+
- `heights`: The set of heights of the channels with the peer
|
|
576
|
+
- `inbound_liquidity`: The inbound liquidity with the peer
|
|
577
|
+
- `outbound_liquidity`: The outbound liquidity with the peer
|
|
578
|
+
|
|
579
|
+
Example:
|
|
580
|
+
|
|
581
|
+
```
|
|
582
|
+
// Rebalance with a target of 1,000,000
|
|
583
|
+
bos rebalance --amount 1*m
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
#### `send`
|
|
587
|
+
|
|
588
|
+
Send defines additional variables:
|
|
589
|
+
|
|
590
|
+
- `eur`: The value of 1 Euro as defined by rate provider
|
|
591
|
+
- `inbound`: The inbound liquidity with the destination
|
|
592
|
+
- `liquidity`: The total capacity with the destination
|
|
593
|
+
- `outbound`: The inbound liquidity with the destination
|
|
594
|
+
- `usd`: The value of 1 US Dollar as defined by rate provider
|
|
595
|
+
|
|
596
|
+
Example:
|
|
597
|
+
|
|
598
|
+
```
|
|
599
|
+
// Send node $1
|
|
600
|
+
bos send <key> --amount 1*usd
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
#### `transfer`
|
|
604
|
+
|
|
605
|
+
Transfer variables:
|
|
606
|
+
|
|
607
|
+
- `out_inbound`: The outbound liquidity with the outbound peer
|
|
608
|
+
- `out_liquidity`: The total inbound+outbound with the outbound peer
|
|
609
|
+
- `out_outbound`: The total outbound liquidity with the outbound peer
|
|
610
|
+
|
|
611
|
+
Example:
|
|
612
|
+
|
|
613
|
+
```
|
|
614
|
+
// Equalize inbound with a mutual peer
|
|
615
|
+
bos transfer node "in_inbound - (in_inbound + out_inbound)/2" --through peer
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### `fees`
|
|
619
|
+
|
|
620
|
+
Variables can be referenced for `--set-fee-rate`
|
|
621
|
+
|
|
622
|
+
- `fee_rate_of_<pubkey>`: Reference other node's fee rate
|
|
623
|
+
- `inbound`: Remote balance with peer
|
|
624
|
+
- `inbound_fee_rate`: Incoming fee rate
|
|
625
|
+
- `outbound`: Local balance with peer
|
|
626
|
+
|
|
627
|
+
You can also use functions:
|
|
628
|
+
|
|
629
|
+
- `bips(n)`: Set fee as parts per thousand
|
|
630
|
+
- `percent(0.00)`: Set fee as fractional percentage
|
|
631
|
+
|
|
632
|
+
Example:
|
|
633
|
+
|
|
634
|
+
```
|
|
635
|
+
// Set the fee rate to a tag to 1% of the value forwarded
|
|
636
|
+
bos fees --to tag --set-fee-rate "percent(1)"
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### `inbound-channel-rules`
|
|
640
|
+
|
|
641
|
+
Pass formulas for rules with `--rule`.
|
|
642
|
+
|
|
643
|
+
Formula variables:
|
|
644
|
+
|
|
645
|
+
- `capacities`: sizes of the peer's public channels
|
|
646
|
+
- `capacity`: size of the inbound channel
|
|
647
|
+
- `channel_ages`: block ages of the peer's public channels
|
|
648
|
+
- `fee_rates`: outbound fee rates for the peer
|
|
649
|
+
- `local_balance`: gifted amount on the incoming channel
|
|
650
|
+
- `public_key`: key of the incoming peer
|
|
651
|
+
|
|
652
|
+
Example:
|
|
653
|
+
|
|
654
|
+
```
|
|
655
|
+
// Reject channels that are smaller than 2,000,000 capacity
|
|
656
|
+
bos inbound-channel-rules "capacity < 2*m"
|
|
657
|
+
```
|
package/bos
CHANGED
|
@@ -420,6 +420,7 @@ prog
|
|
|
420
420
|
.help('Output encrypted remote access credentials. Use with "nodes --add"')
|
|
421
421
|
.option('--cleartext', 'Output remote access credentials without encryption')
|
|
422
422
|
.option('--days <days>', 'Expiration days for credentials', INT, 365)
|
|
423
|
+
.option('--method <method_name>', 'White-list specific method', REPEATABLE)
|
|
423
424
|
.option('--node <node_name>', 'Get credentials for a saved node')
|
|
424
425
|
.option('--nospend', 'Credentials do not include spending privileges')
|
|
425
426
|
.option('--readonly', 'Credentials only include read permissions')
|
|
@@ -432,6 +433,7 @@ prog
|
|
|
432
433
|
is_cleartext: options.cleartext,
|
|
433
434
|
is_nospend: options.nospend,
|
|
434
435
|
is_readonly: options.readonly,
|
|
436
|
+
methods: flatten([options.method].filter(n => !!n)),
|
|
435
437
|
node: options.node,
|
|
436
438
|
},
|
|
437
439
|
responses.returnObject({logger, reject, resolve}));
|
|
@@ -1174,18 +1176,19 @@ prog
|
|
|
1174
1176
|
.help('--avoid can take a channel id or a public key to avoid')
|
|
1175
1177
|
.help('--avoid can take a public_key/public_key to avoid a directed pair')
|
|
1176
1178
|
.help('--avoid can take a FORMULA/public_key to avoid inbound peers')
|
|
1179
|
+
.help('--avoid can take a public_key/FORMULA to avoid outbound peers')
|
|
1177
1180
|
.help('--avoid FORMULA variables: FEE_RATE, BASE_FEE, HEIGHT, AGE')
|
|
1178
1181
|
.help('--in decreases the inbound liquidity with a specific peer/tag')
|
|
1179
|
-
.help('--in-filter vars: HEIGHTS
|
|
1182
|
+
.help('--in-filter vars: CAPACITY/HEIGHTS/(INBOUND|OUTBOUND)_LIQUIDITY')
|
|
1180
1183
|
.help('--out increases the inbound liquidity with a specific peer/tag')
|
|
1181
|
-
.help('--out-filter vars: HEIGHTS
|
|
1184
|
+
.help('--out-filter vars: CAPACITY/HEIGHTS(INBOUND|OUTBOUND)_LIQUIDITY')
|
|
1182
1185
|
.option('--amount <amount>', 'Maximum amount to rebalance')
|
|
1183
1186
|
.option('--avoid <pubkey_or_chanid>', 'Avoid forwarding through', REPEATABLE)
|
|
1184
1187
|
.option('--in <pubkey_or_alias>', 'Route in through a specific peer')
|
|
1185
1188
|
.option('--in-filter <in_filter>', 'Filter inbound tag nodes', REPEATABLE)
|
|
1186
1189
|
.option('--in-target-outbound <amt>', 'Balance up to outbound amount')
|
|
1187
|
-
.option('--max-fee <max_fee>', 'Maximum fee to pay'
|
|
1188
|
-
.option('--max-fee-rate <max_fee_rate>', 'Max fee rate to pay'
|
|
1190
|
+
.option('--max-fee <max_fee>', 'Maximum fee to pay')
|
|
1191
|
+
.option('--max-fee-rate <max_fee_rate>', 'Max fee rate to pay')
|
|
1189
1192
|
.option('--minutes <minutes>', 'Time-out route search after N minutes', INT)
|
|
1190
1193
|
.option('--no-color', 'Mute all colors')
|
|
1191
1194
|
.option('--node <node_name>', 'Node to use for rebalance')
|
|
@@ -1560,6 +1563,7 @@ prog
|
|
|
1560
1563
|
.help('Formulas are supported in amount')
|
|
1561
1564
|
.help('Also supported in formulas: OUT_LIQUIDITY (with outbound peer)')
|
|
1562
1565
|
.help('OUT_INBOUND, OUT_OUTBOUND (when specifying outbound peer)')
|
|
1566
|
+
.help('IN_INBOUND, IN_OUTBOUND (when specifying inbound peer)')
|
|
1563
1567
|
.option('--description', 'Label describing transfer')
|
|
1564
1568
|
.option('--dryrun', 'Avoid actually sending funds')
|
|
1565
1569
|
.option('--in <pubkey_or_alias>', 'Route in through a specific peer')
|
package/commands/api.json
CHANGED
|
@@ -50,11 +50,22 @@
|
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
"arguments": [
|
|
53
|
+
{
|
|
54
|
+
"description": "Request close out to address for cooperative close",
|
|
55
|
+
"named": "address",
|
|
56
|
+
"optional": true
|
|
57
|
+
},
|
|
53
58
|
{
|
|
54
59
|
"description": "Requested chain fee tokens per vbyte",
|
|
55
60
|
"named": "tokens_per_vbyte",
|
|
61
|
+
"optional": true,
|
|
56
62
|
"type": "number"
|
|
57
63
|
},
|
|
64
|
+
{
|
|
65
|
+
"description": "Force close the channel",
|
|
66
|
+
"named": "is_force_close",
|
|
67
|
+
"type": "boolean"
|
|
68
|
+
},
|
|
58
69
|
{
|
|
59
70
|
"description": "Hex encoded funding transaction id",
|
|
60
71
|
"named": "transaction_id",
|
package/commands/call_raw_api.js
CHANGED
|
@@ -77,6 +77,16 @@ module.exports = ({ask, lnd, logger, method}, cbk) => {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
return asyncMapSeries(arguments, (argument, cbk) => {
|
|
80
|
+
if (argument.type === 'boolean') {
|
|
81
|
+
return ask({
|
|
82
|
+
default: false,
|
|
83
|
+
name: argument.named,
|
|
84
|
+
message: argument.description,
|
|
85
|
+
type: 'confirm',
|
|
86
|
+
},
|
|
87
|
+
cbk);
|
|
88
|
+
}
|
|
89
|
+
|
|
80
90
|
return ask({
|
|
81
91
|
default: () => !!argument.optional ? String() : undefined,
|
|
82
92
|
message: argument.description,
|
package/fiat/index.js
CHANGED
|
@@ -3,7 +3,6 @@ const getCoindeskCurrentPrice = require('./get_coindesk_current_price');
|
|
|
3
3
|
const getCoindeskRates = require('./get_coindesk_rates');
|
|
4
4
|
const getCoingeckoRates = require('./get_coingecko_rates');
|
|
5
5
|
const getExchangeRates = require('./get_exchange_rates');
|
|
6
|
-
const getPriceChart = require('./get_price_chart');
|
|
7
6
|
const getPrices = require('./get_prices');
|
|
8
7
|
const marketPairs = require('./market').pairs;
|
|
9
8
|
const priceProviders = require('./market').price_providers;
|
|
@@ -22,7 +21,6 @@ module.exports = {
|
|
|
22
21
|
getCoindeskRates,
|
|
23
22
|
getCoingeckoRates,
|
|
24
23
|
getExchangeRates,
|
|
25
|
-
getPriceChart,
|
|
26
24
|
getPrices,
|
|
27
25
|
pairs,
|
|
28
26
|
priceProviders,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const {noSpendPerms} = require('./constants');
|
|
2
|
+
const {permissionEntities} = require('./constants');
|
|
3
|
+
|
|
4
|
+
const readPerms = permissionEntities.map(entity => `${entity}:read`);
|
|
5
|
+
|
|
6
|
+
/** Derive restrictions for macaroon
|
|
7
|
+
|
|
8
|
+
{
|
|
9
|
+
[is_nospend]: <Restrict Credentials To Non-Spending Permissions Bool>
|
|
10
|
+
[is_readonly]: <Restrict Credentials To Read-Only Permissions Bool>
|
|
11
|
+
[methods]: [<Allow Specific Method String>]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@returns
|
|
15
|
+
{
|
|
16
|
+
[allow]: {
|
|
17
|
+
methods: [<Allow Specific Method String>]
|
|
18
|
+
permissions: [<Entity:Action String>]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
*/
|
|
22
|
+
module.exports = args => {
|
|
23
|
+
const methods = args.methods || [];
|
|
24
|
+
|
|
25
|
+
// Exit early when specific credentials are not requested
|
|
26
|
+
if (!args.is_readonly && !args.is_nospend && !methods.length) {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const permissions = [];
|
|
31
|
+
|
|
32
|
+
if (!!args.is_readonly) {
|
|
33
|
+
readPerms.forEach(n => permissions.push(n));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!!args.is_nospend) {
|
|
37
|
+
noSpendPerms.forEach(n => permissions.push(n));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {allow: {methods, permissions}};
|
|
41
|
+
};
|
package/lnd/find_record.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncReflect = require('async/reflect');
|
|
2
3
|
const {chanFormat} = require('bolt07');
|
|
3
4
|
const {formatTokens} = require('ln-sync');
|
|
4
5
|
const {getChannel} = require('ln-service');
|
|
@@ -6,17 +7,21 @@ const {getChannels} = require('ln-service');
|
|
|
6
7
|
const {getClosedChannels} = require('ln-service');
|
|
7
8
|
const {getHeight} = require('ln-service');
|
|
8
9
|
const {getNetworkGraph} = require('ln-service');
|
|
10
|
+
const {getNode} = require('ln-service');
|
|
9
11
|
const {getPayment} = require('ln-service');
|
|
10
12
|
const {getTransactionRecord} = require('ln-sync');
|
|
11
13
|
const {gray} = require('colorette');
|
|
12
14
|
const moment = require('moment');
|
|
13
15
|
const {returnResult} = require('asyncjs-util');
|
|
14
16
|
|
|
17
|
+
const {findKey} = require('ln-sync');
|
|
18
|
+
|
|
15
19
|
const asBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
16
20
|
const balance = ({display}) => display.trim() || gray('0.00000000');
|
|
17
21
|
const blocksTime = (n, p) => moment.duration(n * 10, 'minutes').humanize(p);
|
|
18
22
|
const {isArray} = Array;
|
|
19
23
|
const isHash = n => !!n && /^[0-9A-F]{64}$/i.test(n);
|
|
24
|
+
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/i.test(n);
|
|
20
25
|
const notFound = 404;
|
|
21
26
|
const standardIdHexLength = Buffer.alloc(32).toString('hex').length;
|
|
22
27
|
|
|
@@ -77,8 +82,47 @@ module.exports = ({lnd, query}, cbk) => {
|
|
|
77
82
|
// Get closed
|
|
78
83
|
getClosed: ['validate', ({}, cbk) => getClosedChannels({lnd}, cbk)],
|
|
79
84
|
|
|
85
|
+
// Determine the public key to use
|
|
86
|
+
getKey: ['validate', asyncReflect(({}, cbk) => {
|
|
87
|
+
if (query.length === standardIdHexLength) {
|
|
88
|
+
return cbk();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return findKey({lnd, query}, cbk);
|
|
92
|
+
})],
|
|
93
|
+
|
|
80
94
|
// Get graph
|
|
81
|
-
getGraph: ['
|
|
95
|
+
getGraph: ['getKey', ({getKey}, cbk) => {
|
|
96
|
+
if (query.length === standardIdHexLength) {
|
|
97
|
+
return cbk(null, {channels: [], nodes: []});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!!getKey.value) {
|
|
101
|
+
return getNode({
|
|
102
|
+
lnd,
|
|
103
|
+
public_key: getKey.value.public_key,
|
|
104
|
+
},
|
|
105
|
+
(err, res) => {
|
|
106
|
+
if (!!err) {
|
|
107
|
+
return cbk(err);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return cbk(null, {
|
|
111
|
+
channels: res.channels,
|
|
112
|
+
nodes: [{
|
|
113
|
+
alias: res.alias,
|
|
114
|
+
color: res.color,
|
|
115
|
+
features: res.features,
|
|
116
|
+
public_key: getKey.value.public_key,
|
|
117
|
+
sockets: res.sockets,
|
|
118
|
+
updated_at: res.last_updated,
|
|
119
|
+
}],
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return getNetworkGraph({lnd}, cbk);
|
|
125
|
+
}],
|
|
82
126
|
|
|
83
127
|
// Get blockchain height
|
|
84
128
|
getHeight: ['validate', ({}, cbk) => getHeight({lnd}, cbk)],
|
package/lnd/get_credentials.js
CHANGED
|
@@ -16,6 +16,7 @@ const {pemAsDer} = require('./../encryption');
|
|
|
16
16
|
is_nospend: <Restrict Credentials To Non-Spending Permissions Bool>
|
|
17
17
|
is_readonly: <Restrict Credentials To Read-Only Permissions Bool>
|
|
18
18
|
logger: <Winston Logger Object> ({info}) => ()
|
|
19
|
+
[methods]: [<Allow Specific Method String>]
|
|
19
20
|
[node]: <Node Name String>
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -81,6 +82,7 @@ module.exports = (args, cbk) => {
|
|
|
81
82
|
is_nospend: args.is_nospend,
|
|
82
83
|
is_readonly: args.is_readonly,
|
|
83
84
|
logger: args.logger,
|
|
85
|
+
methods: args.methods,
|
|
84
86
|
node: args.node,
|
|
85
87
|
},
|
|
86
88
|
cbk);
|
package/lnd/lnd_credentials.js
CHANGED
|
@@ -12,6 +12,7 @@ const {grantAccess} = require('ln-service');
|
|
|
12
12
|
const {restrictMacaroon} = require('ln-service');
|
|
13
13
|
const {returnResult} = require('asyncjs-util');
|
|
14
14
|
|
|
15
|
+
const credentialRestrictions = require('./credential_restrictions');
|
|
15
16
|
const {decryptCiphertext} = require('./../encryption');
|
|
16
17
|
const {derAsPem} = require('./../encryption');
|
|
17
18
|
const getCert = require('./get_cert');
|
|
@@ -38,6 +39,7 @@ const socket = 'localhost:10009';
|
|
|
38
39
|
[is_readonly]: <Restrict Credentials To Read-Only Permissions Bool>
|
|
39
40
|
[key]: <Encrypt to Public Key DER Hex String>
|
|
40
41
|
[logger]: <Winston Logger Object>
|
|
42
|
+
[methods]: [<Allow Specific Method String>]
|
|
41
43
|
[node]: <Node Name String> // Defaults to default local mainnet node creds
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -199,8 +201,14 @@ module.exports = (args, cbk) => {
|
|
|
199
201
|
'macaroon',
|
|
200
202
|
({credentials, macaroon}, cbk) =>
|
|
201
203
|
{
|
|
204
|
+
const {allow} = credentialRestrictions({
|
|
205
|
+
is_nospend: args.is_nospend,
|
|
206
|
+
is_readonly: args.is_readonly,
|
|
207
|
+
methods: args.methods,
|
|
208
|
+
});
|
|
209
|
+
|
|
202
210
|
// Exit early when readonly credentials are not requested
|
|
203
|
-
if (!
|
|
211
|
+
if (!allow) {
|
|
204
212
|
return cbk(null, {macaroon});
|
|
205
213
|
}
|
|
206
214
|
|
|
@@ -210,9 +218,12 @@ module.exports = (args, cbk) => {
|
|
|
210
218
|
socket: credentials.socket,
|
|
211
219
|
});
|
|
212
220
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
221
|
+
return grantAccess({
|
|
222
|
+
lnd,
|
|
223
|
+
methods: allow.methods,
|
|
224
|
+
permissions: allow.permissions,
|
|
225
|
+
},
|
|
226
|
+
cbk);
|
|
216
227
|
}],
|
|
217
228
|
|
|
218
229
|
// Final credentials with encryption applied
|
|
@@ -160,11 +160,48 @@ module.exports = (args, cbk) => {
|
|
|
160
160
|
// Parse the amount specified
|
|
161
161
|
parseAmount: [
|
|
162
162
|
'getChannels',
|
|
163
|
+
'getInKey',
|
|
163
164
|
'getOutKey',
|
|
165
|
+
'getRemoteChannels',
|
|
164
166
|
'getToKey',
|
|
165
167
|
'outPeer',
|
|
166
|
-
({
|
|
168
|
+
({
|
|
169
|
+
getChannels,
|
|
170
|
+
getInKey,
|
|
171
|
+
getOutKey,
|
|
172
|
+
getRemoteChannels,
|
|
173
|
+
getToKey,
|
|
174
|
+
outPeer,
|
|
175
|
+
},
|
|
176
|
+
cbk) =>
|
|
167
177
|
{
|
|
178
|
+
// Calculate the inbound peer inbound liquidity
|
|
179
|
+
const inInbound = getRemoteChannels.channels
|
|
180
|
+
.filter(n => n.partner_public_key === getInKey.public_key)
|
|
181
|
+
.reduce((sum, chan) => {
|
|
182
|
+
// Treat incoming payment as if they were still remote balance
|
|
183
|
+
const inbound = chan.pending_payments.filter(n => !n.is_outgoing);
|
|
184
|
+
|
|
185
|
+
const pending = sumOf(inbound.map(({tokens}) => tokens));
|
|
186
|
+
|
|
187
|
+
return sum + chan.remote_balance + pending;
|
|
188
|
+
},
|
|
189
|
+
Number());
|
|
190
|
+
|
|
191
|
+
// Calculate the inbound peer outbound liquidity
|
|
192
|
+
const inOutbound = getRemoteChannels.channels
|
|
193
|
+
.filter(n => n.partner_public_key === getInKey.public_key)
|
|
194
|
+
.reduce((sum, chan) => {
|
|
195
|
+
// Treat outgoing payment as if they were still local balance
|
|
196
|
+
const outbound = chan.pending_payments
|
|
197
|
+
.filter(n => !!n.is_outgoing);
|
|
198
|
+
|
|
199
|
+
const pending = sumOf(outbound.map(({tokens}) => tokens));
|
|
200
|
+
|
|
201
|
+
return sum + chan.local_balance + pending;
|
|
202
|
+
},
|
|
203
|
+
Number());
|
|
204
|
+
|
|
168
205
|
// Calculate the outbound peer inbound liquidity
|
|
169
206
|
const outInbound = getChannels.channels
|
|
170
207
|
.filter(n => n.partner_public_key === getOutKey.public_key)
|
|
@@ -194,6 +231,8 @@ module.exports = (args, cbk) => {
|
|
|
194
231
|
|
|
195
232
|
// Variables to use in amount
|
|
196
233
|
const variables = {
|
|
234
|
+
in_inbound: inInbound,
|
|
235
|
+
in_outbound: inOutbound,
|
|
197
236
|
out_inbound: outInbound,
|
|
198
237
|
out_liquidity: sumOf(
|
|
199
238
|
getChannels.channels
|
package/package.json
CHANGED
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"bolt03": "1.2.10",
|
|
24
24
|
"bolt07": "1.7.3",
|
|
25
25
|
"caporal": "1.4.0",
|
|
26
|
-
"cbor": "8.0.
|
|
26
|
+
"cbor": "8.0.2",
|
|
27
27
|
"cert-info": "1.5.1",
|
|
28
|
-
"colorette": "2.0.
|
|
28
|
+
"colorette": "2.0.14",
|
|
29
29
|
"crypto-js": "4.1.1",
|
|
30
30
|
"csv-parse": "4.16.3",
|
|
31
31
|
"goldengate": "10.4.0",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"inquirer": "8.1.5",
|
|
36
36
|
"invoices": "2.0.0",
|
|
37
37
|
"ln-accounting": "5.0.3",
|
|
38
|
-
"ln-service": "52.
|
|
38
|
+
"ln-service": "52.10.2",
|
|
39
39
|
"ln-sync": "2.0.2",
|
|
40
40
|
"ln-telegram": "3.3.0",
|
|
41
41
|
"moment": "2.29.1",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"qrcode-terminal": "0.12.0",
|
|
46
46
|
"sanitize-filename": "1.6.3",
|
|
47
47
|
"stats-lite": "2.2.0",
|
|
48
|
-
"table": "6.7.
|
|
48
|
+
"table": "6.7.2",
|
|
49
49
|
"telegraf": "4.4.2",
|
|
50
50
|
"update-notifier": "5.1.0",
|
|
51
51
|
"window-size": "1.1.1"
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
|
|
81
81
|
"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/fiat/*.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"
|
|
82
82
|
},
|
|
83
|
-
"version": "11.
|
|
83
|
+
"version": "11.3.0"
|
|
84
84
|
}
|
package/peers/find_tag_match.js
CHANGED
|
@@ -83,7 +83,10 @@ module.exports = ({channels, filters, tags, query}) => {
|
|
|
83
83
|
const matching = isMatchingFilters({
|
|
84
84
|
filters: filters || [],
|
|
85
85
|
variables: {
|
|
86
|
-
|
|
86
|
+
capacity: sumOf(withPeer.map(n => n.capacity)),
|
|
87
|
+
heights: withPeer.map(n => {
|
|
88
|
+
return decodeChanId({channel: n.id}).block_height;
|
|
89
|
+
}),
|
|
87
90
|
inbound_liquidity: sumOf(withPeer.map(n => n.remote_balance)),
|
|
88
91
|
outbound_liquidity: sumOf(withPeer.map(n => n.local_balance)),
|
|
89
92
|
},
|
package/routing/get_ignores.js
CHANGED
|
@@ -9,13 +9,17 @@ const {returnResult} = require('asyncjs-util');
|
|
|
9
9
|
|
|
10
10
|
const {describeParseError} = require('./../display');
|
|
11
11
|
|
|
12
|
+
const amountVariables = {btc: 1e8, k: 1e3, m: 1e6, mm: 1e6};
|
|
12
13
|
const asFormula = n => ({formula: n.slice(0, n.length-67), key: n.slice(-66)});
|
|
14
|
+
const asOutFilter = n => ({out_filter: n.slice(67), key: n.slice(0, 66)});
|
|
15
|
+
const {assign} = Object;
|
|
13
16
|
const decodePair = n => n.split('/');
|
|
14
17
|
const flatten = arr => [].concat(...arr);
|
|
15
18
|
const heightFromId = id => Number(id.split('x').shift());
|
|
16
19
|
const {isArray} = Array;
|
|
17
20
|
const isChannel = n => /^\d*x\d*x\d*$/.test(n);
|
|
18
21
|
const isFormula = n => /(.*)\/0[2-3][0-9A-F]{64}$/gim.test(n);
|
|
22
|
+
const isOutFilter = n => /^0[2-3][0-9A-F]{64}\/(.*)/gim.test(n);
|
|
19
23
|
const isPair = n => !!n && /^0[2-3][0-9A-F]{64}\/0[2-3][0-9A-F]{64}$/i.test(n);
|
|
20
24
|
const isPublicKey = n => !!n && /^0[2-3][0-9A-F]{64}$/gim.test(n);
|
|
21
25
|
const {keys} = Object;
|
|
@@ -108,6 +112,11 @@ module.exports = (args, cbk) => {
|
|
|
108
112
|
return asFormula(id);
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
// Exit early when the id is an out filter
|
|
116
|
+
if (isOutFilter(id)) {
|
|
117
|
+
return asOutFilter(id);
|
|
118
|
+
}
|
|
119
|
+
|
|
111
120
|
// Exit early when the id is a public key
|
|
112
121
|
if (isPublicKey(id)) {
|
|
113
122
|
return {node: {from_public_key: id}};
|
|
@@ -161,16 +170,96 @@ module.exports = (args, cbk) => {
|
|
|
161
170
|
|
|
162
171
|
// Get the block height for use in formulas
|
|
163
172
|
getHeight: ['sortedAvoids', ({sortedAvoids}, cbk) => {
|
|
164
|
-
const
|
|
173
|
+
const hasFormula = !!sortedAvoids.find(n => !!n.formula);
|
|
174
|
+
const hasOutFilter = !!sortedAvoids.find(n => !!n.out_filter);
|
|
165
175
|
|
|
166
176
|
// Exit early when there are no formulas
|
|
167
|
-
if (!
|
|
177
|
+
if (!hasFormula && !hasOutFilter) {
|
|
168
178
|
return cbk();
|
|
169
179
|
}
|
|
170
180
|
|
|
171
181
|
return getHeight({lnd: args.lnd}, cbk);
|
|
172
182
|
}],
|
|
173
183
|
|
|
184
|
+
// Get out filter avoids
|
|
185
|
+
getOutFilterIgnores: [
|
|
186
|
+
'getHeight',
|
|
187
|
+
'sortedAvoids',
|
|
188
|
+
({getHeight, sortedAvoids}, cbk) =>
|
|
189
|
+
{
|
|
190
|
+
const filters = sortedAvoids
|
|
191
|
+
.filter(n => !!n.out_filter)
|
|
192
|
+
.map(n => ({formula: n.out_filter, key: n.key}));
|
|
193
|
+
|
|
194
|
+
return asyncMap(filters, ({formula, key}, cbk) => {
|
|
195
|
+
return getNode({lnd: args.lnd, public_key: key}, (err, res) => {
|
|
196
|
+
// Exit early when the node in question is unknown
|
|
197
|
+
if (isArray(err) && err.slice().shift() === 404) {
|
|
198
|
+
return cbk(null, []);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!!err) {
|
|
202
|
+
return cbk(err);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const outboundAvoids = res.channels
|
|
206
|
+
.map(({capacity, id, policies}) => {
|
|
207
|
+
const height = heightFromId(id);
|
|
208
|
+
const outPolicy = policies.find(n => n.public_key === key);
|
|
209
|
+
const peerPolicy = policies.find(n => n.public_key !== key);
|
|
210
|
+
|
|
211
|
+
if (!outPolicy || !peerPolicy) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const parser = new Parser();
|
|
216
|
+
const variables = {};
|
|
217
|
+
|
|
218
|
+
assign(variables, amountVariables);
|
|
219
|
+
|
|
220
|
+
assign(variables, {
|
|
221
|
+
capacity,
|
|
222
|
+
height,
|
|
223
|
+
age: getHeight.current_block_height - height,
|
|
224
|
+
base_fee: Number(outPolicy.base_fee_mtokens) || Number(),
|
|
225
|
+
fee_rate: outPolicy.fee_rate || Number(),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
keys(variables).forEach(key => {
|
|
229
|
+
parser.setVariable(key.toLowerCase(), variables[key]);
|
|
230
|
+
parser.setVariable(key.toUpperCase(), variables[key]);
|
|
231
|
+
|
|
232
|
+
return;
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const parsed = parser.parse(formula);
|
|
236
|
+
|
|
237
|
+
if (!!parsed.error) {
|
|
238
|
+
return {error: describeParseError({error: parsed.error})};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (parsed.result === false) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
from_public_key: key,
|
|
247
|
+
to_public_key: peerPolicy.public_key,
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const {error} = outboundAvoids.find(n => !!n && !!n.error) || {};
|
|
252
|
+
|
|
253
|
+
if (!!error) {
|
|
254
|
+
return cbk([400, 'InvalidAvoidDirective', {error, formula}]);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return cbk(null, outboundAvoids.filter(n => !!n));
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
cbk);
|
|
261
|
+
}],
|
|
262
|
+
|
|
174
263
|
// Get formula avoids
|
|
175
264
|
getFormulaIgnores: [
|
|
176
265
|
'getHeight',
|
|
@@ -181,12 +270,17 @@ module.exports = (args, cbk) => {
|
|
|
181
270
|
|
|
182
271
|
return asyncMap(formulas, ({formula, key}, cbk) => {
|
|
183
272
|
return getNode({lnd: args.lnd, public_key: key}, (err, res) => {
|
|
273
|
+
// Exit early when the node in question is unknown
|
|
274
|
+
if (isArray(err) && err.slice().shift() === 404) {
|
|
275
|
+
return cbk(null, []);
|
|
276
|
+
}
|
|
277
|
+
|
|
184
278
|
if (!!err) {
|
|
185
279
|
return cbk(err);
|
|
186
280
|
}
|
|
187
281
|
|
|
188
282
|
const inboundAvoids = res.channels
|
|
189
|
-
.map(({id, policies}) => {
|
|
283
|
+
.map(({capacity, id, policies}) => {
|
|
190
284
|
const height = heightFromId(id);
|
|
191
285
|
const inPolicy = policies.find(n => n.public_key !== key);
|
|
192
286
|
|
|
@@ -195,13 +289,17 @@ module.exports = (args, cbk) => {
|
|
|
195
289
|
}
|
|
196
290
|
|
|
197
291
|
const parser = new Parser();
|
|
292
|
+
const variables = {};
|
|
198
293
|
|
|
199
|
-
|
|
294
|
+
assign(variables, amountVariables);
|
|
295
|
+
|
|
296
|
+
assign(variables, {
|
|
297
|
+
capacity,
|
|
200
298
|
height,
|
|
201
299
|
age: getHeight.current_block_height - height,
|
|
202
300
|
base_fee: Number(inPolicy.base_fee_mtokens) || Number(),
|
|
203
301
|
fee_rate: inPolicy.fee_rate || Number(),
|
|
204
|
-
};
|
|
302
|
+
});
|
|
205
303
|
|
|
206
304
|
keys(variables).forEach(key => {
|
|
207
305
|
parser.setVariable(key.toLowerCase(), variables[key]);
|
|
@@ -263,11 +361,13 @@ module.exports = (args, cbk) => {
|
|
|
263
361
|
combinedIgnores: [
|
|
264
362
|
'getChannelIgnores',
|
|
265
363
|
'getFormulaIgnores',
|
|
364
|
+
'getOutFilterIgnores',
|
|
266
365
|
'getQueryIgnores',
|
|
267
366
|
'sortedAvoids',
|
|
268
367
|
({
|
|
269
368
|
getChannelIgnores,
|
|
270
369
|
getFormulaIgnores,
|
|
370
|
+
getOutFilterIgnores,
|
|
271
371
|
getQueryIgnores,
|
|
272
372
|
sortedAvoids,
|
|
273
373
|
},
|
|
@@ -276,6 +376,7 @@ module.exports = (args, cbk) => {
|
|
|
276
376
|
const ignore = [
|
|
277
377
|
flatten(getChannelIgnores),
|
|
278
378
|
flatten(getFormulaIgnores),
|
|
379
|
+
flatten(getOutFilterIgnores),
|
|
279
380
|
getQueryIgnores,
|
|
280
381
|
sortedAvoids.map(n => n.node).filter(n => !!n),
|
|
281
382
|
];
|
|
@@ -163,9 +163,33 @@ module.exports = ({lnd}, cbk) => {
|
|
|
163
163
|
return cbk(null, balancedChannelRequests.filter(n => !!n));
|
|
164
164
|
}],
|
|
165
165
|
|
|
166
|
+
// Filter out incoming opens that were already accepted
|
|
167
|
+
unacceptedOpens: ['incomingOpens', ({incomingOpens}, cbk) => {
|
|
168
|
+
const asyncFilter = require('async/filter');
|
|
169
|
+
const {getPayment} = require('ln-service');
|
|
170
|
+
return asyncFilter(incomingOpens, (incoming, cbk) => {
|
|
171
|
+
const {id} = parsePaymentRequest({request: incoming.accept_request});
|
|
172
|
+
|
|
173
|
+
return getPayment({id, lnd}, (err, res) => {
|
|
174
|
+
// An unknown payment means the open was not ack'ed
|
|
175
|
+
if (!!err && err.slice().shift() === 404) {
|
|
176
|
+
return cbk(null, true);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!!err) {
|
|
180
|
+
return cbk(err);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// If an accept request was not paid nothing happened yet
|
|
184
|
+
return cbk(null, !!res.is_failed);
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
cbk);
|
|
188
|
+
}],
|
|
189
|
+
|
|
166
190
|
// Final set of active balanced open requests
|
|
167
|
-
opens: ['
|
|
168
|
-
return cbk(null, {incoming:
|
|
191
|
+
opens: ['unacceptedOpens', ({unacceptedOpens}, cbk) => {
|
|
192
|
+
return cbk(null, {incoming: unacceptedOpens});
|
|
169
193
|
}],
|
|
170
194
|
},
|
|
171
195
|
returnResult({reject, resolve, of: 'opens'}, cbk));
|
|
@@ -43,6 +43,14 @@ module.exports = (args, cbk) => {
|
|
|
43
43
|
return cbk([400, 'ExpectedLoggerToManageRebalance'])
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
if (isArray(args.max_fee)) {
|
|
47
|
+
return cbk([400, 'ExpectedSingleMaxFeeValue']);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isArray(args.max_fee_rate)) {
|
|
51
|
+
return cbk([400, 'ExpectedSingleMaxFeeValue']);
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
if (!args.lnd) {
|
|
47
55
|
return cbk([400, 'ExpectedLndToManageRebalance']);
|
|
48
56
|
}
|
|
@@ -85,8 +93,8 @@ module.exports = (args, cbk) => {
|
|
|
85
93
|
in_through: args.in_through,
|
|
86
94
|
lnd: args.lnd,
|
|
87
95
|
logger: args.logger,
|
|
88
|
-
max_fee: args.max_fee,
|
|
89
|
-
max_fee_rate: args.max_fee_rate,
|
|
96
|
+
max_fee: Number(args.max_fee) || undefined,
|
|
97
|
+
max_fee_rate: Number(args.max_fee_rate) || undefined,
|
|
90
98
|
max_rebalance: args.max_rebalance,
|
|
91
99
|
out_filters: args.out_filters,
|
|
92
100
|
out_inbound: args.out_inbound,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const {test} = require('@alexbosworth/tap');
|
|
2
|
+
|
|
3
|
+
const method = require('./../../lnd/credential_restrictions');
|
|
4
|
+
|
|
5
|
+
const tests = [
|
|
6
|
+
{
|
|
7
|
+
args: {},
|
|
8
|
+
description: 'No restrictions results in no allow elements',
|
|
9
|
+
expected: {},
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
args: {is_nospend: true},
|
|
13
|
+
description: 'No spend results in nospend permissions',
|
|
14
|
+
expected: {
|
|
15
|
+
allow: {
|
|
16
|
+
methods: [],
|
|
17
|
+
permissions: [
|
|
18
|
+
'address:read',
|
|
19
|
+
'address:write',
|
|
20
|
+
'info:read',
|
|
21
|
+
'info:write',
|
|
22
|
+
'invoices:read',
|
|
23
|
+
'invoices:write',
|
|
24
|
+
'macaroon:read',
|
|
25
|
+
'message:read',
|
|
26
|
+
'offchain:read',
|
|
27
|
+
'onchain:read',
|
|
28
|
+
'peers:read',
|
|
29
|
+
'peers:write',
|
|
30
|
+
'signer:read',
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
args: {is_readonly: true},
|
|
37
|
+
description: 'Readonly results in read permissions',
|
|
38
|
+
expected: {
|
|
39
|
+
allow: {
|
|
40
|
+
methods: [],
|
|
41
|
+
permissions: [
|
|
42
|
+
'address:read',
|
|
43
|
+
'info:read',
|
|
44
|
+
'invoices:read',
|
|
45
|
+
'macaroon:read',
|
|
46
|
+
'message:read',
|
|
47
|
+
'offchain:read',
|
|
48
|
+
'onchain:read',
|
|
49
|
+
'peers:read',
|
|
50
|
+
'signer:read',
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
args: {methods: ['getWalletInfo']},
|
|
57
|
+
description: 'Readonly results in read permissions',
|
|
58
|
+
expected: {allow: {methods: ['getWalletInfo'], permissions: []}},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
tests.forEach(({args, description, error, expected}) => {
|
|
63
|
+
return test(description, async ({end, strictSame, throws}) => {
|
|
64
|
+
if (!!error) {
|
|
65
|
+
throws(() => method(args), new Error(error), 'Got expected error');
|
|
66
|
+
} else {
|
|
67
|
+
const res = method(args);
|
|
68
|
+
|
|
69
|
+
strictSame(res, expected, 'Got expected result');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return end();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const EventEmitter = require('events');
|
|
2
|
+
|
|
1
3
|
const {createSignedRequest} = require('invoices');
|
|
2
4
|
const {createUnsignedRequest} = require('invoices');
|
|
3
5
|
const sign = require('secp256k1').ecdsaSign;
|
|
@@ -107,6 +109,15 @@ const makeArgs = overrides => {
|
|
|
107
109
|
});
|
|
108
110
|
},
|
|
109
111
|
},
|
|
112
|
+
router: {
|
|
113
|
+
trackPaymentV2: ({}) => {
|
|
114
|
+
const emitter = new EventEmitter();
|
|
115
|
+
|
|
116
|
+
process.nextTick(() => emitter.emit('data', {status: 'FAILED'}));
|
|
117
|
+
|
|
118
|
+
return emitter;
|
|
119
|
+
},
|
|
120
|
+
},
|
|
110
121
|
},
|
|
111
122
|
};
|
|
112
123
|
|
package/fiat/get_price_chart.js
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
const asyncAuto = require('async/auto');
|
|
2
|
-
const {decodePaymentRequest} = require('ln-service');
|
|
3
|
-
const {getHeight} = require('ln-service');
|
|
4
|
-
const moment = require('moment');
|
|
5
|
-
const {payViaPaymentRequest} = require('ln-service');
|
|
6
|
-
const {returnResult} = require('asyncjs-util');
|
|
7
|
-
|
|
8
|
-
const {authenticatedLnd} = require('./../lnd');
|
|
9
|
-
const {decryptPayload} = require('./../encryption');
|
|
10
|
-
const {exchanges} = require('./market');
|
|
11
|
-
const {pairs} = require('./market');
|
|
12
|
-
|
|
13
|
-
const api = 'https://api.suredbits.com/historical/v0/';
|
|
14
|
-
const base64ToHex = base64 => Buffer.from(base64, 'base64').toString('hex');
|
|
15
|
-
const daysCount = 80;
|
|
16
|
-
const defaultMaxFee = 5;
|
|
17
|
-
const {isArray} = Array;
|
|
18
|
-
const maxCltvDelta = 144 * 30;
|
|
19
|
-
const {parse} = JSON;
|
|
20
|
-
const pathfindingTimeoutMs = 1000 * 60 * 5;
|
|
21
|
-
const titleCase = str => `${str.charAt(0).toUpperCase()}${str.slice(1)}`;
|
|
22
|
-
|
|
23
|
-
/** Get historic exchange rates
|
|
24
|
-
|
|
25
|
-
{
|
|
26
|
-
exchange: <Exchange String>
|
|
27
|
-
[fee]: <Desired Maximum Fee Tokens Number>
|
|
28
|
-
[node]: <Saved Node Name String>
|
|
29
|
-
pair: <Pair String>
|
|
30
|
-
request: <Request Function>
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@returns via cbk or Promise
|
|
34
|
-
{
|
|
35
|
-
description: <Price Range Description String>
|
|
36
|
-
prices: [<Price on Day String>]
|
|
37
|
-
}
|
|
38
|
-
*/
|
|
39
|
-
module.exports = ({exchange, fee, node, pair, request}, cbk) => {
|
|
40
|
-
return new Promise((resolve, reject) => {
|
|
41
|
-
return asyncAuto({
|
|
42
|
-
// Get lnd
|
|
43
|
-
getLnd: cbk => authenticatedLnd({node}, cbk),
|
|
44
|
-
|
|
45
|
-
// Check arguments
|
|
46
|
-
validate: cbk => {
|
|
47
|
-
if (!exchanges.find(n => n === exchange)) {
|
|
48
|
-
return cbk([400, 'ExpectedKnownExchangeToGetPriceData']);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (fee !== undefined && !fee) {
|
|
52
|
-
return cbk([400, 'ExpectedNonZeroMaxFeeToGetPriceData']);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!pairs[pair]) {
|
|
56
|
-
return cbk([400, 'ExpectedKnownPairToGetPriceData']);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!pairs[pair].find(n => n === exchange)) {
|
|
60
|
-
return cbk([400, 'UnsupportedExchange', {supported: pairs[pair]}]);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return cbk();
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
// Get price data
|
|
67
|
-
getPrices: ['validate', ({}, cbk) => {
|
|
68
|
-
const year = moment().format('Y');
|
|
69
|
-
|
|
70
|
-
return request({
|
|
71
|
-
json: true,
|
|
72
|
-
url: `${api}${exchange}/${pair.toUpperCase()}/${year}/daily`,
|
|
73
|
-
},
|
|
74
|
-
(err, r, json) => {
|
|
75
|
-
if (!!err) {
|
|
76
|
-
return cbk([503, 'UnexpectedErrorGettingHistoricalPrices', {err}]);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (!r) {
|
|
80
|
-
return cbk([503, 'ExpectedResponseWhenGettingHistoricalPrices']);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (r.statusCode !== 200) {
|
|
84
|
-
return cbk([503, 'UnexpectedStatusCodeGettingHistoricalPrices'])
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (!json) {
|
|
88
|
-
return cbk([503, 'ExpectedResponseDataFromHistoricalPriceApi']);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!json.encryptedData) {
|
|
92
|
-
return cbk([503, 'ExpectedEncryptedHistoricalPriceData']);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (!json.invoice) {
|
|
96
|
-
return cbk([503, 'ExpectedPaymentRequestForHistoricalPriceData']);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
return cbk(null, {
|
|
101
|
-
encrypted: base64ToHex(json.encryptedData),
|
|
102
|
-
request: json.invoice,
|
|
103
|
-
});
|
|
104
|
-
} catch (err) {
|
|
105
|
-
return cbk([503, 'UnexpectedDataFromHistoricalPriceApi', {err}]);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
}],
|
|
109
|
-
|
|
110
|
-
// Decode request
|
|
111
|
-
decodedRequest: ['getLnd', 'getPrices', ({getLnd, getPrices}, cbk) => {
|
|
112
|
-
return decodePaymentRequest({
|
|
113
|
-
lnd: getLnd.lnd,
|
|
114
|
-
request: getPrices.request,
|
|
115
|
-
},
|
|
116
|
-
cbk);
|
|
117
|
-
}],
|
|
118
|
-
|
|
119
|
-
// Get height
|
|
120
|
-
getHeight: ['decodedRequest', 'getLnd', ({getLnd}, cbk) => {
|
|
121
|
-
return getHeight({lnd: getLnd.lnd}, cbk);
|
|
122
|
-
}],
|
|
123
|
-
|
|
124
|
-
// Purchase preimage needed to decrypt price data
|
|
125
|
-
payInvoice: [
|
|
126
|
-
'decodedRequest',
|
|
127
|
-
'getHeight',
|
|
128
|
-
'getLnd',
|
|
129
|
-
'getPrices',
|
|
130
|
-
({decodedRequest, getHeight, getLnd, getPrices}, cbk) =>
|
|
131
|
-
{
|
|
132
|
-
const {tokens} = decodedRequest;
|
|
133
|
-
|
|
134
|
-
// Check that the payment request doesn't require too many tokens
|
|
135
|
-
if (tokens > (fee || defaultMaxFee)) {
|
|
136
|
-
return cbk([400, 'MaxFeePriceFetchFeeTooLow', {needed_fee: tokens}]);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return payViaPaymentRequest({
|
|
140
|
-
lnd: getLnd.lnd,
|
|
141
|
-
max_fee: (fee || defaultMaxFee) - tokens,
|
|
142
|
-
max_timeout_height: getHeight.current_block_height + maxCltvDelta,
|
|
143
|
-
pathfinding_timeout: pathfindingTimeoutMs,
|
|
144
|
-
request: getPrices.request,
|
|
145
|
-
},
|
|
146
|
-
cbk);
|
|
147
|
-
}],
|
|
148
|
-
|
|
149
|
-
// Decrypt price data
|
|
150
|
-
prices: ['getPrices', 'payInvoice', ({getPrices, payInvoice}, cbk) => {
|
|
151
|
-
const {encrypted} = getPrices;
|
|
152
|
-
const {secret} = payInvoice;
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
const {payload} = decryptPayload({encrypted, secret});
|
|
156
|
-
|
|
157
|
-
if (!isArray(parse(payload)) || !parse(payload).length) {
|
|
158
|
-
return cbk([503, 'ExpectedArrayOfPricesInPayload']);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const prices = parse(payload).slice(-daysCount);
|
|
162
|
-
|
|
163
|
-
if (!!prices.find(n => n.pair !== pair.toUpperCase())) {
|
|
164
|
-
return cbk([503, 'ExpectedPriceForSpecifiedHistoricPair']);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (!!prices.find(n => !n.price)) {
|
|
168
|
-
return cbk([503, 'ExpectedPriceForHistoricPriceQuote']);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (!!prices.find(n => !n.timestamp)) {
|
|
172
|
-
return cbk([503, 'ExpectedTimestampForHistoricPriceQuote']);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const [end] = prices.slice().reverse();
|
|
176
|
-
const [start] = prices;
|
|
177
|
-
|
|
178
|
-
const day0 = `${moment(start.timestamp).format('L')}`;
|
|
179
|
-
const fin = `${moment(end.timestamp).format('L')}`;
|
|
180
|
-
const last = `Last Price: ${end.price}`
|
|
181
|
-
const market = `${titleCase(exchange)} ${start.pair}`;
|
|
182
|
-
|
|
183
|
-
return cbk(null, {
|
|
184
|
-
description: `${market} from ${day0} to ${fin}. ${last}.`,
|
|
185
|
-
prices: prices.map(({price}) => price),
|
|
186
|
-
});
|
|
187
|
-
} catch (err) {
|
|
188
|
-
return cbk([503, 'FailedToDecryptHistoricPricesData', {err}]);
|
|
189
|
-
}
|
|
190
|
-
}],
|
|
191
|
-
},
|
|
192
|
-
returnResult({reject, resolve, of: 'prices'}, cbk));
|
|
193
|
-
});
|
|
194
|
-
};
|