@talismn/balances-react 0.0.0-pr2120-20250805025334 → 0.0.0-pr2127-20250806071759

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 +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
+ }>>;
@@ -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>;
@@ -14,6 +14,7 @@ var connectionMeta = require('@talismn/connection-meta');
14
14
  var util = require('@talismn/util');
15
15
  var utils = require('jotai/utils');
16
16
  var rxjs = require('rxjs');
17
+ var dexie = require('dexie');
17
18
  var anylogger = require('anylogger');
18
19
  var utilCrypto = require('@polkadot/util-crypto');
19
20
 
@@ -33,9 +34,7 @@ const enabledTokensAtom = jotai.atom(undefined);
33
34
  const allAddressesAtom = jotai.atom([]);
34
35
 
35
36
  const chaindataProviderAtom = jotai.atom(() => {
36
- return new chaindataProvider.ChaindataProvider({
37
- // TODO pass persistedStorage
38
- });
37
+ return new chaindataProvider.ChaindataProvider({});
39
38
  });
40
39
 
41
40
  const chainConnectorsAtom = jotai.atom(get => {
@@ -86,17 +85,32 @@ var packageJson = {
86
85
 
87
86
  var log = anylogger__default.default(packageJson.name);
88
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
+
89
101
  const tokenRatesAtom = jotai.atom(async get => {
90
102
  // runs a timer to keep tokenRates up to date
91
103
  get(tokenRatesFetcherAtomEffect);
92
- return (await get(tokenRatesDbAtom)).tokenRates;
104
+ return await get(tokenRatesDbAtom);
93
105
  });
94
-
95
- // TODO: Persist to storage
96
- const tokenRates$ = new rxjs.ReplaySubject(1);
97
106
  const tokenRatesDbAtom = utils.atomWithObservable(() => {
98
- tokenRates.tryToDeleteOldTokenRatesDb();
99
- 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));
100
114
  });
101
115
  const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
102
116
  // lets us tear down the existing timer when the effect is restarted
@@ -107,6 +121,7 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
107
121
  const tokensPromise = get(tokensAtom);
108
122
  (async () => {
109
123
  const tokensById = lodashEs.keyBy(await tokensPromise, "id");
124
+ const tokenIds = Object.keys(tokensById);
110
125
  const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
111
126
  const retryTimeout = 5_000; // 5_000ms = 5 seconds
112
127
 
@@ -114,11 +129,21 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
114
129
  try {
115
130
  if (abort.signal.aborted) return; // don't fetch if aborted
116
131
  const tokenRates$1 = await tokenRates.fetchTokenRates(tokensById, tokenRates.ALL_CURRENCY_IDS, coinsApiConfig);
117
- const putTokenRates = {
118
- tokenRates: tokenRates$1
119
- };
132
+ const putTokenRates = Object.entries(tokenRates$1).map(([tokenId, rates]) => ({
133
+ tokenId,
134
+ rates
135
+ }));
120
136
  if (abort.signal.aborted) return; // don't insert into db if aborted
121
- 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
+ });
122
147
  if (abort.signal.aborted) return; // don't schedule next loop if aborted
123
148
  setTimeout(hydrate, loopMs);
124
149
  } catch (error) {
@@ -14,6 +14,7 @@ var connectionMeta = require('@talismn/connection-meta');
14
14
  var util = require('@talismn/util');
15
15
  var utils = require('jotai/utils');
16
16
  var rxjs = require('rxjs');
17
+ var dexie = require('dexie');
17
18
  var anylogger = require('anylogger');
18
19
  var utilCrypto = require('@polkadot/util-crypto');
19
20
 
@@ -33,9 +34,7 @@ const enabledTokensAtom = jotai.atom(undefined);
33
34
  const allAddressesAtom = jotai.atom([]);
34
35
 
35
36
  const chaindataProviderAtom = jotai.atom(() => {
36
- return new chaindataProvider.ChaindataProvider({
37
- // TODO pass persistedStorage
38
- });
37
+ return new chaindataProvider.ChaindataProvider({});
39
38
  });
40
39
 
41
40
  const chainConnectorsAtom = jotai.atom(get => {
@@ -86,17 +85,32 @@ var packageJson = {
86
85
 
87
86
  var log = anylogger__default.default(packageJson.name);
88
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
+
89
101
  const tokenRatesAtom = jotai.atom(async get => {
90
102
  // runs a timer to keep tokenRates up to date
91
103
  get(tokenRatesFetcherAtomEffect);
92
- return (await get(tokenRatesDbAtom)).tokenRates;
104
+ return await get(tokenRatesDbAtom);
93
105
  });
94
-
95
- // TODO: Persist to storage
96
- const tokenRates$ = new rxjs.ReplaySubject(1);
97
106
  const tokenRatesDbAtom = utils.atomWithObservable(() => {
98
- tokenRates.tryToDeleteOldTokenRatesDb();
99
- 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));
100
114
  });
101
115
  const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
102
116
  // lets us tear down the existing timer when the effect is restarted
@@ -107,6 +121,7 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
107
121
  const tokensPromise = get(tokensAtom);
108
122
  (async () => {
109
123
  const tokensById = lodashEs.keyBy(await tokensPromise, "id");
124
+ const tokenIds = Object.keys(tokensById);
110
125
  const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
111
126
  const retryTimeout = 5_000; // 5_000ms = 5 seconds
112
127
 
@@ -114,11 +129,21 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
114
129
  try {
115
130
  if (abort.signal.aborted) return; // don't fetch if aborted
116
131
  const tokenRates$1 = await tokenRates.fetchTokenRates(tokensById, tokenRates.ALL_CURRENCY_IDS, coinsApiConfig);
117
- const putTokenRates = {
118
- tokenRates: tokenRates$1
119
- };
132
+ const putTokenRates = Object.entries(tokenRates$1).map(([tokenId, rates]) => ({
133
+ tokenId,
134
+ rates
135
+ }));
120
136
  if (abort.signal.aborted) return; // don't insert into db if aborted
121
- 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
+ });
122
147
  if (abort.signal.aborted) return; // don't schedule next loop if aborted
123
148
  setTimeout(hydrate, loopMs);
124
149
  } catch (error) {
@@ -1,6 +1,6 @@
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';
@@ -12,7 +12,8 @@ import { ChainConnectorEvm } from '@talismn/chain-connector-evm';
12
12
  import { connectionMetaDb } from '@talismn/connection-meta';
13
13
  import { firstThenDebounce, isTruthy, isAbortError } from '@talismn/util';
14
14
  import { atomWithObservable } from 'jotai/utils';
15
- import { combineLatest, ReplaySubject } from 'rxjs';
15
+ import { combineLatest, Observable, map } from 'rxjs';
16
+ import { liveQuery } from 'dexie';
16
17
  import anylogger from 'anylogger';
17
18
  import { cryptoWaitReady } from '@polkadot/util-crypto';
18
19
 
@@ -28,9 +29,7 @@ const enabledTokensAtom = atom(undefined);
28
29
  const allAddressesAtom = atom([]);
29
30
 
30
31
  const chaindataProviderAtom = atom(() => {
31
- return new ChaindataProvider({
32
- // TODO pass persistedStorage
33
- });
32
+ return new ChaindataProvider({});
34
33
  });
35
34
 
36
35
  const chainConnectorsAtom = atom(get => {
@@ -81,17 +80,32 @@ var packageJson = {
81
80
 
82
81
  var log = anylogger(packageJson.name);
83
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
+
84
96
  const tokenRatesAtom = atom(async get => {
85
97
  // runs a timer to keep tokenRates up to date
86
98
  get(tokenRatesFetcherAtomEffect);
87
- return (await get(tokenRatesDbAtom)).tokenRates;
99
+ return await get(tokenRatesDbAtom);
88
100
  });
89
-
90
- // TODO: Persist to storage
91
- const tokenRates$ = new ReplaySubject(1);
92
101
  const tokenRatesDbAtom = atomWithObservable(() => {
93
- tryToDeleteOldTokenRatesDb();
94
- 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));
95
109
  });
96
110
  const tokenRatesFetcherAtomEffect = atomEffect(get => {
97
111
  // lets us tear down the existing timer when the effect is restarted
@@ -102,6 +116,7 @@ const tokenRatesFetcherAtomEffect = atomEffect(get => {
102
116
  const tokensPromise = get(tokensAtom);
103
117
  (async () => {
104
118
  const tokensById = keyBy(await tokensPromise, "id");
119
+ const tokenIds = Object.keys(tokensById);
105
120
  const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
106
121
  const retryTimeout = 5_000; // 5_000ms = 5 seconds
107
122
 
@@ -109,11 +124,21 @@ const tokenRatesFetcherAtomEffect = atomEffect(get => {
109
124
  try {
110
125
  if (abort.signal.aborted) return; // don't fetch if aborted
111
126
  const tokenRates = await fetchTokenRates(tokensById, ALL_CURRENCY_IDS, coinsApiConfig);
112
- const putTokenRates = {
113
- tokenRates
114
- };
127
+ const putTokenRates = Object.entries(tokenRates).map(([tokenId, rates]) => ({
128
+ tokenId,
129
+ rates
130
+ }));
115
131
  if (abort.signal.aborted) return; // don't insert into db if aborted
116
- 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
+ });
117
142
  if (abort.signal.aborted) return; // don't schedule next loop if aborted
118
143
  setTimeout(hydrate, loopMs);
119
144
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/balances-react",
3
- "version": "0.0.0-pr2120-20250805025334",
3
+ "version": "0.0.0-pr2127-20250806071759",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "GPL-3.0-or-later",
@@ -23,19 +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-pr2120-20250805025334",
32
- "@talismn/chain-connector": "0.0.0-pr2120-20250805025334",
33
- "@talismn/chain-connector-evm": "0.0.0-pr2120-20250805025334",
34
- "@talismn/chaindata-provider": "0.0.0-pr2120-20250805025334",
33
+ "@talismn/chain-connector-evm": "0.0.0-pr2127-20250806071759",
34
+ "@talismn/chain-connector": "0.0.0-pr2127-20250806071759",
35
+ "@talismn/balances": "0.0.0-pr2127-20250806071759",
36
+ "@talismn/connection-meta": "0.0.0-pr2127-20250806071759",
37
+ "@talismn/chaindata-provider": "0.0.0-pr2127-20250806071759",
35
38
  "@talismn/scale": "0.2.0",
36
- "@talismn/token-rates": "0.0.0-pr2120-20250805025334",
37
- "@talismn/connection-meta": "0.0.0-pr2120-20250805025334",
38
- "@talismn/util": "0.0.0-pr2120-20250805025334"
39
+ "@talismn/token-rates": "0.0.0-pr2127-20250806071759",
40
+ "@talismn/util": "0.5.0"
39
41
  },
40
42
  "devDependencies": {
41
43
  "@types/jest": "^29.5.14",