ln-accounting 4.3.1 → 5.0.3
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/README.md +2 -2
- package/esplora/get_esplora_tx.js +88 -0
- package/esplora/get_esplora_vout.js +74 -0
- package/esplora/get_proxy_tx.js +74 -0
- package/esplora/get_proxy_vout.js +62 -0
- package/esplora/index.js +4 -0
- package/fiat/constants.json +1 -1
- package/fiat/get_coingecko_historic_rate.js +80 -0
- package/fiat/get_historic_rate.js +3 -1
- package/package.json +7 -7
- package/records/get_chain_transactions.js +80 -36
- package/report/get_accounting_report.js +1 -1
- package/test/blockstream/test_get_blockstream_tx.js +109 -0
- package/test/fiat/test_get_coincap_historic_rate.js +1 -1
- package/test/fiat/test_get_coindesk_historic_rate.js +1 -1
- package/test/fiat/test_get_coingecko_historic_rate.js +86 -0
- package/test/fiat/test_get_fiat_values.js +1 -1
- package/test/fiat/test_get_historic_rate.js +4 -2
- package/test/harmony/test_categorize_records.js +1 -1
- package/test/harmony/test_chain_fees_as_records.js +1 -1
- package/test/harmony/test_chain_receives_as_records.js +1 -1
- package/test/harmony/test_chain_sends_as_records.js +1 -1
- package/test/harmony/test_formatted_notes.js +1 -1
- package/test/harmony/test_forwards_as_records.js +1 -1
- package/test/harmony/test_harmonize.js +1 -1
- package/test/harmony/test_invoices_as_records.js +1 -1
- package/test/harmony/test_notes_for_chain_transaction.js +1 -1
- package/test/harmony/test_payments_as_records.js +1 -1
- package/test/harmony/test_records_with_fiat.js +1 -1
- package/test/records/test_get_all_invoices.js +1 -1
- package/test/records/test_get_all_payments.js +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## Version 5.0.3
|
|
4
|
+
|
|
5
|
+
- `getChainTransactions`: Add mempool space tx data lookup method
|
|
6
|
+
|
|
7
|
+
## Version 5.0.2
|
|
8
|
+
|
|
9
|
+
- `getChainTransactions`: Optimize lookup speed when an after date is specified
|
|
10
|
+
|
|
11
|
+
## Version 5.0.1
|
|
12
|
+
|
|
13
|
+
- `getAccountingReport`: Adjust sweeps to be net zero for transacted amount, plus fees
|
|
14
|
+
|
|
15
|
+
## Version 5.0.0
|
|
16
|
+
|
|
17
|
+
- `getAccountingReport`: Add support for coingecko historic rate lookup, use as default
|
|
18
|
+
|
|
19
|
+
### Breaking Changes
|
|
20
|
+
|
|
21
|
+
- Node.js version 12 or higher is now required
|
|
22
|
+
|
|
3
23
|
## Version 4.3.1
|
|
4
24
|
|
|
5
25
|
- `getAccountingReport`: Fix reporting for AMP push invoices
|
package/README.md
CHANGED
|
@@ -18,11 +18,11 @@ Note: Chain fees does not include chain fees paid to close channels
|
|
|
18
18
|
[before]: <Records Created Before ISO 8601 Date>
|
|
19
19
|
[category]: <Category Filter String>
|
|
20
20
|
currency: <Base Currency Type String>
|
|
21
|
-
fiat: <Fiat Currency Type String>
|
|
21
|
+
[fiat]: <Fiat Currency Type String>
|
|
22
22
|
lnd: <Authenticated LND gRPC API Object>
|
|
23
23
|
[network]: <Network Name String>
|
|
24
24
|
[rate]: <Exchange Function> ({currency, date, fiat}, cbk) => (err, {cents})
|
|
25
|
-
rate_provider: <Fiat Rate Provider String>
|
|
25
|
+
[rate_provider]: <Fiat Rate Provider String> coindesk || coingecko
|
|
26
26
|
request: <Request Function>
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {returnResult} = require('asyncjs-util');
|
|
3
|
+
|
|
4
|
+
const dateFromEpoch = epoch => new Date(epoch * 1e3).toISOString();
|
|
5
|
+
const {isArray} = Array;
|
|
6
|
+
const url = (api, id) => `${api}tx/${id}`;
|
|
7
|
+
|
|
8
|
+
/** Get transaction details from Esplora-compatible API
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
api: <Esplora API Base String>
|
|
12
|
+
id: <Transaction Id Hex String>
|
|
13
|
+
request: <Request Function>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@returns via cbk or Promise
|
|
17
|
+
{
|
|
18
|
+
[confirmation_height]: <Confirmed In Block At Height Number>
|
|
19
|
+
[created_at]: <Transaction Confirmation Date ISO 8601 Date String>
|
|
20
|
+
[block_id]: <Confirmed In Block With Hash Hex String>
|
|
21
|
+
fee: <Transaction Fee Tokens Number>
|
|
22
|
+
output_addresses: [<Output Address String>]
|
|
23
|
+
}
|
|
24
|
+
*/
|
|
25
|
+
module.exports = ({api, id, request}, cbk) => {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
return asyncAuto({
|
|
28
|
+
// Check argument
|
|
29
|
+
validate: cbk => {
|
|
30
|
+
if (!api) {
|
|
31
|
+
return cbk([400, 'ExpectedBaseEsploraApiToGetEsploraTx']);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!id) {
|
|
35
|
+
return cbk([400, 'ExpectedTransactionIdToGetEsploraTx']);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!request) {
|
|
39
|
+
return cbk([400, 'ExpectedRequestFunctionToGetEsploraTx']);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return cbk();
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// Get tx details
|
|
46
|
+
getDetails: ['validate', ({}, cbk) => {
|
|
47
|
+
return request({json: true, url: url(api, id)}, (err, r, body) => {
|
|
48
|
+
if (!!err) {
|
|
49
|
+
return cbk([503, 'UnexpectedErrorGettingEsploraTx', {err}]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!body) {
|
|
53
|
+
return cbk([503, 'ExpectedTxLookupResultForEsploraTx']);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (body.fee === undefined) {
|
|
57
|
+
return cbk([503, 'ExpectedTransactionFeeInResultFromEsplora']);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!body.status) {
|
|
61
|
+
return cbk([503, 'ExpectedStatusOfEsploraTransaction']);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!isArray(body.vout)) {
|
|
65
|
+
return cbk([503, 'ExpectedOutputsInEsploraTransaction']);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Exit early when transaction is not confirmed
|
|
69
|
+
if (!body.status.confirmed) {
|
|
70
|
+
return cbk(null, {
|
|
71
|
+
fee: body.fee,
|
|
72
|
+
output_addresses: body.vout.map(n => n.scriptpubkey_address),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return cbk(null, {
|
|
77
|
+
block_id: body.status.block_hash,
|
|
78
|
+
confirmation_height: body.status.block_height,
|
|
79
|
+
created_at: dateFromEpoch(body.status.block_time),
|
|
80
|
+
fee: body.fee,
|
|
81
|
+
output_addresses: body.vout.map(n => n.scriptpubkey_address),
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}],
|
|
85
|
+
},
|
|
86
|
+
returnResult({reject, resolve, of: 'getDetails'}, cbk));
|
|
87
|
+
});
|
|
88
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {returnResult} = require('asyncjs-util');
|
|
3
|
+
|
|
4
|
+
const dateFromEpoch = epoch => new Date(epoch * 1e3).toISOString();
|
|
5
|
+
const {isArray} = Array;
|
|
6
|
+
const url = (api, id) => `${api}tx/${id}`;
|
|
7
|
+
|
|
8
|
+
/** Get transaction details from Esplora-compatible API
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
api: <Esplora API Base String>
|
|
12
|
+
id: <Transaction Id Hex String>
|
|
13
|
+
request: <Request Function>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@returns via cbk or Promise
|
|
17
|
+
{
|
|
18
|
+
tokens: <Transaction Output Tokens Number>
|
|
19
|
+
}
|
|
20
|
+
*/
|
|
21
|
+
module.exports = ({api, id, request, vout}, cbk) => {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
return asyncAuto({
|
|
24
|
+
// Check argument
|
|
25
|
+
validate: cbk => {
|
|
26
|
+
if (!api) {
|
|
27
|
+
return cbk([400, 'ExpectedApiPathToGetEsploraVout']);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!id) {
|
|
31
|
+
return cbk([400, 'ExpectedTransactionIdToGetEsploraVout']);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!request) {
|
|
35
|
+
return cbk([400, 'ExpectedRequestFunctionToGetEsploraVout']);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (vout === undefined) {
|
|
39
|
+
return cbk([400, 'ExpectedTransactionOutputIndexToGetEsploraVout']);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return cbk();
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// Get tx details
|
|
46
|
+
getDetails: ['validate', ({}, cbk) => {
|
|
47
|
+
return request({json: true, url: url(api, id)}, (err, r, body) => {
|
|
48
|
+
if (!!err) {
|
|
49
|
+
return cbk([503, 'UnexpectedErrorGettingExploraTx', {err}]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!body) {
|
|
53
|
+
return cbk([503, 'ExpectedTxLookupResultForEsploraTx']);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!isArray(body.vout)) {
|
|
57
|
+
return cbk([503, 'ExpectedOutputsArrayForExploraTx']);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!body.vout[vout]) {
|
|
61
|
+
return cbk([503, 'ExpectedOutputInEsploraTxDetails']);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!body.vout[vout].value) {
|
|
65
|
+
return cbk([503, 'ExpectedOutputValueInEsploraTxDetails']);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return cbk(null, {tokens: body.vout[vout].value});
|
|
69
|
+
});
|
|
70
|
+
}],
|
|
71
|
+
},
|
|
72
|
+
returnResult({reject, resolve, of: 'getDetails'}, cbk));
|
|
73
|
+
});
|
|
74
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {returnResult} = require('asyncjs-util');
|
|
3
|
+
|
|
4
|
+
const getEsploraTx = require('./get_esplora_tx');
|
|
5
|
+
|
|
6
|
+
const apiBlockstreamBtc = 'https://blockstream.info/api/';
|
|
7
|
+
const apiBlockstreamBtcTestnet = 'https://blockstream.info/testnet/api/';
|
|
8
|
+
const apiMempoolSpaceBtc = 'https://mempool.space/api/';
|
|
9
|
+
const btcTestnet = 'btctestnet';
|
|
10
|
+
const random = arr => arr[Math.floor(Math.random() * arr.length)];
|
|
11
|
+
|
|
12
|
+
/** Get a transaction as a proxy for a local transaction
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
id: <Transaction Id Hex String>
|
|
16
|
+
[network]: <Network Name String>
|
|
17
|
+
request: <Request Function>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@returns via cbk or Promise
|
|
21
|
+
{
|
|
22
|
+
[block_id]: <Block Hash Hex String>
|
|
23
|
+
[confirmation_height]: <Transaction Confirmed At Height Number>
|
|
24
|
+
created_at: <Transaction Created At ISO 8601 Date String>>
|
|
25
|
+
fee: <Transaction Fee Tokens Number>
|
|
26
|
+
id: <Transaction Id Hex String>
|
|
27
|
+
is_confirmed: <Transaction Confirmed Bool>
|
|
28
|
+
output_addresses: [<Transaction Output Address String>]
|
|
29
|
+
}
|
|
30
|
+
*/
|
|
31
|
+
module.exports = ({id, network, request}, cbk) => {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
return asyncAuto({
|
|
34
|
+
// Check arguments
|
|
35
|
+
validate: cbk => {
|
|
36
|
+
if (!id) {
|
|
37
|
+
return cbk([400, 'ExpectedPaymentIdToGetProxyTransaction']);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!request) {
|
|
41
|
+
return cbk([400, 'ExpectedRequestFunctionToGetProxyTransaction']);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return cbk();
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// Determine the API to use
|
|
48
|
+
api: ['validate', ({}, cbk) => {
|
|
49
|
+
if (network === btcTestnet) {
|
|
50
|
+
return cbk(null, apiBlockstreamBtcTestnet);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return cbk(null, random([apiBlockstreamBtc, apiMempoolSpaceBtc]));
|
|
54
|
+
}],
|
|
55
|
+
|
|
56
|
+
// Get transaction
|
|
57
|
+
getTx: ['api', ({api}, cbk) => getEsploraTx({api, id, request}, cbk)],
|
|
58
|
+
|
|
59
|
+
// Transaction details
|
|
60
|
+
tx: ['getTx', ({api, getTx}, cbk) => {
|
|
61
|
+
return cbk(null, {
|
|
62
|
+
id,
|
|
63
|
+
block_id: getTx.block_id,
|
|
64
|
+
confirmation_height: getTx.confirmation_height,
|
|
65
|
+
created_at: getTx.created_at || new Date().toISOString(),
|
|
66
|
+
fee: getTx.fee,
|
|
67
|
+
is_confirmed: !!getTx.block_id,
|
|
68
|
+
output_addresses: getTx.output_addresses,
|
|
69
|
+
});
|
|
70
|
+
}],
|
|
71
|
+
},
|
|
72
|
+
returnResult({reject, resolve, of: 'tx'}, cbk));
|
|
73
|
+
});
|
|
74
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {returnResult} = require('asyncjs-util');
|
|
3
|
+
|
|
4
|
+
const getEsploraVout = require('./get_esplora_vout');
|
|
5
|
+
|
|
6
|
+
const apiBlockstreamBtc = 'https://blockstream.info/api/';
|
|
7
|
+
const apiBlockstreamBtcTestnet = 'https://blockstream.info/testnet/api/';
|
|
8
|
+
const apiMempoolSpaceBtc = 'https://mempool.space/api/';
|
|
9
|
+
const btcTestnet = 'btctestnet';
|
|
10
|
+
const random = arr => arr[Math.floor(Math.random() * arr.length)];
|
|
11
|
+
|
|
12
|
+
/** Get a vout from an esplora endpoint
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
id: <Transaction Id Hex String>
|
|
16
|
+
[network]: <Network Name String>
|
|
17
|
+
request: <Request Function>
|
|
18
|
+
vout: <Transaction Output Index Number>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@returns via cbk or Promise
|
|
22
|
+
{
|
|
23
|
+
tokens: <Transaction Output Tokens Number>
|
|
24
|
+
}
|
|
25
|
+
*/
|
|
26
|
+
module.exports = ({id, network, request, vout}, cbk) => {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
return asyncAuto({
|
|
29
|
+
// Check arguments
|
|
30
|
+
validate: cbk => {
|
|
31
|
+
if (!id) {
|
|
32
|
+
return cbk([400, 'ExpectedPaymentIdToGetProxyVout']);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!request) {
|
|
36
|
+
return cbk([400, 'ExpectedRequestFunctionToGetProxyVout']);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (vout === undefined) {
|
|
40
|
+
return cbk([400, 'ExpectedTransactionOutputIndexToGetProxyVout']);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return cbk();
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Determine the API to use
|
|
47
|
+
api: ['validate', ({}, cbk) => {
|
|
48
|
+
if (network === btcTestnet) {
|
|
49
|
+
return cbk(null, apiBlockstreamBtcTestnet);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return cbk(null, random([apiBlockstreamBtc, apiMempoolSpaceBtc]));
|
|
53
|
+
}],
|
|
54
|
+
|
|
55
|
+
// Get transaction vout
|
|
56
|
+
getVout: ['api', ({api}, cbk) => {
|
|
57
|
+
return getEsploraVout({api, id, request, vout}, cbk);
|
|
58
|
+
}],
|
|
59
|
+
},
|
|
60
|
+
returnResult({reject, resolve, of: 'getVout'}, cbk));
|
|
61
|
+
});
|
|
62
|
+
};
|
package/esplora/index.js
ADDED
package/fiat/constants.json
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const {returnResult} = require('asyncjs-util');
|
|
3
|
+
|
|
4
|
+
const asCoingeckoDate = yyyymmdd => yyyymmdd.split('-').reverse().join('-');
|
|
5
|
+
const centsPerDollar = 100;
|
|
6
|
+
const dateComponents = date => date.substring(0, 'yyyy-mm-dd'.length);
|
|
7
|
+
const remoteServiceTimeoutMs = 30 * 1000;
|
|
8
|
+
const url = 'https://api.coingecko.com/api/v3/coins/bitcoin/history';
|
|
9
|
+
|
|
10
|
+
/** Get the number of cents for a big unit token from coingecko
|
|
11
|
+
|
|
12
|
+
{
|
|
13
|
+
currency: <Currency Type String>
|
|
14
|
+
date: <ISO 8601 Date String>
|
|
15
|
+
fiat: <Fiat Type String>
|
|
16
|
+
request: <Request Function>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@returns via cbk or Promise
|
|
20
|
+
{
|
|
21
|
+
cents: <Cents Per Token Number>
|
|
22
|
+
}
|
|
23
|
+
*/
|
|
24
|
+
module.exports = ({currency, date, fiat, request}, cbk) => {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
return asyncAuto({
|
|
27
|
+
// Check arguments
|
|
28
|
+
validate: cbk => {
|
|
29
|
+
if (currency !== 'BTC') {
|
|
30
|
+
return cbk([400, 'UnsupportedCurrencyForCoingeckoFiatRateLookup']);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!date) {
|
|
34
|
+
return cbk([400, 'ExpectedDateForCoingeckoRateLookup']);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (fiat !== 'USD') {
|
|
38
|
+
return cbk([400, 'UnsupportedFiatTypeForCoingeckoFiatRateLookup']);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!request) {
|
|
42
|
+
return cbk([400, 'ExpectedRequestMethodForCoingeckoFiatRateLookup']);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return cbk();
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// Get rate
|
|
49
|
+
getRate: ['validate', ({}, cbk) => {
|
|
50
|
+
return request({
|
|
51
|
+
url,
|
|
52
|
+
json: true,
|
|
53
|
+
qs: {
|
|
54
|
+
date: asCoingeckoDate(dateComponents(date)),
|
|
55
|
+
localization: false,
|
|
56
|
+
},
|
|
57
|
+
timeout: remoteServiceTimeoutMs,
|
|
58
|
+
},
|
|
59
|
+
(err, r, body) => {
|
|
60
|
+
if (!!err) {
|
|
61
|
+
return cbk([503, 'UnexpectedErrGettingCoingeckoPastRate', {err}]);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!body || !body.market_data || !body.market_data.current_price) {
|
|
65
|
+
return cbk([503, 'UnexpectedResponseInCoingeckoPastRateResponse']);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!body.market_data.current_price.usd) {
|
|
69
|
+
return cbk([503, 'ExpectedCoingeckoCurrentPriceForFiat']);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const cents = body.market_data.current_price.usd * centsPerDollar;
|
|
73
|
+
|
|
74
|
+
return cbk(null, {cents});
|
|
75
|
+
});
|
|
76
|
+
}],
|
|
77
|
+
},
|
|
78
|
+
returnResult({reject, resolve, of: 'getRate'}, cbk));
|
|
79
|
+
});
|
|
80
|
+
};
|
|
@@ -4,8 +4,9 @@ const {returnResult} = require('asyncjs-util');
|
|
|
4
4
|
|
|
5
5
|
const getCoincapHistoricRate = require('./get_coincap_historic_rate');
|
|
6
6
|
const getCoindeskHistoricRate = require('./get_coindesk_historic_rate');
|
|
7
|
+
const getCoingeckoHistoricRate = require('./get_coingecko_historic_rate');
|
|
7
8
|
|
|
8
|
-
const defaultRateProvider = '
|
|
9
|
+
const defaultRateProvider = 'coingecko';
|
|
9
10
|
const interval = retryCount => Math.random() * 5000 * Math.pow(2, retryCount);
|
|
10
11
|
const times = 10;
|
|
11
12
|
|
|
@@ -55,6 +56,7 @@ module.exports = ({currency, date, fiat, provider, request}, cbk) => {
|
|
|
55
56
|
const providers = {
|
|
56
57
|
coincap: getCoincapHistoricRate,
|
|
57
58
|
coindesk: getCoindeskHistoricRate,
|
|
59
|
+
coingecko: getCoingeckoHistoricRate,
|
|
58
60
|
};
|
|
59
61
|
|
|
60
62
|
const source = providers[provider || defaultRateProvider];
|
package/package.json
CHANGED
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
"url": "https://github.com/alexbosworth/ln-accounting/issues"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"async": "3.2.
|
|
10
|
+
"async": "3.2.1",
|
|
11
11
|
"asyncjs-util": "1.2.6",
|
|
12
12
|
"bitcoinjs-lib": "5.2.0",
|
|
13
|
-
"goldengate": "10.
|
|
13
|
+
"goldengate": "10.4.0",
|
|
14
14
|
"json2csv": "5.0.6",
|
|
15
|
-
"ln-service": "
|
|
15
|
+
"ln-service": "52.4.0"
|
|
16
16
|
},
|
|
17
17
|
"description": "lnd accounting reports",
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"tap": "15.0.
|
|
19
|
+
"@alexbosworth/tap": "15.0.10"
|
|
20
20
|
},
|
|
21
21
|
"engines": {
|
|
22
|
-
"node": ">=
|
|
22
|
+
"node": ">=12"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
|
25
25
|
"accounting",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"url": "https://github.com/alexbosworth/ln-accounting.git"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
|
-
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 test/fiat/*.js test/harmony/*.js test/records/*.js"
|
|
36
|
+
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 test/blockstream/*.js test/fiat/*.js test/harmony/*.js test/records/*.js"
|
|
37
37
|
},
|
|
38
|
-
"version": "
|
|
38
|
+
"version": "5.0.3"
|
|
39
39
|
}
|
|
@@ -3,25 +3,29 @@ const asyncMapSeries = require('async/mapSeries');
|
|
|
3
3
|
const asyncRetry = require('async/retry');
|
|
4
4
|
const {getChainTransactions} = require('ln-service');
|
|
5
5
|
const {getClosedChannels} = require('ln-service');
|
|
6
|
+
const {getHeight} = require('ln-service');
|
|
6
7
|
const {getSweepTransactions} = require('ln-service');
|
|
7
8
|
const {returnResult} = require('asyncjs-util');
|
|
8
9
|
const {Transaction} = require('bitcoinjs-lib');
|
|
9
10
|
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
11
|
+
const {getProxyTx} = require('./../esplora');
|
|
12
|
+
const {getProxyVout} = require('./../esplora');
|
|
12
13
|
|
|
14
|
+
const dateAsMs = date => new Date(date).getTime();
|
|
13
15
|
const {fromHex} = Transaction;
|
|
14
|
-
const interval =
|
|
16
|
+
const interval = 200;
|
|
15
17
|
const {isArray} = Array;
|
|
18
|
+
const msAsBlocks = ms => Math.ceil(ms / 1000 / 60 / 2.5);
|
|
19
|
+
const {now} = Date;
|
|
16
20
|
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
|
|
17
|
-
const times =
|
|
21
|
+
const times = 15;
|
|
18
22
|
|
|
19
23
|
/** Get chain transactions, including sweep fees
|
|
20
24
|
|
|
21
25
|
{
|
|
22
26
|
[after]: <Records Created After ISO 8601 Date>
|
|
23
27
|
[before]: <Records Created Before ISO 8601 Date>
|
|
24
|
-
lnd: <Authenticated LND Object>
|
|
28
|
+
lnd: <Authenticated LND API Object>
|
|
25
29
|
[network]: <Network Name String>
|
|
26
30
|
request: <Request Function>
|
|
27
31
|
}
|
|
@@ -63,11 +67,27 @@ module.exports = ({after, before, lnd, network, request}, cbk) => {
|
|
|
63
67
|
// Get the closed channels
|
|
64
68
|
getClosed: ['validate', ({}, cbk) => getClosedChannels({lnd}, cbk)],
|
|
65
69
|
|
|
70
|
+
// Get the chain height
|
|
71
|
+
getHeight: ['validate', ({}, cbk) => getHeight({lnd}, cbk)],
|
|
72
|
+
|
|
66
73
|
// Get the sweep transactions
|
|
67
74
|
getSweeps: ['validate', ({}, cbk) => getSweepTransactions({lnd}, cbk)],
|
|
68
75
|
|
|
69
76
|
// Get the regular set of chain transactions
|
|
70
|
-
getTx: ['
|
|
77
|
+
getTx: ['getHeight', ({getHeight}, cbk) => {
|
|
78
|
+
// Exit early when there is no after constraint
|
|
79
|
+
if (!after) {
|
|
80
|
+
return getChainTransactions({lnd}, cbk);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const height = getHeight.current_block_height;
|
|
84
|
+
|
|
85
|
+
return getChainTransactions({
|
|
86
|
+
lnd,
|
|
87
|
+
after: height - msAsBlocks(now() - dateAsMs(after)),
|
|
88
|
+
},
|
|
89
|
+
cbk);
|
|
90
|
+
}],
|
|
71
91
|
|
|
72
92
|
// Time-relevant sweep transactions
|
|
73
93
|
sweepTransactions: ['getSweeps', ({getSweeps}, cbk) => {
|
|
@@ -96,7 +116,7 @@ module.exports = ({after, before, lnd, network, request}, cbk) => {
|
|
|
96
116
|
}
|
|
97
117
|
|
|
98
118
|
return asyncRetry({interval, times}, cbk => {
|
|
99
|
-
return
|
|
119
|
+
return getProxyVout({
|
|
100
120
|
network,
|
|
101
121
|
request,
|
|
102
122
|
id: spend.transaction_id,
|
|
@@ -131,12 +151,13 @@ module.exports = ({after, before, lnd, network, request}, cbk) => {
|
|
|
131
151
|
confirmation_count: tx.confirmation_count,
|
|
132
152
|
confirmation_height: tx.confirmation_height,
|
|
133
153
|
created_at: tx.created_at,
|
|
154
|
+
description: 'Sweep',
|
|
134
155
|
fee: tx.fee || (sumOf(spends.map(n => n.tokens)) - totalOut),
|
|
135
156
|
id: tx.id,
|
|
136
157
|
is_confirmed: tx.is_confirmed,
|
|
137
158
|
is_outgoing: true,
|
|
138
159
|
output_addresses: tx.output_addresses,
|
|
139
|
-
tokens: tx.tokens,
|
|
160
|
+
tokens: tx.fee || (sumOf(spends.map(n => n.tokens)) - totalOut),
|
|
140
161
|
transaction: tx.transaction,
|
|
141
162
|
});
|
|
142
163
|
});
|
|
@@ -145,14 +166,32 @@ module.exports = ({after, before, lnd, network, request}, cbk) => {
|
|
|
145
166
|
}],
|
|
146
167
|
|
|
147
168
|
// Calculate closing fees for channels that were locally initiated
|
|
148
|
-
getClosingFees: [
|
|
169
|
+
getClosingFees: [
|
|
170
|
+
'getClosed',
|
|
171
|
+
'getHeight',
|
|
172
|
+
'getTx',
|
|
173
|
+
({getClosed, getHeight, getTx}, cbk) =>
|
|
174
|
+
{
|
|
175
|
+
const height = getHeight.current_block_height;
|
|
176
|
+
|
|
149
177
|
const channels = getClosed.channels
|
|
150
178
|
.filter(n => !!n.close_transaction_id)
|
|
151
|
-
.filter(n => n.is_partner_initiated === false)
|
|
179
|
+
.filter(n => n.is_partner_initiated === false)
|
|
180
|
+
.filter(channel => {
|
|
181
|
+
// Exit early when not checking channel close height vs after
|
|
182
|
+
if (!after || !channel.close_confirm_height) {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const afterHeight = height - msAsBlocks(now() - dateAsMs(after));
|
|
187
|
+
|
|
188
|
+
// Do not consider channels that closed before the after constraint
|
|
189
|
+
return channel.close_confirm_height > afterHeight;
|
|
190
|
+
});
|
|
152
191
|
|
|
153
192
|
return asyncMapSeries(channels, (channel, cbk) => {
|
|
154
193
|
const tx = getTx.transactions.find(tx => {
|
|
155
|
-
return tx.id === channel.close_transaction_id
|
|
194
|
+
return tx.id === channel.close_transaction_id;
|
|
156
195
|
});
|
|
157
196
|
|
|
158
197
|
const hasMissingLocalData = (() => {
|
|
@@ -172,28 +211,32 @@ module.exports = ({after, before, lnd, network, request}, cbk) => {
|
|
|
172
211
|
|
|
173
212
|
// Exit early when the close transaction is missing
|
|
174
213
|
if (hasMissingLocalData) {
|
|
175
|
-
return
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
},
|
|
180
|
-
(err, res) => {
|
|
181
|
-
if (!!err) {
|
|
182
|
-
return cbk(err);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return cbk(null, {
|
|
186
|
-
block_id: res.block_id,
|
|
187
|
-
confirmation_height: res.confirmation_height,
|
|
188
|
-
created_at: res.created_at || new Date().toISOString(),
|
|
189
|
-
fee: res.fee,
|
|
214
|
+
return asyncRetry({interval, times}, cbk => {
|
|
215
|
+
return getProxyTx({
|
|
216
|
+
network,
|
|
217
|
+
request,
|
|
190
218
|
id: channel.close_transaction_id,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
219
|
+
},
|
|
220
|
+
(err, res) => {
|
|
221
|
+
if (!!err) {
|
|
222
|
+
return cbk(err);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return cbk(null, {
|
|
226
|
+
block_id: res.block_id,
|
|
227
|
+
confirmation_height: res.confirmation_height,
|
|
228
|
+
created_at: res.created_at,
|
|
229
|
+
description: 'Channel close',
|
|
230
|
+
fee: res.fee,
|
|
231
|
+
id: res.id,
|
|
232
|
+
is_confirmed: res.is_confirmed,
|
|
233
|
+
is_outgoing: true,
|
|
234
|
+
output_addresses: res.output_addresses,
|
|
235
|
+
tokens: res.fee,
|
|
236
|
+
});
|
|
195
237
|
});
|
|
196
|
-
}
|
|
238
|
+
},
|
|
239
|
+
cbk);
|
|
197
240
|
}
|
|
198
241
|
|
|
199
242
|
const inputs = fromHex(tx.transaction).ins.map(({hash, index}) => {
|
|
@@ -216,13 +259,13 @@ module.exports = ({after, before, lnd, network, request}, cbk) => {
|
|
|
216
259
|
confirmation_count: tx.confirmation_count,
|
|
217
260
|
confirmation_height: tx.confirmation_height,
|
|
218
261
|
created_at: tx.created_at,
|
|
219
|
-
description: tx.description
|
|
262
|
+
description: `Channel close: ${tx.description}`,
|
|
220
263
|
fee: inputsValue - sumOf(outputsValue),
|
|
221
264
|
id: tx.id,
|
|
222
265
|
is_confirmed: tx.is_confirmed,
|
|
223
266
|
is_outgoing: true,
|
|
224
267
|
output_addresses: tx.output_addresses,
|
|
225
|
-
tokens:
|
|
268
|
+
tokens: inputsValue - sumOf(outputsValue),
|
|
226
269
|
transaction: tx.transaction,
|
|
227
270
|
});
|
|
228
271
|
},
|
|
@@ -231,16 +274,17 @@ module.exports = ({after, before, lnd, network, request}, cbk) => {
|
|
|
231
274
|
|
|
232
275
|
// Consolidate transactions, including missing fees
|
|
233
276
|
transactions: [
|
|
277
|
+
'getClosed',
|
|
234
278
|
'getClosingFees',
|
|
235
279
|
'getSweepFees',
|
|
236
280
|
'getTx',
|
|
237
|
-
({getClosingFees, getSweepFees, getTx}, cbk) =>
|
|
281
|
+
({getClosed, getClosingFees, getSweepFees, getTx}, cbk) =>
|
|
238
282
|
{
|
|
239
|
-
const
|
|
283
|
+
const closeIds = getClosed.channels.map(n => n.close_transaction_id);
|
|
240
284
|
const sweeps = getSweepFees.map(({id}) => id);
|
|
241
285
|
|
|
242
286
|
const normalTx = getTx.transactions.filter(({id}) => {
|
|
243
|
-
return !
|
|
287
|
+
return !closeIds.includes(id) && !sweeps.includes(id);
|
|
244
288
|
});
|
|
245
289
|
|
|
246
290
|
const transactions = []
|
|
@@ -42,7 +42,7 @@ const times = 10;
|
|
|
42
42
|
lnd: <LND gRPC Object>
|
|
43
43
|
[network]: <Network Name String>
|
|
44
44
|
[rate]: <Exchange Function> ({currency, date, fiat}, cbk) => (err, {cents})
|
|
45
|
-
[rate_provider]: <Fiat Rate Provider String> //
|
|
45
|
+
[rate_provider]: <Fiat Rate Provider String> // coindesk || coingecko
|
|
46
46
|
request: <Request Function>
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const {test} = require('@alexbosworth/tap');
|
|
2
|
+
|
|
3
|
+
const method = require('./../../blockstream/get_blockstream_tx');
|
|
4
|
+
|
|
5
|
+
const makeArgs = overrides => {
|
|
6
|
+
const args = {
|
|
7
|
+
id: Buffer.alloc(32).toString('hex'),
|
|
8
|
+
network: 'btc',
|
|
9
|
+
request: ({}, cbk) => cbk(null, null, {
|
|
10
|
+
fee: 1,
|
|
11
|
+
status: {
|
|
12
|
+
block_hash: Buffer.alloc(32).toString('hex'),
|
|
13
|
+
block_height: 1,
|
|
14
|
+
block_time: 1,
|
|
15
|
+
confirmed: true,
|
|
16
|
+
},
|
|
17
|
+
vout: [{scriptpubkey_address: 'bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'}],
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
Object.keys(overrides).forEach(k => args[k] = overrides[k]);
|
|
22
|
+
|
|
23
|
+
return args;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const tests = [
|
|
27
|
+
{
|
|
28
|
+
args: makeArgs({id: undefined}),
|
|
29
|
+
description: 'An id is required',
|
|
30
|
+
error: [400, 'ExpectedTransactionIdToGetBlockstreamTxFee'],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
args: makeArgs({network: 'network'}),
|
|
34
|
+
description: 'A known network is required',
|
|
35
|
+
error: [400, 'UnsupportedNetworkToGetBlockstreamTxFee'],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
args: makeArgs({request: undefined}),
|
|
39
|
+
description: 'A request method is required',
|
|
40
|
+
error: [400, 'ExpectedRequestFunctionToGetBlockstreamTxFee'],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
args: makeArgs({request: ({}, cbk) => cbk('err')}),
|
|
44
|
+
description: 'Request errors are passed back',
|
|
45
|
+
error: [503, 'UnexpectedErrorGettingBlockstreamTxFee', {err: 'err'}],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
args: makeArgs({request: ({}, cbk) => cbk()}),
|
|
49
|
+
description: 'A response body is expected',
|
|
50
|
+
error: [503, 'ExpectedTxLookupResultForBlockstreamTxFee'],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
args: makeArgs({request: ({}, cbk) => cbk(null, null, {})}),
|
|
54
|
+
description: 'A transaction fee is required',
|
|
55
|
+
error: [503, 'ExpectedTransactionFeeInResultFromBlockstream'],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
args: makeArgs({request: ({}, cbk) => cbk(null, null, {fee: 1})}),
|
|
59
|
+
description: 'Transaction status is required',
|
|
60
|
+
error: [503, 'ExpectedStatusOfBlockstreamTransaction'],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
args: makeArgs({
|
|
64
|
+
request: ({}, cbk) => cbk(null, null, {fee: 1, status: {}}),
|
|
65
|
+
}),
|
|
66
|
+
description: 'A vout array is required',
|
|
67
|
+
error: [503, 'ExpectedOutputsInBlockstreamTransaction'],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
args: makeArgs({
|
|
71
|
+
network: 'btctestnet',
|
|
72
|
+
request: ({}, cbk) => cbk(null, null, {
|
|
73
|
+
fee: 1,
|
|
74
|
+
status: {},
|
|
75
|
+
vout: [{scriptpubkey_address: 'bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'}],
|
|
76
|
+
}),
|
|
77
|
+
}),
|
|
78
|
+
description: 'An unconfirmed tx is returned',
|
|
79
|
+
expected: {
|
|
80
|
+
fee: 1,
|
|
81
|
+
output_addresses: ['bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
args: makeArgs({}),
|
|
86
|
+
description: 'Blockstream tx info is returned',
|
|
87
|
+
expected: {
|
|
88
|
+
confirmation_height: 1,
|
|
89
|
+
created_at: '1970-01-01T00:00:01.000Z',
|
|
90
|
+
block_id: Buffer.alloc(32).toString('hex'),
|
|
91
|
+
fee: 1,
|
|
92
|
+
output_addresses: ['bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj']
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
tests.forEach(({args, description, error, expected}) => {
|
|
98
|
+
return test(description, async ({end, rejects, strictSame}) => {
|
|
99
|
+
if (!!error) {
|
|
100
|
+
await rejects(method(args), error, 'Got expected error');
|
|
101
|
+
} else {
|
|
102
|
+
const res = await method(args);
|
|
103
|
+
|
|
104
|
+
strictSame(res, expected, 'Got expected result');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return end();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const {test} = require('@alexbosworth/tap');
|
|
2
|
+
|
|
3
|
+
const method = require('./../../fiat/get_coingecko_historic_rate');
|
|
4
|
+
|
|
5
|
+
const api = ({qs}, cbk) => {
|
|
6
|
+
return cbk(null, null, {market_data: {current_price: {usd: 12.34}}});
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const tests = [
|
|
10
|
+
{
|
|
11
|
+
args: {},
|
|
12
|
+
description: 'A currency is required',
|
|
13
|
+
error: [400, 'UnsupportedCurrencyForCoingeckoFiatRateLookup'],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
args: {currency: 'BTC'},
|
|
17
|
+
description: 'A date is required',
|
|
18
|
+
error: [400, 'ExpectedDateForCoingeckoRateLookup'],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
args: {currency: 'BTC', date: new Date().toISOString()},
|
|
22
|
+
description: 'A currency type is required',
|
|
23
|
+
error: [400, 'UnsupportedFiatTypeForCoingeckoFiatRateLookup'],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
args: {currency: 'BTC', date: new Date().toISOString(), fiat: 'USD'},
|
|
27
|
+
description: 'A request method is required',
|
|
28
|
+
error: [400, 'ExpectedRequestMethodForCoingeckoFiatRateLookup'],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
args: {
|
|
32
|
+
date: new Date().toISOString(),
|
|
33
|
+
currency: 'BTC',
|
|
34
|
+
fiat: 'USD',
|
|
35
|
+
request: ({}, cbk) => cbk('err'),
|
|
36
|
+
},
|
|
37
|
+
description: 'Errors returned from request',
|
|
38
|
+
error: [503, 'UnexpectedErrGettingCoingeckoPastRate', {err: 'err'}],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
args: {
|
|
42
|
+
date: new Date().toISOString(),
|
|
43
|
+
currency: 'BTC',
|
|
44
|
+
fiat: 'USD',
|
|
45
|
+
request: ({}, cbk) => cbk(),
|
|
46
|
+
},
|
|
47
|
+
description: 'A body is expected in response',
|
|
48
|
+
error: [503, 'UnexpectedResponseInCoingeckoPastRateResponse'],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
args: {
|
|
52
|
+
date: new Date().toISOString(),
|
|
53
|
+
currency: 'BTC',
|
|
54
|
+
fiat: 'USD',
|
|
55
|
+
request: ({}, cbk) => cbk(null, null, {
|
|
56
|
+
market_data: {current_price: {}},
|
|
57
|
+
}),
|
|
58
|
+
},
|
|
59
|
+
description: 'A price is expected in response',
|
|
60
|
+
error: [503, 'ExpectedCoingeckoCurrentPriceForFiat'],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
args: {
|
|
64
|
+
date: new Date().toISOString(),
|
|
65
|
+
currency: 'BTC',
|
|
66
|
+
fiat: 'USD',
|
|
67
|
+
request: ({qs}, cbk) => api({qs}, cbk),
|
|
68
|
+
},
|
|
69
|
+
description: 'Get coindesk historic rate',
|
|
70
|
+
expected: {cents: 1234},
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
tests.forEach(({args, description, error, expected}) => {
|
|
75
|
+
return test(description, async ({end, rejects, strictSame}) => {
|
|
76
|
+
if (!!error) {
|
|
77
|
+
await rejects(method(args), error, 'Got expected error');
|
|
78
|
+
} else {
|
|
79
|
+
const res = await method(args);
|
|
80
|
+
|
|
81
|
+
strictSame(res, expected, 'Got expected return value');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return end();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
const {test} = require('tap');
|
|
1
|
+
const {test} = require('@alexbosworth/tap');
|
|
2
2
|
|
|
3
3
|
const getHistoricRate = require('./../../fiat/get_historic_rate');
|
|
4
4
|
|
|
5
5
|
const date = new Date().toISOString();
|
|
6
6
|
|
|
7
|
-
const api = ({}, cbk) =>
|
|
7
|
+
const api = ({}, cbk) => {
|
|
8
|
+
return cbk(null, null, {market_data: {current_price: {usd: 12.34}}});
|
|
9
|
+
};
|
|
8
10
|
|
|
9
11
|
const tests = [
|
|
10
12
|
{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {test} = require('tap');
|
|
1
|
+
const {test} = require('@alexbosworth/tap');
|
|
2
2
|
|
|
3
3
|
const {getAllPayments} = require('./../../records');
|
|
4
4
|
|
|
@@ -99,6 +99,7 @@ const tests = [
|
|
|
99
99
|
expected: {
|
|
100
100
|
payments: [{
|
|
101
101
|
attempts: [{
|
|
102
|
+
confirmed_at: '1970-01-01T00:00:00.001Z',
|
|
102
103
|
is_confirmed: false,
|
|
103
104
|
is_failed: true,
|
|
104
105
|
is_pending: false,
|
|
@@ -122,6 +123,7 @@ const tests = [
|
|
|
122
123
|
total_mtokens: '1000',
|
|
123
124
|
},
|
|
124
125
|
}],
|
|
126
|
+
confirmed_at: undefined,
|
|
125
127
|
created_at: '1970-01-01T00:00:01.000Z',
|
|
126
128
|
destination: Buffer.alloc(33).toString('hex'),
|
|
127
129
|
fee: 1,
|