balanceofsatoshis 25.3.1 → 25.4.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 +6 -0
- package/README.md +16 -2
- package/balances/constants.json +14 -1
- package/balances/get_accounting_report.js +95 -10
- package/balances/get_historic_rate.js +75 -0
- package/balances/get_record_rate.js +77 -0
- package/balances/guard_chain_rate.js +50 -0
- package/bos +8 -0
- package/commands/constants.json +1 -1
- package/commands/simple_request.js +48 -13
- package/encryption/encode_as_cbor.js +28 -0
- package/encryption/encrypt_to_secret.js +6 -4
- package/encryption/index.js +2 -0
- package/lnd/get_credentials.js +2 -2
- package/package.json +9 -9
- package/swaps/get_paid_service.js +8 -6
- package/swaps/swap_api_key.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## 25.4.0
|
|
4
|
+
|
|
5
|
+
- `accounting`: After 2021-10-19 derive USD prices from chain payments data
|
|
6
|
+
- `accounting`: Add `--rate-provider coinbase`
|
|
7
|
+
- `telegram`: Reduce telegram linkification in messages
|
|
8
|
+
|
|
3
9
|
## 25.3.1
|
|
4
10
|
|
|
5
11
|
- `closed`: Avoid using external chain data lookups to derive resolution info
|
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@ Commands for working with LND balances.
|
|
|
7
7
|
|
|
8
8
|
Supported LND versions:
|
|
9
9
|
|
|
10
|
-
- v0.21.0-beta to v0.21.
|
|
11
|
-
- v0.20.0-beta to v0.20.
|
|
10
|
+
- v0.21.0-beta to v0.21.5-beta
|
|
11
|
+
- v0.20.0-beta to v0.20.6-beta
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
@@ -298,6 +298,20 @@ If that is set, it will use that node if no node is specified.
|
|
|
298
298
|
You can also add a JSON file to your .bos directory: config.json, add
|
|
299
299
|
`{"default_saved_node": "nodename"}` to set the default via a file instead
|
|
300
300
|
|
|
301
|
+
## Files in `~/.bos`
|
|
302
|
+
|
|
303
|
+
`bos` keeps its files in `~/.bos`, or in `BOS_DATA_PATH` when that is set:
|
|
304
|
+
|
|
305
|
+
- `chain_prices.json`: USD prices of BTC that `bos accounting` derived from
|
|
306
|
+
chain data, kept so that a day is only derived once. Prices are derived
|
|
307
|
+
this way when no `--rate-provider` is given, and the prices of the days
|
|
308
|
+
up to the release of `bos` come with it rather than being written here.
|
|
309
|
+
The file can be deleted, prices are then derived again when needed
|
|
310
|
+
- `config.json`: settings you add, like a default saved node
|
|
311
|
+
- `tags.json`: the tags made with `bos tags`
|
|
312
|
+
- `telegram_bot_api_key`: the bot API key that `bos telegram` asked for
|
|
313
|
+
- `YOUR_NODE_NAME/credentials.json`: the credentials of a saved node
|
|
314
|
+
|
|
301
315
|
## Linux Fu
|
|
302
316
|
|
|
303
317
|
Some commands are designed to return outputs that can be piped or used in other CLI programs.
|
package/balances/constants.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
+
"chainPricesFile": "chain_prices.json",
|
|
3
|
+
"coinbaseProvider": "coinbase",
|
|
2
4
|
"defaultCurrency": "BTC",
|
|
3
5
|
"defaultFiat": "USD",
|
|
6
|
+
"failedRequestErrors": ["UnexpectedErrorGettingCoinbaseCandles"],
|
|
7
|
+
"fsMethods": ["getFile", "makeDirectory", "renameFile", "writeFile"],
|
|
8
|
+
"mainnet": "btc",
|
|
4
9
|
"notFoundIndex": -1,
|
|
5
10
|
"monthNumbers": [
|
|
6
11
|
"01",
|
|
@@ -25,5 +30,13 @@
|
|
|
25
30
|
"11",
|
|
26
31
|
"12"
|
|
27
32
|
],
|
|
28
|
-
"monthOffset": 1
|
|
33
|
+
"monthOffset": 1,
|
|
34
|
+
"pricesFileErrors": [
|
|
35
|
+
"ExpectedPricesObjectInPricesFile",
|
|
36
|
+
"FailedToParsePricesFile",
|
|
37
|
+
"FailedToReadPricesFile",
|
|
38
|
+
"FailedToReplacePricesFile",
|
|
39
|
+
"FailedToWritePricesFile",
|
|
40
|
+
"UnsupportedPricesFileVersion"
|
|
41
|
+
]
|
|
29
42
|
}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
+
const {chainRates} = require('@alexbosworth/fiat');
|
|
2
3
|
const {getAccountingReport} = require('ln-accounting');
|
|
3
4
|
const {getNetwork} = require('ln-sync');
|
|
4
5
|
const {returnResult} = require('asyncjs-util');
|
|
5
6
|
|
|
6
7
|
const categories = require('./accounting_categories');
|
|
8
|
+
const {chainPricesFile} = require('./constants');
|
|
9
|
+
const {coinbaseProvider} = require('./constants');
|
|
7
10
|
const {defaultCurrency} = require('./constants');
|
|
8
11
|
const {defaultFiat} = require('./constants');
|
|
12
|
+
const {fsMethods} = require('./constants');
|
|
13
|
+
const getRecordRate = require('./get_record_rate');
|
|
14
|
+
const guardChainRate = require('./guard_chain_rate');
|
|
15
|
+
const {homePath} = require('./../storage');
|
|
16
|
+
const {mainnet} = require('./constants');
|
|
9
17
|
const {monthNumbers} = require('./constants');
|
|
10
18
|
const {monthOffset} = require('./constants');
|
|
11
19
|
const {notFoundIndex} = require('./constants');
|
|
@@ -26,9 +34,16 @@ const summaryHeadings = ['Total', 'Asset', 'Report Date', 'Total Fiat'];
|
|
|
26
34
|
[currency]: <Currency Label String>
|
|
27
35
|
[date]: <Day of Month String>
|
|
28
36
|
[fiat]: <Fiat Type String>
|
|
37
|
+
[fs]: {
|
|
38
|
+
getFile: <Read File Contents Function> (path, cbk) => {}
|
|
39
|
+
makeDirectory: <Make Directory Function> (path, cbk) => {}
|
|
40
|
+
renameFile: <Rename File Function> (from, to, cbk) => {}
|
|
41
|
+
writeFile: <Write File Contents Function> (path, contents, cbk) => {}
|
|
42
|
+
}
|
|
29
43
|
[is_csv]: <Return CSV Output Bool>
|
|
30
44
|
[is_fiat_disabled]: <Omit Fiat Conversion Bool>
|
|
31
45
|
lnd: <Authenticated LND API Object>
|
|
46
|
+
[logger]: <Winston Logger Object>
|
|
32
47
|
[month]: <Month for Report String>
|
|
33
48
|
[node]: <Node Name String>
|
|
34
49
|
[rate_provider]: <Rate Provider String>
|
|
@@ -47,6 +62,8 @@ module.exports = (args, cbk) => {
|
|
|
47
62
|
return asyncAuto({
|
|
48
63
|
// Validate
|
|
49
64
|
validate: cbk => {
|
|
65
|
+
const {fs} = args;
|
|
66
|
+
|
|
50
67
|
if (!args.category || !categories[args.category]) {
|
|
51
68
|
return cbk([400, 'ExpectedKnownAccountingRecordsCategory']);
|
|
52
69
|
}
|
|
@@ -55,6 +72,10 @@ module.exports = (args, cbk) => {
|
|
|
55
72
|
return cbk([400, 'ExpectedMonthForDateToGetAccountingReport']);
|
|
56
73
|
}
|
|
57
74
|
|
|
75
|
+
if (!!fs && !fsMethods.every(n => typeof fs[n] === 'function')) {
|
|
76
|
+
return cbk([400, 'ExpectedFileSystemMethodsToGetAccountingReport']);
|
|
77
|
+
}
|
|
78
|
+
|
|
58
79
|
if (!args.lnd) {
|
|
59
80
|
return cbk([400, 'ExpectedAuthenticatedLndToGetAccountingReport']);
|
|
60
81
|
}
|
|
@@ -68,25 +89,88 @@ module.exports = (args, cbk) => {
|
|
|
68
89
|
|
|
69
90
|
// Get date range
|
|
70
91
|
dateRange: ['validate', ({}, cbk) => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
92
|
+
const range = (() => {
|
|
93
|
+
try {
|
|
94
|
+
return {
|
|
95
|
+
value: rangeForDate({
|
|
96
|
+
date: args.date,
|
|
97
|
+
month: args.month,
|
|
98
|
+
year: args.year,
|
|
99
|
+
}),
|
|
100
|
+
};
|
|
101
|
+
} catch (err) {
|
|
102
|
+
return {err};
|
|
103
|
+
}
|
|
104
|
+
})();
|
|
105
|
+
|
|
106
|
+
// Exit early when the date range cannot be determined
|
|
107
|
+
if (!!range.err) {
|
|
108
|
+
return cbk([400, range.err.message]);
|
|
79
109
|
}
|
|
110
|
+
|
|
111
|
+
return cbk(null, range.value);
|
|
80
112
|
}],
|
|
81
113
|
|
|
82
114
|
// Get the network name
|
|
83
115
|
getNetwork: ['validate', ({}, cbk) => getNetwork({lnd: args.lnd}, cbk)],
|
|
84
116
|
|
|
117
|
+
// Make the method that gets chain prices, used without a rate provider
|
|
118
|
+
chain: ['getNetwork', ({getNetwork}, cbk) => {
|
|
119
|
+
// Exit early when fiat is disabled or a rate provider is specified
|
|
120
|
+
if (!!args.is_fiat_disabled || !!args.rate_provider) {
|
|
121
|
+
return cbk();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Exit early when there are no chain prices for the network or no
|
|
125
|
+
// file system to keep them in
|
|
126
|
+
if (!args.fs || getNetwork.network !== mainnet) {
|
|
127
|
+
return cbk();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Progress is only logged when it cannot end up in CSV output
|
|
131
|
+
const logger = !args.is_csv ? args.logger : undefined;
|
|
132
|
+
|
|
133
|
+
const {getChainRate} = chainRates({
|
|
134
|
+
logger,
|
|
135
|
+
fs: args.fs,
|
|
136
|
+
lnd: args.lnd,
|
|
137
|
+
path: homePath({file: chainPricesFile}).path,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return cbk(null, guardChainRate({getChainRate}));
|
|
141
|
+
}],
|
|
142
|
+
|
|
143
|
+
// Without a rate provider, records are valued with chain prices, and
|
|
144
|
+
// at their hour on Coinbase Exchange when chain prices cannot value them
|
|
145
|
+
rate: ['chain', ({chain}, cbk) => {
|
|
146
|
+
// Exit early when fiat is disabled
|
|
147
|
+
if (!!args.is_fiat_disabled) {
|
|
148
|
+
return cbk();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Exit early when a rate provider other than Coinbase is specified
|
|
152
|
+
if (!!args.rate_provider && args.rate_provider !== coinbaseProvider) {
|
|
153
|
+
return cbk();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return cbk(null, {
|
|
157
|
+
rate: (record, cbk) => getRecordRate({
|
|
158
|
+
currency: record.currency,
|
|
159
|
+
date: record.date,
|
|
160
|
+
fiat: record.fiat,
|
|
161
|
+
get_chain_rate: !!chain ? chain.getChainRate : undefined,
|
|
162
|
+
request: args.request,
|
|
163
|
+
},
|
|
164
|
+
cbk),
|
|
165
|
+
});
|
|
166
|
+
}],
|
|
167
|
+
|
|
85
168
|
// Get accounting info
|
|
86
169
|
getAccounting: [
|
|
87
170
|
'dateRange',
|
|
88
171
|
'getNetwork',
|
|
89
|
-
|
|
172
|
+
'rate',
|
|
173
|
+
({dateRange, getNetwork, rate}, cbk) =>
|
|
90
174
|
{
|
|
91
175
|
return getAccountingReport({
|
|
92
176
|
after: dateRange.after,
|
|
@@ -96,7 +180,8 @@ module.exports = (args, cbk) => {
|
|
|
96
180
|
fiat: !args.is_fiat_disabled ? (args.fiat || defaultFiat) : null,
|
|
97
181
|
lnd: args.lnd,
|
|
98
182
|
network: getNetwork.network,
|
|
99
|
-
|
|
183
|
+
rate: !!rate ? rate.rate : undefined,
|
|
184
|
+
rate_provider: !rate ? args.rate_provider : undefined,
|
|
100
185
|
request: args.request,
|
|
101
186
|
},
|
|
102
187
|
cbk);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncRetry = require('async/retry');
|
|
3
|
+
const {getPrices} = require('@alexbosworth/fiat');
|
|
4
|
+
const {returnResult} = require('asyncjs-util');
|
|
5
|
+
|
|
6
|
+
const {failedRequestErrors} = require('./constants');
|
|
7
|
+
|
|
8
|
+
const isFailedRequest = err => !!err && failedRequestErrors.includes(err[1]);
|
|
9
|
+
const retryDelaysMs = [1000, 2000];
|
|
10
|
+
const supportedCurrency = 'BTC';
|
|
11
|
+
|
|
12
|
+
/** Get the fiat rate of BTC at the hour of a date from a rate provider
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
currency: <Currency Type String>
|
|
16
|
+
date: <ISO 8601 Date String>
|
|
17
|
+
fiat: <Fiat Type String>
|
|
18
|
+
from: <Rate Provider String>
|
|
19
|
+
request: <Request Function>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@returns via cbk or Promise
|
|
23
|
+
{
|
|
24
|
+
cents: <Cents Per BTC Number>
|
|
25
|
+
}
|
|
26
|
+
*/
|
|
27
|
+
module.exports = ({currency, date, fiat, from, request}, cbk) => {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
return asyncAuto({
|
|
30
|
+
// Check arguments
|
|
31
|
+
validate: cbk => {
|
|
32
|
+
if (currency !== supportedCurrency) {
|
|
33
|
+
return cbk([400, 'UnsupportedCurrencyToGetHistoricRate']);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!date) {
|
|
37
|
+
return cbk([400, 'ExpectedDateToGetHistoricRate']);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!fiat) {
|
|
41
|
+
return cbk([400, 'ExpectedFiatToGetHistoricRate']);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!from) {
|
|
45
|
+
return cbk([400, 'ExpectedRateProviderToGetHistoricRate']);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!request) {
|
|
49
|
+
return cbk([400, 'ExpectedRequestFunctionToGetHistoricRate']);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return cbk();
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// Get the price at the hour of the date, trying a failed request again
|
|
56
|
+
getPrice: ['validate', ({}, cbk) => {
|
|
57
|
+
return asyncRetry({
|
|
58
|
+
errorFilter: isFailedRequest,
|
|
59
|
+
interval: count => retryDelaysMs[count - 1],
|
|
60
|
+
times: retryDelaysMs.length + 1,
|
|
61
|
+
},
|
|
62
|
+
cbk => getPrices({date, from, request, symbols: [fiat]}, cbk),
|
|
63
|
+
cbk);
|
|
64
|
+
}],
|
|
65
|
+
|
|
66
|
+
// The rate of the fiat
|
|
67
|
+
rate: ['getPrice', ({getPrice}, cbk) => {
|
|
68
|
+
const [ticker] = getPrice.tickers;
|
|
69
|
+
|
|
70
|
+
return cbk(null, {cents: ticker.rate});
|
|
71
|
+
}],
|
|
72
|
+
},
|
|
73
|
+
returnResult({reject, resolve, of: 'rate'}, cbk));
|
|
74
|
+
});
|
|
75
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {returnResult} = require('asyncjs-util');
|
|
3
|
+
|
|
4
|
+
const {coinbaseProvider} = require('./constants');
|
|
5
|
+
const getHistoricRate = require('./get_historic_rate');
|
|
6
|
+
|
|
7
|
+
/** Get the fiat rate of BTC for an accounting record
|
|
8
|
+
|
|
9
|
+
{
|
|
10
|
+
currency: <Currency Type String>
|
|
11
|
+
date: <Record ISO 8601 Date String>
|
|
12
|
+
fiat: <Fiat Type String>
|
|
13
|
+
[get_chain_rate]: <Get Chain Rate Function>
|
|
14
|
+
request: <Request Function>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@returns via cbk or Promise
|
|
18
|
+
{
|
|
19
|
+
cents: <Cents Per BTC Number>
|
|
20
|
+
}
|
|
21
|
+
*/
|
|
22
|
+
module.exports = (args, cbk) => {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
return asyncAuto({
|
|
25
|
+
// Check arguments
|
|
26
|
+
validate: cbk => {
|
|
27
|
+
if (!args.request) {
|
|
28
|
+
return cbk([400, 'ExpectedRequestFunctionToGetRecordRate']);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return cbk();
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// Get the price derived from chain data
|
|
35
|
+
getChainRate: ['validate', ({}, cbk) => {
|
|
36
|
+
// Exit early when there are no chain prices
|
|
37
|
+
if (!args.get_chain_rate) {
|
|
38
|
+
return cbk();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return args.get_chain_rate({
|
|
42
|
+
currency: args.currency,
|
|
43
|
+
date: args.date,
|
|
44
|
+
fiat: args.fiat,
|
|
45
|
+
},
|
|
46
|
+
cbk);
|
|
47
|
+
}],
|
|
48
|
+
|
|
49
|
+
// Get the price at the hour of the record on Coinbase Exchange
|
|
50
|
+
getCoinbaseRate: ['getChainRate', ({getChainRate}, cbk) => {
|
|
51
|
+
// Exit early when the record has a chain price
|
|
52
|
+
if (!!getChainRate) {
|
|
53
|
+
return cbk();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return getHistoricRate({
|
|
57
|
+
currency: args.currency,
|
|
58
|
+
date: args.date,
|
|
59
|
+
fiat: args.fiat,
|
|
60
|
+
from: coinbaseProvider,
|
|
61
|
+
request: args.request,
|
|
62
|
+
},
|
|
63
|
+
cbk);
|
|
64
|
+
}],
|
|
65
|
+
|
|
66
|
+
// The rate of the record
|
|
67
|
+
rate: [
|
|
68
|
+
'getChainRate',
|
|
69
|
+
'getCoinbaseRate',
|
|
70
|
+
({getChainRate, getCoinbaseRate}, cbk) =>
|
|
71
|
+
{
|
|
72
|
+
return cbk(null, {cents: (getChainRate || getCoinbaseRate).cents});
|
|
73
|
+
}],
|
|
74
|
+
},
|
|
75
|
+
returnResult({reject, resolve, of: 'rate'}, cbk));
|
|
76
|
+
});
|
|
77
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const {pricesFileErrors} = require('./constants');
|
|
2
|
+
|
|
3
|
+
const isFileError = err => !!err && pricesFileErrors.includes(err[1]);
|
|
4
|
+
|
|
5
|
+
/** Make a chain rate lookup that only fails on prices file errors
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
getChainRate: <Get Chain Rate Function>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@returns
|
|
12
|
+
{
|
|
13
|
+
getChainRate: <Get Chain Rate Method> ({
|
|
14
|
+
currency: <Currency To Price String>
|
|
15
|
+
date: <Date To Price ISO 8601 Date String>
|
|
16
|
+
fiat: <Fiat Currency To Price In String>
|
|
17
|
+
}, cbk) => (err, [{
|
|
18
|
+
cents: <US Cents Per BTC Number>
|
|
19
|
+
}])
|
|
20
|
+
}
|
|
21
|
+
*/
|
|
22
|
+
module.exports = ({getChainRate}) => {
|
|
23
|
+
const failure = {};
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
getChainRate: (args, cbk) => {
|
|
27
|
+
// Exit early when the prices file failed, since it would fail again
|
|
28
|
+
if (!!failure.err) {
|
|
29
|
+
return cbk(failure.err);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return getChainRate(args, (err, res) => {
|
|
33
|
+
// Exit early when there is a chain price
|
|
34
|
+
if (!err) {
|
|
35
|
+
return cbk(null, res);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Exit early without a price when chain prices cannot value the date
|
|
39
|
+
if (!isFileError(err)) {
|
|
40
|
+
return cbk();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// A prices file error is kept, as trying again cannot fix the file
|
|
44
|
+
failure.err = err;
|
|
45
|
+
|
|
46
|
+
return cbk(err);
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
};
|
package/bos
CHANGED
|
@@ -70,6 +70,7 @@ prog
|
|
|
70
70
|
.argument('<category>', 'Report category', keys(accountingCategories))
|
|
71
71
|
.help(`Categories: ${keys(accountingCategories).join(', ')}`)
|
|
72
72
|
.help(`Rate providers: ${rateProviders.join(', ')}`)
|
|
73
|
+
.help('By default fiat rates after 2021-10-19 use chain data derived rates')
|
|
73
74
|
.help('Privacy note: this requests tx related data from third parties')
|
|
74
75
|
.option('--csv', 'Output a CSV')
|
|
75
76
|
.option('--date <day>', 'Show only records for specific date')
|
|
@@ -84,8 +85,15 @@ prog
|
|
|
84
85
|
return new Promise(async (resolve, reject) => {
|
|
85
86
|
try {
|
|
86
87
|
return balances.getAccountingReport({
|
|
88
|
+
logger,
|
|
87
89
|
category: args.category,
|
|
88
90
|
date: options.date,
|
|
91
|
+
fs: {
|
|
92
|
+
writeFile,
|
|
93
|
+
getFile: readFile,
|
|
94
|
+
makeDirectory: mkdir,
|
|
95
|
+
renameFile: rename,
|
|
96
|
+
},
|
|
89
97
|
is_csv: !!options.csv,
|
|
90
98
|
is_fiat_disabled: options.disableFiat,
|
|
91
99
|
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
|
package/commands/constants.json
CHANGED
|
@@ -8,8 +8,10 @@ const {parse} = JSON;
|
|
|
8
8
|
/** Simplified version of request method
|
|
9
9
|
|
|
10
10
|
{
|
|
11
|
+
[headers]: <Request Headers Object>
|
|
11
12
|
[json]: <Interpret Result as JSON Bool>
|
|
12
13
|
[method]: <HTTPS Method String>
|
|
14
|
+
[qs]: <Query String Arguments Object>
|
|
13
15
|
[timeout]: <Timeout MS Number>
|
|
14
16
|
url: <HTTPS URL String>
|
|
15
17
|
}
|
|
@@ -29,15 +31,28 @@ module.exports = (args, cbk) => {
|
|
|
29
31
|
return cbk([400, 'ExpectedHttpsProtocolToExecuteSimpleRequest']);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
let
|
|
34
|
+
let isFinished = false;
|
|
33
35
|
const qs = url.searchParams;
|
|
34
36
|
|
|
37
|
+
// Call back once, since a request that times out also errors
|
|
38
|
+
const finish = (err, res, body) => {
|
|
39
|
+
// Exit early when the request has already called back
|
|
40
|
+
if (isFinished) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
isFinished = true;
|
|
45
|
+
|
|
46
|
+
return cbk(err, res, body);
|
|
47
|
+
};
|
|
48
|
+
|
|
35
49
|
// Include passed query string arguments
|
|
36
50
|
if (!!args.qs) {
|
|
37
51
|
keys(args.qs).forEach(key => qs.set(key, args.qs[key]));
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
const req = https.request({
|
|
55
|
+
headers: args.headers,
|
|
41
56
|
hostname: url.hostname,
|
|
42
57
|
method: args.method,
|
|
43
58
|
path: url.pathname + asQueryString(qs.toString()),
|
|
@@ -50,31 +65,51 @@ module.exports = (args, cbk) => {
|
|
|
50
65
|
// Collect response chunks
|
|
51
66
|
res.on('data', data => body.push(data));
|
|
52
67
|
|
|
68
|
+
// A response that is cut off is an error
|
|
69
|
+
res.on('error', err => {
|
|
70
|
+
return finish([503, 'UnexpectedErrorReceivingSimpleRequest', {err}]);
|
|
71
|
+
});
|
|
72
|
+
|
|
53
73
|
// Response is finished
|
|
54
74
|
res.on('end', () => {
|
|
55
|
-
if (!!error) {
|
|
56
|
-
return cbk(err);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
75
|
const combined = Buffer.concat(body).toString();
|
|
60
76
|
|
|
77
|
+
// Exit early when the response is not meant to be JSON
|
|
61
78
|
if (!args.json) {
|
|
62
|
-
return
|
|
79
|
+
return finish(null, res, combined);
|
|
63
80
|
}
|
|
64
81
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
// Only the parsing is guarded, so the callback is called once
|
|
83
|
+
const parsed = (() => {
|
|
84
|
+
try {
|
|
85
|
+
return {json: parse(combined)};
|
|
86
|
+
} catch (err) {
|
|
87
|
+
return {err};
|
|
88
|
+
}
|
|
89
|
+
})();
|
|
90
|
+
|
|
91
|
+
// Exit early when the response is not JSON
|
|
92
|
+
if (!!parsed.err) {
|
|
93
|
+
return finish([503, 'ExpectedJsonResponseToSimpleRequest', {
|
|
94
|
+
err: parsed.err,
|
|
95
|
+
}]);
|
|
69
96
|
}
|
|
97
|
+
|
|
98
|
+
return finish(null, res, parsed.json);
|
|
70
99
|
});
|
|
71
100
|
});
|
|
72
101
|
|
|
73
|
-
|
|
102
|
+
// A request that fails before it is answered is an error
|
|
103
|
+
req.on('error', err => {
|
|
104
|
+
return finish([503, 'UnexpectedErrorExecutingSimpleRequest', {err}]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// A request that is not answered in time is an error
|
|
74
108
|
req.on('timeout', () => {
|
|
75
|
-
|
|
109
|
+
finish([503, 'RequestTimedOut']);
|
|
76
110
|
|
|
77
|
-
|
|
111
|
+
// The request is stopped, and the error that comes of it is not passed on
|
|
112
|
+
return req.destroy();
|
|
78
113
|
});
|
|
79
114
|
|
|
80
115
|
req.end();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const {Encoder} = require('cbor');
|
|
2
|
+
|
|
3
|
+
const bufferAsHex = buffer => buffer.toString('hex');
|
|
4
|
+
|
|
5
|
+
/** Encode a value as CBOR
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
value: <Value to Encode Object>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@returns
|
|
12
|
+
{
|
|
13
|
+
cbor: <CBOR Encoded Hex String>
|
|
14
|
+
}
|
|
15
|
+
*/
|
|
16
|
+
module.exports = ({value}) => {
|
|
17
|
+
const chunks = [];
|
|
18
|
+
const encoder = new Encoder();
|
|
19
|
+
|
|
20
|
+
// Gather the encoding as it is emitted, reading it later can cut it short
|
|
21
|
+
encoder.on('data', chunk => chunks.push(chunk));
|
|
22
|
+
|
|
23
|
+
encoder.pushAny(value);
|
|
24
|
+
|
|
25
|
+
encoder.end();
|
|
26
|
+
|
|
27
|
+
return {cbor: bufferAsHex(Buffer.concat(chunks))};
|
|
28
|
+
};
|
|
@@ -3,11 +3,11 @@ const {randomBytes} = require('crypto');
|
|
|
3
3
|
const {scrypt} = require('crypto');
|
|
4
4
|
|
|
5
5
|
const asyncAuto = require('async/auto');
|
|
6
|
-
const {encode} = require('cbor');
|
|
7
6
|
const {returnResult} = require('asyncjs-util');
|
|
8
7
|
|
|
8
|
+
const encodeAsCbor = require('./encode_as_cbor');
|
|
9
|
+
|
|
9
10
|
const algorithm = 'aes-256-gcm';
|
|
10
|
-
const cbor = n => encode(n).toString('hex');
|
|
11
11
|
const derivation = 'scrypt';
|
|
12
12
|
const digest = 'sha512';
|
|
13
13
|
const fromHex = n => Buffer.from(n, 'hex');
|
|
@@ -37,7 +37,7 @@ module.exports = ({encoding, from, plain, secret, to}, cbk) => {
|
|
|
37
37
|
// Check arguments
|
|
38
38
|
validate: cbk => {
|
|
39
39
|
if (!plain) {
|
|
40
|
-
return cbk([400, '
|
|
40
|
+
return cbk([400, 'ExpectedDataToEncryptToSecret']);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
if (!secret) {
|
|
@@ -90,7 +90,9 @@ module.exports = ({encoding, from, plain, secret, to}, cbk) => {
|
|
|
90
90
|
const encrypted = Buffer.concat(updated);
|
|
91
91
|
const tag = cipher.getAuthTag();
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
const value = {encrypted, iv, salt, settings, tag};
|
|
94
|
+
|
|
95
|
+
return cbk(null, {cipher: encodeAsCbor({value}).cbor});
|
|
94
96
|
}],
|
|
95
97
|
},
|
|
96
98
|
returnResult({reject, resolve, of: 'encrypt'}, cbk));
|
package/encryption/index.js
CHANGED
|
@@ -2,6 +2,7 @@ const certExpiration = require('./cert_expiration');
|
|
|
2
2
|
const decryptCiphertext = require('./decrypt_ciphertext');
|
|
3
3
|
const decryptWithNode = require('./decrypt_with_node');
|
|
4
4
|
const derAsPem = require('./der_as_pem');
|
|
5
|
+
const encodeAsCbor = require('./encode_as_cbor');
|
|
5
6
|
const encryptToNode = require('./encrypt_to_node');
|
|
6
7
|
const encryptToPublicKeys = require('./encrypt_to_public_keys');
|
|
7
8
|
const pemAsDer = require('./pem_as_der');
|
|
@@ -11,6 +12,7 @@ module.exports = {
|
|
|
11
12
|
decryptCiphertext,
|
|
12
13
|
decryptWithNode,
|
|
13
14
|
derAsPem,
|
|
15
|
+
encodeAsCbor,
|
|
14
16
|
encryptToNode,
|
|
15
17
|
encryptToPublicKeys,
|
|
16
18
|
pemAsDer,
|
package/lnd/get_credentials.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
-
const {encode} = require('cbor');
|
|
3
2
|
const {grantAccess} = require('ln-service');
|
|
4
3
|
const moment = require('moment');
|
|
5
4
|
const {returnResult} = require('asyncjs-util');
|
|
6
5
|
|
|
6
|
+
const {encodeAsCbor} = require('./../encryption');
|
|
7
7
|
const lndCredentials = require('./lnd_credentials');
|
|
8
8
|
const {pemAsDer} = require('./../encryption');
|
|
9
9
|
|
|
@@ -112,7 +112,7 @@ module.exports = (args, cbk) => {
|
|
|
112
112
|
};
|
|
113
113
|
|
|
114
114
|
return cbk(null, {
|
|
115
|
-
exported_credentials:
|
|
115
|
+
exported_credentials: encodeAsCbor({value: credentials}).cbor,
|
|
116
116
|
});
|
|
117
117
|
}],
|
|
118
118
|
},
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@alexbosworth/blockchain": "4.12.0",
|
|
19
19
|
"@alexbosworth/caporal": "2.0.0",
|
|
20
|
-
"@alexbosworth/fiat": "
|
|
20
|
+
"@alexbosworth/fiat": "3.0.0",
|
|
21
21
|
"@alexbosworth/formulas": "1.5.0",
|
|
22
22
|
"@alexbosworth/html2unicode": "1.1.5",
|
|
23
23
|
"asciichart": "1.5.25",
|
|
@@ -34,24 +34,24 @@
|
|
|
34
34
|
"grammy": "1.46.0",
|
|
35
35
|
"import-lazy": "4.0.0",
|
|
36
36
|
"ini": "7.0.0",
|
|
37
|
-
"inquirer": "14.2.
|
|
37
|
+
"inquirer": "14.2.5",
|
|
38
38
|
"ln-accounting": "10.0.9",
|
|
39
|
-
"ln-service": "60.1.
|
|
39
|
+
"ln-service": "60.1.8",
|
|
40
40
|
"ln-sync": "8.0.9",
|
|
41
|
-
"ln-telegram": "8.
|
|
41
|
+
"ln-telegram": "8.2.0",
|
|
42
42
|
"moment": "2.31.0",
|
|
43
43
|
"paid-services": "10.3.3",
|
|
44
|
-
"probing": "7.0.
|
|
44
|
+
"probing": "7.0.2",
|
|
45
45
|
"qrcode-terminal": "0.12.0",
|
|
46
46
|
"sanitize-filename": "1.6.4",
|
|
47
47
|
"socks-proxy-agent": "10.1.0",
|
|
48
48
|
"table": "6.9.0",
|
|
49
|
-
"tiny-secp256k1": "2.2.
|
|
49
|
+
"tiny-secp256k1": "2.2.5",
|
|
50
50
|
"window-size": "1.1.1"
|
|
51
51
|
},
|
|
52
52
|
"description": "Lightning balance CLI",
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"invoices": "6.3.
|
|
54
|
+
"invoices": "6.3.3",
|
|
55
55
|
"ln-docker-daemons": "8.1.3"
|
|
56
56
|
},
|
|
57
57
|
"engines": {
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"integration-tests": "node test/integration",
|
|
102
102
|
"postpack": "PACKAGE_VERSION=$(cat package.json | grep \\\"version\\\" | head -1 | awk -F: '{ print $2 }' | sed 's/[\",]//g' | tr -d '[[:space:]]') && git tag -s v$PACKAGE_VERSION -m v$PACKAGE_VERSION && git push github --tags",
|
|
103
103
|
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64 -t alexbosworth/balanceofsatoshis -t alexbosworth/balanceofsatoshis:$npm_package_version --push .",
|
|
104
|
-
"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"
|
|
104
|
+
"test": "node --experimental-test-coverage --test test/arrays/*.js test/balances/*.js test/chain/*.js test/commands/*.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"
|
|
105
105
|
},
|
|
106
|
-
"version": "25.
|
|
106
|
+
"version": "25.4.0"
|
|
107
107
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
-
const {encode} = require('cbor');
|
|
3
2
|
const {genericSwapAuth} = require('goldengate');
|
|
4
3
|
const {genericSwapService} = require('goldengate');
|
|
5
4
|
const {getNetwork} = require('ln-sync');
|
|
@@ -14,13 +13,13 @@ const {returnResult} = require('asyncjs-util');
|
|
|
14
13
|
const {swapUserId} = require('goldengate');
|
|
15
14
|
|
|
16
15
|
const decodeSwapApiKey = require('./decode_swap_api_key');
|
|
16
|
+
const {encodeAsCbor} = require('./../encryption');
|
|
17
17
|
|
|
18
18
|
const bitcoinNetwork = 'btc';
|
|
19
19
|
const bitcoinTestnetNetwork = 'btctestnet';
|
|
20
20
|
const bufferFromBase64 = base64 => Buffer.from(base64, 'base64');
|
|
21
21
|
const bufferFromHex = hex => Buffer.from(hex, 'hex');
|
|
22
22
|
const connectError = 'FailedToConnectToService';
|
|
23
|
-
const encodeCbor = json => encode(json).toString('hex');
|
|
24
23
|
const httpMatch = /^https?:\/\//;
|
|
25
24
|
const {isArray} = Array;
|
|
26
25
|
const mainnetSocket = 'https://balanceofsatoshis.com:11011';
|
|
@@ -222,10 +221,13 @@ module.exports = ({fetch, lnd, socket, token}, cbk) => {
|
|
|
222
221
|
const paid = payForMacaroon.tokens;
|
|
223
222
|
const {service} = remote;
|
|
224
223
|
|
|
225
|
-
const token =
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
224
|
+
const token = encodeAsCbor({
|
|
225
|
+
value: {
|
|
226
|
+
macaroon: bufferFromBase64(getUnpaidMacaroon.macaroon),
|
|
227
|
+
preimage: bufferFromHex(payForMacaroon.secret),
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
.cbor;
|
|
229
231
|
|
|
230
232
|
return cbk(null, {id, metadata, paid, service, token});
|
|
231
233
|
}],
|
package/swaps/swap_api_key.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
|
-
const {encode} = require('cbor');
|
|
3
2
|
const {returnResult} = require('asyncjs-util');
|
|
4
3
|
const {swapUserId} = require('goldengate');
|
|
5
4
|
|
|
6
5
|
const decodeSwapApiKey = require('./decode_swap_api_key');
|
|
6
|
+
const {encodeAsCbor} = require('./../encryption');
|
|
7
7
|
const getPaidService = require('./get_paid_service');
|
|
8
8
|
|
|
9
9
|
/** Purchase or inspect a swap API key
|
|
@@ -75,7 +75,7 @@ module.exports = (args, cbk) => {
|
|
|
75
75
|
const preimage = Buffer.from(args.preimage, 'hex');
|
|
76
76
|
|
|
77
77
|
return cbk(null, {
|
|
78
|
-
api_key:
|
|
78
|
+
api_key: encodeAsCbor({value: {macaroon, preimage}}).cbor,
|
|
79
79
|
id: swapUserId({macaroon: macaroon.toString('base64')}),
|
|
80
80
|
});
|
|
81
81
|
}],
|