@talismn/balances-react 0.0.0-pr563-20230221193038 → 0.0.0-pr563-20230222051735

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 CHANGED
@@ -1,12 +1,12 @@
1
1
  # @talismn/balances-react
2
2
 
3
- ## 0.0.0-pr563-20230221193038
3
+ ## 0.0.0-pr563-20230222051735
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - 536eddb: fix: ported useDbCache related perf fixes to @talismn/balances-react
8
- - Updated dependencies [536eddb]
9
- - @talismn/balances@0.0.0-pr563-20230221193038
7
+ - 536eddb5: fix: ported useDbCache related perf fixes to @talismn/balances-react
8
+ - Updated dependencies [536eddb5]
9
+ - @talismn/balances@0.0.0-pr563-20230222051735
10
10
 
11
11
  ## 0.3.3
12
12
 
@@ -15,8 +15,8 @@ type DbCache = {
15
15
  evmNetworksWithoutTestnetsMap: Record<EvmNetworkId, EvmNetwork | CustomEvmNetwork>;
16
16
  tokensWithTestnetsMap: Record<TokenId, Token>;
17
17
  tokensWithoutTestnetsMap: Record<TokenId, Token>;
18
- balances: BalanceJson[];
19
18
  tokenRatesMap: Record<TokenId, TokenRates>;
19
+ balances: BalanceJson[];
20
20
  };
21
21
  type DbCacheProviderProps = {
22
22
  useTestnets?: boolean;
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- export type DbEntityType = "chains" | "evmNetworks" | "tokens" | "balances";
2
+ export type DbEntityType = "chains" | "evmNetworks" | "tokens" | "tokenRates" | "balances";
3
3
  export declare const SubscriptionsProvider: import("react").FC<{
4
4
  children?: import("react").ReactNode;
5
5
  }>, useSubscriptions: () => ((callback?: ((val: unknown) => void) | undefined) => import("../util").Unsubscribe)[];
@@ -1,3 +1,3 @@
1
- import { IToken, TokenId } from "@talismn/chaindata-provider";
2
- import { TokenRatesList } from "@talismn/token-rates";
3
- export declare function useTokenRates(tokens?: Record<TokenId, IToken>): TokenRatesList;
1
+ import { TokenId } from "@talismn/chaindata-provider";
2
+ export declare function useTokenRates(): Record<string, import("@talismn/token-rates").TokenRates>;
3
+ export declare function useTokenRate(tokenId?: TokenId): import("@talismn/token-rates").TokenRates | undefined;
@@ -3,11 +3,12 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var react = require('react');
6
+ var jsxRuntime = require('react/jsx-runtime');
6
7
  var balances = require('@talismn/balances');
8
+ var tokenRates = require('@talismn/token-rates');
7
9
  var dexieReactHooks = require('dexie-react-hooks');
8
10
  var reactUse = require('react-use');
9
11
  var chaindataProviderExtension = require('@talismn/chaindata-provider-extension');
10
- var tokenRates = require('@talismn/token-rates');
11
12
  var anylogger = require('anylogger');
12
13
  var rxjs = require('rxjs');
13
14
  var chainConnector = require('@talismn/chain-connector');
@@ -17,11 +18,6 @@ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e };
17
18
 
18
19
  var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
19
20
 
20
- /**
21
- * This utility generates a context provider from a react hook passed as argument
22
- *
23
- * @returns an array containing the provider and the consumer hook
24
- */
25
21
  const provideContext = useProviderContext => {
26
22
  // automatic typing based on our hook's return type
27
23
 
@@ -33,9 +29,10 @@ const provideContext = useProviderContext => {
33
29
  ...props
34
30
  }) => {
35
31
  const ctx = useProviderContext(props);
36
- return /*#__PURE__*/React.createElement(Context.Provider, {
37
- value: ctx
38
- }, children);
32
+ return /*#__PURE__*/jsxRuntime.jsx(Context.Provider, {
33
+ value: ctx,
34
+ children: children
35
+ });
39
36
  };
40
37
  const useProvidedContext = () => {
41
38
  const context = react.useContext(Context);
@@ -65,25 +62,6 @@ function useChaindataProvider(options = {}) {
65
62
  }
66
63
  const [ChaindataProvider, useChaindata] = provideContext(useChaindataProvider);
67
64
 
68
- function useTokenRates(tokens) {
69
- const generation = react.useRef(0);
70
- const [tokenRates$1, setTokenRates] = react.useState({});
71
- react.useEffect(() => {
72
- if (!tokens) return;
73
- if (Object.keys(tokens).length < 1) return;
74
-
75
- // when we make a new request, we want to ignore any old requests which haven't yet completed
76
- // otherwise we risk replacing the most recent data with older data
77
- generation.current = (generation.current + 1) % Number.MAX_SAFE_INTEGER;
78
- const thisGeneration = generation.current;
79
- tokenRates.fetchTokenRates(tokens).then(tokenRates => {
80
- if (thisGeneration !== generation.current) return;
81
- setTokenRates(tokenRates);
82
- });
83
- }, [tokens]);
84
- return tokenRates$1;
85
- }
86
-
87
65
  const filterNoTestnet = ({
88
66
  isTestnet
89
67
  }) => isTestnet === false;
@@ -100,13 +78,11 @@ const DEFAULT_VALUE = {
100
78
  evmNetworksWithoutTestnetsMap: {},
101
79
  tokensWithTestnetsMap: {},
102
80
  tokensWithoutTestnetsMap: {},
103
- balances: [],
104
- tokenRatesMap: {}
81
+ tokenRatesMap: {},
82
+ balances: []
105
83
  };
106
- const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, tokenRates) => {
107
- if (!chainsMap || !evmNetworksMap || !tokensMap || !allBalances
108
- // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
109
- /* || !tokenRates */) return DEFAULT_VALUE;
84
+ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, tokenRates, allBalances) => {
85
+ if (!chainsMap || !evmNetworksMap || !tokensMap || !tokenRates || !allBalances) return DEFAULT_VALUE;
110
86
 
111
87
  // BEGIN: temp hack to indicate that
112
88
  // - EVM GLMR is a mirror of substrate GLMR
@@ -134,6 +110,10 @@ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, t
134
110
  const tokensWithoutTestnets = tokensWithTestnets.filter(filterNoTestnet).filter(token => token.chain && chainsWithoutTestnetsMap[token.chain.id] || token.evmNetwork && evmNetworksWithoutTestnetsMap[token.evmNetwork.id]);
135
111
  const tokensWithTestnetsMap = Object.fromEntries(tokensWithTestnets.map(token => [token.id, token]));
136
112
  const tokensWithoutTestnetsMap = Object.fromEntries(tokensWithoutTestnets.map(token => [token.id, token]));
113
+ const tokenRatesMap = Object.fromEntries(tokenRates.map(({
114
+ tokenId,
115
+ rates
116
+ }) => [tokenId, rates]));
137
117
 
138
118
  // return only balances for which we have a token
139
119
  const balances = allBalances.filter(b => tokensWithTestnetsMap[b.tokenId]);
@@ -150,46 +130,41 @@ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, t
150
130
  evmNetworksWithoutTestnetsMap,
151
131
  tokensWithTestnetsMap,
152
132
  tokensWithoutTestnetsMap,
153
- balances,
154
- tokenRatesMap: tokenRates ?? {}
133
+ tokenRatesMap,
134
+ balances
155
135
  };
156
136
  };
157
137
  const useDbCacheProvider = ({
158
138
  useTestnets = false
159
139
  }) => {
160
140
  const chaindataProvider = useChaindata();
161
- const chainList = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.chains(), [chaindataProvider]);
162
- const evmNetworkList = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.evmNetworks(), [chaindataProvider]);
163
- const tokenList = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.tokens(), [chaindataProvider]);
141
+ const chainList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.chains(), [chaindataProvider]);
142
+ const evmNetworkList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.evmNetworks(), [chaindataProvider]);
143
+ const tokenList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.tokens(), [chaindataProvider]);
144
+ const tokenRates$1 = dexieReactHooks.useLiveQuery(() => tokenRates.db.tokenRates.toArray(), []);
164
145
  const rawBalances = dexieReactHooks.useLiveQuery(() => balances.db.balances.toArray(), []);
165
-
166
- // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
167
- const tokenRates = useTokenRates();
168
146
  const [dbData, setDbData] = react.useState(DEFAULT_VALUE);
169
147
 
170
148
  // debounce every 500ms to prevent hammering UI with updates
171
149
  reactUse.useDebounce(() => {
172
- setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, rawBalances, tokenRates));
173
- }, 500, [chainList, evmNetworkList, tokenList, rawBalances, tokenRates, useTestnets]);
150
+ setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates$1, rawBalances));
151
+ }, 500, [chainList, evmNetworkList, tokenList, rawBalances, tokenRates$1, useTestnets]);
174
152
  const refInitialized = react.useRef(false);
175
153
 
176
154
  // force an update as soon as all datasources are fetched, so UI can display data ASAP
177
155
  react.useEffect(() => {
178
- if (!refInitialized.current && chainList && evmNetworkList && tokenList && rawBalances
179
- // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
180
- // && tokenRates
181
- ) {
182
- setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, rawBalances, tokenRates));
156
+ if (!refInitialized.current && chainList && evmNetworkList && tokenList && tokenRates$1 && rawBalances) {
157
+ setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates$1, rawBalances));
183
158
  refInitialized.current = true;
184
159
  }
185
- }, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates, useTestnets]);
160
+ }, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates$1, useTestnets]);
186
161
  return dbData;
187
162
  };
188
163
  const [DbCacheProvider, useDbCache] = provideContext(useDbCacheProvider);
189
164
 
190
165
  var packageJson = {
191
166
  name: "@talismn/balances-react",
192
- version: "0.0.0-pr563-20230221193038",
167
+ version: "0.0.0-pr563-20230222051735",
193
168
  author: "Talisman",
194
169
  homepage: "https://talisman.xyz",
195
170
  license: "UNLICENSED",
@@ -222,7 +197,7 @@ var packageJson = {
222
197
  "@talismn/chaindata-provider-extension": "workspace:^",
223
198
  "@talismn/token-rates": "workspace:^",
224
199
  anylogger: "^1.0.11",
225
- dexie: "^3.2.3",
200
+ dexie: "^3.2.2",
226
201
  "dexie-react-hooks": "^1.1.1",
227
202
  "react-use": "^17.4.0",
228
203
  rxjs: "^7.8.0"
@@ -319,14 +294,14 @@ function useChainConnectorsProvider(options) {
319
294
  }
320
295
  const [ChainConnectorsProvider, useChainConnectors] = provideContext(useChainConnectorsProvider);
321
296
 
322
- const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeBalances()];
297
+ const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeTokenRates(), useSubscribeBalances()];
323
298
  const [SubscriptionsProvider, useSubscriptions] = provideContext(useSubscriptionsProvider);
324
299
 
325
300
  /**
326
301
  * This hook is responsible for fetching the data used for balances and inserting it into the db.
327
302
  */
328
303
  const useDbCacheSubscription = subscribeTo => {
329
- const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeBalances] = useSubscriptions();
304
+ const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances] = useSubscriptions();
330
305
  react.useEffect(() => {
331
306
  switch (subscribeTo) {
332
307
  case "chains":
@@ -335,10 +310,12 @@ const useDbCacheSubscription = subscribeTo => {
335
310
  return subscribeHydrateEvmNetworks();
336
311
  case "tokens":
337
312
  return subscribeHydrateTokens();
313
+ case "tokenRates":
314
+ return subscribeTokenRates();
338
315
  case "balances":
339
316
  return subscribeBalances();
340
317
  }
341
- }, [subscribeBalances, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTo]);
318
+ }, [subscribeTo, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances]);
342
319
  };
343
320
  function useSubscribeChaindataHydrate(type) {
344
321
  const chaindata =
@@ -370,12 +347,55 @@ function useSubscribeChaindataHydrate(type) {
370
347
  const subscribe = useMulticastSubscription(createSubscription);
371
348
  return subscribe;
372
349
  }
350
+ function useSubscribeTokenRates() {
351
+ const chaindataProvider = useChaindata();
352
+ const tokens = dexieReactHooks.useLiveQuery(() => chaindataProvider?.tokens(), [chaindataProvider]);
353
+ const generationRef = react.useRef(0);
354
+ const createSubscription = react.useCallback(() => {
355
+ if (!chaindataProvider) return;
356
+ if (!tokens) return;
357
+ if (Object.keys(tokens).length < 1) return;
358
+
359
+ // when we make a new request, we want to ignore any old requests which haven't yet completed
360
+ // otherwise we risk replacing the most recent data with older data
361
+ const generation = (generationRef.current + 1) % Number.MAX_SAFE_INTEGER;
362
+ generationRef.current = generation;
363
+ let active = true;
364
+ const REFRESH_INTERVAL = 300_000; // 300_000ms = 5 minutes
365
+ const RETRY_INTERVAL = 5_000; // 5_000ms = 5 seconds
366
+
367
+ const hydrate = async () => {
368
+ if (!active) return;
369
+ if (generationRef.current !== generation) return;
370
+ try {
371
+ const tokenRates$1 = await tokenRates.fetchTokenRates(tokens);
372
+ if (!active) return;
373
+ if (generationRef.current !== generation) return;
374
+ const putTokenRates = Object.entries(tokenRates$1).map(([tokenId, rates]) => ({
375
+ tokenId,
376
+ rates
377
+ }));
378
+ tokenRates.db.transaction("rw", tokenRates.db.tokenRates, async () => await tokenRates.db.tokenRates.bulkPut(putTokenRates));
379
+ setTimeout(hydrate, REFRESH_INTERVAL);
380
+ } catch (error) {
381
+ log.error(`Failed to fetch tokenRates, retrying in ${Math.round(RETRY_INTERVAL / 1000)} seconds`, error);
382
+ setTimeout(hydrate, RETRY_INTERVAL);
383
+ }
384
+ };
385
+ hydrate();
386
+ return () => {
387
+ active = false;
388
+ };
389
+ }, [chaindataProvider, tokens]);
390
+ const subscribe = useMulticastSubscription(createSubscription);
391
+ return subscribe;
392
+ }
373
393
  function useSubscribeBalances() {
374
394
  const balanceModules = useBalanceModules();
375
395
  const chaindataProvider = useChaindata();
376
396
  const chainConnectors = useChainConnectors();
377
397
  const [allAddresses] = useAllAddresses();
378
- const tokens = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.tokens(), [chaindataProvider]);
398
+ const tokens = dexieReactHooks.useLiveQuery(() => chaindataProvider?.tokens(), [chaindataProvider]);
379
399
  const tokenIds = react.useMemo(() => Object.values(tokens ?? {}).map(({
380
400
  id
381
401
  }) => id), [tokens]);
@@ -464,6 +484,19 @@ function useEvmNetwork(evmNetworkId, withTestnets) {
464
484
  return evmNetworkId ? evmNetworks[evmNetworkId] : undefined;
465
485
  }
466
486
 
487
+ function useTokenRates() {
488
+ // keep db data up to date
489
+ useDbCacheSubscription("tokenRates");
490
+ const {
491
+ tokenRatesMap
492
+ } = useDbCache();
493
+ return tokenRatesMap;
494
+ }
495
+ function useTokenRate(tokenId) {
496
+ const tokenRates = useTokenRates();
497
+ return tokenId ? tokenRates[tokenId] : undefined;
498
+ }
499
+
467
500
  function useTokens(withTestnets) {
468
501
  // keep db data up to date
469
502
  useDbCacheSubscription("tokens");
@@ -482,12 +515,7 @@ const useBalancesHydrate = withTestnets => {
482
515
  const chains = useChains(withTestnets);
483
516
  const evmNetworks = useEvmNetworks(withTestnets);
484
517
  const tokens = useTokens(withTestnets);
485
-
486
- // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
487
- // useDbCacheSubscription("tokenRates")
488
- const {
489
- tokenRatesMap: tokenRates
490
- } = useDbCache();
518
+ const tokenRates = useTokenRates();
491
519
  return react.useMemo(() => ({
492
520
  chains,
493
521
  evmNetworks,
@@ -531,15 +559,23 @@ const BalancesProvider = ({
531
559
  onfinalityApiKey,
532
560
  useTestnets,
533
561
  children
534
- }) => /*#__PURE__*/React.createElement(ChaindataProvider, {
535
- onfinalityApiKey: onfinalityApiKey
536
- }, /*#__PURE__*/React.createElement(ChainConnectorsProvider, {
537
- onfinalityApiKey: onfinalityApiKey
538
- }, /*#__PURE__*/React.createElement(AllAddressesProvider, null, /*#__PURE__*/React.createElement(BalanceModulesProvider, {
539
- balanceModules: balanceModules
540
- }, /*#__PURE__*/React.createElement(DbCacheProvider, {
541
- useTestnets: useTestnets
542
- }, /*#__PURE__*/React.createElement(SubscriptionsProvider, null, children))))));
562
+ }) => /*#__PURE__*/jsxRuntime.jsx(ChaindataProvider, {
563
+ onfinalityApiKey: onfinalityApiKey,
564
+ children: /*#__PURE__*/jsxRuntime.jsx(ChainConnectorsProvider, {
565
+ onfinalityApiKey: onfinalityApiKey,
566
+ children: /*#__PURE__*/jsxRuntime.jsx(AllAddressesProvider, {
567
+ children: /*#__PURE__*/jsxRuntime.jsx(BalanceModulesProvider, {
568
+ balanceModules: balanceModules,
569
+ children: /*#__PURE__*/jsxRuntime.jsx(DbCacheProvider, {
570
+ useTestnets: useTestnets,
571
+ children: /*#__PURE__*/jsxRuntime.jsx(SubscriptionsProvider, {
572
+ children: children
573
+ })
574
+ })
575
+ })
576
+ })
577
+ })
578
+ });
543
579
 
544
580
  exports.AllAddressesProvider = AllAddressesProvider;
545
581
  exports.BalanceModulesProvider = BalanceModulesProvider;
@@ -565,5 +601,6 @@ exports.useEvmNetworks = useEvmNetworks;
565
601
  exports.useMulticastSubscription = useMulticastSubscription;
566
602
  exports.useSubscriptions = useSubscriptions;
567
603
  exports.useToken = useToken;
604
+ exports.useTokenRate = useTokenRate;
568
605
  exports.useTokenRates = useTokenRates;
569
606
  exports.useTokens = useTokens;
@@ -3,11 +3,12 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var react = require('react');
6
+ var jsxRuntime = require('react/jsx-runtime');
6
7
  var balances = require('@talismn/balances');
8
+ var tokenRates = require('@talismn/token-rates');
7
9
  var dexieReactHooks = require('dexie-react-hooks');
8
10
  var reactUse = require('react-use');
9
11
  var chaindataProviderExtension = require('@talismn/chaindata-provider-extension');
10
- var tokenRates = require('@talismn/token-rates');
11
12
  var anylogger = require('anylogger');
12
13
  var rxjs = require('rxjs');
13
14
  var chainConnector = require('@talismn/chain-connector');
@@ -17,11 +18,6 @@ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e };
17
18
 
18
19
  var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
19
20
 
20
- /**
21
- * This utility generates a context provider from a react hook passed as argument
22
- *
23
- * @returns an array containing the provider and the consumer hook
24
- */
25
21
  const provideContext = useProviderContext => {
26
22
  // automatic typing based on our hook's return type
27
23
 
@@ -33,9 +29,10 @@ const provideContext = useProviderContext => {
33
29
  ...props
34
30
  }) => {
35
31
  const ctx = useProviderContext(props);
36
- return /*#__PURE__*/React.createElement(Context.Provider, {
37
- value: ctx
38
- }, children);
32
+ return /*#__PURE__*/jsxRuntime.jsx(Context.Provider, {
33
+ value: ctx,
34
+ children: children
35
+ });
39
36
  };
40
37
  const useProvidedContext = () => {
41
38
  const context = react.useContext(Context);
@@ -65,25 +62,6 @@ function useChaindataProvider(options = {}) {
65
62
  }
66
63
  const [ChaindataProvider, useChaindata] = provideContext(useChaindataProvider);
67
64
 
68
- function useTokenRates(tokens) {
69
- const generation = react.useRef(0);
70
- const [tokenRates$1, setTokenRates] = react.useState({});
71
- react.useEffect(() => {
72
- if (!tokens) return;
73
- if (Object.keys(tokens).length < 1) return;
74
-
75
- // when we make a new request, we want to ignore any old requests which haven't yet completed
76
- // otherwise we risk replacing the most recent data with older data
77
- generation.current = (generation.current + 1) % Number.MAX_SAFE_INTEGER;
78
- const thisGeneration = generation.current;
79
- tokenRates.fetchTokenRates(tokens).then(tokenRates => {
80
- if (thisGeneration !== generation.current) return;
81
- setTokenRates(tokenRates);
82
- });
83
- }, [tokens]);
84
- return tokenRates$1;
85
- }
86
-
87
65
  const filterNoTestnet = ({
88
66
  isTestnet
89
67
  }) => isTestnet === false;
@@ -100,13 +78,11 @@ const DEFAULT_VALUE = {
100
78
  evmNetworksWithoutTestnetsMap: {},
101
79
  tokensWithTestnetsMap: {},
102
80
  tokensWithoutTestnetsMap: {},
103
- balances: [],
104
- tokenRatesMap: {}
81
+ tokenRatesMap: {},
82
+ balances: []
105
83
  };
106
- const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, tokenRates) => {
107
- if (!chainsMap || !evmNetworksMap || !tokensMap || !allBalances
108
- // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
109
- /* || !tokenRates */) return DEFAULT_VALUE;
84
+ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, tokenRates, allBalances) => {
85
+ if (!chainsMap || !evmNetworksMap || !tokensMap || !tokenRates || !allBalances) return DEFAULT_VALUE;
110
86
 
111
87
  // BEGIN: temp hack to indicate that
112
88
  // - EVM GLMR is a mirror of substrate GLMR
@@ -134,6 +110,10 @@ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, t
134
110
  const tokensWithoutTestnets = tokensWithTestnets.filter(filterNoTestnet).filter(token => token.chain && chainsWithoutTestnetsMap[token.chain.id] || token.evmNetwork && evmNetworksWithoutTestnetsMap[token.evmNetwork.id]);
135
111
  const tokensWithTestnetsMap = Object.fromEntries(tokensWithTestnets.map(token => [token.id, token]));
136
112
  const tokensWithoutTestnetsMap = Object.fromEntries(tokensWithoutTestnets.map(token => [token.id, token]));
113
+ const tokenRatesMap = Object.fromEntries(tokenRates.map(({
114
+ tokenId,
115
+ rates
116
+ }) => [tokenId, rates]));
137
117
 
138
118
  // return only balances for which we have a token
139
119
  const balances = allBalances.filter(b => tokensWithTestnetsMap[b.tokenId]);
@@ -150,46 +130,41 @@ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, t
150
130
  evmNetworksWithoutTestnetsMap,
151
131
  tokensWithTestnetsMap,
152
132
  tokensWithoutTestnetsMap,
153
- balances,
154
- tokenRatesMap: tokenRates ?? {}
133
+ tokenRatesMap,
134
+ balances
155
135
  };
156
136
  };
157
137
  const useDbCacheProvider = ({
158
138
  useTestnets = false
159
139
  }) => {
160
140
  const chaindataProvider = useChaindata();
161
- const chainList = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.chains(), [chaindataProvider]);
162
- const evmNetworkList = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.evmNetworks(), [chaindataProvider]);
163
- const tokenList = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.tokens(), [chaindataProvider]);
141
+ const chainList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.chains(), [chaindataProvider]);
142
+ const evmNetworkList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.evmNetworks(), [chaindataProvider]);
143
+ const tokenList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.tokens(), [chaindataProvider]);
144
+ const tokenRates$1 = dexieReactHooks.useLiveQuery(() => tokenRates.db.tokenRates.toArray(), []);
164
145
  const rawBalances = dexieReactHooks.useLiveQuery(() => balances.db.balances.toArray(), []);
165
-
166
- // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
167
- const tokenRates = useTokenRates();
168
146
  const [dbData, setDbData] = react.useState(DEFAULT_VALUE);
169
147
 
170
148
  // debounce every 500ms to prevent hammering UI with updates
171
149
  reactUse.useDebounce(() => {
172
- setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, rawBalances, tokenRates));
173
- }, 500, [chainList, evmNetworkList, tokenList, rawBalances, tokenRates, useTestnets]);
150
+ setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates$1, rawBalances));
151
+ }, 500, [chainList, evmNetworkList, tokenList, rawBalances, tokenRates$1, useTestnets]);
174
152
  const refInitialized = react.useRef(false);
175
153
 
176
154
  // force an update as soon as all datasources are fetched, so UI can display data ASAP
177
155
  react.useEffect(() => {
178
- if (!refInitialized.current && chainList && evmNetworkList && tokenList && rawBalances
179
- // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
180
- // && tokenRates
181
- ) {
182
- setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, rawBalances, tokenRates));
156
+ if (!refInitialized.current && chainList && evmNetworkList && tokenList && tokenRates$1 && rawBalances) {
157
+ setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates$1, rawBalances));
183
158
  refInitialized.current = true;
184
159
  }
185
- }, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates, useTestnets]);
160
+ }, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates$1, useTestnets]);
186
161
  return dbData;
187
162
  };
188
163
  const [DbCacheProvider, useDbCache] = provideContext(useDbCacheProvider);
189
164
 
190
165
  var packageJson = {
191
166
  name: "@talismn/balances-react",
192
- version: "0.0.0-pr563-20230221193038",
167
+ version: "0.0.0-pr563-20230222051735",
193
168
  author: "Talisman",
194
169
  homepage: "https://talisman.xyz",
195
170
  license: "UNLICENSED",
@@ -222,7 +197,7 @@ var packageJson = {
222
197
  "@talismn/chaindata-provider-extension": "workspace:^",
223
198
  "@talismn/token-rates": "workspace:^",
224
199
  anylogger: "^1.0.11",
225
- dexie: "^3.2.3",
200
+ dexie: "^3.2.2",
226
201
  "dexie-react-hooks": "^1.1.1",
227
202
  "react-use": "^17.4.0",
228
203
  rxjs: "^7.8.0"
@@ -319,14 +294,14 @@ function useChainConnectorsProvider(options) {
319
294
  }
320
295
  const [ChainConnectorsProvider, useChainConnectors] = provideContext(useChainConnectorsProvider);
321
296
 
322
- const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeBalances()];
297
+ const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeTokenRates(), useSubscribeBalances()];
323
298
  const [SubscriptionsProvider, useSubscriptions] = provideContext(useSubscriptionsProvider);
324
299
 
325
300
  /**
326
301
  * This hook is responsible for fetching the data used for balances and inserting it into the db.
327
302
  */
328
303
  const useDbCacheSubscription = subscribeTo => {
329
- const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeBalances] = useSubscriptions();
304
+ const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances] = useSubscriptions();
330
305
  react.useEffect(() => {
331
306
  switch (subscribeTo) {
332
307
  case "chains":
@@ -335,10 +310,12 @@ const useDbCacheSubscription = subscribeTo => {
335
310
  return subscribeHydrateEvmNetworks();
336
311
  case "tokens":
337
312
  return subscribeHydrateTokens();
313
+ case "tokenRates":
314
+ return subscribeTokenRates();
338
315
  case "balances":
339
316
  return subscribeBalances();
340
317
  }
341
- }, [subscribeBalances, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTo]);
318
+ }, [subscribeTo, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances]);
342
319
  };
343
320
  function useSubscribeChaindataHydrate(type) {
344
321
  const chaindata =
@@ -370,12 +347,55 @@ function useSubscribeChaindataHydrate(type) {
370
347
  const subscribe = useMulticastSubscription(createSubscription);
371
348
  return subscribe;
372
349
  }
350
+ function useSubscribeTokenRates() {
351
+ const chaindataProvider = useChaindata();
352
+ const tokens = dexieReactHooks.useLiveQuery(() => chaindataProvider?.tokens(), [chaindataProvider]);
353
+ const generationRef = react.useRef(0);
354
+ const createSubscription = react.useCallback(() => {
355
+ if (!chaindataProvider) return;
356
+ if (!tokens) return;
357
+ if (Object.keys(tokens).length < 1) return;
358
+
359
+ // when we make a new request, we want to ignore any old requests which haven't yet completed
360
+ // otherwise we risk replacing the most recent data with older data
361
+ const generation = (generationRef.current + 1) % Number.MAX_SAFE_INTEGER;
362
+ generationRef.current = generation;
363
+ let active = true;
364
+ const REFRESH_INTERVAL = 300_000; // 300_000ms = 5 minutes
365
+ const RETRY_INTERVAL = 5_000; // 5_000ms = 5 seconds
366
+
367
+ const hydrate = async () => {
368
+ if (!active) return;
369
+ if (generationRef.current !== generation) return;
370
+ try {
371
+ const tokenRates$1 = await tokenRates.fetchTokenRates(tokens);
372
+ if (!active) return;
373
+ if (generationRef.current !== generation) return;
374
+ const putTokenRates = Object.entries(tokenRates$1).map(([tokenId, rates]) => ({
375
+ tokenId,
376
+ rates
377
+ }));
378
+ tokenRates.db.transaction("rw", tokenRates.db.tokenRates, async () => await tokenRates.db.tokenRates.bulkPut(putTokenRates));
379
+ setTimeout(hydrate, REFRESH_INTERVAL);
380
+ } catch (error) {
381
+ log.error(`Failed to fetch tokenRates, retrying in ${Math.round(RETRY_INTERVAL / 1000)} seconds`, error);
382
+ setTimeout(hydrate, RETRY_INTERVAL);
383
+ }
384
+ };
385
+ hydrate();
386
+ return () => {
387
+ active = false;
388
+ };
389
+ }, [chaindataProvider, tokens]);
390
+ const subscribe = useMulticastSubscription(createSubscription);
391
+ return subscribe;
392
+ }
373
393
  function useSubscribeBalances() {
374
394
  const balanceModules = useBalanceModules();
375
395
  const chaindataProvider = useChaindata();
376
396
  const chainConnectors = useChainConnectors();
377
397
  const [allAddresses] = useAllAddresses();
378
- const tokens = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.tokens(), [chaindataProvider]);
398
+ const tokens = dexieReactHooks.useLiveQuery(() => chaindataProvider?.tokens(), [chaindataProvider]);
379
399
  const tokenIds = react.useMemo(() => Object.values(tokens ?? {}).map(({
380
400
  id
381
401
  }) => id), [tokens]);
@@ -464,6 +484,19 @@ function useEvmNetwork(evmNetworkId, withTestnets) {
464
484
  return evmNetworkId ? evmNetworks[evmNetworkId] : undefined;
465
485
  }
466
486
 
487
+ function useTokenRates() {
488
+ // keep db data up to date
489
+ useDbCacheSubscription("tokenRates");
490
+ const {
491
+ tokenRatesMap
492
+ } = useDbCache();
493
+ return tokenRatesMap;
494
+ }
495
+ function useTokenRate(tokenId) {
496
+ const tokenRates = useTokenRates();
497
+ return tokenId ? tokenRates[tokenId] : undefined;
498
+ }
499
+
467
500
  function useTokens(withTestnets) {
468
501
  // keep db data up to date
469
502
  useDbCacheSubscription("tokens");
@@ -482,12 +515,7 @@ const useBalancesHydrate = withTestnets => {
482
515
  const chains = useChains(withTestnets);
483
516
  const evmNetworks = useEvmNetworks(withTestnets);
484
517
  const tokens = useTokens(withTestnets);
485
-
486
- // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
487
- // useDbCacheSubscription("tokenRates")
488
- const {
489
- tokenRatesMap: tokenRates
490
- } = useDbCache();
518
+ const tokenRates = useTokenRates();
491
519
  return react.useMemo(() => ({
492
520
  chains,
493
521
  evmNetworks,
@@ -531,15 +559,23 @@ const BalancesProvider = ({
531
559
  onfinalityApiKey,
532
560
  useTestnets,
533
561
  children
534
- }) => /*#__PURE__*/React.createElement(ChaindataProvider, {
535
- onfinalityApiKey: onfinalityApiKey
536
- }, /*#__PURE__*/React.createElement(ChainConnectorsProvider, {
537
- onfinalityApiKey: onfinalityApiKey
538
- }, /*#__PURE__*/React.createElement(AllAddressesProvider, null, /*#__PURE__*/React.createElement(BalanceModulesProvider, {
539
- balanceModules: balanceModules
540
- }, /*#__PURE__*/React.createElement(DbCacheProvider, {
541
- useTestnets: useTestnets
542
- }, /*#__PURE__*/React.createElement(SubscriptionsProvider, null, children))))));
562
+ }) => /*#__PURE__*/jsxRuntime.jsx(ChaindataProvider, {
563
+ onfinalityApiKey: onfinalityApiKey,
564
+ children: /*#__PURE__*/jsxRuntime.jsx(ChainConnectorsProvider, {
565
+ onfinalityApiKey: onfinalityApiKey,
566
+ children: /*#__PURE__*/jsxRuntime.jsx(AllAddressesProvider, {
567
+ children: /*#__PURE__*/jsxRuntime.jsx(BalanceModulesProvider, {
568
+ balanceModules: balanceModules,
569
+ children: /*#__PURE__*/jsxRuntime.jsx(DbCacheProvider, {
570
+ useTestnets: useTestnets,
571
+ children: /*#__PURE__*/jsxRuntime.jsx(SubscriptionsProvider, {
572
+ children: children
573
+ })
574
+ })
575
+ })
576
+ })
577
+ })
578
+ });
543
579
 
544
580
  exports.AllAddressesProvider = AllAddressesProvider;
545
581
  exports.BalanceModulesProvider = BalanceModulesProvider;
@@ -565,5 +601,6 @@ exports.useEvmNetworks = useEvmNetworks;
565
601
  exports.useMulticastSubscription = useMulticastSubscription;
566
602
  exports.useSubscriptions = useSubscriptions;
567
603
  exports.useToken = useToken;
604
+ exports.useTokenRate = useTokenRate;
568
605
  exports.useTokenRates = useTokenRates;
569
606
  exports.useTokens = useTokens;
@@ -1,19 +1,15 @@
1
1
  import { useContext, createContext, useState, useEffect, useRef, useMemo, useCallback } from 'react';
2
- import { db, balances, Balances } from '@talismn/balances';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { db as db$1, balances, Balances } from '@talismn/balances';
4
+ import { db, fetchTokenRates } from '@talismn/token-rates';
3
5
  import { useLiveQuery } from 'dexie-react-hooks';
4
6
  import { useDebounce } from 'react-use';
5
7
  import { ChaindataProviderExtension } from '@talismn/chaindata-provider-extension';
6
- import { fetchTokenRates } from '@talismn/token-rates';
7
8
  import anylogger from 'anylogger';
8
9
  import { Observable, defer, shareReplay } from 'rxjs';
9
10
  import { ChainConnector } from '@talismn/chain-connector';
10
11
  import { ChainConnectorEvm } from '@talismn/chain-connector-evm';
11
12
 
12
- /**
13
- * This utility generates a context provider from a react hook passed as argument
14
- *
15
- * @returns an array containing the provider and the consumer hook
16
- */
17
13
  const provideContext = useProviderContext => {
18
14
  // automatic typing based on our hook's return type
19
15
 
@@ -25,9 +21,10 @@ const provideContext = useProviderContext => {
25
21
  ...props
26
22
  }) => {
27
23
  const ctx = useProviderContext(props);
28
- return /*#__PURE__*/React.createElement(Context.Provider, {
29
- value: ctx
30
- }, children);
24
+ return /*#__PURE__*/jsx(Context.Provider, {
25
+ value: ctx,
26
+ children: children
27
+ });
31
28
  };
32
29
  const useProvidedContext = () => {
33
30
  const context = useContext(Context);
@@ -57,25 +54,6 @@ function useChaindataProvider(options = {}) {
57
54
  }
58
55
  const [ChaindataProvider, useChaindata] = provideContext(useChaindataProvider);
59
56
 
60
- function useTokenRates(tokens) {
61
- const generation = useRef(0);
62
- const [tokenRates, setTokenRates] = useState({});
63
- useEffect(() => {
64
- if (!tokens) return;
65
- if (Object.keys(tokens).length < 1) return;
66
-
67
- // when we make a new request, we want to ignore any old requests which haven't yet completed
68
- // otherwise we risk replacing the most recent data with older data
69
- generation.current = (generation.current + 1) % Number.MAX_SAFE_INTEGER;
70
- const thisGeneration = generation.current;
71
- fetchTokenRates(tokens).then(tokenRates => {
72
- if (thisGeneration !== generation.current) return;
73
- setTokenRates(tokenRates);
74
- });
75
- }, [tokens]);
76
- return tokenRates;
77
- }
78
-
79
57
  const filterNoTestnet = ({
80
58
  isTestnet
81
59
  }) => isTestnet === false;
@@ -92,13 +70,11 @@ const DEFAULT_VALUE = {
92
70
  evmNetworksWithoutTestnetsMap: {},
93
71
  tokensWithTestnetsMap: {},
94
72
  tokensWithoutTestnetsMap: {},
95
- balances: [],
96
- tokenRatesMap: {}
73
+ tokenRatesMap: {},
74
+ balances: []
97
75
  };
98
- const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, tokenRates) => {
99
- if (!chainsMap || !evmNetworksMap || !tokensMap || !allBalances
100
- // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
101
- /* || !tokenRates */) return DEFAULT_VALUE;
76
+ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, tokenRates, allBalances) => {
77
+ if (!chainsMap || !evmNetworksMap || !tokensMap || !tokenRates || !allBalances) return DEFAULT_VALUE;
102
78
 
103
79
  // BEGIN: temp hack to indicate that
104
80
  // - EVM GLMR is a mirror of substrate GLMR
@@ -126,6 +102,10 @@ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, t
126
102
  const tokensWithoutTestnets = tokensWithTestnets.filter(filterNoTestnet).filter(token => token.chain && chainsWithoutTestnetsMap[token.chain.id] || token.evmNetwork && evmNetworksWithoutTestnetsMap[token.evmNetwork.id]);
127
103
  const tokensWithTestnetsMap = Object.fromEntries(tokensWithTestnets.map(token => [token.id, token]));
128
104
  const tokensWithoutTestnetsMap = Object.fromEntries(tokensWithoutTestnets.map(token => [token.id, token]));
105
+ const tokenRatesMap = Object.fromEntries(tokenRates.map(({
106
+ tokenId,
107
+ rates
108
+ }) => [tokenId, rates]));
129
109
 
130
110
  // return only balances for which we have a token
131
111
  const balances = allBalances.filter(b => tokensWithTestnetsMap[b.tokenId]);
@@ -142,36 +122,31 @@ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, t
142
122
  evmNetworksWithoutTestnetsMap,
143
123
  tokensWithTestnetsMap,
144
124
  tokensWithoutTestnetsMap,
145
- balances,
146
- tokenRatesMap: tokenRates ?? {}
125
+ tokenRatesMap,
126
+ balances
147
127
  };
148
128
  };
149
129
  const useDbCacheProvider = ({
150
130
  useTestnets = false
151
131
  }) => {
152
132
  const chaindataProvider = useChaindata();
153
- const chainList = useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.chains(), [chaindataProvider]);
154
- const evmNetworkList = useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.evmNetworks(), [chaindataProvider]);
155
- const tokenList = useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.tokens(), [chaindataProvider]);
156
- const rawBalances = useLiveQuery(() => db.balances.toArray(), []);
157
-
158
- // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
159
- const tokenRates = useTokenRates();
133
+ const chainList = useLiveQuery(() => chaindataProvider?.chains(), [chaindataProvider]);
134
+ const evmNetworkList = useLiveQuery(() => chaindataProvider?.evmNetworks(), [chaindataProvider]);
135
+ const tokenList = useLiveQuery(() => chaindataProvider?.tokens(), [chaindataProvider]);
136
+ const tokenRates = useLiveQuery(() => db.tokenRates.toArray(), []);
137
+ const rawBalances = useLiveQuery(() => db$1.balances.toArray(), []);
160
138
  const [dbData, setDbData] = useState(DEFAULT_VALUE);
161
139
 
162
140
  // debounce every 500ms to prevent hammering UI with updates
163
141
  useDebounce(() => {
164
- setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, rawBalances, tokenRates));
142
+ setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates, rawBalances));
165
143
  }, 500, [chainList, evmNetworkList, tokenList, rawBalances, tokenRates, useTestnets]);
166
144
  const refInitialized = useRef(false);
167
145
 
168
146
  // force an update as soon as all datasources are fetched, so UI can display data ASAP
169
147
  useEffect(() => {
170
- if (!refInitialized.current && chainList && evmNetworkList && tokenList && rawBalances
171
- // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
172
- // && tokenRates
173
- ) {
174
- setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, rawBalances, tokenRates));
148
+ if (!refInitialized.current && chainList && evmNetworkList && tokenList && tokenRates && rawBalances) {
149
+ setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates, rawBalances));
175
150
  refInitialized.current = true;
176
151
  }
177
152
  }, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates, useTestnets]);
@@ -181,7 +156,7 @@ const [DbCacheProvider, useDbCache] = provideContext(useDbCacheProvider);
181
156
 
182
157
  var packageJson = {
183
158
  name: "@talismn/balances-react",
184
- version: "0.0.0-pr563-20230221193038",
159
+ version: "0.0.0-pr563-20230222051735",
185
160
  author: "Talisman",
186
161
  homepage: "https://talisman.xyz",
187
162
  license: "UNLICENSED",
@@ -214,7 +189,7 @@ var packageJson = {
214
189
  "@talismn/chaindata-provider-extension": "workspace:^",
215
190
  "@talismn/token-rates": "workspace:^",
216
191
  anylogger: "^1.0.11",
217
- dexie: "^3.2.3",
192
+ dexie: "^3.2.2",
218
193
  "dexie-react-hooks": "^1.1.1",
219
194
  "react-use": "^17.4.0",
220
195
  rxjs: "^7.8.0"
@@ -311,14 +286,14 @@ function useChainConnectorsProvider(options) {
311
286
  }
312
287
  const [ChainConnectorsProvider, useChainConnectors] = provideContext(useChainConnectorsProvider);
313
288
 
314
- const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeBalances()];
289
+ const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeTokenRates(), useSubscribeBalances()];
315
290
  const [SubscriptionsProvider, useSubscriptions] = provideContext(useSubscriptionsProvider);
316
291
 
317
292
  /**
318
293
  * This hook is responsible for fetching the data used for balances and inserting it into the db.
319
294
  */
320
295
  const useDbCacheSubscription = subscribeTo => {
321
- const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeBalances] = useSubscriptions();
296
+ const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances] = useSubscriptions();
322
297
  useEffect(() => {
323
298
  switch (subscribeTo) {
324
299
  case "chains":
@@ -327,10 +302,12 @@ const useDbCacheSubscription = subscribeTo => {
327
302
  return subscribeHydrateEvmNetworks();
328
303
  case "tokens":
329
304
  return subscribeHydrateTokens();
305
+ case "tokenRates":
306
+ return subscribeTokenRates();
330
307
  case "balances":
331
308
  return subscribeBalances();
332
309
  }
333
- }, [subscribeBalances, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTo]);
310
+ }, [subscribeTo, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances]);
334
311
  };
335
312
  function useSubscribeChaindataHydrate(type) {
336
313
  const chaindata =
@@ -362,12 +339,55 @@ function useSubscribeChaindataHydrate(type) {
362
339
  const subscribe = useMulticastSubscription(createSubscription);
363
340
  return subscribe;
364
341
  }
342
+ function useSubscribeTokenRates() {
343
+ const chaindataProvider = useChaindata();
344
+ const tokens = useLiveQuery(() => chaindataProvider?.tokens(), [chaindataProvider]);
345
+ const generationRef = useRef(0);
346
+ const createSubscription = useCallback(() => {
347
+ if (!chaindataProvider) return;
348
+ if (!tokens) return;
349
+ if (Object.keys(tokens).length < 1) return;
350
+
351
+ // when we make a new request, we want to ignore any old requests which haven't yet completed
352
+ // otherwise we risk replacing the most recent data with older data
353
+ const generation = (generationRef.current + 1) % Number.MAX_SAFE_INTEGER;
354
+ generationRef.current = generation;
355
+ let active = true;
356
+ const REFRESH_INTERVAL = 300_000; // 300_000ms = 5 minutes
357
+ const RETRY_INTERVAL = 5_000; // 5_000ms = 5 seconds
358
+
359
+ const hydrate = async () => {
360
+ if (!active) return;
361
+ if (generationRef.current !== generation) return;
362
+ try {
363
+ const tokenRates = await fetchTokenRates(tokens);
364
+ if (!active) return;
365
+ if (generationRef.current !== generation) return;
366
+ const putTokenRates = Object.entries(tokenRates).map(([tokenId, rates]) => ({
367
+ tokenId,
368
+ rates
369
+ }));
370
+ db.transaction("rw", db.tokenRates, async () => await db.tokenRates.bulkPut(putTokenRates));
371
+ setTimeout(hydrate, REFRESH_INTERVAL);
372
+ } catch (error) {
373
+ log.error(`Failed to fetch tokenRates, retrying in ${Math.round(RETRY_INTERVAL / 1000)} seconds`, error);
374
+ setTimeout(hydrate, RETRY_INTERVAL);
375
+ }
376
+ };
377
+ hydrate();
378
+ return () => {
379
+ active = false;
380
+ };
381
+ }, [chaindataProvider, tokens]);
382
+ const subscribe = useMulticastSubscription(createSubscription);
383
+ return subscribe;
384
+ }
365
385
  function useSubscribeBalances() {
366
386
  const balanceModules = useBalanceModules();
367
387
  const chaindataProvider = useChaindata();
368
388
  const chainConnectors = useChainConnectors();
369
389
  const [allAddresses] = useAllAddresses();
370
- const tokens = useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.tokens(), [chaindataProvider]);
390
+ const tokens = useLiveQuery(() => chaindataProvider?.tokens(), [chaindataProvider]);
371
391
  const tokenIds = useMemo(() => Object.values(tokens ?? {}).map(({
372
392
  id
373
393
  }) => id), [tokens]);
@@ -402,7 +422,7 @@ function useSubscribeBalances() {
402
422
  unsub.then(unsubscribe => unsubscribe());
403
423
 
404
424
  // set this subscription's balances in the store to status: cache
405
- db.transaction("rw", db.balances, async () => await db.balances.filter(balance => {
425
+ db$1.transaction("rw", db$1.balances, async () => await db$1.balances.filter(balance => {
406
426
  if (balance.source !== balanceModule.type) return false;
407
427
  if (!Object.keys(addressesByModuleToken).includes(balance.tokenId)) return false;
408
428
  if (!addressesByModuleToken[balance.tokenId].includes(balance.address)) return false;
@@ -417,7 +437,7 @@ function useSubscribeBalances() {
417
437
  id,
418
438
  ...balance
419
439
  }));
420
- db.transaction("rw", db.balances, async () => await db.balances.bulkPut(putBalances));
440
+ db$1.transaction("rw", db$1.balances, async () => await db$1.balances.bulkPut(putBalances));
421
441
  });
422
442
  return unsubscribe;
423
443
  });
@@ -456,6 +476,19 @@ function useEvmNetwork(evmNetworkId, withTestnets) {
456
476
  return evmNetworkId ? evmNetworks[evmNetworkId] : undefined;
457
477
  }
458
478
 
479
+ function useTokenRates() {
480
+ // keep db data up to date
481
+ useDbCacheSubscription("tokenRates");
482
+ const {
483
+ tokenRatesMap
484
+ } = useDbCache();
485
+ return tokenRatesMap;
486
+ }
487
+ function useTokenRate(tokenId) {
488
+ const tokenRates = useTokenRates();
489
+ return tokenId ? tokenRates[tokenId] : undefined;
490
+ }
491
+
459
492
  function useTokens(withTestnets) {
460
493
  // keep db data up to date
461
494
  useDbCacheSubscription("tokens");
@@ -474,12 +507,7 @@ const useBalancesHydrate = withTestnets => {
474
507
  const chains = useChains(withTestnets);
475
508
  const evmNetworks = useEvmNetworks(withTestnets);
476
509
  const tokens = useTokens(withTestnets);
477
-
478
- // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
479
- // useDbCacheSubscription("tokenRates")
480
- const {
481
- tokenRatesMap: tokenRates
482
- } = useDbCache();
510
+ const tokenRates = useTokenRates();
483
511
  return useMemo(() => ({
484
512
  chains,
485
513
  evmNetworks,
@@ -523,14 +551,22 @@ const BalancesProvider = ({
523
551
  onfinalityApiKey,
524
552
  useTestnets,
525
553
  children
526
- }) => /*#__PURE__*/React.createElement(ChaindataProvider, {
527
- onfinalityApiKey: onfinalityApiKey
528
- }, /*#__PURE__*/React.createElement(ChainConnectorsProvider, {
529
- onfinalityApiKey: onfinalityApiKey
530
- }, /*#__PURE__*/React.createElement(AllAddressesProvider, null, /*#__PURE__*/React.createElement(BalanceModulesProvider, {
531
- balanceModules: balanceModules
532
- }, /*#__PURE__*/React.createElement(DbCacheProvider, {
533
- useTestnets: useTestnets
534
- }, /*#__PURE__*/React.createElement(SubscriptionsProvider, null, children))))));
535
-
536
- export { AllAddressesProvider, BalanceModulesProvider, BalancesProvider, ChainConnectorsProvider, ChaindataProvider, DbCacheProvider, SubscriptionsProvider, createMulticastSubscription, provideContext, useAllAddresses, useBalanceModules, useBalances, useBalancesHydrate, useChain, useChainConnectors, useChaindata, useChains, useDbCache, useDbCacheSubscription, useEvmNetwork, useEvmNetworks, useMulticastSubscription, useSubscriptions, useToken, useTokenRates, useTokens };
554
+ }) => /*#__PURE__*/jsx(ChaindataProvider, {
555
+ onfinalityApiKey: onfinalityApiKey,
556
+ children: /*#__PURE__*/jsx(ChainConnectorsProvider, {
557
+ onfinalityApiKey: onfinalityApiKey,
558
+ children: /*#__PURE__*/jsx(AllAddressesProvider, {
559
+ children: /*#__PURE__*/jsx(BalanceModulesProvider, {
560
+ balanceModules: balanceModules,
561
+ children: /*#__PURE__*/jsx(DbCacheProvider, {
562
+ useTestnets: useTestnets,
563
+ children: /*#__PURE__*/jsx(SubscriptionsProvider, {
564
+ children: children
565
+ })
566
+ })
567
+ })
568
+ })
569
+ })
570
+ });
571
+
572
+ export { AllAddressesProvider, BalanceModulesProvider, BalancesProvider, ChainConnectorsProvider, ChaindataProvider, DbCacheProvider, SubscriptionsProvider, createMulticastSubscription, provideContext, useAllAddresses, useBalanceModules, useBalances, useBalancesHydrate, useChain, useChainConnectors, useChaindata, useChains, useDbCache, useDbCacheSubscription, useEvmNetwork, useEvmNetworks, useMulticastSubscription, useSubscriptions, useToken, useTokenRate, useTokenRates, useTokens };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/balances-react",
3
- "version": "0.0.0-pr563-20230221193038",
3
+ "version": "0.0.0-pr563-20230222051735",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "UNLICENSED",
@@ -26,14 +26,14 @@
26
26
  "clean": "rm -rf dist && rm -rf .turbo rm -rf node_modules"
27
27
  },
28
28
  "dependencies": {
29
- "@talismn/balances": "^0.0.0-pr563-20230221193038",
29
+ "@talismn/balances": "^0.0.0-pr563-20230222051735",
30
30
  "@talismn/chain-connector": "^0.4.2",
31
31
  "@talismn/chain-connector-evm": "^0.4.2",
32
32
  "@talismn/chaindata-provider": "^0.4.2",
33
33
  "@talismn/chaindata-provider-extension": "^0.4.2",
34
34
  "@talismn/token-rates": "^0.1.14",
35
35
  "anylogger": "^1.0.11",
36
- "dexie": "^3.2.3",
36
+ "dexie": "^3.2.2",
37
37
  "dexie-react-hooks": "^1.1.1",
38
38
  "react-use": "^17.4.0",
39
39
  "rxjs": "^7.8.0"