@talismn/balances-react 0.0.0-pr593-20230303151944 → 0.0.0-pr595-20230305153032
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/CHANGELOG.md +8 -7
- package/dist/declarations/src/hooks/index.d.ts +0 -18
- package/dist/declarations/src/hooks/useBalances.d.ts +6 -3
- package/dist/declarations/src/hooks/useChaindata.d.ts +23 -6
- package/dist/declarations/src/hooks/useTokenRates.d.ts +3 -3
- package/dist/declarations/src/index.d.ts +0 -1
- package/dist/talismn-balances-react.cjs.dev.js +244 -477
- package/dist/talismn-balances-react.cjs.prod.js +244 -477
- package/dist/talismn-balances-react.esm.js +246 -461
- package/package.json +8 -9
- 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/useChainConnectors.d.ts +0 -12
- package/dist/declarations/src/hooks/useChains.d.ts +0 -3
- package/dist/declarations/src/hooks/useDbCache.d.ts +0 -27
- package/dist/declarations/src/hooks/useDbCacheSubscription.d.ts +0 -9
- package/dist/declarations/src/hooks/useEvmNetworks.d.ts +0 -3
- package/dist/declarations/src/hooks/useTokens.d.ts +0 -3
- 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
|
@@ -1,162 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { db, fetchTokenRates } from '@talismn/token-rates';
|
|
1
|
+
import { Balances, db, balances } from '@talismn/balances';
|
|
2
|
+
import { ChainConnector } from '@talismn/chain-connector';
|
|
3
|
+
import { ChainConnectorEvm } from '@talismn/chain-connector-evm';
|
|
5
4
|
import { useLiveQuery } from 'dexie-react-hooks';
|
|
5
|
+
import { useState, useEffect, useRef } from 'react';
|
|
6
6
|
import { useDebounce } from 'react-use';
|
|
7
|
-
import { ChaindataProviderExtension } from '@talismn/chaindata-provider-extension';
|
|
8
7
|
import anylogger from 'anylogger';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { ChainConnectorEvm } from '@talismn/chain-connector-evm';
|
|
12
|
-
|
|
13
|
-
const provideContext = useProviderContext => {
|
|
14
|
-
// automatic typing based on our hook's return type
|
|
15
|
-
|
|
16
|
-
const Context = /*#__PURE__*/createContext({
|
|
17
|
-
__provideContextInternalDefaultValue: true
|
|
18
|
-
});
|
|
19
|
-
const Provider = ({
|
|
20
|
-
children,
|
|
21
|
-
...props
|
|
22
|
-
}) => {
|
|
23
|
-
const ctx = useProviderContext(props);
|
|
24
|
-
return /*#__PURE__*/jsx(Context.Provider, {
|
|
25
|
-
value: ctx,
|
|
26
|
-
children: children
|
|
27
|
-
});
|
|
28
|
-
};
|
|
29
|
-
const useProvidedContext = () => {
|
|
30
|
-
const context = useContext(Context);
|
|
31
|
-
if (typeof context === "object" && context && "__provideContextInternalDefaultValue" in context) throw new Error("This hook requires a provider to be present above it in the tree");
|
|
32
|
-
return context;
|
|
33
|
-
};
|
|
34
|
-
const result = [Provider, useProvidedContext];
|
|
35
|
-
return result;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const useAllAddressesProvider = () => useState([]);
|
|
39
|
-
const [AllAddressesProvider, useAllAddresses] = provideContext(useAllAddressesProvider);
|
|
40
|
-
|
|
41
|
-
const useBalanceModulesProvider = ({
|
|
42
|
-
balanceModules
|
|
43
|
-
}) => balanceModules;
|
|
44
|
-
const [BalanceModulesProvider, useBalanceModules] = provideContext(useBalanceModulesProvider);
|
|
45
|
-
|
|
46
|
-
function useChaindataProvider(options = {}) {
|
|
47
|
-
const [chaindata, setChaindata] = useState();
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
setChaindata(new ChaindataProviderExtension({
|
|
50
|
-
onfinalityApiKey: options.onfinalityApiKey
|
|
51
|
-
}));
|
|
52
|
-
}, [options.onfinalityApiKey]);
|
|
53
|
-
return chaindata;
|
|
54
|
-
}
|
|
55
|
-
const [ChaindataProvider, useChaindata] = provideContext(useChaindataProvider);
|
|
56
|
-
|
|
57
|
-
const filterNoTestnet = ({
|
|
58
|
-
isTestnet
|
|
59
|
-
}) => isTestnet === false;
|
|
60
|
-
const DEFAULT_VALUE = {
|
|
61
|
-
chainsWithTestnets: [],
|
|
62
|
-
chainsWithoutTestnets: [],
|
|
63
|
-
evmNetworksWithTestnets: [],
|
|
64
|
-
evmNetworksWithoutTestnets: [],
|
|
65
|
-
tokensWithTestnets: [],
|
|
66
|
-
tokensWithoutTestnets: [],
|
|
67
|
-
chainsWithTestnetsMap: {},
|
|
68
|
-
chainsWithoutTestnetsMap: {},
|
|
69
|
-
evmNetworksWithTestnetsMap: {},
|
|
70
|
-
evmNetworksWithoutTestnetsMap: {},
|
|
71
|
-
tokensWithTestnetsMap: {},
|
|
72
|
-
tokensWithoutTestnetsMap: {},
|
|
73
|
-
tokenRatesMap: {},
|
|
74
|
-
balances: []
|
|
75
|
-
};
|
|
76
|
-
const consolidateDbCache = (chainsMap, evmNetworksMap, tokensMap, tokenRates, allBalances) => {
|
|
77
|
-
if (!chainsMap || !evmNetworksMap || !tokensMap || !tokenRates || !allBalances) return DEFAULT_VALUE;
|
|
78
|
-
|
|
79
|
-
// BEGIN: temp hack to indicate that
|
|
80
|
-
// - EVM GLMR is a mirror of substrate GLMR
|
|
81
|
-
// - EVM MOVR is a mirror of substrate MOVR
|
|
82
|
-
// - EVM DEV is a mirror of substrate DEV
|
|
83
|
-
// - EVM ACA is a mirror of substrate ACA
|
|
84
|
-
const mirrorTokenIds = {
|
|
85
|
-
"1284-evm-native-glmr": "moonbeam-substrate-native-glmr",
|
|
86
|
-
"1285-evm-native-movr": "moonriver-substrate-native-movr",
|
|
87
|
-
"1287-evm-native-dev": "moonbase-alpha-testnet-substrate-native-dev",
|
|
88
|
-
"787-evm-native-aca": "acala-substrate-native-aca"
|
|
89
|
-
};
|
|
90
|
-
Object.entries(mirrorTokenIds).filter(([mirrorToken]) => tokensMap[mirrorToken]).forEach(([mirrorToken, mirrorOf]) => tokensMap[mirrorToken].mirrorOf = mirrorOf);
|
|
91
|
-
// END: temp hack
|
|
92
|
-
|
|
93
|
-
const chainsWithTestnets = Object.values(chainsMap);
|
|
94
|
-
const chainsWithoutTestnets = chainsWithTestnets.filter(filterNoTestnet);
|
|
95
|
-
const chainsWithoutTestnetsMap = Object.fromEntries(chainsWithoutTestnets.map(network => [network.id, network]));
|
|
96
|
-
const evmNetworksWithTestnets = Object.values(evmNetworksMap);
|
|
97
|
-
const evmNetworksWithoutTestnets = evmNetworksWithTestnets.filter(filterNoTestnet);
|
|
98
|
-
const evmNetworksWithoutTestnetsMap = Object.fromEntries(evmNetworksWithoutTestnets.map(network => [network.id, network]));
|
|
99
|
-
|
|
100
|
-
// ensure that we have corresponding network for each token
|
|
101
|
-
const tokensWithTestnets = Object.values(tokensMap).filter(token => token.chain && chainsMap[token.chain.id] || token.evmNetwork && evmNetworksMap[token.evmNetwork.id]);
|
|
102
|
-
const tokensWithoutTestnets = tokensWithTestnets.filter(filterNoTestnet).filter(token => token.chain && chainsWithoutTestnetsMap[token.chain.id] || token.evmNetwork && evmNetworksWithoutTestnetsMap[token.evmNetwork.id]);
|
|
103
|
-
const tokensWithTestnetsMap = Object.fromEntries(tokensWithTestnets.map(token => [token.id, token]));
|
|
104
|
-
const tokensWithoutTestnetsMap = Object.fromEntries(tokensWithoutTestnets.map(token => [token.id, token]));
|
|
105
|
-
const tokenRatesMap = Object.fromEntries(tokenRates.map(({
|
|
106
|
-
tokenId,
|
|
107
|
-
rates
|
|
108
|
-
}) => [tokenId, rates]));
|
|
109
|
-
|
|
110
|
-
// return only balances for which we have a token
|
|
111
|
-
const balances = allBalances.filter(b => tokensWithTestnetsMap[b.tokenId]);
|
|
112
|
-
return {
|
|
113
|
-
chainsWithTestnets,
|
|
114
|
-
chainsWithoutTestnets,
|
|
115
|
-
evmNetworksWithTestnets,
|
|
116
|
-
evmNetworksWithoutTestnets,
|
|
117
|
-
tokensWithTestnets,
|
|
118
|
-
tokensWithoutTestnets,
|
|
119
|
-
chainsWithTestnetsMap: chainsMap,
|
|
120
|
-
chainsWithoutTestnetsMap,
|
|
121
|
-
evmNetworksWithTestnetsMap: evmNetworksMap,
|
|
122
|
-
evmNetworksWithoutTestnetsMap,
|
|
123
|
-
tokensWithTestnetsMap,
|
|
124
|
-
tokensWithoutTestnetsMap,
|
|
125
|
-
tokenRatesMap,
|
|
126
|
-
balances
|
|
127
|
-
};
|
|
128
|
-
};
|
|
129
|
-
const useDbCacheProvider = ({
|
|
130
|
-
useTestnets = false
|
|
131
|
-
}) => {
|
|
132
|
-
const chaindataProvider = useChaindata();
|
|
133
|
-
const chainList = useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.chains(), [chaindataProvider]);
|
|
134
|
-
const evmNetworkList = useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.evmNetworks(), [chaindataProvider]);
|
|
135
|
-
const tokenList = useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.tokens(), [chaindataProvider]);
|
|
136
|
-
const tokenRates = useLiveQuery(() => db.tokenRates.toArray(), []);
|
|
137
|
-
const rawBalances = useLiveQuery(() => db$1.balances.toArray(), []);
|
|
138
|
-
const [dbData, setDbData] = useState(DEFAULT_VALUE);
|
|
139
|
-
|
|
140
|
-
// debounce every 500ms to prevent hammering UI with updates
|
|
141
|
-
useDebounce(() => {
|
|
142
|
-
setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates, rawBalances));
|
|
143
|
-
}, 500, [chainList, evmNetworkList, tokenList, rawBalances, tokenRates, useTestnets]);
|
|
144
|
-
const refInitialized = useRef(false);
|
|
145
|
-
|
|
146
|
-
// force an update as soon as all datasources are fetched, so UI can display data ASAP
|
|
147
|
-
useEffect(() => {
|
|
148
|
-
if (!refInitialized.current && chainList && evmNetworkList && tokenList && tokenRates && rawBalances) {
|
|
149
|
-
setDbData(consolidateDbCache(chainList, evmNetworkList, tokenList, tokenRates, rawBalances));
|
|
150
|
-
refInitialized.current = true;
|
|
151
|
-
}
|
|
152
|
-
}, [chainList, evmNetworkList, rawBalances, tokenList, tokenRates, useTestnets]);
|
|
153
|
-
return dbData;
|
|
154
|
-
};
|
|
155
|
-
const [DbCacheProvider, useDbCache] = provideContext(useDbCacheProvider);
|
|
8
|
+
import { ChaindataProviderExtension } from '@talismn/chaindata-provider-extension';
|
|
9
|
+
import { fetchTokenRates } from '@talismn/token-rates';
|
|
156
10
|
|
|
157
11
|
var packageJson = {
|
|
158
12
|
name: "@talismn/balances-react",
|
|
159
|
-
version: "0.0.0-
|
|
13
|
+
version: "0.0.0-pr595-20230305153032",
|
|
160
14
|
author: "Talisman",
|
|
161
15
|
homepage: "https://talisman.xyz",
|
|
162
16
|
license: "UNLICENSED",
|
|
@@ -191,8 +45,7 @@ var packageJson = {
|
|
|
191
45
|
anylogger: "^1.0.11",
|
|
192
46
|
dexie: "^3.2.3",
|
|
193
47
|
"dexie-react-hooks": "^1.1.1",
|
|
194
|
-
"react-use": "^17.4.0"
|
|
195
|
-
rxjs: "^7.8.0"
|
|
48
|
+
"react-use": "^17.4.0"
|
|
196
49
|
},
|
|
197
50
|
devDependencies: {
|
|
198
51
|
"@talismn/eslint-config": "workspace:^",
|
|
@@ -219,312 +72,139 @@ var packageJson = {
|
|
|
219
72
|
|
|
220
73
|
var log = anylogger(packageJson.name);
|
|
221
74
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
* An example of when this is useful is when we want to subscribe to some data from multiple components, but we only want
|
|
226
|
-
* to actively keep that data hydrated when at least one component is subscribed to it.
|
|
227
|
-
*
|
|
228
|
-
* When the first component subscribes, the `upstream` function will be called. It should then set up a subscription and return a teardown function.
|
|
229
|
-
* When subsequent components subscribe, they will be added to the existing subscription.
|
|
230
|
-
* When the last component unsubscribes, the teardown function returned from the `upstream` function will be called.
|
|
231
|
-
*
|
|
232
|
-
* @param upstream A function that takes a "next" callback function as an argument, and returns either an unsubscribe function or void.
|
|
233
|
-
* @returns A subscription function that can be used to subscribe to the multicast observable.
|
|
234
|
-
*/
|
|
235
|
-
const useMulticastSubscription = upstream => {
|
|
236
|
-
const subscribe = useMemo(() => createMulticastSubscription(upstream), [upstream]);
|
|
237
|
-
return subscribe;
|
|
238
|
-
};
|
|
239
|
-
const createMulticastSubscription = upstream => {
|
|
240
|
-
// Create an upstream observable using the provided function.
|
|
241
|
-
const upstreamObservable = new Observable(subscriber => {
|
|
242
|
-
const unsubscribe = upstream(val => subscriber.next(val));
|
|
243
|
-
return () => {
|
|
244
|
-
typeof unsubscribe === "function" && unsubscribe();
|
|
245
|
-
};
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// Create a multicast observable from the upstream observable, using the shareReplay operator.
|
|
249
|
-
const multicastObservable = defer(() => upstreamObservable).pipe(shareReplay({
|
|
250
|
-
bufferSize: 1,
|
|
251
|
-
refCount: true
|
|
252
|
-
}));
|
|
75
|
+
// TODO: Allow user to call useChaindata from multiple places
|
|
76
|
+
function useChaindata(options = {}) {
|
|
77
|
+
const [chaindataProvider, setChaindataProvider] = useState(null);
|
|
253
78
|
|
|
254
|
-
//
|
|
255
|
-
const
|
|
256
|
-
const subscription = multicastObservable.subscribe(callback);
|
|
257
|
-
const unsubscribe = () => subscription.unsubscribe();
|
|
258
|
-
return unsubscribe;
|
|
259
|
-
};
|
|
260
|
-
return subscribe;
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
function useChainConnectorsProvider(options) {
|
|
264
|
-
// chaindata dependency
|
|
265
|
-
const chaindata = useChaindata();
|
|
266
|
-
|
|
267
|
-
// substrate connector
|
|
268
|
-
const [substrate, setSubstrate] = useState();
|
|
269
|
-
useEffect(() => {
|
|
270
|
-
if (!chaindata) return;
|
|
271
|
-
setSubstrate(new ChainConnector(chaindata));
|
|
272
|
-
}, [chaindata]);
|
|
273
|
-
|
|
274
|
-
// evm connector
|
|
275
|
-
const [evm, setEvm] = useState();
|
|
79
|
+
// this number is incremented each time the chaindataProvider has fetched new data
|
|
80
|
+
const [generation, setGeneration] = useState(0);
|
|
276
81
|
useEffect(() => {
|
|
277
|
-
|
|
278
|
-
setEvm(new ChainConnectorEvm(chaindata, {
|
|
82
|
+
const chaindataProvider = new ChaindataProviderExtension({
|
|
279
83
|
onfinalityApiKey: options.onfinalityApiKey
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
substrate,
|
|
284
|
-
evm
|
|
285
|
-
}), [substrate, evm]);
|
|
286
|
-
}
|
|
287
|
-
const [ChainConnectorsProvider, useChainConnectors] = provideContext(useChainConnectorsProvider);
|
|
288
|
-
|
|
289
|
-
const useSubscriptionsProvider = () => [useSubscribeChaindataHydrate("chains"), useSubscribeChaindataHydrate("evmNetworks"), useSubscribeChaindataHydrate("tokens"), useSubscribeTokenRates(), useSubscribeBalances()];
|
|
290
|
-
const [SubscriptionsProvider, useSubscriptions] = provideContext(useSubscriptionsProvider);
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* This hook is responsible for fetching the data used for balances and inserting it into the db.
|
|
294
|
-
*/
|
|
295
|
-
const useDbCacheSubscription = subscribeTo => {
|
|
296
|
-
const [subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances] = useSubscriptions();
|
|
297
|
-
useEffect(() => {
|
|
298
|
-
switch (subscribeTo) {
|
|
299
|
-
case "chains":
|
|
300
|
-
return subscribeHydrateChains();
|
|
301
|
-
case "evmNetworks":
|
|
302
|
-
return subscribeHydrateEvmNetworks();
|
|
303
|
-
case "tokens":
|
|
304
|
-
return subscribeHydrateTokens();
|
|
305
|
-
case "tokenRates":
|
|
306
|
-
return subscribeTokenRates();
|
|
307
|
-
case "balances":
|
|
308
|
-
return subscribeBalances();
|
|
309
|
-
}
|
|
310
|
-
}, [subscribeTo, subscribeHydrateChains, subscribeHydrateEvmNetworks, subscribeHydrateTokens, subscribeTokenRates, subscribeBalances]);
|
|
311
|
-
};
|
|
312
|
-
function useSubscribeChaindataHydrate(type) {
|
|
313
|
-
const chaindata =
|
|
314
|
-
// cheeky hack to give us access to the hydrate methods
|
|
315
|
-
useChaindata();
|
|
316
|
-
const createSubscription = useCallback(() => {
|
|
317
|
-
if (!chaindata) return;
|
|
318
|
-
let active = true;
|
|
319
|
-
const interval = 300_000; // 300_000ms = 300s = 5 minutes
|
|
320
|
-
|
|
84
|
+
});
|
|
85
|
+
let shouldHydrate = true;
|
|
86
|
+
const timer = 300_000; // 300_000ms = 300s = 5 minutes
|
|
321
87
|
const hydrate = async () => {
|
|
322
|
-
if (!
|
|
88
|
+
if (!shouldHydrate) return;
|
|
323
89
|
try {
|
|
324
|
-
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
setTimeout(hydrate, interval);
|
|
90
|
+
const updated = await chaindataProvider.hydrate();
|
|
91
|
+
if (updated) setGeneration(generation => (generation + 1) % Number.MAX_SAFE_INTEGER);
|
|
92
|
+
setTimeout(hydrate, timer);
|
|
328
93
|
} catch (error) {
|
|
329
94
|
const retryTimeout = 5_000; // 5_000ms = 5 seconds
|
|
330
95
|
log.error(`Failed to fetch chaindata, retrying in ${Math.round(retryTimeout / 1000)} seconds`, error);
|
|
331
96
|
setTimeout(hydrate, retryTimeout);
|
|
332
97
|
}
|
|
333
98
|
};
|
|
99
|
+
setChaindataProvider(chaindataProvider);
|
|
334
100
|
hydrate();
|
|
335
101
|
return () => {
|
|
336
|
-
|
|
337
|
-
};
|
|
338
|
-
}, [chaindata, type]);
|
|
339
|
-
const subscribe = useMulticastSubscription(createSubscription);
|
|
340
|
-
return subscribe;
|
|
341
|
-
}
|
|
342
|
-
function useSubscribeTokenRates() {
|
|
343
|
-
const chaindataProvider = useChaindata();
|
|
344
|
-
const tokens = useLiveQuery(() => chaindataProvider === null || chaindataProvider === void 0 ? void 0 : chaindataProvider.tokens(), [chaindataProvider]);
|
|
345
|
-
const generationRef = useRef(0);
|
|
346
|
-
const createSubscription = useCallback(() => {
|
|
347
|
-
if (!chaindataProvider) return;
|
|
348
|
-
if (!tokens) return;
|
|
349
|
-
if (Object.keys(tokens).length < 1) return;
|
|
350
|
-
|
|
351
|
-
// when we make a new request, we want to ignore any old requests which haven't yet completed
|
|
352
|
-
// otherwise we risk replacing the most recent data with older data
|
|
353
|
-
const generation = (generationRef.current + 1) % Number.MAX_SAFE_INTEGER;
|
|
354
|
-
generationRef.current = generation;
|
|
355
|
-
let active = true;
|
|
356
|
-
const REFRESH_INTERVAL = 300_000; // 300_000ms = 5 minutes
|
|
357
|
-
const RETRY_INTERVAL = 5_000; // 5_000ms = 5 seconds
|
|
358
|
-
|
|
359
|
-
const hydrate = async () => {
|
|
360
|
-
if (!active) return;
|
|
361
|
-
if (generationRef.current !== generation) return;
|
|
362
|
-
try {
|
|
363
|
-
const tokenRates = await fetchTokenRates(tokens);
|
|
364
|
-
if (!active) return;
|
|
365
|
-
if (generationRef.current !== generation) return;
|
|
366
|
-
const putTokenRates = Object.entries(tokenRates).map(([tokenId, rates]) => ({
|
|
367
|
-
tokenId,
|
|
368
|
-
rates
|
|
369
|
-
}));
|
|
370
|
-
db.transaction("rw", db.tokenRates, async () => await db.tokenRates.bulkPut(putTokenRates));
|
|
371
|
-
setTimeout(hydrate, REFRESH_INTERVAL);
|
|
372
|
-
} catch (error) {
|
|
373
|
-
log.error(`Failed to fetch tokenRates, retrying in ${Math.round(RETRY_INTERVAL / 1000)} seconds`, error);
|
|
374
|
-
setTimeout(hydrate, RETRY_INTERVAL);
|
|
375
|
-
}
|
|
376
|
-
};
|
|
377
|
-
hydrate();
|
|
378
|
-
return () => {
|
|
379
|
-
active = false;
|
|
102
|
+
shouldHydrate = false;
|
|
380
103
|
};
|
|
381
|
-
}, [
|
|
382
|
-
|
|
383
|
-
return
|
|
104
|
+
}, [options.onfinalityApiKey]);
|
|
105
|
+
if (chaindataProvider) chaindataProvider.generation = generation;
|
|
106
|
+
return chaindataProvider;
|
|
384
107
|
}
|
|
385
|
-
function
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}) => id), [tokens]);
|
|
394
|
-
const addressesByToken = useMemo(() => Object.fromEntries(tokenIds.map(tokenId => [tokenId, allAddresses])), [allAddresses, tokenIds]);
|
|
395
|
-
const generationRef = useRef(0);
|
|
396
|
-
const createSubscription = useCallback(() => {
|
|
397
|
-
if (!chainConnectors.substrate) return;
|
|
398
|
-
if (!chainConnectors.evm) return;
|
|
399
|
-
if (!chaindataProvider) return;
|
|
400
|
-
const generation = (generationRef.current + 1) % Number.MAX_SAFE_INTEGER;
|
|
401
|
-
generationRef.current = generation;
|
|
402
|
-
const unsubs = balanceModules.map(balanceModule => {
|
|
403
|
-
// filter out tokens to only include those which this module knows how to fetch balances for
|
|
404
|
-
const moduleTokenIds = Object.values(tokens ?? {}).filter(({
|
|
405
|
-
type
|
|
406
|
-
}) => type === balanceModule.type).map(({
|
|
407
|
-
id
|
|
408
|
-
}) => id);
|
|
409
|
-
const addressesByModuleToken = Object.fromEntries(Object.entries(addressesByToken).filter(([tokenId]) => moduleTokenIds.includes(tokenId)));
|
|
410
|
-
const subscribe = createMulticastSubscription(next => {
|
|
411
|
-
const unsub = balances(balanceModule, chainConnectors, chaindataProvider, addressesByModuleToken, (error, balances) => {
|
|
412
|
-
// log errors
|
|
413
|
-
if (error) return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
|
|
414
|
-
// ignore empty balance responses
|
|
415
|
-
if (!balances) return;
|
|
416
|
-
// ignore balances from old subscriptions which are still in the process of unsubscribing
|
|
417
|
-
if (generationRef.current !== generation) return;
|
|
418
|
-
next(balances);
|
|
419
|
-
});
|
|
420
|
-
return () => {
|
|
421
|
-
// unsubscribe from upstream
|
|
422
|
-
unsub.then(unsubscribe => unsubscribe());
|
|
423
|
-
|
|
424
|
-
// set this subscription's balances in the store to status: cache
|
|
425
|
-
db$1.transaction("rw", db$1.balances, async () => await db$1.balances.filter(balance => {
|
|
426
|
-
if (balance.source !== balanceModule.type) return false;
|
|
427
|
-
if (!Object.keys(addressesByModuleToken).includes(balance.tokenId)) return false;
|
|
428
|
-
if (!addressesByModuleToken[balance.tokenId].includes(balance.address)) return false;
|
|
429
|
-
return true;
|
|
430
|
-
}).modify({
|
|
431
|
-
status: "cache"
|
|
432
|
-
}));
|
|
433
|
-
};
|
|
434
|
-
});
|
|
435
|
-
const unsubscribe = subscribe(balances => {
|
|
436
|
-
const putBalances = Object.entries(balances.toJSON()).map(([id, balance]) => ({
|
|
437
|
-
id,
|
|
438
|
-
...balance
|
|
439
|
-
}));
|
|
440
|
-
db$1.transaction("rw", db$1.balances, async () => await db$1.balances.bulkPut(putBalances));
|
|
441
|
-
});
|
|
442
|
-
return unsubscribe;
|
|
108
|
+
function useChains(chaindata) {
|
|
109
|
+
const [chains, setChains] = useState();
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (!chaindata) return;
|
|
112
|
+
const thisGeneration = chaindata.generation;
|
|
113
|
+
chaindata.chains().then(chains => {
|
|
114
|
+
if (thisGeneration !== chaindata.generation) return;
|
|
115
|
+
setChains(chains);
|
|
443
116
|
});
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}, [addressesByToken, balanceModules, chainConnectors, chaindataProvider, tokens]);
|
|
447
|
-
const subscribe = useMulticastSubscription(createSubscription);
|
|
448
|
-
return subscribe;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function useChains(withTestnets) {
|
|
452
|
-
// keep db data up to date
|
|
453
|
-
useDbCacheSubscription("chains");
|
|
454
|
-
const {
|
|
455
|
-
chainsWithTestnetsMap,
|
|
456
|
-
chainsWithoutTestnetsMap
|
|
457
|
-
} = useDbCache();
|
|
458
|
-
return withTestnets ? chainsWithTestnetsMap : chainsWithoutTestnetsMap;
|
|
117
|
+
}, [chaindata, chaindata === null || chaindata === void 0 ? void 0 : chaindata.generation]);
|
|
118
|
+
return chains || {};
|
|
459
119
|
}
|
|
460
|
-
function useChain(
|
|
461
|
-
const
|
|
462
|
-
|
|
120
|
+
function useChain(chaindata, chainId) {
|
|
121
|
+
const [chain, setChain] = useState();
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (chaindata === null) return;
|
|
124
|
+
if (!chainId) return;
|
|
125
|
+
chaindata.getChain(chainId).then(setChain);
|
|
126
|
+
}, [chainId, chaindata, chaindata === null || chaindata === void 0 ? void 0 : chaindata.generation]);
|
|
127
|
+
return chain;
|
|
463
128
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
129
|
+
function useEvmNetworks(chaindata) {
|
|
130
|
+
const [evmNetworks, setEvmNetworks] = useState();
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (!chaindata) return;
|
|
133
|
+
const thisGeneration = chaindata.generation;
|
|
134
|
+
chaindata.evmNetworks().then(evmNetworks => {
|
|
135
|
+
if (thisGeneration !== chaindata.generation) return;
|
|
136
|
+
setEvmNetworks(evmNetworks);
|
|
137
|
+
});
|
|
138
|
+
}, [chaindata, chaindata === null || chaindata === void 0 ? void 0 : chaindata.generation]);
|
|
139
|
+
return evmNetworks || {};
|
|
473
140
|
}
|
|
474
|
-
function useEvmNetwork(
|
|
475
|
-
const
|
|
476
|
-
|
|
141
|
+
function useEvmNetwork(chaindata, evmNetworkId) {
|
|
142
|
+
const [evmNetwork, setEvmNetwork] = useState();
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
if (chaindata === null) return;
|
|
145
|
+
if (!evmNetworkId) return;
|
|
146
|
+
chaindata.getEvmNetwork(evmNetworkId).then(setEvmNetwork);
|
|
147
|
+
}, [chaindata, chaindata === null || chaindata === void 0 ? void 0 : chaindata.generation, evmNetworkId]);
|
|
148
|
+
return evmNetwork;
|
|
477
149
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
150
|
+
function useTokens(chaindata) {
|
|
151
|
+
const [tokens, setTokens] = useState();
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (!chaindata) return;
|
|
154
|
+
const thisGeneration = chaindata.generation;
|
|
155
|
+
chaindata.tokens().then(tokens => {
|
|
156
|
+
if (thisGeneration !== chaindata.generation) return;
|
|
157
|
+
setTokens(tokens);
|
|
158
|
+
});
|
|
159
|
+
}, [chaindata, chaindata === null || chaindata === void 0 ? void 0 : chaindata.generation]);
|
|
160
|
+
return tokens || {};
|
|
486
161
|
}
|
|
487
|
-
function
|
|
488
|
-
const
|
|
489
|
-
|
|
162
|
+
function useToken(chaindata, tokenId) {
|
|
163
|
+
const [token, setToken] = useState();
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
if (chaindata === null) return;
|
|
166
|
+
if (!tokenId) return;
|
|
167
|
+
chaindata.getToken(tokenId).then(setToken);
|
|
168
|
+
}, [chaindata, chaindata === null || chaindata === void 0 ? void 0 : chaindata.generation, tokenId]);
|
|
169
|
+
return token;
|
|
490
170
|
}
|
|
491
171
|
|
|
492
|
-
function
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
} = useDbCache();
|
|
499
|
-
return withTestnets ? tokensWithTestnetsMap : tokensWithoutTestnetsMap;
|
|
500
|
-
}
|
|
501
|
-
function useToken(tokenId, withTestnets) {
|
|
502
|
-
const tokens = useTokens(withTestnets);
|
|
503
|
-
return tokenId ? tokens[tokenId] : undefined;
|
|
504
|
-
}
|
|
172
|
+
function useTokenRates(tokens) {
|
|
173
|
+
const generation = useRef(0);
|
|
174
|
+
const [tokenRates, setTokenRates] = useState();
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (!tokens) return;
|
|
177
|
+
if (Object.keys(tokens).length < 1) return;
|
|
505
178
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
};
|
|
179
|
+
// when we make a new request, we want to ignore any old requests which haven't yet completed
|
|
180
|
+
// otherwise we risk replacing the most recent data with older data
|
|
181
|
+
generation.current = (generation.current + 1) % Number.MAX_SAFE_INTEGER;
|
|
182
|
+
const thisGeneration = generation.current;
|
|
183
|
+
fetchTokenRates(tokens).then(tokenRates => {
|
|
184
|
+
if (thisGeneration !== generation.current) return;
|
|
185
|
+
setTokenRates(tokenRates);
|
|
186
|
+
});
|
|
187
|
+
}, [tokens]);
|
|
188
|
+
return tokenRates || {};
|
|
189
|
+
}
|
|
518
190
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
191
|
+
// TODO: Add the equivalent functionalty of `useDbCache` directly to this library.
|
|
192
|
+
//
|
|
193
|
+
// How it will work:
|
|
194
|
+
//
|
|
195
|
+
// useChains/useEvmNetworks/useTokens/useTokenRates will all make use of a
|
|
196
|
+
// useCachedDb hook, which internally subscribes to all of the db tables
|
|
197
|
+
// for everything, and then filters the subscribed data based on what params
|
|
198
|
+
// the caller of useChains/useTokens/etc has provided.
|
|
199
|
+
function useBalances(
|
|
200
|
+
// TODO: Make this array of BalanceModules more type-safe
|
|
201
|
+
balanceModules, chaindataProvider, addressesByToken, options = {}) {
|
|
202
|
+
useBalancesSubscriptions(balanceModules, chaindataProvider, addressesByToken, options);
|
|
203
|
+
const chains = useChains(chaindataProvider);
|
|
204
|
+
const evmNetworks = useEvmNetworks(chaindataProvider);
|
|
205
|
+
const tokens = useTokens(chaindataProvider);
|
|
206
|
+
const tokenRates = useTokenRates(tokens);
|
|
207
|
+
const balances = useLiveQuery(async () => new Balances(await db.balances.filter(balance => {
|
|
528
208
|
// check that this balance is included in our queried balance modules
|
|
529
209
|
if (!balanceModules.map(({
|
|
530
210
|
type
|
|
@@ -541,32 +221,137 @@ function useBalances(addressesByToken) {
|
|
|
541
221
|
|
|
542
222
|
// keep this balance
|
|
543
223
|
return true;
|
|
544
|
-
}),
|
|
224
|
+
}).toArray(),
|
|
545
225
|
// hydrate balance chains, evmNetworks, tokens and tokenRates
|
|
546
|
-
|
|
226
|
+
{
|
|
227
|
+
chains,
|
|
228
|
+
evmNetworks,
|
|
229
|
+
tokens,
|
|
230
|
+
tokenRates
|
|
231
|
+
}), [balanceModules, addressesByToken, chains, evmNetworks, tokens, tokenRates]);
|
|
232
|
+
|
|
233
|
+
// debounce every 100ms to prevent hammering UI with updates
|
|
234
|
+
const [debouncedBalances, setDebouncedBalances] = useState(balances);
|
|
235
|
+
useDebounce(() => balances && setDebouncedBalances(balances), 100, [balances]);
|
|
236
|
+
return debouncedBalances;
|
|
547
237
|
}
|
|
548
238
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
239
|
+
// TODO: Turn into react context
|
|
240
|
+
const subscriptions = {};
|
|
241
|
+
|
|
242
|
+
// This hook is responsible for allowing us to call useBalances
|
|
243
|
+
// from multiple components, without setting up unnecessary
|
|
244
|
+
// balance subscriptions
|
|
245
|
+
function useBalancesSubscriptions(
|
|
246
|
+
// TODO: Make this array of BalanceModules more type-safe
|
|
247
|
+
balanceModules, chaindataProvider, addressesByToken, options = {}) {
|
|
248
|
+
// const subscriptions = useRef<
|
|
249
|
+
// Record<string, { unsub: Promise<() => void>; refcount: number; generation: number }>
|
|
250
|
+
// >({})
|
|
251
|
+
|
|
252
|
+
const addSubscription = (key, balanceModule, chainConnectors, chaindataProvider, addressesByToken) => {
|
|
253
|
+
// create subscription if it doesn't already exist
|
|
254
|
+
if (!subscriptions[key] || subscriptions[key].refcount === 0) {
|
|
255
|
+
var _subscriptions$key;
|
|
256
|
+
const generation = ((((_subscriptions$key = subscriptions[key]) === null || _subscriptions$key === void 0 ? void 0 : _subscriptions$key.generation) || 0) + 1) % Number.MAX_SAFE_INTEGER;
|
|
257
|
+
const unsub = balances(balanceModule, chainConnectors, chaindataProvider, addressesByToken, (error, balances) => {
|
|
258
|
+
if (error) return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
|
|
259
|
+
if (!balances) return;
|
|
260
|
+
|
|
261
|
+
// ignore balances from old subscriptions which are still in the process of unsubscribing
|
|
262
|
+
if (subscriptions[key].generation !== generation) return;
|
|
263
|
+
const putBalances = Object.entries(balances.toJSON()).map(([id, balance]) => ({
|
|
264
|
+
id,
|
|
265
|
+
...balance
|
|
266
|
+
}));
|
|
267
|
+
db.transaction("rw", db.balances, async () => await db.balances.bulkPut(putBalances));
|
|
268
|
+
});
|
|
269
|
+
subscriptions[key] = {
|
|
270
|
+
unsub,
|
|
271
|
+
refcount: 0,
|
|
272
|
+
generation
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// bump up the refcount by 1
|
|
277
|
+
subscriptions[key].refcount += 1;
|
|
278
|
+
};
|
|
279
|
+
const removeSubscription = (key, balanceModule, addressesByToken) => {
|
|
280
|
+
// ignore dead subscriptions
|
|
281
|
+
if (!subscriptions[key] || subscriptions[key].refcount === 0) return;
|
|
282
|
+
|
|
283
|
+
// drop the refcount by one
|
|
284
|
+
subscriptions[key].refcount -= 1;
|
|
285
|
+
|
|
286
|
+
// unsubscribe if refcount is now 0 (nobody wants this subcription anymore)
|
|
287
|
+
if (subscriptions[key].refcount < 1) {
|
|
288
|
+
// remove subscription
|
|
289
|
+
subscriptions[key].unsub.then(unsub => unsub());
|
|
290
|
+
delete subscriptions[key];
|
|
291
|
+
|
|
292
|
+
// set this subscription's balances in the store to status: cache
|
|
293
|
+
db.transaction("rw", db.balances, async () => await db.balances.filter(balance => {
|
|
294
|
+
if (balance.source !== balanceModule.type) return false;
|
|
295
|
+
if (!Object.keys(addressesByToken).includes(balance.tokenId)) return false;
|
|
296
|
+
if (!addressesByToken[balance.tokenId].includes(balance.address)) return false;
|
|
297
|
+
return true;
|
|
298
|
+
}).modify({
|
|
299
|
+
status: "cache"
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
const chainConnector = useChainConnector(chaindataProvider);
|
|
304
|
+
const chainConnectorEvm = useChainConnectorEvm(chaindataProvider, options);
|
|
305
|
+
const tokens = useTokens(chaindataProvider);
|
|
306
|
+
useEffect(() => {
|
|
307
|
+
if (chainConnector === null) return;
|
|
308
|
+
if (chainConnectorEvm === null) return;
|
|
309
|
+
if (chaindataProvider === null) return;
|
|
310
|
+
if (addressesByToken === null) return;
|
|
311
|
+
const unsubs = balanceModules.map(balanceModule => {
|
|
312
|
+
const subscriptionKey = `${balanceModule.type}-${JSON.stringify(addressesByToken)}`;
|
|
313
|
+
|
|
314
|
+
// filter out tokens to only include those which this module knows how to fetch balances for
|
|
315
|
+
const moduleTokenIds = Object.values(tokens).filter(({
|
|
316
|
+
type
|
|
317
|
+
}) => type === balanceModule.type).map(({
|
|
318
|
+
id
|
|
319
|
+
}) => id);
|
|
320
|
+
const addressesByModuleToken = Object.fromEntries(Object.entries(addressesByToken).filter(([tokenId]) => moduleTokenIds.includes(tokenId)));
|
|
321
|
+
|
|
322
|
+
// add balance subscription for this module
|
|
323
|
+
addSubscription(subscriptionKey, balanceModule, {
|
|
324
|
+
substrate: chainConnector,
|
|
325
|
+
evm: chainConnectorEvm
|
|
326
|
+
}, chaindataProvider, addressesByModuleToken);
|
|
327
|
+
|
|
328
|
+
// return an unsub method, to be called when this effect unmounts
|
|
329
|
+
return () => removeSubscription(subscriptionKey, balanceModule, addressesByToken);
|
|
330
|
+
});
|
|
331
|
+
const unsubAll = () => unsubs.forEach(unsub => unsub());
|
|
332
|
+
return unsubAll;
|
|
333
|
+
}, [addressesByToken, balanceModules, chainConnector, chainConnectorEvm, chaindataProvider, tokens]);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// TODO: Allow advanced users of this library to provide their own chain connector
|
|
337
|
+
function useChainConnector(chaindataProvider) {
|
|
338
|
+
const [chainConnector, setChainConnector] = useState(null);
|
|
339
|
+
useEffect(() => {
|
|
340
|
+
if (chaindataProvider === null) return;
|
|
341
|
+
setChainConnector(new ChainConnector(chaindataProvider));
|
|
342
|
+
}, [chaindataProvider]);
|
|
343
|
+
return chainConnector;
|
|
344
|
+
}
|
|
345
|
+
// TODO: Allow advanced users of this library to provide their own chain connector
|
|
346
|
+
function useChainConnectorEvm(chaindataProvider, options = {}) {
|
|
347
|
+
const [chainConnectorEvm, setChainConnectorEvm] = useState(null);
|
|
348
|
+
useEffect(() => {
|
|
349
|
+
if (chaindataProvider === null) return;
|
|
350
|
+
setChainConnectorEvm(new ChainConnectorEvm(chaindataProvider, {
|
|
351
|
+
onfinalityApiKey: options.onfinalityApiKey
|
|
352
|
+
}));
|
|
353
|
+
}, [chaindataProvider, options.onfinalityApiKey]);
|
|
354
|
+
return chainConnectorEvm;
|
|
355
|
+
}
|
|
571
356
|
|
|
572
|
-
export {
|
|
357
|
+
export { useBalances, useChain, useChaindata, useChains, useEvmNetwork, useEvmNetworks, useToken, useTokenRates, useTokens };
|