@talismn/balances-react 0.0.0-pr557-20230216040942 → 0.0.0-pr563-20230221193038

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.
@@ -2,23 +2,194 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var react = require('react');
5
6
  var balances = require('@talismn/balances');
6
- var chainConnector = require('@talismn/chain-connector');
7
- var chainConnectorEvm = require('@talismn/chain-connector-evm');
8
7
  var dexieReactHooks = require('dexie-react-hooks');
9
- var react = require('react');
10
8
  var reactUse = require('react-use');
11
- var anylogger = require('anylogger');
12
9
  var chaindataProviderExtension = require('@talismn/chaindata-provider-extension');
13
10
  var tokenRates = require('@talismn/token-rates');
11
+ var anylogger = require('anylogger');
12
+ var rxjs = require('rxjs');
13
+ var chainConnector = require('@talismn/chain-connector');
14
+ var chainConnectorEvm = require('@talismn/chain-connector-evm');
14
15
 
15
16
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
16
17
 
17
18
  var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
18
19
 
20
+ /**
21
+ * This utility generates a context provider from a react hook passed as argument
22
+ *
23
+ * @returns an array containing the provider and the consumer hook
24
+ */
25
+ const provideContext = useProviderContext => {
26
+ // automatic typing based on our hook's return type
27
+
28
+ const Context = /*#__PURE__*/react.createContext({
29
+ __provideContextInteralDefaultValue: true
30
+ });
31
+ const Provider = ({
32
+ children,
33
+ ...props
34
+ }) => {
35
+ const ctx = useProviderContext(props);
36
+ return /*#__PURE__*/React.createElement(Context.Provider, {
37
+ value: ctx
38
+ }, children);
39
+ };
40
+ const useProvidedContext = () => {
41
+ const context = react.useContext(Context);
42
+ if (typeof context === "object" && context && "__provideContextInteralDefaultValue" in context) throw new Error("This hook requires a provider to be present above it in the tree");
43
+ return context;
44
+ };
45
+ const result = [Provider, useProvidedContext];
46
+ return result;
47
+ };
48
+
49
+ const useAllAddressesProvider = () => react.useState([]);
50
+ const [AllAddressesProvider, useAllAddresses] = provideContext(useAllAddressesProvider);
51
+
52
+ const useBalanceModulesProvider = ({
53
+ balanceModules
54
+ }) => balanceModules;
55
+ const [BalanceModulesProvider, useBalanceModules] = provideContext(useBalanceModulesProvider);
56
+
57
+ function useChaindataProvider(options = {}) {
58
+ const [chaindata, setChaindata] = react.useState();
59
+ react.useEffect(() => {
60
+ setChaindata(new chaindataProviderExtension.ChaindataProviderExtension({
61
+ onfinalityApiKey: options.onfinalityApiKey
62
+ }));
63
+ }, [options.onfinalityApiKey]);
64
+ return chaindata;
65
+ }
66
+ const [ChaindataProvider, useChaindata] = provideContext(useChaindataProvider);
67
+
68
+ function useTokenRates(tokens) {
69
+ const generation = react.useRef(0);
70
+ const [tokenRates$1, setTokenRates] = react.useState({});
71
+ react.useEffect(() => {
72
+ if (!tokens) return;
73
+ if (Object.keys(tokens).length < 1) return;
74
+
75
+ // when we make a new request, we want to ignore any old requests which haven't yet completed
76
+ // otherwise we risk replacing the most recent data with older data
77
+ generation.current = (generation.current + 1) % Number.MAX_SAFE_INTEGER;
78
+ const thisGeneration = generation.current;
79
+ tokenRates.fetchTokenRates(tokens).then(tokenRates => {
80
+ if (thisGeneration !== generation.current) return;
81
+ setTokenRates(tokenRates);
82
+ });
83
+ }, [tokens]);
84
+ return tokenRates$1;
85
+ }
86
+
87
+ const filterNoTestnet = ({
88
+ isTestnet
89
+ }) => isTestnet === false;
90
+ const DEFAULT_VALUE = {
91
+ chainsWithTestnets: [],
92
+ chainsWithoutTestnets: [],
93
+ evmNetworksWithTestnets: [],
94
+ evmNetworksWithoutTestnets: [],
95
+ tokensWithTestnets: [],
96
+ tokensWithoutTestnets: [],
97
+ chainsWithTestnetsMap: {},
98
+ chainsWithoutTestnetsMap: {},
99
+ evmNetworksWithTestnetsMap: {},
100
+ evmNetworksWithoutTestnetsMap: {},
101
+ tokensWithTestnetsMap: {},
102
+ tokensWithoutTestnetsMap: {},
103
+ balances: [],
104
+ tokenRatesMap: {}
105
+ };
106
+ const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, allBalances, tokenRates) => {
107
+ if (!chainsMap || !evmNetworksMap || !tokensMap || !allBalances
108
+ // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
109
+ /* || !tokenRates */) return DEFAULT_VALUE;
110
+
111
+ // BEGIN: temp hack to indicate that
112
+ // - EVM GLMR is a mirror of substrate GLMR
113
+ // - EVM MOVR is a mirror of substrate MOVR
114
+ // - EVM DEV is a mirror of substrate DEV
115
+ // - EVM ACA is a mirror of substrate ACA
116
+ const mirrorTokenIds = {
117
+ "1284-evm-native-glmr": "moonbeam-substrate-native-glmr",
118
+ "1285-evm-native-movr": "moonriver-substrate-native-movr",
119
+ "1287-evm-native-dev": "moonbase-alpha-testnet-substrate-native-dev",
120
+ "787-evm-native-aca": "acala-substrate-native-aca"
121
+ };
122
+ Object.entries(mirrorTokenIds).filter(([mirrorToken]) => tokensMap[mirrorToken]).forEach(([mirrorToken, mirrorOf]) => tokensMap[mirrorToken].mirrorOf = mirrorOf);
123
+ // END: temp hack
124
+
125
+ const chainsWithTestnets = Object.values(chainsMap);
126
+ const chainsWithoutTestnets = chainsWithTestnets.filter(filterNoTestnet);
127
+ const chainsWithoutTestnetsMap = Object.fromEntries(chainsWithoutTestnets.map(network => [network.id, network]));
128
+ const evmNetworksWithTestnets = Object.values(evmNetworksMap);
129
+ const evmNetworksWithoutTestnets = evmNetworksWithTestnets.filter(filterNoTestnet);
130
+ const evmNetworksWithoutTestnetsMap = Object.fromEntries(evmNetworksWithoutTestnets.map(network => [network.id, network]));
131
+
132
+ // ensure that we have corresponding network for each token
133
+ const tokensWithTestnets = Object.values(tokensMap).filter(token => token.chain && chainsMap[token.chain.id] || token.evmNetwork && evmNetworksMap[token.evmNetwork.id]);
134
+ const tokensWithoutTestnets = tokensWithTestnets.filter(filterNoTestnet).filter(token => token.chain && chainsWithoutTestnetsMap[token.chain.id] || token.evmNetwork && evmNetworksWithoutTestnetsMap[token.evmNetwork.id]);
135
+ const tokensWithTestnetsMap = Object.fromEntries(tokensWithTestnets.map(token => [token.id, token]));
136
+ const tokensWithoutTestnetsMap = Object.fromEntries(tokensWithoutTestnets.map(token => [token.id, token]));
137
+
138
+ // return only balances for which we have a token
139
+ const balances = allBalances.filter(b => tokensWithTestnetsMap[b.tokenId]);
140
+ return {
141
+ chainsWithTestnets,
142
+ chainsWithoutTestnets,
143
+ evmNetworksWithTestnets,
144
+ evmNetworksWithoutTestnets,
145
+ tokensWithTestnets,
146
+ tokensWithoutTestnets,
147
+ chainsWithTestnetsMap: chainsMap,
148
+ chainsWithoutTestnetsMap,
149
+ evmNetworksWithTestnetsMap: evmNetworksMap,
150
+ evmNetworksWithoutTestnetsMap,
151
+ tokensWithTestnetsMap,
152
+ tokensWithoutTestnetsMap,
153
+ balances,
154
+ tokenRatesMap: tokenRates ?? {}
155
+ };
156
+ };
157
+ const useDbCacheProvider = ({
158
+ useTestnets = false
159
+ }) => {
160
+ const chaindataProvider = useChaindata();
161
+ const chainList = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.chains(), [chaindataProvider]);
162
+ const evmNetworkList = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.evmNetworks(), [chaindataProvider]);
163
+ const tokenList = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.tokens(), [chaindataProvider]);
164
+ const rawBalances = dexieReactHooks.useLiveQuery(() => balances.db.balances.toArray(), []);
165
+
166
+ // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
167
+ const tokenRates = useTokenRates();
168
+ const [dbData, setDbData] = react.useState(DEFAULT_VALUE);
169
+
170
+ // debounce every 500ms to prevent hammering UI with updates
171
+ reactUse.useDebounce(() => {
172
+ setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, rawBalances, tokenRates));
173
+ }, 500, [chainList, evmNetworkList, tokenList, rawBalances, tokenRates, useTestnets]);
174
+ const refInitialized = react.useRef(false);
175
+
176
+ // force an update as soon as all datasources are fetched, so UI can display data ASAP
177
+ react.useEffect(() => {
178
+ if (!refInitialized.current && chainList && evmNetworkList && tokenList && rawBalances
179
+ // TODO: Store tokenRates in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
180
+ // && tokenRates
181
+ ) {
182
+ setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, rawBalances, tokenRates));
183
+ refInitialized.current = true;
184
+ }
185
+ }, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates, useTestnets]);
186
+ return dbData;
187
+ };
188
+ const [DbCacheProvider, useDbCache] = provideContext(useDbCacheProvider);
189
+
19
190
  var packageJson = {
20
191
  name: "@talismn/balances-react",
21
- version: "0.0.0-pr557-20230216040942",
192
+ version: "0.0.0-pr563-20230221193038",
22
193
  author: "Talisman",
23
194
  homepage: "https://talisman.xyz",
24
195
  license: "UNLICENSED",
@@ -51,9 +222,10 @@ var packageJson = {
51
222
  "@talismn/chaindata-provider-extension": "workspace:^",
52
223
  "@talismn/token-rates": "workspace:^",
53
224
  anylogger: "^1.0.11",
54
- dexie: "^3.2.2",
225
+ dexie: "^3.2.3",
55
226
  "dexie-react-hooks": "^1.1.1",
56
- "react-use": "^17.4.0"
227
+ "react-use": "^17.4.0",
228
+ rxjs: "^7.8.0"
57
229
  },
58
230
  devDependencies: {
59
231
  "@talismn/eslint-config": "workspace:^",
@@ -80,139 +252,259 @@ var packageJson = {
80
252
 
81
253
  var log = anylogger__default["default"](packageJson.name);
82
254
 
83
- // TODO: Allow user to call useChaindata from multiple places
84
- function useChaindata(options = {}) {
85
- const [chaindataProvider, setChaindataProvider] = react.useState(null);
255
+ /**
256
+ * Creates a subscription function that can be used to subscribe to a multicast observable created from an upstream source.
257
+ *
258
+ * An example of when this is useful is when we want to subscribe to some data from multiple components, but we only want
259
+ * to actively keep that data hydrated when at least one component is subscribed to it.
260
+ *
261
+ * When the first component subscribes, the `upstream` function will be called. It should then set up a subscription and return a teardown function.
262
+ * When subsequent components subscribe, they will be added to the existing subscription.
263
+ * When the last component unsubscribes, the teardown function returned from the `upstream` function will be called.
264
+ *
265
+ * @param upstream A function that takes a "next" callback function as an argument, and returns either an unsubscribe function or void.
266
+ * @returns A subscription function that can be used to subscribe to the multicast observable.
267
+ */
268
+ const useMulticastSubscription = upstream => {
269
+ const subscribe = react.useMemo(() => createMulticastSubscription(upstream), [upstream]);
270
+ return subscribe;
271
+ };
272
+ const createMulticastSubscription = upstream => {
273
+ // Create an upstream observable using the provided function.
274
+ const upstreamObservable = new rxjs.Observable(subscriber => {
275
+ const unsubscribe = upstream(val => subscriber.next(val));
276
+ return () => {
277
+ typeof unsubscribe === "function" && unsubscribe();
278
+ };
279
+ });
280
+
281
+ // Create a multicast observable from the upstream observable, using the shareReplay operator.
282
+ const multicastObservable = rxjs.defer(() => upstreamObservable).pipe(rxjs.shareReplay({
283
+ bufferSize: 1,
284
+ refCount: true
285
+ }));
286
+
287
+ // Create a subscription function that subscribes to the multicast observable and returns an unsubscribe function.
288
+ const subscribe = callback => {
289
+ const subscription = multicastObservable.subscribe(callback);
290
+ const unsubscribe = () => subscription.unsubscribe();
291
+ return unsubscribe;
292
+ };
293
+ return subscribe;
294
+ };
295
+
296
+ function useChainConnectorsProvider(options) {
297
+ // chaindata dependency
298
+ const chaindata = useChaindata();
86
299
 
87
- // this number is incremented each time the chaindataProvider has fetched new data
88
- const [generation, setGeneration] = react.useState(0);
300
+ // substrate connector
301
+ const [substrate, setSubstrate] = react.useState();
89
302
  react.useEffect(() => {
90
- const chaindataProvider = new chaindataProviderExtension.ChaindataProviderExtension({
303
+ if (!chaindata) return;
304
+ setSubstrate(new chainConnector.ChainConnector(chaindata));
305
+ }, [chaindata]);
306
+
307
+ // evm connector
308
+ const [evm, setEvm] = react.useState();
309
+ react.useEffect(() => {
310
+ if (!chaindata) return;
311
+ setEvm(new chainConnectorEvm.ChainConnectorEvm(chaindata, {
91
312
  onfinalityApiKey: options.onfinalityApiKey
92
- });
93
- let shouldHydrate = true;
94
- const timer = 300_000; // 300_000ms = 300s = 5 minutes
313
+ }));
314
+ }, [chaindata, options.onfinalityApiKey]);
315
+ return react.useMemo(() => ({
316
+ substrate,
317
+ evm
318
+ }), [substrate, evm]);
319
+ }
320
+ const [ChainConnectorsProvider, useChainConnectors] = provideContext(useChainConnectorsProvider);
321
+
322
+ const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeBalances()];
323
+ const [SubscriptionsProvider, useSubscriptions] = provideContext(useSubscriptionsProvider);
324
+
325
+ /**
326
+ * This hook is responsible for fetching the data used for balances and inserting it into the db.
327
+ */
328
+ const useDbCacheSubscription = subscribeTo => {
329
+ const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeBalances] = useSubscriptions();
330
+ react.useEffect(() => {
331
+ switch (subscribeTo) {
332
+ case "chains":
333
+ return subscribeHydrateChains();
334
+ case "evmNetworks":
335
+ return subscribeHydrateEvmNetworks();
336
+ case "tokens":
337
+ return subscribeHydrateTokens();
338
+ case "balances":
339
+ return subscribeBalances();
340
+ }
341
+ }, [subscribeBalances, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTo]);
342
+ };
343
+ function useSubscribeChaindataHydrate(type) {
344
+ const chaindata =
345
+ // cheeky hack to give us access to the hydrate methods
346
+ useChaindata();
347
+ const createSubscription = react.useCallback(() => {
348
+ if (!chaindata) return;
349
+ let active = true;
350
+ const interval = 300_000; // 300_000ms = 300s = 5 minutes
351
+
95
352
  const hydrate = async () => {
96
- if (!shouldHydrate) return;
353
+ if (!active) return;
97
354
  try {
98
- const updated = await chaindataProvider.hydrate();
99
- if (updated) setGeneration(generation => (generation + 1) % Number.MAX_SAFE_INTEGER);
100
- setTimeout(hydrate, timer);
355
+ if (type === "chains") await chaindata.hydrateChains();
356
+ if (type === "evmNetworks") await chaindata.hydrateEvmNetworks();
357
+ if (type === "tokens") await chaindata.hydrateTokens();
358
+ setTimeout(hydrate, interval);
101
359
  } catch (error) {
102
360
  const retryTimeout = 5_000; // 5_000ms = 5 seconds
103
361
  log.error(`Failed to fetch chaindata, retrying in ${Math.round(retryTimeout / 1000)} seconds`, error);
104
362
  setTimeout(hydrate, retryTimeout);
105
363
  }
106
364
  };
107
- setChaindataProvider(chaindataProvider);
108
365
  hydrate();
109
366
  return () => {
110
- shouldHydrate = false;
367
+ active = false;
111
368
  };
112
- }, [options.onfinalityApiKey]);
113
- if (chaindataProvider) chaindataProvider.generation = generation;
114
- return chaindataProvider;
369
+ }, [chaindata, type]);
370
+ const subscribe = useMulticastSubscription(createSubscription);
371
+ return subscribe;
115
372
  }
116
- function useChains(chaindata) {
117
- const [chains, setChains] = react.useState();
118
- react.useEffect(() => {
119
- if (!chaindata) return;
120
- const thisGeneration = chaindata.generation;
121
- chaindata.chains().then(chains => {
122
- if (thisGeneration !== chaindata.generation) return;
123
- setChains(chains);
373
+ function useSubscribeBalances() {
374
+ const balanceModules = useBalanceModules();
375
+ const chaindataProvider = useChaindata();
376
+ const chainConnectors = useChainConnectors();
377
+ const [allAddresses] = useAllAddresses();
378
+ const tokens = dexieReactHooks.useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.tokens(), [chaindataProvider]);
379
+ const tokenIds = react.useMemo(() => Object.values(tokens ?? {}).map(({
380
+ id
381
+ }) => id), [tokens]);
382
+ const addressesByToken = react.useMemo(() => Object.fromEntries(tokenIds.map(tokenId => [tokenId, allAddresses])), [allAddresses, tokenIds]);
383
+ const generationRef = react.useRef(0);
384
+ const createSubscription = react.useCallback(() => {
385
+ if (!chainConnectors.substrate) return;
386
+ if (!chainConnectors.evm) return;
387
+ if (!chaindataProvider) return;
388
+ const generation = (generationRef.current + 1) % Number.MAX_SAFE_INTEGER;
389
+ generationRef.current = generation;
390
+ const unsubs = balanceModules.map(balanceModule => {
391
+ // filter out tokens to only include those which this module knows how to fetch balances for
392
+ const moduleTokenIds = Object.values(tokens ?? {}).filter(({
393
+ type
394
+ }) => type === balanceModule.type).map(({
395
+ id
396
+ }) => id);
397
+ const addressesByModuleToken = Object.fromEntries(Object.entries(addressesByToken).filter(([tokenId]) => moduleTokenIds.includes(tokenId)));
398
+ const subscribe = createMulticastSubscription(next => {
399
+ const unsub = balances.balances(balanceModule, chainConnectors, chaindataProvider, addressesByModuleToken, (error, balances) => {
400
+ // log errors
401
+ if (error) return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
402
+ // ignore empty balance responses
403
+ if (!balances) return;
404
+ // ignore balances from old subscriptions which are still in the process of unsubscribing
405
+ if (generationRef.current !== generation) return;
406
+ next(balances);
407
+ });
408
+ return () => {
409
+ // unsubscribe from upstream
410
+ unsub.then(unsubscribe => unsubscribe());
411
+
412
+ // set this subscription's balances in the store to status: cache
413
+ balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.filter(balance => {
414
+ if (balance.source !== balanceModule.type) return false;
415
+ if (!Object.keys(addressesByModuleToken).includes(balance.tokenId)) return false;
416
+ if (!addressesByModuleToken[balance.tokenId].includes(balance.address)) return false;
417
+ return true;
418
+ }).modify({
419
+ status: "cache"
420
+ }));
421
+ };
422
+ });
423
+ const unsubscribe = subscribe(balances$1 => {
424
+ const putBalances = Object.entries(balances$1.toJSON()).map(([id, balance]) => ({
425
+ id,
426
+ ...balance
427
+ }));
428
+ balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.bulkPut(putBalances));
429
+ });
430
+ return unsubscribe;
124
431
  });
125
- }, [chaindata, chaindata === null || chaindata === void 0 ? void 0 : chaindata.generation]);
126
- return chains || {};
432
+ const unsubscribeAll = () => unsubs.forEach(unsub => unsub());
433
+ return unsubscribeAll;
434
+ }, [addressesByToken, balanceModules, chainConnectors, chaindataProvider, tokens]);
435
+ const subscribe = useMulticastSubscription(createSubscription);
436
+ return subscribe;
127
437
  }
128
- function useChain(chaindata, chainId) {
129
- const [chain, setChain] = react.useState();
130
- react.useEffect(() => {
131
- if (chaindata === null) return;
132
- if (!chainId) return;
133
- chaindata.getChain(chainId).then(setChain);
134
- }, [chainId, chaindata, chaindata === null || chaindata === void 0 ? void 0 : chaindata.generation]);
135
- return chain;
438
+
439
+ function useChains(withTestnets) {
440
+ // keep db data up to date
441
+ useDbCacheSubscription("chains");
442
+ const {
443
+ chainsWithTestnetsMap,
444
+ chainsWithoutTestnetsMap
445
+ } = useDbCache();
446
+ return withTestnets ? chainsWithTestnetsMap : chainsWithoutTestnetsMap;
136
447
  }
137
- function useEvmNetworks(chaindata) {
138
- const [evmNetworks, setEvmNetworks] = react.useState();
139
- react.useEffect(() => {
140
- if (!chaindata) return;
141
- const thisGeneration = chaindata.generation;
142
- chaindata.evmNetworks().then(evmNetworks => {
143
- if (thisGeneration !== chaindata.generation) return;
144
- setEvmNetworks(evmNetworks);
145
- });
146
- }, [chaindata, chaindata === null || chaindata === void 0 ? void 0 : chaindata.generation]);
147
- return evmNetworks || {};
448
+ function useChain(chainId, withTestnets) {
449
+ const chains = useChains(withTestnets);
450
+ return chainId ? chains[chainId] : undefined;
148
451
  }
149
- function useEvmNetwork(chaindata, evmNetworkId) {
150
- const [evmNetwork, setEvmNetwork] = react.useState();
151
- react.useEffect(() => {
152
- if (chaindata === null) return;
153
- if (!evmNetworkId) return;
154
- chaindata.getEvmNetwork(evmNetworkId).then(setEvmNetwork);
155
- }, [chaindata, chaindata === null || chaindata === void 0 ? void 0 : chaindata.generation, evmNetworkId]);
156
- return evmNetwork;
452
+
453
+ function useEvmNetworks(withTestnets) {
454
+ // keep db data up to date
455
+ useDbCacheSubscription("evmNetworks");
456
+ const {
457
+ evmNetworksWithTestnetsMap,
458
+ evmNetworksWithoutTestnetsMap
459
+ } = useDbCache();
460
+ return withTestnets ? evmNetworksWithTestnetsMap : evmNetworksWithoutTestnetsMap;
157
461
  }
158
- function useTokens(chaindata) {
159
- const [tokens, setTokens] = react.useState();
160
- react.useEffect(() => {
161
- if (!chaindata) return;
162
- const thisGeneration = chaindata.generation;
163
- chaindata.tokens().then(tokens => {
164
- if (thisGeneration !== chaindata.generation) return;
165
- setTokens(tokens);
166
- });
167
- }, [chaindata, chaindata === null || chaindata === void 0 ? void 0 : chaindata.generation]);
168
- return tokens || {};
462
+ function useEvmNetwork(evmNetworkId, withTestnets) {
463
+ const evmNetworks = useEvmNetworks(withTestnets);
464
+ return evmNetworkId ? evmNetworks[evmNetworkId] : undefined;
169
465
  }
170
- function useToken(chaindata, tokenId) {
171
- const [token, setToken] = react.useState();
172
- react.useEffect(() => {
173
- if (chaindata === null) return;
174
- if (!tokenId) return;
175
- chaindata.getToken(tokenId).then(setToken);
176
- }, [chaindata, chaindata === null || chaindata === void 0 ? void 0 : chaindata.generation, tokenId]);
177
- return token;
466
+
467
+ function useTokens(withTestnets) {
468
+ // keep db data up to date
469
+ useDbCacheSubscription("tokens");
470
+ const {
471
+ tokensWithTestnetsMap,
472
+ tokensWithoutTestnetsMap
473
+ } = useDbCache();
474
+ return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
475
+ }
476
+ function useToken(tokenId, withTestnets) {
477
+ const tokens = useTokens(withTestnets);
478
+ return tokenId ? tokens[tokenId] : undefined;
178
479
  }
179
480
 
180
- function useTokenRates(tokens) {
181
- const generation = react.useRef(0);
182
- const [tokenRates$1, setTokenRates] = react.useState();
183
- react.useEffect(() => {
184
- if (!tokens) return;
185
- if (Object.keys(tokens).length < 1) return;
481
+ const useBalancesHydrate = withTestnets => {
482
+ const chains = useChains(withTestnets);
483
+ const evmNetworks = useEvmNetworks(withTestnets);
484
+ const tokens = useTokens(withTestnets);
186
485
 
187
- // when we make a new request, we want to ignore any old requests which haven't yet completed
188
- // otherwise we risk replacing the most recent data with older data
189
- generation.current = (generation.current + 1) % Number.MAX_SAFE_INTEGER;
190
- const thisGeneration = generation.current;
191
- tokenRates.fetchTokenRates(tokens).then(tokenRates => {
192
- if (thisGeneration !== generation.current) return;
193
- setTokenRates(tokenRates);
194
- });
195
- }, [tokens]);
196
- return tokenRates$1 || {};
197
- }
486
+ // TODO: Store in a DB so that we don't have to wait for tokens before we can begin to fetch tokenRates
487
+ // useDbCacheSubscription("tokenRates")
488
+ const {
489
+ tokenRatesMap: tokenRates
490
+ } = useDbCache();
491
+ return react.useMemo(() => ({
492
+ chains,
493
+ evmNetworks,
494
+ tokens,
495
+ tokenRates
496
+ }), [chains, evmNetworks, tokens, tokenRates]);
497
+ };
198
498
 
199
- // TODO: Add the equivalent functionalty of `useDbCache` directly to this library.
200
- //
201
- // How it will work:
202
- //
203
- // useChains/useEvmNetworks/useTokens/useTokenRates will all make use of a
204
- // useCachedDb hook, which internally subscribes to all of the db tables
205
- // for everything, and then filters the subscribed data based on what params
206
- // the caller of useChains/useTokens/etc has provided.
207
- function useBalances(
208
- // TODO: Make this array of BalanceModules more type-safe
209
- balanceModules, chaindataProvider, addressesByToken, options = {}) {
210
- useBalancesSubscriptions(balanceModules, chaindataProvider, addressesByToken, options);
211
- const chains = useChains(chaindataProvider);
212
- const evmNetworks = useEvmNetworks(chaindataProvider);
213
- const tokens = useTokens(chaindataProvider);
214
- const tokenRates = useTokenRates(tokens);
215
- const balances$1 = dexieReactHooks.useLiveQuery(async () => new balances.Balances(await balances.db.balances.filter(balance => {
499
+ function useBalances(addressesByToken) {
500
+ // keep db data up to date
501
+ useDbCacheSubscription("balances");
502
+ const balanceModules = useBalanceModules();
503
+ const {
504
+ balances: balances$1
505
+ } = useDbCache();
506
+ const hydrate = useBalancesHydrate();
507
+ return react.useMemo(() => new balances.Balances(balances$1.filter(balance => {
216
508
  // check that this balance is included in our queried balance modules
217
509
  if (!balanceModules.map(({
218
510
  type
@@ -229,145 +521,49 @@ balanceModules, chaindataProvider, addressesByToken, options = {}) {
229
521
 
230
522
  // keep this balance
231
523
  return true;
232
- }).toArray(),
524
+ }),
233
525
  // hydrate balance chains, evmNetworks, tokens and tokenRates
234
- {
235
- chains,
236
- evmNetworks,
237
- tokens,
238
- tokenRates
239
- }), [balanceModules, addressesByToken, chains, evmNetworks, tokens, tokenRates]);
240
-
241
- // debounce every 100ms to prevent hammering UI with updates
242
- const [debouncedBalances, setDebouncedBalances] = react.useState(balances$1);
243
- reactUse.useDebounce(() => balances$1 && setDebouncedBalances(balances$1), 100, [balances$1]);
244
- return debouncedBalances;
245
- }
246
-
247
- // TODO: Turn into react context
248
- const subscriptions = {};
249
-
250
- // This hook is responsible for allowing us to call useBalances
251
- // from multiple components, without setting up unnecessary
252
- // balance subscriptions
253
- function useBalancesSubscriptions(
254
- // TODO: Make this array of BalanceModules more type-safe
255
- balanceModules, chaindataProvider, addressesByToken, options = {}) {
256
- // const subscriptions = useRef<
257
- // Record<string, { unsub: Promise<() => void>; refcount: number; generation: number }>
258
- // >({})
259
-
260
- const addSubscription = (key, balanceModule, chainConnectors, chaindataProvider, addressesByToken) => {
261
- // create subscription if it doesn't already exist
262
- if (!subscriptions[key] || subscriptions[key].refcount === 0) {
263
- var _subscriptions$key;
264
- const generation = ((((_subscriptions$key = subscriptions[key]) === null || _subscriptions$key === void 0 ? void 0 : _subscriptions$key.generation) || 0) + 1) % Number.MAX_SAFE_INTEGER;
265
- const unsub = balances.balances(balanceModule, chainConnectors, chaindataProvider, addressesByToken, (error, balances$1) => {
266
- if (error) return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
267
- if (!balances$1) return;
268
-
269
- // ignore balances from old subscriptions which are still in the process of unsubscribing
270
- if (subscriptions[key].generation !== generation) return;
271
- const putBalances = Object.entries(balances$1.toJSON()).map(([id, balance]) => ({
272
- id,
273
- ...balance
274
- }));
275
- balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.bulkPut(putBalances));
276
- });
277
- subscriptions[key] = {
278
- unsub,
279
- refcount: 0,
280
- generation
281
- };
282
- }
283
-
284
- // bump up the refcount by 1
285
- subscriptions[key].refcount += 1;
286
- };
287
- const removeSubscription = (key, balanceModule, addressesByToken) => {
288
- // ignore dead subscriptions
289
- if (!subscriptions[key] || subscriptions[key].refcount === 0) return;
290
-
291
- // drop the refcount by one
292
- subscriptions[key].refcount -= 1;
293
-
294
- // unsubscribe if refcount is now 0 (nobody wants this subcription anymore)
295
- if (subscriptions[key].refcount < 1) {
296
- // remove subscription
297
- subscriptions[key].unsub.then(unsub => unsub());
298
- delete subscriptions[key];
299
-
300
- // set this subscription's balances in the store to status: cache
301
- balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.filter(balance => {
302
- if (balance.source !== balanceModule.type) return false;
303
- if (!Object.keys(addressesByToken).includes(balance.tokenId)) return false;
304
- if (!addressesByToken[balance.tokenId].includes(balance.address)) return false;
305
- return true;
306
- }).modify({
307
- status: "cache"
308
- }));
309
- }
310
- };
311
- const chainConnector = useChainConnector(chaindataProvider);
312
- const chainConnectorEvm = useChainConnectorEvm(chaindataProvider, options);
313
- const tokens = useTokens(chaindataProvider);
314
- react.useEffect(() => {
315
- if (chainConnector === null) return;
316
- if (chainConnectorEvm === null) return;
317
- if (chaindataProvider === null) return;
318
- if (addressesByToken === null) return;
319
- const unsubs = balanceModules.map(balanceModule => {
320
- const subscriptionKey = `${balanceModule.type}-${JSON.stringify(addressesByToken)}`;
321
-
322
- // filter out tokens to only include those which this module knows how to fetch balances for
323
- const moduleTokenIds = Object.values(tokens).filter(({
324
- type
325
- }) => type === balanceModule.type).map(({
326
- id
327
- }) => id);
328
- const addressesByModuleToken = Object.fromEntries(Object.entries(addressesByToken).filter(([tokenId]) => moduleTokenIds.includes(tokenId)));
329
-
330
- // add balance subscription for this module
331
- addSubscription(subscriptionKey, balanceModule, {
332
- substrate: chainConnector,
333
- evm: chainConnectorEvm
334
- }, chaindataProvider, addressesByModuleToken);
335
-
336
- // return an unsub method, to be called when this effect unmounts
337
- return () => removeSubscription(subscriptionKey, balanceModule, addressesByToken);
338
- });
339
- const unsubAll = () => unsubs.forEach(unsub => unsub());
340
- return unsubAll;
341
- }, [addressesByToken, balanceModules, chainConnector, chainConnectorEvm, chaindataProvider, tokens]);
526
+ hydrate), [balances$1, hydrate, balanceModules, addressesByToken]);
342
527
  }
343
528
 
344
- // TODO: Allow advanced users of this library to provide their own chain connector
345
- function useChainConnector(chaindataProvider) {
346
- const [chainConnector$1, setChainConnector] = react.useState(null);
347
- react.useEffect(() => {
348
- if (chaindataProvider === null) return;
349
- setChainConnector(new chainConnector.ChainConnector(chaindataProvider));
350
- }, [chaindataProvider]);
351
- return chainConnector$1;
352
- }
353
- // TODO: Allow advanced users of this library to provide their own chain connector
354
- function useChainConnectorEvm(chaindataProvider, options = {}) {
355
- const [chainConnectorEvm$1, setChainConnectorEvm] = react.useState(null);
356
- react.useEffect(() => {
357
- if (chaindataProvider === null) return;
358
- setChainConnectorEvm(new chainConnectorEvm.ChainConnectorEvm(chaindataProvider, {
359
- onfinalityApiKey: options.onfinalityApiKey
360
- }));
361
- }, [chaindataProvider, options.onfinalityApiKey]);
362
- return chainConnectorEvm$1;
363
- }
529
+ const BalancesProvider = ({
530
+ balanceModules,
531
+ onfinalityApiKey,
532
+ useTestnets,
533
+ children
534
+ }) => /*#__PURE__*/React.createElement(ChaindataProvider, {
535
+ onfinalityApiKey: onfinalityApiKey
536
+ }, /*#__PURE__*/React.createElement(ChainConnectorsProvider, {
537
+ onfinalityApiKey: onfinalityApiKey
538
+ }, /*#__PURE__*/React.createElement(AllAddressesProvider, null, /*#__PURE__*/React.createElement(BalanceModulesProvider, {
539
+ balanceModules: balanceModules
540
+ }, /*#__PURE__*/React.createElement(DbCacheProvider, {
541
+ useTestnets: useTestnets
542
+ }, /*#__PURE__*/React.createElement(SubscriptionsProvider, null, children))))));
364
543
 
544
+ exports.AllAddressesProvider = AllAddressesProvider;
545
+ exports.BalanceModulesProvider = BalanceModulesProvider;
546
+ exports.BalancesProvider = BalancesProvider;
547
+ exports.ChainConnectorsProvider = ChainConnectorsProvider;
548
+ exports.ChaindataProvider = ChaindataProvider;
549
+ exports.DbCacheProvider = DbCacheProvider;
550
+ exports.SubscriptionsProvider = SubscriptionsProvider;
551
+ exports.createMulticastSubscription = createMulticastSubscription;
552
+ exports.provideContext = provideContext;
553
+ exports.useAllAddresses = useAllAddresses;
554
+ exports.useBalanceModules = useBalanceModules;
365
555
  exports.useBalances = useBalances;
556
+ exports.useBalancesHydrate = useBalancesHydrate;
366
557
  exports.useChain = useChain;
558
+ exports.useChainConnectors = useChainConnectors;
367
559
  exports.useChaindata = useChaindata;
368
560
  exports.useChains = useChains;
561
+ exports.useDbCache = useDbCache;
562
+ exports.useDbCacheSubscription = useDbCacheSubscription;
369
563
  exports.useEvmNetwork = useEvmNetwork;
370
564
  exports.useEvmNetworks = useEvmNetworks;
565
+ exports.useMulticastSubscription = useMulticastSubscription;
566
+ exports.useSubscriptions = useSubscriptions;
371
567
  exports.useToken = useToken;
372
568
  exports.useTokenRates = useTokenRates;
373
569
  exports.useTokens = useTokens;