@talismn/balances-react 0.0.0-pr642-20230322025617 → 0.0.0-pr644-20230322035559

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,6 +1,6 @@
1
1
  # @talismn/balances-react
2
2
 
3
- ## 0.0.0-pr642-20230322025617
3
+ ## 0.0.0-pr644-20230322035559
4
4
 
5
5
  ### Minor Changes
6
6
 
@@ -8,18 +8,21 @@
8
8
 
9
9
  ### Patch Changes
10
10
 
11
+ - 3068bd6: feat: stale balances and exponential rpc backoff
11
12
  - 6643a4e: fix: tokenRates in @talismn/balances-react
12
13
  - 6643a4e: fix: ported useDbCache related perf fixes to @talismn/balances-react
14
+ - Updated dependencies [3068bd6]
13
15
  - Updated dependencies [6643a4e]
14
16
  - Updated dependencies [79f6ccf]
15
17
  - Updated dependencies [6643a4e]
16
18
  - Updated dependencies [c24dc1f]
17
- - @talismn/token-rates@0.0.0-pr642-20230322025617
18
- - @talismn/balances@0.0.0-pr642-20230322025617
19
- - @talismn/chaindata-provider-extension@0.0.0-pr642-20230322025617
20
- - @talismn/chaindata-provider@0.0.0-pr642-20230322025617
21
- - @talismn/chain-connector@0.0.0-pr642-20230322025617
22
- - @talismn/chain-connector-evm@0.0.0-pr642-20230322025617
19
+ - @talismn/chain-connector@0.0.0-pr644-20230322035559
20
+ - @talismn/connection-meta@0.0.0-pr644-20230322035559
21
+ - @talismn/balances@0.0.0-pr644-20230322035559
22
+ - @talismn/token-rates@0.0.0-pr644-20230322035559
23
+ - @talismn/chaindata-provider-extension@0.0.0-pr644-20230322035559
24
+ - @talismn/chaindata-provider@0.0.0-pr644-20230322035559
25
+ - @talismn/chain-connector-evm@0.0.0-pr644-20230322035559
23
26
 
24
27
  ## 0.3.3
25
28
 
@@ -2,6 +2,7 @@ export * from "./useAllAddresses";
2
2
  export * from "./useBalanceModules";
3
3
  export * from "./useBalances";
4
4
  export * from "./useBalancesHydrate";
5
+ export * from "./useBalancesStatus";
5
6
  export * from "./useChainConnectors";
6
7
  export * from "./useChaindata";
7
8
  export * from "./useChains";
@@ -0,0 +1,19 @@
1
+ import { Balances } from "@talismn/balances";
2
+ export type BalancesStatus = {
3
+ status: "live";
4
+ } | {
5
+ status: "fetching";
6
+ } | {
7
+ status: "stale";
8
+ staleChains: string[];
9
+ };
10
+ /**
11
+ * Given a collection of `Balances`, this hook returns a `BalancesStatus` summary for the collection.
12
+ *
13
+ * @param balances The collection of balances to get the status from.
14
+ * @param isLoadingLocks Because the wallet currently fetches locks outside of the balances api, this param can be used to indicate that the locks are still loading, even if the `Balances` collection is not.
15
+ * @returns An instance of `BalancesStatus` which represents the status of the balances collection.
16
+
17
+ */
18
+ export declare const useBalancesStatus: (balances: Balances, isLoadingLocks?: boolean) => BalancesStatus;
19
+ export declare const getStaleChains: (balances: Balances) => string[];
@@ -6,6 +6,7 @@ var react = require('react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var chainConnector = require('@talismn/chain-connector');
8
8
  var chainConnectorEvm = require('@talismn/chain-connector-evm');
9
+ var connectionMeta = require('@talismn/connection-meta');
9
10
  var chaindataProviderExtension = require('@talismn/chaindata-provider-extension');
10
11
  var balances = require('@talismn/balances');
11
12
  var tokenRates = require('@talismn/token-rates');
@@ -73,7 +74,7 @@ function useChainConnectorsProvider(options) {
73
74
  const chaindata = useChaindata();
74
75
 
75
76
  // substrate connector
76
- const substrate = react.useMemo(() => new chainConnector.ChainConnector(chaindata), [chaindata]);
77
+ const substrate = react.useMemo(() => new chainConnector.ChainConnector(chaindata, connectionMeta.connectionMetaDb), [chaindata]);
77
78
 
78
79
  // evm connector
79
80
  const evm = react.useMemo(() => new chainConnectorEvm.ChainConnectorEvm(chaindata, {
@@ -199,7 +200,7 @@ const [DbCacheProvider, useDbCache] = provideContext(useDbCacheProvider);
199
200
 
200
201
  var packageJson = {
201
202
  name: "@talismn/balances-react",
202
- version: "0.0.0-pr642-20230322025617",
203
+ version: "0.0.0-pr644-20230322035559",
203
204
  author: "Talisman",
204
205
  homepage: "https://talisman.xyz",
205
206
  license: "UNLICENSED",
@@ -230,6 +231,7 @@ var packageJson = {
230
231
  "@talismn/chain-connector-evm": "workspace:^",
231
232
  "@talismn/chaindata-provider": "workspace:^",
232
233
  "@talismn/chaindata-provider-extension": "workspace:^",
234
+ "@talismn/connection-meta": "workspace:^",
233
235
  "@talismn/token-rates": "workspace:^",
234
236
  anylogger: "^1.0.11",
235
237
  "blueimp-md5": "2.19.0",
@@ -313,20 +315,6 @@ const useSharedSubscription = (key, subscribe) => {
313
315
  }, [key, subscribe]);
314
316
  };
315
317
 
316
- function useTokens(withTestnets) {
317
- // keep db data up to date
318
- useDbCacheSubscription("tokens");
319
- const {
320
- tokensWithTestnetsMap,
321
- tokensWithoutTestnetsMap
322
- } = useDbCache();
323
- return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
324
- }
325
- function useToken(tokenId, withTestnets) {
326
- const tokens = useTokens(withTestnets);
327
- return tokenId ? tokens[tokenId] : undefined;
328
- }
329
-
330
318
  /**
331
319
  * Creates a subscription function that can be used to subscribe to a multicast observable created from an upstream source.
332
320
  *
@@ -404,7 +392,7 @@ function useDbCacheTokenRatesSubscription() {
404
392
  const {
405
393
  withTestnets
406
394
  } = useWithTestnets();
407
- const tokens = useTokens(withTestnets);
395
+ const tokens = useTokens$1(withTestnets);
408
396
  const subscriptionKey = react.useMemo(
409
397
  // not super sexy but we need key to change based on this stuff
410
398
  () => {
@@ -431,7 +419,7 @@ function useDbCacheBalancesSubscription() {
431
419
  const chaindataProvider = useChaindata();
432
420
  const chainConnectors = useChainConnectors();
433
421
  const [allAddresses] = useAllAddresses();
434
- const tokens = useTokens(withTestnets);
422
+ const tokens = useTokens$1(withTestnets);
435
423
  const subscriptionKey = react.useMemo(
436
424
  // not super sexy but we need key to change based on this stuff
437
425
  () => {
@@ -442,10 +430,19 @@ function useDbCacheBalancesSubscription() {
442
430
  }, [allAddresses, balanceModules, chainConnectors, chaindataProvider, tokens]);
443
431
  const subscription = react.useCallback(() => {
444
432
  if (!Object.values(tokens ?? {}).length || !allAddresses.length) return () => {};
445
- return subscribeBalances(tokens ?? {}, allAddresses, chainConnectors, chaindataProvider, balanceModules);
446
- }, [allAddresses, balanceModules, chainConnectors, chaindataProvider, tokens]);
433
+ return subscribeBalances(tokens ?? {}, allAddresses, balanceModules);
434
+ }, [allAddresses, balanceModules, tokens]);
447
435
  useSharedSubscription(subscriptionKey, subscription);
448
436
  }
437
+
438
+ // subscriptionless version of useTokens, prevents circular dependency
439
+ const useTokens$1 = withTestnets => {
440
+ const {
441
+ tokensWithTestnetsMap,
442
+ tokensWithoutTestnetsMap
443
+ } = useDbCache();
444
+ return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
445
+ };
449
446
  const subscribeChainDataHydrate = (provider, type) => {
450
447
  const chaindata = provider;
451
448
  const delay = 300_000; // 300_000ms = 300s = 5 minutes
@@ -501,7 +498,7 @@ const subscribeTokenRates = tokens => {
501
498
  if (timeout) clearTimeout(timeout);
502
499
  };
503
500
  };
504
- const subscribeBalances = (tokens, addresses, chainConnectors, provider, balanceModules) => {
501
+ const subscribeBalances = (tokens, addresses, balanceModules) => {
505
502
  const tokenIds = Object.values(tokens).map(({
506
503
  id
507
504
  }) => id);
@@ -525,28 +522,41 @@ const subscribeBalances = (tokens, addresses, chainConnectors, provider, balance
525
522
  id
526
523
  }) => id);
527
524
  const addressesByModuleToken = Object.fromEntries(Object.entries(addressesByToken).filter(([tokenId]) => moduleTokenIds.includes(tokenId)));
528
- const unsub = balances.balances(balanceModule, addressesByModuleToken, (error, balances) => {
525
+ const unsub = balances.balances(balanceModule, addressesByModuleToken, (error, balances$1) => {
529
526
  // log errors
530
- if (error) return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
527
+ if (error) {
528
+ if (error?.type === "STALE_RPC_ERROR") return balances.db.balances.where({
529
+ source: balanceModule.type,
530
+ chainId: error.chainId
531
+ }).filter(balance => {
532
+ if (!Object.keys(addressesByModuleToken).includes(balance.tokenId)) return false;
533
+ if (!addressesByModuleToken[balance.tokenId].includes(balance.address)) return false;
534
+ return true;
535
+ }).modify({
536
+ status: "stale"
537
+ });
538
+ return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
539
+ }
531
540
  // ignore empty balance responses
532
- if (!balances) return;
541
+ if (!balances$1) return;
533
542
  // ignore balances from old subscriptions which are still in the process of unsubscribing
534
543
  if (unsubscribed) return;
535
- updateDb(balances);
544
+ updateDb(balances$1);
536
545
  });
537
546
  return () => {
538
547
  // wait 2 seconds before actually unsubscribing, allowing for websocket to be reused
539
548
  unsub.then(unsubscribe => {
540
549
  setTimeout(unsubscribe, 2_000);
541
550
  });
542
- balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.filter(balance => {
543
- if (balance.source !== balanceModule.type) return false;
551
+ balances.db.balances.where({
552
+ source: balanceModule.type
553
+ }).filter(balance => {
544
554
  if (!Object.keys(addressesByModuleToken).includes(balance.tokenId)) return false;
545
555
  if (!addressesByModuleToken[balance.tokenId].includes(balance.address)) return false;
546
556
  return true;
547
557
  }).modify({
548
558
  status: "cache"
549
- }));
559
+ });
550
560
  };
551
561
  });
552
562
  const unsubscribeAll = () => {
@@ -597,6 +607,20 @@ function useTokenRate(tokenId) {
597
607
  return tokenId ? tokenRates[tokenId] : undefined;
598
608
  }
599
609
 
610
+ function useTokens(withTestnets) {
611
+ // keep db data up to date
612
+ useDbCacheSubscription("tokens");
613
+ const {
614
+ tokensWithTestnetsMap,
615
+ tokensWithoutTestnetsMap
616
+ } = useDbCache();
617
+ return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
618
+ }
619
+ function useToken(tokenId, withTestnets) {
620
+ const tokens = useTokens(withTestnets);
621
+ return tokenId ? tokens[tokenId] : undefined;
622
+ }
623
+
600
624
  const useBalancesHydrate = () => {
601
625
  const {
602
626
  withTestnets
@@ -643,6 +667,35 @@ function useBalances(addressesByToken) {
643
667
  hydrate), [balances$1, hydrate, balanceModules, addressesByToken]);
644
668
  }
645
669
 
670
+ /**
671
+ * Given a collection of `Balances`, this hook returns a `BalancesStatus` summary for the collection.
672
+ *
673
+ * @param balances The collection of balances to get the status from.
674
+ * @param isLoadingLocks Because the wallet currently fetches locks outside of the balances api, this param can be used to indicate that the locks are still loading, even if the `Balances` collection is not.
675
+ * @returns An instance of `BalancesStatus` which represents the status of the balances collection.
676
+
677
+ */
678
+ const useBalancesStatus = (balances, isLoadingLocks) => react.useMemo(() => {
679
+ // stale
680
+ const staleChains = getStaleChains(balances);
681
+ if (staleChains.length > 0) return {
682
+ status: "stale",
683
+ staleChains
684
+ };
685
+
686
+ // fetching
687
+ const hasCachedBalances = balances.each.some(b => b.status === "cache");
688
+ if (hasCachedBalances || isLoadingLocks) return {
689
+ status: "fetching"
690
+ };
691
+
692
+ // live
693
+ return {
694
+ status: "live"
695
+ };
696
+ }, [balances, isLoadingLocks]);
697
+ const getStaleChains = balances => [...new Set(balances.sorted.filter(b => b.status === "stale").map(b => b.chain?.name ?? b.chainId ?? "Unknown"))];
698
+
646
699
  const BalancesProvider = ({
647
700
  balanceModules,
648
701
  onfinalityApiKey,
@@ -674,11 +727,13 @@ exports.ChaindataProvider = ChaindataProvider;
674
727
  exports.DbCacheProvider = DbCacheProvider;
675
728
  exports.WithTestnetsProvider = WithTestnetsProvider;
676
729
  exports.createMulticastSubscription = createMulticastSubscription;
730
+ exports.getStaleChains = getStaleChains;
677
731
  exports.provideContext = provideContext;
678
732
  exports.useAllAddresses = useAllAddresses;
679
733
  exports.useBalanceModules = useBalanceModules;
680
734
  exports.useBalances = useBalances;
681
735
  exports.useBalancesHydrate = useBalancesHydrate;
736
+ exports.useBalancesStatus = useBalancesStatus;
682
737
  exports.useChain = useChain;
683
738
  exports.useChainConnectors = useChainConnectors;
684
739
  exports.useChaindata = useChaindata;
@@ -6,6 +6,7 @@ var react = require('react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var chainConnector = require('@talismn/chain-connector');
8
8
  var chainConnectorEvm = require('@talismn/chain-connector-evm');
9
+ var connectionMeta = require('@talismn/connection-meta');
9
10
  var chaindataProviderExtension = require('@talismn/chaindata-provider-extension');
10
11
  var balances = require('@talismn/balances');
11
12
  var tokenRates = require('@talismn/token-rates');
@@ -73,7 +74,7 @@ function useChainConnectorsProvider(options) {
73
74
  const chaindata = useChaindata();
74
75
 
75
76
  // substrate connector
76
- const substrate = react.useMemo(() => new chainConnector.ChainConnector(chaindata), [chaindata]);
77
+ const substrate = react.useMemo(() => new chainConnector.ChainConnector(chaindata, connectionMeta.connectionMetaDb), [chaindata]);
77
78
 
78
79
  // evm connector
79
80
  const evm = react.useMemo(() => new chainConnectorEvm.ChainConnectorEvm(chaindata, {
@@ -199,7 +200,7 @@ const [DbCacheProvider, useDbCache] = provideContext(useDbCacheProvider);
199
200
 
200
201
  var packageJson = {
201
202
  name: "@talismn/balances-react",
202
- version: "0.0.0-pr642-20230322025617",
203
+ version: "0.0.0-pr644-20230322035559",
203
204
  author: "Talisman",
204
205
  homepage: "https://talisman.xyz",
205
206
  license: "UNLICENSED",
@@ -230,6 +231,7 @@ var packageJson = {
230
231
  "@talismn/chain-connector-evm": "workspace:^",
231
232
  "@talismn/chaindata-provider": "workspace:^",
232
233
  "@talismn/chaindata-provider-extension": "workspace:^",
234
+ "@talismn/connection-meta": "workspace:^",
233
235
  "@talismn/token-rates": "workspace:^",
234
236
  anylogger: "^1.0.11",
235
237
  "blueimp-md5": "2.19.0",
@@ -313,20 +315,6 @@ const useSharedSubscription = (key, subscribe) => {
313
315
  }, [key, subscribe]);
314
316
  };
315
317
 
316
- function useTokens(withTestnets) {
317
- // keep db data up to date
318
- useDbCacheSubscription("tokens");
319
- const {
320
- tokensWithTestnetsMap,
321
- tokensWithoutTestnetsMap
322
- } = useDbCache();
323
- return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
324
- }
325
- function useToken(tokenId, withTestnets) {
326
- const tokens = useTokens(withTestnets);
327
- return tokenId ? tokens[tokenId] : undefined;
328
- }
329
-
330
318
  /**
331
319
  * Creates a subscription function that can be used to subscribe to a multicast observable created from an upstream source.
332
320
  *
@@ -404,7 +392,7 @@ function useDbCacheTokenRatesSubscription() {
404
392
  const {
405
393
  withTestnets
406
394
  } = useWithTestnets();
407
- const tokens = useTokens(withTestnets);
395
+ const tokens = useTokens$1(withTestnets);
408
396
  const subscriptionKey = react.useMemo(
409
397
  // not super sexy but we need key to change based on this stuff
410
398
  () => {
@@ -431,7 +419,7 @@ function useDbCacheBalancesSubscription() {
431
419
  const chaindataProvider = useChaindata();
432
420
  const chainConnectors = useChainConnectors();
433
421
  const [allAddresses] = useAllAddresses();
434
- const tokens = useTokens(withTestnets);
422
+ const tokens = useTokens$1(withTestnets);
435
423
  const subscriptionKey = react.useMemo(
436
424
  // not super sexy but we need key to change based on this stuff
437
425
  () => {
@@ -442,10 +430,19 @@ function useDbCacheBalancesSubscription() {
442
430
  }, [allAddresses, balanceModules, chainConnectors, chaindataProvider, tokens]);
443
431
  const subscription = react.useCallback(() => {
444
432
  if (!Object.values(tokens ?? {}).length || !allAddresses.length) return () => {};
445
- return subscribeBalances(tokens ?? {}, allAddresses, chainConnectors, chaindataProvider, balanceModules);
446
- }, [allAddresses, balanceModules, chainConnectors, chaindataProvider, tokens]);
433
+ return subscribeBalances(tokens ?? {}, allAddresses, balanceModules);
434
+ }, [allAddresses, balanceModules, tokens]);
447
435
  useSharedSubscription(subscriptionKey, subscription);
448
436
  }
437
+
438
+ // subscriptionless version of useTokens, prevents circular dependency
439
+ const useTokens$1 = withTestnets => {
440
+ const {
441
+ tokensWithTestnetsMap,
442
+ tokensWithoutTestnetsMap
443
+ } = useDbCache();
444
+ return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
445
+ };
449
446
  const subscribeChainDataHydrate = (provider, type) => {
450
447
  const chaindata = provider;
451
448
  const delay = 300_000; // 300_000ms = 300s = 5 minutes
@@ -501,7 +498,7 @@ const subscribeTokenRates = tokens => {
501
498
  if (timeout) clearTimeout(timeout);
502
499
  };
503
500
  };
504
- const subscribeBalances = (tokens, addresses, chainConnectors, provider, balanceModules) => {
501
+ const subscribeBalances = (tokens, addresses, balanceModules) => {
505
502
  const tokenIds = Object.values(tokens).map(({
506
503
  id
507
504
  }) => id);
@@ -525,28 +522,41 @@ const subscribeBalances = (tokens, addresses, chainConnectors, provider, balance
525
522
  id
526
523
  }) => id);
527
524
  const addressesByModuleToken = Object.fromEntries(Object.entries(addressesByToken).filter(([tokenId]) => moduleTokenIds.includes(tokenId)));
528
- const unsub = balances.balances(balanceModule, addressesByModuleToken, (error, balances) => {
525
+ const unsub = balances.balances(balanceModule, addressesByModuleToken, (error, balances$1) => {
529
526
  // log errors
530
- if (error) return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
527
+ if (error) {
528
+ if (error?.type === "STALE_RPC_ERROR") return balances.db.balances.where({
529
+ source: balanceModule.type,
530
+ chainId: error.chainId
531
+ }).filter(balance => {
532
+ if (!Object.keys(addressesByModuleToken).includes(balance.tokenId)) return false;
533
+ if (!addressesByModuleToken[balance.tokenId].includes(balance.address)) return false;
534
+ return true;
535
+ }).modify({
536
+ status: "stale"
537
+ });
538
+ return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
539
+ }
531
540
  // ignore empty balance responses
532
- if (!balances) return;
541
+ if (!balances$1) return;
533
542
  // ignore balances from old subscriptions which are still in the process of unsubscribing
534
543
  if (unsubscribed) return;
535
- updateDb(balances);
544
+ updateDb(balances$1);
536
545
  });
537
546
  return () => {
538
547
  // wait 2 seconds before actually unsubscribing, allowing for websocket to be reused
539
548
  unsub.then(unsubscribe => {
540
549
  setTimeout(unsubscribe, 2_000);
541
550
  });
542
- balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.filter(balance => {
543
- if (balance.source !== balanceModule.type) return false;
551
+ balances.db.balances.where({
552
+ source: balanceModule.type
553
+ }).filter(balance => {
544
554
  if (!Object.keys(addressesByModuleToken).includes(balance.tokenId)) return false;
545
555
  if (!addressesByModuleToken[balance.tokenId].includes(balance.address)) return false;
546
556
  return true;
547
557
  }).modify({
548
558
  status: "cache"
549
- }));
559
+ });
550
560
  };
551
561
  });
552
562
  const unsubscribeAll = () => {
@@ -597,6 +607,20 @@ function useTokenRate(tokenId) {
597
607
  return tokenId ? tokenRates[tokenId] : undefined;
598
608
  }
599
609
 
610
+ function useTokens(withTestnets) {
611
+ // keep db data up to date
612
+ useDbCacheSubscription("tokens");
613
+ const {
614
+ tokensWithTestnetsMap,
615
+ tokensWithoutTestnetsMap
616
+ } = useDbCache();
617
+ return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
618
+ }
619
+ function useToken(tokenId, withTestnets) {
620
+ const tokens = useTokens(withTestnets);
621
+ return tokenId ? tokens[tokenId] : undefined;
622
+ }
623
+
600
624
  const useBalancesHydrate = () => {
601
625
  const {
602
626
  withTestnets
@@ -643,6 +667,35 @@ function useBalances(addressesByToken) {
643
667
  hydrate), [balances$1, hydrate, balanceModules, addressesByToken]);
644
668
  }
645
669
 
670
+ /**
671
+ * Given a collection of `Balances`, this hook returns a `BalancesStatus` summary for the collection.
672
+ *
673
+ * @param balances The collection of balances to get the status from.
674
+ * @param isLoadingLocks Because the wallet currently fetches locks outside of the balances api, this param can be used to indicate that the locks are still loading, even if the `Balances` collection is not.
675
+ * @returns An instance of `BalancesStatus` which represents the status of the balances collection.
676
+
677
+ */
678
+ const useBalancesStatus = (balances, isLoadingLocks) => react.useMemo(() => {
679
+ // stale
680
+ const staleChains = getStaleChains(balances);
681
+ if (staleChains.length > 0) return {
682
+ status: "stale",
683
+ staleChains
684
+ };
685
+
686
+ // fetching
687
+ const hasCachedBalances = balances.each.some(b => b.status === "cache");
688
+ if (hasCachedBalances || isLoadingLocks) return {
689
+ status: "fetching"
690
+ };
691
+
692
+ // live
693
+ return {
694
+ status: "live"
695
+ };
696
+ }, [balances, isLoadingLocks]);
697
+ const getStaleChains = balances => [...new Set(balances.sorted.filter(b => b.status === "stale").map(b => b.chain?.name ?? b.chainId ?? "Unknown"))];
698
+
646
699
  const BalancesProvider = ({
647
700
  balanceModules,
648
701
  onfinalityApiKey,
@@ -674,11 +727,13 @@ exports.ChaindataProvider = ChaindataProvider;
674
727
  exports.DbCacheProvider = DbCacheProvider;
675
728
  exports.WithTestnetsProvider = WithTestnetsProvider;
676
729
  exports.createMulticastSubscription = createMulticastSubscription;
730
+ exports.getStaleChains = getStaleChains;
677
731
  exports.provideContext = provideContext;
678
732
  exports.useAllAddresses = useAllAddresses;
679
733
  exports.useBalanceModules = useBalanceModules;
680
734
  exports.useBalances = useBalances;
681
735
  exports.useBalancesHydrate = useBalancesHydrate;
736
+ exports.useBalancesStatus = useBalancesStatus;
682
737
  exports.useChain = useChain;
683
738
  exports.useChainConnectors = useChainConnectors;
684
739
  exports.useChaindata = useChaindata;
@@ -2,6 +2,7 @@ import { useContext, createContext, useState, useEffect, useMemo, useRef, useCal
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
  import { ChainConnector } from '@talismn/chain-connector';
4
4
  import { ChainConnectorEvm } from '@talismn/chain-connector-evm';
5
+ import { connectionMetaDb } from '@talismn/connection-meta';
5
6
  import { ChaindataProviderExtension } from '@talismn/chaindata-provider-extension';
6
7
  import { db as db$1, balances, Balances } from '@talismn/balances';
7
8
  import { db, fetchTokenRates } from '@talismn/token-rates';
@@ -64,7 +65,7 @@ function useChainConnectorsProvider(options) {
64
65
  const chaindata = useChaindata();
65
66
 
66
67
  // substrate connector
67
- const substrate = useMemo(() => new ChainConnector(chaindata), [chaindata]);
68
+ const substrate = useMemo(() => new ChainConnector(chaindata, connectionMetaDb), [chaindata]);
68
69
 
69
70
  // evm connector
70
71
  const evm = useMemo(() => new ChainConnectorEvm(chaindata, {
@@ -190,7 +191,7 @@ const [DbCacheProvider, useDbCache] = provideContext(useDbCacheProvider);
190
191
 
191
192
  var packageJson = {
192
193
  name: "@talismn/balances-react",
193
- version: "0.0.0-pr642-20230322025617",
194
+ version: "0.0.0-pr644-20230322035559",
194
195
  author: "Talisman",
195
196
  homepage: "https://talisman.xyz",
196
197
  license: "UNLICENSED",
@@ -221,6 +222,7 @@ var packageJson = {
221
222
  "@talismn/chain-connector-evm": "workspace:^",
222
223
  "@talismn/chaindata-provider": "workspace:^",
223
224
  "@talismn/chaindata-provider-extension": "workspace:^",
225
+ "@talismn/connection-meta": "workspace:^",
224
226
  "@talismn/token-rates": "workspace:^",
225
227
  anylogger: "^1.0.11",
226
228
  "blueimp-md5": "2.19.0",
@@ -304,20 +306,6 @@ const useSharedSubscription = (key, subscribe) => {
304
306
  }, [key, subscribe]);
305
307
  };
306
308
 
307
- function useTokens(withTestnets) {
308
- // keep db data up to date
309
- useDbCacheSubscription("tokens");
310
- const {
311
- tokensWithTestnetsMap,
312
- tokensWithoutTestnetsMap
313
- } = useDbCache();
314
- return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
315
- }
316
- function useToken(tokenId, withTestnets) {
317
- const tokens = useTokens(withTestnets);
318
- return tokenId ? tokens[tokenId] : undefined;
319
- }
320
-
321
309
  /**
322
310
  * Creates a subscription function that can be used to subscribe to a multicast observable created from an upstream source.
323
311
  *
@@ -395,7 +383,7 @@ function useDbCacheTokenRatesSubscription() {
395
383
  const {
396
384
  withTestnets
397
385
  } = useWithTestnets();
398
- const tokens = useTokens(withTestnets);
386
+ const tokens = useTokens$1(withTestnets);
399
387
  const subscriptionKey = useMemo(
400
388
  // not super sexy but we need key to change based on this stuff
401
389
  () => {
@@ -422,7 +410,7 @@ function useDbCacheBalancesSubscription() {
422
410
  const chaindataProvider = useChaindata();
423
411
  const chainConnectors = useChainConnectors();
424
412
  const [allAddresses] = useAllAddresses();
425
- const tokens = useTokens(withTestnets);
413
+ const tokens = useTokens$1(withTestnets);
426
414
  const subscriptionKey = useMemo(
427
415
  // not super sexy but we need key to change based on this stuff
428
416
  () => {
@@ -433,10 +421,19 @@ function useDbCacheBalancesSubscription() {
433
421
  }, [allAddresses, balanceModules, chainConnectors, chaindataProvider, tokens]);
434
422
  const subscription = useCallback(() => {
435
423
  if (!Object.values(tokens ?? {}).length || !allAddresses.length) return () => {};
436
- return subscribeBalances(tokens ?? {}, allAddresses, chainConnectors, chaindataProvider, balanceModules);
437
- }, [allAddresses, balanceModules, chainConnectors, chaindataProvider, tokens]);
424
+ return subscribeBalances(tokens ?? {}, allAddresses, balanceModules);
425
+ }, [allAddresses, balanceModules, tokens]);
438
426
  useSharedSubscription(subscriptionKey, subscription);
439
427
  }
428
+
429
+ // subscriptionless version of useTokens, prevents circular dependency
430
+ const useTokens$1 = withTestnets => {
431
+ const {
432
+ tokensWithTestnetsMap,
433
+ tokensWithoutTestnetsMap
434
+ } = useDbCache();
435
+ return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
436
+ };
440
437
  const subscribeChainDataHydrate = (provider, type) => {
441
438
  const chaindata = provider;
442
439
  const delay = 300_000; // 300_000ms = 300s = 5 minutes
@@ -492,7 +489,7 @@ const subscribeTokenRates = tokens => {
492
489
  if (timeout) clearTimeout(timeout);
493
490
  };
494
491
  };
495
- const subscribeBalances = (tokens, addresses, chainConnectors, provider, balanceModules) => {
492
+ const subscribeBalances = (tokens, addresses, balanceModules) => {
496
493
  const tokenIds = Object.values(tokens).map(({
497
494
  id
498
495
  }) => id);
@@ -518,7 +515,19 @@ const subscribeBalances = (tokens, addresses, chainConnectors, provider, balance
518
515
  const addressesByModuleToken = Object.fromEntries(Object.entries(addressesByToken).filter(([tokenId]) => moduleTokenIds.includes(tokenId)));
519
516
  const unsub = balances(balanceModule, addressesByModuleToken, (error, balances) => {
520
517
  // log errors
521
- if (error) return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
518
+ if (error) {
519
+ if (error?.type === "STALE_RPC_ERROR") return db$1.balances.where({
520
+ source: balanceModule.type,
521
+ chainId: error.chainId
522
+ }).filter(balance => {
523
+ if (!Object.keys(addressesByModuleToken).includes(balance.tokenId)) return false;
524
+ if (!addressesByModuleToken[balance.tokenId].includes(balance.address)) return false;
525
+ return true;
526
+ }).modify({
527
+ status: "stale"
528
+ });
529
+ return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
530
+ }
522
531
  // ignore empty balance responses
523
532
  if (!balances) return;
524
533
  // ignore balances from old subscriptions which are still in the process of unsubscribing
@@ -530,14 +539,15 @@ const subscribeBalances = (tokens, addresses, chainConnectors, provider, balance
530
539
  unsub.then(unsubscribe => {
531
540
  setTimeout(unsubscribe, 2_000);
532
541
  });
533
- db$1.transaction("rw", db$1.balances, async () => await db$1.balances.filter(balance => {
534
- if (balance.source !== balanceModule.type) return false;
542
+ db$1.balances.where({
543
+ source: balanceModule.type
544
+ }).filter(balance => {
535
545
  if (!Object.keys(addressesByModuleToken).includes(balance.tokenId)) return false;
536
546
  if (!addressesByModuleToken[balance.tokenId].includes(balance.address)) return false;
537
547
  return true;
538
548
  }).modify({
539
549
  status: "cache"
540
- }));
550
+ });
541
551
  };
542
552
  });
543
553
  const unsubscribeAll = () => {
@@ -588,6 +598,20 @@ function useTokenRate(tokenId) {
588
598
  return tokenId ? tokenRates[tokenId] : undefined;
589
599
  }
590
600
 
601
+ function useTokens(withTestnets) {
602
+ // keep db data up to date
603
+ useDbCacheSubscription("tokens");
604
+ const {
605
+ tokensWithTestnetsMap,
606
+ tokensWithoutTestnetsMap
607
+ } = useDbCache();
608
+ return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
609
+ }
610
+ function useToken(tokenId, withTestnets) {
611
+ const tokens = useTokens(withTestnets);
612
+ return tokenId ? tokens[tokenId] : undefined;
613
+ }
614
+
591
615
  const useBalancesHydrate = () => {
592
616
  const {
593
617
  withTestnets
@@ -634,6 +658,35 @@ function useBalances(addressesByToken) {
634
658
  hydrate), [balances, hydrate, balanceModules, addressesByToken]);
635
659
  }
636
660
 
661
+ /**
662
+ * Given a collection of `Balances`, this hook returns a `BalancesStatus` summary for the collection.
663
+ *
664
+ * @param balances The collection of balances to get the status from.
665
+ * @param isLoadingLocks Because the wallet currently fetches locks outside of the balances api, this param can be used to indicate that the locks are still loading, even if the `Balances` collection is not.
666
+ * @returns An instance of `BalancesStatus` which represents the status of the balances collection.
667
+
668
+ */
669
+ const useBalancesStatus = (balances, isLoadingLocks) => useMemo(() => {
670
+ // stale
671
+ const staleChains = getStaleChains(balances);
672
+ if (staleChains.length > 0) return {
673
+ status: "stale",
674
+ staleChains
675
+ };
676
+
677
+ // fetching
678
+ const hasCachedBalances = balances.each.some(b => b.status === "cache");
679
+ if (hasCachedBalances || isLoadingLocks) return {
680
+ status: "fetching"
681
+ };
682
+
683
+ // live
684
+ return {
685
+ status: "live"
686
+ };
687
+ }, [balances, isLoadingLocks]);
688
+ const getStaleChains = balances => [...new Set(balances.sorted.filter(b => b.status === "stale").map(b => b.chain?.name ?? b.chainId ?? "Unknown"))];
689
+
637
690
  const BalancesProvider = ({
638
691
  balanceModules,
639
692
  onfinalityApiKey,
@@ -657,4 +710,4 @@ const BalancesProvider = ({
657
710
  })
658
711
  });
659
712
 
660
- export { AllAddressesProvider, BalanceModulesProvider, BalancesProvider, ChainConnectorsProvider, ChaindataProvider, DbCacheProvider, WithTestnetsProvider, createMulticastSubscription, provideContext, useAllAddresses, useBalanceModules, useBalances, useBalancesHydrate, useChain, useChainConnectors, useChaindata, useChains, useDbCache, useDbCacheBalancesSubscription, useDbCacheSubscription, useDbCacheTokenRatesSubscription, useEvmNetwork, useEvmNetworks, useMulticastSubscription, useToken, useTokenRate, useTokenRates, useTokens, useWithTestnets };
713
+ export { AllAddressesProvider, BalanceModulesProvider, BalancesProvider, ChainConnectorsProvider, ChaindataProvider, DbCacheProvider, WithTestnetsProvider, createMulticastSubscription, getStaleChains, provideContext, useAllAddresses, useBalanceModules, useBalances, useBalancesHydrate, useBalancesStatus, useChain, useChainConnectors, useChaindata, useChains, useDbCache, useDbCacheBalancesSubscription, useDbCacheSubscription, useDbCacheTokenRatesSubscription, useEvmNetwork, useEvmNetworks, useMulticastSubscription, useToken, useTokenRate, useTokenRates, useTokens, useWithTestnets };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/balances-react",
3
- "version": "0.0.0-pr642-20230322025617",
3
+ "version": "0.0.0-pr644-20230322035559",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "UNLICENSED",
@@ -26,12 +26,13 @@
26
26
  "clean": "rm -rf dist && rm -rf .turbo rm -rf node_modules"
27
27
  },
28
28
  "dependencies": {
29
- "@talismn/balances": "^0.0.0-pr642-20230322025617",
30
- "@talismn/chain-connector": "^0.0.0-pr642-20230322025617",
31
- "@talismn/chain-connector-evm": "^0.0.0-pr642-20230322025617",
32
- "@talismn/chaindata-provider": "^0.0.0-pr642-20230322025617",
33
- "@talismn/chaindata-provider-extension": "^0.0.0-pr642-20230322025617",
34
- "@talismn/token-rates": "^0.0.0-pr642-20230322025617",
29
+ "@talismn/balances": "^0.0.0-pr644-20230322035559",
30
+ "@talismn/chain-connector": "^0.0.0-pr644-20230322035559",
31
+ "@talismn/chain-connector-evm": "^0.0.0-pr644-20230322035559",
32
+ "@talismn/chaindata-provider": "^0.0.0-pr644-20230322035559",
33
+ "@talismn/chaindata-provider-extension": "^0.0.0-pr644-20230322035559",
34
+ "@talismn/connection-meta": "^0.0.0-pr644-20230322035559",
35
+ "@talismn/token-rates": "^0.0.0-pr644-20230322035559",
35
36
  "anylogger": "^1.0.11",
36
37
  "blueimp-md5": "2.19.0",
37
38
  "dexie": "^3.2.3",