@exodus/market-history 1.0.0 → 2.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ## 1.1.0 (2023-01-24)
7
+
8
+ ### Features
9
+
10
+ - add #/config per-key events ([#303](https://github.com/ExodusMovement/exodus-hydra/issues/303)) ([5391435](https://github.com/ExodusMovement/exodus-hydra/commit/539143598da10966ec18a93d9200542565682ecf))
11
+ - add `restricted-imports` eslint rule ([#719](https://github.com/ExodusMovement/exodus-hydra/issues/719)) ([175de9c](https://github.com/ExodusMovement/exodus-hydra/commit/175de9c19ec00e5a12441022c313837d58f38882))
12
+ - add market history monitor ([#206](https://github.com/ExodusMovement/exodus-hydra/issues/206)) ([121f026](https://github.com/ExodusMovement/exodus-hydra/commit/121f0268f2a3c5cb0a6a4b2788947714740a8c21))
13
+ - immediately show portfolio chart ([#1877](https://github.com/ExodusMovement/exodus-hydra/issues/1877)) ([ed6d9f6](https://github.com/ExodusMovement/exodus-hydra/commit/ed6d9f6e3daa3195fa6de1b91487315ac3282489))
14
+ - prevent monitors from running multiple times ([#497](https://github.com/ExodusMovement/exodus-hydra/issues/497)) ([81238ca](https://github.com/ExodusMovement/exodus-hydra/commit/81238cacaf893cb5019b5e02c481bcace1193f43))
15
+ - use real fusion sync in config ([#258](https://github.com/ExodusMovement/exodus-hydra/issues/258)) ([56f351c](https://github.com/ExodusMovement/exodus-hydra/commit/56f351cc18942499b47b5b3afecafaf181c2989b)), closes [#262](https://github.com/ExodusMovement/exodus-hydra/issues/262)
16
+
17
+ ### Bug Fixes
18
+
19
+ - do not load rates on initial currency load ([#1548](https://github.com/ExodusMovement/exodus-hydra/issues/1548)) ([2eb5713](https://github.com/ExodusMovement/exodus-hydra/commit/2eb5713fe53ce3816a0857e917c1de6df91a15b2))
20
+ - do sync if started on start ([#766](https://github.com/ExodusMovement/exodus-hydra/issues/766)) ([1f19664](https://github.com/ExodusMovement/exodus-hydra/commit/1f19664bb8839328e6bb6ec9c9e965f78d8ae86b))
21
+ - fetch market-history prices for all assets, not only solana ([#829](https://github.com/ExodusMovement/exodus-hydra/issues/829)) ([36aa1d5](https://github.com/ExodusMovement/exodus-hydra/commit/36aa1d5dffb3773722bd930202be7c521fb71579))
22
+ - missing await ([#748](https://github.com/ExodusMovement/exodus-hydra/issues/748)) ([3f6b527](https://github.com/ExodusMovement/exodus-hydra/commit/3f6b527e04b7224324e9a2462c361a5c79816146))
23
+ - start monitors on popup re-open ([#249](https://github.com/ExodusMovement/exodus-hydra/issues/249)) ([2b6d257](https://github.com/ExodusMovement/exodus-hydra/commit/2b6d257eb5fd625801a695f6579b114509869750))
24
+ - use new sync event for local-config change ([#302](https://github.com/ExodusMovement/exodus-hydra/issues/302)) ([2042801](https://github.com/ExodusMovement/exodus-hydra/commit/2042801293b30f3347949f5bd687f665c7fa4ee9))
25
+
26
+ ### Performance Improvements
27
+
28
+ - send less data from market prices module ([#1451](https://github.com/ExodusMovement/exodus-hydra/issues/1451)) ([eef3673](https://github.com/ExodusMovement/exodus-hydra/commit/eef3673c2dab332f4b1f071a001fccb0977b02c7))
package/README.md CHANGED
@@ -6,19 +6,24 @@ returns prices on interval `close`.
6
6
  ## Install
7
7
 
8
8
  ```sh
9
- yarn add @exodus/@exodus/market-history
9
+ yarn add @exodus/market-history
10
10
  ```
11
11
 
12
12
  ## Usage
13
13
 
14
14
  ```js
15
- import createMarketHistoryMonitor from '@exodus/market-history'
15
+ import createMarketHistoryMonitor from '@exodus/market-history/module'
16
16
 
17
- const marketHistoryMonitor = createMarketHistoryMonitor({
17
+ const marketHistoryMonitor = createMarketHistoryMonitor.factory({
18
18
  assetsModule,
19
19
  currencyAtom,
20
20
  storage: marketHistoryStorage,
21
- pricingServer,
21
+ pricingClient,
22
+ getCacheKey: ({ currency, assetName, granularity }) =>
23
+ `prices-${currency}-${assetName}-${granularity}`, // optional
24
+ remoteConfigRefreshIntervalAtom, // optional
25
+ clearCacheAtom, // optional. set value if you want to clear cache.
26
+ remoteConfigClearCacheAtom, // optional. If you want to clear cache set any value in this atom.
22
27
  })
23
28
 
24
29
  marketHistoryMonitor.on('market-history', ({ currency, granularity, prices }) => {
@@ -0,0 +1,296 @@
1
+ import { mapValues, SynchronizedTime } from '@exodus/basic-utils'
2
+ import ExodusModule from '@exodus/module'
3
+ import { fetchHistoricalPrices, fetchPricesInterval } from '@exodus/price-api'
4
+ import ms from 'ms'
5
+
6
+ import { getAssetFromTicker, getAssetTickers, parseGranularity } from '../utils'
7
+ import { createInMemoryAtom, difference } from '@exodus/atoms'
8
+
9
+ const CLEAR_MARKET_HISTORY_CACHE_KEY = 'clear-market-history-cache'
10
+ const CLEAR_MARKET_HISTORY_CACHE_FROM_REMOTE_CONFIG_KEY =
11
+ 'clear-market-history-cache-from-remote-config'
12
+ const MARKET_HISTORY_REFRESH_KEY = 'market-history-cache-refresh'
13
+
14
+ const transformPriceEntries = (entries = []) => {
15
+ const closePrices = entries.map((entry) => [entry[0], entry[1].close])
16
+ return Object.fromEntries(closePrices)
17
+ }
18
+
19
+ const transformPricesByAssetName = (pricesByAssetName) =>
20
+ mapValues(pricesByAssetName, (entries) => transformPriceEntries(entries))
21
+
22
+ // when provided cache version from local or remote config is different from stored on device we clear the cache and fetch new prices
23
+ const _invalidateStorageCache = async ({
24
+ storage,
25
+ clearRuntimeCache,
26
+ clearCacheVersion,
27
+ remoteConfigClearCacheVersion,
28
+ }) => {
29
+ const clearCacheVersionInStorage = await storage.get(CLEAR_MARKET_HISTORY_CACHE_KEY)
30
+ const clearCacheFromRemoteConfigVersionInStorage = await storage.get(
31
+ CLEAR_MARKET_HISTORY_CACHE_FROM_REMOTE_CONFIG_KEY
32
+ )
33
+ const localAreEqual = !clearCacheVersion || clearCacheVersionInStorage === clearCacheVersion
34
+ const remoteAreEqual =
35
+ !remoteConfigClearCacheVersion ||
36
+ clearCacheFromRemoteConfigVersionInStorage === remoteConfigClearCacheVersion
37
+
38
+ if (localAreEqual && remoteAreEqual) return // we already cleared cache
39
+
40
+ await storage.clear()
41
+ clearRuntimeCache()
42
+
43
+ return Promise.all([
44
+ storage
45
+ .set(CLEAR_MARKET_HISTORY_CACHE_KEY, clearCacheVersion || '')
46
+ .catch((e) => console.warn(e)),
47
+ storage
48
+ .set(CLEAR_MARKET_HISTORY_CACHE_FROM_REMOTE_CONFIG_KEY, remoteConfigClearCacheVersion || '')
49
+ .catch((e) => console.warn(e)),
50
+ ])
51
+ }
52
+
53
+ const getCacheKeyDefault = ({ currency, assetName, granularity }) =>
54
+ `prices-${currency}-${assetName}-${granularity}`
55
+
56
+ const MODULE_ID = 'marketHistory'
57
+
58
+ class MarketHistoryMonitor extends ExodusModule {
59
+ #pricingClient
60
+ #currencyAtom
61
+ #currency = null
62
+ #getCacheKey = getCacheKeyDefault
63
+ #remoteConfigRefreshIntervalAtom = null
64
+ #clearCacheAtom = null
65
+ #remoteConfigClearCacheAtom = null
66
+ #runtimeCache = new Map()
67
+
68
+ constructor({
69
+ assetsModule,
70
+ storage,
71
+ currencyAtom,
72
+ pricingClient,
73
+ getCacheKey = getCacheKeyDefault,
74
+ remoteConfigRefreshIntervalAtom = null,
75
+ clearCacheAtom = createInMemoryAtom({ defaultValue: null }),
76
+ remoteConfigClearCacheAtom = createInMemoryAtom({ defaultValue: null }),
77
+ }) {
78
+ super({ name: 'MarketHistoryMonitor' })
79
+
80
+ this.assetsModule = assetsModule
81
+ this.storage = storage
82
+ this.#pricingClient = pricingClient
83
+ this.#getCacheKey = getCacheKey
84
+ this.#remoteConfigRefreshIntervalAtom = remoteConfigRefreshIntervalAtom
85
+ this.#clearCacheAtom = clearCacheAtom
86
+ this.#remoteConfigClearCacheAtom = remoteConfigClearCacheAtom
87
+ this.#currencyAtom = currencyAtom
88
+ }
89
+
90
+ #started = false
91
+
92
+ #setCache = async ({ currency, granularity, pricesByAssetName }) => {
93
+ const changes = Object.keys(pricesByAssetName).reduce((acc, assetName) => {
94
+ const key = this.#getCacheKey({ currency, assetName, granularity })
95
+ const values = pricesByAssetName[assetName]
96
+ if (values.length > 0) {
97
+ acc[key] = values
98
+ this.#runtimeCache.set(key, values)
99
+ }
100
+ return acc
101
+ }, {})
102
+
103
+ await this.storage.batchSet(changes)
104
+ }
105
+
106
+ #getCache = async ({ currency, granularity, assetName }) => {
107
+ const key = this.#getCacheKey({ currency, assetName, granularity })
108
+ const cachedValue = this.#runtimeCache.get(key)
109
+ if (cachedValue) {
110
+ return cachedValue
111
+ }
112
+ const cache = (await this.storage.get(key)) || []
113
+ this.#runtimeCache.set(key, cache)
114
+
115
+ return cache
116
+ }
117
+
118
+ #getRuntimeCacheKey = ({ fiatTicker, granularity, assetTicker }) =>
119
+ this.#getCacheKey({
120
+ currency: fiatTicker,
121
+ granularity,
122
+ assetName: getAssetFromTicker(this.assetsModule.getAssets(), assetTicker).name,
123
+ })
124
+
125
+ #fetch = async ({ currency, granularity }) => {
126
+ const assets = this.assetsModule.getAssets()
127
+ const assetTickers = getAssetTickers(assets)
128
+
129
+ const cacheRefreshData = (await this.storage.get(MARKET_HISTORY_REFRESH_KEY)) || {}
130
+
131
+ const cacheRefreshKey = `${granularity}-${currency}`
132
+ const latestRefreshTimestamp = cacheRefreshData[cacheRefreshKey] || 0
133
+ const refreshIntervalMs = this.#remoteConfigRefreshIntervalAtom
134
+ ? await this.#remoteConfigRefreshIntervalAtom.get()
135
+ : null
136
+ const ignoreCache =
137
+ !!refreshIntervalMs &&
138
+ !Number.isNaN(refreshIntervalMs) &&
139
+ SynchronizedTime.now() - latestRefreshTimestamp > refreshIntervalMs
140
+ if (ignoreCache) {
141
+ cacheRefreshData[cacheRefreshKey] = SynchronizedTime.now()
142
+ await this.storage.set(MARKET_HISTORY_REFRESH_KEY, cacheRefreshData)
143
+ }
144
+
145
+ const { fetchedPricesMap } = await fetchHistoricalPrices({
146
+ api: (...args) => this.#pricingClient.historicalPrice(...args),
147
+ assetTickers,
148
+ fiatTicker: currency,
149
+ granularity,
150
+ ignoreInvalidSymbols: true,
151
+ getCurrentTime: SynchronizedTime.now,
152
+ getCacheFromStorage: async (ticker) =>
153
+ this.#getCache({
154
+ currency,
155
+ granularity,
156
+ assetName: getAssetFromTicker(assets, ticker)?.name,
157
+ }),
158
+ ignoreCache,
159
+ runtimeCache: this.#runtimeCache,
160
+ getRuntimeCacheKey: ({ fiatTicker, granularity, assetTicker }) =>
161
+ this.#getRuntimeCacheKey({ fiatTicker, granularity, assetTicker }),
162
+ })
163
+
164
+ const pricesByAssetName = mapValues(assets, (asset) => {
165
+ const assetPricesMap = fetchedPricesMap.get(asset.ticker)
166
+ return assetPricesMap ? [...assetPricesMap] : []
167
+ })
168
+
169
+ this.#setCache({ currency, granularity, pricesByAssetName })
170
+
171
+ return transformPricesByAssetName(pricesByAssetName)
172
+ }
173
+
174
+ update = async (granularity) => {
175
+ const parsedGranularity = parseGranularity(granularity)
176
+ const currency = this.#currency
177
+
178
+ try {
179
+ const prices = await this.#fetch({ currency, granularity })
180
+ this.emit('market-history', { currency, granularity: parsedGranularity, prices })
181
+ } catch (error) {
182
+ console.error(
183
+ 'MarketHistoryMonitor: Failed to fetch rates',
184
+ currency,
185
+ parsedGranularity,
186
+ error
187
+ )
188
+ }
189
+ }
190
+
191
+ #updateAll = () => {
192
+ return Promise.all([this.update('hour'), this.update('day')])
193
+ }
194
+
195
+ #syncGranularity = async (granularity) => {
196
+ const parsedGranularity = parseGranularity(granularity)
197
+ const currency = this.#currency
198
+ const assets = this.assetsModule.getAssets()
199
+
200
+ const cachedPricesByAssetName = Object.fromEntries(
201
+ Object.keys(assets).map((assetName) => {
202
+ const key = this.#getCacheKey({ currency, assetName, granularity })
203
+ return [assetName, this.#runtimeCache.get(key)]
204
+ })
205
+ )
206
+
207
+ const prices = transformPricesByAssetName(cachedPricesByAssetName)
208
+ this.emit('market-history', { currency, granularity: parsedGranularity, prices })
209
+ }
210
+
211
+ sync = async () => {
212
+ this.#syncGranularity('day')
213
+ this.#syncGranularity('hour')
214
+ }
215
+
216
+ invalidateStorage = async ({ remoteConfigClearCacheVersion }) => {
217
+ const clearCacheVersion = await this.#clearCacheAtom.get() // don't think we need to observe it considering this runs only on startup and set manually in config
218
+ return _invalidateStorageCache({
219
+ storage: this.storage,
220
+ clearRuntimeCache: () => {
221
+ this.#runtimeCache = new Map()
222
+ },
223
+ clearCacheVersion,
224
+ remoteConfigClearCacheVersion,
225
+ })
226
+ }
227
+
228
+ #listenRemoteConfigClearCacheVersionChanges = () => {
229
+ difference(this.#remoteConfigClearCacheAtom).observe(async ({ current, previous }) => {
230
+ if (previous !== undefined && current !== previous) {
231
+ await this.invalidateStorage({ remoteConfigClearCacheVersion: current })
232
+ await this.#updateAll()
233
+ }
234
+ })
235
+ }
236
+
237
+ #setupTimers = () => {
238
+ const jitter = ms('3s')
239
+ const getCurrentTime = SynchronizedTime.now
240
+ const updateDayPrices = this.update.bind(this, 'day')
241
+ const updateHourPrices = this.update.bind(this, 'hour')
242
+
243
+ fetchPricesInterval({
244
+ func: updateDayPrices,
245
+ granularity: 'day',
246
+ jitter,
247
+ getCurrentTime,
248
+ })
249
+
250
+ fetchPricesInterval({
251
+ func: updateHourPrices,
252
+ granularity: 'hour',
253
+ jitter,
254
+ getCurrentTime,
255
+ })
256
+ }
257
+
258
+ start = async () => {
259
+ if (this.#started) {
260
+ await this.sync()
261
+ return
262
+ }
263
+
264
+ this.#started = true
265
+
266
+ const remoteConfigClearCacheVersion = await this.#remoteConfigClearCacheAtom.get()
267
+ await this.invalidateStorage({ remoteConfigClearCacheVersion })
268
+ this.#listenRemoteConfigClearCacheVersionChanges()
269
+
270
+ await this.#currencyAtom.observe((currency) => {
271
+ if (this.#currency) {
272
+ this.#currency = currency
273
+ this.#updateAll()
274
+ } else {
275
+ this.#currency = currency
276
+ this.#setupTimers()
277
+ }
278
+ })
279
+ }
280
+ }
281
+
282
+ const createMarketHistoryMonitor = (args = {}) => new MarketHistoryMonitor({ ...args })
283
+
284
+ export default {
285
+ id: MODULE_ID,
286
+ factory: createMarketHistoryMonitor,
287
+ dependencies: [
288
+ 'pricingClient',
289
+ 'currencyAtom',
290
+ 'storage',
291
+ 'assetsModule',
292
+ 'clearCacheAtom',
293
+ 'remoteConfigClearCacheAtom',
294
+ 'remoteConfigRefreshIntervalAtom',
295
+ ],
296
+ }
package/package.json CHANGED
@@ -1,30 +1,37 @@
1
1
  {
2
2
  "name": "@exodus/market-history",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "module to fetch historical prices for assets",
5
5
  "author": "Exodus Movement Inc",
6
6
  "license": "UNLICENSED",
7
7
  "scripts": {
8
- "lint": "eslint .",
8
+ "lint": "eslint . --ignore-path ../../.gitignore",
9
9
  "lint:fix": "yarn lint --fix",
10
10
  "test": "jest"
11
11
  },
12
12
  "files": [
13
- "index.js",
14
- "utils.js"
13
+ "module",
14
+ "utils.js",
15
+ "CHANGELOG.md",
16
+ "README.md",
17
+ "!**/__tests__"
15
18
  ],
16
19
  "dependencies": {
17
20
  "@exodus/basic-utils": "^1.0.0",
18
- "@exodus/module": "^1.0.0",
19
- "@exodus/price-api": "^2.0.11",
21
+ "@exodus/module": "^1.1.0",
22
+ "@exodus/price-api": "^3.2.1",
20
23
  "ms": "^0.7.1"
21
24
  },
22
25
  "devDependencies": {
23
26
  "@exodus/assets": "^8.0.72",
24
- "@exodus/atoms": "^1.0.0",
25
- "@exodus/storage-memory": "^1.0.0"
27
+ "@exodus/atoms": "^2.3.0",
28
+ "@exodus/storage-memory": "^1.0.0",
29
+ "eslint": "^8.33.0",
30
+ "events": "^3.3.0",
31
+ "jest": "^29.1.2"
26
32
  },
27
33
  "peerDependencies": {
28
34
  "debug": ">= 3.0.0"
29
- }
30
- }
35
+ },
36
+ "gitHead": "37c209ea7fe3d24bc78fbb530f738a8ff3a87723"
37
+ }
package/utils.js CHANGED
@@ -3,8 +3,17 @@ export const getAssetTickers = (assets) =>
3
3
  .map((asset) => asset.ticker)
4
4
  .sort()
5
5
 
6
- export const getAssetFromTicker = (assets, ticker) =>
7
- Object.values(assets).find((asset) => asset.ticker === ticker)
6
+ let assetFromTickerCache = {}
7
+ export const getAssetFromTicker = (assets, ticker) => {
8
+ if (assetFromTickerCache[ticker]) return assetFromTickerCache[ticker]
9
+ return Object.values(assets).find((asset) => {
10
+ if (asset.ticker === ticker) {
11
+ assetFromTickerCache[ticker] = asset
12
+ return true
13
+ }
14
+ return false
15
+ })
16
+ }
8
17
 
9
18
  export const parseGranularity = (granularity) => {
10
19
  switch (granularity) {
package/index.js DELETED
@@ -1,171 +0,0 @@
1
- import { mapValues, SynchronizedTime } from '@exodus/basic-utils'
2
- import ExodusModule from '@exodus/module'
3
- import { fetchHistoricalPrices, fetchPricesInterval } from '@exodus/price-api'
4
- import ms from 'ms'
5
-
6
- import { getAssetFromTicker, getAssetTickers, parseGranularity } from './utils'
7
-
8
- const MARKET_HISTORY_CACHE_VERSION = 'v1'
9
-
10
- const transformPriceEntries = (entries = []) => {
11
- const closePrices = entries.map((entry) => [entry[0], entry[1].close])
12
- return Object.fromEntries(closePrices)
13
- }
14
-
15
- const transformPricesByAssetName = (pricesByAssetName) =>
16
- mapValues(pricesByAssetName, (entries) => transformPriceEntries(entries))
17
-
18
- class MarketHistoryMonitor extends ExodusModule {
19
- #pricingServer
20
- #currencyAtom
21
- #previousCurrency = null
22
-
23
- constructor({ assetsModule, storage, currencyAtom, pricingServer }) {
24
- super({ name: 'MarketHistoryMonitor' })
25
-
26
- this.assetsModule = assetsModule
27
- this.storage = storage
28
- this.#pricingServer = pricingServer
29
-
30
- this.#currencyAtom = currencyAtom
31
- this.#currencyAtom.observe((value) => value && this.#onCurrencyChange(value))
32
- }
33
-
34
- #started = false
35
-
36
- #onCurrencyChange = async (value) => {
37
- if (this.#previousCurrency === null) {
38
- this.#previousCurrency = value
39
- return
40
- }
41
-
42
- this.#update('day')
43
- this.#update('hour')
44
-
45
- this.#previousCurrency = value
46
- }
47
-
48
- #getCacheKey = ({ currency, assetName, granularity }) => {
49
- return `${currency}-${assetName}-${granularity}-${MARKET_HISTORY_CACHE_VERSION}`
50
- }
51
-
52
- #setCache = async ({ currency, granularity, pricesByAssetName }) => {
53
- const assets = this.assetsModule.getAssets()
54
-
55
- const promises = Object.values(assets).map((asset) => {
56
- const key = this.#getCacheKey({ currency, assetName: asset.name, granularity })
57
- const values = pricesByAssetName[asset.name]
58
- return this.storage.set(key, values)
59
- })
60
-
61
- await Promise.all(promises)
62
- }
63
-
64
- #getCache = async ({ currency, granularity, assetName }) => {
65
- const key = this.#getCacheKey({ currency, assetName, granularity })
66
- const cache = await this.storage.get(key)
67
- return cache || []
68
- }
69
-
70
- #fetch = async ({ currency, granularity }) => {
71
- const assets = this.assetsModule.getAssets()
72
- const assetTickers = getAssetTickers(assets)
73
-
74
- const pricesMap = await fetchHistoricalPrices({
75
- api: (...args) => this.#pricingServer.historicalPrice(...args),
76
- assetTickers,
77
- fiatTicker: currency,
78
- granularity,
79
- ignoreInvalidSymbols: true,
80
- getCurrentTime: SynchronizedTime.now,
81
- getCacheFromStorage: async (ticker) =>
82
- this.#getCache({
83
- currency,
84
- granularity,
85
- assetName: getAssetFromTicker(assets, ticker)?.name,
86
- }),
87
- })
88
-
89
- const pricesByAssetName = mapValues(assets, (asset) => {
90
- const assetPricesMap = pricesMap.get(asset.ticker)
91
- return assetPricesMap ? [...assetPricesMap] : []
92
- })
93
-
94
- this.#setCache({ currency, granularity, pricesByAssetName })
95
-
96
- return transformPricesByAssetName(pricesByAssetName)
97
- }
98
-
99
- #getCurrency = async () => {
100
- return this.#currencyAtom.get()
101
- }
102
-
103
- #update = async (granularity) => {
104
- const parsedGranularity = parseGranularity(granularity)
105
- const currency = await this.#getCurrency()
106
-
107
- try {
108
- const prices = await this.#fetch({ currency, granularity })
109
- this.emit('market-history', { currency, granularity: parsedGranularity, prices })
110
- } catch (error) {
111
- console.error(
112
- 'MarketHistoryMonitor: Failed to fetch rates',
113
- currency,
114
- parsedGranularity,
115
- error
116
- )
117
- }
118
- }
119
-
120
- #syncGranularity = async (granularity) => {
121
- const parsedGranularity = parseGranularity(granularity)
122
- const currency = await this.#getCurrency()
123
- const assets = this.assetsModule.getAssets()
124
-
125
- const keys = Object.keys(assets).map((assetName) =>
126
- this.#getCacheKey({ currency, assetName, granularity })
127
- )
128
- const cachedPrices = await this.storage.batchGet(keys)
129
-
130
- const cachedPricesByAssetName = Object.fromEntries(
131
- Object.keys(assets).map((assetName, index) => [assetName, cachedPrices[index]])
132
- )
133
-
134
- const prices = transformPricesByAssetName(cachedPricesByAssetName)
135
- this.emit('market-history', { currency, granularity: parsedGranularity, prices })
136
- }
137
-
138
- sync = async () => {
139
- this.#syncGranularity('day')
140
- this.#syncGranularity('hour')
141
- }
142
-
143
- start = async () => {
144
- if (this.#started) return
145
-
146
- this.#started = true
147
-
148
- const jitter = ms('3s')
149
- const getCurrentTime = SynchronizedTime.now
150
- const updateDayPrices = this.#update.bind(this, 'day')
151
- const updateHourPrices = this.#update.bind(this, 'hour')
152
-
153
- fetchPricesInterval({
154
- func: updateDayPrices,
155
- granularity: 'day',
156
- jitter,
157
- getCurrentTime,
158
- })
159
-
160
- fetchPricesInterval({
161
- func: updateHourPrices,
162
- granularity: 'hour',
163
- jitter,
164
- getCurrentTime,
165
- })
166
- }
167
- }
168
-
169
- const createMarketHistoryMonitor = (args = {}) => new MarketHistoryMonitor({ ...args })
170
-
171
- export default createMarketHistoryMonitor