@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,110 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {returnResult} = require('asyncjs-util');
3
+
4
+ const {confirmationsAfterDay} = require('./../constants');
5
+ const findHeightForTime = require('./find_height_for_time');
6
+ const {isCalendarDay} = require('./../../dates');
7
+ const {msPerSecond} = require('./../constants');
8
+
9
+ const dayFormat = /^\d{4}-\d{2}-\d{2}$/;
10
+ const isHeight = n => Number.isInteger(n) && n >= 0;
11
+ const range = (a, b) => [...Array(b - a).keys()].map(i => a + i);
12
+ const secondsPerDay = 60 * 60 * 24;
13
+
14
+ /** Get the heights of the blocks that belong to a UTC calendar day
15
+
16
+ {
17
+ date: <Day YYYY-MM-DD String>
18
+ height: <Current Best Chain Height Number>
19
+ lnd: <Authenticated LND API Object>
20
+ [times]: <Block Times By Height Map Object>
21
+ }
22
+
23
+ @returns via cbk or Promise
24
+ {
25
+ heights: [<Block Height Number>]
26
+ is_complete: <Day Is Complete Bool>
27
+ }
28
+ */
29
+ module.exports = (args, cbk) => {
30
+ return new Promise((resolve, reject) => {
31
+ return asyncAuto({
32
+ // Check arguments
33
+ validate: cbk => {
34
+ if (!args.date || !dayFormat.test(args.date)) {
35
+ return cbk([400, 'ExpectedYyyyMmDdDateToGetBlocksForDate']);
36
+ }
37
+
38
+ if (!isCalendarDay({day: args.date}).is_calendar_day) {
39
+ return cbk([400, 'ExpectedValidDateToGetBlocksForDate']);
40
+ }
41
+
42
+ if (!isHeight(args.height)) {
43
+ return cbk([400, 'ExpectedCurrentChainHeightToGetBlocksForDate']);
44
+ }
45
+
46
+ if (!args.lnd) {
47
+ return cbk([400, 'ExpectedAuthenticatedLndToGetBlocksForDate']);
48
+ }
49
+
50
+ return cbk();
51
+ },
52
+
53
+ // Determine the start and end times of the day
54
+ times: ['validate', ({}, cbk) => {
55
+ const start = Date.parse(`${args.date}T00:00:00.000Z`) / msPerSecond;
56
+
57
+ return cbk(null, {start, end: start + secondsPerDay});
58
+ }],
59
+
60
+ // Block times are kept to avoid retrieving them twice
61
+ known: ['validate', ({}, cbk) => cbk(null, args.times || new Map())],
62
+
63
+ // Find the first block on the day
64
+ getFirst: ['known', 'times', ({known, times}, cbk) => {
65
+ return findHeightForTime({
66
+ height: args.height,
67
+ lnd: args.lnd,
68
+ low: Number(),
69
+ time: times.start,
70
+ times: known,
71
+ },
72
+ cbk);
73
+ }],
74
+
75
+ // Find the block after the last block on the day
76
+ getNext: [
77
+ 'getFirst',
78
+ 'known',
79
+ 'times',
80
+ ({getFirst, known, times}, cbk) =>
81
+ {
82
+ return findHeightForTime({
83
+ height: args.height,
84
+ lnd: args.lnd,
85
+ low: getFirst.at_or_after,
86
+ time: times.end,
87
+ times: known,
88
+ },
89
+ cbk);
90
+ }],
91
+
92
+ // Determine the blocks on the day
93
+ blocks: ['getFirst', 'getNext', ({getFirst, getNext}, cbk) => {
94
+ const first = getFirst.at_or_after;
95
+ const next = getNext.at_or_after;
96
+
97
+ const heights = range(first, next);
98
+
99
+ // A day is complete when it has blocks and enough blocks after it
100
+ const confirmed = next + confirmationsAfterDay <= args.height + 1;
101
+
102
+ return cbk(null, {
103
+ heights,
104
+ is_complete: !!heights.length && confirmed,
105
+ });
106
+ }],
107
+ },
108
+ returnResult({reject, resolve, of: 'blocks'}, cbk));
109
+ });
110
+ };
@@ -0,0 +1,92 @@
1
+ const asyncAuto = require('async/auto');
2
+ const asyncMapLimit = require('async/mapLimit');
3
+ const {returnResult} = require('asyncjs-util');
4
+
5
+ const blockHeaderTime = require('./block_header_time');
6
+ const {fetchLimit} = require('./../constants');
7
+ const getBlockHeader = require('./get_block_header');
8
+
9
+ const {floor} = Math;
10
+ const isHeight = n => Number.isInteger(n) && n >= 0;
11
+ const {max} = Math;
12
+ const medianWindowBlocks = 11;
13
+ const range = (a, b) => [...Array(b - a).keys()].map(i => a + i);
14
+
15
+ /** Get the median time past of a block
16
+
17
+ {
18
+ height: <Block Height Number>
19
+ lnd: <Authenticated LND API Object>
20
+ [times]: <Block Times By Height Map Object>
21
+ }
22
+
23
+ @returns via cbk or Promise
24
+ {
25
+ time: <Median Time Past Epoch Seconds Number>
26
+ }
27
+ */
28
+ module.exports = ({height, lnd, times}, cbk) => {
29
+ return new Promise((resolve, reject) => {
30
+ return asyncAuto({
31
+ // Check arguments
32
+ validate: cbk => {
33
+ if (!isHeight(height)) {
34
+ return cbk([400, 'ExpectedBlockHeightToGetMedianTime']);
35
+ }
36
+
37
+ if (!lnd) {
38
+ return cbk([400, 'ExpectedAuthenticatedLndToGetMedianTime']);
39
+ }
40
+
41
+ return cbk();
42
+ },
43
+
44
+ // Get the times of the block and the blocks before it
45
+ getTimes: ['validate', ({}, cbk) => {
46
+ const known = times || new Map();
47
+ const start = max(Number(), height - medianWindowBlocks + 1);
48
+
49
+ const heights = range(start, height + 1);
50
+
51
+ return asyncMapLimit(heights, fetchLimit, (height, cbk) => {
52
+ // Exit early when the time of the block is already known
53
+ if (known.has(height)) {
54
+ return cbk(null, known.get(height));
55
+ }
56
+
57
+ return getBlockHeader({height, lnd}, (err, res) => {
58
+ if (!!err) {
59
+ return cbk(err);
60
+ }
61
+
62
+ const {time} = (() => {
63
+ try {
64
+ return blockHeaderTime({header: res.header});
65
+ } catch (err) {
66
+ return {};
67
+ }
68
+ })();
69
+
70
+ // Exit early when the header has no time to read
71
+ if (time === undefined) {
72
+ return cbk([503, 'ExpectedBlockHeaderToGetMedianTime']);
73
+ }
74
+
75
+ known.set(height, time);
76
+
77
+ return cbk(null, time);
78
+ });
79
+ },
80
+ cbk);
81
+ }],
82
+
83
+ // The median time is the middle of the sorted times
84
+ median: ['getTimes', ({getTimes}, cbk) => {
85
+ const sorted = getTimes.slice().sort((a, b) => a - b);
86
+
87
+ return cbk(null, {time: sorted[floor(sorted.length / 2)]});
88
+ }],
89
+ },
90
+ returnResult({reject, resolve, of: 'median'}, cbk));
91
+ });
92
+ };
@@ -0,0 +1,19 @@
1
+ const blockHeaderHash = require('./block_header_hash');
2
+ const findCompleteDay = require('./find_complete_day');
3
+ const getBlockHash = require('./get_block_hash');
4
+ const getBlockPayments = require('./get_block_payments');
5
+ const getBlocksForDate = require('./get_blocks_for_date');
6
+ const getMedianTime = require('./get_median_time');
7
+ const sampleHeights = require('./sample_heights');
8
+ const windowHeights = require('./window_heights');
9
+
10
+ module.exports = {
11
+ blockHeaderHash,
12
+ findCompleteDay,
13
+ getBlockHash,
14
+ getBlockPayments,
15
+ getBlocksForDate,
16
+ getMedianTime,
17
+ sampleHeights,
18
+ windowHeights,
19
+ };
@@ -0,0 +1,178 @@
1
+ const {compactIntAsNumber} = require('@alexbosworth/blockchain');
2
+
3
+ const {blockHeaderByteLength} = require('./../constants');
4
+ const {maxTokens} = require('./../constants');
5
+ const {minTokens} = require('./../constants');
6
+
7
+ const byteCountHash = 32;
8
+ const byteCountInt32 = 4;
9
+ const byteCountInt64 = 8;
10
+ const byteCountMarkerFlag = 2;
11
+ const coinbaseInputIndex = 0xffffffff;
12
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
13
+ const isHex = n => !!n && !(n.length % 2) && /^[0-9A-F]*$/i.test(n);
14
+ const maxInputsCount = 5;
15
+ const maxWitnessBytes = 500;
16
+ const opReturn = 0x6a;
17
+ const paymentOutputsCount = 2;
18
+ const witnessFlagValue = 1;
19
+ const witnessMarkerValue = 0;
20
+
21
+ /** Derive payment-like outputs from a raw block for on-chain price analysis
22
+
23
+ {
24
+ block: <Hex Encoded Raw Block String>
25
+ }
26
+
27
+ @throws
28
+ <Error>
29
+
30
+ @returns
31
+ {
32
+ transactions: [{
33
+ outputs: [<Output Tokens Number>]
34
+ }]
35
+ }
36
+ */
37
+ module.exports = ({block}) => {
38
+ if (!isHex(block)) {
39
+ throw new Error('ExpectedHexEncodedBlockToDerivePaymentOutputs');
40
+ }
41
+
42
+ const buffer = hexAsBuffer(block);
43
+
44
+ // Exit early with error when the block header data is cut short
45
+ if (buffer.length <= blockHeaderByteLength) {
46
+ throw new Error('ExpectedBlockHeaderAndTransactionsToDeriveOutputs');
47
+ }
48
+
49
+ const transactions = [];
50
+
51
+ // A compact int count of transactions follows the block header
52
+ const txCount = compactIntAsNumber({
53
+ encoded: buffer,
54
+ start: blockHeaderByteLength,
55
+ });
56
+
57
+ const readCount = () => {
58
+ const count = compactIntAsNumber({encoded: buffer, start: cursor});
59
+
60
+ cursor += count.bytes;
61
+
62
+ return count.number;
63
+ };
64
+
65
+ let cursor = blockHeaderByteLength + txCount.bytes;
66
+ let remaining = txCount.number;
67
+
68
+ // Iterate forward through the block, parsing each transaction
69
+ while (remaining--) {
70
+ const outputs = [];
71
+
72
+ let hasOpReturn = false;
73
+ let isCoinbase = false;
74
+ let isOverWitnessLimit = false;
75
+
76
+ cursor += byteCountInt32;
77
+
78
+ // SegWit transactions have a marker and flag following the version
79
+ const isSegWit = buffer[cursor] === witnessMarkerValue &&
80
+ buffer[cursor + 1] === witnessFlagValue;
81
+
82
+ if (isSegWit) {
83
+ cursor += byteCountMarkerFlag;
84
+ }
85
+
86
+ const inputsCount = readCount();
87
+
88
+ for (let i = 0; i < inputsCount; i++) {
89
+ const hash = buffer.subarray(cursor, cursor + byteCountHash);
90
+
91
+ cursor += byteCountHash;
92
+
93
+ const vout = buffer.readUInt32LE(cursor);
94
+
95
+ cursor += byteCountInt32;
96
+
97
+ // A coinbase input spends a null outpoint
98
+ if (vout === coinbaseInputIndex && hash.every(n => !n)) {
99
+ isCoinbase = true;
100
+ }
101
+
102
+ const scriptLength = readCount();
103
+
104
+ cursor += scriptLength + byteCountInt32;
105
+ }
106
+
107
+ const outputsCount = readCount();
108
+
109
+ for (let i = 0; i < outputsCount; i++) {
110
+ const tokens = Number(buffer.readBigUInt64LE(cursor));
111
+
112
+ cursor += byteCountInt64;
113
+
114
+ const scriptLength = readCount();
115
+
116
+ if (!!scriptLength && buffer[cursor] === opReturn) {
117
+ hasOpReturn = true;
118
+ }
119
+
120
+ cursor += scriptLength;
121
+
122
+ outputs.push(tokens);
123
+ }
124
+
125
+ // SegWit places a witness stack for every input after the outputs
126
+ if (isSegWit) {
127
+ for (let i = 0; i < inputsCount; i++) {
128
+ const itemsCount = readCount();
129
+
130
+ let witnessBytes = 0;
131
+
132
+ for (let j = 0; j < itemsCount; j++) {
133
+ const itemLength = readCount();
134
+
135
+ cursor += itemLength;
136
+
137
+ witnessBytes += itemLength;
138
+ }
139
+
140
+ if (witnessBytes > maxWitnessBytes) {
141
+ isOverWitnessLimit = true;
142
+ }
143
+ }
144
+ }
145
+
146
+ cursor += byteCountInt32;
147
+
148
+ // Exit early with error when the transaction runs past the block data
149
+ if (cursor > buffer.length) {
150
+ throw new Error('UnexpectedTransactionLengthInBlockToDeriveOutputs');
151
+ }
152
+
153
+ // Exit early when the transaction does not look like a regular payment
154
+ if (isCoinbase || hasOpReturn || isOverWitnessLimit) {
155
+ continue;
156
+ }
157
+
158
+ if (inputsCount > maxInputsCount || outputsCount !== paymentOutputsCount) {
159
+ continue;
160
+ }
161
+
162
+ const tokens = outputs.filter(n => n > minTokens && n < maxTokens);
163
+
164
+ // Exit early when no output is in the analyzed range
165
+ if (!tokens.length) {
166
+ continue;
167
+ }
168
+
169
+ transactions.push({outputs: tokens});
170
+ }
171
+
172
+ // Exit early with error when there is extra data after the transactions
173
+ if (cursor !== buffer.length) {
174
+ throw new Error('UnexpectedDataAfterBlockTransactionsToDeriveOutputs');
175
+ }
176
+
177
+ return {transactions};
178
+ };
@@ -0,0 +1,33 @@
1
+ const {floor} = Math;
2
+ const range = n => [...Array(n).keys()];
3
+
4
+ /** Pick evenly spaced heights to get a quick sample of a range of blocks
5
+
6
+ {
7
+ count: <Sample Blocks Count Number>
8
+ heights: [<Block Height Number>]
9
+ [initial]: <Blocks In the First Sample Count Number>
10
+ }
11
+
12
+ @returns
13
+ {
14
+ heights: [<Sampled Block Height Number>]
15
+ }
16
+ */
17
+ module.exports = ({count, heights, initial}) => {
18
+ // Exit early when there are not more blocks than the sample size
19
+ if (heights.length <= count) {
20
+ return {heights};
21
+ }
22
+
23
+ // A doubled sample takes the start of each stretch to keep the first one
24
+ const isDoubled = !!initial && count > initial;
25
+
26
+ // Positions are counted in half stretches so that every sample of a range
27
+ // maps them to the same heights
28
+ const halves = range(count).map(i => isDoubled ? 2 * i : 2 * i + 1);
29
+
30
+ const indices = halves.map(n => floor(n * heights.length / (2 * count)));
31
+
32
+ return {heights: indices.map(index => heights[index])};
33
+ };
@@ -0,0 +1,34 @@
1
+ const {half} = require('./../constants');
2
+
3
+ const {ceil} = Math;
4
+
5
+ /** Split block heights into consecutive windows of blocks
6
+
7
+ {
8
+ count: <Blocks Per Window Count Number>
9
+ heights: [<Block Height Number>]
10
+ }
11
+
12
+ @returns
13
+ {
14
+ windows: [[<Block Height Number>]]
15
+ }
16
+ */
17
+ module.exports = ({count, heights}) => {
18
+ const starts = [...Array(ceil(heights.length / count)).keys()];
19
+
20
+ const windows = starts.map(i => heights.slice(i * count, (i + 1) * count));
21
+
22
+ const [last] = windows.slice(-1);
23
+
24
+ // Exit early when the last window is long enough to stand on its own
25
+ if (windows.length < 2 || last.length >= count * half) {
26
+ return {windows};
27
+ }
28
+
29
+ const [beforeLast] = windows.slice(-2);
30
+
31
+ const joined = beforeLast.concat(last);
32
+
33
+ return {windows: windows.slice(Number(), -2).concat([joined])};
34
+ };
@@ -0,0 +1,129 @@
1
+ const asyncQueue = require('async/queue');
2
+
3
+ const {dayOfDate} = require('./../dates');
4
+ const {earliestSupportedDay} = require('./known');
5
+ const {fsMethods} = require('./../storage/constants');
6
+ const {getChainPrice} = require('./estimates');
7
+ const {knownPrices} = require('./known');
8
+
9
+ const supportedCurrency = 'BTC';
10
+ const supportedFiat = 'USD';
11
+
12
+ /** Make a method that gets historic USD prices of BTC from chain data
13
+
14
+ {
15
+ fs: {
16
+ getFile: <Read File Contents Function> (path, cbk) => {}
17
+ makeDirectory: <Make Directory Function> (path, cbk) => {}
18
+ renameFile: <Rename File Function> (from, to, cbk) => {}
19
+ writeFile: <Write File Contents Function> (path, contents, cbk) => {}
20
+ }
21
+ lnd: <Authenticated LND API Object>
22
+ [logger]: <Winston Logger Object>
23
+ path: <Prices File Path String>
24
+ }
25
+
26
+ @throws
27
+ <Error>
28
+
29
+ @returns
30
+ {
31
+ getChainRate: <Get Chain Rate Method> ({
32
+ currency: <Currency To Price String> // BTC
33
+ date: <Date To Price ISO 8601 Date String> // priced on its day in UTC
34
+ fiat: <Fiat Currency To Price In String> // USD
35
+ }, cbk) => (err, {
36
+ cents: <US Cents Per BTC on the Day of the Date Number>
37
+ })
38
+ }
39
+ */
40
+ module.exports = ({fs, lnd, logger, path}) => {
41
+ if (!fs || !fsMethods.every(method => typeof fs[method] === 'function')) {
42
+ throw new Error('ExpectedFileSystemMethodsToMakeChainRates');
43
+ }
44
+
45
+ if (!lnd) {
46
+ throw new Error('ExpectedAuthenticatedLndToMakeChainRates');
47
+ }
48
+
49
+ if (!path) {
50
+ throw new Error('ExpectedPricesFilePathToMakeChainRates');
51
+ }
52
+
53
+ const estimates = {provisional: {}, samples: Number()};
54
+
55
+ // A day the data cannot price is not derived again by this method
56
+ const unsupported = new Map();
57
+
58
+ // Lookups are serialized so that each estimate can guide the next one
59
+ const queue = asyncQueue(({date}, cbk) => {
60
+ // The shipped prices are read the first time a price is looked up
61
+ const shipped = (() => {
62
+ try {
63
+ return {rates: knownPrices({}).rates};
64
+ } catch (err) {
65
+ return {err};
66
+ }
67
+ })();
68
+
69
+ // Exit early when the shipped prices cannot be read
70
+ if (!!shipped.err) {
71
+ return cbk([503, 'FailedToReadKnownPricesToGetChainRate', {
72
+ err: shipped.err,
73
+ }]);
74
+ }
75
+
76
+ return getChainPrice({
77
+ date,
78
+ estimates,
79
+ fs,
80
+ lnd,
81
+ logger,
82
+ path,
83
+ unsupported,
84
+ known: shipped.rates,
85
+ },
86
+ cbk);
87
+ });
88
+
89
+ const getChainRate = ({currency, date, fiat}, cbk) => {
90
+ if (currency !== supportedCurrency) {
91
+ return cbk([400, 'UnsupportedCurrencyToGetChainRate']);
92
+ }
93
+
94
+ if (!date || typeof date !== 'string') {
95
+ return cbk([400, 'ExpectedDateToGetChainRate']);
96
+ }
97
+
98
+ if (fiat !== supportedFiat) {
99
+ return cbk([400, 'UnsupportedFiatToGetChainRate']);
100
+ }
101
+
102
+ // A date is priced on its day in UTC
103
+ const {day} = dayOfDate({date});
104
+
105
+ // Exit early when the date is not a readable date on a calendar day
106
+ if (!day) {
107
+ return cbk([400, 'ExpectedValidDateToGetChainRate']);
108
+ }
109
+
110
+ const earliest = earliestSupportedDay({}).day;
111
+
112
+ // Exit early when the date is before the days prices are derived for
113
+ if (day < earliest) {
114
+ return cbk([400, 'DateBeforeSupportedRangeToGetChainRate', {
115
+ earliest_day: earliest,
116
+ }]);
117
+ }
118
+
119
+ return queue.push({date: day}, (err, res) => {
120
+ if (!!err) {
121
+ return cbk(err);
122
+ }
123
+
124
+ return cbk(null, {cents: res.cents});
125
+ });
126
+ };
127
+
128
+ return {getChainRate};
129
+ };
@@ -0,0 +1,26 @@
1
+ {
2
+ "blockHeaderByteLength": 80,
3
+ "blockHeaderHexLength": 160,
4
+ "centsPerDollar": 100,
5
+ "confirmationsAfterDay": 6,
6
+ "defaultMaxPrice": 10000000,
7
+ "defaultMinPrice": 100,
8
+ "fetchLimit": 4,
9
+ "half": 0.5,
10
+ "kernelBins": 2,
11
+ "maxTokens": 10000000000000,
12
+ "minAliasMargin": 1,
13
+ "minTokens": 1000,
14
+ "msPerDay": 86400000,
15
+ "msPerSecond": 1000,
16
+ "noiseRadius": 12,
17
+ "roundTokensMultiple": 1000,
18
+ "sampleLogInterval": 10,
19
+ "spikeKernel": [0.3, 0.7, 1, 0.7, 0.3],
20
+ "tokensPerBigUnit": 100000000,
21
+ "unpricedErrors": [
22
+ "ExpectedPaymentOutputsInBlocksToGetPrice",
23
+ "FailedToDerivePriceFromOutputs",
24
+ "FailedToDerivePriceInWidenedWindow"
25
+ ]
26
+ }