@talismn/balances 0.0.0-pr2043-20250619165342 → 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-20250619165342"};
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,12 +2732,15 @@ 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 {
2686
2741
  return await pResult;
2687
2742
  } catch (cause) {
2743
+ if (isAbortError(cause)) throw cause;
2688
2744
  throw new Error(`Failed to fetch metadataRpc for network ${networkId}`, {
2689
2745
  cause
2690
2746
  });
@@ -2702,6 +2758,8 @@ const POOL = new PQueue({
2702
2758
  });
2703
2759
  const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, specVersion, signal) => {
2704
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
+ );
2705
2763
  const pResult = POOL.add(() => fetchMiniMetadatas(chainConnector, chaindataProvider, networkId, specVersion), {
2706
2764
  signal
2707
2765
  });
@@ -2709,6 +2767,7 @@ const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, sp
2709
2767
  try {
2710
2768
  return await pResult;
2711
2769
  } catch (cause) {
2770
+ if (isAbortError(cause)) throw cause;
2712
2771
  throw new Error(`Failed to fetch metadataRpc for network ${networkId}`, {
2713
2772
  cause
2714
2773
  });
@@ -2716,9 +2775,10 @@ const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, sp
2716
2775
  CACHE.delete(networkId);
2717
2776
  }
2718
2777
  };
2778
+ const DotBalanceModuleTypeSchema = z.keyof(DotNetworkBalancesConfigSchema);
2719
2779
  const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion, signal) => {
2720
2780
  const start = performance.now();
2721
- log.debug("[miniMetadata] fetching minimetadatas for %s", chainId);
2781
+ log.info("[miniMetadata] fetching minimetadatas for %s", chainId);
2722
2782
  try {
2723
2783
  const metadataRpc = await getMetadataRpc(chainConnector, chainId);
2724
2784
  signal?.throwIfAborted();
@@ -2729,10 +2789,14 @@ const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, sp
2729
2789
  const modules = defaultBalanceModules.map(mod => mod({
2730
2790
  chainConnectors,
2731
2791
  chaindataProvider
2732
- })).filter(mod => mod.type.startsWith("substrate-"));
2792
+ })).filter(mod => DotBalanceModuleTypeSchema.safeParse(mod.type).success);
2733
2793
  return Promise.all(modules.map(async mod => {
2734
2794
  const source = mod.type;
2735
- 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);
2736
2800
  return {
2737
2801
  id: deriveMiniMetadataId({
2738
2802
  source,
@@ -3058,10 +3122,23 @@ const decompress = data => {
3058
3122
  };
3059
3123
 
3060
3124
  const moduleType$4 = "substrate-assets";
3125
+ const SubAssetsTokenConfigSchema = TokenConfigBaseSchema.extend({
3126
+ assetId: SubAssetsTokenSchema.shape.assetId,
3127
+ existentialDeposit: SubAssetsTokenSchema.shape.existentialDeposit
3128
+ });
3061
3129
  const UNSUPPORTED_CHAIN_META$3 = {
3062
3130
  miniMetadata: null,
3063
3131
  extra: null
3064
3132
  };
3133
+
3134
+ // {
3135
+ // tokens?: Array<
3136
+ // {
3137
+ // assetId: number | string
3138
+ // } & BalancesConfigTokenParams
3139
+ // >
3140
+ // }
3141
+
3065
3142
  const SubAssetsModule = hydrate => {
3066
3143
  const {
3067
3144
  chainConnectors,
@@ -3085,8 +3162,8 @@ const SubAssetsModule = hydrate => {
3085
3162
  extra: null
3086
3163
  };
3087
3164
  },
3088
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
3089
- if ((moduleConfig?.tokens ?? []).length < 1) return {};
3165
+ async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
3166
+ if (!tokens?.length) return {};
3090
3167
  const {
3091
3168
  miniMetadata
3092
3169
  } = chainMeta;
@@ -3095,8 +3172,8 @@ const SubAssetsModule = hydrate => {
3095
3172
  const scaleBuilder = getDynamicBuilder(getLookupFn(metadata));
3096
3173
  const assetCoder = scaleBuilder.buildStorage("Assets", "Asset");
3097
3174
  const metadataCoder = scaleBuilder.buildStorage("Assets", "Metadata");
3098
- const tokens = {};
3099
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
3175
+ const tokenList = {};
3176
+ for (const tokenConfig of tokens ?? []) {
3100
3177
  try {
3101
3178
  const assetId = String(tokenConfig.assetId);
3102
3179
  const assetStateKey = tryEncode(assetCoder, BigInt(assetId)) ?? tryEncode(assetCoder, assetId);
@@ -3128,13 +3205,13 @@ const SubAssetsModule = hydrate => {
3128
3205
  }
3129
3206
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
3130
3207
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
3131
- tokens[token.id] = token;
3208
+ tokenList[token.id] = token;
3132
3209
  } catch (error) {
3133
3210
  log.error(`Failed to build substrate-assets token ${tokenConfig.assetId} (${tokenConfig.symbol}) on ${chainId}`, error);
3134
3211
  continue;
3135
3212
  }
3136
3213
  }
3137
- return tokens;
3214
+ return tokenList;
3138
3215
  },
3139
3216
  // TODO: Don't create empty subscriptions
3140
3217
  async subscribeBalances({
@@ -3164,10 +3241,10 @@ const SubAssetsModule = hydrate => {
3164
3241
  }
3165
3242
  }));
3166
3243
  return () => {
3167
- controller.abort();
3168
3244
  pUnsubs.then(unsubs => {
3169
3245
  unsubs.forEach(unsubscribe => unsubscribe());
3170
3246
  });
3247
+ controller.abort();
3171
3248
  };
3172
3249
  },
3173
3250
  async fetchBalances(addressesByToken) {
@@ -3351,6 +3428,19 @@ const UNSUPPORTED_CHAIN_META$2 = {
3351
3428
  miniMetadata: null,
3352
3429
  extra: null
3353
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
+
3354
3444
  const SubForeignAssetsModule = hydrate => {
3355
3445
  const {
3356
3446
  chainConnectors,
@@ -3375,8 +3465,8 @@ const SubForeignAssetsModule = hydrate => {
3375
3465
  extra: null
3376
3466
  };
3377
3467
  },
3378
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
3379
- if ((moduleConfig?.tokens ?? []).length < 1) return {};
3468
+ async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
3469
+ if (!tokens?.length) return {};
3380
3470
  const {
3381
3471
  miniMetadata
3382
3472
  } = chainMeta;
@@ -3386,8 +3476,8 @@ const SubForeignAssetsModule = hydrate => {
3386
3476
  const scaleBuilder = getDynamicBuilder(getLookupFn(unifiedMetadata));
3387
3477
  const assetCoder = scaleBuilder.buildStorage("ForeignAssets", "Asset");
3388
3478
  const metadataCoder = scaleBuilder.buildStorage("ForeignAssets", "Metadata");
3389
- const tokens = {};
3390
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
3479
+ const tokenList = {};
3480
+ for (const tokenConfig of tokens ?? []) {
3391
3481
  try {
3392
3482
  const onChainId = (() => {
3393
3483
  try {
@@ -3422,13 +3512,13 @@ const SubForeignAssetsModule = hydrate => {
3422
3512
  };
3423
3513
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
3424
3514
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
3425
- tokens[token.id] = token;
3515
+ tokenList[token.id] = token;
3426
3516
  } catch (error) {
3427
3517
  log.error(`Failed to build substrate-foreignassets token ${tokenConfig.onChainId} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
3428
3518
  continue;
3429
3519
  }
3430
3520
  }
3431
- return tokens;
3521
+ return tokenList;
3432
3522
  },
3433
3523
  // TODO: Don't create empty subscriptions
3434
3524
  async subscribeBalances({
@@ -3457,10 +3547,10 @@ const SubForeignAssetsModule = hydrate => {
3457
3547
  }
3458
3548
  }));
3459
3549
  return () => {
3460
- controller.abort();
3461
3550
  pUnsubs.then(unsubs => {
3462
3551
  unsubs.forEach(unsubscribe => unsubscribe());
3463
3552
  });
3553
+ controller.abort();
3464
3554
  };
3465
3555
  },
3466
3556
  async fetchBalances(addressesByToken) {
@@ -3647,263 +3737,6 @@ const asObservable = handler => (...args) => new Observable(subscriber => {
3647
3737
  return unsubscribe;
3648
3738
  });
3649
3739
 
3650
- /**
3651
- * Crowdloan contributions are stored in the `childstate` key returned by this function.
3652
- */
3653
- const crowdloanFundContributionsChildKey = fundIndex => u8aToHex(u8aConcat(":child_storage:default:", blake2AsU8a(u8aConcat("crowdloan", u32.enc(fundIndex)))));
3654
-
3655
- // TODO make this method chain-specific
3656
- async function subscribeCrowdloans(chaindataProvider, chainConnector, addressesByToken, callback, signal) {
3657
- const allChains = await chaindataProvider.chainsById();
3658
- const tokens = await chaindataProvider.tokensById();
3659
-
3660
- // there should be only one network here when subscribing to balances, we've split it up by network at the top level
3661
- const networkIds = keys(addressesByToken).map(tokenId => parseTokenId(tokenId).networkId);
3662
- const miniMetadatas = new Map();
3663
- for (const networkId of networkIds) miniMetadatas.set(networkId, await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native"));
3664
- const crowdloanTokenIds = Object.entries(tokens).filter(([, token]) => {
3665
- // ignore non-native tokens
3666
- if (token.type !== "substrate-native") return;
3667
- // ignore tokens on chains with no crowdloans pallet
3668
- const miniMetadata = miniMetadatas.get(token.networkId);
3669
- return typeof miniMetadata?.extra?.crowdloanPalletId === "string";
3670
- }).map(([tokenId]) => tokenId);
3671
-
3672
- // crowdloan contributions can only be done by the native token on chains with the crowdloan pallet
3673
- const addressesByCrowdloanToken = Object.fromEntries(Object.entries(addressesByToken)
3674
- // remove ethereum addresses
3675
- .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !isEthereumAddress(address))])
3676
- // remove tokens which aren't crowdloan tokens
3677
- .filter(([tokenId]) => crowdloanTokenIds.includes(tokenId)));
3678
- const uniqueChainIds = getUniqueChainIds(addressesByCrowdloanToken, tokens);
3679
- const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
3680
- const chainStorageCoders = buildStorageCoders({
3681
- chainIds: uniqueChainIds,
3682
- chains,
3683
- miniMetadatas,
3684
- coders: {
3685
- parachains: ["Paras", "Parachains"],
3686
- funds: ["Crowdloan", "Funds"]
3687
- }
3688
- });
3689
- const tokenSubscriptions = [];
3690
- for (const [tokenId, addresses] of Object.entries(addressesByCrowdloanToken)) {
3691
- const token = tokens[tokenId];
3692
- if (!token) {
3693
- log.warn(`Token ${tokenId} not found`);
3694
- continue;
3695
- }
3696
- if (token.type !== "substrate-native") {
3697
- log.debug(`This module doesn't handle tokens of type ${token.type}`);
3698
- continue;
3699
- }
3700
- const chainId = token.networkId;
3701
- if (!chainId) {
3702
- log.warn(`Token ${tokenId} has no chain`);
3703
- continue;
3704
- }
3705
- const chain = chains[chainId];
3706
- if (!chain) {
3707
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
3708
- continue;
3709
- }
3710
- const subscribeParaIds = callback => {
3711
- const scaleCoder = chainStorageCoders.get(chainId)?.parachains;
3712
- const queries = [0].flatMap(() => {
3713
- const stateKey = encodeStateKey(scaleCoder);
3714
- if (!stateKey) return [];
3715
- const decodeResult = change => {
3716
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3717
-
3718
- const decoded = decodeScale(scaleCoder, change, `Failed to decode parachains on chain ${chainId}`);
3719
- const paraIds = decoded ?? [];
3720
- return paraIds;
3721
- };
3722
- return {
3723
- chainId,
3724
- stateKey,
3725
- decodeResult
3726
- };
3727
- });
3728
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3729
- return () => subscription.then(unsubscribe => unsubscribe());
3730
- };
3731
- const subscribeParaFundIndexes = (paraIds, callback) => {
3732
- const scaleCoder = chainStorageCoders.get(chainId)?.funds;
3733
- const queries = paraIds.flatMap(paraId => {
3734
- const stateKey = encodeStateKey(scaleCoder, `Invalid paraId in ${chainId} funds query ${paraId}`, paraId);
3735
- if (!stateKey) return [];
3736
- const decodeResult = change => {
3737
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3738
-
3739
- const decoded = decodeScale(scaleCoder, change, `Failed to decode paras on chain ${chainId}`);
3740
- const firstPeriod = decoded?.first_period?.toString?.() ?? "";
3741
- const lastPeriod = decoded?.last_period?.toString?.() ?? "";
3742
- const fundPeriod = `${firstPeriod}-${lastPeriod}`;
3743
- const fundIndex = decoded?.fund_index ?? decoded?.trie_index;
3744
- return {
3745
- paraId,
3746
- fundPeriod,
3747
- fundIndex
3748
- };
3749
- };
3750
- return {
3751
- chainId,
3752
- stateKey,
3753
- decodeResult
3754
- };
3755
- });
3756
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3757
- return () => subscription.then(unsubscribe => unsubscribe());
3758
- };
3759
- const subscribeFundContributions = (funds, addresses, callback) => {
3760
- // TODO: Watch system_events in order to subscribe to changes, then redo the contributions query when changes are detected:
3761
- // https://github.com/polkadot-js/api/blob/8fe02a14345b57e6abb8f7f2c2b624cf70c51b23/packages/api-derive/src/crowdloan/ownContributions.ts#L32-L47
3762
- //
3763
- // For now we just re-fetch all contributions on a timer and then only send them to the subscription callback when they have changed
3764
-
3765
- const queries = funds.map(({
3766
- paraId,
3767
- fundIndex
3768
- }) => ({
3769
- paraId,
3770
- fundIndex,
3771
- addresses,
3772
- childKey: crowdloanFundContributionsChildKey(fundIndex),
3773
- storageKeys: addresses.map(address => u8aToHex(decodeAnyAddress(address)))
3774
- }));
3775
-
3776
- // track whether our caller is still subscribed
3777
- let subscriptionActive = true;
3778
- let previousContributions = null;
3779
- const fetchContributions = async () => {
3780
- try {
3781
- const results = await Promise.all(queries.map(async ({
3782
- paraId,
3783
- fundIndex,
3784
- addresses,
3785
- childKey,
3786
- storageKeys
3787
- }) => ({
3788
- paraId,
3789
- fundIndex,
3790
- addresses,
3791
- result: await chainConnector.send(chainId, "childstate_getStorageEntries", [childKey, storageKeys])
3792
- })));
3793
- const contributions = results.flatMap(queryResult => {
3794
- const {
3795
- paraId,
3796
- fundIndex,
3797
- addresses,
3798
- result
3799
- } = queryResult;
3800
- return (Array.isArray(result) ? result : []).flatMap((encoded, index) => {
3801
- const amount = (() => {
3802
- try {
3803
- return typeof encoded === "string" ? u128.dec(encoded) ?? 0n : 0n;
3804
- } catch {
3805
- return 0n;
3806
- }
3807
- })().toString();
3808
- return {
3809
- paraId,
3810
- fundIndex,
3811
- address: addresses[index],
3812
- amount
3813
- };
3814
- });
3815
- });
3816
-
3817
- // ignore these results if our caller has tried to close this subscription
3818
- if (!subscriptionActive) return;
3819
-
3820
- // ignore these results if they're the same as what we previously fetched
3821
- if (isEqual(previousContributions, contributions)) return;
3822
- previousContributions = contributions;
3823
- callback(null, contributions);
3824
- } catch (error) {
3825
- callback(error);
3826
- }
3827
- };
3828
-
3829
- // set up polling for contributions
3830
- const crowdloanContributionsPollInterval = 60_000; // 60_000ms === 1 minute
3831
- const pollContributions = async () => {
3832
- if (!subscriptionActive) return;
3833
- try {
3834
- await fetchContributions();
3835
- } catch (error) {
3836
- // log any errors, but don't cancel the poll for contributions when one fetch fails
3837
- log.error(error);
3838
- }
3839
- if (!subscriptionActive) return;
3840
- setTimeout(pollContributions, crowdloanContributionsPollInterval);
3841
- };
3842
-
3843
- // start polling
3844
- pollContributions();
3845
- return () => {
3846
- // stop polling
3847
- subscriptionActive = false;
3848
- };
3849
- };
3850
- const paraIds$ = asObservable(subscribeParaIds)().pipe(scan((_, next) => Array.from(new Set(next.flatMap(paraIds => paraIds))), []), share());
3851
- const fundIndexesByParaId$ = paraIds$.pipe(map(paraIds => asObservable(subscribeParaFundIndexes)(paraIds)), switchAll(), scan((state, next) => {
3852
- for (const fund of next) {
3853
- const {
3854
- paraId,
3855
- fundIndex
3856
- } = fund;
3857
- if (typeof fundIndex === "number") {
3858
- state.set(paraId, (state.get(paraId) ?? new Set()).add(fundIndex));
3859
- }
3860
- }
3861
- return state;
3862
- }, new Map()));
3863
- const contributionsByAddress$ = fundIndexesByParaId$.pipe(map(fundIndexesByParaId => Array.from(fundIndexesByParaId).flatMap(([paraId, fundIndexes]) => Array.from(fundIndexes).map(fundIndex => ({
3864
- paraId,
3865
- fundIndex
3866
- })))), map(funds => asObservable(subscribeFundContributions)(funds, addresses)), switchAll(), scan((state, next) => {
3867
- for (const contribution of next) {
3868
- const {
3869
- address
3870
- } = contribution;
3871
- state.set(address, (state.get(address) ?? new Set()).add(contribution));
3872
- }
3873
- return state;
3874
- }, new Map()));
3875
- const subscription = contributionsByAddress$.subscribe({
3876
- next: contributionsByAddress => {
3877
- const balances = Array.from(contributionsByAddress).map(([address, contributions]) => {
3878
- return {
3879
- source: "substrate-native",
3880
- status: "live",
3881
- address,
3882
- networkId: chainId,
3883
- tokenId,
3884
- values: Array.from(contributions).map(({
3885
- amount,
3886
- paraId
3887
- }) => ({
3888
- type: "crowdloan",
3889
- label: "crowdloan",
3890
- source: "crowdloan",
3891
- amount: amount,
3892
- meta: {
3893
- paraId
3894
- }
3895
- }))
3896
- };
3897
- });
3898
- if (balances.length > 0) callback(null, balances);
3899
- },
3900
- error: error => callback(error)
3901
- });
3902
- tokenSubscriptions.push(() => subscription.unsubscribe());
3903
- }
3904
- return () => tokenSubscriptions.forEach(unsub => unsub());
3905
- }
3906
-
3907
3740
  /**
3908
3741
  * Each nominationPool in the nominationPools pallet has access to some accountIds which have no
3909
3742
  * associated private key. Instead, they are derived from this function.
@@ -3935,6 +3768,7 @@ async function subscribeNompoolStaking(chaindataProvider, chainConnector, addres
3935
3768
  const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native");
3936
3769
  miniMetadatas.set(networkId, miniMetadata);
3937
3770
  }
3771
+ signal?.throwIfAborted();
3938
3772
  const nomPoolTokenIds = Object.entries(tokens).filter(([, token]) => {
3939
3773
  // ignore non-native tokens
3940
3774
  if (token.type !== "substrate-native") return false;
@@ -4252,9 +4086,10 @@ async function subscribeSubtensorStaking(chaindataProvider, chainConnector, addr
4252
4086
  const networkIds = keys(addressesByToken).map(tokenId => parseTokenId(tokenId).networkId);
4253
4087
  const miniMetadatas = new Map();
4254
4088
  for (const networkId of networkIds) {
4255
- const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native");
4089
+ const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native", signal);
4256
4090
  miniMetadatas.set(networkId, miniMetadata);
4257
4091
  }
4092
+ signal?.throwIfAborted();
4258
4093
  const subtensorTokenIds = Object.entries(tokens).filter(([, token]) => {
4259
4094
  // ignore non-native tokens
4260
4095
  if (token.type !== "substrate-native") return false;
@@ -4460,7 +4295,9 @@ async function subscribeSubtensorStaking(chaindataProvider, chainConnector, addr
4460
4295
  });
4461
4296
 
4462
4297
  // use the abortController to tear the subscription down when we don't need it anymore
4463
- abortController.signal.onabort = () => subscription.unsubscribe();
4298
+ abortController.signal.addEventListener("abort", () => {
4299
+ subscription.unsubscribe();
4300
+ });
4464
4301
  }
4465
4302
  return () => abortController.abort();
4466
4303
  }
@@ -4546,6 +4383,10 @@ const getLockTitle = (lock, {
4546
4383
 
4547
4384
  const moduleType$2 = "substrate-native";
4548
4385
 
4386
+ // {
4387
+ // disable?: boolean
4388
+ // } & BalancesConfigTokenParams
4389
+
4549
4390
  /**
4550
4391
  * Function to merge two 'sub sources' of the same balance together, or
4551
4392
  * two instances of the same balance with different values.
@@ -5023,6 +4864,7 @@ const UNSUPPORTED_CHAIN_META$1 = {
5023
4864
  miniMetadata: null,
5024
4865
  extra: null
5025
4866
  };
4867
+ const SubNativeTokenConfigSchema = TokenConfigBaseSchema;
5026
4868
  const SubNativeModule = hydrate => {
5027
4869
  const {
5028
4870
  chainConnectors,
@@ -5038,7 +4880,7 @@ const SubNativeModule = hydrate => {
5038
4880
  // subscribeBalances was split by network to prevent all subs to wait for all minimetadatas to be ready.
5039
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
5040
4882
  // TODO refactor this be actually network specific
5041
- const subscribeChainBalances = async (chainId, opts, callback) => {
4883
+ const subscribeChainBalances = (chainId, opts, callback, signal) => {
5042
4884
  const {
5043
4885
  addressesByToken,
5044
4886
  initialBalances
@@ -5137,13 +4979,19 @@ const SubNativeModule = hydrate => {
5137
4979
  return from(queryCache.getQueries(newAddressesByToken)).pipe(switchMap(baseQueries => {
5138
4980
  return new Observable(subscriber => {
5139
4981
  if (!chainConnectors.substrate) return;
5140
- const unsubSubtensorStaking = subscribeSubtensorStaking(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("subtensor-staking"));
5141
- const unsubNompoolStaking = subscribeNompoolStaking(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("nompools-staking"));
5142
- 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
+ // )
5143
4991
  const unsubBase = subscribeBase(baseQueries, chainConnectors.substrate, handleUpdateForSource("base"));
5144
4992
  subscriber.add(async () => (await unsubSubtensorStaking)());
5145
4993
  subscriber.add(async () => (await unsubNompoolStaking)());
5146
- subscriber.add(async () => (await unsubCrowdloans)());
4994
+ // subscriber.add(async () => (await unsubCrowdloans)())
5147
4995
  subscriber.add(async () => (await unsubBase)());
5148
4996
  });
5149
4997
  }));
@@ -5175,8 +5023,13 @@ const SubNativeModule = hydrate => {
5175
5023
  const nonCurrentTokens = Object.keys(addressesByToken).filter(tokenId => !currentTokens.has(tokenId)).sort(sortChains);
5176
5024
 
5177
5025
  // break nonCurrentTokens into chunks of POLLING_WINDOW_SIZE
5178
- 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({
5179
5030
  [nonCurrentTokenId]: addressesByToken[nonCurrentTokenId]
5031
+ }), {
5032
+ signal
5180
5033
  }));
5181
5034
 
5182
5035
  // now poll every 30s on chains which are not subscriptionTokens
@@ -5189,7 +5042,9 @@ const SubNativeModule = hydrate => {
5189
5042
  Object.keys(addressesByToken).filter(tokenId => !subscribedTokenIds.includes(tokenId))), exhaustMap(tokenIds => from(arrayChunk(tokenIds, POLLING_WINDOW_SIZE)).pipe(concatMap(async tokenChunk => {
5190
5043
  // tokenChunk is a chunk of tokenIds with size POLLING_WINDOW_SIZE
5191
5044
  const pollingTokenAddresses = Object.fromEntries(tokenChunk.map(tokenId => [tokenId, addressesByToken[tokenId]]));
5192
- await poll(pollingTokenAddresses);
5045
+ await pool.add(() => poll(pollingTokenAddresses), {
5046
+ signal
5047
+ });
5193
5048
  return true;
5194
5049
  })))).subscribe();
5195
5050
  return () => {
@@ -5234,7 +5089,7 @@ const SubNativeModule = hydrate => {
5234
5089
  };
5235
5090
  const existentialDeposit = getConstantValue("Balances", "ExistentialDeposit")?.toString();
5236
5091
  const nominationPoolsPalletId = getConstantValue("NominationPools", "PalletId")?.asText();
5237
- const crowdloanPalletId = getConstantValue("Crowdloan", "PalletId")?.asText();
5092
+ const crowdloanPalletId = getConstantValue("Crowdloan", "PalletId")?.asText(); // TODO yeet
5238
5093
  const hasSubtensorPallet = getConstantValue("SubtensorModule", "KeySwapCost") !== undefined;
5239
5094
 
5240
5095
  //
@@ -5257,10 +5112,13 @@ const SubNativeModule = hydrate => {
5257
5112
  }, {
5258
5113
  pallet: "Crowdloan",
5259
5114
  items: ["Funds"]
5260
- }, {
5115
+ },
5116
+ // TODO yeet
5117
+ {
5261
5118
  pallet: "Paras",
5262
5119
  items: ["Parachains"]
5263
5120
  },
5121
+ // TODO yeet
5264
5122
  // TotalColdkeyStake is used until v.2.2.1, then it is replaced by StakingHotkeys+Stake
5265
5123
  // Need to keep TotalColdkeyStake for a while so chaindata keeps including it in miniMetadatas, so it doesnt break old versions of the wallet
5266
5124
  {
@@ -5303,22 +5161,24 @@ const SubNativeModule = hydrate => {
5303
5161
  const {
5304
5162
  existentialDeposit
5305
5163
  } = chainMeta.extra ?? {};
5164
+ if (existentialDeposit === undefined) log.warn("Substrate native module: existentialDeposit is undefined for %s, using 0", chainId);
5306
5165
  const id = subNativeTokenId(chainId);
5307
5166
  const nativeToken = {
5308
5167
  id,
5309
5168
  type: "substrate-native",
5310
5169
  platform: "polkadot",
5311
- isDefault: moduleConfig?.isDefault ?? true,
5170
+ isDefault: true,
5312
5171
  symbol: symbol,
5313
- name: moduleConfig?.name ?? symbol,
5172
+ name: symbol,
5314
5173
  decimals: decimals,
5315
- logo: moduleConfig?.logo,
5316
5174
  existentialDeposit: existentialDeposit ?? "0",
5317
5175
  networkId: chainId
5318
5176
  };
5319
- if (moduleConfig?.symbol) nativeToken.symbol = moduleConfig?.symbol;
5320
- if (moduleConfig?.coingeckoId) nativeToken.coingeckoId = moduleConfig?.coingeckoId;
5321
- 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
+
5322
5182
  return {
5323
5183
  [nativeToken.id]: nativeToken
5324
5184
  };
@@ -5350,11 +5210,11 @@ const SubNativeModule = hydrate => {
5350
5210
  return subscribeChainBalances(networkId, {
5351
5211
  addressesByToken: addressesByTokenByNetwork[networkId] ?? {},
5352
5212
  initialBalances: initialBalancesByNetwork[networkId] ?? []
5353
- }, safeCallback);
5213
+ }, safeCallback, controller.signal);
5354
5214
  }));
5355
5215
  return () => {
5356
- controller.abort();
5357
5216
  unsubsribeFns.then(fns => fns.forEach(unsubscribe => unsubscribe()));
5217
+ controller.abort();
5358
5218
  };
5359
5219
  },
5360
5220
  fetchBalances,
@@ -6572,6 +6432,22 @@ var psp22Abi = {
6572
6432
  };
6573
6433
 
6574
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
+
6575
6451
  const SubPsp22Module = hydrate => {
6576
6452
  const {
6577
6453
  chainConnectors,
@@ -6588,7 +6464,8 @@ const SubPsp22Module = hydrate => {
6588
6464
  extra: null
6589
6465
  };
6590
6466
  },
6591
- async fetchSubstrateChainTokens(chainId, _chainMeta, moduleConfig) {
6467
+ async fetchSubstrateChainTokens(chainId, _chainMeta, moduleConfig, tokens) {
6468
+ if (!tokens?.length) return {};
6592
6469
  // const { isTestnet } = chainMeta
6593
6470
 
6594
6471
  const registry = new TypeRegistry();
@@ -6600,12 +6477,12 @@ const SubPsp22Module = hydrate => {
6600
6477
  chainId,
6601
6478
  registry
6602
6479
  });
6603
- const tokens = {};
6604
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
6480
+ const tokenList = {};
6481
+ for (const tokenConfig of tokens ?? []) {
6605
6482
  try {
6606
6483
  let symbol = tokenConfig?.symbol ?? "Unit";
6607
6484
  let decimals = tokenConfig?.decimals ?? 0;
6608
- const existentialDeposit = tokenConfig?.ed ?? "0";
6485
+ const existentialDeposit = tokenConfig?.existentialDeposit ?? "0";
6609
6486
  const contractAddress = tokenConfig?.contractAddress ?? undefined;
6610
6487
  if (contractAddress === undefined) continue;
6611
6488
  await (async () => {
@@ -6638,13 +6515,13 @@ const SubPsp22Module = hydrate => {
6638
6515
  };
6639
6516
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
6640
6517
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
6641
- tokens[token.id] = token;
6518
+ tokenList[token.id] = token;
6642
6519
  } catch (error) {
6643
6520
  log.error(`Failed to build substrate-psp22 token ${tokenConfig.contractAddress} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
6644
6521
  continue;
6645
6522
  }
6646
6523
  }
6647
- return tokens;
6524
+ return tokenList;
6648
6525
  },
6649
6526
  // TODO: Don't create empty subscriptions
6650
6527
  async subscribeBalances({
@@ -6819,11 +6696,28 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
6819
6696
  };
6820
6697
 
6821
6698
  const moduleType = "substrate-tokens";
6699
+ const SubTokensTokenConfigSchema = TokenConfigBaseSchema.extend({
6700
+ onChainId: SubTokensTokenSchema.shape.onChainId,
6701
+ existentialDeposit: SubTokensTokenSchema.shape.existentialDeposit
6702
+ });
6822
6703
  const defaultPalletId = "Tokens";
6823
6704
  const UNSUPPORTED_CHAIN_META = {
6824
6705
  miniMetadata: null,
6825
6706
  extra: {}
6826
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
+
6827
6721
  const SubTokensModule = hydrate => {
6828
6722
  const {
6829
6723
  chainConnectors,
@@ -6849,13 +6743,14 @@ const SubTokensModule = hydrate => {
6849
6743
  }
6850
6744
  };
6851
6745
  },
6852
- async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
6853
- const tokens = {};
6854
- for (const tokenConfig of moduleConfig?.tokens ?? []) {
6746
+ async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
6747
+ const tokenList = {};
6748
+ for (const tokenConfig of tokens ?? []) {
6855
6749
  try {
6750
+ // TODO fetch metadata from chain, like we do for assets
6856
6751
  const symbol = tokenConfig?.symbol ?? "Unit";
6857
6752
  const decimals = tokenConfig?.decimals ?? 0;
6858
- const existentialDeposit = tokenConfig?.ed ?? "0";
6753
+ const existentialDeposit = tokenConfig?.existentialDeposit ?? "0";
6859
6754
  const onChainId = tokenConfig?.onChainId ?? undefined;
6860
6755
  if (onChainId === undefined) continue;
6861
6756
  const id = subTokensTokenId(chainId, onChainId);
@@ -6874,13 +6769,13 @@ const SubTokensModule = hydrate => {
6874
6769
  };
6875
6770
  if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
6876
6771
  if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
6877
- tokens[token.id] = token;
6772
+ tokenList[token.id] = token;
6878
6773
  } catch (error) {
6879
6774
  log.error(`Failed to build substrate-tokens token ${tokenConfig.onChainId} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
6880
6775
  continue;
6881
6776
  }
6882
6777
  }
6883
- return tokens;
6778
+ return tokenList;
6884
6779
  },
6885
6780
  // TODO: Don't create empty subscriptions
6886
6781
  async subscribeBalances({
@@ -6910,10 +6805,10 @@ const SubTokensModule = hydrate => {
6910
6805
  }
6911
6806
  }));
6912
6807
  return () => {
6913
- controller.abort();
6914
6808
  pUnsubs.then(unsubs => {
6915
6809
  unsubs.forEach(unsubscribe => unsubscribe());
6916
6810
  });
6811
+ controller.abort();
6917
6812
  };
6918
6813
  },
6919
6814
  async fetchBalances(addressesByToken) {
@@ -7122,4 +7017,4 @@ async function buildQueries(chainConnector, chaindataProvider, addressesByToken,
7122
7017
 
7123
7018
  const defaultBalanceModules = [EvmErc20Module, EvmNativeModule, EvmUniswapV2Module, SubAssetsModule, SubForeignAssetsModule, SubNativeModule, SubPsp22Module, SubTokensModule];
7124
7019
 
7125
- 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 };