@talismn/balances-react 0.0.0-pr2080-20250710073919 → 0.0.0-pr2091-20250715125148

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.
@@ -1,12 +1,10 @@
1
- import { AnyBalanceModule, Hydrate } from "@talismn/balances";
2
1
  import { ReactNode } from "react";
3
- export { evmErc20TokenId, evmNativeTokenId, subNativeTokenId, subAssetTokenId, subPsp22TokenId, subTokensTokenId, } from "@talismn/chaindata-provider";
2
+ export { evmErc20TokenId, evmNativeTokenId, subAssetTokenId, subNativeTokenId, subPsp22TokenId, subTokensTokenId, } from "@talismn/chaindata-provider";
4
3
  export * from "./hooks/useBalances";
5
4
  export * from "./hooks/useChainConnectors";
6
5
  export * from "./hooks/useChaindata";
7
6
  export * from "./hooks/useTokenRates";
8
7
  export * from "./atoms/allAddresses";
9
- export * from "./atoms/balanceModules";
10
8
  export * from "./atoms/balances";
11
9
  export * from "./atoms/chainConnectors";
12
10
  export * from "./atoms/chaindata";
@@ -15,10 +13,6 @@ export * from "./atoms/config";
15
13
  export * from "./atoms/cryptoWaitReady";
16
14
  export * from "./atoms/tokenRates";
17
15
  export type BalancesConfig = {
18
- /**
19
- * Optionally provide your own array of BalanceModules, when you don't want to use the defaults.
20
- */
21
- balanceModules?: Array<(hydrate: Hydrate) => AnyBalanceModule>;
22
16
  coinsApiUrl?: string;
23
17
  /** Enables balances fetching for tokens on testnet chains. */
24
18
  withTestnets?: boolean;
@@ -71,4 +65,4 @@ export type BalancesConfig = {
71
65
  enabledTokens?: string[];
72
66
  children?: ReactNode;
73
67
  };
74
- export declare const BalancesProvider: ({ balanceModules, coinsApiUrl, withTestnets, enabledChains, enabledTokens, children, }: BalancesConfig) => import("react/jsx-runtime").JSX.Element;
68
+ export declare const BalancesProvider: ({ coinsApiUrl, withTestnets, enabledChains, enabledTokens, children, }: BalancesConfig) => import("react/jsx-runtime").JSX.Element;
@@ -2,29 +2,27 @@
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
7
  var chaindataProvider = require('@talismn/chaindata-provider');
9
- var util = require('@talismn/util');
8
+ var balances = require('@talismn/balances');
10
9
  var jotaiEffect = require('jotai-effect');
11
- var utils = require('jotai/utils');
12
- var lodash = require('lodash');
13
- var rxjs = require('rxjs');
14
- var anylogger = require('anylogger');
10
+ var lodashEs = require('lodash-es');
15
11
  var chainConnector = require('@talismn/chain-connector');
16
12
  var chainConnectorEvm = require('@talismn/chain-connector-evm');
13
+ var chainConnectorSol = require('@talismn/chain-connector-sol');
17
14
  var connectionMeta = require('@talismn/connection-meta');
15
+ var util = require('@talismn/util');
16
+ var utils = require('jotai/utils');
17
+ var rxjs = require('rxjs');
18
18
  var dexie = require('dexie');
19
- var isEqual = require('lodash/isEqual');
19
+ var anylogger = require('anylogger');
20
20
  var utilCrypto = require('@polkadot/util-crypto');
21
21
 
22
22
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
23
23
 
24
24
  var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
25
- var isEqual__default = /*#__PURE__*/_interopDefault(isEqual);
26
25
 
27
- const balanceModuleCreatorsAtom = jotai.atom(balances.defaultBalanceModules);
28
26
  const innerCoinsApiConfigAtom = jotai.atom(tokenRates.DEFAULT_COINSAPI_CONFIG);
29
27
  const coinsApiConfigAtom = jotai.atom(get => get(innerCoinsApiConfigAtom), (_get, set, options) => set(innerCoinsApiConfigAtom, {
30
28
  apiUrl: options.apiUrl ?? tokenRates.DEFAULT_COINSAPI_CONFIG.apiUrl
@@ -36,55 +34,6 @@ const enabledTokensAtom = jotai.atom(undefined);
36
34
  /** Sets the list of addresses for which token balances will be fetched by the balances subscription */
37
35
  const allAddressesAtom = jotai.atom([]);
38
36
 
39
- var packageJson = {
40
- name: "@talismn/balances-react"};
41
-
42
- var log = anylogger__default.default(packageJson.name);
43
-
44
- /**
45
- // Persistence backend for indexedDB
46
- // Add a new backend by implementing the BalancesPersistBackend interface
47
- // configureStore can be called with a different indexedDB table
48
- */
49
- const {
50
- persistData,
51
- retrieveData
52
- } = balances.configureStore();
53
-
54
- /**
55
- // Persistence backend for localStorage
56
- */
57
- const localStoragePersist = async balances$1 => {
58
- const storedBalances = balances$1.map(b => {
59
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
60
- const {
61
- status,
62
- ...rest
63
- } = b;
64
- return rest;
65
- });
66
- const deflated = balances.compress(storedBalances);
67
- localStorage.setItem("talismanBalances", deflated.toString());
68
- };
69
- const localStorageRetrieve = async () => {
70
- const deflated = localStorage.getItem("talismanBalances");
71
- if (deflated) {
72
- // deflated will be a long string of numbers separated by commas
73
- const deflatedArray = deflated.split(",").map(n => parseInt(n, 10));
74
- const deflatedBytes = new Uint8Array(deflatedArray.length);
75
- deflatedArray.forEach((n, i) => deflatedBytes[i] = n);
76
- return balances.decompress(deflatedBytes).map(b => ({
77
- ...b,
78
- status: "cache"
79
- }));
80
- }
81
- return [];
82
- };
83
- const localStorageBalancesPersistBackend = {
84
- persist: localStoragePersist,
85
- retrieve: localStorageRetrieve
86
- };
87
-
88
37
  const chaindataProviderAtom = jotai.atom(() => {
89
38
  return new chaindataProvider.ChaindataProvider({});
90
39
  });
@@ -93,25 +42,52 @@ const chainConnectorsAtom = jotai.atom(get => {
93
42
  const chaindataProvider = get(chaindataProviderAtom);
94
43
  const substrate = new chainConnector.ChainConnector(chaindataProvider, connectionMeta.connectionMetaDb);
95
44
  const evm = new chainConnectorEvm.ChainConnectorEvm(chaindataProvider);
45
+ const solana = new chainConnectorSol.ChainConnectorSol(chaindataProvider);
96
46
  return {
97
47
  substrate,
98
- evm
48
+ evm,
49
+ solana
99
50
  };
100
51
  });
101
52
 
102
- const balanceModulesAtom = jotai.atom(get => {
103
- const balanceModuleCreators = get(balanceModuleCreatorsAtom);
104
- const chainConnectors = get(chainConnectorsAtom);
105
- const chaindataProvider = get(chaindataProviderAtom);
106
- if (!chainConnectors.substrate) return [];
107
- if (!chainConnectors.evm) return [];
108
- if (!chaindataProvider) return [];
109
- return balanceModuleCreators.map(mod => mod({
110
- chainConnectors,
111
- chaindataProvider
112
- }));
53
+ const balancesProviderAtom = jotai.atom(get => {
54
+ return new balances.BalancesProvider(get(chaindataProviderAtom), get(chainConnectorsAtom)) // TODO pass storage
55
+ ;
56
+ });
57
+
58
+ const chaindataAtom = utils.atomWithObservable(get => {
59
+ return rxjs.combineLatest({
60
+ networks: get(chaindataProviderAtom).networks$,
61
+ tokens: get(chaindataProviderAtom).tokens$
62
+ }).pipe(util.firstThenDebounce(1_000));
63
+ });
64
+ const filteredChaindataAtom = jotai.atom(async get => {
65
+ const enabledNetworkIds = get(enabledChainsAtom);
66
+ const enabledTokenIds = get(enabledTokensAtom);
67
+ const enableTestnets = get(enableTestnetsAtom);
68
+ const chaindata = await get(chaindataAtom);
69
+ const networks = chaindata.networks.filter(n => (enabledNetworkIds?.includes(n.id) || n.isDefault) && (enableTestnets || !n.isTestnet));
70
+ const networkById = lodashEs.keyBy(networks, n => n.id);
71
+ const tokens = chaindata.tokens.filter(token => (enabledTokenIds?.includes(token.id) || token.isDefault) && networkById[token.networkId]);
72
+ return {
73
+ networks,
74
+ tokens
75
+ };
76
+ });
77
+ const tokensAtom = jotai.atom(async get => {
78
+ const chaindata = await get(filteredChaindataAtom);
79
+ return chaindata.tokens;
80
+ });
81
+ const networksAtom = jotai.atom(async get => {
82
+ const chaindata = await get(filteredChaindataAtom);
83
+ return chaindata.networks;
113
84
  });
114
85
 
86
+ var packageJson = {
87
+ name: "@talismn/balances-react"};
88
+
89
+ var log = anylogger__default.default(packageJson.name);
90
+
115
91
  /**
116
92
  * Converts a dexie Observable into an rxjs Observable.
117
93
  */
@@ -125,54 +101,6 @@ function dexieToRxjs(o) {
125
101
  });
126
102
  }
127
103
 
128
- const chainsAtom = jotai.atom(async get => (await get(chaindataAtom)).chains);
129
- const chainsByIdAtom = jotai.atom(async get => (await get(chaindataAtom)).chainsById);
130
- const chainsByGenesisHashAtom = jotai.atom(async get => (await get(chaindataAtom)).chainsByGenesisHash);
131
- const evmNetworksAtom = jotai.atom(async get => (await get(chaindataAtom)).evmNetworks);
132
- const evmNetworksByIdAtom = jotai.atom(async get => (await get(chaindataAtom)).evmNetworksById);
133
- const tokensAtom = jotai.atom(async get => (await get(chaindataAtom)).tokens);
134
- const tokensByIdAtom = jotai.atom(async get => (await get(chaindataAtom)).tokensById);
135
- const miniMetadatasAtom = jotai.atom(async get => (await get(chaindataAtom)).miniMetadatas);
136
- const chaindataAtom = utils.atomWithObservable(get => {
137
- const enableTestnets = get(enableTestnetsAtom);
138
- const filterTestnets = items => enableTestnets ? items : items.filter(({
139
- isTestnet
140
- }) => !isTestnet);
141
- const filterMapTestnets = items => enableTestnets ? items : Object.fromEntries(Object.entries(items).filter(([, {
142
- isTestnet
143
- }]) => !isTestnet));
144
- const filterEnabledTokens = tokens => tokens.filter(token => token.isDefault || "isCustom" in token && token.isCustom);
145
- const filterMapEnabledTokens = tokensById => Object.fromEntries(Object.entries(tokensById).filter(([, token]) => token.isDefault || "isCustom" in token && token.isCustom));
146
- const distinctUntilIsEqual = rxjs.distinctUntilChanged((a, b) => isEqual__default.default(a, b));
147
- const chains = get(chaindataProviderAtom).getNetworks$("polkadot").pipe(distinctUntilIsEqual, rxjs.map(filterTestnets), distinctUntilIsEqual);
148
- const chainsById = get(chaindataProviderAtom).getNetworksMapById$("polkadot").pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), distinctUntilIsEqual);
149
- const chainsByGenesisHash = get(chaindataProviderAtom).getNetworksMapByGenesisHash$().pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), distinctUntilIsEqual);
150
- const evmNetworks = get(chaindataProviderAtom).getNetworks$("ethereum").pipe(distinctUntilIsEqual, rxjs.map(filterTestnets), distinctUntilIsEqual);
151
- const networks = get(chaindataProviderAtom).getNetworks$().pipe(distinctUntilIsEqual, rxjs.map(filterTestnets), distinctUntilIsEqual);
152
- const evmNetworksById = get(chaindataProviderAtom).getNetworksMapById$("ethereum").pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), distinctUntilIsEqual);
153
- const networksById = get(chaindataProviderAtom).getNetworksMapById$().pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), distinctUntilIsEqual);
154
- const tokens = get(chaindataProviderAtom).tokens$.pipe(distinctUntilIsEqual, rxjs.map(filterEnabledTokens), distinctUntilIsEqual);
155
- const tokensById = get(chaindataProviderAtom).getTokensMapById$().pipe(distinctUntilIsEqual, rxjs.map(filterMapEnabledTokens), distinctUntilIsEqual);
156
- const miniMetadatasObservable = dexieToRxjs(dexie.liveQuery(() => balances.db.miniMetadatas.toArray()));
157
- const miniMetadatas = rxjs.combineLatest([miniMetadatasObservable.pipe(distinctUntilIsEqual), chainsById]).pipe(rxjs.map(([miniMetadatas, chainsById]) => miniMetadatas.filter(m => chainsById[m.chainId])), distinctUntilIsEqual);
158
- return rxjs.combineLatest({
159
- networks,
160
- networksById,
161
- chains,
162
- chainsById,
163
- chainsByGenesisHash,
164
- evmNetworks,
165
- evmNetworksById,
166
- tokens,
167
- tokensById,
168
- miniMetadatas
169
- }).pipe(
170
- // debounce to prevent hammering UI with updates
171
- util.firstThenDebounce(1_000), distinctUntilIsEqual);
172
- });
173
-
174
- const cryptoWaitReadyAtom = jotai.atom(async () => await utilCrypto.cryptoWaitReady());
175
-
176
104
  const tokenRatesAtom = jotai.atom(async get => {
177
105
  // runs a timer to keep tokenRates up to date
178
106
  get(tokenRatesFetcherAtomEffect);
@@ -193,9 +121,9 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
193
121
 
194
122
  // we have to get these synchronously so that jotai knows to restart our timer when they change
195
123
  const coinsApiConfig = get(coinsApiConfigAtom);
196
- const tokensByIdPromise = get(tokensByIdAtom);
124
+ const tokensPromise = get(tokensAtom);
197
125
  (async () => {
198
- const tokensById = await tokensByIdPromise;
126
+ const tokensById = lodashEs.keyBy(await tokensPromise, "id");
199
127
  const tokenIds = Object.keys(tokensById);
200
128
  const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
201
129
  const retryTimeout = 5_000; // 5_000ms = 5 seconds
@@ -236,213 +164,42 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
236
164
  return () => abort.abort("Unsubscribed");
237
165
  });
238
166
 
239
- const allBalancesAtom = jotai.atom(async get => {
240
- // set up our subscription to fetch balances from the various blockchains
241
- get(balancesSubscriptionAtomEffect);
242
- const [balances$1, hydrateData] = await Promise.all([get(balancesObservableAtom), get(balancesHydrateDataAtom)]);
243
- return new balances.Balances(Object.values(balances$1).filter(balance => !!hydrateData?.tokens?.[balance.tokenId]),
244
- // hydrate balance chains, evmNetworks, tokens and tokenRates
245
- hydrateData);
167
+ const addressesByTokenIdAtom = jotai.atom(async get => {
168
+ const [tokens, addresses] = await Promise.all([get(tokensAtom), get(allAddressesAtom)]);
169
+ return lodashEs.fromPairs(tokens.map(token => [token.id, addresses]));
246
170
  });
247
- const balancesObservable = new rxjs.BehaviorSubject({});
248
- const balancesObservableAtom = utils.atomWithObservable(() => balancesObservable);
249
- const balancesPersistBackendAtom = jotai.atom(localStorageBalancesPersistBackend);
250
- const hydrateBalancesObservableAtom = jotai.atom(async get => {
251
- const persistBackend = get(balancesPersistBackendAtom);
252
- const balances$1 = await persistBackend.retrieve();
253
- balancesObservable.next(Object.fromEntries(balances$1.map(b => [balances.getBalanceId(b), {
254
- ...b,
255
- status: "cache"
256
- }])));
171
+ const rawBalancesAtom = jotai.atom([]);
172
+ const subscribeBalancesAtom = jotaiEffect.atomEffect((get, set) => {
173
+ const unsub = (async () => {
174
+ const balancesProvider = get(balancesProviderAtom);
175
+ const addressesByTokenId = await get(addressesByTokenIdAtom);
176
+ const sub = balancesProvider.getBalances$(addressesByTokenId).subscribe(balances => {
177
+ set(rawBalancesAtom, balances.balances);
178
+ });
179
+ return () => {
180
+ return sub.unsubscribe();
181
+ };
182
+ })();
183
+ return () => {
184
+ unsub.then(unsubscribe => unsubscribe());
185
+ };
257
186
  });
258
187
  const balancesHydrateDataAtom = jotai.atom(async get => {
259
- const [{
260
- networksById,
261
- tokensById
262
- }, tokenRates] = await Promise.all([get(chaindataAtom), get(tokenRatesAtom)]);
188
+ const [chaindata, tokenRates] = await Promise.all([get(chaindataAtom), get(tokenRatesAtom)]);
189
+ const networksById = lodashEs.keyBy(chaindata.networks, "id");
190
+ const tokensById = lodashEs.keyBy(chaindata.tokens, "id");
263
191
  return {
264
192
  networks: networksById,
265
193
  tokens: tokensById,
266
194
  tokenRates
267
195
  };
268
196
  });
269
- const balancesSubscriptionAtomEffect = jotaiEffect.atomEffect(get => {
270
- // lets us tear down the existing subscriptions when the atomEffect is restarted
271
- const abort = new AbortController();
272
-
273
- // we have to specify these synchronously, otherwise jotai won't know
274
- // that it needs to restart our subscriptions when they change
275
- const atomDependencies = Promise.all([get(cryptoWaitReadyAtom), get(balanceModulesAtom), get(allAddressesAtom), get(chainsAtom), get(chainsByIdAtom), get(evmNetworksAtom), get(evmNetworksByIdAtom), get(tokensAtom), get(tokensByIdAtom), get(miniMetadatasAtom), get(enabledChainsAtom), get(enabledTokensAtom), get(hydrateBalancesObservableAtom)]);
276
- const persistBackend = get(balancesPersistBackendAtom);
277
- const unsubsPromise = (async () => {
278
- const [_cryptoReady, balanceModules, allAddresses, chains, chainsById, evmNetworks, evmNetworksById, tokens, tokensById, _miniMetadatas, enabledChainsConfig, enabledTokensConfig] = await atomDependencies;
279
- if (abort.signal.aborted) return;
280
-
281
- // persist data every thirty seconds
282
- balancesObservable.pipe(rxjs.debounceTime(10000)).subscribe(balancesUpdate => {
283
- persistBackend.persist(Object.values(balancesUpdate));
284
- });
285
- const updateBalances = async balancesUpdates => {
286
- if (abort.signal.aborted) return;
287
- const updatesWithIds = new balances.Balances(balancesUpdates);
288
- const existing = balancesObservable.value;
289
-
290
- // update initialising set here - before filtering out zero balances
291
- // while this may include stale balances, the important thing is that the balance is no longer "initialising"
292
- // balancesUpdates.forEach((b) => this.#initialising.delete(getBalanceId(b)))
293
-
294
- const newlyZeroBalances = [];
295
- const changedBalances = Object.fromEntries(updatesWithIds.each.filter(newB => {
296
- const isZero = newB.total.tokens === "0";
297
- // Keep new balances which are not zeros
298
- const existingB = existing[newB.id];
299
- if (!existingB && !isZero) return true;
300
- const hasChanged = !lodash.isEqual(existingB, newB.toJSON());
301
- // Collect balances now confirmed to be zero separately, so they can be filtered out from the main set
302
- if (existingB && hasChanged && isZero) newlyZeroBalances.push(newB.id);
303
- // Keep changed balances, which are not known zeros
304
- return hasChanged && !isZero;
305
- }).map(b => [b.id, b.toJSON()]));
306
- if (Object.keys(changedBalances).length === 0 && newlyZeroBalances.length === 0) return;
307
- const nonZeroBalances = newlyZeroBalances.length > 0 ? Object.fromEntries(Object.entries(existing).filter(([id]) => !newlyZeroBalances.includes(id))) : existing;
308
- const newBalancesState = {
309
- ...nonZeroBalances,
310
- ...changedBalances
311
- };
312
- if (Object.keys(newBalancesState).length === 0) return;
313
- balancesObservable.next(newBalancesState);
314
- };
315
- const deleteBalances = async balancesFilter => {
316
- if (abort.signal.aborted) return;
317
- const balancesToKeep = Object.fromEntries(new balances.Balances(Object.values(await get(balancesObservableAtom))).each.filter(b => !balancesFilter(b)).map(b => [b.id, b.toJSON()]));
318
- balancesObservable.next(balancesToKeep);
319
- };
320
- const enabledChainIds = enabledChainsConfig?.map(genesisHash => chains.find(chain => chain.genesisHash === genesisHash)?.id);
321
- const enabledChainsFilter = enabledChainIds ? token => token.platform === "polkadot" && enabledChainIds?.includes(token.networkId) : () => true;
322
- const enabledTokensFilter = enabledTokensConfig ? token => enabledTokensConfig.includes(token.id) : () => true;
323
- const enabledTokenIds = tokens.filter(enabledChainsFilter).filter(enabledTokensFilter).map(({
324
- id
325
- }) => id);
326
- if (enabledTokenIds.length < 1 || allAddresses.length < 1) return;
327
- const addressesByTokenByModule = {};
328
- enabledTokenIds.flatMap(tokenId => tokensById[tokenId]).forEach(token => {
329
- // filter out tokens on chains/evmNetworks which have no rpcs
330
- const hasRpcs = token.networkId && (chainsById[token.networkId]?.rpcs?.length ?? 0) > 0 || token.networkId && (evmNetworksById[token.networkId]?.rpcs?.length ?? 0) > 0;
331
- if (!hasRpcs) return;
332
- if (!addressesByTokenByModule[token.type]) addressesByTokenByModule[token.type] = {};
333
- addressesByTokenByModule[token.type][token.id] = allAddresses.filter(address => {
334
- // for each address, fetch balances only from compatible chains
335
- return util.isEthereumAddress(address) ? token.networkId || chainsById[token.networkId ?? ""]?.account === "secp256k1" : token.networkId && chainsById[token.networkId ?? ""]?.account !== "secp256k1";
336
- });
337
- });
338
-
339
- // Delete invalid cached balances
340
- const chainIds = new Set(chains.map(chain => chain.id));
341
- const evmNetworkIds = new Set(evmNetworks.map(evmNetwork => evmNetwork.id));
342
- await deleteBalances(balance => {
343
- // delete cached balances for accounts which don't exist anymore
344
- if (!balance.address || !allAddresses.includes(balance.address)) return true;
345
-
346
- // delete cached balances when chain/evmNetwork doesn't exist
347
- if (!chainIds.has(balance.networkId) && !evmNetworkIds.has(balance.networkId)) return true;
348
-
349
- // delete cached balance when token doesn't exist / is disabled
350
- if (!enabledTokenIds.includes(balance.tokenId)) return true;
351
-
352
- // delete cached balance when module doesn't exist
353
- if (!balanceModules.find(module => module.type === balance.source)) return true;
354
-
355
- // delete cached balance for accounts on incompatible chains
356
- // (substrate accounts shouldn't have evm balances)
357
- // (evm accounts shouldn't have substrate balances (unless the chain uses secp256k1 accounts))
358
- const chain = chains.find(({
359
- id
360
- }) => id === balance.networkId) || null;
361
- const hasChain = chainIds.has(balance.networkId);
362
- const hasEvmNetwork = evmNetworkIds.has(balance.networkId);
363
- const chainUsesSecp256k1Accounts = chain?.account === "secp256k1";
364
- if (!util.isEthereumAddress(balance.address)) {
365
- if (!hasChain) return true;
366
- if (chainUsesSecp256k1Accounts) return true;
367
- }
368
- if (util.isEthereumAddress(balance.address)) {
369
- if (!hasEvmNetwork && !chainUsesSecp256k1Accounts) return true;
370
- }
371
-
372
- // keep balance
373
- return false;
374
- });
375
- if (abort.signal.aborted) return;
376
-
377
- // after 30 seconds, change the status of all balances still initializing to stale
378
- setTimeout(() => {
379
- if (abort.signal.aborted) return;
380
- const staleObservable = balancesObservable.pipe(rxjs.map(val => Object.values(val).filter(({
381
- status
382
- }) => status === "cache").map(balance => ({
383
- ...balance,
384
- status: "stale"
385
- }))));
386
- rxjs.firstValueFrom(staleObservable).then(v => {
387
- if (v.length) updateBalances(v);
388
- });
389
- }, 30_000);
390
- return balanceModules.map(balanceModule => {
391
- const unsub = balances.balances(balanceModule, addressesByTokenByModule[balanceModule.type] ?? {}, (error, balances) => {
392
- // log errors
393
- if (error) {
394
- if (error?.type === "STALE_RPC_ERROR" || error?.type === "WEBSOCKET_ALLOCATION_EXHAUSTED_ERROR") {
395
- const addressesByModuleToken = addressesByTokenByModule[balanceModule.type] ?? {};
396
- const staleObservable = balancesObservable.pipe(rxjs.map(val => Object.values(val).filter(balance => {
397
- const {
398
- tokenId,
399
- address,
400
- source
401
- } = balance;
402
- const chainComparison = error.chainId ? "chainId" in balance && error.chainId === balance.chainId : error.evmNetworkId ? "evmNetworkId" in balance && error.evmNetworkId === balance.evmNetworkId : true;
403
- return chainComparison && addressesByModuleToken[tokenId]?.includes(address) && source === balanceModule.type;
404
- }).map(balance => ({
405
- ...balance,
406
- status: "stale"
407
- }))));
408
- rxjs.firstValueFrom(staleObservable).then(v => {
409
- if (v.length) updateBalances(v);
410
- });
411
- }
412
- return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
413
- }
414
-
415
- // ignore empty balance responses
416
- if (!balances) return;
417
- // ignore balances from old subscriptions which are still in the process of unsubscribing
418
- if (abort.signal.aborted) return;
419
-
420
- // good balances
421
- if (balances) {
422
- if ("status" in balances) {
423
- // For modules using the new SubscriptionResultWithStatus pattern
424
- //TODO fix initialisin
425
- // if (result.status === "initialising") this.#initialising.add(balanceModule.type)
426
- // else this.#initialising.delete(balanceModule.type)
427
- updateBalances(balances.data);
428
- } else {
429
- // add good ones to initialisedBalances
430
- updateBalances(Object.values(balances.toJSON()));
431
- }
432
- }
433
- });
434
- return () => unsub.then(unsubscribe => unsubscribe());
435
- });
436
- })();
437
-
438
- // close the existing subscriptions when our effect unmounts
439
- // (wait 2 seconds before actually unsubscribing, to allow the websocket to be reused in that time)
440
- const unsubscribe = () => unsubsPromise.then(unsubs => {
441
- persistBackend.persist(Object.values(balancesObservable.value));
442
- unsubs?.forEach(unsub => unsub());
443
- });
444
- abort.signal.addEventListener("abort", () => setTimeout(unsubscribe, 2_000));
445
- return () => abort.abort("Unsubscribed");
197
+ const balancesAtom = jotai.atom(async get => {
198
+ // subscribe to balancesProvider getBalance with addressesByTokenIdAtom as param
199
+ get(subscribeBalancesAtom);
200
+ const hydrate = await get(balancesHydrateDataAtom);
201
+ const rawBalances = get(rawBalancesAtom);
202
+ return new balances.Balances(rawBalances, hydrate);
446
203
  });
447
204
 
448
205
  const useSetBalancesAddresses = addresses => {
@@ -459,10 +216,8 @@ const useSetBalancesAddresses = addresses => {
459
216
  * @returns a Balances object containing the current balances state.
460
217
  */
461
218
 
462
- const useBalances = persistBackend => {
463
- const [, setPersistBackend] = jotai.useAtom(balancesPersistBackendAtom);
464
- if (persistBackend) setPersistBackend(persistBackend);
465
- return jotai.useAtomValue(allBalancesAtom);
219
+ const useBalances = () => {
220
+ return jotai.useAtomValue(balancesAtom);
466
221
  };
467
222
 
468
223
  // TODO: Extract to shared definition between extension and @talismn/balances-react
@@ -499,30 +254,41 @@ const useChainConnectors = () => jotai.useAtomValue(chainConnectorsAtom);
499
254
 
500
255
  const useChaindataProvider = () => jotai.useAtomValue(chaindataProviderAtom);
501
256
  const useChaindata = () => jotai.useAtomValue(chaindataAtom);
502
- const useChains = () => jotai.useAtomValue(chainsByIdAtom);
503
- const useChainsByGenesisHash = () => jotai.useAtomValue(chainsByGenesisHashAtom);
504
- const useEvmNetworks = () => jotai.useAtomValue(evmNetworksByIdAtom);
505
- const useTokens = () => jotai.useAtomValue(tokensByIdAtom);
506
- const useMiniMetadatas = () => jotai.useAtomValue(miniMetadatasAtom);
507
- const useChain = chainId => useChains()[chainId ?? ""] ?? undefined;
508
- const useEvmNetwork = evmNetworkId => useEvmNetworks()[evmNetworkId ?? ""] ?? undefined;
509
- const useToken = tokenId => useTokens()[tokenId ?? ""] ?? undefined;
257
+ const useNetworks = () => useChaindata().networks;
258
+ const useNetworksById = () => {
259
+ const {
260
+ networks
261
+ } = useChaindata();
262
+ return react.useMemo(() => lodashEs.keyBy(networks, n => n.id), [networks]);
263
+ };
264
+ const useNetwork = networkId => {
265
+ const networksById = useNetworksById();
266
+ return networksById[networkId ?? ""] ?? null;
267
+ };
268
+ const useTokens = () => useChaindata().tokens;
269
+ const useTokensById = () => {
270
+ const {
271
+ tokens
272
+ } = useChaindata();
273
+ return react.useMemo(() => lodashEs.keyBy(tokens, t => t.id), [tokens]);
274
+ };
275
+ const useToken = tokenId => {
276
+ const tokensById = useTokensById();
277
+ return tokensById[tokenId ?? ""] ?? null;
278
+ };
510
279
 
511
280
  const useTokenRates = () => jotai.useAtomValue(tokenRatesAtom);
512
281
  const useTokenRate = tokenId => useTokenRates()[tokenId ?? ""] ?? undefined;
513
282
 
283
+ const cryptoWaitReadyAtom = jotai.atom(async () => await utilCrypto.cryptoWaitReady());
284
+
514
285
  const BalancesProvider = ({
515
- balanceModules,
516
286
  coinsApiUrl,
517
287
  withTestnets,
518
288
  enabledChains,
519
289
  enabledTokens,
520
290
  children
521
291
  }) => {
522
- const setBalanceModules = jotai.useSetAtom(balanceModuleCreatorsAtom);
523
- react.useEffect(() => {
524
- if (balanceModules !== undefined) setBalanceModules(balanceModules);
525
- }, [balanceModules, setBalanceModules]);
526
292
  const setCoinsApiConfig = jotai.useSetAtom(coinsApiConfigAtom);
527
293
  react.useEffect(() => {
528
294
  setCoinsApiConfig({
@@ -572,41 +338,30 @@ Object.defineProperty(exports, "subTokensTokenId", {
572
338
  });
573
339
  exports.BalancesProvider = BalancesProvider;
574
340
  exports.allAddressesAtom = allAddressesAtom;
575
- exports.allBalancesAtom = allBalancesAtom;
576
- exports.balanceModuleCreatorsAtom = balanceModuleCreatorsAtom;
577
- exports.balanceModulesAtom = balanceModulesAtom;
578
- exports.balancesPersistBackendAtom = balancesPersistBackendAtom;
341
+ exports.balancesAtom = balancesAtom;
579
342
  exports.chainConnectorsAtom = chainConnectorsAtom;
580
343
  exports.chaindataAtom = chaindataAtom;
581
344
  exports.chaindataProviderAtom = chaindataProviderAtom;
582
- exports.chainsAtom = chainsAtom;
583
- exports.chainsByGenesisHashAtom = chainsByGenesisHashAtom;
584
- exports.chainsByIdAtom = chainsByIdAtom;
585
345
  exports.coinsApiConfigAtom = coinsApiConfigAtom;
586
346
  exports.cryptoWaitReadyAtom = cryptoWaitReadyAtom;
587
347
  exports.enableTestnetsAtom = enableTestnetsAtom;
588
348
  exports.enabledChainsAtom = enabledChainsAtom;
589
349
  exports.enabledTokensAtom = enabledTokensAtom;
590
- exports.evmNetworksAtom = evmNetworksAtom;
591
- exports.evmNetworksByIdAtom = evmNetworksByIdAtom;
592
350
  exports.getStaleChains = getStaleChains;
593
- exports.miniMetadatasAtom = miniMetadatasAtom;
351
+ exports.networksAtom = networksAtom;
594
352
  exports.tokenRatesAtom = tokenRatesAtom;
595
353
  exports.tokensAtom = tokensAtom;
596
- exports.tokensByIdAtom = tokensByIdAtom;
597
354
  exports.useBalances = useBalances;
598
355
  exports.useBalancesStatus = useBalancesStatus;
599
- exports.useChain = useChain;
600
356
  exports.useChainConnectors = useChainConnectors;
601
357
  exports.useChaindata = useChaindata;
602
358
  exports.useChaindataProvider = useChaindataProvider;
603
- exports.useChains = useChains;
604
- exports.useChainsByGenesisHash = useChainsByGenesisHash;
605
- exports.useEvmNetwork = useEvmNetwork;
606
- exports.useEvmNetworks = useEvmNetworks;
607
- exports.useMiniMetadatas = useMiniMetadatas;
359
+ exports.useNetwork = useNetwork;
360
+ exports.useNetworks = useNetworks;
361
+ exports.useNetworksById = useNetworksById;
608
362
  exports.useSetBalancesAddresses = useSetBalancesAddresses;
609
363
  exports.useToken = useToken;
610
364
  exports.useTokenRate = useTokenRate;
611
365
  exports.useTokenRates = useTokenRates;
612
366
  exports.useTokens = useTokens;
367
+ exports.useTokensById = useTokensById;