@talismn/balances-react 0.0.0-pr563-20230221230003 → 0.0.0-pr563-20230222052739

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,15 @@
1
1
  # @talismn/balances-react
2
2
 
3
- ## 0.0.0-pr563-20230221230003
3
+ ## 0.0.0-pr563-20230222052739
4
4
 
5
5
  ### Patch Changes
6
6
 
7
+ - 5ddeee95: fix: tokenRates in @talismn/balances-react
7
8
  - 536eddb5: fix: ported useDbCache related perf fixes to @talismn/balances-react
9
+ - Updated dependencies [5ddeee95]
8
10
  - Updated dependencies [536eddb5]
9
- - @talismn/balances@0.0.0-pr563-20230221230003
11
+ - @talismn/token-rates@0.0.0-pr563-20230222052739
12
+ - @talismn/balances@0.0.0-pr563-20230222052739
10
13
 
11
14
  ## 0.3.3
12
15
 
@@ -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;
@@ -5,10 +5,10 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var react = require('react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var balances = require('@talismn/balances');
8
+ var tokenRates = require('@talismn/token-rates');
8
9
  var dexieReactHooks = require('dexie-react-hooks');
9
10
  var reactUse = require('react-use');
10
11
  var chaindataProviderExtension = require('@talismn/chaindata-provider-extension');
11
- var tokenRates = require('@talismn/token-rates');
12
12
  var anylogger = require('anylogger');
13
13
  var rxjs = require('rxjs');
14
14
  var chainConnector = require('@talismn/chain-connector');
@@ -62,25 +62,6 @@ function useChaindataProvider(options = {}) {
62
62
  }
63
63
  const [ChaindataProvider, useChaindata] = provideContext(useChaindataProvider);
64
64
 
65
- function useTokenRates(tokens) {
66
- const generation = react.useRef(0);
67
- const [tokenRates$1, setTokenRates] = react.useState({});
68
- react.useEffect(() => {
69
- if (!tokens) return;
70
- if (Object.keys(tokens).length < 1) return;
71
-
72
- // when we make a new request, we want to ignore any old requests which haven't yet completed
73
- // otherwise we risk replacing the most recent data with older data
74
- generation.current = (generation.current + 1) % Number.MAX_SAFE_INTEGER;
75
- const thisGeneration = generation.current;
76
- tokenRates.fetchTokenRates(tokens).then(tokenRates => {
77
- if (thisGeneration !== generation.current) return;
78
- setTokenRates(tokenRates);
79
- });
80
- }, [tokens]);
81
- return tokenRates$1;
82
- }
83
-
84
65
  const filterNoTestnet = ({
85
66
  isTestnet
86
67
  }) => isTestnet === false;
@@ -97,13 +78,11 @@ const DEFAULT_VALUE = {
97
78
  evmNetworksWithoutTestnetsMap: {},
98
79
  tokensWithTestnetsMap: {},
99
80
  tokensWithoutTestnetsMap: {},
100
- balances: [],
101
- tokenRatesMap: {}
81
+ tokenRatesMap: {},
82
+ balances: []
102
83
  };
103
- const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, tokenRates) => {
104
- if (!chainsMap || !evmNetworksMap || !tokensMap || !allBalances
105
- // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
106
- /* || !tokenRates */) return DEFAULT_VALUE;
84
+ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, tokenRates, allBalances) => {
85
+ if (!chainsMap || !evmNetworksMap || !tokensMap || !tokenRates || !allBalances) return DEFAULT_VALUE;
107
86
 
108
87
  // BEGIN: temp hack to indicate that
109
88
  // - EVM GLMR is a mirror of substrate GLMR
@@ -131,6 +110,10 @@ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, t
131
110
  const tokensWithoutTestnets = tokensWithTestnets.filter(filterNoTestnet).filter(token => token.chain && chainsWithoutTestnetsMap[token.chain.id] || token.evmNetwork && evmNetworksWithoutTestnetsMap[token.evmNetwork.id]);
132
111
  const tokensWithTestnetsMap = Object.fromEntries(tokensWithTestnets.map(token => [token.id, token]));
133
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]));
134
117
 
135
118
  // return only balances for which we have a token
136
119
  const balances = allBalances.filter(b => tokensWithTestnetsMap[b.tokenId]);
@@ -147,8 +130,8 @@ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, t
147
130
  evmNetworksWithoutTestnetsMap,
148
131
  tokensWithTestnetsMap,
149
132
  tokensWithoutTestnetsMap,
150
- balances,
151
- tokenRatesMap: tokenRates ?? {}
133
+ tokenRatesMap,
134
+ balances
152
135
  };
153
136
  };
154
137
  const useDbCacheProvider = ({
@@ -158,35 +141,30 @@ const useDbCacheProvider = ({
158
141
  const chainList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.chains(), [chaindataProvider]);
159
142
  const evmNetworkList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.evmNetworks(), [chaindataProvider]);
160
143
  const tokenList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.tokens(), [chaindataProvider]);
144
+ const tokenRates$1 = dexieReactHooks.useLiveQuery(() => tokenRates.db.tokenRates.toArray(), []);
161
145
  const rawBalances = dexieReactHooks.useLiveQuery(() => balances.db.balances.toArray(), []);
162
-
163
- // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
164
- const tokenRates = useTokenRates();
165
146
  const [dbData, setDbData] = react.useState(DEFAULT_VALUE);
166
147
 
167
148
  // debounce every 500ms to prevent hammering UI with updates
168
149
  reactUse.useDebounce(() => {
169
- setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, rawBalances, tokenRates));
170
- }, 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]);
171
152
  const refInitialized = react.useRef(false);
172
153
 
173
154
  // force an update as soon as all datasources are fetched, so UI can display data ASAP
174
155
  react.useEffect(() => {
175
- if (!refInitialized.current && chainList && evmNetworkList && tokenList && rawBalances
176
- // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
177
- // && tokenRates
178
- ) {
179
- 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));
180
158
  refInitialized.current = true;
181
159
  }
182
- }, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates, useTestnets]);
160
+ }, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates$1, useTestnets]);
183
161
  return dbData;
184
162
  };
185
163
  const [DbCacheProvider, useDbCache] = provideContext(useDbCacheProvider);
186
164
 
187
165
  var packageJson = {
188
166
  name: "@talismn/balances-react",
189
- version: "0.0.0-pr563-20230221230003",
167
+ version: "0.0.0-pr563-20230222052739",
190
168
  author: "Talisman",
191
169
  homepage: "https://talisman.xyz",
192
170
  license: "UNLICENSED",
@@ -316,14 +294,14 @@ function useChainConnectorsProvider(options) {
316
294
  }
317
295
  const [ChainConnectorsProvider, useChainConnectors] = provideContext(useChainConnectorsProvider);
318
296
 
319
- const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeBalances()];
297
+ const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeTokenRates(), useSubscribeBalances()];
320
298
  const [SubscriptionsProvider, useSubscriptions] = provideContext(useSubscriptionsProvider);
321
299
 
322
300
  /**
323
301
  * This hook is responsible for fetching the data used for balances and inserting it into the db.
324
302
  */
325
303
  const useDbCacheSubscription = subscribeTo => {
326
- const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeBalances] = useSubscriptions();
304
+ const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances] = useSubscriptions();
327
305
  react.useEffect(() => {
328
306
  switch (subscribeTo) {
329
307
  case "chains":
@@ -332,10 +310,12 @@ const useDbCacheSubscription = subscribeTo => {
332
310
  return subscribeHydrateEvmNetworks();
333
311
  case "tokens":
334
312
  return subscribeHydrateTokens();
313
+ case "tokenRates":
314
+ return subscribeTokenRates();
335
315
  case "balances":
336
316
  return subscribeBalances();
337
317
  }
338
- }, [subscribeBalances, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTo]);
318
+ }, [subscribeTo, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances]);
339
319
  };
340
320
  function useSubscribeChaindataHydrate(type) {
341
321
  const chaindata =
@@ -367,6 +347,49 @@ function useSubscribeChaindataHydrate(type) {
367
347
  const subscribe = useMulticastSubscription(createSubscription);
368
348
  return subscribe;
369
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
+ }
370
393
  function useSubscribeBalances() {
371
394
  const balanceModules = useBalanceModules();
372
395
  const chaindataProvider = useChaindata();
@@ -461,6 +484,19 @@ function useEvmNetwork(evmNetworkId, withTestnets) {
461
484
  return evmNetworkId ? evmNetworks[evmNetworkId] : undefined;
462
485
  }
463
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
+
464
500
  function useTokens(withTestnets) {
465
501
  // keep db data up to date
466
502
  useDbCacheSubscription("tokens");
@@ -479,12 +515,7 @@ const useBalancesHydrate = withTestnets => {
479
515
  const chains = useChains(withTestnets);
480
516
  const evmNetworks = useEvmNetworks(withTestnets);
481
517
  const tokens = useTokens(withTestnets);
482
-
483
- // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
484
- // useDbCacheSubscription("tokenRates")
485
- const {
486
- tokenRatesMap: tokenRates
487
- } = useDbCache();
518
+ const tokenRates = useTokenRates();
488
519
  return react.useMemo(() => ({
489
520
  chains,
490
521
  evmNetworks,
@@ -570,5 +601,6 @@ exports.useEvmNetworks = useEvmNetworks;
570
601
  exports.useMulticastSubscription = useMulticastSubscription;
571
602
  exports.useSubscriptions = useSubscriptions;
572
603
  exports.useToken = useToken;
604
+ exports.useTokenRate = useTokenRate;
573
605
  exports.useTokenRates = useTokenRates;
574
606
  exports.useTokens = useTokens;
@@ -5,10 +5,10 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var react = require('react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var balances = require('@talismn/balances');
8
+ var tokenRates = require('@talismn/token-rates');
8
9
  var dexieReactHooks = require('dexie-react-hooks');
9
10
  var reactUse = require('react-use');
10
11
  var chaindataProviderExtension = require('@talismn/chaindata-provider-extension');
11
- var tokenRates = require('@talismn/token-rates');
12
12
  var anylogger = require('anylogger');
13
13
  var rxjs = require('rxjs');
14
14
  var chainConnector = require('@talismn/chain-connector');
@@ -62,25 +62,6 @@ function useChaindataProvider(options = {}) {
62
62
  }
63
63
  const [ChaindataProvider, useChaindata] = provideContext(useChaindataProvider);
64
64
 
65
- function useTokenRates(tokens) {
66
- const generation = react.useRef(0);
67
- const [tokenRates$1, setTokenRates] = react.useState({});
68
- react.useEffect(() => {
69
- if (!tokens) return;
70
- if (Object.keys(tokens).length < 1) return;
71
-
72
- // when we make a new request, we want to ignore any old requests which haven't yet completed
73
- // otherwise we risk replacing the most recent data with older data
74
- generation.current = (generation.current + 1) % Number.MAX_SAFE_INTEGER;
75
- const thisGeneration = generation.current;
76
- tokenRates.fetchTokenRates(tokens).then(tokenRates => {
77
- if (thisGeneration !== generation.current) return;
78
- setTokenRates(tokenRates);
79
- });
80
- }, [tokens]);
81
- return tokenRates$1;
82
- }
83
-
84
65
  const filterNoTestnet = ({
85
66
  isTestnet
86
67
  }) => isTestnet === false;
@@ -97,13 +78,11 @@ const DEFAULT_VALUE = {
97
78
  evmNetworksWithoutTestnetsMap: {},
98
79
  tokensWithTestnetsMap: {},
99
80
  tokensWithoutTestnetsMap: {},
100
- balances: [],
101
- tokenRatesMap: {}
81
+ tokenRatesMap: {},
82
+ balances: []
102
83
  };
103
- const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, tokenRates) => {
104
- if (!chainsMap || !evmNetworksMap || !tokensMap || !allBalances
105
- // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
106
- /* || !tokenRates */) return DEFAULT_VALUE;
84
+ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, tokenRates, allBalances) => {
85
+ if (!chainsMap || !evmNetworksMap || !tokensMap || !tokenRates || !allBalances) return DEFAULT_VALUE;
107
86
 
108
87
  // BEGIN: temp hack to indicate that
109
88
  // - EVM GLMR is a mirror of substrate GLMR
@@ -131,6 +110,10 @@ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, t
131
110
  const tokensWithoutTestnets = tokensWithTestnets.filter(filterNoTestnet).filter(token => token.chain && chainsWithoutTestnetsMap[token.chain.id] || token.evmNetwork && evmNetworksWithoutTestnetsMap[token.evmNetwork.id]);
132
111
  const tokensWithTestnetsMap = Object.fromEntries(tokensWithTestnets.map(token => [token.id, token]));
133
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]));
134
117
 
135
118
  // return only balances for which we have a token
136
119
  const balances = allBalances.filter(b => tokensWithTestnetsMap[b.tokenId]);
@@ -147,8 +130,8 @@ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, t
147
130
  evmNetworksWithoutTestnetsMap,
148
131
  tokensWithTestnetsMap,
149
132
  tokensWithoutTestnetsMap,
150
- balances,
151
- tokenRatesMap: tokenRates ?? {}
133
+ tokenRatesMap,
134
+ balances
152
135
  };
153
136
  };
154
137
  const useDbCacheProvider = ({
@@ -158,35 +141,30 @@ const useDbCacheProvider = ({
158
141
  const chainList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.chains(), [chaindataProvider]);
159
142
  const evmNetworkList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.evmNetworks(), [chaindataProvider]);
160
143
  const tokenList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.tokens(), [chaindataProvider]);
144
+ const tokenRates$1 = dexieReactHooks.useLiveQuery(() => tokenRates.db.tokenRates.toArray(), []);
161
145
  const rawBalances = dexieReactHooks.useLiveQuery(() => balances.db.balances.toArray(), []);
162
-
163
- // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
164
- const tokenRates = useTokenRates();
165
146
  const [dbData, setDbData] = react.useState(DEFAULT_VALUE);
166
147
 
167
148
  // debounce every 500ms to prevent hammering UI with updates
168
149
  reactUse.useDebounce(() => {
169
- setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, rawBalances, tokenRates));
170
- }, 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]);
171
152
  const refInitialized = react.useRef(false);
172
153
 
173
154
  // force an update as soon as all datasources are fetched, so UI can display data ASAP
174
155
  react.useEffect(() => {
175
- if (!refInitialized.current && chainList && evmNetworkList && tokenList && rawBalances
176
- // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
177
- // && tokenRates
178
- ) {
179
- 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));
180
158
  refInitialized.current = true;
181
159
  }
182
- }, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates, useTestnets]);
160
+ }, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates$1, useTestnets]);
183
161
  return dbData;
184
162
  };
185
163
  const [DbCacheProvider, useDbCache] = provideContext(useDbCacheProvider);
186
164
 
187
165
  var packageJson = {
188
166
  name: "@talismn/balances-react",
189
- version: "0.0.0-pr563-20230221230003",
167
+ version: "0.0.0-pr563-20230222052739",
190
168
  author: "Talisman",
191
169
  homepage: "https://talisman.xyz",
192
170
  license: "UNLICENSED",
@@ -316,14 +294,14 @@ function useChainConnectorsProvider(options) {
316
294
  }
317
295
  const [ChainConnectorsProvider, useChainConnectors] = provideContext(useChainConnectorsProvider);
318
296
 
319
- const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeBalances()];
297
+ const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeTokenRates(), useSubscribeBalances()];
320
298
  const [SubscriptionsProvider, useSubscriptions] = provideContext(useSubscriptionsProvider);
321
299
 
322
300
  /**
323
301
  * This hook is responsible for fetching the data used for balances and inserting it into the db.
324
302
  */
325
303
  const useDbCacheSubscription = subscribeTo => {
326
- const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeBalances] = useSubscriptions();
304
+ const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances] = useSubscriptions();
327
305
  react.useEffect(() => {
328
306
  switch (subscribeTo) {
329
307
  case "chains":
@@ -332,10 +310,12 @@ const useDbCacheSubscription = subscribeTo => {
332
310
  return subscribeHydrateEvmNetworks();
333
311
  case "tokens":
334
312
  return subscribeHydrateTokens();
313
+ case "tokenRates":
314
+ return subscribeTokenRates();
335
315
  case "balances":
336
316
  return subscribeBalances();
337
317
  }
338
- }, [subscribeBalances, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTo]);
318
+ }, [subscribeTo, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances]);
339
319
  };
340
320
  function useSubscribeChaindataHydrate(type) {
341
321
  const chaindata =
@@ -367,6 +347,49 @@ function useSubscribeChaindataHydrate(type) {
367
347
  const subscribe = useMulticastSubscription(createSubscription);
368
348
  return subscribe;
369
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
+ }
370
393
  function useSubscribeBalances() {
371
394
  const balanceModules = useBalanceModules();
372
395
  const chaindataProvider = useChaindata();
@@ -461,6 +484,19 @@ function useEvmNetwork(evmNetworkId, withTestnets) {
461
484
  return evmNetworkId ? evmNetworks[evmNetworkId] : undefined;
462
485
  }
463
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
+
464
500
  function useTokens(withTestnets) {
465
501
  // keep db data up to date
466
502
  useDbCacheSubscription("tokens");
@@ -479,12 +515,7 @@ const useBalancesHydrate = withTestnets => {
479
515
  const chains = useChains(withTestnets);
480
516
  const evmNetworks = useEvmNetworks(withTestnets);
481
517
  const tokens = useTokens(withTestnets);
482
-
483
- // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
484
- // useDbCacheSubscription("tokenRates")
485
- const {
486
- tokenRatesMap: tokenRates
487
- } = useDbCache();
518
+ const tokenRates = useTokenRates();
488
519
  return react.useMemo(() => ({
489
520
  chains,
490
521
  evmNetworks,
@@ -570,5 +601,6 @@ exports.useEvmNetworks = useEvmNetworks;
570
601
  exports.useMulticastSubscription = useMulticastSubscription;
571
602
  exports.useSubscriptions = useSubscriptions;
572
603
  exports.useToken = useToken;
604
+ exports.useTokenRate = useTokenRate;
573
605
  exports.useTokenRates = useTokenRates;
574
606
  exports.useTokens = useTokens;
@@ -1,10 +1,10 @@
1
1
  import { useContext, createContext, useState, useEffect, useRef, useMemo, useCallback } from 'react';
2
2
  import { jsx } from 'react/jsx-runtime';
3
- import { db, balances, Balances } from '@talismn/balances';
3
+ import { db as db$1, balances, Balances } from '@talismn/balances';
4
+ import { db, fetchTokenRates } from '@talismn/token-rates';
4
5
  import { useLiveQuery } from 'dexie-react-hooks';
5
6
  import { useDebounce } from 'react-use';
6
7
  import { ChaindataProviderExtension } from '@talismn/chaindata-provider-extension';
7
- import { fetchTokenRates } from '@talismn/token-rates';
8
8
  import anylogger from 'anylogger';
9
9
  import { Observable, defer, shareReplay } from 'rxjs';
10
10
  import { ChainConnector } from '@talismn/chain-connector';
@@ -54,25 +54,6 @@ function useChaindataProvider(options = {}) {
54
54
  }
55
55
  const [ChaindataProvider, useChaindata] = provideContext(useChaindataProvider);
56
56
 
57
- function useTokenRates(tokens) {
58
- const generation = useRef(0);
59
- const [tokenRates, setTokenRates] = useState({});
60
- useEffect(() => {
61
- if (!tokens) return;
62
- if (Object.keys(tokens).length < 1) return;
63
-
64
- // when we make a new request, we want to ignore any old requests which haven't yet completed
65
- // otherwise we risk replacing the most recent data with older data
66
- generation.current = (generation.current + 1) % Number.MAX_SAFE_INTEGER;
67
- const thisGeneration = generation.current;
68
- fetchTokenRates(tokens).then(tokenRates => {
69
- if (thisGeneration !== generation.current) return;
70
- setTokenRates(tokenRates);
71
- });
72
- }, [tokens]);
73
- return tokenRates;
74
- }
75
-
76
57
  const filterNoTestnet = ({
77
58
  isTestnet
78
59
  }) => isTestnet === false;
@@ -89,13 +70,11 @@ const DEFAULT_VALUE = {
89
70
  evmNetworksWithoutTestnetsMap: {},
90
71
  tokensWithTestnetsMap: {},
91
72
  tokensWithoutTestnetsMap: {},
92
- balances: [],
93
- tokenRatesMap: {}
73
+ tokenRatesMap: {},
74
+ balances: []
94
75
  };
95
- const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, tokenRates) => {
96
- if (!chainsMap || !evmNetworksMap || !tokensMap || !allBalances
97
- // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
98
- /* || !tokenRates */) return DEFAULT_VALUE;
76
+ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, tokenRates, allBalances) => {
77
+ if (!chainsMap || !evmNetworksMap || !tokensMap || !tokenRates || !allBalances) return DEFAULT_VALUE;
99
78
 
100
79
  // BEGIN: temp hack to indicate that
101
80
  // - EVM GLMR is a mirror of substrate GLMR
@@ -123,6 +102,10 @@ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, t
123
102
  const tokensWithoutTestnets = tokensWithTestnets.filter(filterNoTestnet).filter(token => token.chain && chainsWithoutTestnetsMap[token.chain.id] || token.evmNetwork && evmNetworksWithoutTestnetsMap[token.evmNetwork.id]);
124
103
  const tokensWithTestnetsMap = Object.fromEntries(tokensWithTestnets.map(token => [token.id, token]));
125
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]));
126
109
 
127
110
  // return only balances for which we have a token
128
111
  const balances = allBalances.filter(b => tokensWithTestnetsMap[b.tokenId]);
@@ -139,8 +122,8 @@ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, t
139
122
  evmNetworksWithoutTestnetsMap,
140
123
  tokensWithTestnetsMap,
141
124
  tokensWithoutTestnetsMap,
142
- balances,
143
- tokenRatesMap: tokenRates ?? {}
125
+ tokenRatesMap,
126
+ balances
144
127
  };
145
128
  };
146
129
  const useDbCacheProvider = ({
@@ -150,25 +133,20 @@ const useDbCacheProvider = ({
150
133
  const chainList = useLiveQuery(() => chaindataProvider?.chains(), [chaindataProvider]);
151
134
  const evmNetworkList = useLiveQuery(() => chaindataProvider?.evmNetworks(), [chaindataProvider]);
152
135
  const tokenList = useLiveQuery(() => chaindataProvider?.tokens(), [chaindataProvider]);
153
- const rawBalances = useLiveQuery(() => db.balances.toArray(), []);
154
-
155
- // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
156
- const tokenRates = useTokenRates();
136
+ const tokenRates = useLiveQuery(() => db.tokenRates.toArray(), []);
137
+ const rawBalances = useLiveQuery(() => db$1.balances.toArray(), []);
157
138
  const [dbData, setDbData] = useState(DEFAULT_VALUE);
158
139
 
159
140
  // debounce every 500ms to prevent hammering UI with updates
160
141
  useDebounce(() => {
161
- setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, rawBalances, tokenRates));
142
+ setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates, rawBalances));
162
143
  }, 500, [chainList, evmNetworkList, tokenList, rawBalances, tokenRates, useTestnets]);
163
144
  const refInitialized = useRef(false);
164
145
 
165
146
  // force an update as soon as all datasources are fetched, so UI can display data ASAP
166
147
  useEffect(() => {
167
- if (!refInitialized.current && chainList && evmNetworkList && tokenList && rawBalances
168
- // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
169
- // && tokenRates
170
- ) {
171
- setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, rawBalances, tokenRates));
148
+ if (!refInitialized.current && chainList && evmNetworkList && tokenList && tokenRates && rawBalances) {
149
+ setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates, rawBalances));
172
150
  refInitialized.current = true;
173
151
  }
174
152
  }, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates, useTestnets]);
@@ -178,7 +156,7 @@ const [DbCacheProvider, useDbCache] = provideContext(useDbCacheProvider);
178
156
 
179
157
  var packageJson = {
180
158
  name: "@talismn/balances-react",
181
- version: "0.0.0-pr563-20230221230003",
159
+ version: "0.0.0-pr563-20230222052739",
182
160
  author: "Talisman",
183
161
  homepage: "https://talisman.xyz",
184
162
  license: "UNLICENSED",
@@ -308,14 +286,14 @@ function useChainConnectorsProvider(options) {
308
286
  }
309
287
  const [ChainConnectorsProvider, useChainConnectors] = provideContext(useChainConnectorsProvider);
310
288
 
311
- const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeBalances()];
289
+ const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeTokenRates(), useSubscribeBalances()];
312
290
  const [SubscriptionsProvider, useSubscriptions] = provideContext(useSubscriptionsProvider);
313
291
 
314
292
  /**
315
293
  * This hook is responsible for fetching the data used for balances and inserting it into the db.
316
294
  */
317
295
  const useDbCacheSubscription = subscribeTo => {
318
- const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeBalances] = useSubscriptions();
296
+ const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances] = useSubscriptions();
319
297
  useEffect(() => {
320
298
  switch (subscribeTo) {
321
299
  case "chains":
@@ -324,10 +302,12 @@ const useDbCacheSubscription = subscribeTo => {
324
302
  return subscribeHydrateEvmNetworks();
325
303
  case "tokens":
326
304
  return subscribeHydrateTokens();
305
+ case "tokenRates":
306
+ return subscribeTokenRates();
327
307
  case "balances":
328
308
  return subscribeBalances();
329
309
  }
330
- }, [subscribeBalances, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTo]);
310
+ }, [subscribeTo, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances]);
331
311
  };
332
312
  function useSubscribeChaindataHydrate(type) {
333
313
  const chaindata =
@@ -359,6 +339,49 @@ function useSubscribeChaindataHydrate(type) {
359
339
  const subscribe = useMulticastSubscription(createSubscription);
360
340
  return subscribe;
361
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
+ }
362
385
  function useSubscribeBalances() {
363
386
  const balanceModules = useBalanceModules();
364
387
  const chaindataProvider = useChaindata();
@@ -399,7 +422,7 @@ function useSubscribeBalances() {
399
422
  unsub.then(unsubscribe => unsubscribe());
400
423
 
401
424
  // set this subscription's balances in the store to status: cache
402
- 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 => {
403
426
  if (balance.source !== balanceModule.type) return false;
404
427
  if (!Object.keys(addressesByModuleToken).includes(balance.tokenId)) return false;
405
428
  if (!addressesByModuleToken[balance.tokenId].includes(balance.address)) return false;
@@ -414,7 +437,7 @@ function useSubscribeBalances() {
414
437
  id,
415
438
  ...balance
416
439
  }));
417
- 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));
418
441
  });
419
442
  return unsubscribe;
420
443
  });
@@ -453,6 +476,19 @@ function useEvmNetwork(evmNetworkId, withTestnets) {
453
476
  return evmNetworkId ? evmNetworks[evmNetworkId] : undefined;
454
477
  }
455
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
+
456
492
  function useTokens(withTestnets) {
457
493
  // keep db data up to date
458
494
  useDbCacheSubscription("tokens");
@@ -471,12 +507,7 @@ const useBalancesHydrate = withTestnets => {
471
507
  const chains = useChains(withTestnets);
472
508
  const evmNetworks = useEvmNetworks(withTestnets);
473
509
  const tokens = useTokens(withTestnets);
474
-
475
- // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
476
- // useDbCacheSubscription("tokenRates")
477
- const {
478
- tokenRatesMap: tokenRates
479
- } = useDbCache();
510
+ const tokenRates = useTokenRates();
480
511
  return useMemo(() => ({
481
512
  chains,
482
513
  evmNetworks,
@@ -538,4 +569,4 @@ const BalancesProvider = ({
538
569
  })
539
570
  });
540
571
 
541
- 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 };
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-20230221230003",
3
+ "version": "0.0.0-pr563-20230222052739",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "UNLICENSED",
@@ -26,12 +26,12 @@
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-20230221230003",
29
+ "@talismn/balances": "^0.0.0-pr563-20230222052739",
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
- "@talismn/token-rates": "^0.1.14",
34
+ "@talismn/token-rates": "^0.0.0-pr563-20230222052739",
35
35
  "anylogger": "^1.0.11",
36
36
  "dexie": "^3.2.2",
37
37
  "dexie-react-hooks": "^1.1.1",