@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.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import BigNumber from "bignumber.js";
2
2
  import { EvmErc20TokenSchema, EvmNativeTokenSchema, EvmUniswapV2TokenSchema, MINIMETADATA_VERSION, MINIMETADATA_VERSION as MINIMETADATA_VERSION$1, SolNativeTokenSchema, SolSplTokenSchema, SolToken2022TokenSchema, SubAssetsTokenSchema, SubDTaoTokenSchema, SubForeignAssetsTokenSchema, SubHydrationTokenSchema, SubNativeTokenSchema, SubPsp22TokenSchema, SubTokensTokenSchema, TokenBaseSchema, TokenSchema, evmErc20TokenId, evmNativeTokenId, evmUniswapV2TokenId, getCleanToken, isAccountPlatformCompatibleWithNetwork, isNetworkDot, isTokenOfType, parseEvmErc20TokenId, parseSolSplTokenId, parseSolToken2022TokenId, parseSubDTaoTokenId, parseTokenId, solNativeTokenId, solSplTokenId, solToken2022TokenId, subAssetTokenId, subDTaoTokenId, subForeignAssetTokenId, subHydrationTokenId, subNativeTokenId, subPsp22TokenId, subTokensTokenId } from "@talismn/chaindata-provider";
3
- import { getAccountPlatformFromAddress, isAddressEqual, isEthereumAddress, isOnCurveSolanaAddress, isSolanaAddress, normalizeAddress } from "@talismn/crypto";
3
+ import { frontierH160ToSs58Mirror, frontierSs58ToTruncatedH160, getAccountPlatformFromAddress, isAddressEqual, isEthereumAddress, isOnCurveSolanaAddress, isSolanaAddress, normalizeAddress } from "@talismn/crypto";
4
4
  import { BigMath, LruMap, createTimeSlicer, forEachWithYield, getErrorMessage, getSharedObservable, isAbortError, isArrayOf, isBigInt, isErrorOfName, isNotNil, isTruthy, keepAlive, mapWithYield, planckToTokens, reportJsActivity, switchMapChunked, yieldToEventLoop } from "@talismn/util";
5
5
  import { assign, fromPairs, isEqual, keyBy, keys, mergeWith, omit, toPairs, uniq, values } from "lodash-es";
6
- import { BehaviorSubject, EMPTY, Observable, ReplaySubject, auditTime, catchError, combineLatest, defer, distinctUntilChanged, filter, firstValueFrom, from, map, of, shareReplay, startWith, switchMap, tap, timer } from "rxjs";
6
+ import { BehaviorSubject, EMPTY, Observable, ReplaySubject, auditTime, catchError, combineLatest, defer, distinctUntilChanged, filter, firstValueFrom, from, map, of, retry, shareReplay, startWith, switchMap, tap, timer } from "rxjs";
7
7
  import { encodeFunctionData, erc20Abi, erc20Abi_bytes32, getContract, hexToString, parseAbi, withRetry } from "viem";
8
8
  import { newTokenRates } from "@talismn/token-rates";
9
9
  import anylogger from "anylogger";
@@ -18,9 +18,9 @@ import { publicKey, sol } from "@metaplex-foundation/umi";
18
18
  import { TOKEN_2022_PROGRAM_ADDRESS, fetchMint, getMintDecoder as getMintDecoder$1, getTransferCheckedInstruction, getTransferCheckedWithFeeInstruction } from "@solana-program/token-2022";
19
19
  import { AccountId, Binary, Enum, getSs58AddressInfo } from "@polkadot-api/substrate-bindings";
20
20
  import { fromHex, mergeUint8, toHex as toHex$1 } from "@polkadot-api/utils";
21
+ import { fetchBestMetadata, fetchStorageKeysPaged } from "@talismn/sapi";
21
22
  import upperFirst from "lodash-es/upperFirst";
22
23
  import { Bytes, Enum as Enum$1, Result, Struct, compact, enhanceCodec, u128, u32 } from "scale-ts";
23
- import { fetchBestMetadata } from "@talismn/sapi";
24
24
  //#region src/configureBigNumber.ts
25
25
  BigNumber.set({ STRICT: false });
26
26
  var configureBigNumber_default = BigNumber;
@@ -415,7 +415,7 @@ var Balance = class {
415
415
  }
416
416
  /** The frozen balance of this token. Is included in the free amount. */
417
417
  get locked() {
418
- if (this.#cachedLocked === void 0) this.#cachedLocked = this.#format(this.locks.map(({ amount }) => amount.planck).reduce((a, b) => BigMath.max(a, b), 0n));
418
+ if (this.#cachedLocked === void 0) this.#cachedLocked = this.#format(excludeFromTransferableAmount(this.locks));
419
419
  return this.#cachedLocked;
420
420
  }
421
421
  get locks() {
@@ -484,7 +484,8 @@ var Balance = class {
484
484
  const newCalculation = () => BigMath.max(this.locked.planck, this.reserved.planck);
485
485
  const baseUnavailable = this.#storage.useLegacyTransferableCalculation ? oldCalculation() : newCalculation();
486
486
  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);
487
- return this.#format(baseUnavailable + nomPoolStakedPlancks);
487
+ const extra = this.getValue("extra");
488
+ return this.#format(baseUnavailable + nomPoolStakedPlancks + includeInTotalExtraAmount(extra));
488
489
  };
489
490
  /** The feePayable balance of this token. Is generally the free amount - the feeFrozen amount. */
490
491
  get feePayable() {
@@ -1665,18 +1666,31 @@ const fetchRpcQueryPack = async (connector, networkId, queries, at) => {
1665
1666
  return decodeRpcQueryPackChunked(queries, new Map(result.changes), { label: `rpcQueryPack decode ${networkId}` });
1666
1667
  };
1667
1668
  /**
1669
+ * The connector reports a chain that has been unreachable for a while with a StaleRpcError,
1670
+ * but keeps the subscription registered and re-issues it when a socket reconnects (the
1671
+ * first notification after that is a full snapshot again). Matched by its `type` rather
1672
+ * than `instanceof`, so a second copy of @talismn/chain-connectors still matches.
1673
+ */
1674
+ const isStaleRpcError = (error) => typeof error === "object" && error !== null && error.type === "STALE_RPC_ERROR";
1675
+ /**
1668
1676
  * Wraps the raw state_subscribeStorage subscription and emits, for every storage change
1669
1677
  * callback, a SNAPSHOT of the accumulated changes keyed by state key.
1670
1678
  *
1671
1679
  * A snapshot (shallow copy of string refs, cheap) is required because decoding is chunked
1672
1680
  * and asynchronous: a later websocket callback must not mutate the map that an in-flight
1673
1681
  * decode is reading.
1682
+ *
1683
+ * A StaleRpcError does not end the stream. Erroring would unsubscribe, and with no
1684
+ * subscription holding the chain's socket, every later request (eg the dtao poll) would
1685
+ * open and close a socket of its own, starting from the network's first RPC each time.
1674
1686
  */
1675
1687
  const getRawStorageUpdates$ = (connector, networkId, allStateKeys, timeout) => new Observable((subscriber) => {
1676
1688
  const changesCache = /* @__PURE__ */ new Map();
1677
1689
  const promUnsub = connector.subscribe(networkId, "state_subscribeStorage", "state_storage", [allStateKeys], (error, result) => {
1678
- if (error) subscriber.error(error);
1679
- else if (result) {
1690
+ if (error) {
1691
+ if (isStaleRpcError(error)) log_default.warn(`Storage subscription stale on ${networkId}`);
1692
+ else subscriber.error(error);
1693
+ } else if (result) {
1680
1694
  for (const [stateKey, encodedResult] of result.changes) changesCache.set(stateKey, encodedResult);
1681
1695
  subscriber.next(new Map(changesCache));
1682
1696
  }
@@ -2562,15 +2576,16 @@ const hasRuntimeApi = (metadata, apiName, method) => {
2562
2576
  if (!api?.methods) return false;
2563
2577
  return api.methods.some((m) => m.name === method);
2564
2578
  };
2565
- const getConstantValue = (metadataRpc, pallet, constant) => {
2566
- const { unifiedMetadata, builder } = parseMetadataRpc(metadataRpc);
2579
+ const toParsedMetadata = (metadata) => typeof metadata === "string" ? parseMetadataRpc(metadata) : metadata;
2580
+ const getConstantValue = (metadata, pallet, constant) => {
2581
+ const { unifiedMetadata, builder } = toParsedMetadata(metadata);
2567
2582
  const codec = builder.buildConstant(pallet, constant);
2568
2583
  const encodedValue = unifiedMetadata.pallets.find(({ name }) => name === pallet)?.constants.find(({ name }) => name === constant)?.value;
2569
2584
  if (!encodedValue) throw new Error(`Constant ${pallet}.${constant} not found`);
2570
2585
  return codec.dec(encodedValue);
2571
2586
  };
2572
- const tryGetConstantValue = (metadataRpc, pallet, constant) => {
2573
- const { unifiedMetadata, builder } = parseMetadataRpc(metadataRpc);
2587
+ const tryGetConstantValue = (metadata, pallet, constant) => {
2588
+ const { unifiedMetadata, builder } = toParsedMetadata(metadata);
2574
2589
  const encodedValue = unifiedMetadata.pallets.find(({ name }) => name === pallet)?.constants.find(({ name }) => name === constant)?.value;
2575
2590
  if (!encodedValue) return null;
2576
2591
  return builder.buildConstant(pallet, constant).dec(encodedValue);
@@ -2704,7 +2719,7 @@ const fetchOnChainTokenData$1 = async (connector, tokenId) => {
2704
2719
  //#region src/modules/sol-spl/fetchBalances.ts
2705
2720
  const SPL_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
2706
2721
  const dynamicTokenMetadataCache$1 = new LruMap(1024);
2707
- const associatedTokenAccountCache = new LruMap(1024);
2722
+ const associatedTokenAccountCache$1 = new LruMap(1024);
2708
2723
  const fetchBalances$8 = async ({ networkId, tokensWithAddresses, connector }) => {
2709
2724
  if (!tokensWithAddresses.length) return {
2710
2725
  success: [],
@@ -2724,7 +2739,7 @@ const fetchBalances$8 = async ({ networkId, tokensWithAddresses, connector }) =>
2724
2739
  return {
2725
2740
  mintAddress,
2726
2741
  value,
2727
- isSpendable: value !== 0n && state !== "frozen" && d.pubkey === await getAssociatedTokenAccount(address$2, mintAddress)
2742
+ isSpendable: value !== 0n && state !== "frozen" && d.pubkey === await getAssociatedTokenAccount$1(address$2, mintAddress)
2728
2743
  };
2729
2744
  } catch {
2730
2745
  log_default.warn("Failed to parse token amount", {
@@ -2817,16 +2832,16 @@ const fetchBalances$8 = async ({ networkId, tokensWithAddresses, connector }) =>
2817
2832
  };
2818
2833
  };
2819
2834
  const getBalanceKey$1 = (tokenId, address) => `${tokenId}:${address}`;
2820
- const getAssociatedTokenAccount = async (owner, mint) => {
2835
+ const getAssociatedTokenAccount$1 = async (owner, mint) => {
2821
2836
  const key = `${owner}:${mint}`;
2822
- const cached = associatedTokenAccountCache.get(key);
2837
+ const cached = associatedTokenAccountCache$1.get(key);
2823
2838
  if (cached) return cached;
2824
2839
  const [associatedTokenAccount] = await findAssociatedTokenPda({
2825
2840
  mint: address(mint),
2826
2841
  owner: address(owner),
2827
2842
  tokenProgram: TOKEN_PROGRAM_ADDRESS
2828
2843
  });
2829
- associatedTokenAccountCache.set(key, associatedTokenAccount);
2844
+ associatedTokenAccountCache$1.set(key, associatedTokenAccount);
2830
2845
  return associatedTokenAccount;
2831
2846
  };
2832
2847
  //#endregion
@@ -3276,6 +3291,7 @@ const fetchOnChainTokenData = async (connector, tokenId) => {
3276
3291
  //#region src/modules/sol-token2022/fetchBalances.ts
3277
3292
  const TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
3278
3293
  const dynamicTokenMetadataCache = /* @__PURE__ */ new Map();
3294
+ const associatedTokenAccountCache = new LruMap(1024);
3279
3295
  const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) => {
3280
3296
  if (!tokensWithAddresses.length) return {
3281
3297
  success: [],
@@ -3287,19 +3303,15 @@ const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) =>
3287
3303
  const knownTokenIds = new Set(tokensWithAddresses.map(([token]) => token.id));
3288
3304
  const unknownMints = /* @__PURE__ */ new Set();
3289
3305
  const balancesPerAddress = await Promise.all(accountAddresses.map(async (address$1) => {
3290
- const balances = (await rpc.getTokenAccountsByOwner(address(address$1), { programId: address(TOKEN_2022_PROGRAM_ID) }, { encoding: "jsonParsed" }).send()).value.map((d) => {
3306
+ const tokenAccounts = await rpc.getTokenAccountsByOwner(address(address$1), { programId: address(TOKEN_2022_PROGRAM_ID) }, { encoding: "jsonParsed" }).send();
3307
+ const tokenAccountHoldings = await Promise.all(tokenAccounts.value.map(async (d) => {
3291
3308
  try {
3292
- const mintAddress = d.account.data.parsed.info.mint;
3293
- const value = d.account.data.parsed.info.tokenAmount.amount ?? "0";
3294
- const tokenId = solToken2022TokenId(networkId, mintAddress);
3295
- if (!knownTokenIds.has(tokenId) && value !== "0") unknownMints.add(mintAddress);
3309
+ const { mint: mintAddress, state, tokenAmount } = d.account.data.parsed.info;
3310
+ const value = BigInt(tokenAmount.amount ?? "0");
3296
3311
  return {
3297
- tokenId,
3298
- networkId,
3299
- address: address$1,
3300
- source: MODULE_TYPE$7,
3301
- status: "live",
3302
- value
3312
+ mintAddress,
3313
+ value,
3314
+ isSpendable: value !== 0n && state !== "frozen" && d.pubkey === await getAssociatedTokenAccount(address$1, mintAddress)
3303
3315
  };
3304
3316
  } catch {
3305
3317
  log_default.warn("Failed to parse token amount", {
@@ -3308,7 +3320,36 @@ const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) =>
3308
3320
  });
3309
3321
  return null;
3310
3322
  }
3311
- }).filter(isNotNil);
3323
+ }));
3324
+ const holdingsPerTokenId = /* @__PURE__ */ new Map();
3325
+ for (const { mintAddress, value, isSpendable } of tokenAccountHoldings.filter(isNotNil)) {
3326
+ const tokenId = solToken2022TokenId(networkId, mintAddress);
3327
+ if (!knownTokenIds.has(tokenId) && value !== 0n) unknownMints.add(mintAddress);
3328
+ const { total, spendable } = holdingsPerTokenId.get(tokenId) ?? {
3329
+ total: 0n,
3330
+ spendable: 0n
3331
+ };
3332
+ holdingsPerTokenId.set(tokenId, {
3333
+ total: total + value,
3334
+ spendable: spendable + (isSpendable ? value : 0n)
3335
+ });
3336
+ }
3337
+ const balances = [...holdingsPerTokenId].map(([tokenId, { total, spendable }]) => ({
3338
+ tokenId,
3339
+ networkId,
3340
+ address: address$1,
3341
+ source: MODULE_TYPE$7,
3342
+ status: "live",
3343
+ ...spendable === total ? { value: total.toString() } : { values: [{
3344
+ type: "free",
3345
+ label: "free",
3346
+ amount: total.toString()
3347
+ }, {
3348
+ type: "locked",
3349
+ label: "locked",
3350
+ amount: (total - spendable).toString()
3351
+ }] }
3352
+ }));
3312
3353
  setDetectedTokenIds(address$1, MODULE_TYPE$7, balances.map((b) => b.tokenId));
3313
3354
  return [address$1, balances];
3314
3355
  }));
@@ -3363,6 +3404,18 @@ const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) =>
3363
3404
  };
3364
3405
  };
3365
3406
  const getBalanceKey = (tokenId, address) => `${tokenId}:${address}`;
3407
+ const getAssociatedTokenAccount = async (owner, mint) => {
3408
+ const key = `${owner}:${mint}`;
3409
+ const cached = associatedTokenAccountCache.get(key);
3410
+ if (cached) return cached;
3411
+ const [associatedTokenAccount] = await findAssociatedTokenPda({
3412
+ mint: address(mint),
3413
+ owner: address(owner),
3414
+ tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
3415
+ });
3416
+ associatedTokenAccountCache.set(key, associatedTokenAccount);
3417
+ return associatedTokenAccount;
3418
+ };
3366
3419
  //#endregion
3367
3420
  //#region src/modules/sol-token2022/fetchTokens.ts
3368
3421
  const fetchTokens$7 = async ({ networkId, tokens, connector, cache }) => {
@@ -3620,14 +3673,15 @@ const deriveMiniMetadataId = ({ source, chainId, specVersion }) => toHex(Twox128
3620
3673
  const getMiniMetadata$6 = ({ networkId, specVersion, metadataRpc }) => {
3621
3674
  const source = MODULE_TYPE$6;
3622
3675
  const chainId = networkId;
3623
- const systemVersion = getConstantValue(metadataRpc, "System", "Version");
3676
+ const parsed = parseMetadataRpc(metadataRpc);
3677
+ const { unifiedMetadata } = parsed;
3678
+ const systemVersion = getConstantValue(parsed, "System", "Version");
3624
3679
  if (specVersion !== systemVersion.spec_version) throw new Error(`specVersion mismatch: expected ${specVersion}, metadata got ${systemVersion.spec_version}`);
3625
3680
  const id = deriveMiniMetadataId({
3626
3681
  source,
3627
3682
  chainId,
3628
3683
  specVersion
3629
3684
  });
3630
- const { unifiedMetadata } = parseMetadataRpc(metadataRpc);
3631
3685
  if (unifiedMetadata.version < 14) throw new Error(`Unsupported metadata version: ${unifiedMetadata.version}. Minimum required is 14.`);
3632
3686
  return {
3633
3687
  id,
@@ -3635,12 +3689,11 @@ const getMiniMetadata$6 = ({ networkId, specVersion, metadataRpc }) => {
3635
3689
  chainId,
3636
3690
  specVersion,
3637
3691
  version: MINIMETADATA_VERSION$1,
3638
- data: getData$4(metadataRpc),
3692
+ data: getData$4(parsed),
3639
3693
  extra: null
3640
3694
  };
3641
3695
  };
3642
- const getData$4 = (metadataRpc) => {
3643
- const { metadata, unifiedMetadata } = parseMetadataRpc(metadataRpc);
3696
+ const getData$4 = ({ metadata, unifiedMetadata }) => {
3644
3697
  if (!hasStorageItems(unifiedMetadata, "Assets", [
3645
3698
  "Account",
3646
3699
  "Asset",
@@ -3818,27 +3871,6 @@ const fetchBasketClaims = async (connector, networkId, metadataRpc, addresses, a
3818
3871
  }
3819
3872
  };
3820
3873
  //#endregion
3821
- //#region src/modules/substrate-dtao/fetchStorageKeysPaged.ts
3822
- const PAGE_SIZE = 1e3;
3823
- /**
3824
- * Prefix scan via state_getKeysPaged. The unpaged state_getKeys is classified as heavy storage
3825
- * work and rejected by some public nodes (eg the bittensor testnet one: "Storage work rate limit
3826
- * exceeded"), while the paged variant is universally allowed.
3827
- */
3828
- const fetchStorageKeysPaged = async (connector, networkId, keyPrefix, at) => {
3829
- const keys = [];
3830
- let startKey;
3831
- do {
3832
- const params = [keyPrefix, PAGE_SIZE];
3833
- if (startKey || at) params.push(startKey ?? null);
3834
- if (at) params.push(at);
3835
- const page = await connector.send(networkId, "state_getKeysPaged", params);
3836
- keys.push(...page);
3837
- startKey = page.length === PAGE_SIZE ? page[page.length - 1] : void 0;
3838
- } while (startKey);
3839
- return keys;
3840
- };
3841
- //#endregion
3842
3874
  //#region src/modules/substrate-dtao/convictionLocks.ts
3843
3875
  const convictionLockKey = (address, netuid) => `${address}:${netuid}`;
3844
3876
  const getConvictionLockLabel = (lockType) => lockType === "perpetual" ? "Perpetual Conviction Lock" : "Decaying Conviction Lock";
@@ -3935,7 +3967,7 @@ const fetchConvictionLockStorageKeys = async (connector, networkId, addresses, s
3935
3967
  }
3936
3968
  let stateKeys;
3937
3969
  try {
3938
- stateKeys = await fetchStorageKeysPaged(connector, networkId, keyPrefix, at);
3970
+ stateKeys = await fetchStorageKeysPaged((method, params) => connector.send(networkId, method, params), keyPrefix, { at });
3939
3971
  } catch (cause) {
3940
3972
  log_default.warn(`Failed to fetch conviction Lock keys (address=${address}) on ${networkId}`, { cause });
3941
3973
  throw cause;
@@ -4359,7 +4391,7 @@ const fetchTokens$5 = async ({ networkId, connector, tokens, miniMetadata }) =>
4359
4391
  const fetchTransferableTokensMap = async (connector, metadata, networkId) => {
4360
4392
  const { builder } = parseMetadataRpcCached(metadata);
4361
4393
  const transferToggleCodec = builder.buildStorage("SubtensorModule", "TransferToggle");
4362
- const transferToggleKeys = await fetchStorageKeysPaged(connector, networkId, getStorageKeyPrefix("SubtensorModule", "TransferToggle"));
4394
+ const transferToggleKeys = await fetchStorageKeysPaged((method, params) => connector.send(networkId, method, params), getStorageKeyPrefix("SubtensorModule", "TransferToggle"));
4363
4395
  const transferToggleResults = await connector.send(networkId, "state_queryStorageAt", [transferToggleKeys]);
4364
4396
  const transferToggleEntries = transferToggleResults.length ? transferToggleResults[0].changes : [];
4365
4397
  return fromPairs(transferToggleEntries.map(([key, value]) => {
@@ -4807,14 +4839,15 @@ const fetchTokens$4 = async ({ networkId, tokens, connector, miniMetadata }) =>
4807
4839
  const getMiniMetadata$4 = ({ networkId, specVersion, metadataRpc }) => {
4808
4840
  const source = MODULE_TYPE$4;
4809
4841
  const chainId = networkId;
4810
- const systemVersion = getConstantValue(metadataRpc, "System", "Version");
4842
+ const parsed = parseMetadataRpc(metadataRpc);
4843
+ const { unifiedMetadata } = parsed;
4844
+ const systemVersion = getConstantValue(parsed, "System", "Version");
4811
4845
  if (specVersion !== systemVersion.spec_version) throw new Error(`specVersion mismatch: expected ${specVersion}, metadata got ${systemVersion.spec_version}`);
4812
4846
  const id = deriveMiniMetadataId({
4813
4847
  source,
4814
4848
  chainId,
4815
4849
  specVersion
4816
4850
  });
4817
- const { unifiedMetadata } = parseMetadataRpc(metadataRpc);
4818
4851
  if (unifiedMetadata.version < 14) throw new Error(`Unsupported metadata version: ${unifiedMetadata.version}. Minimum required is 14.`);
4819
4852
  return {
4820
4853
  id,
@@ -4822,12 +4855,11 @@ const getMiniMetadata$4 = ({ networkId, specVersion, metadataRpc }) => {
4822
4855
  chainId,
4823
4856
  specVersion,
4824
4857
  version: MINIMETADATA_VERSION$1,
4825
- data: getData$2(metadataRpc),
4858
+ data: getData$2(parsed),
4826
4859
  extra: null
4827
4860
  };
4828
4861
  };
4829
- const getData$2 = (metadataRpc) => {
4830
- const { metadata, unifiedMetadata } = parseMetadataRpc(metadataRpc);
4862
+ const getData$2 = ({ metadata, unifiedMetadata }) => {
4831
4863
  if (!hasStorageItems(unifiedMetadata, "ForeignAssets", [
4832
4864
  "Account",
4833
4865
  "Asset",
@@ -5144,6 +5176,17 @@ const SubHydrationTokenConfigSchema = z.strictObject({
5144
5176
  ...TokenConfigBaseSchema.shape
5145
5177
  });
5146
5178
  //#endregion
5179
+ //#region src/modules/substrate-native/evmMirror.ts
5180
+ const EVM_MIRROR_LOCK_LABEL = "evm-mirror";
5181
+ const isEvmMirrorWithdrawableLock = (lock) => lock.meta?.type === "evm-mirror-withdrawable";
5182
+ const findEvmMirrorWithdrawable = (locks) => {
5183
+ const lock = locks?.find(isEvmMirrorWithdrawableLock);
5184
+ return lock ? {
5185
+ ...lock.meta,
5186
+ amount: lock.amount.planck
5187
+ } : null;
5188
+ };
5189
+ //#endregion
5147
5190
  //#region src/modules/substrate-native/config.ts
5148
5191
  const MODULE_TYPE$2 = SubNativeTokenSchema.shape.type.value;
5149
5192
  const PLATFORM$2 = SubNativeTokenSchema.shape.platform.value;
@@ -5189,7 +5232,7 @@ const baseLockLabels = ["fees", "misc"];
5189
5232
  const isBaseLock = (lock) => baseLockLabels.includes(lock.label);
5190
5233
  const isNonBaseLock = (lock) => !isBaseLock(lock);
5191
5234
  const filterBaseLocks = (locks) => {
5192
- if (!locks.some(isNonBaseLock)) return locks;
5235
+ if (!locks.some((lock) => isNonBaseLock(lock) && !lock.includeInTransferable)) return locks;
5193
5236
  return locks.filter(isNonBaseLock);
5194
5237
  };
5195
5238
  const getLockTitle = (lock, { balance } = {}) => {
@@ -5200,6 +5243,7 @@ const getLockTitle = (lock, { balance } = {}) => {
5200
5243
  if (lock.label === "nompools-unbonding") return "Pooled Staking";
5201
5244
  if (lock.label === "subtensor-staking") return "Root Staking";
5202
5245
  if (lock.label === "dapp-staking") return "DApp Staking";
5246
+ if (lock.label === "evm-mirror") return "EVM Balance";
5203
5247
  if (lock.label === "fees") return "Locked (Fees)";
5204
5248
  if (lock.label === "misc") return "Locked";
5205
5249
  if (lock.label.startsWith("other")) return "Locked";
@@ -5207,7 +5251,7 @@ const getLockTitle = (lock, { balance } = {}) => {
5207
5251
  };
5208
5252
  //#endregion
5209
5253
  //#region src/modules/substrate-native/queries/buildBaseQueries.ts
5210
- const buildBaseQueries = (networkId, balanceDefs, miniMetadata) => {
5254
+ const buildBaseQueries = (networkId, balanceDefs, miniMetadata, hasFrontierMirrorWithdraw = false) => {
5211
5255
  const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
5212
5256
  account: ["System", "Account"],
5213
5257
  stakingLedger: ["Staking", "Ledger"],
@@ -5219,6 +5263,7 @@ const buildBaseQueries = (networkId, balanceDefs, miniMetadata) => {
5219
5263
  });
5220
5264
  if (!networkStorageCoders) throw new Error(`No network storage coders found for networkId: ${networkId}`);
5221
5265
  return balanceDefs.map(({ token, address }) => {
5266
+ const evmMirror = hasFrontierMirrorWithdraw ? getEvmMirror(address) : null;
5222
5267
  const getStateKeys = () => {
5223
5268
  try {
5224
5269
  return [
@@ -5227,7 +5272,8 @@ const buildBaseQueries = (networkId, balanceDefs, miniMetadata) => {
5227
5272
  networkStorageCoders.freezes ? networkStorageCoders.freezes.keys.enc(address) : null,
5228
5273
  networkStorageCoders.holds ? networkStorageCoders.holds.keys.enc(address) : null,
5229
5274
  networkStorageCoders.stakingLedger ? networkStorageCoders.stakingLedger.keys.enc(address) : null,
5230
- networkStorageCoders.poolMembers ? networkStorageCoders.poolMembers.keys.enc(address) : null
5275
+ networkStorageCoders.poolMembers ? networkStorageCoders.poolMembers.keys.enc(address) : null,
5276
+ networkStorageCoders.account && evmMirror ? networkStorageCoders.account.keys.enc(frontierH160ToSs58Mirror(evmMirror.h160)) : null
5231
5277
  ];
5232
5278
  } catch {
5233
5279
  return [];
@@ -5246,7 +5292,7 @@ const buildBaseQueries = (networkId, balanceDefs, miniMetadata) => {
5246
5292
  useLegacyTransferableCalculation: miniMetadata.extra.useLegacyTransferableCalculation
5247
5293
  };
5248
5294
  let nomPoolMemberInfo = null;
5249
- const [accountChange, lockChange, freezesChange, holdsChange, stakingLedgerChange, nomPoolMemberChange] = changes;
5295
+ const [accountChange, lockChange, freezesChange, holdsChange, stakingLedgerChange, nomPoolMemberChange, evmMirrorAccountChange] = changes;
5250
5296
  if (networkStorageCoders.account) {
5251
5297
  const baseValues = decodeBaseResult(networkStorageCoders.account, accountChange, networkId);
5252
5298
  balance.values.push(...baseValues);
@@ -5271,6 +5317,10 @@ const buildBaseQueries = (networkId, balanceDefs, miniMetadata) => {
5271
5317
  const nomPoolMemberValue = decodePoolMemberResult(networkStorageCoders.poolMembers, nomPoolMemberChange, networkId);
5272
5318
  if (nomPoolMemberValue) nomPoolMemberInfo = nomPoolMemberValue;
5273
5319
  }
5320
+ if (networkStorageCoders.account && evmMirror && evmMirrorAccountChange) {
5321
+ const evmMirrorValues = decodeEvmMirrorResult(networkStorageCoders.account, evmMirrorAccountChange, evmMirror, networkId);
5322
+ balance.values.push(...evmMirrorValues);
5323
+ }
5274
5324
  return {
5275
5325
  balance,
5276
5326
  nomPoolMemberInfo
@@ -5308,6 +5358,35 @@ const decodeBaseResult = (coder, value, networkId) => {
5308
5358
  }
5309
5359
  ];
5310
5360
  };
5361
+ const getEvmMirror = (address) => {
5362
+ try {
5363
+ return {
5364
+ type: "evm-mirror-withdrawable",
5365
+ h160: frontierSs58ToTruncatedH160(address)
5366
+ };
5367
+ } catch {
5368
+ return null;
5369
+ }
5370
+ };
5371
+ const decodeEvmMirrorResult = (coder, value, meta, networkId) => {
5372
+ const free = decodeScale(coder, value, `Failed to decode EVM mirror balance on chain ${networkId}`)?.data?.free ?? 0n;
5373
+ if (free <= 0n) return [];
5374
+ const withdrawable = {
5375
+ source: "substrate-native-evm-mirror",
5376
+ label: EVM_MIRROR_LOCK_LABEL,
5377
+ amount: free.toString(),
5378
+ meta
5379
+ };
5380
+ return [{
5381
+ ...withdrawable,
5382
+ type: "locked",
5383
+ includeInTransferable: true
5384
+ }, {
5385
+ ...withdrawable,
5386
+ type: "extra",
5387
+ includeInTotal: true
5388
+ }];
5389
+ };
5311
5390
  const decodeLocksResult = (coder, value, networkId) => {
5312
5391
  return decodeScale(coder, value, `Failed to decode lock on chain ${networkId}`)?.map?.((lock) => {
5313
5392
  const id = lock?.id ? Binary.toText(Binary.fromHex(lock.id)) : void 0;
@@ -5475,7 +5554,7 @@ const getNomPoolStateKeys = (coders, nomPoolMemberInfo, extra) => {
5475
5554
  };
5476
5555
  //#endregion
5477
5556
  //#region src/modules/substrate-native/fetchBalances.ts
5478
- const fetchBalances$2 = async ({ networkId, tokensWithAddresses, connector, miniMetadata }) => {
5557
+ const fetchBalances$2 = async ({ networkId, tokensWithAddresses, connector, miniMetadata, hasFrontierMirrorWithdraw }) => {
5479
5558
  if (!tokensWithAddresses.length) return {
5480
5559
  success: [],
5481
5560
  errors: []
@@ -5514,7 +5593,7 @@ const fetchBalances$2 = async ({ networkId, tokensWithAddresses, connector, mini
5514
5593
  }))
5515
5594
  };
5516
5595
  }
5517
- const baseQueries = buildBaseQueries(networkId, balanceDefs, miniMetadata);
5596
+ const baseQueries = buildBaseQueries(networkId, balanceDefs, miniMetadata, hasFrontierMirrorWithdraw);
5518
5597
  const partialBalances = await fetchRpcQueryPack(connector, networkId, baseQueries);
5519
5598
  const nomPoolQueries = buildNomPoolQueries(networkId, partialBalances, miniMetadata);
5520
5599
  return {
@@ -5566,14 +5645,15 @@ const getChainProperties = async (connector, networkId) => {
5566
5645
  const getMiniMetadata$2 = ({ networkId, specVersion, metadataRpc, config }) => {
5567
5646
  const source = MODULE_TYPE$2;
5568
5647
  const chainId = networkId;
5569
- const systemVersion = getConstantValue(metadataRpc, "System", "Version");
5648
+ const parsed = parseMetadataRpc(metadataRpc);
5649
+ const { metadata, unifiedMetadata } = parsed;
5650
+ const systemVersion = getConstantValue(parsed, "System", "Version");
5570
5651
  if (specVersion !== systemVersion.spec_version) throw new Error(`specVersion mismatch: expected ${specVersion}, metadata got ${systemVersion.spec_version}`);
5571
5652
  const id = deriveMiniMetadataId({
5572
5653
  source,
5573
5654
  chainId,
5574
5655
  specVersion
5575
5656
  });
5576
- const { metadata, unifiedMetadata } = parseMetadataRpc(metadataRpc);
5577
5657
  if (unifiedMetadata.version < 14) throw new Error(`Unsupported metadata version: ${unifiedMetadata.version}. Minimum required is 14.`);
5578
5658
  if (config?.disable) return {
5579
5659
  id,
@@ -5584,8 +5664,8 @@ const getMiniMetadata$2 = ({ networkId, specVersion, metadataRpc, config }) => {
5584
5664
  data: null,
5585
5665
  extra: { disable: true }
5586
5666
  };
5587
- const existentialDeposit = tryGetConstantValue(metadataRpc, "Balances", "ExistentialDeposit")?.toString();
5588
- const nominationPoolsPalletIdHex = tryGetConstantValue(metadataRpc, "NominationPools", "PalletId");
5667
+ const existentialDeposit = tryGetConstantValue(parsed, "Balances", "ExistentialDeposit")?.toString();
5668
+ const nominationPoolsPalletIdHex = tryGetConstantValue(parsed, "NominationPools", "PalletId");
5589
5669
  const nominationPoolsPalletId = nominationPoolsPalletIdHex ? Binary.toText(Binary.fromHex(nominationPoolsPalletIdHex)) : void 0;
5590
5670
  const useLegacyTransferableCalculation = !Boolean(unifiedMetadata.pallets.find(({ name }) => name === "Balances")?.storage?.items.find(({ name }) => name === "Freezes"));
5591
5671
  compactMetadata(metadata, [
@@ -5696,7 +5776,7 @@ const getTransferAllEncodedArgs = (to, codec) => {
5696
5776
  };
5697
5777
  //#endregion
5698
5778
  //#region src/modules/substrate-native/subscribeBalances.ts
5699
- const subscribeBalances$2 = ({ networkId, tokensWithAddresses, connector, miniMetadata }) => {
5779
+ const subscribeBalances$2 = ({ networkId, tokensWithAddresses, connector, miniMetadata, hasFrontierMirrorWithdraw }) => {
5700
5780
  if (!tokensWithAddresses.length) return of({
5701
5781
  success: [],
5702
5782
  errors: []
@@ -5704,7 +5784,7 @@ const subscribeBalances$2 = ({ networkId, tokensWithAddresses, connector, miniMe
5704
5784
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
5705
5785
  return defer(async () => {
5706
5786
  await yieldToEventLoop();
5707
- return buildBaseQueries(networkId, balanceDefs, miniMetadata);
5787
+ return buildBaseQueries(networkId, balanceDefs, miniMetadata, hasFrontierMirrorWithdraw);
5708
5788
  }).pipe(switchMap((baseQueries) => getRpcQueryPack$(connector, networkId, baseQueries)), switchMap((partialBalances) => {
5709
5789
  const nomPoolQueries = buildNomPoolQueries(networkId, partialBalances, miniMetadata);
5710
5790
  return getRpcQueryPack$(connector, networkId, nomPoolQueries);
@@ -6750,6 +6830,8 @@ const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, sp
6750
6830
  * Kept tight — a real incoming transfer above 0.1% of the position emits immediately.
6751
6831
  */
6752
6832
  const GENERIC_DRIFT_TOLERANCE_BPS = 10n;
6833
+ /** Delay before resubscribing a substrate module stream that errored: 2s, doubling, capped at 1min */
6834
+ const substrateModuleRetryDelay = (retryCount) => timer(Math.min(6e4, 2e3 * 2 ** (retryCount - 1)));
6753
6835
  const DEFAULT_STORAGE = {
6754
6836
  balances: [],
6755
6837
  miniMetadatas: []
@@ -6831,7 +6913,7 @@ var BalancesProvider = class {
6831
6913
  switch (mod.platform) {
6832
6914
  case "ethereum": return this.getEthereumNetworkModuleBalances$(networkId, tokensWithAddresses, mod);
6833
6915
  case "solana": return this.getSolanaNetworkModuleBalances$(networkId, tokensWithAddresses, mod);
6834
- case "polkadot": return this.getPolkadotNetworkModuleBalances$(networkId, tokensWithAddresses, mod);
6916
+ case "polkadot": return this.getPolkadotNetworkModuleBalances$(networkId, tokensWithAddresses, mod, isNetworkDot(network) && !!network.hasFrontierMirrorWithdraw);
6835
6917
  default:
6836
6918
  log_default.warn("[balances] Unsupported network platform for module", {
6837
6919
  networkId,
@@ -6856,11 +6938,12 @@ var BalancesProvider = class {
6856
6938
  };
6857
6939
  }, { label: `network balances ${networkId}` }), distinctUntilChanged(isEqualBalancesResult));
6858
6940
  }
6859
- getPolkadotNetworkModuleBalances$(networkId, tokensWithAddresses, mod) {
6941
+ getPolkadotNetworkModuleBalances$(networkId, tokensWithAddresses, mod, hasFrontierMirrorWithdraw) {
6860
6942
  return getSharedObservable(`BalancesProvider.getPolkadotNetworkModuleBalances$`, {
6861
6943
  networkId,
6862
6944
  mod,
6863
- tokensWithAddresses
6945
+ tokensWithAddresses,
6946
+ hasFrontierMirrorWithdraw
6864
6947
  }, () => {
6865
6948
  if (!tokensWithAddresses.length) return of({
6866
6949
  status: "live",
@@ -6884,8 +6967,12 @@ var BalancesProvider = class {
6884
6967
  networkId,
6885
6968
  tokensWithAddresses,
6886
6969
  connector: this.#chainConnectors.substrate,
6887
- miniMetadata
6888
- })), stabilizeModuleResults(mod.type === "substrate-dtao" ? void 0 : classifySmallAmountDrift(GENERIC_DRIFT_TOLERANCE_BPS)), catchError(() => EMPTY), tap((results) => {
6970
+ miniMetadata,
6971
+ hasFrontierMirrorWithdraw
6972
+ })), stabilizeModuleResults(mod.type === "substrate-dtao" ? void 0 : classifySmallAmountDrift(GENERIC_DRIFT_TOLERANCE_BPS)), retry({
6973
+ delay: (_error, retryCount) => substrateModuleRetryDelay(retryCount),
6974
+ resetOnSuccess: true
6975
+ }), tap((results) => {
6889
6976
  reportJsActivity(`module ${mod.type} ${networkId} (${results.success.length})`);
6890
6977
  if (results.dynamicTokens?.length) this.#chaindataProvider.registerDynamicTokens(results.dynamicTokens);
6891
6978
  }), map((results) => ({
@@ -7232,6 +7319,6 @@ const mergeSortedBalanceArrays = async (arrays, slicer) => {
7232
7319
  };
7233
7320
  const sortByMiniMetadataId = (a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
7234
7321
  //#endregion
7235
- export { ALPHA_PRICE_SCALE, BALANCE_MODULES, Balance, BalanceFormatter, BalanceValueGetter, Balances, BalancesProvider, Change24hCurrencyFormatter, EvmErc20BalanceModule, EvmErc20TokenConfigSchema, EvmNativeBalanceModule, EvmNativeTokenConfigSchema, EvmUniswapV2BalanceModule, EvmUniswapV2TokenConfigSchema, FiatSumBalancesFormatter, MINIMETADATA_VERSION, PlanckSumBalancesFormatter, SolNativeBalanceModule, SolNativeTokenConfigSchema, SolSplBalanceModule, SolSplTokenConfigSchema, SolToken2022BalanceModule, SolToken2022TokenConfigSchema, SubAssetsBalanceModule, SubAssetsTokenConfigSchema, SubDTaoBalanceModule, SubDTaoTokenConfigSchema, SubForeignAssetsBalanceModule, SubForeignAssetsTokenConfigSchema, SubHydrationBalanceModule, SubHydrationTokenConfigSchema, SubNativeBalanceModule, SubNativeMiniMetadataExtraSchema, SubNativeModuleConfigSchema, SubNativeTokenConfigSchema, SubPsp22BalanceModule, SubPsp22TokenConfigSchema, SubTokensBalanceModule, SubTokensMiniMetadataExtraSchema, SubTokensModuleConfigSchema, SubTokensTokenConfigSchema, SumBalancesFormatter, TAO_DECIMALS, abiMulticall, alphaToTao, calculateToken2022TransferFee, deriveMiniMetadataId, erc20BalancesAggregatorAbi, excludeFromFeePayableLocks, excludeFromTransferableAmount, filterBaseLocks, filterMirrorTokens, findDTaoClaimablePlancks, findDTaoConvictionLock, findDTaoRootStakeHold, getBalanceFingerprint, getBalanceId, getBalanceStorageFingerprint, getConvictionLockLabel, getDTaoClaimablePlancks, getEpochTransferFee, getExtension, getLockTitle, getLockedType, getMintExtensions, getRawLocks, getRawTotalPlanck, getSweepStaleVariant, getTokenMetadata, getTransferFeeConfig, getTransferHook, getValueId, includeInTotalExtraAmount, isDTaoClaimableLock, isEqualBalanceArrays, isEqualBalancesResult, isEqualMiniMetadatas, isEqualModuleResults, isNonTransferable, taoToAlpha, taoToAlphaCeil, uniswapV2PairAbi };
7322
+ export { ALPHA_PRICE_SCALE, BALANCE_MODULES, Balance, BalanceFormatter, BalanceValueGetter, Balances, BalancesProvider, Change24hCurrencyFormatter, EVM_MIRROR_LOCK_LABEL, EvmErc20BalanceModule, EvmErc20TokenConfigSchema, EvmNativeBalanceModule, EvmNativeTokenConfigSchema, EvmUniswapV2BalanceModule, EvmUniswapV2TokenConfigSchema, FiatSumBalancesFormatter, MINIMETADATA_VERSION, PlanckSumBalancesFormatter, SolNativeBalanceModule, SolNativeTokenConfigSchema, SolSplBalanceModule, SolSplTokenConfigSchema, SolToken2022BalanceModule, SolToken2022TokenConfigSchema, SubAssetsBalanceModule, SubAssetsTokenConfigSchema, SubDTaoBalanceModule, SubDTaoTokenConfigSchema, SubForeignAssetsBalanceModule, SubForeignAssetsTokenConfigSchema, SubHydrationBalanceModule, SubHydrationTokenConfigSchema, SubNativeBalanceModule, SubNativeMiniMetadataExtraSchema, SubNativeModuleConfigSchema, SubNativeTokenConfigSchema, SubPsp22BalanceModule, SubPsp22TokenConfigSchema, SubTokensBalanceModule, SubTokensMiniMetadataExtraSchema, SubTokensModuleConfigSchema, SubTokensTokenConfigSchema, SumBalancesFormatter, TAO_DECIMALS, abiMulticall, alphaToTao, calculateToken2022TransferFee, deriveMiniMetadataId, erc20BalancesAggregatorAbi, excludeFromFeePayableLocks, excludeFromTransferableAmount, filterBaseLocks, filterMirrorTokens, findDTaoClaimablePlancks, findDTaoConvictionLock, findDTaoRootStakeHold, findEvmMirrorWithdrawable, getBalanceFingerprint, getBalanceId, getBalanceStorageFingerprint, getConvictionLockLabel, getDTaoClaimablePlancks, getEpochTransferFee, getExtension, getLockTitle, getLockedType, getMintExtensions, getRawLocks, getRawTotalPlanck, getSweepStaleVariant, getTokenMetadata, getTransferFeeConfig, getTransferHook, getValueId, includeInTotalExtraAmount, isDTaoClaimableLock, isEqualBalanceArrays, isEqualBalancesResult, isEqualMiniMetadatas, isEqualModuleResults, isEvmMirrorWithdrawableLock, isNonTransferable, taoToAlpha, taoToAlphaCeil, uniswapV2PairAbi };
7236
7323
 
7237
7324
  //# sourceMappingURL=index.mjs.map