@talismn/balances-react 0.0.0-pr587-20230301175306 → 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-20230301175306",
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,191 +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);
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
+ };
442
+ });
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;
528
451
  });
529
- return () => {
530
- // TODO should we add a timeout before unsubscribe to prevent closing sockets to quickly ?
531
- unsub.then(unsubscribe => unsubscribe());
532
- balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.filter(balance => {
533
- if (balance.source !== balanceModule.type) return false;
534
- if (!Object.keys(addressesByModuleToken).includes(balance.tokenId)) return false;
535
- if (!addressesByModuleToken[balance.tokenId].includes(balance.address)) return false;
536
- return true;
537
- }).modify({
538
- status: "cache"
539
- }));
540
- };
541
- });
542
- const unsubscribeAll = () => {
543
- unsubscribed = true;
544
- unsubs.forEach(unsub => unsub.then(unsubscribe => unsubscribe()));
545
- };
546
- return unsubscribeAll;
547
- };
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
+ }
548
458
 
549
459
  function useChains(withTestnets) {
550
460
  // keep db data up to date
@@ -576,7 +486,7 @@ function useEvmNetwork(evmNetworkId, withTestnets) {
576
486
 
577
487
  function useTokenRates() {
578
488
  // keep db data up to date
579
- useDbCacheTokenRatesSubscription();
489
+ useDbCacheSubscription("tokenRates");
580
490
  const {
581
491
  tokenRatesMap
582
492
  } = useDbCache();
@@ -587,10 +497,21 @@ function useTokenRate(tokenId) {
587
497
  return tokenId ? tokenRates[tokenId] : undefined;
588
498
  }
589
499
 
590
- const useBalancesHydrate = () => {
500
+ function useTokens(withTestnets) {
501
+ // keep db data up to date
502
+ useDbCacheSubscription("tokens");
591
503
  const {
592
- withTestnets
593
- } = 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 => {
594
515
  const chains = useChains(withTestnets);
595
516
  const evmNetworks = useEvmNetworks(withTestnets);
596
517
  const tokens = useTokens(withTestnets);
@@ -605,7 +526,7 @@ const useBalancesHydrate = () => {
605
526
 
606
527
  function useBalances(addressesByToken) {
607
528
  // keep db data up to date
608
- useDbCacheBalancesSubscription();
529
+ useDbCacheSubscription("balances");
609
530
  const balanceModules = useBalanceModules();
610
531
  const {
611
532
  balances: balances$1
@@ -636,18 +557,18 @@ function useBalances(addressesByToken) {
636
557
  const BalancesProvider = ({
637
558
  balanceModules,
638
559
  onfinalityApiKey,
639
- withTestnets,
560
+ useTestnets,
640
561
  children
641
- }) => /*#__PURE__*/jsxRuntime.jsx(WithTestnetsProvider, {
642
- withTestnets: withTestnets,
643
- children: /*#__PURE__*/jsxRuntime.jsx(ChaindataProvider, {
562
+ }) => /*#__PURE__*/jsxRuntime.jsx(ChaindataProvider, {
563
+ onfinalityApiKey: onfinalityApiKey,
564
+ children: /*#__PURE__*/jsxRuntime.jsx(ChainConnectorsProvider, {
644
565
  onfinalityApiKey: onfinalityApiKey,
645
- children: /*#__PURE__*/jsxRuntime.jsx(ChainConnectorsProvider, {
646
- onfinalityApiKey: onfinalityApiKey,
647
- children: /*#__PURE__*/jsxRuntime.jsx(AllAddressesProvider, {
648
- children: /*#__PURE__*/jsxRuntime.jsx(BalanceModulesProvider, {
649
- balanceModules: balanceModules,
650
- 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, {
651
572
  children: children
652
573
  })
653
574
  })
@@ -662,7 +583,7 @@ exports.BalancesProvider = BalancesProvider;
662
583
  exports.ChainConnectorsProvider = ChainConnectorsProvider;
663
584
  exports.ChaindataProvider = ChaindataProvider;
664
585
  exports.DbCacheProvider = DbCacheProvider;
665
- exports.WithTestnetsProvider = WithTestnetsProvider;
586
+ exports.SubscriptionsProvider = SubscriptionsProvider;
666
587
  exports.createMulticastSubscription = createMulticastSubscription;
667
588
  exports.provideContext = provideContext;
668
589
  exports.useAllAddresses = useAllAddresses;
@@ -674,14 +595,12 @@ exports.useChainConnectors = useChainConnectors;
674
595
  exports.useChaindata = useChaindata;
675
596
  exports.useChains = useChains;
676
597
  exports.useDbCache = useDbCache;
677
- exports.useDbCacheBalancesSubscription = useDbCacheBalancesSubscription;
678
598
  exports.useDbCacheSubscription = useDbCacheSubscription;
679
- exports.useDbCacheTokenRatesSubscription = useDbCacheTokenRatesSubscription;
680
599
  exports.useEvmNetwork = useEvmNetwork;
681
600
  exports.useEvmNetworks = useEvmNetworks;
682
601
  exports.useMulticastSubscription = useMulticastSubscription;
602
+ exports.useSubscriptions = useSubscriptions;
683
603
  exports.useToken = useToken;
684
604
  exports.useTokenRate = useTokenRate;
685
605
  exports.useTokenRates = useTokenRates;
686
606
  exports.useTokens = useTokens;
687
- exports.useWithTestnets = useWithTestnets;