@talismn/balances-react 0.0.0-pr2075-20250708160640 → 0.0.0-pr2075-20250709134044

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,24 +1,23 @@
1
- import { atom, useSetAtom, useAtom, useAtomValue } from 'jotai';
1
+ import { atom, useSetAtom, useAtomValue } from 'jotai';
2
2
  import { useMemo, useEffect } from 'react';
3
- import { defaultBalanceModules, configureStore, decompress, compress, db, Balances, getBalanceId, balances } from '@talismn/balances';
4
3
  import { DEFAULT_COINSAPI_CONFIG, db as db$1, fetchTokenRates, ALL_CURRENCY_IDS } from '@talismn/token-rates';
5
4
  import { jsx, Fragment } from 'react/jsx-runtime';
6
5
  import { ChaindataProvider } from '@talismn/chaindata-provider';
7
6
  export { evmErc20TokenId, evmNativeTokenId, subAssetTokenId, subNativeTokenId, subPsp22TokenId, subTokensTokenId } from '@talismn/chaindata-provider';
7
+ import { BalancesProvider as BalancesProvider$1, db, Balances } from '@talismn/balances';
8
8
  import { firstThenDebounce, isTruthy, isAbortError, isEthereumAddress } from '@talismn/util';
9
9
  import { atomEffect } from 'jotai-effect';
10
10
  import { atomWithObservable } from 'jotai/utils';
11
- import { isEqual as isEqual$1 } from 'lodash';
12
- import { Observable, distinctUntilChanged, map, combineLatest, BehaviorSubject, debounceTime, firstValueFrom } from 'rxjs';
13
- import anylogger from 'anylogger';
11
+ import { fromPairs, isEqual as isEqual$1 } from 'lodash';
12
+ import { Observable, distinctUntilChanged, map, combineLatest, BehaviorSubject, firstValueFrom } from 'rxjs';
14
13
  import { ChainConnector } from '@talismn/chain-connector';
15
14
  import { ChainConnectorEvm } from '@talismn/chain-connector-evm';
16
15
  import { connectionMetaDb } from '@talismn/connection-meta';
17
16
  import { liveQuery } from 'dexie';
18
17
  import isEqual from 'lodash/isEqual';
19
18
  import { cryptoWaitReady } from '@polkadot/util-crypto';
19
+ import anylogger from 'anylogger';
20
20
 
21
- const balanceModuleCreatorsAtom = atom(defaultBalanceModules);
22
21
  const innerCoinsApiConfigAtom = atom(DEFAULT_COINSAPI_CONFIG);
23
22
  const coinsApiConfigAtom = atom(get => get(innerCoinsApiConfigAtom), (_get, set, options) => set(innerCoinsApiConfigAtom, {
24
23
  apiUrl: options.apiUrl ?? DEFAULT_COINSAPI_CONFIG.apiUrl
@@ -30,55 +29,6 @@ const enabledTokensAtom = atom(undefined);
30
29
  /** Sets the list of addresses for which token balances will be fetched by the balances subscription */
31
30
  const allAddressesAtom = atom([]);
32
31
 
33
- var packageJson = {
34
- name: "@talismn/balances-react"};
35
-
36
- var log = anylogger(packageJson.name);
37
-
38
- /**
39
- // Persistence backend for indexedDB
40
- // Add a new backend by implementing the BalancesPersistBackend interface
41
- // configureStore can be called with a different indexedDB table
42
- */
43
- const {
44
- persistData,
45
- retrieveData
46
- } = configureStore();
47
-
48
- /**
49
- // Persistence backend for localStorage
50
- */
51
- const localStoragePersist = async balances => {
52
- const storedBalances = balances.map(b => {
53
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
54
- const {
55
- status,
56
- ...rest
57
- } = b;
58
- return rest;
59
- });
60
- const deflated = compress(storedBalances);
61
- localStorage.setItem("talismanBalances", deflated.toString());
62
- };
63
- const localStorageRetrieve = async () => {
64
- const deflated = localStorage.getItem("talismanBalances");
65
- if (deflated) {
66
- // deflated will be a long string of numbers separated by commas
67
- const deflatedArray = deflated.split(",").map(n => parseInt(n, 10));
68
- const deflatedBytes = new Uint8Array(deflatedArray.length);
69
- deflatedArray.forEach((n, i) => deflatedBytes[i] = n);
70
- return decompress(deflatedBytes).map(b => ({
71
- ...b,
72
- status: "cache"
73
- }));
74
- }
75
- return [];
76
- };
77
- const localStorageBalancesPersistBackend = {
78
- persist: localStoragePersist,
79
- retrieve: localStorageRetrieve
80
- };
81
-
82
32
  const chaindataProviderAtom = atom(() => {
83
33
  return new ChaindataProvider({});
84
34
  });
@@ -93,17 +43,9 @@ const chainConnectorsAtom = atom(get => {
93
43
  };
94
44
  });
95
45
 
96
- const balanceModulesAtom = atom(get => {
97
- const balanceModuleCreators = get(balanceModuleCreatorsAtom);
98
- const chainConnectors = get(chainConnectorsAtom);
99
- const chaindataProvider = get(chaindataProviderAtom);
100
- if (!chainConnectors.substrate) return [];
101
- if (!chainConnectors.evm) return [];
102
- if (!chaindataProvider) return [];
103
- return balanceModuleCreators.map(mod => mod({
104
- chainConnectors,
105
- chaindataProvider
106
- }));
46
+ const balancesProviderAtom = atom(get => {
47
+ return new BalancesProvider$1(get(chaindataProviderAtom), get(chainConnectorsAtom)) // TODO pass storage
48
+ ;
107
49
  });
108
50
 
109
51
  /**
@@ -167,6 +109,11 @@ const chaindataAtom = atomWithObservable(get => {
167
109
 
168
110
  const cryptoWaitReadyAtom = atom(async () => await cryptoWaitReady());
169
111
 
112
+ var packageJson = {
113
+ name: "@talismn/balances-react"};
114
+
115
+ var log = anylogger(packageJson.name);
116
+
170
117
  const tokenRatesAtom = atom(async get => {
171
118
  // runs a timer to keep tokenRates up to date
172
119
  get(tokenRatesFetcherAtomEffect);
@@ -240,14 +187,20 @@ const allBalancesAtom = atom(async get => {
240
187
  });
241
188
  const balancesObservable = new BehaviorSubject({});
242
189
  const balancesObservableAtom = atomWithObservable(() => balancesObservable);
243
- const balancesPersistBackendAtom = atom(localStorageBalancesPersistBackend);
244
- const hydrateBalancesObservableAtom = atom(async get => {
245
- const persistBackend = get(balancesPersistBackendAtom);
246
- const balances = await persistBackend.retrieve();
247
- balancesObservable.next(Object.fromEntries(balances.map(b => [getBalanceId(b), {
248
- ...b,
249
- status: "cache"
250
- }])));
190
+
191
+ // export const balancesPersistBackendAtom = atom<BalancesPersistBackend>(
192
+ // localStorageBalancesPersistBackend,
193
+ // )
194
+
195
+ const hydrateBalancesObservableAtom = atom(async () => {
196
+ // const persistBackend = get(balancesPersistBackendAtom)
197
+ // const balances = await persistBackend.retrieve()
198
+
199
+ balancesObservable.next({}
200
+ // Object.fromEntries(
201
+ // balances.map((b) => [getBalanceId(b), { ...b, status: "cache" } as BalanceJson]),
202
+ // ),
203
+ );
251
204
  });
252
205
  const balancesHydrateDataAtom = atom(async get => {
253
206
  const [{
@@ -266,16 +219,21 @@ const balancesSubscriptionAtomEffect = atomEffect(get => {
266
219
 
267
220
  // we have to specify these synchronously, otherwise jotai won't know
268
221
  // that it needs to restart our subscriptions when they change
269
- 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)]);
270
- const persistBackend = get(balancesPersistBackendAtom);
222
+ const atomDependencies = Promise.all([get(cryptoWaitReadyAtom),
223
+ // get(balanceModulesAtom), // TODO yeet
224
+ get(balancesProviderAtom), get(allAddressesAtom), get(chainsAtom), get(chainsByIdAtom), get(evmNetworksAtom), get(evmNetworksByIdAtom), get(tokensAtom), get(tokensByIdAtom), get(miniMetadatasAtom), get(enabledChainsAtom), get(enabledTokensAtom), get(hydrateBalancesObservableAtom)]);
225
+
226
+ // const persistBackend = get(balancesPersistBackendAtom)
227
+
271
228
  const unsubsPromise = (async () => {
272
- const [_cryptoReady, balanceModules, allAddresses, chains, chainsById, evmNetworks, evmNetworksById, tokens, tokensById, _miniMetadatas, enabledChainsConfig, enabledTokensConfig] = await atomDependencies;
229
+ const [_cryptoReady, balancesProvider, allAddresses, chains, chainsById, evmNetworks, evmNetworksById, tokens, tokensById, _miniMetadatas, enabledChainsConfig, enabledTokensConfig] = await atomDependencies;
273
230
  if (abort.signal.aborted) return;
274
231
 
275
232
  // persist data every thirty seconds
276
- balancesObservable.pipe(debounceTime(10000)).subscribe(balancesUpdate => {
277
- persistBackend.persist(Object.values(balancesUpdate));
278
- });
233
+ // balancesObservable.pipe(debounceTime(10000)).subscribe((balancesUpdate) => {
234
+ // persistBackend.persist(Object.values(balancesUpdate))
235
+ // })
236
+
279
237
  const updateBalances = async balancesUpdates => {
280
238
  if (abort.signal.aborted) return;
281
239
  const updatesWithIds = new Balances(balancesUpdates);
@@ -329,6 +287,7 @@ const balancesSubscriptionAtomEffect = atomEffect(get => {
329
287
  return isEthereumAddress(address) ? token.networkId || chainsById[token.networkId ?? ""]?.account === "secp256k1" : token.networkId && chainsById[token.networkId ?? ""]?.account !== "secp256k1";
330
288
  });
331
289
  });
290
+ const addressesByTokenId = fromPairs(enabledTokenIds.map(tokenId => [tokenId, allAddresses]));
332
291
 
333
292
  // Delete invalid cached balances
334
293
  const chainIds = new Set(chains.map(chain => chain.id));
@@ -344,7 +303,7 @@ const balancesSubscriptionAtomEffect = atomEffect(get => {
344
303
  if (!enabledTokenIds.includes(balance.tokenId)) return true;
345
304
 
346
305
  // delete cached balance when module doesn't exist
347
- if (!balanceModules.find(module => module.type === balance.source)) return true;
306
+ // if (!balanceModules.find((module) => module.type === balance.source)) return true
348
307
 
349
308
  // delete cached balance for accounts on incompatible chains
350
309
  // (substrate accounts shouldn't have evm balances)
@@ -381,62 +340,96 @@ const balancesSubscriptionAtomEffect = atomEffect(get => {
381
340
  if (v.length) updateBalances(v);
382
341
  });
383
342
  }, 30_000);
384
- return balanceModules.map(balanceModule => {
385
- const unsub = balances(balanceModule, addressesByTokenByModule[balanceModule.type] ?? {}, (error, balances) => {
386
- // log errors
387
- if (error) {
388
- if (error?.type === "STALE_RPC_ERROR" || error?.type === "WEBSOCKET_ALLOCATION_EXHAUSTED_ERROR") {
389
- const addressesByModuleToken = addressesByTokenByModule[balanceModule.type] ?? {};
390
- const staleObservable = balancesObservable.pipe(map(val => Object.values(val).filter(balance => {
391
- const {
392
- tokenId,
393
- address,
394
- source
395
- } = balance;
396
- const chainComparison = error.chainId ? "chainId" in balance && error.chainId === balance.chainId : error.evmNetworkId ? "evmNetworkId" in balance && error.evmNetworkId === balance.evmNetworkId : true;
397
- return chainComparison && addressesByModuleToken[tokenId]?.includes(address) && source === balanceModule.type;
398
- }).map(balance => ({
399
- ...balance,
400
- status: "stale"
401
- }))));
402
- firstValueFrom(staleObservable).then(v => {
403
- if (v.length) updateBalances(v);
404
- });
405
- }
406
- return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
407
- }
408
-
409
- // ignore empty balance responses
410
- if (!balances) return;
411
- // ignore balances from old subscriptions which are still in the process of unsubscribing
412
- if (abort.signal.aborted) return;
413
-
414
- // good balances
415
- if (balances) {
416
- if ("status" in balances) {
417
- // For modules using the new SubscriptionResultWithStatus pattern
418
- //TODO fix initialisin
419
- // if (result.status === "initialising") this.#initialising.add(balanceModule.type)
420
- // else this.#initialising.delete(balanceModule.type)
421
- updateBalances(balances.data);
422
- } else {
423
- // add good ones to initialisedBalances
424
- updateBalances(Object.values(balances.toJSON()));
425
- }
426
- }
427
- });
428
- return () => unsub.then(unsubscribe => unsubscribe());
343
+ const sub = balancesProvider.getBalances$(addressesByTokenId).subscribe(balances => {
344
+ updateBalances(balances.balances);
429
345
  });
346
+ return () => {
347
+ sub.unsubscribe();
348
+ };
349
+
350
+ // return balanceModules.map((balanceModule) => {
351
+ // const unsub = balancesFn(
352
+ // balanceModule,
353
+ // addressesByTokenByModule[balanceModule.type] ?? {},
354
+ // (error, balances) => {
355
+ // // log errors
356
+ // if (error) {
357
+ // if (
358
+ // error?.type === "STALE_RPC_ERROR" ||
359
+ // error?.type === "WEBSOCKET_ALLOCATION_EXHAUSTED_ERROR"
360
+ // ) {
361
+ // const addressesByModuleToken = addressesByTokenByModule[balanceModule.type] ?? {}
362
+
363
+ // const staleObservable = balancesObservable.pipe(
364
+ // map((val) =>
365
+ // Object.values(val)
366
+ // .filter((balance) => {
367
+ // const { tokenId, address, source } = balance
368
+ // const chainComparison = error.chainId
369
+ // ? "chainId" in balance && error.chainId === balance.chainId
370
+ // : error.evmNetworkId
371
+ // ? "evmNetworkId" in balance && error.evmNetworkId === balance.evmNetworkId
372
+ // : true
373
+ // return (
374
+ // chainComparison &&
375
+ // addressesByModuleToken[tokenId]?.includes(address) &&
376
+ // source === balanceModule.type
377
+ // )
378
+ // })
379
+ // .map((balance) => ({ ...balance, status: "stale" }) as BalanceJson),
380
+ // ),
381
+ // )
382
+
383
+ // firstValueFrom(staleObservable).then((v) => {
384
+ // if (v.length) updateBalances(v)
385
+ // })
386
+ // }
387
+
388
+ // return log.error(`Failed to fetch ${balanceModule.type} balances`, error)
389
+ // }
390
+
391
+ // // ignore empty balance responses
392
+ // if (!balances) return
393
+ // // ignore balances from old subscriptions which are still in the process of unsubscribing
394
+ // if (abort.signal.aborted) return
395
+
396
+ // // good balances
397
+ // if (balances) {
398
+ // if ("status" in balances) {
399
+ // // For modules using the new SubscriptionResultWithStatus pattern
400
+ // //TODO fix initialisin
401
+ // // if (result.status === "initialising") this.#initialising.add(balanceModule.type)
402
+ // // else this.#initialising.delete(balanceModule.type)
403
+ // updateBalances(balances.data)
404
+ // } else {
405
+ // // add good ones to initialisedBalances
406
+ // updateBalances(Object.values(balances.toJSON()))
407
+ // }
408
+ // }
409
+ // },
410
+ // )
411
+
412
+ // return () => unsub.then((unsubscribe) => unsubscribe())
413
+ // })
430
414
  })();
431
415
 
432
416
  // close the existing subscriptions when our effect unmounts
433
417
  // (wait 2 seconds before actually unsubscribing, to allow the websocket to be reused in that time)
434
- const unsubscribe = () => unsubsPromise.then(unsubs => {
435
- persistBackend.persist(Object.values(balancesObservable.value));
436
- unsubs?.forEach(unsub => unsub());
437
- });
438
- abort.signal.addEventListener("abort", () => setTimeout(unsubscribe, 2_000));
439
- return () => abort.abort("Unsubscribed");
418
+ // const unsubscribe = () =>
419
+ // unsubsPromise.then((unsubs) => {
420
+ // persistBackend.persist(Object.values(balancesObservable.value))
421
+ // unsubs?.forEach((unsub) => unsub())
422
+ // })
423
+ // abort.signal.addEventListener("abort", () => setTimeout(unsubscribe, 2_000))
424
+
425
+ // return () => abort.abort("Unsubscribed")
426
+
427
+ return () => {
428
+ unsubsPromise.then(unsub => {
429
+ unsub?.();
430
+ //console.log("unsubscribed")
431
+ });
432
+ };
440
433
  });
441
434
 
442
435
  const useSetBalancesAddresses = addresses => {
@@ -453,9 +446,7 @@ const useSetBalancesAddresses = addresses => {
453
446
  * @returns a Balances object containing the current balances state.
454
447
  */
455
448
 
456
- const useBalances = persistBackend => {
457
- const [, setPersistBackend] = useAtom(balancesPersistBackendAtom);
458
- if (persistBackend) setPersistBackend(persistBackend);
449
+ const useBalances = () => {
459
450
  return useAtomValue(allBalancesAtom);
460
451
  };
461
452
 
@@ -506,17 +497,12 @@ const useTokenRates = () => useAtomValue(tokenRatesAtom);
506
497
  const useTokenRate = tokenId => useTokenRates()[tokenId ?? ""] ?? undefined;
507
498
 
508
499
  const BalancesProvider = ({
509
- balanceModules,
510
500
  coinsApiUrl,
511
501
  withTestnets,
512
502
  enabledChains,
513
503
  enabledTokens,
514
504
  children
515
505
  }) => {
516
- const setBalanceModules = useSetAtom(balanceModuleCreatorsAtom);
517
- useEffect(() => {
518
- if (balanceModules !== undefined) setBalanceModules(balanceModules);
519
- }, [balanceModules, setBalanceModules]);
520
506
  const setCoinsApiConfig = useSetAtom(coinsApiConfigAtom);
521
507
  useEffect(() => {
522
508
  setCoinsApiConfig({
@@ -540,4 +526,4 @@ const BalancesProvider = ({
540
526
  });
541
527
  };
542
528
 
543
- export { BalancesProvider, allAddressesAtom, allBalancesAtom, balanceModuleCreatorsAtom, balanceModulesAtom, balancesPersistBackendAtom, chainConnectorsAtom, chaindataAtom, chaindataProviderAtom, chainsAtom, chainsByGenesisHashAtom, chainsByIdAtom, coinsApiConfigAtom, cryptoWaitReadyAtom, enableTestnetsAtom, enabledChainsAtom, enabledTokensAtom, evmNetworksAtom, evmNetworksByIdAtom, getStaleChains, miniMetadatasAtom, tokenRatesAtom, tokensAtom, tokensByIdAtom, useBalances, useBalancesStatus, useChain, useChainConnectors, useChaindata, useChaindataProvider, useChains, useChainsByGenesisHash, useEvmNetwork, useEvmNetworks, useMiniMetadatas, useSetBalancesAddresses, useToken, useTokenRate, useTokenRates, useTokens };
529
+ export { BalancesProvider, allAddressesAtom, allBalancesAtom, chainConnectorsAtom, chaindataAtom, chaindataProviderAtom, chainsAtom, chainsByGenesisHashAtom, chainsByIdAtom, coinsApiConfigAtom, cryptoWaitReadyAtom, enableTestnetsAtom, enabledChainsAtom, enabledTokensAtom, evmNetworksAtom, evmNetworksByIdAtom, getStaleChains, miniMetadatasAtom, tokenRatesAtom, tokensAtom, tokensByIdAtom, useBalances, useBalancesStatus, useChain, useChainConnectors, useChaindata, useChaindataProvider, useChains, useChainsByGenesisHash, useEvmNetwork, useEvmNetworks, useMiniMetadatas, useSetBalancesAddresses, useToken, useTokenRate, useTokenRates, useTokens };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/balances-react",
3
- "version": "0.0.0-pr2075-20250708160640",
3
+ "version": "0.0.0-pr2075-20250709134044",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "GPL-3.0-or-later",
@@ -30,14 +30,14 @@
30
30
  "lodash": "4.17.21",
31
31
  "react-use": "^17.5.1",
32
32
  "rxjs": "^7.8.1",
33
- "@talismn/balances": "0.0.0-pr2075-20250708160640",
34
- "@talismn/chain-connector": "0.0.0-pr2075-20250708160640",
35
- "@talismn/chaindata-provider": "0.0.0-pr2075-20250708160640",
36
- "@talismn/scale": "0.0.0-pr2075-20250708160640",
37
- "@talismn/chain-connector-evm": "0.0.0-pr2075-20250708160640",
38
- "@talismn/connection-meta": "0.0.0-pr2075-20250708160640",
39
- "@talismn/util": "0.0.0-pr2075-20250708160640",
40
- "@talismn/token-rates": "0.0.0-pr2075-20250708160640"
33
+ "@talismn/chain-connector": "0.0.0-pr2075-20250709134044",
34
+ "@talismn/balances": "0.0.0-pr2075-20250709134044",
35
+ "@talismn/chain-connector-evm": "0.0.0-pr2075-20250709134044",
36
+ "@talismn/chaindata-provider": "0.0.0-pr2075-20250709134044",
37
+ "@talismn/scale": "0.0.0-pr2075-20250709134044",
38
+ "@talismn/connection-meta": "0.0.0-pr2075-20250709134044",
39
+ "@talismn/token-rates": "0.0.0-pr2075-20250709134044",
40
+ "@talismn/util": "0.0.0-pr2075-20250709134044"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/jest": "^29.5.14",
@@ -48,8 +48,8 @@
48
48
  "react": "^18.3.1",
49
49
  "ts-jest": "^29.2.5",
50
50
  "typescript": "^5.6.3",
51
- "@talismn/eslint-config": "0.0.3",
52
- "@talismn/tsconfig": "0.0.2"
51
+ "@talismn/tsconfig": "0.0.2",
52
+ "@talismn/eslint-config": "0.0.3"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@polkadot/util-crypto": "*",
@@ -1,2 +0,0 @@
1
- import { AnyBalanceModule } from "@talismn/balances";
2
- export declare const balanceModulesAtom: import("jotai").Atom<AnyBalanceModule[]>;
@@ -1,10 +0,0 @@
1
- import { BalanceJson } from "@talismn/balances";
2
- type PersistFn = (balances: BalanceJson[]) => Promise<void>;
3
- type RetrieveFn = () => Promise<BalanceJson[]>;
4
- export type BalancesPersistBackend = {
5
- persist: PersistFn;
6
- retrieve: RetrieveFn;
7
- };
8
- export declare const indexedDbBalancesPersistBackend: BalancesPersistBackend;
9
- export declare const localStorageBalancesPersistBackend: BalancesPersistBackend;
10
- export {};