@talismn/balances-react 0.0.0-pr2136-20250814032256 → 0.0.0-pr2136-20250815050739

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.
@@ -92,31 +92,6 @@ export declare const chaindataAtom: import("jotai").Atom<{
92
92
  "evm-erc20"?: undefined;
93
93
  "evm-uniswapv2"?: undefined;
94
94
  } | undefined;
95
- } | {
96
- id: string;
97
- name: string;
98
- nativeTokenId: string;
99
- nativeCurrency: {
100
- decimals: number;
101
- symbol: string;
102
- name: string;
103
- coingeckoId?: string | undefined;
104
- mirrorOf?: string | undefined;
105
- logo?: string | undefined;
106
- };
107
- blockExplorerUrls: string[];
108
- platform: "solana";
109
- genesisHash: string;
110
- rpcs: string[];
111
- isTestnet?: boolean | undefined;
112
- isDefault?: boolean | undefined;
113
- forceScan?: boolean | undefined;
114
- logo?: string | undefined;
115
- themeColor?: string | undefined;
116
- balancesConfig?: {
117
- "sol-native"?: undefined;
118
- "sol-spl"?: undefined;
119
- } | undefined;
120
95
  })[];
121
96
  tokens: ({
122
97
  id: string;
@@ -262,33 +237,6 @@ export declare const chaindataAtom: import("jotai").Atom<{
262
237
  coingeckoId?: string | undefined;
263
238
  noDiscovery?: boolean | undefined;
264
239
  mirrorOf?: string | undefined;
265
- } | {
266
- id: string;
267
- networkId: string;
268
- decimals: number;
269
- symbol: string;
270
- type: "sol-native";
271
- platform: "solana";
272
- isDefault?: boolean | undefined;
273
- name?: string | undefined;
274
- logo?: string | undefined;
275
- coingeckoId?: string | undefined;
276
- noDiscovery?: boolean | undefined;
277
- mirrorOf?: string | undefined;
278
- } | {
279
- id: string;
280
- networkId: string;
281
- decimals: number;
282
- symbol: string;
283
- type: "sol-spl";
284
- platform: "solana";
285
- mintAddress: string;
286
- isDefault?: boolean | undefined;
287
- name?: string | undefined;
288
- logo?: string | undefined;
289
- coingeckoId?: string | undefined;
290
- noDiscovery?: boolean | undefined;
291
- mirrorOf?: string | undefined;
292
240
  })[];
293
241
  } | Promise<{
294
242
  networks: ({
@@ -384,31 +332,6 @@ export declare const chaindataAtom: import("jotai").Atom<{
384
332
  "evm-erc20"?: undefined;
385
333
  "evm-uniswapv2"?: undefined;
386
334
  } | undefined;
387
- } | {
388
- id: string;
389
- name: string;
390
- nativeTokenId: string;
391
- nativeCurrency: {
392
- decimals: number;
393
- symbol: string;
394
- name: string;
395
- coingeckoId?: string | undefined;
396
- mirrorOf?: string | undefined;
397
- logo?: string | undefined;
398
- };
399
- blockExplorerUrls: string[];
400
- platform: "solana";
401
- genesisHash: string;
402
- rpcs: string[];
403
- isTestnet?: boolean | undefined;
404
- isDefault?: boolean | undefined;
405
- forceScan?: boolean | undefined;
406
- logo?: string | undefined;
407
- themeColor?: string | undefined;
408
- balancesConfig?: {
409
- "sol-native"?: undefined;
410
- "sol-spl"?: undefined;
411
- } | undefined;
412
335
  })[];
413
336
  tokens: ({
414
337
  id: string;
@@ -554,33 +477,6 @@ export declare const chaindataAtom: import("jotai").Atom<{
554
477
  coingeckoId?: string | undefined;
555
478
  noDiscovery?: boolean | undefined;
556
479
  mirrorOf?: string | undefined;
557
- } | {
558
- id: string;
559
- networkId: string;
560
- decimals: number;
561
- symbol: string;
562
- type: "sol-native";
563
- platform: "solana";
564
- isDefault?: boolean | undefined;
565
- name?: string | undefined;
566
- logo?: string | undefined;
567
- coingeckoId?: string | undefined;
568
- noDiscovery?: boolean | undefined;
569
- mirrorOf?: string | undefined;
570
- } | {
571
- id: string;
572
- networkId: string;
573
- decimals: number;
574
- symbol: string;
575
- type: "sol-spl";
576
- platform: "solana";
577
- mintAddress: string;
578
- isDefault?: boolean | undefined;
579
- name?: string | undefined;
580
- logo?: string | undefined;
581
- coingeckoId?: string | undefined;
582
- noDiscovery?: boolean | undefined;
583
- mirrorOf?: string | undefined;
584
480
  })[];
585
481
  }>>;
586
482
  export declare const tokensAtom: import("jotai").Atom<Promise<({
@@ -727,33 +623,6 @@ export declare const tokensAtom: import("jotai").Atom<Promise<({
727
623
  coingeckoId?: string | undefined;
728
624
  noDiscovery?: boolean | undefined;
729
625
  mirrorOf?: string | undefined;
730
- } | {
731
- id: string;
732
- networkId: string;
733
- decimals: number;
734
- symbol: string;
735
- type: "sol-native";
736
- platform: "solana";
737
- isDefault?: boolean | undefined;
738
- name?: string | undefined;
739
- logo?: string | undefined;
740
- coingeckoId?: string | undefined;
741
- noDiscovery?: boolean | undefined;
742
- mirrorOf?: string | undefined;
743
- } | {
744
- id: string;
745
- networkId: string;
746
- decimals: number;
747
- symbol: string;
748
- type: "sol-spl";
749
- platform: "solana";
750
- mintAddress: string;
751
- isDefault?: boolean | undefined;
752
- name?: string | undefined;
753
- logo?: string | undefined;
754
- coingeckoId?: string | undefined;
755
- noDiscovery?: boolean | undefined;
756
- mirrorOf?: string | undefined;
757
626
  })[]>>;
758
627
  export declare const networksAtom: import("jotai").Atom<Promise<({
759
628
  id: string;
@@ -848,29 +717,4 @@ export declare const networksAtom: import("jotai").Atom<Promise<({
848
717
  "evm-erc20"?: undefined;
849
718
  "evm-uniswapv2"?: undefined;
850
719
  } | undefined;
851
- } | {
852
- id: string;
853
- name: string;
854
- nativeTokenId: string;
855
- nativeCurrency: {
856
- decimals: number;
857
- symbol: string;
858
- name: string;
859
- coingeckoId?: string | undefined;
860
- mirrorOf?: string | undefined;
861
- logo?: string | undefined;
862
- };
863
- blockExplorerUrls: string[];
864
- platform: "solana";
865
- genesisHash: string;
866
- rpcs: string[];
867
- isTestnet?: boolean | undefined;
868
- isDefault?: boolean | undefined;
869
- forceScan?: boolean | undefined;
870
- logo?: string | undefined;
871
- themeColor?: string | undefined;
872
- balancesConfig?: {
873
- "sol-native"?: undefined;
874
- "sol-spl"?: undefined;
875
- } | undefined;
876
720
  })[]>>;
@@ -1 +1,3 @@
1
- export declare const tokenRatesAtom: import("jotai").Atom<Promise<import("@talismn/token-rates").TokenRatesList>>;
1
+ export declare const tokenRatesAtom: import("jotai").Atom<Promise<{
2
+ [k: string]: import("@talismn/token-rates").TokenRates;
3
+ }>>;
@@ -94,31 +94,6 @@ export declare const useChaindata: () => {
94
94
  "evm-erc20"?: undefined;
95
95
  "evm-uniswapv2"?: undefined;
96
96
  } | undefined;
97
- } | {
98
- id: string;
99
- name: string;
100
- nativeTokenId: string;
101
- nativeCurrency: {
102
- decimals: number;
103
- symbol: string;
104
- name: string;
105
- coingeckoId?: string | undefined;
106
- mirrorOf?: string | undefined;
107
- logo?: string | undefined;
108
- };
109
- blockExplorerUrls: string[];
110
- platform: "solana";
111
- genesisHash: string;
112
- rpcs: string[];
113
- isTestnet?: boolean | undefined;
114
- isDefault?: boolean | undefined;
115
- forceScan?: boolean | undefined;
116
- logo?: string | undefined;
117
- themeColor?: string | undefined;
118
- balancesConfig?: {
119
- "sol-native"?: undefined;
120
- "sol-spl"?: undefined;
121
- } | undefined;
122
97
  })[];
123
98
  tokens: ({
124
99
  id: string;
@@ -264,33 +239,6 @@ export declare const useChaindata: () => {
264
239
  coingeckoId?: string | undefined;
265
240
  noDiscovery?: boolean | undefined;
266
241
  mirrorOf?: string | undefined;
267
- } | {
268
- id: string;
269
- networkId: string;
270
- decimals: number;
271
- symbol: string;
272
- type: "sol-native";
273
- platform: "solana";
274
- isDefault?: boolean | undefined;
275
- name?: string | undefined;
276
- logo?: string | undefined;
277
- coingeckoId?: string | undefined;
278
- noDiscovery?: boolean | undefined;
279
- mirrorOf?: string | undefined;
280
- } | {
281
- id: string;
282
- networkId: string;
283
- decimals: number;
284
- symbol: string;
285
- type: "sol-spl";
286
- platform: "solana";
287
- mintAddress: string;
288
- isDefault?: boolean | undefined;
289
- name?: string | undefined;
290
- logo?: string | undefined;
291
- coingeckoId?: string | undefined;
292
- noDiscovery?: boolean | undefined;
293
- mirrorOf?: string | undefined;
294
242
  })[];
295
243
  };
296
244
  export declare const useNetworks: () => ({
@@ -386,31 +334,6 @@ export declare const useNetworks: () => ({
386
334
  "evm-erc20"?: undefined;
387
335
  "evm-uniswapv2"?: undefined;
388
336
  } | undefined;
389
- } | {
390
- id: string;
391
- name: string;
392
- nativeTokenId: string;
393
- nativeCurrency: {
394
- decimals: number;
395
- symbol: string;
396
- name: string;
397
- coingeckoId?: string | undefined;
398
- mirrorOf?: string | undefined;
399
- logo?: string | undefined;
400
- };
401
- blockExplorerUrls: string[];
402
- platform: "solana";
403
- genesisHash: string;
404
- rpcs: string[];
405
- isTestnet?: boolean | undefined;
406
- isDefault?: boolean | undefined;
407
- forceScan?: boolean | undefined;
408
- logo?: string | undefined;
409
- themeColor?: string | undefined;
410
- balancesConfig?: {
411
- "sol-native"?: undefined;
412
- "sol-spl"?: undefined;
413
- } | undefined;
414
337
  })[];
415
338
  export declare const useNetworksById: () => import("lodash").Dictionary<{
416
339
  id: string;
@@ -505,31 +428,6 @@ export declare const useNetworksById: () => import("lodash").Dictionary<{
505
428
  "evm-erc20"?: undefined;
506
429
  "evm-uniswapv2"?: undefined;
507
430
  } | undefined;
508
- } | {
509
- id: string;
510
- name: string;
511
- nativeTokenId: string;
512
- nativeCurrency: {
513
- decimals: number;
514
- symbol: string;
515
- name: string;
516
- coingeckoId?: string | undefined;
517
- mirrorOf?: string | undefined;
518
- logo?: string | undefined;
519
- };
520
- blockExplorerUrls: string[];
521
- platform: "solana";
522
- genesisHash: string;
523
- rpcs: string[];
524
- isTestnet?: boolean | undefined;
525
- isDefault?: boolean | undefined;
526
- forceScan?: boolean | undefined;
527
- logo?: string | undefined;
528
- themeColor?: string | undefined;
529
- balancesConfig?: {
530
- "sol-native"?: undefined;
531
- "sol-spl"?: undefined;
532
- } | undefined;
533
431
  }>;
534
432
  export declare const useNetwork: (networkId?: NetworkId) => {
535
433
  id: string;
@@ -624,31 +522,6 @@ export declare const useNetwork: (networkId?: NetworkId) => {
624
522
  "evm-erc20"?: undefined;
625
523
  "evm-uniswapv2"?: undefined;
626
524
  } | undefined;
627
- } | {
628
- id: string;
629
- name: string;
630
- nativeTokenId: string;
631
- nativeCurrency: {
632
- decimals: number;
633
- symbol: string;
634
- name: string;
635
- coingeckoId?: string | undefined;
636
- mirrorOf?: string | undefined;
637
- logo?: string | undefined;
638
- };
639
- blockExplorerUrls: string[];
640
- platform: "solana";
641
- genesisHash: string;
642
- rpcs: string[];
643
- isTestnet?: boolean | undefined;
644
- isDefault?: boolean | undefined;
645
- forceScan?: boolean | undefined;
646
- logo?: string | undefined;
647
- themeColor?: string | undefined;
648
- balancesConfig?: {
649
- "sol-native"?: undefined;
650
- "sol-spl"?: undefined;
651
- } | undefined;
652
525
  };
653
526
  export declare const useTokens: () => ({
654
527
  id: string;
@@ -794,33 +667,6 @@ export declare const useTokens: () => ({
794
667
  coingeckoId?: string | undefined;
795
668
  noDiscovery?: boolean | undefined;
796
669
  mirrorOf?: string | undefined;
797
- } | {
798
- id: string;
799
- networkId: string;
800
- decimals: number;
801
- symbol: string;
802
- type: "sol-native";
803
- platform: "solana";
804
- isDefault?: boolean | undefined;
805
- name?: string | undefined;
806
- logo?: string | undefined;
807
- coingeckoId?: string | undefined;
808
- noDiscovery?: boolean | undefined;
809
- mirrorOf?: string | undefined;
810
- } | {
811
- id: string;
812
- networkId: string;
813
- decimals: number;
814
- symbol: string;
815
- type: "sol-spl";
816
- platform: "solana";
817
- mintAddress: string;
818
- isDefault?: boolean | undefined;
819
- name?: string | undefined;
820
- logo?: string | undefined;
821
- coingeckoId?: string | undefined;
822
- noDiscovery?: boolean | undefined;
823
- mirrorOf?: string | undefined;
824
670
  })[];
825
671
  export declare const useTokensById: () => import("lodash").Dictionary<{
826
672
  id: string;
@@ -966,33 +812,6 @@ export declare const useTokensById: () => import("lodash").Dictionary<{
966
812
  coingeckoId?: string | undefined;
967
813
  noDiscovery?: boolean | undefined;
968
814
  mirrorOf?: string | undefined;
969
- } | {
970
- id: string;
971
- networkId: string;
972
- decimals: number;
973
- symbol: string;
974
- type: "sol-native";
975
- platform: "solana";
976
- isDefault?: boolean | undefined;
977
- name?: string | undefined;
978
- logo?: string | undefined;
979
- coingeckoId?: string | undefined;
980
- noDiscovery?: boolean | undefined;
981
- mirrorOf?: string | undefined;
982
- } | {
983
- id: string;
984
- networkId: string;
985
- decimals: number;
986
- symbol: string;
987
- type: "sol-spl";
988
- platform: "solana";
989
- mintAddress: string;
990
- isDefault?: boolean | undefined;
991
- name?: string | undefined;
992
- logo?: string | undefined;
993
- coingeckoId?: string | undefined;
994
- noDiscovery?: boolean | undefined;
995
- mirrorOf?: string | undefined;
996
815
  }>;
997
816
  export declare const useToken: (tokenId?: TokenId) => {
998
817
  id: string;
@@ -1138,31 +957,4 @@ export declare const useToken: (tokenId?: TokenId) => {
1138
957
  coingeckoId?: string | undefined;
1139
958
  noDiscovery?: boolean | undefined;
1140
959
  mirrorOf?: string | undefined;
1141
- } | {
1142
- id: string;
1143
- networkId: string;
1144
- decimals: number;
1145
- symbol: string;
1146
- type: "sol-native";
1147
- platform: "solana";
1148
- isDefault?: boolean | undefined;
1149
- name?: string | undefined;
1150
- logo?: string | undefined;
1151
- coingeckoId?: string | undefined;
1152
- noDiscovery?: boolean | undefined;
1153
- mirrorOf?: string | undefined;
1154
- } | {
1155
- id: string;
1156
- networkId: string;
1157
- decimals: number;
1158
- symbol: string;
1159
- type: "sol-spl";
1160
- platform: "solana";
1161
- mintAddress: string;
1162
- isDefault?: boolean | undefined;
1163
- name?: string | undefined;
1164
- logo?: string | undefined;
1165
- coingeckoId?: string | undefined;
1166
- noDiscovery?: boolean | undefined;
1167
- mirrorOf?: string | undefined;
1168
960
  };
@@ -1,3 +1,5 @@
1
1
  import { TokenId } from "@talismn/chaindata-provider";
2
- export declare const useTokenRates: () => import("@talismn/token-rates").TokenRatesList;
2
+ export declare const useTokenRates: () => {
3
+ [k: string]: import("@talismn/token-rates").TokenRates;
4
+ };
3
5
  export declare const useTokenRate: (tokenId?: TokenId) => import("@talismn/token-rates").TokenRates;
@@ -0,0 +1,6 @@
1
+ import { Observable as DexieObservable } from "dexie";
2
+ import { Observable as RxjsObservable } from "rxjs";
3
+ /**
4
+ * Converts a dexie Observable into an rxjs Observable.
5
+ */
6
+ export declare function dexieToRxjs<T>(o: DexieObservable<T>): RxjsObservable<T>;
@@ -8,11 +8,13 @@ var chaindataProvider = require('@talismn/chaindata-provider');
8
8
  var balances = require('@talismn/balances');
9
9
  var jotaiEffect = require('jotai-effect');
10
10
  var lodashEs = require('lodash-es');
11
- var chainConnectors = require('@talismn/chain-connectors');
11
+ var chainConnector = require('@talismn/chain-connector');
12
+ var chainConnectorEvm = require('@talismn/chain-connector-evm');
12
13
  var connectionMeta = require('@talismn/connection-meta');
13
14
  var util = require('@talismn/util');
14
15
  var utils = require('jotai/utils');
15
16
  var rxjs = require('rxjs');
17
+ var dexie = require('dexie');
16
18
  var anylogger = require('anylogger');
17
19
  var utilCrypto = require('@polkadot/util-crypto');
18
20
 
@@ -32,20 +34,16 @@ const enabledTokensAtom = jotai.atom(undefined);
32
34
  const allAddressesAtom = jotai.atom([]);
33
35
 
34
36
  const chaindataProviderAtom = jotai.atom(() => {
35
- return new chaindataProvider.ChaindataProvider({
36
- // TODO pass persistedStorage
37
- });
37
+ return new chaindataProvider.ChaindataProvider({});
38
38
  });
39
39
 
40
40
  const chainConnectorsAtom = jotai.atom(get => {
41
41
  const chaindataProvider = get(chaindataProviderAtom);
42
- const substrate = new chainConnectors.ChainConnectorDot(chaindataProvider, connectionMeta.connectionMetaDb);
43
- const evm = new chainConnectors.ChainConnectorEth(chaindataProvider);
44
- const solana = new chainConnectors.ChainConnectorSol(chaindataProvider);
42
+ const substrate = new chainConnector.ChainConnector(chaindataProvider, connectionMeta.connectionMetaDb);
43
+ const evm = new chainConnectorEvm.ChainConnectorEvm(chaindataProvider);
45
44
  return {
46
45
  substrate,
47
- evm,
48
- solana
46
+ evm
49
47
  };
50
48
  });
51
49
 
@@ -87,17 +85,32 @@ var packageJson = {
87
85
 
88
86
  var log = anylogger__default.default(packageJson.name);
89
87
 
88
+ /**
89
+ * Converts a dexie Observable into an rxjs Observable.
90
+ */
91
+ function dexieToRxjs(o) {
92
+ return new rxjs.Observable(observer => {
93
+ const subscription = o.subscribe({
94
+ next: value => observer.next(value),
95
+ error: error => observer.error(error)
96
+ });
97
+ return () => subscription.unsubscribe();
98
+ });
99
+ }
100
+
90
101
  const tokenRatesAtom = jotai.atom(async get => {
91
102
  // runs a timer to keep tokenRates up to date
92
103
  get(tokenRatesFetcherAtomEffect);
93
- return (await get(tokenRatesDbAtom)).tokenRates;
104
+ return await get(tokenRatesDbAtom);
94
105
  });
95
-
96
- // TODO: Persist to storage
97
- const tokenRates$ = new rxjs.ReplaySubject(1);
98
106
  const tokenRatesDbAtom = utils.atomWithObservable(() => {
99
- tokenRates.tryToDeleteOldTokenRatesDb();
100
- return tokenRates$.asObservable();
107
+ const dbRatesToMap = dbRates => Object.fromEntries(dbRates.map(({
108
+ tokenId,
109
+ rates
110
+ }) => [tokenId, rates]));
111
+
112
+ // retrieve fetched tokenRates from the db
113
+ return dexieToRxjs(dexie.liveQuery(() => tokenRates.db.tokenRates.toArray())).pipe(rxjs.map(dbRatesToMap));
101
114
  });
102
115
  const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
103
116
  // lets us tear down the existing timer when the effect is restarted
@@ -108,6 +121,7 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
108
121
  const tokensPromise = get(tokensAtom);
109
122
  (async () => {
110
123
  const tokensById = lodashEs.keyBy(await tokensPromise, "id");
124
+ const tokenIds = Object.keys(tokensById);
111
125
  const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
112
126
  const retryTimeout = 5_000; // 5_000ms = 5 seconds
113
127
 
@@ -115,11 +129,21 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
115
129
  try {
116
130
  if (abort.signal.aborted) return; // don't fetch if aborted
117
131
  const tokenRates$1 = await tokenRates.fetchTokenRates(tokensById, tokenRates.ALL_CURRENCY_IDS, coinsApiConfig);
118
- const putTokenRates = {
119
- tokenRates: tokenRates$1
120
- };
132
+ const putTokenRates = Object.entries(tokenRates$1).map(([tokenId, rates]) => ({
133
+ tokenId,
134
+ rates
135
+ }));
121
136
  if (abort.signal.aborted) return; // don't insert into db if aborted
122
- tokenRates$.next(putTokenRates);
137
+ await tokenRates.db.transaction("rw", tokenRates.db.tokenRates, async () => {
138
+ // override all tokenRates
139
+ await tokenRates.db.tokenRates.bulkPut(putTokenRates);
140
+
141
+ // delete tokenRates for tokens which no longer exist
142
+ const validTokenIds = new Set(tokenIds);
143
+ const tokenRatesIds = await tokenRates.db.tokenRates.toCollection().primaryKeys();
144
+ const deleteIds = tokenRatesIds.filter(id => !validTokenIds.has(id));
145
+ if (deleteIds.length > 0) await tokenRates.db.tokenRates.bulkDelete(deleteIds);
146
+ });
123
147
  if (abort.signal.aborted) return; // don't schedule next loop if aborted
124
148
  setTimeout(hydrate, loopMs);
125
149
  } catch (error) {
@@ -8,11 +8,13 @@ var chaindataProvider = require('@talismn/chaindata-provider');
8
8
  var balances = require('@talismn/balances');
9
9
  var jotaiEffect = require('jotai-effect');
10
10
  var lodashEs = require('lodash-es');
11
- var chainConnectors = require('@talismn/chain-connectors');
11
+ var chainConnector = require('@talismn/chain-connector');
12
+ var chainConnectorEvm = require('@talismn/chain-connector-evm');
12
13
  var connectionMeta = require('@talismn/connection-meta');
13
14
  var util = require('@talismn/util');
14
15
  var utils = require('jotai/utils');
15
16
  var rxjs = require('rxjs');
17
+ var dexie = require('dexie');
16
18
  var anylogger = require('anylogger');
17
19
  var utilCrypto = require('@polkadot/util-crypto');
18
20
 
@@ -32,20 +34,16 @@ const enabledTokensAtom = jotai.atom(undefined);
32
34
  const allAddressesAtom = jotai.atom([]);
33
35
 
34
36
  const chaindataProviderAtom = jotai.atom(() => {
35
- return new chaindataProvider.ChaindataProvider({
36
- // TODO pass persistedStorage
37
- });
37
+ return new chaindataProvider.ChaindataProvider({});
38
38
  });
39
39
 
40
40
  const chainConnectorsAtom = jotai.atom(get => {
41
41
  const chaindataProvider = get(chaindataProviderAtom);
42
- const substrate = new chainConnectors.ChainConnectorDot(chaindataProvider, connectionMeta.connectionMetaDb);
43
- const evm = new chainConnectors.ChainConnectorEth(chaindataProvider);
44
- const solana = new chainConnectors.ChainConnectorSol(chaindataProvider);
42
+ const substrate = new chainConnector.ChainConnector(chaindataProvider, connectionMeta.connectionMetaDb);
43
+ const evm = new chainConnectorEvm.ChainConnectorEvm(chaindataProvider);
45
44
  return {
46
45
  substrate,
47
- evm,
48
- solana
46
+ evm
49
47
  };
50
48
  });
51
49
 
@@ -87,17 +85,32 @@ var packageJson = {
87
85
 
88
86
  var log = anylogger__default.default(packageJson.name);
89
87
 
88
+ /**
89
+ * Converts a dexie Observable into an rxjs Observable.
90
+ */
91
+ function dexieToRxjs(o) {
92
+ return new rxjs.Observable(observer => {
93
+ const subscription = o.subscribe({
94
+ next: value => observer.next(value),
95
+ error: error => observer.error(error)
96
+ });
97
+ return () => subscription.unsubscribe();
98
+ });
99
+ }
100
+
90
101
  const tokenRatesAtom = jotai.atom(async get => {
91
102
  // runs a timer to keep tokenRates up to date
92
103
  get(tokenRatesFetcherAtomEffect);
93
- return (await get(tokenRatesDbAtom)).tokenRates;
104
+ return await get(tokenRatesDbAtom);
94
105
  });
95
-
96
- // TODO: Persist to storage
97
- const tokenRates$ = new rxjs.ReplaySubject(1);
98
106
  const tokenRatesDbAtom = utils.atomWithObservable(() => {
99
- tokenRates.tryToDeleteOldTokenRatesDb();
100
- return tokenRates$.asObservable();
107
+ const dbRatesToMap = dbRates => Object.fromEntries(dbRates.map(({
108
+ tokenId,
109
+ rates
110
+ }) => [tokenId, rates]));
111
+
112
+ // retrieve fetched tokenRates from the db
113
+ return dexieToRxjs(dexie.liveQuery(() => tokenRates.db.tokenRates.toArray())).pipe(rxjs.map(dbRatesToMap));
101
114
  });
102
115
  const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
103
116
  // lets us tear down the existing timer when the effect is restarted
@@ -108,6 +121,7 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
108
121
  const tokensPromise = get(tokensAtom);
109
122
  (async () => {
110
123
  const tokensById = lodashEs.keyBy(await tokensPromise, "id");
124
+ const tokenIds = Object.keys(tokensById);
111
125
  const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
112
126
  const retryTimeout = 5_000; // 5_000ms = 5 seconds
113
127
 
@@ -115,11 +129,21 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
115
129
  try {
116
130
  if (abort.signal.aborted) return; // don't fetch if aborted
117
131
  const tokenRates$1 = await tokenRates.fetchTokenRates(tokensById, tokenRates.ALL_CURRENCY_IDS, coinsApiConfig);
118
- const putTokenRates = {
119
- tokenRates: tokenRates$1
120
- };
132
+ const putTokenRates = Object.entries(tokenRates$1).map(([tokenId, rates]) => ({
133
+ tokenId,
134
+ rates
135
+ }));
121
136
  if (abort.signal.aborted) return; // don't insert into db if aborted
122
- tokenRates$.next(putTokenRates);
137
+ await tokenRates.db.transaction("rw", tokenRates.db.tokenRates, async () => {
138
+ // override all tokenRates
139
+ await tokenRates.db.tokenRates.bulkPut(putTokenRates);
140
+
141
+ // delete tokenRates for tokens which no longer exist
142
+ const validTokenIds = new Set(tokenIds);
143
+ const tokenRatesIds = await tokenRates.db.tokenRates.toCollection().primaryKeys();
144
+ const deleteIds = tokenRatesIds.filter(id => !validTokenIds.has(id));
145
+ if (deleteIds.length > 0) await tokenRates.db.tokenRates.bulkDelete(deleteIds);
146
+ });
123
147
  if (abort.signal.aborted) return; // don't schedule next loop if aborted
124
148
  setTimeout(hydrate, loopMs);
125
149
  } catch (error) {
@@ -1,17 +1,19 @@
1
1
  import { atom, useSetAtom, useAtomValue } from 'jotai';
2
2
  import { useMemo, useEffect } from 'react';
3
- import { DEFAULT_COINSAPI_CONFIG, tryToDeleteOldTokenRatesDb, fetchTokenRates, ALL_CURRENCY_IDS } from '@talismn/token-rates';
3
+ import { DEFAULT_COINSAPI_CONFIG, db, fetchTokenRates, ALL_CURRENCY_IDS } from '@talismn/token-rates';
4
4
  import { jsx, Fragment } from 'react/jsx-runtime';
5
5
  import { ChaindataProvider } from '@talismn/chaindata-provider';
6
6
  export { evmErc20TokenId, evmNativeTokenId, subAssetTokenId, subNativeTokenId, subPsp22TokenId, subTokensTokenId } from '@talismn/chaindata-provider';
7
7
  import { BalancesProvider as BalancesProvider$1, Balances } from '@talismn/balances';
8
8
  import { atomEffect } from 'jotai-effect';
9
9
  import { keyBy, fromPairs } from 'lodash-es';
10
- import { ChainConnectorDot, ChainConnectorEth, ChainConnectorSol } from '@talismn/chain-connectors';
10
+ import { ChainConnector } from '@talismn/chain-connector';
11
+ import { ChainConnectorEvm } from '@talismn/chain-connector-evm';
11
12
  import { connectionMetaDb } from '@talismn/connection-meta';
12
13
  import { firstThenDebounce, isTruthy, isAbortError } from '@talismn/util';
13
14
  import { atomWithObservable } from 'jotai/utils';
14
- import { combineLatest, ReplaySubject } from 'rxjs';
15
+ import { combineLatest, Observable, map } from 'rxjs';
16
+ import { liveQuery } from 'dexie';
15
17
  import anylogger from 'anylogger';
16
18
  import { cryptoWaitReady } from '@polkadot/util-crypto';
17
19
 
@@ -27,20 +29,16 @@ const enabledTokensAtom = atom(undefined);
27
29
  const allAddressesAtom = atom([]);
28
30
 
29
31
  const chaindataProviderAtom = atom(() => {
30
- return new ChaindataProvider({
31
- // TODO pass persistedStorage
32
- });
32
+ return new ChaindataProvider({});
33
33
  });
34
34
 
35
35
  const chainConnectorsAtom = atom(get => {
36
36
  const chaindataProvider = get(chaindataProviderAtom);
37
- const substrate = new ChainConnectorDot(chaindataProvider, connectionMetaDb);
38
- const evm = new ChainConnectorEth(chaindataProvider);
39
- const solana = new ChainConnectorSol(chaindataProvider);
37
+ const substrate = new ChainConnector(chaindataProvider, connectionMetaDb);
38
+ const evm = new ChainConnectorEvm(chaindataProvider);
40
39
  return {
41
40
  substrate,
42
- evm,
43
- solana
41
+ evm
44
42
  };
45
43
  });
46
44
 
@@ -82,17 +80,32 @@ var packageJson = {
82
80
 
83
81
  var log = anylogger(packageJson.name);
84
82
 
83
+ /**
84
+ * Converts a dexie Observable into an rxjs Observable.
85
+ */
86
+ function dexieToRxjs(o) {
87
+ return new Observable(observer => {
88
+ const subscription = o.subscribe({
89
+ next: value => observer.next(value),
90
+ error: error => observer.error(error)
91
+ });
92
+ return () => subscription.unsubscribe();
93
+ });
94
+ }
95
+
85
96
  const tokenRatesAtom = atom(async get => {
86
97
  // runs a timer to keep tokenRates up to date
87
98
  get(tokenRatesFetcherAtomEffect);
88
- return (await get(tokenRatesDbAtom)).tokenRates;
99
+ return await get(tokenRatesDbAtom);
89
100
  });
90
-
91
- // TODO: Persist to storage
92
- const tokenRates$ = new ReplaySubject(1);
93
101
  const tokenRatesDbAtom = atomWithObservable(() => {
94
- tryToDeleteOldTokenRatesDb();
95
- return tokenRates$.asObservable();
102
+ const dbRatesToMap = dbRates => Object.fromEntries(dbRates.map(({
103
+ tokenId,
104
+ rates
105
+ }) => [tokenId, rates]));
106
+
107
+ // retrieve fetched tokenRates from the db
108
+ return dexieToRxjs(liveQuery(() => db.tokenRates.toArray())).pipe(map(dbRatesToMap));
96
109
  });
97
110
  const tokenRatesFetcherAtomEffect = atomEffect(get => {
98
111
  // lets us tear down the existing timer when the effect is restarted
@@ -103,6 +116,7 @@ const tokenRatesFetcherAtomEffect = atomEffect(get => {
103
116
  const tokensPromise = get(tokensAtom);
104
117
  (async () => {
105
118
  const tokensById = keyBy(await tokensPromise, "id");
119
+ const tokenIds = Object.keys(tokensById);
106
120
  const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
107
121
  const retryTimeout = 5_000; // 5_000ms = 5 seconds
108
122
 
@@ -110,11 +124,21 @@ const tokenRatesFetcherAtomEffect = atomEffect(get => {
110
124
  try {
111
125
  if (abort.signal.aborted) return; // don't fetch if aborted
112
126
  const tokenRates = await fetchTokenRates(tokensById, ALL_CURRENCY_IDS, coinsApiConfig);
113
- const putTokenRates = {
114
- tokenRates
115
- };
127
+ const putTokenRates = Object.entries(tokenRates).map(([tokenId, rates]) => ({
128
+ tokenId,
129
+ rates
130
+ }));
116
131
  if (abort.signal.aborted) return; // don't insert into db if aborted
117
- tokenRates$.next(putTokenRates);
132
+ await db.transaction("rw", db.tokenRates, async () => {
133
+ // override all tokenRates
134
+ await db.tokenRates.bulkPut(putTokenRates);
135
+
136
+ // delete tokenRates for tokens which no longer exist
137
+ const validTokenIds = new Set(tokenIds);
138
+ const tokenRatesIds = await db.tokenRates.toCollection().primaryKeys();
139
+ const deleteIds = tokenRatesIds.filter(id => !validTokenIds.has(id));
140
+ if (deleteIds.length > 0) await db.tokenRates.bulkDelete(deleteIds);
141
+ });
118
142
  if (abort.signal.aborted) return; // don't schedule next loop if aborted
119
143
  setTimeout(hydrate, loopMs);
120
144
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/balances-react",
3
- "version": "0.0.0-pr2136-20250814032256",
3
+ "version": "0.0.0-pr2136-20250815050739",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "GPL-3.0-or-later",
@@ -23,18 +23,21 @@
23
23
  "dependencies": {
24
24
  "anylogger": "^1.0.11",
25
25
  "blueimp-md5": "2.19.0",
26
+ "dexie": "^4.0.9",
27
+ "dexie-react-hooks": "^1.1.7",
26
28
  "jotai": "~2",
27
29
  "jotai-effect": "~1",
28
30
  "lodash-es": "4.17.21",
29
31
  "react-use": "^17.5.1",
30
32
  "rxjs": "^7.8.1",
31
- "@talismn/balances": "0.0.0-pr2136-20250814032256",
32
- "@talismn/chain-connectors": "0.0.0-pr2136-20250814032256",
33
- "@talismn/connection-meta": "0.0.0-pr2136-20250814032256",
33
+ "@talismn/balances": "0.0.0-pr2136-20250815050739",
34
+ "@talismn/chain-connector-evm": "0.0.0-pr2136-20250815050739",
35
+ "@talismn/chain-connector": "0.0.0-pr2136-20250815050739",
36
+ "@talismn/chaindata-provider": "0.0.0-pr2136-20250815050739",
34
37
  "@talismn/scale": "0.2.0",
35
- "@talismn/token-rates": "0.0.0-pr2136-20250814032256",
36
- "@talismn/util": "0.0.0-pr2136-20250814032256",
37
- "@talismn/chaindata-provider": "0.0.0-pr2136-20250814032256"
38
+ "@talismn/connection-meta": "0.0.0-pr2136-20250815050739",
39
+ "@talismn/token-rates": "0.0.0-pr2136-20250815050739",
40
+ "@talismn/util": "0.5.0"
38
41
  },
39
42
  "devDependencies": {
40
43
  "@types/jest": "^29.5.14",