@talismn/balances 3.0.6 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -45,10 +45,10 @@ let _metaplex_foundation_umi = require("@metaplex-foundation/umi");
45
45
  let _solana_program_token_2022 = require("@solana-program/token-2022");
46
46
  let _polkadot_api_substrate_bindings = require("@polkadot-api/substrate-bindings");
47
47
  let _polkadot_api_utils = require("@polkadot-api/utils");
48
+ let _talismn_sapi = require("@talismn/sapi");
48
49
  let lodash_es_upperFirst = require("lodash-es/upperFirst");
49
50
  lodash_es_upperFirst = __toESM(lodash_es_upperFirst);
50
51
  let scale_ts = require("scale-ts");
51
- let _talismn_sapi = require("@talismn/sapi");
52
52
  //#region src/configureBigNumber.ts
53
53
  bignumber_js.default.set({ STRICT: false });
54
54
  var configureBigNumber_default = bignumber_js.default;
@@ -443,7 +443,7 @@ var Balance = class {
443
443
  }
444
444
  /** The frozen balance of this token. Is included in the free amount. */
445
445
  get locked() {
446
- if (this.#cachedLocked === void 0) this.#cachedLocked = this.#format(this.locks.map(({ amount }) => amount.planck).reduce((a, b) => _talismn_util.BigMath.max(a, b), 0n));
446
+ if (this.#cachedLocked === void 0) this.#cachedLocked = this.#format(excludeFromTransferableAmount(this.locks));
447
447
  return this.#cachedLocked;
448
448
  }
449
449
  get locks() {
@@ -512,7 +512,8 @@ var Balance = class {
512
512
  const newCalculation = () => _talismn_util.BigMath.max(this.locked.planck, this.reserved.planck);
513
513
  const baseUnavailable = this.#storage.useLegacyTransferableCalculation ? oldCalculation() : newCalculation();
514
514
  const nomPoolStakedPlancks = this.locks.some((lock) => lock.source === "substrate-native-holds" && lock.label === "DelegatedStaking") ? 0n : this.nompools.map(({ amount }) => amount.planck).reduce((a, b) => a + b, 0n);
515
- return this.#format(baseUnavailable + nomPoolStakedPlancks);
515
+ const extra = this.getValue("extra");
516
+ return this.#format(baseUnavailable + nomPoolStakedPlancks + includeInTotalExtraAmount(extra));
516
517
  };
517
518
  /** The feePayable balance of this token. Is generally the free amount - the feeFrozen amount. */
518
519
  get feePayable() {
@@ -1693,18 +1694,31 @@ const fetchRpcQueryPack = async (connector, networkId, queries, at) => {
1693
1694
  return decodeRpcQueryPackChunked(queries, new Map(result.changes), { label: `rpcQueryPack decode ${networkId}` });
1694
1695
  };
1695
1696
  /**
1697
+ * The connector reports a chain that has been unreachable for a while with a StaleRpcError,
1698
+ * but keeps the subscription registered and re-issues it when a socket reconnects (the
1699
+ * first notification after that is a full snapshot again). Matched by its `type` rather
1700
+ * than `instanceof`, so a second copy of @talismn/chain-connectors still matches.
1701
+ */
1702
+ const isStaleRpcError = (error) => typeof error === "object" && error !== null && error.type === "STALE_RPC_ERROR";
1703
+ /**
1696
1704
  * Wraps the raw state_subscribeStorage subscription and emits, for every storage change
1697
1705
  * callback, a SNAPSHOT of the accumulated changes keyed by state key.
1698
1706
  *
1699
1707
  * A snapshot (shallow copy of string refs, cheap) is required because decoding is chunked
1700
1708
  * and asynchronous: a later websocket callback must not mutate the map that an in-flight
1701
1709
  * decode is reading.
1710
+ *
1711
+ * A StaleRpcError does not end the stream. Erroring would unsubscribe, and with no
1712
+ * subscription holding the chain's socket, every later request (eg the dtao poll) would
1713
+ * open and close a socket of its own, starting from the network's first RPC each time.
1702
1714
  */
1703
1715
  const getRawStorageUpdates$ = (connector, networkId, allStateKeys, timeout) => new rxjs.Observable((subscriber) => {
1704
1716
  const changesCache = /* @__PURE__ */ new Map();
1705
1717
  const promUnsub = connector.subscribe(networkId, "state_subscribeStorage", "state_storage", [allStateKeys], (error, result) => {
1706
- if (error) subscriber.error(error);
1707
- else if (result) {
1718
+ if (error) {
1719
+ if (isStaleRpcError(error)) log_default.warn(`Storage subscription stale on ${networkId}`);
1720
+ else subscriber.error(error);
1721
+ } else if (result) {
1708
1722
  for (const [stateKey, encodedResult] of result.changes) changesCache.set(stateKey, encodedResult);
1709
1723
  subscriber.next(new Map(changesCache));
1710
1724
  }
@@ -2590,15 +2604,16 @@ const hasRuntimeApi = (metadata, apiName, method) => {
2590
2604
  if (!api?.methods) return false;
2591
2605
  return api.methods.some((m) => m.name === method);
2592
2606
  };
2593
- const getConstantValue = (metadataRpc, pallet, constant) => {
2594
- const { unifiedMetadata, builder } = (0, _talismn_scale.parseMetadataRpc)(metadataRpc);
2607
+ const toParsedMetadata = (metadata) => typeof metadata === "string" ? (0, _talismn_scale.parseMetadataRpc)(metadata) : metadata;
2608
+ const getConstantValue = (metadata, pallet, constant) => {
2609
+ const { unifiedMetadata, builder } = toParsedMetadata(metadata);
2595
2610
  const codec = builder.buildConstant(pallet, constant);
2596
2611
  const encodedValue = unifiedMetadata.pallets.find(({ name }) => name === pallet)?.constants.find(({ name }) => name === constant)?.value;
2597
2612
  if (!encodedValue) throw new Error(`Constant ${pallet}.${constant} not found`);
2598
2613
  return codec.dec(encodedValue);
2599
2614
  };
2600
- const tryGetConstantValue = (metadataRpc, pallet, constant) => {
2601
- const { unifiedMetadata, builder } = (0, _talismn_scale.parseMetadataRpc)(metadataRpc);
2615
+ const tryGetConstantValue = (metadata, pallet, constant) => {
2616
+ const { unifiedMetadata, builder } = toParsedMetadata(metadata);
2602
2617
  const encodedValue = unifiedMetadata.pallets.find(({ name }) => name === pallet)?.constants.find(({ name }) => name === constant)?.value;
2603
2618
  if (!encodedValue) return null;
2604
2619
  return builder.buildConstant(pallet, constant).dec(encodedValue);
@@ -2732,7 +2747,7 @@ const fetchOnChainTokenData$1 = async (connector, tokenId) => {
2732
2747
  //#region src/modules/sol-spl/fetchBalances.ts
2733
2748
  const SPL_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
2734
2749
  const dynamicTokenMetadataCache$1 = new _talismn_util.LruMap(1024);
2735
- const associatedTokenAccountCache = new _talismn_util.LruMap(1024);
2750
+ const associatedTokenAccountCache$1 = new _talismn_util.LruMap(1024);
2736
2751
  const fetchBalances$8 = async ({ networkId, tokensWithAddresses, connector }) => {
2737
2752
  if (!tokensWithAddresses.length) return {
2738
2753
  success: [],
@@ -2752,7 +2767,7 @@ const fetchBalances$8 = async ({ networkId, tokensWithAddresses, connector }) =>
2752
2767
  return {
2753
2768
  mintAddress,
2754
2769
  value,
2755
- isSpendable: value !== 0n && state !== "frozen" && d.pubkey === await getAssociatedTokenAccount(address, mintAddress)
2770
+ isSpendable: value !== 0n && state !== "frozen" && d.pubkey === await getAssociatedTokenAccount$1(address, mintAddress)
2756
2771
  };
2757
2772
  } catch {
2758
2773
  log_default.warn("Failed to parse token amount", {
@@ -2845,16 +2860,16 @@ const fetchBalances$8 = async ({ networkId, tokensWithAddresses, connector }) =>
2845
2860
  };
2846
2861
  };
2847
2862
  const getBalanceKey$1 = (tokenId, address) => `${tokenId}:${address}`;
2848
- const getAssociatedTokenAccount = async (owner, mint) => {
2863
+ const getAssociatedTokenAccount$1 = async (owner, mint) => {
2849
2864
  const key = `${owner}:${mint}`;
2850
- const cached = associatedTokenAccountCache.get(key);
2865
+ const cached = associatedTokenAccountCache$1.get(key);
2851
2866
  if (cached) return cached;
2852
2867
  const [associatedTokenAccount] = await (0, _solana_program_token.findAssociatedTokenPda)({
2853
2868
  mint: (0, _solana_kit.address)(mint),
2854
2869
  owner: (0, _solana_kit.address)(owner),
2855
2870
  tokenProgram: _solana_program_token.TOKEN_PROGRAM_ADDRESS
2856
2871
  });
2857
- associatedTokenAccountCache.set(key, associatedTokenAccount);
2872
+ associatedTokenAccountCache$1.set(key, associatedTokenAccount);
2858
2873
  return associatedTokenAccount;
2859
2874
  };
2860
2875
  //#endregion
@@ -3304,6 +3319,7 @@ const fetchOnChainTokenData = async (connector, tokenId) => {
3304
3319
  //#region src/modules/sol-token2022/fetchBalances.ts
3305
3320
  const TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
3306
3321
  const dynamicTokenMetadataCache = /* @__PURE__ */ new Map();
3322
+ const associatedTokenAccountCache = new _talismn_util.LruMap(1024);
3307
3323
  const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) => {
3308
3324
  if (!tokensWithAddresses.length) return {
3309
3325
  success: [],
@@ -3315,19 +3331,15 @@ const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) =>
3315
3331
  const knownTokenIds = new Set(tokensWithAddresses.map(([token]) => token.id));
3316
3332
  const unknownMints = /* @__PURE__ */ new Set();
3317
3333
  const balancesPerAddress = await Promise.all(accountAddresses.map(async (address) => {
3318
- const balances = (await rpc.getTokenAccountsByOwner((0, _solana_kit.address)(address), { programId: (0, _solana_kit.address)(TOKEN_2022_PROGRAM_ID) }, { encoding: "jsonParsed" }).send()).value.map((d) => {
3334
+ const tokenAccounts = await rpc.getTokenAccountsByOwner((0, _solana_kit.address)(address), { programId: (0, _solana_kit.address)(TOKEN_2022_PROGRAM_ID) }, { encoding: "jsonParsed" }).send();
3335
+ const tokenAccountHoldings = await Promise.all(tokenAccounts.value.map(async (d) => {
3319
3336
  try {
3320
- const mintAddress = d.account.data.parsed.info.mint;
3321
- const value = d.account.data.parsed.info.tokenAmount.amount ?? "0";
3322
- const tokenId = (0, _talismn_chaindata_provider.solToken2022TokenId)(networkId, mintAddress);
3323
- if (!knownTokenIds.has(tokenId) && value !== "0") unknownMints.add(mintAddress);
3337
+ const { mint: mintAddress, state, tokenAmount } = d.account.data.parsed.info;
3338
+ const value = BigInt(tokenAmount.amount ?? "0");
3324
3339
  return {
3325
- tokenId,
3326
- networkId,
3327
- address,
3328
- source: MODULE_TYPE$7,
3329
- status: "live",
3330
- value
3340
+ mintAddress,
3341
+ value,
3342
+ isSpendable: value !== 0n && state !== "frozen" && d.pubkey === await getAssociatedTokenAccount(address, mintAddress)
3331
3343
  };
3332
3344
  } catch {
3333
3345
  log_default.warn("Failed to parse token amount", {
@@ -3336,7 +3348,36 @@ const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) =>
3336
3348
  });
3337
3349
  return null;
3338
3350
  }
3339
- }).filter(_talismn_util.isNotNil);
3351
+ }));
3352
+ const holdingsPerTokenId = /* @__PURE__ */ new Map();
3353
+ for (const { mintAddress, value, isSpendable } of tokenAccountHoldings.filter(_talismn_util.isNotNil)) {
3354
+ const tokenId = (0, _talismn_chaindata_provider.solToken2022TokenId)(networkId, mintAddress);
3355
+ if (!knownTokenIds.has(tokenId) && value !== 0n) unknownMints.add(mintAddress);
3356
+ const { total, spendable } = holdingsPerTokenId.get(tokenId) ?? {
3357
+ total: 0n,
3358
+ spendable: 0n
3359
+ };
3360
+ holdingsPerTokenId.set(tokenId, {
3361
+ total: total + value,
3362
+ spendable: spendable + (isSpendable ? value : 0n)
3363
+ });
3364
+ }
3365
+ const balances = [...holdingsPerTokenId].map(([tokenId, { total, spendable }]) => ({
3366
+ tokenId,
3367
+ networkId,
3368
+ address,
3369
+ source: MODULE_TYPE$7,
3370
+ status: "live",
3371
+ ...spendable === total ? { value: total.toString() } : { values: [{
3372
+ type: "free",
3373
+ label: "free",
3374
+ amount: total.toString()
3375
+ }, {
3376
+ type: "locked",
3377
+ label: "locked",
3378
+ amount: (total - spendable).toString()
3379
+ }] }
3380
+ }));
3340
3381
  setDetectedTokenIds(address, MODULE_TYPE$7, balances.map((b) => b.tokenId));
3341
3382
  return [address, balances];
3342
3383
  }));
@@ -3391,6 +3432,18 @@ const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) =>
3391
3432
  };
3392
3433
  };
3393
3434
  const getBalanceKey = (tokenId, address) => `${tokenId}:${address}`;
3435
+ const getAssociatedTokenAccount = async (owner, mint) => {
3436
+ const key = `${owner}:${mint}`;
3437
+ const cached = associatedTokenAccountCache.get(key);
3438
+ if (cached) return cached;
3439
+ const [associatedTokenAccount] = await (0, _solana_program_token.findAssociatedTokenPda)({
3440
+ mint: (0, _solana_kit.address)(mint),
3441
+ owner: (0, _solana_kit.address)(owner),
3442
+ tokenProgram: _solana_program_token_2022.TOKEN_2022_PROGRAM_ADDRESS
3443
+ });
3444
+ associatedTokenAccountCache.set(key, associatedTokenAccount);
3445
+ return associatedTokenAccount;
3446
+ };
3394
3447
  //#endregion
3395
3448
  //#region src/modules/sol-token2022/fetchTokens.ts
3396
3449
  const fetchTokens$7 = async ({ networkId, tokens, connector, cache }) => {
@@ -3648,14 +3701,15 @@ const deriveMiniMetadataId = ({ source, chainId, specVersion }) => (0, _talismn_
3648
3701
  const getMiniMetadata$6 = ({ networkId, specVersion, metadataRpc }) => {
3649
3702
  const source = MODULE_TYPE$6;
3650
3703
  const chainId = networkId;
3651
- const systemVersion = getConstantValue(metadataRpc, "System", "Version");
3704
+ const parsed = (0, _talismn_scale.parseMetadataRpc)(metadataRpc);
3705
+ const { unifiedMetadata } = parsed;
3706
+ const systemVersion = getConstantValue(parsed, "System", "Version");
3652
3707
  if (specVersion !== systemVersion.spec_version) throw new Error(`specVersion mismatch: expected ${specVersion}, metadata got ${systemVersion.spec_version}`);
3653
3708
  const id = deriveMiniMetadataId({
3654
3709
  source,
3655
3710
  chainId,
3656
3711
  specVersion
3657
3712
  });
3658
- const { unifiedMetadata } = (0, _talismn_scale.parseMetadataRpc)(metadataRpc);
3659
3713
  if (unifiedMetadata.version < 14) throw new Error(`Unsupported metadata version: ${unifiedMetadata.version}. Minimum required is 14.`);
3660
3714
  return {
3661
3715
  id,
@@ -3663,12 +3717,11 @@ const getMiniMetadata$6 = ({ networkId, specVersion, metadataRpc }) => {
3663
3717
  chainId,
3664
3718
  specVersion,
3665
3719
  version: _talismn_chaindata_provider.MINIMETADATA_VERSION,
3666
- data: getData$4(metadataRpc),
3720
+ data: getData$4(parsed),
3667
3721
  extra: null
3668
3722
  };
3669
3723
  };
3670
- const getData$4 = (metadataRpc) => {
3671
- const { metadata, unifiedMetadata } = (0, _talismn_scale.parseMetadataRpc)(metadataRpc);
3724
+ const getData$4 = ({ metadata, unifiedMetadata }) => {
3672
3725
  if (!hasStorageItems(unifiedMetadata, "Assets", [
3673
3726
  "Account",
3674
3727
  "Asset",
@@ -3846,27 +3899,6 @@ const fetchBasketClaims = async (connector, networkId, metadataRpc, addresses, a
3846
3899
  }
3847
3900
  };
3848
3901
  //#endregion
3849
- //#region src/modules/substrate-dtao/fetchStorageKeysPaged.ts
3850
- const PAGE_SIZE = 1e3;
3851
- /**
3852
- * Prefix scan via state_getKeysPaged. The unpaged state_getKeys is classified as heavy storage
3853
- * work and rejected by some public nodes (eg the bittensor testnet one: "Storage work rate limit
3854
- * exceeded"), while the paged variant is universally allowed.
3855
- */
3856
- const fetchStorageKeysPaged = async (connector, networkId, keyPrefix, at) => {
3857
- const keys = [];
3858
- let startKey;
3859
- do {
3860
- const params = [keyPrefix, PAGE_SIZE];
3861
- if (startKey || at) params.push(startKey ?? null);
3862
- if (at) params.push(at);
3863
- const page = await connector.send(networkId, "state_getKeysPaged", params);
3864
- keys.push(...page);
3865
- startKey = page.length === PAGE_SIZE ? page[page.length - 1] : void 0;
3866
- } while (startKey);
3867
- return keys;
3868
- };
3869
- //#endregion
3870
3902
  //#region src/modules/substrate-dtao/convictionLocks.ts
3871
3903
  const convictionLockKey = (address, netuid) => `${address}:${netuid}`;
3872
3904
  const getConvictionLockLabel = (lockType) => lockType === "perpetual" ? "Perpetual Conviction Lock" : "Decaying Conviction Lock";
@@ -3963,7 +3995,7 @@ const fetchConvictionLockStorageKeys = async (connector, networkId, addresses, s
3963
3995
  }
3964
3996
  let stateKeys;
3965
3997
  try {
3966
- stateKeys = await fetchStorageKeysPaged(connector, networkId, keyPrefix, at);
3998
+ stateKeys = await (0, _talismn_sapi.fetchStorageKeysPaged)((method, params) => connector.send(networkId, method, params), keyPrefix, { at });
3967
3999
  } catch (cause) {
3968
4000
  log_default.warn(`Failed to fetch conviction Lock keys (address=${address}) on ${networkId}`, { cause });
3969
4001
  throw cause;
@@ -4387,7 +4419,7 @@ const fetchTokens$5 = async ({ networkId, connector, tokens, miniMetadata }) =>
4387
4419
  const fetchTransferableTokensMap = async (connector, metadata, networkId) => {
4388
4420
  const { builder } = parseMetadataRpcCached(metadata);
4389
4421
  const transferToggleCodec = builder.buildStorage("SubtensorModule", "TransferToggle");
4390
- const transferToggleKeys = await fetchStorageKeysPaged(connector, networkId, (0, _talismn_scale.getStorageKeyPrefix)("SubtensorModule", "TransferToggle"));
4422
+ const transferToggleKeys = await (0, _talismn_sapi.fetchStorageKeysPaged)((method, params) => connector.send(networkId, method, params), (0, _talismn_scale.getStorageKeyPrefix)("SubtensorModule", "TransferToggle"));
4391
4423
  const transferToggleResults = await connector.send(networkId, "state_queryStorageAt", [transferToggleKeys]);
4392
4424
  const transferToggleEntries = transferToggleResults.length ? transferToggleResults[0].changes : [];
4393
4425
  return (0, lodash_es.fromPairs)(transferToggleEntries.map(([key, value]) => {
@@ -4835,14 +4867,15 @@ const fetchTokens$4 = async ({ networkId, tokens, connector, miniMetadata }) =>
4835
4867
  const getMiniMetadata$4 = ({ networkId, specVersion, metadataRpc }) => {
4836
4868
  const source = MODULE_TYPE$4;
4837
4869
  const chainId = networkId;
4838
- const systemVersion = getConstantValue(metadataRpc, "System", "Version");
4870
+ const parsed = (0, _talismn_scale.parseMetadataRpc)(metadataRpc);
4871
+ const { unifiedMetadata } = parsed;
4872
+ const systemVersion = getConstantValue(parsed, "System", "Version");
4839
4873
  if (specVersion !== systemVersion.spec_version) throw new Error(`specVersion mismatch: expected ${specVersion}, metadata got ${systemVersion.spec_version}`);
4840
4874
  const id = deriveMiniMetadataId({
4841
4875
  source,
4842
4876
  chainId,
4843
4877
  specVersion
4844
4878
  });
4845
- const { unifiedMetadata } = (0, _talismn_scale.parseMetadataRpc)(metadataRpc);
4846
4879
  if (unifiedMetadata.version < 14) throw new Error(`Unsupported metadata version: ${unifiedMetadata.version}. Minimum required is 14.`);
4847
4880
  return {
4848
4881
  id,
@@ -4850,12 +4883,11 @@ const getMiniMetadata$4 = ({ networkId, specVersion, metadataRpc }) => {
4850
4883
  chainId,
4851
4884
  specVersion,
4852
4885
  version: _talismn_chaindata_provider.MINIMETADATA_VERSION,
4853
- data: getData$2(metadataRpc),
4886
+ data: getData$2(parsed),
4854
4887
  extra: null
4855
4888
  };
4856
4889
  };
4857
- const getData$2 = (metadataRpc) => {
4858
- const { metadata, unifiedMetadata } = (0, _talismn_scale.parseMetadataRpc)(metadataRpc);
4890
+ const getData$2 = ({ metadata, unifiedMetadata }) => {
4859
4891
  if (!hasStorageItems(unifiedMetadata, "ForeignAssets", [
4860
4892
  "Account",
4861
4893
  "Asset",
@@ -5172,6 +5204,17 @@ const SubHydrationTokenConfigSchema = zod_v4.default.strictObject({
5172
5204
  ...TokenConfigBaseSchema.shape
5173
5205
  });
5174
5206
  //#endregion
5207
+ //#region src/modules/substrate-native/evmMirror.ts
5208
+ const EVM_MIRROR_LOCK_LABEL = "evm-mirror";
5209
+ const isEvmMirrorWithdrawableLock = (lock) => lock.meta?.type === "evm-mirror-withdrawable";
5210
+ const findEvmMirrorWithdrawable = (locks) => {
5211
+ const lock = locks?.find(isEvmMirrorWithdrawableLock);
5212
+ return lock ? {
5213
+ ...lock.meta,
5214
+ amount: lock.amount.planck
5215
+ } : null;
5216
+ };
5217
+ //#endregion
5175
5218
  //#region src/modules/substrate-native/config.ts
5176
5219
  const MODULE_TYPE$2 = _talismn_chaindata_provider.SubNativeTokenSchema.shape.type.value;
5177
5220
  const PLATFORM$2 = _talismn_chaindata_provider.SubNativeTokenSchema.shape.platform.value;
@@ -5217,7 +5260,7 @@ const baseLockLabels = ["fees", "misc"];
5217
5260
  const isBaseLock = (lock) => baseLockLabels.includes(lock.label);
5218
5261
  const isNonBaseLock = (lock) => !isBaseLock(lock);
5219
5262
  const filterBaseLocks = (locks) => {
5220
- if (!locks.some(isNonBaseLock)) return locks;
5263
+ if (!locks.some((lock) => isNonBaseLock(lock) && !lock.includeInTransferable)) return locks;
5221
5264
  return locks.filter(isNonBaseLock);
5222
5265
  };
5223
5266
  const getLockTitle = (lock, { balance } = {}) => {
@@ -5228,6 +5271,7 @@ const getLockTitle = (lock, { balance } = {}) => {
5228
5271
  if (lock.label === "nompools-unbonding") return "Pooled Staking";
5229
5272
  if (lock.label === "subtensor-staking") return "Root Staking";
5230
5273
  if (lock.label === "dapp-staking") return "DApp Staking";
5274
+ if (lock.label === "evm-mirror") return "EVM Balance";
5231
5275
  if (lock.label === "fees") return "Locked (Fees)";
5232
5276
  if (lock.label === "misc") return "Locked";
5233
5277
  if (lock.label.startsWith("other")) return "Locked";
@@ -5235,7 +5279,7 @@ const getLockTitle = (lock, { balance } = {}) => {
5235
5279
  };
5236
5280
  //#endregion
5237
5281
  //#region src/modules/substrate-native/queries/buildBaseQueries.ts
5238
- const buildBaseQueries = (networkId, balanceDefs, miniMetadata) => {
5282
+ const buildBaseQueries = (networkId, balanceDefs, miniMetadata, hasFrontierMirrorWithdraw = false) => {
5239
5283
  const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
5240
5284
  account: ["System", "Account"],
5241
5285
  stakingLedger: ["Staking", "Ledger"],
@@ -5247,6 +5291,7 @@ const buildBaseQueries = (networkId, balanceDefs, miniMetadata) => {
5247
5291
  });
5248
5292
  if (!networkStorageCoders) throw new Error(`No network storage coders found for networkId: ${networkId}`);
5249
5293
  return balanceDefs.map(({ token, address }) => {
5294
+ const evmMirror = hasFrontierMirrorWithdraw ? getEvmMirror(address) : null;
5250
5295
  const getStateKeys = () => {
5251
5296
  try {
5252
5297
  return [
@@ -5255,7 +5300,8 @@ const buildBaseQueries = (networkId, balanceDefs, miniMetadata) => {
5255
5300
  networkStorageCoders.freezes ? networkStorageCoders.freezes.keys.enc(address) : null,
5256
5301
  networkStorageCoders.holds ? networkStorageCoders.holds.keys.enc(address) : null,
5257
5302
  networkStorageCoders.stakingLedger ? networkStorageCoders.stakingLedger.keys.enc(address) : null,
5258
- networkStorageCoders.poolMembers ? networkStorageCoders.poolMembers.keys.enc(address) : null
5303
+ networkStorageCoders.poolMembers ? networkStorageCoders.poolMembers.keys.enc(address) : null,
5304
+ networkStorageCoders.account && evmMirror ? networkStorageCoders.account.keys.enc((0, _talismn_crypto.frontierH160ToSs58Mirror)(evmMirror.h160)) : null
5259
5305
  ];
5260
5306
  } catch {
5261
5307
  return [];
@@ -5274,7 +5320,7 @@ const buildBaseQueries = (networkId, balanceDefs, miniMetadata) => {
5274
5320
  useLegacyTransferableCalculation: miniMetadata.extra.useLegacyTransferableCalculation
5275
5321
  };
5276
5322
  let nomPoolMemberInfo = null;
5277
- const [accountChange, lockChange, freezesChange, holdsChange, stakingLedgerChange, nomPoolMemberChange] = changes;
5323
+ const [accountChange, lockChange, freezesChange, holdsChange, stakingLedgerChange, nomPoolMemberChange, evmMirrorAccountChange] = changes;
5278
5324
  if (networkStorageCoders.account) {
5279
5325
  const baseValues = decodeBaseResult(networkStorageCoders.account, accountChange, networkId);
5280
5326
  balance.values.push(...baseValues);
@@ -5299,6 +5345,10 @@ const buildBaseQueries = (networkId, balanceDefs, miniMetadata) => {
5299
5345
  const nomPoolMemberValue = decodePoolMemberResult(networkStorageCoders.poolMembers, nomPoolMemberChange, networkId);
5300
5346
  if (nomPoolMemberValue) nomPoolMemberInfo = nomPoolMemberValue;
5301
5347
  }
5348
+ if (networkStorageCoders.account && evmMirror && evmMirrorAccountChange) {
5349
+ const evmMirrorValues = decodeEvmMirrorResult(networkStorageCoders.account, evmMirrorAccountChange, evmMirror, networkId);
5350
+ balance.values.push(...evmMirrorValues);
5351
+ }
5302
5352
  return {
5303
5353
  balance,
5304
5354
  nomPoolMemberInfo
@@ -5336,6 +5386,35 @@ const decodeBaseResult = (coder, value, networkId) => {
5336
5386
  }
5337
5387
  ];
5338
5388
  };
5389
+ const getEvmMirror = (address) => {
5390
+ try {
5391
+ return {
5392
+ type: "evm-mirror-withdrawable",
5393
+ h160: (0, _talismn_crypto.frontierSs58ToTruncatedH160)(address)
5394
+ };
5395
+ } catch {
5396
+ return null;
5397
+ }
5398
+ };
5399
+ const decodeEvmMirrorResult = (coder, value, meta, networkId) => {
5400
+ const free = (0, _talismn_scale.decodeScale)(coder, value, `Failed to decode EVM mirror balance on chain ${networkId}`)?.data?.free ?? 0n;
5401
+ if (free <= 0n) return [];
5402
+ const withdrawable = {
5403
+ source: "substrate-native-evm-mirror",
5404
+ label: EVM_MIRROR_LOCK_LABEL,
5405
+ amount: free.toString(),
5406
+ meta
5407
+ };
5408
+ return [{
5409
+ ...withdrawable,
5410
+ type: "locked",
5411
+ includeInTransferable: true
5412
+ }, {
5413
+ ...withdrawable,
5414
+ type: "extra",
5415
+ includeInTotal: true
5416
+ }];
5417
+ };
5339
5418
  const decodeLocksResult = (coder, value, networkId) => {
5340
5419
  return (0, _talismn_scale.decodeScale)(coder, value, `Failed to decode lock on chain ${networkId}`)?.map?.((lock) => {
5341
5420
  const id = lock?.id ? _polkadot_api_substrate_bindings.Binary.toText(_polkadot_api_substrate_bindings.Binary.fromHex(lock.id)) : void 0;
@@ -5503,7 +5582,7 @@ const getNomPoolStateKeys = (coders, nomPoolMemberInfo, extra) => {
5503
5582
  };
5504
5583
  //#endregion
5505
5584
  //#region src/modules/substrate-native/fetchBalances.ts
5506
- const fetchBalances$2 = async ({ networkId, tokensWithAddresses, connector, miniMetadata }) => {
5585
+ const fetchBalances$2 = async ({ networkId, tokensWithAddresses, connector, miniMetadata, hasFrontierMirrorWithdraw }) => {
5507
5586
  if (!tokensWithAddresses.length) return {
5508
5587
  success: [],
5509
5588
  errors: []
@@ -5542,7 +5621,7 @@ const fetchBalances$2 = async ({ networkId, tokensWithAddresses, connector, mini
5542
5621
  }))
5543
5622
  };
5544
5623
  }
5545
- const baseQueries = buildBaseQueries(networkId, balanceDefs, miniMetadata);
5624
+ const baseQueries = buildBaseQueries(networkId, balanceDefs, miniMetadata, hasFrontierMirrorWithdraw);
5546
5625
  const partialBalances = await fetchRpcQueryPack(connector, networkId, baseQueries);
5547
5626
  const nomPoolQueries = buildNomPoolQueries(networkId, partialBalances, miniMetadata);
5548
5627
  return {
@@ -5594,14 +5673,15 @@ const getChainProperties = async (connector, networkId) => {
5594
5673
  const getMiniMetadata$2 = ({ networkId, specVersion, metadataRpc, config }) => {
5595
5674
  const source = MODULE_TYPE$2;
5596
5675
  const chainId = networkId;
5597
- const systemVersion = getConstantValue(metadataRpc, "System", "Version");
5676
+ const parsed = (0, _talismn_scale.parseMetadataRpc)(metadataRpc);
5677
+ const { metadata, unifiedMetadata } = parsed;
5678
+ const systemVersion = getConstantValue(parsed, "System", "Version");
5598
5679
  if (specVersion !== systemVersion.spec_version) throw new Error(`specVersion mismatch: expected ${specVersion}, metadata got ${systemVersion.spec_version}`);
5599
5680
  const id = deriveMiniMetadataId({
5600
5681
  source,
5601
5682
  chainId,
5602
5683
  specVersion
5603
5684
  });
5604
- const { metadata, unifiedMetadata } = (0, _talismn_scale.parseMetadataRpc)(metadataRpc);
5605
5685
  if (unifiedMetadata.version < 14) throw new Error(`Unsupported metadata version: ${unifiedMetadata.version}. Minimum required is 14.`);
5606
5686
  if (config?.disable) return {
5607
5687
  id,
@@ -5612,8 +5692,8 @@ const getMiniMetadata$2 = ({ networkId, specVersion, metadataRpc, config }) => {
5612
5692
  data: null,
5613
5693
  extra: { disable: true }
5614
5694
  };
5615
- const existentialDeposit = tryGetConstantValue(metadataRpc, "Balances", "ExistentialDeposit")?.toString();
5616
- const nominationPoolsPalletIdHex = tryGetConstantValue(metadataRpc, "NominationPools", "PalletId");
5695
+ const existentialDeposit = tryGetConstantValue(parsed, "Balances", "ExistentialDeposit")?.toString();
5696
+ const nominationPoolsPalletIdHex = tryGetConstantValue(parsed, "NominationPools", "PalletId");
5617
5697
  const nominationPoolsPalletId = nominationPoolsPalletIdHex ? _polkadot_api_substrate_bindings.Binary.toText(_polkadot_api_substrate_bindings.Binary.fromHex(nominationPoolsPalletIdHex)) : void 0;
5618
5698
  const useLegacyTransferableCalculation = !Boolean(unifiedMetadata.pallets.find(({ name }) => name === "Balances")?.storage?.items.find(({ name }) => name === "Freezes"));
5619
5699
  (0, _talismn_scale.compactMetadata)(metadata, [
@@ -5724,7 +5804,7 @@ const getTransferAllEncodedArgs = (to, codec) => {
5724
5804
  };
5725
5805
  //#endregion
5726
5806
  //#region src/modules/substrate-native/subscribeBalances.ts
5727
- const subscribeBalances$2 = ({ networkId, tokensWithAddresses, connector, miniMetadata }) => {
5807
+ const subscribeBalances$2 = ({ networkId, tokensWithAddresses, connector, miniMetadata, hasFrontierMirrorWithdraw }) => {
5728
5808
  if (!tokensWithAddresses.length) return (0, rxjs.of)({
5729
5809
  success: [],
5730
5810
  errors: []
@@ -5732,7 +5812,7 @@ const subscribeBalances$2 = ({ networkId, tokensWithAddresses, connector, miniMe
5732
5812
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
5733
5813
  return (0, rxjs.defer)(async () => {
5734
5814
  await (0, _talismn_util.yieldToEventLoop)();
5735
- return buildBaseQueries(networkId, balanceDefs, miniMetadata);
5815
+ return buildBaseQueries(networkId, balanceDefs, miniMetadata, hasFrontierMirrorWithdraw);
5736
5816
  }).pipe((0, rxjs.switchMap)((baseQueries) => getRpcQueryPack$(connector, networkId, baseQueries)), (0, rxjs.switchMap)((partialBalances) => {
5737
5817
  const nomPoolQueries = buildNomPoolQueries(networkId, partialBalances, miniMetadata);
5738
5818
  return getRpcQueryPack$(connector, networkId, nomPoolQueries);
@@ -6778,6 +6858,8 @@ const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, sp
6778
6858
  * Kept tight — a real incoming transfer above 0.1% of the position emits immediately.
6779
6859
  */
6780
6860
  const GENERIC_DRIFT_TOLERANCE_BPS = 10n;
6861
+ /** Delay before resubscribing a substrate module stream that errored: 2s, doubling, capped at 1min */
6862
+ const substrateModuleRetryDelay = (retryCount) => (0, rxjs.timer)(Math.min(6e4, 2e3 * 2 ** (retryCount - 1)));
6781
6863
  const DEFAULT_STORAGE = {
6782
6864
  balances: [],
6783
6865
  miniMetadatas: []
@@ -6859,7 +6941,7 @@ var BalancesProvider = class {
6859
6941
  switch (mod.platform) {
6860
6942
  case "ethereum": return this.getEthereumNetworkModuleBalances$(networkId, tokensWithAddresses, mod);
6861
6943
  case "solana": return this.getSolanaNetworkModuleBalances$(networkId, tokensWithAddresses, mod);
6862
- case "polkadot": return this.getPolkadotNetworkModuleBalances$(networkId, tokensWithAddresses, mod);
6944
+ case "polkadot": return this.getPolkadotNetworkModuleBalances$(networkId, tokensWithAddresses, mod, (0, _talismn_chaindata_provider.isNetworkDot)(network) && !!network.hasFrontierMirrorWithdraw);
6863
6945
  default:
6864
6946
  log_default.warn("[balances] Unsupported network platform for module", {
6865
6947
  networkId,
@@ -6884,11 +6966,12 @@ var BalancesProvider = class {
6884
6966
  };
6885
6967
  }, { label: `network balances ${networkId}` }), (0, rxjs.distinctUntilChanged)(isEqualBalancesResult));
6886
6968
  }
6887
- getPolkadotNetworkModuleBalances$(networkId, tokensWithAddresses, mod) {
6969
+ getPolkadotNetworkModuleBalances$(networkId, tokensWithAddresses, mod, hasFrontierMirrorWithdraw) {
6888
6970
  return (0, _talismn_util.getSharedObservable)(`BalancesProvider.getPolkadotNetworkModuleBalances$`, {
6889
6971
  networkId,
6890
6972
  mod,
6891
- tokensWithAddresses
6973
+ tokensWithAddresses,
6974
+ hasFrontierMirrorWithdraw
6892
6975
  }, () => {
6893
6976
  if (!tokensWithAddresses.length) return (0, rxjs.of)({
6894
6977
  status: "live",
@@ -6912,8 +6995,12 @@ var BalancesProvider = class {
6912
6995
  networkId,
6913
6996
  tokensWithAddresses,
6914
6997
  connector: this.#chainConnectors.substrate,
6915
- miniMetadata
6916
- })), stabilizeModuleResults(mod.type === "substrate-dtao" ? void 0 : classifySmallAmountDrift(GENERIC_DRIFT_TOLERANCE_BPS)), (0, rxjs.catchError)(() => rxjs.EMPTY), (0, rxjs.tap)((results) => {
6998
+ miniMetadata,
6999
+ hasFrontierMirrorWithdraw
7000
+ })), stabilizeModuleResults(mod.type === "substrate-dtao" ? void 0 : classifySmallAmountDrift(GENERIC_DRIFT_TOLERANCE_BPS)), (0, rxjs.retry)({
7001
+ delay: (_error, retryCount) => substrateModuleRetryDelay(retryCount),
7002
+ resetOnSuccess: true
7003
+ }), (0, rxjs.tap)((results) => {
6917
7004
  (0, _talismn_util.reportJsActivity)(`module ${mod.type} ${networkId} (${results.success.length})`);
6918
7005
  if (results.dynamicTokens?.length) this.#chaindataProvider.registerDynamicTokens(results.dynamicTokens);
6919
7006
  }), (0, rxjs.map)((results) => ({
@@ -7268,6 +7355,7 @@ exports.BalanceValueGetter = BalanceValueGetter;
7268
7355
  exports.Balances = Balances;
7269
7356
  exports.BalancesProvider = BalancesProvider;
7270
7357
  exports.Change24hCurrencyFormatter = Change24hCurrencyFormatter;
7358
+ exports.EVM_MIRROR_LOCK_LABEL = EVM_MIRROR_LOCK_LABEL;
7271
7359
  exports.EvmErc20BalanceModule = EvmErc20BalanceModule;
7272
7360
  exports.EvmErc20TokenConfigSchema = EvmErc20TokenConfigSchema;
7273
7361
  exports.EvmNativeBalanceModule = EvmNativeBalanceModule;
@@ -7320,6 +7408,7 @@ exports.filterMirrorTokens = filterMirrorTokens;
7320
7408
  exports.findDTaoClaimablePlancks = findDTaoClaimablePlancks;
7321
7409
  exports.findDTaoConvictionLock = findDTaoConvictionLock;
7322
7410
  exports.findDTaoRootStakeHold = findDTaoRootStakeHold;
7411
+ exports.findEvmMirrorWithdrawable = findEvmMirrorWithdrawable;
7323
7412
  exports.getBalanceFingerprint = getBalanceFingerprint;
7324
7413
  exports.getBalanceId = getBalanceId;
7325
7414
  exports.getBalanceStorageFingerprint = getBalanceStorageFingerprint;
@@ -7343,6 +7432,7 @@ exports.isEqualBalanceArrays = isEqualBalanceArrays;
7343
7432
  exports.isEqualBalancesResult = isEqualBalancesResult;
7344
7433
  exports.isEqualMiniMetadatas = isEqualMiniMetadatas;
7345
7434
  exports.isEqualModuleResults = isEqualModuleResults;
7435
+ exports.isEvmMirrorWithdrawableLock = isEvmMirrorWithdrawableLock;
7346
7436
  exports.isNonTransferable = isNonTransferable;
7347
7437
  exports.taoToAlpha = taoToAlpha;
7348
7438
  exports.taoToAlphaCeil = taoToAlphaCeil;