@talismn/balances-react 0.3.0 → 0.3.2

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,26 @@
1
1
  # @talismn/balances-react
2
2
 
3
+ ## 0.3.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 62d7a783: fix: allow @talismn/balances-react users to specify onfinality api key
8
+ - @talismn/balances@0.3.2
9
+
10
+ ## 0.3.1
11
+
12
+ ### Patch Changes
13
+
14
+ - 8adc7f06: feat: switched build tool to preconstruct
15
+ - Updated dependencies [8adc7f06]
16
+ - Updated dependencies [cfe8d276]
17
+ - @talismn/chaindata-provider-extension@0.4.1
18
+ - @talismn/chain-connector-evm@0.4.1
19
+ - @talismn/chaindata-provider@0.4.1
20
+ - @talismn/chain-connector@0.4.1
21
+ - @talismn/token-rates@0.1.13
22
+ - @talismn/balances@0.3.1
23
+
3
24
  ## 0.3.0
4
25
 
5
26
  ### Patch Changes
@@ -0,0 +1,6 @@
1
+ import { AddressesByToken, BalanceModule } from "@talismn/balances";
2
+ import { ChaindataProvider, Token } from "@talismn/chaindata-provider";
3
+ export type Options = {
4
+ onfinalityApiKey?: string;
5
+ };
6
+ export declare function useBalances(balanceModules: Array<BalanceModule<any, any, any, any>>, chaindataProvider: ChaindataProvider | null, addressesByToken: AddressesByToken<Token> | null, options?: Options): any;
@@ -1,5 +1,8 @@
1
1
  import { Chain, ChainId, ChainList, ChaindataProvider, EvmNetwork, EvmNetworkId, EvmNetworkList, Token, TokenId, TokenList } from "@talismn/chaindata-provider";
2
- export declare function useChaindata(): (ChaindataProvider & {
2
+ export type Options = {
3
+ onfinalityApiKey?: string;
4
+ };
5
+ export declare function useChaindata(options?: Options): (ChaindataProvider & {
3
6
  generation?: number | undefined;
4
7
  }) | null;
5
8
  export declare function useChains(chaindata: (ChaindataProvider & {
File without changes
File without changes
@@ -0,0 +1 @@
1
+ export * from "./declarations/src/index";
@@ -0,0 +1,372 @@
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.2",
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
+ function useChaindata(options = {}) {
85
+ const [chaindataProvider, setChaindataProvider] = react.useState(null);
86
+
87
+ // this number is incremented each time the chaindataProvider has fetched new data
88
+ const [generation, setGeneration] = react.useState(0);
89
+ react.useEffect(() => {
90
+ const chaindataProvider = new chaindataProviderExtension.ChaindataProviderExtension({
91
+ onfinalityApiKey: options.onfinalityApiKey
92
+ });
93
+ let shouldHydrate = true;
94
+ const timer = 300_000; // 300_000ms = 300s = 5 minutes
95
+ const hydrate = async () => {
96
+ if (!shouldHydrate) return;
97
+ try {
98
+ const updated = await chaindataProvider.hydrate();
99
+ if (updated) setGeneration(generation => (generation + 1) % Number.MAX_SAFE_INTEGER);
100
+ setTimeout(hydrate, timer);
101
+ } catch (error) {
102
+ const retryTimeout = 5_000; // 5_000ms = 5 seconds
103
+ log.error(`Failed to fetch chaindata, retrying in ${Math.round(retryTimeout / 1000)} seconds`, error);
104
+ setTimeout(hydrate, retryTimeout);
105
+ }
106
+ };
107
+ setChaindataProvider(chaindataProvider);
108
+ hydrate();
109
+ return () => {
110
+ shouldHydrate = false;
111
+ };
112
+ }, [options.onfinalityApiKey]);
113
+ if (chaindataProvider) chaindataProvider.generation = generation;
114
+ return chaindataProvider;
115
+ }
116
+ function useChains(chaindata) {
117
+ const [chains, setChains] = react.useState();
118
+ react.useEffect(() => {
119
+ if (!chaindata) return;
120
+ const thisGeneration = chaindata.generation;
121
+ chaindata.chains().then(chains => {
122
+ if (thisGeneration !== chaindata.generation) return;
123
+ setChains(chains);
124
+ });
125
+ }, [chaindata, chaindata?.generation]);
126
+ return chains || {};
127
+ }
128
+ function useChain(chaindata, chainId) {
129
+ const [chain, setChain] = react.useState();
130
+ react.useEffect(() => {
131
+ if (chaindata === null) return;
132
+ if (!chainId) return;
133
+ chaindata.getChain(chainId).then(setChain);
134
+ }, [chainId, chaindata, chaindata?.generation]);
135
+ return chain;
136
+ }
137
+ function useEvmNetworks(chaindata) {
138
+ const [evmNetworks, setEvmNetworks] = react.useState();
139
+ react.useEffect(() => {
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?.generation]);
147
+ return evmNetworks || {};
148
+ }
149
+ function useEvmNetwork(chaindata, evmNetworkId) {
150
+ const [evmNetwork, setEvmNetwork] = react.useState();
151
+ react.useEffect(() => {
152
+ if (chaindata === null) return;
153
+ if (!evmNetworkId) return;
154
+ chaindata.getEvmNetwork(evmNetworkId).then(setEvmNetwork);
155
+ }, [chaindata, chaindata?.generation, evmNetworkId]);
156
+ return evmNetwork;
157
+ }
158
+ function useTokens(chaindata) {
159
+ const [tokens, setTokens] = react.useState();
160
+ react.useEffect(() => {
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?.generation]);
168
+ return tokens || {};
169
+ }
170
+ function useToken(chaindata, tokenId) {
171
+ const [token, setToken] = react.useState();
172
+ react.useEffect(() => {
173
+ if (chaindata === null) return;
174
+ if (!tokenId) return;
175
+ chaindata.getToken(tokenId).then(setToken);
176
+ }, [chaindata, chaindata?.generation, tokenId]);
177
+ return token;
178
+ }
179
+
180
+ function useTokenRates(tokens) {
181
+ const generation = react.useRef(0);
182
+ const [tokenRates$1, setTokenRates] = react.useState();
183
+ react.useEffect(() => {
184
+ if (!tokens) return;
185
+ if (Object.keys(tokens).length < 1) return;
186
+
187
+ // when we make a new request, we want to ignore any old requests which haven't yet completed
188
+ // otherwise we risk replacing the most recent data with older data
189
+ generation.current = (generation.current + 1) % Number.MAX_SAFE_INTEGER;
190
+ const thisGeneration = generation.current;
191
+ tokenRates.fetchTokenRates(tokens).then(tokenRates => {
192
+ if (thisGeneration !== generation.current) return;
193
+ setTokenRates(tokenRates);
194
+ });
195
+ }, [tokens]);
196
+ return tokenRates$1 || {};
197
+ }
198
+
199
+ // TODO: Add the equivalent functionalty of `useDbCache` directly to this library.
200
+ //
201
+ // How it will work:
202
+ //
203
+ // useChains/useEvmNetworks/useTokens/useTokenRates will all make use of a
204
+ // useCachedDb hook, which internally subscribes to all of the db tables
205
+ // for everything, and then filters the subscribed data based on what params
206
+ // the caller of useChains/useTokens/etc has provided.
207
+ function useBalances(
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 => {
216
+ // check that this balance is included in our queried balance modules
217
+ if (!balanceModules.map(({
218
+ type
219
+ }) => type).includes(balance.source)) return false;
220
+
221
+ // check that our query includes some tokens and addresses
222
+ if (!addressesByToken) return false;
223
+
224
+ // check that this balance is included in our queried tokens
225
+ if (!Object.keys(addressesByToken).includes(balance.tokenId)) return false;
226
+
227
+ // check that this balance is included in our queried addresses for this token
228
+ if (!addressesByToken[balance.tokenId].includes(balance.address)) return false;
229
+
230
+ // keep this balance
231
+ return true;
232
+ }).toArray(),
233
+ // 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
+ const generation = ((subscriptions[key]?.generation || 0) + 1) % Number.MAX_SAFE_INTEGER;
264
+ const unsub = balances.balances(balanceModule, chainConnectors, chaindataProvider, addressesByToken, (error, balances$1) => {
265
+ if (error) return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
266
+ if (!balances$1) return;
267
+
268
+ // ignore balances from old subscriptions which are still in the process of unsubscribing
269
+ if (subscriptions[key].generation !== generation) return;
270
+ const putBalances = Object.entries(balances$1.toJSON()).map(([id, balance]) => ({
271
+ id,
272
+ ...balance
273
+ }));
274
+ balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.bulkPut(putBalances));
275
+ });
276
+ subscriptions[key] = {
277
+ unsub,
278
+ refcount: 0,
279
+ generation
280
+ };
281
+ }
282
+
283
+ // bump up the refcount by 1
284
+ subscriptions[key].refcount += 1;
285
+ };
286
+ const removeSubscription = (key, balanceModule, addressesByToken) => {
287
+ // ignore dead subscriptions
288
+ if (!subscriptions[key] || subscriptions[key].refcount === 0) return;
289
+
290
+ // drop the refcount by one
291
+ subscriptions[key].refcount -= 1;
292
+
293
+ // unsubscribe if refcount is now 0 (nobody wants this subcription anymore)
294
+ if (subscriptions[key].refcount < 1) {
295
+ // remove subscription
296
+ subscriptions[key].unsub.then(unsub => unsub());
297
+ delete subscriptions[key];
298
+
299
+ // set this subscription's balances in the store to status: cache
300
+ balances.db.transaction("rw", balances.db.balances, async () => await balances.db.balances.filter(balance => {
301
+ if (balance.source !== balanceModule.type) return false;
302
+ if (!Object.keys(addressesByToken).includes(balance.tokenId)) return false;
303
+ if (!addressesByToken[balance.tokenId].includes(balance.address)) return false;
304
+ return true;
305
+ }).modify({
306
+ status: "cache"
307
+ }));
308
+ }
309
+ };
310
+ const chainConnector = useChainConnector(chaindataProvider);
311
+ const chainConnectorEvm = useChainConnectorEvm(chaindataProvider, options);
312
+ const tokens = useTokens(chaindataProvider);
313
+ react.useEffect(() => {
314
+ if (chainConnector === null) return;
315
+ if (chainConnectorEvm === null) return;
316
+ if (chaindataProvider === null) return;
317
+ if (addressesByToken === null) return;
318
+ const unsubs = balanceModules.map(balanceModule => {
319
+ const subscriptionKey = `${balanceModule.type}-${JSON.stringify(addressesByToken)}`;
320
+
321
+ // filter out tokens to only include those which this module knows how to fetch balances for
322
+ const moduleTokenIds = Object.values(tokens).filter(({
323
+ type
324
+ }) => type === balanceModule.type).map(({
325
+ id
326
+ }) => id);
327
+ const addressesByModuleToken = Object.fromEntries(Object.entries(addressesByToken).filter(([tokenId]) => moduleTokenIds.includes(tokenId)));
328
+
329
+ // add balance subscription for this module
330
+ addSubscription(subscriptionKey, balanceModule, {
331
+ substrate: chainConnector,
332
+ evm: chainConnectorEvm
333
+ }, chaindataProvider, addressesByModuleToken);
334
+
335
+ // return an unsub method, to be called when this effect unmounts
336
+ return () => removeSubscription(subscriptionKey, balanceModule, addressesByToken);
337
+ });
338
+ const unsubAll = () => unsubs.forEach(unsub => unsub());
339
+ return unsubAll;
340
+ }, [addressesByToken, balanceModules, chainConnector, chainConnectorEvm, chaindataProvider, tokens]);
341
+ }
342
+
343
+ // TODO: Allow advanced users of this library to provide their own chain connector
344
+ function useChainConnector(chaindataProvider) {
345
+ const [chainConnector$1, setChainConnector] = react.useState(null);
346
+ react.useEffect(() => {
347
+ if (chaindataProvider === null) return;
348
+ setChainConnector(new chainConnector.ChainConnector(chaindataProvider));
349
+ }, [chaindataProvider]);
350
+ return chainConnector$1;
351
+ }
352
+ // TODO: Allow advanced users of this library to provide their own chain connector
353
+ function useChainConnectorEvm(chaindataProvider, options = {}) {
354
+ const [chainConnectorEvm$1, setChainConnectorEvm] = react.useState(null);
355
+ react.useEffect(() => {
356
+ if (chaindataProvider === null) return;
357
+ setChainConnectorEvm(new chainConnectorEvm.ChainConnectorEvm(chaindataProvider, {
358
+ onfinalityApiKey: options.onfinalityApiKey
359
+ }));
360
+ }, [chaindataProvider, options.onfinalityApiKey]);
361
+ return chainConnectorEvm$1;
362
+ }
363
+
364
+ exports.useBalances = useBalances;
365
+ exports.useChain = useChain;
366
+ exports.useChaindata = useChaindata;
367
+ exports.useChains = useChains;
368
+ exports.useEvmNetwork = useEvmNetwork;
369
+ exports.useEvmNetworks = useEvmNetworks;
370
+ exports.useToken = useToken;
371
+ exports.useTokenRates = useTokenRates;
372
+ 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
+ }