@talismn/balances-react 0.9.12 → 1.0.0
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/atoms/balances.d.ts +1 -5
- package/dist/declarations/src/atoms/balancesProvider.d.ts +2 -0
- package/dist/declarations/src/atoms/chaindata.d.ts +708 -31
- package/dist/declarations/src/atoms/chaindataProvider.d.ts +0 -3
- package/dist/declarations/src/atoms/config.d.ts +0 -7
- package/dist/declarations/src/hooks/useBalances.d.ts +1 -2
- package/dist/declarations/src/hooks/useChaindata.d.ts +944 -20
- package/dist/declarations/src/index.d.ts +2 -12
- package/dist/talismn-balances-react.cjs.dev.js +116 -430
- package/dist/talismn-balances-react.cjs.prod.js +116 -430
- package/dist/talismn-balances-react.esm.js +113 -409
- package/package.json +11 -11
- package/dist/declarations/src/atoms/balanceModules.d.ts +0 -2
- package/dist/declarations/src/util/balancesPersist.d.ts +0 -10
@@ -2,30 +2,26 @@
|
|
2
2
|
|
3
3
|
var jotai = require('jotai');
|
4
4
|
var react = require('react');
|
5
|
-
var balances = require('@talismn/balances');
|
6
5
|
var tokenRates = require('@talismn/token-rates');
|
7
6
|
var jsxRuntime = require('react/jsx-runtime');
|
8
|
-
var
|
7
|
+
var chaindataProvider = require('@talismn/chaindata-provider');
|
8
|
+
var balances = require('@talismn/balances');
|
9
9
|
var jotaiEffect = require('jotai-effect');
|
10
|
-
var
|
11
|
-
var lodash = require('lodash');
|
12
|
-
var rxjs = require('rxjs');
|
13
|
-
var anylogger = require('anylogger');
|
10
|
+
var lodashEs = require('lodash-es');
|
14
11
|
var chainConnector = require('@talismn/chain-connector');
|
15
12
|
var chainConnectorEvm = require('@talismn/chain-connector-evm');
|
16
13
|
var connectionMeta = require('@talismn/connection-meta');
|
17
|
-
var
|
18
|
-
var
|
14
|
+
var util = require('@talismn/util');
|
15
|
+
var utils = require('jotai/utils');
|
16
|
+
var rxjs = require('rxjs');
|
19
17
|
var dexie = require('dexie');
|
20
|
-
var
|
18
|
+
var anylogger = require('anylogger');
|
19
|
+
var utilCrypto = require('@polkadot/util-crypto');
|
21
20
|
|
22
21
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
23
22
|
|
24
23
|
var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
|
25
|
-
var isEqual__default = /*#__PURE__*/_interopDefault(isEqual);
|
26
24
|
|
27
|
-
const balanceModuleCreatorsAtom = jotai.atom(balances.defaultBalanceModules);
|
28
|
-
const onfinalityApiKeyAtom = jotai.atom(undefined);
|
29
25
|
const innerCoinsApiConfigAtom = jotai.atom(tokenRates.DEFAULT_COINSAPI_CONFIG);
|
30
26
|
const coinsApiConfigAtom = jotai.atom(get => get(innerCoinsApiConfigAtom), (_get, set, options) => set(innerCoinsApiConfigAtom, {
|
31
27
|
apiUrl: options.apiUrl ?? tokenRates.DEFAULT_COINSAPI_CONFIG.apiUrl
|
@@ -37,137 +33,58 @@ const enabledTokensAtom = jotai.atom(undefined);
|
|
37
33
|
/** Sets the list of addresses for which token balances will be fetched by the balances subscription */
|
38
34
|
const allAddressesAtom = jotai.atom([]);
|
39
35
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
var log = anylogger__default.default(packageJson.name);
|
44
|
-
|
45
|
-
/**
|
46
|
-
// Persistence backend for indexedDB
|
47
|
-
// Add a new backend by implementing the BalancesPersistBackend interface
|
48
|
-
// configureStore can be called with a different indexedDB table
|
49
|
-
*/
|
50
|
-
const {
|
51
|
-
persistData,
|
52
|
-
retrieveData
|
53
|
-
} = balances.configureStore();
|
54
|
-
|
55
|
-
/**
|
56
|
-
// Persistence backend for localStorage
|
57
|
-
*/
|
58
|
-
const localStoragePersist = async balances$1 => {
|
59
|
-
const storedBalances = balances$1.map(b => {
|
60
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
61
|
-
const {
|
62
|
-
status,
|
63
|
-
...rest
|
64
|
-
} = b;
|
65
|
-
return rest;
|
66
|
-
});
|
67
|
-
const deflated = balances.compress(storedBalances);
|
68
|
-
localStorage.setItem("talismanBalances", deflated.toString());
|
69
|
-
};
|
70
|
-
const localStorageRetrieve = async () => {
|
71
|
-
const deflated = localStorage.getItem("talismanBalances");
|
72
|
-
if (deflated) {
|
73
|
-
// deflated will be a long string of numbers separated by commas
|
74
|
-
const deflatedArray = deflated.split(",").map(n => parseInt(n, 10));
|
75
|
-
const deflatedBytes = new Uint8Array(deflatedArray.length);
|
76
|
-
deflatedArray.forEach((n, i) => deflatedBytes[i] = n);
|
77
|
-
return balances.decompress(deflatedBytes).map(b => ({
|
78
|
-
...b,
|
79
|
-
status: "cache"
|
80
|
-
}));
|
81
|
-
}
|
82
|
-
return [];
|
83
|
-
};
|
84
|
-
const localStorageBalancesPersistBackend = {
|
85
|
-
persist: localStoragePersist,
|
86
|
-
retrieve: localStorageRetrieve
|
87
|
-
};
|
88
|
-
|
89
|
-
const cryptoWaitReadyAtom = jotai.atom(async () => await utilCrypto.cryptoWaitReady());
|
90
|
-
|
91
|
-
const chaindataProviderAtom = jotai.atom(get => {
|
92
|
-
// runs a timer to keep chaindata hydrated
|
93
|
-
get(chaindataHydrateAtomEffect);
|
94
|
-
return new chaindataProvider.ChaindataProvider({
|
95
|
-
onfinalityApiKey: get(onfinalityApiKeyAtom)
|
96
|
-
});
|
36
|
+
const chaindataProviderAtom = jotai.atom(() => {
|
37
|
+
return new chaindataProvider.ChaindataProvider({});
|
97
38
|
});
|
98
|
-
const miniMetadataHydratedAtom = jotai.atom(false);
|
99
39
|
|
100
|
-
|
101
|
-
const chaindataHydrateAtomEffect = jotaiEffect.atomEffect((get, set) => {
|
40
|
+
const chainConnectorsAtom = jotai.atom(get => {
|
102
41
|
const chaindataProvider = get(chaindataProviderAtom);
|
103
|
-
const
|
104
|
-
const
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
let timeout = null;
|
109
|
-
const hydrate = async () => {
|
110
|
-
try {
|
111
|
-
await get(cryptoWaitReadyAtom);
|
112
|
-
await balances.hydrateChaindataAndMiniMetadata(chaindataProvider, miniMetadataUpdater);
|
113
|
-
await balances.updateCustomMiniMetadata(chaindataProvider, miniMetadataUpdater);
|
114
|
-
await balances.updateEvmTokens(chaindataProvider, evmTokenFetcher);
|
115
|
-
set(miniMetadataHydratedAtom, true);
|
116
|
-
timeout = setTimeout(hydrate, loopMs);
|
117
|
-
} catch (error) {
|
118
|
-
log.error(`Failed to hydrate chaindata, retrying in ${Math.round(retryTimeout / 1000)} seconds`, error);
|
119
|
-
timeout = setTimeout(hydrate, retryTimeout);
|
120
|
-
}
|
42
|
+
const substrate = new chainConnector.ChainConnector(chaindataProvider, connectionMeta.connectionMetaDb);
|
43
|
+
const evm = new chainConnectorEvm.ChainConnectorEvm(chaindataProvider);
|
44
|
+
return {
|
45
|
+
substrate,
|
46
|
+
evm
|
121
47
|
};
|
122
|
-
|
123
|
-
// launch the loop
|
124
|
-
hydrate();
|
125
|
-
|
126
|
-
// return an unsub function to shut down the loop
|
127
|
-
return () => timeout && clearTimeout(timeout);
|
128
48
|
});
|
129
49
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
const chaindataProvider = get(chaindataProviderAtom);
|
134
|
-
const balanceModules = get(balanceModulesAtom);
|
135
|
-
return new balances.MiniMetadataUpdater(chainConnectors, chaindataProvider, balanceModules);
|
50
|
+
const balancesProviderAtom = jotai.atom(get => {
|
51
|
+
return new balances.BalancesProvider(get(chaindataProviderAtom), get(chainConnectorsAtom)) // TODO pass storage
|
52
|
+
;
|
136
53
|
});
|
137
54
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
55
|
+
const chaindataAtom = utils.atomWithObservable(get => {
|
56
|
+
return rxjs.combineLatest({
|
57
|
+
networks: get(chaindataProviderAtom).networks$,
|
58
|
+
tokens: get(chaindataProviderAtom).tokens$
|
59
|
+
}).pipe(util.firstThenDebounce(1_000));
|
143
60
|
});
|
144
|
-
|
145
|
-
const
|
146
|
-
const
|
147
|
-
const
|
148
|
-
const
|
149
|
-
const
|
150
|
-
|
151
|
-
|
61
|
+
const filteredChaindataAtom = jotai.atom(async get => {
|
62
|
+
const enabledNetworkIds = get(enabledChainsAtom);
|
63
|
+
const enabledTokenIds = get(enabledTokensAtom);
|
64
|
+
const enableTestnets = get(enableTestnetsAtom);
|
65
|
+
const chaindata = await get(chaindataAtom);
|
66
|
+
const networks = chaindata.networks.filter(n => (enabledNetworkIds?.includes(n.id) || n.isDefault) && (enableTestnets || !n.isTestnet));
|
67
|
+
const networkById = lodashEs.keyBy(networks, n => n.id);
|
68
|
+
const tokens = chaindata.tokens.filter(token => (enabledTokenIds?.includes(token.id) || token.isDefault) && networkById[token.networkId]);
|
152
69
|
return {
|
153
|
-
|
154
|
-
|
70
|
+
networks,
|
71
|
+
tokens
|
155
72
|
};
|
156
73
|
});
|
157
|
-
|
158
|
-
const
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
if (!chaindataProvider) return [];
|
165
|
-
return balanceModuleCreators.map(mod => mod({
|
166
|
-
chainConnectors,
|
167
|
-
chaindataProvider
|
168
|
-
}));
|
74
|
+
const tokensAtom = jotai.atom(async get => {
|
75
|
+
const chaindata = await get(filteredChaindataAtom);
|
76
|
+
return chaindata.tokens;
|
77
|
+
});
|
78
|
+
const networksAtom = jotai.atom(async get => {
|
79
|
+
const chaindata = await get(filteredChaindataAtom);
|
80
|
+
return chaindata.networks;
|
169
81
|
});
|
170
82
|
|
83
|
+
var packageJson = {
|
84
|
+
name: "@talismn/balances-react"};
|
85
|
+
|
86
|
+
var log = anylogger__default.default(packageJson.name);
|
87
|
+
|
171
88
|
/**
|
172
89
|
* Converts a dexie Observable into an rxjs Observable.
|
173
90
|
*/
|
@@ -181,48 +98,6 @@ function dexieToRxjs(o) {
|
|
181
98
|
});
|
182
99
|
}
|
183
100
|
|
184
|
-
const chainsAtom = jotai.atom(async get => (await get(chaindataAtom)).chains);
|
185
|
-
const chainsByIdAtom = jotai.atom(async get => (await get(chaindataAtom)).chainsById);
|
186
|
-
const chainsByGenesisHashAtom = jotai.atom(async get => (await get(chaindataAtom)).chainsByGenesisHash);
|
187
|
-
const evmNetworksAtom = jotai.atom(async get => (await get(chaindataAtom)).evmNetworks);
|
188
|
-
const evmNetworksByIdAtom = jotai.atom(async get => (await get(chaindataAtom)).evmNetworksById);
|
189
|
-
const tokensAtom = jotai.atom(async get => (await get(chaindataAtom)).tokens);
|
190
|
-
const tokensByIdAtom = jotai.atom(async get => (await get(chaindataAtom)).tokensById);
|
191
|
-
const miniMetadatasAtom = jotai.atom(async get => (await get(chaindataAtom)).miniMetadatas);
|
192
|
-
const chaindataAtom = utils.atomWithObservable(get => {
|
193
|
-
const enableTestnets = get(enableTestnetsAtom);
|
194
|
-
const filterTestnets = items => enableTestnets ? items : items.filter(({
|
195
|
-
isTestnet
|
196
|
-
}) => !isTestnet);
|
197
|
-
const filterMapTestnets = items => enableTestnets ? items : Object.fromEntries(Object.entries(items).filter(([, {
|
198
|
-
isTestnet
|
199
|
-
}]) => !isTestnet));
|
200
|
-
const filterEnabledTokens = tokens => tokens.filter(token => token.isDefault || "isCustom" in token && token.isCustom);
|
201
|
-
const filterMapEnabledTokens = tokensById => Object.fromEntries(Object.entries(tokensById).filter(([, token]) => token.isDefault || "isCustom" in token && token.isCustom));
|
202
|
-
const distinctUntilIsEqual = rxjs.distinctUntilChanged((a, b) => isEqual__default.default(a, b));
|
203
|
-
const chains = get(chaindataProviderAtom).chainsObservable.pipe(distinctUntilIsEqual, rxjs.map(filterTestnets), distinctUntilIsEqual);
|
204
|
-
const chainsById = get(chaindataProviderAtom).chainsByIdObservable.pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), distinctUntilIsEqual);
|
205
|
-
const chainsByGenesisHash = get(chaindataProviderAtom).chainsByGenesisHashObservable.pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), distinctUntilIsEqual);
|
206
|
-
const evmNetworks = get(chaindataProviderAtom).evmNetworksObservable.pipe(distinctUntilIsEqual, rxjs.map(filterTestnets), distinctUntilIsEqual);
|
207
|
-
const evmNetworksById = get(chaindataProviderAtom).evmNetworksByIdObservable.pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), distinctUntilIsEqual);
|
208
|
-
const tokens = get(chaindataProviderAtom).tokensObservable.pipe(distinctUntilIsEqual, rxjs.map(filterTestnets), rxjs.map(filterEnabledTokens), distinctUntilIsEqual);
|
209
|
-
const tokensById = get(chaindataProviderAtom).tokensByIdObservable.pipe(distinctUntilIsEqual, rxjs.map(filterMapTestnets), rxjs.map(filterMapEnabledTokens), distinctUntilIsEqual);
|
210
|
-
const miniMetadatasObservable = dexieToRxjs(dexie.liveQuery(() => balances.db.miniMetadatas.toArray()));
|
211
|
-
const miniMetadatas = rxjs.combineLatest([miniMetadatasObservable.pipe(distinctUntilIsEqual), chainsById]).pipe(rxjs.map(([miniMetadatas, chainsById]) => miniMetadatas.filter(m => chainsById[m.chainId])), distinctUntilIsEqual);
|
212
|
-
return rxjs.combineLatest({
|
213
|
-
chains,
|
214
|
-
chainsById,
|
215
|
-
chainsByGenesisHash,
|
216
|
-
evmNetworks,
|
217
|
-
evmNetworksById,
|
218
|
-
tokens,
|
219
|
-
tokensById,
|
220
|
-
miniMetadatas
|
221
|
-
}).pipe(
|
222
|
-
// debounce to prevent hammering UI with updates
|
223
|
-
util.firstThenDebounce(1_000), distinctUntilIsEqual);
|
224
|
-
});
|
225
|
-
|
226
101
|
const tokenRatesAtom = jotai.atom(async get => {
|
227
102
|
// runs a timer to keep tokenRates up to date
|
228
103
|
get(tokenRatesFetcherAtomEffect);
|
@@ -243,9 +118,9 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
|
|
243
118
|
|
244
119
|
// we have to get these synchronously so that jotai knows to restart our timer when they change
|
245
120
|
const coinsApiConfig = get(coinsApiConfigAtom);
|
246
|
-
const
|
121
|
+
const tokensPromise = get(tokensAtom);
|
247
122
|
(async () => {
|
248
|
-
const tokensById = await
|
123
|
+
const tokensById = lodashEs.keyBy(await tokensPromise, "id");
|
249
124
|
const tokenIds = Object.keys(tokensById);
|
250
125
|
const loopMs = 300_000; // 300_000ms = 300s = 5 minutes
|
251
126
|
const retryTimeout = 5_000; // 5_000ms = 5 seconds
|
@@ -275,7 +150,7 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
|
|
275
150
|
const retrying = !abort.signal.aborted;
|
276
151
|
const messageParts = ["Failed to fetch tokenRates", retrying && `retrying in ${Math.round(retryTimeout / 1000)} seconds`, !retrying && `giving up (timer no longer needed)`].filter(util.isTruthy);
|
277
152
|
log.error(messageParts.join(", "), error);
|
278
|
-
if (
|
153
|
+
if (util.isAbortError(error)) return; // don't schedule retry if aborted
|
279
154
|
setTimeout(hydrate, retryTimeout);
|
280
155
|
}
|
281
156
|
};
|
@@ -286,218 +161,42 @@ const tokenRatesFetcherAtomEffect = jotaiEffect.atomEffect(get => {
|
|
286
161
|
return () => abort.abort("Unsubscribed");
|
287
162
|
});
|
288
163
|
|
289
|
-
const
|
290
|
-
|
291
|
-
|
292
|
-
const [balances$1, hydrateData] = await Promise.all([get(balancesObservableAtom), get(balancesHydrateDataAtom)]);
|
293
|
-
return new balances.Balances(Object.values(balances$1).filter(balance => !!hydrateData?.tokens?.[balance.tokenId]),
|
294
|
-
// hydrate balance chains, evmNetworks, tokens and tokenRates
|
295
|
-
hydrateData);
|
164
|
+
const addressesByTokenIdAtom = jotai.atom(async get => {
|
165
|
+
const [tokens, addresses] = await Promise.all([get(tokensAtom), get(allAddressesAtom)]);
|
166
|
+
return lodashEs.fromPairs(tokens.map(token => [token.id, addresses]));
|
296
167
|
});
|
297
|
-
const
|
298
|
-
const
|
299
|
-
const
|
300
|
-
const
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
168
|
+
const rawBalancesAtom = jotai.atom([]);
|
169
|
+
const subscribeBalancesAtom = jotaiEffect.atomEffect((get, set) => {
|
170
|
+
const unsub = (async () => {
|
171
|
+
const balancesProvider = get(balancesProviderAtom);
|
172
|
+
const addressesByTokenId = await get(addressesByTokenIdAtom);
|
173
|
+
const sub = balancesProvider.getBalances$(addressesByTokenId).subscribe(balances => {
|
174
|
+
set(rawBalancesAtom, balances.balances);
|
175
|
+
});
|
176
|
+
return () => {
|
177
|
+
return sub.unsubscribe();
|
178
|
+
};
|
179
|
+
})();
|
180
|
+
return () => {
|
181
|
+
unsub.then(unsubscribe => unsubscribe());
|
182
|
+
};
|
307
183
|
});
|
308
184
|
const balancesHydrateDataAtom = jotai.atom(async get => {
|
309
|
-
const [
|
310
|
-
|
311
|
-
|
312
|
-
tokensById
|
313
|
-
}, tokenRates] = await Promise.all([get(chaindataAtom), get(tokenRatesAtom)]);
|
185
|
+
const [chaindata, tokenRates] = await Promise.all([get(chaindataAtom), get(tokenRatesAtom)]);
|
186
|
+
const networksById = lodashEs.keyBy(chaindata.networks, "id");
|
187
|
+
const tokensById = lodashEs.keyBy(chaindata.tokens, "id");
|
314
188
|
return {
|
315
|
-
|
316
|
-
evmNetworks: evmNetworksById,
|
189
|
+
networks: networksById,
|
317
190
|
tokens: tokensById,
|
318
191
|
tokenRates
|
319
192
|
};
|
320
193
|
});
|
321
|
-
const
|
322
|
-
//
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
const atomDependencies = Promise.all([get(cryptoWaitReadyAtom), get(balanceModulesAtom), get(miniMetadataHydratedAtom), get(allAddressesAtom), get(chainsAtom), get(chainsByIdAtom), get(evmNetworksAtom), get(evmNetworksByIdAtom), get(tokensAtom), get(tokensByIdAtom), get(miniMetadatasAtom), get(enabledChainsAtom), get(enabledTokensAtom), get(hydrateBalancesObservableAtom)]);
|
328
|
-
const persistBackend = get(balancesPersistBackendAtom);
|
329
|
-
const unsubsPromise = (async () => {
|
330
|
-
const [_cryptoReady, balanceModules, miniMetadataHydrated, allAddresses, chains, chainsById, evmNetworks, evmNetworksById, tokens, tokensById, _miniMetadatas, enabledChainsConfig, enabledTokensConfig] = await atomDependencies;
|
331
|
-
if (!miniMetadataHydrated) return;
|
332
|
-
if (abort.signal.aborted) return;
|
333
|
-
|
334
|
-
// persist data every thirty seconds
|
335
|
-
balancesObservable.pipe(rxjs.debounceTime(10000)).subscribe(balancesUpdate => {
|
336
|
-
persistBackend.persist(Object.values(balancesUpdate));
|
337
|
-
});
|
338
|
-
const updateBalances = async balancesUpdates => {
|
339
|
-
if (abort.signal.aborted) return;
|
340
|
-
const updatesWithIds = new balances.Balances(balancesUpdates);
|
341
|
-
const existing = balancesObservable.value;
|
342
|
-
|
343
|
-
// update initialising set here - before filtering out zero balances
|
344
|
-
// while this may include stale balances, the important thing is that the balance is no longer "initialising"
|
345
|
-
// balancesUpdates.forEach((b) => this.#initialising.delete(getBalanceId(b)))
|
346
|
-
|
347
|
-
const newlyZeroBalances = [];
|
348
|
-
const changedBalances = Object.fromEntries(updatesWithIds.each.filter(newB => {
|
349
|
-
const isZero = newB.total.tokens === "0";
|
350
|
-
// Keep new balances which are not zeros
|
351
|
-
const existingB = existing[newB.id];
|
352
|
-
if (!existingB && !isZero) return true;
|
353
|
-
const hasChanged = !lodash.isEqual(existingB, newB.toJSON());
|
354
|
-
// Collect balances now confirmed to be zero separately, so they can be filtered out from the main set
|
355
|
-
if (existingB && hasChanged && isZero) newlyZeroBalances.push(newB.id);
|
356
|
-
// Keep changed balances, which are not known zeros
|
357
|
-
return hasChanged && !isZero;
|
358
|
-
}).map(b => [b.id, b.toJSON()]));
|
359
|
-
if (Object.keys(changedBalances).length === 0 && newlyZeroBalances.length === 0) return;
|
360
|
-
const nonZeroBalances = newlyZeroBalances.length > 0 ? Object.fromEntries(Object.entries(existing).filter(([id]) => !newlyZeroBalances.includes(id))) : existing;
|
361
|
-
const newBalancesState = {
|
362
|
-
...nonZeroBalances,
|
363
|
-
...changedBalances
|
364
|
-
};
|
365
|
-
if (Object.keys(newBalancesState).length === 0) return;
|
366
|
-
balancesObservable.next(newBalancesState);
|
367
|
-
};
|
368
|
-
const deleteBalances = async balancesFilter => {
|
369
|
-
if (abort.signal.aborted) return;
|
370
|
-
const balancesToKeep = Object.fromEntries(new balances.Balances(Object.values(await get(balancesObservableAtom))).each.filter(b => !balancesFilter(b)).map(b => [b.id, b.toJSON()]));
|
371
|
-
balancesObservable.next(balancesToKeep);
|
372
|
-
};
|
373
|
-
const enabledChainIds = enabledChainsConfig?.map(genesisHash => chains.find(chain => chain.genesisHash === genesisHash)?.id);
|
374
|
-
const enabledChainsFilter = enabledChainIds ? token => token.chain && enabledChainIds?.includes(token.chain.id) : () => true;
|
375
|
-
const enabledTokensFilter = enabledTokensConfig ? token => enabledTokensConfig.includes(token.id) : () => true;
|
376
|
-
const enabledTokenIds = tokens.filter(enabledChainsFilter).filter(enabledTokensFilter).map(({
|
377
|
-
id
|
378
|
-
}) => id);
|
379
|
-
if (enabledTokenIds.length < 1 || allAddresses.length < 1) return;
|
380
|
-
const addressesByTokenByModule = {};
|
381
|
-
enabledTokenIds.flatMap(tokenId => tokensById[tokenId]).forEach(token => {
|
382
|
-
// filter out tokens on chains/evmNetworks which have no rpcs
|
383
|
-
const hasRpcs = token.chain?.id && (chainsById[token.chain.id]?.rpcs?.length ?? 0) > 0 || token.evmNetwork?.id && (evmNetworksById[token.evmNetwork.id]?.rpcs?.length ?? 0) > 0;
|
384
|
-
if (!hasRpcs) return;
|
385
|
-
if (!addressesByTokenByModule[token.type]) addressesByTokenByModule[token.type] = {};
|
386
|
-
addressesByTokenByModule[token.type][token.id] = allAddresses.filter(address => {
|
387
|
-
// for each address, fetch balances only from compatible chains
|
388
|
-
return util.isEthereumAddress(address) ? token.evmNetwork?.id || chainsById[token.chain?.id ?? ""]?.account === "secp256k1" : token.chain?.id && chainsById[token.chain?.id ?? ""]?.account !== "secp256k1";
|
389
|
-
});
|
390
|
-
});
|
391
|
-
|
392
|
-
// Delete invalid cached balances
|
393
|
-
const chainIds = new Set(chains.map(chain => chain.id));
|
394
|
-
const evmNetworkIds = new Set(evmNetworks.map(evmNetwork => evmNetwork.id));
|
395
|
-
await deleteBalances(balance => {
|
396
|
-
// delete cached balances for accounts which don't exist anymore
|
397
|
-
if (!balance.address || !allAddresses.includes(balance.address)) return true;
|
398
|
-
|
399
|
-
// delete cached balances when chain/evmNetwork doesn't exist
|
400
|
-
if (balance.chainId === undefined && balance.evmNetworkId === undefined) return true;
|
401
|
-
if (balance.chainId !== undefined && !chainIds.has(balance.chainId)) return true;
|
402
|
-
if (balance.evmNetworkId !== undefined && !evmNetworkIds.has(balance.evmNetworkId)) return true;
|
403
|
-
|
404
|
-
// delete cached balance when token doesn't exist / is disabled
|
405
|
-
if (!enabledTokenIds.includes(balance.tokenId)) return true;
|
406
|
-
|
407
|
-
// delete cached balance when module doesn't exist
|
408
|
-
if (!balanceModules.find(module => module.type === balance.source)) return true;
|
409
|
-
|
410
|
-
// delete cached balance for accounts on incompatible chains
|
411
|
-
// (substrate accounts shouldn't have evm balances)
|
412
|
-
// (evm accounts shouldn't have substrate balances (unless the chain uses secp256k1 accounts))
|
413
|
-
const chain = balance.chainId && chains.find(({
|
414
|
-
id
|
415
|
-
}) => id === balance.chainId) || null;
|
416
|
-
const hasChain = balance.chainId && chainIds.has(balance.chainId);
|
417
|
-
const hasEvmNetwork = balance.evmNetworkId && evmNetworkIds.has(balance.evmNetworkId);
|
418
|
-
const chainUsesSecp256k1Accounts = chain?.account === "secp256k1";
|
419
|
-
if (!util.isEthereumAddress(balance.address)) {
|
420
|
-
if (!hasChain) return true;
|
421
|
-
if (chainUsesSecp256k1Accounts) return true;
|
422
|
-
}
|
423
|
-
if (util.isEthereumAddress(balance.address)) {
|
424
|
-
if (!hasEvmNetwork && !chainUsesSecp256k1Accounts) return true;
|
425
|
-
}
|
426
|
-
|
427
|
-
// keep balance
|
428
|
-
return false;
|
429
|
-
});
|
430
|
-
if (abort.signal.aborted) return;
|
431
|
-
|
432
|
-
// after 30 seconds, change the status of all balances still initializing to stale
|
433
|
-
setTimeout(() => {
|
434
|
-
if (abort.signal.aborted) return;
|
435
|
-
const staleObservable = balancesObservable.pipe(rxjs.map(val => Object.values(val).filter(({
|
436
|
-
status
|
437
|
-
}) => status === "cache").map(balance => ({
|
438
|
-
...balance,
|
439
|
-
status: "stale"
|
440
|
-
}))));
|
441
|
-
rxjs.firstValueFrom(staleObservable).then(v => {
|
442
|
-
if (v.length) updateBalances(v);
|
443
|
-
});
|
444
|
-
}, 30_000);
|
445
|
-
return balanceModules.map(balanceModule => {
|
446
|
-
const unsub = balances.balances(balanceModule, addressesByTokenByModule[balanceModule.type] ?? {}, (error, balances) => {
|
447
|
-
// log errors
|
448
|
-
if (error) {
|
449
|
-
if (error?.type === "STALE_RPC_ERROR" || error?.type === "WEBSOCKET_ALLOCATION_EXHAUSTED_ERROR") {
|
450
|
-
const addressesByModuleToken = addressesByTokenByModule[balanceModule.type] ?? {};
|
451
|
-
const staleObservable = balancesObservable.pipe(rxjs.map(val => Object.values(val).filter(balance => {
|
452
|
-
const {
|
453
|
-
tokenId,
|
454
|
-
address,
|
455
|
-
source
|
456
|
-
} = balance;
|
457
|
-
const chainComparison = error.chainId ? "chainId" in balance && error.chainId === balance.chainId : error.evmNetworkId ? "evmNetworkId" in balance && error.evmNetworkId === balance.evmNetworkId : true;
|
458
|
-
return chainComparison && addressesByModuleToken[tokenId]?.includes(address) && source === balanceModule.type;
|
459
|
-
}).map(balance => ({
|
460
|
-
...balance,
|
461
|
-
status: "stale"
|
462
|
-
}))));
|
463
|
-
rxjs.firstValueFrom(staleObservable).then(v => {
|
464
|
-
if (v.length) updateBalances(v);
|
465
|
-
});
|
466
|
-
}
|
467
|
-
return log.error(`Failed to fetch ${balanceModule.type} balances`, error);
|
468
|
-
}
|
469
|
-
|
470
|
-
// ignore empty balance responses
|
471
|
-
if (!balances) return;
|
472
|
-
// ignore balances from old subscriptions which are still in the process of unsubscribing
|
473
|
-
if (abort.signal.aborted) return;
|
474
|
-
|
475
|
-
// good balances
|
476
|
-
if (balances) {
|
477
|
-
if ("status" in balances) {
|
478
|
-
// For modules using the new SubscriptionResultWithStatus pattern
|
479
|
-
//TODO fix initialisin
|
480
|
-
// if (result.status === "initialising") this.#initialising.add(balanceModule.type)
|
481
|
-
// else this.#initialising.delete(balanceModule.type)
|
482
|
-
updateBalances(balances.data);
|
483
|
-
} else {
|
484
|
-
// add good ones to initialisedBalances
|
485
|
-
updateBalances(Object.values(balances.toJSON()));
|
486
|
-
}
|
487
|
-
}
|
488
|
-
});
|
489
|
-
return () => unsub.then(unsubscribe => unsubscribe());
|
490
|
-
});
|
491
|
-
})();
|
492
|
-
|
493
|
-
// close the existing subscriptions when our effect unmounts
|
494
|
-
// (wait 2 seconds before actually unsubscribing, to allow the websocket to be reused in that time)
|
495
|
-
const unsubscribe = () => unsubsPromise.then(unsubs => {
|
496
|
-
persistBackend.persist(Object.values(balancesObservable.value));
|
497
|
-
unsubs?.forEach(unsub => unsub());
|
498
|
-
});
|
499
|
-
abort.signal.addEventListener("abort", () => setTimeout(unsubscribe, 2_000));
|
500
|
-
return () => abort.abort("Unsubscribed");
|
194
|
+
const balancesAtom = jotai.atom(async get => {
|
195
|
+
// subscribe to balancesProvider getBalance with addressesByTokenIdAtom as param
|
196
|
+
get(subscribeBalancesAtom);
|
197
|
+
const hydrate = await get(balancesHydrateDataAtom);
|
198
|
+
const rawBalances = get(rawBalancesAtom);
|
199
|
+
return new balances.Balances(rawBalances, hydrate);
|
501
200
|
});
|
502
201
|
|
503
202
|
const useSetBalancesAddresses = addresses => {
|
@@ -514,10 +213,8 @@ const useSetBalancesAddresses = addresses => {
|
|
514
213
|
* @returns a Balances object containing the current balances state.
|
515
214
|
*/
|
516
215
|
|
517
|
-
const useBalances =
|
518
|
-
|
519
|
-
if (persistBackend) setPersistBackend(persistBackend);
|
520
|
-
return jotai.useAtomValue(allBalancesAtom);
|
216
|
+
const useBalances = () => {
|
217
|
+
return jotai.useAtomValue(balancesAtom);
|
521
218
|
};
|
522
219
|
|
523
220
|
// TODO: Extract to shared definition between extension and @talismn/balances-react
|
@@ -548,41 +245,47 @@ const useBalancesStatus = balances => react.useMemo(() => {
|
|
548
245
|
status: "live"
|
549
246
|
};
|
550
247
|
}, [balances]);
|
551
|
-
const getStaleChains = balances => [...new Set(balances.
|
248
|
+
const getStaleChains = balances => [...new Set(balances.each.filter(b => b.status === "stale").map(b => b.network?.name ?? b.networkId ?? "Unknown"))];
|
552
249
|
|
553
250
|
const useChainConnectors = () => jotai.useAtomValue(chainConnectorsAtom);
|
554
251
|
|
555
252
|
const useChaindataProvider = () => jotai.useAtomValue(chaindataProviderAtom);
|
556
253
|
const useChaindata = () => jotai.useAtomValue(chaindataAtom);
|
557
|
-
const
|
558
|
-
const
|
559
|
-
const
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
const
|
254
|
+
const useNetworks = () => useChaindata().networks;
|
255
|
+
const useNetworksById = () => {
|
256
|
+
const {
|
257
|
+
networks
|
258
|
+
} = useChaindata();
|
259
|
+
return react.useMemo(() => lodashEs.keyBy(networks, n => n.id), [networks]);
|
260
|
+
};
|
261
|
+
const useNetwork = networkId => {
|
262
|
+
const networksById = useNetworksById();
|
263
|
+
return networksById[networkId ?? ""] ?? null;
|
264
|
+
};
|
265
|
+
const useTokens = () => useChaindata().tokens;
|
266
|
+
const useTokensById = () => {
|
267
|
+
const {
|
268
|
+
tokens
|
269
|
+
} = useChaindata();
|
270
|
+
return react.useMemo(() => lodashEs.keyBy(tokens, t => t.id), [tokens]);
|
271
|
+
};
|
272
|
+
const useToken = tokenId => {
|
273
|
+
const tokensById = useTokensById();
|
274
|
+
return tokensById[tokenId ?? ""] ?? null;
|
275
|
+
};
|
565
276
|
|
566
277
|
const useTokenRates = () => jotai.useAtomValue(tokenRatesAtom);
|
567
278
|
const useTokenRate = tokenId => useTokenRates()[tokenId ?? ""] ?? undefined;
|
568
279
|
|
280
|
+
const cryptoWaitReadyAtom = jotai.atom(async () => await utilCrypto.cryptoWaitReady());
|
281
|
+
|
569
282
|
const BalancesProvider = ({
|
570
|
-
balanceModules,
|
571
|
-
onfinalityApiKey,
|
572
283
|
coinsApiUrl,
|
573
284
|
withTestnets,
|
574
285
|
enabledChains,
|
575
286
|
enabledTokens,
|
576
287
|
children
|
577
288
|
}) => {
|
578
|
-
const setBalanceModules = jotai.useSetAtom(balanceModuleCreatorsAtom);
|
579
|
-
react.useEffect(() => {
|
580
|
-
if (balanceModules !== undefined) setBalanceModules(balanceModules);
|
581
|
-
}, [balanceModules, setBalanceModules]);
|
582
|
-
const setOnfinalityApiKey = jotai.useSetAtom(onfinalityApiKeyAtom);
|
583
|
-
react.useEffect(() => {
|
584
|
-
setOnfinalityApiKey(onfinalityApiKey);
|
585
|
-
}, [onfinalityApiKey, setOnfinalityApiKey]);
|
586
289
|
const setCoinsApiConfig = jotai.useSetAtom(coinsApiConfigAtom);
|
587
290
|
react.useEffect(() => {
|
588
291
|
setCoinsApiConfig({
|
@@ -608,71 +311,54 @@ const BalancesProvider = ({
|
|
608
311
|
|
609
312
|
Object.defineProperty(exports, "evmErc20TokenId", {
|
610
313
|
enumerable: true,
|
611
|
-
get: function () { return
|
314
|
+
get: function () { return chaindataProvider.evmErc20TokenId; }
|
612
315
|
});
|
613
316
|
Object.defineProperty(exports, "evmNativeTokenId", {
|
614
317
|
enumerable: true,
|
615
|
-
get: function () { return
|
318
|
+
get: function () { return chaindataProvider.evmNativeTokenId; }
|
616
319
|
});
|
617
320
|
Object.defineProperty(exports, "subAssetTokenId", {
|
618
321
|
enumerable: true,
|
619
|
-
get: function () { return
|
620
|
-
});
|
621
|
-
Object.defineProperty(exports, "subEquilibriumTokenId", {
|
622
|
-
enumerable: true,
|
623
|
-
get: function () { return balances.subEquilibriumTokenId; }
|
322
|
+
get: function () { return chaindataProvider.subAssetTokenId; }
|
624
323
|
});
|
625
324
|
Object.defineProperty(exports, "subNativeTokenId", {
|
626
325
|
enumerable: true,
|
627
|
-
get: function () { return
|
326
|
+
get: function () { return chaindataProvider.subNativeTokenId; }
|
628
327
|
});
|
629
328
|
Object.defineProperty(exports, "subPsp22TokenId", {
|
630
329
|
enumerable: true,
|
631
|
-
get: function () { return
|
330
|
+
get: function () { return chaindataProvider.subPsp22TokenId; }
|
632
331
|
});
|
633
332
|
Object.defineProperty(exports, "subTokensTokenId", {
|
634
333
|
enumerable: true,
|
635
|
-
get: function () { return
|
334
|
+
get: function () { return chaindataProvider.subTokensTokenId; }
|
636
335
|
});
|
637
336
|
exports.BalancesProvider = BalancesProvider;
|
638
337
|
exports.allAddressesAtom = allAddressesAtom;
|
639
|
-
exports.
|
640
|
-
exports.balanceModuleCreatorsAtom = balanceModuleCreatorsAtom;
|
641
|
-
exports.balanceModulesAtom = balanceModulesAtom;
|
642
|
-
exports.balancesPersistBackendAtom = balancesPersistBackendAtom;
|
338
|
+
exports.balancesAtom = balancesAtom;
|
643
339
|
exports.chainConnectorsAtom = chainConnectorsAtom;
|
644
340
|
exports.chaindataAtom = chaindataAtom;
|
645
341
|
exports.chaindataProviderAtom = chaindataProviderAtom;
|
646
|
-
exports.chainsAtom = chainsAtom;
|
647
|
-
exports.chainsByGenesisHashAtom = chainsByGenesisHashAtom;
|
648
|
-
exports.chainsByIdAtom = chainsByIdAtom;
|
649
342
|
exports.coinsApiConfigAtom = coinsApiConfigAtom;
|
650
343
|
exports.cryptoWaitReadyAtom = cryptoWaitReadyAtom;
|
651
344
|
exports.enableTestnetsAtom = enableTestnetsAtom;
|
652
345
|
exports.enabledChainsAtom = enabledChainsAtom;
|
653
346
|
exports.enabledTokensAtom = enabledTokensAtom;
|
654
|
-
exports.evmNetworksAtom = evmNetworksAtom;
|
655
|
-
exports.evmNetworksByIdAtom = evmNetworksByIdAtom;
|
656
347
|
exports.getStaleChains = getStaleChains;
|
657
|
-
exports.
|
658
|
-
exports.miniMetadatasAtom = miniMetadatasAtom;
|
659
|
-
exports.onfinalityApiKeyAtom = onfinalityApiKeyAtom;
|
348
|
+
exports.networksAtom = networksAtom;
|
660
349
|
exports.tokenRatesAtom = tokenRatesAtom;
|
661
350
|
exports.tokensAtom = tokensAtom;
|
662
|
-
exports.tokensByIdAtom = tokensByIdAtom;
|
663
351
|
exports.useBalances = useBalances;
|
664
352
|
exports.useBalancesStatus = useBalancesStatus;
|
665
|
-
exports.useChain = useChain;
|
666
353
|
exports.useChainConnectors = useChainConnectors;
|
667
354
|
exports.useChaindata = useChaindata;
|
668
355
|
exports.useChaindataProvider = useChaindataProvider;
|
669
|
-
exports.
|
670
|
-
exports.
|
671
|
-
exports.
|
672
|
-
exports.useEvmNetworks = useEvmNetworks;
|
673
|
-
exports.useMiniMetadatas = useMiniMetadatas;
|
356
|
+
exports.useNetwork = useNetwork;
|
357
|
+
exports.useNetworks = useNetworks;
|
358
|
+
exports.useNetworksById = useNetworksById;
|
674
359
|
exports.useSetBalancesAddresses = useSetBalancesAddresses;
|
675
360
|
exports.useToken = useToken;
|
676
361
|
exports.useTokenRate = useTokenRate;
|
677
362
|
exports.useTokenRates = useTokenRates;
|
678
363
|
exports.useTokens = useTokens;
|
364
|
+
exports.useTokensById = useTokensById;
|