@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,40 +1,42 @@
1
1
  'use strict';
2
2
 
3
- var PromisePool = require('@supercharge/promise-pool');
4
- var chaindataProvider = require('@talismn/chaindata-provider');
5
- var sapi = require('@talismn/sapi');
6
3
  var dexie = require('dexie');
7
- var isEqual = require('lodash/isEqual');
8
- var rxjs = require('rxjs');
9
4
  var anylogger = require('anylogger');
5
+ var chaindataProvider = require('@talismn/chaindata-provider');
10
6
  var tokenRates = require('@talismn/token-rates');
11
7
  var util = require('@talismn/util');
12
8
  var BigNumber = require('bignumber.js');
13
9
  var util$1 = require('@polkadot/util');
14
10
  var utilCrypto = require('@polkadot/util-crypto');
15
11
  var pako = require('pako');
12
+ var z = require('zod/v4');
16
13
  var viem = require('viem');
14
+ var lodash = require('lodash');
15
+ var isEqual = require('lodash/isEqual');
17
16
  var txwrapperCore = require('@substrate/txwrapper-core');
18
17
  var scale = require('@talismn/scale');
19
18
  var camelCase = require('lodash/camelCase');
19
+ var PQueue = require('p-queue');
20
+ var sapi = require('@talismn/sapi');
20
21
  var types = require('@polkadot/types');
21
22
  var groupBy = require('lodash/groupBy');
22
23
  var utils = require('@polkadot-api/utils');
23
24
  var polkadotApi = require('polkadot-api');
24
25
  var chainConnector = require('@talismn/chain-connector');
26
+ var rxjs = require('rxjs');
25
27
  var scaleTs = require('scale-ts');
26
28
  var upperFirst = require('lodash/upperFirst');
27
29
  var apiContract = require('@polkadot/api-contract');
28
- var lzString = require('lz-string');
29
30
 
30
31
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
31
32
 
32
- var PromisePool__default = /*#__PURE__*/_interopDefault(PromisePool);
33
- var isEqual__default = /*#__PURE__*/_interopDefault(isEqual);
34
33
  var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
35
34
  var BigNumber__default = /*#__PURE__*/_interopDefault(BigNumber);
36
35
  var pako__default = /*#__PURE__*/_interopDefault(pako);
36
+ var z__default = /*#__PURE__*/_interopDefault(z);
37
+ var isEqual__default = /*#__PURE__*/_interopDefault(isEqual);
37
38
  var camelCase__default = /*#__PURE__*/_interopDefault(camelCase);
39
+ var PQueue__default = /*#__PURE__*/_interopDefault(PQueue);
38
40
  var groupBy__default = /*#__PURE__*/_interopDefault(groupBy);
39
41
  var upperFirst__default = /*#__PURE__*/_interopDefault(upperFirst);
40
42
 
@@ -71,48 +73,11 @@ const DefaultBalanceModule = type => ({
71
73
  // internal
72
74
  //
73
75
 
74
- /**
75
- * Fetches tokens for EVM networks.
76
- */
77
- class EvmTokenFetcher {
78
- #chaindataProvider;
79
- #balanceModules;
80
- constructor(chaindataProvider, balanceModules) {
81
- this.#chaindataProvider = chaindataProvider;
82
- this.#balanceModules = balanceModules;
83
- }
84
- async update(evmNetworkIds) {
85
- await this.updateEvmNetworks(evmNetworkIds);
86
- }
87
- async updateEvmNetworks(evmNetworkIds) {
88
- const evmNetworks = new Map((await this.#chaindataProvider.evmNetworks()).map(evmNetwork => [evmNetwork.id, evmNetwork]));
89
- const allEvmTokens = {};
90
- const evmNetworkConcurrency = 10;
91
- await PromisePool.PromisePool.withConcurrency(evmNetworkConcurrency).for(evmNetworkIds).process(async evmNetworkId => {
92
- const evmNetwork = evmNetworks.get(evmNetworkId);
93
- if (!evmNetwork) return;
94
- for (const mod of this.#balanceModules.filter(m => m.type.startsWith("evm-"))) {
95
- const balancesConfig = (evmNetwork.balancesConfig ?? []).find(({
96
- moduleType
97
- }) => moduleType === mod.type);
98
- const moduleConfig = balancesConfig?.moduleConfig ?? {};
99
-
100
- // chainMeta arg only needs the isTestnet property, let's save a db roundtrip for now
101
- const isTestnet = evmNetwork.isTestnet ?? false;
102
- const tokens = await mod.fetchEvmChainTokens(evmNetworkId, {
103
- isTestnet
104
- }, moduleConfig);
105
- for (const [tokenId, token] of Object.entries(tokens)) allEvmTokens[tokenId] = token;
106
- }
107
- });
108
- await this.#chaindataProvider.updateEvmNetworkTokens(Object.values(allEvmTokens));
109
- }
110
- }
111
-
112
- var packageJson = {
113
- name: "@talismn/balances"};
76
+ var pkg = {
77
+ name: "@talismn/balances",
78
+ version: "0.0.0-pr2075-20250703111149"};
114
79
 
115
- var log = anylogger__default.default(packageJson.name);
80
+ var log = anylogger__default.default(pkg.name);
116
81
 
117
82
  function excludeFromTransferableAmount(locks) {
118
83
  if (typeof locks === "string") return BigInt(locks);
@@ -322,15 +287,13 @@ class Balances {
322
287
  return new SumBalancesFormatter(this);
323
288
  }
324
289
  }
325
- const isBalanceEvm = balance => "evmNetworkId" in balance;
326
290
  const getBalanceId = balance => {
327
291
  const {
328
292
  source,
329
293
  address,
330
294
  tokenId
331
295
  } = balance;
332
- const locationId = isBalanceEvm(balance) ? balance.evmNetworkId : balance.chainId;
333
- return [source, address, locationId, tokenId].filter(util.isTruthy).join("::");
296
+ return [source, address, tokenId].join("::");
334
297
  };
335
298
 
336
299
  /**
@@ -392,23 +355,17 @@ class Balance {
392
355
  get address() {
393
356
  return this.#storage.address;
394
357
  }
395
- get chainId() {
396
- return isBalanceEvm(this.#storage) ? undefined : this.#storage.chainId;
397
- }
398
- get chain() {
399
- return this.#db?.chains && this.chainId && this.#db?.chains[this.chainId] || null;
358
+ get networkId() {
359
+ return this.#storage.networkId;
400
360
  }
401
- get evmNetworkId() {
402
- return isBalanceEvm(this.#storage) ? this.#storage.evmNetworkId : undefined;
403
- }
404
- get evmNetwork() {
405
- return this.#db?.evmNetworks && this.evmNetworkId && this.#db?.evmNetworks[this.evmNetworkId] || null;
361
+ get network() {
362
+ return this.#db?.networks?.[this.networkId] || null;
406
363
  }
407
364
  get tokenId() {
408
365
  return this.#storage.tokenId;
409
366
  }
410
367
  get token() {
411
- return this.#db?.tokens && this.#db?.tokens[this.tokenId] || null;
368
+ return this.#db?.tokens?.[this.tokenId] || null;
412
369
  }
413
370
  get decimals() {
414
371
  return this.token?.decimals || null;
@@ -421,9 +378,9 @@ class Balance {
421
378
  //
422
379
  // This means that those rates are always available for calculating the uniswapv2 rates,
423
380
  // regardless of whether or not the underlying erc20s are actually in chaindata and enabled.
424
- if (this.isSource("evm-uniswapv2") && this.token?.type === "evm-uniswapv2" && this.evmNetworkId) {
425
- const tokenId0 = evmErc20TokenId$1(this.evmNetworkId, this.token.tokenAddress0);
426
- const tokenId1 = evmErc20TokenId$1(this.evmNetworkId, this.token.tokenAddress1);
381
+ if (this.isSource("evm-uniswapv2") && this.token?.type === "evm-uniswapv2") {
382
+ const tokenId0 = chaindataProvider.evmErc20TokenId(this.networkId, this.token.tokenAddress0);
383
+ const tokenId1 = chaindataProvider.evmErc20TokenId(this.networkId, this.token.tokenAddress1);
427
384
  const decimals = this.token.decimals;
428
385
  const decimals0 = this.token.decimals0;
429
386
  const decimals1 = this.token.decimals1;
@@ -502,9 +459,7 @@ class Balance {
502
459
  const nomPoolStakedPlancks = this.locks.some(lock => lock.source === "substrate-native-holds" && lock.label === "DelegatedStaking") ? 0n : this.nompools.map(({
503
460
  amount
504
461
  }) => amount.planck).reduce((a, b) => a + b, 0n);
505
- return this.#format(this.free.planck + this.reserved.planck + nomPoolStakedPlancks + this.crowdloans.map(({
506
- amount
507
- }) => amount.planck).reduce((a, b) => a + b, 0n) + this.subtensor.map(({
462
+ return this.#format(this.free.planck + this.reserved.planck + nomPoolStakedPlancks + this.subtensor.map(({
508
463
  amount
509
464
  }) => amount.planck).reduce((a, b) => a + b, 0n) + includeInTotalExtraAmount(extra));
510
465
  }
@@ -540,9 +495,6 @@ class Balance {
540
495
  get locks() {
541
496
  return this.getValue("locked");
542
497
  }
543
- get crowdloans() {
544
- return this.getValue("crowdloan");
545
- }
546
498
  get nompools() {
547
499
  return this.getValue("nompool");
548
500
  }
@@ -621,7 +573,7 @@ class Balance {
621
573
  const nomPoolStakedPlancks = this.locks.some(lock => lock.source === "substrate-native-holds" && lock.label === "DelegatedStaking") ? 0n : this.nompools.map(({
622
574
  amount
623
575
  }) => amount.planck).reduce((a, b) => a + b, 0n);
624
- const otherUnavailable = nomPoolStakedPlancks + this.crowdloans.reduce((total, each) => total + each.amount.planck, 0n) + this.subtensor.reduce((total, each) => total + each.amount.planck, 0n);
576
+ const otherUnavailable = nomPoolStakedPlancks + this.subtensor.reduce((total, each) => total + each.amount.planck, 0n);
625
577
  return this.#format(baseUnavailable + otherUnavailable);
626
578
  }
627
579
 
@@ -856,10 +808,6 @@ const filterMirrorTokens = (balance, i, balances) => {
856
808
  return !mirrorOf || !balances.find(b => b.tokenId === mirrorOf);
857
809
  };
858
810
 
859
- // TODO: Move this into a common module which can then be imported both here and into EvmErc20Module
860
- // We can't import this directly from EvmErc20Module because then we'd have a circular dependency
861
- const evmErc20TokenId$1 = (chainId, tokenContractAddress) => `${chainId}-evm-erc20-${tokenContractAddress}`.toLowerCase();
862
-
863
811
  /**
864
812
  * `BalanceTypes` is an automatically determined sub-selection of `PluginBalanceTypes`.
865
813
  *
@@ -890,7 +838,6 @@ const getValueId = amount => {
890
838
  const getMetaId = () => {
891
839
  const meta = amount.meta;
892
840
  if (!meta) return "";
893
- if (amount.type === "crowdloan") return meta.paraId?.toString() ?? "";
894
841
  if (amount.type === "nompool") return meta.poolId?.toString() ?? "";
895
842
  if (amount.type === "subtensor") {
896
843
  const {
@@ -914,10 +861,9 @@ const getValueId = amount => {
914
861
  const deriveMiniMetadataId = ({
915
862
  source,
916
863
  chainId,
917
- specName,
918
864
  specVersion,
919
- balancesConfig
920
- }) => util$1.u8aToHex(utilCrypto.xxhashAsU8a(new TextEncoder().encode(`${source}${chainId}${specName}${specVersion}${balancesConfig}`), 64), undefined, false);
865
+ libVersion
866
+ }) => util$1.u8aToHex(utilCrypto.xxhashAsU8a(new TextEncoder().encode(`${source}${chainId}${specVersion}${libVersion}`), 64), undefined, false);
921
867
 
922
868
  // for DB version 3, Wallet version 1.21.0
923
869
  const upgradeRemoveSymbolFromNativeTokenId = async tx => {
@@ -1012,230 +958,9 @@ class TalismanBalancesDatabase extends dexie.Dexie {
1012
958
  }
1013
959
  const db = new TalismanBalancesDatabase();
1014
960
 
1015
- const minimumHydrationInterval = 300_000; // 300_000ms = 300s = 5 minutes
1016
-
1017
- /**
1018
- * A substrate dapp needs access to a set of types when it wants to communicate with a blockchain node.
1019
- *
1020
- * These types are used to encode requests & decode responses via the SCALE codec.
1021
- * Each chain generally has its own set of types.
1022
- *
1023
- * Substrate provides a construct to retrieve these types from a blockchain node.
1024
- * The chain metadata.
1025
- *
1026
- * The metadata includes the types required for any communication with the chain,
1027
- * including lots of methods which are not relevant to balance fetching.
1028
- *
1029
- * As such, the metadata can clock in at around 1-2MB per chain, which is a lot of storage
1030
- * for browser-based dapps which want to connect to lots of chains.
1031
- *
1032
- * By utilizing the wonderful [scale-ts](https://github.com/unstoppablejs/unstoppablejs/tree/main/packages/scale-ts#readme) library,
1033
- * we can trim the chain metadata down so that it only includes the types we need for balance fetching.
1034
- *
1035
- * Each balance module has a function to do just that, `BalanceModule::fetchSubstrateChainMeta`.
1036
- *
1037
- * But, we only want to run this operation when necessary.
1038
- *
1039
- * The purpose of this class, `MiniMetadataUpdater`, is to maintain a local cache of
1040
- * trimmed-down metadatas, which we'll refer to as `MiniMetadatas`.
1041
- */
1042
- class MiniMetadataUpdater {
1043
- #lastHydratedMiniMetadatasAt = 0;
1044
- #lastHydratedCustomChainsAt = 0;
1045
- #chainConnectors;
1046
- #chaindataProvider;
1047
- #balanceModules;
1048
- constructor(chainConnectors, chaindataProvider, balanceModules) {
1049
- this.#chainConnectors = chainConnectors;
1050
- this.#chaindataProvider = chaindataProvider;
1051
- this.#balanceModules = balanceModules;
1052
- }
1053
-
1054
- /** Subscribe to the metadata for a chain */
1055
- subscribe(chainId) {
1056
- return rxjs.from(dexie.liveQuery(() => db.miniMetadatas.filter(m => m.chainId === chainId).toArray().then(array => array[0])));
1057
- }
1058
- async update(chainIds) {
1059
- await this.updateSubstrateChains(chainIds);
1060
- }
1061
- async statuses(chains) {
1062
- const ids = await db.miniMetadatas.orderBy("id").primaryKeys();
1063
- const wantedIdsByChain = new Map(chains.flatMap(({
1064
- id: chainId,
1065
- specName,
1066
- specVersion,
1067
- balancesConfig
1068
- }) => {
1069
- if (specName === null) return [];
1070
- if (specVersion === null) return [];
1071
- return [[chainId, this.#balanceModules.filter(m => m.type.startsWith("substrate-")).map(({
1072
- type: source
1073
- }) => deriveMiniMetadataId({
1074
- source,
1075
- chainId: chainId,
1076
- specName: specName,
1077
- specVersion: specVersion,
1078
- balancesConfig: JSON.stringify((balancesConfig ?? []).find(({
1079
- moduleType
1080
- }) => moduleType === source)?.moduleConfig ?? {})
1081
- }))]];
1082
- }));
1083
- const statusesByChain = new Map(Array.from(wantedIdsByChain.entries()).map(([chainId, wantedIds]) => [chainId, wantedIds.every(wantedId => ids.includes(wantedId)) ? "good" : "none"]));
1084
- return {
1085
- wantedIdsByChain,
1086
- statusesByChain
1087
- };
1088
- }
1089
- async hydrateFromChaindata() {
1090
- const now = Date.now();
1091
- if (now - this.#lastHydratedMiniMetadatasAt < minimumHydrationInterval) return false;
1092
- const dbHasMiniMetadatas = (await db.miniMetadatas.count()) > 0;
1093
- try {
1094
- try {
1095
- // TODO: Move `fetchMiniMetadatas` into this package,
1096
- // so that we don't have a circular import between `@talismn/balances` and `@talismn/chaindata-provider`.
1097
- var miniMetadatas = await chaindataProvider.fetchMiniMetadatas(); // eslint-disable-line no-var
1098
- if (miniMetadatas.length <= 0) throw new Error("Ignoring empty chaindata miniMetadatas response");
1099
- } catch (error) {
1100
- if (dbHasMiniMetadatas) throw error;
1101
- // On first start-up (db is empty), if we fail to fetch miniMetadatas then we should
1102
- // initialize the DB with the list of miniMetadatas inside our init/mini-metadatas.json file.
1103
- // This data will represent a relatively recent copy of what's in chaindata,
1104
- // which will be better for our users than to have nothing at all.
1105
- var miniMetadatas = await chaindataProvider.fetchInitMiniMetadatas(); // eslint-disable-line no-var
1106
- }
1107
- await db.miniMetadatas.bulkPut(miniMetadatas);
1108
- this.#lastHydratedMiniMetadatasAt = now;
1109
- return true;
1110
- } catch (error) {
1111
- log.warn(`Failed to hydrate miniMetadatas from chaindata`, error);
1112
- return false;
1113
- }
1114
- }
1115
- async hydrateCustomChains() {
1116
- const now = Date.now();
1117
- if (now - this.#lastHydratedCustomChainsAt < minimumHydrationInterval) return false;
1118
- const chains = await this.#chaindataProvider.chains();
1119
- const customChains = chains.filter(chain => "isCustom" in chain && chain.isCustom);
1120
- const updatedCustomChains = [];
1121
- const concurrency = 4;
1122
- (await PromisePool.PromisePool.withConcurrency(concurrency).for(customChains).process(async customChain => {
1123
- const send = (method, params) => this.#chainConnectors.substrate?.send(customChain.id, method, params);
1124
- const [genesisHash, runtimeVersion, chainName, chainType] = await Promise.all([send("chain_getBlockHash", [0]), send("state_getRuntimeVersion", []), send("system_chain", []), send("system_chainType", [])]);
1125
-
1126
- // deconstruct rpc data
1127
- const {
1128
- specName,
1129
- implName
1130
- } = runtimeVersion;
1131
- const specVersion = String(runtimeVersion.specVersion);
1132
- const changed = customChain.genesisHash !== genesisHash || customChain.chainName !== chainName || !isEqual__default.default(customChain.chainType, chainType) || customChain.implName !== implName || customChain.specName !== specName || customChain.specVersion !== specVersion;
1133
- if (!changed) return;
1134
- customChain.genesisHash = genesisHash;
1135
- customChain.chainName = chainName;
1136
- customChain.chainType = chainType;
1137
- customChain.implName = implName;
1138
- customChain.specName = specName;
1139
- customChain.specVersion = specVersion;
1140
- updatedCustomChains.push(customChain);
1141
- })).errors.forEach(error => log.error("Error hydrating custom chains", error));
1142
- if (updatedCustomChains.length > 0) {
1143
- await this.#chaindataProvider.transaction("rw", ["chains"], async () => {
1144
- for (const updatedCustomChain of updatedCustomChains) {
1145
- await this.#chaindataProvider.removeCustomChain(updatedCustomChain.id);
1146
- await this.#chaindataProvider.addCustomChain(updatedCustomChain);
1147
- }
1148
- });
1149
- }
1150
- if (updatedCustomChains.length > 0) this.#lastHydratedCustomChainsAt = now;
1151
- return true;
1152
- }
1153
- async updateSubstrateChains(chainIds) {
1154
- const chains = new Map((await this.#chaindataProvider.chains()).map(chain => [chain.id, chain]));
1155
- const filteredChains = chainIds.flatMap(chainId => chains.get(chainId) ?? []);
1156
- const ids = await db.miniMetadatas.orderBy("id").primaryKeys();
1157
- const {
1158
- wantedIdsByChain,
1159
- statusesByChain
1160
- } = await this.statuses(filteredChains);
1161
-
1162
- // clean up store
1163
- const wantedIds = Array.from(wantedIdsByChain.values()).flatMap(ids => ids);
1164
- const unwantedIds = ids.filter(id => !wantedIds.includes(id));
1165
- if (unwantedIds.length > 0) {
1166
- const chainIds = Array.from(new Set((await db.miniMetadatas.bulkGet(unwantedIds)).map(m => m?.chainId)));
1167
- log.info(`Pruning ${unwantedIds.length} miniMetadatas on chains ${chainIds.join(", ")}`);
1168
- await db.miniMetadatas.bulkDelete(unwantedIds);
1169
- }
1170
- const needUpdates = Array.from(statusesByChain.entries()).filter(([, status]) => status !== "good").map(([chainId]) => chainId);
1171
- if (needUpdates.length > 0) log.info(`${needUpdates.length} miniMetadatas need updates (${needUpdates.join(", ")})`);
1172
- const availableTokenLogos = await chaindataProvider.availableTokenLogoFilenames().catch(error => {
1173
- log.error("Failed to fetch available token logos", error);
1174
- return [];
1175
- });
1176
- const concurrency = 12;
1177
- (await PromisePool.PromisePool.withConcurrency(concurrency).for(needUpdates).process(async chainId => {
1178
- log.info(`Updating metadata for chain ${chainId}`);
1179
- const chain = chains.get(chainId);
1180
- if (!chain) return;
1181
- const {
1182
- specName,
1183
- specVersion
1184
- } = chain;
1185
- if (specName === null) return;
1186
- if (specVersion === null) return;
1187
- const fetchMetadata = async () => {
1188
- try {
1189
- return await sapi.fetchBestMetadata((method, params, isCacheable) => {
1190
- if (!this.#chainConnectors.substrate) throw new Error("Substrate connector is not available");
1191
- return this.#chainConnectors.substrate.send(chainId, method, params, isCacheable);
1192
- }, true // allow v14 fallback
1193
- );
1194
- } catch (err) {
1195
- log.warn(`Failed to fetch metadata for chain ${chainId}`);
1196
- return undefined;
1197
- }
1198
- };
1199
- const [metadataRpc, systemProperties] = await Promise.all([fetchMetadata(), this.#chainConnectors.substrate?.send(chainId, "system_properties", [])]);
1200
- for (const mod of this.#balanceModules.filter(m => m.type.startsWith("substrate-"))) {
1201
- const balancesConfig = (chain.balancesConfig ?? []).find(({
1202
- moduleType
1203
- }) => moduleType === mod.type);
1204
- const moduleConfig = balancesConfig?.moduleConfig ?? {};
1205
- const chainMeta = await mod.fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc, systemProperties);
1206
- const tokens = await mod.fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig);
1207
-
1208
- // update tokens in chaindata
1209
- await this.#chaindataProvider.updateChainTokens(chainId, mod.type, Object.values(tokens), availableTokenLogos);
1210
-
1211
- // update miniMetadatas
1212
- const {
1213
- miniMetadata: data,
1214
- metadataVersion: version,
1215
- ...extra
1216
- } = chainMeta ?? {};
1217
- await db.miniMetadatas.put({
1218
- id: deriveMiniMetadataId({
1219
- source: mod.type,
1220
- chainId,
1221
- specName,
1222
- specVersion,
1223
- balancesConfig: JSON.stringify(moduleConfig)
1224
- }),
1225
- source: mod.type,
1226
- chainId,
1227
- specName,
1228
- specVersion,
1229
- balancesConfig: JSON.stringify(moduleConfig),
1230
- // TODO: Standardise return value from `fetchSubstrateChainMeta`
1231
- version,
1232
- data,
1233
- extra: JSON.stringify(extra)
1234
- });
1235
- }
1236
- })).errors.forEach(error => log.error("Error updating chain metadata", error));
1237
- }
1238
- }
961
+ const TokenConfigBaseSchema = chaindataProvider.TokenBaseSchema.partial().omit({
962
+ id: true
963
+ });
1239
964
 
1240
965
  const erc20Abi = [{
1241
966
  constant: true,
@@ -1467,8 +1192,11 @@ const erc20Abi = [{
1467
1192
 
1468
1193
  const erc20BalancesAggregatorAbi = viem.parseAbi(["struct AccountToken {address account; address token;}", "function balances(AccountToken[] memory accountTokens) public view returns (uint256[] memory)"]);
1469
1194
 
1470
- const moduleType$8 = "evm-erc20";
1471
- const evmErc20TokenId = (chainId, tokenContractAddress) => `${chainId}-evm-erc20-${tokenContractAddress}`.toLowerCase();
1195
+ const moduleType$7 = "evm-erc20";
1196
+ const EvmErc20TokenConfigSchema = z__default.default.strictObject({
1197
+ contractAddress: chaindataProvider.EvmErc20TokenSchema.shape.contractAddress,
1198
+ ...TokenConfigBaseSchema.shape
1199
+ });
1472
1200
  const EvmErc20Module = hydrate => {
1473
1201
  const {
1474
1202
  chainConnectors,
@@ -1492,38 +1220,36 @@ const EvmErc20Module = hydrate => {
1492
1220
  }, {});
1493
1221
  };
1494
1222
  const getModuleTokens = async () => {
1495
- return await chaindataProvider$1.tokensByIdForType(moduleType$8);
1223
+ return await chaindataProvider$1.getTokensMapById(moduleType$7);
1496
1224
  };
1497
1225
  const getErc20Aggregators = async () => {
1498
- const evmNetworks = await chaindataProvider$1.evmNetworks();
1499
- return Object.fromEntries(evmNetworks.filter(n => n.erc20aggregator).map(n => [n.id, n.erc20aggregator]));
1226
+ const evmNetworks = await chaindataProvider$1.getNetworks("ethereum");
1227
+ return Object.fromEntries(evmNetworks.filter(n => n.contracts?.Erc20Aggregator).map(n => [n.id, n.contracts.Erc20Aggregator]));
1500
1228
  };
1501
1229
  return {
1502
- ...DefaultBalanceModule(moduleType$8),
1230
+ ...DefaultBalanceModule(moduleType$7),
1503
1231
  /**
1504
1232
  * This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L280-L284).
1505
1233
  * 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.
1506
1234
  */
1507
- async fetchEvmChainMeta(chainId) {
1508
- const isTestnet = (await chaindataProvider$1.evmNetworkById(chainId))?.isTestnet || false;
1235
+ async fetchEvmChainMeta(_chainId) {
1509
1236
  return {
1510
- isTestnet
1237
+ miniMetadata: null,
1238
+ extra: null
1511
1239
  };
1512
1240
  },
1513
1241
  /**
1514
1242
  * This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L338-L343).
1515
1243
  * 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.
1516
1244
  */
1517
- async fetchEvmChainTokens(chainId, chainMeta, moduleConfig) {
1518
- const {
1519
- isTestnet
1520
- } = chainMeta;
1245
+ async fetchEvmChainTokens(chainId, _chainMeta, moduleConfig, tokens) {
1521
1246
  const chainTokens = {};
1522
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
1247
+ for (const tokenConfig of tokens ?? []) {
1523
1248
  const {
1524
1249
  contractAddress,
1525
1250
  symbol: contractSymbol,
1526
- decimals: contractDecimals
1251
+ decimals: contractDecimals,
1252
+ name: contractName
1527
1253
  } = tokenConfig;
1528
1254
  // TODO : in chaindata's build, filter out all tokens that don't have any of these
1529
1255
  if (!contractAddress || !contractSymbol || contractDecimals === undefined) {
@@ -1533,26 +1259,29 @@ const EvmErc20Module = hydrate => {
1533
1259
  const symbol = tokenConfig?.symbol ?? contractSymbol ?? "ETH";
1534
1260
  const decimals = typeof tokenConfig?.decimals === "number" ? tokenConfig.decimals : typeof contractDecimals === "number" ? contractDecimals : 18;
1535
1261
  if (!symbol || typeof decimals !== "number") continue;
1536
- const id = evmErc20TokenId(chainId, contractAddress);
1262
+ const id = chaindataProvider.evmErc20TokenId(chainId, contractAddress);
1537
1263
  const token = {
1538
1264
  id,
1539
1265
  type: "evm-erc20",
1540
- isTestnet,
1266
+ platform: "ethereum",
1541
1267
  isDefault: tokenConfig.isDefault ?? true,
1542
1268
  symbol,
1543
1269
  decimals,
1544
- logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
1270
+ name: contractName ?? symbol,
1271
+ logo: tokenConfig?.logo,
1545
1272
  contractAddress,
1546
- evmNetwork: {
1547
- id: chainId
1548
- }
1273
+ networkId: chainId
1549
1274
  };
1550
1275
  if (tokenConfig?.symbol) token.symbol = tokenConfig?.symbol;
1551
1276
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
1552
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
1553
1277
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
1554
1278
  if (tokenConfig?.noDiscovery) token.noDiscovery = tokenConfig?.noDiscovery;
1555
- chainTokens[token.id] = token;
1279
+ const validation = chaindataProvider.EvmErc20TokenSchema.safeParse(token);
1280
+ if (validation.success) {
1281
+ chainTokens[token.id] = token;
1282
+ } else {
1283
+ log.warn("Ignoring invalid token", token.id, validation.error.message, validation.error.issues);
1284
+ }
1556
1285
  }
1557
1286
  return chainTokens;
1558
1287
  },
@@ -1564,18 +1293,18 @@ const EvmErc20Module = hydrate => {
1564
1293
  const subscriptionInterval = 6_000; // 6_000ms == 6 seconds
1565
1294
  const initDelay = 1_500; // 1_500ms == 1.5 seconds
1566
1295
  const initialisingBalances = new Set();
1567
- const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.evmNetworkId));
1296
+ const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.networkId));
1568
1297
  const tokens = await getModuleTokens();
1569
1298
 
1570
1299
  // for chains with a zero balance we only call fetchBalances once every 5 subscriptionIntervals
1571
1300
  // if subscriptionInterval is 6 seconds, this means we only poll chains with a zero balance every 30 seconds
1572
1301
  let zeroBalanceSubscriptionIntervalCounter = 0;
1573
- const evmNetworks = await chaindataProvider$1.evmNetworksById();
1302
+ const evmNetworks = await chaindataProvider$1.getNetworksMapById("ethereum");
1574
1303
  const ethAddressesByToken = Object.fromEntries(Object.entries(addressesByToken).map(([tokenId, addresses]) => {
1575
1304
  const ethAddresses = addresses.filter(util.isEthereumAddress);
1576
1305
  if (ethAddresses.length === 0) return null;
1577
1306
  const token = tokens[tokenId];
1578
- const evmNetworkId = token.evmNetwork?.id;
1307
+ const evmNetworkId = token.networkId;
1579
1308
  if (!evmNetworkId) return null;
1580
1309
  return [tokenId, ethAddresses];
1581
1310
  }).filter(x => Boolean(x)));
@@ -1701,20 +1430,17 @@ const fetchBalances$3 = async (evmChainConnector, tokenAddressesByNetwork, erc20
1701
1430
  results: [],
1702
1431
  errors: []
1703
1432
  };
1704
- await Promise.all(Object.entries(tokenAddressesByNetwork).map(async ([evmNetworkId, networkParams]) => {
1705
- const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(evmNetworkId);
1706
- if (!publicClient) throw new EvmErc20NetworkError(`Could not get rpc provider for evm network ${evmNetworkId}`, evmNetworkId);
1707
- const balances = await getEvmTokenBalances(publicClient, networkParams, result.errors, erc20Aggregators[evmNetworkId]);
1433
+ await Promise.all(Object.entries(tokenAddressesByNetwork).map(async ([networkId, networkParams]) => {
1434
+ const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(networkId);
1435
+ if (!publicClient) throw new EvmErc20NetworkError(`Could not get rpc provider for evm network ${networkId}`, networkId);
1436
+ const balances = await getEvmTokenBalances(publicClient, networkParams, result.errors, erc20Aggregators[networkId]);
1708
1437
 
1709
1438
  // consider only non null balances in the results
1710
1439
  result.results.push(...balances.filter(util.isTruthy).map((free, i) => ({
1711
1440
  source: "evm-erc20",
1712
1441
  status: "live",
1713
1442
  address: networkParams[i].address,
1714
- multiChainId: {
1715
- evmChainId: evmNetworkId
1716
- },
1717
- evmNetworkId,
1443
+ networkId,
1718
1444
  tokenId: networkParams[i].token.id,
1719
1445
  value: free
1720
1446
  })));
@@ -1785,7 +1511,7 @@ function groupAddressesByTokenByEvmNetwork$1(addressesByToken, tokens) {
1785
1511
  log.error(`Token ${tokenId} not found`);
1786
1512
  return byChain;
1787
1513
  }
1788
- const chainId = token.evmNetwork?.id;
1514
+ const chainId = token.networkId;
1789
1515
  if (!chainId) {
1790
1516
  log.error(`Token ${tokenId} has no evm network`);
1791
1517
  return byChain;
@@ -1798,67 +1524,38 @@ function groupAddressesByTokenByEvmNetwork$1(addressesByToken, tokens) {
1798
1524
 
1799
1525
  const abiMulticall = viem.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)"]);
1800
1526
 
1801
- const moduleType$7 = "evm-native";
1802
- const evmNativeTokenId = chainId => `${chainId}-evm-native`.toLowerCase().replace(/ /g, "-");
1803
- const getEvmNetworkIdFromTokenId = tokenId => {
1804
- const evmNetworkId = tokenId.split("-")[0];
1805
- if (!evmNetworkId) throw new Error(`Can't detect chainId for token ${tokenId}`);
1806
- return evmNetworkId;
1807
- };
1527
+ const moduleType$6 = "evm-native";
1528
+ const EvmNativeTokenConfigSchema = TokenConfigBaseSchema;
1808
1529
  const EvmNativeModule = hydrate => {
1809
1530
  const {
1810
1531
  chainConnectors,
1811
1532
  chaindataProvider: chaindataProvider$1
1812
1533
  } = hydrate;
1813
1534
  const getModuleTokens = async () => {
1814
- return await chaindataProvider$1.tokensByIdForType(moduleType$7);
1535
+ return await chaindataProvider$1.getTokensMapById(moduleType$6);
1815
1536
  };
1816
1537
  return {
1817
- ...DefaultBalanceModule(moduleType$7),
1538
+ ...DefaultBalanceModule(moduleType$6),
1818
1539
  get tokens() {
1819
- return chaindataProvider$1.tokensByIdForType(moduleType$7);
1540
+ return chaindataProvider$1.getTokensMapById(moduleType$6);
1820
1541
  },
1821
1542
  /**
1822
1543
  * This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L280-L284).
1823
1544
  * 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.
1824
1545
  */
1825
- async fetchEvmChainMeta(chainId) {
1826
- const isTestnet = (await chaindataProvider$1.evmNetworkById(chainId))?.isTestnet || false;
1546
+ async fetchEvmChainMeta(_chainId) {
1827
1547
  return {
1828
- isTestnet
1548
+ miniMetadata: null,
1549
+ extra: null
1829
1550
  };
1830
1551
  },
1831
1552
  /**
1832
1553
  * This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L338-L343).
1833
1554
  * 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.
1834
1555
  */
1835
- async fetchEvmChainTokens(chainId, chainMeta, moduleConfig) {
1836
- const {
1837
- isTestnet
1838
- } = chainMeta;
1839
- const symbol = moduleConfig?.symbol ?? "ETH";
1840
- const decimals = typeof moduleConfig?.decimals === "number" ? moduleConfig.decimals : 18;
1841
- const id = evmNativeTokenId(chainId);
1842
- const nativeToken = {
1843
- id,
1844
- type: "evm-native",
1845
- isTestnet,
1846
- isDefault: true,
1847
- symbol,
1848
- decimals,
1849
- logo: moduleConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
1850
- evmNetwork: {
1851
- id: chainId
1852
- }
1853
- };
1854
- if (moduleConfig?.symbol) nativeToken.symbol = moduleConfig?.symbol;
1855
- if (moduleConfig?.coingeckoId) nativeToken.coingeckoId = moduleConfig?.coingeckoId;
1856
- if (moduleConfig?.dcentName) nativeToken.dcentName = moduleConfig?.dcentName;
1857
- if (moduleConfig?.mirrorOf) nativeToken.mirrorOf = moduleConfig?.mirrorOf;
1858
- if (moduleConfig?.noDiscovery) nativeToken.noDiscovery = moduleConfig?.noDiscovery;
1859
- return {
1860
- [nativeToken.id]: nativeToken
1861
- };
1556
+ async fetchEvmChainTokens() {
1557
+ // token is currently generated in chaindata from the EthNetworkConfig["nativeCurrency"] field
1558
+ return {};
1862
1559
  },
1863
1560
  async subscribeBalances({
1864
1561
  addressesByToken,
@@ -1869,11 +1566,11 @@ const EvmNativeModule = hydrate => {
1869
1566
  const initDelay = 500; // 500ms == 0.5 seconds
1870
1567
 
1871
1568
  const tokens = await getModuleTokens();
1872
- const ethAddressesByToken = Object.fromEntries(Object.entries(addressesByToken).map(([tokenId, addresses]) => {
1569
+ const ethAddressesByToken = lodash.fromPairs(lodash.toPairs(addressesByToken).map(([tokenId, addresses]) => {
1873
1570
  const ethAddresses = addresses.filter(util.isEthereumAddress);
1874
1571
  if (ethAddresses.length === 0) return null;
1875
1572
  const token = tokens[tokenId];
1876
- const evmNetworkId = token.evmNetwork?.id;
1573
+ const evmNetworkId = token.networkId;
1877
1574
  if (!evmNetworkId) return null;
1878
1575
  return [tokenId, ethAddresses];
1879
1576
  }).filter(x => Boolean(x)));
@@ -1883,16 +1580,16 @@ const EvmNativeModule = hydrate => {
1883
1580
  let zeroBalanceSubscriptionIntervalCounter = 0;
1884
1581
 
1885
1582
  // setup initialising balances for all active evm networks
1886
- const activeEvmNetworkIds = Object.keys(ethAddressesByToken).map(getEvmNetworkIdFromTokenId);
1583
+ const activeEvmNetworkIds = lodash.keys(ethAddressesByToken).map(chaindataProvider.networkIdFromTokenId);
1887
1584
  const initialisingBalances = new Set(activeEvmNetworkIds);
1888
- const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.evmNetworkId));
1585
+ const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.networkId));
1889
1586
  const poll = async () => {
1890
1587
  if (!subscriptionActive) return;
1891
1588
  zeroBalanceSubscriptionIntervalCounter = (zeroBalanceSubscriptionIntervalCounter + 1) % 5;
1892
1589
  try {
1893
1590
  // fetch balance for each network sequentially to prevent creating a big queue of http requests (browser can only handle 2 at a time)
1894
1591
  for (const [tokenId, addresses] of Object.entries(ethAddressesByToken)) {
1895
- const evmNetworkId = getEvmNetworkIdFromTokenId(tokenId);
1592
+ const evmNetworkId = chaindataProvider.networkIdFromTokenId(tokenId);
1896
1593
 
1897
1594
  // a zero balance network is one that has initialised and does not have a positive balance
1898
1595
  const isZeroBalanceNetwork = !initialisingBalances.has(evmNetworkId) && !positiveBalanceNetworks.has(evmNetworkId);
@@ -1912,10 +1609,10 @@ const EvmNativeModule = hydrate => {
1912
1609
  log.error(balance.message, balance.networkId);
1913
1610
  initialisingBalances.delete(balance.networkId);
1914
1611
  } else {
1915
- if (balance.evmNetworkId) {
1916
- initialisingBalances.delete(balance.evmNetworkId);
1612
+ if (balance.networkId) {
1613
+ initialisingBalances.delete(balance.networkId);
1917
1614
  if (BigInt(balance.value) > 0n) {
1918
- positiveBalanceNetworks.add(balance.evmNetworkId);
1615
+ positiveBalanceNetworks.add(balance.networkId);
1919
1616
  }
1920
1617
  resultBalances.push(balance);
1921
1618
  }
@@ -1965,23 +1662,20 @@ const fetchBalances$2 = async (evmChainConnector, addressesByToken, tokens) => {
1965
1662
  if (!evmChainConnector) throw new Error(`This module requires an evm chain connector`);
1966
1663
  return Promise.all(Object.entries(addressesByToken).map(async ([tokenId, addresses]) => {
1967
1664
  const token = tokens[tokenId];
1968
- const evmNetworkId = token.evmNetwork?.id;
1969
- if (!evmNetworkId) throw new Error(`Token ${token.id} has no evm network`);
1970
- const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(evmNetworkId);
1971
- if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${evmNetworkId}`);
1665
+ const networkId = token.networkId;
1666
+ if (!networkId) throw new Error(`Token ${token.id} has no evm network`);
1667
+ const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(networkId);
1668
+ if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
1972
1669
 
1973
1670
  // fetch all balances
1974
1671
  const freeBalances = await getFreeBalances(publicClient, addresses);
1975
1672
  const balanceResults = addresses.map((address, i) => {
1976
- if (freeBalances[i] === "error") return new EvmNativeBalanceError("Could not fetch balance ", evmNetworkId);
1673
+ if (freeBalances[i] === "error") return new EvmNativeBalanceError("Could not fetch balance ", networkId);
1977
1674
  return {
1978
1675
  source: "evm-native",
1979
1676
  status: "live",
1980
1677
  address: address,
1981
- multiChainId: {
1982
- evmChainId: evmNetworkId
1983
- },
1984
- evmNetworkId,
1678
+ networkId,
1985
1679
  tokenId,
1986
1680
  value: freeBalances[i].toString()
1987
1681
  };
@@ -2034,7 +1728,7 @@ async function getFreeBalances(publicClient, addresses) {
2034
1728
  });
2035
1729
  } catch (err) {
2036
1730
  const errorMessage = util.hasOwnProperty(err, "shortMessage") ? err.shortMessage : util.hasOwnProperty(err, "message") ? err.message : err;
2037
- log.warn(`Failed to get balance from chain ${publicClient.chain?.id} for ${ethAddresses.length} addresses: ${errorMessage}`);
1731
+ log.warn(`Failed to get balance from chain ${publicClient.chain.id} for ${ethAddresses.length} addresses: ${errorMessage}`);
2038
1732
  return ethAddresses.map(() => "error");
2039
1733
  }
2040
1734
  }
@@ -2602,8 +2296,20 @@ const uniswapV2PairAbi = [{
2602
2296
  type: "function"
2603
2297
  }];
2604
2298
 
2605
- const moduleType$6 = "evm-uniswapv2";
2606
- const evmUniswapV2TokenId = (chainId, contractAddress) => `${chainId}-evm-uniswapv2-${contractAddress}`.toLowerCase();
2299
+ const moduleType$5 = "evm-uniswapv2";
2300
+ const EvmUniswapV2TokenConfigSchema = z__default.default.strictObject({
2301
+ contractAddress: chaindataProvider.EvmUniswapV2TokenSchema.shape.contractAddress,
2302
+ ...TokenConfigBaseSchema.shape,
2303
+ // on chaindata side these are fetched by a dedicated task
2304
+ symbol0: chaindataProvider.EvmUniswapV2TokenSchema.shape.symbol0.optional(),
2305
+ symbol1: chaindataProvider.EvmUniswapV2TokenSchema.shape.symbol1.optional(),
2306
+ decimals0: chaindataProvider.EvmUniswapV2TokenSchema.shape.decimals0.optional(),
2307
+ decimals1: chaindataProvider.EvmUniswapV2TokenSchema.shape.decimals1.optional(),
2308
+ tokenAddress0: chaindataProvider.EvmUniswapV2TokenSchema.shape.tokenAddress0.optional(),
2309
+ tokenAddress1: chaindataProvider.EvmUniswapV2TokenSchema.shape.tokenAddress1.optional(),
2310
+ coingeckoId0: chaindataProvider.EvmUniswapV2TokenSchema.shape.coingeckoId0.optional(),
2311
+ coingeckoId1: chaindataProvider.EvmUniswapV2TokenSchema.shape.coingeckoId1.optional()
2312
+ });
2607
2313
  const EvmUniswapV2Module = hydrate => {
2608
2314
  const {
2609
2315
  chainConnectors,
@@ -2612,19 +2318,16 @@ const EvmUniswapV2Module = hydrate => {
2612
2318
  const chainConnector = chainConnectors.evm;
2613
2319
  util$1.assert(chainConnector, "This module requires an evm chain connector");
2614
2320
  return {
2615
- ...DefaultBalanceModule(moduleType$6),
2616
- async fetchEvmChainMeta(chainId) {
2617
- const isTestnet = (await chaindataProvider$1.evmNetworkById(chainId))?.isTestnet || false;
2321
+ ...DefaultBalanceModule(moduleType$5),
2322
+ async fetchEvmChainMeta(_chainId) {
2618
2323
  return {
2619
- isTestnet
2324
+ miniMetadata: null,
2325
+ extra: null
2620
2326
  };
2621
2327
  },
2622
- async fetchEvmChainTokens(chainId, chainMeta, moduleConfig) {
2623
- const {
2624
- isTestnet
2625
- } = chainMeta;
2328
+ async fetchEvmChainTokens(chainId, _chainMeta, moduleConfig, pools) {
2626
2329
  const tokens = {};
2627
- for (const tokenConfig of moduleConfig?.pools ?? []) {
2330
+ for (const tokenConfig of pools ?? []) {
2628
2331
  const {
2629
2332
  contractAddress,
2630
2333
  decimals,
@@ -2635,21 +2338,23 @@ const EvmUniswapV2Module = hydrate => {
2635
2338
  tokenAddress0,
2636
2339
  tokenAddress1,
2637
2340
  coingeckoId0,
2638
- coingeckoId1
2341
+ coingeckoId1,
2342
+ name
2639
2343
  } = tokenConfig;
2640
2344
  if (!contractAddress || decimals === undefined || symbol0 === undefined || decimals0 === undefined || symbol1 === undefined || decimals1 === undefined || tokenAddress0 === undefined || tokenAddress1 === undefined) {
2641
2345
  log.warn("ignoring token on chain %s", chainId, tokenConfig);
2642
2346
  continue;
2643
2347
  }
2644
- const id = evmUniswapV2TokenId(chainId, contractAddress);
2348
+ const id = chaindataProvider.evmUniswapV2TokenId(chainId, contractAddress);
2645
2349
  const token = {
2646
2350
  id,
2647
2351
  type: "evm-uniswapv2",
2648
- isTestnet,
2352
+ platform: "ethereum",
2649
2353
  isDefault: tokenConfig.isDefault ?? false,
2650
2354
  symbol: `${symbol0 ?? "UNKNOWN"}/${symbol1 ?? "UNKNOWN"}`,
2355
+ name: name ?? `${symbol0 ?? "UNKNOWN"}/${symbol1 ?? "UNKNOWN"}`,
2651
2356
  decimals,
2652
- logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl("uniswap"),
2357
+ logo: tokenConfig?.logo || chaindataProvider.getGithubTokenLogoUrl("uniswap"),
2653
2358
  symbol0,
2654
2359
  decimals0,
2655
2360
  symbol1,
@@ -2659,13 +2364,10 @@ const EvmUniswapV2Module = hydrate => {
2659
2364
  tokenAddress1,
2660
2365
  coingeckoId0,
2661
2366
  coingeckoId1,
2662
- evmNetwork: {
2663
- id: chainId
2664
- }
2367
+ networkId: chainId
2665
2368
  };
2666
2369
  if (tokenConfig?.symbol) token.symbol = tokenConfig?.symbol;
2667
2370
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
2668
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
2669
2371
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
2670
2372
  if (tokenConfig?.noDiscovery) token.noDiscovery = tokenConfig?.noDiscovery;
2671
2373
  tokens[token.id] = token;
@@ -2681,9 +2383,9 @@ const EvmUniswapV2Module = hydrate => {
2681
2383
  const initDelay = 1_500; // 1_500ms == 1.5 seconds
2682
2384
 
2683
2385
  const initialBalancesByNetwork = initialBalances?.reduce((result, b) => {
2684
- if (!b.evmNetworkId) return result;
2685
- if (!result[b.evmNetworkId]) result[b.evmNetworkId] = {};
2686
- result[b.evmNetworkId][getBalanceId(b)] = b;
2386
+ if (!b.networkId) return result;
2387
+ if (!result[b.networkId]) result[b.networkId] = {};
2388
+ result[b.networkId][getBalanceId(b)] = b;
2687
2389
  return result;
2688
2390
  }, {});
2689
2391
  const cache = new Map(Object.entries(initialBalancesByNetwork ?? {}));
@@ -2691,8 +2393,8 @@ const EvmUniswapV2Module = hydrate => {
2691
2393
  // for chains with a zero balance we only call fetchBalances once every 5 subscriptionIntervals
2692
2394
  // if subscriptionInterval is 6 seconds, this means we only poll chains with a zero balance every 30 seconds
2693
2395
  let zeroBalanceSubscriptionIntervalCounter = 0;
2694
- const evmNetworks = await chaindataProvider$1.evmNetworksById();
2695
- const tokens = await chaindataProvider$1.tokensById();
2396
+ const evmNetworks = await chaindataProvider$1.getNetworksMapById("ethereum");
2397
+ const tokens = await chaindataProvider$1.getTokensMapById();
2696
2398
  const poll = async () => {
2697
2399
  if (!subscriptionActive) return;
2698
2400
  zeroBalanceSubscriptionIntervalCounter = (zeroBalanceSubscriptionIntervalCounter + 1) % 5;
@@ -2735,20 +2437,20 @@ const EvmUniswapV2Module = hydrate => {
2735
2437
  },
2736
2438
  async fetchBalances(addressesByToken) {
2737
2439
  if (!chainConnectors.evm) throw new Error(`This module requires an evm chain connector`);
2738
- const evmNetworks = await chaindataProvider$1.evmNetworksById();
2739
- const tokens = await chaindataProvider$1.tokensById();
2440
+ const evmNetworks = await chaindataProvider$1.getNetworksMapById("ethereum");
2441
+ const tokens = await chaindataProvider$1.getTokensMapById();
2740
2442
  return fetchBalances$1(chainConnectors.evm, evmNetworks, tokens, addressesByToken);
2741
2443
  }
2742
2444
  };
2743
2445
  };
2744
2446
  const fetchBalances$1 = async (evmChainConnector, evmNetworks, tokens, addressesByToken) => {
2745
2447
  const addressesByTokenGroupedByEvmNetwork = groupAddressesByTokenByEvmNetwork(addressesByToken, tokens);
2746
- const balances = (await Promise.allSettled(Object.entries(addressesByTokenGroupedByEvmNetwork).map(async ([evmNetworkId, addressesByToken]) => {
2448
+ const balances = (await Promise.allSettled(Object.entries(addressesByTokenGroupedByEvmNetwork).map(async ([networkId, addressesByToken]) => {
2747
2449
  if (!evmChainConnector) throw new Error(`This module requires an evm chain connector`);
2748
- const evmNetwork = evmNetworks[evmNetworkId];
2749
- if (!evmNetwork) throw new Error(`Evm network ${evmNetworkId} not found`);
2750
- const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(evmNetworkId);
2751
- if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${evmNetworkId}`);
2450
+ const evmNetwork = evmNetworks[networkId];
2451
+ if (!evmNetwork) throw new Error(`Evm network ${networkId} not found`);
2452
+ const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(networkId);
2453
+ if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
2752
2454
  const tokensAndAddresses = Object.entries(addressesByToken).reduce((tokensAndAddresses, [tokenId, addresses]) => {
2753
2455
  const token = tokens[tokenId];
2754
2456
  if (!token) {
@@ -2770,10 +2472,7 @@ const fetchBalances$1 = async (evmChainConnector, evmNetworks, tokens, addresses
2770
2472
  source: "evm-uniswapv2",
2771
2473
  status: "live",
2772
2474
  address: address,
2773
- multiChainId: {
2774
- evmChainId: evmNetwork.id
2775
- },
2776
- evmNetworkId,
2475
+ networkId,
2777
2476
  tokenId: token.id,
2778
2477
  values: await getPoolBalance(publicClient, token.contractAddress, address)
2779
2478
  }));
@@ -2809,7 +2508,7 @@ function groupAddressesByTokenByEvmNetwork(addressesByToken, tokens) {
2809
2508
  log.error(`Token ${tokenId} not found`);
2810
2509
  return byChain;
2811
2510
  }
2812
- const chainId = token.evmNetwork?.id;
2511
+ const chainId = token.networkId;
2813
2512
  if (!chainId) {
2814
2513
  log.error(`Token ${tokenId} has no evm network`);
2815
2514
  return byChain;
@@ -2884,6 +2583,170 @@ async function getPoolBalance(publicClient, contractAddress, accountAddress) {
2884
2583
  }
2885
2584
  }
2886
2585
 
2586
+ const libVersion = pkg.version;
2587
+
2588
+ // cache the promise so it can be shared across multiple calls
2589
+ const CACHE_GET_SPEC_VERSION = new Map();
2590
+ const fetchSpecVersion = async (chainConnector, networkId) => {
2591
+ const {
2592
+ specVersion
2593
+ } = await chainConnector.send(networkId, "state_getRuntimeVersion", [], true);
2594
+ return specVersion;
2595
+ };
2596
+
2597
+ /**
2598
+ * fetches the spec version of a network. current request is cached in case of multiple calls (all balance modules will kick in at once)
2599
+ */
2600
+ const getSpecVersion = async (chainConnector, networkId) => {
2601
+ if (CACHE_GET_SPEC_VERSION.has(networkId)) return CACHE_GET_SPEC_VERSION.get(networkId);
2602
+ const pResult = fetchSpecVersion(chainConnector, networkId);
2603
+ CACHE_GET_SPEC_VERSION.set(networkId, pResult);
2604
+ try {
2605
+ return await pResult;
2606
+ } catch (cause) {
2607
+ throw new Error(`Failed to fetch specVersion for network ${networkId}`, {
2608
+ cause
2609
+ });
2610
+ } finally {
2611
+ CACHE_GET_SPEC_VERSION.delete(networkId);
2612
+ }
2613
+ };
2614
+
2615
+ // share requests as all modules will call this at once
2616
+ const CACHE$1 = new Map();
2617
+ const getMetadataRpc = async (chainConnector, networkId) => {
2618
+ if (CACHE$1.has(networkId)) return CACHE$1.get(networkId);
2619
+ const pResult = sapi.fetchBestMetadata((method, params, isCacheable) => chainConnector.send(networkId, method, params, isCacheable, {
2620
+ expectErrors: true
2621
+ }), true // allow fallback to 14 as modules dont use any v15 or v16 specifics yet
2622
+ );
2623
+ CACHE$1.set(networkId, pResult);
2624
+ try {
2625
+ return await pResult;
2626
+ } catch (cause) {
2627
+ if (util.isAbortError(cause)) throw cause;
2628
+ throw new Error(`Failed to fetch metadataRpc for network ${networkId}`, {
2629
+ cause
2630
+ });
2631
+ } finally {
2632
+ CACHE$1.delete(networkId);
2633
+ }
2634
+ };
2635
+
2636
+ // share requests as all modules will call this at once
2637
+ const CACHE = new Map();
2638
+
2639
+ // ensures we dont fetch miniMetadatas on more than 4 chains at once
2640
+ const POOL = new PQueue__default.default({
2641
+ concurrency: 4
2642
+ });
2643
+ const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, specVersion, signal) => {
2644
+ if (CACHE.has(networkId)) return CACHE.get(networkId);
2645
+ 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
2646
+ );
2647
+ const pResult = POOL.add(() => fetchMiniMetadatas(chainConnector, chaindataProvider, networkId, specVersion), {
2648
+ signal
2649
+ });
2650
+ CACHE.set(networkId, pResult);
2651
+ try {
2652
+ return await pResult;
2653
+ } catch (cause) {
2654
+ if (util.isAbortError(cause)) throw cause;
2655
+ throw new Error(`Failed to fetch metadataRpc for network ${networkId}`, {
2656
+ cause
2657
+ });
2658
+ } finally {
2659
+ CACHE.delete(networkId);
2660
+ }
2661
+ };
2662
+ const DotBalanceModuleTypeSchema = z__default.default.keyof(chaindataProvider.DotNetworkBalancesConfigSchema);
2663
+ const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion, signal) => {
2664
+ const start = performance.now();
2665
+ log.info("[miniMetadata] fetching minimetadatas for %s", chainId);
2666
+ try {
2667
+ const metadataRpc = await getMetadataRpc(chainConnector, chainId);
2668
+ signal?.throwIfAborted();
2669
+ const chainConnectors = {
2670
+ substrate: chainConnector,
2671
+ evm: {} // wont be used but workarounds error for module creation
2672
+ };
2673
+ const modules = defaultBalanceModules.map(mod => mod({
2674
+ chainConnectors,
2675
+ chaindataProvider
2676
+ })).filter(mod => DotBalanceModuleTypeSchema.safeParse(mod.type).success);
2677
+ return Promise.all(modules.map(async mod => {
2678
+ const source = mod.type;
2679
+ const chain = await chaindataProvider.getNetworkById(chainId, "polkadot");
2680
+ const balancesConfig = chain?.balancesConfig?.[mod.type];
2681
+ const chainMeta = await mod.fetchSubstrateChainMeta(chainId, balancesConfig,
2682
+ // TODO better typing
2683
+ metadataRpc);
2684
+ return {
2685
+ id: deriveMiniMetadataId({
2686
+ source,
2687
+ chainId,
2688
+ specVersion,
2689
+ libVersion
2690
+ }),
2691
+ source,
2692
+ chainId,
2693
+ specVersion,
2694
+ libVersion,
2695
+ data: chainMeta?.miniMetadata ?? null,
2696
+ extra: chainMeta?.extra ?? null
2697
+ };
2698
+ }));
2699
+ } finally {
2700
+ log.debug("[miniMetadata] updated miniMetadatas for %s in %sms", chainId, (performance.now() - start).toFixed(2));
2701
+ }
2702
+ };
2703
+
2704
+ const getUpdatedMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion, signal) => {
2705
+ const miniMetadatas = await getMiniMetadatas(chainConnector, chaindataProvider, chainId, specVersion, signal);
2706
+ signal?.throwIfAborted();
2707
+ await db.transaction("readwrite", "miniMetadatas", async tx => {
2708
+ await tx.miniMetadatas.where({
2709
+ chainId
2710
+ }).delete();
2711
+ await tx.miniMetadatas.bulkPut(miniMetadatas);
2712
+ });
2713
+ return miniMetadatas;
2714
+ };
2715
+
2716
+ const getMiniMetadata = async (chaindataProvider, chainConnector, chainId, source, signal) => {
2717
+ const specVersion = await getSpecVersion(chainConnector, chainId);
2718
+ signal?.throwIfAborted();
2719
+ const miniMetadataId = deriveMiniMetadataId({
2720
+ source,
2721
+ chainId,
2722
+ specVersion,
2723
+ libVersion
2724
+ });
2725
+
2726
+ // lookup local ones
2727
+ const [dbMiniMetadata, ghMiniMetadata] = await Promise.all([db.miniMetadatas.get(miniMetadataId), chaindataProvider.miniMetadataById(miniMetadataId)]);
2728
+ signal?.throwIfAborted();
2729
+ const miniMetadata = dbMiniMetadata ?? ghMiniMetadata;
2730
+ if (miniMetadata) return miniMetadata;
2731
+
2732
+ // update from live chain metadata and persist locally
2733
+ const miniMetadatas = await getUpdatedMiniMetadatas(chainConnector, chaindataProvider, chainId, specVersion, signal);
2734
+ signal?.throwIfAborted();
2735
+ const found = miniMetadatas.find(m => m.id === miniMetadataId);
2736
+ if (!found) {
2737
+ log.warn("MiniMetadata not found in updated miniMetadatas", {
2738
+ source,
2739
+ chainId,
2740
+ specVersion,
2741
+ libVersion,
2742
+ miniMetadataId,
2743
+ miniMetadatas
2744
+ });
2745
+ throw new Error(`MiniMetadata not found for ${source} on ${chainId}`);
2746
+ }
2747
+ return found;
2748
+ };
2749
+
2887
2750
  /**
2888
2751
  * Wraps a BalanceModule's fetch/subscribe methods with a single `balances` method.
2889
2752
  * This `balances` method will subscribe if a callback parameter is provided, or otherwise fetch.
@@ -2899,44 +2762,16 @@ async function balances(balanceModule, addressesByToken, callback) {
2899
2762
  return await balanceModule.fetchBalances(addressesByToken);
2900
2763
  }
2901
2764
 
2902
- /**
2903
- * Given a `moduleType` and a `chain` from a chaindataProvider, this function will find the chainMeta
2904
- * associated with the given balanceModule for the given chain.
2905
- */
2906
- const findChainMeta = (miniMetadatas, moduleType, chain) => {
2907
- if (!chain) return [undefined, undefined];
2908
- if (!chain.specName) return [undefined, undefined];
2909
- if (!chain.specVersion) return [undefined, undefined];
2910
-
2911
- // TODO: This is spaghetti to import this here, it should be injected into each balance module or something.
2912
- const metadataId = deriveMiniMetadataId({
2913
- source: moduleType,
2914
- chainId: chain.id,
2915
- specName: chain.specName,
2916
- specVersion: chain.specVersion,
2917
- balancesConfig: JSON.stringify(chain.balancesConfig?.find(config => config.moduleType === moduleType)?.moduleConfig ?? {})
2918
- });
2919
-
2920
- // TODO: Fix this (needs to fetch miniMetadata without being async)
2921
- const miniMetadata = miniMetadatas.get(metadataId);
2922
- const chainMeta = miniMetadata ? {
2923
- miniMetadata: miniMetadata.data,
2924
- metadataVersion: miniMetadata.version,
2925
- ...JSON.parse(miniMetadata.extra)
2926
- } : undefined;
2927
- return [chainMeta, miniMetadata];
2928
- };
2929
-
2765
+ // TODO remove this one in favor of the network specific one below
2930
2766
  const buildStorageCoders = ({
2931
2767
  chainIds,
2932
2768
  chains,
2933
2769
  miniMetadatas,
2934
- moduleType,
2935
2770
  coders
2936
2771
  }) => new Map([...chainIds].flatMap(chainId => {
2937
2772
  const chain = chains[chainId];
2938
2773
  if (!chain) return [];
2939
- const [, miniMetadata] = findChainMeta(miniMetadatas, moduleType, chain);
2774
+ const miniMetadata = miniMetadatas.get(chainId); // findMiniMetadata<TBalanceModule>(miniMetadatas, moduleType, chain)
2940
2775
  if (!miniMetadata) return [];
2941
2776
  if (!miniMetadata.data) return [];
2942
2777
  const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata.data));
@@ -2959,6 +2794,28 @@ const buildStorageCoders = ({
2959
2794
  return [];
2960
2795
  }
2961
2796
  }));
2797
+ const buildNetworkStorageCoders = (chainId, miniMetadata, coders) => {
2798
+ if (!miniMetadata.data) return null;
2799
+ const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata.data));
2800
+ try {
2801
+ const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
2802
+ const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
2803
+ const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
2804
+ chainId
2805
+ }) : moduleMethodOrFn;
2806
+ try {
2807
+ return [[key, scaleBuilder.buildStorage(module, method)]];
2808
+ } catch (cause) {
2809
+ log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
2810
+ return [];
2811
+ }
2812
+ }));
2813
+ return builtCoders;
2814
+ } catch (cause) {
2815
+ log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
2816
+ }
2817
+ return null;
2818
+ };
2962
2819
 
2963
2820
  /**
2964
2821
  * Decodes & unwraps outputs and errors of a given result, contract, and method.
@@ -3040,7 +2897,7 @@ const detectTransferMethod = metadataRpc => {
3040
2897
  return hasDeprecatedTransferCall ? "transfer" : "transfer_allow_death";
3041
2898
  };
3042
2899
 
3043
- const getUniqueChainIds = (addressesByToken, tokens) => [...new Set(Object.keys(addressesByToken).map(tokenId => tokens[tokenId]?.chain?.id).flatMap(chainId => chainId ? [chainId] : []))];
2900
+ const getUniqueChainIds = (addressesByToken, tokens) => [...new Set(Object.keys(addressesByToken).map(tokenId => tokens[tokenId]?.networkId).flatMap(chainId => chainId ? [chainId] : []))];
3044
2901
 
3045
2902
  const makeContractCaller = ({
3046
2903
  chainConnector,
@@ -3149,8 +3006,15 @@ const decompress = data => {
3149
3006
  return JSON.parse(decompressed);
3150
3007
  };
3151
3008
 
3152
- const moduleType$5 = "substrate-assets";
3153
- const subAssetTokenId = (chainId, assetId, tokenSymbol) => `${chainId}-substrate-assets-${assetId}-${tokenSymbol}`.toLowerCase().replace(/ /g, "-");
3009
+ const moduleType$4 = "substrate-assets";
3010
+ const SubAssetsTokenConfigSchema = z__default.default.strictObject({
3011
+ assetId: chaindataProvider.SubAssetsTokenSchema.shape.assetId,
3012
+ ...TokenConfigBaseSchema.shape
3013
+ });
3014
+ const UNSUPPORTED_CHAIN_META$2 = {
3015
+ miniMetadata: null,
3016
+ extra: null
3017
+ };
3154
3018
  const SubAssetsModule = hydrate => {
3155
3019
  const {
3156
3020
  chainConnectors,
@@ -3159,16 +3023,10 @@ const SubAssetsModule = hydrate => {
3159
3023
  const chainConnector = chainConnectors.substrate;
3160
3024
  util$1.assert(chainConnector, "This module requires a substrate chain connector");
3161
3025
  return {
3162
- ...DefaultBalanceModule(moduleType$5),
3026
+ ...DefaultBalanceModule(moduleType$4),
3027
+ // TODO make synchronous at the module definition level ?
3163
3028
  async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
3164
- const isTestnet = (await chaindataProvider$1.chainById(chainId))?.isTestnet || false;
3165
- if (metadataRpc === undefined) return {
3166
- isTestnet
3167
- };
3168
- if ((moduleConfig?.tokens ?? []).length < 1) return {
3169
- isTestnet
3170
- };
3171
- const metadataVersion = scale.getMetadataVersion(metadataRpc);
3029
+ if (!metadataRpc) return UNSUPPORTED_CHAIN_META$2;
3172
3030
  const metadata = scale.decAnyMetadata(metadataRpc);
3173
3031
  scale.compactMetadata(metadata, [{
3174
3032
  pallet: "Assets",
@@ -3176,82 +3034,98 @@ const SubAssetsModule = hydrate => {
3176
3034
  }]);
3177
3035
  const miniMetadata = scale.encodeMetadata(metadata);
3178
3036
  return {
3179
- isTestnet,
3180
3037
  miniMetadata,
3181
- metadataVersion
3038
+ extra: null
3182
3039
  };
3183
3040
  },
3184
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
3185
- if ((moduleConfig?.tokens ?? []).length < 1) return {};
3041
+ async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
3042
+ if (!tokens?.length) return {};
3186
3043
  const {
3187
- isTestnet,
3188
- miniMetadata,
3189
- metadataVersion
3044
+ miniMetadata
3190
3045
  } = chainMeta;
3191
- if (miniMetadata === undefined || metadataVersion === undefined) return {};
3192
- if (metadataVersion < 14) return {};
3046
+ if (!miniMetadata) return {};
3193
3047
  const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata));
3194
3048
  const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
3195
3049
  const assetCoder = scaleBuilder.buildStorage("Assets", "Asset");
3196
3050
  const metadataCoder = scaleBuilder.buildStorage("Assets", "Metadata");
3197
- const tokens = {};
3198
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
3051
+ const tokenList = {};
3052
+ for (const tokenConfig of tokens ?? []) {
3199
3053
  try {
3200
- const assetId = typeof tokenConfig.assetId === "number" ? tokenConfig.assetId.toString() : tokenConfig.assetId;
3054
+ const assetId = String(tokenConfig.assetId);
3201
3055
  const assetStateKey = tryEncode(assetCoder, BigInt(assetId)) ?? tryEncode(assetCoder, assetId);
3202
3056
  const metadataStateKey = tryEncode(metadataCoder, BigInt(assetId)) ?? tryEncode(metadataCoder, assetId);
3203
3057
  if (assetStateKey === null || metadataStateKey === null) throw new Error(`Failed to encode stateKey for asset ${assetId} on chain ${chainId}`);
3204
3058
  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)]);
3205
3059
  const existentialDeposit = assetsAsset?.min_balance?.toString?.() ?? "0";
3206
3060
  const symbol = assetsMetadata?.symbol?.asText?.() ?? "Unit";
3061
+ const name = assetsMetadata?.name?.asText?.() ?? symbol;
3207
3062
  const decimals = assetsMetadata?.decimals ?? 0;
3208
3063
  const isFrozen = assetsMetadata?.is_frozen ?? false;
3209
- const id = subAssetTokenId(chainId, assetId, symbol);
3064
+ const id = chaindataProvider.subAssetTokenId(chainId, assetId);
3210
3065
  const token = {
3211
3066
  id,
3212
3067
  type: "substrate-assets",
3213
- isTestnet,
3068
+ platform: "polkadot",
3214
3069
  isDefault: tokenConfig?.isDefault ?? true,
3215
- symbol,
3070
+ symbol: tokenConfig?.symbol ?? symbol,
3071
+ name: tokenConfig?.name ?? name,
3216
3072
  decimals,
3217
- logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
3073
+ logo: tokenConfig?.logo,
3218
3074
  existentialDeposit,
3219
3075
  assetId,
3220
3076
  isFrozen,
3221
- chain: {
3222
- id: chainId
3223
- }
3077
+ networkId: chainId
3224
3078
  };
3225
3079
  if (tokenConfig?.symbol) {
3226
3080
  token.symbol = tokenConfig?.symbol;
3227
- token.id = subAssetTokenId(chainId, assetId, token.symbol);
3081
+ token.id = chaindataProvider.subAssetTokenId(chainId, assetId);
3228
3082
  }
3229
3083
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
3230
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
3231
3084
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
3232
- tokens[token.id] = token;
3085
+ tokenList[token.id] = token;
3233
3086
  } catch (error) {
3234
3087
  log.error(`Failed to build substrate-assets token ${tokenConfig.assetId} (${tokenConfig.symbol}) on ${chainId}`, error);
3235
3088
  continue;
3236
3089
  }
3237
3090
  }
3238
- return tokens;
3091
+ return tokenList;
3239
3092
  },
3240
3093
  // TODO: Don't create empty subscriptions
3241
3094
  async subscribeBalances({
3242
3095
  addressesByToken
3243
3096
  }, callback) {
3244
- const queries = await buildQueries$4(chaindataProvider$1, addressesByToken);
3245
- const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
3246
- if (error) return callback(error);
3247
- const balances = result?.filter(b => b !== null) ?? [];
3248
- if (balances.length > 0) callback(null, new Balances(balances));
3249
- });
3250
- return unsubscribe;
3097
+ const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
3098
+ const networkId = chaindataProvider.parseSubAssetTokenId(tokenId).networkId;
3099
+ if (!acc[networkId]) acc[networkId] = {};
3100
+ acc[networkId][tokenId] = addressesByToken[tokenId];
3101
+ return acc;
3102
+ }, {});
3103
+ const controller = new AbortController();
3104
+ const pUnsubs = Promise.all(lodash.toPairs(byNetwork).map(async ([networkId, addressesByToken]) => {
3105
+ try {
3106
+ const queries = await buildNetworkQueries$2(networkId, chainConnector, chaindataProvider$1, addressesByToken, controller.signal);
3107
+ if (controller.signal.aborted) return () => {};
3108
+ const stateHelper = new RpcStateQueryHelper(chainConnector, queries);
3109
+ return await stateHelper.subscribe((error, result) => {
3110
+ if (error) return callback(error);
3111
+ const balances = result?.filter(b => b !== null) ?? [];
3112
+ if (balances.length > 0) callback(null, new Balances(balances));
3113
+ });
3114
+ } catch (err) {
3115
+ if (!util.isAbortError(err)) log.error(`Failed to subscribe balances for network ${networkId}`, err);
3116
+ return () => {};
3117
+ }
3118
+ }));
3119
+ return () => {
3120
+ pUnsubs.then(unsubs => {
3121
+ unsubs.forEach(unsubscribe => unsubscribe());
3122
+ });
3123
+ controller.abort();
3124
+ };
3251
3125
  },
3252
3126
  async fetchBalances(addressesByToken) {
3253
3127
  util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
3254
- const queries = await buildQueries$4(chaindataProvider$1, addressesByToken);
3128
+ const queries = await buildQueries$3(chainConnector, chaindataProvider$1, addressesByToken);
3255
3129
  const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
3256
3130
  const balances = result?.filter(b => b !== null) ?? [];
3257
3131
  return new Balances(balances);
@@ -3272,11 +3146,10 @@ const SubAssetsModule = hydrate => {
3272
3146
  transferMethod,
3273
3147
  userExtensions
3274
3148
  }) {
3275
- const token = await chaindataProvider$1.tokenById(tokenId);
3149
+ const token = await chaindataProvider$1.getTokenById(tokenId, "substrate-assets");
3276
3150
  util$1.assert(token, `Token ${tokenId} not found in store`);
3277
- if (token.type !== "substrate-assets") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
3278
- const chainId = token.chain.id;
3279
- const chain = await chaindataProvider$1.chainById(chainId);
3151
+ const chainId = token.networkId;
3152
+ const chain = await chaindataProvider$1.getNetworkById(chainId, "polkadot");
3280
3153
  util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
3281
3154
  const {
3282
3155
  genesisHash
@@ -3321,23 +3194,16 @@ const SubAssetsModule = hydrate => {
3321
3194
  }
3322
3195
  };
3323
3196
  };
3324
- async function buildQueries$4(chaindataProvider, addressesByToken) {
3325
- const allChains = await chaindataProvider.chainsById();
3326
- const tokens = await chaindataProvider.tokensById();
3327
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
3328
- const uniqueChainIds = getUniqueChainIds(addressesByToken, tokens);
3329
- const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
3330
- const chainStorageCoders = buildStorageCoders({
3331
- chainIds: uniqueChainIds,
3332
- chains,
3333
- miniMetadatas,
3334
- moduleType: "substrate-assets",
3335
- coders: {
3336
- storage: ["Assets", "Account"]
3337
- }
3197
+ async function buildNetworkQueries$2(networkId, chainConnector, chaindataProvider, addressesByToken, signal) {
3198
+ const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType$4, signal);
3199
+ const chain = await chaindataProvider.getNetworkById(networkId, "polkadot");
3200
+ const tokensById = await chaindataProvider.getTokensMapById();
3201
+ signal?.throwIfAborted();
3202
+ const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
3203
+ storage: ["Assets", "Account"]
3338
3204
  });
3339
3205
  return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
3340
- const token = tokens[tokenId];
3206
+ const token = tokensById[tokenId];
3341
3207
  if (!token) {
3342
3208
  log.warn(`Token ${tokenId} not found`);
3343
3209
  return [];
@@ -3346,27 +3212,22 @@ async function buildQueries$4(chaindataProvider, addressesByToken) {
3346
3212
  log.debug(`This module doesn't handle tokens of type ${token.type}`);
3347
3213
  return [];
3348
3214
  }
3349
- const chainId = token.chain?.id;
3350
- if (!chainId) {
3351
- log.warn(`Token ${tokenId} has no chain`);
3352
- return [];
3353
- }
3354
- const chain = chains[chainId];
3215
+ //
3355
3216
  if (!chain) {
3356
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
3217
+ log.warn(`Chain ${networkId} for token ${tokenId} not found`);
3357
3218
  return [];
3358
3219
  }
3359
3220
  return addresses.flatMap(address => {
3360
- const scaleCoder = chainStorageCoders.get(chainId)?.storage;
3361
- const stateKey = tryEncode(scaleCoder, BigInt(token.assetId), address) ?? tryEncode(scaleCoder, token.assetId, address);
3221
+ const scaleCoder = networkStorageCoders?.storage;
3222
+ const stateKey = tryEncode(scaleCoder, Number(token.assetId), address) ?? tryEncode(scaleCoder, BigInt(token.assetId), address);
3362
3223
  if (!stateKey) {
3363
- log.warn(`Invalid assetId / address in ${chainId} storage query ${token.assetId} / ${address}`);
3224
+ log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
3364
3225
  return [];
3365
3226
  }
3366
3227
  const decodeResult = change => {
3367
3228
  /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3368
3229
 
3369
- const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${chainId}`) ?? {
3230
+ const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
3370
3231
  balance: 0n,
3371
3232
  status: {
3372
3233
  type: "Liquid"
@@ -3400,22 +3261,30 @@ async function buildQueries$4(chaindataProvider, addressesByToken) {
3400
3261
  source: "substrate-assets",
3401
3262
  status: "live",
3402
3263
  address,
3403
- multiChainId: {
3404
- subChainId: chainId
3405
- },
3406
- chainId,
3264
+ networkId,
3407
3265
  tokenId: token.id,
3408
3266
  values: balanceValues
3409
3267
  };
3410
3268
  };
3411
3269
  return {
3412
- chainId,
3270
+ chainId: networkId,
3413
3271
  stateKey,
3414
3272
  decodeResult
3415
3273
  };
3416
3274
  });
3417
3275
  });
3418
3276
  }
3277
+ async function buildQueries$3(chainConnector, chaindataProvider$1, addressesByToken, signal) {
3278
+ const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
3279
+ const networkId = chaindataProvider.parseSubAssetTokenId(tokenId).networkId;
3280
+ if (!acc[networkId]) acc[networkId] = {};
3281
+ acc[networkId][tokenId] = addressesByToken[tokenId];
3282
+ return acc;
3283
+ }, {});
3284
+ return (await Promise.all(lodash.toPairs(byNetwork).map(([networkId, addressesByToken]) => {
3285
+ return buildNetworkQueries$2(networkId, chainConnector, chaindataProvider$1, addressesByToken, signal);
3286
+ }))).flat();
3287
+ }
3419
3288
  // NOTE: Different chains need different formats for assetId when encoding the stateKey
3420
3289
  // E.g. Polkadot Asset Hub needs it to be a string, Astar needs it to be a bigint
3421
3290
  //
@@ -3428,9 +3297,16 @@ const tryEncode = (scaleCoder, ...args) => {
3428
3297
  }
3429
3298
  };
3430
3299
 
3431
- const moduleType$4 = "substrate-equilibrium";
3432
- const subEquilibriumTokenId = (chainId, tokenSymbol) => `${chainId}-substrate-equilibrium-${tokenSymbol}`.toLowerCase().replace(/ /g, "-");
3433
- const SubEquilibriumModule = hydrate => {
3300
+ const moduleType$3 = "substrate-foreignassets";
3301
+ const UNSUPPORTED_CHAIN_META$1 = {
3302
+ miniMetadata: null,
3303
+ extra: null
3304
+ };
3305
+ const SubForeignAssetsTokenConfigSchema = z__default.default.strictObject({
3306
+ onChainId: chaindataProvider.SubForeignAssetsTokenSchema.shape.onChainId,
3307
+ ...TokenConfigBaseSchema.shape
3308
+ });
3309
+ const SubForeignAssetsModule = hydrate => {
3434
3310
  const {
3435
3311
  chainConnectors,
3436
3312
  chaindataProvider: chaindataProvider$1
@@ -3438,312 +3314,35 @@ const SubEquilibriumModule = hydrate => {
3438
3314
  const chainConnector = chainConnectors.substrate;
3439
3315
  util$1.assert(chainConnector, "This module requires a substrate chain connector");
3440
3316
  return {
3441
- ...DefaultBalanceModule(moduleType$4),
3317
+ ...DefaultBalanceModule(moduleType$3),
3442
3318
  async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
3443
- const isTestnet = (await chaindataProvider$1.chainById(chainId))?.isTestnet || false;
3444
- if (metadataRpc === undefined) return {
3445
- isTestnet
3446
- };
3447
- if (moduleConfig?.disable !== false) return {
3448
- isTestnet
3449
- }; // default to disabled
3450
-
3319
+ if (metadataRpc === undefined) return UNSUPPORTED_CHAIN_META$1;
3451
3320
  const metadataVersion = scale.getMetadataVersion(metadataRpc);
3321
+ if (metadataVersion < 14) return UNSUPPORTED_CHAIN_META$1;
3452
3322
  const metadata = scale.decAnyMetadata(metadataRpc);
3453
3323
  scale.compactMetadata(metadata, [{
3454
- pallet: "EqAssets",
3455
- items: ["Assets"]
3456
- }, {
3457
- pallet: "System",
3458
- items: ["Account"]
3324
+ pallet: "ForeignAssets",
3325
+ items: ["Account", "Asset", "Metadata"]
3459
3326
  }]);
3460
3327
  const miniMetadata = scale.encodeMetadata(metadata);
3461
3328
  return {
3462
- isTestnet,
3463
3329
  miniMetadata,
3464
- metadataVersion
3330
+ extra: null
3465
3331
  };
3466
3332
  },
3467
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
3468
- // default to disabled
3469
- if (moduleConfig?.disable !== false) return {};
3333
+ async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
3334
+ if (!tokens?.length) return {};
3470
3335
  const {
3471
- isTestnet,
3472
- miniMetadata,
3473
- metadataVersion
3336
+ miniMetadata
3474
3337
  } = chainMeta;
3475
- if (miniMetadata === undefined || metadataVersion === undefined) return {};
3476
- if (metadataVersion < 14) return {};
3477
- try {
3478
- const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata));
3479
- const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
3480
- const assetsCoder = scaleBuilder.buildStorage("EqAssets", "Assets");
3481
- const stateKey = assetsCoder.keys.enc();
3482
-
3483
- /** NOTE: Just a guideline, the RPC can return whatever it wants */
3484
-
3485
- const assetsResult = await chainConnector.send(chainId, "state_getStorage", [stateKey]).then(result => assetsCoder.value.dec(result) ?? null);
3486
- const tokens = (Array.isArray(assetsResult) ? assetsResult : []).flatMap(asset => {
3487
- if (!asset) return [];
3488
- if (!asset?.id) return [];
3489
- const assetId = asset.id.toString(10);
3490
- const symbol = tokenSymbolFromU64Id(asset.id);
3491
- const id = subEquilibriumTokenId(chainId, symbol);
3492
- const decimals = DEFAULT_DECIMALS$1;
3493
- const tokenConfig = (moduleConfig?.tokens ?? []).find(token => token.assetId === assetId);
3494
- const token = {
3495
- id,
3496
- type: "substrate-equilibrium",
3497
- isTestnet,
3498
- isDefault: tokenConfig?.isDefault ?? true,
3499
- symbol,
3500
- decimals,
3501
- logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
3502
- // TODO: Fetch the ED
3503
- existentialDeposit: "0",
3504
- assetId,
3505
- chain: {
3506
- id: chainId
3507
- }
3508
- };
3509
- if (tokenConfig?.symbol) {
3510
- token.symbol = tokenConfig?.symbol;
3511
- token.id = subEquilibriumTokenId(chainId, token.symbol);
3512
- }
3513
- if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
3514
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
3515
- if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
3516
- return [[token.id, token]];
3517
- });
3518
- return Object.fromEntries(tokens);
3519
- } catch (error) {
3520
- log.error(`Failed to build substrate-equilibrium tokens on ${chainId}`, error?.message ?? error);
3521
- return {};
3522
- }
3523
- },
3524
- // TODO: Don't create empty subscriptions
3525
- async subscribeBalances({
3526
- addressesByToken
3527
- }, callback) {
3528
- const queries = await buildQueries$3(chaindataProvider$1, addressesByToken);
3529
- const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
3530
- if (error) return callback(error);
3531
- const balances = result?.flatMap(balances => balances) ?? [];
3532
- if (balances.length > 0) callback(null, new Balances(balances));
3533
- });
3534
- return unsubscribe;
3535
- },
3536
- async fetchBalances(addressesByToken) {
3537
- util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
3538
- const queries = await buildQueries$3(chaindataProvider$1, addressesByToken);
3539
- const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
3540
- const balances = result?.flatMap(balances => balances) ?? [];
3541
- return new Balances(balances);
3542
- },
3543
- async transferToken({
3544
- tokenId,
3545
- from,
3546
- to,
3547
- amount,
3548
- registry,
3549
- metadataRpc,
3550
- blockHash,
3551
- blockNumber,
3552
- nonce,
3553
- specVersion,
3554
- transactionVersion,
3555
- tip,
3556
- transferMethod,
3557
- userExtensions
3558
- }) {
3559
- const token = await chaindataProvider$1.tokenById(tokenId);
3560
- util$1.assert(token, `Token ${tokenId} not found in store`);
3561
- if (token.type !== "substrate-equilibrium") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
3562
- const chainId = token.chain.id;
3563
- const chain = await chaindataProvider$1.chainById(chainId);
3564
- util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
3565
- const {
3566
- genesisHash
3567
- } = chain;
3568
- const {
3569
- assetId
3570
- } = token;
3571
- const pallet = "EqBalances";
3572
- const method = transferMethod === "transfer_all" ?
3573
- // the eqBalances pallet has no transfer_all method
3574
- "transfer" : transferMethod === "transfer_keep_alive" ?
3575
- // the eqBalances pallet has no transfer_keep_alive method
3576
- "transfer" : "transfer";
3577
- const args = {
3578
- asset: assetId,
3579
- to,
3580
- value: amount
3581
- };
3582
- const unsigned = txwrapperCore.defineMethod({
3583
- method: {
3584
- pallet: camelCase__default.default(pallet),
3585
- name: camelCase__default.default(method),
3586
- args
3587
- },
3588
- address: from,
3589
- blockHash,
3590
- blockNumber,
3591
- eraPeriod: 64,
3592
- genesisHash,
3593
- metadataRpc,
3594
- nonce,
3595
- specVersion,
3596
- tip: tip ? Number(tip) : 0,
3597
- transactionVersion
3598
- }, {
3599
- metadataRpc,
3600
- registry,
3601
- userExtensions
3602
- });
3603
- return {
3604
- type: "substrate",
3605
- callData: unsigned.method
3606
- };
3607
- }
3608
- };
3609
- };
3610
- async function buildQueries$3(chaindataProvider, addressesByToken) {
3611
- const allChains = await chaindataProvider.chainsById();
3612
- const tokens = await chaindataProvider.tokensById();
3613
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
3614
- const uniqueChainIds = getUniqueChainIds(addressesByToken, tokens);
3615
- const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
3616
- const chainStorageCoders = buildStorageCoders({
3617
- chainIds: uniqueChainIds,
3618
- chains,
3619
- miniMetadatas,
3620
- moduleType: "substrate-equilibrium",
3621
- coders: {
3622
- storage: ["System", "Account"]
3623
- }
3624
- });
3625
-
3626
- // equilibrium returns all chain tokens for each address in the one query
3627
- // so, we only need to make one query per address, rather than one query per token per address
3628
- const addressesByChain = new Map();
3629
- const tokensByAddress = new Map();
3630
- Object.entries(addressesByToken).map(([tokenId, addresses]) => {
3631
- const token = tokens[tokenId];
3632
- if (!token) return log.warn(`Token ${tokenId} not found`);
3633
- if (token.type !== "substrate-equilibrium") return log.debug(`This module doesn't handle tokens of type ${token.type}`);
3634
- const chainId = token?.chain?.id;
3635
- if (!chainId) return log.warn(`Token ${tokenId} has no chain`);
3636
- const byChain = addressesByChain.get(chainId) ?? new Set();
3637
- addresses.forEach(address => {
3638
- byChain?.add(address);
3639
- tokensByAddress.set(address, (tokensByAddress.get(address) ?? new Set()).add(token));
3640
- });
3641
- addressesByChain.set(chainId, byChain);
3642
- });
3643
- return Array.from(addressesByChain).flatMap(([chainId, addresses]) => {
3644
- const chain = chains[chainId];
3645
- if (!chain) {
3646
- log.warn(`Chain ${chainId} not found`);
3647
- return [];
3648
- }
3649
- return Array.from(addresses).flatMap(address => {
3650
- const scaleCoder = chainStorageCoders.get(chainId)?.storage;
3651
- const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address in ${chainId} storage query ${address}`, address);
3652
- if (!stateKey) return [];
3653
- const decodeResult = change => {
3654
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3655
-
3656
- const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode eqBalances on chain ${chainId}`);
3657
- const tokenBalances = Object.fromEntries((decoded?.data?.value?.balance ?? []).map(balance => ({
3658
- id: (balance?.[0] ?? 0n)?.toString?.(),
3659
- free: balance?.[1]?.type === "Positive" ? (balance?.[1]?.value ?? 0n).toString() : balance?.[1]?.type === "Negative" ? ((balance?.[1]?.value ?? 0n) * -1n).toString() : "0"
3660
- })).map(({
3661
- id,
3662
- free
3663
- }) => [id, free]).filter(([id, free]) => id !== undefined && free !== undefined));
3664
- const result = Array.from(tokensByAddress.get(address) ?? []).filter(t => t.chain.id === chainId).map(token => {
3665
- const value = tokenBalances[token.assetId];
3666
- return {
3667
- source: "substrate-equilibrium",
3668
- status: "live",
3669
- address,
3670
- multiChainId: {
3671
- subChainId: chainId
3672
- },
3673
- chainId,
3674
- tokenId: token.id,
3675
- value
3676
- };
3677
- }).filter(b => b !== undefined);
3678
- return result;
3679
- };
3680
- return {
3681
- chainId,
3682
- stateKey,
3683
- decodeResult
3684
- };
3685
- });
3686
- });
3687
- }
3688
- const DEFAULT_DECIMALS$1 = 9;
3689
- const tokenSymbolFromU64Id = u64 => {
3690
- const bytes = [];
3691
- let num = typeof u64 === "number" ? BigInt(u64) : util.isBigInt(u64) ? u64 : u64.toBigInt();
3692
- do {
3693
- bytes.unshift(Number(num % 256n));
3694
- num = num / 256n;
3695
- } while (num > 0);
3696
- return new TextDecoder("utf-8").decode(new Uint8Array(bytes)).toUpperCase();
3697
- };
3698
-
3699
- const moduleType$3 = "substrate-foreignassets";
3700
- const subForeignAssetTokenId = (chainId, tokenSymbol) => `${chainId}-substrate-foreignassets-${tokenSymbol}`.toLowerCase().replace(/ /g, "-");
3701
- const SubForeignAssetsModule = hydrate => {
3702
- const {
3703
- chainConnectors,
3704
- chaindataProvider: chaindataProvider$1
3705
- } = hydrate;
3706
- const chainConnector = chainConnectors.substrate;
3707
- util$1.assert(chainConnector, "This module requires a substrate chain connector");
3708
- return {
3709
- ...DefaultBalanceModule(moduleType$3),
3710
- async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
3711
- const isTestnet = (await chaindataProvider$1.chainById(chainId))?.isTestnet || false;
3712
- if (metadataRpc === undefined) return {
3713
- isTestnet
3714
- };
3715
- if ((moduleConfig?.tokens ?? []).length < 1) return {
3716
- isTestnet
3717
- };
3718
- const metadataVersion = scale.getMetadataVersion(metadataRpc);
3719
- const metadata = scale.decAnyMetadata(metadataRpc);
3720
- scale.compactMetadata(metadata, [{
3721
- pallet: "ForeignAssets",
3722
- items: ["Account", "Asset", "Metadata"]
3723
- }]);
3724
- const miniMetadata = scale.encodeMetadata(metadata);
3725
- return {
3726
- isTestnet,
3727
- miniMetadata,
3728
- metadataVersion
3729
- };
3730
- },
3731
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
3732
- if ((moduleConfig?.tokens ?? []).length < 1) return {};
3733
- const {
3734
- isTestnet,
3735
- miniMetadata,
3736
- metadataVersion
3737
- } = chainMeta;
3738
- if (miniMetadata === undefined || metadataVersion === undefined) return {};
3739
- if (metadataVersion < 14) return {};
3338
+ if (!miniMetadata) return {};
3740
3339
  const metadata = scale.decAnyMetadata(miniMetadata);
3741
3340
  const unifiedMetadata = scale.unifyMetadata(metadata);
3742
3341
  const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(unifiedMetadata));
3743
3342
  const assetCoder = scaleBuilder.buildStorage("ForeignAssets", "Asset");
3744
3343
  const metadataCoder = scaleBuilder.buildStorage("ForeignAssets", "Metadata");
3745
- const tokens = {};
3746
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
3344
+ const tokenList = {};
3345
+ for (const tokenConfig of tokens ?? []) {
3747
3346
  try {
3748
3347
  const onChainId = (() => {
3749
3348
  try {
@@ -3758,54 +3357,70 @@ const SubForeignAssetsModule = hydrate => {
3758
3357
  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)]);
3759
3358
  const existentialDeposit = assetsAsset?.min_balance?.toString?.() ?? "0";
3760
3359
  const symbol = assetsMetadata?.symbol?.asText?.() ?? "Unit";
3360
+ const name = assetsMetadata?.name?.asText?.() ?? symbol;
3761
3361
  const decimals = assetsMetadata?.decimals ?? 0;
3762
3362
  const isFrozen = assetsMetadata?.is_frozen ?? false;
3763
- const id = subForeignAssetTokenId(chainId, symbol);
3363
+ const id = chaindataProvider.subForeignAssetTokenId(chainId, tokenConfig.onChainId);
3764
3364
  const token = {
3765
3365
  id,
3766
3366
  type: "substrate-foreignassets",
3767
- isTestnet,
3367
+ platform: "polkadot",
3768
3368
  isDefault: tokenConfig?.isDefault ?? true,
3769
3369
  symbol,
3770
3370
  decimals,
3771
- logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
3371
+ name: tokenConfig?.name ?? name,
3372
+ logo: tokenConfig?.logo,
3772
3373
  existentialDeposit,
3773
3374
  onChainId: tokenConfig.onChainId,
3774
3375
  isFrozen,
3775
- chain: {
3776
- id: chainId
3777
- }
3376
+ networkId: chainId
3778
3377
  };
3779
- if (tokenConfig?.symbol) {
3780
- token.symbol = tokenConfig?.symbol;
3781
- token.id = subForeignAssetTokenId(chainId, token.symbol);
3782
- }
3783
3378
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
3784
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
3785
3379
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
3786
- tokens[token.id] = token;
3380
+ tokenList[token.id] = token;
3787
3381
  } catch (error) {
3788
3382
  log.error(`Failed to build substrate-foreignassets token ${tokenConfig.onChainId} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
3789
3383
  continue;
3790
3384
  }
3791
3385
  }
3792
- return tokens;
3386
+ return tokenList;
3793
3387
  },
3794
3388
  // TODO: Don't create empty subscriptions
3795
3389
  async subscribeBalances({
3796
3390
  addressesByToken
3797
3391
  }, callback) {
3798
- const queries = await buildQueries$2(chaindataProvider$1, addressesByToken);
3799
- const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
3800
- if (error) return callback(error);
3801
- const balances = result?.filter(b => b !== null) ?? [];
3802
- if (balances.length > 0) callback(null, new Balances(balances));
3803
- });
3804
- return unsubscribe;
3392
+ const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
3393
+ const networkId = chaindataProvider.parseSubForeignAssetTokenId(tokenId).networkId;
3394
+ if (!acc[networkId]) acc[networkId] = {};
3395
+ acc[networkId][tokenId] = addressesByToken[tokenId];
3396
+ return acc;
3397
+ }, {});
3398
+ const controller = new AbortController();
3399
+ const pUnsubs = Promise.all(lodash.toPairs(byNetwork).map(async ([networkId, addressesByToken]) => {
3400
+ try {
3401
+ const queries = await buildNetworkQueries$1(networkId, chainConnector, chaindataProvider$1, addressesByToken, controller.signal);
3402
+ if (controller.signal.aborted) return () => {};
3403
+ const stateHelper = new RpcStateQueryHelper(chainConnector, queries);
3404
+ return await stateHelper.subscribe((error, result) => {
3405
+ if (error) return callback(error);
3406
+ const balances = result?.filter(b => b !== null) ?? [];
3407
+ if (balances.length > 0) callback(null, new Balances(balances));
3408
+ });
3409
+ } catch (err) {
3410
+ if (!util.isAbortError(err)) log.error(`Failed to subscribe ${moduleType$3} balances for network ${networkId}`, err);
3411
+ return () => {};
3412
+ }
3413
+ }));
3414
+ return () => {
3415
+ pUnsubs.then(unsubs => {
3416
+ unsubs.forEach(unsubscribe => unsubscribe());
3417
+ });
3418
+ controller.abort();
3419
+ };
3805
3420
  },
3806
3421
  async fetchBalances(addressesByToken) {
3807
3422
  util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
3808
- const queries = await buildQueries$2(chaindataProvider$1, addressesByToken);
3423
+ const queries = await buildQueries$2(chainConnector, chaindataProvider$1, addressesByToken);
3809
3424
  const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
3810
3425
  const balances = result?.filter(b => b !== null) ?? [];
3811
3426
  return new Balances(balances);
@@ -3817,11 +3432,10 @@ const SubForeignAssetsModule = hydrate => {
3817
3432
  transferMethod,
3818
3433
  metadataRpc
3819
3434
  }) {
3820
- const token = await chaindataProvider$1.tokenById(tokenId);
3435
+ const token = await chaindataProvider$1.getTokenById(tokenId, "substrate-foreignassets");
3821
3436
  util$1.assert(token, `Token ${tokenId} not found in store`);
3822
- if (token.type !== "substrate-foreignassets") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
3823
- const chainId = token.chain.id;
3824
- const chain = await chaindataProvider$1.chainById(chainId);
3437
+ const chainId = token.networkId;
3438
+ const chain = await chaindataProvider$1.getNetworkById(chainId, "polkadot");
3825
3439
  util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
3826
3440
  const onChainId = (() => {
3827
3441
  try {
@@ -3861,23 +3475,16 @@ const SubForeignAssetsModule = hydrate => {
3861
3475
  }
3862
3476
  };
3863
3477
  };
3864
- async function buildQueries$2(chaindataProvider, addressesByToken) {
3865
- const allChains = await chaindataProvider.chainsById();
3866
- const tokens = await chaindataProvider.tokensById();
3867
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
3868
- const uniqueChainIds = getUniqueChainIds(addressesByToken, tokens);
3869
- const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
3870
- const chainStorageCoders = buildStorageCoders({
3871
- chainIds: uniqueChainIds,
3872
- chains,
3873
- miniMetadatas,
3874
- moduleType: "substrate-foreignassets",
3875
- coders: {
3876
- storage: ["ForeignAssets", "Account"]
3877
- }
3478
+ async function buildNetworkQueries$1(networkId, chainConnector, chaindataProvider, addressesByToken, signal) {
3479
+ const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType$3, signal);
3480
+ const chain = await chaindataProvider.getNetworkById(networkId, "polkadot");
3481
+ const tokensById = await chaindataProvider.getTokensMapById();
3482
+ signal?.throwIfAborted();
3483
+ const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
3484
+ storage: ["ForeignAssets", "Account"]
3878
3485
  });
3879
3486
  return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
3880
- const token = tokens[tokenId];
3487
+ const token = tokensById[tokenId];
3881
3488
  if (!token) {
3882
3489
  log.warn(`Token ${tokenId} not found`);
3883
3490
  return [];
@@ -3886,18 +3493,12 @@ async function buildQueries$2(chaindataProvider, addressesByToken) {
3886
3493
  log.debug(`This module doesn't handle tokens of type ${token.type}`);
3887
3494
  return [];
3888
3495
  }
3889
- const chainId = token.chain?.id;
3890
- if (!chainId) {
3891
- log.warn(`Token ${tokenId} has no chain`);
3892
- return [];
3893
- }
3894
- const chain = chains[chainId];
3895
3496
  if (!chain) {
3896
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
3497
+ log.warn(`Chain ${networkId} for token ${tokenId} not found`);
3897
3498
  return [];
3898
3499
  }
3899
3500
  return addresses.flatMap(address => {
3900
- const scaleCoder = chainStorageCoders.get(chainId)?.storage;
3501
+ const scaleCoder = networkStorageCoders?.storage;
3901
3502
  const onChainId = (() => {
3902
3503
  try {
3903
3504
  return scale.papiParse(token.onChainId);
@@ -3905,12 +3506,12 @@ async function buildQueries$2(chaindataProvider, addressesByToken) {
3905
3506
  return token.onChainId;
3906
3507
  }
3907
3508
  })();
3908
- const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${chainId} storage query ${address} / ${token.onChainId}`, onChainId, address);
3509
+ const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${networkId} storage query ${address} / ${token.onChainId}`, onChainId, address);
3909
3510
  if (!stateKey) return [];
3910
3511
  const decodeResult = change => {
3911
3512
  /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3912
3513
 
3913
- const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-foreignassets balance on chain ${chainId}`) ?? {
3514
+ const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-foreignassets balance on chain ${networkId}`) ?? {
3914
3515
  balance: 0n,
3915
3516
  status: {
3916
3517
  type: "Liquid"
@@ -3944,29 +3545,54 @@ async function buildQueries$2(chaindataProvider, addressesByToken) {
3944
3545
  source: "substrate-foreignassets",
3945
3546
  status: "live",
3946
3547
  address,
3947
- multiChainId: {
3948
- subChainId: chainId
3949
- },
3950
- chainId,
3548
+ networkId,
3951
3549
  tokenId: token.id,
3952
3550
  values: balanceValues
3953
3551
  };
3954
3552
  };
3955
3553
  return {
3956
- chainId,
3554
+ chainId: networkId,
3957
3555
  stateKey,
3958
3556
  decodeResult
3959
3557
  };
3960
3558
  });
3961
3559
  });
3962
3560
  }
3561
+ async function buildQueries$2(chainConnector, chaindataProvider$1, addressesByToken, signal) {
3562
+ const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
3563
+ const networkId = chaindataProvider.parseSubForeignAssetTokenId(tokenId).networkId;
3564
+ if (!acc[networkId]) acc[networkId] = {};
3565
+ acc[networkId][tokenId] = addressesByToken[tokenId];
3566
+ return acc;
3567
+ }, {});
3568
+ return (await Promise.all(lodash.toPairs(byNetwork).map(([networkId, addressesByToken]) => {
3569
+ return buildNetworkQueries$1(networkId, chainConnector, chaindataProvider$1, addressesByToken, signal);
3570
+ }))).flat();
3571
+ }
3572
+
3573
+ const getAddresssesByTokenByNetwork = addressesByToken => {
3574
+ const addressesByTokenByNetwork = lodash.toPairs(addressesByToken).reduce((acc, [tokenId, addresses]) => {
3575
+ const networkId = chaindataProvider.parseTokenId(tokenId).networkId;
3576
+ if (!acc[networkId]) acc[networkId] = {};
3577
+ acc[networkId][tokenId] = addresses;
3578
+ return acc;
3579
+ }, {});
3580
+ return addressesByTokenByNetwork;
3581
+ };
3963
3582
 
3964
3583
  async function subscribeBase(queries, chainConnector, callback) {
3965
- const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
3966
- if (error) callback(error);
3967
- if (result && result.length > 0) callback(null, result);
3968
- });
3969
- return unsubscribe;
3584
+ try {
3585
+ const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
3586
+ if (error) callback(error);
3587
+ if (result && result.length > 0) callback(null, result);
3588
+ });
3589
+ return unsubscribe;
3590
+ } catch (err) {
3591
+ if (!util.isAbortError(err)) log.error("Error subscribing to base queries", {
3592
+ err
3593
+ });
3594
+ return () => {};
3595
+ }
3970
3596
  }
3971
3597
 
3972
3598
  /**
@@ -3982,262 +3608,6 @@ const asObservable = handler => (...args) => new rxjs.Observable(subscriber => {
3982
3608
  return unsubscribe;
3983
3609
  });
3984
3610
 
3985
- /**
3986
- * Crowdloan contributions are stored in the `childstate` key returned by this function.
3987
- */
3988
- const crowdloanFundContributionsChildKey = fundIndex => util$1.u8aToHex(util$1.u8aConcat(":child_storage:default:", utilCrypto.blake2AsU8a(util$1.u8aConcat("crowdloan", scaleTs.u32.enc(fundIndex)))));
3989
-
3990
- async function subscribeCrowdloans(chaindataProvider, chainConnector, addressesByToken, callback) {
3991
- const allChains = await chaindataProvider.chainsById();
3992
- const tokens = await chaindataProvider.tokensById();
3993
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
3994
- const crowdloanTokenIds = Object.entries(tokens).filter(([, token]) => {
3995
- // ignore non-native tokens
3996
- if (token.type !== "substrate-native") return;
3997
- // ignore tokens on chains with no crowdloans pallet
3998
- const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", allChains[token.chain.id]);
3999
- return typeof chainMeta?.crowdloanPalletId === "string";
4000
- }).map(([tokenId]) => tokenId);
4001
-
4002
- // crowdloan contributions can only be done by the native token on chains with the crowdloan pallet
4003
- const addressesByCrowdloanToken = Object.fromEntries(Object.entries(addressesByToken)
4004
- // remove ethereum addresses
4005
- .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !util.isEthereumAddress(address))])
4006
- // remove tokens which aren't crowdloan tokens
4007
- .filter(([tokenId]) => crowdloanTokenIds.includes(tokenId)));
4008
- const uniqueChainIds = getUniqueChainIds(addressesByCrowdloanToken, tokens);
4009
- const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
4010
- const chainStorageCoders = buildStorageCoders({
4011
- chainIds: uniqueChainIds,
4012
- chains,
4013
- miniMetadatas,
4014
- moduleType: "substrate-native",
4015
- coders: {
4016
- parachains: ["Paras", "Parachains"],
4017
- funds: ["Crowdloan", "Funds"]
4018
- }
4019
- });
4020
- const tokenSubscriptions = [];
4021
- for (const [tokenId, addresses] of Object.entries(addressesByCrowdloanToken)) {
4022
- const token = tokens[tokenId];
4023
- if (!token) {
4024
- log.warn(`Token ${tokenId} not found`);
4025
- continue;
4026
- }
4027
- if (token.type !== "substrate-native") {
4028
- log.debug(`This module doesn't handle tokens of type ${token.type}`);
4029
- continue;
4030
- }
4031
- const chainId = token.chain?.id;
4032
- if (!chainId) {
4033
- log.warn(`Token ${tokenId} has no chain`);
4034
- continue;
4035
- }
4036
- const chain = chains[chainId];
4037
- if (!chain) {
4038
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
4039
- continue;
4040
- }
4041
- const subscribeParaIds = callback => {
4042
- const scaleCoder = chainStorageCoders.get(chainId)?.parachains;
4043
- const queries = [0].flatMap(() => {
4044
- const stateKey = scale.encodeStateKey(scaleCoder);
4045
- if (!stateKey) return [];
4046
- const decodeResult = change => {
4047
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4048
-
4049
- const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode parachains on chain ${chainId}`);
4050
- const paraIds = decoded ?? [];
4051
- return paraIds;
4052
- };
4053
- return {
4054
- chainId,
4055
- stateKey,
4056
- decodeResult
4057
- };
4058
- });
4059
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
4060
- return () => subscription.then(unsubscribe => unsubscribe());
4061
- };
4062
- const subscribeParaFundIndexes = (paraIds, callback) => {
4063
- const scaleCoder = chainStorageCoders.get(chainId)?.funds;
4064
- const queries = paraIds.flatMap(paraId => {
4065
- const stateKey = scale.encodeStateKey(scaleCoder, `Invalid paraId in ${chainId} funds query ${paraId}`, paraId);
4066
- if (!stateKey) return [];
4067
- const decodeResult = change => {
4068
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4069
-
4070
- const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode paras on chain ${chainId}`);
4071
- const firstPeriod = decoded?.first_period?.toString?.() ?? "";
4072
- const lastPeriod = decoded?.last_period?.toString?.() ?? "";
4073
- const fundPeriod = `${firstPeriod}-${lastPeriod}`;
4074
- const fundIndex = decoded?.fund_index ?? decoded?.trie_index;
4075
- return {
4076
- paraId,
4077
- fundPeriod,
4078
- fundIndex
4079
- };
4080
- };
4081
- return {
4082
- chainId,
4083
- stateKey,
4084
- decodeResult
4085
- };
4086
- });
4087
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
4088
- return () => subscription.then(unsubscribe => unsubscribe());
4089
- };
4090
- const subscribeFundContributions = (funds, addresses, callback) => {
4091
- // TODO: Watch system_events in order to subscribe to changes, then redo the contributions query when changes are detected:
4092
- // https://github.com/polkadot-js/api/blob/8fe02a14345b57e6abb8f7f2c2b624cf70c51b23/packages/api-derive/src/crowdloan/ownContributions.ts#L32-L47
4093
- //
4094
- // For now we just re-fetch all contributions on a timer and then only send them to the subscription callback when they have changed
4095
-
4096
- const queries = funds.map(({
4097
- paraId,
4098
- fundIndex
4099
- }) => ({
4100
- paraId,
4101
- fundIndex,
4102
- addresses,
4103
- childKey: crowdloanFundContributionsChildKey(fundIndex),
4104
- storageKeys: addresses.map(address => util$1.u8aToHex(util.decodeAnyAddress(address)))
4105
- }));
4106
-
4107
- // track whether our caller is still subscribed
4108
- let subscriptionActive = true;
4109
- let previousContributions = null;
4110
- const fetchContributions = async () => {
4111
- try {
4112
- const results = await Promise.all(queries.map(async ({
4113
- paraId,
4114
- fundIndex,
4115
- addresses,
4116
- childKey,
4117
- storageKeys
4118
- }) => ({
4119
- paraId,
4120
- fundIndex,
4121
- addresses,
4122
- result: await chainConnector.send(chainId, "childstate_getStorageEntries", [childKey, storageKeys])
4123
- })));
4124
- const contributions = results.flatMap(queryResult => {
4125
- const {
4126
- paraId,
4127
- fundIndex,
4128
- addresses,
4129
- result
4130
- } = queryResult;
4131
- return (Array.isArray(result) ? result : []).flatMap((encoded, index) => {
4132
- const amount = (() => {
4133
- try {
4134
- return typeof encoded === "string" ? scaleTs.u128.dec(encoded) ?? 0n : 0n;
4135
- } catch {
4136
- return 0n;
4137
- }
4138
- })().toString();
4139
- return {
4140
- paraId,
4141
- fundIndex,
4142
- address: addresses[index],
4143
- amount
4144
- };
4145
- });
4146
- });
4147
-
4148
- // ignore these results if our caller has tried to close this subscription
4149
- if (!subscriptionActive) return;
4150
-
4151
- // ignore these results if they're the same as what we previously fetched
4152
- if (isEqual__default.default(previousContributions, contributions)) return;
4153
- previousContributions = contributions;
4154
- callback(null, contributions);
4155
- } catch (error) {
4156
- callback(error);
4157
- }
4158
- };
4159
-
4160
- // set up polling for contributions
4161
- const crowdloanContributionsPollInterval = 60_000; // 60_000ms === 1 minute
4162
- const pollContributions = async () => {
4163
- if (!subscriptionActive) return;
4164
- try {
4165
- await fetchContributions();
4166
- } catch (error) {
4167
- // log any errors, but don't cancel the poll for contributions when one fetch fails
4168
- log.error(error);
4169
- }
4170
- if (!subscriptionActive) return;
4171
- setTimeout(pollContributions, crowdloanContributionsPollInterval);
4172
- };
4173
-
4174
- // start polling
4175
- pollContributions();
4176
- return () => {
4177
- // stop polling
4178
- subscriptionActive = false;
4179
- };
4180
- };
4181
- const paraIds$ = asObservable(subscribeParaIds)().pipe(rxjs.scan((_, next) => Array.from(new Set(next.flatMap(paraIds => paraIds))), []), rxjs.share());
4182
- const fundIndexesByParaId$ = paraIds$.pipe(rxjs.map(paraIds => asObservable(subscribeParaFundIndexes)(paraIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
4183
- for (const fund of next) {
4184
- const {
4185
- paraId,
4186
- fundIndex
4187
- } = fund;
4188
- if (typeof fundIndex === "number") {
4189
- state.set(paraId, (state.get(paraId) ?? new Set()).add(fundIndex));
4190
- }
4191
- }
4192
- return state;
4193
- }, new Map()));
4194
- const contributionsByAddress$ = fundIndexesByParaId$.pipe(rxjs.map(fundIndexesByParaId => Array.from(fundIndexesByParaId).flatMap(([paraId, fundIndexes]) => Array.from(fundIndexes).map(fundIndex => ({
4195
- paraId,
4196
- fundIndex
4197
- })))), rxjs.map(funds => asObservable(subscribeFundContributions)(funds, addresses)), rxjs.switchAll(), rxjs.scan((state, next) => {
4198
- for (const contribution of next) {
4199
- const {
4200
- address
4201
- } = contribution;
4202
- state.set(address, (state.get(address) ?? new Set()).add(contribution));
4203
- }
4204
- return state;
4205
- }, new Map()));
4206
- const subscription = contributionsByAddress$.subscribe({
4207
- next: contributionsByAddress => {
4208
- const balances = Array.from(contributionsByAddress).map(([address, contributions]) => {
4209
- return {
4210
- source: "substrate-native",
4211
- status: "live",
4212
- address,
4213
- multiChainId: {
4214
- subChainId: chainId
4215
- },
4216
- chainId,
4217
- tokenId,
4218
- values: Array.from(contributions).map(({
4219
- amount,
4220
- paraId
4221
- }) => ({
4222
- type: "crowdloan",
4223
- label: "crowdloan",
4224
- source: "crowdloan",
4225
- amount: amount,
4226
- meta: {
4227
- paraId
4228
- }
4229
- }))
4230
- };
4231
- });
4232
- if (balances.length > 0) callback(null, balances);
4233
- },
4234
- error: error => callback(error)
4235
- });
4236
- tokenSubscriptions.push(() => subscription.unsubscribe());
4237
- }
4238
- return () => tokenSubscriptions.forEach(unsub => unsub());
4239
- }
4240
-
4241
3611
  /**
4242
3612
  * Each nominationPool in the nominationPools pallet has access to some accountIds which have no
4243
3613
  * associated private key. Instead, they are derived from this function.
@@ -4257,280 +3627,293 @@ const nompoolAccountId = (palletId, poolId, index) => {
4257
3627
  /** The stash account for the nomination pool */
4258
3628
  const nompoolStashAccountId = (palletId, poolId) => nompoolAccountId(palletId, poolId, 0);
4259
3629
 
4260
- async function subscribeNompoolStaking(chaindataProvider, chainConnector, addressesByToken, callback) {
4261
- const allChains = await chaindataProvider.chainsById();
4262
- const tokens = await chaindataProvider.tokensById();
4263
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
4264
- const nomPoolTokenIds = Object.entries(tokens).filter(([, token]) => {
4265
- // ignore non-native tokens
4266
- if (token.type !== "substrate-native") return false;
4267
- // ignore tokens on chains with no nompools pallet
4268
- const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", allChains[token.chain.id]);
4269
- return typeof chainMeta?.nominationPoolsPalletId === "string";
4270
- }).map(([tokenId]) => tokenId);
4271
-
4272
- // staking can only be done by the native token on chains with the staking pallet
4273
- const addressesByNomPoolToken = Object.fromEntries(Object.entries(addressesByToken)
4274
- // remove ethereum addresses
4275
- .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !util.isEthereumAddress(address))])
4276
- // remove tokens which aren't nom pool tokens
4277
- .filter(([tokenId]) => nomPoolTokenIds.includes(tokenId)));
4278
- const uniqueChainIds = getUniqueChainIds(addressesByNomPoolToken, tokens);
4279
- const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
4280
- const chainStorageCoders = buildStorageCoders({
4281
- chainIds: uniqueChainIds,
4282
- chains,
4283
- miniMetadatas,
4284
- moduleType: "substrate-native",
4285
- coders: {
4286
- poolMembers: ["NominationPools", "PoolMembers"],
4287
- bondedPools: ["NominationPools", "BondedPools"],
4288
- ledger: ["Staking", "Ledger"],
4289
- metadata: ["NominationPools", "Metadata"]
4290
- }
4291
- });
4292
- const resultUnsubscribes = [];
4293
- for (const [tokenId, addresses] of Object.entries(addressesByNomPoolToken)) {
4294
- const token = tokens[tokenId];
4295
- if (!token) {
4296
- log.warn(`Token ${tokenId} not found`);
4297
- continue;
4298
- }
4299
- if (token.type !== "substrate-native") {
4300
- log.debug(`This module doesn't handle tokens of type ${token.type}`);
4301
- continue;
4302
- }
4303
- const chainId = token.chain?.id;
4304
- if (!chainId) {
4305
- log.warn(`Token ${tokenId} has no chain`);
4306
- continue;
4307
- }
4308
- const chain = chains[chainId];
4309
- if (!chain) {
4310
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
4311
- continue;
3630
+ // TODO make this method chain-specific
3631
+ async function subscribeNompoolStaking(chaindataProvider$1, chainConnector, addressesByToken, callback, signal) {
3632
+ try {
3633
+ const allChains = await chaindataProvider$1.getNetworksMapById("polkadot");
3634
+ const tokens = await chaindataProvider$1.getTokensMapById();
3635
+
3636
+ // there should be only one network here when subscribing to balances, we've split it up by network at the top level
3637
+ const networkIds = lodash.keys(addressesByToken).map(tokenId => chaindataProvider.parseTokenId(tokenId).networkId);
3638
+ const miniMetadatas = new Map();
3639
+ for (const networkId of networkIds) {
3640
+ const miniMetadata = await getMiniMetadata(chaindataProvider$1, chainConnector, networkId, "substrate-native");
3641
+ miniMetadatas.set(networkId, miniMetadata);
4312
3642
  }
4313
- const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", chain);
4314
- const {
4315
- nominationPoolsPalletId
4316
- } = chainMeta ?? {};
4317
- const subscribePoolMembers = (addresses, callback) => {
4318
- const scaleCoder = chainStorageCoders.get(chainId)?.poolMembers;
4319
- const queries = addresses.flatMap(address => {
4320
- const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address in ${chainId} poolMembers query ${address}`, address);
4321
- if (!stateKey) return [];
4322
- const decodeResult = change => {
4323
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4324
-
4325
- const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode poolMembers on chain ${chainId}`);
4326
- const poolId = decoded?.pool_id?.toString?.();
4327
- const points = decoded?.points?.toString?.();
4328
- const unbondingEras = Array.from(decoded?.unbonding_eras ?? []).flatMap(entry => {
4329
- if (entry === undefined) return [];
4330
- const [key, value] = Array.from(entry);
4331
- const era = key?.toString?.();
4332
- const amount = value?.toString?.();
4333
- if (typeof era !== "string" || typeof amount !== "string") return [];
3643
+ signal?.throwIfAborted();
3644
+ const nomPoolTokenIds = Object.entries(tokens).filter(([, token]) => {
3645
+ // ignore non-native tokens
3646
+ if (token.type !== "substrate-native") return false;
3647
+
3648
+ // ignore tokens on chains with no nompools pallet
3649
+ const miniMetadata = miniMetadatas.get(token.networkId);
3650
+ return typeof miniMetadata?.extra?.nominationPoolsPalletId === "string";
3651
+ }).map(([tokenId]) => tokenId);
3652
+
3653
+ // staking can only be done by the native token on chains with the staking pallet
3654
+ const addressesByNomPoolToken = Object.fromEntries(Object.entries(addressesByToken)
3655
+ // remove ethereum addresses
3656
+ .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !util.isEthereumAddress(address))])
3657
+ // remove tokens which aren't nom pool tokens
3658
+ .filter(([tokenId]) => nomPoolTokenIds.includes(tokenId)));
3659
+ const uniqueChainIds = getUniqueChainIds(addressesByNomPoolToken, tokens);
3660
+ const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
3661
+ const chainStorageCoders = buildStorageCoders({
3662
+ chainIds: uniqueChainIds,
3663
+ chains,
3664
+ miniMetadatas,
3665
+ coders: {
3666
+ poolMembers: ["NominationPools", "PoolMembers"],
3667
+ bondedPools: ["NominationPools", "BondedPools"],
3668
+ ledger: ["Staking", "Ledger"],
3669
+ metadata: ["NominationPools", "Metadata"]
3670
+ }
3671
+ });
3672
+ const resultUnsubscribes = [];
3673
+ for (const [tokenId, addresses] of Object.entries(addressesByNomPoolToken)) {
3674
+ const token = tokens[tokenId];
3675
+ if (!token) {
3676
+ log.warn(`Token ${tokenId} not found`);
3677
+ continue;
3678
+ }
3679
+ if (token.type !== "substrate-native") {
3680
+ log.debug(`This module doesn't handle tokens of type ${token.type}`);
3681
+ continue;
3682
+ }
3683
+ const chainId = token.networkId;
3684
+ if (!chainId) {
3685
+ log.warn(`Token ${tokenId} has no chain`);
3686
+ continue;
3687
+ }
3688
+ const chain = chains[chainId];
3689
+ if (!chain) {
3690
+ log.warn(`Chain ${chainId} for token ${tokenId} not found`);
3691
+ continue;
3692
+ }
3693
+ const miniMetadata = miniMetadatas.get(chainId);
3694
+ const {
3695
+ nominationPoolsPalletId
3696
+ } = miniMetadata?.extra ?? {};
3697
+ const subscribePoolMembers = (addresses, callback) => {
3698
+ const scaleCoder = chainStorageCoders.get(chainId)?.poolMembers;
3699
+ const queries = addresses.flatMap(address => {
3700
+ const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address in ${chainId} poolMembers query ${address}`, address);
3701
+ if (!stateKey) return [];
3702
+ const decodeResult = change => {
3703
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3704
+
3705
+ const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode poolMembers on chain ${chainId}`);
3706
+ const poolId = decoded?.pool_id?.toString?.();
3707
+ const points = decoded?.points?.toString?.();
3708
+ const unbondingEras = Array.from(decoded?.unbonding_eras ?? []).flatMap(entry => {
3709
+ if (entry === undefined) return [];
3710
+ const [key, value] = Array.from(entry);
3711
+ const era = key?.toString?.();
3712
+ const amount = value?.toString?.();
3713
+ if (typeof era !== "string" || typeof amount !== "string") return [];
3714
+ return {
3715
+ era,
3716
+ amount
3717
+ };
3718
+ });
4334
3719
  return {
4335
- era,
4336
- amount
3720
+ tokenId,
3721
+ address,
3722
+ poolId,
3723
+ points,
3724
+ unbondingEras
4337
3725
  };
4338
- });
3726
+ };
4339
3727
  return {
4340
- tokenId,
4341
- address,
3728
+ chainId,
3729
+ stateKey,
3730
+ decodeResult
3731
+ };
3732
+ });
3733
+ const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3734
+ return () => subscription.then(unsubscribe => unsubscribe());
3735
+ };
3736
+ const subscribePoolPoints = (poolIds, callback) => {
3737
+ if (poolIds.length === 0) callback(null, []);
3738
+ const scaleCoder = chainStorageCoders.get(chainId)?.bondedPools;
3739
+ const queries = poolIds.flatMap(poolId => {
3740
+ const stateKey = scale.encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} bondedPools query ${poolId}`, poolId);
3741
+ if (!stateKey) return [];
3742
+ const decodeResult = change => {
3743
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3744
+
3745
+ const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode bondedPools on chain ${chainId}`);
3746
+ const points = decoded?.points?.toString?.();
3747
+ return {
3748
+ poolId,
3749
+ points
3750
+ };
3751
+ };
3752
+ return {
3753
+ chainId,
3754
+ stateKey,
3755
+ decodeResult
3756
+ };
3757
+ });
3758
+ const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3759
+ return () => subscription.then(unsubscribe => unsubscribe());
3760
+ };
3761
+ const subscribePoolStake = (poolIds, callback) => {
3762
+ if (poolIds.length === 0) callback(null, []);
3763
+ const scaleCoder = chainStorageCoders.get(chainId)?.ledger;
3764
+ const queries = poolIds.flatMap(poolId => {
3765
+ if (!nominationPoolsPalletId) return [];
3766
+ const stashAddress = nompoolStashAccountId(nominationPoolsPalletId, poolId);
3767
+ const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address in ${chainId} ledger query ${stashAddress}`, stashAddress);
3768
+ if (!stateKey) return [];
3769
+ const decodeResult = change => {
3770
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3771
+
3772
+ const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode ledger on chain ${chainId}`);
3773
+ const activeStake = decoded?.active?.toString?.();
3774
+ return {
3775
+ poolId,
3776
+ activeStake
3777
+ };
3778
+ };
3779
+ return {
3780
+ chainId,
3781
+ stateKey,
3782
+ decodeResult
3783
+ };
3784
+ });
3785
+ const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3786
+ return () => subscription.then(unsubscribe => unsubscribe());
3787
+ };
3788
+ const subscribePoolMetadata = (poolIds, callback) => {
3789
+ if (poolIds.length === 0) callback(null, []);
3790
+ const scaleCoder = chainStorageCoders.get(chainId)?.metadata;
3791
+ const queries = poolIds.flatMap(poolId => {
3792
+ if (!nominationPoolsPalletId) return [];
3793
+ const stateKey = scale.encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} metadata query ${poolId}`, poolId);
3794
+ if (!stateKey) return [];
3795
+ const decodeResult = change => {
3796
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3797
+
3798
+ const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode metadata on chain ${chainId}`);
3799
+ const metadata = decoded?.asText?.();
3800
+ return {
3801
+ poolId,
3802
+ metadata
3803
+ };
3804
+ };
3805
+ return {
3806
+ chainId,
3807
+ stateKey,
3808
+ decodeResult
3809
+ };
3810
+ });
3811
+ const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3812
+ return () => subscription.then(unsubscribe => unsubscribe());
3813
+ };
3814
+ const poolMembersByAddress$ = asObservable(subscribePoolMembers)(addresses).pipe(rxjs.scan((state, next) => {
3815
+ for (const poolMembers of next) {
3816
+ const {
4342
3817
  poolId,
4343
3818
  points,
4344
3819
  unbondingEras
4345
- };
4346
- };
4347
- return {
4348
- chainId,
4349
- stateKey,
4350
- decodeResult
4351
- };
4352
- });
4353
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
4354
- return () => subscription.then(unsubscribe => unsubscribe());
4355
- };
4356
- const subscribePoolPoints = (poolIds, callback) => {
4357
- if (poolIds.length === 0) callback(null, []);
4358
- const scaleCoder = chainStorageCoders.get(chainId)?.bondedPools;
4359
- const queries = poolIds.flatMap(poolId => {
4360
- const stateKey = scale.encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} bondedPools query ${poolId}`, poolId);
4361
- if (!stateKey) return [];
4362
- const decodeResult = change => {
4363
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4364
-
4365
- const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode bondedPools on chain ${chainId}`);
4366
- const points = decoded?.points?.toString?.();
4367
- return {
3820
+ } = poolMembers;
3821
+ if (typeof poolId === "string" && typeof points === "string") state.set(poolMembers.address, {
3822
+ poolId,
3823
+ points,
3824
+ unbondingEras
3825
+ });else state.set(poolMembers.address, null);
3826
+ }
3827
+ return state;
3828
+ }, new Map()), rxjs.share());
3829
+ const poolIdByAddress$ = poolMembersByAddress$.pipe(rxjs.map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.poolId ?? null]))));
3830
+ const pointsByAddress$ = poolMembersByAddress$.pipe(rxjs.map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.points ?? null]))));
3831
+ const unbondingErasByAddress$ = poolMembersByAddress$.pipe(rxjs.map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.unbondingEras ?? null]))));
3832
+ const poolIds$ = poolIdByAddress$.pipe(rxjs.map(byAddress => [...new Set(Array.from(byAddress.values()).flatMap(poolId => poolId ?? []))]));
3833
+ const pointsByPool$ = poolIds$.pipe(rxjs.map(poolIds => asObservable(subscribePoolPoints)(poolIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
3834
+ for (const poolPoints of next) {
3835
+ const {
4368
3836
  poolId,
4369
3837
  points
4370
- };
4371
- };
4372
- return {
4373
- chainId,
4374
- stateKey,
4375
- decodeResult
4376
- };
4377
- });
4378
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
4379
- return () => subscription.then(unsubscribe => unsubscribe());
4380
- };
4381
- const subscribePoolStake = (poolIds, callback) => {
4382
- if (poolIds.length === 0) callback(null, []);
4383
- const scaleCoder = chainStorageCoders.get(chainId)?.ledger;
4384
- const queries = poolIds.flatMap(poolId => {
4385
- if (!nominationPoolsPalletId) return [];
4386
- const stashAddress = nompoolStashAccountId(nominationPoolsPalletId, poolId);
4387
- const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address in ${chainId} ledger query ${stashAddress}`, stashAddress);
4388
- if (!stateKey) return [];
4389
- const decodeResult = change => {
4390
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4391
-
4392
- const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode ledger on chain ${chainId}`);
4393
- const activeStake = decoded?.active?.toString?.();
4394
- return {
3838
+ } = poolPoints;
3839
+ if (typeof points === "string") state.set(poolId, points);else state.delete(poolId);
3840
+ }
3841
+ return state;
3842
+ }, new Map()));
3843
+ const stakeByPool$ = poolIds$.pipe(rxjs.map(poolIds => asObservable(subscribePoolStake)(poolIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
3844
+ for (const poolStake of next) {
3845
+ const {
4395
3846
  poolId,
4396
3847
  activeStake
4397
- };
4398
- };
4399
- return {
4400
- chainId,
4401
- stateKey,
4402
- decodeResult
4403
- };
4404
- });
4405
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
4406
- return () => subscription.then(unsubscribe => unsubscribe());
4407
- };
4408
- const subscribePoolMetadata = (poolIds, callback) => {
4409
- if (poolIds.length === 0) callback(null, []);
4410
- const scaleCoder = chainStorageCoders.get(chainId)?.metadata;
4411
- const queries = poolIds.flatMap(poolId => {
4412
- if (!nominationPoolsPalletId) return [];
4413
- const stateKey = scale.encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} metadata query ${poolId}`, poolId);
4414
- if (!stateKey) return [];
4415
- const decodeResult = change => {
4416
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4417
-
4418
- const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode metadata on chain ${chainId}`);
4419
- const metadata = decoded?.asText?.();
4420
- return {
3848
+ } = poolStake;
3849
+ if (typeof activeStake === "string") state.set(poolId, activeStake);else state.delete(poolId);
3850
+ }
3851
+ return state;
3852
+ }, new Map()));
3853
+ const metadataByPool$ = poolIds$.pipe(rxjs.map(poolIds => asObservable(subscribePoolMetadata)(poolIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
3854
+ for (const poolMetadata of next) {
3855
+ const {
4421
3856
  poolId,
4422
3857
  metadata
4423
- };
4424
- };
4425
- return {
4426
- chainId,
4427
- stateKey,
4428
- decodeResult
4429
- };
4430
- });
4431
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
4432
- return () => subscription.then(unsubscribe => unsubscribe());
4433
- };
4434
- const poolMembersByAddress$ = asObservable(subscribePoolMembers)(addresses).pipe(rxjs.scan((state, next) => {
4435
- for (const poolMembers of next) {
4436
- const {
4437
- poolId,
4438
- points,
4439
- unbondingEras
4440
- } = poolMembers;
4441
- if (typeof poolId === "string" && typeof points === "string") state.set(poolMembers.address, {
4442
- poolId,
4443
- points,
4444
- unbondingEras
4445
- });else state.set(poolMembers.address, null);
4446
- }
4447
- return state;
4448
- }, new Map()), rxjs.share());
4449
- const poolIdByAddress$ = poolMembersByAddress$.pipe(rxjs.map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.poolId ?? null]))));
4450
- const pointsByAddress$ = poolMembersByAddress$.pipe(rxjs.map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.points ?? null]))));
4451
- const unbondingErasByAddress$ = poolMembersByAddress$.pipe(rxjs.map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.unbondingEras ?? null]))));
4452
- const poolIds$ = poolIdByAddress$.pipe(rxjs.map(byAddress => [...new Set(Array.from(byAddress.values()).flatMap(poolId => poolId ?? []))]));
4453
- const pointsByPool$ = poolIds$.pipe(rxjs.map(poolIds => asObservable(subscribePoolPoints)(poolIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
4454
- for (const poolPoints of next) {
4455
- const {
4456
- poolId,
4457
- points
4458
- } = poolPoints;
4459
- if (typeof points === "string") state.set(poolId, points);else state.delete(poolId);
4460
- }
4461
- return state;
4462
- }, new Map()));
4463
- const stakeByPool$ = poolIds$.pipe(rxjs.map(poolIds => asObservable(subscribePoolStake)(poolIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
4464
- for (const poolStake of next) {
4465
- const {
4466
- poolId,
4467
- activeStake
4468
- } = poolStake;
4469
- if (typeof activeStake === "string") state.set(poolId, activeStake);else state.delete(poolId);
4470
- }
4471
- return state;
4472
- }, new Map()));
4473
- const metadataByPool$ = poolIds$.pipe(rxjs.map(poolIds => asObservable(subscribePoolMetadata)(poolIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
4474
- for (const poolMetadata of next) {
4475
- const {
4476
- poolId,
4477
- metadata
4478
- } = poolMetadata;
4479
- if (typeof metadata === "string") state.set(poolId, metadata);else state.delete(poolId);
4480
- }
4481
- return state;
4482
- }, new Map()));
4483
- const subscription = rxjs.combineLatest([poolIdByAddress$, pointsByAddress$, unbondingErasByAddress$, pointsByPool$, stakeByPool$, metadataByPool$]).subscribe({
4484
- next: ([poolIdByAddress, pointsByAddress, unbondingErasByAddress, pointsByPool, stakeByPool, metadataByPool]) => {
4485
- const balances = Array.from(poolIdByAddress).map(([address, poolId]) => {
4486
- const parsedPoolId = poolId === null ? undefined : parseInt(poolId);
4487
- const points = pointsByAddress.get(address) ?? "0";
4488
- const poolPoints = pointsByPool.get(poolId ?? "") ?? "0";
4489
- const poolStake = stakeByPool.get(poolId ?? "") ?? "0";
4490
- const poolMetadata = poolId ? metadataByPool.get(poolId) ?? `Pool ${poolId}` : undefined;
4491
- const amount = points === "0" || poolPoints === "0" || poolStake === "0" ? 0n : BigInt(poolStake) * BigInt(points) / BigInt(poolPoints);
4492
- const unbondingAmount = (unbondingErasByAddress.get(address) ?? []).reduce((total, {
4493
- amount
4494
- }) => total + BigInt(amount ?? "0"), 0n);
4495
- return {
4496
- source: "substrate-native",
4497
- status: "live",
4498
- address,
4499
- multiChainId: {
4500
- subChainId: chainId
4501
- },
4502
- chainId,
4503
- tokenId,
4504
- values: [{
4505
- source: "nompools-staking",
4506
- type: "nompool",
4507
- label: "nompools-staking",
4508
- amount: amount.toString(),
4509
- meta: {
3858
+ } = poolMetadata;
3859
+ if (typeof metadata === "string") state.set(poolId, metadata);else state.delete(poolId);
3860
+ }
3861
+ return state;
3862
+ }, new Map()));
3863
+ const subscription = rxjs.combineLatest([poolIdByAddress$, pointsByAddress$, unbondingErasByAddress$, pointsByPool$, stakeByPool$, metadataByPool$]).subscribe({
3864
+ next: ([poolIdByAddress, pointsByAddress, unbondingErasByAddress, pointsByPool, stakeByPool, metadataByPool]) => {
3865
+ const balances = Array.from(poolIdByAddress).map(([address, poolId]) => {
3866
+ const parsedPoolId = poolId === null ? undefined : parseInt(poolId);
3867
+ const points = pointsByAddress.get(address) ?? "0";
3868
+ const poolPoints = pointsByPool.get(poolId ?? "") ?? "0";
3869
+ const poolStake = stakeByPool.get(poolId ?? "") ?? "0";
3870
+ const poolMetadata = poolId ? metadataByPool.get(poolId) ?? `Pool ${poolId}` : undefined;
3871
+ const amount = points === "0" || poolPoints === "0" || poolStake === "0" ? 0n : BigInt(poolStake) * BigInt(points) / BigInt(poolPoints);
3872
+ const unbondingAmount = (unbondingErasByAddress.get(address) ?? []).reduce((total, {
3873
+ amount
3874
+ }) => total + BigInt(amount ?? "0"), 0n);
3875
+ return {
3876
+ source: "substrate-native",
3877
+ status: "live",
3878
+ address,
3879
+ networkId: chainId,
3880
+ tokenId,
3881
+ values: [{
3882
+ source: "nompools-staking",
4510
3883
  type: "nompool",
4511
- poolId: parsedPoolId,
4512
- description: poolMetadata
4513
- }
4514
- }, {
4515
- source: "nompools-staking",
4516
- type: "nompool",
4517
- label: "nompools-unbonding",
4518
- amount: unbondingAmount.toString(),
4519
- meta: {
4520
- poolId: parsedPoolId,
4521
- description: poolMetadata,
4522
- unbonding: true
4523
- }
4524
- }]
4525
- };
4526
- }).filter(util.isNotNil);
4527
- if (balances.length > 0) callback(null, balances);
4528
- },
4529
- error: error => callback(error)
3884
+ label: "nompools-staking",
3885
+ amount: amount.toString(),
3886
+ meta: {
3887
+ type: "nompool",
3888
+ poolId: parsedPoolId,
3889
+ description: poolMetadata
3890
+ }
3891
+ }, {
3892
+ source: "nompools-staking",
3893
+ type: "nompool",
3894
+ label: "nompools-unbonding",
3895
+ amount: unbondingAmount.toString(),
3896
+ meta: {
3897
+ poolId: parsedPoolId,
3898
+ description: poolMetadata,
3899
+ unbonding: true
3900
+ }
3901
+ }]
3902
+ };
3903
+ }).filter(util.isNotNil);
3904
+ if (balances.length > 0) callback(null, balances);
3905
+ },
3906
+ error: error => callback(error)
3907
+ });
3908
+ resultUnsubscribes.push(() => subscription.unsubscribe());
3909
+ }
3910
+ return () => resultUnsubscribes.forEach(unsub => unsub());
3911
+ } catch (err) {
3912
+ if (!util.isAbortError(err)) log.error("Error subscribing to nom pool staking", {
3913
+ err
4530
3914
  });
4531
- resultUnsubscribes.push(() => subscription.unsubscribe());
3915
+ return () => {};
4532
3916
  }
4533
- return () => resultUnsubscribes.forEach(unsub => unsub());
4534
3917
  }
4535
3918
 
4536
3919
  const SUBTENSOR_ROOT_NETUID = 0;
@@ -4567,230 +3950,241 @@ const calculateTaoFromDynamicInfo = ({
4567
3950
  dynamicInfo
4568
3951
  });
4569
3952
  return calculateTaoAmountFromAlpha({
4570
- alphaPrice,
4571
- alphaStaked
4572
- });
4573
- };
4574
-
4575
- async function subscribeSubtensorStaking(chaindataProvider, chainConnector, addressesByToken, callback) {
4576
- const allChains = await chaindataProvider.chainsById();
4577
- const tokens = await chaindataProvider.tokensById();
4578
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
4579
- const subtensorTokenIds = Object.entries(tokens).filter(([, token]) => {
4580
- // ignore non-native tokens
4581
- if (token.type !== "substrate-native") return false;
4582
- // ignore tokens on chains with no subtensor pallet
4583
- const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", allChains[token.chain.id]);
4584
- return chainMeta?.hasSubtensorPallet === true;
4585
- }).map(([tokenId]) => tokenId);
4586
-
4587
- // staking can only be done by the native token on chains with the subtensor pallet
4588
- const addressesBySubtensorToken = Object.fromEntries(Object.entries(addressesByToken)
4589
- // remove ethereum addresses
4590
- .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !util.isEthereumAddress(address))])
4591
- // remove tokens which aren't subtensor staking tokens
4592
- .filter(([tokenId]) => subtensorTokenIds.includes(tokenId)));
4593
- const uniqueChainIds = getUniqueChainIds(addressesBySubtensorToken, tokens);
4594
- const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
4595
- const abortController = new AbortController();
4596
- for (const [tokenId, addresses] of Object.entries(addressesBySubtensorToken)) {
4597
- const token = tokens[tokenId];
4598
- if (!token) {
4599
- log.warn(`Token ${tokenId} not found`);
4600
- continue;
4601
- }
4602
- if (token.type !== "substrate-native") {
4603
- log.debug(`This module doesn't handle tokens of type ${token.type}`);
4604
- continue;
4605
- }
4606
- const chainId = token.chain?.id;
4607
- if (!chainId) {
4608
- log.warn(`Token ${tokenId} has no chain`);
4609
- continue;
4610
- }
4611
- const chain = chains[chainId];
4612
- if (!chain) {
4613
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
4614
- continue;
4615
- }
4616
- const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", chain);
4617
- if (!chainMeta?.miniMetadata) {
4618
- log.warn(`MiniMetadata for chain ${chainId} not found`);
4619
- continue;
3953
+ alphaPrice,
3954
+ alphaStaked
3955
+ });
3956
+ };
3957
+
3958
+ // TODO make this method chain-specific
3959
+ async function subscribeSubtensorStaking(chaindataProvider$1, chainConnector, addressesByToken, callback, signal) {
3960
+ try {
3961
+ const allChains = await chaindataProvider$1.getNetworksMapById("polkadot");
3962
+ const tokens = await chaindataProvider$1.getTokensMapById();
3963
+
3964
+ // there should be only one network here when subscribing to balances, we've split it up by network at the top level
3965
+ const networkIds = lodash.keys(addressesByToken).map(tokenId => chaindataProvider.parseTokenId(tokenId).networkId);
3966
+ const miniMetadatas = new Map();
3967
+ for (const networkId of networkIds) {
3968
+ const miniMetadata = await getMiniMetadata(chaindataProvider$1, chainConnector, networkId, "substrate-native", signal);
3969
+ miniMetadatas.set(networkId, miniMetadata);
4620
3970
  }
4621
- const scaleApi = sapi.getScaleApi({
4622
- chainId,
4623
- send: (...args) => chainConnector.send(chainId, ...args, {
4624
- expectErrors: true
4625
- } // don't pollute the wallet logs when this request fails
4626
- )
4627
- }, chainMeta.miniMetadata, token, chain.hasCheckMetadataHash, chain.signedExtensions, chain.registryTypes);
4628
-
4629
- // sets the number of addresses to query in parallel (per chain, since each chain runs in parallel to the others)
4630
- const concurrency = 4;
4631
- // In-memory cache for successful dynamic info results
4632
- const dynamicInfoCache = new Map();
4633
- const fetchDynamicInfoForNetuids = async uniqueNetuids => {
4634
- const MAX_RETRIES = 3;
4635
- const RETRY_DELAY_MS = 500;
4636
- const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
4637
- const fetchInfo = async netuid => {
4638
- if (netuid === 0) return null;
4639
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
4640
- try {
4641
- const params = [netuid];
4642
- const result = await scaleApi.getRuntimeCallValue("SubnetInfoRuntimeApi", "get_dynamic_info", params);
4643
- dynamicInfoCache.set(netuid, result); // Cache successful response
4644
- return result;
4645
- } catch (error) {
4646
- log.trace(`Attempt ${attempt} failed for netuid ${netuid}:`, error);
4647
- if (attempt < MAX_RETRIES) {
4648
- const backoffTime = RETRY_DELAY_MS * 2 ** (attempt - 1);
4649
- log.trace(`Retrying in ${backoffTime}ms...`);
4650
- await delay(backoffTime);
3971
+ signal?.throwIfAborted();
3972
+ const subtensorTokenIds = Object.entries(tokens).filter(([, token]) => {
3973
+ // ignore non-native tokens
3974
+ if (token.type !== "substrate-native") return false;
3975
+ // ignore tokens on chains with no subtensor pallet
3976
+ const miniMetadata = miniMetadatas.get(token.networkId);
3977
+ return miniMetadata?.extra?.hasSubtensorPallet === true;
3978
+ }).map(([tokenId]) => tokenId);
3979
+
3980
+ // staking can only be done by the native token on chains with the subtensor pallet
3981
+ const addressesBySubtensorToken = Object.fromEntries(Object.entries(addressesByToken)
3982
+ // remove ethereum addresses
3983
+ .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !util.isEthereumAddress(address))])
3984
+ // remove tokens which aren't subtensor staking tokens
3985
+ .filter(([tokenId]) => subtensorTokenIds.includes(tokenId)));
3986
+ const uniqueChainIds = getUniqueChainIds(addressesBySubtensorToken, tokens);
3987
+ const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
3988
+ const abortController = new AbortController();
3989
+ for (const [tokenId, addresses] of Object.entries(addressesBySubtensorToken)) {
3990
+ const token = tokens[tokenId];
3991
+ if (!token) {
3992
+ log.warn(`Token ${tokenId} not found`);
3993
+ continue;
3994
+ }
3995
+ if (token.type !== "substrate-native") {
3996
+ log.debug(`This module doesn't handle tokens of type ${token.type}`);
3997
+ continue;
3998
+ }
3999
+ const chainId = token.networkId;
4000
+ const chain = chains[chainId];
4001
+ if (!chain) {
4002
+ log.warn(`Chain ${chainId} for token ${tokenId} not found`);
4003
+ continue;
4004
+ }
4005
+ const miniMetadata = miniMetadatas.get(token.networkId);
4006
+ if (!miniMetadata?.data) {
4007
+ log.warn(`MiniMetadata for chain ${chainId} not found`);
4008
+ continue;
4009
+ }
4010
+ const scaleApi = sapi.getScaleApi({
4011
+ chainId,
4012
+ send: (...args) => chainConnector.send(chainId, ...args, {
4013
+ expectErrors: true
4014
+ } // don't pollute the wallet logs when this request fails
4015
+ )
4016
+ }, miniMetadata.data, token, chain.hasCheckMetadataHash, chain.signedExtensions, chain.registryTypes);
4017
+
4018
+ // sets the number of addresses to query in parallel (per chain, since each chain runs in parallel to the others)
4019
+ const concurrency = 4;
4020
+ // In-memory cache for successful dynamic info results
4021
+ const dynamicInfoCache = new Map();
4022
+ const fetchDynamicInfoForNetuids = async uniqueNetuids => {
4023
+ const MAX_RETRIES = 3;
4024
+ const RETRY_DELAY_MS = 500;
4025
+ const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
4026
+ const fetchInfo = async netuid => {
4027
+ if (netuid === 0) return null;
4028
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
4029
+ try {
4030
+ const params = [netuid];
4031
+ const result = await scaleApi.getRuntimeCallValue("SubnetInfoRuntimeApi", "get_dynamic_info", params);
4032
+ dynamicInfoCache.set(netuid, result); // Cache successful response
4033
+ return result;
4034
+ } catch (error) {
4035
+ log.trace(`Attempt ${attempt} failed for netuid ${netuid}:`, error);
4036
+ if (attempt < MAX_RETRIES) {
4037
+ const backoffTime = RETRY_DELAY_MS * 2 ** (attempt - 1);
4038
+ log.trace(`Retrying in ${backoffTime}ms...`);
4039
+ await delay(backoffTime);
4040
+ }
4651
4041
  }
4652
4042
  }
4653
- }
4654
- if (dynamicInfoCache.has(netuid)) {
4655
- return dynamicInfoCache.get(netuid); // Use cached value on failure
4656
- }
4657
- log.trace(`Failed to fetch dynamic info for netuid ${netuid} after ${MAX_RETRIES} attempts.`);
4658
- return null;
4043
+ if (dynamicInfoCache.has(netuid)) {
4044
+ return dynamicInfoCache.get(netuid); // Use cached value on failure
4045
+ }
4046
+ log.trace(`Failed to fetch dynamic info for netuid ${netuid} after ${MAX_RETRIES} attempts.`);
4047
+ return null;
4048
+ };
4049
+ return Promise.all(uniqueNetuids.map(fetchInfo));
4659
4050
  };
4660
- return Promise.all(uniqueNetuids.map(fetchInfo));
4661
- };
4662
- const subtensorQueries = rxjs.from(addresses).pipe(
4663
- // mergeMap lets us run N concurrent queries, where N is the value of `concurrency`
4664
- rxjs.mergeMap(async address => {
4665
- const queryMethods = [async () => {
4666
- if (chain.isTestnet) return [];
4667
- const params = [address];
4668
- const result = await scaleApi.getRuntimeCallValue("StakeInfoRuntimeApi", "get_stake_info_for_coldkey", params);
4669
- if (!Array.isArray(result)) return [];
4670
- const uniqueNetuids = Array.from(new Set(result.map(item => Number(item.netuid)).filter(netuid => netuid !== SUBTENSOR_ROOT_NETUID)));
4671
- await fetchDynamicInfoForNetuids(uniqueNetuids);
4672
- const stakes = result?.map(({
4673
- coldkey,
4674
- hotkey,
4675
- netuid,
4676
- stake
4677
- }) => {
4678
- return {
4679
- address: coldkey,
4051
+ const subtensorQueries = rxjs.from(addresses).pipe(
4052
+ // mergeMap lets us run N concurrent queries, where N is the value of `concurrency`
4053
+ rxjs.mergeMap(async address => {
4054
+ const queryMethods = [async () => {
4055
+ if (chain.isTestnet) return [];
4056
+ const params = [address];
4057
+ const result = await scaleApi.getRuntimeCallValue("StakeInfoRuntimeApi", "get_stake_info_for_coldkey", params);
4058
+ if (!Array.isArray(result)) return [];
4059
+ const uniqueNetuids = Array.from(new Set(result.map(item => Number(item.netuid)).filter(netuid => netuid !== SUBTENSOR_ROOT_NETUID)));
4060
+ await fetchDynamicInfoForNetuids(uniqueNetuids);
4061
+ const stakes = result?.map(({
4062
+ coldkey,
4680
4063
  hotkey,
4681
- netuid: Number(netuid),
4682
- stake: BigInt(stake),
4683
- dynamicInfo: dynamicInfoCache.get(Number(netuid))
4684
- };
4685
- }).filter(({
4686
- stake
4687
- }) => stake >= SUBTENSOR_MIN_STAKE_AMOUNT_PLANK);
4688
- return stakes;
4689
- }];
4690
- const errors = [];
4691
- for (const queryMethod of queryMethods) {
4692
- try {
4693
- // try each query method
4694
- return await queryMethod();
4695
- } catch (cause) {
4696
- // if it fails, keep track of the error and try the next one
4697
- errors.push(cause);
4064
+ netuid,
4065
+ stake
4066
+ }) => {
4067
+ return {
4068
+ address: coldkey,
4069
+ hotkey,
4070
+ netuid: Number(netuid),
4071
+ stake: BigInt(stake),
4072
+ dynamicInfo: dynamicInfoCache.get(Number(netuid))
4073
+ };
4074
+ }).filter(({
4075
+ stake
4076
+ }) => stake >= SUBTENSOR_MIN_STAKE_AMOUNT_PLANK);
4077
+ return stakes;
4078
+ }];
4079
+ const errors = [];
4080
+ for (const queryMethod of queryMethods) {
4081
+ try {
4082
+ // try each query method
4083
+ return await queryMethod();
4084
+ } catch (cause) {
4085
+ // if it fails, keep track of the error and try the next one
4086
+ errors.push(cause);
4087
+ }
4698
4088
  }
4699
- }
4700
4089
 
4701
- // if we get to here, that means that all query methods failed
4702
- // let's throw the errors back to the native balance module
4703
- throw new Error([`Failed to fetch ${tokenId} subtensor staked balance for ${address}:`, ...errors.map(error => String(error))].join("\n\t"));
4704
- }, concurrency),
4705
- // instead of emitting each balance as it's fetched, toArray waits for them all to fetch and then it collects them into an array
4706
- rxjs.toArray(),
4707
- // this mergeMap flattens our Array<Array<Stakes>> into just an Array<Stakes>
4708
- rxjs.mergeMap(stakes => stakes),
4709
- // convert our Array<Stakes> into Array<Balances>, which we can then return to the native balance module
4710
- rxjs.map(stakes => stakes.map(({
4711
- address,
4712
- hotkey,
4713
- stake,
4714
- netuid,
4715
- dynamicInfo
4716
- }) => {
4717
- const {
4718
- token_symbol,
4719
- subnet_name,
4720
- subnet_identity
4721
- } = dynamicInfo ?? {};
4722
- const tokenSymbol = new TextDecoder().decode(Uint8Array.from(token_symbol ?? []));
4723
- const subnetName = new TextDecoder().decode(Uint8Array.from(subnet_name ?? []));
4724
-
4725
- /** Map from Record<string, Binary> to Record<string, string> */
4726
- const binaryToText = input => Object.entries(input).reduce((acc, [key, value]) => {
4727
- acc[key] = value.asText();
4728
- return acc;
4729
- }, {});
4730
- const subnetIdentity = subnet_identity ? binaryToText(subnet_identity) : undefined;
4731
-
4732
- // Add 1n balance if failed to fetch dynamic info, so the position is not ignored by Balance lib and is displayed in the UI.
4733
- const alphaStakedInTao = dynamicInfo ? calculateTaoFromDynamicInfo({
4734
- dynamicInfo,
4735
- alphaStaked: stake
4736
- }) : 1n;
4737
- const alphaToTaoRate = calculateTaoFromDynamicInfo({
4738
- dynamicInfo: dynamicInfo ?? null,
4739
- alphaStaked: ONE_ALPHA_TOKEN
4740
- }).toString();
4741
- const stakeByNetuid = Number(netuid) === SUBTENSOR_ROOT_NETUID ? stake : alphaStakedInTao;
4742
- return {
4743
- source: "substrate-native",
4744
- status: "live",
4090
+ // if we get to here, that means that all query methods failed
4091
+ // let's throw the errors back to the native balance module
4092
+ throw new Error([`Failed to fetch ${tokenId} subtensor staked balance for ${address}:`, ...errors.map(error => String(error))].join("\n\t"));
4093
+ }, concurrency),
4094
+ // instead of emitting each balance as it's fetched, toArray waits for them all to fetch and then it collects them into an array
4095
+ rxjs.toArray(),
4096
+ // this mergeMap flattens our Array<Array<Stakes>> into just an Array<Stakes>
4097
+ rxjs.mergeMap(stakes => stakes),
4098
+ // convert our Array<Stakes> into Array<Balances>, which we can then return to the native balance module
4099
+ rxjs.map(stakes => stakes.map(({
4745
4100
  address,
4746
- multiChainId: {
4747
- subChainId: chainId
4748
- },
4749
- chainId,
4750
- tokenId,
4751
- values: [{
4752
- source: "subtensor-staking",
4753
- type: "subtensor",
4754
- label: "subtensor-staking",
4755
- amount: stakeByNetuid.toString(),
4756
- meta: {
4757
- type: "subtensor-staking",
4758
- hotkey,
4759
- netuid,
4760
- amountStaked: stake.toString(),
4761
- alphaToTaoRate,
4762
- dynamicInfo: {
4763
- tokenSymbol,
4764
- subnetName,
4765
- subnetIdentity: {
4766
- ...subnetIdentity,
4767
- subnetName: subnetIdentity?.subnet_name || subnetName
4101
+ hotkey,
4102
+ stake,
4103
+ netuid,
4104
+ dynamicInfo
4105
+ }) => {
4106
+ const {
4107
+ token_symbol,
4108
+ subnet_name,
4109
+ subnet_identity
4110
+ } = dynamicInfo ?? {};
4111
+ const tokenSymbol = new TextDecoder().decode(Uint8Array.from(token_symbol ?? []));
4112
+ const subnetName = new TextDecoder().decode(Uint8Array.from(subnet_name ?? []));
4113
+
4114
+ /** Map from Record<string, Binary> to Record<string, string> */
4115
+ const binaryToText = input => Object.entries(input).reduce((acc, [key, value]) => {
4116
+ acc[key] = value.asText();
4117
+ return acc;
4118
+ }, {});
4119
+ const subnetIdentity = subnet_identity ? binaryToText(subnet_identity) : undefined;
4120
+
4121
+ // Add 1n balance if failed to fetch dynamic info, so the position is not ignored by Balance lib and is displayed in the UI.
4122
+ const alphaStakedInTao = dynamicInfo ? calculateTaoFromDynamicInfo({
4123
+ dynamicInfo,
4124
+ alphaStaked: stake
4125
+ }) : 1n;
4126
+ const alphaToTaoRate = calculateTaoFromDynamicInfo({
4127
+ dynamicInfo: dynamicInfo ?? null,
4128
+ alphaStaked: ONE_ALPHA_TOKEN
4129
+ }).toString();
4130
+ const stakeByNetuid = Number(netuid) === SUBTENSOR_ROOT_NETUID ? stake : alphaStakedInTao;
4131
+ return {
4132
+ source: "substrate-native",
4133
+ status: "live",
4134
+ address,
4135
+ networkId: chainId,
4136
+ tokenId,
4137
+ values: [{
4138
+ source: "subtensor-staking",
4139
+ type: "subtensor",
4140
+ label: "subtensor-staking",
4141
+ amount: stakeByNetuid.toString(),
4142
+ meta: {
4143
+ type: "subtensor-staking",
4144
+ hotkey,
4145
+ netuid,
4146
+ amountStaked: stake.toString(),
4147
+ alphaToTaoRate,
4148
+ dynamicInfo: {
4149
+ tokenSymbol,
4150
+ subnetName,
4151
+ subnetIdentity: {
4152
+ ...subnetIdentity,
4153
+ subnetName: subnetIdentity?.subnet_name || subnetName
4154
+ }
4768
4155
  }
4769
4156
  }
4770
- }
4771
- }]
4772
- };
4773
- })));
4157
+ }]
4158
+ };
4159
+ })));
4160
+
4161
+ // This observable will run the subtensorQueries on a 30s (30_000ms) interval.
4162
+ // However, if the last run has not yet completed (e.g. its been 30s but we're still fetching some balances),
4163
+ // then exhaustMap will wait until the next interval (so T: 60s, T: 90s, T: 120s, etc) before re-executing the subtensorQueries.
4164
+ const subtensorQueriesInterval = rxjs.interval(30_000).pipe(rxjs.startWith(0),
4165
+ // start immediately
4166
+ rxjs.exhaustMap(() => {
4167
+ return subtensorQueries;
4168
+ }));
4774
4169
 
4775
- // This observable will run the subtensorQueries on a 30s (30_000ms) interval.
4776
- // However, if the last run has not yet completed (e.g. its been 30s but we're still fetching some balances),
4777
- // then exhaustMap will wait until the next interval (so T: 60s, T: 90s, T: 120s, etc) before re-executing the subtensorQueries.
4778
- const subtensorQueriesInterval = rxjs.interval(30_000).pipe(rxjs.startWith(0),
4779
- // start immediately
4780
- rxjs.exhaustMap(() => {
4781
- return subtensorQueries;
4782
- }));
4170
+ // subscribe to the balances
4171
+ const subscription = subtensorQueriesInterval.subscribe({
4172
+ next: balances => callback(null, balances),
4173
+ error: error => callback(error)
4174
+ });
4783
4175
 
4784
- // subscribe to the balances
4785
- const subscription = subtensorQueriesInterval.subscribe({
4786
- next: balances => callback(null, balances),
4787
- error: error => callback(error)
4176
+ // use the abortController to tear the subscription down when we don't need it anymore
4177
+ abortController.signal.addEventListener("abort", () => {
4178
+ subscription.unsubscribe();
4179
+ });
4180
+ }
4181
+ return () => abortController.abort();
4182
+ } catch (err) {
4183
+ if (!util.isAbortError(err)) log.error("Error subscribing to subtensor staking", {
4184
+ err
4788
4185
  });
4789
-
4790
- // use the abortController to tear the subscription down when we don't need it anymore
4791
- abortController.signal.onabort = () => subscription.unsubscribe();
4186
+ return () => {};
4792
4187
  }
4793
- return () => abortController.abort();
4794
4188
  }
4795
4189
 
4796
4190
  const getOtherType = input => `other-${input}`;
@@ -4846,18 +4240,14 @@ const filterBaseLocks = locks => {
4846
4240
  };
4847
4241
 
4848
4242
  // TODO: Make these titles translatable
4849
- const getLockTitle = (lock, {
4243
+ const getLockTitle = (lock,
4244
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
4245
+ {
4850
4246
  balance
4851
4247
  } = {}) => {
4852
4248
  if (!lock.label) return lock.label;
4853
4249
  if (lock.label === "democracy") return "Governance";
4854
- if (lock.label === "crowdloan") {
4855
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
4856
- const paraId = lock.meta?.paraId;
4857
- if (!paraId) return "Crowdloan";
4858
- const name = balance?.chain?.parathreads?.find(parathread => parathread?.paraId === paraId)?.name;
4859
- return `${name ? name : `Parachain ${paraId}`} Crowdloan`;
4860
- }
4250
+ if (lock.label === "crowdloan") return "Crowdloan";
4861
4251
  if (lock.label === "nompools-staking") return "Pooled Staking";
4862
4252
  if (lock.label === "nompools-unbonding") return "Pooled Staking";
4863
4253
  if (lock.label === "subtensor-staking") return "Root Staking";
@@ -4869,7 +4259,6 @@ const getLockTitle = (lock, {
4869
4259
  };
4870
4260
 
4871
4261
  const moduleType$2 = "substrate-native";
4872
- const subNativeTokenId = chainId => `${chainId}-substrate-native`.toLowerCase().replace(/ /g, "-");
4873
4262
 
4874
4263
  /**
4875
4264
  * Function to merge two 'sub sources' of the same balance together, or
@@ -4947,7 +4336,7 @@ async function buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas,
4947
4336
  log.debug(`This module doesn't handle tokens of type ${token.type}`);
4948
4337
  return outerResult;
4949
4338
  }
4950
- const chainId = token.chain?.id;
4339
+ const chainId = token.networkId;
4951
4340
  if (!chainId) {
4952
4341
  log.warn(`Token ${tokenId} has no chain`);
4953
4342
  return outerResult;
@@ -4957,10 +4346,10 @@ async function buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas,
4957
4346
  log.warn(`Chain ${chainId} for token ${tokenId} not found`);
4958
4347
  return outerResult;
4959
4348
  }
4960
- const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", chain);
4349
+ const miniMetadata = miniMetadatas.get(chainId);
4961
4350
  const {
4962
4351
  useLegacyTransferableCalculation
4963
- } = chainMeta ?? {};
4352
+ } = miniMetadata?.extra ?? {};
4964
4353
  addresses.flat().forEach(address => {
4965
4354
  const queryKey = `${tokenId}-${address}`;
4966
4355
  // We share this balanceJson between the base and the lock query for this address
@@ -4968,10 +4357,7 @@ async function buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas,
4968
4357
  source: "substrate-native",
4969
4358
  status: "live",
4970
4359
  address,
4971
- multiChainId: {
4972
- subChainId: chainId
4973
- },
4974
- chainId,
4360
+ networkId: chainId,
4975
4361
  tokenId,
4976
4362
  values: []
4977
4363
  };
@@ -5243,75 +4629,19 @@ const updateStakingLocksUsingUnbondingLocks = values => {
5243
4629
  return [...otherValues, ...stakingLocks];
5244
4630
  };
5245
4631
 
5246
- const detectMiniMetadataChanges = () => {
5247
- let previousMap = null;
5248
- return rxjs.pipe(rxjs.map(currMap => {
5249
- if (!currMap) return null;
5250
- const changes = new Set();
5251
- if (previousMap) {
5252
- // Check for added or changed keys/values
5253
- for (const [key, value] of currMap) {
5254
- if (!previousMap.has(key) || !isEqual__default.default(previousMap.get(key), value)) {
5255
- changes.add(value.chainId);
5256
- }
5257
- }
5258
-
5259
- // Check for removed keys
5260
- for (const [key, value] of previousMap) {
5261
- if (!currMap.has(key)) {
5262
- changes.add(value.chainId);
5263
- }
5264
- }
5265
- }
5266
- previousMap = currMap;
5267
- return changes.size > 0 ? changes : null;
5268
- }),
5269
- // Filter out null emissions (no changes)
5270
- rxjs.filter(changes => changes !== null));
5271
- };
5272
-
5273
- // NOTE: `liveQuery` is not initialized until commonMetadataObservable is subscribed to.
5274
- const commonMetadataObservable = rxjs.from(dexie.liveQuery(() => db.miniMetadatas.where("source").equals("substrate-native").toArray())).pipe(rxjs.map(items => new Map(items.map(item => [item.id, item]))),
5275
- // `refCount: true` will unsubscribe from the DB when commonMetadataObservable has no more subscribers
5276
- rxjs.shareReplay({
5277
- bufferSize: 1,
5278
- refCount: true
5279
- }));
5280
4632
  class QueryCache {
4633
+ #chaindataProvider;
4634
+ #chainConnector;
4635
+ miniMetadatas = new Map();
5281
4636
  balanceQueryCache = new Map();
5282
- metadataSub = null;
5283
- constructor(chaindataProvider) {
4637
+ constructor(chaindataProvider, chainConnector) {
5284
4638
  this.chaindataProvider = chaindataProvider;
5285
- }
5286
- ensureSetup() {
5287
- if (this.metadataSub) return;
5288
- this.metadataSub = commonMetadataObservable.pipe(util.firstThenDebounce(500), detectMiniMetadataChanges(), rxjs.combineLatestWith(this.chaindataProvider.tokensObservable), rxjs.distinctUntilChanged()).subscribe(([miniMetadataChanges, tokens]) => {
5289
- // invalidate cache entries for any chains with new metadata
5290
- const tokensByChainId = tokens.filter(token => token.type === "substrate-native").reduce((result, token) => {
5291
- if (!token.chain?.id) return result;
5292
- result[token.chain.id] ? result[token.chain.id].push(token) : result[token.chain.id] = [token];
5293
- return result;
5294
- }, {});
5295
- miniMetadataChanges.forEach(chainId => {
5296
- const chainTokens = tokensByChainId[chainId];
5297
- if (!chainTokens) return;
5298
- chainTokens.forEach(token => {
5299
- const tokenId = token.id;
5300
- const cacheKeys = this.balanceQueryCache.keys();
5301
- for (const key of cacheKeys) {
5302
- if (key.startsWith(`${tokenId}-`)) this.balanceQueryCache.delete(key);
5303
- }
5304
- });
5305
- });
5306
- });
5307
- }
5308
- destroy() {
5309
- this.metadataSub?.unsubscribe();
4639
+ this.#chaindataProvider = chaindataProvider;
4640
+ this.#chainConnector = chainConnector;
5310
4641
  }
5311
4642
  async getQueries(addressesByToken) {
5312
- this.ensureSetup();
5313
- const chains = await this.chaindataProvider.chainsById();
5314
- const tokens = await this.chaindataProvider.tokensById();
4643
+ const chains = await this.chaindataProvider.getNetworksMapById("polkadot");
4644
+ const tokens = await this.chaindataProvider.getTokensMapById();
5315
4645
  const queryResults = Object.entries(addressesByToken).reduce((result, [tokenId, addresses]) => {
5316
4646
  addresses.forEach(address => {
5317
4647
  const key = `${tokenId}-${address}`;
@@ -5327,15 +4657,19 @@ class QueryCache {
5327
4657
  existing: [],
5328
4658
  newAddressesByToken: {}
5329
4659
  });
4660
+ const byNetwork = getAddresssesByTokenByNetwork(addressesByToken);
4661
+ for (const networkId of lodash.keys(byNetwork)) {
4662
+ if (this.miniMetadatas.has(networkId)) continue;
4663
+ const miniMetadata = await getMiniMetadata(this.#chaindataProvider, this.#chainConnector, networkId, "substrate-native");
4664
+ this.miniMetadatas.set(networkId, miniMetadata);
4665
+ }
5330
4666
 
5331
4667
  // build queries for token/address pairs which have not been queried before
5332
- const miniMetadatas = await rxjs.firstValueFrom(commonMetadataObservable);
5333
- const uniqueChainIds = getUniqueChainIds(queryResults.newAddressesByToken, tokens);
4668
+ const uniqueChainIds = lodash.keys(byNetwork); // getUniqueChainIds(queryResults.newAddressesByToken, tokens)
5334
4669
  const chainStorageCoders = buildStorageCoders({
5335
4670
  chainIds: uniqueChainIds,
5336
4671
  chains,
5337
- miniMetadatas,
5338
- moduleType: "substrate-native",
4672
+ miniMetadatas: this.miniMetadatas,
5339
4673
  coders: {
5340
4674
  base: ["System", "Account"],
5341
4675
  stakingLedger: ["Staking", "Ledger"],
@@ -5345,7 +4679,7 @@ class QueryCache {
5345
4679
  freezes: ["Balances", "Freezes"]
5346
4680
  }
5347
4681
  });
5348
- const queries = await buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas, queryResults.newAddressesByToken);
4682
+ const queries = await buildQueries$1(chains, tokens, chainStorageCoders, this.miniMetadatas, queryResults.newAddressesByToken);
5349
4683
  // now update the cache
5350
4684
  Object.entries(queries).forEach(([key, query]) => {
5351
4685
  this.balanceQueryCache.set(key, query);
@@ -5354,14 +4688,11 @@ class QueryCache {
5354
4688
  }
5355
4689
  }
5356
4690
 
5357
- const RELAY_TOKENS = ["polkadot-substrate-native", "kusama-substrate-native"];
5358
- const PUBLIC_GOODS_TOKENS = ["polkadot-asset-hub-substrate-native", "kusama-asset-hub-substrate-native"];
5359
- const sortChains = (a, b) => {
4691
+ const IMPORTANT_TOKENS = [chaindataProvider.subNativeTokenId("polkadot"), chaindataProvider.subNativeTokenId("kusama"), chaindataProvider.subNativeTokenId("polkadot-asset-hub"), chaindataProvider.subNativeTokenId("kusama-asset-hub"), chaindataProvider.subNativeTokenId("bittensor")];
4692
+ const sortChainsNativeTokensByPriority = (a, b) => {
5360
4693
  // polkadot and kusama should be checked first
5361
- if (RELAY_TOKENS.includes(a)) return -1;
5362
- if (RELAY_TOKENS.includes(b)) return 1;
5363
- if (PUBLIC_GOODS_TOKENS.includes(a)) return -1;
5364
- if (PUBLIC_GOODS_TOKENS.includes(b)) return 1;
4694
+ if (IMPORTANT_TOKENS.includes(a)) return -1;
4695
+ if (IMPORTANT_TOKENS.includes(b)) return 1;
5365
4696
  return 0;
5366
4697
  };
5367
4698
 
@@ -5377,10 +4708,30 @@ class SubNativeBalanceError extends Error {
5377
4708
  }
5378
4709
  }
5379
4710
 
5380
- const DEFAULT_SYMBOL = "Unit";
5381
- const DEFAULT_DECIMALS = 0;
4711
+ const DotNetworkPropertiesSimple = z__default.default.object({
4712
+ tokenDecimals: z__default.default.number().optional().default(0),
4713
+ tokenSymbol: z__default.default.string().optional().default("Unit")
4714
+ });
4715
+ const DotNetworkPropertiesArray = z__default.default.object({
4716
+ tokenDecimals: z__default.default.array(z__default.default.number()).nonempty(),
4717
+ tokenSymbol: z__default.default.array(z__default.default.string()).nonempty()
4718
+ });
4719
+ const DotNetworkPropertiesSchema = z__default.default.union([DotNetworkPropertiesSimple, DotNetworkPropertiesArray]).transform(val => ({
4720
+ tokenDecimals: Array.isArray(val.tokenDecimals) ? val.tokenDecimals[0] : val.tokenDecimals,
4721
+ tokenSymbol: Array.isArray(val.tokenSymbol) ? val.tokenSymbol[0] : val.tokenSymbol
4722
+ }));
4723
+ const getChainProperties = async (chainConnector, networkId) => {
4724
+ const properties = await chainConnector.send(networkId, "system_properties", [], true);
4725
+ return DotNetworkPropertiesSchema.parse(properties);
4726
+ };
4727
+
5382
4728
  const POLLING_WINDOW_SIZE = 20;
5383
4729
  const MAX_SUBSCRIPTION_SIZE = 40;
4730
+ const EMPTY_CHAIN_META = {
4731
+ miniMetadata: null,
4732
+ extra: null
4733
+ };
4734
+ const SubNativeTokenConfigSchema = TokenConfigBaseSchema;
5384
4735
  const SubNativeModule = hydrate => {
5385
4736
  const {
5386
4737
  chainConnectors,
@@ -5388,33 +4739,198 @@ const SubNativeModule = hydrate => {
5388
4739
  } = hydrate;
5389
4740
  const chainConnector$1 = chainConnectors.substrate;
5390
4741
  util$1.assert(chainConnector$1, "This module requires a substrate chain connector");
5391
- const queryCache = new QueryCache(chaindataProvider$1);
4742
+ const queryCache = new QueryCache(chaindataProvider$1, chainConnector$1);
5392
4743
  const getModuleTokens = async () => {
5393
- return await chaindataProvider$1.tokensByIdForType(moduleType$2);
4744
+ return await chaindataProvider$1.getTokensMapById(moduleType$2);
5394
4745
  };
5395
- return {
5396
- ...DefaultBalanceModule(moduleType$2),
5397
- async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc, systemProperties) {
5398
- const isTestnet = (await chaindataProvider$1.chainById(chainId))?.isTestnet || false;
5399
- if (moduleConfig?.disable === true || metadataRpc === undefined) return {
5400
- isTestnet
5401
- };
5402
4746
 
5403
- //
5404
- // extract system_properties
5405
- //
4747
+ // subscribeBalances was split by network to prevent all subs to wait for all minimetadatas to be ready.
4748
+ // 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
4749
+ // TODO refactor this be actually network specific
4750
+ // Note: had to extract this function from the result object or this.subscribeBalances wouldn't be typed correctly
4751
+ const subscribeChainBalances = (chainId, opts, callback, signal) => {
4752
+ const {
4753
+ addressesByToken,
4754
+ initialBalances
4755
+ } = opts;
4756
+ // full record of balances for this module
4757
+ const subNativeBalances = new rxjs.BehaviorSubject(Object.fromEntries(initialBalances?.map(b => [getBalanceId(b), b]) ?? []));
4758
+ // tokens which have a known positive balance
4759
+ const positiveBalanceTokens = subNativeBalances.pipe(rxjs.map(balances => Array.from(new Set(Object.values(balances).map(b => b.tokenId)))), rxjs.share());
4760
+
4761
+ // tokens that will be subscribed to, simply a slice of the positive balance tokens of size MAX_SUBSCRIPTION_SIZE
4762
+ const subscriptionTokens = positiveBalanceTokens.pipe(rxjs.map(tokens => tokens.sort(sortChainsNativeTokensByPriority).slice(0, MAX_SUBSCRIPTION_SIZE)));
4763
+
4764
+ // an initialised balance is one where we have received a response for any type of 'subsource',
4765
+ // until then they are initialising. We only need to maintain one map of tokens to addresses for this
4766
+ const initialisingBalances = Object.entries(addressesByToken).reduce((acc, [tokenId, addresses]) => {
4767
+ acc.set(tokenId, new Set(addresses));
4768
+ return acc;
4769
+ }, new Map());
4770
+
4771
+ // after thirty seconds, we need to kill the initialising balances
4772
+ const initBalancesTimeout = setTimeout(() => {
4773
+ initialisingBalances.clear();
4774
+ // manually call the callback to ensure the caller gets the correct status
4775
+ callback(null, {
4776
+ status: "live",
4777
+ data: Object.values(subNativeBalances.getValue())
4778
+ });
4779
+ }, 30_000);
4780
+ const _callbackSub = subNativeBalances.pipe(rxjs.debounceTime(100)).subscribe({
4781
+ next: balances => {
4782
+ callback(null, {
4783
+ status: initialisingBalances.size > 0 ? "initialising" : "live",
4784
+ data: Object.values(balances)
4785
+ });
4786
+ },
4787
+ error: error => callback(error),
4788
+ complete: () => {
4789
+ initialisingBalances.clear();
4790
+ clearTimeout(initBalancesTimeout);
4791
+ }
4792
+ });
4793
+ const unsubDeferred = util.Deferred();
4794
+ // we return this to the caller so that they can let us know when they're no longer interested in this subscription
4795
+ const callerUnsubscribe = () => {
4796
+ subNativeBalances.complete();
4797
+ _callbackSub.unsubscribe();
4798
+ return unsubDeferred.reject(new Error(`Caller unsubscribed`));
4799
+ };
4800
+ // we queue up our work to clean up our subscription when this promise rejects
4801
+ const callerUnsubscribed = unsubDeferred.promise;
4802
+
4803
+ // The update handler is to allow us to merge balances with the same id, and manage initialising and positive balances state for each
4804
+ // balance type and network
4805
+ const handleUpdateForSource = source => (error, result) => {
4806
+ if (result) {
4807
+ const currentBalances = subNativeBalances.getValue();
4808
+
4809
+ // first merge any balances with the same id within the result
4810
+ const accumulatedUpdates = result.filter(b => b.values.length > 0).reduce((acc, b) => {
4811
+ const bId = getBalanceId(b);
4812
+ acc[bId] = mergeBalances(acc[bId], b, source, false);
4813
+ return acc;
4814
+ }, {});
4815
+
4816
+ // then merge these with the current balances
4817
+ const mergedBalances = {};
4818
+ Object.entries(accumulatedUpdates).forEach(([bId, b]) => {
4819
+ // merge the values from the new balance into the existing balance, if there is one
4820
+ mergedBalances[bId] = mergeBalances(currentBalances[bId], b, source, true);
4821
+
4822
+ // update initialisingBalances to remove balances which have been updated
4823
+ const intialisingForToken = initialisingBalances.get(b.tokenId);
4824
+ if (intialisingForToken) {
4825
+ intialisingForToken.delete(b.address);
4826
+ if (intialisingForToken.size === 0) initialisingBalances.delete(b.tokenId);else initialisingBalances.set(b.tokenId, intialisingForToken);
4827
+ }
4828
+ });
4829
+ subNativeBalances.next({
4830
+ ...currentBalances,
4831
+ ...mergedBalances
4832
+ });
4833
+ }
4834
+ if (error) {
4835
+ if (error instanceof SubNativeBalanceError) {
4836
+ // this type of error doesn't need to be handled by the caller
4837
+ initialisingBalances.delete(error.tokenId);
4838
+ } else return callback(error);
4839
+ }
4840
+ };
5406
4841
 
5407
- const {
5408
- tokenSymbol,
5409
- tokenDecimals
5410
- } = systemProperties ?? {};
5411
- const symbol = (Array.isArray(tokenSymbol) ? tokenSymbol[0] : tokenSymbol) ?? DEFAULT_SYMBOL;
5412
- const decimals = (Array.isArray(tokenDecimals) ? tokenDecimals[0] : tokenDecimals) ?? DEFAULT_DECIMALS;
4842
+ // subscribe to addresses and tokens for which we have a known positive balance
4843
+ const positiveSub = subscriptionTokens.pipe(rxjs.debounceTime(1000), rxjs.takeUntil(callerUnsubscribed), rxjs.map(tokenIds => tokenIds.reduce((acc, tokenId) => {
4844
+ acc[tokenId] = addressesByToken[tokenId];
4845
+ return acc;
4846
+ }, {})), rxjs.distinctUntilChanged(isEqual__default.default), rxjs.switchMap(newAddressesByToken => {
4847
+ return rxjs.from(queryCache.getQueries(newAddressesByToken)).pipe(rxjs.switchMap(baseQueries => {
4848
+ return new rxjs.Observable(subscriber => {
4849
+ if (!chainConnectors.substrate) return;
4850
+ const unsubSubtensorStaking = subscribeSubtensorStaking(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("subtensor-staking"), signal);
4851
+ const unsubNompoolStaking = subscribeNompoolStaking(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("nompools-staking"), signal);
4852
+ const unsubBase = subscribeBase(baseQueries, chainConnectors.substrate, handleUpdateForSource("base"));
4853
+ subscriber.add(async () => (await unsubSubtensorStaking)());
4854
+ subscriber.add(async () => (await unsubNompoolStaking)());
4855
+ subscriber.add(async () => (await unsubBase)());
4856
+ });
4857
+ }));
4858
+ })).subscribe();
4859
+
4860
+ // for chains where we don't have a known positive balance, poll rather than subscribe
4861
+ const poll = async (addressesByToken = {}) => {
4862
+ const handleUpdate = handleUpdateForSource("base");
4863
+ try {
4864
+ const balances = await fetchBalances(addressesByToken);
4865
+ handleUpdate(null, Object.values(balances.toJSON()));
4866
+ } catch (error) {
4867
+ if (error instanceof chainConnector.ChainConnectionError) {
4868
+ // coerce ChainConnection errors into SubNativeBalance errors
4869
+ const errorChainId = error.chainId;
4870
+ Object.entries(await getModuleTokens()).filter(([, token]) => token.networkId === errorChainId).forEach(([tokenId]) => {
4871
+ const wrappedError = new SubNativeBalanceError(tokenId, error.message);
4872
+ handleUpdate(wrappedError);
4873
+ });
4874
+ } else {
4875
+ log.error("unknown substrate native balance error", error);
4876
+ handleUpdate(error);
4877
+ }
4878
+ }
4879
+ };
4880
+ // do one poll to get things started
4881
+ const currentBalances = subNativeBalances.getValue();
4882
+ const currentTokens = new Set(Object.values(currentBalances).map(b => b.tokenId));
4883
+ const nonCurrentTokens = Object.keys(addressesByToken).filter(tokenId => !currentTokens.has(tokenId)).sort(sortChainsNativeTokensByPriority);
4884
+
4885
+ // break nonCurrentTokens into chunks of POLLING_WINDOW_SIZE
4886
+ const pool = new PQueue__default.default({
4887
+ concurrency: POLLING_WINDOW_SIZE
4888
+ });
4889
+ nonCurrentTokens.forEach(nonCurrentTokenId => pool.add(() => poll({
4890
+ [nonCurrentTokenId]: addressesByToken[nonCurrentTokenId]
4891
+ }), {
4892
+ signal
4893
+ }));
4894
+
4895
+ // now poll every 30s on chains which are not subscriptionTokens
4896
+ // we chunk this observable into batches of positive token ids, to prevent eating all the websocket connections
4897
+ const pollingSub = rxjs.interval(30_000) // emit values every 30 seconds
4898
+ .pipe(rxjs.takeUntil(callerUnsubscribed), rxjs.withLatestFrom(subscriptionTokens),
4899
+ // Combine latest value from subscriptionTokens with each interval tick
4900
+ rxjs.map(([, subscribedTokenIds]) =>
4901
+ // Filter out tokens that are not subscribed
4902
+ Object.keys(addressesByToken).filter(tokenId => !subscribedTokenIds.includes(tokenId))), rxjs.exhaustMap(tokenIds => rxjs.from(util$1.arrayChunk(tokenIds, POLLING_WINDOW_SIZE)).pipe(rxjs.concatMap(async tokenChunk => {
4903
+ // tokenChunk is a chunk of tokenIds with size POLLING_WINDOW_SIZE
4904
+ const pollingTokenAddresses = Object.fromEntries(tokenChunk.map(tokenId => [tokenId, addressesByToken[tokenId]]));
4905
+ await pool.add(() => poll(pollingTokenAddresses), {
4906
+ signal
4907
+ });
4908
+ return true;
4909
+ })))).subscribe();
4910
+ return () => {
4911
+ callerUnsubscribe();
4912
+ positiveSub.unsubscribe();
4913
+ pollingSub.unsubscribe();
4914
+ };
4915
+ };
4916
+ const fetchBalances = async addressesByToken => {
4917
+ util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
4918
+ const queries = await queryCache.getQueries(addressesByToken);
4919
+ util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
4920
+ const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
4921
+ return new Balances(result ?? []);
4922
+ };
4923
+ return {
4924
+ ...DefaultBalanceModule(moduleType$2),
4925
+ async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
4926
+ if (moduleConfig?.disable) return EMPTY_CHAIN_META;
4927
+ if (!metadataRpc) return EMPTY_CHAIN_META;
5413
4928
 
5414
4929
  //
5415
4930
  // process metadata into SCALE encoders/decoders
5416
4931
  //
5417
4932
  const metadataVersion = scale.getMetadataVersion(metadataRpc);
4933
+ if (metadataVersion < 14) return EMPTY_CHAIN_META;
5418
4934
  const metadata = scale.decAnyMetadata(metadataRpc);
5419
4935
  const unifiedMetadata = scale.unifyMetadata(metadata);
5420
4936
 
@@ -5434,7 +4950,6 @@ const SubNativeModule = hydrate => {
5434
4950
  };
5435
4951
  const existentialDeposit = getConstantValue("Balances", "ExistentialDeposit")?.toString();
5436
4952
  const nominationPoolsPalletId = getConstantValue("NominationPools", "PalletId")?.asText();
5437
- const crowdloanPalletId = getConstantValue("Crowdloan", "PalletId")?.asText();
5438
4953
  const hasSubtensorPallet = getConstantValue("SubtensorModule", "KeySwapCost") !== undefined;
5439
4954
 
5440
4955
  //
@@ -5454,15 +4969,10 @@ const SubNativeModule = hydrate => {
5454
4969
  }, {
5455
4970
  pallet: "Staking",
5456
4971
  items: ["Ledger"]
5457
- }, {
5458
- pallet: "Crowdloan",
5459
- items: ["Funds"]
5460
- }, {
5461
- pallet: "Paras",
5462
- items: ["Parachains"]
5463
4972
  },
5464
4973
  // TotalColdkeyStake is used until v.2.2.1, then it is replaced by StakingHotkeys+Stake
5465
4974
  // Need to keep TotalColdkeyStake for a while so chaindata keeps including it in miniMetadatas, so it doesnt break old versions of the wallet
4975
+ // TODO: Since chaindata v4 this is safe to now delete
5466
4976
  {
5467
4977
  pallet: "SubtensorModule",
5468
4978
  items: ["TotalColdkeyStake", "StakingHotkeys", "Stake"]
@@ -5481,47 +4991,40 @@ const SubNativeModule = hydrate => {
5481
4991
  }) => name === "Freezes"));
5482
4992
  const useLegacyTransferableCalculation = !hasFreezesItem;
5483
4993
  const chainMeta = {
5484
- isTestnet,
5485
- useLegacyTransferableCalculation,
5486
- symbol,
5487
- decimals,
5488
- existentialDeposit,
5489
- nominationPoolsPalletId,
5490
- crowdloanPalletId,
5491
- hasSubtensorPallet,
5492
4994
  miniMetadata,
5493
- metadataVersion
4995
+ extra: {
4996
+ useLegacyTransferableCalculation,
4997
+ existentialDeposit,
4998
+ nominationPoolsPalletId,
4999
+ hasSubtensorPallet
5000
+ }
5494
5001
  };
5495
- if (!useLegacyTransferableCalculation) delete chainMeta.useLegacyTransferableCalculation;
5496
- if (!hasSubtensorPallet) delete chainMeta.hasSubtensorPallet;
5002
+ if (!useLegacyTransferableCalculation) delete chainMeta.extra?.useLegacyTransferableCalculation;
5003
+ if (!hasSubtensorPallet) delete chainMeta.extra?.hasSubtensorPallet;
5497
5004
  return chainMeta;
5498
5005
  },
5499
5006
  async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
5500
5007
  if (moduleConfig?.disable === true) return {};
5501
5008
  const {
5502
- isTestnet,
5503
- symbol,
5504
- decimals,
5009
+ tokenSymbol: symbol,
5010
+ tokenDecimals: decimals
5011
+ } = await getChainProperties(chainConnector$1, chainId);
5012
+ const {
5505
5013
  existentialDeposit
5506
- } = chainMeta;
5507
- const id = subNativeTokenId(chainId);
5014
+ } = chainMeta.extra ?? {};
5015
+ if (existentialDeposit === undefined) log.warn("Substrate native module: existentialDeposit is undefined for %s, using 0", chainId);
5016
+ const id = chaindataProvider.subNativeTokenId(chainId);
5508
5017
  const nativeToken = {
5509
5018
  id,
5510
5019
  type: "substrate-native",
5511
- isTestnet,
5512
- isDefault: moduleConfig?.isDefault ?? true,
5513
- symbol: symbol ?? DEFAULT_SYMBOL,
5514
- decimals: decimals ?? DEFAULT_DECIMALS,
5515
- logo: moduleConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
5020
+ platform: "polkadot",
5021
+ isDefault: true,
5022
+ symbol: symbol,
5023
+ name: symbol,
5024
+ decimals: decimals,
5516
5025
  existentialDeposit: existentialDeposit ?? "0",
5517
- chain: {
5518
- id: chainId
5519
- }
5026
+ networkId: chainId
5520
5027
  };
5521
- if (moduleConfig?.symbol) nativeToken.symbol = moduleConfig?.symbol;
5522
- if (moduleConfig?.coingeckoId) nativeToken.coingeckoId = moduleConfig?.coingeckoId;
5523
- if (moduleConfig?.dcentName) nativeToken.dcentName = moduleConfig?.dcentName;
5524
- if (moduleConfig?.mirrorOf) nativeToken.mirrorOf = moduleConfig?.mirrorOf;
5525
5028
  return {
5526
5029
  [nativeToken.id]: nativeToken
5527
5030
  };
@@ -5531,169 +5034,36 @@ const SubNativeModule = hydrate => {
5531
5034
  initialBalances
5532
5035
  }, callback) {
5533
5036
  util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
5534
-
5535
- // full record of balances for this module
5536
- const subNativeBalances = new rxjs.BehaviorSubject(Object.fromEntries(initialBalances?.map(b => [getBalanceId(b), b]) ?? []));
5537
- // tokens which have a known positive balance
5538
- const positiveBalanceTokens = subNativeBalances.pipe(rxjs.map(balances => Array.from(new Set(Object.values(balances).map(b => b.tokenId)))), rxjs.share());
5539
-
5540
- // tokens that will be subscribed to, simply a slice of the positive balance tokens of size MAX_SUBSCRIPTION_SIZE
5541
- const subscriptionTokens = positiveBalanceTokens.pipe(rxjs.map(tokens => tokens.sort(sortChains).slice(0, MAX_SUBSCRIPTION_SIZE)));
5542
-
5543
- // an initialised balance is one where we have received a response for any type of 'subsource',
5544
- // until then they are initialising. We only need to maintain one map of tokens to addresses for this
5545
- const initialisingBalances = Object.entries(addressesByToken).reduce((acc, [tokenId, addresses]) => {
5546
- acc.set(tokenId, new Set(addresses));
5547
- return acc;
5548
- }, new Map());
5549
-
5550
- // after thirty seconds, we need to kill the initialising balances
5551
- const initBalancesTimeout = setTimeout(() => {
5552
- initialisingBalances.clear();
5553
- // manually call the callback to ensure the caller gets the correct status
5554
- callback(null, {
5555
- status: "live",
5556
- data: Object.values(subNativeBalances.getValue())
5557
- });
5558
- }, 30_000);
5559
- const _callbackSub = subNativeBalances.pipe(rxjs.debounceTime(100)).subscribe({
5560
- next: balances => {
5561
- callback(null, {
5562
- status: initialisingBalances.size > 0 ? "initialising" : "live",
5563
- data: Object.values(balances)
5564
- });
5565
- },
5566
- error: error => callback(error),
5567
- complete: () => {
5568
- initialisingBalances.clear();
5569
- clearTimeout(initBalancesTimeout);
5570
- }
5571
- });
5572
- const unsubDeferred = util.Deferred();
5573
- // we return this to the caller so that they can let us know when they're no longer interested in this subscription
5574
- const callerUnsubscribe = () => {
5575
- subNativeBalances.complete();
5576
- _callbackSub.unsubscribe();
5577
- return unsubDeferred.reject(new Error(`Caller unsubscribed`));
5037
+ const addressesByTokenByNetwork = getAddresssesByTokenByNetwork(addressesByToken);
5038
+ const initialBalancesByNetwork = lodash.groupBy(initialBalances ?? [], "networkId");
5039
+ const controller = new AbortController();
5040
+ const safeCallback = (error, result) => {
5041
+ if (controller.signal.aborted) return;
5042
+ if (util.isAbortError(error)) return;
5043
+ // typescript isnt happy with fowarding parameters as is
5044
+ return error ? callback(error, undefined) : callback(error, result);
5578
5045
  };
5579
- // we queue up our work to clean up our subscription when this promise rejects
5580
- const callerUnsubscribed = unsubDeferred.promise;
5581
-
5582
- // The update handler is to allow us to merge balances with the same id, and manage initialising and positive balances state for each
5583
- // balance type and network
5584
- const handleUpdateForSource = source => (error, result) => {
5585
- if (result) {
5586
- const currentBalances = subNativeBalances.getValue();
5587
-
5588
- // first merge any balances with the same id within the result
5589
- const accumulatedUpdates = result.filter(b => b.values.length > 0).reduce((acc, b) => {
5590
- const bId = getBalanceId(b);
5591
- acc[bId] = mergeBalances(acc[bId], b, source, false);
5592
- return acc;
5593
- }, {});
5594
-
5595
- // then merge these with the current balances
5596
- const mergedBalances = {};
5597
- Object.entries(accumulatedUpdates).forEach(([bId, b]) => {
5598
- // merge the values from the new balance into the existing balance, if there is one
5599
- mergedBalances[bId] = mergeBalances(currentBalances[bId], b, source, true);
5600
-
5601
- // update initialisingBalances to remove balances which have been updated
5602
- const intialisingForToken = initialisingBalances.get(b.tokenId);
5603
- if (intialisingForToken) {
5604
- intialisingForToken.delete(b.address);
5605
- if (intialisingForToken.size === 0) initialisingBalances.delete(b.tokenId);else initialisingBalances.set(b.tokenId, intialisingForToken);
5606
- }
5607
- });
5608
- subNativeBalances.next({
5609
- ...currentBalances,
5610
- ...mergedBalances
5611
- });
5612
- }
5613
- if (error) {
5614
- if (error instanceof SubNativeBalanceError) {
5615
- // this type of error doesn't need to be handled by the caller
5616
- initialisingBalances.delete(error.tokenId);
5617
- } else return callback(error);
5618
- }
5619
- };
5620
-
5621
- // subscribe to addresses and tokens for which we have a known positive balance
5622
- const positiveSub = subscriptionTokens.pipe(rxjs.debounceTime(1000), rxjs.takeUntil(callerUnsubscribed), rxjs.map(tokenIds => tokenIds.reduce((acc, tokenId) => {
5623
- acc[tokenId] = addressesByToken[tokenId];
5624
- return acc;
5625
- }, {})), rxjs.distinctUntilChanged(isEqual__default.default), rxjs.switchMap(newAddressesByToken => {
5626
- return rxjs.from(queryCache.getQueries(newAddressesByToken)).pipe(rxjs.switchMap(baseQueries => {
5627
- return new rxjs.Observable(subscriber => {
5628
- if (!chainConnectors.substrate) return;
5629
- const unsubSubtensorStaking = subscribeSubtensorStaking(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("subtensor-staking"));
5630
- const unsubNompoolStaking = subscribeNompoolStaking(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("nompools-staking"));
5631
- const unsubCrowdloans = subscribeCrowdloans(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("crowdloan"));
5632
- const unsubBase = subscribeBase(baseQueries, chainConnectors.substrate, handleUpdateForSource("base"));
5633
- subscriber.add(async () => (await unsubSubtensorStaking)());
5634
- subscriber.add(async () => (await unsubNompoolStaking)());
5635
- subscriber.add(async () => (await unsubCrowdloans)());
5636
- subscriber.add(async () => (await unsubBase)());
5637
- });
5638
- }));
5639
- })).subscribe();
5640
-
5641
- // for chains where we don't have a known positive balance, poll rather than subscribe
5642
- const poll = async (addressesByToken = {}) => {
5643
- const handleUpdate = handleUpdateForSource("base");
5046
+ const unsubsribeFns = Promise.all(lodash.keys(addressesByTokenByNetwork).map(async networkId => {
5644
5047
  try {
5645
- const balances = await this.fetchBalances(addressesByToken);
5646
- handleUpdate(null, Object.values(balances.toJSON()));
5647
- } catch (error) {
5648
- if (error instanceof chainConnector.ChainConnectionError) {
5649
- // coerce ChainConnection errors into SubNativeBalance errors
5650
- const errorChainId = error.chainId;
5651
- Object.entries(await getModuleTokens()).filter(([, token]) => token.chain?.id === errorChainId).forEach(([tokenId]) => {
5652
- const wrappedError = new SubNativeBalanceError(tokenId, error.message);
5653
- handleUpdate(wrappedError);
5654
- });
5655
- } else {
5656
- log.error("unknown substrate native balance error", error);
5657
- handleUpdate(error);
5658
- }
5048
+ // this is what we want to be done separately for each network
5049
+ // this will update the DB so minimetadata will be available when it's used, everywhere else down the tree of subscribeChainBalances
5050
+ await getMiniMetadata(chaindataProvider$1, chainConnector$1, networkId, moduleType$2, controller.signal);
5051
+ } catch (err) {
5052
+ if (!util.isAbortError(err)) log.warn("Failed to get native token miniMetadata for network", networkId, err);
5053
+ return () => {};
5659
5054
  }
5660
- };
5661
- // do one poll to get things started
5662
- const currentBalances = subNativeBalances.getValue();
5663
- const currentTokens = new Set(Object.values(currentBalances).map(b => b.tokenId));
5664
- const nonCurrentTokens = Object.keys(addressesByToken).filter(tokenId => !currentTokens.has(tokenId)).sort(sortChains);
5665
-
5666
- // break nonCurrentTokens into chunks of POLLING_WINDOW_SIZE
5667
- await PromisePool__default.default.withConcurrency(POLLING_WINDOW_SIZE).for(nonCurrentTokens).process(async nonCurrentTokenId => await poll({
5668
- [nonCurrentTokenId]: addressesByToken[nonCurrentTokenId]
5055
+ if (controller.signal.aborted) return () => {};
5056
+ return subscribeChainBalances(networkId, {
5057
+ addressesByToken: addressesByTokenByNetwork[networkId] ?? {},
5058
+ initialBalances: initialBalancesByNetwork[networkId] ?? []
5059
+ }, safeCallback, controller.signal);
5669
5060
  }));
5670
-
5671
- // now poll every 30s on chains which are not subscriptionTokens
5672
- // we chunk this observable into batches of positive token ids, to prevent eating all the websocket connections
5673
- const pollingSub = rxjs.interval(30_000) // emit values every 30 seconds
5674
- .pipe(rxjs.takeUntil(callerUnsubscribed), rxjs.withLatestFrom(subscriptionTokens),
5675
- // Combine latest value from subscriptionTokens with each interval tick
5676
- rxjs.map(([, subscribedTokenIds]) =>
5677
- // Filter out tokens that are not subscribed
5678
- Object.keys(addressesByToken).filter(tokenId => !subscribedTokenIds.includes(tokenId))), rxjs.exhaustMap(tokenIds => rxjs.from(util$1.arrayChunk(tokenIds, POLLING_WINDOW_SIZE)).pipe(rxjs.concatMap(async tokenChunk => {
5679
- // tokenChunk is a chunk of tokenIds with size POLLING_WINDOW_SIZE
5680
- const pollingTokenAddresses = Object.fromEntries(tokenChunk.map(tokenId => [tokenId, addressesByToken[tokenId]]));
5681
- await poll(pollingTokenAddresses);
5682
- return true;
5683
- })))).subscribe();
5684
5061
  return () => {
5685
- callerUnsubscribe();
5686
- positiveSub.unsubscribe();
5687
- pollingSub.unsubscribe();
5062
+ unsubsribeFns.then(fns => fns.forEach(unsubscribe => unsubscribe()));
5063
+ controller.abort();
5688
5064
  };
5689
5065
  },
5690
- async fetchBalances(addressesByToken) {
5691
- util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
5692
- const queries = await queryCache.getQueries(addressesByToken);
5693
- util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
5694
- const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
5695
- return new Balances(result ?? []);
5696
- },
5066
+ fetchBalances,
5697
5067
  async transferToken({
5698
5068
  tokenId,
5699
5069
  from,
@@ -5710,11 +5080,10 @@ const SubNativeModule = hydrate => {
5710
5080
  transferMethod,
5711
5081
  userExtensions
5712
5082
  }) {
5713
- const token = await chaindataProvider$1.tokenById(tokenId);
5083
+ const token = await chaindataProvider$1.getTokenById(tokenId, "substrate-native");
5714
5084
  util$1.assert(token, `Token ${tokenId} not found in store`);
5715
- if (token.type !== "substrate-native") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
5716
- const chainId = token.chain.id;
5717
- const chain = await chaindataProvider$1.chainById(chainId);
5085
+ const chainId = token.networkId;
5086
+ const chain = await chaindataProvider$1.getNetworkById(chainId, "polkadot");
5718
5087
  util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
5719
5088
  const {
5720
5089
  genesisHash
@@ -6908,7 +6277,10 @@ var psp22Abi = {
6908
6277
  };
6909
6278
 
6910
6279
  const moduleType$1 = "substrate-psp22";
6911
- const subPsp22TokenId = (chainId, tokenSymbol) => `${chainId}-substrate-psp22-${tokenSymbol}`.toLowerCase().replace(/ /g, "-");
6280
+ const SubPsp22TokenConfigSchema = z__default.default.strictObject({
6281
+ contractAddress: chaindataProvider.SubPsp22TokenSchema.shape.contractAddress,
6282
+ ...TokenConfigBaseSchema.shape
6283
+ });
6912
6284
  const SubPsp22Module = hydrate => {
6913
6285
  const {
6914
6286
  chainConnectors,
@@ -6918,16 +6290,15 @@ const SubPsp22Module = hydrate => {
6918
6290
  util$1.assert(chainConnector, "This module requires a substrate chain connector");
6919
6291
  return {
6920
6292
  ...DefaultBalanceModule(moduleType$1),
6921
- async fetchSubstrateChainMeta(chainId) {
6922
- const isTestnet = (await chaindataProvider$1.chainById(chainId))?.isTestnet || false;
6293
+ async fetchSubstrateChainMeta(_chainId) {
6294
+ // we dont need anything
6923
6295
  return {
6924
- isTestnet
6296
+ miniMetadata: null,
6297
+ extra: null
6925
6298
  };
6926
6299
  },
6927
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
6928
- const {
6929
- isTestnet
6930
- } = chainMeta;
6300
+ async fetchSubstrateChainTokens(chainId, _chainMeta, moduleConfig, tokens) {
6301
+ if (!tokens?.length) return {};
6931
6302
  const registry = new types.TypeRegistry();
6932
6303
  const Psp22Abi = new apiContract.Abi(psp22Abi);
6933
6304
 
@@ -6937,12 +6308,11 @@ const SubPsp22Module = hydrate => {
6937
6308
  chainId,
6938
6309
  registry
6939
6310
  });
6940
- const tokens = {};
6941
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
6311
+ const tokenList = {};
6312
+ for (const tokenConfig of tokens ?? []) {
6942
6313
  try {
6943
6314
  let symbol = tokenConfig?.symbol ?? "Unit";
6944
6315
  let decimals = tokenConfig?.decimals ?? 0;
6945
- const existentialDeposit = tokenConfig?.ed ?? "0";
6946
6316
  const contractAddress = tokenConfig?.contractAddress ?? undefined;
6947
6317
  if (contractAddress === undefined) continue;
6948
6318
  await (async () => {
@@ -6958,35 +6328,28 @@ const SubPsp22Module = hydrate => {
6958
6328
  const decimalsData = decimalsResult.toJSON()?.result?.ok?.data;
6959
6329
  decimals = typeof decimalsData === "string" && decimalsData.startsWith("0x") ? util$1.hexToNumber(decimalsData) : decimals;
6960
6330
  })();
6961
- const id = subPsp22TokenId(chainId, symbol);
6331
+ const id = chaindataProvider.subPsp22TokenId(chainId, contractAddress);
6962
6332
  const token = {
6963
6333
  id,
6964
6334
  type: "substrate-psp22",
6965
- isTestnet,
6335
+ platform: "polkadot",
6966
6336
  isDefault: tokenConfig.isDefault ?? true,
6967
6337
  symbol,
6968
6338
  decimals,
6969
- logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
6970
- existentialDeposit,
6339
+ name: tokenConfig?.name || symbol,
6340
+ logo: tokenConfig?.logo,
6971
6341
  contractAddress,
6972
- chain: {
6973
- id: chainId
6974
- }
6342
+ networkId: chainId
6975
6343
  };
6976
- if (tokenConfig?.symbol) {
6977
- token.symbol = tokenConfig?.symbol;
6978
- token.id = subPsp22TokenId(chainId, token.symbol);
6979
- }
6980
6344
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
6981
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
6982
6345
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
6983
- tokens[token.id] = token;
6346
+ tokenList[token.id] = token;
6984
6347
  } catch (error) {
6985
6348
  log.error(`Failed to build substrate-psp22 token ${tokenConfig.contractAddress} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
6986
6349
  continue;
6987
6350
  }
6988
6351
  }
6989
- return tokens;
6352
+ return tokenList;
6990
6353
  },
6991
6354
  // TODO: Don't create empty subscriptions
6992
6355
  async subscribeBalances({
@@ -6996,7 +6359,7 @@ const SubPsp22Module = hydrate => {
6996
6359
  const subscriptionInterval = 12_000; // 12_000ms == 12 seconds
6997
6360
  const initDelay = 3_000; // 3000ms == 3 seconds
6998
6361
  const cache = new Map();
6999
- const tokens = await chaindataProvider$1.tokensById();
6362
+ const tokens = await chaindataProvider$1.getTokensMapById();
7000
6363
  const poll = async () => {
7001
6364
  if (!subscriptionActive) return;
7002
6365
  try {
@@ -7023,7 +6386,7 @@ const SubPsp22Module = hydrate => {
7023
6386
  },
7024
6387
  async fetchBalances(addressesByToken) {
7025
6388
  util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
7026
- const tokens = await chaindataProvider$1.tokensById();
6389
+ const tokens = await chaindataProvider$1.getTokensMapById();
7027
6390
  return fetchBalances(chainConnectors.substrate, tokens, addressesByToken);
7028
6391
  },
7029
6392
  async transferToken({
@@ -7041,11 +6404,11 @@ const SubPsp22Module = hydrate => {
7041
6404
  tip,
7042
6405
  userExtensions
7043
6406
  }) {
7044
- const token = await chaindataProvider$1.tokenById(tokenId);
6407
+ const token = await chaindataProvider$1.getTokenById(tokenId, "substrate-psp22");
7045
6408
  util$1.assert(token, `Token ${tokenId} not found in store`);
7046
6409
  if (token.type !== "substrate-psp22") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
7047
- const chainId = token.chain.id;
7048
- const chain = await chaindataProvider$1.chainById(chainId);
6410
+ const chainId = token.networkId;
6411
+ const chain = await chaindataProvider$1.getNetworkById(chainId, "polkadot");
7049
6412
  util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
7050
6413
  const {
7051
6414
  genesisHash
@@ -7121,7 +6484,7 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
7121
6484
  // TODO: Use `decodeOutput` from `./util/decodeOutput`
7122
6485
  const contractCall = makeContractCaller({
7123
6486
  chainConnector,
7124
- chainId: token.chain.id,
6487
+ chainId: token.networkId,
7125
6488
  registry
7126
6489
  });
7127
6490
  if (token.contractAddress === undefined) {
@@ -7138,10 +6501,7 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
7138
6501
  source: "substrate-psp22",
7139
6502
  status: "live",
7140
6503
  address,
7141
- multiChainId: {
7142
- subChainId: token.chain.id
7143
- },
7144
- chainId: token.chain.id,
6504
+ networkId: token.networkId,
7145
6505
  tokenId,
7146
6506
  value: balance
7147
6507
  };
@@ -7164,8 +6524,16 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
7164
6524
  };
7165
6525
 
7166
6526
  const moduleType = "substrate-tokens";
6527
+ const SubTokensTokenConfigSchema = z__default.default.strictObject({
6528
+ onChainId: chaindataProvider.SubTokensTokenSchema.shape.onChainId,
6529
+ ...TokenConfigBaseSchema.shape,
6530
+ existentialDeposit: chaindataProvider.SubTokensTokenSchema.shape.existentialDeposit.optional()
6531
+ });
7167
6532
  const defaultPalletId = "Tokens";
7168
- const subTokensTokenId = (chainId, onChainId) => `${chainId}-substrate-tokens-${lzString.compressToEncodedURIComponent(String(onChainId))}`;
6533
+ const UNSUPPORTED_CHAIN_META = {
6534
+ miniMetadata: null,
6535
+ extra: {}
6536
+ };
7169
6537
  const SubTokensModule = hydrate => {
7170
6538
  const {
7171
6539
  chainConnectors,
@@ -7176,14 +6544,7 @@ const SubTokensModule = hydrate => {
7176
6544
  return {
7177
6545
  ...DefaultBalanceModule(moduleType),
7178
6546
  async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
7179
- const isTestnet = (await chaindataProvider$1.chainById(chainId))?.isTestnet || false;
7180
- if (metadataRpc === undefined) return {
7181
- isTestnet
7182
- };
7183
- if ((moduleConfig?.tokens ?? []).length < 1) return {
7184
- isTestnet
7185
- };
7186
- const metadataVersion = scale.getMetadataVersion(metadataRpc);
6547
+ if (metadataRpc === undefined) return UNSUPPORTED_CHAIN_META;
7187
6548
  const metadata = scale.decAnyMetadata(metadataRpc);
7188
6549
  const palletId = moduleConfig?.palletId ?? defaultPalletId;
7189
6550
  scale.compactMetadata(metadata, [{
@@ -7191,74 +6552,83 @@ const SubTokensModule = hydrate => {
7191
6552
  items: ["Accounts"]
7192
6553
  }]);
7193
6554
  const miniMetadata = scale.encodeMetadata(metadata);
7194
- return palletId === defaultPalletId ? {
7195
- isTestnet,
7196
- miniMetadata,
7197
- metadataVersion
7198
- } : {
7199
- isTestnet,
7200
- palletId,
6555
+ return {
7201
6556
  miniMetadata,
7202
- metadataVersion
6557
+ extra: {
6558
+ palletId
6559
+ }
7203
6560
  };
7204
6561
  },
7205
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
7206
- const {
7207
- isTestnet
7208
- } = chainMeta;
7209
- const tokens = {};
7210
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
6562
+ async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
6563
+ const tokenList = {};
6564
+ for (const tokenConfig of tokens ?? []) {
7211
6565
  try {
6566
+ // TODO fetch metadata from chain, like we do for assets
7212
6567
  const symbol = tokenConfig?.symbol ?? "Unit";
7213
6568
  const decimals = tokenConfig?.decimals ?? 0;
7214
- const existentialDeposit = tokenConfig?.ed ?? "0";
6569
+ const existentialDeposit = tokenConfig?.existentialDeposit ?? "0";
7215
6570
  const onChainId = tokenConfig?.onChainId ?? undefined;
7216
6571
  if (onChainId === undefined) continue;
7217
- const id = subTokensTokenId(chainId, onChainId);
6572
+ const id = chaindataProvider.subTokensTokenId(chainId, onChainId);
7218
6573
  const token = {
7219
6574
  id,
7220
6575
  type: "substrate-tokens",
7221
- isTestnet,
6576
+ platform: "polkadot",
7222
6577
  isDefault: tokenConfig.isDefault ?? true,
7223
6578
  symbol,
7224
6579
  decimals,
7225
- logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
6580
+ name: tokenConfig?.name ?? symbol,
6581
+ logo: tokenConfig?.logo,
7226
6582
  existentialDeposit,
7227
6583
  onChainId,
7228
- chain: {
7229
- id: chainId
7230
- }
6584
+ networkId: chainId
7231
6585
  };
7232
- if (tokenConfig?.symbol) {
7233
- token.symbol = tokenConfig?.symbol;
7234
- token.id = subTokensTokenId(chainId, token.onChainId);
7235
- }
7236
6586
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
7237
- if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
7238
6587
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
7239
- tokens[token.id] = token;
6588
+ tokenList[token.id] = token;
7240
6589
  } catch (error) {
7241
6590
  log.error(`Failed to build substrate-tokens token ${tokenConfig.onChainId} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
7242
6591
  continue;
7243
6592
  }
7244
6593
  }
7245
- return tokens;
6594
+ return tokenList;
7246
6595
  },
7247
6596
  // TODO: Don't create empty subscriptions
7248
6597
  async subscribeBalances({
7249
6598
  addressesByToken
7250
6599
  }, callback) {
7251
- const queries = await buildQueries(chaindataProvider$1, addressesByToken);
7252
- const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
7253
- if (error) return callback(error);
7254
- const balances = result?.filter(b => b !== null) ?? [];
7255
- if (balances.length > 0) callback(null, new Balances(balances));
7256
- });
7257
- return unsubscribe;
6600
+ const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
6601
+ const networkId = chaindataProvider.parseSubTokensTokenId(tokenId).networkId;
6602
+ if (!acc[networkId]) acc[networkId] = {};
6603
+ acc[networkId][tokenId] = addressesByToken[tokenId];
6604
+ return acc;
6605
+ }, {});
6606
+ const controller = new AbortController();
6607
+ const pUnsubs = Promise.all(lodash.toPairs(byNetwork).map(async ([networkId, addressesByToken]) => {
6608
+ try {
6609
+ const queries = await buildNetworkQueries(networkId, chainConnector, chaindataProvider$1, addressesByToken, controller.signal);
6610
+ if (controller.signal.aborted) return () => {};
6611
+ const stateHelper = new RpcStateQueryHelper(chainConnector, queries);
6612
+ return await stateHelper.subscribe((error, result) => {
6613
+ if (error) return callback(error);
6614
+ const balances = result?.filter(b => b !== null) ?? [];
6615
+ if (balances.length > 0) callback(null, new Balances(balances));
6616
+ });
6617
+ } catch (err) {
6618
+ if (!util.isAbortError(err)) log.error(`Failed to subscribe balances for network ${networkId}`, err);
6619
+ return () => {};
6620
+ }
6621
+ }));
6622
+ return () => {
6623
+ pUnsubs.then(unsubs => {
6624
+ unsubs.forEach(unsubscribe => unsubscribe());
6625
+ });
6626
+ controller.abort();
6627
+ };
7258
6628
  },
7259
6629
  async fetchBalances(addressesByToken) {
7260
6630
  util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
7261
- const queries = await buildQueries(chaindataProvider$1, addressesByToken);
6631
+ const queries = await buildQueries(chainConnector, chaindataProvider$1, addressesByToken);
7262
6632
  const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
7263
6633
  const balances = result?.filter(b => b !== null) ?? [];
7264
6634
  return new Balances(balances);
@@ -7270,15 +6640,13 @@ const SubTokensModule = hydrate => {
7270
6640
  transferMethod,
7271
6641
  metadataRpc
7272
6642
  }) {
7273
- const token = await chaindataProvider$1.tokenById(tokenId);
6643
+ const token = await chaindataProvider$1.getTokenById(tokenId, "substrate-tokens");
7274
6644
  util$1.assert(token, `Token ${tokenId} not found in store`);
7275
- if (token.type !== "substrate-tokens") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
7276
- const chainId = token.chain.id;
7277
- const chain = await chaindataProvider$1.chainById(chainId);
6645
+ const chainId = token.networkId;
6646
+ const chain = await chaindataProvider$1.getNetworkById(chainId, "polkadot");
7278
6647
  util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
7279
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
7280
- const [chainMeta] = findChainMeta(miniMetadatas, moduleType, chain);
7281
- const tokensPallet = chainMeta?.palletId ?? defaultPalletId;
6648
+ const miniMetadata = await getMiniMetadata(chaindataProvider$1, chainConnector, chainId, moduleType);
6649
+ const tokensPallet = miniMetadata?.extra?.palletId ?? defaultPalletId;
7282
6650
  const onChainId = (() => {
7283
6651
  try {
7284
6652
  return scale.papiParse(token.onChainId);
@@ -7377,23 +6745,15 @@ const SubTokensModule = hydrate => {
7377
6745
  }
7378
6746
  };
7379
6747
  };
7380
- async function buildQueries(chaindataProvider, addressesByToken) {
7381
- const allChains = await chaindataProvider.chainsById();
7382
- const tokens = await chaindataProvider.tokensById();
7383
- const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
7384
- const tokensPalletByChain = new Map(Object.values(allChains).map(chain => [chain.id, findChainMeta(miniMetadatas, moduleType, chain)[0]?.palletId]));
7385
- const uniqueChainIds = getUniqueChainIds(addressesByToken, tokens);
7386
- const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
7387
- const chainStorageCoders = buildStorageCoders({
7388
- chainIds: uniqueChainIds,
7389
- chains,
7390
- miniMetadatas,
7391
- moduleType: "substrate-tokens",
7392
- coders: {
7393
- storage: ({
7394
- chainId
7395
- }) => [tokensPalletByChain.get(chainId) ?? defaultPalletId, "Accounts"]
7396
- }
6748
+ async function buildNetworkQueries(networkId, chainConnector, chaindataProvider, addressesByToken, signal) {
6749
+ const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType, signal);
6750
+ const chain = await chaindataProvider.getNetworkById(networkId, "polkadot");
6751
+ const tokens = await chaindataProvider.getTokensMapById();
6752
+ if (!chain) return [];
6753
+ signal?.throwIfAborted();
6754
+ const palletId = miniMetadata.extra.palletId ?? defaultPalletId;
6755
+ const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
6756
+ storage: [palletId, "Accounts"]
7397
6757
  });
7398
6758
  return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
7399
6759
  const token = tokens[tokenId];
@@ -7405,18 +6765,8 @@ async function buildQueries(chaindataProvider, addressesByToken) {
7405
6765
  log.debug(`This module doesn't handle tokens of type ${token.type}`);
7406
6766
  return [];
7407
6767
  }
7408
- const chainId = token.chain?.id;
7409
- if (!chainId) {
7410
- log.warn(`Token ${tokenId} has no chain`);
7411
- return [];
7412
- }
7413
- const chain = chains[chainId];
7414
- if (!chain) {
7415
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
7416
- return [];
7417
- }
7418
6768
  return addresses.flatMap(address => {
7419
- const scaleCoder = chainStorageCoders.get(chainId)?.storage;
6769
+ const scaleCoder = networkStorageCoders?.storage;
7420
6770
  const onChainId = (() => {
7421
6771
  try {
7422
6772
  return scale.papiParse(token.onChainId);
@@ -7424,12 +6774,12 @@ async function buildQueries(chaindataProvider, addressesByToken) {
7424
6774
  return token.onChainId;
7425
6775
  }
7426
6776
  })();
7427
- const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${chainId} storage query ${address} / ${token.onChainId}`, address, onChainId);
6777
+ const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${networkId} storage query ${address} / ${token.onChainId}`, address, onChainId);
7428
6778
  if (!stateKey) return [];
7429
6779
  const decodeResult = change => {
7430
6780
  /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
7431
6781
 
7432
- const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-tokens balance on chain ${chainId}`) ?? {
6782
+ const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-tokens balance on chain ${networkId}`) ?? {
7433
6783
  free: 0n,
7434
6784
  reserved: 0n,
7435
6785
  frozen: 0n
@@ -7454,50 +6804,32 @@ async function buildQueries(chaindataProvider, addressesByToken) {
7454
6804
  source: "substrate-tokens",
7455
6805
  status: "live",
7456
6806
  address,
7457
- multiChainId: {
7458
- subChainId: chainId
7459
- },
7460
- chainId,
6807
+ networkId,
7461
6808
  tokenId: token.id,
7462
6809
  values: balanceValues
7463
6810
  };
7464
6811
  };
7465
6812
  return {
7466
- chainId,
6813
+ chainId: networkId,
7467
6814
  stateKey,
7468
6815
  decodeResult
7469
6816
  };
7470
6817
  });
7471
6818
  });
7472
6819
  }
6820
+ async function buildQueries(chainConnector, chaindataProvider$1, addressesByToken, signal) {
6821
+ const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
6822
+ const networkId = chaindataProvider.parseSubTokensTokenId(tokenId).networkId;
6823
+ if (!acc[networkId]) acc[networkId] = {};
6824
+ acc[networkId][tokenId] = addressesByToken[tokenId];
6825
+ return acc;
6826
+ }, {});
6827
+ return (await Promise.all(lodash.toPairs(byNetwork).map(([networkId, addressesByToken]) => {
6828
+ return buildNetworkQueries(networkId, chainConnector, chaindataProvider$1, addressesByToken, signal);
6829
+ }))).flat();
6830
+ }
7473
6831
 
7474
- const defaultBalanceModules = [EvmErc20Module, EvmNativeModule, EvmUniswapV2Module, SubAssetsModule, SubEquilibriumModule, SubForeignAssetsModule, SubNativeModule, SubPsp22Module, SubTokensModule];
7475
-
7476
- /** Pulls the latest chaindata from https://github.com/TalismanSociety/chaindata */
7477
- const hydrateChaindataAndMiniMetadata = async (chaindataProvider, miniMetadataUpdater) => {
7478
- // need chains to be provisioned first, or substrate balances won't fetch on first subscription
7479
- await chaindataProvider.hydrateChains();
7480
- await Promise.all([miniMetadataUpdater.hydrateFromChaindata(), miniMetadataUpdater.hydrateCustomChains()]);
7481
- const chains = await chaindataProvider.chains();
7482
- const {
7483
- statusesByChain
7484
- } = await miniMetadataUpdater.statuses(chains);
7485
- const goodChains = [...statusesByChain.entries()].flatMap(([chainId, status]) => status === "good" ? chainId : []);
7486
- await chaindataProvider.hydrateSubstrateTokens(goodChains);
7487
- };
7488
-
7489
- /** Builds any missing miniMetadatas (e.g. for the user's custom substrate chains) */
7490
- const updateCustomMiniMetadata = async (chaindataProvider, miniMetadataUpdater) => {
7491
- const chainIds = await chaindataProvider.chainIds();
7492
- await miniMetadataUpdater.update(chainIds);
7493
- };
7494
-
7495
- /** Fetches any missing Evm Tokens */
7496
- const updateEvmTokens = async (chaindataProvider, evmTokenFetcher) => {
7497
- await chaindataProvider.hydrateEvmNetworks();
7498
- const evmNetworkIds = await chaindataProvider.evmNetworkIds();
7499
- await evmTokenFetcher.update(evmNetworkIds);
7500
- };
6832
+ const defaultBalanceModules = [EvmErc20Module, EvmNativeModule, EvmUniswapV2Module, SubAssetsModule, SubForeignAssetsModule, SubNativeModule, SubPsp22Module, SubTokensModule];
7501
6833
 
7502
6834
  exports.Balance = Balance;
7503
6835
  exports.BalanceFormatter = BalanceFormatter;
@@ -7506,11 +6838,12 @@ exports.Balances = Balances;
7506
6838
  exports.Change24hCurrencyFormatter = Change24hCurrencyFormatter;
7507
6839
  exports.DefaultBalanceModule = DefaultBalanceModule;
7508
6840
  exports.EvmErc20Module = EvmErc20Module;
6841
+ exports.EvmErc20TokenConfigSchema = EvmErc20TokenConfigSchema;
7509
6842
  exports.EvmNativeModule = EvmNativeModule;
7510
- exports.EvmTokenFetcher = EvmTokenFetcher;
6843
+ exports.EvmNativeTokenConfigSchema = EvmNativeTokenConfigSchema;
7511
6844
  exports.EvmUniswapV2Module = EvmUniswapV2Module;
6845
+ exports.EvmUniswapV2TokenConfigSchema = EvmUniswapV2TokenConfigSchema;
7512
6846
  exports.FiatSumBalancesFormatter = FiatSumBalancesFormatter;
7513
- exports.MiniMetadataUpdater = MiniMetadataUpdater;
7514
6847
  exports.ONE_ALPHA_TOKEN = ONE_ALPHA_TOKEN;
7515
6848
  exports.PlanckSumBalancesFormatter = PlanckSumBalancesFormatter;
7516
6849
  exports.RpcStateQueryHelper = RpcStateQueryHelper;
@@ -7518,15 +6851,20 @@ exports.SCALE_FACTOR = SCALE_FACTOR;
7518
6851
  exports.SUBTENSOR_MIN_STAKE_AMOUNT_PLANK = SUBTENSOR_MIN_STAKE_AMOUNT_PLANK;
7519
6852
  exports.SUBTENSOR_ROOT_NETUID = SUBTENSOR_ROOT_NETUID;
7520
6853
  exports.SubAssetsModule = SubAssetsModule;
7521
- exports.SubEquilibriumModule = SubEquilibriumModule;
6854
+ exports.SubAssetsTokenConfigSchema = SubAssetsTokenConfigSchema;
7522
6855
  exports.SubForeignAssetsModule = SubForeignAssetsModule;
6856
+ exports.SubForeignAssetsTokenConfigSchema = SubForeignAssetsTokenConfigSchema;
7523
6857
  exports.SubNativeModule = SubNativeModule;
6858
+ exports.SubNativeTokenConfigSchema = SubNativeTokenConfigSchema;
7524
6859
  exports.SubPsp22Module = SubPsp22Module;
6860
+ exports.SubPsp22TokenConfigSchema = SubPsp22TokenConfigSchema;
7525
6861
  exports.SubTokensModule = SubTokensModule;
6862
+ exports.SubTokensTokenConfigSchema = SubTokensTokenConfigSchema;
7526
6863
  exports.SumBalancesFormatter = SumBalancesFormatter;
7527
6864
  exports.TalismanBalancesDatabase = TalismanBalancesDatabase;
7528
6865
  exports.abiMulticall = abiMulticall;
7529
6866
  exports.balances = balances;
6867
+ exports.buildNetworkStorageCoders = buildNetworkStorageCoders;
7530
6868
  exports.buildStorageCoders = buildStorageCoders;
7531
6869
  exports.calculateAlphaPrice = calculateAlphaPrice;
7532
6870
  exports.calculateTaoAmountFromAlpha = calculateTaoAmountFromAlpha;
@@ -7541,27 +6879,14 @@ exports.deriveMiniMetadataId = deriveMiniMetadataId;
7541
6879
  exports.detectTransferMethod = detectTransferMethod;
7542
6880
  exports.erc20Abi = erc20Abi;
7543
6881
  exports.erc20BalancesAggregatorAbi = erc20BalancesAggregatorAbi;
7544
- exports.evmErc20TokenId = evmErc20TokenId;
7545
- exports.evmNativeTokenId = evmNativeTokenId;
7546
- exports.evmUniswapV2TokenId = evmUniswapV2TokenId;
7547
6882
  exports.excludeFromFeePayableLocks = excludeFromFeePayableLocks;
7548
6883
  exports.excludeFromTransferableAmount = excludeFromTransferableAmount;
7549
6884
  exports.filterBaseLocks = filterBaseLocks;
7550
6885
  exports.filterMirrorTokens = filterMirrorTokens;
7551
- exports.findChainMeta = findChainMeta;
7552
6886
  exports.getBalanceId = getBalanceId;
7553
6887
  exports.getLockTitle = getLockTitle;
7554
6888
  exports.getUniqueChainIds = getUniqueChainIds;
7555
6889
  exports.getValueId = getValueId;
7556
- exports.hydrateChaindataAndMiniMetadata = hydrateChaindataAndMiniMetadata;
7557
6890
  exports.includeInTotalExtraAmount = includeInTotalExtraAmount;
7558
6891
  exports.makeContractCaller = makeContractCaller;
7559
- exports.subAssetTokenId = subAssetTokenId;
7560
- exports.subEquilibriumTokenId = subEquilibriumTokenId;
7561
- exports.subForeignAssetTokenId = subForeignAssetTokenId;
7562
- exports.subNativeTokenId = subNativeTokenId;
7563
- exports.subPsp22TokenId = subPsp22TokenId;
7564
- exports.subTokensTokenId = subTokensTokenId;
7565
6892
  exports.uniswapV2PairAbi = uniswapV2PairAbi;
7566
- exports.updateCustomMiniMetadata = updateCustomMiniMetadata;
7567
- exports.updateEvmTokens = updateEvmTokens;