@talismn/balances-react 0.0.0-pr587-20230302015709 → 0.0.0-pr589-20230302073822

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.
@@ -9,7 +9,6 @@ var tokenRates = require('@talismn/token-rates');
9
9
  var dexieReactHooks = require('dexie-react-hooks');
10
10
  var reactUse = require('react-use');
11
11
  var chaindataProviderExtension = require('@talismn/chaindata-provider-extension');
12
- var md5 = require('blueimp-md5');
13
12
  var anylogger = require('anylogger');
14
13
  var rxjs = require('rxjs');
15
14
  var chainConnector = require('@talismn/chain-connector');
@@ -17,7 +16,6 @@ var chainConnectorEvm = require('@talismn/chain-connector-evm');
17
16
 
18
17
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
19
18
 
20
- var md5__default = /*#__PURE__*/_interopDefault(md5);
21
19
  var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
22
20
 
23
21
  const provideContext = useProviderContext => {
@@ -54,15 +52,13 @@ const useBalanceModulesProvider = ({
54
52
  const [BalanceModulesProvider, useBalanceModules] = provideContext(useBalanceModulesProvider);
55
53
 
56
54
  function useChaindataProvider(options = {}) {
57
- const [onfinalityApiKey, setOnfinalityApiKey] = react.useState(options.onfinalityApiKey);
58
-
59
- // make sure we recreate provider only when the onfinalityApiKey changes
55
+ const [chaindata, setChaindata] = react.useState();
60
56
  react.useEffect(() => {
61
- if (options.onfinalityApiKey !== onfinalityApiKey) setOnfinalityApiKey(options.onfinalityApiKey);
62
- }, [options.onfinalityApiKey, onfinalityApiKey]);
63
- return react.useMemo(() => new chaindataProviderExtension.ChaindataProviderExtension({
64
- onfinalityApiKey
65
- }), [onfinalityApiKey]);
57
+ setChaindata(new chaindataProviderExtension.ChaindataProviderExtension({
58
+ onfinalityApiKey: options.onfinalityApiKey
59
+ }));
60
+ }, [options.onfinalityApiKey]);
61
+ return chaindata;
66
62
  }
67
63
  const [ChaindataProvider, useChaindata] = provideContext(useChaindataProvider);
68
64
 
@@ -138,7 +134,9 @@ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, tokenRates, al
138
134
  balances
139
135
  };
140
136
  };
141
- const useDbCacheProvider = () => {
137
+ const useDbCacheProvider = ({
138
+ useTestnets = false
139
+ }) => {
142
140
  const chaindataProvider = useChaindata();
143
141
  const chainList = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.chains(), [chaindataProvider]);
144
142
  const evmNetworkList = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.evmNetworks(), [chaindataProvider]);
@@ -150,7 +148,7 @@ const useDbCacheProvider = () => {
150
148
  // debounce every 500ms to prevent hammering UI with updates
151
149
  reactUse.useDebounce(() => {
152
150
  setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates$1, rawBalances));
153
- }, 500, [chainList, evmNetworkList, tokenList, rawBalances, tokenRates$1]);
151
+ }, 500, [chainList, evmNetworkList, tokenList, rawBalances, tokenRates$1, useTestnets]);
154
152
  const refInitialized = react.useRef(false);
155
153
 
156
154
  // force an update as soon as all datasources are fetched, so UI can display data ASAP
@@ -159,14 +157,14 @@ const useDbCacheProvider = () => {
159
157
  setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates$1, rawBalances));
160
158
  refInitialized.current = true;
161
159
  }
162
- }, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates$1]);
160
+ }, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates$1, useTestnets]);
163
161
  return dbData;
164
162
  };
165
163
  const [DbCacheProvider, useDbCache] = provideContext(useDbCacheProvider);
166
164
 
167
165
  var packageJson = {
168
166
  name: "@talismn/balances-react",
169
- version: "0.0.0-pr587-20230302015709",
167
+ version: "0.0.0-pr589-20230302073822",
170
168
  author: "Talisman",
171
169
  homepage: "https://talisman.xyz",
172
170
  license: "UNLICENSED",
@@ -199,7 +197,6 @@ var packageJson = {
199
197
  "@talismn/chaindata-provider-extension": "workspace:^",
200
198
  "@talismn/token-rates": "workspace:^",
201
199
  anylogger: "^1.0.11",
202
- "blueimp-md5": "2.19.0",
203
200
  dexie: "^3.2.3",
204
201
  "dexie-react-hooks": "^1.1.1",
205
202
  "react-use": "^17.4.0",
@@ -230,95 +227,6 @@ var packageJson = {
230
227
 
231
228
  var log = anylogger__default["default"](packageJson.name);
232
229
 
233
- // global data store containing all subscriptions
234
- const subscriptions = {};
235
-
236
- /**
237
- * This hook ensures a subscription is created only once, and unsubscribe automatically as soon as there is no consumer to the hook
238
- * @param key key that is unique to the subscription's parameters
239
- * @param subscribe // subscribe function that will be shared by all consumers of the key
240
- */
241
- const useSharedSubscription = (key, subscribe) => {
242
- // create the rxJS subject if it doesn't exist
243
- if (!subscriptions[key]) subscriptions[key] = {
244
- subject: new rxjs.Subject()
245
- };
246
- react.useEffect(() => {
247
- // subscribe to subject.
248
- // it won't change but we need to count subscribers, to unsubscribe main subscription when no more observers
249
- const s = subscriptions[key].subject.subscribe();
250
- return () => {
251
- // unsubscribe from our local observable updates to prevent memory leaks
252
- s.unsubscribe();
253
- const {
254
- subject,
255
- unsubscribe
256
- } = subscriptions[key];
257
- if (!subject.observed && unsubscribe) {
258
- log.debug(`[useSharedSubscription] unsubscribing ${key}`);
259
-
260
- // unsubscribe from backend updates to prevent unnecessary network connections
261
- unsubscribe();
262
- delete subscriptions[key].unsubscribe;
263
- }
264
- };
265
- }, [key]);
266
-
267
- // Initialize subscription
268
- react.useEffect(() => {
269
- const {
270
- unsubscribe
271
- } = subscriptions[key];
272
- // launch the subscription if it's a new key
273
- if (!unsubscribe) {
274
- const cb = subscribe();
275
- log.debug(`[useSharedSubscription] subscribing ${key}`);
276
- if (cb) subscriptions[key].unsubscribe = cb;
277
- // this error should only happen when developping a new hook, let it bubble up
278
- else throw new Error(`${key} subscribe did not return an unsubscribe callback`);
279
- }
280
- }, [key, subscribe]);
281
- };
282
-
283
- function useChainConnectorsProvider(options) {
284
- const [onfinalityApiKey, setOnfinalityApiKey] = react.useState(options.onfinalityApiKey);
285
-
286
- // make sure we recreate provider only when the onfinalityApiKey changes
287
- react.useEffect(() => {
288
- if (options.onfinalityApiKey !== onfinalityApiKey) setOnfinalityApiKey(options.onfinalityApiKey);
289
- }, [options.onfinalityApiKey, onfinalityApiKey]);
290
-
291
- // chaindata dependency
292
- const chaindata = useChaindata();
293
-
294
- // substrate connector
295
- const substrate = react.useMemo(() => new chainConnector.ChainConnector(chaindata), [chaindata]);
296
-
297
- // evm connector
298
- const evm = react.useMemo(() => new chainConnectorEvm.ChainConnectorEvm(chaindata, {
299
- onfinalityApiKey
300
- }), [chaindata, onfinalityApiKey]);
301
- return react.useMemo(() => ({
302
- substrate,
303
- evm
304
- }), [substrate, evm]);
305
- }
306
- const [ChainConnectorsProvider, useChainConnectors] = provideContext(useChainConnectorsProvider);
307
-
308
- function useTokens(withTestnets) {
309
- // keep db data up to date
310
- useDbCacheSubscription("tokens");
311
- const {
312
- tokensWithTestnetsMap,
313
- tokensWithoutTestnetsMap
314
- } = useDbCache();
315
- return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
316
- }
317
- function useToken(tokenId, withTestnets) {
318
- const tokens = useTokens(withTestnets);
319
- return tokenId ? tokens[tokenId] : undefined;
320
- }
321
-
322
230
  /**
323
231
  * Creates a subscription function that can be used to subscribe to a multicast observable created from an upstream source.
324
232
  *
@@ -360,193 +268,193 @@ const createMulticastSubscription = upstream => {
360
268
  return subscribe;
361
269
  };
362
270
 
363
- const useWithTestnetsProvider = ({
364
- withTestnets
365
- }) => {
366
- return {
367
- withTestnets
368
- };
369
- };
370
- const [WithTestnetsProvider, useWithTestnets] = provideContext(useWithTestnetsProvider);
271
+ function useChainConnectorsProvider(options) {
272
+ // chaindata dependency
273
+ const chaindata = useChaindata();
274
+
275
+ // substrate connector
276
+ const [substrate, setSubstrate] = react.useState();
277
+ react.useEffect(() => {
278
+ if (!chaindata) return;
279
+ setSubstrate(new chainConnector.ChainConnector(chaindata));
280
+ }, [chaindata]);
281
+
282
+ // evm connector
283
+ const [evm, setEvm] = react.useState();
284
+ react.useEffect(() => {
285
+ if (!chaindata) return;
286
+ setEvm(new chainConnectorEvm.ChainConnectorEvm(chaindata, {
287
+ onfinalityApiKey: options.onfinalityApiKey
288
+ }));
289
+ }, [chaindata, options.onfinalityApiKey]);
290
+ return react.useMemo(() => ({
291
+ substrate,
292
+ evm
293
+ }), [substrate, evm]);
294
+ }
295
+ const [ChainConnectorsProvider, useChainConnectors] = provideContext(useChainConnectorsProvider);
296
+
297
+ const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeTokenRates(), useSubscribeBalances()];
298
+ const [SubscriptionsProvider, useSubscriptions] = provideContext(useSubscriptionsProvider);
371
299
 
372
300
  /**
373
301
  * This hook is responsible for fetching the data used for balances and inserting it into the db.
374
302
  */
375
303
  const useDbCacheSubscription = subscribeTo => {
376
- const provider = useChaindata();
377
-
378
- // can't handle balances & tokenRates here as they have other dependencies, it would trigger to many subscriptions
379
- const subscribe = react.useCallback(() => {
304
+ const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances] = useSubscriptions();
305
+ react.useEffect(() => {
380
306
  switch (subscribeTo) {
381
307
  case "chains":
382
- return subscribeChainDataHydrate(provider, "chains");
308
+ return subscribeHydrateChains();
383
309
  case "evmNetworks":
384
- return subscribeChainDataHydrate(provider, "evmNetworks");
310
+ return subscribeHydrateEvmNetworks();
385
311
  case "tokens":
386
- return subscribeChainDataHydrate(provider, "tokens");
312
+ return subscribeHydrateTokens();
313
+ case "tokenRates":
314
+ return subscribeTokenRates();
315
+ case "balances":
316
+ return subscribeBalances();
387
317
  }
388
- }, [provider, subscribeTo]);
389
- useSharedSubscription(subscribeTo, subscribe);
318
+ }, [subscribeTo, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances]);
390
319
  };
391
-
392
- /**
393
- * This hook is responsible for fetching the data used for token rates and inserting it into the db.
394
- */
395
- function useDbCacheTokenRatesSubscription() {
396
- const {
397
- withTestnets
398
- } = useWithTestnets();
399
- const tokens = useTokens(withTestnets);
400
- const subscriptionKey = react.useMemo(
401
- // not super sexy but we need key to change based on this stuff
402
- () => {
403
- const key = Object.values(tokens ?? {}).map(({
404
- id
405
- }) => id).sort().join();
406
- return `tokenRates-${md5__default["default"](key)}`;
407
- }, [tokens]);
408
- const subscription = react.useCallback(() => {
409
- if (!Object.values(tokens ?? {}).length) return () => {};
410
- return subscribeTokenRates(tokens);
411
- }, [tokens]);
412
- useSharedSubscription(subscriptionKey, subscription);
320
+ function useSubscribeChaindataHydrate(type) {
321
+ const chaindata =
322
+ // cheeky hack to give us access to the hydrate methods
323
+ useChaindata();
324
+ const createSubscription = react.useCallback(() => {
325
+ if (!chaindata) return;
326
+ let active = true;
327
+ const interval = 300_000; // 300_000ms = 300s = 5 minutes
328
+
329
+ const hydrate = async () => {
330
+ if (!active) return;
331
+ try {
332
+ if (type === "chains") await chaindata.hydrateChains();
333
+ if (type === "evmNetworks") await chaindata.hydrateEvmNetworks();
334
+ if (type === "tokens") await chaindata.hydrateTokens();
335
+ setTimeout(hydrate, interval);
336
+ } catch (error) {
337
+ const retryTimeout = 5_000; // 5_000ms = 5 seconds
338
+ log.error(`Failed to fetch chaindata, retrying in ${Math.round(retryTimeout / 1000)} seconds`, error);
339
+ setTimeout(hydrate, retryTimeout);
340
+ }
341
+ };
342
+ hydrate();
343
+ return () => {
344
+ active = false;
345
+ };
346
+ }, [chaindata, type]);
347
+ const subscribe = useMulticastSubscription(createSubscription);
348
+ return subscribe;
413
349
  }
414
-
415
- /**
416
- * This hook is responsible for fetching the data used for balances and inserting it into the db.
417
- */
418
- function useDbCacheBalancesSubscription() {
419
- const {
420
- withTestnets
421
- } = useWithTestnets();
350
+ function useSubscribeTokenRates() {
351
+ const chaindataProvider = useChaindata();
352
+ const tokens = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : 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
+ }
393
+ function useSubscribeBalances() {
422
394
  const balanceModules = useBalanceModules();
423
395
  const chaindataProvider = useChaindata();
424
396
  const chainConnectors = useChainConnectors();
425
397
  const [allAddresses] = useAllAddresses();
426
- const tokens = useTokens(withTestnets);
427
- const subscriptionKey = react.useMemo(
428
- // not super sexy but we need key to change based on this stuff
429
- () => {
430
- const key = allAddresses.sort().join().concat(...Object.values(tokens ?? {}).map(({
431
- id
432
- }) => id).sort()).concat(`evm:${!!chainConnectors.evm}`, `sub:${!!chainConnectors.substrate}`, ...balanceModules.map(m => m.type).sort(), `cd:${!!chaindataProvider}`);
433
- return `balances-${md5__default["default"](key)}`;
434
- }, [allAddresses, balanceModules, chainConnectors, chaindataProvider, tokens]);
435
- const subscription = react.useCallback(() => {
436
- if (!Object.values(tokens ?? {}).length || !allAddresses.length) return () => {};
437
- return subscribeBalances(tokens ?? {}, allAddresses, chainConnectors, chaindataProvider, balanceModules);
438
- }, [allAddresses, balanceModules, chainConnectors, chaindataProvider, tokens]);
439
- useSharedSubscription(subscriptionKey, subscription);
440
- }
441
- const subscribeChainDataHydrate = (provider, type) => {
442
- const chaindata = provider;
443
- const delay = 300_000; // 300_000ms = 300s = 5 minutes
444
-
445
- let timeout = null;
446
- const hydrate = async () => {
447
- try {
448
- if (type === "chains") await chaindata.hydrateChains();
449
- if (type === "evmNetworks") await chaindata.hydrateEvmNetworks();
450
- if (type === "tokens") await chaindata.hydrateTokens();
451
- timeout = setTimeout(hydrate, delay);
452
- } catch (error) {
453
- const retryTimeout = 5_000; // 5_000ms = 5 seconds
454
- log.error(`Failed to fetch chaindata, retrying in ${Math.round(retryTimeout / 1000)} seconds`, error);
455
- timeout = setTimeout(hydrate, retryTimeout);
456
- }
457
- };
458
-
459
- // launch the loop
460
- hydrate();
461
- return () => {
462
- if (timeout) clearTimeout(timeout);
463
- };
464
- };
465
- const subscribeTokenRates = tokens => {
466
- const REFRESH_INTERVAL = 300_000; // 6 minutes
467
- const RETRY_INTERVAL = 5_000; // 5 sec
468
-
469
- let timeout = null;
470
- const refreshTokenRates = async () => {
471
- try {
472
- if (timeout) clearTimeout(timeout);
473
- const tokenRates$1 = await tokenRates.fetchTokenRates(tokens);
474
- const putTokenRates = Object.entries(tokenRates$1).map(([tokenId, rates]) => ({
475
- tokenId,
476
- rates
477
- }));
478
- tokenRates.db.transaction("rw", tokenRates.db.tokenRates, async () => await tokenRates.db.tokenRates.bulkPut(putTokenRates));
479
- timeout = setTimeout(() => {
480
- refreshTokenRates();
481
- }, REFRESH_INTERVAL);
482
- } catch (error) {
483
- log.error(`Failed to fetch tokenRates, retrying in ${Math.round(RETRY_INTERVAL / 1000)} seconds`, error);
484
- setTimeout(async () => {
485
- refreshTokenRates();
486
- }, RETRY_INTERVAL);
487
- }
488
- };
489
-
490
- // launch the loop
491
- refreshTokenRates();
492
- return () => {
493
- if (timeout) clearTimeout(timeout);
494
- };
495
- };
496
- const subscribeBalances = (tokens, addresses, chainConnectors, provider, balanceModules) => {
497
- const tokenIds = Object.values(tokens).map(({
398
+ const tokens = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.tokens(), [chaindataProvider]);
399
+ const tokenIds = react.useMemo(() => Object.values(tokens ?? {}).map(({
498
400
  id
499
- }) => id);
500
- const addressesByToken = Object.fromEntries(tokenIds.map(tokenId => [tokenId, addresses]));
501
- const updateDb = balances$1 => {
502
- const putBalances = Object.entries(balances$1.toJSON()).map(([id, balance]) => ({
503
- id,
504
- ...balance
505
- }));
506
- balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.bulkPut(putBalances));
507
- };
508
- let unsubscribed = false;
509
-
510
- // eslint-disable-next-line no-console
511
- log.log("subscribing to balance changes for %d tokens and %d addresses", tokenIds.length, addresses.length);
512
- const unsubs = balanceModules.map(async balanceModule => {
513
- // filter out tokens to only include those which this module knows how to fetch balances for
514
- const moduleTokenIds = Object.values(tokens ?? {}).filter(({
515
- type
516
- }) => type === balanceModule.type).map(({
517
- id
518
- }) => id);
519
- const addressesByModuleToken = Object.fromEntries(Object.entries(addressesByToken).filter(([tokenId]) => moduleTokenIds.includes(tokenId)));
520
- const unsub = balances.balances(balanceModule, chainConnectors, provider, addressesByModuleToken, (error, balances) => {
521
- // log errors
522
- if (error) return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
523
- // ignore empty balance responses
524
- if (!balances) return;
525
- // ignore balances from old subscriptions which are still in the process of unsubscribing
526
- if (unsubscribed) return;
527
- updateDb(balances);
528
- });
529
- return () => {
530
- // wait 2 seconds before actually unsubscribing, allowing for websocket to be reused
531
- unsub.then(unsubscribe => {
532
- setTimeout(unsubscribe, 2_000);
401
+ }) => id), [tokens]);
402
+ const addressesByToken = react.useMemo(() => Object.fromEntries(tokenIds.map(tokenId => [tokenId, allAddresses])), [allAddresses, tokenIds]);
403
+ const generationRef = react.useRef(0);
404
+ const createSubscription = react.useCallback(() => {
405
+ if (!chainConnectors.substrate) return;
406
+ if (!chainConnectors.evm) return;
407
+ if (!chaindataProvider) return;
408
+ const generation = (generationRef.current + 1) % Number.MAX_SAFE_INTEGER;
409
+ generationRef.current = generation;
410
+ const unsubs = balanceModules.map(balanceModule => {
411
+ // filter out tokens to only include those which this module knows how to fetch balances for
412
+ const moduleTokenIds = Object.values(tokens ?? {}).filter(({
413
+ type
414
+ }) => type === balanceModule.type).map(({
415
+ id
416
+ }) => id);
417
+ const addressesByModuleToken = Object.fromEntries(Object.entries(addressesByToken).filter(([tokenId]) => moduleTokenIds.includes(tokenId)));
418
+ const subscribe = createMulticastSubscription(next => {
419
+ const unsub = balances.balances(balanceModule, chainConnectors, chaindataProvider, addressesByModuleToken, (error, balances) => {
420
+ // log errors
421
+ if (error) return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
422
+ // ignore empty balance responses
423
+ if (!balances) return;
424
+ // ignore balances from old subscriptions which are still in the process of unsubscribing
425
+ if (generationRef.current !== generation) return;
426
+ next(balances);
427
+ });
428
+ return () => {
429
+ // unsubscribe from upstream
430
+ unsub.then(unsubscribe => unsubscribe());
431
+
432
+ // set this subscription's balances in the store to status: cache
433
+ balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.filter(balance => {
434
+ if (balance.source !== balanceModule.type) return false;
435
+ if (!Object.keys(addressesByModuleToken).includes(balance.tokenId)) return false;
436
+ if (!addressesByModuleToken[balance.tokenId].includes(balance.address)) return false;
437
+ return true;
438
+ }).modify({
439
+ status: "cache"
440
+ }));
441
+ };
533
442
  });
534
- balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.filter(balance => {
535
- if (balance.source !== balanceModule.type) return false;
536
- if (!Object.keys(addressesByModuleToken).includes(balance.tokenId)) return false;
537
- if (!addressesByModuleToken[balance.tokenId].includes(balance.address)) return false;
538
- return true;
539
- }).modify({
540
- status: "cache"
541
- }));
542
- };
543
- });
544
- const unsubscribeAll = () => {
545
- unsubscribed = true;
546
- unsubs.forEach(unsub => unsub.then(unsubscribe => unsubscribe()));
547
- };
548
- return unsubscribeAll;
549
- };
443
+ const unsubscribe = subscribe(balances$1 => {
444
+ const putBalances = Object.entries(balances$1.toJSON()).map(([id, balance]) => ({
445
+ id,
446
+ ...balance
447
+ }));
448
+ balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.bulkPut(putBalances));
449
+ });
450
+ return unsubscribe;
451
+ });
452
+ const unsubscribeAll = () => unsubs.forEach(unsub => unsub());
453
+ return unsubscribeAll;
454
+ }, [addressesByToken, balanceModules, chainConnectors, chaindataProvider, tokens]);
455
+ const subscribe = useMulticastSubscription(createSubscription);
456
+ return subscribe;
457
+ }
550
458
 
551
459
  function useChains(withTestnets) {
552
460
  // keep db data up to date
@@ -578,7 +486,7 @@ function useEvmNetwork(evmNetworkId, withTestnets) {
578
486
 
579
487
  function useTokenRates() {
580
488
  // keep db data up to date
581
- useDbCacheTokenRatesSubscription();
489
+ useDbCacheSubscription("tokenRates");
582
490
  const {
583
491
  tokenRatesMap
584
492
  } = useDbCache();
@@ -589,10 +497,21 @@ function useTokenRate(tokenId) {
589
497
  return tokenId ? tokenRates[tokenId] : undefined;
590
498
  }
591
499
 
592
- const useBalancesHydrate = () => {
500
+ function useTokens(withTestnets) {
501
+ // keep db data up to date
502
+ useDbCacheSubscription("tokens");
593
503
  const {
594
- withTestnets
595
- } = useWithTestnets();
504
+ tokensWithTestnetsMap,
505
+ tokensWithoutTestnetsMap
506
+ } = useDbCache();
507
+ return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
508
+ }
509
+ function useToken(tokenId, withTestnets) {
510
+ const tokens = useTokens(withTestnets);
511
+ return tokenId ? tokens[tokenId] : undefined;
512
+ }
513
+
514
+ const useBalancesHydrate = withTestnets => {
596
515
  const chains = useChains(withTestnets);
597
516
  const evmNetworks = useEvmNetworks(withTestnets);
598
517
  const tokens = useTokens(withTestnets);
@@ -607,7 +526,7 @@ const useBalancesHydrate = () => {
607
526
 
608
527
  function useBalances(addressesByToken) {
609
528
  // keep db data up to date
610
- useDbCacheBalancesSubscription();
529
+ useDbCacheSubscription("balances");
611
530
  const balanceModules = useBalanceModules();
612
531
  const {
613
532
  balances: balances$1
@@ -638,18 +557,18 @@ function useBalances(addressesByToken) {
638
557
  const BalancesProvider = ({
639
558
  balanceModules,
640
559
  onfinalityApiKey,
641
- withTestnets,
560
+ useTestnets,
642
561
  children
643
- }) => /*#__PURE__*/jsxRuntime.jsx(WithTestnetsProvider, {
644
- withTestnets: withTestnets,
645
- children: /*#__PURE__*/jsxRuntime.jsx(ChaindataProvider, {
562
+ }) => /*#__PURE__*/jsxRuntime.jsx(ChaindataProvider, {
563
+ onfinalityApiKey: onfinalityApiKey,
564
+ children: /*#__PURE__*/jsxRuntime.jsx(ChainConnectorsProvider, {
646
565
  onfinalityApiKey: onfinalityApiKey,
647
- children: /*#__PURE__*/jsxRuntime.jsx(ChainConnectorsProvider, {
648
- onfinalityApiKey: onfinalityApiKey,
649
- children: /*#__PURE__*/jsxRuntime.jsx(AllAddressesProvider, {
650
- children: /*#__PURE__*/jsxRuntime.jsx(BalanceModulesProvider, {
651
- balanceModules: balanceModules,
652
- children: /*#__PURE__*/jsxRuntime.jsx(DbCacheProvider, {
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, {
653
572
  children: children
654
573
  })
655
574
  })
@@ -664,7 +583,7 @@ exports.BalancesProvider = BalancesProvider;
664
583
  exports.ChainConnectorsProvider = ChainConnectorsProvider;
665
584
  exports.ChaindataProvider = ChaindataProvider;
666
585
  exports.DbCacheProvider = DbCacheProvider;
667
- exports.WithTestnetsProvider = WithTestnetsProvider;
586
+ exports.SubscriptionsProvider = SubscriptionsProvider;
668
587
  exports.createMulticastSubscription = createMulticastSubscription;
669
588
  exports.provideContext = provideContext;
670
589
  exports.useAllAddresses = useAllAddresses;
@@ -676,14 +595,12 @@ exports.useChainConnectors = useChainConnectors;
676
595
  exports.useChaindata = useChaindata;
677
596
  exports.useChains = useChains;
678
597
  exports.useDbCache = useDbCache;
679
- exports.useDbCacheBalancesSubscription = useDbCacheBalancesSubscription;
680
598
  exports.useDbCacheSubscription = useDbCacheSubscription;
681
- exports.useDbCacheTokenRatesSubscription = useDbCacheTokenRatesSubscription;
682
599
  exports.useEvmNetwork = useEvmNetwork;
683
600
  exports.useEvmNetworks = useEvmNetworks;
684
601
  exports.useMulticastSubscription = useMulticastSubscription;
602
+ exports.useSubscriptions = useSubscriptions;
685
603
  exports.useToken = useToken;
686
604
  exports.useTokenRate = useTokenRate;
687
605
  exports.useTokenRates = useTokenRates;
688
606
  exports.useTokens = useTokens;
689
- exports.useWithTestnets = useWithTestnets;