@talismn/balances 0.0.0-pr2043-20250619170346 → 0.0.0-pr2043-20250620074243

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.
@@ -1,11 +1,11 @@
1
1
  import { Dexie } from 'dexie';
2
2
  import anylogger from 'anylogger';
3
- import { evmErc20TokenId, EvmErc20TokenSchema, networkIdFromTokenId, evmNativeTokenId, evmUniswapV2TokenId, getGithubTokenLogoUrl, parseSubAssetTokenId, subAssetTokenId, parseSubForeignAssetTokenId, subForeignAssetTokenId, parseTokenId, subNativeTokenId, subPsp22TokenId, parseSubTokensTokenId, subTokensTokenId } from '@talismn/chaindata-provider';
3
+ import { evmErc20TokenId, TokenBaseSchema, EvmErc20TokenSchema, networkIdFromTokenId, EvmUniswapV2TokenSchema, evmUniswapV2TokenId, getGithubTokenLogoUrl, DotNetworkBalancesConfigSchema, SubAssetsTokenSchema, parseSubAssetTokenId, subAssetTokenId, SubForeignAssetsTokenSchema, parseSubForeignAssetTokenId, subForeignAssetTokenId, parseTokenId, subNativeTokenId, SubPsp22TokenSchema, subPsp22TokenId, SubTokensTokenSchema, parseSubTokensTokenId, subTokensTokenId } from '@talismn/chaindata-provider';
4
4
  import { newTokenRates } from '@talismn/token-rates';
5
- import { isBigInt, BigMath, planckToTokens, isArrayOf, isTruthy, isEthereumAddress, hasOwnProperty, isAbortError, decodeAnyAddress, isNotNil, blake2Concat, Deferred } from '@talismn/util';
5
+ import { isBigInt, BigMath, planckToTokens, isArrayOf, isTruthy, isEthereumAddress, hasOwnProperty, isAbortError, isNotNil, decodeAnyAddress, blake2Concat, Deferred } from '@talismn/util';
6
6
  import BigNumber from 'bignumber.js';
7
- import { u8aToHex, assert, stringCamelCase, u8aConcatStrict, u8aConcat, arrayChunk, u8aToString, hexToNumber, hexToU8a } from '@polkadot/util';
8
- import { xxhashAsU8a, blake2AsU8a } from '@polkadot/util-crypto';
7
+ import { u8aToHex, assert, stringCamelCase, u8aConcatStrict, arrayChunk, u8aToString, hexToNumber, hexToU8a } from '@polkadot/util';
8
+ import { xxhashAsU8a } from '@polkadot/util-crypto';
9
9
  import pako from 'pako';
10
10
  import { parseAbi, isHex, hexToBigInt } from 'viem';
11
11
  import { fromPairs, toPairs, keys, groupBy as groupBy$1 } from 'lodash';
@@ -14,17 +14,16 @@ import { defineMethod } from '@substrate/txwrapper-core';
14
14
  import { unifyMetadata, decAnyMetadata, getDynamicBuilder, getLookupFn, compactMetadata, encodeMetadata, decodeScale, papiParse, getMetadataVersion, encodeStateKey } from '@talismn/scale';
15
15
  import camelCase from 'lodash/camelCase';
16
16
  import PQueue from 'p-queue';
17
+ import z from 'zod/v4';
17
18
  import { fetchBestMetadata, getScaleApi } from '@talismn/sapi';
18
19
  import { Metadata, TypeRegistry } from '@polkadot/types';
19
20
  import groupBy from 'lodash/groupBy';
20
21
  import { mergeUint8, toHex } from '@polkadot-api/utils';
21
22
  import { Binary, AccountId } from 'polkadot-api';
22
- import PromisePool from '@supercharge/promise-pool';
23
23
  import { ChainConnectionError } from '@talismn/chain-connector';
24
24
  import { Observable, scan, share, map, switchAll, combineLatest, from, mergeMap, toArray, interval, startWith, exhaustMap, BehaviorSubject, debounceTime, takeUntil, distinctUntilChanged, switchMap, withLatestFrom, concatMap } from 'rxjs';
25
- import { u32, u128, Struct } from 'scale-ts';
25
+ import { u32, Struct, u128 } from 'scale-ts';
26
26
  import upperFirst from 'lodash/upperFirst';
27
- import z from 'zod/v4';
28
27
  import { Abi } from '@polkadot/api-contract';
29
28
 
30
29
  // Record<string, unknown> | undefined
@@ -113,7 +112,7 @@ class EvmTokenFetcher {
113
112
 
114
113
  var pkg = {
115
114
  name: "@talismn/balances",
116
- version: "0.0.0-pr2043-20250619170346"};
115
+ version: "0.0.0-pr2043-20250620074243"};
117
116
 
118
117
  var log = anylogger(pkg.name);
119
118
 
@@ -1017,6 +1016,10 @@ class TalismanBalancesDatabase extends Dexie {
1017
1016
  }
1018
1017
  const db = new TalismanBalancesDatabase();
1019
1018
 
1019
+ const TokenConfigBaseSchema = TokenBaseSchema.partial().omit({
1020
+ id: true
1021
+ });
1022
+
1020
1023
  const erc20Abi = [{
1021
1024
  constant: true,
1022
1025
  inputs: [],
@@ -1248,6 +1251,20 @@ const erc20Abi = [{
1248
1251
  const erc20BalancesAggregatorAbi = parseAbi(["struct AccountToken {address account; address token;}", "function balances(AccountToken[] memory accountTokens) public view returns (uint256[] memory)"]);
1249
1252
 
1250
1253
  const moduleType$7 = "evm-erc20";
1254
+ // {
1255
+ // tokens?: Array<
1256
+ // {
1257
+ // symbol?: string
1258
+ // decimals?: number
1259
+ // name?: string
1260
+ // contractAddress?: `0x${string}`
1261
+ // } & BalancesConfigTokenParams
1262
+ // >
1263
+ // }
1264
+
1265
+ const EvmErc20TokenConfigSchema = TokenConfigBaseSchema.extend({
1266
+ contractAddress: EvmErc20TokenSchema.shape.contractAddress
1267
+ });
1251
1268
  const EvmErc20Module = hydrate => {
1252
1269
  const {
1253
1270
  chainConnectors,
@@ -1293,11 +1310,11 @@ const EvmErc20Module = hydrate => {
1293
1310
  * This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L338-L343).
1294
1311
  * 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.
1295
1312
  */
1296
- async fetchEvmChainTokens(chainId, _chainMeta, moduleConfig) {
1313
+ async fetchEvmChainTokens(chainId, _chainMeta, moduleConfig, tokens) {
1297
1314
  //const { isTestnet } = chainMeta
1298
1315
 
1299
1316
  const chainTokens = {};
1300
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
1317
+ for (const tokenConfig of tokens ?? []) {
1301
1318
  const {
1302
1319
  contractAddress,
1303
1320
  symbol: contractSymbol,
@@ -1581,6 +1598,7 @@ function groupAddressesByTokenByEvmNetwork$1(addressesByToken, tokens) {
1581
1598
  const abiMulticall = parseAbi(["struct Call { address target; bytes callData; }", "struct Call3 { address target; bool allowFailure; bytes callData; }", "struct Call3Value { address target; bool allowFailure; uint256 value; bytes callData; }", "struct Result { bool success; bytes returnData; }", "function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData)", "function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData)", "function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData)", "function blockAndAggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData)", "function getBasefee() view returns (uint256 basefee)", "function getBlockHash(uint256 blockNumber) view returns (bytes32 blockHash)", "function getBlockNumber() view returns (uint256 blockNumber)", "function getChainId() view returns (uint256 chainid)", "function getCurrentBlockCoinbase() view returns (address coinbase)", "function getCurrentBlockDifficulty() view returns (uint256 difficulty)", "function getCurrentBlockGasLimit() view returns (uint256 gaslimit)", "function getCurrentBlockTimestamp() view returns (uint256 timestamp)", "function getEthBalance(address addr) view returns (uint256 balance)", "function getLastBlockHash() view returns (bytes32 blockHash)", "function tryAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (Result[] memory returnData)", "function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData)"]);
1582
1599
 
1583
1600
  const moduleType$6 = "evm-native";
1601
+ const EvmNativeTokenConfigSchema = TokenConfigBaseSchema;
1584
1602
  const EvmNativeModule = hydrate => {
1585
1603
  const {
1586
1604
  chainConnectors,
@@ -1608,29 +1626,34 @@ const EvmNativeModule = hydrate => {
1608
1626
  * This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L338-L343).
1609
1627
  * 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.
1610
1628
  */
1611
- async fetchEvmChainTokens(networkId, chainMeta, moduleConfig) {
1612
- const symbol = moduleConfig?.symbol ?? "ETH";
1613
- const decimals = typeof moduleConfig?.decimals === "number" ? moduleConfig.decimals : 18;
1614
- const name = moduleConfig?.name ?? symbol;
1615
- const id = evmNativeTokenId(networkId);
1616
- const nativeToken = {
1617
- platform: "ethereum",
1618
- id,
1619
- type: "evm-native",
1620
- isDefault: true,
1621
- symbol,
1622
- decimals,
1623
- name,
1624
- logo: moduleConfig?.logo,
1625
- networkId
1626
- };
1627
- if (moduleConfig?.symbol) nativeToken.symbol = moduleConfig?.symbol;
1628
- if (moduleConfig?.coingeckoId) nativeToken.coingeckoId = moduleConfig?.coingeckoId;
1629
- if (moduleConfig?.mirrorOf) nativeToken.mirrorOf = moduleConfig?.mirrorOf;
1630
- if (moduleConfig?.noDiscovery) nativeToken.noDiscovery = moduleConfig?.noDiscovery;
1631
- return {
1632
- [nativeToken.id]: nativeToken
1633
- };
1629
+ async fetchEvmChainTokens() {
1630
+ // networkId, chainMeta, moduleConfig, tokens
1631
+ // TODO ? seems unneeded, this info is set on the EthNetworkConfig["nativeCurrency"] field
1632
+ return {};
1633
+
1634
+ // const symbol = moduleConfig?.symbol ?? "ETH"
1635
+ // const decimals = typeof moduleConfig?.decimals === "number" ? moduleConfig.decimals : 18
1636
+ // const name = moduleConfig?.name ?? symbol
1637
+
1638
+ // const id = evmNativeTokenId(networkId)
1639
+ // const nativeToken: EvmNativeToken = {
1640
+ // platform: "ethereum",
1641
+ // id,
1642
+ // type: "evm-native",
1643
+ // isDefault: true,
1644
+ // symbol,
1645
+ // decimals,
1646
+ // name,
1647
+ // logo: moduleConfig?.logo,
1648
+ // networkId,
1649
+ // }
1650
+
1651
+ // if (moduleConfig?.symbol) nativeToken.symbol = moduleConfig?.symbol
1652
+ // if (moduleConfig?.coingeckoId) nativeToken.coingeckoId = moduleConfig?.coingeckoId
1653
+ // if (moduleConfig?.mirrorOf) nativeToken.mirrorOf = moduleConfig?.mirrorOf
1654
+ // if (moduleConfig?.noDiscovery) nativeToken.noDiscovery = moduleConfig?.noDiscovery
1655
+
1656
+ // return { [nativeToken.id]: nativeToken }
1634
1657
  },
1635
1658
  async subscribeBalances({
1636
1659
  addressesByToken,
@@ -2372,6 +2395,36 @@ const uniswapV2PairAbi = [{
2372
2395
  }];
2373
2396
 
2374
2397
  const moduleType$5 = "evm-uniswapv2";
2398
+ const EvmUniswapV2TokenConfigSchema = TokenConfigBaseSchema.extend({
2399
+ contractAddress: EvmUniswapV2TokenSchema.shape.contractAddress,
2400
+ // the ones below are unused and prone to error, feels better to always fetch these from chain
2401
+ symbol0: EvmUniswapV2TokenSchema.shape.symbol0.optional(),
2402
+ symbol1: EvmUniswapV2TokenSchema.shape.symbol1.optional(),
2403
+ decimals0: EvmUniswapV2TokenSchema.shape.decimals0.optional(),
2404
+ decimals1: EvmUniswapV2TokenSchema.shape.decimals1.optional(),
2405
+ tokenAddress0: EvmUniswapV2TokenSchema.shape.tokenAddress0.optional(),
2406
+ tokenAddress1: EvmUniswapV2TokenSchema.shape.tokenAddress1.optional(),
2407
+ coingeckoId0: EvmUniswapV2TokenSchema.shape.coingeckoId0.optional(),
2408
+ coingeckoId1: EvmUniswapV2TokenSchema.shape.coingeckoId1.optional()
2409
+ });
2410
+
2411
+ // {
2412
+ // pools?: Array<
2413
+ // {
2414
+ // contractAddress?: `0x${string}`
2415
+ // decimals?: number
2416
+ // symbol0?: string
2417
+ // symbol1?: string
2418
+ // decimals0?: number
2419
+ // decimals1?: number
2420
+ // tokenAddress0?: `0x${string}`
2421
+ // tokenAddress1?: `0x${string}`
2422
+ // coingeckoId0?: string
2423
+ // coingeckoId1?: string
2424
+ // } & BalancesConfigTokenParams
2425
+ // >
2426
+ // }
2427
+
2375
2428
  const EvmUniswapV2Module = hydrate => {
2376
2429
  const {
2377
2430
  chainConnectors,
@@ -2387,9 +2440,9 @@ const EvmUniswapV2Module = hydrate => {
2387
2440
  extra: null
2388
2441
  };
2389
2442
  },
2390
- async fetchEvmChainTokens(chainId, _chainMeta, moduleConfig) {
2443
+ async fetchEvmChainTokens(chainId, _chainMeta, moduleConfig, pools) {
2391
2444
  const tokens = {};
2392
- for (const tokenConfig of moduleConfig?.pools ?? []) {
2445
+ for (const tokenConfig of pools ?? []) {
2393
2446
  const {
2394
2447
  contractAddress,
2395
2448
  decimals,
@@ -2679,7 +2732,9 @@ const getSpecVersion = async (chainConnector, networkId) => {
2679
2732
  const CACHE$1 = new Map();
2680
2733
  const getMetadataRpc = async (chainConnector, networkId) => {
2681
2734
  if (CACHE$1.has(networkId)) return CACHE$1.get(networkId);
2682
- const pResult = fetchBestMetadata((...args) => chainConnector.send(networkId, ...args), true // allow fallback to 14 as modules dont use any v15 or v16 specifics yet
2735
+ const pResult = fetchBestMetadata((method, params, isCacheable) => chainConnector.send(networkId, method, params, isCacheable, {
2736
+ expectErrors: true
2737
+ }), true // allow fallback to 14 as modules dont use any v15 or v16 specifics yet
2683
2738
  );
2684
2739
  CACHE$1.set(networkId, pResult);
2685
2740
  try {
@@ -2703,6 +2758,8 @@ const POOL = new PQueue({
2703
2758
  });
2704
2759
  const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, specVersion, signal) => {
2705
2760
  if (CACHE.has(networkId)) return CACHE.get(networkId);
2761
+ 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
2762
+ );
2706
2763
  const pResult = POOL.add(() => fetchMiniMetadatas(chainConnector, chaindataProvider, networkId, specVersion), {
2707
2764
  signal
2708
2765
  });
@@ -2718,9 +2775,10 @@ const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, sp
2718
2775
  CACHE.delete(networkId);
2719
2776
  }
2720
2777
  };
2778
+ const DotBalanceModuleTypeSchema = z.keyof(DotNetworkBalancesConfigSchema);
2721
2779
  const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion, signal) => {
2722
2780
  const start = performance.now();
2723
- log.debug("[miniMetadata] fetching minimetadatas for %s", chainId);
2781
+ log.info("[miniMetadata] fetching minimetadatas for %s", chainId);
2724
2782
  try {
2725
2783
  const metadataRpc = await getMetadataRpc(chainConnector, chainId);
2726
2784
  signal?.throwIfAborted();
@@ -2731,10 +2789,14 @@ const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, sp
2731
2789
  const modules = defaultBalanceModules.map(mod => mod({
2732
2790
  chainConnectors,
2733
2791
  chaindataProvider
2734
- })).filter(mod => mod.type.startsWith("substrate-"));
2792
+ })).filter(mod => DotBalanceModuleTypeSchema.safeParse(mod.type).success);
2735
2793
  return Promise.all(modules.map(async mod => {
2736
2794
  const source = mod.type;
2737
- const chainMeta = await mod.fetchSubstrateChainMeta(chainId, {}, metadataRpc);
2795
+ const chain = await chaindataProvider.chainById(chainId);
2796
+ const balancesConfig = chain?.balancesConfig?.[mod.type];
2797
+ const chainMeta = await mod.fetchSubstrateChainMeta(chainId, balancesConfig,
2798
+ // TODO fix typings
2799
+ metadataRpc);
2738
2800
  return {
2739
2801
  id: deriveMiniMetadataId({
2740
2802
  source,
@@ -3060,10 +3122,23 @@ const decompress = data => {
3060
3122
  };
3061
3123
 
3062
3124
  const moduleType$4 = "substrate-assets";
3125
+ const SubAssetsTokenConfigSchema = TokenConfigBaseSchema.extend({
3126
+ assetId: SubAssetsTokenSchema.shape.assetId,
3127
+ existentialDeposit: SubAssetsTokenSchema.shape.existentialDeposit
3128
+ });
3063
3129
  const UNSUPPORTED_CHAIN_META$3 = {
3064
3130
  miniMetadata: null,
3065
3131
  extra: null
3066
3132
  };
3133
+
3134
+ // {
3135
+ // tokens?: Array<
3136
+ // {
3137
+ // assetId: number | string
3138
+ // } & BalancesConfigTokenParams
3139
+ // >
3140
+ // }
3141
+
3067
3142
  const SubAssetsModule = hydrate => {
3068
3143
  const {
3069
3144
  chainConnectors,
@@ -3087,8 +3162,8 @@ const SubAssetsModule = hydrate => {
3087
3162
  extra: null
3088
3163
  };
3089
3164
  },
3090
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
3091
- if ((moduleConfig?.tokens ?? []).length < 1) return {};
3165
+ async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
3166
+ if (!tokens?.length) return {};
3092
3167
  const {
3093
3168
  miniMetadata
3094
3169
  } = chainMeta;
@@ -3097,8 +3172,8 @@ const SubAssetsModule = hydrate => {
3097
3172
  const scaleBuilder = getDynamicBuilder(getLookupFn(metadata));
3098
3173
  const assetCoder = scaleBuilder.buildStorage("Assets", "Asset");
3099
3174
  const metadataCoder = scaleBuilder.buildStorage("Assets", "Metadata");
3100
- const tokens = {};
3101
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
3175
+ const tokenList = {};
3176
+ for (const tokenConfig of tokens ?? []) {
3102
3177
  try {
3103
3178
  const assetId = String(tokenConfig.assetId);
3104
3179
  const assetStateKey = tryEncode(assetCoder, BigInt(assetId)) ?? tryEncode(assetCoder, assetId);
@@ -3130,13 +3205,13 @@ const SubAssetsModule = hydrate => {
3130
3205
  }
3131
3206
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
3132
3207
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
3133
- tokens[token.id] = token;
3208
+ tokenList[token.id] = token;
3134
3209
  } catch (error) {
3135
3210
  log.error(`Failed to build substrate-assets token ${tokenConfig.assetId} (${tokenConfig.symbol}) on ${chainId}`, error);
3136
3211
  continue;
3137
3212
  }
3138
3213
  }
3139
- return tokens;
3214
+ return tokenList;
3140
3215
  },
3141
3216
  // TODO: Don't create empty subscriptions
3142
3217
  async subscribeBalances({
@@ -3166,10 +3241,10 @@ const SubAssetsModule = hydrate => {
3166
3241
  }
3167
3242
  }));
3168
3243
  return () => {
3169
- controller.abort();
3170
3244
  pUnsubs.then(unsubs => {
3171
3245
  unsubs.forEach(unsubscribe => unsubscribe());
3172
3246
  });
3247
+ controller.abort();
3173
3248
  };
3174
3249
  },
3175
3250
  async fetchBalances(addressesByToken) {
@@ -3353,6 +3428,19 @@ const UNSUPPORTED_CHAIN_META$2 = {
3353
3428
  miniMetadata: null,
3354
3429
  extra: null
3355
3430
  };
3431
+ const SubForeignAssetsTokenConfigSchema = TokenConfigBaseSchema.extend({
3432
+ onChainId: SubForeignAssetsTokenSchema.shape.onChainId,
3433
+ existentialDeposit: SubForeignAssetsTokenSchema.shape.existentialDeposit
3434
+ });
3435
+
3436
+ // {
3437
+ // tokens?: Array<
3438
+ // {
3439
+ // onChainId: string
3440
+ // } & BalancesConfigTokenParams
3441
+ // >
3442
+ // }
3443
+
3356
3444
  const SubForeignAssetsModule = hydrate => {
3357
3445
  const {
3358
3446
  chainConnectors,
@@ -3377,8 +3465,8 @@ const SubForeignAssetsModule = hydrate => {
3377
3465
  extra: null
3378
3466
  };
3379
3467
  },
3380
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
3381
- if ((moduleConfig?.tokens ?? []).length < 1) return {};
3468
+ async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
3469
+ if (!tokens?.length) return {};
3382
3470
  const {
3383
3471
  miniMetadata
3384
3472
  } = chainMeta;
@@ -3388,8 +3476,8 @@ const SubForeignAssetsModule = hydrate => {
3388
3476
  const scaleBuilder = getDynamicBuilder(getLookupFn(unifiedMetadata));
3389
3477
  const assetCoder = scaleBuilder.buildStorage("ForeignAssets", "Asset");
3390
3478
  const metadataCoder = scaleBuilder.buildStorage("ForeignAssets", "Metadata");
3391
- const tokens = {};
3392
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
3479
+ const tokenList = {};
3480
+ for (const tokenConfig of tokens ?? []) {
3393
3481
  try {
3394
3482
  const onChainId = (() => {
3395
3483
  try {
@@ -3424,13 +3512,13 @@ const SubForeignAssetsModule = hydrate => {
3424
3512
  };
3425
3513
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
3426
3514
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
3427
- tokens[token.id] = token;
3515
+ tokenList[token.id] = token;
3428
3516
  } catch (error) {
3429
3517
  log.error(`Failed to build substrate-foreignassets token ${tokenConfig.onChainId} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
3430
3518
  continue;
3431
3519
  }
3432
3520
  }
3433
- return tokens;
3521
+ return tokenList;
3434
3522
  },
3435
3523
  // TODO: Don't create empty subscriptions
3436
3524
  async subscribeBalances({
@@ -3459,10 +3547,10 @@ const SubForeignAssetsModule = hydrate => {
3459
3547
  }
3460
3548
  }));
3461
3549
  return () => {
3462
- controller.abort();
3463
3550
  pUnsubs.then(unsubs => {
3464
3551
  unsubs.forEach(unsubscribe => unsubscribe());
3465
3552
  });
3553
+ controller.abort();
3466
3554
  };
3467
3555
  },
3468
3556
  async fetchBalances(addressesByToken) {
@@ -3649,263 +3737,6 @@ const asObservable = handler => (...args) => new Observable(subscriber => {
3649
3737
  return unsubscribe;
3650
3738
  });
3651
3739
 
3652
- /**
3653
- * Crowdloan contributions are stored in the `childstate` key returned by this function.
3654
- */
3655
- const crowdloanFundContributionsChildKey = fundIndex => u8aToHex(u8aConcat(":child_storage:default:", blake2AsU8a(u8aConcat("crowdloan", u32.enc(fundIndex)))));
3656
-
3657
- // TODO make this method chain-specific
3658
- async function subscribeCrowdloans(chaindataProvider, chainConnector, addressesByToken, callback, signal) {
3659
- const allChains = await chaindataProvider.chainsById();
3660
- const tokens = await chaindataProvider.tokensById();
3661
-
3662
- // there should be only one network here when subscribing to balances, we've split it up by network at the top level
3663
- const networkIds = keys(addressesByToken).map(tokenId => parseTokenId(tokenId).networkId);
3664
- const miniMetadatas = new Map();
3665
- for (const networkId of networkIds) miniMetadatas.set(networkId, await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native"));
3666
- const crowdloanTokenIds = Object.entries(tokens).filter(([, token]) => {
3667
- // ignore non-native tokens
3668
- if (token.type !== "substrate-native") return;
3669
- // ignore tokens on chains with no crowdloans pallet
3670
- const miniMetadata = miniMetadatas.get(token.networkId);
3671
- return typeof miniMetadata?.extra?.crowdloanPalletId === "string";
3672
- }).map(([tokenId]) => tokenId);
3673
-
3674
- // crowdloan contributions can only be done by the native token on chains with the crowdloan pallet
3675
- const addressesByCrowdloanToken = Object.fromEntries(Object.entries(addressesByToken)
3676
- // remove ethereum addresses
3677
- .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !isEthereumAddress(address))])
3678
- // remove tokens which aren't crowdloan tokens
3679
- .filter(([tokenId]) => crowdloanTokenIds.includes(tokenId)));
3680
- const uniqueChainIds = getUniqueChainIds(addressesByCrowdloanToken, tokens);
3681
- const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
3682
- const chainStorageCoders = buildStorageCoders({
3683
- chainIds: uniqueChainIds,
3684
- chains,
3685
- miniMetadatas,
3686
- coders: {
3687
- parachains: ["Paras", "Parachains"],
3688
- funds: ["Crowdloan", "Funds"]
3689
- }
3690
- });
3691
- const tokenSubscriptions = [];
3692
- for (const [tokenId, addresses] of Object.entries(addressesByCrowdloanToken)) {
3693
- const token = tokens[tokenId];
3694
- if (!token) {
3695
- log.warn(`Token ${tokenId} not found`);
3696
- continue;
3697
- }
3698
- if (token.type !== "substrate-native") {
3699
- log.debug(`This module doesn't handle tokens of type ${token.type}`);
3700
- continue;
3701
- }
3702
- const chainId = token.networkId;
3703
- if (!chainId) {
3704
- log.warn(`Token ${tokenId} has no chain`);
3705
- continue;
3706
- }
3707
- const chain = chains[chainId];
3708
- if (!chain) {
3709
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
3710
- continue;
3711
- }
3712
- const subscribeParaIds = callback => {
3713
- const scaleCoder = chainStorageCoders.get(chainId)?.parachains;
3714
- const queries = [0].flatMap(() => {
3715
- const stateKey = encodeStateKey(scaleCoder);
3716
- if (!stateKey) return [];
3717
- const decodeResult = change => {
3718
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3719
-
3720
- const decoded = decodeScale(scaleCoder, change, `Failed to decode parachains on chain ${chainId}`);
3721
- const paraIds = decoded ?? [];
3722
- return paraIds;
3723
- };
3724
- return {
3725
- chainId,
3726
- stateKey,
3727
- decodeResult
3728
- };
3729
- });
3730
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3731
- return () => subscription.then(unsubscribe => unsubscribe());
3732
- };
3733
- const subscribeParaFundIndexes = (paraIds, callback) => {
3734
- const scaleCoder = chainStorageCoders.get(chainId)?.funds;
3735
- const queries = paraIds.flatMap(paraId => {
3736
- const stateKey = encodeStateKey(scaleCoder, `Invalid paraId in ${chainId} funds query ${paraId}`, paraId);
3737
- if (!stateKey) return [];
3738
- const decodeResult = change => {
3739
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3740
-
3741
- const decoded = decodeScale(scaleCoder, change, `Failed to decode paras on chain ${chainId}`);
3742
- const firstPeriod = decoded?.first_period?.toString?.() ?? "";
3743
- const lastPeriod = decoded?.last_period?.toString?.() ?? "";
3744
- const fundPeriod = `${firstPeriod}-${lastPeriod}`;
3745
- const fundIndex = decoded?.fund_index ?? decoded?.trie_index;
3746
- return {
3747
- paraId,
3748
- fundPeriod,
3749
- fundIndex
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 subscribeFundContributions = (funds, addresses, callback) => {
3762
- // TODO: Watch system_events in order to subscribe to changes, then redo the contributions query when changes are detected:
3763
- // https://github.com/polkadot-js/api/blob/8fe02a14345b57e6abb8f7f2c2b624cf70c51b23/packages/api-derive/src/crowdloan/ownContributions.ts#L32-L47
3764
- //
3765
- // For now we just re-fetch all contributions on a timer and then only send them to the subscription callback when they have changed
3766
-
3767
- const queries = funds.map(({
3768
- paraId,
3769
- fundIndex
3770
- }) => ({
3771
- paraId,
3772
- fundIndex,
3773
- addresses,
3774
- childKey: crowdloanFundContributionsChildKey(fundIndex),
3775
- storageKeys: addresses.map(address => u8aToHex(decodeAnyAddress(address)))
3776
- }));
3777
-
3778
- // track whether our caller is still subscribed
3779
- let subscriptionActive = true;
3780
- let previousContributions = null;
3781
- const fetchContributions = async () => {
3782
- try {
3783
- const results = await Promise.all(queries.map(async ({
3784
- paraId,
3785
- fundIndex,
3786
- addresses,
3787
- childKey,
3788
- storageKeys
3789
- }) => ({
3790
- paraId,
3791
- fundIndex,
3792
- addresses,
3793
- result: await chainConnector.send(chainId, "childstate_getStorageEntries", [childKey, storageKeys])
3794
- })));
3795
- const contributions = results.flatMap(queryResult => {
3796
- const {
3797
- paraId,
3798
- fundIndex,
3799
- addresses,
3800
- result
3801
- } = queryResult;
3802
- return (Array.isArray(result) ? result : []).flatMap((encoded, index) => {
3803
- const amount = (() => {
3804
- try {
3805
- return typeof encoded === "string" ? u128.dec(encoded) ?? 0n : 0n;
3806
- } catch {
3807
- return 0n;
3808
- }
3809
- })().toString();
3810
- return {
3811
- paraId,
3812
- fundIndex,
3813
- address: addresses[index],
3814
- amount
3815
- };
3816
- });
3817
- });
3818
-
3819
- // ignore these results if our caller has tried to close this subscription
3820
- if (!subscriptionActive) return;
3821
-
3822
- // ignore these results if they're the same as what we previously fetched
3823
- if (isEqual(previousContributions, contributions)) return;
3824
- previousContributions = contributions;
3825
- callback(null, contributions);
3826
- } catch (error) {
3827
- callback(error);
3828
- }
3829
- };
3830
-
3831
- // set up polling for contributions
3832
- const crowdloanContributionsPollInterval = 60_000; // 60_000ms === 1 minute
3833
- const pollContributions = async () => {
3834
- if (!subscriptionActive) return;
3835
- try {
3836
- await fetchContributions();
3837
- } catch (error) {
3838
- // log any errors, but don't cancel the poll for contributions when one fetch fails
3839
- log.error(error);
3840
- }
3841
- if (!subscriptionActive) return;
3842
- setTimeout(pollContributions, crowdloanContributionsPollInterval);
3843
- };
3844
-
3845
- // start polling
3846
- pollContributions();
3847
- return () => {
3848
- // stop polling
3849
- subscriptionActive = false;
3850
- };
3851
- };
3852
- const paraIds$ = asObservable(subscribeParaIds)().pipe(scan((_, next) => Array.from(new Set(next.flatMap(paraIds => paraIds))), []), share());
3853
- const fundIndexesByParaId$ = paraIds$.pipe(map(paraIds => asObservable(subscribeParaFundIndexes)(paraIds)), switchAll(), scan((state, next) => {
3854
- for (const fund of next) {
3855
- const {
3856
- paraId,
3857
- fundIndex
3858
- } = fund;
3859
- if (typeof fundIndex === "number") {
3860
- state.set(paraId, (state.get(paraId) ?? new Set()).add(fundIndex));
3861
- }
3862
- }
3863
- return state;
3864
- }, new Map()));
3865
- const contributionsByAddress$ = fundIndexesByParaId$.pipe(map(fundIndexesByParaId => Array.from(fundIndexesByParaId).flatMap(([paraId, fundIndexes]) => Array.from(fundIndexes).map(fundIndex => ({
3866
- paraId,
3867
- fundIndex
3868
- })))), map(funds => asObservable(subscribeFundContributions)(funds, addresses)), switchAll(), scan((state, next) => {
3869
- for (const contribution of next) {
3870
- const {
3871
- address
3872
- } = contribution;
3873
- state.set(address, (state.get(address) ?? new Set()).add(contribution));
3874
- }
3875
- return state;
3876
- }, new Map()));
3877
- const subscription = contributionsByAddress$.subscribe({
3878
- next: contributionsByAddress => {
3879
- const balances = Array.from(contributionsByAddress).map(([address, contributions]) => {
3880
- return {
3881
- source: "substrate-native",
3882
- status: "live",
3883
- address,
3884
- networkId: chainId,
3885
- tokenId,
3886
- values: Array.from(contributions).map(({
3887
- amount,
3888
- paraId
3889
- }) => ({
3890
- type: "crowdloan",
3891
- label: "crowdloan",
3892
- source: "crowdloan",
3893
- amount: amount,
3894
- meta: {
3895
- paraId
3896
- }
3897
- }))
3898
- };
3899
- });
3900
- if (balances.length > 0) callback(null, balances);
3901
- },
3902
- error: error => callback(error)
3903
- });
3904
- tokenSubscriptions.push(() => subscription.unsubscribe());
3905
- }
3906
- return () => tokenSubscriptions.forEach(unsub => unsub());
3907
- }
3908
-
3909
3740
  /**
3910
3741
  * Each nominationPool in the nominationPools pallet has access to some accountIds which have no
3911
3742
  * associated private key. Instead, they are derived from this function.
@@ -3937,6 +3768,7 @@ async function subscribeNompoolStaking(chaindataProvider, chainConnector, addres
3937
3768
  const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native");
3938
3769
  miniMetadatas.set(networkId, miniMetadata);
3939
3770
  }
3771
+ signal?.throwIfAborted();
3940
3772
  const nomPoolTokenIds = Object.entries(tokens).filter(([, token]) => {
3941
3773
  // ignore non-native tokens
3942
3774
  if (token.type !== "substrate-native") return false;
@@ -4254,9 +4086,10 @@ async function subscribeSubtensorStaking(chaindataProvider, chainConnector, addr
4254
4086
  const networkIds = keys(addressesByToken).map(tokenId => parseTokenId(tokenId).networkId);
4255
4087
  const miniMetadatas = new Map();
4256
4088
  for (const networkId of networkIds) {
4257
- const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native");
4089
+ const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native", signal);
4258
4090
  miniMetadatas.set(networkId, miniMetadata);
4259
4091
  }
4092
+ signal?.throwIfAborted();
4260
4093
  const subtensorTokenIds = Object.entries(tokens).filter(([, token]) => {
4261
4094
  // ignore non-native tokens
4262
4095
  if (token.type !== "substrate-native") return false;
@@ -4462,7 +4295,9 @@ async function subscribeSubtensorStaking(chaindataProvider, chainConnector, addr
4462
4295
  });
4463
4296
 
4464
4297
  // use the abortController to tear the subscription down when we don't need it anymore
4465
- abortController.signal.onabort = () => subscription.unsubscribe();
4298
+ abortController.signal.addEventListener("abort", () => {
4299
+ subscription.unsubscribe();
4300
+ });
4466
4301
  }
4467
4302
  return () => abortController.abort();
4468
4303
  }
@@ -4548,6 +4383,10 @@ const getLockTitle = (lock, {
4548
4383
 
4549
4384
  const moduleType$2 = "substrate-native";
4550
4385
 
4386
+ // {
4387
+ // disable?: boolean
4388
+ // } & BalancesConfigTokenParams
4389
+
4551
4390
  /**
4552
4391
  * Function to merge two 'sub sources' of the same balance together, or
4553
4392
  * two instances of the same balance with different values.
@@ -5025,6 +4864,7 @@ const UNSUPPORTED_CHAIN_META$1 = {
5025
4864
  miniMetadata: null,
5026
4865
  extra: null
5027
4866
  };
4867
+ const SubNativeTokenConfigSchema = TokenConfigBaseSchema;
5028
4868
  const SubNativeModule = hydrate => {
5029
4869
  const {
5030
4870
  chainConnectors,
@@ -5040,7 +4880,7 @@ const SubNativeModule = hydrate => {
5040
4880
  // subscribeBalances was split by network to prevent all subs to wait for all minimetadatas to be ready.
5041
4881
  // 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
5042
4882
  // TODO refactor this be actually network specific
5043
- const subscribeChainBalances = async (chainId, opts, callback) => {
4883
+ const subscribeChainBalances = (chainId, opts, callback, signal) => {
5044
4884
  const {
5045
4885
  addressesByToken,
5046
4886
  initialBalances
@@ -5139,13 +4979,19 @@ const SubNativeModule = hydrate => {
5139
4979
  return from(queryCache.getQueries(newAddressesByToken)).pipe(switchMap(baseQueries => {
5140
4980
  return new Observable(subscriber => {
5141
4981
  if (!chainConnectors.substrate) return;
5142
- const unsubSubtensorStaking = subscribeSubtensorStaking(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("subtensor-staking"));
5143
- const unsubNompoolStaking = subscribeNompoolStaking(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("nompools-staking"));
5144
- const unsubCrowdloans = subscribeCrowdloans(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("crowdloan"));
4982
+ const unsubSubtensorStaking = subscribeSubtensorStaking(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("subtensor-staking"), signal);
4983
+ const unsubNompoolStaking = subscribeNompoolStaking(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("nompools-staking"), signal);
4984
+ // const unsubCrowdloans = subscribeCrowdloans(
4985
+ // chaindataProvider,
4986
+ // chainConnectors.substrate,
4987
+ // newAddressesByToken,
4988
+ // handleUpdateForSource("crowdloan"),
4989
+ // signal,
4990
+ // )
5145
4991
  const unsubBase = subscribeBase(baseQueries, chainConnectors.substrate, handleUpdateForSource("base"));
5146
4992
  subscriber.add(async () => (await unsubSubtensorStaking)());
5147
4993
  subscriber.add(async () => (await unsubNompoolStaking)());
5148
- subscriber.add(async () => (await unsubCrowdloans)());
4994
+ // subscriber.add(async () => (await unsubCrowdloans)())
5149
4995
  subscriber.add(async () => (await unsubBase)());
5150
4996
  });
5151
4997
  }));
@@ -5177,8 +5023,13 @@ const SubNativeModule = hydrate => {
5177
5023
  const nonCurrentTokens = Object.keys(addressesByToken).filter(tokenId => !currentTokens.has(tokenId)).sort(sortChains);
5178
5024
 
5179
5025
  // break nonCurrentTokens into chunks of POLLING_WINDOW_SIZE
5180
- await PromisePool.withConcurrency(POLLING_WINDOW_SIZE).for(nonCurrentTokens).process(async nonCurrentTokenId => await poll({
5026
+ const pool = new PQueue({
5027
+ concurrency: POLLING_WINDOW_SIZE
5028
+ });
5029
+ nonCurrentTokens.forEach(nonCurrentTokenId => pool.add(() => poll({
5181
5030
  [nonCurrentTokenId]: addressesByToken[nonCurrentTokenId]
5031
+ }), {
5032
+ signal
5182
5033
  }));
5183
5034
 
5184
5035
  // now poll every 30s on chains which are not subscriptionTokens
@@ -5191,7 +5042,9 @@ const SubNativeModule = hydrate => {
5191
5042
  Object.keys(addressesByToken).filter(tokenId => !subscribedTokenIds.includes(tokenId))), exhaustMap(tokenIds => from(arrayChunk(tokenIds, POLLING_WINDOW_SIZE)).pipe(concatMap(async tokenChunk => {
5192
5043
  // tokenChunk is a chunk of tokenIds with size POLLING_WINDOW_SIZE
5193
5044
  const pollingTokenAddresses = Object.fromEntries(tokenChunk.map(tokenId => [tokenId, addressesByToken[tokenId]]));
5194
- await poll(pollingTokenAddresses);
5045
+ await pool.add(() => poll(pollingTokenAddresses), {
5046
+ signal
5047
+ });
5195
5048
  return true;
5196
5049
  })))).subscribe();
5197
5050
  return () => {
@@ -5236,7 +5089,7 @@ const SubNativeModule = hydrate => {
5236
5089
  };
5237
5090
  const existentialDeposit = getConstantValue("Balances", "ExistentialDeposit")?.toString();
5238
5091
  const nominationPoolsPalletId = getConstantValue("NominationPools", "PalletId")?.asText();
5239
- const crowdloanPalletId = getConstantValue("Crowdloan", "PalletId")?.asText();
5092
+ const crowdloanPalletId = getConstantValue("Crowdloan", "PalletId")?.asText(); // TODO yeet
5240
5093
  const hasSubtensorPallet = getConstantValue("SubtensorModule", "KeySwapCost") !== undefined;
5241
5094
 
5242
5095
  //
@@ -5259,10 +5112,13 @@ const SubNativeModule = hydrate => {
5259
5112
  }, {
5260
5113
  pallet: "Crowdloan",
5261
5114
  items: ["Funds"]
5262
- }, {
5115
+ },
5116
+ // TODO yeet
5117
+ {
5263
5118
  pallet: "Paras",
5264
5119
  items: ["Parachains"]
5265
5120
  },
5121
+ // TODO yeet
5266
5122
  // TotalColdkeyStake is used until v.2.2.1, then it is replaced by StakingHotkeys+Stake
5267
5123
  // Need to keep TotalColdkeyStake for a while so chaindata keeps including it in miniMetadatas, so it doesnt break old versions of the wallet
5268
5124
  {
@@ -5305,22 +5161,24 @@ const SubNativeModule = hydrate => {
5305
5161
  const {
5306
5162
  existentialDeposit
5307
5163
  } = chainMeta.extra ?? {};
5164
+ if (existentialDeposit === undefined) log.warn("Substrate native module: existentialDeposit is undefined for %s, using 0", chainId);
5308
5165
  const id = subNativeTokenId(chainId);
5309
5166
  const nativeToken = {
5310
5167
  id,
5311
5168
  type: "substrate-native",
5312
5169
  platform: "polkadot",
5313
- isDefault: moduleConfig?.isDefault ?? true,
5170
+ isDefault: true,
5314
5171
  symbol: symbol,
5315
- name: moduleConfig?.name ?? symbol,
5172
+ name: symbol,
5316
5173
  decimals: decimals,
5317
- logo: moduleConfig?.logo,
5318
5174
  existentialDeposit: existentialDeposit ?? "0",
5319
5175
  networkId: chainId
5320
5176
  };
5321
- if (moduleConfig?.symbol) nativeToken.symbol = moduleConfig?.symbol;
5322
- if (moduleConfig?.coingeckoId) nativeToken.coingeckoId = moduleConfig?.coingeckoId;
5323
- if (moduleConfig?.mirrorOf) nativeToken.mirrorOf = moduleConfig?.mirrorOf;
5177
+
5178
+ // if (moduleConfig?.symbol) nativeToken.symbol = moduleConfig?.symbol
5179
+ // if (moduleConfig?.coingeckoId) nativeToken.coingeckoId = moduleConfig?.coingeckoId
5180
+ // if (moduleConfig?.mirrorOf) nativeToken.mirrorOf = moduleConfig?.mirrorOf
5181
+
5324
5182
  return {
5325
5183
  [nativeToken.id]: nativeToken
5326
5184
  };
@@ -5352,11 +5210,11 @@ const SubNativeModule = hydrate => {
5352
5210
  return subscribeChainBalances(networkId, {
5353
5211
  addressesByToken: addressesByTokenByNetwork[networkId] ?? {},
5354
5212
  initialBalances: initialBalancesByNetwork[networkId] ?? []
5355
- }, safeCallback);
5213
+ }, safeCallback, controller.signal);
5356
5214
  }));
5357
5215
  return () => {
5358
- controller.abort();
5359
5216
  unsubsribeFns.then(fns => fns.forEach(unsubscribe => unsubscribe()));
5217
+ controller.abort();
5360
5218
  };
5361
5219
  },
5362
5220
  fetchBalances,
@@ -6574,6 +6432,22 @@ var psp22Abi = {
6574
6432
  };
6575
6433
 
6576
6434
  const moduleType$1 = "substrate-psp22";
6435
+ const SubPsp22TokenConfigSchema = TokenConfigBaseSchema.extend({
6436
+ contractAddress: SubPsp22TokenSchema.shape.contractAddress,
6437
+ existentialDeposit: SubPsp22TokenSchema.shape.existentialDeposit.optional()
6438
+ });
6439
+
6440
+ // {
6441
+ // tokens?: Array<
6442
+ // {
6443
+ // symbol?: string
6444
+ // decimals?: number
6445
+ // ed?: string
6446
+ // contractAddress: string
6447
+ // } & BalancesConfigTokenParams
6448
+ // >
6449
+ // }
6450
+
6577
6451
  const SubPsp22Module = hydrate => {
6578
6452
  const {
6579
6453
  chainConnectors,
@@ -6590,7 +6464,8 @@ const SubPsp22Module = hydrate => {
6590
6464
  extra: null
6591
6465
  };
6592
6466
  },
6593
- async fetchSubstrateChainTokens(chainId, _chainMeta, moduleConfig) {
6467
+ async fetchSubstrateChainTokens(chainId, _chainMeta, moduleConfig, tokens) {
6468
+ if (!tokens?.length) return {};
6594
6469
  // const { isTestnet } = chainMeta
6595
6470
 
6596
6471
  const registry = new TypeRegistry();
@@ -6602,12 +6477,12 @@ const SubPsp22Module = hydrate => {
6602
6477
  chainId,
6603
6478
  registry
6604
6479
  });
6605
- const tokens = {};
6606
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
6480
+ const tokenList = {};
6481
+ for (const tokenConfig of tokens ?? []) {
6607
6482
  try {
6608
6483
  let symbol = tokenConfig?.symbol ?? "Unit";
6609
6484
  let decimals = tokenConfig?.decimals ?? 0;
6610
- const existentialDeposit = tokenConfig?.ed ?? "0";
6485
+ const existentialDeposit = tokenConfig?.existentialDeposit ?? "0";
6611
6486
  const contractAddress = tokenConfig?.contractAddress ?? undefined;
6612
6487
  if (contractAddress === undefined) continue;
6613
6488
  await (async () => {
@@ -6640,13 +6515,13 @@ const SubPsp22Module = hydrate => {
6640
6515
  };
6641
6516
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
6642
6517
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
6643
- tokens[token.id] = token;
6518
+ tokenList[token.id] = token;
6644
6519
  } catch (error) {
6645
6520
  log.error(`Failed to build substrate-psp22 token ${tokenConfig.contractAddress} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
6646
6521
  continue;
6647
6522
  }
6648
6523
  }
6649
- return tokens;
6524
+ return tokenList;
6650
6525
  },
6651
6526
  // TODO: Don't create empty subscriptions
6652
6527
  async subscribeBalances({
@@ -6821,11 +6696,28 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
6821
6696
  };
6822
6697
 
6823
6698
  const moduleType = "substrate-tokens";
6699
+ const SubTokensTokenConfigSchema = TokenConfigBaseSchema.extend({
6700
+ onChainId: SubTokensTokenSchema.shape.onChainId,
6701
+ existentialDeposit: SubTokensTokenSchema.shape.existentialDeposit
6702
+ });
6824
6703
  const defaultPalletId = "Tokens";
6825
6704
  const UNSUPPORTED_CHAIN_META = {
6826
6705
  miniMetadata: null,
6827
6706
  extra: {}
6828
6707
  };
6708
+
6709
+ // {
6710
+ // palletId?: string // TODO unlikely it will ever be used - remove this ?
6711
+ // tokens?: Array<
6712
+ // {
6713
+ // symbol?: string
6714
+ // decimals?: number
6715
+ // ed?: string
6716
+ // onChainId?: string | number
6717
+ // } & BalancesConfigTokenParams
6718
+ // >
6719
+ // }
6720
+
6829
6721
  const SubTokensModule = hydrate => {
6830
6722
  const {
6831
6723
  chainConnectors,
@@ -6851,13 +6743,14 @@ const SubTokensModule = hydrate => {
6851
6743
  }
6852
6744
  };
6853
6745
  },
6854
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
6855
- const tokens = {};
6856
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
6746
+ async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
6747
+ const tokenList = {};
6748
+ for (const tokenConfig of tokens ?? []) {
6857
6749
  try {
6750
+ // TODO fetch metadata from chain, like we do for assets
6858
6751
  const symbol = tokenConfig?.symbol ?? "Unit";
6859
6752
  const decimals = tokenConfig?.decimals ?? 0;
6860
- const existentialDeposit = tokenConfig?.ed ?? "0";
6753
+ const existentialDeposit = tokenConfig?.existentialDeposit ?? "0";
6861
6754
  const onChainId = tokenConfig?.onChainId ?? undefined;
6862
6755
  if (onChainId === undefined) continue;
6863
6756
  const id = subTokensTokenId(chainId, onChainId);
@@ -6876,13 +6769,13 @@ const SubTokensModule = hydrate => {
6876
6769
  };
6877
6770
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
6878
6771
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
6879
- tokens[token.id] = token;
6772
+ tokenList[token.id] = token;
6880
6773
  } catch (error) {
6881
6774
  log.error(`Failed to build substrate-tokens token ${tokenConfig.onChainId} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
6882
6775
  continue;
6883
6776
  }
6884
6777
  }
6885
- return tokens;
6778
+ return tokenList;
6886
6779
  },
6887
6780
  // TODO: Don't create empty subscriptions
6888
6781
  async subscribeBalances({
@@ -6912,10 +6805,10 @@ const SubTokensModule = hydrate => {
6912
6805
  }
6913
6806
  }));
6914
6807
  return () => {
6915
- controller.abort();
6916
6808
  pUnsubs.then(unsubs => {
6917
6809
  unsubs.forEach(unsubscribe => unsubscribe());
6918
6810
  });
6811
+ controller.abort();
6919
6812
  };
6920
6813
  },
6921
6814
  async fetchBalances(addressesByToken) {
@@ -7124,4 +7017,4 @@ async function buildQueries(chainConnector, chaindataProvider, addressesByToken,
7124
7017
 
7125
7018
  const defaultBalanceModules = [EvmErc20Module, EvmNativeModule, EvmUniswapV2Module, SubAssetsModule, SubForeignAssetsModule, SubNativeModule, SubPsp22Module, SubTokensModule];
7126
7019
 
7127
- export { Balance, BalanceFormatter, BalanceValueGetter, Balances, Change24hCurrencyFormatter, DefaultBalanceModule, EvmErc20Module, EvmNativeModule, EvmTokenFetcher, EvmUniswapV2Module, FiatSumBalancesFormatter, ONE_ALPHA_TOKEN, PlanckSumBalancesFormatter, RpcStateQueryHelper, SCALE_FACTOR, SUBTENSOR_MIN_STAKE_AMOUNT_PLANK, SUBTENSOR_ROOT_NETUID, SubAssetsModule, SubForeignAssetsModule, SubNativeModule, SubPsp22Module, SubTokensModule, SumBalancesFormatter, TalismanBalancesDatabase, abiMulticall, balances, buildNetworkStorageCoders, buildStorageCoders, calculateAlphaPrice, calculateTaoAmountFromAlpha, calculateTaoFromDynamicInfo, compress, configureStore, db, decodeOutput, decompress, defaultBalanceModules, deriveMiniMetadataId, detectTransferMethod, erc20Abi, erc20BalancesAggregatorAbi, excludeFromFeePayableLocks, excludeFromTransferableAmount, filterBaseLocks, filterMirrorTokens, getBalanceId, getLockTitle, getUniqueChainIds, getValueId, includeInTotalExtraAmount, makeContractCaller, uniswapV2PairAbi };
7020
+ export { Balance, BalanceFormatter, BalanceValueGetter, Balances, Change24hCurrencyFormatter, DefaultBalanceModule, EvmErc20Module, EvmErc20TokenConfigSchema, EvmNativeModule, EvmNativeTokenConfigSchema, EvmTokenFetcher, EvmUniswapV2Module, EvmUniswapV2TokenConfigSchema, FiatSumBalancesFormatter, ONE_ALPHA_TOKEN, PlanckSumBalancesFormatter, RpcStateQueryHelper, SCALE_FACTOR, SUBTENSOR_MIN_STAKE_AMOUNT_PLANK, SUBTENSOR_ROOT_NETUID, SubAssetsModule, SubAssetsTokenConfigSchema, SubForeignAssetsModule, SubForeignAssetsTokenConfigSchema, SubNativeModule, SubNativeTokenConfigSchema, SubPsp22Module, SubPsp22TokenConfigSchema, SubTokensModule, SubTokensTokenConfigSchema, SumBalancesFormatter, TalismanBalancesDatabase, abiMulticall, balances, buildNetworkStorageCoders, buildStorageCoders, calculateAlphaPrice, calculateTaoAmountFromAlpha, calculateTaoFromDynamicInfo, compress, configureStore, db, decodeOutput, decompress, defaultBalanceModules, deriveMiniMetadataId, detectTransferMethod, erc20Abi, erc20BalancesAggregatorAbi, excludeFromFeePayableLocks, excludeFromTransferableAmount, filterBaseLocks, filterMirrorTokens, getBalanceId, getLockTitle, getUniqueChainIds, getValueId, includeInTotalExtraAmount, makeContractCaller, uniswapV2PairAbi };