@talismn/balances-react 1.3.1 → 1.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/dist/{declarations/src/hooks/useChaindata.d.ts → index.d.mts} +1066 -9
- package/dist/index.d.ts +2297 -0
- package/dist/index.js +472 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +413 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +29 -28
- package/dist/declarations/src/atoms/allAddresses.d.ts +0 -4
- package/dist/declarations/src/atoms/balances.d.ts +0 -2
- package/dist/declarations/src/atoms/balancesProvider.d.ts +0 -2
- package/dist/declarations/src/atoms/chainConnectors.d.ts +0 -2
- package/dist/declarations/src/atoms/chaindata.d.ts +0 -930
- package/dist/declarations/src/atoms/chaindataProvider.d.ts +0 -3
- package/dist/declarations/src/atoms/config.d.ts +0 -11
- package/dist/declarations/src/atoms/cryptoWaitReady.d.ts +0 -1
- package/dist/declarations/src/atoms/tokenRates.d.ts +0 -1
- package/dist/declarations/src/hooks/useBalances.d.ts +0 -26
- package/dist/declarations/src/hooks/useChainConnectors.d.ts +0 -1
- package/dist/declarations/src/hooks/useTokenRates.d.ts +0 -3
- package/dist/declarations/src/index.d.ts +0 -68
- package/dist/declarations/src/log.d.ts +0 -2
- package/dist/talismn-balances-react.cjs.d.ts +0 -1
- package/dist/talismn-balances-react.cjs.dev.js +0 -351
- package/dist/talismn-balances-react.cjs.js +0 -7
- package/dist/talismn-balances-react.cjs.prod.js +0 -351
- package/dist/talismn-balances-react.esm.js +0 -293
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var jotai = require('jotai');
|
|
4
|
-
var react = require('react');
|
|
5
|
-
var tokenRates = require('@talismn/token-rates');
|
|
6
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
-
var chaindataProvider = require('@talismn/chaindata-provider');
|
|
8
|
-
var balances = require('@talismn/balances');
|
|
9
|
-
var jotaiEffect = require('jotai-effect');
|
|
10
|
-
var lodashEs = require('lodash-es');
|
|
11
|
-
var chainConnectors = require('@talismn/chain-connectors');
|
|
12
|
-
var connectionMeta = require('@talismn/connection-meta');
|
|
13
|
-
var util = require('@talismn/util');
|
|
14
|
-
var utils = require('jotai/utils');
|
|
15
|
-
var rxjs = require('rxjs');
|
|
16
|
-
var anylogger = require('anylogger');
|
|
17
|
-
var utilCrypto = require('@polkadot/util-crypto');
|
|
18
|
-
|
|
19
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
20
|
-
|
|
21
|
-
var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
|
|
22
|
-
|
|
23
|
-
const innerCoinsApiConfigAtom = jotai.atom(tokenRates.DEFAULT_COINSAPI_CONFIG);
|
|
24
|
-
const coinsApiConfigAtom = jotai.atom(get => get(innerCoinsApiConfigAtom), (_get, set, options) => set(innerCoinsApiConfigAtom, {
|
|
25
|
-
apiUrl: options.apiUrl ?? tokenRates.DEFAULT_COINSAPI_CONFIG.apiUrl
|
|
26
|
-
}));
|
|
27
|
-
const enableTestnetsAtom = jotai.atom(false);
|
|
28
|
-
const enabledChainsAtom = jotai.atom(undefined);
|
|
29
|
-
const enabledTokensAtom = jotai.atom(undefined);
|
|
30
|
-
|
|
31
|
-
/** Sets the list of addresses for which token balances will be fetched by the balances subscription */
|
|
32
|
-
const allAddressesAtom = jotai.atom([]);
|
|
33
|
-
|
|
34
|
-
const chaindataProviderAtom = jotai.atom(() => {
|
|
35
|
-
return new chaindataProvider.ChaindataProvider({
|
|
36
|
-
// TODO pass persistedStorage
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
const useSyncSwapsChaindata = () => jotai.useAtom(syncSwapsChaindataAtomEffect);
|
|
40
|
-
const syncSwapsChaindataAtomEffect = jotaiEffect.atomEffect(get => {
|
|
41
|
-
const chaindataProvider = get(chaindataProviderAtom);
|
|
42
|
-
|
|
43
|
-
// keep subscription open when swaps modal is open
|
|
44
|
-
const subscription = chaindataProvider.networks$.subscribe();
|
|
45
|
-
|
|
46
|
-
// close susbcription when swaps modal closes
|
|
47
|
-
return () => subscription.unsubscribe();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const chainConnectorsAtom = jotai.atom(get => {
|
|
51
|
-
const chaindataProvider = get(chaindataProviderAtom);
|
|
52
|
-
const substrate = new chainConnectors.ChainConnectorDot(chaindataProvider, connectionMeta.connectionMetaDb);
|
|
53
|
-
const evm = new chainConnectors.ChainConnectorEth(chaindataProvider);
|
|
54
|
-
const solana = new chainConnectors.ChainConnectorSol(chaindataProvider);
|
|
55
|
-
return {
|
|
56
|
-
substrate,
|
|
57
|
-
evm,
|
|
58
|
-
solana
|
|
59
|
-
};
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const balancesProviderAtom = jotai.atom(get => {
|
|
63
|
-
return new balances.BalancesProvider(get(chaindataProviderAtom), get(chainConnectorsAtom)) // TODO pass storage
|
|
64
|
-
;
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const chaindataAtom = utils.atomWithObservable(get => {
|
|
68
|
-
return rxjs.combineLatest({
|
|
69
|
-
networks: get(chaindataProviderAtom).networks$,
|
|
70
|
-
tokens: get(chaindataProviderAtom).tokens$
|
|
71
|
-
}).pipe(util.firstThenDebounce(1_000));
|
|
72
|
-
});
|
|
73
|
-
const filteredChaindataAtom = jotai.atom(async get => {
|
|
74
|
-
const enabledNetworkIds = get(enabledChainsAtom);
|
|
75
|
-
const enabledTokenIds = get(enabledTokensAtom);
|
|
76
|
-
const enableTestnets = get(enableTestnetsAtom);
|
|
77
|
-
const chaindata = await get(chaindataAtom);
|
|
78
|
-
const networks = chaindata.networks.filter(n => (enabledNetworkIds?.includes(n.id) || n.isDefault) && (enableTestnets || !n.isTestnet));
|
|
79
|
-
const networkById = lodashEs.keyBy(networks, n => n.id);
|
|
80
|
-
const tokens = chaindata.tokens.filter(token => (enabledTokenIds?.includes(token.id) || token.isDefault) && networkById[token.networkId]);
|
|
81
|
-
return {
|
|
82
|
-
networks,
|
|
83
|
-
tokens
|
|
84
|
-
};
|
|
85
|
-
});
|
|
86
|
-
const tokensAtom = jotai.atom(async get => {
|
|
87
|
-
const chaindata = await get(filteredChaindataAtom);
|
|
88
|
-
return chaindata.tokens;
|
|
89
|
-
});
|
|
90
|
-
const networksAtom = jotai.atom(async get => {
|
|
91
|
-
const chaindata = await get(filteredChaindataAtom);
|
|
92
|
-
return chaindata.networks;
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
var packageJson = {
|
|
96
|
-
name: "@talismn/balances-react"};
|
|
97
|
-
|
|
98
|
-
var log = anylogger__default.default(packageJson.name);
|
|
99
|
-
|
|
100
|
-
const tokenRatesAtom = jotai.atom(async get => {
|
|
101
|
-
// runs a timer to keep tokenRates up to date
|
|
102
|
-
get(tokenRatesFetcherAtomEffect);
|
|
103
|
-
return (await get(tokenRatesDbAtom)).tokenRates;
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// TODO: Persist to storage
|
|
107
|
-
const tokenRates$ = new rxjs.ReplaySubject(1);
|
|
108
|
-
const tokenRatesDbAtom = utils.atomWithObservable(() => {
|
|
109
|
-
tokenRates.tryToDeleteOldTokenRatesDb();
|
|
110
|
-
return tokenRates$.asObservable();
|
|
111
|
-
});
|
|
112
|
-
const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
|
|
113
|
-
// lets us tear down the existing timer when the effect is restarted
|
|
114
|
-
const abort = new AbortController();
|
|
115
|
-
|
|
116
|
-
// we have to get these synchronously so that jotai knows to restart our timer when they change
|
|
117
|
-
const coinsApiConfig = get(coinsApiConfigAtom);
|
|
118
|
-
const tokensPromise = get(tokensAtom);
|
|
119
|
-
(async () => {
|
|
120
|
-
const tokensById = lodashEs.keyBy(await tokensPromise, "id");
|
|
121
|
-
const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
|
|
122
|
-
const retryTimeout = 5_000; // 5_000ms = 5 seconds
|
|
123
|
-
|
|
124
|
-
const hydrate = async () => {
|
|
125
|
-
try {
|
|
126
|
-
if (abort.signal.aborted) return; // don't fetch if aborted
|
|
127
|
-
const tokenRates$1 = await tokenRates.fetchTokenRates(tokensById, tokenRates.ALL_CURRENCY_IDS, coinsApiConfig);
|
|
128
|
-
const putTokenRates = {
|
|
129
|
-
tokenRates: tokenRates$1
|
|
130
|
-
};
|
|
131
|
-
if (abort.signal.aborted) return; // don't insert into db if aborted
|
|
132
|
-
tokenRates$.next(putTokenRates);
|
|
133
|
-
if (abort.signal.aborted) return; // don't schedule next loop if aborted
|
|
134
|
-
setTimeout(hydrate, loopMs);
|
|
135
|
-
} catch (error) {
|
|
136
|
-
const retrying = !abort.signal.aborted;
|
|
137
|
-
const messageParts = ["Failed to fetch tokenRates", retrying && `retrying in ${Math.round(retryTimeout / 1000)} seconds`, !retrying && `giving up (timer no longer needed)`].filter(util.isTruthy);
|
|
138
|
-
log.error(messageParts.join(", "), error);
|
|
139
|
-
if (util.isAbortError(error)) return; // don't schedule retry if aborted
|
|
140
|
-
setTimeout(hydrate, retryTimeout);
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
// launch the loop
|
|
145
|
-
hydrate();
|
|
146
|
-
})();
|
|
147
|
-
return () => abort.abort("Unsubscribed");
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const addressesByTokenIdAtom = jotai.atom(async get => {
|
|
151
|
-
const [tokens, addresses] = await Promise.all([get(tokensAtom), get(allAddressesAtom)]);
|
|
152
|
-
return lodashEs.fromPairs(tokens.map(token => [token.id, addresses]));
|
|
153
|
-
});
|
|
154
|
-
const rawBalancesAtom = jotai.atom([]);
|
|
155
|
-
const subscribeBalancesAtom = jotaiEffect.atomEffect((get, set) => {
|
|
156
|
-
const unsub = (async () => {
|
|
157
|
-
const balancesProvider = get(balancesProviderAtom);
|
|
158
|
-
const addressesByTokenId = await get(addressesByTokenIdAtom);
|
|
159
|
-
const sub = balancesProvider.getBalances$(addressesByTokenId).subscribe(balances => {
|
|
160
|
-
set(rawBalancesAtom, balances.balances);
|
|
161
|
-
});
|
|
162
|
-
return () => {
|
|
163
|
-
return sub.unsubscribe();
|
|
164
|
-
};
|
|
165
|
-
})();
|
|
166
|
-
return () => {
|
|
167
|
-
unsub.then(unsubscribe => unsubscribe());
|
|
168
|
-
};
|
|
169
|
-
});
|
|
170
|
-
const balancesHydrateDataAtom = jotai.atom(async get => {
|
|
171
|
-
const [chaindata, tokenRates] = await Promise.all([get(chaindataAtom), get(tokenRatesAtom)]);
|
|
172
|
-
const networksById = lodashEs.keyBy(chaindata.networks, "id");
|
|
173
|
-
const tokensById = lodashEs.keyBy(chaindata.tokens, "id");
|
|
174
|
-
return {
|
|
175
|
-
networks: networksById,
|
|
176
|
-
tokens: tokensById,
|
|
177
|
-
tokenRates
|
|
178
|
-
};
|
|
179
|
-
});
|
|
180
|
-
const balancesAtom = jotai.atom(async get => {
|
|
181
|
-
// subscribe to balancesProvider getBalance with addressesByTokenIdAtom as param
|
|
182
|
-
get(subscribeBalancesAtom);
|
|
183
|
-
const hydrate = await get(balancesHydrateDataAtom);
|
|
184
|
-
const rawBalances = get(rawBalancesAtom);
|
|
185
|
-
return new balances.Balances(rawBalances, hydrate);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const useSetBalancesAddresses = addresses => {
|
|
189
|
-
const setAllAddresses = jotai.useSetAtom(allAddressesAtom);
|
|
190
|
-
react.useEffect(() => {
|
|
191
|
-
setAllAddresses(a => JSON.stringify(a) === JSON.stringify(addresses) ? a : addresses);
|
|
192
|
-
}, [addresses, setAllAddresses]);
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* @name useBalances
|
|
197
|
-
* @description Hook to get the current balances state.
|
|
198
|
-
* @param persistBackend an optional BalancesPersistBackend backend to use for persisting the balances state. By default, indexedDB is used.
|
|
199
|
-
* @returns a Balances object containing the current balances state.
|
|
200
|
-
*/
|
|
201
|
-
|
|
202
|
-
const useBalances = () => {
|
|
203
|
-
return jotai.useAtomValue(balancesAtom);
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
// TODO: Extract to shared definition between extension and @talismn/balances-react
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Given a collection of `Balances`, this hook returns a `BalancesStatus` summary for the collection.
|
|
210
|
-
*
|
|
211
|
-
* @param balances The collection of balances to get the status from.
|
|
212
|
-
* @returns An instance of `BalancesStatus` which represents the status of the balances collection.
|
|
213
|
-
|
|
214
|
-
*/
|
|
215
|
-
const useBalancesStatus = balances => react.useMemo(() => {
|
|
216
|
-
// stale
|
|
217
|
-
const staleChains = getStaleChains(balances);
|
|
218
|
-
if (staleChains.length > 0) return {
|
|
219
|
-
status: "stale",
|
|
220
|
-
staleChains
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
// fetching
|
|
224
|
-
const hasCachedBalances = balances.each.some(b => b.status === "cache");
|
|
225
|
-
if (hasCachedBalances) return {
|
|
226
|
-
status: "fetching"
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// live
|
|
230
|
-
return {
|
|
231
|
-
status: "live"
|
|
232
|
-
};
|
|
233
|
-
}, [balances]);
|
|
234
|
-
const getStaleChains = balances => [...new Set(balances.each.filter(b => b.status === "stale").map(b => b.network?.name ?? b.networkId ?? "Unknown"))];
|
|
235
|
-
|
|
236
|
-
const useChainConnectors = () => jotai.useAtomValue(chainConnectorsAtom);
|
|
237
|
-
|
|
238
|
-
const useChaindataProvider = () => jotai.useAtomValue(chaindataProviderAtom);
|
|
239
|
-
const useChaindata = () => jotai.useAtomValue(chaindataAtom);
|
|
240
|
-
const useNetworks = () => useChaindata().networks;
|
|
241
|
-
const useNetworksById = () => {
|
|
242
|
-
const {
|
|
243
|
-
networks
|
|
244
|
-
} = useChaindata();
|
|
245
|
-
return react.useMemo(() => lodashEs.keyBy(networks, n => n.id), [networks]);
|
|
246
|
-
};
|
|
247
|
-
const useNetwork = networkId => {
|
|
248
|
-
const networksById = useNetworksById();
|
|
249
|
-
return networksById[networkId ?? ""] ?? null;
|
|
250
|
-
};
|
|
251
|
-
const useTokens = () => useChaindata().tokens;
|
|
252
|
-
const useTokensById = () => {
|
|
253
|
-
const {
|
|
254
|
-
tokens
|
|
255
|
-
} = useChaindata();
|
|
256
|
-
return react.useMemo(() => lodashEs.keyBy(tokens, t => t.id), [tokens]);
|
|
257
|
-
};
|
|
258
|
-
const useToken = tokenId => {
|
|
259
|
-
const tokensById = useTokensById();
|
|
260
|
-
return tokensById[tokenId ?? ""] ?? null;
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
const useTokenRates = () => jotai.useAtomValue(tokenRatesAtom);
|
|
264
|
-
const useTokenRate = tokenId => useTokenRates()[tokenId ?? ""] ?? undefined;
|
|
265
|
-
|
|
266
|
-
const cryptoWaitReadyAtom = jotai.atom(async () => await utilCrypto.cryptoWaitReady());
|
|
267
|
-
|
|
268
|
-
const BalancesProvider = ({
|
|
269
|
-
coinsApiUrl,
|
|
270
|
-
withTestnets,
|
|
271
|
-
enabledChains,
|
|
272
|
-
enabledTokens,
|
|
273
|
-
children
|
|
274
|
-
}) => {
|
|
275
|
-
const setCoinsApiConfig = jotai.useSetAtom(coinsApiConfigAtom);
|
|
276
|
-
react.useEffect(() => {
|
|
277
|
-
setCoinsApiConfig({
|
|
278
|
-
apiUrl: coinsApiUrl
|
|
279
|
-
});
|
|
280
|
-
}, [coinsApiUrl, setCoinsApiConfig]);
|
|
281
|
-
const setEnableTestnets = jotai.useSetAtom(enableTestnetsAtom);
|
|
282
|
-
react.useEffect(() => {
|
|
283
|
-
setEnableTestnets(withTestnets ?? false);
|
|
284
|
-
}, [setEnableTestnets, withTestnets]);
|
|
285
|
-
const setEnabledChains = jotai.useSetAtom(enabledChainsAtom);
|
|
286
|
-
react.useEffect(() => {
|
|
287
|
-
setEnabledChains(enabledChains);
|
|
288
|
-
}, [enabledChains, setEnabledChains]);
|
|
289
|
-
const setEnabledTokens = jotai.useSetAtom(enabledTokensAtom);
|
|
290
|
-
react.useEffect(() => {
|
|
291
|
-
setEnabledTokens(enabledTokens);
|
|
292
|
-
}, [enabledTokens, setEnabledTokens]);
|
|
293
|
-
return /*#__PURE__*/jsxRuntime.jsx(jsxRuntime.Fragment, {
|
|
294
|
-
children: children
|
|
295
|
-
});
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
Object.defineProperty(exports, "evmErc20TokenId", {
|
|
299
|
-
enumerable: true,
|
|
300
|
-
get: function () { return chaindataProvider.evmErc20TokenId; }
|
|
301
|
-
});
|
|
302
|
-
Object.defineProperty(exports, "evmNativeTokenId", {
|
|
303
|
-
enumerable: true,
|
|
304
|
-
get: function () { return chaindataProvider.evmNativeTokenId; }
|
|
305
|
-
});
|
|
306
|
-
Object.defineProperty(exports, "subAssetTokenId", {
|
|
307
|
-
enumerable: true,
|
|
308
|
-
get: function () { return chaindataProvider.subAssetTokenId; }
|
|
309
|
-
});
|
|
310
|
-
Object.defineProperty(exports, "subNativeTokenId", {
|
|
311
|
-
enumerable: true,
|
|
312
|
-
get: function () { return chaindataProvider.subNativeTokenId; }
|
|
313
|
-
});
|
|
314
|
-
Object.defineProperty(exports, "subPsp22TokenId", {
|
|
315
|
-
enumerable: true,
|
|
316
|
-
get: function () { return chaindataProvider.subPsp22TokenId; }
|
|
317
|
-
});
|
|
318
|
-
Object.defineProperty(exports, "subTokensTokenId", {
|
|
319
|
-
enumerable: true,
|
|
320
|
-
get: function () { return chaindataProvider.subTokensTokenId; }
|
|
321
|
-
});
|
|
322
|
-
exports.BalancesProvider = BalancesProvider;
|
|
323
|
-
exports.allAddressesAtom = allAddressesAtom;
|
|
324
|
-
exports.balancesAtom = balancesAtom;
|
|
325
|
-
exports.chainConnectorsAtom = chainConnectorsAtom;
|
|
326
|
-
exports.chaindataAtom = chaindataAtom;
|
|
327
|
-
exports.chaindataProviderAtom = chaindataProviderAtom;
|
|
328
|
-
exports.coinsApiConfigAtom = coinsApiConfigAtom;
|
|
329
|
-
exports.cryptoWaitReadyAtom = cryptoWaitReadyAtom;
|
|
330
|
-
exports.enableTestnetsAtom = enableTestnetsAtom;
|
|
331
|
-
exports.enabledChainsAtom = enabledChainsAtom;
|
|
332
|
-
exports.enabledTokensAtom = enabledTokensAtom;
|
|
333
|
-
exports.getStaleChains = getStaleChains;
|
|
334
|
-
exports.networksAtom = networksAtom;
|
|
335
|
-
exports.tokenRatesAtom = tokenRatesAtom;
|
|
336
|
-
exports.tokensAtom = tokensAtom;
|
|
337
|
-
exports.useBalances = useBalances;
|
|
338
|
-
exports.useBalancesStatus = useBalancesStatus;
|
|
339
|
-
exports.useChainConnectors = useChainConnectors;
|
|
340
|
-
exports.useChaindata = useChaindata;
|
|
341
|
-
exports.useChaindataProvider = useChaindataProvider;
|
|
342
|
-
exports.useNetwork = useNetwork;
|
|
343
|
-
exports.useNetworks = useNetworks;
|
|
344
|
-
exports.useNetworksById = useNetworksById;
|
|
345
|
-
exports.useSetBalancesAddresses = useSetBalancesAddresses;
|
|
346
|
-
exports.useSyncSwapsChaindata = useSyncSwapsChaindata;
|
|
347
|
-
exports.useToken = useToken;
|
|
348
|
-
exports.useTokenRate = useTokenRate;
|
|
349
|
-
exports.useTokenRates = useTokenRates;
|
|
350
|
-
exports.useTokens = useTokens;
|
|
351
|
-
exports.useTokensById = useTokensById;
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
import { atom, useAtom, useSetAtom, useAtomValue } from 'jotai';
|
|
2
|
-
import { useMemo, useEffect } from 'react';
|
|
3
|
-
import { DEFAULT_COINSAPI_CONFIG, tryToDeleteOldTokenRatesDb, fetchTokenRates, ALL_CURRENCY_IDS } from '@talismn/token-rates';
|
|
4
|
-
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
5
|
-
import { ChaindataProvider } from '@talismn/chaindata-provider';
|
|
6
|
-
export { evmErc20TokenId, evmNativeTokenId, subAssetTokenId, subNativeTokenId, subPsp22TokenId, subTokensTokenId } from '@talismn/chaindata-provider';
|
|
7
|
-
import { BalancesProvider as BalancesProvider$1, Balances } from '@talismn/balances';
|
|
8
|
-
import { atomEffect } from 'jotai-effect';
|
|
9
|
-
import { keyBy, fromPairs } from 'lodash-es';
|
|
10
|
-
import { ChainConnectorDot, ChainConnectorEth, ChainConnectorSol } from '@talismn/chain-connectors';
|
|
11
|
-
import { connectionMetaDb } from '@talismn/connection-meta';
|
|
12
|
-
import { firstThenDebounce, isTruthy, isAbortError } from '@talismn/util';
|
|
13
|
-
import { atomWithObservable } from 'jotai/utils';
|
|
14
|
-
import { combineLatest, ReplaySubject } from 'rxjs';
|
|
15
|
-
import anylogger from 'anylogger';
|
|
16
|
-
import { cryptoWaitReady } from '@polkadot/util-crypto';
|
|
17
|
-
|
|
18
|
-
const innerCoinsApiConfigAtom = atom(DEFAULT_COINSAPI_CONFIG);
|
|
19
|
-
const coinsApiConfigAtom = atom(get => get(innerCoinsApiConfigAtom), (_get, set, options) => set(innerCoinsApiConfigAtom, {
|
|
20
|
-
apiUrl: options.apiUrl ?? DEFAULT_COINSAPI_CONFIG.apiUrl
|
|
21
|
-
}));
|
|
22
|
-
const enableTestnetsAtom = atom(false);
|
|
23
|
-
const enabledChainsAtom = atom(undefined);
|
|
24
|
-
const enabledTokensAtom = atom(undefined);
|
|
25
|
-
|
|
26
|
-
/** Sets the list of addresses for which token balances will be fetched by the balances subscription */
|
|
27
|
-
const allAddressesAtom = atom([]);
|
|
28
|
-
|
|
29
|
-
const chaindataProviderAtom = atom(() => {
|
|
30
|
-
return new ChaindataProvider({
|
|
31
|
-
// TODO pass persistedStorage
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
const useSyncSwapsChaindata = () => useAtom(syncSwapsChaindataAtomEffect);
|
|
35
|
-
const syncSwapsChaindataAtomEffect = atomEffect(get => {
|
|
36
|
-
const chaindataProvider = get(chaindataProviderAtom);
|
|
37
|
-
|
|
38
|
-
// keep subscription open when swaps modal is open
|
|
39
|
-
const subscription = chaindataProvider.networks$.subscribe();
|
|
40
|
-
|
|
41
|
-
// close susbcription when swaps modal closes
|
|
42
|
-
return () => subscription.unsubscribe();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const chainConnectorsAtom = atom(get => {
|
|
46
|
-
const chaindataProvider = get(chaindataProviderAtom);
|
|
47
|
-
const substrate = new ChainConnectorDot(chaindataProvider, connectionMetaDb);
|
|
48
|
-
const evm = new ChainConnectorEth(chaindataProvider);
|
|
49
|
-
const solana = new ChainConnectorSol(chaindataProvider);
|
|
50
|
-
return {
|
|
51
|
-
substrate,
|
|
52
|
-
evm,
|
|
53
|
-
solana
|
|
54
|
-
};
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const balancesProviderAtom = atom(get => {
|
|
58
|
-
return new BalancesProvider$1(get(chaindataProviderAtom), get(chainConnectorsAtom)) // TODO pass storage
|
|
59
|
-
;
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const chaindataAtom = atomWithObservable(get => {
|
|
63
|
-
return combineLatest({
|
|
64
|
-
networks: get(chaindataProviderAtom).networks$,
|
|
65
|
-
tokens: get(chaindataProviderAtom).tokens$
|
|
66
|
-
}).pipe(firstThenDebounce(1_000));
|
|
67
|
-
});
|
|
68
|
-
const filteredChaindataAtom = atom(async get => {
|
|
69
|
-
const enabledNetworkIds = get(enabledChainsAtom);
|
|
70
|
-
const enabledTokenIds = get(enabledTokensAtom);
|
|
71
|
-
const enableTestnets = get(enableTestnetsAtom);
|
|
72
|
-
const chaindata = await get(chaindataAtom);
|
|
73
|
-
const networks = chaindata.networks.filter(n => (enabledNetworkIds?.includes(n.id) || n.isDefault) && (enableTestnets || !n.isTestnet));
|
|
74
|
-
const networkById = keyBy(networks, n => n.id);
|
|
75
|
-
const tokens = chaindata.tokens.filter(token => (enabledTokenIds?.includes(token.id) || token.isDefault) && networkById[token.networkId]);
|
|
76
|
-
return {
|
|
77
|
-
networks,
|
|
78
|
-
tokens
|
|
79
|
-
};
|
|
80
|
-
});
|
|
81
|
-
const tokensAtom = atom(async get => {
|
|
82
|
-
const chaindata = await get(filteredChaindataAtom);
|
|
83
|
-
return chaindata.tokens;
|
|
84
|
-
});
|
|
85
|
-
const networksAtom = atom(async get => {
|
|
86
|
-
const chaindata = await get(filteredChaindataAtom);
|
|
87
|
-
return chaindata.networks;
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
var packageJson = {
|
|
91
|
-
name: "@talismn/balances-react"};
|
|
92
|
-
|
|
93
|
-
var log = anylogger(packageJson.name);
|
|
94
|
-
|
|
95
|
-
const tokenRatesAtom = atom(async get => {
|
|
96
|
-
// runs a timer to keep tokenRates up to date
|
|
97
|
-
get(tokenRatesFetcherAtomEffect);
|
|
98
|
-
return (await get(tokenRatesDbAtom)).tokenRates;
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// TODO: Persist to storage
|
|
102
|
-
const tokenRates$ = new ReplaySubject(1);
|
|
103
|
-
const tokenRatesDbAtom = atomWithObservable(() => {
|
|
104
|
-
tryToDeleteOldTokenRatesDb();
|
|
105
|
-
return tokenRates$.asObservable();
|
|
106
|
-
});
|
|
107
|
-
const tokenRatesFetcherAtomEffect = atomEffect(get => {
|
|
108
|
-
// lets us tear down the existing timer when the effect is restarted
|
|
109
|
-
const abort = new AbortController();
|
|
110
|
-
|
|
111
|
-
// we have to get these synchronously so that jotai knows to restart our timer when they change
|
|
112
|
-
const coinsApiConfig = get(coinsApiConfigAtom);
|
|
113
|
-
const tokensPromise = get(tokensAtom);
|
|
114
|
-
(async () => {
|
|
115
|
-
const tokensById = keyBy(await tokensPromise, "id");
|
|
116
|
-
const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
|
|
117
|
-
const retryTimeout = 5_000; // 5_000ms = 5 seconds
|
|
118
|
-
|
|
119
|
-
const hydrate = async () => {
|
|
120
|
-
try {
|
|
121
|
-
if (abort.signal.aborted) return; // don't fetch if aborted
|
|
122
|
-
const tokenRates = await fetchTokenRates(tokensById, ALL_CURRENCY_IDS, coinsApiConfig);
|
|
123
|
-
const putTokenRates = {
|
|
124
|
-
tokenRates
|
|
125
|
-
};
|
|
126
|
-
if (abort.signal.aborted) return; // don't insert into db if aborted
|
|
127
|
-
tokenRates$.next(putTokenRates);
|
|
128
|
-
if (abort.signal.aborted) return; // don't schedule next loop if aborted
|
|
129
|
-
setTimeout(hydrate, loopMs);
|
|
130
|
-
} catch (error) {
|
|
131
|
-
const retrying = !abort.signal.aborted;
|
|
132
|
-
const messageParts = ["Failed to fetch tokenRates", retrying && `retrying in ${Math.round(retryTimeout / 1000)} seconds`, !retrying && `giving up (timer no longer needed)`].filter(isTruthy);
|
|
133
|
-
log.error(messageParts.join(", "), error);
|
|
134
|
-
if (isAbortError(error)) return; // don't schedule retry if aborted
|
|
135
|
-
setTimeout(hydrate, retryTimeout);
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
// launch the loop
|
|
140
|
-
hydrate();
|
|
141
|
-
})();
|
|
142
|
-
return () => abort.abort("Unsubscribed");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const addressesByTokenIdAtom = atom(async get => {
|
|
146
|
-
const [tokens, addresses] = await Promise.all([get(tokensAtom), get(allAddressesAtom)]);
|
|
147
|
-
return fromPairs(tokens.map(token => [token.id, addresses]));
|
|
148
|
-
});
|
|
149
|
-
const rawBalancesAtom = atom([]);
|
|
150
|
-
const subscribeBalancesAtom = atomEffect((get, set) => {
|
|
151
|
-
const unsub = (async () => {
|
|
152
|
-
const balancesProvider = get(balancesProviderAtom);
|
|
153
|
-
const addressesByTokenId = await get(addressesByTokenIdAtom);
|
|
154
|
-
const sub = balancesProvider.getBalances$(addressesByTokenId).subscribe(balances => {
|
|
155
|
-
set(rawBalancesAtom, balances.balances);
|
|
156
|
-
});
|
|
157
|
-
return () => {
|
|
158
|
-
return sub.unsubscribe();
|
|
159
|
-
};
|
|
160
|
-
})();
|
|
161
|
-
return () => {
|
|
162
|
-
unsub.then(unsubscribe => unsubscribe());
|
|
163
|
-
};
|
|
164
|
-
});
|
|
165
|
-
const balancesHydrateDataAtom = atom(async get => {
|
|
166
|
-
const [chaindata, tokenRates] = await Promise.all([get(chaindataAtom), get(tokenRatesAtom)]);
|
|
167
|
-
const networksById = keyBy(chaindata.networks, "id");
|
|
168
|
-
const tokensById = keyBy(chaindata.tokens, "id");
|
|
169
|
-
return {
|
|
170
|
-
networks: networksById,
|
|
171
|
-
tokens: tokensById,
|
|
172
|
-
tokenRates
|
|
173
|
-
};
|
|
174
|
-
});
|
|
175
|
-
const balancesAtom = atom(async get => {
|
|
176
|
-
// subscribe to balancesProvider getBalance with addressesByTokenIdAtom as param
|
|
177
|
-
get(subscribeBalancesAtom);
|
|
178
|
-
const hydrate = await get(balancesHydrateDataAtom);
|
|
179
|
-
const rawBalances = get(rawBalancesAtom);
|
|
180
|
-
return new Balances(rawBalances, hydrate);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
const useSetBalancesAddresses = addresses => {
|
|
184
|
-
const setAllAddresses = useSetAtom(allAddressesAtom);
|
|
185
|
-
useEffect(() => {
|
|
186
|
-
setAllAddresses(a => JSON.stringify(a) === JSON.stringify(addresses) ? a : addresses);
|
|
187
|
-
}, [addresses, setAllAddresses]);
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* @name useBalances
|
|
192
|
-
* @description Hook to get the current balances state.
|
|
193
|
-
* @param persistBackend an optional BalancesPersistBackend backend to use for persisting the balances state. By default, indexedDB is used.
|
|
194
|
-
* @returns a Balances object containing the current balances state.
|
|
195
|
-
*/
|
|
196
|
-
|
|
197
|
-
const useBalances = () => {
|
|
198
|
-
return useAtomValue(balancesAtom);
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
// TODO: Extract to shared definition between extension and @talismn/balances-react
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Given a collection of `Balances`, this hook returns a `BalancesStatus` summary for the collection.
|
|
205
|
-
*
|
|
206
|
-
* @param balances The collection of balances to get the status from.
|
|
207
|
-
* @returns An instance of `BalancesStatus` which represents the status of the balances collection.
|
|
208
|
-
|
|
209
|
-
*/
|
|
210
|
-
const useBalancesStatus = balances => useMemo(() => {
|
|
211
|
-
// stale
|
|
212
|
-
const staleChains = getStaleChains(balances);
|
|
213
|
-
if (staleChains.length > 0) return {
|
|
214
|
-
status: "stale",
|
|
215
|
-
staleChains
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
// fetching
|
|
219
|
-
const hasCachedBalances = balances.each.some(b => b.status === "cache");
|
|
220
|
-
if (hasCachedBalances) return {
|
|
221
|
-
status: "fetching"
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
// live
|
|
225
|
-
return {
|
|
226
|
-
status: "live"
|
|
227
|
-
};
|
|
228
|
-
}, [balances]);
|
|
229
|
-
const getStaleChains = balances => [...new Set(balances.each.filter(b => b.status === "stale").map(b => b.network?.name ?? b.networkId ?? "Unknown"))];
|
|
230
|
-
|
|
231
|
-
const useChainConnectors = () => useAtomValue(chainConnectorsAtom);
|
|
232
|
-
|
|
233
|
-
const useChaindataProvider = () => useAtomValue(chaindataProviderAtom);
|
|
234
|
-
const useChaindata = () => useAtomValue(chaindataAtom);
|
|
235
|
-
const useNetworks = () => useChaindata().networks;
|
|
236
|
-
const useNetworksById = () => {
|
|
237
|
-
const {
|
|
238
|
-
networks
|
|
239
|
-
} = useChaindata();
|
|
240
|
-
return useMemo(() => keyBy(networks, n => n.id), [networks]);
|
|
241
|
-
};
|
|
242
|
-
const useNetwork = networkId => {
|
|
243
|
-
const networksById = useNetworksById();
|
|
244
|
-
return networksById[networkId ?? ""] ?? null;
|
|
245
|
-
};
|
|
246
|
-
const useTokens = () => useChaindata().tokens;
|
|
247
|
-
const useTokensById = () => {
|
|
248
|
-
const {
|
|
249
|
-
tokens
|
|
250
|
-
} = useChaindata();
|
|
251
|
-
return useMemo(() => keyBy(tokens, t => t.id), [tokens]);
|
|
252
|
-
};
|
|
253
|
-
const useToken = tokenId => {
|
|
254
|
-
const tokensById = useTokensById();
|
|
255
|
-
return tokensById[tokenId ?? ""] ?? null;
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
const useTokenRates = () => useAtomValue(tokenRatesAtom);
|
|
259
|
-
const useTokenRate = tokenId => useTokenRates()[tokenId ?? ""] ?? undefined;
|
|
260
|
-
|
|
261
|
-
const cryptoWaitReadyAtom = atom(async () => await cryptoWaitReady());
|
|
262
|
-
|
|
263
|
-
const BalancesProvider = ({
|
|
264
|
-
coinsApiUrl,
|
|
265
|
-
withTestnets,
|
|
266
|
-
enabledChains,
|
|
267
|
-
enabledTokens,
|
|
268
|
-
children
|
|
269
|
-
}) => {
|
|
270
|
-
const setCoinsApiConfig = useSetAtom(coinsApiConfigAtom);
|
|
271
|
-
useEffect(() => {
|
|
272
|
-
setCoinsApiConfig({
|
|
273
|
-
apiUrl: coinsApiUrl
|
|
274
|
-
});
|
|
275
|
-
}, [coinsApiUrl, setCoinsApiConfig]);
|
|
276
|
-
const setEnableTestnets = useSetAtom(enableTestnetsAtom);
|
|
277
|
-
useEffect(() => {
|
|
278
|
-
setEnableTestnets(withTestnets ?? false);
|
|
279
|
-
}, [setEnableTestnets, withTestnets]);
|
|
280
|
-
const setEnabledChains = useSetAtom(enabledChainsAtom);
|
|
281
|
-
useEffect(() => {
|
|
282
|
-
setEnabledChains(enabledChains);
|
|
283
|
-
}, [enabledChains, setEnabledChains]);
|
|
284
|
-
const setEnabledTokens = useSetAtom(enabledTokensAtom);
|
|
285
|
-
useEffect(() => {
|
|
286
|
-
setEnabledTokens(enabledTokens);
|
|
287
|
-
}, [enabledTokens, setEnabledTokens]);
|
|
288
|
-
return /*#__PURE__*/jsx(Fragment, {
|
|
289
|
-
children: children
|
|
290
|
-
});
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
export { BalancesProvider, allAddressesAtom, balancesAtom, chainConnectorsAtom, chaindataAtom, chaindataProviderAtom, coinsApiConfigAtom, cryptoWaitReadyAtom, enableTestnetsAtom, enabledChainsAtom, enabledTokensAtom, getStaleChains, networksAtom, tokenRatesAtom, tokensAtom, useBalances, useBalancesStatus, useChainConnectors, useChaindata, useChaindataProvider, useNetwork, useNetworks, useNetworksById, useSetBalancesAddresses, useSyncSwapsChaindata, useToken, useTokenRate, useTokenRates, useTokens, useTokensById };
|