@talismn/balances-react 0.2.3 → 0.3.1

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 CHANGED
@@ -1,5 +1,34 @@
1
1
  # @talismn/balances-react
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 8adc7f06: feat: switched build tool to preconstruct
8
+ - Updated dependencies [8adc7f06]
9
+ - Updated dependencies [cfe8d276]
10
+ - @talismn/chaindata-provider-extension@0.4.1
11
+ - @talismn/chain-connector-evm@0.4.1
12
+ - @talismn/chaindata-provider@0.4.1
13
+ - @talismn/chain-connector@0.4.1
14
+ - @talismn/token-rates@0.1.13
15
+ - @talismn/balances@0.3.1
16
+
17
+ ## 0.3.0
18
+
19
+ ### Patch Changes
20
+
21
+ - 4aa691d: feat: new balance modules
22
+ - Updated dependencies [4aa691d]
23
+ - Updated dependencies [cd6a684]
24
+ - Updated dependencies [a63dbb3]
25
+ - @talismn/chaindata-provider-extension@0.2.1
26
+ - @talismn/chain-connector-evm@0.4.0
27
+ - @talismn/chaindata-provider@0.2.1
28
+ - @talismn/chain-connector@0.2.1
29
+ - @talismn/token-rates@0.1.12
30
+ - @talismn/balances@0.3.0
31
+
3
32
  ## 0.2.3
4
33
 
5
34
  ### Patch Changes
File without changes
File without changes
@@ -0,0 +1 @@
1
+ export * from "./declarations/src/index";
@@ -0,0 +1,369 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var balances = require('@talismn/balances');
6
+ var chainConnector = require('@talismn/chain-connector');
7
+ var chainConnectorEvm = require('@talismn/chain-connector-evm');
8
+ var dexieReactHooks = require('dexie-react-hooks');
9
+ var react = require('react');
10
+ var reactUse = require('react-use');
11
+ var anylogger = require('anylogger');
12
+ var chaindataProviderExtension = require('@talismn/chaindata-provider-extension');
13
+ var tokenRates = require('@talismn/token-rates');
14
+
15
+ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
16
+
17
+ var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
18
+
19
+ var packageJson = {
20
+ name: "@talismn/balances-react",
21
+ version: "0.3.1",
22
+ author: "Talisman",
23
+ homepage: "https://talisman.xyz",
24
+ license: "UNLICENSED",
25
+ publishConfig: {
26
+ access: "public"
27
+ },
28
+ repository: {
29
+ directory: "packages/balances-react",
30
+ type: "git",
31
+ url: "https://github.com/talismansociety/talisman.git"
32
+ },
33
+ main: "dist/talismn-balances-react.cjs.js",
34
+ module: "dist/talismn-balances-react.esm.js",
35
+ files: [
36
+ "/dist"
37
+ ],
38
+ engines: {
39
+ node: ">=14"
40
+ },
41
+ scripts: {
42
+ test: "jest",
43
+ lint: "eslint . --max-warnings 0",
44
+ clean: "rm -rf dist && rm -rf .turbo rm -rf node_modules"
45
+ },
46
+ dependencies: {
47
+ "@talismn/balances": "workspace:^",
48
+ "@talismn/chain-connector": "workspace:^",
49
+ "@talismn/chain-connector-evm": "workspace:^",
50
+ "@talismn/chaindata-provider": "workspace:^",
51
+ "@talismn/chaindata-provider-extension": "workspace:^",
52
+ "@talismn/token-rates": "workspace:^",
53
+ anylogger: "^1.0.11",
54
+ dexie: "^3.2.2",
55
+ "dexie-react-hooks": "^1.1.1",
56
+ "react-use": "^17.4.0"
57
+ },
58
+ devDependencies: {
59
+ "@talismn/eslint-config": "workspace:^",
60
+ "@talismn/tsconfig": "workspace:^",
61
+ "@types/jest": "^27.5.1",
62
+ "@types/react": "^18.0.17",
63
+ eslint: "^8.4.0",
64
+ jest: "^28.1.0",
65
+ react: "^18.2.0",
66
+ "ts-jest": "^28.0.2",
67
+ typescript: "^4.6.4"
68
+ },
69
+ peerDependencies: {
70
+ react: "*",
71
+ "react-dom": "*"
72
+ },
73
+ eslintConfig: {
74
+ root: true,
75
+ "extends": [
76
+ "@talismn/eslint-config/react"
77
+ ]
78
+ }
79
+ };
80
+
81
+ var log = anylogger__default["default"](packageJson.name);
82
+
83
+ // TODO: Allow user to call useChaindata from multiple places
84
+
85
+ function useChaindata() {
86
+ const [chaindataProvider, setChaindataProvider] = react.useState(null);
87
+
88
+ // this number is incremented each time the chaindataProvider has fetched new data
89
+ const [generation, setGeneration] = react.useState(0);
90
+ react.useEffect(() => {
91
+ const chaindataProvider = new chaindataProviderExtension.ChaindataProviderExtension();
92
+ let shouldHydrate = true;
93
+ const timer = 300_000; // 300_000ms = 300s = 5 minutes
94
+ const hydrate = async () => {
95
+ if (!shouldHydrate) return;
96
+ try {
97
+ const updated = await chaindataProvider.hydrate();
98
+ if (updated) setGeneration(generation => (generation + 1) % Number.MAX_SAFE_INTEGER);
99
+ setTimeout(hydrate, timer);
100
+ } catch (error) {
101
+ const retryTimeout = 5_000; // 5_000ms = 5 seconds
102
+ log.error(`Failed to fetch chaindata, retrying in ${Math.round(retryTimeout / 1000)} seconds`, error);
103
+ setTimeout(hydrate, retryTimeout);
104
+ }
105
+ };
106
+ setChaindataProvider(chaindataProvider);
107
+ hydrate();
108
+ return () => {
109
+ shouldHydrate = false;
110
+ };
111
+ }, []);
112
+ if (chaindataProvider) chaindataProvider.generation = generation;
113
+ return chaindataProvider;
114
+ }
115
+ function useChains(chaindata) {
116
+ const [chains, setChains] = react.useState();
117
+ react.useEffect(() => {
118
+ if (!chaindata) return;
119
+ const thisGeneration = chaindata.generation;
120
+ chaindata.chains().then(chains => {
121
+ if (thisGeneration !== chaindata.generation) return;
122
+ setChains(chains);
123
+ });
124
+ }, [chaindata?.generation]);
125
+ return chains || {};
126
+ }
127
+ function useChain(chaindata, chainId) {
128
+ const [chain, setChain] = react.useState();
129
+ react.useEffect(() => {
130
+ if (chaindata === null) return;
131
+ if (!chainId) return;
132
+ chaindata.getChain(chainId).then(setChain);
133
+ }, [chaindata?.generation]);
134
+ return chain;
135
+ }
136
+ function useEvmNetworks(chaindata) {
137
+ const [evmNetworks, setEvmNetworks] = react.useState();
138
+ react.useEffect(() => {
139
+ if (!chaindata) return;
140
+ const thisGeneration = chaindata.generation;
141
+ chaindata.evmNetworks().then(evmNetworks => {
142
+ if (thisGeneration !== chaindata.generation) return;
143
+ setEvmNetworks(evmNetworks);
144
+ });
145
+ }, [chaindata?.generation]);
146
+ return evmNetworks || {};
147
+ }
148
+ function useEvmNetwork(chaindata, evmNetworkId) {
149
+ const [evmNetwork, setEvmNetwork] = react.useState();
150
+ react.useEffect(() => {
151
+ if (chaindata === null) return;
152
+ if (!evmNetworkId) return;
153
+ chaindata.getEvmNetwork(evmNetworkId).then(setEvmNetwork);
154
+ }, [chaindata?.generation]);
155
+ return evmNetwork;
156
+ }
157
+ function useTokens(chaindata) {
158
+ const [tokens, setTokens] = react.useState();
159
+ react.useEffect(() => {
160
+ if (!chaindata) return;
161
+ const thisGeneration = chaindata.generation;
162
+ chaindata.tokens().then(tokens => {
163
+ if (thisGeneration !== chaindata.generation) return;
164
+ setTokens(tokens);
165
+ });
166
+ }, [chaindata?.generation]);
167
+ return tokens || {};
168
+ }
169
+ function useToken(chaindata, tokenId) {
170
+ const [token, setToken] = react.useState();
171
+ react.useEffect(() => {
172
+ if (chaindata === null) return;
173
+ if (!tokenId) return;
174
+ chaindata.getToken(tokenId).then(setToken);
175
+ }, [chaindata?.generation]);
176
+ return token;
177
+ }
178
+
179
+ function useTokenRates(tokens) {
180
+ const generation = react.useRef(0);
181
+ const [tokenRates$1, setTokenRates] = react.useState();
182
+ react.useEffect(() => {
183
+ if (!tokens) return;
184
+ if (Object.keys(tokens).length < 1) return;
185
+
186
+ // when we make a new request, we want to ignore any old requests which haven't yet completed
187
+ // otherwise we risk replacing the most recent data with older data
188
+ generation.current = (generation.current + 1) % Number.MAX_SAFE_INTEGER;
189
+ const thisGeneration = generation.current;
190
+ tokenRates.fetchTokenRates(tokens).then(tokenRates => {
191
+ if (thisGeneration !== generation.current) return;
192
+ setTokenRates(tokenRates);
193
+ });
194
+ }, [tokens]);
195
+ return tokenRates$1 || {};
196
+ }
197
+
198
+ // TODO: Add the equivalent functionalty of `useDbCache` directly to this library.
199
+ //
200
+ // How it will work:
201
+ //
202
+ // useChains/useEvmNetworks/useTokens/useTokenRates will all make use of a
203
+ // useCachedDb hook, which internally subscribes to all of the db tables
204
+ // for everything, and then filters the subscribed data based on what params
205
+ // the caller of useChains/useTokens/etc has provided.
206
+ function useBalances(
207
+ // TODO: Make this array of BalanceModules more type-safe
208
+ balanceModules, chaindataProvider, addressesByToken) {
209
+ useBalancesSubscriptions(balanceModules, chaindataProvider, addressesByToken);
210
+ const chains = useChains(chaindataProvider);
211
+ const evmNetworks = useEvmNetworks(chaindataProvider);
212
+ const tokens = useTokens(chaindataProvider);
213
+ const tokenRates = useTokenRates(tokens);
214
+ const balances$1 = dexieReactHooks.useLiveQuery(async () => new balances.Balances(await balances.db.balances.filter(balance => {
215
+ // check that this balance is included in our queried balance modules
216
+ if (!balanceModules.map(({
217
+ type
218
+ }) => type).includes(balance.source)) return false;
219
+
220
+ // check that our query includes some tokens and addresses
221
+ if (!addressesByToken) return false;
222
+
223
+ // check that this balance is included in our queried tokens
224
+ if (!Object.keys(addressesByToken).includes(balance.tokenId)) return false;
225
+
226
+ // check that this balance is included in our queried addresses for this token
227
+ if (!addressesByToken[balance.tokenId].includes(balance.address)) return false;
228
+
229
+ // keep this balance
230
+ return true;
231
+ }).toArray(),
232
+ // hydrate balance chains, evmNetworks, tokens and tokenRates
233
+ {
234
+ chains,
235
+ evmNetworks,
236
+ tokens,
237
+ tokenRates
238
+ }), [balanceModules, addressesByToken, chains, evmNetworks, tokens, tokenRates]);
239
+
240
+ // debounce every 100ms to prevent hammering UI with updates
241
+ const [debouncedBalances, setDebouncedBalances] = react.useState(balances$1);
242
+ reactUse.useDebounce(() => balances$1 && setDebouncedBalances(balances$1), 100, [balances$1]);
243
+ return debouncedBalances;
244
+ }
245
+
246
+ // TODO: Turn into react context
247
+ const subscriptions = {};
248
+
249
+ // This hook is responsible for allowing us to call useBalances
250
+ // from multiple components, without setting up unnecessary
251
+ // balance subscriptions
252
+ function useBalancesSubscriptions(
253
+ // TODO: Make this array of BalanceModules more type-safe
254
+ balanceModules, chaindataProvider, addressesByToken) {
255
+ // const subscriptions = useRef<
256
+ // Record<string, { unsub: Promise<() => void>; refcount: number; generation: number }>
257
+ // >({})
258
+
259
+ const addSubscription = (key, balanceModule, chainConnectors, chaindataProvider, addressesByToken) => {
260
+ // create subscription if it doesn't already exist
261
+ if (!subscriptions[key] || subscriptions[key].refcount === 0) {
262
+ const generation = ((subscriptions[key]?.generation || 0) + 1) % Number.MAX_SAFE_INTEGER;
263
+ const unsub = balances.balances(balanceModule, chainConnectors, chaindataProvider, addressesByToken, (error, balances$1) => {
264
+ if (error) return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
265
+ if (!balances$1) return;
266
+
267
+ // ignore balances from old subscriptions which are still in the process of unsubscribing
268
+ if (subscriptions[key].generation !== generation) return;
269
+ const putBalances = Object.entries(balances$1.toJSON()).map(([id, balance]) => ({
270
+ id,
271
+ ...balance
272
+ }));
273
+ balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.bulkPut(putBalances));
274
+ });
275
+ subscriptions[key] = {
276
+ unsub,
277
+ refcount: 0,
278
+ generation
279
+ };
280
+ }
281
+
282
+ // bump up the refcount by 1
283
+ subscriptions[key].refcount += 1;
284
+ };
285
+ const removeSubscription = (key, balanceModule, addressesByToken) => {
286
+ // ignore dead subscriptions
287
+ if (!subscriptions[key] || subscriptions[key].refcount === 0) return;
288
+
289
+ // drop the refcount by one
290
+ subscriptions[key].refcount -= 1;
291
+
292
+ // unsubscribe if refcount is now 0 (nobody wants this subcription anymore)
293
+ if (subscriptions[key].refcount < 1) {
294
+ // remove subscription
295
+ subscriptions[key].unsub.then(unsub => unsub());
296
+ delete subscriptions[key];
297
+
298
+ // set this subscription's balances in the store to status: cache
299
+ balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.filter(balance => {
300
+ if (balance.source !== balanceModule.type) return false;
301
+ if (!Object.keys(addressesByToken).includes(balance.tokenId)) return false;
302
+ if (!addressesByToken[balance.tokenId].includes(balance.address)) return false;
303
+ return true;
304
+ }).modify({
305
+ status: "cache"
306
+ }));
307
+ }
308
+ };
309
+ const chainConnector = useChainConnector(chaindataProvider);
310
+ const chainConnectorEvm = useChainConnectorEvm(chaindataProvider);
311
+ const tokens = useTokens(chaindataProvider);
312
+ react.useEffect(() => {
313
+ if (chainConnector === null) return;
314
+ if (chainConnectorEvm === null) return;
315
+ if (chaindataProvider === null) return;
316
+ if (addressesByToken === null) return;
317
+ const unsubs = balanceModules.map(balanceModule => {
318
+ const subscriptionKey = `${balanceModule.type}-${JSON.stringify(addressesByToken)}`;
319
+
320
+ // filter out tokens to only include those which this module knows how to fetch balances for
321
+ const moduleTokenIds = Object.values(tokens).filter(({
322
+ type
323
+ }) => type === balanceModule.type).map(({
324
+ id
325
+ }) => id);
326
+ const addressesByModuleToken = Object.fromEntries(Object.entries(addressesByToken).filter(([tokenId]) => moduleTokenIds.includes(tokenId)));
327
+
328
+ // add balance subscription for this module
329
+ addSubscription(subscriptionKey, balanceModule, {
330
+ substrate: chainConnector,
331
+ evm: chainConnectorEvm
332
+ }, chaindataProvider, addressesByModuleToken);
333
+
334
+ // return an unsub method, to be called when this effect unmounts
335
+ return () => removeSubscription(subscriptionKey, balanceModule, addressesByToken);
336
+ });
337
+ const unsubAll = () => unsubs.forEach(unsub => unsub());
338
+ return unsubAll;
339
+ }, [addressesByToken, chainConnector, chainConnectorEvm, chaindataProvider, tokens]);
340
+ }
341
+
342
+ // TODO: Allow advanced users of this library to provide their own chain connector
343
+ function useChainConnector(chaindataProvider) {
344
+ const [chainConnector$1, setChainConnector] = react.useState(null);
345
+ react.useEffect(() => {
346
+ if (chaindataProvider === null) return;
347
+ setChainConnector(new chainConnector.ChainConnector(chaindataProvider));
348
+ }, [chaindataProvider]);
349
+ return chainConnector$1;
350
+ }
351
+ // TODO: Allow advanced users of this library to provide their own chain connector
352
+ function useChainConnectorEvm(chaindataProvider) {
353
+ const [chainConnectorEvm$1, setChainConnectorEvm] = react.useState(null);
354
+ react.useEffect(() => {
355
+ if (chaindataProvider === null) return;
356
+ setChainConnectorEvm(new chainConnectorEvm.ChainConnectorEvm(chaindataProvider));
357
+ }, [chaindataProvider]);
358
+ return chainConnectorEvm$1;
359
+ }
360
+
361
+ exports.useBalances = useBalances;
362
+ exports.useChain = useChain;
363
+ exports.useChaindata = useChaindata;
364
+ exports.useChains = useChains;
365
+ exports.useEvmNetwork = useEvmNetwork;
366
+ exports.useEvmNetworks = useEvmNetworks;
367
+ exports.useToken = useToken;
368
+ exports.useTokenRates = useTokenRates;
369
+ exports.useTokens = useTokens;
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ if (process.env.NODE_ENV === "production") {
4
+ module.exports = require("./talismn-balances-react.cjs.prod.js");
5
+ } else {
6
+ module.exports = require("./talismn-balances-react.cjs.dev.js");
7
+ }