ln-accounting 10.0.6 → 10.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
},
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Alex Bosworth",
|
|
7
|
-
"url": "https://
|
|
7
|
+
"url": "https://x.com/alexbosworth"
|
|
8
8
|
},
|
|
9
9
|
"bugs": {
|
|
10
10
|
"url": "https://github.com/alexbosworth/ln-accounting/issues"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
+
"@alexbosworth/blockchain": "4.10.0",
|
|
14
|
+
"@alexbosworth/formulas": "1.5.0",
|
|
13
15
|
"@json2csv/plainjs": "7.0.8",
|
|
14
16
|
"async": "3.2.6",
|
|
15
17
|
"asyncjs-util": "1.2.12",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"hot-formula-parser": "4.0.0",
|
|
19
|
-
"ln-service": "59.4.1"
|
|
18
|
+
"goldengate": "16.0.8",
|
|
19
|
+
"ln-service": "60.1.2"
|
|
20
20
|
},
|
|
21
21
|
"description": "lnd accounting reports",
|
|
22
22
|
"engines": {
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
"scripts": {
|
|
37
37
|
"test": "node --experimental-test-coverage --test"
|
|
38
38
|
},
|
|
39
|
-
"version": "10.0.
|
|
39
|
+
"version": "10.0.8"
|
|
40
40
|
}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
const asyncAuto = require('async/auto');
|
|
2
2
|
const asyncMapSeries = require('async/mapSeries');
|
|
3
3
|
const asyncRetry = require('async/retry');
|
|
4
|
+
const {componentsOfTransaction} = require('@alexbosworth/blockchain');
|
|
4
5
|
const {getChainTransactions} = require('ln-service');
|
|
5
6
|
const {getClosedChannels} = require('ln-service');
|
|
6
7
|
const {getHeight} = require('ln-service');
|
|
7
8
|
const {getSweepTransactions} = require('ln-service');
|
|
8
9
|
const {returnResult} = require('asyncjs-util');
|
|
9
|
-
const {Transaction} = require('bitcoinjs-lib');
|
|
10
10
|
|
|
11
11
|
const {getProxyTx} = require('./../esplora');
|
|
12
12
|
const {getProxyVout} = require('./../esplora');
|
|
13
13
|
|
|
14
14
|
const dateAsMs = date => new Date(date).getTime();
|
|
15
|
-
const {fromHex} = Transaction;
|
|
16
15
|
const interval = 200;
|
|
17
16
|
const {isArray} = Array;
|
|
18
17
|
const msAsBlocks = ms => Math.ceil(ms / 1000 / 60 / 2.5);
|
|
@@ -141,9 +140,11 @@ module.exports = ({after, before, lnd, network, request}, cbk) => {
|
|
|
141
140
|
return cbk(err);
|
|
142
141
|
}
|
|
143
142
|
|
|
144
|
-
const {
|
|
143
|
+
const {outputs} = componentsOfTransaction({
|
|
144
|
+
transaction: tx.transaction,
|
|
145
|
+
});
|
|
145
146
|
|
|
146
|
-
const totalOut =
|
|
147
|
+
const totalOut = sumOf(outputs.map(n => n.tokens));
|
|
147
148
|
|
|
148
149
|
return cbk(null, {
|
|
149
150
|
spends,
|
|
@@ -194,17 +195,17 @@ module.exports = ({after, before, lnd, network, request}, cbk) => {
|
|
|
194
195
|
return tx.id === channel.close_transaction_id;
|
|
195
196
|
});
|
|
196
197
|
|
|
198
|
+
const components = !tx ? null : componentsOfTransaction({
|
|
199
|
+
transaction: tx.transaction,
|
|
200
|
+
});
|
|
201
|
+
|
|
197
202
|
const hasMissingLocalData = (() => {
|
|
198
|
-
if (!
|
|
203
|
+
if (!components) {
|
|
199
204
|
return true;
|
|
200
205
|
}
|
|
201
206
|
|
|
202
|
-
const inputs = fromHex(tx.transaction).ins;
|
|
203
|
-
|
|
204
207
|
// Confirm that the inputs to the tx are local
|
|
205
|
-
return !!inputs.find(({
|
|
206
|
-
const id = hash.slice().reverse().toString('hex');
|
|
207
|
-
|
|
208
|
+
return !!components.inputs.find(({id}) => {
|
|
208
209
|
return !getTx.transactions.find(n => n.id === id);
|
|
209
210
|
});
|
|
210
211
|
})();
|
|
@@ -239,20 +240,22 @@ module.exports = ({after, before, lnd, network, request}, cbk) => {
|
|
|
239
240
|
cbk);
|
|
240
241
|
}
|
|
241
242
|
|
|
242
|
-
const inputs =
|
|
243
|
-
const id = hash.slice().reverse().toString('hex');
|
|
244
|
-
|
|
243
|
+
const inputs = components.inputs.map(({id, vout}) => {
|
|
245
244
|
const matching = getTx.transactions.find(n => n.id === id);
|
|
246
245
|
|
|
246
|
+
const {outputs} = componentsOfTransaction({
|
|
247
|
+
transaction: matching.transaction,
|
|
248
|
+
});
|
|
249
|
+
|
|
247
250
|
return {
|
|
248
|
-
tokens:
|
|
251
|
+
tokens: outputs[vout].tokens,
|
|
249
252
|
transaction_id: id,
|
|
250
|
-
transaction_vout:
|
|
253
|
+
transaction_vout: vout,
|
|
251
254
|
};
|
|
252
255
|
});
|
|
253
256
|
|
|
254
257
|
const inputsValue = sumOf(inputs.map(n => n.tokens));
|
|
255
|
-
const outputsValue =
|
|
258
|
+
const outputsValue = components.outputs.map(n => n.tokens);
|
|
256
259
|
|
|
257
260
|
return cbk(null, {
|
|
258
261
|
block_id: tx.block_id,
|
package/report/parse_amount.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {evaluateFormula} = require('@alexbosworth/formulas');
|
|
2
2
|
|
|
3
|
-
const {ceil} = Math;
|
|
4
3
|
const {isArray} = Array;
|
|
5
4
|
const {keys} = Object;
|
|
6
5
|
const {round} = Math;
|
|
@@ -27,50 +26,20 @@ module.exports = ({amount, variables}) => {
|
|
|
27
26
|
throw new Error('CannotParseMultipleAmounts');
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
const
|
|
29
|
+
const constants = {};
|
|
31
30
|
|
|
32
31
|
keys(variables || {}).forEach(key => {
|
|
33
|
-
|
|
34
|
-
parser.setVariable(key.toUpperCase(), variables[key]);
|
|
32
|
+
constants[key.toLowerCase()] = variables[key];
|
|
35
33
|
|
|
36
34
|
return;
|
|
37
35
|
});
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
parser.setVariable('mm', 1e6);
|
|
44
|
-
parser.setVariable('MM', 1e6);
|
|
45
|
-
parser.setVariable('k', 1e3);
|
|
46
|
-
parser.setVariable('K', 1e3);
|
|
37
|
+
constants.btc = 1e8;
|
|
38
|
+
constants.m = 1e6;
|
|
39
|
+
constants.mm = 1e6;
|
|
40
|
+
constants.k = 1e3;
|
|
47
41
|
|
|
48
|
-
const
|
|
42
|
+
const {result} = evaluateFormula({constants, formula: amount});
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
case '#DIV/0!':
|
|
52
|
-
throw new Error('CannotDivideByZeroInSpecifiedAmount');
|
|
53
|
-
|
|
54
|
-
case '#ERROR!':
|
|
55
|
-
throw new Error('FailedToParseSpecifiedAmount');
|
|
56
|
-
|
|
57
|
-
case '#N/A':
|
|
58
|
-
case '#NAME?':
|
|
59
|
-
throw new Error('UnrecognizedVariableOrFunctionInSpecifiedAmount');
|
|
60
|
-
|
|
61
|
-
case '#NUM!':
|
|
62
|
-
throw new Error('InvalidNumberFoundInSpecifiedAmount');
|
|
63
|
-
|
|
64
|
-
case '#VALUE!':
|
|
65
|
-
throw new Error('UnexpectedValueTypeInSpecifiedAmount');
|
|
66
|
-
|
|
67
|
-
default:
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (!!parsed.error) {
|
|
72
|
-
throw new Error(parsed.error);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return {tokens: round(parsed.result)};
|
|
44
|
+
return {tokens: round(result)};
|
|
76
45
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const {deepEqual} = require('node:assert').strict;
|
|
2
|
+
const {fail} = require('node:assert').strict;
|
|
2
3
|
const {rejects} = require('node:assert').strict;
|
|
3
4
|
const test = require('node:test');
|
|
4
5
|
|
|
@@ -28,6 +29,11 @@ const tests = [
|
|
|
28
29
|
description: 'A currency is required',
|
|
29
30
|
error: [400, 'UnsupportedCurrencyForCoingeckoFiatRateLookup'],
|
|
30
31
|
},
|
|
32
|
+
{
|
|
33
|
+
args: makeArgs({currency: 'ETH'}),
|
|
34
|
+
description: 'Only BTC is supported as a currency',
|
|
35
|
+
error: [400, 'UnsupportedCurrencyForCoingeckoFiatRateLookup'],
|
|
36
|
+
},
|
|
31
37
|
{
|
|
32
38
|
args: makeArgs({date: undefined}),
|
|
33
39
|
description: 'A date is required',
|
|
@@ -38,6 +44,11 @@ const tests = [
|
|
|
38
44
|
description: 'A fiat type is required',
|
|
39
45
|
error: [400, 'UnsupportedFiatTypeForCoingeckoFiatRateLookup'],
|
|
40
46
|
},
|
|
47
|
+
{
|
|
48
|
+
args: makeArgs({fiat: 'EUR'}),
|
|
49
|
+
description: 'Only USD is supported as a fiat type',
|
|
50
|
+
error: [400, 'UnsupportedFiatTypeForCoingeckoFiatRateLookup'],
|
|
51
|
+
},
|
|
41
52
|
{
|
|
42
53
|
args: makeArgs({rates: undefined}),
|
|
43
54
|
description: 'A rates object is required',
|
|
@@ -58,6 +69,16 @@ const tests = [
|
|
|
58
69
|
description: 'A body is expected in response',
|
|
59
70
|
error: [503, 'UnexpectedResponseInCoingeckoPastRateResponse'],
|
|
60
71
|
},
|
|
72
|
+
{
|
|
73
|
+
args: makeArgs({request: ({}, cbk) => cbk(null, null, {})}),
|
|
74
|
+
description: 'Market data is expected in response',
|
|
75
|
+
error: [503, 'UnexpectedResponseInCoingeckoPastRateResponse'],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
args: makeArgs({request: ({}, cbk) => cbk(null, null, {market_data: {}})}),
|
|
79
|
+
description: 'A current price is expected in market data',
|
|
80
|
+
error: [503, 'UnexpectedResponseInCoingeckoPastRateResponse'],
|
|
81
|
+
},
|
|
61
82
|
{
|
|
62
83
|
args: makeArgs({
|
|
63
84
|
request: ({}, cbk) => cbk(null, null, {
|
|
@@ -72,6 +93,48 @@ const tests = [
|
|
|
72
93
|
description: 'Get coingecko historic rate',
|
|
73
94
|
expected: {cents: 1234},
|
|
74
95
|
},
|
|
96
|
+
{
|
|
97
|
+
args: makeArgs({
|
|
98
|
+
date: '2020-01-02T03:04:05.000Z',
|
|
99
|
+
rates: {'2020-01-02T12:00:00.000Z': 5678},
|
|
100
|
+
request: ({}, cbk) => fail('Expected cached rate to be used'),
|
|
101
|
+
}),
|
|
102
|
+
description: 'A known rate for the same day is used instead of a request',
|
|
103
|
+
expected: {cents: 5678},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
args: makeArgs({
|
|
107
|
+
date: '2020-01-02T03:04:05.000Z',
|
|
108
|
+
rates: {'2020-01-01T03:04:05.000Z': 5678},
|
|
109
|
+
}),
|
|
110
|
+
description: 'A known rate for a different day is not used',
|
|
111
|
+
expected: {cents: 1234},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
args: makeArgs({
|
|
115
|
+
date: '2020-01-02T03:04:05.000Z',
|
|
116
|
+
rates: {'2020-01-02T03:04:05.000Z': 0},
|
|
117
|
+
}),
|
|
118
|
+
description: 'A falsy known rate falls back to a request',
|
|
119
|
+
expected: {cents: 1234},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
args: makeArgs({
|
|
123
|
+
date: '2020-01-02T03:04:05.000Z',
|
|
124
|
+
request: (args, cbk) => {
|
|
125
|
+
deepEqual(args, {
|
|
126
|
+
json: true,
|
|
127
|
+
qs: {date: '02-01-2020', localization: false},
|
|
128
|
+
timeout: 30000,
|
|
129
|
+
url: 'https://api.coingecko.com/api/v3/coins/bitcoin/history',
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return api(args, cbk);
|
|
133
|
+
},
|
|
134
|
+
}),
|
|
135
|
+
description: 'Request is made with the expected arguments',
|
|
136
|
+
expected: {cents: 1234},
|
|
137
|
+
},
|
|
75
138
|
];
|
|
76
139
|
|
|
77
140
|
tests.forEach(({args, description, error, expected}) => {
|
|
@@ -8,27 +8,27 @@ const tests = [
|
|
|
8
8
|
{
|
|
9
9
|
args: {amount: 'amount'},
|
|
10
10
|
description: 'A value is required',
|
|
11
|
-
error: '
|
|
11
|
+
error: 'ExpectedAllKnownConstantsInFormulaToEvaluate',
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
args: {amount: '1/0'},
|
|
15
15
|
description: 'Dividing by zero is not allowed',
|
|
16
|
-
error: '
|
|
16
|
+
error: 'ExpectedNonZeroDivisorForFormulaEvaluation',
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
args: {amount: '0.0.0'},
|
|
20
20
|
description: 'A generic invalid amount is rejected',
|
|
21
|
-
error: '
|
|
21
|
+
error: 'UnexpectedInvalidDecimalNumberValue',
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
args: {amount: 'OCT2DEC()'},
|
|
25
|
-
description: 'Invalid
|
|
26
|
-
error: '
|
|
25
|
+
description: 'Invalid functions are rejected',
|
|
26
|
+
error: 'UnexpectedFunctionForFormulaEvaluation',
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
args: {amount: '"string" + 1'},
|
|
30
30
|
description: 'Invalid formulas are rejected',
|
|
31
|
-
error: '
|
|
31
|
+
error: 'ExpectedBoolOrFiniteNumberForNumberConversion',
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
args: {amount: '1.20969468*btc', variables: {variable: 'value'}},
|