@talismn/balances-react 0.9.11 → 1.0.0

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,30 +2,26 @@
2
2
 
3
3
  var jotai = require('jotai');
4
4
  var react = require('react');
5
- var balances = require('@talismn/balances');
6
5
  var tokenRates = require('@talismn/token-rates');
7
6
  var jsxRuntime = require('react/jsx-runtime');
8
- var util = require('@talismn/util');
7
+ var chaindataProvider = require('@talismn/chaindata-provider');
8
+ var balances = require('@talismn/balances');
9
9
  var jotaiEffect = require('jotai-effect');
10
- var utils = require('jotai/utils');
11
- var lodash = require('lodash');
12
- var rxjs = require('rxjs');
13
- var anylogger = require('anylogger');
10
+ var lodashEs = require('lodash-es');
14
11
  var chainConnector = require('@talismn/chain-connector');
15
12
  var chainConnectorEvm = require('@talismn/chain-connector-evm');
16
13
  var connectionMeta = require('@talismn/connection-meta');
17
- var chaindataProvider = require('@talismn/chaindata-provider');
18
- var utilCrypto = require('@polkadot/util-crypto');
14
+ var util = require('@talismn/util');
15
+ var utils = require('jotai/utils');
16
+ var rxjs = require('rxjs');
19
17
  var dexie = require('dexie');
20
- var isEqual = require('lodash/isEqual');
18
+ var anylogger = require('anylogger');
19
+ var utilCrypto = require('@polkadot/util-crypto');
21
20
 
22
21
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
23
22
 
24
23
  var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
25
- var isEqual__default = /*#__PURE__*/_interopDefault(isEqual);
26
24
 
27
- const balanceModuleCreatorsAtom = jotai.atom(balances.defaultBalanceModules);
28
- const onfinalityApiKeyAtom = jotai.atom(undefined);
29
25
  const innerCoinsApiConfigAtom = jotai.atom(tokenRates.DEFAULT_COINSAPI_CONFIG);
30
26
  const coinsApiConfigAtom = jotai.atom(get => get(innerCoinsApiConfigAtom), (_get, set, options) => set(innerCoinsApiConfigAtom, {
31
27
  apiUrl: options.apiUrl ?? tokenRates.DEFAULT_COINSAPI_CONFIG.apiUrl
@@ -37,137 +33,58 @@ const enabledTokensAtom = jotai.atom(undefined);
37
33
  /** Sets the list of addresses for which token balances will be fetched by the balances subscription */
38
34
  const allAddressesAtom = jotai.atom([]);
39
35
 
40
- var packageJson = {
41
- name: "@talismn/balances-react"};
42
-
43
- var log = anylogger__default.default(packageJson.name);
44
-
45
- /**
46
- // Persistence backend for indexedDB
47
- // Add a new backend by implementing the BalancesPersistBackend interface
48
- // configureStore can be called with a different indexedDB table
49
- */
50
- const {
51
- persistData,
52
- retrieveData
53
- } = balances.configureStore();
54
-
55
- /**
56
- // Persistence backend for localStorage
57
- */
58
- const localStoragePersist = async balances$1 => {
59
- const storedBalances = balances$1.map(b => {
60
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
61
- const {
62
- status,
63
- ...rest
64
- } = b;
65
- return rest;
66
- });
67
- const deflated = balances.compress(storedBalances);
68
- localStorage.setItem("talismanBalances", deflated.toString());
69
- };
70
- const localStorageRetrieve = async () => {
71
- const deflated = localStorage.getItem("talismanBalances");
72
- if (deflated) {
73
- // deflated will be a long string of numbers separated by commas
74
- const deflatedArray = deflated.split(",").map(n => parseInt(n, 10));
75
- const deflatedBytes = new Uint8Array(deflatedArray.length);
76
- deflatedArray.forEach((n, i) => deflatedBytes[i] = n);
77
- return balances.decompress(deflatedBytes).map(b => ({
78
- ...b,
79
- status: "cache"
80
- }));
81
- }
82
- return [];
83
- };
84
- const localStorageBalancesPersistBackend = {
85
- persist: localStoragePersist,
86
- retrieve: localStorageRetrieve
87
- };
88
-
89
- const cryptoWaitReadyAtom = jotai.atom(async () => await utilCrypto.cryptoWaitReady());
90
-
91
- const chaindataProviderAtom = jotai.atom(get => {
92
- // runs a timer to keep chaindata hydrated
93
- get(chaindataHydrateAtomEffect);
94
- return new chaindataProvider.ChaindataProvider({
95
- onfinalityApiKey: get(onfinalityApiKeyAtom)
96
- });
36
+ const chaindataProviderAtom = jotai.atom(() => {
37
+ return new chaindataProvider.ChaindataProvider({});
97
38
  });
98
- const miniMetadataHydratedAtom = jotai.atom(false);
99
39
 
100
- /** This atomEffect keeps chaindata hydrated (i.e. up to date with the GitHub repo) */
101
- const chaindataHydrateAtomEffect = jotaiEffect.atomEffect((get, set) => {
40
+ const chainConnectorsAtom = jotai.atom(get => {
102
41
  const chaindataProvider = get(chaindataProviderAtom);
103
- const miniMetadataUpdater = get(miniMetadataUpdaterAtom);
104
- const evmTokenFetcher = get(evmTokenFetcherAtom);
105
- const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
106
- const retryTimeout = 5_000; // 5_000ms = 5 seconds
107
-
108
- let timeout = null;
109
- const hydrate = async () => {
110
- try {
111
- await get(cryptoWaitReadyAtom);
112
- await balances.hydrateChaindataAndMiniMetadata(chaindataProvider, miniMetadataUpdater);
113
- await balances.updateCustomMiniMetadata(chaindataProvider, miniMetadataUpdater);
114
- await balances.updateEvmTokens(chaindataProvider, evmTokenFetcher);
115
- set(miniMetadataHydratedAtom, true);
116
- timeout = setTimeout(hydrate, loopMs);
117
- } catch (error) {
118
- log.error(`Failed to hydrate chaindata, retrying in ${Math.round(retryTimeout / 1000)} seconds`, error);
119
- timeout = setTimeout(hydrate, retryTimeout);
120
- }
42
+ const substrate = new chainConnector.ChainConnector(chaindataProvider, connectionMeta.connectionMetaDb);
43
+ const evm = new chainConnectorEvm.ChainConnectorEvm(chaindataProvider);
44
+ return {
45
+ substrate,
46
+ evm
121
47
  };
122
-
123
- // launch the loop
124
- hydrate();
125
-
126
- // return an unsub function to shut down the loop
127
- return () => timeout && clearTimeout(timeout);
128
48
  });
129
49
 
130
- /** MiniMetadataUpdater is a class used for hydrating chaindata */
131
- const miniMetadataUpdaterAtom = jotai.atom(get => {
132
- const chainConnectors = get(chainConnectorsAtom);
133
- const chaindataProvider = get(chaindataProviderAtom);
134
- const balanceModules = get(balanceModulesAtom);
135
- return new balances.MiniMetadataUpdater(chainConnectors, chaindataProvider, balanceModules);
50
+ const balancesProviderAtom = jotai.atom(get => {
51
+ return new balances.BalancesProvider(get(chaindataProviderAtom), get(chainConnectorsAtom)) // TODO pass storage
52
+ ;
136
53
  });
137
54
 
138
- /** EvmTokenFetcher is a class used for hydrating chaindata */
139
- const evmTokenFetcherAtom = jotai.atom(get => {
140
- const chaindataProvider = get(chaindataProviderAtom);
141
- const balanceModules = get(balanceModulesAtom);
142
- return new balances.EvmTokenFetcher(chaindataProvider, balanceModules);
55
+ const chaindataAtom = utils.atomWithObservable(get => {
56
+ return rxjs.combineLatest({
57
+ networks: get(chaindataProviderAtom).networks$,
58
+ tokens: get(chaindataProviderAtom).tokens$
59
+ }).pipe(util.firstThenDebounce(1_000));
143
60
  });
144
-
145
- const chainConnectorsAtom = jotai.atom(get => {
146
- const onfinalityApiKey = get(onfinalityApiKeyAtom);
147
- const chaindataProvider = get(chaindataProviderAtom);
148
- const substrate = new chainConnector.ChainConnector(chaindataProvider, connectionMeta.connectionMetaDb);
149
- const evm = new chainConnectorEvm.ChainConnectorEvm(chaindataProvider, {
150
- onfinalityApiKey
151
- });
61
+ const filteredChaindataAtom = jotai.atom(async get => {
62
+ const enabledNetworkIds = get(enabledChainsAtom);
63
+ const enabledTokenIds = get(enabledTokensAtom);
64
+ const enableTestnets = get(enableTestnetsAtom);
65
+ const chaindata = await get(chaindataAtom);
66
+ const networks = chaindata.networks.filter(n => (enabledNetworkIds?.includes(n.id) || n.isDefault) && (enableTestnets || !n.isTestnet));
67
+ const networkById = lodashEs.keyBy(networks, n => n.id);
68
+ const tokens = chaindata.tokens.filter(token => (enabledTokenIds?.includes(token.id) || token.isDefault) && networkById[token.networkId]);
152
69
  return {
153
- substrate,
154
- evm
70
+ networks,
71
+ tokens
155
72
  };
156
73
  });
157
-
158
- const balanceModulesAtom = jotai.atom(get => {
159
- const balanceModuleCreators = get(balanceModuleCreatorsAtom);
160
- const chainConnectors = get(chainConnectorsAtom);
161
- const chaindataProvider = get(chaindataProviderAtom);
162
- if (!chainConnectors.substrate) return [];
163
- if (!chainConnectors.evm) return [];
164
- if (!chaindataProvider) return [];
165
- return balanceModuleCreators.map(mod => mod({
166
- chainConnectors,
167
- chaindataProvider
168
- }));
74
+ const tokensAtom = jotai.atom(async get => {
75
+ const chaindata = await get(filteredChaindataAtom);
76
+ return chaindata.tokens;
77
+ });
78
+ const networksAtom = jotai.atom(async get => {
79
+ const chaindata = await get(filteredChaindataAtom);
80
+ return chaindata.networks;
169
81
  });
170
82
 
83
+ var packageJson = {
84
+ name: "@talismn/balances-react"};
85
+
86
+ var log = anylogger__default.default(packageJson.name);
87
+
171
88
  /**
172
89
  * Converts a dexie Observable into an rxjs Observable.
173
90
  */
@@ -181,48 +98,6 @@ function dexieToRxjs(o) {
181
98
  });
182
99
  }
183
100
 
184
- const chainsAtom = jotai.atom(async get => (await get(chaindataAtom)).chains);
185
- const chainsByIdAtom = jotai.atom(async get => (await get(chaindataAtom)).chainsById);
186
- const chainsByGenesisHashAtom = jotai.atom(async get => (await get(chaindataAtom)).chainsByGenesisHash);
187
- const evmNetworksAtom = jotai.atom(async get => (await get(chaindataAtom)).evmNetworks);
188
- const evmNetworksByIdAtom = jotai.atom(async get => (await get(chaindataAtom)).evmNetworksById);
189
- const tokensAtom = jotai.atom(async get => (await get(chaindataAtom)).tokens);
190
- const tokensByIdAtom = jotai.atom(async get => (await get(chaindataAtom)).tokensById);
191
- const miniMetadatasAtom = jotai.atom(async get => (await get(chaindataAtom)).miniMetadatas);
192
- const chaindataAtom = utils.atomWithObservable(get => {
193
- const enableTestnets = get(enableTestnetsAtom);
194
- const filterTestnets = items => enableTestnets ? items : items.filter(({
195
- isTestnet
196
- }) => !isTestnet);
197
- const filterMapTestnets = items => enableTestnets ? items : Object.fromEntries(Object.entries(items).filter(([, {
198
- isTestnet
199
- }]) => !isTestnet));
200
- const filterEnabledTokens = tokens => tokens.filter(token => token.isDefault || "isCustom" in token && token.isCustom);
201
- const filterMapEnabledTokens = tokensById => Object.fromEntries(Object.entries(tokensById).filter(([, token]) => token.isDefault || "isCustom" in token && token.isCustom));
202
- const distinctUntilIsEqual = rxjs.distinctUntilChanged((a, b) => isEqual__default.default(a, b));
203
- const chains = get(chaindataProviderAtom).chainsObservable.pipe(distinctUntilIsEqual, rxjs.map(filterTestnets), distinctUntilIsEqual);
204
- const chainsById = get(chaindataProviderAtom).chainsByIdObservable.pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), distinctUntilIsEqual);
205
- const chainsByGenesisHash = get(chaindataProviderAtom).chainsByGenesisHashObservable.pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), distinctUntilIsEqual);
206
- const evmNetworks = get(chaindataProviderAtom).evmNetworksObservable.pipe(distinctUntilIsEqual, rxjs.map(filterTestnets), distinctUntilIsEqual);
207
- const evmNetworksById = get(chaindataProviderAtom).evmNetworksByIdObservable.pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), distinctUntilIsEqual);
208
- const tokens = get(chaindataProviderAtom).tokensObservable.pipe(distinctUntilIsEqual, rxjs.map(filterTestnets), rxjs.map(filterEnabledTokens), distinctUntilIsEqual);
209
- const tokensById = get(chaindataProviderAtom).tokensByIdObservable.pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), rxjs.map(filterMapEnabledTokens), distinctUntilIsEqual);
210
- const miniMetadatasObservable = dexieToRxjs(dexie.liveQuery(() => balances.db.miniMetadatas.toArray()));
211
- const miniMetadatas = rxjs.combineLatest([miniMetadatasObservable.pipe(distinctUntilIsEqual), chainsById]).pipe(rxjs.map(([miniMetadatas, chainsById]) => miniMetadatas.filter(m => chainsById[m.chainId])), distinctUntilIsEqual);
212
- return rxjs.combineLatest({
213
- chains,
214
- chainsById,
215
- chainsByGenesisHash,
216
- evmNetworks,
217
- evmNetworksById,
218
- tokens,
219
- tokensById,
220
- miniMetadatas
221
- }).pipe(
222
- // debounce to prevent hammering UI with updates
223
- util.firstThenDebounce(1_000), distinctUntilIsEqual);
224
- });
225
-
226
101
  const tokenRatesAtom = jotai.atom(async get => {
227
102
  // runs a timer to keep tokenRates up to date
228
103
  get(tokenRatesFetcherAtomEffect);
@@ -243,9 +118,9 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
243
118
 
244
119
  // we have to get these synchronously so that jotai knows to restart our timer when they change
245
120
  const coinsApiConfig = get(coinsApiConfigAtom);
246
- const tokensByIdPromise = get(tokensByIdAtom);
121
+ const tokensPromise = get(tokensAtom);
247
122
  (async () => {
248
- const tokensById = await tokensByIdPromise;
123
+ const tokensById = lodashEs.keyBy(await tokensPromise, "id");
249
124
  const tokenIds = Object.keys(tokensById);
250
125
  const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
251
126
  const retryTimeout = 5_000; // 5_000ms = 5 seconds
@@ -275,7 +150,7 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
275
150
  const retrying = !abort.signal.aborted;
276
151
  const messageParts = ["Failed to fetch tokenRates", retrying && `retrying in ${Math.round(retryTimeout / 1000)} seconds`, !retrying && `giving up (timer no longer needed)`].filter(util.isTruthy);
277
152
  log.error(messageParts.join(", "), error);
278
- if (abort.signal.aborted) return; // don't schedule retry if aborted
153
+ if (util.isAbortError(error)) return; // don't schedule retry if aborted
279
154
  setTimeout(hydrate, retryTimeout);
280
155
  }
281
156
  };
@@ -286,218 +161,42 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
286
161
  return () => abort.abort("Unsubscribed");
287
162
  });
288
163
 
289
- const allBalancesAtom = jotai.atom(async get => {
290
- // set up our subscription to fetch balances from the various blockchains
291
- get(balancesSubscriptionAtomEffect);
292
- const [balances$1, hydrateData] = await Promise.all([get(balancesObservableAtom), get(balancesHydrateDataAtom)]);
293
- return new balances.Balances(Object.values(balances$1).filter(balance => !!hydrateData?.tokens?.[balance.tokenId]),
294
- // hydrate balance chains, evmNetworks, tokens and tokenRates
295
- hydrateData);
164
+ const addressesByTokenIdAtom = jotai.atom(async get => {
165
+ const [tokens, addresses] = await Promise.all([get(tokensAtom), get(allAddressesAtom)]);
166
+ return lodashEs.fromPairs(tokens.map(token => [token.id, addresses]));
296
167
  });
297
- const balancesObservable = new rxjs.BehaviorSubject({});
298
- const balancesObservableAtom = utils.atomWithObservable(() => balancesObservable);
299
- const balancesPersistBackendAtom = jotai.atom(localStorageBalancesPersistBackend);
300
- const hydrateBalancesObservableAtom = jotai.atom(async get => {
301
- const persistBackend = get(balancesPersistBackendAtom);
302
- const balances$1 = await persistBackend.retrieve();
303
- balancesObservable.next(Object.fromEntries(balances$1.map(b => [balances.getBalanceId(b), {
304
- ...b,
305
- status: "cache"
306
- }])));
168
+ const rawBalancesAtom = jotai.atom([]);
169
+ const subscribeBalancesAtom = jotaiEffect.atomEffect((get, set) => {
170
+ const unsub = (async () => {
171
+ const balancesProvider = get(balancesProviderAtom);
172
+ const addressesByTokenId = await get(addressesByTokenIdAtom);
173
+ const sub = balancesProvider.getBalances$(addressesByTokenId).subscribe(balances => {
174
+ set(rawBalancesAtom, balances.balances);
175
+ });
176
+ return () => {
177
+ return sub.unsubscribe();
178
+ };
179
+ })();
180
+ return () => {
181
+ unsub.then(unsubscribe => unsubscribe());
182
+ };
307
183
  });
308
184
  const balancesHydrateDataAtom = jotai.atom(async get => {
309
- const [{
310
- chainsById,
311
- evmNetworksById,
312
- tokensById
313
- }, tokenRates] = await Promise.all([get(chaindataAtom), get(tokenRatesAtom)]);
185
+ const [chaindata, tokenRates] = await Promise.all([get(chaindataAtom), get(tokenRatesAtom)]);
186
+ const networksById = lodashEs.keyBy(chaindata.networks, "id");
187
+ const tokensById = lodashEs.keyBy(chaindata.tokens, "id");
314
188
  return {
315
- chains: chainsById,
316
- evmNetworks: evmNetworksById,
189
+ networks: networksById,
317
190
  tokens: tokensById,
318
191
  tokenRates
319
192
  };
320
193
  });
321
- const balancesSubscriptionAtomEffect = jotaiEffect.atomEffect(get => {
322
- // lets us tear down the existing subscriptions when the atomEffect is restarted
323
- const abort = new AbortController();
324
-
325
- // we have to specify these synchronously, otherwise jotai won't know
326
- // that it needs to restart our subscriptions when they change
327
- const atomDependencies = Promise.all([get(cryptoWaitReadyAtom), get(balanceModulesAtom), get(miniMetadataHydratedAtom), get(allAddressesAtom), get(chainsAtom), get(chainsByIdAtom), get(evmNetworksAtom), get(evmNetworksByIdAtom), get(tokensAtom), get(tokensByIdAtom), get(miniMetadatasAtom), get(enabledChainsAtom), get(enabledTokensAtom), get(hydrateBalancesObservableAtom)]);
328
- const persistBackend = get(balancesPersistBackendAtom);
329
- const unsubsPromise = (async () => {
330
- const [_cryptoReady, balanceModules, miniMetadataHydrated, allAddresses, chains, chainsById, evmNetworks, evmNetworksById, tokens, tokensById, _miniMetadatas, enabledChainsConfig, enabledTokensConfig] = await atomDependencies;
331
- if (!miniMetadataHydrated) return;
332
- if (abort.signal.aborted) return;
333
-
334
- // persist data every thirty seconds
335
- balancesObservable.pipe(rxjs.debounceTime(10000)).subscribe(balancesUpdate => {
336
- persistBackend.persist(Object.values(balancesUpdate));
337
- });
338
- const updateBalances = async balancesUpdates => {
339
- if (abort.signal.aborted) return;
340
- const updatesWithIds = new balances.Balances(balancesUpdates);
341
- const existing = balancesObservable.value;
342
-
343
- // update initialising set here - before filtering out zero balances
344
- // while this may include stale balances, the important thing is that the balance is no longer "initialising"
345
- // balancesUpdates.forEach((b) => this.#initialising.delete(getBalanceId(b)))
346
-
347
- const newlyZeroBalances = [];
348
- const changedBalances = Object.fromEntries(updatesWithIds.each.filter(newB => {
349
- const isZero = newB.total.tokens === "0";
350
- // Keep new balances which are not zeros
351
- const existingB = existing[newB.id];
352
- if (!existingB && !isZero) return true;
353
- const hasChanged = !lodash.isEqual(existingB, newB.toJSON());
354
- // Collect balances now confirmed to be zero separately, so they can be filtered out from the main set
355
- if (existingB && hasChanged && isZero) newlyZeroBalances.push(newB.id);
356
- // Keep changed balances, which are not known zeros
357
- return hasChanged && !isZero;
358
- }).map(b => [b.id, b.toJSON()]));
359
- if (Object.keys(changedBalances).length === 0 && newlyZeroBalances.length === 0) return;
360
- const nonZeroBalances = newlyZeroBalances.length > 0 ? Object.fromEntries(Object.entries(existing).filter(([id]) => !newlyZeroBalances.includes(id))) : existing;
361
- const newBalancesState = {
362
- ...nonZeroBalances,
363
- ...changedBalances
364
- };
365
- if (Object.keys(newBalancesState).length === 0) return;
366
- balancesObservable.next(newBalancesState);
367
- };
368
- const deleteBalances = async balancesFilter => {
369
- if (abort.signal.aborted) return;
370
- const balancesToKeep = Object.fromEntries(new balances.Balances(Object.values(await get(balancesObservableAtom))).each.filter(b => !balancesFilter(b)).map(b => [b.id, b.toJSON()]));
371
- balancesObservable.next(balancesToKeep);
372
- };
373
- const enabledChainIds = enabledChainsConfig?.map(genesisHash => chains.find(chain => chain.genesisHash === genesisHash)?.id);
374
- const enabledChainsFilter = enabledChainIds ? token => token.chain && enabledChainIds?.includes(token.chain.id) : () => true;
375
- const enabledTokensFilter = enabledTokensConfig ? token => enabledTokensConfig.includes(token.id) : () => true;
376
- const enabledTokenIds = tokens.filter(enabledChainsFilter).filter(enabledTokensFilter).map(({
377
- id
378
- }) => id);
379
- if (enabledTokenIds.length < 1 || allAddresses.length < 1) return;
380
- const addressesByTokenByModule = {};
381
- enabledTokenIds.flatMap(tokenId => tokensById[tokenId]).forEach(token => {
382
- // filter out tokens on chains/evmNetworks which have no rpcs
383
- const hasRpcs = token.chain?.id && (chainsById[token.chain.id]?.rpcs?.length ?? 0) > 0 || token.evmNetwork?.id && (evmNetworksById[token.evmNetwork.id]?.rpcs?.length ?? 0) > 0;
384
- if (!hasRpcs) return;
385
- if (!addressesByTokenByModule[token.type]) addressesByTokenByModule[token.type] = {};
386
- addressesByTokenByModule[token.type][token.id] = allAddresses.filter(address => {
387
- // for each address, fetch balances only from compatible chains
388
- return util.isEthereumAddress(address) ? token.evmNetwork?.id || chainsById[token.chain?.id ?? ""]?.account === "secp256k1" : token.chain?.id && chainsById[token.chain?.id ?? ""]?.account !== "secp256k1";
389
- });
390
- });
391
-
392
- // Delete invalid cached balances
393
- const chainIds = new Set(chains.map(chain => chain.id));
394
- const evmNetworkIds = new Set(evmNetworks.map(evmNetwork => evmNetwork.id));
395
- await deleteBalances(balance => {
396
- // delete cached balances for accounts which don't exist anymore
397
- if (!balance.address || !allAddresses.includes(balance.address)) return true;
398
-
399
- // delete cached balances when chain/evmNetwork doesn't exist
400
- if (balance.chainId === undefined && balance.evmNetworkId === undefined) return true;
401
- if (balance.chainId !== undefined && !chainIds.has(balance.chainId)) return true;
402
- if (balance.evmNetworkId !== undefined && !evmNetworkIds.has(balance.evmNetworkId)) return true;
403
-
404
- // delete cached balance when token doesn't exist / is disabled
405
- if (!enabledTokenIds.includes(balance.tokenId)) return true;
406
-
407
- // delete cached balance when module doesn't exist
408
- if (!balanceModules.find(module => module.type === balance.source)) return true;
409
-
410
- // delete cached balance for accounts on incompatible chains
411
- // (substrate accounts shouldn't have evm balances)
412
- // (evm accounts shouldn't have substrate balances (unless the chain uses secp256k1 accounts))
413
- const chain = balance.chainId && chains.find(({
414
- id
415
- }) => id === balance.chainId) || null;
416
- const hasChain = balance.chainId && chainIds.has(balance.chainId);
417
- const hasEvmNetwork = balance.evmNetworkId && evmNetworkIds.has(balance.evmNetworkId);
418
- const chainUsesSecp256k1Accounts = chain?.account === "secp256k1";
419
- if (!util.isEthereumAddress(balance.address)) {
420
- if (!hasChain) return true;
421
- if (chainUsesSecp256k1Accounts) return true;
422
- }
423
- if (util.isEthereumAddress(balance.address)) {
424
- if (!hasEvmNetwork && !chainUsesSecp256k1Accounts) return true;
425
- }
426
-
427
- // keep balance
428
- return false;
429
- });
430
- if (abort.signal.aborted) return;
431
-
432
- // after 30 seconds, change the status of all balances still initializing to stale
433
- setTimeout(() => {
434
- if (abort.signal.aborted) return;
435
- const staleObservable = balancesObservable.pipe(rxjs.map(val => Object.values(val).filter(({
436
- status
437
- }) => status === "cache").map(balance => ({
438
- ...balance,
439
- status: "stale"
440
- }))));
441
- rxjs.firstValueFrom(staleObservable).then(v => {
442
- if (v.length) updateBalances(v);
443
- });
444
- }, 30_000);
445
- return balanceModules.map(balanceModule => {
446
- const unsub = balances.balances(balanceModule, addressesByTokenByModule[balanceModule.type] ?? {}, (error, balances) => {
447
- // log errors
448
- if (error) {
449
- if (error?.type === "STALE_RPC_ERROR" || error?.type === "WEBSOCKET_ALLOCATION_EXHAUSTED_ERROR") {
450
- const addressesByModuleToken = addressesByTokenByModule[balanceModule.type] ?? {};
451
- const staleObservable = balancesObservable.pipe(rxjs.map(val => Object.values(val).filter(balance => {
452
- const {
453
- tokenId,
454
- address,
455
- source
456
- } = balance;
457
- const chainComparison = error.chainId ? "chainId" in balance && error.chainId === balance.chainId : error.evmNetworkId ? "evmNetworkId" in balance && error.evmNetworkId === balance.evmNetworkId : true;
458
- return chainComparison && addressesByModuleToken[tokenId]?.includes(address) && source === balanceModule.type;
459
- }).map(balance => ({
460
- ...balance,
461
- status: "stale"
462
- }))));
463
- rxjs.firstValueFrom(staleObservable).then(v => {
464
- if (v.length) updateBalances(v);
465
- });
466
- }
467
- return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
468
- }
469
-
470
- // ignore empty balance responses
471
- if (!balances) return;
472
- // ignore balances from old subscriptions which are still in the process of unsubscribing
473
- if (abort.signal.aborted) return;
474
-
475
- // good balances
476
- if (balances) {
477
- if ("status" in balances) {
478
- // For modules using the new SubscriptionResultWithStatus pattern
479
- //TODO fix initialisin
480
- // if (result.status === "initialising") this.#initialising.add(balanceModule.type)
481
- // else this.#initialising.delete(balanceModule.type)
482
- updateBalances(balances.data);
483
- } else {
484
- // add good ones to initialisedBalances
485
- updateBalances(Object.values(balances.toJSON()));
486
- }
487
- }
488
- });
489
- return () => unsub.then(unsubscribe => unsubscribe());
490
- });
491
- })();
492
-
493
- // close the existing subscriptions when our effect unmounts
494
- // (wait 2 seconds before actually unsubscribing, to allow the websocket to be reused in that time)
495
- const unsubscribe = () => unsubsPromise.then(unsubs => {
496
- persistBackend.persist(Object.values(balancesObservable.value));
497
- unsubs?.forEach(unsub => unsub());
498
- });
499
- abort.signal.addEventListener("abort", () => setTimeout(unsubscribe, 2_000));
500
- return () => abort.abort("Unsubscribed");
194
+ const balancesAtom = jotai.atom(async get => {
195
+ // subscribe to balancesProvider getBalance with addressesByTokenIdAtom as param
196
+ get(subscribeBalancesAtom);
197
+ const hydrate = await get(balancesHydrateDataAtom);
198
+ const rawBalances = get(rawBalancesAtom);
199
+ return new balances.Balances(rawBalances, hydrate);
501
200
  });
502
201
 
503
202
  const useSetBalancesAddresses = addresses => {
@@ -514,10 +213,8 @@ const useSetBalancesAddresses = addresses => {
514
213
  * @returns a Balances object containing the current balances state.
515
214
  */
516
215
 
517
- const useBalances = persistBackend => {
518
- const [, setPersistBackend] = jotai.useAtom(balancesPersistBackendAtom);
519
- if (persistBackend) setPersistBackend(persistBackend);
520
- return jotai.useAtomValue(allBalancesAtom);
216
+ const useBalances = () => {
217
+ return jotai.useAtomValue(balancesAtom);
521
218
  };
522
219
 
523
220
  // TODO: Extract to shared definition between extension and @talismn/balances-react
@@ -548,41 +245,47 @@ const useBalancesStatus = balances => react.useMemo(() => {
548
245
  status: "live"
549
246
  };
550
247
  }, [balances]);
551
- const getStaleChains = balances => [...new Set(balances.sorted.filter(b => b.status === "stale").map(b => b.chain?.name ?? b.chainId ?? "Unknown"))];
248
+ const getStaleChains = balances => [...new Set(balances.each.filter(b => b.status === "stale").map(b => b.network?.name ?? b.networkId ?? "Unknown"))];
552
249
 
553
250
  const useChainConnectors = () => jotai.useAtomValue(chainConnectorsAtom);
554
251
 
555
252
  const useChaindataProvider = () => jotai.useAtomValue(chaindataProviderAtom);
556
253
  const useChaindata = () => jotai.useAtomValue(chaindataAtom);
557
- const useChains = () => jotai.useAtomValue(chainsByIdAtom);
558
- const useChainsByGenesisHash = () => jotai.useAtomValue(chainsByGenesisHashAtom);
559
- const useEvmNetworks = () => jotai.useAtomValue(evmNetworksByIdAtom);
560
- const useTokens = () => jotai.useAtomValue(tokensByIdAtom);
561
- const useMiniMetadatas = () => jotai.useAtomValue(miniMetadatasAtom);
562
- const useChain = chainId => useChains()[chainId ?? ""] ?? undefined;
563
- const useEvmNetwork = evmNetworkId => useEvmNetworks()[evmNetworkId ?? ""] ?? undefined;
564
- const useToken = tokenId => useTokens()[tokenId ?? ""] ?? undefined;
254
+ const useNetworks = () => useChaindata().networks;
255
+ const useNetworksById = () => {
256
+ const {
257
+ networks
258
+ } = useChaindata();
259
+ return react.useMemo(() => lodashEs.keyBy(networks, n => n.id), [networks]);
260
+ };
261
+ const useNetwork = networkId => {
262
+ const networksById = useNetworksById();
263
+ return networksById[networkId ?? ""] ?? null;
264
+ };
265
+ const useTokens = () => useChaindata().tokens;
266
+ const useTokensById = () => {
267
+ const {
268
+ tokens
269
+ } = useChaindata();
270
+ return react.useMemo(() => lodashEs.keyBy(tokens, t => t.id), [tokens]);
271
+ };
272
+ const useToken = tokenId => {
273
+ const tokensById = useTokensById();
274
+ return tokensById[tokenId ?? ""] ?? null;
275
+ };
565
276
 
566
277
  const useTokenRates = () => jotai.useAtomValue(tokenRatesAtom);
567
278
  const useTokenRate = tokenId => useTokenRates()[tokenId ?? ""] ?? undefined;
568
279
 
280
+ const cryptoWaitReadyAtom = jotai.atom(async () => await utilCrypto.cryptoWaitReady());
281
+
569
282
  const BalancesProvider = ({
570
- balanceModules,
571
- onfinalityApiKey,
572
283
  coinsApiUrl,
573
284
  withTestnets,
574
285
  enabledChains,
575
286
  enabledTokens,
576
287
  children
577
288
  }) => {
578
- const setBalanceModules = jotai.useSetAtom(balanceModuleCreatorsAtom);
579
- react.useEffect(() => {
580
- if (balanceModules !== undefined) setBalanceModules(balanceModules);
581
- }, [balanceModules, setBalanceModules]);
582
- const setOnfinalityApiKey = jotai.useSetAtom(onfinalityApiKeyAtom);
583
- react.useEffect(() => {
584
- setOnfinalityApiKey(onfinalityApiKey);
585
- }, [onfinalityApiKey, setOnfinalityApiKey]);
586
289
  const setCoinsApiConfig = jotai.useSetAtom(coinsApiConfigAtom);
587
290
  react.useEffect(() => {
588
291
  setCoinsApiConfig({
@@ -608,71 +311,54 @@ const BalancesProvider = ({
608
311
 
609
312
  Object.defineProperty(exports, "evmErc20TokenId", {
610
313
  enumerable: true,
611
- get: function () { return balances.evmErc20TokenId; }
314
+ get: function () { return chaindataProvider.evmErc20TokenId; }
612
315
  });
613
316
  Object.defineProperty(exports, "evmNativeTokenId", {
614
317
  enumerable: true,
615
- get: function () { return balances.evmNativeTokenId; }
318
+ get: function () { return chaindataProvider.evmNativeTokenId; }
616
319
  });
617
320
  Object.defineProperty(exports, "subAssetTokenId", {
618
321
  enumerable: true,
619
- get: function () { return balances.subAssetTokenId; }
620
- });
621
- Object.defineProperty(exports, "subEquilibriumTokenId", {
622
- enumerable: true,
623
- get: function () { return balances.subEquilibriumTokenId; }
322
+ get: function () { return chaindataProvider.subAssetTokenId; }
624
323
  });
625
324
  Object.defineProperty(exports, "subNativeTokenId", {
626
325
  enumerable: true,
627
- get: function () { return balances.subNativeTokenId; }
326
+ get: function () { return chaindataProvider.subNativeTokenId; }
628
327
  });
629
328
  Object.defineProperty(exports, "subPsp22TokenId", {
630
329
  enumerable: true,
631
- get: function () { return balances.subPsp22TokenId; }
330
+ get: function () { return chaindataProvider.subPsp22TokenId; }
632
331
  });
633
332
  Object.defineProperty(exports, "subTokensTokenId", {
634
333
  enumerable: true,
635
- get: function () { return balances.subTokensTokenId; }
334
+ get: function () { return chaindataProvider.subTokensTokenId; }
636
335
  });
637
336
  exports.BalancesProvider = BalancesProvider;
638
337
  exports.allAddressesAtom = allAddressesAtom;
639
- exports.allBalancesAtom = allBalancesAtom;
640
- exports.balanceModuleCreatorsAtom = balanceModuleCreatorsAtom;
641
- exports.balanceModulesAtom = balanceModulesAtom;
642
- exports.balancesPersistBackendAtom = balancesPersistBackendAtom;
338
+ exports.balancesAtom = balancesAtom;
643
339
  exports.chainConnectorsAtom = chainConnectorsAtom;
644
340
  exports.chaindataAtom = chaindataAtom;
645
341
  exports.chaindataProviderAtom = chaindataProviderAtom;
646
- exports.chainsAtom = chainsAtom;
647
- exports.chainsByGenesisHashAtom = chainsByGenesisHashAtom;
648
- exports.chainsByIdAtom = chainsByIdAtom;
649
342
  exports.coinsApiConfigAtom = coinsApiConfigAtom;
650
343
  exports.cryptoWaitReadyAtom = cryptoWaitReadyAtom;
651
344
  exports.enableTestnetsAtom = enableTestnetsAtom;
652
345
  exports.enabledChainsAtom = enabledChainsAtom;
653
346
  exports.enabledTokensAtom = enabledTokensAtom;
654
- exports.evmNetworksAtom = evmNetworksAtom;
655
- exports.evmNetworksByIdAtom = evmNetworksByIdAtom;
656
347
  exports.getStaleChains = getStaleChains;
657
- exports.miniMetadataHydratedAtom = miniMetadataHydratedAtom;
658
- exports.miniMetadatasAtom = miniMetadatasAtom;
659
- exports.onfinalityApiKeyAtom = onfinalityApiKeyAtom;
348
+ exports.networksAtom = networksAtom;
660
349
  exports.tokenRatesAtom = tokenRatesAtom;
661
350
  exports.tokensAtom = tokensAtom;
662
- exports.tokensByIdAtom = tokensByIdAtom;
663
351
  exports.useBalances = useBalances;
664
352
  exports.useBalancesStatus = useBalancesStatus;
665
- exports.useChain = useChain;
666
353
  exports.useChainConnectors = useChainConnectors;
667
354
  exports.useChaindata = useChaindata;
668
355
  exports.useChaindataProvider = useChaindataProvider;
669
- exports.useChains = useChains;
670
- exports.useChainsByGenesisHash = useChainsByGenesisHash;
671
- exports.useEvmNetwork = useEvmNetwork;
672
- exports.useEvmNetworks = useEvmNetworks;
673
- exports.useMiniMetadatas = useMiniMetadatas;
356
+ exports.useNetwork = useNetwork;
357
+ exports.useNetworks = useNetworks;
358
+ exports.useNetworksById = useNetworksById;
674
359
  exports.useSetBalancesAddresses = useSetBalancesAddresses;
675
360
  exports.useToken = useToken;
676
361
  exports.useTokenRate = useTokenRate;
677
362
  exports.useTokenRates = useTokenRates;
678
363
  exports.useTokens = useTokens;
364
+ exports.useTokensById = useTokensById;