@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,607 @@
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
+ const checkKnownEstimates = require('./check_known_estimates');
7
+ const {confirmationsAfterDay} = require('./../constants');
8
+ const currentEstimates = require('./current_estimates');
9
+ const {dayOfDate} = require('./../../dates');
10
+ const {earliestSupportedDay} = require('./../known');
11
+ const estimateAndRemember = require('./estimate_and_remember');
12
+ const {findCompleteDay} = require('./../blocks');
13
+ const findStartingDay = require('./find_starting_day');
14
+ const {getBlocksForDate} = require('./../blocks');
15
+ const {getMedianTime} = require('./../blocks');
16
+ const getProvisionalPrice = require('./get_provisional_price');
17
+ const {msPerDay} = require('./../constants');
18
+ const {msPerSecond} = require('./../constants');
19
+ const nearestEstimate = require('./nearest_estimate');
20
+
21
+ const {abs} = Math;
22
+ const dayStart = day => Date.parse(`${day}T00:00:00.000Z`);
23
+ const {isArray} = Array;
24
+ const isHeight = n => Number.isInteger(n) && n >= 0;
25
+ const {max} = Math;
26
+ const maxProvisionalAge = 12;
27
+ const maxTipLagMs = 1000 * 60 * 60 * 6;
28
+ const minProvisionalBlocks = 36;
29
+ const minSupportedDay = earliestSupportedDay({}).day;
30
+ const range = (a, b) => [...Array(b - a).keys()].map(i => a + i);
31
+ const startSavingDays = 7;
32
+ const yesterday = -1;
33
+
34
+ /** Get the USD price of BTC on the day of a date using only chain data
35
+
36
+ {
37
+ date: <ISO 8601 Date String>
38
+ get_prices: <Get Known Prices Function> ({}, cbk) => (err, {
39
+ [priors]: {<Day YYYY-MM-DD String>: {cents: <USD Cents Number>}}
40
+ [provisional]: {
41
+ <Day YYYY-MM-DD String>: {
42
+ [blocks]: [<First Block Height Number>, <Last Block Height Number>]
43
+ cents: <USD Cents Number>
44
+ }
45
+ }
46
+ [rates]: {<Day YYYY-MM-DD String>: {cents: <USD Cents Number>}}
47
+ })
48
+ height: <Current Best Chain Height Number>
49
+ lnd: <Authenticated LND API Object>
50
+ [logger]: <Winston Logger Object>
51
+ put_price: <Remember Price Function> ({
52
+ agreement: <Spike Strengths Agreement With Template Weights Number>
53
+ blocks: [<First Block Height Number>, <Last Block Height Number>]
54
+ cents: <USD Cents Per BTC Number>
55
+ confidence: <Best Position Score Over Best Alias Position Score Number>
56
+ coverage: <Share of Spike Excess At Round USD Positions Number>
57
+ date: <Day YYYY-MM-DD String>
58
+ dispersion: <Peak Width Beyond Smoothing As Price Fraction Number>
59
+ [drift]: <Second Half Price Change From First Half Price Number>
60
+ evidence: <Share of Prior Weight Observable At the Price Number>
61
+ hash: <Last Block Hash Hex String>
62
+ [is_provisional]: <Price Is For An Incomplete Day Bool>
63
+ [is_sample]: <Price Is A Rough Estimate From Sample Blocks Bool>
64
+ [is_widened]: <Price Was Found Outside the Expected Price Window Bool>
65
+ outputs: <Payment Outputs Considered Count Number>
66
+ payments: <Estimated Round USD Payments Count Number>
67
+ rivalry: <Best Patterned Rival Position Score Over Best Score Number>
68
+ round_shift: <Price Change If Round BTC Values Were Counted Number>
69
+ significance: <Payment Peak Beyond Noise In Standard Deviations Number>
70
+ spikes: [{
71
+ strength: <Spike Strength Relative to the Strongest Spike Number>
72
+ usd: <Round USD Amount Number>
73
+ }]
74
+ stability: <Largest Price Change From Perturbed Refinement Number>
75
+ version: <Method Version String>
76
+ [windows]: <Supported Windows Inside the Price Window Count Number>
77
+ }, cbk) => (err)
78
+ [remove_prices]: <Remove Prices Function> ({
79
+ removed: [{
80
+ day: <Day YYYY-MM-DD String>
81
+ hash: <Last Block Hash Hex String>
82
+ }]
83
+ }, cbk) => (err)
84
+ [unsupported]: <Unsupported Estimates Map Object>
85
+ }
86
+
87
+ @returns via cbk or Promise
88
+ {
89
+ cents: <USD Cents Per BTC Number>
90
+ is_provisional: <Price Is Provisional Bool>
91
+ }
92
+ */
93
+ module.exports = (args, cbk) => {
94
+ // The blocks of the day priced in full are kept for its other estimates
95
+ const dayBlocks = new Map();
96
+
97
+ return new Promise((resolve, reject) => {
98
+ return asyncAuto({
99
+ // Check arguments
100
+ validate: cbk => {
101
+ if (!args.date || typeof args.date !== 'string') {
102
+ return cbk([400, 'ExpectedDateToGetPriceForDate']);
103
+ }
104
+
105
+ const {day} = dayOfDate({date: args.date});
106
+
107
+ if (!day) {
108
+ return cbk([400, 'ExpectedValidDateToGetPriceForDate']);
109
+ }
110
+
111
+ if (day < minSupportedDay) {
112
+ return cbk([400, 'DateBeforeSupportedRangeToGetPriceForDate', {
113
+ earliest_day: minSupportedDay,
114
+ }]);
115
+ }
116
+
117
+ if (typeof args.get_prices !== 'function') {
118
+ return cbk([400, 'ExpectedGetPricesFunctionToGetPriceForDate']);
119
+ }
120
+
121
+ if (!isHeight(args.height)) {
122
+ return cbk([400, 'ExpectedCurrentChainHeightToGetPriceForDate']);
123
+ }
124
+
125
+ if (!args.lnd) {
126
+ return cbk([400, 'ExpectedAuthenticatedLndToGetPriceForDate']);
127
+ }
128
+
129
+ if (typeof args.put_price !== 'function') {
130
+ return cbk([400, 'ExpectedPutPriceFunctionToGetPriceForDate']);
131
+ }
132
+
133
+ if (!!args.remove_prices && typeof args.remove_prices !== 'function') {
134
+ return cbk([400, 'ExpectedRemovePricesFunctionToGetPriceForDate']);
135
+ }
136
+
137
+ return cbk();
138
+ },
139
+
140
+ // The day of the date in UTC
141
+ day: ['validate', ({}, cbk) => cbk(null, dayOfDate(args).day)],
142
+
143
+ // Block times are kept across days to avoid retrieving them twice
144
+ times: ['validate', ({}, cbk) => cbk(null, new Map())],
145
+
146
+ // Get the known prices
147
+ getPrices: ['validate', ({}, cbk) => args.get_prices({}, cbk)],
148
+
149
+ // The known prices that were derived by the current method
150
+ estimates: ['getPrices', ({getPrices}, cbk) => {
151
+ if (!getPrices) {
152
+ return cbk([503, 'ExpectedKnownPricesToGetPriceForDate']);
153
+ }
154
+
155
+ const {priors, provisional, rates} = currentEstimates({
156
+ priors: getPrices.priors,
157
+ provisional: getPrices.provisional,
158
+ rates: getPrices.rates,
159
+ });
160
+
161
+ return cbk(null, {priors, provisional, rates});
162
+ }],
163
+
164
+ // Check that recently derived known prices are still in the chain
165
+ checkKnown: ['estimates', ({estimates}, cbk) => {
166
+ return checkKnownEstimates({
167
+ estimates,
168
+ height: args.height,
169
+ lnd: args.lnd,
170
+ },
171
+ cbk);
172
+ }],
173
+
174
+ // Prices from replaced blocks are removed where they are kept too
175
+ removeKnown: ['checkKnown', ({checkKnown}, cbk) => {
176
+ const {removed} = checkKnown;
177
+
178
+ // Exit early when there is nothing to remove or nowhere to remove it
179
+ if (!removed.length || !args.remove_prices) {
180
+ return cbk();
181
+ }
182
+
183
+ return args.remove_prices({removed}, cbk);
184
+ }],
185
+
186
+ // Estimate the price for a day and remember it when it is supported
187
+ estimate: ['estimates', 'times', ({estimates, times}, cbk) => {
188
+ const estimate = (target, cbk) => {
189
+ return estimateAndRemember({
190
+ estimates,
191
+ times,
192
+ blocks: target.blocks,
193
+ day: target.day,
194
+ height: args.height,
195
+ is_sample: target.is_sample,
196
+ is_unguided: target.is_unguided,
197
+ lnd: args.lnd,
198
+ logger: args.logger,
199
+ put_price: args.put_price,
200
+ unsupported: args.unsupported,
201
+ },
202
+ cbk);
203
+ };
204
+
205
+ return cbk(null, estimate);
206
+ }],
207
+
208
+ // Check if the day is complete and can have a final price
209
+ getBlocks: [
210
+ 'day',
211
+ 'estimates',
212
+ 'removeKnown',
213
+ 'times',
214
+ ({day, estimates, times}, cbk) =>
215
+ {
216
+ // Exit early when the price is already known
217
+ if (!!estimates.rates[day]) {
218
+ return cbk();
219
+ }
220
+
221
+ return getBlocksForDate({
222
+ times,
223
+ date: day,
224
+ height: args.height,
225
+ lnd: args.lnd,
226
+ },
227
+ cbk);
228
+ }],
229
+
230
+ // A recent provisional price of an incomplete day is reused
231
+ reuse: [
232
+ 'day',
233
+ 'estimates',
234
+ 'getBlocks',
235
+ ({day, estimates, getBlocks}, cbk) =>
236
+ {
237
+ // Exit early when the price is known or the day is complete
238
+ if (!getBlocks || !!getBlocks.is_complete) {
239
+ return cbk();
240
+ }
241
+
242
+ const {blocks} = estimates.provisional[day] || {};
243
+
244
+ // A provisional price ends six blocks below the tip it was derived
245
+ // at, so it is reused until about six more blocks have been mined
246
+ const age = !isArray(blocks) ? Infinity : args.height - blocks[1];
247
+
248
+ // Exit early when there is no recent provisional price
249
+ if (age > maxProvisionalAge) {
250
+ return cbk();
251
+ }
252
+
253
+ return cbk(null, {
254
+ cents: estimates.provisional[day].cents,
255
+ is_provisional: true,
256
+ });
257
+ }],
258
+
259
+ // Get the time of the chain tip when the day is not complete
260
+ getTipTime: [
261
+ 'getBlocks',
262
+ 'reuse',
263
+ 'times',
264
+ ({getBlocks, reuse, times}, cbk) =>
265
+ {
266
+ // Exit early when the price is known or the day is complete
267
+ if (!getBlocks || !!getBlocks.is_complete || !!reuse) {
268
+ return cbk();
269
+ }
270
+
271
+ return getMedianTime({
272
+ times,
273
+ height: args.height,
274
+ lnd: args.lnd,
275
+ },
276
+ cbk);
277
+ }],
278
+
279
+ // A day that has not started, or that the chain has not reached, fails
280
+ checkStarted: ['day', 'getTipTime', ({day, getTipTime}, cbk) => {
281
+ // Exit early when there is no incomplete day to check
282
+ if (!getTipTime) {
283
+ return cbk();
284
+ }
285
+
286
+ const tipTime = getTipTime.time * msPerSecond;
287
+
288
+ // The chain tip can be later than the clock, as in a simulation
289
+ if (dayStart(day) > max(Date.now(), tipTime)) {
290
+ return cbk([400, 'DateAfterCurrentDayToGetPriceForDate', {day}]);
291
+ }
292
+
293
+ // A chain that is far behind the day has none of its blocks yet
294
+ if (dayStart(day) - tipTime > maxTipLagMs) {
295
+ return cbk([503, 'ChainHasNotReachedDayToGetPriceForDate', {day}]);
296
+ }
297
+
298
+ return cbk();
299
+ }],
300
+
301
+ // The latest complete day guides the price of an incomplete day
302
+ getLatest: [
303
+ 'checkStarted',
304
+ 'getBlocks',
305
+ 'reuse',
306
+ 'times',
307
+ ({getBlocks, reuse, times}, cbk) =>
308
+ {
309
+ // Exit early when the price is known or the day is complete
310
+ if (!getBlocks || !!getBlocks.is_complete || !!reuse) {
311
+ return cbk();
312
+ }
313
+
314
+ return findCompleteDay({
315
+ times,
316
+ height: args.height,
317
+ lnd: args.lnd,
318
+ },
319
+ cbk);
320
+ }],
321
+
322
+ // A complete day is bridged to directly, and an incomplete day through
323
+ // the latest complete day before it, so the guide of its provisional
324
+ // price is never further back, both from complete day estimates only
325
+ route: [
326
+ 'day',
327
+ 'getBlocks',
328
+ 'getLatest',
329
+ 'reuse',
330
+ ({day, getBlocks, getLatest, reuse}, cbk) =>
331
+ {
332
+ // Exit early when the price is already known or is reused
333
+ if (!getBlocks || !!reuse) {
334
+ return cbk();
335
+ }
336
+
337
+ // Exit early when the day is complete
338
+ if (!!getBlocks.is_complete) {
339
+ return cbk(null, {is_complete: true, target: day});
340
+ }
341
+
342
+ return cbk(null, {latest: getLatest.day, target: getLatest.day});
343
+ }],
344
+
345
+ // The nearest estimate that the day can be bridged to from
346
+ nearest: ['estimates', 'route', ({estimates, route}, cbk) => {
347
+ // Exit early when there is nothing to bridge to
348
+ if (!route) {
349
+ return cbk();
350
+ }
351
+
352
+ return cbk(null, nearestEstimate({
353
+ estimates,
354
+ day: route.target,
355
+ latest: route.latest,
356
+ }));
357
+ }],
358
+
359
+ // A complete day far from any estimate can start from the latest
360
+ // complete day instead, so the latest complete day is found for it
361
+ getStart: [
362
+ 'nearest',
363
+ 'route',
364
+ 'times',
365
+ ({nearest, route, times}, cbk) =>
366
+ {
367
+ // Exit early when there is no complete day to bridge to
368
+ if (!route || !route.is_complete) {
369
+ return cbk();
370
+ }
371
+
372
+ const {estimate} = nearest;
373
+
374
+ // Exit early when starting over cannot save a week of bridging
375
+ if (!!estimate && estimate.distance <= startSavingDays) {
376
+ return cbk();
377
+ }
378
+
379
+ return findCompleteDay({
380
+ times,
381
+ height: args.height,
382
+ lnd: args.lnd,
383
+ },
384
+ cbk);
385
+ }],
386
+
387
+ // Start from the latest complete day when no estimate is near the day
388
+ coldStart: [
389
+ 'estimate',
390
+ 'getStart',
391
+ 'nearest',
392
+ 'route',
393
+ ({estimate, getStart, nearest, route}, cbk) =>
394
+ {
395
+ // Exit early when there is nothing to bridge to
396
+ if (!route) {
397
+ return cbk();
398
+ }
399
+
400
+ const closest = nearest.estimate;
401
+
402
+ // Exit early when starting over cannot save a week of bridging
403
+ if (!!closest && closest.distance <= startSavingDays) {
404
+ return cbk();
405
+ }
406
+
407
+ // Either kind of day starts from the latest complete day
408
+ const latest = !!getStart ? getStart.day : route.latest;
409
+
410
+ const away = abs(dayStart(latest) - dayStart(route.target)) / msPerDay;
411
+
412
+ // Exit early when the latest complete day is not a week nearer
413
+ if (!!closest && closest.distance - away <= startSavingDays) {
414
+ return cbk();
415
+ }
416
+
417
+ return findStartingDay({
418
+ latest,
419
+ logger: args.logger,
420
+ estimate: (target, cbk) => estimate({
421
+ blocks: target.day === route.target ? dayBlocks : undefined,
422
+ day: target.day,
423
+ is_sample: target.is_sample,
424
+ is_unguided: target.is_unguided,
425
+ },
426
+ cbk),
427
+ },
428
+ cbk);
429
+ }],
430
+
431
+ // Step day by day from the nearest estimate towards the day
432
+ bridge: [
433
+ 'coldStart',
434
+ 'estimate',
435
+ 'estimates',
436
+ 'route',
437
+ ({estimate, estimates, route}, cbk) =>
438
+ {
439
+ // Exit early when there is nothing to bridge to
440
+ if (!route) {
441
+ return cbk();
442
+ }
443
+
444
+ const {latest, target} = route;
445
+
446
+ const nearest = nearestEstimate({estimates, latest, day: target});
447
+
448
+ // Exit early when there is no complete day to bridge from
449
+ if (!nearest.estimate) {
450
+ return cbk();
451
+ }
452
+
453
+ // A complete day is reached when its neighbor has an estimate, and
454
+ // the latest complete day when it has an estimate itself
455
+ const reach = !!route.is_complete ? 1 : 0;
456
+
457
+ // Every step brings the nearest estimate a day closer to the target
458
+ const steps = max(Number(), nearest.estimate.distance - reach);
459
+
460
+ return asyncTimesSeries(steps, (i, cbk) => {
461
+ const {estimate: guide} = nearestEstimate({
462
+ estimates,
463
+ latest,
464
+ day: target,
465
+ });
466
+
467
+ const days = guide.day < target ? 1 : yesterday;
468
+
469
+ const next = addDays({days, day: guide.day}).day;
470
+
471
+ // The latest complete day keeps its blocks from the start
472
+ const blocks = next === route.latest ? dayBlocks : undefined;
473
+
474
+ return estimate({blocks, day: next, is_sample: true}, (err, res) => {
475
+ if (!!err) {
476
+ return cbk(err);
477
+ }
478
+
479
+ // An undetermined latest complete day leaves the day before it
480
+ // to guide the provisional price, as it did before the bridge
481
+ // reached the latest complete day itself
482
+ if (!res.is_supported && !route.is_complete && next === target) {
483
+ return cbk();
484
+ }
485
+
486
+ if (!res.is_supported) {
487
+ return cbk([503, 'FailedToFindSupportedPriceToBridgeDay', {
488
+ day: next,
489
+ }]);
490
+ }
491
+
492
+ return cbk();
493
+ });
494
+ },
495
+ cbk);
496
+ }],
497
+
498
+ // Estimate a complete day that has no price yet
499
+ estimateDay: [
500
+ 'bridge',
501
+ 'day',
502
+ 'estimate',
503
+ 'estimates',
504
+ 'getBlocks',
505
+ 'reuse',
506
+ ({day, estimate, estimates, getBlocks, reuse}, cbk) =>
507
+ {
508
+ // Exit early when the price is known or reused, or the day is not over
509
+ if (!!estimates.rates[day] || !!reuse || !getBlocks.is_complete) {
510
+ return cbk();
511
+ }
512
+
513
+ return estimate({day, blocks: dayBlocks, is_sample: false}, cbk);
514
+ }],
515
+
516
+ // The price for the day, or that it needs a provisional price
517
+ getPrice: [
518
+ 'day',
519
+ 'estimateDay',
520
+ 'estimates',
521
+ 'reuse',
522
+ ({day, estimateDay, estimates, reuse}, cbk) =>
523
+ {
524
+ // Exit early when the data do not support the price of a complete day
525
+ if (!!estimateDay && !estimateDay.is_supported) {
526
+ return cbk([503, 'FailedToFindSupportedPrice', {day}]);
527
+ }
528
+
529
+ // Exit early when a complete day was priced from its blocks
530
+ if (!!estimateDay) {
531
+ return cbk(null, {cents: estimateDay.cents});
532
+ }
533
+
534
+ // Exit early when the price is known
535
+ if (!!estimates.rates[day]) {
536
+ return cbk(null, {cents: estimates.rates[day].cents});
537
+ }
538
+
539
+ // Exit early when a recent provisional price is reused
540
+ if (!!reuse) {
541
+ return cbk(null, reuse);
542
+ }
543
+
544
+ // The day is not over yet and needs a provisional price
545
+ return cbk(null, {is_provisional: true});
546
+ }],
547
+
548
+ // The blocks to use for a provisional price
549
+ provisionalBlocks: [
550
+ 'getBlocks',
551
+ 'getPrice',
552
+ ({getBlocks, getPrice}, cbk) =>
553
+ {
554
+ // Exit early when there is no provisional price to derive
555
+ if (!getPrice.is_provisional || !!getPrice.cents) {
556
+ return cbk();
557
+ }
558
+
559
+ const end = args.height - confirmationsAfterDay + 1;
560
+
561
+ // The confirmed blocks of the day so far
562
+ const confirmed = getBlocks.heights.filter(height => height < end);
563
+
564
+ const first = !confirmed.length ? end : confirmed[Number()];
565
+ const missing = minProvisionalBlocks - confirmed.length;
566
+
567
+ // Pad with the blocks before the day when the day has just begun
568
+ const start = max(Number(), first - max(Number(), missing));
569
+
570
+ return cbk(null, range(start, first).concat(confirmed));
571
+ }],
572
+
573
+ // Derive a provisional price for an incomplete day
574
+ provisional: [
575
+ 'day',
576
+ 'estimates',
577
+ 'getPrice',
578
+ 'provisionalBlocks',
579
+ ({day, estimates, getPrice, provisionalBlocks}, cbk) =>
580
+ {
581
+ // Exit early when there is no provisional price to derive
582
+ if (!provisionalBlocks) {
583
+ return cbk(null, getPrice);
584
+ }
585
+
586
+ return getProvisionalPrice({
587
+ day,
588
+ estimates,
589
+ heights: provisionalBlocks,
590
+ lnd: args.lnd,
591
+ logger: args.logger,
592
+ put_price: args.put_price,
593
+ },
594
+ cbk);
595
+ }],
596
+
597
+ // Final price
598
+ price: ['provisional', ({provisional}, cbk) => {
599
+ return cbk(null, {
600
+ cents: provisional.cents,
601
+ is_provisional: !!provisional.is_provisional,
602
+ });
603
+ }],
604
+ },
605
+ returnResult({reject, resolve, of: 'price'}, cbk));
606
+ });
607
+ };