@talismn/balances-react 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/declarations/src/atoms/allAddresses.d.ts +4 -0
- package/dist/declarations/src/atoms/balanceModules.d.ts +2 -0
- package/dist/declarations/src/atoms/balances.d.ts +6 -0
- package/dist/declarations/src/atoms/chainConnectors.d.ts +2 -0
- package/dist/declarations/src/atoms/chaindata.d.ts +34 -0
- package/dist/declarations/src/atoms/chaindataProvider.d.ts +5 -0
- package/dist/declarations/src/atoms/config.d.ts +18 -0
- package/dist/declarations/src/atoms/cryptoWaitReady.d.ts +1 -0
- package/dist/declarations/src/atoms/tokenRates.d.ts +3 -0
- package/dist/declarations/src/hooks/useBalances.d.ts +27 -3
- package/dist/declarations/src/hooks/useChainConnectors.d.ts +1 -12
- package/dist/declarations/src/hooks/useChaindata.d.ts +23 -7
- package/dist/declarations/src/hooks/useTokenRates.d.ts +4 -2
- package/dist/declarations/src/index.d.ts +80 -2
- package/dist/declarations/src/util/balancesPersist.d.ts +10 -0
- package/dist/declarations/src/util/dexieToRxjs.d.ts +6 -0
- package/dist/talismn-balances-react.cjs.dev.js +606 -595
- package/dist/talismn-balances-react.cjs.prod.js +606 -595
- package/dist/talismn-balances-react.esm.js +550 -575
- package/package.json +32 -26
- package/CHANGELOG.md +0 -438
- package/dist/declarations/src/hooks/index.d.ts +0 -23
- package/dist/declarations/src/hooks/useAllAddresses.d.ts +0 -4
- package/dist/declarations/src/hooks/useBalanceModules.d.ts +0 -8
- package/dist/declarations/src/hooks/useBalancesHydrate.d.ts +0 -2
- package/dist/declarations/src/hooks/useBalancesStatus.d.ts +0 -18
- package/dist/declarations/src/hooks/useChains.d.ts +0 -3
- package/dist/declarations/src/hooks/useDbCache.d.ts +0 -24
- package/dist/declarations/src/hooks/useDbCacheSubscription.d.ts +0 -13
- package/dist/declarations/src/hooks/useEvmNetworks.d.ts +0 -3
- package/dist/declarations/src/hooks/useTokens.d.ts +0 -3
- package/dist/declarations/src/hooks/useWithTestnets.d.ts +0 -8
- package/dist/declarations/src/util/index.d.ts +0 -2
- package/dist/declarations/src/util/provideContext.d.ts +0 -9
- package/dist/declarations/src/util/useMulticastSubscription.d.ts +0 -16
- package/dist/declarations/src/util/useSharedSubscription.d.ts +0 -9
@@ -1,191 +1,52 @@
|
|
1
1
|
'use strict';
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
var jotai = require('jotai');
|
5
4
|
var react = require('react');
|
5
|
+
var balances = require('@talismn/balances');
|
6
|
+
var tokenRates = require('@talismn/token-rates');
|
6
7
|
var jsxRuntime = require('react/jsx-runtime');
|
8
|
+
var util = require('@talismn/util');
|
9
|
+
var jotaiEffect = require('jotai-effect');
|
10
|
+
var utils = require('jotai/utils');
|
11
|
+
var lodash = require('lodash');
|
12
|
+
var rxjs = require('rxjs');
|
13
|
+
var anylogger = require('anylogger');
|
7
14
|
var chainConnector = require('@talismn/chain-connector');
|
8
15
|
var chainConnectorEvm = require('@talismn/chain-connector-evm');
|
9
16
|
var connectionMeta = require('@talismn/connection-meta');
|
10
|
-
var
|
11
|
-
var
|
12
|
-
var
|
13
|
-
var
|
14
|
-
var reactUse = require('react-use');
|
15
|
-
var md5 = require('blueimp-md5');
|
16
|
-
var anylogger = require('anylogger');
|
17
|
-
var rxjs = require('rxjs');
|
17
|
+
var chaindataProvider = require('@talismn/chaindata-provider');
|
18
|
+
var utilCrypto = require('@polkadot/util-crypto');
|
19
|
+
var dexie = require('dexie');
|
20
|
+
var isEqual = require('lodash/isEqual');
|
18
21
|
|
19
|
-
function _interopDefault (e) { return e && e.__esModule ? e : {
|
22
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
20
23
|
|
21
|
-
var md5__default = /*#__PURE__*/_interopDefault(md5);
|
22
24
|
var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
var isEqual__default = /*#__PURE__*/_interopDefault(isEqual);
|
26
|
+
|
27
|
+
const balanceModuleCreatorsAtom = jotai.atom(balances.defaultBalanceModules);
|
28
|
+
const onfinalityApiKeyAtom = jotai.atom(undefined);
|
29
|
+
const innerCoingeckoConfigAtom = jotai.atom(tokenRates.DEFAULT_COINGECKO_CONFIG);
|
30
|
+
const coingeckoConfigAtom = jotai.atom(get => get(innerCoingeckoConfigAtom), (_get, set, options) => {
|
31
|
+
const apiUrl = options.apiUrl ?? tokenRates.DEFAULT_COINGECKO_CONFIG.apiUrl;
|
32
|
+
const apiKeyName = options.apiKeyName ?? tokenRates.DEFAULT_COINGECKO_CONFIG.apiKeyName;
|
33
|
+
const apiKeyValue = options.apiKeyValue ?? tokenRates.DEFAULT_COINGECKO_CONFIG.apiKeyValue;
|
34
|
+
set(innerCoingeckoConfigAtom, {
|
35
|
+
apiUrl,
|
36
|
+
apiKeyName,
|
37
|
+
apiKeyValue
|
29
38
|
});
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
const ctx = useProviderContext(props);
|
35
|
-
return /*#__PURE__*/jsxRuntime.jsx(Context.Provider, {
|
36
|
-
value: ctx,
|
37
|
-
children: children
|
38
|
-
});
|
39
|
-
};
|
40
|
-
const useProvidedContext = () => {
|
41
|
-
const context = react.useContext(Context);
|
42
|
-
if (typeof context === "object" && context && "__provideContextInternalDefaultValue" in context) throw new Error("This hook requires a provider to be present above it in the tree");
|
43
|
-
return context;
|
44
|
-
};
|
45
|
-
const result = [Provider, useProvidedContext];
|
46
|
-
return result;
|
47
|
-
};
|
48
|
-
|
49
|
-
const useAllAddressesProvider = () => react.useState([]);
|
50
|
-
const [AllAddressesProvider, useAllAddresses] = provideContext(useAllAddressesProvider);
|
51
|
-
|
52
|
-
function useChaindataProvider(options = {}) {
|
53
|
-
const [onfinalityApiKey, setOnfinalityApiKey] = react.useState(options.onfinalityApiKey);
|
54
|
-
|
55
|
-
// make sure we recreate provider only when the onfinalityApiKey changes
|
56
|
-
react.useEffect(() => {
|
57
|
-
if (options.onfinalityApiKey !== onfinalityApiKey) setOnfinalityApiKey(options.onfinalityApiKey);
|
58
|
-
}, [options.onfinalityApiKey, onfinalityApiKey]);
|
59
|
-
return react.useMemo(() => new chaindataProviderExtension.ChaindataProviderExtension({
|
60
|
-
onfinalityApiKey
|
61
|
-
}), [onfinalityApiKey]);
|
62
|
-
}
|
63
|
-
const [ChaindataProvider, useChaindata] = provideContext(useChaindataProvider);
|
64
|
-
|
65
|
-
function useChainConnectorsProvider(options) {
|
66
|
-
const [onfinalityApiKey, setOnfinalityApiKey] = react.useState(options.onfinalityApiKey);
|
67
|
-
|
68
|
-
// make sure we recreate provider only when the onfinalityApiKey changes
|
69
|
-
react.useEffect(() => {
|
70
|
-
if (options.onfinalityApiKey !== onfinalityApiKey) setOnfinalityApiKey(options.onfinalityApiKey);
|
71
|
-
}, [options.onfinalityApiKey, onfinalityApiKey]);
|
72
|
-
|
73
|
-
// chaindata dependency
|
74
|
-
const chaindata = useChaindata();
|
75
|
-
|
76
|
-
// substrate connector
|
77
|
-
const substrate = react.useMemo(() => new chainConnector.ChainConnector(chaindata, connectionMeta.connectionMetaDb), [chaindata]);
|
78
|
-
|
79
|
-
// evm connector
|
80
|
-
const evm = react.useMemo(() => new chainConnectorEvm.ChainConnectorEvm(chaindata, {
|
81
|
-
onfinalityApiKey
|
82
|
-
}), [chaindata, onfinalityApiKey]);
|
83
|
-
return react.useMemo(() => ({
|
84
|
-
substrate,
|
85
|
-
evm
|
86
|
-
}), [substrate, evm]);
|
87
|
-
}
|
88
|
-
const [ChainConnectorsProvider, useChainConnectors] = provideContext(useChainConnectorsProvider);
|
89
|
-
|
90
|
-
const useBalanceModulesProvider = ({
|
91
|
-
balanceModules
|
92
|
-
}) => {
|
93
|
-
const chainConnectors = useChainConnectors();
|
94
|
-
const chaindataProvider = useChaindata();
|
95
|
-
const hydrated = react.useMemo(() => chainConnectors.substrate && chainConnectors.evm && chaindataProvider ? balanceModules.map(mod => mod({
|
96
|
-
chainConnectors,
|
97
|
-
chaindataProvider
|
98
|
-
})) : [], [balanceModules, chainConnectors, chaindataProvider]);
|
99
|
-
return hydrated;
|
100
|
-
};
|
101
|
-
const [BalanceModulesProvider, useBalanceModules] = provideContext(useBalanceModulesProvider);
|
102
|
-
|
103
|
-
const filterNoTestnet = ({
|
104
|
-
isTestnet
|
105
|
-
}) => isTestnet === false;
|
106
|
-
const DEFAULT_VALUE = {
|
107
|
-
chainsWithTestnets: [],
|
108
|
-
chainsWithoutTestnets: [],
|
109
|
-
evmNetworksWithTestnets: [],
|
110
|
-
evmNetworksWithoutTestnets: [],
|
111
|
-
tokensWithTestnets: [],
|
112
|
-
tokensWithoutTestnets: [],
|
113
|
-
chainsWithTestnetsMap: {},
|
114
|
-
chainsWithoutTestnetsMap: {},
|
115
|
-
evmNetworksWithTestnetsMap: {},
|
116
|
-
evmNetworksWithoutTestnetsMap: {},
|
117
|
-
tokensWithTestnetsMap: {},
|
118
|
-
tokensWithoutTestnetsMap: {},
|
119
|
-
tokenRatesMap: {},
|
120
|
-
balances: []
|
121
|
-
};
|
122
|
-
const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, tokenRates, allBalances) => {
|
123
|
-
if (!chainsMap || !evmNetworksMap || !tokensMap || !tokenRates || !allBalances) return DEFAULT_VALUE;
|
124
|
-
const chainsWithTestnets = Object.values(chainsMap);
|
125
|
-
const chainsWithoutTestnets = chainsWithTestnets.filter(filterNoTestnet);
|
126
|
-
const chainsWithoutTestnetsMap = Object.fromEntries(chainsWithoutTestnets.map(network => [network.id, network]));
|
127
|
-
const evmNetworksWithTestnets = Object.values(evmNetworksMap);
|
128
|
-
const evmNetworksWithoutTestnets = evmNetworksWithTestnets.filter(filterNoTestnet);
|
129
|
-
const evmNetworksWithoutTestnetsMap = Object.fromEntries(evmNetworksWithoutTestnets.map(network => [network.id, network]));
|
130
|
-
|
131
|
-
// ensure that we have corresponding network for each token
|
132
|
-
const tokensWithTestnets = Object.values(tokensMap).filter(token => token.chain && chainsMap[token.chain.id] || token.evmNetwork && evmNetworksMap[token.evmNetwork.id]);
|
133
|
-
const tokensWithoutTestnets = tokensWithTestnets.filter(filterNoTestnet).filter(token => token.chain && chainsWithoutTestnetsMap[token.chain.id] || token.evmNetwork && evmNetworksWithoutTestnetsMap[token.evmNetwork.id]);
|
134
|
-
const tokensWithTestnetsMap = Object.fromEntries(tokensWithTestnets.map(token => [token.id, token]));
|
135
|
-
const tokensWithoutTestnetsMap = Object.fromEntries(tokensWithoutTestnets.map(token => [token.id, token]));
|
136
|
-
const tokenRatesMap = Object.fromEntries(tokenRates.map(({
|
137
|
-
tokenId,
|
138
|
-
rates
|
139
|
-
}) => [tokenId, rates]));
|
39
|
+
});
|
40
|
+
const enableTestnetsAtom = jotai.atom(false);
|
41
|
+
const enabledChainsAtom = jotai.atom(undefined);
|
42
|
+
const enabledTokensAtom = jotai.atom(undefined);
|
140
43
|
|
141
|
-
|
142
|
-
|
143
|
-
return {
|
144
|
-
chainsWithTestnets,
|
145
|
-
chainsWithoutTestnets,
|
146
|
-
evmNetworksWithTestnets,
|
147
|
-
evmNetworksWithoutTestnets,
|
148
|
-
tokensWithTestnets,
|
149
|
-
tokensWithoutTestnets,
|
150
|
-
chainsWithTestnetsMap: chainsMap,
|
151
|
-
chainsWithoutTestnetsMap,
|
152
|
-
evmNetworksWithTestnetsMap: evmNetworksMap,
|
153
|
-
evmNetworksWithoutTestnetsMap,
|
154
|
-
tokensWithTestnetsMap,
|
155
|
-
tokensWithoutTestnetsMap,
|
156
|
-
tokenRatesMap,
|
157
|
-
balances
|
158
|
-
};
|
159
|
-
};
|
160
|
-
const useDbCacheProvider = () => {
|
161
|
-
const chaindataProvider = useChaindata();
|
162
|
-
const chainList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.chains(), [chaindataProvider]);
|
163
|
-
const evmNetworkList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.evmNetworks(), [chaindataProvider]);
|
164
|
-
const tokenList = dexieReactHooks.useLiveQuery(() => chaindataProvider?.tokens(), [chaindataProvider]);
|
165
|
-
const tokenRates$1 = dexieReactHooks.useLiveQuery(() => tokenRates.db.tokenRates.toArray(), []);
|
166
|
-
const rawBalances = dexieReactHooks.useLiveQuery(() => balances.db.balances.toArray(), []);
|
167
|
-
const [dbData, setDbData] = react.useState(DEFAULT_VALUE);
|
168
|
-
|
169
|
-
// debounce every 500ms to prevent hammering UI with updates
|
170
|
-
reactUse.useDebounce(() => {
|
171
|
-
setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates$1, rawBalances));
|
172
|
-
}, 500, [chainList, evmNetworkList, tokenList, tokenRates$1, rawBalances]);
|
173
|
-
const refInitialized = react.useRef(false);
|
174
|
-
|
175
|
-
// force an update as soon as all datasources are fetched, so UI can display data ASAP
|
176
|
-
react.useEffect(() => {
|
177
|
-
if (!refInitialized.current && chainList && evmNetworkList && tokenList && tokenRates$1 && rawBalances) {
|
178
|
-
setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates$1, rawBalances));
|
179
|
-
refInitialized.current = true;
|
180
|
-
}
|
181
|
-
}, [chainList, evmNetworkList, tokenList, tokenRates$1, rawBalances]);
|
182
|
-
return dbData;
|
183
|
-
};
|
184
|
-
const [DbCacheProvider, useDbCache] = provideContext(useDbCacheProvider);
|
44
|
+
/** Sets the list of addresses for which token balances will be fetched by the balances subscription */
|
45
|
+
const allAddressesAtom = jotai.atom([]);
|
185
46
|
|
186
47
|
var packageJson = {
|
187
48
|
name: "@talismn/balances-react",
|
188
|
-
version: "0.
|
49
|
+
version: "0.7.0",
|
189
50
|
author: "Talisman",
|
190
51
|
homepage: "https://talisman.xyz",
|
191
52
|
license: "GPL-3.0-or-later",
|
@@ -208,35 +69,41 @@ var packageJson = {
|
|
208
69
|
scripts: {
|
209
70
|
test: "jest",
|
210
71
|
lint: "eslint src --max-warnings 0",
|
211
|
-
clean: "rm -rf dist
|
72
|
+
clean: "rm -rf dist .turbo node_modules"
|
212
73
|
},
|
213
74
|
dependencies: {
|
214
75
|
"@talismn/balances": "workspace:*",
|
215
76
|
"@talismn/chain-connector": "workspace:*",
|
216
77
|
"@talismn/chain-connector-evm": "workspace:*",
|
217
78
|
"@talismn/chaindata-provider": "workspace:*",
|
218
|
-
"@talismn/chaindata-provider-extension": "workspace:*",
|
219
79
|
"@talismn/connection-meta": "workspace:*",
|
80
|
+
"@talismn/scale": "workspace:*",
|
220
81
|
"@talismn/token-rates": "workspace:*",
|
82
|
+
"@talismn/util": "workspace:*",
|
221
83
|
anylogger: "^1.0.11",
|
222
84
|
"blueimp-md5": "2.19.0",
|
223
|
-
dexie: "^
|
224
|
-
"dexie-react-hooks": "^1.1.
|
225
|
-
|
85
|
+
dexie: "^4.0.9",
|
86
|
+
"dexie-react-hooks": "^1.1.7",
|
87
|
+
jotai: "~2",
|
88
|
+
"jotai-effect": "~1",
|
89
|
+
lodash: "4.17.21",
|
90
|
+
"react-use": "^17.5.1",
|
226
91
|
rxjs: "^7.8.1"
|
227
92
|
},
|
228
93
|
devDependencies: {
|
229
94
|
"@talismn/eslint-config": "workspace:*",
|
230
95
|
"@talismn/tsconfig": "workspace:*",
|
231
|
-
"@types/jest": "^
|
232
|
-
"@types/
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
96
|
+
"@types/jest": "^29.5.14",
|
97
|
+
"@types/lodash": "^4.17.12",
|
98
|
+
"@types/react": "^18.3.12",
|
99
|
+
eslint: "^8.57.1",
|
100
|
+
jest: "^29.7.0",
|
101
|
+
react: "^18.3.1",
|
102
|
+
"ts-jest": "^29.2.5",
|
103
|
+
typescript: "^5.6.3"
|
238
104
|
},
|
239
105
|
peerDependencies: {
|
106
|
+
"@polkadot/util-crypto": "*",
|
240
107
|
react: "*",
|
241
108
|
"react-dom": "*"
|
242
109
|
},
|
@@ -248,416 +115,484 @@ var packageJson = {
|
|
248
115
|
}
|
249
116
|
};
|
250
117
|
|
251
|
-
var log = anylogger__default
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
react.useEffect(() => {
|
267
|
-
// subscribe to subject.
|
268
|
-
// it won't change but we need to count subscribers, to unsubscribe main subscription when no more observers
|
269
|
-
const s = subscriptions[key].subject.subscribe();
|
270
|
-
return () => {
|
271
|
-
// unsubscribe from our local observable updates to prevent memory leaks
|
272
|
-
s.unsubscribe();
|
273
|
-
const {
|
274
|
-
subject,
|
275
|
-
unsubscribe
|
276
|
-
} = subscriptions[key];
|
277
|
-
if (!subject.observed && unsubscribe) {
|
278
|
-
log.debug(`[useSharedSubscription] unsubscribing ${key}`);
|
279
|
-
|
280
|
-
// unsubscribe from backend updates to prevent unnecessary network connections
|
281
|
-
unsubscribe();
|
282
|
-
delete subscriptions[key].unsubscribe;
|
283
|
-
}
|
284
|
-
};
|
285
|
-
}, [key]);
|
286
|
-
|
287
|
-
// Initialize subscription
|
288
|
-
react.useEffect(() => {
|
118
|
+
var log = anylogger__default.default(packageJson.name);
|
119
|
+
|
120
|
+
/**
|
121
|
+
// Persistence backend for indexedDB
|
122
|
+
// Add a new backend by implementing the BalancesPersistBackend interface
|
123
|
+
// configureStore can be called with a different indexedDB table
|
124
|
+
*/
|
125
|
+
balances.configureStore();
|
126
|
+
|
127
|
+
/**
|
128
|
+
// Persistence backend for localStorage
|
129
|
+
*/
|
130
|
+
const localStoragePersist = async balances$1 => {
|
131
|
+
const storedBalances = balances$1.map(b => {
|
132
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
289
133
|
const {
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
const cb = subscribe();
|
295
|
-
log.debug(`[useSharedSubscription] subscribing ${key}`);
|
296
|
-
if (cb) subscriptions[key].unsubscribe = cb;
|
297
|
-
// this error should only happen when developping a new hook, let it bubble up
|
298
|
-
else throw new Error(`${key} subscribe did not return an unsubscribe callback`);
|
299
|
-
}
|
300
|
-
}, [key, subscribe]);
|
301
|
-
};
|
302
|
-
|
303
|
-
/**
|
304
|
-
* Creates a subscription function that can be used to subscribe to a multicast observable created from an upstream source.
|
305
|
-
*
|
306
|
-
* An example of when this is useful is when we want to subscribe to some data from multiple components, but we only want
|
307
|
-
* to actively keep that data hydrated when at least one component is subscribed to it.
|
308
|
-
*
|
309
|
-
* When the first component subscribes, the `upstream` function will be called. It should then set up a subscription and return a teardown function.
|
310
|
-
* When subsequent components subscribe, they will be added to the existing subscription.
|
311
|
-
* When the last component unsubscribes, the teardown function returned from the `upstream` function will be called.
|
312
|
-
*
|
313
|
-
* @param upstream A function that takes a "next" callback function as an argument, and returns either an unsubscribe function or void.
|
314
|
-
* @returns A subscription function that can be used to subscribe to the multicast observable.
|
315
|
-
*/
|
316
|
-
const useMulticastSubscription = upstream => {
|
317
|
-
const subscribe = react.useMemo(() => createMulticastSubscription(upstream), [upstream]);
|
318
|
-
return subscribe;
|
319
|
-
};
|
320
|
-
const createMulticastSubscription = upstream => {
|
321
|
-
// Create an upstream observable using the provided function.
|
322
|
-
const upstreamObservable = new rxjs.Observable(subscriber => {
|
323
|
-
const unsubscribe = upstream(val => subscriber.next(val));
|
324
|
-
return () => {
|
325
|
-
typeof unsubscribe === "function" && unsubscribe();
|
326
|
-
};
|
134
|
+
status,
|
135
|
+
...rest
|
136
|
+
} = b;
|
137
|
+
return rest;
|
327
138
|
});
|
328
|
-
|
329
|
-
|
330
|
-
const multicastObservable = rxjs.defer(() => upstreamObservable).pipe(rxjs.shareReplay({
|
331
|
-
bufferSize: 1,
|
332
|
-
refCount: true
|
333
|
-
}));
|
334
|
-
|
335
|
-
// Create a subscription function that subscribes to the multicast observable and returns an unsubscribe function.
|
336
|
-
const subscribe = callback => {
|
337
|
-
const subscription = multicastObservable.subscribe(callback);
|
338
|
-
const unsubscribe = () => subscription.unsubscribe();
|
339
|
-
return unsubscribe;
|
340
|
-
};
|
341
|
-
return subscribe;
|
139
|
+
const deflated = balances.compress(storedBalances);
|
140
|
+
localStorage.setItem("talismanBalances", deflated.toString());
|
342
141
|
};
|
343
|
-
|
344
|
-
const
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
142
|
+
const localStorageRetrieve = async () => {
|
143
|
+
const deflated = localStorage.getItem("talismanBalances");
|
144
|
+
if (deflated) {
|
145
|
+
// deflated will be a long string of numbers separated by commas
|
146
|
+
const deflatedArray = deflated.split(",").map(n => parseInt(n, 10));
|
147
|
+
const deflatedBytes = new Uint8Array(deflatedArray.length);
|
148
|
+
deflatedArray.forEach((n, i) => deflatedBytes[i] = n);
|
149
|
+
return balances.decompress(deflatedBytes).map(b => ({
|
150
|
+
...b,
|
151
|
+
status: "cache"
|
152
|
+
}));
|
153
|
+
}
|
154
|
+
return [];
|
350
155
|
};
|
351
|
-
const
|
352
|
-
|
353
|
-
|
354
|
-
* This hook is responsible for fetching the data used for balances and inserting it into the db.
|
355
|
-
*/
|
356
|
-
const useDbCacheSubscription = subscribeTo => {
|
357
|
-
const provider = useChaindata();
|
358
|
-
|
359
|
-
// can't handle balances & tokenRates here as they have other dependencies, it would trigger to many subscriptions
|
360
|
-
const subscribe = react.useCallback(() => {
|
361
|
-
switch (subscribeTo) {
|
362
|
-
case "chains":
|
363
|
-
return subscribeChainDataHydrate(provider, "chains");
|
364
|
-
case "evmNetworks":
|
365
|
-
return subscribeChainDataHydrate(provider, "evmNetworks");
|
366
|
-
case "tokens":
|
367
|
-
return subscribeChainDataHydrate(provider, "tokens");
|
368
|
-
}
|
369
|
-
}, [provider, subscribeTo]);
|
370
|
-
useSharedSubscription(subscribeTo, subscribe);
|
156
|
+
const localStorageBalancesPersistBackend = {
|
157
|
+
persist: localStoragePersist,
|
158
|
+
retrieve: localStorageRetrieve
|
371
159
|
};
|
372
160
|
|
373
|
-
|
374
|
-
* This hook is responsible for fetching the data used for token rates and inserting it into the db.
|
375
|
-
*/
|
376
|
-
function useDbCacheTokenRatesSubscription() {
|
377
|
-
const {
|
378
|
-
withTestnets
|
379
|
-
} = useWithTestnets();
|
380
|
-
const tokens = useTokens$1(withTestnets);
|
381
|
-
const subscriptionKey = react.useMemo(
|
382
|
-
// not super sexy but we need key to change based on this stuff
|
383
|
-
() => {
|
384
|
-
const key = Object.values(tokens ?? {}).map(({
|
385
|
-
id
|
386
|
-
}) => id).sort().join();
|
387
|
-
return `tokenRates-${md5__default["default"](key)}`;
|
388
|
-
}, [tokens]);
|
389
|
-
const subscription = react.useCallback(() => {
|
390
|
-
if (!Object.values(tokens ?? {}).length) return () => {};
|
391
|
-
return subscribeTokenRates(tokens);
|
392
|
-
}, [tokens]);
|
393
|
-
useSharedSubscription(subscriptionKey, subscription);
|
394
|
-
}
|
161
|
+
const cryptoWaitReadyAtom = jotai.atom(async () => await utilCrypto.cryptoWaitReady());
|
395
162
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
const chaindataProvider = useChaindata();
|
405
|
-
const chainConnectors = useChainConnectors();
|
406
|
-
const [allAddresses] = useAllAddresses();
|
407
|
-
const tokens = useTokens$1(withTestnets);
|
408
|
-
const subscriptionKey = react.useMemo(
|
409
|
-
// not super sexy but we need key to change based on this stuff
|
410
|
-
() => {
|
411
|
-
const key = allAddresses.sort().join().concat(...Object.values(tokens ?? {}).map(({
|
412
|
-
id
|
413
|
-
}) => id).sort()).concat(`evm:${!!chainConnectors.evm}`, `sub:${!!chainConnectors.substrate}`, ...balanceModules.map(m => m.type).sort(), `cd:${!!chaindataProvider}`);
|
414
|
-
return `balances-${md5__default["default"](key)}`;
|
415
|
-
}, [allAddresses, balanceModules, chainConnectors, chaindataProvider, tokens]);
|
416
|
-
const subscription = react.useCallback(() => {
|
417
|
-
if (!Object.values(tokens ?? {}).length || !allAddresses.length) return () => {};
|
418
|
-
return subscribeBalances(tokens ?? {}, allAddresses, balanceModules);
|
419
|
-
}, [allAddresses, balanceModules, tokens]);
|
420
|
-
useSharedSubscription(subscriptionKey, subscription);
|
421
|
-
}
|
163
|
+
const chaindataProviderAtom = jotai.atom(get => {
|
164
|
+
// runs a timer to keep chaindata hydrated
|
165
|
+
get(chaindataHydrateAtomEffect);
|
166
|
+
return new chaindataProvider.ChaindataProvider({
|
167
|
+
onfinalityApiKey: get(onfinalityApiKeyAtom)
|
168
|
+
});
|
169
|
+
});
|
170
|
+
const miniMetadataHydratedAtom = jotai.atom(false);
|
422
171
|
|
423
|
-
|
424
|
-
const
|
425
|
-
const
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
};
|
431
|
-
const subscribeChainDataHydrate = (provider, type) => {
|
432
|
-
const chaindata = provider;
|
433
|
-
const delay = 300_000; // 300_000ms = 300s = 5 minutes
|
172
|
+
/** This atomEffect keeps chaindata hydrated (i.e. up to date with the GitHub repo) */
|
173
|
+
const chaindataHydrateAtomEffect = jotaiEffect.atomEffect((get, set) => {
|
174
|
+
const chaindataProvider = get(chaindataProviderAtom);
|
175
|
+
const miniMetadataUpdater = get(miniMetadataUpdaterAtom);
|
176
|
+
const evmTokenFetcher = get(evmTokenFetcherAtom);
|
177
|
+
const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
|
178
|
+
const retryTimeout = 5_000; // 5_000ms = 5 seconds
|
434
179
|
|
435
180
|
let timeout = null;
|
436
181
|
const hydrate = async () => {
|
437
182
|
try {
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
183
|
+
await get(cryptoWaitReadyAtom);
|
184
|
+
await balances.hydrateChaindataAndMiniMetadata(chaindataProvider, miniMetadataUpdater);
|
185
|
+
await balances.updateCustomMiniMetadata(chaindataProvider, miniMetadataUpdater);
|
186
|
+
await balances.updateEvmTokens(chaindataProvider, evmTokenFetcher);
|
187
|
+
set(miniMetadataHydratedAtom, true);
|
188
|
+
timeout = setTimeout(hydrate, loopMs);
|
442
189
|
} catch (error) {
|
443
|
-
|
444
|
-
log.error(`Failed to fetch chaindata, retrying in ${Math.round(retryTimeout / 1000)} seconds`, error);
|
190
|
+
log.error(`Failed to hydrate chaindata, retrying in ${Math.round(retryTimeout / 1000)} seconds`, error);
|
445
191
|
timeout = setTimeout(hydrate, retryTimeout);
|
446
192
|
}
|
447
193
|
};
|
448
194
|
|
449
195
|
// launch the loop
|
450
196
|
hydrate();
|
451
|
-
return () => {
|
452
|
-
if (timeout) clearTimeout(timeout);
|
453
|
-
};
|
454
|
-
};
|
455
|
-
const subscribeTokenRates = tokens => {
|
456
|
-
const REFRESH_INTERVAL = 300_000; // 6 minutes
|
457
|
-
const RETRY_INTERVAL = 5_000; // 5 sec
|
458
197
|
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
if (timeout) clearTimeout(timeout);
|
463
|
-
const tokenRates$1 = await tokenRates.fetchTokenRates(tokens);
|
464
|
-
const putTokenRates = Object.entries(tokenRates$1).map(([tokenId, rates]) => ({
|
465
|
-
tokenId,
|
466
|
-
rates
|
467
|
-
}));
|
468
|
-
tokenRates.db.transaction("rw", tokenRates.db.tokenRates, async () => await tokenRates.db.tokenRates.bulkPut(putTokenRates));
|
469
|
-
timeout = setTimeout(() => {
|
470
|
-
refreshTokenRates();
|
471
|
-
}, REFRESH_INTERVAL);
|
472
|
-
} catch (error) {
|
473
|
-
log.error(`Failed to fetch tokenRates, retrying in ${Math.round(RETRY_INTERVAL / 1000)} seconds`, error);
|
474
|
-
setTimeout(async () => {
|
475
|
-
refreshTokenRates();
|
476
|
-
}, RETRY_INTERVAL);
|
477
|
-
}
|
478
|
-
};
|
198
|
+
// return an unsub function to shut down the loop
|
199
|
+
return () => timeout && clearTimeout(timeout);
|
200
|
+
});
|
479
201
|
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
const
|
491
|
-
const
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
202
|
+
/** MiniMetadataUpdater is a class used for hydrating chaindata */
|
203
|
+
const miniMetadataUpdaterAtom = jotai.atom(get => {
|
204
|
+
const chainConnectors = get(chainConnectorsAtom);
|
205
|
+
const chaindataProvider = get(chaindataProviderAtom);
|
206
|
+
const balanceModules = get(balanceModulesAtom);
|
207
|
+
return new balances.MiniMetadataUpdater(chainConnectors, chaindataProvider, balanceModules);
|
208
|
+
});
|
209
|
+
|
210
|
+
/** EvmTokenFetcher is a class used for hydrating chaindata */
|
211
|
+
const evmTokenFetcherAtom = jotai.atom(get => {
|
212
|
+
const chaindataProvider = get(chaindataProviderAtom);
|
213
|
+
const balanceModules = get(balanceModulesAtom);
|
214
|
+
return new balances.EvmTokenFetcher(chaindataProvider, balanceModules);
|
215
|
+
});
|
216
|
+
|
217
|
+
const chainConnectorsAtom = jotai.atom(get => {
|
218
|
+
const onfinalityApiKey = get(onfinalityApiKeyAtom);
|
219
|
+
const chaindataProvider = get(chaindataProviderAtom);
|
220
|
+
const substrate = new chainConnector.ChainConnector(chaindataProvider, connectionMeta.connectionMetaDb);
|
221
|
+
const evm = new chainConnectorEvm.ChainConnectorEvm(chaindataProvider, {
|
222
|
+
onfinalityApiKey
|
223
|
+
});
|
224
|
+
return {
|
225
|
+
substrate,
|
226
|
+
evm
|
504
227
|
};
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
228
|
+
});
|
229
|
+
|
230
|
+
const balanceModulesAtom = jotai.atom(get => {
|
231
|
+
const balanceModuleCreators = get(balanceModuleCreatorsAtom);
|
232
|
+
const chainConnectors = get(chainConnectorsAtom);
|
233
|
+
const chaindataProvider = get(chaindataProviderAtom);
|
234
|
+
if (!chainConnectors.substrate) return [];
|
235
|
+
if (!chainConnectors.evm) return [];
|
236
|
+
if (!chaindataProvider) return [];
|
237
|
+
return balanceModuleCreators.map(mod => mod({
|
238
|
+
chainConnectors,
|
239
|
+
chaindataProvider
|
240
|
+
}));
|
241
|
+
});
|
242
|
+
|
243
|
+
/**
|
244
|
+
* Converts a dexie Observable into an rxjs Observable.
|
245
|
+
*/
|
246
|
+
function dexieToRxjs(o) {
|
247
|
+
return new rxjs.Observable(observer => {
|
248
|
+
const subscription = o.subscribe({
|
249
|
+
next: value => observer.next(value),
|
250
|
+
error: error => observer.error(error)
|
251
|
+
});
|
252
|
+
return () => subscription.unsubscribe();
|
253
|
+
});
|
254
|
+
}
|
255
|
+
|
256
|
+
const chainsAtom = jotai.atom(async get => (await get(chaindataAtom)).chains);
|
257
|
+
const chainsByIdAtom = jotai.atom(async get => (await get(chaindataAtom)).chainsById);
|
258
|
+
const chainsByGenesisHashAtom = jotai.atom(async get => (await get(chaindataAtom)).chainsByGenesisHash);
|
259
|
+
const evmNetworksAtom = jotai.atom(async get => (await get(chaindataAtom)).evmNetworks);
|
260
|
+
const evmNetworksByIdAtom = jotai.atom(async get => (await get(chaindataAtom)).evmNetworksById);
|
261
|
+
const tokensAtom = jotai.atom(async get => (await get(chaindataAtom)).tokens);
|
262
|
+
const tokensByIdAtom = jotai.atom(async get => (await get(chaindataAtom)).tokensById);
|
263
|
+
const miniMetadatasAtom = jotai.atom(async get => (await get(chaindataAtom)).miniMetadatas);
|
264
|
+
const chaindataAtom = utils.atomWithObservable(get => {
|
265
|
+
const enableTestnets = get(enableTestnetsAtom);
|
266
|
+
const filterTestnets = items => enableTestnets ? items : items.filter(({
|
267
|
+
isTestnet
|
268
|
+
}) => !isTestnet);
|
269
|
+
const filterMapTestnets = items => enableTestnets ? items : Object.fromEntries(Object.entries(items).filter(([, {
|
270
|
+
isTestnet
|
271
|
+
}]) => !isTestnet));
|
272
|
+
const filterEnabledTokens = tokens => tokens.filter(token => token.isDefault || "isCustom" in token && token.isCustom);
|
273
|
+
const filterMapEnabledTokens = tokensById => Object.fromEntries(Object.entries(tokensById).filter(([, token]) => token.isDefault || "isCustom" in token && token.isCustom));
|
274
|
+
const distinctUntilIsEqual = rxjs.distinctUntilChanged((a, b) => isEqual__default.default(a, b));
|
275
|
+
const chains = get(chaindataProviderAtom).chainsObservable.pipe(distinctUntilIsEqual, rxjs.map(filterTestnets), distinctUntilIsEqual);
|
276
|
+
const chainsById = get(chaindataProviderAtom).chainsByIdObservable.pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), distinctUntilIsEqual);
|
277
|
+
const chainsByGenesisHash = get(chaindataProviderAtom).chainsByGenesisHashObservable.pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), distinctUntilIsEqual);
|
278
|
+
const evmNetworks = get(chaindataProviderAtom).evmNetworksObservable.pipe(distinctUntilIsEqual, rxjs.map(filterTestnets), distinctUntilIsEqual);
|
279
|
+
const evmNetworksById = get(chaindataProviderAtom).evmNetworksByIdObservable.pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), distinctUntilIsEqual);
|
280
|
+
const tokens = get(chaindataProviderAtom).tokensObservable.pipe(distinctUntilIsEqual, rxjs.map(filterTestnets), rxjs.map(filterEnabledTokens), distinctUntilIsEqual);
|
281
|
+
const tokensById = get(chaindataProviderAtom).tokensByIdObservable.pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), rxjs.map(filterMapEnabledTokens), distinctUntilIsEqual);
|
282
|
+
const miniMetadatasObservable = dexieToRxjs(dexie.liveQuery(() => balances.db.miniMetadatas.toArray()));
|
283
|
+
const miniMetadatas = rxjs.combineLatest([miniMetadatasObservable.pipe(distinctUntilIsEqual), chainsById]).pipe(rxjs.map(([miniMetadatas, chainsById]) => miniMetadatas.filter(m => chainsById[m.chainId])), distinctUntilIsEqual);
|
284
|
+
return rxjs.combineLatest({
|
285
|
+
chains,
|
286
|
+
chainsById,
|
287
|
+
chainsByGenesisHash,
|
288
|
+
evmNetworks,
|
289
|
+
evmNetworksById,
|
290
|
+
tokens,
|
291
|
+
tokensById,
|
292
|
+
miniMetadatas
|
293
|
+
}).pipe(
|
294
|
+
// debounce to prevent hammering UI with updates
|
295
|
+
util.firstThenDebounce(1_000), distinctUntilIsEqual);
|
296
|
+
});
|
297
|
+
|
298
|
+
const tokenRatesAtom = jotai.atom(async get => {
|
299
|
+
// runs a timer to keep tokenRates up to date
|
300
|
+
get(tokenRatesFetcherAtomEffect);
|
301
|
+
return await get(tokenRatesDbAtom);
|
302
|
+
});
|
303
|
+
const tokenRatesDbAtom = utils.atomWithObservable(() => {
|
304
|
+
const dbRatesToMap = dbRates => Object.fromEntries(dbRates.map(({
|
305
|
+
tokenId,
|
306
|
+
rates
|
307
|
+
}) => [tokenId, rates]));
|
308
|
+
|
309
|
+
// retrieve fetched tokenRates from the db
|
310
|
+
return dexieToRxjs(dexie.liveQuery(() => tokenRates.db.tokenRates.toArray())).pipe(rxjs.map(dbRatesToMap));
|
311
|
+
});
|
312
|
+
const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
|
313
|
+
// lets us tear down the existing timer when the effect is restarted
|
314
|
+
const abort = new AbortController();
|
315
|
+
|
316
|
+
// we have to get these synchronously so that jotai knows to restart our timer when they change
|
317
|
+
const coingeckoConfig = get(coingeckoConfigAtom);
|
318
|
+
const tokensByIdPromise = get(tokensByIdAtom);
|
319
|
+
(async () => {
|
320
|
+
const tokensById = await tokensByIdPromise;
|
321
|
+
const tokenIds = Object.keys(tokensById);
|
322
|
+
const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
|
323
|
+
const retryTimeout = 5_000; // 5_000ms = 5 seconds
|
324
|
+
|
325
|
+
const hydrate = async () => {
|
326
|
+
try {
|
327
|
+
if (abort.signal.aborted) return; // don't fetch if aborted
|
328
|
+
const tokenRates$1 = await tokenRates.fetchTokenRates(tokensById, coingeckoConfig);
|
329
|
+
const putTokenRates = Object.entries(tokenRates$1).map(([tokenId, rates]) => ({
|
330
|
+
tokenId,
|
331
|
+
rates
|
332
|
+
}));
|
333
|
+
if (abort.signal.aborted) return; // don't insert into db if aborted
|
334
|
+
await tokenRates.db.transaction("rw", tokenRates.db.tokenRates, async () => {
|
335
|
+
// override all tokenRates
|
336
|
+
await tokenRates.db.tokenRates.bulkPut(putTokenRates);
|
337
|
+
|
338
|
+
// delete tokenRates for tokens which no longer exist
|
339
|
+
const validTokenIds = new Set(tokenIds);
|
340
|
+
const tokenRatesIds = await tokenRates.db.tokenRates.toCollection().primaryKeys();
|
341
|
+
const deleteIds = tokenRatesIds.filter(id => !validTokenIds.has(id));
|
342
|
+
if (deleteIds.length > 0) await tokenRates.db.tokenRates.bulkDelete(deleteIds);
|
343
|
+
});
|
344
|
+
if (abort.signal.aborted) return; // don't schedule next loop if aborted
|
345
|
+
setTimeout(hydrate, loopMs);
|
346
|
+
} catch (error) {
|
347
|
+
const retrying = !abort.signal.aborted;
|
348
|
+
const messageParts = ["Failed to fetch tokenRates", retrying && `retrying in ${Math.round(retryTimeout / 1000)} seconds`, !retrying && `giving up (timer no longer needed)`].filter(Boolean);
|
349
|
+
log.error(messageParts.join(", "), error);
|
350
|
+
if (abort.signal.aborted) return; // don't schedule retry if aborted
|
351
|
+
setTimeout(hydrate, retryTimeout);
|
352
|
+
}
|
353
|
+
};
|
354
|
+
|
355
|
+
// launch the loop
|
356
|
+
hydrate();
|
357
|
+
})();
|
358
|
+
return () => abort.abort("Unsubscribed");
|
359
|
+
});
|
360
|
+
|
361
|
+
const allBalancesAtom = jotai.atom(async get => {
|
362
|
+
// set up our subscription to fetch balances from the various blockchains
|
363
|
+
get(balancesSubscriptionAtomEffect);
|
364
|
+
const [balances$1, hydrateData] = await Promise.all([get(balancesObservableAtom), get(balancesHydrateDataAtom)]);
|
365
|
+
return new balances.Balances(Object.values(balances$1).filter(balance => !!hydrateData?.tokens?.[balance.tokenId]),
|
366
|
+
// hydrate balance chains, evmNetworks, tokens and tokenRates
|
367
|
+
hydrateData);
|
368
|
+
});
|
369
|
+
const balancesObservable = new rxjs.BehaviorSubject({});
|
370
|
+
const balancesObservableAtom = utils.atomWithObservable(() => balancesObservable);
|
371
|
+
const balancesPersistBackendAtom = jotai.atom(localStorageBalancesPersistBackend);
|
372
|
+
const hydrateBalancesObservableAtom = jotai.atom(async get => {
|
373
|
+
const persistBackend = get(balancesPersistBackendAtom);
|
374
|
+
const balances$1 = await persistBackend.retrieve();
|
375
|
+
balancesObservable.next(Object.fromEntries(balances$1.map(b => [balances.getBalanceId(b), {
|
376
|
+
...b,
|
377
|
+
status: "cache"
|
378
|
+
}])));
|
379
|
+
});
|
380
|
+
const balancesHydrateDataAtom = jotai.atom(async get => {
|
381
|
+
const [{
|
382
|
+
chainsById,
|
383
|
+
evmNetworksById,
|
384
|
+
tokensById
|
385
|
+
}, tokenRates] = await Promise.all([get(chaindataAtom), get(tokenRatesAtom)]);
|
386
|
+
return {
|
387
|
+
chains: chainsById,
|
388
|
+
evmNetworks: evmNetworksById,
|
389
|
+
tokens: tokensById,
|
390
|
+
tokenRates
|
512
391
|
};
|
513
|
-
|
514
|
-
|
515
|
-
//
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
392
|
+
});
|
393
|
+
const balancesSubscriptionAtomEffect = jotaiEffect.atomEffect(get => {
|
394
|
+
// lets us tear down the existing subscriptions when the atomEffect is restarted
|
395
|
+
const abort = new AbortController();
|
396
|
+
|
397
|
+
// we have to specify these synchronously, otherwise jotai won't know
|
398
|
+
// that it needs to restart our subscriptions when they change
|
399
|
+
const atomDependencies = Promise.all([get(cryptoWaitReadyAtom), get(balanceModulesAtom), get(miniMetadataHydratedAtom), get(allAddressesAtom), get(chainsAtom), get(chainsByIdAtom), get(evmNetworksAtom), get(evmNetworksByIdAtom), get(tokensAtom), get(tokensByIdAtom), get(miniMetadatasAtom), get(enabledChainsAtom), get(enabledTokensAtom), get(hydrateBalancesObservableAtom)]);
|
400
|
+
const persistBackend = get(balancesPersistBackendAtom);
|
401
|
+
const unsubsPromise = (async () => {
|
402
|
+
const [_cryptoReady, balanceModules, miniMetadataHydrated, allAddresses, chains, chainsById, evmNetworks, evmNetworksById, tokens, tokensById, _miniMetadatas, enabledChainsConfig, enabledTokensConfig] = await atomDependencies;
|
403
|
+
if (!miniMetadataHydrated) return;
|
404
|
+
if (abort.signal.aborted) return;
|
405
|
+
|
406
|
+
// persist data every thirty seconds
|
407
|
+
balancesObservable.pipe(rxjs.debounceTime(10000)).subscribe(balancesUpdate => {
|
408
|
+
persistBackend.persist(Object.values(balancesUpdate));
|
409
|
+
});
|
410
|
+
const updateBalances = async balancesUpdates => {
|
411
|
+
if (abort.signal.aborted) return;
|
412
|
+
const updatesWithIds = new balances.Balances(balancesUpdates);
|
413
|
+
const existing = balancesObservable.value;
|
414
|
+
|
415
|
+
// update initialising set here - before filtering out zero balances
|
416
|
+
// while this may include stale balances, the important thing is that the balance is no longer "initialising"
|
417
|
+
// balancesUpdates.forEach((b) => this.#initialising.delete(getBalanceId(b)))
|
418
|
+
|
419
|
+
const newlyZeroBalances = [];
|
420
|
+
const changedBalances = Object.fromEntries(updatesWithIds.each.filter(newB => {
|
421
|
+
const isZero = newB.total.tokens === "0";
|
422
|
+
// Keep new balances which are not zeros
|
423
|
+
const existingB = existing[newB.id];
|
424
|
+
if (!existingB && !isZero) return true;
|
425
|
+
const hasChanged = !lodash.isEqual(existingB, newB.toJSON());
|
426
|
+
// Collect balances now confirmed to be zero separately, so they can be filtered out from the main set
|
427
|
+
if (existingB && hasChanged && isZero) newlyZeroBalances.push(newB.id);
|
428
|
+
// Keep changed balances, which are not known zeros
|
429
|
+
return hasChanged && !isZero;
|
430
|
+
}).map(b => [b.id, b.toJSON()]));
|
431
|
+
if (Object.keys(changedBalances).length === 0 && newlyZeroBalances.length === 0) return;
|
432
|
+
const nonZeroBalances = newlyZeroBalances.length > 0 ? Object.fromEntries(Object.entries(existing).filter(([id]) => !newlyZeroBalances.includes(id))) : existing;
|
433
|
+
const newBalancesState = {
|
434
|
+
...nonZeroBalances,
|
435
|
+
...changedBalances
|
436
|
+
};
|
437
|
+
if (Object.keys(newBalancesState).length === 0) return;
|
438
|
+
balancesObservable.next(newBalancesState);
|
439
|
+
};
|
440
|
+
const deleteBalances = async balancesFilter => {
|
441
|
+
if (abort.signal.aborted) return;
|
442
|
+
const balancesToKeep = Object.fromEntries(new balances.Balances(Object.values(await get(balancesObservableAtom))).each.filter(b => !balancesFilter(b)).map(b => [b.id, b.toJSON()]));
|
443
|
+
balancesObservable.next(balancesToKeep);
|
444
|
+
};
|
445
|
+
const enabledChainIds = enabledChainsConfig?.map(genesisHash => chains.find(chain => chain.genesisHash === genesisHash)?.id);
|
446
|
+
const enabledChainsFilter = enabledChainIds ? token => token.chain && enabledChainIds?.includes(token.chain.id) : () => true;
|
447
|
+
const enabledTokensFilter = enabledTokensConfig ? token => enabledTokensConfig.includes(token.id) : () => true;
|
448
|
+
const enabledTokenIds = tokens.filter(enabledChainsFilter).filter(enabledTokensFilter).map(({
|
522
449
|
id
|
523
450
|
}) => id);
|
524
|
-
|
525
|
-
const
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
451
|
+
if (enabledTokenIds.length < 1 || allAddresses.length < 1) return;
|
452
|
+
const addressesByTokenByModule = {};
|
453
|
+
enabledTokenIds.flatMap(tokenId => tokensById[tokenId]).forEach(token => {
|
454
|
+
// filter out tokens on chains/evmNetworks which have no rpcs
|
455
|
+
const hasRpcs = token.chain?.id && (chainsById[token.chain.id]?.rpcs?.length ?? 0) > 0 || token.evmNetwork?.id && (evmNetworksById[token.evmNetwork.id]?.rpcs?.length ?? 0) > 0;
|
456
|
+
if (!hasRpcs) return;
|
457
|
+
if (!addressesByTokenByModule[token.type]) addressesByTokenByModule[token.type] = {};
|
458
|
+
addressesByTokenByModule[token.type][token.id] = allAddresses.filter(address => {
|
459
|
+
// for each address, fetch balances only from compatible chains
|
460
|
+
return util.isEthereumAddress(address) ? token.evmNetwork?.id || chainsById[token.chain?.id ?? ""]?.account === "secp256k1" : token.chain?.id && chainsById[token.chain?.id ?? ""]?.account !== "secp256k1";
|
461
|
+
});
|
462
|
+
});
|
463
|
+
|
464
|
+
// Delete invalid cached balances
|
465
|
+
const chainIds = new Set(chains.map(chain => chain.id));
|
466
|
+
const evmNetworkIds = new Set(evmNetworks.map(evmNetwork => evmNetwork.id));
|
467
|
+
await deleteBalances(balance => {
|
468
|
+
// delete cached balances for accounts which don't exist anymore
|
469
|
+
if (!balance.address || !allAddresses.includes(balance.address)) return true;
|
470
|
+
|
471
|
+
// delete cached balances when chain/evmNetwork doesn't exist
|
472
|
+
if (balance.chainId === undefined && balance.evmNetworkId === undefined) return true;
|
473
|
+
if (balance.chainId !== undefined && !chainIds.has(balance.chainId)) return true;
|
474
|
+
if (balance.evmNetworkId !== undefined && !evmNetworkIds.has(balance.evmNetworkId)) return true;
|
475
|
+
|
476
|
+
// delete cached balance when token doesn't exist / is disabled
|
477
|
+
if (!enabledTokenIds.includes(balance.tokenId)) return true;
|
478
|
+
|
479
|
+
// delete cached balance when module doesn't exist
|
480
|
+
if (!balanceModules.find(module => module.type === balance.source)) return true;
|
481
|
+
|
482
|
+
// delete cached balance for accounts on incompatible chains
|
483
|
+
// (substrate accounts shouldn't have evm balances)
|
484
|
+
// (evm accounts shouldn't have substrate balances (unless the chain uses secp256k1 accounts))
|
485
|
+
const chain = balance.chainId && chains.find(({
|
486
|
+
id
|
487
|
+
}) => id === balance.chainId) || null;
|
488
|
+
const hasChain = balance.chainId && chainIds.has(balance.chainId);
|
489
|
+
const hasEvmNetwork = balance.evmNetworkId && evmNetworkIds.has(balance.evmNetworkId);
|
490
|
+
const chainUsesSecp256k1Accounts = chain?.account === "secp256k1";
|
491
|
+
if (!util.isEthereumAddress(balance.address)) {
|
492
|
+
if (!hasChain) return true;
|
493
|
+
if (chainUsesSecp256k1Accounts) return true;
|
494
|
+
}
|
495
|
+
if (util.isEthereumAddress(balance.address)) {
|
496
|
+
if (!hasEvmNetwork && !chainUsesSecp256k1Accounts) return true;
|
539
497
|
}
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
if (unsubscribed) return;
|
544
|
-
updateDb(balances$1);
|
498
|
+
|
499
|
+
// keep balance
|
500
|
+
return false;
|
545
501
|
});
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
502
|
+
if (abort.signal.aborted) return;
|
503
|
+
|
504
|
+
// after 30 seconds, change the status of all balances still initializing to stale
|
505
|
+
setTimeout(() => {
|
506
|
+
if (abort.signal.aborted) return;
|
507
|
+
const staleObservable = balancesObservable.pipe(rxjs.map(val => Object.values(val).filter(({
|
508
|
+
status
|
509
|
+
}) => status === "cache").map(balance => ({
|
510
|
+
...balance,
|
511
|
+
status: "stale"
|
512
|
+
}))));
|
513
|
+
rxjs.firstValueFrom(staleObservable).then(v => {
|
514
|
+
if (v.length) updateBalances(v);
|
550
515
|
});
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
}
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
}
|
516
|
+
}, 30_000);
|
517
|
+
return balanceModules.map(balanceModule => {
|
518
|
+
const unsub = balances.balances(balanceModule, addressesByTokenByModule[balanceModule.type] ?? {}, (error, balances) => {
|
519
|
+
// log errors
|
520
|
+
if (error) {
|
521
|
+
if (error?.type === "STALE_RPC_ERROR" || error?.type === "WEBSOCKET_ALLOCATION_EXHAUSTED_ERROR") {
|
522
|
+
const addressesByModuleToken = addressesByTokenByModule[balanceModule.type] ?? {};
|
523
|
+
const staleObservable = balancesObservable.pipe(rxjs.map(val => Object.values(val).filter(balance => {
|
524
|
+
const {
|
525
|
+
tokenId,
|
526
|
+
address,
|
527
|
+
source
|
528
|
+
} = balance;
|
529
|
+
const chainComparison = error.chainId ? "chainId" in balance && error.chainId === balance.chainId : error.evmNetworkId ? "evmNetworkId" in balance && error.evmNetworkId === balance.evmNetworkId : true;
|
530
|
+
return chainComparison && addressesByModuleToken[tokenId]?.includes(address) && source === balanceModule.type;
|
531
|
+
}).map(balance => ({
|
532
|
+
...balance,
|
533
|
+
status: "stale"
|
534
|
+
}))));
|
535
|
+
rxjs.firstValueFrom(staleObservable).then(v => {
|
536
|
+
if (v.length) updateBalances(v);
|
537
|
+
});
|
538
|
+
}
|
539
|
+
return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
|
540
|
+
}
|
541
|
+
|
542
|
+
// ignore empty balance responses
|
543
|
+
if (!balances) return;
|
544
|
+
// ignore balances from old subscriptions which are still in the process of unsubscribing
|
545
|
+
if (abort.signal.aborted) return;
|
546
|
+
|
547
|
+
// good balances
|
548
|
+
if (balances) {
|
549
|
+
if ("status" in balances) {
|
550
|
+
// For modules using the new SubscriptionResultWithStatus pattern
|
551
|
+
//TODO fix initialisin
|
552
|
+
// if (result.status === "initialising") this.#initialising.add(balanceModule.type)
|
553
|
+
// else this.#initialising.delete(balanceModule.type)
|
554
|
+
updateBalances(balances.data);
|
555
|
+
} else {
|
556
|
+
// add good ones to initialisedBalances
|
557
|
+
updateBalances(Object.values(balances.toJSON()));
|
558
|
+
}
|
559
|
+
}
|
560
|
+
});
|
561
|
+
return () => unsub.then(unsubscribe => unsubscribe());
|
562
|
+
});
|
563
|
+
})();
|
574
564
|
|
575
|
-
|
576
|
-
//
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
return
|
583
|
-
}
|
584
|
-
function useEvmNetwork(evmNetworkId, withTestnets) {
|
585
|
-
const evmNetworks = useEvmNetworks(withTestnets);
|
586
|
-
return evmNetworkId ? evmNetworks[evmNetworkId] : undefined;
|
587
|
-
}
|
565
|
+
// close the existing subscriptions when our effect unmounts
|
566
|
+
// (wait 2 seconds before actually unsubscribing, to allow the websocket to be reused in that time)
|
567
|
+
const unsubscribe = () => unsubsPromise.then(unsubs => {
|
568
|
+
persistBackend.persist(Object.values(balancesObservable.value));
|
569
|
+
unsubs?.forEach(unsub => unsub());
|
570
|
+
});
|
571
|
+
abort.signal.addEventListener("abort", () => setTimeout(unsubscribe, 2_000));
|
572
|
+
return () => abort.abort("Unsubscribed");
|
573
|
+
});
|
588
574
|
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
return tokenRatesMap;
|
596
|
-
}
|
597
|
-
function useTokenRate(tokenId) {
|
598
|
-
const tokenRates = useTokenRates();
|
599
|
-
return tokenId ? tokenRates[tokenId] : undefined;
|
600
|
-
}
|
575
|
+
const useSetBalancesAddresses = addresses => {
|
576
|
+
const setAllAddresses = jotai.useSetAtom(allAddressesAtom);
|
577
|
+
react.useEffect(() => {
|
578
|
+
setAllAddresses(a => JSON.stringify(a) === JSON.stringify(addresses) ? a : addresses);
|
579
|
+
}, [addresses, setAllAddresses]);
|
580
|
+
};
|
601
581
|
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
} = useDbCache();
|
609
|
-
return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
|
610
|
-
}
|
611
|
-
function useToken(tokenId, withTestnets) {
|
612
|
-
const tokens = useTokens(withTestnets);
|
613
|
-
return tokenId ? tokens[tokenId] : undefined;
|
614
|
-
}
|
582
|
+
/**
|
583
|
+
* @name useBalances
|
584
|
+
* @description Hook to get the current balances state.
|
585
|
+
* @param persistBackend an optional BalancesPersistBackend backend to use for persisting the balances state. By default, indexedDB is used.
|
586
|
+
* @returns a Balances object containing the current balances state.
|
587
|
+
*/
|
615
588
|
|
616
|
-
const
|
617
|
-
const
|
618
|
-
|
619
|
-
|
620
|
-
const chains = useChains(withTestnets);
|
621
|
-
const evmNetworks = useEvmNetworks(withTestnets);
|
622
|
-
const tokens = useTokens(withTestnets);
|
623
|
-
const tokenRates = useTokenRates();
|
624
|
-
return react.useMemo(() => ({
|
625
|
-
chains,
|
626
|
-
evmNetworks,
|
627
|
-
tokens,
|
628
|
-
tokenRates
|
629
|
-
}), [chains, evmNetworks, tokens, tokenRates]);
|
589
|
+
const useBalances = persistBackend => {
|
590
|
+
const [, setPersistBackend] = jotai.useAtom(balancesPersistBackendAtom);
|
591
|
+
if (persistBackend) setPersistBackend(persistBackend);
|
592
|
+
return jotai.useAtomValue(allBalancesAtom);
|
630
593
|
};
|
631
594
|
|
632
|
-
|
633
|
-
// keep db data up to date
|
634
|
-
useDbCacheBalancesSubscription();
|
635
|
-
const balanceModules = useBalanceModules();
|
636
|
-
const {
|
637
|
-
balances: balances$1
|
638
|
-
} = useDbCache();
|
639
|
-
const hydrate = useBalancesHydrate();
|
640
|
-
return react.useMemo(() => new balances.Balances(balances.deriveStatuses([...balances.getValidSubscriptionIds()], balances$1.filter(balance => {
|
641
|
-
// check that this balance is included in our queried balance modules
|
642
|
-
if (!balanceModules.map(({
|
643
|
-
type
|
644
|
-
}) => type).includes(balance.source)) return false;
|
645
|
-
|
646
|
-
// check that our query includes some tokens and addresses
|
647
|
-
if (!addressesByToken) return false;
|
648
|
-
|
649
|
-
// check that this balance is included in our queried tokens
|
650
|
-
if (!Object.keys(addressesByToken).includes(balance.tokenId)) return false;
|
651
|
-
|
652
|
-
// check that this balance is included in our queried addresses for this token
|
653
|
-
if (!addressesByToken[balance.tokenId].includes(balance.address)) return false;
|
654
|
-
|
655
|
-
// keep this balance
|
656
|
-
return true;
|
657
|
-
})),
|
658
|
-
// hydrate balance chains, evmNetworks, tokens and tokenRates
|
659
|
-
hydrate), [balances$1, hydrate, balanceModules, addressesByToken]);
|
660
|
-
}
|
595
|
+
// TODO: Extract to shared definition between extension and @talismn/balances-react
|
661
596
|
|
662
597
|
/**
|
663
598
|
* Given a collection of `Balances`, this hook returns a `BalancesStatus` summary for the collection.
|
@@ -687,57 +622,133 @@ const useBalancesStatus = balances => react.useMemo(() => {
|
|
687
622
|
}, [balances]);
|
688
623
|
const getStaleChains = balances => [...new Set(balances.sorted.filter(b => b.status === "stale").map(b => b.chain?.name ?? b.chainId ?? "Unknown"))];
|
689
624
|
|
625
|
+
const useChainConnectors = () => jotai.useAtomValue(chainConnectorsAtom);
|
626
|
+
|
627
|
+
const useChaindataProvider = () => jotai.useAtomValue(chaindataProviderAtom);
|
628
|
+
const useChaindata = () => jotai.useAtomValue(chaindataAtom);
|
629
|
+
const useChains = () => jotai.useAtomValue(chainsByIdAtom);
|
630
|
+
const useChainsByGenesisHash = () => jotai.useAtomValue(chainsByGenesisHashAtom);
|
631
|
+
const useEvmNetworks = () => jotai.useAtomValue(evmNetworksByIdAtom);
|
632
|
+
const useTokens = () => jotai.useAtomValue(tokensByIdAtom);
|
633
|
+
const useMiniMetadatas = () => jotai.useAtomValue(miniMetadatasAtom);
|
634
|
+
const useChain = chainId => useChains()[chainId ?? ""] ?? undefined;
|
635
|
+
const useEvmNetwork = evmNetworkId => useEvmNetworks()[evmNetworkId ?? ""] ?? undefined;
|
636
|
+
const useToken = tokenId => useTokens()[tokenId ?? ""] ?? undefined;
|
637
|
+
|
638
|
+
const useTokenRates = () => jotai.useAtomValue(tokenRatesAtom);
|
639
|
+
const useTokenRate = tokenId => useTokenRates()[tokenId ?? ""] ?? undefined;
|
640
|
+
|
690
641
|
const BalancesProvider = ({
|
691
642
|
balanceModules,
|
692
643
|
onfinalityApiKey,
|
644
|
+
coingeckoApiUrl,
|
645
|
+
coingeckoApiKeyName,
|
646
|
+
coingeckoApiKeyValue,
|
693
647
|
withTestnets,
|
648
|
+
enabledChains,
|
649
|
+
enabledTokens,
|
694
650
|
children
|
695
|
-
}) =>
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
});
|
651
|
+
}) => {
|
652
|
+
const setBalanceModules = jotai.useSetAtom(balanceModuleCreatorsAtom);
|
653
|
+
react.useEffect(() => {
|
654
|
+
if (balanceModules !== undefined) setBalanceModules(balanceModules);
|
655
|
+
}, [balanceModules, setBalanceModules]);
|
656
|
+
const setOnfinalityApiKey = jotai.useSetAtom(onfinalityApiKeyAtom);
|
657
|
+
react.useEffect(() => {
|
658
|
+
setOnfinalityApiKey(onfinalityApiKey);
|
659
|
+
}, [onfinalityApiKey, setOnfinalityApiKey]);
|
660
|
+
const setCoingeckoConfig = jotai.useSetAtom(coingeckoConfigAtom);
|
661
|
+
react.useEffect(() => {
|
662
|
+
setCoingeckoConfig({
|
663
|
+
apiUrl: coingeckoApiUrl,
|
664
|
+
apiKeyName: coingeckoApiKeyName,
|
665
|
+
apiKeyValue: coingeckoApiKeyValue
|
666
|
+
});
|
667
|
+
}, [coingeckoApiKeyName, coingeckoApiKeyValue, coingeckoApiUrl, setCoingeckoConfig]);
|
668
|
+
const setEnableTestnets = jotai.useSetAtom(enableTestnetsAtom);
|
669
|
+
react.useEffect(() => {
|
670
|
+
setEnableTestnets(withTestnets ?? false);
|
671
|
+
}, [setEnableTestnets, withTestnets]);
|
672
|
+
const setEnabledChains = jotai.useSetAtom(enabledChainsAtom);
|
673
|
+
react.useEffect(() => {
|
674
|
+
setEnabledChains(enabledChains);
|
675
|
+
}, [enabledChains, setEnabledChains]);
|
676
|
+
const setEnabledTokens = jotai.useSetAtom(enabledTokensAtom);
|
677
|
+
react.useEffect(() => {
|
678
|
+
setEnabledTokens(enabledTokens);
|
679
|
+
}, [enabledTokens, setEnabledTokens]);
|
680
|
+
return /*#__PURE__*/jsxRuntime.jsx(jsxRuntime.Fragment, {
|
681
|
+
children: children
|
682
|
+
});
|
683
|
+
};
|
712
684
|
|
713
|
-
exports
|
714
|
-
|
685
|
+
Object.defineProperty(exports, "evmErc20TokenId", {
|
686
|
+
enumerable: true,
|
687
|
+
get: function () { return balances.evmErc20TokenId; }
|
688
|
+
});
|
689
|
+
Object.defineProperty(exports, "evmNativeTokenId", {
|
690
|
+
enumerable: true,
|
691
|
+
get: function () { return balances.evmNativeTokenId; }
|
692
|
+
});
|
693
|
+
Object.defineProperty(exports, "subAssetTokenId", {
|
694
|
+
enumerable: true,
|
695
|
+
get: function () { return balances.subAssetTokenId; }
|
696
|
+
});
|
697
|
+
Object.defineProperty(exports, "subEquilibriumTokenId", {
|
698
|
+
enumerable: true,
|
699
|
+
get: function () { return balances.subEquilibriumTokenId; }
|
700
|
+
});
|
701
|
+
Object.defineProperty(exports, "subNativeTokenId", {
|
702
|
+
enumerable: true,
|
703
|
+
get: function () { return balances.subNativeTokenId; }
|
704
|
+
});
|
705
|
+
Object.defineProperty(exports, "subPsp22TokenId", {
|
706
|
+
enumerable: true,
|
707
|
+
get: function () { return balances.subPsp22TokenId; }
|
708
|
+
});
|
709
|
+
Object.defineProperty(exports, "subTokensTokenId", {
|
710
|
+
enumerable: true,
|
711
|
+
get: function () { return balances.subTokensTokenId; }
|
712
|
+
});
|
715
713
|
exports.BalancesProvider = BalancesProvider;
|
716
|
-
exports.
|
717
|
-
exports.
|
718
|
-
exports.
|
719
|
-
exports.
|
720
|
-
exports.
|
714
|
+
exports.allAddressesAtom = allAddressesAtom;
|
715
|
+
exports.allBalancesAtom = allBalancesAtom;
|
716
|
+
exports.balanceModuleCreatorsAtom = balanceModuleCreatorsAtom;
|
717
|
+
exports.balanceModulesAtom = balanceModulesAtom;
|
718
|
+
exports.balancesPersistBackendAtom = balancesPersistBackendAtom;
|
719
|
+
exports.chainConnectorsAtom = chainConnectorsAtom;
|
720
|
+
exports.chaindataAtom = chaindataAtom;
|
721
|
+
exports.chaindataProviderAtom = chaindataProviderAtom;
|
722
|
+
exports.chainsAtom = chainsAtom;
|
723
|
+
exports.chainsByGenesisHashAtom = chainsByGenesisHashAtom;
|
724
|
+
exports.chainsByIdAtom = chainsByIdAtom;
|
725
|
+
exports.coingeckoConfigAtom = coingeckoConfigAtom;
|
726
|
+
exports.cryptoWaitReadyAtom = cryptoWaitReadyAtom;
|
727
|
+
exports.enableTestnetsAtom = enableTestnetsAtom;
|
728
|
+
exports.enabledChainsAtom = enabledChainsAtom;
|
729
|
+
exports.enabledTokensAtom = enabledTokensAtom;
|
730
|
+
exports.evmNetworksAtom = evmNetworksAtom;
|
731
|
+
exports.evmNetworksByIdAtom = evmNetworksByIdAtom;
|
721
732
|
exports.getStaleChains = getStaleChains;
|
722
|
-
exports.
|
723
|
-
exports.
|
724
|
-
exports.
|
733
|
+
exports.miniMetadataHydratedAtom = miniMetadataHydratedAtom;
|
734
|
+
exports.miniMetadatasAtom = miniMetadatasAtom;
|
735
|
+
exports.onfinalityApiKeyAtom = onfinalityApiKeyAtom;
|
736
|
+
exports.tokenRatesAtom = tokenRatesAtom;
|
737
|
+
exports.tokensAtom = tokensAtom;
|
738
|
+
exports.tokensByIdAtom = tokensByIdAtom;
|
725
739
|
exports.useBalances = useBalances;
|
726
|
-
exports.useBalancesHydrate = useBalancesHydrate;
|
727
740
|
exports.useBalancesStatus = useBalancesStatus;
|
728
741
|
exports.useChain = useChain;
|
729
742
|
exports.useChainConnectors = useChainConnectors;
|
730
743
|
exports.useChaindata = useChaindata;
|
744
|
+
exports.useChaindataProvider = useChaindataProvider;
|
731
745
|
exports.useChains = useChains;
|
732
|
-
exports.
|
733
|
-
exports.useDbCacheBalancesSubscription = useDbCacheBalancesSubscription;
|
734
|
-
exports.useDbCacheSubscription = useDbCacheSubscription;
|
735
|
-
exports.useDbCacheTokenRatesSubscription = useDbCacheTokenRatesSubscription;
|
746
|
+
exports.useChainsByGenesisHash = useChainsByGenesisHash;
|
736
747
|
exports.useEvmNetwork = useEvmNetwork;
|
737
748
|
exports.useEvmNetworks = useEvmNetworks;
|
738
|
-
exports.
|
749
|
+
exports.useMiniMetadatas = useMiniMetadatas;
|
750
|
+
exports.useSetBalancesAddresses = useSetBalancesAddresses;
|
739
751
|
exports.useToken = useToken;
|
740
752
|
exports.useTokenRate = useTokenRate;
|
741
753
|
exports.useTokenRates = useTokenRates;
|
742
754
|
exports.useTokens = useTokens;
|
743
|
-
exports.useWithTestnets = useWithTestnets;
|