@talismn/balances-react 0.3.0 → 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 +14 -0
- package/dist/{hooks → declarations/src/hooks}/index.d.ts +0 -0
- package/dist/{hooks → declarations/src/hooks}/useBalances.d.ts +0 -0
- package/dist/{hooks → declarations/src/hooks}/useChaindata.d.ts +0 -0
- package/dist/{hooks → declarations/src/hooks}/useTokenRates.d.ts +0 -0
- package/dist/{index.d.ts → declarations/src/index.d.ts} +0 -0
- package/dist/{log.d.ts → declarations/src/log.d.ts} +0 -0
- package/dist/talismn-balances-react.cjs.d.ts +1 -0
- package/dist/talismn-balances-react.cjs.dev.js +369 -0
- package/dist/talismn-balances-react.cjs.js +7 -0
- package/dist/talismn-balances-react.cjs.prod.js +369 -0
- package/dist/talismn-balances-react.esm.js +353 -0
- package/package.json +12 -16
- package/dist/hooks/index.js +0 -3
- package/dist/hooks/useBalances.js +0 -158
- package/dist/hooks/useChaindata.js +0 -112
- package/dist/hooks/useTokenRates.js +0 -22
- package/dist/index.js +0 -1
- package/dist/log.js +0 -5
@@ -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;
|