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