@alexbosworth/fiat 2.0.0 → 3.0.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.
Files changed (105) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +93 -1
  3. package/arrays/index.js +3 -0
  4. package/arrays/sort_by.js +44 -0
  5. package/chain/add_days.js +22 -0
  6. package/chain/blocks/block_header_hash.js +29 -0
  7. package/chain/blocks/block_header_time.js +25 -0
  8. package/chain/blocks/find_complete_day.js +90 -0
  9. package/chain/blocks/find_height_for_time.js +93 -0
  10. package/chain/blocks/get_block_hash.js +60 -0
  11. package/chain/blocks/get_block_header.js +45 -0
  12. package/chain/blocks/get_block_payments.js +100 -0
  13. package/chain/blocks/get_blocks_for_date.js +110 -0
  14. package/chain/blocks/get_median_time.js +92 -0
  15. package/chain/blocks/index.js +19 -0
  16. package/chain/blocks/payment_outputs_from_block.js +178 -0
  17. package/chain/blocks/sample_heights.js +33 -0
  18. package/chain/blocks/window_heights.js +34 -0
  19. package/chain/chain_rates.js +129 -0
  20. package/chain/constants.json +26 -0
  21. package/chain/estimates/check_known_estimates.js +119 -0
  22. package/chain/estimates/current_estimates.js +66 -0
  23. package/chain/estimates/estimate_and_remember.js +199 -0
  24. package/chain/estimates/estimate_day_price.js +361 -0
  25. package/chain/estimates/estimate_record.js +107 -0
  26. package/chain/estimates/estimate_windows.js +246 -0
  27. package/chain/estimates/find_starting_day.js +86 -0
  28. package/chain/estimates/forget_estimates.js +113 -0
  29. package/chain/estimates/get_chain_price.js +167 -0
  30. package/chain/estimates/get_price_for_date.js +607 -0
  31. package/chain/estimates/get_price_from_blocks.js +281 -0
  32. package/chain/estimates/get_provisional_price.js +163 -0
  33. package/chain/estimates/index.js +25 -0
  34. package/chain/estimates/is_alias_ratio.js +38 -0
  35. package/chain/estimates/is_supported_estimate.js +81 -0
  36. package/chain/estimates/nearest_estimate.js +49 -0
  37. package/chain/estimates/price_window.js +25 -0
  38. package/chain/estimates/remember_estimate.js +83 -0
  39. package/chain/estimates/store_estimate.js +198 -0
  40. package/chain/histogram/best_position.js +63 -0
  41. package/chain/histogram/histogram_excess.js +50 -0
  42. package/chain/histogram/histogram_peak.js +87 -0
  43. package/chain/histogram/implied_prices_histogram.js +53 -0
  44. package/chain/histogram/index.js +33 -0
  45. package/chain/histogram/is_round_tokens.js +26 -0
  46. package/chain/histogram/kernel_width.js +25 -0
  47. package/chain/histogram/lower_bound.js +28 -0
  48. package/chain/histogram/noise_of_neighbors.js +28 -0
  49. package/chain/histogram/noise_strength.js +35 -0
  50. package/chain/histogram/noise_strengths.js +29 -0
  51. package/chain/histogram/outputs_histogram.js +80 -0
  52. package/chain/histogram/parabola_peak.js +28 -0
  53. package/chain/histogram/payments_near_position.js +31 -0
  54. package/chain/histogram/peak_width.js +37 -0
  55. package/chain/histogram/position_score.js +38 -0
  56. package/chain/histogram/score_position.js +94 -0
  57. package/chain/histogram/smooth_bins.js +29 -0
  58. package/chain/histogram/sorted_neighbors.js +89 -0
  59. package/chain/histogram/spike_strengths.js +33 -0
  60. package/chain/histogram/template_agreement.js +27 -0
  61. package/chain/index.js +3 -0
  62. package/chain/known/earliest_supported_day.js +12 -0
  63. package/chain/known/index.js +11 -0
  64. package/chain/known/known_prices.js +43 -0
  65. package/chain/known/known_prices.json +1836 -0
  66. package/chain/known/method_version.js +26 -0
  67. package/chain/known/parse_known_prices.js +67 -0
  68. package/chain/outputs/alias_shifts.js +31 -0
  69. package/chain/outputs/cap_near_round_values.js +83 -0
  70. package/chain/outputs/cap_repeated_values.js +33 -0
  71. package/chain/outputs/dollar_bin_for_price.js +19 -0
  72. package/chain/outputs/estimate_from_outputs.js +321 -0
  73. package/chain/outputs/index.js +11 -0
  74. package/chain/outputs/peak_around_price.js +86 -0
  75. package/chain/outputs/price_for_dollar_bin.js +21 -0
  76. package/chain/outputs/price_from_outputs.js +98 -0
  77. package/chain/outputs/refine_price.js +73 -0
  78. package/chain/outputs/round_amounts.js +49 -0
  79. package/chain/outputs/template.json +22 -0
  80. package/coinbase/constants.json +5 -0
  81. package/coinbase/get_coinbase_day_candles.js +155 -0
  82. package/coinbase/get_coinbase_historic_price.js +135 -0
  83. package/coinbase/index.js +7 -1
  84. package/dates/constants.json +3 -0
  85. package/dates/day_of_date.js +26 -0
  86. package/dates/index.js +5 -0
  87. package/dates/is_calendar_day.js +16 -0
  88. package/dates/time_of_date.js +61 -0
  89. package/docs/METHODOLOGY.md +984 -0
  90. package/index.js +2 -1
  91. package/package.json +29 -8
  92. package/rates/get_prices.js +51 -8
  93. package/storage/constants.json +4 -0
  94. package/storage/get_prices_file.js +169 -0
  95. package/storage/index.js +4 -0
  96. package/storage/is_prices.js +31 -0
  97. package/storage/put_prices_file.js +252 -0
  98. package/storage/temp_path.js +22 -0
  99. package/.gitattributes +0 -1
  100. package/.tm_properties +0 -1
  101. package/test/coinbase/test_get_coinbase_current_price.js +0 -77
  102. package/test/coindesk/test_get_coindesk_current_price.js +0 -95
  103. package/test/coingecko/test_get_coingecko_rates.js +0 -93
  104. package/test/rates/test_get_current_price.js +0 -74
  105. package/test/rates/test_get_prices.js +0 -78
@@ -0,0 +1,246 @@
1
+ const asyncAuto = require('async/auto');
2
+ const asyncMapSeries = require('async/mapSeries');
3
+ const {returnResult} = require('asyncjs-util');
4
+
5
+ const {getMedianTime} = require('./../blocks');
6
+ const getPriceFromBlocks = require('./get_price_from_blocks');
7
+ const {half} = require('./../constants');
8
+ const isSupportedEstimate = require('./is_supported_estimate');
9
+ const {sortBy} = require('./../../arrays');
10
+ const {unpricedErrors} = require('./../constants');
11
+ const {windowHeights} = require('./../blocks');
12
+
13
+ const {isArray} = Array;
14
+ const isHeight = n => Number.isInteger(n) && n >= 0;
15
+ const {isInteger} = Number;
16
+ const {max} = Math;
17
+ const sum = arr => arr.reduce((sum, n) => sum + n, Number());
18
+ const windowBlocksCount = 12;
19
+
20
+ /** Estimate the USD price of BTC on a day from windows of its blocks
21
+
22
+ {
23
+ blocks: <Retrieved Blocks By Height Map Object>
24
+ heights: [<Block Height Number>]
25
+ [is_capping_near_round]: <Cap Values Just Off Round BTC Amounts Too Bool>
26
+ lnd: <Authenticated LND API Object>
27
+ [max_price]: <Maximum Possible USD Price Number>
28
+ [min_price]: <Minimum Possible USD Price Number>
29
+ times: <Block Times By Height Map Object>
30
+ }
31
+
32
+ @returns via cbk or Promise
33
+ {
34
+ [agreement]: <Spike Strengths Agreement With Template Weights Number>
35
+ [cents]: <USD Cents Per BTC Number>
36
+ [confidence]: <Best Position Score Over Best Alias Position Score Number>
37
+ [coverage]: <Share of Spike Excess At Round USD Positions Number>
38
+ [dispersion]: <Peak Width Beyond Smoothing As Price Fraction Number>
39
+ [evidence]: <Share of Prior Weight Observable At the Price Number>
40
+ is_supported: <Supported Windows Cover Enough of the Day Bool>
41
+ [is_unsettled]: <Refinement Was Still Moving After Its Last Pass Bool>
42
+ [outputs]: <Payment Outputs Considered Count Number>
43
+ [payments]: <Estimated Round USD Payments Count Number>
44
+ [rivalry]: <Best Patterned Rival Position Score Over Best Score Number>
45
+ [round_shift]: <Price Change If Round BTC Values Were Counted Number>
46
+ [significance]: <Payment Peak Beyond Noise In Standard Deviations Number>
47
+ [spikes]: [{
48
+ strength: <Spike Strength Relative to the Strongest Spike Number>
49
+ usd: <Round USD Amount Number>
50
+ }]
51
+ [stability]: <Largest Price Change From Perturbed Refinement Number>
52
+ windows: [{
53
+ blocks: [<First Block Height Number>, <Last Block Height Number>]
54
+ [cents]: <USD Cents Per BTC Number>
55
+ is_supported: <Window Price Is Supported By the Data Bool>
56
+ [is_widened]: <Search Was Widened Beyond the Price Window Bool>
57
+ seconds: <Time Covered By the Window Seconds Number>
58
+ }]
59
+ }
60
+ */
61
+ module.exports = (args, cbk) => {
62
+ return new Promise((resolve, reject) => {
63
+ return asyncAuto({
64
+ // Check arguments
65
+ validate: cbk => {
66
+ if (!args.blocks) {
67
+ return cbk([400, 'ExpectedRetrievedBlocksMapToEstimateWindows']);
68
+ }
69
+
70
+ if (!isArray(args.heights) || !args.heights.length) {
71
+ return cbk([400, 'ExpectedBlockHeightsToEstimateWindows']);
72
+ }
73
+
74
+ if (!args.heights.every(isHeight)) {
75
+ return cbk([400, 'ExpectedBlockHeightNumbersToEstimateWindows']);
76
+ }
77
+
78
+ if (!args.lnd) {
79
+ return cbk([400, 'ExpectedAuthenticatedLndToEstimateWindows']);
80
+ }
81
+
82
+ if (!args.times) {
83
+ return cbk([400, 'ExpectedBlockTimesMapToEstimateWindows']);
84
+ }
85
+
86
+ return cbk();
87
+ },
88
+
89
+ // Split the blocks into windows
90
+ windows: ['validate', ({}, cbk) => {
91
+ const heights = args.heights.slice().sort((a, b) => a - b);
92
+
93
+ return cbk(null, windowHeights({heights, count: windowBlocksCount}));
94
+ }],
95
+
96
+ // Estimate each window on its own
97
+ estimates: ['windows', ({windows}, cbk) => {
98
+ return asyncMapSeries(windows.windows, (heights, cbk) => {
99
+ return getPriceFromBlocks({
100
+ heights,
101
+ blocks: args.blocks,
102
+ is_capping_near_round: args.is_capping_near_round,
103
+ lnd: args.lnd,
104
+ max_price: args.max_price,
105
+ min_price: args.min_price,
106
+ },
107
+ (err, res) => {
108
+ // A window whose outputs cannot be priced is not supported
109
+ if (!!err && unpricedErrors.includes(err[1])) {
110
+ return cbk(null, {heights});
111
+ }
112
+
113
+ if (!!err) {
114
+ return cbk(err);
115
+ }
116
+
117
+ const support = isSupportedEstimate({
118
+ confidence: res.confidence,
119
+ evidence: res.evidence,
120
+ is_unconstrained: !args.min_price || !!res.is_widened,
121
+ payments: res.payments,
122
+ rivalry: res.rivalry,
123
+ significance: res.significance,
124
+ spikes: res.spikes,
125
+ stability: res.stability,
126
+ });
127
+
128
+ return cbk(null, {
129
+ heights,
130
+ estimate: res,
131
+ is_supported: support.is_supported,
132
+ });
133
+ });
134
+ },
135
+ cbk);
136
+ }],
137
+
138
+ // Get the median times past bounding the windows, from the block before
139
+ getTimes: ['estimates', 'windows', ({windows}, cbk) => {
140
+ const [[first]] = windows.windows;
141
+
142
+ // The first block of the chain has no block before it
143
+ const start = max(Number(), first - 1);
144
+
145
+ const bounds = [start].concat(windows.windows.map(heights => {
146
+ return heights[heights.length - 1];
147
+ }));
148
+
149
+ // The retrieved blocks already have their timestamps
150
+ args.blocks.forEach((block, height) => {
151
+ // Exit early when the time is known or the block has no time
152
+ if (args.times.has(height) || !isInteger(block.time)) {
153
+ return;
154
+ }
155
+
156
+ return args.times.set(height, block.time);
157
+ });
158
+
159
+ return asyncMapSeries(bounds, (height, cbk) => {
160
+ return getMedianTime({
161
+ height,
162
+ lnd: args.lnd,
163
+ times: args.times,
164
+ },
165
+ cbk);
166
+ },
167
+ cbk);
168
+ }],
169
+
170
+ // Price the day from the windows that are supported
171
+ price: ['estimates', 'getTimes', ({estimates, getTimes}, cbk) => {
172
+ // Each window covers the time since the end of the window before it
173
+ const windows = estimates.map((window, index) => {
174
+ const {estimate} = window;
175
+ const [first] = window.heights;
176
+ const [last] = window.heights.slice(-1);
177
+
178
+ const elapsed = getTimes[index + 1].time - getTimes[index].time;
179
+
180
+ // A window whose search had to be widened is not counted
181
+ const isWidened = !!estimate && !!estimate.is_widened;
182
+
183
+ return {
184
+ estimate,
185
+ blocks: [first, last],
186
+ cents: !estimate ? undefined : estimate.cents,
187
+ is_counted: !!window.is_supported && !isWidened,
188
+ is_supported: !!window.is_supported,
189
+ is_widened: isWidened || undefined,
190
+ seconds: max(Number(), elapsed),
191
+ };
192
+ });
193
+
194
+ const counted = windows.filter(n => n.is_counted);
195
+
196
+ const coveredSeconds = sum(counted.map(n => n.seconds));
197
+ const totalSeconds = sum(windows.map(n => n.seconds));
198
+
199
+ const reported = windows.map(window => ({
200
+ blocks: window.blocks,
201
+ cents: window.cents,
202
+ is_supported: window.is_supported,
203
+ is_widened: window.is_widened,
204
+ seconds: window.seconds,
205
+ }));
206
+
207
+ // Exit early when the supported windows cover too little of the day
208
+ if (!totalSeconds || coveredSeconds < totalSeconds * half) {
209
+ return cbk(null, {is_supported: false, windows: reported});
210
+ }
211
+
212
+ // The median is the price with half of the covered time below it
213
+ const {sorted} = sortBy({array: counted, attribute: 'cents'});
214
+
215
+ const below = sorted.map((window, position) => {
216
+ return sum(sorted.slice(Number(), position + 1).map(n => n.seconds));
217
+ });
218
+
219
+ const middle = below.findIndex(n => n >= coveredSeconds * half);
220
+
221
+ const {estimate} = sorted[middle];
222
+
223
+ // Measures are the median window's, outputs and payments are totals
224
+ return cbk(null, {
225
+ agreement: estimate.agreement,
226
+ cents: estimate.cents,
227
+ confidence: estimate.confidence,
228
+ coverage: estimate.coverage,
229
+ dispersion: estimate.dispersion,
230
+ evidence: estimate.evidence,
231
+ is_supported: true,
232
+ is_unsettled: estimate.is_unsettled,
233
+ outputs: sum(counted.map(n => n.estimate.outputs)),
234
+ payments: sum(counted.map(n => n.estimate.payments)),
235
+ rivalry: estimate.rivalry,
236
+ round_shift: estimate.round_shift,
237
+ significance: estimate.significance,
238
+ spikes: estimate.spikes,
239
+ stability: estimate.stability,
240
+ windows: reported,
241
+ });
242
+ }],
243
+ },
244
+ returnResult({reject, resolve, of: 'price'}, cbk));
245
+ });
246
+ };
@@ -0,0 +1,86 @@
1
+ const asyncAuto = require('async/auto');
2
+ const asyncTimesSeries = require('async/timesSeries');
3
+ const {returnResult} = require('asyncjs-util');
4
+
5
+ const addDays = require('./../add_days');
6
+
7
+ const maxStartDays = 7;
8
+
9
+ /** Find a starting day whose price its own blocks clearly identify
10
+
11
+ {
12
+ estimate: <Estimate Day Price Function> ({
13
+ day: <Day YYYY-MM-DD String>
14
+ is_sample: <Estimate From a Sample of Blocks Bool>
15
+ is_unguided: <Estimate Without a Neighboring Guide Bool>
16
+ }, cbk) => (err, {is_supported: <Price Is Supported Bool>})
17
+ latest: <Latest Complete Day YYYY-MM-DD String>
18
+ [logger]: <Winston Logger Object>
19
+ }
20
+
21
+ @returns via cbk or Promise
22
+ {
23
+ day: <Supported Starting Day YYYY-MM-DD String>
24
+ }
25
+ */
26
+ module.exports = ({estimate, latest, logger}, cbk) => {
27
+ return new Promise((resolve, reject) => {
28
+ return asyncAuto({
29
+ // Try the latest complete day, then the days before it
30
+ findStart: cbk => {
31
+ const start = {};
32
+ const tried = [];
33
+
34
+ return asyncTimesSeries(maxStartDays, (i, cbk) => {
35
+ // Exit early when a supported starting day was already found
36
+ if (!!start.day) {
37
+ return cbk();
38
+ }
39
+
40
+ const {day} = addDays({day: latest, days: -i});
41
+
42
+ tried.push(day);
43
+
44
+ return estimate({
45
+ day,
46
+ is_sample: false,
47
+ is_unguided: true,
48
+ },
49
+ (err, res) => {
50
+ if (!!err) {
51
+ return cbk(err);
52
+ }
53
+
54
+ // Exit early when the day clearly identifies its price
55
+ if (!!res.is_supported) {
56
+ start.day = day;
57
+
58
+ return cbk();
59
+ }
60
+
61
+ if (!!logger) {
62
+ logger.info({unsupported_starting_day: day});
63
+ }
64
+
65
+ return cbk();
66
+ });
67
+ },
68
+ err => {
69
+ if (!!err) {
70
+ return cbk(err);
71
+ }
72
+
73
+ // Exit early when no starting price is clearly identified
74
+ if (!start.day) {
75
+ return cbk([503, 'FailedToFindSupportedStartingPrice', {
76
+ days: tried,
77
+ }]);
78
+ }
79
+
80
+ return cbk(null, {day: start.day});
81
+ });
82
+ },
83
+ },
84
+ returnResult({reject, resolve, of: 'findStart'}, cbk));
85
+ });
86
+ };
@@ -0,0 +1,113 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {returnResult} = require('asyncjs-util');
3
+
4
+ const currentEstimates = require('./current_estimates');
5
+ const {putPricesFile} = require('./../../storage');
6
+
7
+ const {isArray} = Array;
8
+ const types = ['priors', 'provisional', 'rates'];
9
+
10
+ /** Forget estimates whose last blocks are no longer in the chain
11
+
12
+ {
13
+ estimates: {
14
+ priors: {<Day YYYY-MM-DD String>: <Estimate Object>}
15
+ [provisional]: {<Day YYYY-MM-DD String>: <Estimate Object>}
16
+ rates: {<Day YYYY-MM-DD String>: <Estimate Object>}
17
+ }
18
+ fs: {
19
+ getFile: <Read File Contents Function> (path, cbk) => {}
20
+ makeDirectory: <Make Directory Function> (path, cbk) => {}
21
+ renameFile: <Rename File Function> (from, to, cbk) => {}
22
+ writeFile: <Write File Contents Function> (path, contents, cbk) => {}
23
+ }
24
+ path: <Prices File Path String>
25
+ removed: [{
26
+ day: <Day YYYY-MM-DD String>
27
+ hash: <Last Block Hash Hex String>
28
+ }]
29
+ }
30
+
31
+ @returns via cbk or Promise
32
+ */
33
+ module.exports = ({estimates, fs, path, removed}, cbk) => {
34
+ return new Promise((resolve, reject) => {
35
+ return asyncAuto({
36
+ // Check arguments
37
+ validate: cbk => {
38
+ if (!estimates || !estimates.priors || !estimates.rates) {
39
+ return cbk([400, 'ExpectedKnownEstimatesToForgetEstimates']);
40
+ }
41
+
42
+ if (!fs) {
43
+ return cbk([400, 'ExpectedFileSystemMethodsToForgetEstimates']);
44
+ }
45
+
46
+ if (!path) {
47
+ return cbk([400, 'ExpectedPricesFilePathToForgetEstimates']);
48
+ }
49
+
50
+ if (!isArray(removed)) {
51
+ return cbk([400, 'ExpectedRemovedEstimatesToForgetEstimates']);
52
+ }
53
+
54
+ return cbk();
55
+ },
56
+
57
+ // Forget the estimates that are kept in memory
58
+ forget: ['validate', ({}, cbk) => {
59
+ removed.forEach(({day, hash}) => {
60
+ types.filter(type => !!estimates[type]).forEach(type => {
61
+ const known = estimates[type][day];
62
+
63
+ if (!!known && known.hash === hash) {
64
+ delete estimates[type][day];
65
+ }
66
+ });
67
+ });
68
+
69
+ return cbk();
70
+ }],
71
+
72
+ // Remove the estimates from the prices file
73
+ write: ['forget', ({}, cbk) => {
74
+ // Exit early when there is nothing to remove
75
+ if (!removed.length) {
76
+ return cbk();
77
+ }
78
+
79
+ return putPricesFile({
80
+ fs,
81
+ path,
82
+ removed,
83
+ priors: estimates.priors,
84
+ provisional: estimates.provisional,
85
+ rates: estimates.rates,
86
+ },
87
+ cbk);
88
+ }],
89
+
90
+ // Keep the current prices that are now in the file
91
+ update: ['write', ({write}, cbk) => {
92
+ // Exit early when the file was not written
93
+ if (!write) {
94
+ return cbk();
95
+ }
96
+
97
+ // Prices in the file from other versions of the method are not used
98
+ const current = currentEstimates({
99
+ priors: write.priors,
100
+ provisional: write.provisional,
101
+ rates: write.rates,
102
+ });
103
+
104
+ estimates.priors = current.priors;
105
+ estimates.provisional = current.provisional;
106
+ estimates.rates = current.rates;
107
+
108
+ return cbk();
109
+ }],
110
+ },
111
+ returnResult({reject, resolve}, cbk));
112
+ });
113
+ };
@@ -0,0 +1,167 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {getHeight} = require('ln-service');
3
+ const {returnResult} = require('asyncjs-util');
4
+
5
+ const currentEstimates = require('./current_estimates');
6
+ const {dayOfDate} = require('./../../dates');
7
+ const forgetEstimates = require('./forget_estimates');
8
+ const getPriceForDate = require('./get_price_for_date');
9
+ const {getPricesFile} = require('./../../storage');
10
+ const storeEstimate = require('./store_estimate');
11
+
12
+ const {assign} = Object;
13
+
14
+ /** Get the USD price of BTC on a date derived from chain data
15
+
16
+ {
17
+ date: <Day YYYY-MM-DD String>
18
+ estimates: {
19
+ [priors]: {<Day YYYY-MM-DD String>: <Estimate Object>}
20
+ provisional: {<Day YYYY-MM-DD String>: {cents: <USD Cents Number>}}
21
+ [rates]: {<Day YYYY-MM-DD String>: <Estimate Object>}
22
+ samples: <Count of Sample Estimates Stored Number>
23
+ }
24
+ fs: {
25
+ getFile: <Read File Contents Function> (path, cbk) => {}
26
+ makeDirectory: <Make Directory Function> (path, cbk) => {}
27
+ renameFile: <Rename File Function> (from, to, cbk) => {}
28
+ writeFile: <Write File Contents Function> (path, contents, cbk) => {}
29
+ }
30
+ [known]: {
31
+ <Day YYYY-MM-DD String>: {
32
+ cents: <USD Cents Per BTC Number>
33
+ version: <Method Version String>
34
+ }
35
+ }
36
+ lnd: <Authenticated LND API Object>
37
+ [logger]: <Winston Logger Object>
38
+ path: <Prices File Path String>
39
+ [unsupported]: <Unsupported Estimates Map Object>
40
+ }
41
+
42
+ @returns via cbk or Promise
43
+ {
44
+ cents: <USD Cents Per BTC Number>
45
+ }
46
+ */
47
+ module.exports = (args, cbk) => {
48
+ const {date, estimates, fs, lnd, logger, path, unsupported} = args;
49
+
50
+ const known = args.known || {};
51
+
52
+ return new Promise((resolve, reject) => {
53
+ return asyncAuto({
54
+ // Check arguments
55
+ validate: cbk => {
56
+ if (!date) {
57
+ return cbk([400, 'ExpectedDateToGetChainPrice']);
58
+ }
59
+
60
+ if (!dayOfDate({date}).day) {
61
+ return cbk([400, 'ExpectedValidDateToGetChainPrice']);
62
+ }
63
+
64
+ if (!estimates) {
65
+ return cbk([400, 'ExpectedKnownEstimatesToGetChainPrice']);
66
+ }
67
+
68
+ if (!fs) {
69
+ return cbk([400, 'ExpectedFileSystemMethodsToGetChainPrice']);
70
+ }
71
+
72
+ if (!lnd) {
73
+ return cbk([400, 'ExpectedAuthenticatedLndToGetChainPrice']);
74
+ }
75
+
76
+ if (!path) {
77
+ return cbk([400, 'ExpectedPricesFilePathToGetChainPrice']);
78
+ }
79
+
80
+ return cbk();
81
+ },
82
+
83
+ // A price shipped with the method answers for its day
84
+ shipped: ['validate', ({}, cbk) => {
85
+ const shipped = known[dayOfDate({date}).day];
86
+
87
+ return cbk(null, !shipped ? undefined : {cents: shipped.cents});
88
+ }],
89
+
90
+ // Get the previously derived prices
91
+ getPrices: ['shipped', ({shipped}, cbk) => {
92
+ // Exit early when the price was shipped or the prices are loaded
93
+ if (!!shipped || !!estimates.rates) {
94
+ return cbk();
95
+ }
96
+
97
+ return getPricesFile({fs, path}, cbk);
98
+ }],
99
+
100
+ // Keep the previously derived prices of the current method
101
+ loadPrices: ['getPrices', ({getPrices}, cbk) => {
102
+ // Exit early when there are no prices to load
103
+ if (!getPrices) {
104
+ return cbk();
105
+ }
106
+
107
+ // Prices derived by another version of the method are not used
108
+ const current = currentEstimates({
109
+ priors: getPrices.priors,
110
+ provisional: getPrices.provisional,
111
+ rates: getPrices.rates,
112
+ });
113
+
114
+ estimates.priors = current.priors;
115
+ estimates.provisional = current.provisional;
116
+ estimates.rates = current.rates;
117
+
118
+ return cbk();
119
+ }],
120
+
121
+ // Get the current chain height
122
+ getHeight: ['shipped', ({shipped}, cbk) => {
123
+ // Exit early when the price was shipped with the method
124
+ if (!!shipped) {
125
+ return cbk();
126
+ }
127
+
128
+ return getHeight({lnd}, cbk);
129
+ }],
130
+
131
+ // Derive the price for the date
132
+ getPrice: [
133
+ 'getHeight',
134
+ 'loadPrices',
135
+ 'shipped',
136
+ ({getHeight, shipped}, cbk) =>
137
+ {
138
+ // Exit early when the price was shipped with the method
139
+ if (!!shipped) {
140
+ return cbk(null, shipped);
141
+ }
142
+
143
+ // Shipped prices guide the days after them but are never stored
144
+ return getPriceForDate({
145
+ date,
146
+ lnd,
147
+ logger,
148
+ unsupported,
149
+ get_prices: ({}, cbk) => cbk(null, {
150
+ priors: estimates.priors,
151
+ provisional: estimates.provisional,
152
+ rates: assign({}, known, estimates.rates),
153
+ }),
154
+ height: getHeight.current_block_height,
155
+ put_price: (estimate, cbk) => {
156
+ return storeEstimate({estimate, estimates, fs, logger, path}, cbk);
157
+ },
158
+ remove_prices: ({removed}, cbk) => {
159
+ return forgetEstimates({estimates, fs, path, removed}, cbk);
160
+ },
161
+ },
162
+ cbk);
163
+ }],
164
+ },
165
+ returnResult({reject, resolve, of: 'getPrice'}, cbk));
166
+ });
167
+ };