@talismn/balances 0.0.0-pr2067-20250630033435 → 0.0.0-pr2075-20250703111149

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.
Files changed (46) hide show
  1. package/dist/declarations/src/BalanceModule.d.ts +23 -12
  2. package/dist/declarations/src/getMiniMetadata/getMetadataRpc.d.ts +3 -0
  3. package/dist/declarations/src/getMiniMetadata/getMiniMetadatas.d.ts +4 -0
  4. package/dist/declarations/src/getMiniMetadata/getSpecVersion.d.ts +6 -0
  5. package/dist/declarations/src/getMiniMetadata/getUpdatedMiniMetadatas.d.ts +4 -0
  6. package/dist/declarations/src/getMiniMetadata/index.d.ts +5 -0
  7. package/dist/declarations/src/index.d.ts +0 -3
  8. package/dist/declarations/src/libVersion.d.ts +1 -0
  9. package/dist/declarations/src/modules/EvmErc20Module.d.ts +20 -23
  10. package/dist/declarations/src/modules/EvmNativeModule.d.ts +19 -18
  11. package/dist/declarations/src/modules/EvmUniswapV2Module.d.ts +28 -30
  12. package/dist/declarations/src/modules/SubstrateAssetsModule.d.ts +20 -18
  13. package/dist/declarations/src/modules/SubstrateForeignAssetsModule.d.ts +20 -18
  14. package/dist/declarations/src/modules/SubstrateNativeModule/index.d.ts +17 -4
  15. package/dist/declarations/src/modules/SubstrateNativeModule/subscribeNompoolStaking.d.ts +3 -3
  16. package/dist/declarations/src/modules/SubstrateNativeModule/subscribeSubtensorStaking.d.ts +3 -3
  17. package/dist/declarations/src/modules/SubstrateNativeModule/types.d.ts +6 -23
  18. package/dist/declarations/src/modules/SubstrateNativeModule/util/QueryCache.d.ts +6 -6
  19. package/dist/declarations/src/modules/SubstrateNativeModule/util/buildQueries.d.ts +5 -4
  20. package/dist/declarations/src/modules/SubstrateNativeModule/util/sortChains.d.ts +1 -1
  21. package/dist/declarations/src/modules/SubstrateNativeModule/util/systemProperties.d.ts +5 -0
  22. package/dist/declarations/src/modules/SubstratePsp22Module.d.ts +20 -19
  23. package/dist/declarations/src/modules/SubstrateTokensModule.d.ts +22 -22
  24. package/dist/declarations/src/modules/index.d.ts +213 -38
  25. package/dist/declarations/src/modules/util/InferBalanceModuleTypes.d.ts +5 -3
  26. package/dist/declarations/src/modules/util/buildStorageCoders.d.ts +16 -7
  27. package/dist/declarations/src/modules/util/getAddresssesByTokenByNetwork.d.ts +3 -0
  28. package/dist/declarations/src/modules/util/getUniqueChainIds.d.ts +2 -2
  29. package/dist/declarations/src/modules/util/index.d.ts +0 -1
  30. package/dist/declarations/src/types/balances.d.ts +220 -72
  31. package/dist/declarations/src/types/balancetypes.d.ts +8 -18
  32. package/dist/declarations/src/types/minimetadatas.d.ts +6 -24
  33. package/dist/declarations/src/types/tokens.d.ts +11 -0
  34. package/dist/talismn-balances.cjs.dev.js +1415 -2090
  35. package/dist/talismn-balances.cjs.prod.js +1415 -2090
  36. package/dist/talismn-balances.esm.js +1417 -2086
  37. package/package.json +9 -8
  38. package/dist/declarations/src/EvmTokenFetcher.d.ts +0 -11
  39. package/dist/declarations/src/MiniMetadataUpdater.d.ts +0 -43
  40. package/dist/declarations/src/modules/SubstrateEquilibriumModule.d.ts +0 -39
  41. package/dist/declarations/src/modules/SubstrateNativeModule/subscribeCrowdloans.d.ts +0 -5
  42. package/dist/declarations/src/modules/SubstrateNativeModule/util/crowdloanFundContributionsChildKey.d.ts +0 -4
  43. package/dist/declarations/src/modules/SubstrateNativeModule/util/detectMiniMetadataChanges.d.ts +0 -2
  44. package/dist/declarations/src/modules/util/findChainMeta.d.ts +0 -8
  45. package/dist/declarations/src/util/hydrateChaindata.d.ts +0 -8
  46. package/dist/declarations/src/util/index.d.ts +0 -1
@@ -1,29 +1,30 @@
1
- import PromisePool$1, { PromisePool } from '@supercharge/promise-pool';
2
- import { fetchMiniMetadatas, fetchInitMiniMetadatas, availableTokenLogoFilenames, githubTokenLogoUrl } from '@talismn/chaindata-provider';
3
- import { fetchBestMetadata, getScaleApi } from '@talismn/sapi';
4
- import { Dexie, liveQuery } from 'dexie';
5
- import isEqual from 'lodash/isEqual';
6
- import { from, Observable, scan, share, map, switchAll, combineLatest, mergeMap, toArray, interval, startWith, exhaustMap, pipe, filter, shareReplay, combineLatestWith, distinctUntilChanged, firstValueFrom, BehaviorSubject, debounceTime, takeUntil, switchMap, withLatestFrom, concatMap } from 'rxjs';
1
+ import { Dexie } from 'dexie';
7
2
  import anylogger from 'anylogger';
3
+ import { evmErc20TokenId, TokenBaseSchema, EvmErc20TokenSchema, networkIdFromTokenId, EvmUniswapV2TokenSchema, evmUniswapV2TokenId, getGithubTokenLogoUrl, DotNetworkBalancesConfigSchema, SubAssetsTokenSchema, parseSubAssetTokenId, subAssetTokenId, SubForeignAssetsTokenSchema, parseSubForeignAssetTokenId, subForeignAssetTokenId, parseTokenId, subNativeTokenId, SubPsp22TokenSchema, subPsp22TokenId, SubTokensTokenSchema, parseSubTokensTokenId, subTokensTokenId } from '@talismn/chaindata-provider';
8
4
  import { newTokenRates } from '@talismn/token-rates';
9
- import { isBigInt, BigMath, planckToTokens, isTruthy, isArrayOf, isEthereumAddress, hasOwnProperty, decodeAnyAddress, isNotNil, blake2Concat, firstThenDebounce, Deferred } from '@talismn/util';
5
+ import { isBigInt, BigMath, planckToTokens, isArrayOf, isTruthy, isEthereumAddress, hasOwnProperty, isAbortError, isNotNil, decodeAnyAddress, blake2Concat, Deferred } from '@talismn/util';
10
6
  import BigNumber from 'bignumber.js';
11
- import { u8aToHex, assert, stringCamelCase, u8aConcatStrict, u8aConcat, arrayChunk, u8aToString, hexToNumber, hexToU8a } from '@polkadot/util';
12
- import { xxhashAsU8a, blake2AsU8a } from '@polkadot/util-crypto';
7
+ import { u8aToHex, assert, stringCamelCase, u8aConcatStrict, arrayChunk, u8aToString, hexToNumber, hexToU8a } from '@polkadot/util';
8
+ import { xxhashAsU8a } from '@polkadot/util-crypto';
13
9
  import pako from 'pako';
10
+ import z from 'zod/v4';
14
11
  import { parseAbi, isHex, hexToBigInt } from 'viem';
12
+ import { fromPairs, toPairs, keys, groupBy as groupBy$1 } from 'lodash';
13
+ import isEqual from 'lodash/isEqual';
15
14
  import { defineMethod } from '@substrate/txwrapper-core';
16
- import { unifyMetadata, decAnyMetadata, getDynamicBuilder, getLookupFn, getMetadataVersion, compactMetadata, encodeMetadata, decodeScale, encodeStateKey, papiParse } from '@talismn/scale';
15
+ import { unifyMetadata, decAnyMetadata, getDynamicBuilder, getLookupFn, compactMetadata, encodeMetadata, decodeScale, papiParse, getMetadataVersion, encodeStateKey } from '@talismn/scale';
17
16
  import camelCase from 'lodash/camelCase';
17
+ import PQueue from 'p-queue';
18
+ import { fetchBestMetadata, getScaleApi } from '@talismn/sapi';
18
19
  import { Metadata, TypeRegistry } from '@polkadot/types';
19
20
  import groupBy from 'lodash/groupBy';
20
21
  import { mergeUint8, toHex } from '@polkadot-api/utils';
21
22
  import { Binary, AccountId } from 'polkadot-api';
22
23
  import { ChainConnectionError } from '@talismn/chain-connector';
23
- import { u32, u128, Struct } from 'scale-ts';
24
+ import { Observable, scan, share, map, switchAll, combineLatest, from, mergeMap, toArray, interval, startWith, exhaustMap, BehaviorSubject, debounceTime, takeUntil, distinctUntilChanged, switchMap, withLatestFrom, concatMap } from 'rxjs';
25
+ import { u32, Struct, u128 } from 'scale-ts';
24
26
  import upperFirst from 'lodash/upperFirst';
25
27
  import { Abi } from '@polkadot/api-contract';
26
- import { compressToEncodedURIComponent } from 'lz-string';
27
28
 
28
29
  // TODO: Document default balances module purpose/usage
29
30
  const DefaultBalanceModule = type => ({
@@ -58,48 +59,11 @@ const DefaultBalanceModule = type => ({
58
59
  // internal
59
60
  //
60
61
 
61
- /**
62
- * Fetches tokens for EVM networks.
63
- */
64
- class EvmTokenFetcher {
65
- #chaindataProvider;
66
- #balanceModules;
67
- constructor(chaindataProvider, balanceModules) {
68
- this.#chaindataProvider = chaindataProvider;
69
- this.#balanceModules = balanceModules;
70
- }
71
- async update(evmNetworkIds) {
72
- await this.updateEvmNetworks(evmNetworkIds);
73
- }
74
- async updateEvmNetworks(evmNetworkIds) {
75
- const evmNetworks = new Map((await this.#chaindataProvider.evmNetworks()).map(evmNetwork => [evmNetwork.id, evmNetwork]));
76
- const allEvmTokens = {};
77
- const evmNetworkConcurrency = 10;
78
- await PromisePool.withConcurrency(evmNetworkConcurrency).for(evmNetworkIds).process(async evmNetworkId => {
79
- const evmNetwork = evmNetworks.get(evmNetworkId);
80
- if (!evmNetwork) return;
81
- for (const mod of this.#balanceModules.filter(m => m.type.startsWith("evm-"))) {
82
- const balancesConfig = (evmNetwork.balancesConfig ?? []).find(({
83
- moduleType
84
- }) => moduleType === mod.type);
85
- const moduleConfig = balancesConfig?.moduleConfig ?? {};
86
-
87
- // chainMeta arg only needs the isTestnet property, let's save a db roundtrip for now
88
- const isTestnet = evmNetwork.isTestnet ?? false;
89
- const tokens = await mod.fetchEvmChainTokens(evmNetworkId, {
90
- isTestnet
91
- }, moduleConfig);
92
- for (const [tokenId, token] of Object.entries(tokens)) allEvmTokens[tokenId] = token;
93
- }
94
- });
95
- await this.#chaindataProvider.updateEvmNetworkTokens(Object.values(allEvmTokens));
96
- }
97
- }
98
-
99
- var packageJson = {
100
- name: "@talismn/balances"};
62
+ var pkg = {
63
+ name: "@talismn/balances",
64
+ version: "0.0.0-pr2075-20250703111149"};
101
65
 
102
- var log = anylogger(packageJson.name);
66
+ var log = anylogger(pkg.name);
103
67
 
104
68
  function excludeFromTransferableAmount(locks) {
105
69
  if (typeof locks === "string") return BigInt(locks);
@@ -309,15 +273,13 @@ class Balances {
309
273
  return new SumBalancesFormatter(this);
310
274
  }
311
275
  }
312
- const isBalanceEvm = balance => "evmNetworkId" in balance;
313
276
  const getBalanceId = balance => {
314
277
  const {
315
278
  source,
316
279
  address,
317
280
  tokenId
318
281
  } = balance;
319
- const locationId = isBalanceEvm(balance) ? balance.evmNetworkId : balance.chainId;
320
- return [source, address, locationId, tokenId].filter(isTruthy).join("::");
282
+ return [source, address, tokenId].join("::");
321
283
  };
322
284
 
323
285
  /**
@@ -379,23 +341,17 @@ class Balance {
379
341
  get address() {
380
342
  return this.#storage.address;
381
343
  }
382
- get chainId() {
383
- return isBalanceEvm(this.#storage) ? undefined : this.#storage.chainId;
384
- }
385
- get chain() {
386
- return this.#db?.chains && this.chainId && this.#db?.chains[this.chainId] || null;
344
+ get networkId() {
345
+ return this.#storage.networkId;
387
346
  }
388
- get evmNetworkId() {
389
- return isBalanceEvm(this.#storage) ? this.#storage.evmNetworkId : undefined;
390
- }
391
- get evmNetwork() {
392
- return this.#db?.evmNetworks && this.evmNetworkId && this.#db?.evmNetworks[this.evmNetworkId] || null;
347
+ get network() {
348
+ return this.#db?.networks?.[this.networkId] || null;
393
349
  }
394
350
  get tokenId() {
395
351
  return this.#storage.tokenId;
396
352
  }
397
353
  get token() {
398
- return this.#db?.tokens && this.#db?.tokens[this.tokenId] || null;
354
+ return this.#db?.tokens?.[this.tokenId] || null;
399
355
  }
400
356
  get decimals() {
401
357
  return this.token?.decimals || null;
@@ -408,9 +364,9 @@ class Balance {
408
364
  //
409
365
  // This means that those rates are always available for calculating the uniswapv2 rates,
410
366
  // regardless of whether or not the underlying erc20s are actually in chaindata and enabled.
411
- if (this.isSource("evm-uniswapv2") && this.token?.type === "evm-uniswapv2" && this.evmNetworkId) {
412
- const tokenId0 = evmErc20TokenId$1(this.evmNetworkId, this.token.tokenAddress0);
413
- const tokenId1 = evmErc20TokenId$1(this.evmNetworkId, this.token.tokenAddress1);
367
+ if (this.isSource("evm-uniswapv2") && this.token?.type === "evm-uniswapv2") {
368
+ const tokenId0 = evmErc20TokenId(this.networkId, this.token.tokenAddress0);
369
+ const tokenId1 = evmErc20TokenId(this.networkId, this.token.tokenAddress1);
414
370
  const decimals = this.token.decimals;
415
371
  const decimals0 = this.token.decimals0;
416
372
  const decimals1 = this.token.decimals1;
@@ -489,9 +445,7 @@ class Balance {
489
445
  const nomPoolStakedPlancks = this.locks.some(lock => lock.source === "substrate-native-holds" && lock.label === "DelegatedStaking") ? 0n : this.nompools.map(({
490
446
  amount
491
447
  }) => amount.planck).reduce((a, b) => a + b, 0n);
492
- return this.#format(this.free.planck + this.reserved.planck + nomPoolStakedPlancks + this.crowdloans.map(({
493
- amount
494
- }) => amount.planck).reduce((a, b) => a + b, 0n) + this.subtensor.map(({
448
+ return this.#format(this.free.planck + this.reserved.planck + nomPoolStakedPlancks + this.subtensor.map(({
495
449
  amount
496
450
  }) => amount.planck).reduce((a, b) => a + b, 0n) + includeInTotalExtraAmount(extra));
497
451
  }
@@ -527,9 +481,6 @@ class Balance {
527
481
  get locks() {
528
482
  return this.getValue("locked");
529
483
  }
530
- get crowdloans() {
531
- return this.getValue("crowdloan");
532
- }
533
484
  get nompools() {
534
485
  return this.getValue("nompool");
535
486
  }
@@ -608,7 +559,7 @@ class Balance {
608
559
  const nomPoolStakedPlancks = this.locks.some(lock => lock.source === "substrate-native-holds" && lock.label === "DelegatedStaking") ? 0n : this.nompools.map(({
609
560
  amount
610
561
  }) => amount.planck).reduce((a, b) => a + b, 0n);
611
- const otherUnavailable = nomPoolStakedPlancks + this.crowdloans.reduce((total, each) => total + each.amount.planck, 0n) + this.subtensor.reduce((total, each) => total + each.amount.planck, 0n);
562
+ const otherUnavailable = nomPoolStakedPlancks + this.subtensor.reduce((total, each) => total + each.amount.planck, 0n);
612
563
  return this.#format(baseUnavailable + otherUnavailable);
613
564
  }
614
565
 
@@ -843,10 +794,6 @@ const filterMirrorTokens = (balance, i, balances) => {
843
794
  return !mirrorOf || !balances.find(b => b.tokenId === mirrorOf);
844
795
  };
845
796
 
846
- // TODO: Move this into a common module which can then be imported both here and into EvmErc20Module
847
- // We can't import this directly from EvmErc20Module because then we'd have a circular dependency
848
- const evmErc20TokenId$1 = (chainId, tokenContractAddress) => `${chainId}-evm-erc20-${tokenContractAddress}`.toLowerCase();
849
-
850
797
  /**
851
798
  * `BalanceTypes` is an automatically determined sub-selection of `PluginBalanceTypes`.
852
799
  *
@@ -877,7 +824,6 @@ const getValueId = amount => {
877
824
  const getMetaId = () => {
878
825
  const meta = amount.meta;
879
826
  if (!meta) return "";
880
- if (amount.type === "crowdloan") return meta.paraId?.toString() ?? "";
881
827
  if (amount.type === "nompool") return meta.poolId?.toString() ?? "";
882
828
  if (amount.type === "subtensor") {
883
829
  const {
@@ -901,10 +847,9 @@ const getValueId = amount => {
901
847
  const deriveMiniMetadataId = ({
902
848
  source,
903
849
  chainId,
904
- specName,
905
850
  specVersion,
906
- balancesConfig
907
- }) => u8aToHex(xxhashAsU8a(new TextEncoder().encode(`${source}${chainId}${specName}${specVersion}${balancesConfig}`), 64), undefined, false);
851
+ libVersion
852
+ }) => u8aToHex(xxhashAsU8a(new TextEncoder().encode(`${source}${chainId}${specVersion}${libVersion}`), 64), undefined, false);
908
853
 
909
854
  // for DB version 3, Wallet version 1.21.0
910
855
  const upgradeRemoveSymbolFromNativeTokenId = async tx => {
@@ -999,230 +944,9 @@ class TalismanBalancesDatabase extends Dexie {
999
944
  }
1000
945
  const db = new TalismanBalancesDatabase();
1001
946
 
1002
- const minimumHydrationInterval = 300_000; // 300_000ms = 300s = 5 minutes
1003
-
1004
- /**
1005
- * A substrate dapp needs access to a set of types when it wants to communicate with a blockchain node.
1006
- *
1007
- * These types are used to encode requests & decode responses via the SCALE codec.
1008
- * Each chain generally has its own set of types.
1009
- *
1010
- * Substrate provides a construct to retrieve these types from a blockchain node.
1011
- * The chain metadata.
1012
- *
1013
- * The metadata includes the types required for any communication with the chain,
1014
- * including lots of methods which are not relevant to balance fetching.
1015
- *
1016
- * As such, the metadata can clock in at around 1-2MB per chain, which is a lot of storage
1017
- * for browser-based dapps which want to connect to lots of chains.
1018
- *
1019
- * By utilizing the wonderful [scale-ts](https://github.com/unstoppablejs/unstoppablejs/tree/main/packages/scale-ts#readme) library,
1020
- * we can trim the chain metadata down so that it only includes the types we need for balance fetching.
1021
- *
1022
- * Each balance module has a function to do just that, `BalanceModule::fetchSubstrateChainMeta`.
1023
- *
1024
- * But, we only want to run this operation when necessary.
1025
- *
1026
- * The purpose of this class, `MiniMetadataUpdater`, is to maintain a local cache of
1027
- * trimmed-down metadatas, which we'll refer to as `MiniMetadatas`.
1028
- */
1029
- class MiniMetadataUpdater {
1030
- #lastHydratedMiniMetadatasAt = 0;
1031
- #lastHydratedCustomChainsAt = 0;
1032
- #chainConnectors;
1033
- #chaindataProvider;
1034
- #balanceModules;
1035
- constructor(chainConnectors, chaindataProvider, balanceModules) {
1036
- this.#chainConnectors = chainConnectors;
1037
- this.#chaindataProvider = chaindataProvider;
1038
- this.#balanceModules = balanceModules;
1039
- }
1040
-
1041
- /** Subscribe to the metadata for a chain */
1042
- subscribe(chainId) {
1043
- return from(liveQuery(() => db.miniMetadatas.filter(m => m.chainId === chainId).toArray().then(array => array[0])));
1044
- }
1045
- async update(chainIds) {
1046
- await this.updateSubstrateChains(chainIds);
1047
- }
1048
- async statuses(chains) {
1049
- const ids = await db.miniMetadatas.orderBy("id").primaryKeys();
1050
- const wantedIdsByChain = new Map(chains.flatMap(({
1051
- id: chainId,
1052
- specName,
1053
- specVersion,
1054
- balancesConfig
1055
- }) => {
1056
- if (specName === null) return [];
1057
- if (specVersion === null) return [];
1058
- return [[chainId, this.#balanceModules.filter(m => m.type.startsWith("substrate-")).map(({
1059
- type: source
1060
- }) => deriveMiniMetadataId({
1061
- source,
1062
- chainId: chainId,
1063
- specName: specName,
1064
- specVersion: specVersion,
1065
- balancesConfig: JSON.stringify((balancesConfig ?? []).find(({
1066
- moduleType
1067
- }) => moduleType === source)?.moduleConfig ?? {})
1068
- }))]];
1069
- }));
1070
- const statusesByChain = new Map(Array.from(wantedIdsByChain.entries()).map(([chainId, wantedIds]) => [chainId, wantedIds.every(wantedId => ids.includes(wantedId)) ? "good" : "none"]));
1071
- return {
1072
- wantedIdsByChain,
1073
- statusesByChain
1074
- };
1075
- }
1076
- async hydrateFromChaindata() {
1077
- const now = Date.now();
1078
- if (now - this.#lastHydratedMiniMetadatasAt < minimumHydrationInterval) return false;
1079
- const dbHasMiniMetadatas = (await db.miniMetadatas.count()) > 0;
1080
- try {
1081
- try {
1082
- // TODO: Move `fetchMiniMetadatas` into this package,
1083
- // so that we don't have a circular import between `@talismn/balances` and `@talismn/chaindata-provider`.
1084
- var miniMetadatas = await fetchMiniMetadatas(); // eslint-disable-line no-var
1085
- if (miniMetadatas.length <= 0) throw new Error("Ignoring empty chaindata miniMetadatas response");
1086
- } catch (error) {
1087
- if (dbHasMiniMetadatas) throw error;
1088
- // On first start-up (db is empty), if we fail to fetch miniMetadatas then we should
1089
- // initialize the DB with the list of miniMetadatas inside our init/mini-metadatas.json file.
1090
- // This data will represent a relatively recent copy of what's in chaindata,
1091
- // which will be better for our users than to have nothing at all.
1092
- var miniMetadatas = await fetchInitMiniMetadatas(); // eslint-disable-line no-var
1093
- }
1094
- await db.miniMetadatas.bulkPut(miniMetadatas);
1095
- this.#lastHydratedMiniMetadatasAt = now;
1096
- return true;
1097
- } catch (error) {
1098
- log.warn(`Failed to hydrate miniMetadatas from chaindata`, error);
1099
- return false;
1100
- }
1101
- }
1102
- async hydrateCustomChains() {
1103
- const now = Date.now();
1104
- if (now - this.#lastHydratedCustomChainsAt < minimumHydrationInterval) return false;
1105
- const chains = await this.#chaindataProvider.chains();
1106
- const customChains = chains.filter(chain => "isCustom" in chain && chain.isCustom);
1107
- const updatedCustomChains = [];
1108
- const concurrency = 4;
1109
- (await PromisePool.withConcurrency(concurrency).for(customChains).process(async customChain => {
1110
- const send = (method, params) => this.#chainConnectors.substrate?.send(customChain.id, method, params);
1111
- const [genesisHash, runtimeVersion, chainName, chainType] = await Promise.all([send("chain_getBlockHash", [0]), send("state_getRuntimeVersion", []), send("system_chain", []), send("system_chainType", [])]);
1112
-
1113
- // deconstruct rpc data
1114
- const {
1115
- specName,
1116
- implName
1117
- } = runtimeVersion;
1118
- const specVersion = String(runtimeVersion.specVersion);
1119
- const changed = customChain.genesisHash !== genesisHash || customChain.chainName !== chainName || !isEqual(customChain.chainType, chainType) || customChain.implName !== implName || customChain.specName !== specName || customChain.specVersion !== specVersion;
1120
- if (!changed) return;
1121
- customChain.genesisHash = genesisHash;
1122
- customChain.chainName = chainName;
1123
- customChain.chainType = chainType;
1124
- customChain.implName = implName;
1125
- customChain.specName = specName;
1126
- customChain.specVersion = specVersion;
1127
- updatedCustomChains.push(customChain);
1128
- })).errors.forEach(error => log.error("Error hydrating custom chains", error));
1129
- if (updatedCustomChains.length > 0) {
1130
- await this.#chaindataProvider.transaction("rw", ["chains"], async () => {
1131
- for (const updatedCustomChain of updatedCustomChains) {
1132
- await this.#chaindataProvider.removeCustomChain(updatedCustomChain.id);
1133
- await this.#chaindataProvider.addCustomChain(updatedCustomChain);
1134
- }
1135
- });
1136
- }
1137
- if (updatedCustomChains.length > 0) this.#lastHydratedCustomChainsAt = now;
1138
- return true;
1139
- }
1140
- async updateSubstrateChains(chainIds) {
1141
- const chains = new Map((await this.#chaindataProvider.chains()).map(chain => [chain.id, chain]));
1142
- const filteredChains = chainIds.flatMap(chainId => chains.get(chainId) ?? []);
1143
- const ids = await db.miniMetadatas.orderBy("id").primaryKeys();
1144
- const {
1145
- wantedIdsByChain,
1146
- statusesByChain
1147
- } = await this.statuses(filteredChains);
1148
-
1149
- // clean up store
1150
- const wantedIds = Array.from(wantedIdsByChain.values()).flatMap(ids => ids);
1151
- const unwantedIds = ids.filter(id => !wantedIds.includes(id));
1152
- if (unwantedIds.length > 0) {
1153
- const chainIds = Array.from(new Set((await db.miniMetadatas.bulkGet(unwantedIds)).map(m => m?.chainId)));
1154
- log.info(`Pruning ${unwantedIds.length} miniMetadatas on chains ${chainIds.join(", ")}`);
1155
- await db.miniMetadatas.bulkDelete(unwantedIds);
1156
- }
1157
- const needUpdates = Array.from(statusesByChain.entries()).filter(([, status]) => status !== "good").map(([chainId]) => chainId);
1158
- if (needUpdates.length > 0) log.info(`${needUpdates.length} miniMetadatas need updates (${needUpdates.join(", ")})`);
1159
- const availableTokenLogos = await availableTokenLogoFilenames().catch(error => {
1160
- log.error("Failed to fetch available token logos", error);
1161
- return [];
1162
- });
1163
- const concurrency = 12;
1164
- (await PromisePool.withConcurrency(concurrency).for(needUpdates).process(async chainId => {
1165
- log.info(`Updating metadata for chain ${chainId}`);
1166
- const chain = chains.get(chainId);
1167
- if (!chain) return;
1168
- const {
1169
- specName,
1170
- specVersion
1171
- } = chain;
1172
- if (specName === null) return;
1173
- if (specVersion === null) return;
1174
- const fetchMetadata = async () => {
1175
- try {
1176
- return await fetchBestMetadata((method, params, isCacheable) => {
1177
- if (!this.#chainConnectors.substrate) throw new Error("Substrate connector is not available");
1178
- return this.#chainConnectors.substrate.send(chainId, method, params, isCacheable);
1179
- }, true // allow v14 fallback
1180
- );
1181
- } catch (err) {
1182
- log.warn(`Failed to fetch metadata for chain ${chainId}`);
1183
- return undefined;
1184
- }
1185
- };
1186
- const [metadataRpc, systemProperties] = await Promise.all([fetchMetadata(), this.#chainConnectors.substrate?.send(chainId, "system_properties", [])]);
1187
- for (const mod of this.#balanceModules.filter(m => m.type.startsWith("substrate-"))) {
1188
- const balancesConfig = (chain.balancesConfig ?? []).find(({
1189
- moduleType
1190
- }) => moduleType === mod.type);
1191
- const moduleConfig = balancesConfig?.moduleConfig ?? {};
1192
- const chainMeta = await mod.fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc, systemProperties);
1193
- const tokens = await mod.fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig);
1194
-
1195
- // update tokens in chaindata
1196
- await this.#chaindataProvider.updateChainTokens(chainId, mod.type, Object.values(tokens), availableTokenLogos);
1197
-
1198
- // update miniMetadatas
1199
- const {
1200
- miniMetadata: data,
1201
- metadataVersion: version,
1202
- ...extra
1203
- } = chainMeta ?? {};
1204
- await db.miniMetadatas.put({
1205
- id: deriveMiniMetadataId({
1206
- source: mod.type,
1207
- chainId,
1208
- specName,
1209
- specVersion,
1210
- balancesConfig: JSON.stringify(moduleConfig)
1211
- }),
1212
- source: mod.type,
1213
- chainId,
1214
- specName,
1215
- specVersion,
1216
- balancesConfig: JSON.stringify(moduleConfig),
1217
- // TODO: Standardise return value from `fetchSubstrateChainMeta`
1218
- version,
1219
- data,
1220
- extra: JSON.stringify(extra)
1221
- });
1222
- }
1223
- })).errors.forEach(error => log.error("Error updating chain metadata", error));
1224
- }
1225
- }
947
+ const TokenConfigBaseSchema = TokenBaseSchema.partial().omit({
948
+ id: true
949
+ });
1226
950
 
1227
951
  const erc20Abi = [{
1228
952
  constant: true,
@@ -1454,8 +1178,11 @@ const erc20Abi = [{
1454
1178
 
1455
1179
  const erc20BalancesAggregatorAbi = parseAbi(["struct AccountToken {address account; address token;}", "function balances(AccountToken[] memory accountTokens) public view returns (uint256[] memory)"]);
1456
1180
 
1457
- const moduleType$8 = "evm-erc20";
1458
- const evmErc20TokenId = (chainId, tokenContractAddress) => `${chainId}-evm-erc20-${tokenContractAddress}`.toLowerCase();
1181
+ const moduleType$7 = "evm-erc20";
1182
+ const EvmErc20TokenConfigSchema = z.strictObject({
1183
+ contractAddress: EvmErc20TokenSchema.shape.contractAddress,
1184
+ ...TokenConfigBaseSchema.shape
1185
+ });
1459
1186
  const EvmErc20Module = hydrate => {
1460
1187
  const {
1461
1188
  chainConnectors,
@@ -1479,38 +1206,36 @@ const EvmErc20Module = hydrate => {
1479
1206
  }, {});
1480
1207
  };
1481
1208
  const getModuleTokens = async () => {
1482
- return await chaindataProvider.tokensByIdForType(moduleType$8);
1209
+ return await chaindataProvider.getTokensMapById(moduleType$7);
1483
1210
  };
1484
1211
  const getErc20Aggregators = async () => {
1485
- const evmNetworks = await chaindataProvider.evmNetworks();
1486
- return Object.fromEntries(evmNetworks.filter(n => n.erc20aggregator).map(n => [n.id, n.erc20aggregator]));
1212
+ const evmNetworks = await chaindataProvider.getNetworks("ethereum");
1213
+ return Object.fromEntries(evmNetworks.filter(n => n.contracts?.Erc20Aggregator).map(n => [n.id, n.contracts.Erc20Aggregator]));
1487
1214
  };
1488
1215
  return {
1489
- ...DefaultBalanceModule(moduleType$8),
1216
+ ...DefaultBalanceModule(moduleType$7),
1490
1217
  /**
1491
1218
  * This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L280-L284).
1492
1219
  * In a future version of the balance libraries, we may build some kind of async scheduling system which will keep the chainmeta for each chain up to date without relying on a squid.
1493
1220
  */
1494
- async fetchEvmChainMeta(chainId) {
1495
- const isTestnet = (await chaindataProvider.evmNetworkById(chainId))?.isTestnet || false;
1221
+ async fetchEvmChainMeta(_chainId) {
1496
1222
  return {
1497
- isTestnet
1223
+ miniMetadata: null,
1224
+ extra: null
1498
1225
  };
1499
1226
  },
1500
1227
  /**
1501
1228
  * This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L338-L343).
1502
1229
  * In a future version of the balance libraries, we may build some kind of async scheduling system which will keep the list of tokens for each chain up to date without relying on a squid.
1503
1230
  */
1504
- async fetchEvmChainTokens(chainId, chainMeta, moduleConfig) {
1505
- const {
1506
- isTestnet
1507
- } = chainMeta;
1231
+ async fetchEvmChainTokens(chainId, _chainMeta, moduleConfig, tokens) {
1508
1232
  const chainTokens = {};
1509
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
1233
+ for (const tokenConfig of tokens ?? []) {
1510
1234
  const {
1511
1235
  contractAddress,
1512
1236
  symbol: contractSymbol,
1513
- decimals: contractDecimals
1237
+ decimals: contractDecimals,
1238
+ name: contractName
1514
1239
  } = tokenConfig;
1515
1240
  // TODO : in chaindata's build, filter out all tokens that don't have any of these
1516
1241
  if (!contractAddress || !contractSymbol || contractDecimals === undefined) {
@@ -1524,22 +1249,25 @@ const EvmErc20Module = hydrate => {
1524
1249
  const token = {
1525
1250
  id,
1526
1251
  type: "evm-erc20",
1527
- isTestnet,
1252
+ platform: "ethereum",
1528
1253
  isDefault: tokenConfig.isDefault ?? true,
1529
1254
  symbol,
1530
1255
  decimals,
1531
- logo: tokenConfig?.logo || githubTokenLogoUrl(id),
1256
+ name: contractName ?? symbol,
1257
+ logo: tokenConfig?.logo,
1532
1258
  contractAddress,
1533
- evmNetwork: {
1534
- id: chainId
1535
- }
1259
+ networkId: chainId
1536
1260
  };
1537
1261
  if (tokenConfig?.symbol) token.symbol = tokenConfig?.symbol;
1538
1262
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
1539
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
1540
1263
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
1541
1264
  if (tokenConfig?.noDiscovery) token.noDiscovery = tokenConfig?.noDiscovery;
1542
- chainTokens[token.id] = token;
1265
+ const validation = EvmErc20TokenSchema.safeParse(token);
1266
+ if (validation.success) {
1267
+ chainTokens[token.id] = token;
1268
+ } else {
1269
+ log.warn("Ignoring invalid token", token.id, validation.error.message, validation.error.issues);
1270
+ }
1543
1271
  }
1544
1272
  return chainTokens;
1545
1273
  },
@@ -1551,18 +1279,18 @@ const EvmErc20Module = hydrate => {
1551
1279
  const subscriptionInterval = 6_000; // 6_000ms == 6 seconds
1552
1280
  const initDelay = 1_500; // 1_500ms == 1.5 seconds
1553
1281
  const initialisingBalances = new Set();
1554
- const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.evmNetworkId));
1282
+ const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.networkId));
1555
1283
  const tokens = await getModuleTokens();
1556
1284
 
1557
1285
  // for chains with a zero balance we only call fetchBalances once every 5 subscriptionIntervals
1558
1286
  // if subscriptionInterval is 6 seconds, this means we only poll chains with a zero balance every 30 seconds
1559
1287
  let zeroBalanceSubscriptionIntervalCounter = 0;
1560
- const evmNetworks = await chaindataProvider.evmNetworksById();
1288
+ const evmNetworks = await chaindataProvider.getNetworksMapById("ethereum");
1561
1289
  const ethAddressesByToken = Object.fromEntries(Object.entries(addressesByToken).map(([tokenId, addresses]) => {
1562
1290
  const ethAddresses = addresses.filter(isEthereumAddress);
1563
1291
  if (ethAddresses.length === 0) return null;
1564
1292
  const token = tokens[tokenId];
1565
- const evmNetworkId = token.evmNetwork?.id;
1293
+ const evmNetworkId = token.networkId;
1566
1294
  if (!evmNetworkId) return null;
1567
1295
  return [tokenId, ethAddresses];
1568
1296
  }).filter(x => Boolean(x)));
@@ -1688,20 +1416,17 @@ const fetchBalances$3 = async (evmChainConnector, tokenAddressesByNetwork, erc20
1688
1416
  results: [],
1689
1417
  errors: []
1690
1418
  };
1691
- await Promise.all(Object.entries(tokenAddressesByNetwork).map(async ([evmNetworkId, networkParams]) => {
1692
- const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(evmNetworkId);
1693
- if (!publicClient) throw new EvmErc20NetworkError(`Could not get rpc provider for evm network ${evmNetworkId}`, evmNetworkId);
1694
- const balances = await getEvmTokenBalances(publicClient, networkParams, result.errors, erc20Aggregators[evmNetworkId]);
1419
+ await Promise.all(Object.entries(tokenAddressesByNetwork).map(async ([networkId, networkParams]) => {
1420
+ const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(networkId);
1421
+ if (!publicClient) throw new EvmErc20NetworkError(`Could not get rpc provider for evm network ${networkId}`, networkId);
1422
+ const balances = await getEvmTokenBalances(publicClient, networkParams, result.errors, erc20Aggregators[networkId]);
1695
1423
 
1696
1424
  // consider only non null balances in the results
1697
1425
  result.results.push(...balances.filter(isTruthy).map((free, i) => ({
1698
1426
  source: "evm-erc20",
1699
1427
  status: "live",
1700
1428
  address: networkParams[i].address,
1701
- multiChainId: {
1702
- evmChainId: evmNetworkId
1703
- },
1704
- evmNetworkId,
1429
+ networkId,
1705
1430
  tokenId: networkParams[i].token.id,
1706
1431
  value: free
1707
1432
  })));
@@ -1772,7 +1497,7 @@ function groupAddressesByTokenByEvmNetwork$1(addressesByToken, tokens) {
1772
1497
  log.error(`Token ${tokenId} not found`);
1773
1498
  return byChain;
1774
1499
  }
1775
- const chainId = token.evmNetwork?.id;
1500
+ const chainId = token.networkId;
1776
1501
  if (!chainId) {
1777
1502
  log.error(`Token ${tokenId} has no evm network`);
1778
1503
  return byChain;
@@ -1785,67 +1510,38 @@ function groupAddressesByTokenByEvmNetwork$1(addressesByToken, tokens) {
1785
1510
 
1786
1511
  const abiMulticall = parseAbi(["struct Call { address target; bytes callData; }", "struct Call3 { address target; bool allowFailure; bytes callData; }", "struct Call3Value { address target; bool allowFailure; uint256 value; bytes callData; }", "struct Result { bool success; bytes returnData; }", "function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData)", "function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData)", "function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData)", "function blockAndAggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData)", "function getBasefee() view returns (uint256 basefee)", "function getBlockHash(uint256 blockNumber) view returns (bytes32 blockHash)", "function getBlockNumber() view returns (uint256 blockNumber)", "function getChainId() view returns (uint256 chainid)", "function getCurrentBlockCoinbase() view returns (address coinbase)", "function getCurrentBlockDifficulty() view returns (uint256 difficulty)", "function getCurrentBlockGasLimit() view returns (uint256 gaslimit)", "function getCurrentBlockTimestamp() view returns (uint256 timestamp)", "function getEthBalance(address addr) view returns (uint256 balance)", "function getLastBlockHash() view returns (bytes32 blockHash)", "function tryAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (Result[] memory returnData)", "function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData)"]);
1787
1512
 
1788
- const moduleType$7 = "evm-native";
1789
- const evmNativeTokenId = chainId => `${chainId}-evm-native`.toLowerCase().replace(/ /g, "-");
1790
- const getEvmNetworkIdFromTokenId = tokenId => {
1791
- const evmNetworkId = tokenId.split("-")[0];
1792
- if (!evmNetworkId) throw new Error(`Can't detect chainId for token ${tokenId}`);
1793
- return evmNetworkId;
1794
- };
1513
+ const moduleType$6 = "evm-native";
1514
+ const EvmNativeTokenConfigSchema = TokenConfigBaseSchema;
1795
1515
  const EvmNativeModule = hydrate => {
1796
1516
  const {
1797
1517
  chainConnectors,
1798
1518
  chaindataProvider
1799
1519
  } = hydrate;
1800
1520
  const getModuleTokens = async () => {
1801
- return await chaindataProvider.tokensByIdForType(moduleType$7);
1521
+ return await chaindataProvider.getTokensMapById(moduleType$6);
1802
1522
  };
1803
1523
  return {
1804
- ...DefaultBalanceModule(moduleType$7),
1524
+ ...DefaultBalanceModule(moduleType$6),
1805
1525
  get tokens() {
1806
- return chaindataProvider.tokensByIdForType(moduleType$7);
1526
+ return chaindataProvider.getTokensMapById(moduleType$6);
1807
1527
  },
1808
1528
  /**
1809
1529
  * This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L280-L284).
1810
1530
  * In a future version of the balance libraries, we may build some kind of async scheduling system which will keep the chainmeta for each chain up to date without relying on a squid.
1811
1531
  */
1812
- async fetchEvmChainMeta(chainId) {
1813
- const isTestnet = (await chaindataProvider.evmNetworkById(chainId))?.isTestnet || false;
1532
+ async fetchEvmChainMeta(_chainId) {
1814
1533
  return {
1815
- isTestnet
1534
+ miniMetadata: null,
1535
+ extra: null
1816
1536
  };
1817
1537
  },
1818
1538
  /**
1819
1539
  * This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L338-L343).
1820
1540
  * In a future version of the balance libraries, we may build some kind of async scheduling system which will keep the list of tokens for each chain up to date without relying on a squid.
1821
1541
  */
1822
- async fetchEvmChainTokens(chainId, chainMeta, moduleConfig) {
1823
- const {
1824
- isTestnet
1825
- } = chainMeta;
1826
- const symbol = moduleConfig?.symbol ?? "ETH";
1827
- const decimals = typeof moduleConfig?.decimals === "number" ? moduleConfig.decimals : 18;
1828
- const id = evmNativeTokenId(chainId);
1829
- const nativeToken = {
1830
- id,
1831
- type: "evm-native",
1832
- isTestnet,
1833
- isDefault: true,
1834
- symbol,
1835
- decimals,
1836
- logo: moduleConfig?.logo || githubTokenLogoUrl(id),
1837
- evmNetwork: {
1838
- id: chainId
1839
- }
1840
- };
1841
- if (moduleConfig?.symbol) nativeToken.symbol = moduleConfig?.symbol;
1842
- if (moduleConfig?.coingeckoId) nativeToken.coingeckoId = moduleConfig?.coingeckoId;
1843
- if (moduleConfig?.dcentName) nativeToken.dcentName = moduleConfig?.dcentName;
1844
- if (moduleConfig?.mirrorOf) nativeToken.mirrorOf = moduleConfig?.mirrorOf;
1845
- if (moduleConfig?.noDiscovery) nativeToken.noDiscovery = moduleConfig?.noDiscovery;
1846
- return {
1847
- [nativeToken.id]: nativeToken
1848
- };
1542
+ async fetchEvmChainTokens() {
1543
+ // token is currently generated in chaindata from the EthNetworkConfig["nativeCurrency"] field
1544
+ return {};
1849
1545
  },
1850
1546
  async subscribeBalances({
1851
1547
  addressesByToken,
@@ -1856,11 +1552,11 @@ const EvmNativeModule = hydrate => {
1856
1552
  const initDelay = 500; // 500ms == 0.5 seconds
1857
1553
 
1858
1554
  const tokens = await getModuleTokens();
1859
- const ethAddressesByToken = Object.fromEntries(Object.entries(addressesByToken).map(([tokenId, addresses]) => {
1555
+ const ethAddressesByToken = fromPairs(toPairs(addressesByToken).map(([tokenId, addresses]) => {
1860
1556
  const ethAddresses = addresses.filter(isEthereumAddress);
1861
1557
  if (ethAddresses.length === 0) return null;
1862
1558
  const token = tokens[tokenId];
1863
- const evmNetworkId = token.evmNetwork?.id;
1559
+ const evmNetworkId = token.networkId;
1864
1560
  if (!evmNetworkId) return null;
1865
1561
  return [tokenId, ethAddresses];
1866
1562
  }).filter(x => Boolean(x)));
@@ -1870,16 +1566,16 @@ const EvmNativeModule = hydrate => {
1870
1566
  let zeroBalanceSubscriptionIntervalCounter = 0;
1871
1567
 
1872
1568
  // setup initialising balances for all active evm networks
1873
- const activeEvmNetworkIds = Object.keys(ethAddressesByToken).map(getEvmNetworkIdFromTokenId);
1569
+ const activeEvmNetworkIds = keys(ethAddressesByToken).map(networkIdFromTokenId);
1874
1570
  const initialisingBalances = new Set(activeEvmNetworkIds);
1875
- const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.evmNetworkId));
1571
+ const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.networkId));
1876
1572
  const poll = async () => {
1877
1573
  if (!subscriptionActive) return;
1878
1574
  zeroBalanceSubscriptionIntervalCounter = (zeroBalanceSubscriptionIntervalCounter + 1) % 5;
1879
1575
  try {
1880
1576
  // fetch balance for each network sequentially to prevent creating a big queue of http requests (browser can only handle 2 at a time)
1881
1577
  for (const [tokenId, addresses] of Object.entries(ethAddressesByToken)) {
1882
- const evmNetworkId = getEvmNetworkIdFromTokenId(tokenId);
1578
+ const evmNetworkId = networkIdFromTokenId(tokenId);
1883
1579
 
1884
1580
  // a zero balance network is one that has initialised and does not have a positive balance
1885
1581
  const isZeroBalanceNetwork = !initialisingBalances.has(evmNetworkId) && !positiveBalanceNetworks.has(evmNetworkId);
@@ -1899,10 +1595,10 @@ const EvmNativeModule = hydrate => {
1899
1595
  log.error(balance.message, balance.networkId);
1900
1596
  initialisingBalances.delete(balance.networkId);
1901
1597
  } else {
1902
- if (balance.evmNetworkId) {
1903
- initialisingBalances.delete(balance.evmNetworkId);
1598
+ if (balance.networkId) {
1599
+ initialisingBalances.delete(balance.networkId);
1904
1600
  if (BigInt(balance.value) > 0n) {
1905
- positiveBalanceNetworks.add(balance.evmNetworkId);
1601
+ positiveBalanceNetworks.add(balance.networkId);
1906
1602
  }
1907
1603
  resultBalances.push(balance);
1908
1604
  }
@@ -1952,23 +1648,20 @@ const fetchBalances$2 = async (evmChainConnector, addressesByToken, tokens) => {
1952
1648
  if (!evmChainConnector) throw new Error(`This module requires an evm chain connector`);
1953
1649
  return Promise.all(Object.entries(addressesByToken).map(async ([tokenId, addresses]) => {
1954
1650
  const token = tokens[tokenId];
1955
- const evmNetworkId = token.evmNetwork?.id;
1956
- if (!evmNetworkId) throw new Error(`Token ${token.id} has no evm network`);
1957
- const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(evmNetworkId);
1958
- if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${evmNetworkId}`);
1651
+ const networkId = token.networkId;
1652
+ if (!networkId) throw new Error(`Token ${token.id} has no evm network`);
1653
+ const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(networkId);
1654
+ if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
1959
1655
 
1960
1656
  // fetch all balances
1961
1657
  const freeBalances = await getFreeBalances(publicClient, addresses);
1962
1658
  const balanceResults = addresses.map((address, i) => {
1963
- if (freeBalances[i] === "error") return new EvmNativeBalanceError("Could not fetch balance ", evmNetworkId);
1659
+ if (freeBalances[i] === "error") return new EvmNativeBalanceError("Could not fetch balance ", networkId);
1964
1660
  return {
1965
1661
  source: "evm-native",
1966
1662
  status: "live",
1967
1663
  address: address,
1968
- multiChainId: {
1969
- evmChainId: evmNetworkId
1970
- },
1971
- evmNetworkId,
1664
+ networkId,
1972
1665
  tokenId,
1973
1666
  value: freeBalances[i].toString()
1974
1667
  };
@@ -2021,7 +1714,7 @@ async function getFreeBalances(publicClient, addresses) {
2021
1714
  });
2022
1715
  } catch (err) {
2023
1716
  const errorMessage = hasOwnProperty(err, "shortMessage") ? err.shortMessage : hasOwnProperty(err, "message") ? err.message : err;
2024
- log.warn(`Failed to get balance from chain ${publicClient.chain?.id} for ${ethAddresses.length} addresses: ${errorMessage}`);
1717
+ log.warn(`Failed to get balance from chain ${publicClient.chain.id} for ${ethAddresses.length} addresses: ${errorMessage}`);
2025
1718
  return ethAddresses.map(() => "error");
2026
1719
  }
2027
1720
  }
@@ -2589,8 +2282,20 @@ const uniswapV2PairAbi = [{
2589
2282
  type: "function"
2590
2283
  }];
2591
2284
 
2592
- const moduleType$6 = "evm-uniswapv2";
2593
- const evmUniswapV2TokenId = (chainId, contractAddress) => `${chainId}-evm-uniswapv2-${contractAddress}`.toLowerCase();
2285
+ const moduleType$5 = "evm-uniswapv2";
2286
+ const EvmUniswapV2TokenConfigSchema = z.strictObject({
2287
+ contractAddress: EvmUniswapV2TokenSchema.shape.contractAddress,
2288
+ ...TokenConfigBaseSchema.shape,
2289
+ // on chaindata side these are fetched by a dedicated task
2290
+ symbol0: EvmUniswapV2TokenSchema.shape.symbol0.optional(),
2291
+ symbol1: EvmUniswapV2TokenSchema.shape.symbol1.optional(),
2292
+ decimals0: EvmUniswapV2TokenSchema.shape.decimals0.optional(),
2293
+ decimals1: EvmUniswapV2TokenSchema.shape.decimals1.optional(),
2294
+ tokenAddress0: EvmUniswapV2TokenSchema.shape.tokenAddress0.optional(),
2295
+ tokenAddress1: EvmUniswapV2TokenSchema.shape.tokenAddress1.optional(),
2296
+ coingeckoId0: EvmUniswapV2TokenSchema.shape.coingeckoId0.optional(),
2297
+ coingeckoId1: EvmUniswapV2TokenSchema.shape.coingeckoId1.optional()
2298
+ });
2594
2299
  const EvmUniswapV2Module = hydrate => {
2595
2300
  const {
2596
2301
  chainConnectors,
@@ -2599,19 +2304,16 @@ const EvmUniswapV2Module = hydrate => {
2599
2304
  const chainConnector = chainConnectors.evm;
2600
2305
  assert(chainConnector, "This module requires an evm chain connector");
2601
2306
  return {
2602
- ...DefaultBalanceModule(moduleType$6),
2603
- async fetchEvmChainMeta(chainId) {
2604
- const isTestnet = (await chaindataProvider.evmNetworkById(chainId))?.isTestnet || false;
2307
+ ...DefaultBalanceModule(moduleType$5),
2308
+ async fetchEvmChainMeta(_chainId) {
2605
2309
  return {
2606
- isTestnet
2310
+ miniMetadata: null,
2311
+ extra: null
2607
2312
  };
2608
2313
  },
2609
- async fetchEvmChainTokens(chainId, chainMeta, moduleConfig) {
2610
- const {
2611
- isTestnet
2612
- } = chainMeta;
2314
+ async fetchEvmChainTokens(chainId, _chainMeta, moduleConfig, pools) {
2613
2315
  const tokens = {};
2614
- for (const tokenConfig of moduleConfig?.pools ?? []) {
2316
+ for (const tokenConfig of pools ?? []) {
2615
2317
  const {
2616
2318
  contractAddress,
2617
2319
  decimals,
@@ -2622,7 +2324,8 @@ const EvmUniswapV2Module = hydrate => {
2622
2324
  tokenAddress0,
2623
2325
  tokenAddress1,
2624
2326
  coingeckoId0,
2625
- coingeckoId1
2327
+ coingeckoId1,
2328
+ name
2626
2329
  } = tokenConfig;
2627
2330
  if (!contractAddress || decimals === undefined || symbol0 === undefined || decimals0 === undefined || symbol1 === undefined || decimals1 === undefined || tokenAddress0 === undefined || tokenAddress1 === undefined) {
2628
2331
  log.warn("ignoring token on chain %s", chainId, tokenConfig);
@@ -2632,11 +2335,12 @@ const EvmUniswapV2Module = hydrate => {
2632
2335
  const token = {
2633
2336
  id,
2634
2337
  type: "evm-uniswapv2",
2635
- isTestnet,
2338
+ platform: "ethereum",
2636
2339
  isDefault: tokenConfig.isDefault ?? false,
2637
2340
  symbol: `${symbol0 ?? "UNKNOWN"}/${symbol1 ?? "UNKNOWN"}`,
2341
+ name: name ?? `${symbol0 ?? "UNKNOWN"}/${symbol1 ?? "UNKNOWN"}`,
2638
2342
  decimals,
2639
- logo: tokenConfig?.logo || githubTokenLogoUrl("uniswap"),
2343
+ logo: tokenConfig?.logo || getGithubTokenLogoUrl("uniswap"),
2640
2344
  symbol0,
2641
2345
  decimals0,
2642
2346
  symbol1,
@@ -2646,13 +2350,10 @@ const EvmUniswapV2Module = hydrate => {
2646
2350
  tokenAddress1,
2647
2351
  coingeckoId0,
2648
2352
  coingeckoId1,
2649
- evmNetwork: {
2650
- id: chainId
2651
- }
2353
+ networkId: chainId
2652
2354
  };
2653
2355
  if (tokenConfig?.symbol) token.symbol = tokenConfig?.symbol;
2654
2356
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
2655
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
2656
2357
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
2657
2358
  if (tokenConfig?.noDiscovery) token.noDiscovery = tokenConfig?.noDiscovery;
2658
2359
  tokens[token.id] = token;
@@ -2668,9 +2369,9 @@ const EvmUniswapV2Module = hydrate => {
2668
2369
  const initDelay = 1_500; // 1_500ms == 1.5 seconds
2669
2370
 
2670
2371
  const initialBalancesByNetwork = initialBalances?.reduce((result, b) => {
2671
- if (!b.evmNetworkId) return result;
2672
- if (!result[b.evmNetworkId]) result[b.evmNetworkId] = {};
2673
- result[b.evmNetworkId][getBalanceId(b)] = b;
2372
+ if (!b.networkId) return result;
2373
+ if (!result[b.networkId]) result[b.networkId] = {};
2374
+ result[b.networkId][getBalanceId(b)] = b;
2674
2375
  return result;
2675
2376
  }, {});
2676
2377
  const cache = new Map(Object.entries(initialBalancesByNetwork ?? {}));
@@ -2678,8 +2379,8 @@ const EvmUniswapV2Module = hydrate => {
2678
2379
  // for chains with a zero balance we only call fetchBalances once every 5 subscriptionIntervals
2679
2380
  // if subscriptionInterval is 6 seconds, this means we only poll chains with a zero balance every 30 seconds
2680
2381
  let zeroBalanceSubscriptionIntervalCounter = 0;
2681
- const evmNetworks = await chaindataProvider.evmNetworksById();
2682
- const tokens = await chaindataProvider.tokensById();
2382
+ const evmNetworks = await chaindataProvider.getNetworksMapById("ethereum");
2383
+ const tokens = await chaindataProvider.getTokensMapById();
2683
2384
  const poll = async () => {
2684
2385
  if (!subscriptionActive) return;
2685
2386
  zeroBalanceSubscriptionIntervalCounter = (zeroBalanceSubscriptionIntervalCounter + 1) % 5;
@@ -2722,20 +2423,20 @@ const EvmUniswapV2Module = hydrate => {
2722
2423
  },
2723
2424
  async fetchBalances(addressesByToken) {
2724
2425
  if (!chainConnectors.evm) throw new Error(`This module requires an evm chain connector`);
2725
- const evmNetworks = await chaindataProvider.evmNetworksById();
2726
- const tokens = await chaindataProvider.tokensById();
2426
+ const evmNetworks = await chaindataProvider.getNetworksMapById("ethereum");
2427
+ const tokens = await chaindataProvider.getTokensMapById();
2727
2428
  return fetchBalances$1(chainConnectors.evm, evmNetworks, tokens, addressesByToken);
2728
2429
  }
2729
2430
  };
2730
2431
  };
2731
2432
  const fetchBalances$1 = async (evmChainConnector, evmNetworks, tokens, addressesByToken) => {
2732
2433
  const addressesByTokenGroupedByEvmNetwork = groupAddressesByTokenByEvmNetwork(addressesByToken, tokens);
2733
- const balances = (await Promise.allSettled(Object.entries(addressesByTokenGroupedByEvmNetwork).map(async ([evmNetworkId, addressesByToken]) => {
2434
+ const balances = (await Promise.allSettled(Object.entries(addressesByTokenGroupedByEvmNetwork).map(async ([networkId, addressesByToken]) => {
2734
2435
  if (!evmChainConnector) throw new Error(`This module requires an evm chain connector`);
2735
- const evmNetwork = evmNetworks[evmNetworkId];
2736
- if (!evmNetwork) throw new Error(`Evm network ${evmNetworkId} not found`);
2737
- const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(evmNetworkId);
2738
- if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${evmNetworkId}`);
2436
+ const evmNetwork = evmNetworks[networkId];
2437
+ if (!evmNetwork) throw new Error(`Evm network ${networkId} not found`);
2438
+ const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(networkId);
2439
+ if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
2739
2440
  const tokensAndAddresses = Object.entries(addressesByToken).reduce((tokensAndAddresses, [tokenId, addresses]) => {
2740
2441
  const token = tokens[tokenId];
2741
2442
  if (!token) {
@@ -2757,10 +2458,7 @@ const fetchBalances$1 = async (evmChainConnector, evmNetworks, tokens, addresses
2757
2458
  source: "evm-uniswapv2",
2758
2459
  status: "live",
2759
2460
  address: address,
2760
- multiChainId: {
2761
- evmChainId: evmNetwork.id
2762
- },
2763
- evmNetworkId,
2461
+ networkId,
2764
2462
  tokenId: token.id,
2765
2463
  values: await getPoolBalance(publicClient, token.contractAddress, address)
2766
2464
  }));
@@ -2796,7 +2494,7 @@ function groupAddressesByTokenByEvmNetwork(addressesByToken, tokens) {
2796
2494
  log.error(`Token ${tokenId} not found`);
2797
2495
  return byChain;
2798
2496
  }
2799
- const chainId = token.evmNetwork?.id;
2497
+ const chainId = token.networkId;
2800
2498
  if (!chainId) {
2801
2499
  log.error(`Token ${tokenId} has no evm network`);
2802
2500
  return byChain;
@@ -2871,6 +2569,170 @@ async function getPoolBalance(publicClient, contractAddress, accountAddress) {
2871
2569
  }
2872
2570
  }
2873
2571
 
2572
+ const libVersion = pkg.version;
2573
+
2574
+ // cache the promise so it can be shared across multiple calls
2575
+ const CACHE_GET_SPEC_VERSION = new Map();
2576
+ const fetchSpecVersion = async (chainConnector, networkId) => {
2577
+ const {
2578
+ specVersion
2579
+ } = await chainConnector.send(networkId, "state_getRuntimeVersion", [], true);
2580
+ return specVersion;
2581
+ };
2582
+
2583
+ /**
2584
+ * fetches the spec version of a network. current request is cached in case of multiple calls (all balance modules will kick in at once)
2585
+ */
2586
+ const getSpecVersion = async (chainConnector, networkId) => {
2587
+ if (CACHE_GET_SPEC_VERSION.has(networkId)) return CACHE_GET_SPEC_VERSION.get(networkId);
2588
+ const pResult = fetchSpecVersion(chainConnector, networkId);
2589
+ CACHE_GET_SPEC_VERSION.set(networkId, pResult);
2590
+ try {
2591
+ return await pResult;
2592
+ } catch (cause) {
2593
+ throw new Error(`Failed to fetch specVersion for network ${networkId}`, {
2594
+ cause
2595
+ });
2596
+ } finally {
2597
+ CACHE_GET_SPEC_VERSION.delete(networkId);
2598
+ }
2599
+ };
2600
+
2601
+ // share requests as all modules will call this at once
2602
+ const CACHE$1 = new Map();
2603
+ const getMetadataRpc = async (chainConnector, networkId) => {
2604
+ if (CACHE$1.has(networkId)) return CACHE$1.get(networkId);
2605
+ const pResult = fetchBestMetadata((method, params, isCacheable) => chainConnector.send(networkId, method, params, isCacheable, {
2606
+ expectErrors: true
2607
+ }), true // allow fallback to 14 as modules dont use any v15 or v16 specifics yet
2608
+ );
2609
+ CACHE$1.set(networkId, pResult);
2610
+ try {
2611
+ return await pResult;
2612
+ } catch (cause) {
2613
+ if (isAbortError(cause)) throw cause;
2614
+ throw new Error(`Failed to fetch metadataRpc for network ${networkId}`, {
2615
+ cause
2616
+ });
2617
+ } finally {
2618
+ CACHE$1.delete(networkId);
2619
+ }
2620
+ };
2621
+
2622
+ // share requests as all modules will call this at once
2623
+ const CACHE = new Map();
2624
+
2625
+ // ensures we dont fetch miniMetadatas on more than 4 chains at once
2626
+ const POOL = new PQueue({
2627
+ concurrency: 4
2628
+ });
2629
+ const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, specVersion, signal) => {
2630
+ if (CACHE.has(networkId)) return CACHE.get(networkId);
2631
+ if (!signal) log.warn("[miniMetadata] getMiniMetadatas called without signal, this may hang the updates", new Error("No signal provided") // this will show the stack trace
2632
+ );
2633
+ const pResult = POOL.add(() => fetchMiniMetadatas(chainConnector, chaindataProvider, networkId, specVersion), {
2634
+ signal
2635
+ });
2636
+ CACHE.set(networkId, pResult);
2637
+ try {
2638
+ return await pResult;
2639
+ } catch (cause) {
2640
+ if (isAbortError(cause)) throw cause;
2641
+ throw new Error(`Failed to fetch metadataRpc for network ${networkId}`, {
2642
+ cause
2643
+ });
2644
+ } finally {
2645
+ CACHE.delete(networkId);
2646
+ }
2647
+ };
2648
+ const DotBalanceModuleTypeSchema = z.keyof(DotNetworkBalancesConfigSchema);
2649
+ const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion, signal) => {
2650
+ const start = performance.now();
2651
+ log.info("[miniMetadata] fetching minimetadatas for %s", chainId);
2652
+ try {
2653
+ const metadataRpc = await getMetadataRpc(chainConnector, chainId);
2654
+ signal?.throwIfAborted();
2655
+ const chainConnectors = {
2656
+ substrate: chainConnector,
2657
+ evm: {} // wont be used but workarounds error for module creation
2658
+ };
2659
+ const modules = defaultBalanceModules.map(mod => mod({
2660
+ chainConnectors,
2661
+ chaindataProvider
2662
+ })).filter(mod => DotBalanceModuleTypeSchema.safeParse(mod.type).success);
2663
+ return Promise.all(modules.map(async mod => {
2664
+ const source = mod.type;
2665
+ const chain = await chaindataProvider.getNetworkById(chainId, "polkadot");
2666
+ const balancesConfig = chain?.balancesConfig?.[mod.type];
2667
+ const chainMeta = await mod.fetchSubstrateChainMeta(chainId, balancesConfig,
2668
+ // TODO better typing
2669
+ metadataRpc);
2670
+ return {
2671
+ id: deriveMiniMetadataId({
2672
+ source,
2673
+ chainId,
2674
+ specVersion,
2675
+ libVersion
2676
+ }),
2677
+ source,
2678
+ chainId,
2679
+ specVersion,
2680
+ libVersion,
2681
+ data: chainMeta?.miniMetadata ?? null,
2682
+ extra: chainMeta?.extra ?? null
2683
+ };
2684
+ }));
2685
+ } finally {
2686
+ log.debug("[miniMetadata] updated miniMetadatas for %s in %sms", chainId, (performance.now() - start).toFixed(2));
2687
+ }
2688
+ };
2689
+
2690
+ const getUpdatedMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion, signal) => {
2691
+ const miniMetadatas = await getMiniMetadatas(chainConnector, chaindataProvider, chainId, specVersion, signal);
2692
+ signal?.throwIfAborted();
2693
+ await db.transaction("readwrite", "miniMetadatas", async tx => {
2694
+ await tx.miniMetadatas.where({
2695
+ chainId
2696
+ }).delete();
2697
+ await tx.miniMetadatas.bulkPut(miniMetadatas);
2698
+ });
2699
+ return miniMetadatas;
2700
+ };
2701
+
2702
+ const getMiniMetadata = async (chaindataProvider, chainConnector, chainId, source, signal) => {
2703
+ const specVersion = await getSpecVersion(chainConnector, chainId);
2704
+ signal?.throwIfAborted();
2705
+ const miniMetadataId = deriveMiniMetadataId({
2706
+ source,
2707
+ chainId,
2708
+ specVersion,
2709
+ libVersion
2710
+ });
2711
+
2712
+ // lookup local ones
2713
+ const [dbMiniMetadata, ghMiniMetadata] = await Promise.all([db.miniMetadatas.get(miniMetadataId), chaindataProvider.miniMetadataById(miniMetadataId)]);
2714
+ signal?.throwIfAborted();
2715
+ const miniMetadata = dbMiniMetadata ?? ghMiniMetadata;
2716
+ if (miniMetadata) return miniMetadata;
2717
+
2718
+ // update from live chain metadata and persist locally
2719
+ const miniMetadatas = await getUpdatedMiniMetadatas(chainConnector, chaindataProvider, chainId, specVersion, signal);
2720
+ signal?.throwIfAborted();
2721
+ const found = miniMetadatas.find(m => m.id === miniMetadataId);
2722
+ if (!found) {
2723
+ log.warn("MiniMetadata not found in updated miniMetadatas", {
2724
+ source,
2725
+ chainId,
2726
+ specVersion,
2727
+ libVersion,
2728
+ miniMetadataId,
2729
+ miniMetadatas
2730
+ });
2731
+ throw new Error(`MiniMetadata not found for ${source} on ${chainId}`);
2732
+ }
2733
+ return found;
2734
+ };
2735
+
2874
2736
  /**
2875
2737
  * Wraps a BalanceModule's fetch/subscribe methods with a single `balances` method.
2876
2738
  * This `balances` method will subscribe if a callback parameter is provided, or otherwise fetch.
@@ -2886,44 +2748,16 @@ async function balances(balanceModule, addressesByToken, callback) {
2886
2748
  return await balanceModule.fetchBalances(addressesByToken);
2887
2749
  }
2888
2750
 
2889
- /**
2890
- * Given a `moduleType` and a `chain` from a chaindataProvider, this function will find the chainMeta
2891
- * associated with the given balanceModule for the given chain.
2892
- */
2893
- const findChainMeta = (miniMetadatas, moduleType, chain) => {
2894
- if (!chain) return [undefined, undefined];
2895
- if (!chain.specName) return [undefined, undefined];
2896
- if (!chain.specVersion) return [undefined, undefined];
2897
-
2898
- // TODO: This is spaghetti to import this here, it should be injected into each balance module or something.
2899
- const metadataId = deriveMiniMetadataId({
2900
- source: moduleType,
2901
- chainId: chain.id,
2902
- specName: chain.specName,
2903
- specVersion: chain.specVersion,
2904
- balancesConfig: JSON.stringify(chain.balancesConfig?.find(config => config.moduleType === moduleType)?.moduleConfig ?? {})
2905
- });
2906
-
2907
- // TODO: Fix this (needs to fetch miniMetadata without being async)
2908
- const miniMetadata = miniMetadatas.get(metadataId);
2909
- const chainMeta = miniMetadata ? {
2910
- miniMetadata: miniMetadata.data,
2911
- metadataVersion: miniMetadata.version,
2912
- ...JSON.parse(miniMetadata.extra)
2913
- } : undefined;
2914
- return [chainMeta, miniMetadata];
2915
- };
2916
-
2751
+ // TODO remove this one in favor of the network specific one below
2917
2752
  const buildStorageCoders = ({
2918
2753
  chainIds,
2919
2754
  chains,
2920
2755
  miniMetadatas,
2921
- moduleType,
2922
2756
  coders
2923
2757
  }) => new Map([...chainIds].flatMap(chainId => {
2924
2758
  const chain = chains[chainId];
2925
2759
  if (!chain) return [];
2926
- const [, miniMetadata] = findChainMeta(miniMetadatas, moduleType, chain);
2760
+ const miniMetadata = miniMetadatas.get(chainId); // findMiniMetadata<TBalanceModule>(miniMetadatas, moduleType, chain)
2927
2761
  if (!miniMetadata) return [];
2928
2762
  if (!miniMetadata.data) return [];
2929
2763
  const metadata = unifyMetadata(decAnyMetadata(miniMetadata.data));
@@ -2946,6 +2780,28 @@ const buildStorageCoders = ({
2946
2780
  return [];
2947
2781
  }
2948
2782
  }));
2783
+ const buildNetworkStorageCoders = (chainId, miniMetadata, coders) => {
2784
+ if (!miniMetadata.data) return null;
2785
+ const metadata = unifyMetadata(decAnyMetadata(miniMetadata.data));
2786
+ try {
2787
+ const scaleBuilder = getDynamicBuilder(getLookupFn(metadata));
2788
+ const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
2789
+ const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
2790
+ chainId
2791
+ }) : moduleMethodOrFn;
2792
+ try {
2793
+ return [[key, scaleBuilder.buildStorage(module, method)]];
2794
+ } catch (cause) {
2795
+ log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
2796
+ return [];
2797
+ }
2798
+ }));
2799
+ return builtCoders;
2800
+ } catch (cause) {
2801
+ log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
2802
+ }
2803
+ return null;
2804
+ };
2949
2805
 
2950
2806
  /**
2951
2807
  * Decodes & unwraps outputs and errors of a given result, contract, and method.
@@ -3027,7 +2883,7 @@ const detectTransferMethod = metadataRpc => {
3027
2883
  return hasDeprecatedTransferCall ? "transfer" : "transfer_allow_death";
3028
2884
  };
3029
2885
 
3030
- const getUniqueChainIds = (addressesByToken, tokens) => [...new Set(Object.keys(addressesByToken).map(tokenId => tokens[tokenId]?.chain?.id).flatMap(chainId => chainId ? [chainId] : []))];
2886
+ const getUniqueChainIds = (addressesByToken, tokens) => [...new Set(Object.keys(addressesByToken).map(tokenId => tokens[tokenId]?.networkId).flatMap(chainId => chainId ? [chainId] : []))];
3031
2887
 
3032
2888
  const makeContractCaller = ({
3033
2889
  chainConnector,
@@ -3136,8 +2992,15 @@ const decompress = data => {
3136
2992
  return JSON.parse(decompressed);
3137
2993
  };
3138
2994
 
3139
- const moduleType$5 = "substrate-assets";
3140
- const subAssetTokenId = (chainId, assetId, tokenSymbol) => `${chainId}-substrate-assets-${assetId}-${tokenSymbol}`.toLowerCase().replace(/ /g, "-");
2995
+ const moduleType$4 = "substrate-assets";
2996
+ const SubAssetsTokenConfigSchema = z.strictObject({
2997
+ assetId: SubAssetsTokenSchema.shape.assetId,
2998
+ ...TokenConfigBaseSchema.shape
2999
+ });
3000
+ const UNSUPPORTED_CHAIN_META$2 = {
3001
+ miniMetadata: null,
3002
+ extra: null
3003
+ };
3141
3004
  const SubAssetsModule = hydrate => {
3142
3005
  const {
3143
3006
  chainConnectors,
@@ -3146,16 +3009,10 @@ const SubAssetsModule = hydrate => {
3146
3009
  const chainConnector = chainConnectors.substrate;
3147
3010
  assert(chainConnector, "This module requires a substrate chain connector");
3148
3011
  return {
3149
- ...DefaultBalanceModule(moduleType$5),
3012
+ ...DefaultBalanceModule(moduleType$4),
3013
+ // TODO make synchronous at the module definition level ?
3150
3014
  async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
3151
- const isTestnet = (await chaindataProvider.chainById(chainId))?.isTestnet || false;
3152
- if (metadataRpc === undefined) return {
3153
- isTestnet
3154
- };
3155
- if ((moduleConfig?.tokens ?? []).length < 1) return {
3156
- isTestnet
3157
- };
3158
- const metadataVersion = getMetadataVersion(metadataRpc);
3015
+ if (!metadataRpc) return UNSUPPORTED_CHAIN_META$2;
3159
3016
  const metadata = decAnyMetadata(metadataRpc);
3160
3017
  compactMetadata(metadata, [{
3161
3018
  pallet: "Assets",
@@ -3163,82 +3020,98 @@ const SubAssetsModule = hydrate => {
3163
3020
  }]);
3164
3021
  const miniMetadata = encodeMetadata(metadata);
3165
3022
  return {
3166
- isTestnet,
3167
3023
  miniMetadata,
3168
- metadataVersion
3024
+ extra: null
3169
3025
  };
3170
3026
  },
3171
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
3172
- if ((moduleConfig?.tokens ?? []).length < 1) return {};
3027
+ async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
3028
+ if (!tokens?.length) return {};
3173
3029
  const {
3174
- isTestnet,
3175
- miniMetadata,
3176
- metadataVersion
3030
+ miniMetadata
3177
3031
  } = chainMeta;
3178
- if (miniMetadata === undefined || metadataVersion === undefined) return {};
3179
- if (metadataVersion < 14) return {};
3032
+ if (!miniMetadata) return {};
3180
3033
  const metadata = unifyMetadata(decAnyMetadata(miniMetadata));
3181
3034
  const scaleBuilder = getDynamicBuilder(getLookupFn(metadata));
3182
3035
  const assetCoder = scaleBuilder.buildStorage("Assets", "Asset");
3183
3036
  const metadataCoder = scaleBuilder.buildStorage("Assets", "Metadata");
3184
- const tokens = {};
3185
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
3037
+ const tokenList = {};
3038
+ for (const tokenConfig of tokens ?? []) {
3186
3039
  try {
3187
- const assetId = typeof tokenConfig.assetId === "number" ? tokenConfig.assetId.toString() : tokenConfig.assetId;
3040
+ const assetId = String(tokenConfig.assetId);
3188
3041
  const assetStateKey = tryEncode(assetCoder, BigInt(assetId)) ?? tryEncode(assetCoder, assetId);
3189
3042
  const metadataStateKey = tryEncode(metadataCoder, BigInt(assetId)) ?? tryEncode(metadataCoder, assetId);
3190
3043
  if (assetStateKey === null || metadataStateKey === null) throw new Error(`Failed to encode stateKey for asset ${assetId} on chain ${chainId}`);
3191
3044
  const [assetsAsset, assetsMetadata] = await Promise.all([chainConnector.send(chainId, "state_getStorage", [assetStateKey]).then(result => assetCoder.value.dec(result) ?? null), chainConnector.send(chainId, "state_getStorage", [metadataStateKey]).then(result => metadataCoder.value.dec(result) ?? null)]);
3192
3045
  const existentialDeposit = assetsAsset?.min_balance?.toString?.() ?? "0";
3193
3046
  const symbol = assetsMetadata?.symbol?.asText?.() ?? "Unit";
3047
+ const name = assetsMetadata?.name?.asText?.() ?? symbol;
3194
3048
  const decimals = assetsMetadata?.decimals ?? 0;
3195
3049
  const isFrozen = assetsMetadata?.is_frozen ?? false;
3196
- const id = subAssetTokenId(chainId, assetId, symbol);
3050
+ const id = subAssetTokenId(chainId, assetId);
3197
3051
  const token = {
3198
3052
  id,
3199
3053
  type: "substrate-assets",
3200
- isTestnet,
3054
+ platform: "polkadot",
3201
3055
  isDefault: tokenConfig?.isDefault ?? true,
3202
- symbol,
3056
+ symbol: tokenConfig?.symbol ?? symbol,
3057
+ name: tokenConfig?.name ?? name,
3203
3058
  decimals,
3204
- logo: tokenConfig?.logo || githubTokenLogoUrl(id),
3059
+ logo: tokenConfig?.logo,
3205
3060
  existentialDeposit,
3206
3061
  assetId,
3207
3062
  isFrozen,
3208
- chain: {
3209
- id: chainId
3210
- }
3063
+ networkId: chainId
3211
3064
  };
3212
3065
  if (tokenConfig?.symbol) {
3213
3066
  token.symbol = tokenConfig?.symbol;
3214
- token.id = subAssetTokenId(chainId, assetId, token.symbol);
3067
+ token.id = subAssetTokenId(chainId, assetId);
3215
3068
  }
3216
3069
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
3217
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
3218
3070
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
3219
- tokens[token.id] = token;
3071
+ tokenList[token.id] = token;
3220
3072
  } catch (error) {
3221
3073
  log.error(`Failed to build substrate-assets token ${tokenConfig.assetId} (${tokenConfig.symbol}) on ${chainId}`, error);
3222
3074
  continue;
3223
3075
  }
3224
3076
  }
3225
- return tokens;
3077
+ return tokenList;
3226
3078
  },
3227
3079
  // TODO: Don't create empty subscriptions
3228
3080
  async subscribeBalances({
3229
3081
  addressesByToken
3230
3082
  }, callback) {
3231
- const queries = await buildQueries$4(chaindataProvider, addressesByToken);
3232
- const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
3233
- if (error) return callback(error);
3234
- const balances = result?.filter(b => b !== null) ?? [];
3235
- if (balances.length > 0) callback(null, new Balances(balances));
3236
- });
3237
- return unsubscribe;
3083
+ const byNetwork = keys(addressesByToken).reduce((acc, tokenId) => {
3084
+ const networkId = parseSubAssetTokenId(tokenId).networkId;
3085
+ if (!acc[networkId]) acc[networkId] = {};
3086
+ acc[networkId][tokenId] = addressesByToken[tokenId];
3087
+ return acc;
3088
+ }, {});
3089
+ const controller = new AbortController();
3090
+ const pUnsubs = Promise.all(toPairs(byNetwork).map(async ([networkId, addressesByToken]) => {
3091
+ try {
3092
+ const queries = await buildNetworkQueries$2(networkId, chainConnector, chaindataProvider, addressesByToken, controller.signal);
3093
+ if (controller.signal.aborted) return () => {};
3094
+ const stateHelper = new RpcStateQueryHelper(chainConnector, queries);
3095
+ return await stateHelper.subscribe((error, result) => {
3096
+ if (error) return callback(error);
3097
+ const balances = result?.filter(b => b !== null) ?? [];
3098
+ if (balances.length > 0) callback(null, new Balances(balances));
3099
+ });
3100
+ } catch (err) {
3101
+ if (!isAbortError(err)) log.error(`Failed to subscribe balances for network ${networkId}`, err);
3102
+ return () => {};
3103
+ }
3104
+ }));
3105
+ return () => {
3106
+ pUnsubs.then(unsubs => {
3107
+ unsubs.forEach(unsubscribe => unsubscribe());
3108
+ });
3109
+ controller.abort();
3110
+ };
3238
3111
  },
3239
3112
  async fetchBalances(addressesByToken) {
3240
3113
  assert(chainConnectors.substrate, "This module requires a substrate chain connector");
3241
- const queries = await buildQueries$4(chaindataProvider, addressesByToken);
3114
+ const queries = await buildQueries$3(chainConnector, chaindataProvider, addressesByToken);
3242
3115
  const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
3243
3116
  const balances = result?.filter(b => b !== null) ?? [];
3244
3117
  return new Balances(balances);
@@ -3259,11 +3132,10 @@ const SubAssetsModule = hydrate => {
3259
3132
  transferMethod,
3260
3133
  userExtensions
3261
3134
  }) {
3262
- const token = await chaindataProvider.tokenById(tokenId);
3135
+ const token = await chaindataProvider.getTokenById(tokenId, "substrate-assets");
3263
3136
  assert(token, `Token ${tokenId} not found in store`);
3264
- if (token.type !== "substrate-assets") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
3265
- const chainId = token.chain.id;
3266
- const chain = await chaindataProvider.chainById(chainId);
3137
+ const chainId = token.networkId;
3138
+ const chain = await chaindataProvider.getNetworkById(chainId, "polkadot");
3267
3139
  assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
3268
3140
  const {
3269
3141
  genesisHash
@@ -3308,23 +3180,16 @@ const SubAssetsModule = hydrate => {
3308
3180
  }
3309
3181
  };
3310
3182
  };
3311
- async function buildQueries$4(chaindataProvider, addressesByToken) {
3312
- const allChains = await chaindataProvider.chainsById();
3313
- const tokens = await chaindataProvider.tokensById();
3314
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
3315
- const uniqueChainIds = getUniqueChainIds(addressesByToken, tokens);
3316
- const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
3317
- const chainStorageCoders = buildStorageCoders({
3318
- chainIds: uniqueChainIds,
3319
- chains,
3320
- miniMetadatas,
3321
- moduleType: "substrate-assets",
3322
- coders: {
3323
- storage: ["Assets", "Account"]
3324
- }
3183
+ async function buildNetworkQueries$2(networkId, chainConnector, chaindataProvider, addressesByToken, signal) {
3184
+ const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType$4, signal);
3185
+ const chain = await chaindataProvider.getNetworkById(networkId, "polkadot");
3186
+ const tokensById = await chaindataProvider.getTokensMapById();
3187
+ signal?.throwIfAborted();
3188
+ const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
3189
+ storage: ["Assets", "Account"]
3325
3190
  });
3326
3191
  return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
3327
- const token = tokens[tokenId];
3192
+ const token = tokensById[tokenId];
3328
3193
  if (!token) {
3329
3194
  log.warn(`Token ${tokenId} not found`);
3330
3195
  return [];
@@ -3333,27 +3198,22 @@ async function buildQueries$4(chaindataProvider, addressesByToken) {
3333
3198
  log.debug(`This module doesn't handle tokens of type ${token.type}`);
3334
3199
  return [];
3335
3200
  }
3336
- const chainId = token.chain?.id;
3337
- if (!chainId) {
3338
- log.warn(`Token ${tokenId} has no chain`);
3339
- return [];
3340
- }
3341
- const chain = chains[chainId];
3201
+ //
3342
3202
  if (!chain) {
3343
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
3203
+ log.warn(`Chain ${networkId} for token ${tokenId} not found`);
3344
3204
  return [];
3345
3205
  }
3346
3206
  return addresses.flatMap(address => {
3347
- const scaleCoder = chainStorageCoders.get(chainId)?.storage;
3348
- const stateKey = tryEncode(scaleCoder, BigInt(token.assetId), address) ?? tryEncode(scaleCoder, token.assetId, address);
3207
+ const scaleCoder = networkStorageCoders?.storage;
3208
+ const stateKey = tryEncode(scaleCoder, Number(token.assetId), address) ?? tryEncode(scaleCoder, BigInt(token.assetId), address);
3349
3209
  if (!stateKey) {
3350
- log.warn(`Invalid assetId / address in ${chainId} storage query ${token.assetId} / ${address}`);
3210
+ log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
3351
3211
  return [];
3352
3212
  }
3353
3213
  const decodeResult = change => {
3354
3214
  /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3355
3215
 
3356
- const decoded = decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${chainId}`) ?? {
3216
+ const decoded = decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
3357
3217
  balance: 0n,
3358
3218
  status: {
3359
3219
  type: "Liquid"
@@ -3387,22 +3247,30 @@ async function buildQueries$4(chaindataProvider, addressesByToken) {
3387
3247
  source: "substrate-assets",
3388
3248
  status: "live",
3389
3249
  address,
3390
- multiChainId: {
3391
- subChainId: chainId
3392
- },
3393
- chainId,
3250
+ networkId,
3394
3251
  tokenId: token.id,
3395
3252
  values: balanceValues
3396
3253
  };
3397
3254
  };
3398
3255
  return {
3399
- chainId,
3256
+ chainId: networkId,
3400
3257
  stateKey,
3401
3258
  decodeResult
3402
3259
  };
3403
3260
  });
3404
3261
  });
3405
3262
  }
3263
+ async function buildQueries$3(chainConnector, chaindataProvider, addressesByToken, signal) {
3264
+ const byNetwork = keys(addressesByToken).reduce((acc, tokenId) => {
3265
+ const networkId = parseSubAssetTokenId(tokenId).networkId;
3266
+ if (!acc[networkId]) acc[networkId] = {};
3267
+ acc[networkId][tokenId] = addressesByToken[tokenId];
3268
+ return acc;
3269
+ }, {});
3270
+ return (await Promise.all(toPairs(byNetwork).map(([networkId, addressesByToken]) => {
3271
+ return buildNetworkQueries$2(networkId, chainConnector, chaindataProvider, addressesByToken, signal);
3272
+ }))).flat();
3273
+ }
3406
3274
  // NOTE: Different chains need different formats for assetId when encoding the stateKey
3407
3275
  // E.g. Polkadot Asset Hub needs it to be a string, Astar needs it to be a bigint
3408
3276
  //
@@ -3415,9 +3283,16 @@ const tryEncode = (scaleCoder, ...args) => {
3415
3283
  }
3416
3284
  };
3417
3285
 
3418
- const moduleType$4 = "substrate-equilibrium";
3419
- const subEquilibriumTokenId = (chainId, tokenSymbol) => `${chainId}-substrate-equilibrium-${tokenSymbol}`.toLowerCase().replace(/ /g, "-");
3420
- const SubEquilibriumModule = hydrate => {
3286
+ const moduleType$3 = "substrate-foreignassets";
3287
+ const UNSUPPORTED_CHAIN_META$1 = {
3288
+ miniMetadata: null,
3289
+ extra: null
3290
+ };
3291
+ const SubForeignAssetsTokenConfigSchema = z.strictObject({
3292
+ onChainId: SubForeignAssetsTokenSchema.shape.onChainId,
3293
+ ...TokenConfigBaseSchema.shape
3294
+ });
3295
+ const SubForeignAssetsModule = hydrate => {
3421
3296
  const {
3422
3297
  chainConnectors,
3423
3298
  chaindataProvider
@@ -3425,318 +3300,41 @@ const SubEquilibriumModule = hydrate => {
3425
3300
  const chainConnector = chainConnectors.substrate;
3426
3301
  assert(chainConnector, "This module requires a substrate chain connector");
3427
3302
  return {
3428
- ...DefaultBalanceModule(moduleType$4),
3303
+ ...DefaultBalanceModule(moduleType$3),
3429
3304
  async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
3430
- const isTestnet = (await chaindataProvider.chainById(chainId))?.isTestnet || false;
3431
- if (metadataRpc === undefined) return {
3432
- isTestnet
3433
- };
3434
- if (moduleConfig?.disable !== false) return {
3435
- isTestnet
3436
- }; // default to disabled
3437
-
3305
+ if (metadataRpc === undefined) return UNSUPPORTED_CHAIN_META$1;
3438
3306
  const metadataVersion = getMetadataVersion(metadataRpc);
3307
+ if (metadataVersion < 14) return UNSUPPORTED_CHAIN_META$1;
3439
3308
  const metadata = decAnyMetadata(metadataRpc);
3440
3309
  compactMetadata(metadata, [{
3441
- pallet: "EqAssets",
3442
- items: ["Assets"]
3443
- }, {
3444
- pallet: "System",
3445
- items: ["Account"]
3310
+ pallet: "ForeignAssets",
3311
+ items: ["Account", "Asset", "Metadata"]
3446
3312
  }]);
3447
3313
  const miniMetadata = encodeMetadata(metadata);
3448
3314
  return {
3449
- isTestnet,
3450
3315
  miniMetadata,
3451
- metadataVersion
3316
+ extra: null
3452
3317
  };
3453
3318
  },
3454
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
3455
- // default to disabled
3456
- if (moduleConfig?.disable !== false) return {};
3319
+ async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
3320
+ if (!tokens?.length) return {};
3457
3321
  const {
3458
- isTestnet,
3459
- miniMetadata,
3460
- metadataVersion
3322
+ miniMetadata
3461
3323
  } = chainMeta;
3462
- if (miniMetadata === undefined || metadataVersion === undefined) return {};
3463
- if (metadataVersion < 14) return {};
3464
- try {
3465
- const metadata = unifyMetadata(decAnyMetadata(miniMetadata));
3466
- const scaleBuilder = getDynamicBuilder(getLookupFn(metadata));
3467
- const assetsCoder = scaleBuilder.buildStorage("EqAssets", "Assets");
3468
- const stateKey = assetsCoder.keys.enc();
3469
-
3470
- /** NOTE: Just a guideline, the RPC can return whatever it wants */
3471
-
3472
- const assetsResult = await chainConnector.send(chainId, "state_getStorage", [stateKey]).then(result => assetsCoder.value.dec(result) ?? null);
3473
- const tokens = (Array.isArray(assetsResult) ? assetsResult : []).flatMap(asset => {
3474
- if (!asset) return [];
3475
- if (!asset?.id) return [];
3476
- const assetId = asset.id.toString(10);
3477
- const symbol = tokenSymbolFromU64Id(asset.id);
3478
- const id = subEquilibriumTokenId(chainId, symbol);
3479
- const decimals = DEFAULT_DECIMALS$1;
3480
- const tokenConfig = (moduleConfig?.tokens ?? []).find(token => token.assetId === assetId);
3481
- const token = {
3482
- id,
3483
- type: "substrate-equilibrium",
3484
- isTestnet,
3485
- isDefault: tokenConfig?.isDefault ?? true,
3486
- symbol,
3487
- decimals,
3488
- logo: tokenConfig?.logo || githubTokenLogoUrl(id),
3489
- // TODO: Fetch the ED
3490
- existentialDeposit: "0",
3491
- assetId,
3492
- chain: {
3493
- id: chainId
3494
- }
3495
- };
3496
- if (tokenConfig?.symbol) {
3497
- token.symbol = tokenConfig?.symbol;
3498
- token.id = subEquilibriumTokenId(chainId, token.symbol);
3499
- }
3500
- if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
3501
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
3502
- if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
3503
- return [[token.id, token]];
3504
- });
3505
- return Object.fromEntries(tokens);
3506
- } catch (error) {
3507
- log.error(`Failed to build substrate-equilibrium tokens on ${chainId}`, error?.message ?? error);
3508
- return {};
3509
- }
3510
- },
3511
- // TODO: Don't create empty subscriptions
3512
- async subscribeBalances({
3513
- addressesByToken
3514
- }, callback) {
3515
- const queries = await buildQueries$3(chaindataProvider, addressesByToken);
3516
- const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
3517
- if (error) return callback(error);
3518
- const balances = result?.flatMap(balances => balances) ?? [];
3519
- if (balances.length > 0) callback(null, new Balances(balances));
3520
- });
3521
- return unsubscribe;
3522
- },
3523
- async fetchBalances(addressesByToken) {
3524
- assert(chainConnectors.substrate, "This module requires a substrate chain connector");
3525
- const queries = await buildQueries$3(chaindataProvider, addressesByToken);
3526
- const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
3527
- const balances = result?.flatMap(balances => balances) ?? [];
3528
- return new Balances(balances);
3529
- },
3530
- async transferToken({
3531
- tokenId,
3532
- from,
3533
- to,
3534
- amount,
3535
- registry,
3536
- metadataRpc,
3537
- blockHash,
3538
- blockNumber,
3539
- nonce,
3540
- specVersion,
3541
- transactionVersion,
3542
- tip,
3543
- transferMethod,
3544
- userExtensions
3545
- }) {
3546
- const token = await chaindataProvider.tokenById(tokenId);
3547
- assert(token, `Token ${tokenId} not found in store`);
3548
- if (token.type !== "substrate-equilibrium") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
3549
- const chainId = token.chain.id;
3550
- const chain = await chaindataProvider.chainById(chainId);
3551
- assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
3552
- const {
3553
- genesisHash
3554
- } = chain;
3555
- const {
3556
- assetId
3557
- } = token;
3558
- const pallet = "EqBalances";
3559
- const method = transferMethod === "transfer_all" ?
3560
- // the eqBalances pallet has no transfer_all method
3561
- "transfer" : transferMethod === "transfer_keep_alive" ?
3562
- // the eqBalances pallet has no transfer_keep_alive method
3563
- "transfer" : "transfer";
3564
- const args = {
3565
- asset: assetId,
3566
- to,
3567
- value: amount
3568
- };
3569
- const unsigned = defineMethod({
3570
- method: {
3571
- pallet: camelCase(pallet),
3572
- name: camelCase(method),
3573
- args
3574
- },
3575
- address: from,
3576
- blockHash,
3577
- blockNumber,
3578
- eraPeriod: 64,
3579
- genesisHash,
3580
- metadataRpc,
3581
- nonce,
3582
- specVersion,
3583
- tip: tip ? Number(tip) : 0,
3584
- transactionVersion
3585
- }, {
3586
- metadataRpc,
3587
- registry,
3588
- userExtensions
3589
- });
3590
- return {
3591
- type: "substrate",
3592
- callData: unsigned.method
3593
- };
3594
- }
3595
- };
3596
- };
3597
- async function buildQueries$3(chaindataProvider, addressesByToken) {
3598
- const allChains = await chaindataProvider.chainsById();
3599
- const tokens = await chaindataProvider.tokensById();
3600
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
3601
- const uniqueChainIds = getUniqueChainIds(addressesByToken, tokens);
3602
- const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
3603
- const chainStorageCoders = buildStorageCoders({
3604
- chainIds: uniqueChainIds,
3605
- chains,
3606
- miniMetadatas,
3607
- moduleType: "substrate-equilibrium",
3608
- coders: {
3609
- storage: ["System", "Account"]
3610
- }
3611
- });
3612
-
3613
- // equilibrium returns all chain tokens for each address in the one query
3614
- // so, we only need to make one query per address, rather than one query per token per address
3615
- const addressesByChain = new Map();
3616
- const tokensByAddress = new Map();
3617
- Object.entries(addressesByToken).map(([tokenId, addresses]) => {
3618
- const token = tokens[tokenId];
3619
- if (!token) return log.warn(`Token ${tokenId} not found`);
3620
- if (token.type !== "substrate-equilibrium") return log.debug(`This module doesn't handle tokens of type ${token.type}`);
3621
- const chainId = token?.chain?.id;
3622
- if (!chainId) return log.warn(`Token ${tokenId} has no chain`);
3623
- const byChain = addressesByChain.get(chainId) ?? new Set();
3624
- addresses.forEach(address => {
3625
- byChain?.add(address);
3626
- tokensByAddress.set(address, (tokensByAddress.get(address) ?? new Set()).add(token));
3627
- });
3628
- addressesByChain.set(chainId, byChain);
3629
- });
3630
- return Array.from(addressesByChain).flatMap(([chainId, addresses]) => {
3631
- const chain = chains[chainId];
3632
- if (!chain) {
3633
- log.warn(`Chain ${chainId} not found`);
3634
- return [];
3635
- }
3636
- return Array.from(addresses).flatMap(address => {
3637
- const scaleCoder = chainStorageCoders.get(chainId)?.storage;
3638
- const stateKey = encodeStateKey(scaleCoder, `Invalid address in ${chainId} storage query ${address}`, address);
3639
- if (!stateKey) return [];
3640
- const decodeResult = change => {
3641
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3642
-
3643
- const decoded = decodeScale(scaleCoder, change, `Failed to decode eqBalances on chain ${chainId}`);
3644
- const tokenBalances = Object.fromEntries((decoded?.data?.value?.balance ?? []).map(balance => ({
3645
- id: (balance?.[0] ?? 0n)?.toString?.(),
3646
- free: balance?.[1]?.type === "Positive" ? (balance?.[1]?.value ?? 0n).toString() : balance?.[1]?.type === "Negative" ? ((balance?.[1]?.value ?? 0n) * -1n).toString() : "0"
3647
- })).map(({
3648
- id,
3649
- free
3650
- }) => [id, free]).filter(([id, free]) => id !== undefined && free !== undefined));
3651
- const result = Array.from(tokensByAddress.get(address) ?? []).filter(t => t.chain.id === chainId).map(token => {
3652
- const value = tokenBalances[token.assetId];
3653
- return {
3654
- source: "substrate-equilibrium",
3655
- status: "live",
3656
- address,
3657
- multiChainId: {
3658
- subChainId: chainId
3659
- },
3660
- chainId,
3661
- tokenId: token.id,
3662
- value
3663
- };
3664
- }).filter(b => b !== undefined);
3665
- return result;
3666
- };
3667
- return {
3668
- chainId,
3669
- stateKey,
3670
- decodeResult
3671
- };
3672
- });
3673
- });
3674
- }
3675
- const DEFAULT_DECIMALS$1 = 9;
3676
- const tokenSymbolFromU64Id = u64 => {
3677
- const bytes = [];
3678
- let num = typeof u64 === "number" ? BigInt(u64) : isBigInt(u64) ? u64 : u64.toBigInt();
3679
- do {
3680
- bytes.unshift(Number(num % 256n));
3681
- num = num / 256n;
3682
- } while (num > 0);
3683
- return new TextDecoder("utf-8").decode(new Uint8Array(bytes)).toUpperCase();
3684
- };
3685
-
3686
- const moduleType$3 = "substrate-foreignassets";
3687
- const subForeignAssetTokenId = (chainId, tokenSymbol) => `${chainId}-substrate-foreignassets-${tokenSymbol}`.toLowerCase().replace(/ /g, "-");
3688
- const SubForeignAssetsModule = hydrate => {
3689
- const {
3690
- chainConnectors,
3691
- chaindataProvider
3692
- } = hydrate;
3693
- const chainConnector = chainConnectors.substrate;
3694
- assert(chainConnector, "This module requires a substrate chain connector");
3695
- return {
3696
- ...DefaultBalanceModule(moduleType$3),
3697
- async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
3698
- const isTestnet = (await chaindataProvider.chainById(chainId))?.isTestnet || false;
3699
- if (metadataRpc === undefined) return {
3700
- isTestnet
3701
- };
3702
- if ((moduleConfig?.tokens ?? []).length < 1) return {
3703
- isTestnet
3704
- };
3705
- const metadataVersion = getMetadataVersion(metadataRpc);
3706
- const metadata = decAnyMetadata(metadataRpc);
3707
- compactMetadata(metadata, [{
3708
- pallet: "ForeignAssets",
3709
- items: ["Account", "Asset", "Metadata"]
3710
- }]);
3711
- const miniMetadata = encodeMetadata(metadata);
3712
- return {
3713
- isTestnet,
3714
- miniMetadata,
3715
- metadataVersion
3716
- };
3717
- },
3718
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
3719
- if ((moduleConfig?.tokens ?? []).length < 1) return {};
3720
- const {
3721
- isTestnet,
3722
- miniMetadata,
3723
- metadataVersion
3724
- } = chainMeta;
3725
- if (miniMetadata === undefined || metadataVersion === undefined) return {};
3726
- if (metadataVersion < 14) return {};
3727
- const metadata = decAnyMetadata(miniMetadata);
3728
- const unifiedMetadata = unifyMetadata(metadata);
3729
- const scaleBuilder = getDynamicBuilder(getLookupFn(unifiedMetadata));
3730
- const assetCoder = scaleBuilder.buildStorage("ForeignAssets", "Asset");
3731
- const metadataCoder = scaleBuilder.buildStorage("ForeignAssets", "Metadata");
3732
- const tokens = {};
3733
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
3734
- try {
3735
- const onChainId = (() => {
3736
- try {
3737
- return papiParse(tokenConfig.onChainId);
3738
- } catch (error) {
3739
- return tokenConfig.onChainId;
3324
+ if (!miniMetadata) return {};
3325
+ const metadata = decAnyMetadata(miniMetadata);
3326
+ const unifiedMetadata = unifyMetadata(metadata);
3327
+ const scaleBuilder = getDynamicBuilder(getLookupFn(unifiedMetadata));
3328
+ const assetCoder = scaleBuilder.buildStorage("ForeignAssets", "Asset");
3329
+ const metadataCoder = scaleBuilder.buildStorage("ForeignAssets", "Metadata");
3330
+ const tokenList = {};
3331
+ for (const tokenConfig of tokens ?? []) {
3332
+ try {
3333
+ const onChainId = (() => {
3334
+ try {
3335
+ return papiParse(tokenConfig.onChainId);
3336
+ } catch (error) {
3337
+ return tokenConfig.onChainId;
3740
3338
  }
3741
3339
  })();
3742
3340
  if (onChainId === undefined) continue;
@@ -3745,54 +3343,70 @@ const SubForeignAssetsModule = hydrate => {
3745
3343
  const [assetsAsset, assetsMetadata] = await Promise.all([chainConnector.send(chainId, "state_getStorage", [assetStateKey]).then(result => assetCoder.value.dec(result) ?? null), chainConnector.send(chainId, "state_getStorage", [metadataStateKey]).then(result => metadataCoder.value.dec(result) ?? null)]);
3746
3344
  const existentialDeposit = assetsAsset?.min_balance?.toString?.() ?? "0";
3747
3345
  const symbol = assetsMetadata?.symbol?.asText?.() ?? "Unit";
3346
+ const name = assetsMetadata?.name?.asText?.() ?? symbol;
3748
3347
  const decimals = assetsMetadata?.decimals ?? 0;
3749
3348
  const isFrozen = assetsMetadata?.is_frozen ?? false;
3750
- const id = subForeignAssetTokenId(chainId, symbol);
3349
+ const id = subForeignAssetTokenId(chainId, tokenConfig.onChainId);
3751
3350
  const token = {
3752
3351
  id,
3753
3352
  type: "substrate-foreignassets",
3754
- isTestnet,
3353
+ platform: "polkadot",
3755
3354
  isDefault: tokenConfig?.isDefault ?? true,
3756
3355
  symbol,
3757
3356
  decimals,
3758
- logo: tokenConfig?.logo || githubTokenLogoUrl(id),
3357
+ name: tokenConfig?.name ?? name,
3358
+ logo: tokenConfig?.logo,
3759
3359
  existentialDeposit,
3760
3360
  onChainId: tokenConfig.onChainId,
3761
3361
  isFrozen,
3762
- chain: {
3763
- id: chainId
3764
- }
3362
+ networkId: chainId
3765
3363
  };
3766
- if (tokenConfig?.symbol) {
3767
- token.symbol = tokenConfig?.symbol;
3768
- token.id = subForeignAssetTokenId(chainId, token.symbol);
3769
- }
3770
3364
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
3771
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
3772
3365
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
3773
- tokens[token.id] = token;
3366
+ tokenList[token.id] = token;
3774
3367
  } catch (error) {
3775
3368
  log.error(`Failed to build substrate-foreignassets token ${tokenConfig.onChainId} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
3776
3369
  continue;
3777
3370
  }
3778
3371
  }
3779
- return tokens;
3372
+ return tokenList;
3780
3373
  },
3781
3374
  // TODO: Don't create empty subscriptions
3782
3375
  async subscribeBalances({
3783
3376
  addressesByToken
3784
3377
  }, callback) {
3785
- const queries = await buildQueries$2(chaindataProvider, addressesByToken);
3786
- const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
3787
- if (error) return callback(error);
3788
- const balances = result?.filter(b => b !== null) ?? [];
3789
- if (balances.length > 0) callback(null, new Balances(balances));
3790
- });
3791
- return unsubscribe;
3378
+ const byNetwork = keys(addressesByToken).reduce((acc, tokenId) => {
3379
+ const networkId = parseSubForeignAssetTokenId(tokenId).networkId;
3380
+ if (!acc[networkId]) acc[networkId] = {};
3381
+ acc[networkId][tokenId] = addressesByToken[tokenId];
3382
+ return acc;
3383
+ }, {});
3384
+ const controller = new AbortController();
3385
+ const pUnsubs = Promise.all(toPairs(byNetwork).map(async ([networkId, addressesByToken]) => {
3386
+ try {
3387
+ const queries = await buildNetworkQueries$1(networkId, chainConnector, chaindataProvider, addressesByToken, controller.signal);
3388
+ if (controller.signal.aborted) return () => {};
3389
+ const stateHelper = new RpcStateQueryHelper(chainConnector, queries);
3390
+ return await stateHelper.subscribe((error, result) => {
3391
+ if (error) return callback(error);
3392
+ const balances = result?.filter(b => b !== null) ?? [];
3393
+ if (balances.length > 0) callback(null, new Balances(balances));
3394
+ });
3395
+ } catch (err) {
3396
+ if (!isAbortError(err)) log.error(`Failed to subscribe ${moduleType$3} balances for network ${networkId}`, err);
3397
+ return () => {};
3398
+ }
3399
+ }));
3400
+ return () => {
3401
+ pUnsubs.then(unsubs => {
3402
+ unsubs.forEach(unsubscribe => unsubscribe());
3403
+ });
3404
+ controller.abort();
3405
+ };
3792
3406
  },
3793
3407
  async fetchBalances(addressesByToken) {
3794
3408
  assert(chainConnectors.substrate, "This module requires a substrate chain connector");
3795
- const queries = await buildQueries$2(chaindataProvider, addressesByToken);
3409
+ const queries = await buildQueries$2(chainConnector, chaindataProvider, addressesByToken);
3796
3410
  const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
3797
3411
  const balances = result?.filter(b => b !== null) ?? [];
3798
3412
  return new Balances(balances);
@@ -3804,11 +3418,10 @@ const SubForeignAssetsModule = hydrate => {
3804
3418
  transferMethod,
3805
3419
  metadataRpc
3806
3420
  }) {
3807
- const token = await chaindataProvider.tokenById(tokenId);
3421
+ const token = await chaindataProvider.getTokenById(tokenId, "substrate-foreignassets");
3808
3422
  assert(token, `Token ${tokenId} not found in store`);
3809
- if (token.type !== "substrate-foreignassets") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
3810
- const chainId = token.chain.id;
3811
- const chain = await chaindataProvider.chainById(chainId);
3423
+ const chainId = token.networkId;
3424
+ const chain = await chaindataProvider.getNetworkById(chainId, "polkadot");
3812
3425
  assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
3813
3426
  const onChainId = (() => {
3814
3427
  try {
@@ -3848,23 +3461,16 @@ const SubForeignAssetsModule = hydrate => {
3848
3461
  }
3849
3462
  };
3850
3463
  };
3851
- async function buildQueries$2(chaindataProvider, addressesByToken) {
3852
- const allChains = await chaindataProvider.chainsById();
3853
- const tokens = await chaindataProvider.tokensById();
3854
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
3855
- const uniqueChainIds = getUniqueChainIds(addressesByToken, tokens);
3856
- const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
3857
- const chainStorageCoders = buildStorageCoders({
3858
- chainIds: uniqueChainIds,
3859
- chains,
3860
- miniMetadatas,
3861
- moduleType: "substrate-foreignassets",
3862
- coders: {
3863
- storage: ["ForeignAssets", "Account"]
3864
- }
3464
+ async function buildNetworkQueries$1(networkId, chainConnector, chaindataProvider, addressesByToken, signal) {
3465
+ const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType$3, signal);
3466
+ const chain = await chaindataProvider.getNetworkById(networkId, "polkadot");
3467
+ const tokensById = await chaindataProvider.getTokensMapById();
3468
+ signal?.throwIfAborted();
3469
+ const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
3470
+ storage: ["ForeignAssets", "Account"]
3865
3471
  });
3866
3472
  return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
3867
- const token = tokens[tokenId];
3473
+ const token = tokensById[tokenId];
3868
3474
  if (!token) {
3869
3475
  log.warn(`Token ${tokenId} not found`);
3870
3476
  return [];
@@ -3873,18 +3479,12 @@ async function buildQueries$2(chaindataProvider, addressesByToken) {
3873
3479
  log.debug(`This module doesn't handle tokens of type ${token.type}`);
3874
3480
  return [];
3875
3481
  }
3876
- const chainId = token.chain?.id;
3877
- if (!chainId) {
3878
- log.warn(`Token ${tokenId} has no chain`);
3879
- return [];
3880
- }
3881
- const chain = chains[chainId];
3882
3482
  if (!chain) {
3883
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
3483
+ log.warn(`Chain ${networkId} for token ${tokenId} not found`);
3884
3484
  return [];
3885
3485
  }
3886
3486
  return addresses.flatMap(address => {
3887
- const scaleCoder = chainStorageCoders.get(chainId)?.storage;
3487
+ const scaleCoder = networkStorageCoders?.storage;
3888
3488
  const onChainId = (() => {
3889
3489
  try {
3890
3490
  return papiParse(token.onChainId);
@@ -3892,12 +3492,12 @@ async function buildQueries$2(chaindataProvider, addressesByToken) {
3892
3492
  return token.onChainId;
3893
3493
  }
3894
3494
  })();
3895
- const stateKey = encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${chainId} storage query ${address} / ${token.onChainId}`, onChainId, address);
3495
+ const stateKey = encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${networkId} storage query ${address} / ${token.onChainId}`, onChainId, address);
3896
3496
  if (!stateKey) return [];
3897
3497
  const decodeResult = change => {
3898
3498
  /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3899
3499
 
3900
- const decoded = decodeScale(scaleCoder, change, `Failed to decode substrate-foreignassets balance on chain ${chainId}`) ?? {
3500
+ const decoded = decodeScale(scaleCoder, change, `Failed to decode substrate-foreignassets balance on chain ${networkId}`) ?? {
3901
3501
  balance: 0n,
3902
3502
  status: {
3903
3503
  type: "Liquid"
@@ -3931,29 +3531,54 @@ async function buildQueries$2(chaindataProvider, addressesByToken) {
3931
3531
  source: "substrate-foreignassets",
3932
3532
  status: "live",
3933
3533
  address,
3934
- multiChainId: {
3935
- subChainId: chainId
3936
- },
3937
- chainId,
3534
+ networkId,
3938
3535
  tokenId: token.id,
3939
3536
  values: balanceValues
3940
3537
  };
3941
3538
  };
3942
3539
  return {
3943
- chainId,
3540
+ chainId: networkId,
3944
3541
  stateKey,
3945
3542
  decodeResult
3946
3543
  };
3947
3544
  });
3948
3545
  });
3949
3546
  }
3547
+ async function buildQueries$2(chainConnector, chaindataProvider, addressesByToken, signal) {
3548
+ const byNetwork = keys(addressesByToken).reduce((acc, tokenId) => {
3549
+ const networkId = parseSubForeignAssetTokenId(tokenId).networkId;
3550
+ if (!acc[networkId]) acc[networkId] = {};
3551
+ acc[networkId][tokenId] = addressesByToken[tokenId];
3552
+ return acc;
3553
+ }, {});
3554
+ return (await Promise.all(toPairs(byNetwork).map(([networkId, addressesByToken]) => {
3555
+ return buildNetworkQueries$1(networkId, chainConnector, chaindataProvider, addressesByToken, signal);
3556
+ }))).flat();
3557
+ }
3558
+
3559
+ const getAddresssesByTokenByNetwork = addressesByToken => {
3560
+ const addressesByTokenByNetwork = toPairs(addressesByToken).reduce((acc, [tokenId, addresses]) => {
3561
+ const networkId = parseTokenId(tokenId).networkId;
3562
+ if (!acc[networkId]) acc[networkId] = {};
3563
+ acc[networkId][tokenId] = addresses;
3564
+ return acc;
3565
+ }, {});
3566
+ return addressesByTokenByNetwork;
3567
+ };
3950
3568
 
3951
3569
  async function subscribeBase(queries, chainConnector, callback) {
3952
- const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
3953
- if (error) callback(error);
3954
- if (result && result.length > 0) callback(null, result);
3955
- });
3956
- return unsubscribe;
3570
+ try {
3571
+ const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
3572
+ if (error) callback(error);
3573
+ if (result && result.length > 0) callback(null, result);
3574
+ });
3575
+ return unsubscribe;
3576
+ } catch (err) {
3577
+ if (!isAbortError(err)) log.error("Error subscribing to base queries", {
3578
+ err
3579
+ });
3580
+ return () => {};
3581
+ }
3957
3582
  }
3958
3583
 
3959
3584
  /**
@@ -3969,262 +3594,6 @@ const asObservable = handler => (...args) => new Observable(subscriber => {
3969
3594
  return unsubscribe;
3970
3595
  });
3971
3596
 
3972
- /**
3973
- * Crowdloan contributions are stored in the `childstate` key returned by this function.
3974
- */
3975
- const crowdloanFundContributionsChildKey = fundIndex => u8aToHex(u8aConcat(":child_storage:default:", blake2AsU8a(u8aConcat("crowdloan", u32.enc(fundIndex)))));
3976
-
3977
- async function subscribeCrowdloans(chaindataProvider, chainConnector, addressesByToken, callback) {
3978
- const allChains = await chaindataProvider.chainsById();
3979
- const tokens = await chaindataProvider.tokensById();
3980
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
3981
- const crowdloanTokenIds = Object.entries(tokens).filter(([, token]) => {
3982
- // ignore non-native tokens
3983
- if (token.type !== "substrate-native") return;
3984
- // ignore tokens on chains with no crowdloans pallet
3985
- const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", allChains[token.chain.id]);
3986
- return typeof chainMeta?.crowdloanPalletId === "string";
3987
- }).map(([tokenId]) => tokenId);
3988
-
3989
- // crowdloan contributions can only be done by the native token on chains with the crowdloan pallet
3990
- const addressesByCrowdloanToken = Object.fromEntries(Object.entries(addressesByToken)
3991
- // remove ethereum addresses
3992
- .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !isEthereumAddress(address))])
3993
- // remove tokens which aren't crowdloan tokens
3994
- .filter(([tokenId]) => crowdloanTokenIds.includes(tokenId)));
3995
- const uniqueChainIds = getUniqueChainIds(addressesByCrowdloanToken, tokens);
3996
- const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
3997
- const chainStorageCoders = buildStorageCoders({
3998
- chainIds: uniqueChainIds,
3999
- chains,
4000
- miniMetadatas,
4001
- moduleType: "substrate-native",
4002
- coders: {
4003
- parachains: ["Paras", "Parachains"],
4004
- funds: ["Crowdloan", "Funds"]
4005
- }
4006
- });
4007
- const tokenSubscriptions = [];
4008
- for (const [tokenId, addresses] of Object.entries(addressesByCrowdloanToken)) {
4009
- const token = tokens[tokenId];
4010
- if (!token) {
4011
- log.warn(`Token ${tokenId} not found`);
4012
- continue;
4013
- }
4014
- if (token.type !== "substrate-native") {
4015
- log.debug(`This module doesn't handle tokens of type ${token.type}`);
4016
- continue;
4017
- }
4018
- const chainId = token.chain?.id;
4019
- if (!chainId) {
4020
- log.warn(`Token ${tokenId} has no chain`);
4021
- continue;
4022
- }
4023
- const chain = chains[chainId];
4024
- if (!chain) {
4025
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
4026
- continue;
4027
- }
4028
- const subscribeParaIds = callback => {
4029
- const scaleCoder = chainStorageCoders.get(chainId)?.parachains;
4030
- const queries = [0].flatMap(() => {
4031
- const stateKey = encodeStateKey(scaleCoder);
4032
- if (!stateKey) return [];
4033
- const decodeResult = change => {
4034
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4035
-
4036
- const decoded = decodeScale(scaleCoder, change, `Failed to decode parachains on chain ${chainId}`);
4037
- const paraIds = decoded ?? [];
4038
- return paraIds;
4039
- };
4040
- return {
4041
- chainId,
4042
- stateKey,
4043
- decodeResult
4044
- };
4045
- });
4046
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
4047
- return () => subscription.then(unsubscribe => unsubscribe());
4048
- };
4049
- const subscribeParaFundIndexes = (paraIds, callback) => {
4050
- const scaleCoder = chainStorageCoders.get(chainId)?.funds;
4051
- const queries = paraIds.flatMap(paraId => {
4052
- const stateKey = encodeStateKey(scaleCoder, `Invalid paraId in ${chainId} funds query ${paraId}`, paraId);
4053
- if (!stateKey) return [];
4054
- const decodeResult = change => {
4055
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4056
-
4057
- const decoded = decodeScale(scaleCoder, change, `Failed to decode paras on chain ${chainId}`);
4058
- const firstPeriod = decoded?.first_period?.toString?.() ?? "";
4059
- const lastPeriod = decoded?.last_period?.toString?.() ?? "";
4060
- const fundPeriod = `${firstPeriod}-${lastPeriod}`;
4061
- const fundIndex = decoded?.fund_index ?? decoded?.trie_index;
4062
- return {
4063
- paraId,
4064
- fundPeriod,
4065
- fundIndex
4066
- };
4067
- };
4068
- return {
4069
- chainId,
4070
- stateKey,
4071
- decodeResult
4072
- };
4073
- });
4074
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
4075
- return () => subscription.then(unsubscribe => unsubscribe());
4076
- };
4077
- const subscribeFundContributions = (funds, addresses, callback) => {
4078
- // TODO: Watch system_events in order to subscribe to changes, then redo the contributions query when changes are detected:
4079
- // https://github.com/polkadot-js/api/blob/8fe02a14345b57e6abb8f7f2c2b624cf70c51b23/packages/api-derive/src/crowdloan/ownContributions.ts#L32-L47
4080
- //
4081
- // For now we just re-fetch all contributions on a timer and then only send them to the subscription callback when they have changed
4082
-
4083
- const queries = funds.map(({
4084
- paraId,
4085
- fundIndex
4086
- }) => ({
4087
- paraId,
4088
- fundIndex,
4089
- addresses,
4090
- childKey: crowdloanFundContributionsChildKey(fundIndex),
4091
- storageKeys: addresses.map(address => u8aToHex(decodeAnyAddress(address)))
4092
- }));
4093
-
4094
- // track whether our caller is still subscribed
4095
- let subscriptionActive = true;
4096
- let previousContributions = null;
4097
- const fetchContributions = async () => {
4098
- try {
4099
- const results = await Promise.all(queries.map(async ({
4100
- paraId,
4101
- fundIndex,
4102
- addresses,
4103
- childKey,
4104
- storageKeys
4105
- }) => ({
4106
- paraId,
4107
- fundIndex,
4108
- addresses,
4109
- result: await chainConnector.send(chainId, "childstate_getStorageEntries", [childKey, storageKeys])
4110
- })));
4111
- const contributions = results.flatMap(queryResult => {
4112
- const {
4113
- paraId,
4114
- fundIndex,
4115
- addresses,
4116
- result
4117
- } = queryResult;
4118
- return (Array.isArray(result) ? result : []).flatMap((encoded, index) => {
4119
- const amount = (() => {
4120
- try {
4121
- return typeof encoded === "string" ? u128.dec(encoded) ?? 0n : 0n;
4122
- } catch {
4123
- return 0n;
4124
- }
4125
- })().toString();
4126
- return {
4127
- paraId,
4128
- fundIndex,
4129
- address: addresses[index],
4130
- amount
4131
- };
4132
- });
4133
- });
4134
-
4135
- // ignore these results if our caller has tried to close this subscription
4136
- if (!subscriptionActive) return;
4137
-
4138
- // ignore these results if they're the same as what we previously fetched
4139
- if (isEqual(previousContributions, contributions)) return;
4140
- previousContributions = contributions;
4141
- callback(null, contributions);
4142
- } catch (error) {
4143
- callback(error);
4144
- }
4145
- };
4146
-
4147
- // set up polling for contributions
4148
- const crowdloanContributionsPollInterval = 60_000; // 60_000ms === 1 minute
4149
- const pollContributions = async () => {
4150
- if (!subscriptionActive) return;
4151
- try {
4152
- await fetchContributions();
4153
- } catch (error) {
4154
- // log any errors, but don't cancel the poll for contributions when one fetch fails
4155
- log.error(error);
4156
- }
4157
- if (!subscriptionActive) return;
4158
- setTimeout(pollContributions, crowdloanContributionsPollInterval);
4159
- };
4160
-
4161
- // start polling
4162
- pollContributions();
4163
- return () => {
4164
- // stop polling
4165
- subscriptionActive = false;
4166
- };
4167
- };
4168
- const paraIds$ = asObservable(subscribeParaIds)().pipe(scan((_, next) => Array.from(new Set(next.flatMap(paraIds => paraIds))), []), share());
4169
- const fundIndexesByParaId$ = paraIds$.pipe(map(paraIds => asObservable(subscribeParaFundIndexes)(paraIds)), switchAll(), scan((state, next) => {
4170
- for (const fund of next) {
4171
- const {
4172
- paraId,
4173
- fundIndex
4174
- } = fund;
4175
- if (typeof fundIndex === "number") {
4176
- state.set(paraId, (state.get(paraId) ?? new Set()).add(fundIndex));
4177
- }
4178
- }
4179
- return state;
4180
- }, new Map()));
4181
- const contributionsByAddress$ = fundIndexesByParaId$.pipe(map(fundIndexesByParaId => Array.from(fundIndexesByParaId).flatMap(([paraId, fundIndexes]) => Array.from(fundIndexes).map(fundIndex => ({
4182
- paraId,
4183
- fundIndex
4184
- })))), map(funds => asObservable(subscribeFundContributions)(funds, addresses)), switchAll(), scan((state, next) => {
4185
- for (const contribution of next) {
4186
- const {
4187
- address
4188
- } = contribution;
4189
- state.set(address, (state.get(address) ?? new Set()).add(contribution));
4190
- }
4191
- return state;
4192
- }, new Map()));
4193
- const subscription = contributionsByAddress$.subscribe({
4194
- next: contributionsByAddress => {
4195
- const balances = Array.from(contributionsByAddress).map(([address, contributions]) => {
4196
- return {
4197
- source: "substrate-native",
4198
- status: "live",
4199
- address,
4200
- multiChainId: {
4201
- subChainId: chainId
4202
- },
4203
- chainId,
4204
- tokenId,
4205
- values: Array.from(contributions).map(({
4206
- amount,
4207
- paraId
4208
- }) => ({
4209
- type: "crowdloan",
4210
- label: "crowdloan",
4211
- source: "crowdloan",
4212
- amount: amount,
4213
- meta: {
4214
- paraId
4215
- }
4216
- }))
4217
- };
4218
- });
4219
- if (balances.length > 0) callback(null, balances);
4220
- },
4221
- error: error => callback(error)
4222
- });
4223
- tokenSubscriptions.push(() => subscription.unsubscribe());
4224
- }
4225
- return () => tokenSubscriptions.forEach(unsub => unsub());
4226
- }
4227
-
4228
3597
  /**
4229
3598
  * Each nominationPool in the nominationPools pallet has access to some accountIds which have no
4230
3599
  * associated private key. Instead, they are derived from this function.
@@ -4244,280 +3613,293 @@ const nompoolAccountId = (palletId, poolId, index) => {
4244
3613
  /** The stash account for the nomination pool */
4245
3614
  const nompoolStashAccountId = (palletId, poolId) => nompoolAccountId(palletId, poolId, 0);
4246
3615
 
4247
- async function subscribeNompoolStaking(chaindataProvider, chainConnector, addressesByToken, callback) {
4248
- const allChains = await chaindataProvider.chainsById();
4249
- const tokens = await chaindataProvider.tokensById();
4250
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
4251
- const nomPoolTokenIds = Object.entries(tokens).filter(([, token]) => {
4252
- // ignore non-native tokens
4253
- if (token.type !== "substrate-native") return false;
4254
- // ignore tokens on chains with no nompools pallet
4255
- const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", allChains[token.chain.id]);
4256
- return typeof chainMeta?.nominationPoolsPalletId === "string";
4257
- }).map(([tokenId]) => tokenId);
4258
-
4259
- // staking can only be done by the native token on chains with the staking pallet
4260
- const addressesByNomPoolToken = Object.fromEntries(Object.entries(addressesByToken)
4261
- // remove ethereum addresses
4262
- .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !isEthereumAddress(address))])
4263
- // remove tokens which aren't nom pool tokens
4264
- .filter(([tokenId]) => nomPoolTokenIds.includes(tokenId)));
4265
- const uniqueChainIds = getUniqueChainIds(addressesByNomPoolToken, tokens);
4266
- const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
4267
- const chainStorageCoders = buildStorageCoders({
4268
- chainIds: uniqueChainIds,
4269
- chains,
4270
- miniMetadatas,
4271
- moduleType: "substrate-native",
4272
- coders: {
4273
- poolMembers: ["NominationPools", "PoolMembers"],
4274
- bondedPools: ["NominationPools", "BondedPools"],
4275
- ledger: ["Staking", "Ledger"],
4276
- metadata: ["NominationPools", "Metadata"]
4277
- }
4278
- });
4279
- const resultUnsubscribes = [];
4280
- for (const [tokenId, addresses] of Object.entries(addressesByNomPoolToken)) {
4281
- const token = tokens[tokenId];
4282
- if (!token) {
4283
- log.warn(`Token ${tokenId} not found`);
4284
- continue;
4285
- }
4286
- if (token.type !== "substrate-native") {
4287
- log.debug(`This module doesn't handle tokens of type ${token.type}`);
4288
- continue;
4289
- }
4290
- const chainId = token.chain?.id;
4291
- if (!chainId) {
4292
- log.warn(`Token ${tokenId} has no chain`);
4293
- continue;
4294
- }
4295
- const chain = chains[chainId];
4296
- if (!chain) {
4297
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
4298
- continue;
3616
+ // TODO make this method chain-specific
3617
+ async function subscribeNompoolStaking(chaindataProvider, chainConnector, addressesByToken, callback, signal) {
3618
+ try {
3619
+ const allChains = await chaindataProvider.getNetworksMapById("polkadot");
3620
+ const tokens = await chaindataProvider.getTokensMapById();
3621
+
3622
+ // there should be only one network here when subscribing to balances, we've split it up by network at the top level
3623
+ const networkIds = keys(addressesByToken).map(tokenId => parseTokenId(tokenId).networkId);
3624
+ const miniMetadatas = new Map();
3625
+ for (const networkId of networkIds) {
3626
+ const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native");
3627
+ miniMetadatas.set(networkId, miniMetadata);
4299
3628
  }
4300
- const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", chain);
4301
- const {
4302
- nominationPoolsPalletId
4303
- } = chainMeta ?? {};
4304
- const subscribePoolMembers = (addresses, callback) => {
4305
- const scaleCoder = chainStorageCoders.get(chainId)?.poolMembers;
4306
- const queries = addresses.flatMap(address => {
4307
- const stateKey = encodeStateKey(scaleCoder, `Invalid address in ${chainId} poolMembers query ${address}`, address);
4308
- if (!stateKey) return [];
4309
- const decodeResult = change => {
4310
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4311
-
4312
- const decoded = decodeScale(scaleCoder, change, `Failed to decode poolMembers on chain ${chainId}`);
4313
- const poolId = decoded?.pool_id?.toString?.();
4314
- const points = decoded?.points?.toString?.();
4315
- const unbondingEras = Array.from(decoded?.unbonding_eras ?? []).flatMap(entry => {
4316
- if (entry === undefined) return [];
4317
- const [key, value] = Array.from(entry);
4318
- const era = key?.toString?.();
4319
- const amount = value?.toString?.();
4320
- if (typeof era !== "string" || typeof amount !== "string") return [];
3629
+ signal?.throwIfAborted();
3630
+ const nomPoolTokenIds = Object.entries(tokens).filter(([, token]) => {
3631
+ // ignore non-native tokens
3632
+ if (token.type !== "substrate-native") return false;
3633
+
3634
+ // ignore tokens on chains with no nompools pallet
3635
+ const miniMetadata = miniMetadatas.get(token.networkId);
3636
+ return typeof miniMetadata?.extra?.nominationPoolsPalletId === "string";
3637
+ }).map(([tokenId]) => tokenId);
3638
+
3639
+ // staking can only be done by the native token on chains with the staking pallet
3640
+ const addressesByNomPoolToken = Object.fromEntries(Object.entries(addressesByToken)
3641
+ // remove ethereum addresses
3642
+ .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !isEthereumAddress(address))])
3643
+ // remove tokens which aren't nom pool tokens
3644
+ .filter(([tokenId]) => nomPoolTokenIds.includes(tokenId)));
3645
+ const uniqueChainIds = getUniqueChainIds(addressesByNomPoolToken, tokens);
3646
+ const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
3647
+ const chainStorageCoders = buildStorageCoders({
3648
+ chainIds: uniqueChainIds,
3649
+ chains,
3650
+ miniMetadatas,
3651
+ coders: {
3652
+ poolMembers: ["NominationPools", "PoolMembers"],
3653
+ bondedPools: ["NominationPools", "BondedPools"],
3654
+ ledger: ["Staking", "Ledger"],
3655
+ metadata: ["NominationPools", "Metadata"]
3656
+ }
3657
+ });
3658
+ const resultUnsubscribes = [];
3659
+ for (const [tokenId, addresses] of Object.entries(addressesByNomPoolToken)) {
3660
+ const token = tokens[tokenId];
3661
+ if (!token) {
3662
+ log.warn(`Token ${tokenId} not found`);
3663
+ continue;
3664
+ }
3665
+ if (token.type !== "substrate-native") {
3666
+ log.debug(`This module doesn't handle tokens of type ${token.type}`);
3667
+ continue;
3668
+ }
3669
+ const chainId = token.networkId;
3670
+ if (!chainId) {
3671
+ log.warn(`Token ${tokenId} has no chain`);
3672
+ continue;
3673
+ }
3674
+ const chain = chains[chainId];
3675
+ if (!chain) {
3676
+ log.warn(`Chain ${chainId} for token ${tokenId} not found`);
3677
+ continue;
3678
+ }
3679
+ const miniMetadata = miniMetadatas.get(chainId);
3680
+ const {
3681
+ nominationPoolsPalletId
3682
+ } = miniMetadata?.extra ?? {};
3683
+ const subscribePoolMembers = (addresses, callback) => {
3684
+ const scaleCoder = chainStorageCoders.get(chainId)?.poolMembers;
3685
+ const queries = addresses.flatMap(address => {
3686
+ const stateKey = encodeStateKey(scaleCoder, `Invalid address in ${chainId} poolMembers query ${address}`, address);
3687
+ if (!stateKey) return [];
3688
+ const decodeResult = change => {
3689
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3690
+
3691
+ const decoded = decodeScale(scaleCoder, change, `Failed to decode poolMembers on chain ${chainId}`);
3692
+ const poolId = decoded?.pool_id?.toString?.();
3693
+ const points = decoded?.points?.toString?.();
3694
+ const unbondingEras = Array.from(decoded?.unbonding_eras ?? []).flatMap(entry => {
3695
+ if (entry === undefined) return [];
3696
+ const [key, value] = Array.from(entry);
3697
+ const era = key?.toString?.();
3698
+ const amount = value?.toString?.();
3699
+ if (typeof era !== "string" || typeof amount !== "string") return [];
3700
+ return {
3701
+ era,
3702
+ amount
3703
+ };
3704
+ });
4321
3705
  return {
4322
- era,
4323
- amount
3706
+ tokenId,
3707
+ address,
3708
+ poolId,
3709
+ points,
3710
+ unbondingEras
4324
3711
  };
4325
- });
3712
+ };
4326
3713
  return {
4327
- tokenId,
4328
- address,
3714
+ chainId,
3715
+ stateKey,
3716
+ decodeResult
3717
+ };
3718
+ });
3719
+ const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3720
+ return () => subscription.then(unsubscribe => unsubscribe());
3721
+ };
3722
+ const subscribePoolPoints = (poolIds, callback) => {
3723
+ if (poolIds.length === 0) callback(null, []);
3724
+ const scaleCoder = chainStorageCoders.get(chainId)?.bondedPools;
3725
+ const queries = poolIds.flatMap(poolId => {
3726
+ const stateKey = encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} bondedPools query ${poolId}`, poolId);
3727
+ if (!stateKey) return [];
3728
+ const decodeResult = change => {
3729
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3730
+
3731
+ const decoded = decodeScale(scaleCoder, change, `Failed to decode bondedPools on chain ${chainId}`);
3732
+ const points = decoded?.points?.toString?.();
3733
+ return {
3734
+ poolId,
3735
+ points
3736
+ };
3737
+ };
3738
+ return {
3739
+ chainId,
3740
+ stateKey,
3741
+ decodeResult
3742
+ };
3743
+ });
3744
+ const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3745
+ return () => subscription.then(unsubscribe => unsubscribe());
3746
+ };
3747
+ const subscribePoolStake = (poolIds, callback) => {
3748
+ if (poolIds.length === 0) callback(null, []);
3749
+ const scaleCoder = chainStorageCoders.get(chainId)?.ledger;
3750
+ const queries = poolIds.flatMap(poolId => {
3751
+ if (!nominationPoolsPalletId) return [];
3752
+ const stashAddress = nompoolStashAccountId(nominationPoolsPalletId, poolId);
3753
+ const stateKey = encodeStateKey(scaleCoder, `Invalid address in ${chainId} ledger query ${stashAddress}`, stashAddress);
3754
+ if (!stateKey) return [];
3755
+ const decodeResult = change => {
3756
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3757
+
3758
+ const decoded = decodeScale(scaleCoder, change, `Failed to decode ledger on chain ${chainId}`);
3759
+ const activeStake = decoded?.active?.toString?.();
3760
+ return {
3761
+ poolId,
3762
+ activeStake
3763
+ };
3764
+ };
3765
+ return {
3766
+ chainId,
3767
+ stateKey,
3768
+ decodeResult
3769
+ };
3770
+ });
3771
+ const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3772
+ return () => subscription.then(unsubscribe => unsubscribe());
3773
+ };
3774
+ const subscribePoolMetadata = (poolIds, callback) => {
3775
+ if (poolIds.length === 0) callback(null, []);
3776
+ const scaleCoder = chainStorageCoders.get(chainId)?.metadata;
3777
+ const queries = poolIds.flatMap(poolId => {
3778
+ if (!nominationPoolsPalletId) return [];
3779
+ const stateKey = encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} metadata query ${poolId}`, poolId);
3780
+ if (!stateKey) return [];
3781
+ const decodeResult = change => {
3782
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3783
+
3784
+ const decoded = decodeScale(scaleCoder, change, `Failed to decode metadata on chain ${chainId}`);
3785
+ const metadata = decoded?.asText?.();
3786
+ return {
3787
+ poolId,
3788
+ metadata
3789
+ };
3790
+ };
3791
+ return {
3792
+ chainId,
3793
+ stateKey,
3794
+ decodeResult
3795
+ };
3796
+ });
3797
+ const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3798
+ return () => subscription.then(unsubscribe => unsubscribe());
3799
+ };
3800
+ const poolMembersByAddress$ = asObservable(subscribePoolMembers)(addresses).pipe(scan((state, next) => {
3801
+ for (const poolMembers of next) {
3802
+ const {
4329
3803
  poolId,
4330
3804
  points,
4331
3805
  unbondingEras
4332
- };
4333
- };
4334
- return {
4335
- chainId,
4336
- stateKey,
4337
- decodeResult
4338
- };
4339
- });
4340
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
4341
- return () => subscription.then(unsubscribe => unsubscribe());
4342
- };
4343
- const subscribePoolPoints = (poolIds, callback) => {
4344
- if (poolIds.length === 0) callback(null, []);
4345
- const scaleCoder = chainStorageCoders.get(chainId)?.bondedPools;
4346
- const queries = poolIds.flatMap(poolId => {
4347
- const stateKey = encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} bondedPools query ${poolId}`, poolId);
4348
- if (!stateKey) return [];
4349
- const decodeResult = change => {
4350
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4351
-
4352
- const decoded = decodeScale(scaleCoder, change, `Failed to decode bondedPools on chain ${chainId}`);
4353
- const points = decoded?.points?.toString?.();
4354
- return {
3806
+ } = poolMembers;
3807
+ if (typeof poolId === "string" && typeof points === "string") state.set(poolMembers.address, {
3808
+ poolId,
3809
+ points,
3810
+ unbondingEras
3811
+ });else state.set(poolMembers.address, null);
3812
+ }
3813
+ return state;
3814
+ }, new Map()), share());
3815
+ const poolIdByAddress$ = poolMembersByAddress$.pipe(map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.poolId ?? null]))));
3816
+ const pointsByAddress$ = poolMembersByAddress$.pipe(map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.points ?? null]))));
3817
+ const unbondingErasByAddress$ = poolMembersByAddress$.pipe(map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.unbondingEras ?? null]))));
3818
+ const poolIds$ = poolIdByAddress$.pipe(map(byAddress => [...new Set(Array.from(byAddress.values()).flatMap(poolId => poolId ?? []))]));
3819
+ const pointsByPool$ = poolIds$.pipe(map(poolIds => asObservable(subscribePoolPoints)(poolIds)), switchAll(), scan((state, next) => {
3820
+ for (const poolPoints of next) {
3821
+ const {
4355
3822
  poolId,
4356
3823
  points
4357
- };
4358
- };
4359
- return {
4360
- chainId,
4361
- stateKey,
4362
- decodeResult
4363
- };
4364
- });
4365
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
4366
- return () => subscription.then(unsubscribe => unsubscribe());
4367
- };
4368
- const subscribePoolStake = (poolIds, callback) => {
4369
- if (poolIds.length === 0) callback(null, []);
4370
- const scaleCoder = chainStorageCoders.get(chainId)?.ledger;
4371
- const queries = poolIds.flatMap(poolId => {
4372
- if (!nominationPoolsPalletId) return [];
4373
- const stashAddress = nompoolStashAccountId(nominationPoolsPalletId, poolId);
4374
- const stateKey = encodeStateKey(scaleCoder, `Invalid address in ${chainId} ledger query ${stashAddress}`, stashAddress);
4375
- if (!stateKey) return [];
4376
- const decodeResult = change => {
4377
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4378
-
4379
- const decoded = decodeScale(scaleCoder, change, `Failed to decode ledger on chain ${chainId}`);
4380
- const activeStake = decoded?.active?.toString?.();
4381
- return {
3824
+ } = poolPoints;
3825
+ if (typeof points === "string") state.set(poolId, points);else state.delete(poolId);
3826
+ }
3827
+ return state;
3828
+ }, new Map()));
3829
+ const stakeByPool$ = poolIds$.pipe(map(poolIds => asObservable(subscribePoolStake)(poolIds)), switchAll(), scan((state, next) => {
3830
+ for (const poolStake of next) {
3831
+ const {
4382
3832
  poolId,
4383
3833
  activeStake
4384
- };
4385
- };
4386
- return {
4387
- chainId,
4388
- stateKey,
4389
- decodeResult
4390
- };
4391
- });
4392
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
4393
- return () => subscription.then(unsubscribe => unsubscribe());
4394
- };
4395
- const subscribePoolMetadata = (poolIds, callback) => {
4396
- if (poolIds.length === 0) callback(null, []);
4397
- const scaleCoder = chainStorageCoders.get(chainId)?.metadata;
4398
- const queries = poolIds.flatMap(poolId => {
4399
- if (!nominationPoolsPalletId) return [];
4400
- const stateKey = encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} metadata query ${poolId}`, poolId);
4401
- if (!stateKey) return [];
4402
- const decodeResult = change => {
4403
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4404
-
4405
- const decoded = decodeScale(scaleCoder, change, `Failed to decode metadata on chain ${chainId}`);
4406
- const metadata = decoded?.asText?.();
4407
- return {
3834
+ } = poolStake;
3835
+ if (typeof activeStake === "string") state.set(poolId, activeStake);else state.delete(poolId);
3836
+ }
3837
+ return state;
3838
+ }, new Map()));
3839
+ const metadataByPool$ = poolIds$.pipe(map(poolIds => asObservable(subscribePoolMetadata)(poolIds)), switchAll(), scan((state, next) => {
3840
+ for (const poolMetadata of next) {
3841
+ const {
4408
3842
  poolId,
4409
3843
  metadata
4410
- };
4411
- };
4412
- return {
4413
- chainId,
4414
- stateKey,
4415
- decodeResult
4416
- };
4417
- });
4418
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
4419
- return () => subscription.then(unsubscribe => unsubscribe());
4420
- };
4421
- const poolMembersByAddress$ = asObservable(subscribePoolMembers)(addresses).pipe(scan((state, next) => {
4422
- for (const poolMembers of next) {
4423
- const {
4424
- poolId,
4425
- points,
4426
- unbondingEras
4427
- } = poolMembers;
4428
- if (typeof poolId === "string" && typeof points === "string") state.set(poolMembers.address, {
4429
- poolId,
4430
- points,
4431
- unbondingEras
4432
- });else state.set(poolMembers.address, null);
4433
- }
4434
- return state;
4435
- }, new Map()), share());
4436
- const poolIdByAddress$ = poolMembersByAddress$.pipe(map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.poolId ?? null]))));
4437
- const pointsByAddress$ = poolMembersByAddress$.pipe(map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.points ?? null]))));
4438
- const unbondingErasByAddress$ = poolMembersByAddress$.pipe(map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.unbondingEras ?? null]))));
4439
- const poolIds$ = poolIdByAddress$.pipe(map(byAddress => [...new Set(Array.from(byAddress.values()).flatMap(poolId => poolId ?? []))]));
4440
- const pointsByPool$ = poolIds$.pipe(map(poolIds => asObservable(subscribePoolPoints)(poolIds)), switchAll(), scan((state, next) => {
4441
- for (const poolPoints of next) {
4442
- const {
4443
- poolId,
4444
- points
4445
- } = poolPoints;
4446
- if (typeof points === "string") state.set(poolId, points);else state.delete(poolId);
4447
- }
4448
- return state;
4449
- }, new Map()));
4450
- const stakeByPool$ = poolIds$.pipe(map(poolIds => asObservable(subscribePoolStake)(poolIds)), switchAll(), scan((state, next) => {
4451
- for (const poolStake of next) {
4452
- const {
4453
- poolId,
4454
- activeStake
4455
- } = poolStake;
4456
- if (typeof activeStake === "string") state.set(poolId, activeStake);else state.delete(poolId);
4457
- }
4458
- return state;
4459
- }, new Map()));
4460
- const metadataByPool$ = poolIds$.pipe(map(poolIds => asObservable(subscribePoolMetadata)(poolIds)), switchAll(), scan((state, next) => {
4461
- for (const poolMetadata of next) {
4462
- const {
4463
- poolId,
4464
- metadata
4465
- } = poolMetadata;
4466
- if (typeof metadata === "string") state.set(poolId, metadata);else state.delete(poolId);
4467
- }
4468
- return state;
4469
- }, new Map()));
4470
- const subscription = combineLatest([poolIdByAddress$, pointsByAddress$, unbondingErasByAddress$, pointsByPool$, stakeByPool$, metadataByPool$]).subscribe({
4471
- next: ([poolIdByAddress, pointsByAddress, unbondingErasByAddress, pointsByPool, stakeByPool, metadataByPool]) => {
4472
- const balances = Array.from(poolIdByAddress).map(([address, poolId]) => {
4473
- const parsedPoolId = poolId === null ? undefined : parseInt(poolId);
4474
- const points = pointsByAddress.get(address) ?? "0";
4475
- const poolPoints = pointsByPool.get(poolId ?? "") ?? "0";
4476
- const poolStake = stakeByPool.get(poolId ?? "") ?? "0";
4477
- const poolMetadata = poolId ? metadataByPool.get(poolId) ?? `Pool ${poolId}` : undefined;
4478
- const amount = points === "0" || poolPoints === "0" || poolStake === "0" ? 0n : BigInt(poolStake) * BigInt(points) / BigInt(poolPoints);
4479
- const unbondingAmount = (unbondingErasByAddress.get(address) ?? []).reduce((total, {
4480
- amount
4481
- }) => total + BigInt(amount ?? "0"), 0n);
4482
- return {
4483
- source: "substrate-native",
4484
- status: "live",
4485
- address,
4486
- multiChainId: {
4487
- subChainId: chainId
4488
- },
4489
- chainId,
4490
- tokenId,
4491
- values: [{
4492
- source: "nompools-staking",
4493
- type: "nompool",
4494
- label: "nompools-staking",
4495
- amount: amount.toString(),
4496
- meta: {
3844
+ } = poolMetadata;
3845
+ if (typeof metadata === "string") state.set(poolId, metadata);else state.delete(poolId);
3846
+ }
3847
+ return state;
3848
+ }, new Map()));
3849
+ const subscription = combineLatest([poolIdByAddress$, pointsByAddress$, unbondingErasByAddress$, pointsByPool$, stakeByPool$, metadataByPool$]).subscribe({
3850
+ next: ([poolIdByAddress, pointsByAddress, unbondingErasByAddress, pointsByPool, stakeByPool, metadataByPool]) => {
3851
+ const balances = Array.from(poolIdByAddress).map(([address, poolId]) => {
3852
+ const parsedPoolId = poolId === null ? undefined : parseInt(poolId);
3853
+ const points = pointsByAddress.get(address) ?? "0";
3854
+ const poolPoints = pointsByPool.get(poolId ?? "") ?? "0";
3855
+ const poolStake = stakeByPool.get(poolId ?? "") ?? "0";
3856
+ const poolMetadata = poolId ? metadataByPool.get(poolId) ?? `Pool ${poolId}` : undefined;
3857
+ const amount = points === "0" || poolPoints === "0" || poolStake === "0" ? 0n : BigInt(poolStake) * BigInt(points) / BigInt(poolPoints);
3858
+ const unbondingAmount = (unbondingErasByAddress.get(address) ?? []).reduce((total, {
3859
+ amount
3860
+ }) => total + BigInt(amount ?? "0"), 0n);
3861
+ return {
3862
+ source: "substrate-native",
3863
+ status: "live",
3864
+ address,
3865
+ networkId: chainId,
3866
+ tokenId,
3867
+ values: [{
3868
+ source: "nompools-staking",
4497
3869
  type: "nompool",
4498
- poolId: parsedPoolId,
4499
- description: poolMetadata
4500
- }
4501
- }, {
4502
- source: "nompools-staking",
4503
- type: "nompool",
4504
- label: "nompools-unbonding",
4505
- amount: unbondingAmount.toString(),
4506
- meta: {
4507
- poolId: parsedPoolId,
4508
- description: poolMetadata,
4509
- unbonding: true
4510
- }
4511
- }]
4512
- };
4513
- }).filter(isNotNil);
4514
- if (balances.length > 0) callback(null, balances);
4515
- },
4516
- error: error => callback(error)
3870
+ label: "nompools-staking",
3871
+ amount: amount.toString(),
3872
+ meta: {
3873
+ type: "nompool",
3874
+ poolId: parsedPoolId,
3875
+ description: poolMetadata
3876
+ }
3877
+ }, {
3878
+ source: "nompools-staking",
3879
+ type: "nompool",
3880
+ label: "nompools-unbonding",
3881
+ amount: unbondingAmount.toString(),
3882
+ meta: {
3883
+ poolId: parsedPoolId,
3884
+ description: poolMetadata,
3885
+ unbonding: true
3886
+ }
3887
+ }]
3888
+ };
3889
+ }).filter(isNotNil);
3890
+ if (balances.length > 0) callback(null, balances);
3891
+ },
3892
+ error: error => callback(error)
3893
+ });
3894
+ resultUnsubscribes.push(() => subscription.unsubscribe());
3895
+ }
3896
+ return () => resultUnsubscribes.forEach(unsub => unsub());
3897
+ } catch (err) {
3898
+ if (!isAbortError(err)) log.error("Error subscribing to nom pool staking", {
3899
+ err
4517
3900
  });
4518
- resultUnsubscribes.push(() => subscription.unsubscribe());
3901
+ return () => {};
4519
3902
  }
4520
- return () => resultUnsubscribes.forEach(unsub => unsub());
4521
3903
  }
4522
3904
 
4523
3905
  const SUBTENSOR_ROOT_NETUID = 0;
@@ -4554,230 +3936,241 @@ const calculateTaoFromDynamicInfo = ({
4554
3936
  dynamicInfo
4555
3937
  });
4556
3938
  return calculateTaoAmountFromAlpha({
4557
- alphaPrice,
4558
- alphaStaked
4559
- });
4560
- };
4561
-
4562
- async function subscribeSubtensorStaking(chaindataProvider, chainConnector, addressesByToken, callback) {
4563
- const allChains = await chaindataProvider.chainsById();
4564
- const tokens = await chaindataProvider.tokensById();
4565
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
4566
- const subtensorTokenIds = Object.entries(tokens).filter(([, token]) => {
4567
- // ignore non-native tokens
4568
- if (token.type !== "substrate-native") return false;
4569
- // ignore tokens on chains with no subtensor pallet
4570
- const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", allChains[token.chain.id]);
4571
- return chainMeta?.hasSubtensorPallet === true;
4572
- }).map(([tokenId]) => tokenId);
4573
-
4574
- // staking can only be done by the native token on chains with the subtensor pallet
4575
- const addressesBySubtensorToken = Object.fromEntries(Object.entries(addressesByToken)
4576
- // remove ethereum addresses
4577
- .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !isEthereumAddress(address))])
4578
- // remove tokens which aren't subtensor staking tokens
4579
- .filter(([tokenId]) => subtensorTokenIds.includes(tokenId)));
4580
- const uniqueChainIds = getUniqueChainIds(addressesBySubtensorToken, tokens);
4581
- const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
4582
- const abortController = new AbortController();
4583
- for (const [tokenId, addresses] of Object.entries(addressesBySubtensorToken)) {
4584
- const token = tokens[tokenId];
4585
- if (!token) {
4586
- log.warn(`Token ${tokenId} not found`);
4587
- continue;
4588
- }
4589
- if (token.type !== "substrate-native") {
4590
- log.debug(`This module doesn't handle tokens of type ${token.type}`);
4591
- continue;
4592
- }
4593
- const chainId = token.chain?.id;
4594
- if (!chainId) {
4595
- log.warn(`Token ${tokenId} has no chain`);
4596
- continue;
4597
- }
4598
- const chain = chains[chainId];
4599
- if (!chain) {
4600
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
4601
- continue;
4602
- }
4603
- const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", chain);
4604
- if (!chainMeta?.miniMetadata) {
4605
- log.warn(`MiniMetadata for chain ${chainId} not found`);
4606
- continue;
3939
+ alphaPrice,
3940
+ alphaStaked
3941
+ });
3942
+ };
3943
+
3944
+ // TODO make this method chain-specific
3945
+ async function subscribeSubtensorStaking(chaindataProvider, chainConnector, addressesByToken, callback, signal) {
3946
+ try {
3947
+ const allChains = await chaindataProvider.getNetworksMapById("polkadot");
3948
+ const tokens = await chaindataProvider.getTokensMapById();
3949
+
3950
+ // there should be only one network here when subscribing to balances, we've split it up by network at the top level
3951
+ const networkIds = keys(addressesByToken).map(tokenId => parseTokenId(tokenId).networkId);
3952
+ const miniMetadatas = new Map();
3953
+ for (const networkId of networkIds) {
3954
+ const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native", signal);
3955
+ miniMetadatas.set(networkId, miniMetadata);
4607
3956
  }
4608
- const scaleApi = getScaleApi({
4609
- chainId,
4610
- send: (...args) => chainConnector.send(chainId, ...args, {
4611
- expectErrors: true
4612
- } // don't pollute the wallet logs when this request fails
4613
- )
4614
- }, chainMeta.miniMetadata, token, chain.hasCheckMetadataHash, chain.signedExtensions, chain.registryTypes);
4615
-
4616
- // sets the number of addresses to query in parallel (per chain, since each chain runs in parallel to the others)
4617
- const concurrency = 4;
4618
- // In-memory cache for successful dynamic info results
4619
- const dynamicInfoCache = new Map();
4620
- const fetchDynamicInfoForNetuids = async uniqueNetuids => {
4621
- const MAX_RETRIES = 3;
4622
- const RETRY_DELAY_MS = 500;
4623
- const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
4624
- const fetchInfo = async netuid => {
4625
- if (netuid === 0) return null;
4626
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
4627
- try {
4628
- const params = [netuid];
4629
- const result = await scaleApi.getRuntimeCallValue("SubnetInfoRuntimeApi", "get_dynamic_info", params);
4630
- dynamicInfoCache.set(netuid, result); // Cache successful response
4631
- return result;
4632
- } catch (error) {
4633
- log.trace(`Attempt ${attempt} failed for netuid ${netuid}:`, error);
4634
- if (attempt < MAX_RETRIES) {
4635
- const backoffTime = RETRY_DELAY_MS * 2 ** (attempt - 1);
4636
- log.trace(`Retrying in ${backoffTime}ms...`);
4637
- await delay(backoffTime);
3957
+ signal?.throwIfAborted();
3958
+ const subtensorTokenIds = Object.entries(tokens).filter(([, token]) => {
3959
+ // ignore non-native tokens
3960
+ if (token.type !== "substrate-native") return false;
3961
+ // ignore tokens on chains with no subtensor pallet
3962
+ const miniMetadata = miniMetadatas.get(token.networkId);
3963
+ return miniMetadata?.extra?.hasSubtensorPallet === true;
3964
+ }).map(([tokenId]) => tokenId);
3965
+
3966
+ // staking can only be done by the native token on chains with the subtensor pallet
3967
+ const addressesBySubtensorToken = Object.fromEntries(Object.entries(addressesByToken)
3968
+ // remove ethereum addresses
3969
+ .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !isEthereumAddress(address))])
3970
+ // remove tokens which aren't subtensor staking tokens
3971
+ .filter(([tokenId]) => subtensorTokenIds.includes(tokenId)));
3972
+ const uniqueChainIds = getUniqueChainIds(addressesBySubtensorToken, tokens);
3973
+ const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
3974
+ const abortController = new AbortController();
3975
+ for (const [tokenId, addresses] of Object.entries(addressesBySubtensorToken)) {
3976
+ const token = tokens[tokenId];
3977
+ if (!token) {
3978
+ log.warn(`Token ${tokenId} not found`);
3979
+ continue;
3980
+ }
3981
+ if (token.type !== "substrate-native") {
3982
+ log.debug(`This module doesn't handle tokens of type ${token.type}`);
3983
+ continue;
3984
+ }
3985
+ const chainId = token.networkId;
3986
+ const chain = chains[chainId];
3987
+ if (!chain) {
3988
+ log.warn(`Chain ${chainId} for token ${tokenId} not found`);
3989
+ continue;
3990
+ }
3991
+ const miniMetadata = miniMetadatas.get(token.networkId);
3992
+ if (!miniMetadata?.data) {
3993
+ log.warn(`MiniMetadata for chain ${chainId} not found`);
3994
+ continue;
3995
+ }
3996
+ const scaleApi = getScaleApi({
3997
+ chainId,
3998
+ send: (...args) => chainConnector.send(chainId, ...args, {
3999
+ expectErrors: true
4000
+ } // don't pollute the wallet logs when this request fails
4001
+ )
4002
+ }, miniMetadata.data, token, chain.hasCheckMetadataHash, chain.signedExtensions, chain.registryTypes);
4003
+
4004
+ // sets the number of addresses to query in parallel (per chain, since each chain runs in parallel to the others)
4005
+ const concurrency = 4;
4006
+ // In-memory cache for successful dynamic info results
4007
+ const dynamicInfoCache = new Map();
4008
+ const fetchDynamicInfoForNetuids = async uniqueNetuids => {
4009
+ const MAX_RETRIES = 3;
4010
+ const RETRY_DELAY_MS = 500;
4011
+ const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
4012
+ const fetchInfo = async netuid => {
4013
+ if (netuid === 0) return null;
4014
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
4015
+ try {
4016
+ const params = [netuid];
4017
+ const result = await scaleApi.getRuntimeCallValue("SubnetInfoRuntimeApi", "get_dynamic_info", params);
4018
+ dynamicInfoCache.set(netuid, result); // Cache successful response
4019
+ return result;
4020
+ } catch (error) {
4021
+ log.trace(`Attempt ${attempt} failed for netuid ${netuid}:`, error);
4022
+ if (attempt < MAX_RETRIES) {
4023
+ const backoffTime = RETRY_DELAY_MS * 2 ** (attempt - 1);
4024
+ log.trace(`Retrying in ${backoffTime}ms...`);
4025
+ await delay(backoffTime);
4026
+ }
4638
4027
  }
4639
4028
  }
4640
- }
4641
- if (dynamicInfoCache.has(netuid)) {
4642
- return dynamicInfoCache.get(netuid); // Use cached value on failure
4643
- }
4644
- log.trace(`Failed to fetch dynamic info for netuid ${netuid} after ${MAX_RETRIES} attempts.`);
4645
- return null;
4029
+ if (dynamicInfoCache.has(netuid)) {
4030
+ return dynamicInfoCache.get(netuid); // Use cached value on failure
4031
+ }
4032
+ log.trace(`Failed to fetch dynamic info for netuid ${netuid} after ${MAX_RETRIES} attempts.`);
4033
+ return null;
4034
+ };
4035
+ return Promise.all(uniqueNetuids.map(fetchInfo));
4646
4036
  };
4647
- return Promise.all(uniqueNetuids.map(fetchInfo));
4648
- };
4649
- const subtensorQueries = from(addresses).pipe(
4650
- // mergeMap lets us run N concurrent queries, where N is the value of `concurrency`
4651
- mergeMap(async address => {
4652
- const queryMethods = [async () => {
4653
- if (chain.isTestnet) return [];
4654
- const params = [address];
4655
- const result = await scaleApi.getRuntimeCallValue("StakeInfoRuntimeApi", "get_stake_info_for_coldkey", params);
4656
- if (!Array.isArray(result)) return [];
4657
- const uniqueNetuids = Array.from(new Set(result.map(item => Number(item.netuid)).filter(netuid => netuid !== SUBTENSOR_ROOT_NETUID)));
4658
- await fetchDynamicInfoForNetuids(uniqueNetuids);
4659
- const stakes = result?.map(({
4660
- coldkey,
4661
- hotkey,
4662
- netuid,
4663
- stake
4664
- }) => {
4665
- return {
4666
- address: coldkey,
4037
+ const subtensorQueries = from(addresses).pipe(
4038
+ // mergeMap lets us run N concurrent queries, where N is the value of `concurrency`
4039
+ mergeMap(async address => {
4040
+ const queryMethods = [async () => {
4041
+ if (chain.isTestnet) return [];
4042
+ const params = [address];
4043
+ const result = await scaleApi.getRuntimeCallValue("StakeInfoRuntimeApi", "get_stake_info_for_coldkey", params);
4044
+ if (!Array.isArray(result)) return [];
4045
+ const uniqueNetuids = Array.from(new Set(result.map(item => Number(item.netuid)).filter(netuid => netuid !== SUBTENSOR_ROOT_NETUID)));
4046
+ await fetchDynamicInfoForNetuids(uniqueNetuids);
4047
+ const stakes = result?.map(({
4048
+ coldkey,
4667
4049
  hotkey,
4668
- netuid: Number(netuid),
4669
- stake: BigInt(stake),
4670
- dynamicInfo: dynamicInfoCache.get(Number(netuid))
4671
- };
4672
- }).filter(({
4673
- stake
4674
- }) => stake >= SUBTENSOR_MIN_STAKE_AMOUNT_PLANK);
4675
- return stakes;
4676
- }];
4677
- const errors = [];
4678
- for (const queryMethod of queryMethods) {
4679
- try {
4680
- // try each query method
4681
- return await queryMethod();
4682
- } catch (cause) {
4683
- // if it fails, keep track of the error and try the next one
4684
- errors.push(cause);
4050
+ netuid,
4051
+ stake
4052
+ }) => {
4053
+ return {
4054
+ address: coldkey,
4055
+ hotkey,
4056
+ netuid: Number(netuid),
4057
+ stake: BigInt(stake),
4058
+ dynamicInfo: dynamicInfoCache.get(Number(netuid))
4059
+ };
4060
+ }).filter(({
4061
+ stake
4062
+ }) => stake >= SUBTENSOR_MIN_STAKE_AMOUNT_PLANK);
4063
+ return stakes;
4064
+ }];
4065
+ const errors = [];
4066
+ for (const queryMethod of queryMethods) {
4067
+ try {
4068
+ // try each query method
4069
+ return await queryMethod();
4070
+ } catch (cause) {
4071
+ // if it fails, keep track of the error and try the next one
4072
+ errors.push(cause);
4073
+ }
4685
4074
  }
4686
- }
4687
4075
 
4688
- // if we get to here, that means that all query methods failed
4689
- // let's throw the errors back to the native balance module
4690
- throw new Error([`Failed to fetch ${tokenId} subtensor staked balance for ${address}:`, ...errors.map(error => String(error))].join("\n\t"));
4691
- }, concurrency),
4692
- // instead of emitting each balance as it's fetched, toArray waits for them all to fetch and then it collects them into an array
4693
- toArray(),
4694
- // this mergeMap flattens our Array<Array<Stakes>> into just an Array<Stakes>
4695
- mergeMap(stakes => stakes),
4696
- // convert our Array<Stakes> into Array<Balances>, which we can then return to the native balance module
4697
- map(stakes => stakes.map(({
4698
- address,
4699
- hotkey,
4700
- stake,
4701
- netuid,
4702
- dynamicInfo
4703
- }) => {
4704
- const {
4705
- token_symbol,
4706
- subnet_name,
4707
- subnet_identity
4708
- } = dynamicInfo ?? {};
4709
- const tokenSymbol = new TextDecoder().decode(Uint8Array.from(token_symbol ?? []));
4710
- const subnetName = new TextDecoder().decode(Uint8Array.from(subnet_name ?? []));
4711
-
4712
- /** Map from Record<string, Binary> to Record<string, string> */
4713
- const binaryToText = input => Object.entries(input).reduce((acc, [key, value]) => {
4714
- acc[key] = value.asText();
4715
- return acc;
4716
- }, {});
4717
- const subnetIdentity = subnet_identity ? binaryToText(subnet_identity) : undefined;
4718
-
4719
- // Add 1n balance if failed to fetch dynamic info, so the position is not ignored by Balance lib and is displayed in the UI.
4720
- const alphaStakedInTao = dynamicInfo ? calculateTaoFromDynamicInfo({
4721
- dynamicInfo,
4722
- alphaStaked: stake
4723
- }) : 1n;
4724
- const alphaToTaoRate = calculateTaoFromDynamicInfo({
4725
- dynamicInfo: dynamicInfo ?? null,
4726
- alphaStaked: ONE_ALPHA_TOKEN
4727
- }).toString();
4728
- const stakeByNetuid = Number(netuid) === SUBTENSOR_ROOT_NETUID ? stake : alphaStakedInTao;
4729
- return {
4730
- source: "substrate-native",
4731
- status: "live",
4076
+ // if we get to here, that means that all query methods failed
4077
+ // let's throw the errors back to the native balance module
4078
+ throw new Error([`Failed to fetch ${tokenId} subtensor staked balance for ${address}:`, ...errors.map(error => String(error))].join("\n\t"));
4079
+ }, concurrency),
4080
+ // instead of emitting each balance as it's fetched, toArray waits for them all to fetch and then it collects them into an array
4081
+ toArray(),
4082
+ // this mergeMap flattens our Array<Array<Stakes>> into just an Array<Stakes>
4083
+ mergeMap(stakes => stakes),
4084
+ // convert our Array<Stakes> into Array<Balances>, which we can then return to the native balance module
4085
+ map(stakes => stakes.map(({
4732
4086
  address,
4733
- multiChainId: {
4734
- subChainId: chainId
4735
- },
4736
- chainId,
4737
- tokenId,
4738
- values: [{
4739
- source: "subtensor-staking",
4740
- type: "subtensor",
4741
- label: "subtensor-staking",
4742
- amount: stakeByNetuid.toString(),
4743
- meta: {
4744
- type: "subtensor-staking",
4745
- hotkey,
4746
- netuid,
4747
- amountStaked: stake.toString(),
4748
- alphaToTaoRate,
4749
- dynamicInfo: {
4750
- tokenSymbol,
4751
- subnetName,
4752
- subnetIdentity: {
4753
- ...subnetIdentity,
4754
- subnetName: subnetIdentity?.subnet_name || subnetName
4087
+ hotkey,
4088
+ stake,
4089
+ netuid,
4090
+ dynamicInfo
4091
+ }) => {
4092
+ const {
4093
+ token_symbol,
4094
+ subnet_name,
4095
+ subnet_identity
4096
+ } = dynamicInfo ?? {};
4097
+ const tokenSymbol = new TextDecoder().decode(Uint8Array.from(token_symbol ?? []));
4098
+ const subnetName = new TextDecoder().decode(Uint8Array.from(subnet_name ?? []));
4099
+
4100
+ /** Map from Record<string, Binary> to Record<string, string> */
4101
+ const binaryToText = input => Object.entries(input).reduce((acc, [key, value]) => {
4102
+ acc[key] = value.asText();
4103
+ return acc;
4104
+ }, {});
4105
+ const subnetIdentity = subnet_identity ? binaryToText(subnet_identity) : undefined;
4106
+
4107
+ // Add 1n balance if failed to fetch dynamic info, so the position is not ignored by Balance lib and is displayed in the UI.
4108
+ const alphaStakedInTao = dynamicInfo ? calculateTaoFromDynamicInfo({
4109
+ dynamicInfo,
4110
+ alphaStaked: stake
4111
+ }) : 1n;
4112
+ const alphaToTaoRate = calculateTaoFromDynamicInfo({
4113
+ dynamicInfo: dynamicInfo ?? null,
4114
+ alphaStaked: ONE_ALPHA_TOKEN
4115
+ }).toString();
4116
+ const stakeByNetuid = Number(netuid) === SUBTENSOR_ROOT_NETUID ? stake : alphaStakedInTao;
4117
+ return {
4118
+ source: "substrate-native",
4119
+ status: "live",
4120
+ address,
4121
+ networkId: chainId,
4122
+ tokenId,
4123
+ values: [{
4124
+ source: "subtensor-staking",
4125
+ type: "subtensor",
4126
+ label: "subtensor-staking",
4127
+ amount: stakeByNetuid.toString(),
4128
+ meta: {
4129
+ type: "subtensor-staking",
4130
+ hotkey,
4131
+ netuid,
4132
+ amountStaked: stake.toString(),
4133
+ alphaToTaoRate,
4134
+ dynamicInfo: {
4135
+ tokenSymbol,
4136
+ subnetName,
4137
+ subnetIdentity: {
4138
+ ...subnetIdentity,
4139
+ subnetName: subnetIdentity?.subnet_name || subnetName
4140
+ }
4755
4141
  }
4756
4142
  }
4757
- }
4758
- }]
4759
- };
4760
- })));
4143
+ }]
4144
+ };
4145
+ })));
4146
+
4147
+ // This observable will run the subtensorQueries on a 30s (30_000ms) interval.
4148
+ // However, if the last run has not yet completed (e.g. its been 30s but we're still fetching some balances),
4149
+ // then exhaustMap will wait until the next interval (so T: 60s, T: 90s, T: 120s, etc) before re-executing the subtensorQueries.
4150
+ const subtensorQueriesInterval = interval(30_000).pipe(startWith(0),
4151
+ // start immediately
4152
+ exhaustMap(() => {
4153
+ return subtensorQueries;
4154
+ }));
4761
4155
 
4762
- // This observable will run the subtensorQueries on a 30s (30_000ms) interval.
4763
- // However, if the last run has not yet completed (e.g. its been 30s but we're still fetching some balances),
4764
- // then exhaustMap will wait until the next interval (so T: 60s, T: 90s, T: 120s, etc) before re-executing the subtensorQueries.
4765
- const subtensorQueriesInterval = interval(30_000).pipe(startWith(0),
4766
- // start immediately
4767
- exhaustMap(() => {
4768
- return subtensorQueries;
4769
- }));
4156
+ // subscribe to the balances
4157
+ const subscription = subtensorQueriesInterval.subscribe({
4158
+ next: balances => callback(null, balances),
4159
+ error: error => callback(error)
4160
+ });
4770
4161
 
4771
- // subscribe to the balances
4772
- const subscription = subtensorQueriesInterval.subscribe({
4773
- next: balances => callback(null, balances),
4774
- error: error => callback(error)
4162
+ // use the abortController to tear the subscription down when we don't need it anymore
4163
+ abortController.signal.addEventListener("abort", () => {
4164
+ subscription.unsubscribe();
4165
+ });
4166
+ }
4167
+ return () => abortController.abort();
4168
+ } catch (err) {
4169
+ if (!isAbortError(err)) log.error("Error subscribing to subtensor staking", {
4170
+ err
4775
4171
  });
4776
-
4777
- // use the abortController to tear the subscription down when we don't need it anymore
4778
- abortController.signal.onabort = () => subscription.unsubscribe();
4172
+ return () => {};
4779
4173
  }
4780
- return () => abortController.abort();
4781
4174
  }
4782
4175
 
4783
4176
  const getOtherType = input => `other-${input}`;
@@ -4833,18 +4226,14 @@ const filterBaseLocks = locks => {
4833
4226
  };
4834
4227
 
4835
4228
  // TODO: Make these titles translatable
4836
- const getLockTitle = (lock, {
4229
+ const getLockTitle = (lock,
4230
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
4231
+ {
4837
4232
  balance
4838
4233
  } = {}) => {
4839
4234
  if (!lock.label) return lock.label;
4840
4235
  if (lock.label === "democracy") return "Governance";
4841
- if (lock.label === "crowdloan") {
4842
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
4843
- const paraId = lock.meta?.paraId;
4844
- if (!paraId) return "Crowdloan";
4845
- const name = balance?.chain?.parathreads?.find(parathread => parathread?.paraId === paraId)?.name;
4846
- return `${name ? name : `Parachain ${paraId}`} Crowdloan`;
4847
- }
4236
+ if (lock.label === "crowdloan") return "Crowdloan";
4848
4237
  if (lock.label === "nompools-staking") return "Pooled Staking";
4849
4238
  if (lock.label === "nompools-unbonding") return "Pooled Staking";
4850
4239
  if (lock.label === "subtensor-staking") return "Root Staking";
@@ -4856,7 +4245,6 @@ const getLockTitle = (lock, {
4856
4245
  };
4857
4246
 
4858
4247
  const moduleType$2 = "substrate-native";
4859
- const subNativeTokenId = chainId => `${chainId}-substrate-native`.toLowerCase().replace(/ /g, "-");
4860
4248
 
4861
4249
  /**
4862
4250
  * Function to merge two 'sub sources' of the same balance together, or
@@ -4934,7 +4322,7 @@ async function buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas,
4934
4322
  log.debug(`This module doesn't handle tokens of type ${token.type}`);
4935
4323
  return outerResult;
4936
4324
  }
4937
- const chainId = token.chain?.id;
4325
+ const chainId = token.networkId;
4938
4326
  if (!chainId) {
4939
4327
  log.warn(`Token ${tokenId} has no chain`);
4940
4328
  return outerResult;
@@ -4944,10 +4332,10 @@ async function buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas,
4944
4332
  log.warn(`Chain ${chainId} for token ${tokenId} not found`);
4945
4333
  return outerResult;
4946
4334
  }
4947
- const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", chain);
4335
+ const miniMetadata = miniMetadatas.get(chainId);
4948
4336
  const {
4949
4337
  useLegacyTransferableCalculation
4950
- } = chainMeta ?? {};
4338
+ } = miniMetadata?.extra ?? {};
4951
4339
  addresses.flat().forEach(address => {
4952
4340
  const queryKey = `${tokenId}-${address}`;
4953
4341
  // We share this balanceJson between the base and the lock query for this address
@@ -4955,10 +4343,7 @@ async function buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas,
4955
4343
  source: "substrate-native",
4956
4344
  status: "live",
4957
4345
  address,
4958
- multiChainId: {
4959
- subChainId: chainId
4960
- },
4961
- chainId,
4346
+ networkId: chainId,
4962
4347
  tokenId,
4963
4348
  values: []
4964
4349
  };
@@ -5230,75 +4615,19 @@ const updateStakingLocksUsingUnbondingLocks = values => {
5230
4615
  return [...otherValues, ...stakingLocks];
5231
4616
  };
5232
4617
 
5233
- const detectMiniMetadataChanges = () => {
5234
- let previousMap = null;
5235
- return pipe(map(currMap => {
5236
- if (!currMap) return null;
5237
- const changes = new Set();
5238
- if (previousMap) {
5239
- // Check for added or changed keys/values
5240
- for (const [key, value] of currMap) {
5241
- if (!previousMap.has(key) || !isEqual(previousMap.get(key), value)) {
5242
- changes.add(value.chainId);
5243
- }
5244
- }
5245
-
5246
- // Check for removed keys
5247
- for (const [key, value] of previousMap) {
5248
- if (!currMap.has(key)) {
5249
- changes.add(value.chainId);
5250
- }
5251
- }
5252
- }
5253
- previousMap = currMap;
5254
- return changes.size > 0 ? changes : null;
5255
- }),
5256
- // Filter out null emissions (no changes)
5257
- filter(changes => changes !== null));
5258
- };
5259
-
5260
- // NOTE: `liveQuery` is not initialized until commonMetadataObservable is subscribed to.
5261
- const commonMetadataObservable = from(liveQuery(() => db.miniMetadatas.where("source").equals("substrate-native").toArray())).pipe(map(items => new Map(items.map(item => [item.id, item]))),
5262
- // `refCount: true` will unsubscribe from the DB when commonMetadataObservable has no more subscribers
5263
- shareReplay({
5264
- bufferSize: 1,
5265
- refCount: true
5266
- }));
5267
4618
  class QueryCache {
4619
+ #chaindataProvider;
4620
+ #chainConnector;
4621
+ miniMetadatas = new Map();
5268
4622
  balanceQueryCache = new Map();
5269
- metadataSub = null;
5270
- constructor(chaindataProvider) {
4623
+ constructor(chaindataProvider, chainConnector) {
5271
4624
  this.chaindataProvider = chaindataProvider;
5272
- }
5273
- ensureSetup() {
5274
- if (this.metadataSub) return;
5275
- this.metadataSub = commonMetadataObservable.pipe(firstThenDebounce(500), detectMiniMetadataChanges(), combineLatestWith(this.chaindataProvider.tokensObservable), distinctUntilChanged()).subscribe(([miniMetadataChanges, tokens]) => {
5276
- // invalidate cache entries for any chains with new metadata
5277
- const tokensByChainId = tokens.filter(token => token.type === "substrate-native").reduce((result, token) => {
5278
- if (!token.chain?.id) return result;
5279
- result[token.chain.id] ? result[token.chain.id].push(token) : result[token.chain.id] = [token];
5280
- return result;
5281
- }, {});
5282
- miniMetadataChanges.forEach(chainId => {
5283
- const chainTokens = tokensByChainId[chainId];
5284
- if (!chainTokens) return;
5285
- chainTokens.forEach(token => {
5286
- const tokenId = token.id;
5287
- const cacheKeys = this.balanceQueryCache.keys();
5288
- for (const key of cacheKeys) {
5289
- if (key.startsWith(`${tokenId}-`)) this.balanceQueryCache.delete(key);
5290
- }
5291
- });
5292
- });
5293
- });
5294
- }
5295
- destroy() {
5296
- this.metadataSub?.unsubscribe();
4625
+ this.#chaindataProvider = chaindataProvider;
4626
+ this.#chainConnector = chainConnector;
5297
4627
  }
5298
4628
  async getQueries(addressesByToken) {
5299
- this.ensureSetup();
5300
- const chains = await this.chaindataProvider.chainsById();
5301
- const tokens = await this.chaindataProvider.tokensById();
4629
+ const chains = await this.chaindataProvider.getNetworksMapById("polkadot");
4630
+ const tokens = await this.chaindataProvider.getTokensMapById();
5302
4631
  const queryResults = Object.entries(addressesByToken).reduce((result, [tokenId, addresses]) => {
5303
4632
  addresses.forEach(address => {
5304
4633
  const key = `${tokenId}-${address}`;
@@ -5314,15 +4643,19 @@ class QueryCache {
5314
4643
  existing: [],
5315
4644
  newAddressesByToken: {}
5316
4645
  });
4646
+ const byNetwork = getAddresssesByTokenByNetwork(addressesByToken);
4647
+ for (const networkId of keys(byNetwork)) {
4648
+ if (this.miniMetadatas.has(networkId)) continue;
4649
+ const miniMetadata = await getMiniMetadata(this.#chaindataProvider, this.#chainConnector, networkId, "substrate-native");
4650
+ this.miniMetadatas.set(networkId, miniMetadata);
4651
+ }
5317
4652
 
5318
4653
  // build queries for token/address pairs which have not been queried before
5319
- const miniMetadatas = await firstValueFrom(commonMetadataObservable);
5320
- const uniqueChainIds = getUniqueChainIds(queryResults.newAddressesByToken, tokens);
4654
+ const uniqueChainIds = keys(byNetwork); // getUniqueChainIds(queryResults.newAddressesByToken, tokens)
5321
4655
  const chainStorageCoders = buildStorageCoders({
5322
4656
  chainIds: uniqueChainIds,
5323
4657
  chains,
5324
- miniMetadatas,
5325
- moduleType: "substrate-native",
4658
+ miniMetadatas: this.miniMetadatas,
5326
4659
  coders: {
5327
4660
  base: ["System", "Account"],
5328
4661
  stakingLedger: ["Staking", "Ledger"],
@@ -5332,7 +4665,7 @@ class QueryCache {
5332
4665
  freezes: ["Balances", "Freezes"]
5333
4666
  }
5334
4667
  });
5335
- const queries = await buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas, queryResults.newAddressesByToken);
4668
+ const queries = await buildQueries$1(chains, tokens, chainStorageCoders, this.miniMetadatas, queryResults.newAddressesByToken);
5336
4669
  // now update the cache
5337
4670
  Object.entries(queries).forEach(([key, query]) => {
5338
4671
  this.balanceQueryCache.set(key, query);
@@ -5341,14 +4674,11 @@ class QueryCache {
5341
4674
  }
5342
4675
  }
5343
4676
 
5344
- const RELAY_TOKENS = ["polkadot-substrate-native", "kusama-substrate-native"];
5345
- const PUBLIC_GOODS_TOKENS = ["polkadot-asset-hub-substrate-native", "kusama-asset-hub-substrate-native"];
5346
- const sortChains = (a, b) => {
4677
+ const IMPORTANT_TOKENS = [subNativeTokenId("polkadot"), subNativeTokenId("kusama"), subNativeTokenId("polkadot-asset-hub"), subNativeTokenId("kusama-asset-hub"), subNativeTokenId("bittensor")];
4678
+ const sortChainsNativeTokensByPriority = (a, b) => {
5347
4679
  // polkadot and kusama should be checked first
5348
- if (RELAY_TOKENS.includes(a)) return -1;
5349
- if (RELAY_TOKENS.includes(b)) return 1;
5350
- if (PUBLIC_GOODS_TOKENS.includes(a)) return -1;
5351
- if (PUBLIC_GOODS_TOKENS.includes(b)) return 1;
4680
+ if (IMPORTANT_TOKENS.includes(a)) return -1;
4681
+ if (IMPORTANT_TOKENS.includes(b)) return 1;
5352
4682
  return 0;
5353
4683
  };
5354
4684
 
@@ -5364,10 +4694,30 @@ class SubNativeBalanceError extends Error {
5364
4694
  }
5365
4695
  }
5366
4696
 
5367
- const DEFAULT_SYMBOL = "Unit";
5368
- const DEFAULT_DECIMALS = 0;
4697
+ const DotNetworkPropertiesSimple = z.object({
4698
+ tokenDecimals: z.number().optional().default(0),
4699
+ tokenSymbol: z.string().optional().default("Unit")
4700
+ });
4701
+ const DotNetworkPropertiesArray = z.object({
4702
+ tokenDecimals: z.array(z.number()).nonempty(),
4703
+ tokenSymbol: z.array(z.string()).nonempty()
4704
+ });
4705
+ const DotNetworkPropertiesSchema = z.union([DotNetworkPropertiesSimple, DotNetworkPropertiesArray]).transform(val => ({
4706
+ tokenDecimals: Array.isArray(val.tokenDecimals) ? val.tokenDecimals[0] : val.tokenDecimals,
4707
+ tokenSymbol: Array.isArray(val.tokenSymbol) ? val.tokenSymbol[0] : val.tokenSymbol
4708
+ }));
4709
+ const getChainProperties = async (chainConnector, networkId) => {
4710
+ const properties = await chainConnector.send(networkId, "system_properties", [], true);
4711
+ return DotNetworkPropertiesSchema.parse(properties);
4712
+ };
4713
+
5369
4714
  const POLLING_WINDOW_SIZE = 20;
5370
4715
  const MAX_SUBSCRIPTION_SIZE = 40;
4716
+ const EMPTY_CHAIN_META = {
4717
+ miniMetadata: null,
4718
+ extra: null
4719
+ };
4720
+ const SubNativeTokenConfigSchema = TokenConfigBaseSchema;
5371
4721
  const SubNativeModule = hydrate => {
5372
4722
  const {
5373
4723
  chainConnectors,
@@ -5375,33 +4725,198 @@ const SubNativeModule = hydrate => {
5375
4725
  } = hydrate;
5376
4726
  const chainConnector = chainConnectors.substrate;
5377
4727
  assert(chainConnector, "This module requires a substrate chain connector");
5378
- const queryCache = new QueryCache(chaindataProvider);
4728
+ const queryCache = new QueryCache(chaindataProvider, chainConnector);
5379
4729
  const getModuleTokens = async () => {
5380
- return await chaindataProvider.tokensByIdForType(moduleType$2);
4730
+ return await chaindataProvider.getTokensMapById(moduleType$2);
5381
4731
  };
5382
- return {
5383
- ...DefaultBalanceModule(moduleType$2),
5384
- async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc, systemProperties) {
5385
- const isTestnet = (await chaindataProvider.chainById(chainId))?.isTestnet || false;
5386
- if (moduleConfig?.disable === true || metadataRpc === undefined) return {
5387
- isTestnet
5388
- };
5389
4732
 
5390
- //
5391
- // extract system_properties
5392
- //
4733
+ // subscribeBalances was split by network to prevent all subs to wait for all minimetadatas to be ready.
4734
+ // however the multichain logic in there is so deep in the function below that i had to keep it as-is, and call it by per-network chunks
4735
+ // TODO refactor this be actually network specific
4736
+ // Note: had to extract this function from the result object or this.subscribeBalances wouldn't be typed correctly
4737
+ const subscribeChainBalances = (chainId, opts, callback, signal) => {
4738
+ const {
4739
+ addressesByToken,
4740
+ initialBalances
4741
+ } = opts;
4742
+ // full record of balances for this module
4743
+ const subNativeBalances = new BehaviorSubject(Object.fromEntries(initialBalances?.map(b => [getBalanceId(b), b]) ?? []));
4744
+ // tokens which have a known positive balance
4745
+ const positiveBalanceTokens = subNativeBalances.pipe(map(balances => Array.from(new Set(Object.values(balances).map(b => b.tokenId)))), share());
4746
+
4747
+ // tokens that will be subscribed to, simply a slice of the positive balance tokens of size MAX_SUBSCRIPTION_SIZE
4748
+ const subscriptionTokens = positiveBalanceTokens.pipe(map(tokens => tokens.sort(sortChainsNativeTokensByPriority).slice(0, MAX_SUBSCRIPTION_SIZE)));
4749
+
4750
+ // an initialised balance is one where we have received a response for any type of 'subsource',
4751
+ // until then they are initialising. We only need to maintain one map of tokens to addresses for this
4752
+ const initialisingBalances = Object.entries(addressesByToken).reduce((acc, [tokenId, addresses]) => {
4753
+ acc.set(tokenId, new Set(addresses));
4754
+ return acc;
4755
+ }, new Map());
4756
+
4757
+ // after thirty seconds, we need to kill the initialising balances
4758
+ const initBalancesTimeout = setTimeout(() => {
4759
+ initialisingBalances.clear();
4760
+ // manually call the callback to ensure the caller gets the correct status
4761
+ callback(null, {
4762
+ status: "live",
4763
+ data: Object.values(subNativeBalances.getValue())
4764
+ });
4765
+ }, 30_000);
4766
+ const _callbackSub = subNativeBalances.pipe(debounceTime(100)).subscribe({
4767
+ next: balances => {
4768
+ callback(null, {
4769
+ status: initialisingBalances.size > 0 ? "initialising" : "live",
4770
+ data: Object.values(balances)
4771
+ });
4772
+ },
4773
+ error: error => callback(error),
4774
+ complete: () => {
4775
+ initialisingBalances.clear();
4776
+ clearTimeout(initBalancesTimeout);
4777
+ }
4778
+ });
4779
+ const unsubDeferred = Deferred();
4780
+ // we return this to the caller so that they can let us know when they're no longer interested in this subscription
4781
+ const callerUnsubscribe = () => {
4782
+ subNativeBalances.complete();
4783
+ _callbackSub.unsubscribe();
4784
+ return unsubDeferred.reject(new Error(`Caller unsubscribed`));
4785
+ };
4786
+ // we queue up our work to clean up our subscription when this promise rejects
4787
+ const callerUnsubscribed = unsubDeferred.promise;
4788
+
4789
+ // The update handler is to allow us to merge balances with the same id, and manage initialising and positive balances state for each
4790
+ // balance type and network
4791
+ const handleUpdateForSource = source => (error, result) => {
4792
+ if (result) {
4793
+ const currentBalances = subNativeBalances.getValue();
4794
+
4795
+ // first merge any balances with the same id within the result
4796
+ const accumulatedUpdates = result.filter(b => b.values.length > 0).reduce((acc, b) => {
4797
+ const bId = getBalanceId(b);
4798
+ acc[bId] = mergeBalances(acc[bId], b, source, false);
4799
+ return acc;
4800
+ }, {});
4801
+
4802
+ // then merge these with the current balances
4803
+ const mergedBalances = {};
4804
+ Object.entries(accumulatedUpdates).forEach(([bId, b]) => {
4805
+ // merge the values from the new balance into the existing balance, if there is one
4806
+ mergedBalances[bId] = mergeBalances(currentBalances[bId], b, source, true);
4807
+
4808
+ // update initialisingBalances to remove balances which have been updated
4809
+ const intialisingForToken = initialisingBalances.get(b.tokenId);
4810
+ if (intialisingForToken) {
4811
+ intialisingForToken.delete(b.address);
4812
+ if (intialisingForToken.size === 0) initialisingBalances.delete(b.tokenId);else initialisingBalances.set(b.tokenId, intialisingForToken);
4813
+ }
4814
+ });
4815
+ subNativeBalances.next({
4816
+ ...currentBalances,
4817
+ ...mergedBalances
4818
+ });
4819
+ }
4820
+ if (error) {
4821
+ if (error instanceof SubNativeBalanceError) {
4822
+ // this type of error doesn't need to be handled by the caller
4823
+ initialisingBalances.delete(error.tokenId);
4824
+ } else return callback(error);
4825
+ }
4826
+ };
5393
4827
 
5394
- const {
5395
- tokenSymbol,
5396
- tokenDecimals
5397
- } = systemProperties ?? {};
5398
- const symbol = (Array.isArray(tokenSymbol) ? tokenSymbol[0] : tokenSymbol) ?? DEFAULT_SYMBOL;
5399
- const decimals = (Array.isArray(tokenDecimals) ? tokenDecimals[0] : tokenDecimals) ?? DEFAULT_DECIMALS;
4828
+ // subscribe to addresses and tokens for which we have a known positive balance
4829
+ const positiveSub = subscriptionTokens.pipe(debounceTime(1000), takeUntil(callerUnsubscribed), map(tokenIds => tokenIds.reduce((acc, tokenId) => {
4830
+ acc[tokenId] = addressesByToken[tokenId];
4831
+ return acc;
4832
+ }, {})), distinctUntilChanged(isEqual), switchMap(newAddressesByToken => {
4833
+ return from(queryCache.getQueries(newAddressesByToken)).pipe(switchMap(baseQueries => {
4834
+ return new Observable(subscriber => {
4835
+ if (!chainConnectors.substrate) return;
4836
+ const unsubSubtensorStaking = subscribeSubtensorStaking(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("subtensor-staking"), signal);
4837
+ const unsubNompoolStaking = subscribeNompoolStaking(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("nompools-staking"), signal);
4838
+ const unsubBase = subscribeBase(baseQueries, chainConnectors.substrate, handleUpdateForSource("base"));
4839
+ subscriber.add(async () => (await unsubSubtensorStaking)());
4840
+ subscriber.add(async () => (await unsubNompoolStaking)());
4841
+ subscriber.add(async () => (await unsubBase)());
4842
+ });
4843
+ }));
4844
+ })).subscribe();
4845
+
4846
+ // for chains where we don't have a known positive balance, poll rather than subscribe
4847
+ const poll = async (addressesByToken = {}) => {
4848
+ const handleUpdate = handleUpdateForSource("base");
4849
+ try {
4850
+ const balances = await fetchBalances(addressesByToken);
4851
+ handleUpdate(null, Object.values(balances.toJSON()));
4852
+ } catch (error) {
4853
+ if (error instanceof ChainConnectionError) {
4854
+ // coerce ChainConnection errors into SubNativeBalance errors
4855
+ const errorChainId = error.chainId;
4856
+ Object.entries(await getModuleTokens()).filter(([, token]) => token.networkId === errorChainId).forEach(([tokenId]) => {
4857
+ const wrappedError = new SubNativeBalanceError(tokenId, error.message);
4858
+ handleUpdate(wrappedError);
4859
+ });
4860
+ } else {
4861
+ log.error("unknown substrate native balance error", error);
4862
+ handleUpdate(error);
4863
+ }
4864
+ }
4865
+ };
4866
+ // do one poll to get things started
4867
+ const currentBalances = subNativeBalances.getValue();
4868
+ const currentTokens = new Set(Object.values(currentBalances).map(b => b.tokenId));
4869
+ const nonCurrentTokens = Object.keys(addressesByToken).filter(tokenId => !currentTokens.has(tokenId)).sort(sortChainsNativeTokensByPriority);
4870
+
4871
+ // break nonCurrentTokens into chunks of POLLING_WINDOW_SIZE
4872
+ const pool = new PQueue({
4873
+ concurrency: POLLING_WINDOW_SIZE
4874
+ });
4875
+ nonCurrentTokens.forEach(nonCurrentTokenId => pool.add(() => poll({
4876
+ [nonCurrentTokenId]: addressesByToken[nonCurrentTokenId]
4877
+ }), {
4878
+ signal
4879
+ }));
4880
+
4881
+ // now poll every 30s on chains which are not subscriptionTokens
4882
+ // we chunk this observable into batches of positive token ids, to prevent eating all the websocket connections
4883
+ const pollingSub = interval(30_000) // emit values every 30 seconds
4884
+ .pipe(takeUntil(callerUnsubscribed), withLatestFrom(subscriptionTokens),
4885
+ // Combine latest value from subscriptionTokens with each interval tick
4886
+ map(([, subscribedTokenIds]) =>
4887
+ // Filter out tokens that are not subscribed
4888
+ Object.keys(addressesByToken).filter(tokenId => !subscribedTokenIds.includes(tokenId))), exhaustMap(tokenIds => from(arrayChunk(tokenIds, POLLING_WINDOW_SIZE)).pipe(concatMap(async tokenChunk => {
4889
+ // tokenChunk is a chunk of tokenIds with size POLLING_WINDOW_SIZE
4890
+ const pollingTokenAddresses = Object.fromEntries(tokenChunk.map(tokenId => [tokenId, addressesByToken[tokenId]]));
4891
+ await pool.add(() => poll(pollingTokenAddresses), {
4892
+ signal
4893
+ });
4894
+ return true;
4895
+ })))).subscribe();
4896
+ return () => {
4897
+ callerUnsubscribe();
4898
+ positiveSub.unsubscribe();
4899
+ pollingSub.unsubscribe();
4900
+ };
4901
+ };
4902
+ const fetchBalances = async addressesByToken => {
4903
+ assert(chainConnectors.substrate, "This module requires a substrate chain connector");
4904
+ const queries = await queryCache.getQueries(addressesByToken);
4905
+ assert(chainConnectors.substrate, "This module requires a substrate chain connector");
4906
+ const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
4907
+ return new Balances(result ?? []);
4908
+ };
4909
+ return {
4910
+ ...DefaultBalanceModule(moduleType$2),
4911
+ async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
4912
+ if (moduleConfig?.disable) return EMPTY_CHAIN_META;
4913
+ if (!metadataRpc) return EMPTY_CHAIN_META;
5400
4914
 
5401
4915
  //
5402
4916
  // process metadata into SCALE encoders/decoders
5403
4917
  //
5404
4918
  const metadataVersion = getMetadataVersion(metadataRpc);
4919
+ if (metadataVersion < 14) return EMPTY_CHAIN_META;
5405
4920
  const metadata = decAnyMetadata(metadataRpc);
5406
4921
  const unifiedMetadata = unifyMetadata(metadata);
5407
4922
 
@@ -5421,7 +4936,6 @@ const SubNativeModule = hydrate => {
5421
4936
  };
5422
4937
  const existentialDeposit = getConstantValue("Balances", "ExistentialDeposit")?.toString();
5423
4938
  const nominationPoolsPalletId = getConstantValue("NominationPools", "PalletId")?.asText();
5424
- const crowdloanPalletId = getConstantValue("Crowdloan", "PalletId")?.asText();
5425
4939
  const hasSubtensorPallet = getConstantValue("SubtensorModule", "KeySwapCost") !== undefined;
5426
4940
 
5427
4941
  //
@@ -5441,15 +4955,10 @@ const SubNativeModule = hydrate => {
5441
4955
  }, {
5442
4956
  pallet: "Staking",
5443
4957
  items: ["Ledger"]
5444
- }, {
5445
- pallet: "Crowdloan",
5446
- items: ["Funds"]
5447
- }, {
5448
- pallet: "Paras",
5449
- items: ["Parachains"]
5450
4958
  },
5451
4959
  // TotalColdkeyStake is used until v.2.2.1, then it is replaced by StakingHotkeys+Stake
5452
4960
  // Need to keep TotalColdkeyStake for a while so chaindata keeps including it in miniMetadatas, so it doesnt break old versions of the wallet
4961
+ // TODO: Since chaindata v4 this is safe to now delete
5453
4962
  {
5454
4963
  pallet: "SubtensorModule",
5455
4964
  items: ["TotalColdkeyStake", "StakingHotkeys", "Stake"]
@@ -5468,47 +4977,40 @@ const SubNativeModule = hydrate => {
5468
4977
  }) => name === "Freezes"));
5469
4978
  const useLegacyTransferableCalculation = !hasFreezesItem;
5470
4979
  const chainMeta = {
5471
- isTestnet,
5472
- useLegacyTransferableCalculation,
5473
- symbol,
5474
- decimals,
5475
- existentialDeposit,
5476
- nominationPoolsPalletId,
5477
- crowdloanPalletId,
5478
- hasSubtensorPallet,
5479
4980
  miniMetadata,
5480
- metadataVersion
4981
+ extra: {
4982
+ useLegacyTransferableCalculation,
4983
+ existentialDeposit,
4984
+ nominationPoolsPalletId,
4985
+ hasSubtensorPallet
4986
+ }
5481
4987
  };
5482
- if (!useLegacyTransferableCalculation) delete chainMeta.useLegacyTransferableCalculation;
5483
- if (!hasSubtensorPallet) delete chainMeta.hasSubtensorPallet;
4988
+ if (!useLegacyTransferableCalculation) delete chainMeta.extra?.useLegacyTransferableCalculation;
4989
+ if (!hasSubtensorPallet) delete chainMeta.extra?.hasSubtensorPallet;
5484
4990
  return chainMeta;
5485
4991
  },
5486
4992
  async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
5487
4993
  if (moduleConfig?.disable === true) return {};
5488
4994
  const {
5489
- isTestnet,
5490
- symbol,
5491
- decimals,
4995
+ tokenSymbol: symbol,
4996
+ tokenDecimals: decimals
4997
+ } = await getChainProperties(chainConnector, chainId);
4998
+ const {
5492
4999
  existentialDeposit
5493
- } = chainMeta;
5000
+ } = chainMeta.extra ?? {};
5001
+ if (existentialDeposit === undefined) log.warn("Substrate native module: existentialDeposit is undefined for %s, using 0", chainId);
5494
5002
  const id = subNativeTokenId(chainId);
5495
5003
  const nativeToken = {
5496
5004
  id,
5497
5005
  type: "substrate-native",
5498
- isTestnet,
5499
- isDefault: moduleConfig?.isDefault ?? true,
5500
- symbol: symbol ?? DEFAULT_SYMBOL,
5501
- decimals: decimals ?? DEFAULT_DECIMALS,
5502
- logo: moduleConfig?.logo || githubTokenLogoUrl(id),
5006
+ platform: "polkadot",
5007
+ isDefault: true,
5008
+ symbol: symbol,
5009
+ name: symbol,
5010
+ decimals: decimals,
5503
5011
  existentialDeposit: existentialDeposit ?? "0",
5504
- chain: {
5505
- id: chainId
5506
- }
5012
+ networkId: chainId
5507
5013
  };
5508
- if (moduleConfig?.symbol) nativeToken.symbol = moduleConfig?.symbol;
5509
- if (moduleConfig?.coingeckoId) nativeToken.coingeckoId = moduleConfig?.coingeckoId;
5510
- if (moduleConfig?.dcentName) nativeToken.dcentName = moduleConfig?.dcentName;
5511
- if (moduleConfig?.mirrorOf) nativeToken.mirrorOf = moduleConfig?.mirrorOf;
5512
5014
  return {
5513
5015
  [nativeToken.id]: nativeToken
5514
5016
  };
@@ -5518,169 +5020,36 @@ const SubNativeModule = hydrate => {
5518
5020
  initialBalances
5519
5021
  }, callback) {
5520
5022
  assert(chainConnectors.substrate, "This module requires a substrate chain connector");
5521
-
5522
- // full record of balances for this module
5523
- const subNativeBalances = new BehaviorSubject(Object.fromEntries(initialBalances?.map(b => [getBalanceId(b), b]) ?? []));
5524
- // tokens which have a known positive balance
5525
- const positiveBalanceTokens = subNativeBalances.pipe(map(balances => Array.from(new Set(Object.values(balances).map(b => b.tokenId)))), share());
5526
-
5527
- // tokens that will be subscribed to, simply a slice of the positive balance tokens of size MAX_SUBSCRIPTION_SIZE
5528
- const subscriptionTokens = positiveBalanceTokens.pipe(map(tokens => tokens.sort(sortChains).slice(0, MAX_SUBSCRIPTION_SIZE)));
5529
-
5530
- // an initialised balance is one where we have received a response for any type of 'subsource',
5531
- // until then they are initialising. We only need to maintain one map of tokens to addresses for this
5532
- const initialisingBalances = Object.entries(addressesByToken).reduce((acc, [tokenId, addresses]) => {
5533
- acc.set(tokenId, new Set(addresses));
5534
- return acc;
5535
- }, new Map());
5536
-
5537
- // after thirty seconds, we need to kill the initialising balances
5538
- const initBalancesTimeout = setTimeout(() => {
5539
- initialisingBalances.clear();
5540
- // manually call the callback to ensure the caller gets the correct status
5541
- callback(null, {
5542
- status: "live",
5543
- data: Object.values(subNativeBalances.getValue())
5544
- });
5545
- }, 30_000);
5546
- const _callbackSub = subNativeBalances.pipe(debounceTime(100)).subscribe({
5547
- next: balances => {
5548
- callback(null, {
5549
- status: initialisingBalances.size > 0 ? "initialising" : "live",
5550
- data: Object.values(balances)
5551
- });
5552
- },
5553
- error: error => callback(error),
5554
- complete: () => {
5555
- initialisingBalances.clear();
5556
- clearTimeout(initBalancesTimeout);
5557
- }
5558
- });
5559
- const unsubDeferred = Deferred();
5560
- // we return this to the caller so that they can let us know when they're no longer interested in this subscription
5561
- const callerUnsubscribe = () => {
5562
- subNativeBalances.complete();
5563
- _callbackSub.unsubscribe();
5564
- return unsubDeferred.reject(new Error(`Caller unsubscribed`));
5023
+ const addressesByTokenByNetwork = getAddresssesByTokenByNetwork(addressesByToken);
5024
+ const initialBalancesByNetwork = groupBy$1(initialBalances ?? [], "networkId");
5025
+ const controller = new AbortController();
5026
+ const safeCallback = (error, result) => {
5027
+ if (controller.signal.aborted) return;
5028
+ if (isAbortError(error)) return;
5029
+ // typescript isnt happy with fowarding parameters as is
5030
+ return error ? callback(error, undefined) : callback(error, result);
5565
5031
  };
5566
- // we queue up our work to clean up our subscription when this promise rejects
5567
- const callerUnsubscribed = unsubDeferred.promise;
5568
-
5569
- // The update handler is to allow us to merge balances with the same id, and manage initialising and positive balances state for each
5570
- // balance type and network
5571
- const handleUpdateForSource = source => (error, result) => {
5572
- if (result) {
5573
- const currentBalances = subNativeBalances.getValue();
5574
-
5575
- // first merge any balances with the same id within the result
5576
- const accumulatedUpdates = result.filter(b => b.values.length > 0).reduce((acc, b) => {
5577
- const bId = getBalanceId(b);
5578
- acc[bId] = mergeBalances(acc[bId], b, source, false);
5579
- return acc;
5580
- }, {});
5581
-
5582
- // then merge these with the current balances
5583
- const mergedBalances = {};
5584
- Object.entries(accumulatedUpdates).forEach(([bId, b]) => {
5585
- // merge the values from the new balance into the existing balance, if there is one
5586
- mergedBalances[bId] = mergeBalances(currentBalances[bId], b, source, true);
5587
-
5588
- // update initialisingBalances to remove balances which have been updated
5589
- const intialisingForToken = initialisingBalances.get(b.tokenId);
5590
- if (intialisingForToken) {
5591
- intialisingForToken.delete(b.address);
5592
- if (intialisingForToken.size === 0) initialisingBalances.delete(b.tokenId);else initialisingBalances.set(b.tokenId, intialisingForToken);
5593
- }
5594
- });
5595
- subNativeBalances.next({
5596
- ...currentBalances,
5597
- ...mergedBalances
5598
- });
5599
- }
5600
- if (error) {
5601
- if (error instanceof SubNativeBalanceError) {
5602
- // this type of error doesn't need to be handled by the caller
5603
- initialisingBalances.delete(error.tokenId);
5604
- } else return callback(error);
5605
- }
5606
- };
5607
-
5608
- // subscribe to addresses and tokens for which we have a known positive balance
5609
- const positiveSub = subscriptionTokens.pipe(debounceTime(1000), takeUntil(callerUnsubscribed), map(tokenIds => tokenIds.reduce((acc, tokenId) => {
5610
- acc[tokenId] = addressesByToken[tokenId];
5611
- return acc;
5612
- }, {})), distinctUntilChanged(isEqual), switchMap(newAddressesByToken => {
5613
- return from(queryCache.getQueries(newAddressesByToken)).pipe(switchMap(baseQueries => {
5614
- return new Observable(subscriber => {
5615
- if (!chainConnectors.substrate) return;
5616
- const unsubSubtensorStaking = subscribeSubtensorStaking(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("subtensor-staking"));
5617
- const unsubNompoolStaking = subscribeNompoolStaking(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("nompools-staking"));
5618
- const unsubCrowdloans = subscribeCrowdloans(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("crowdloan"));
5619
- const unsubBase = subscribeBase(baseQueries, chainConnectors.substrate, handleUpdateForSource("base"));
5620
- subscriber.add(async () => (await unsubSubtensorStaking)());
5621
- subscriber.add(async () => (await unsubNompoolStaking)());
5622
- subscriber.add(async () => (await unsubCrowdloans)());
5623
- subscriber.add(async () => (await unsubBase)());
5624
- });
5625
- }));
5626
- })).subscribe();
5627
-
5628
- // for chains where we don't have a known positive balance, poll rather than subscribe
5629
- const poll = async (addressesByToken = {}) => {
5630
- const handleUpdate = handleUpdateForSource("base");
5032
+ const unsubsribeFns = Promise.all(keys(addressesByTokenByNetwork).map(async networkId => {
5631
5033
  try {
5632
- const balances = await this.fetchBalances(addressesByToken);
5633
- handleUpdate(null, Object.values(balances.toJSON()));
5634
- } catch (error) {
5635
- if (error instanceof ChainConnectionError) {
5636
- // coerce ChainConnection errors into SubNativeBalance errors
5637
- const errorChainId = error.chainId;
5638
- Object.entries(await getModuleTokens()).filter(([, token]) => token.chain?.id === errorChainId).forEach(([tokenId]) => {
5639
- const wrappedError = new SubNativeBalanceError(tokenId, error.message);
5640
- handleUpdate(wrappedError);
5641
- });
5642
- } else {
5643
- log.error("unknown substrate native balance error", error);
5644
- handleUpdate(error);
5645
- }
5034
+ // this is what we want to be done separately for each network
5035
+ // this will update the DB so minimetadata will be available when it's used, everywhere else down the tree of subscribeChainBalances
5036
+ await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType$2, controller.signal);
5037
+ } catch (err) {
5038
+ if (!isAbortError(err)) log.warn("Failed to get native token miniMetadata for network", networkId, err);
5039
+ return () => {};
5646
5040
  }
5647
- };
5648
- // do one poll to get things started
5649
- const currentBalances = subNativeBalances.getValue();
5650
- const currentTokens = new Set(Object.values(currentBalances).map(b => b.tokenId));
5651
- const nonCurrentTokens = Object.keys(addressesByToken).filter(tokenId => !currentTokens.has(tokenId)).sort(sortChains);
5652
-
5653
- // break nonCurrentTokens into chunks of POLLING_WINDOW_SIZE
5654
- await PromisePool$1.withConcurrency(POLLING_WINDOW_SIZE).for(nonCurrentTokens).process(async nonCurrentTokenId => await poll({
5655
- [nonCurrentTokenId]: addressesByToken[nonCurrentTokenId]
5041
+ if (controller.signal.aborted) return () => {};
5042
+ return subscribeChainBalances(networkId, {
5043
+ addressesByToken: addressesByTokenByNetwork[networkId] ?? {},
5044
+ initialBalances: initialBalancesByNetwork[networkId] ?? []
5045
+ }, safeCallback, controller.signal);
5656
5046
  }));
5657
-
5658
- // now poll every 30s on chains which are not subscriptionTokens
5659
- // we chunk this observable into batches of positive token ids, to prevent eating all the websocket connections
5660
- const pollingSub = interval(30_000) // emit values every 30 seconds
5661
- .pipe(takeUntil(callerUnsubscribed), withLatestFrom(subscriptionTokens),
5662
- // Combine latest value from subscriptionTokens with each interval tick
5663
- map(([, subscribedTokenIds]) =>
5664
- // Filter out tokens that are not subscribed
5665
- Object.keys(addressesByToken).filter(tokenId => !subscribedTokenIds.includes(tokenId))), exhaustMap(tokenIds => from(arrayChunk(tokenIds, POLLING_WINDOW_SIZE)).pipe(concatMap(async tokenChunk => {
5666
- // tokenChunk is a chunk of tokenIds with size POLLING_WINDOW_SIZE
5667
- const pollingTokenAddresses = Object.fromEntries(tokenChunk.map(tokenId => [tokenId, addressesByToken[tokenId]]));
5668
- await poll(pollingTokenAddresses);
5669
- return true;
5670
- })))).subscribe();
5671
5047
  return () => {
5672
- callerUnsubscribe();
5673
- positiveSub.unsubscribe();
5674
- pollingSub.unsubscribe();
5048
+ unsubsribeFns.then(fns => fns.forEach(unsubscribe => unsubscribe()));
5049
+ controller.abort();
5675
5050
  };
5676
5051
  },
5677
- async fetchBalances(addressesByToken) {
5678
- assert(chainConnectors.substrate, "This module requires a substrate chain connector");
5679
- const queries = await queryCache.getQueries(addressesByToken);
5680
- assert(chainConnectors.substrate, "This module requires a substrate chain connector");
5681
- const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
5682
- return new Balances(result ?? []);
5683
- },
5052
+ fetchBalances,
5684
5053
  async transferToken({
5685
5054
  tokenId,
5686
5055
  from,
@@ -5697,11 +5066,10 @@ const SubNativeModule = hydrate => {
5697
5066
  transferMethod,
5698
5067
  userExtensions
5699
5068
  }) {
5700
- const token = await chaindataProvider.tokenById(tokenId);
5069
+ const token = await chaindataProvider.getTokenById(tokenId, "substrate-native");
5701
5070
  assert(token, `Token ${tokenId} not found in store`);
5702
- if (token.type !== "substrate-native") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
5703
- const chainId = token.chain.id;
5704
- const chain = await chaindataProvider.chainById(chainId);
5071
+ const chainId = token.networkId;
5072
+ const chain = await chaindataProvider.getNetworkById(chainId, "polkadot");
5705
5073
  assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
5706
5074
  const {
5707
5075
  genesisHash
@@ -6895,7 +6263,10 @@ var psp22Abi = {
6895
6263
  };
6896
6264
 
6897
6265
  const moduleType$1 = "substrate-psp22";
6898
- const subPsp22TokenId = (chainId, tokenSymbol) => `${chainId}-substrate-psp22-${tokenSymbol}`.toLowerCase().replace(/ /g, "-");
6266
+ const SubPsp22TokenConfigSchema = z.strictObject({
6267
+ contractAddress: SubPsp22TokenSchema.shape.contractAddress,
6268
+ ...TokenConfigBaseSchema.shape
6269
+ });
6899
6270
  const SubPsp22Module = hydrate => {
6900
6271
  const {
6901
6272
  chainConnectors,
@@ -6905,16 +6276,15 @@ const SubPsp22Module = hydrate => {
6905
6276
  assert(chainConnector, "This module requires a substrate chain connector");
6906
6277
  return {
6907
6278
  ...DefaultBalanceModule(moduleType$1),
6908
- async fetchSubstrateChainMeta(chainId) {
6909
- const isTestnet = (await chaindataProvider.chainById(chainId))?.isTestnet || false;
6279
+ async fetchSubstrateChainMeta(_chainId) {
6280
+ // we dont need anything
6910
6281
  return {
6911
- isTestnet
6282
+ miniMetadata: null,
6283
+ extra: null
6912
6284
  };
6913
6285
  },
6914
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
6915
- const {
6916
- isTestnet
6917
- } = chainMeta;
6286
+ async fetchSubstrateChainTokens(chainId, _chainMeta, moduleConfig, tokens) {
6287
+ if (!tokens?.length) return {};
6918
6288
  const registry = new TypeRegistry();
6919
6289
  const Psp22Abi = new Abi(psp22Abi);
6920
6290
 
@@ -6924,12 +6294,11 @@ const SubPsp22Module = hydrate => {
6924
6294
  chainId,
6925
6295
  registry
6926
6296
  });
6927
- const tokens = {};
6928
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
6297
+ const tokenList = {};
6298
+ for (const tokenConfig of tokens ?? []) {
6929
6299
  try {
6930
6300
  let symbol = tokenConfig?.symbol ?? "Unit";
6931
6301
  let decimals = tokenConfig?.decimals ?? 0;
6932
- const existentialDeposit = tokenConfig?.ed ?? "0";
6933
6302
  const contractAddress = tokenConfig?.contractAddress ?? undefined;
6934
6303
  if (contractAddress === undefined) continue;
6935
6304
  await (async () => {
@@ -6945,35 +6314,28 @@ const SubPsp22Module = hydrate => {
6945
6314
  const decimalsData = decimalsResult.toJSON()?.result?.ok?.data;
6946
6315
  decimals = typeof decimalsData === "string" && decimalsData.startsWith("0x") ? hexToNumber(decimalsData) : decimals;
6947
6316
  })();
6948
- const id = subPsp22TokenId(chainId, symbol);
6317
+ const id = subPsp22TokenId(chainId, contractAddress);
6949
6318
  const token = {
6950
6319
  id,
6951
6320
  type: "substrate-psp22",
6952
- isTestnet,
6321
+ platform: "polkadot",
6953
6322
  isDefault: tokenConfig.isDefault ?? true,
6954
6323
  symbol,
6955
6324
  decimals,
6956
- logo: tokenConfig?.logo || githubTokenLogoUrl(id),
6957
- existentialDeposit,
6325
+ name: tokenConfig?.name || symbol,
6326
+ logo: tokenConfig?.logo,
6958
6327
  contractAddress,
6959
- chain: {
6960
- id: chainId
6961
- }
6328
+ networkId: chainId
6962
6329
  };
6963
- if (tokenConfig?.symbol) {
6964
- token.symbol = tokenConfig?.symbol;
6965
- token.id = subPsp22TokenId(chainId, token.symbol);
6966
- }
6967
6330
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
6968
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
6969
6331
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
6970
- tokens[token.id] = token;
6332
+ tokenList[token.id] = token;
6971
6333
  } catch (error) {
6972
6334
  log.error(`Failed to build substrate-psp22 token ${tokenConfig.contractAddress} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
6973
6335
  continue;
6974
6336
  }
6975
6337
  }
6976
- return tokens;
6338
+ return tokenList;
6977
6339
  },
6978
6340
  // TODO: Don't create empty subscriptions
6979
6341
  async subscribeBalances({
@@ -6983,7 +6345,7 @@ const SubPsp22Module = hydrate => {
6983
6345
  const subscriptionInterval = 12_000; // 12_000ms == 12 seconds
6984
6346
  const initDelay = 3_000; // 3000ms == 3 seconds
6985
6347
  const cache = new Map();
6986
- const tokens = await chaindataProvider.tokensById();
6348
+ const tokens = await chaindataProvider.getTokensMapById();
6987
6349
  const poll = async () => {
6988
6350
  if (!subscriptionActive) return;
6989
6351
  try {
@@ -7010,7 +6372,7 @@ const SubPsp22Module = hydrate => {
7010
6372
  },
7011
6373
  async fetchBalances(addressesByToken) {
7012
6374
  assert(chainConnectors.substrate, "This module requires a substrate chain connector");
7013
- const tokens = await chaindataProvider.tokensById();
6375
+ const tokens = await chaindataProvider.getTokensMapById();
7014
6376
  return fetchBalances(chainConnectors.substrate, tokens, addressesByToken);
7015
6377
  },
7016
6378
  async transferToken({
@@ -7028,11 +6390,11 @@ const SubPsp22Module = hydrate => {
7028
6390
  tip,
7029
6391
  userExtensions
7030
6392
  }) {
7031
- const token = await chaindataProvider.tokenById(tokenId);
6393
+ const token = await chaindataProvider.getTokenById(tokenId, "substrate-psp22");
7032
6394
  assert(token, `Token ${tokenId} not found in store`);
7033
6395
  if (token.type !== "substrate-psp22") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
7034
- const chainId = token.chain.id;
7035
- const chain = await chaindataProvider.chainById(chainId);
6396
+ const chainId = token.networkId;
6397
+ const chain = await chaindataProvider.getNetworkById(chainId, "polkadot");
7036
6398
  assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
7037
6399
  const {
7038
6400
  genesisHash
@@ -7108,7 +6470,7 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
7108
6470
  // TODO: Use `decodeOutput` from `./util/decodeOutput`
7109
6471
  const contractCall = makeContractCaller({
7110
6472
  chainConnector,
7111
- chainId: token.chain.id,
6473
+ chainId: token.networkId,
7112
6474
  registry
7113
6475
  });
7114
6476
  if (token.contractAddress === undefined) {
@@ -7125,10 +6487,7 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
7125
6487
  source: "substrate-psp22",
7126
6488
  status: "live",
7127
6489
  address,
7128
- multiChainId: {
7129
- subChainId: token.chain.id
7130
- },
7131
- chainId: token.chain.id,
6490
+ networkId: token.networkId,
7132
6491
  tokenId,
7133
6492
  value: balance
7134
6493
  };
@@ -7151,8 +6510,16 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
7151
6510
  };
7152
6511
 
7153
6512
  const moduleType = "substrate-tokens";
6513
+ const SubTokensTokenConfigSchema = z.strictObject({
6514
+ onChainId: SubTokensTokenSchema.shape.onChainId,
6515
+ ...TokenConfigBaseSchema.shape,
6516
+ existentialDeposit: SubTokensTokenSchema.shape.existentialDeposit.optional()
6517
+ });
7154
6518
  const defaultPalletId = "Tokens";
7155
- const subTokensTokenId = (chainId, onChainId) => `${chainId}-substrate-tokens-${compressToEncodedURIComponent(String(onChainId))}`;
6519
+ const UNSUPPORTED_CHAIN_META = {
6520
+ miniMetadata: null,
6521
+ extra: {}
6522
+ };
7156
6523
  const SubTokensModule = hydrate => {
7157
6524
  const {
7158
6525
  chainConnectors,
@@ -7163,14 +6530,7 @@ const SubTokensModule = hydrate => {
7163
6530
  return {
7164
6531
  ...DefaultBalanceModule(moduleType),
7165
6532
  async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
7166
- const isTestnet = (await chaindataProvider.chainById(chainId))?.isTestnet || false;
7167
- if (metadataRpc === undefined) return {
7168
- isTestnet
7169
- };
7170
- if ((moduleConfig?.tokens ?? []).length < 1) return {
7171
- isTestnet
7172
- };
7173
- const metadataVersion = getMetadataVersion(metadataRpc);
6533
+ if (metadataRpc === undefined) return UNSUPPORTED_CHAIN_META;
7174
6534
  const metadata = decAnyMetadata(metadataRpc);
7175
6535
  const palletId = moduleConfig?.palletId ?? defaultPalletId;
7176
6536
  compactMetadata(metadata, [{
@@ -7178,74 +6538,83 @@ const SubTokensModule = hydrate => {
7178
6538
  items: ["Accounts"]
7179
6539
  }]);
7180
6540
  const miniMetadata = encodeMetadata(metadata);
7181
- return palletId === defaultPalletId ? {
7182
- isTestnet,
7183
- miniMetadata,
7184
- metadataVersion
7185
- } : {
7186
- isTestnet,
7187
- palletId,
6541
+ return {
7188
6542
  miniMetadata,
7189
- metadataVersion
6543
+ extra: {
6544
+ palletId
6545
+ }
7190
6546
  };
7191
6547
  },
7192
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
7193
- const {
7194
- isTestnet
7195
- } = chainMeta;
7196
- const tokens = {};
7197
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
6548
+ async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
6549
+ const tokenList = {};
6550
+ for (const tokenConfig of tokens ?? []) {
7198
6551
  try {
6552
+ // TODO fetch metadata from chain, like we do for assets
7199
6553
  const symbol = tokenConfig?.symbol ?? "Unit";
7200
6554
  const decimals = tokenConfig?.decimals ?? 0;
7201
- const existentialDeposit = tokenConfig?.ed ?? "0";
6555
+ const existentialDeposit = tokenConfig?.existentialDeposit ?? "0";
7202
6556
  const onChainId = tokenConfig?.onChainId ?? undefined;
7203
6557
  if (onChainId === undefined) continue;
7204
6558
  const id = subTokensTokenId(chainId, onChainId);
7205
6559
  const token = {
7206
6560
  id,
7207
6561
  type: "substrate-tokens",
7208
- isTestnet,
6562
+ platform: "polkadot",
7209
6563
  isDefault: tokenConfig.isDefault ?? true,
7210
6564
  symbol,
7211
6565
  decimals,
7212
- logo: tokenConfig?.logo || githubTokenLogoUrl(id),
6566
+ name: tokenConfig?.name ?? symbol,
6567
+ logo: tokenConfig?.logo,
7213
6568
  existentialDeposit,
7214
6569
  onChainId,
7215
- chain: {
7216
- id: chainId
7217
- }
6570
+ networkId: chainId
7218
6571
  };
7219
- if (tokenConfig?.symbol) {
7220
- token.symbol = tokenConfig?.symbol;
7221
- token.id = subTokensTokenId(chainId, token.onChainId);
7222
- }
7223
6572
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
7224
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
7225
6573
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
7226
- tokens[token.id] = token;
6574
+ tokenList[token.id] = token;
7227
6575
  } catch (error) {
7228
6576
  log.error(`Failed to build substrate-tokens token ${tokenConfig.onChainId} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
7229
6577
  continue;
7230
6578
  }
7231
6579
  }
7232
- return tokens;
6580
+ return tokenList;
7233
6581
  },
7234
6582
  // TODO: Don't create empty subscriptions
7235
6583
  async subscribeBalances({
7236
6584
  addressesByToken
7237
6585
  }, callback) {
7238
- const queries = await buildQueries(chaindataProvider, addressesByToken);
7239
- const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
7240
- if (error) return callback(error);
7241
- const balances = result?.filter(b => b !== null) ?? [];
7242
- if (balances.length > 0) callback(null, new Balances(balances));
7243
- });
7244
- return unsubscribe;
6586
+ const byNetwork = keys(addressesByToken).reduce((acc, tokenId) => {
6587
+ const networkId = parseSubTokensTokenId(tokenId).networkId;
6588
+ if (!acc[networkId]) acc[networkId] = {};
6589
+ acc[networkId][tokenId] = addressesByToken[tokenId];
6590
+ return acc;
6591
+ }, {});
6592
+ const controller = new AbortController();
6593
+ const pUnsubs = Promise.all(toPairs(byNetwork).map(async ([networkId, addressesByToken]) => {
6594
+ try {
6595
+ const queries = await buildNetworkQueries(networkId, chainConnector, chaindataProvider, addressesByToken, controller.signal);
6596
+ if (controller.signal.aborted) return () => {};
6597
+ const stateHelper = new RpcStateQueryHelper(chainConnector, queries);
6598
+ return await stateHelper.subscribe((error, result) => {
6599
+ if (error) return callback(error);
6600
+ const balances = result?.filter(b => b !== null) ?? [];
6601
+ if (balances.length > 0) callback(null, new Balances(balances));
6602
+ });
6603
+ } catch (err) {
6604
+ if (!isAbortError(err)) log.error(`Failed to subscribe balances for network ${networkId}`, err);
6605
+ return () => {};
6606
+ }
6607
+ }));
6608
+ return () => {
6609
+ pUnsubs.then(unsubs => {
6610
+ unsubs.forEach(unsubscribe => unsubscribe());
6611
+ });
6612
+ controller.abort();
6613
+ };
7245
6614
  },
7246
6615
  async fetchBalances(addressesByToken) {
7247
6616
  assert(chainConnectors.substrate, "This module requires a substrate chain connector");
7248
- const queries = await buildQueries(chaindataProvider, addressesByToken);
6617
+ const queries = await buildQueries(chainConnector, chaindataProvider, addressesByToken);
7249
6618
  const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
7250
6619
  const balances = result?.filter(b => b !== null) ?? [];
7251
6620
  return new Balances(balances);
@@ -7257,15 +6626,13 @@ const SubTokensModule = hydrate => {
7257
6626
  transferMethod,
7258
6627
  metadataRpc
7259
6628
  }) {
7260
- const token = await chaindataProvider.tokenById(tokenId);
6629
+ const token = await chaindataProvider.getTokenById(tokenId, "substrate-tokens");
7261
6630
  assert(token, `Token ${tokenId} not found in store`);
7262
- if (token.type !== "substrate-tokens") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
7263
- const chainId = token.chain.id;
7264
- const chain = await chaindataProvider.chainById(chainId);
6631
+ const chainId = token.networkId;
6632
+ const chain = await chaindataProvider.getNetworkById(chainId, "polkadot");
7265
6633
  assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
7266
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
7267
- const [chainMeta] = findChainMeta(miniMetadatas, moduleType, chain);
7268
- const tokensPallet = chainMeta?.palletId ?? defaultPalletId;
6634
+ const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, chainId, moduleType);
6635
+ const tokensPallet = miniMetadata?.extra?.palletId ?? defaultPalletId;
7269
6636
  const onChainId = (() => {
7270
6637
  try {
7271
6638
  return papiParse(token.onChainId);
@@ -7364,23 +6731,15 @@ const SubTokensModule = hydrate => {
7364
6731
  }
7365
6732
  };
7366
6733
  };
7367
- async function buildQueries(chaindataProvider, addressesByToken) {
7368
- const allChains = await chaindataProvider.chainsById();
7369
- const tokens = await chaindataProvider.tokensById();
7370
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
7371
- const tokensPalletByChain = new Map(Object.values(allChains).map(chain => [chain.id, findChainMeta(miniMetadatas, moduleType, chain)[0]?.palletId]));
7372
- const uniqueChainIds = getUniqueChainIds(addressesByToken, tokens);
7373
- const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
7374
- const chainStorageCoders = buildStorageCoders({
7375
- chainIds: uniqueChainIds,
7376
- chains,
7377
- miniMetadatas,
7378
- moduleType: "substrate-tokens",
7379
- coders: {
7380
- storage: ({
7381
- chainId
7382
- }) => [tokensPalletByChain.get(chainId) ?? defaultPalletId, "Accounts"]
7383
- }
6734
+ async function buildNetworkQueries(networkId, chainConnector, chaindataProvider, addressesByToken, signal) {
6735
+ const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType, signal);
6736
+ const chain = await chaindataProvider.getNetworkById(networkId, "polkadot");
6737
+ const tokens = await chaindataProvider.getTokensMapById();
6738
+ if (!chain) return [];
6739
+ signal?.throwIfAborted();
6740
+ const palletId = miniMetadata.extra.palletId ?? defaultPalletId;
6741
+ const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
6742
+ storage: [palletId, "Accounts"]
7384
6743
  });
7385
6744
  return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
7386
6745
  const token = tokens[tokenId];
@@ -7392,18 +6751,8 @@ async function buildQueries(chaindataProvider, addressesByToken) {
7392
6751
  log.debug(`This module doesn't handle tokens of type ${token.type}`);
7393
6752
  return [];
7394
6753
  }
7395
- const chainId = token.chain?.id;
7396
- if (!chainId) {
7397
- log.warn(`Token ${tokenId} has no chain`);
7398
- return [];
7399
- }
7400
- const chain = chains[chainId];
7401
- if (!chain) {
7402
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
7403
- return [];
7404
- }
7405
6754
  return addresses.flatMap(address => {
7406
- const scaleCoder = chainStorageCoders.get(chainId)?.storage;
6755
+ const scaleCoder = networkStorageCoders?.storage;
7407
6756
  const onChainId = (() => {
7408
6757
  try {
7409
6758
  return papiParse(token.onChainId);
@@ -7411,12 +6760,12 @@ async function buildQueries(chaindataProvider, addressesByToken) {
7411
6760
  return token.onChainId;
7412
6761
  }
7413
6762
  })();
7414
- const stateKey = encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${chainId} storage query ${address} / ${token.onChainId}`, address, onChainId);
6763
+ const stateKey = encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${networkId} storage query ${address} / ${token.onChainId}`, address, onChainId);
7415
6764
  if (!stateKey) return [];
7416
6765
  const decodeResult = change => {
7417
6766
  /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
7418
6767
 
7419
- const decoded = decodeScale(scaleCoder, change, `Failed to decode substrate-tokens balance on chain ${chainId}`) ?? {
6768
+ const decoded = decodeScale(scaleCoder, change, `Failed to decode substrate-tokens balance on chain ${networkId}`) ?? {
7420
6769
  free: 0n,
7421
6770
  reserved: 0n,
7422
6771
  frozen: 0n
@@ -7441,49 +6790,31 @@ async function buildQueries(chaindataProvider, addressesByToken) {
7441
6790
  source: "substrate-tokens",
7442
6791
  status: "live",
7443
6792
  address,
7444
- multiChainId: {
7445
- subChainId: chainId
7446
- },
7447
- chainId,
6793
+ networkId,
7448
6794
  tokenId: token.id,
7449
6795
  values: balanceValues
7450
6796
  };
7451
6797
  };
7452
6798
  return {
7453
- chainId,
6799
+ chainId: networkId,
7454
6800
  stateKey,
7455
6801
  decodeResult
7456
6802
  };
7457
6803
  });
7458
6804
  });
7459
6805
  }
6806
+ async function buildQueries(chainConnector, chaindataProvider, addressesByToken, signal) {
6807
+ const byNetwork = keys(addressesByToken).reduce((acc, tokenId) => {
6808
+ const networkId = parseSubTokensTokenId(tokenId).networkId;
6809
+ if (!acc[networkId]) acc[networkId] = {};
6810
+ acc[networkId][tokenId] = addressesByToken[tokenId];
6811
+ return acc;
6812
+ }, {});
6813
+ return (await Promise.all(toPairs(byNetwork).map(([networkId, addressesByToken]) => {
6814
+ return buildNetworkQueries(networkId, chainConnector, chaindataProvider, addressesByToken, signal);
6815
+ }))).flat();
6816
+ }
7460
6817
 
7461
- const defaultBalanceModules = [EvmErc20Module, EvmNativeModule, EvmUniswapV2Module, SubAssetsModule, SubEquilibriumModule, SubForeignAssetsModule, SubNativeModule, SubPsp22Module, SubTokensModule];
7462
-
7463
- /** Pulls the latest chaindata from https://github.com/TalismanSociety/chaindata */
7464
- const hydrateChaindataAndMiniMetadata = async (chaindataProvider, miniMetadataUpdater) => {
7465
- // need chains to be provisioned first, or substrate balances won't fetch on first subscription
7466
- await chaindataProvider.hydrateChains();
7467
- await Promise.all([miniMetadataUpdater.hydrateFromChaindata(), miniMetadataUpdater.hydrateCustomChains()]);
7468
- const chains = await chaindataProvider.chains();
7469
- const {
7470
- statusesByChain
7471
- } = await miniMetadataUpdater.statuses(chains);
7472
- const goodChains = [...statusesByChain.entries()].flatMap(([chainId, status]) => status === "good" ? chainId : []);
7473
- await chaindataProvider.hydrateSubstrateTokens(goodChains);
7474
- };
7475
-
7476
- /** Builds any missing miniMetadatas (e.g. for the user's custom substrate chains) */
7477
- const updateCustomMiniMetadata = async (chaindataProvider, miniMetadataUpdater) => {
7478
- const chainIds = await chaindataProvider.chainIds();
7479
- await miniMetadataUpdater.update(chainIds);
7480
- };
7481
-
7482
- /** Fetches any missing Evm Tokens */
7483
- const updateEvmTokens = async (chaindataProvider, evmTokenFetcher) => {
7484
- await chaindataProvider.hydrateEvmNetworks();
7485
- const evmNetworkIds = await chaindataProvider.evmNetworkIds();
7486
- await evmTokenFetcher.update(evmNetworkIds);
7487
- };
6818
+ const defaultBalanceModules = [EvmErc20Module, EvmNativeModule, EvmUniswapV2Module, SubAssetsModule, SubForeignAssetsModule, SubNativeModule, SubPsp22Module, SubTokensModule];
7488
6819
 
7489
- export { Balance, BalanceFormatter, BalanceValueGetter, Balances, Change24hCurrencyFormatter, DefaultBalanceModule, EvmErc20Module, EvmNativeModule, EvmTokenFetcher, EvmUniswapV2Module, FiatSumBalancesFormatter, MiniMetadataUpdater, ONE_ALPHA_TOKEN, PlanckSumBalancesFormatter, RpcStateQueryHelper, SCALE_FACTOR, SUBTENSOR_MIN_STAKE_AMOUNT_PLANK, SUBTENSOR_ROOT_NETUID, SubAssetsModule, SubEquilibriumModule, SubForeignAssetsModule, SubNativeModule, SubPsp22Module, SubTokensModule, SumBalancesFormatter, TalismanBalancesDatabase, abiMulticall, balances, buildStorageCoders, calculateAlphaPrice, calculateTaoAmountFromAlpha, calculateTaoFromDynamicInfo, compress, configureStore, db, decodeOutput, decompress, defaultBalanceModules, deriveMiniMetadataId, detectTransferMethod, erc20Abi, erc20BalancesAggregatorAbi, evmErc20TokenId, evmNativeTokenId, evmUniswapV2TokenId, excludeFromFeePayableLocks, excludeFromTransferableAmount, filterBaseLocks, filterMirrorTokens, findChainMeta, getBalanceId, getLockTitle, getUniqueChainIds, getValueId, hydrateChaindataAndMiniMetadata, includeInTotalExtraAmount, makeContractCaller, subAssetTokenId, subEquilibriumTokenId, subForeignAssetTokenId, subNativeTokenId, subPsp22TokenId, subTokensTokenId, uniswapV2PairAbi, updateCustomMiniMetadata, updateEvmTokens };
6820
+ export { Balance, BalanceFormatter, BalanceValueGetter, Balances, Change24hCurrencyFormatter, DefaultBalanceModule, EvmErc20Module, EvmErc20TokenConfigSchema, EvmNativeModule, EvmNativeTokenConfigSchema, EvmUniswapV2Module, EvmUniswapV2TokenConfigSchema, FiatSumBalancesFormatter, ONE_ALPHA_TOKEN, PlanckSumBalancesFormatter, RpcStateQueryHelper, SCALE_FACTOR, SUBTENSOR_MIN_STAKE_AMOUNT_PLANK, SUBTENSOR_ROOT_NETUID, SubAssetsModule, SubAssetsTokenConfigSchema, SubForeignAssetsModule, SubForeignAssetsTokenConfigSchema, SubNativeModule, SubNativeTokenConfigSchema, SubPsp22Module, SubPsp22TokenConfigSchema, SubTokensModule, SubTokensTokenConfigSchema, SumBalancesFormatter, TalismanBalancesDatabase, abiMulticall, balances, buildNetworkStorageCoders, buildStorageCoders, calculateAlphaPrice, calculateTaoAmountFromAlpha, calculateTaoFromDynamicInfo, compress, configureStore, db, decodeOutput, decompress, defaultBalanceModules, deriveMiniMetadataId, detectTransferMethod, erc20Abi, erc20BalancesAggregatorAbi, excludeFromFeePayableLocks, excludeFromTransferableAmount, filterBaseLocks, filterMirrorTokens, getBalanceId, getLockTitle, getUniqueChainIds, getValueId, includeInTotalExtraAmount, makeContractCaller, uniswapV2PairAbi };