@talismn/balances 0.0.0-pr2075-20250708143919 → 0.0.0-pr2075-20250708160640

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.
@@ -11,13 +11,13 @@ import pako from 'pako';
11
11
  import { parseAbi, erc20Abi, getContract, ContractFunctionExecutionError, hexToString, erc20Abi_bytes32, encodeFunctionData, isHex, hexToBigInt, withRetry } from 'viem';
12
12
  import { assign, omit, isEqual, fromPairs, toPairs, keys, keyBy, uniq, values, groupBy as groupBy$1 } from 'lodash';
13
13
  import z from 'zod/v4';
14
- import { Observable, distinctUntilChanged, of, map, timer, switchMap, from, firstValueFrom, combineLatest, scan, share, switchAll, mergeMap, toArray, interval, startWith, exhaustMap, BehaviorSubject, debounceTime, takeUntil, withLatestFrom, concatMap, filter, tap } from 'rxjs';
14
+ import { of, Observable, distinctUntilChanged, map, timer, switchMap, from, firstValueFrom, combineLatest, scan, share, switchAll, mergeMap, toArray, interval, startWith, exhaustMap, BehaviorSubject, debounceTime, takeUntil, withLatestFrom, concatMap, filter, tap } from 'rxjs';
15
15
  import isEqual$1 from 'lodash/isEqual';
16
- import { unifyMetadata, decAnyMetadata, getDynamicBuilder, getLookupFn, decodeScale, parseMetadataRpc, getStorageKeyPrefix, compactMetadata, encodeMetadata, toHex, papiParse, papiStringify, getMetadataVersion, encodeStateKey } from '@talismn/scale';
17
- import { Metadata, TypeRegistry } from '@polkadot/types';
18
- import groupBy from 'lodash/groupBy';
16
+ import { decodeScale, parseMetadataRpc, getStorageKeyPrefix, compactMetadata, encodeMetadata, toHex, unifyMetadata, decAnyMetadata, getDynamicBuilder, getLookupFn, papiParse, papiStringify, getMetadataVersion, encodeStateKey } from '@talismn/scale';
19
17
  import { mergeUint8, toHex as toHex$1 } from '@polkadot-api/utils';
20
18
  import { Binary, Enum, AccountId } from 'polkadot-api';
19
+ import { Metadata, TypeRegistry } from '@polkadot/types';
20
+ import groupBy from 'lodash/groupBy';
21
21
  import upperFirst from 'lodash/upperFirst';
22
22
  import { u32, Struct, u128 } from 'scale-ts';
23
23
  import { Abi } from '@polkadot/api-contract';
@@ -1540,6 +1540,10 @@ const fetchBalances$c = async ({
1540
1540
  tokensWithAddresses,
1541
1541
  connector
1542
1542
  }) => {
1543
+ if (!tokensWithAddresses.length) return {
1544
+ success: [],
1545
+ errors: []
1546
+ };
1543
1547
  const client = await connector.getPublicClientForEvmNetwork(networkId);
1544
1548
  if (!client) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
1545
1549
  for (const [token, addresses] of tokensWithAddresses) {
@@ -1558,7 +1562,6 @@ const fetchWithoutAggregator$1 = async (client, balanceDefs) => {
1558
1562
  success: [],
1559
1563
  errors: []
1560
1564
  };
1561
- log.debug("fetching %s balances without aggregator", MODULE_TYPE$8, balanceDefs.length);
1562
1565
  const results = await Promise.allSettled(balanceDefs.map(async ({
1563
1566
  token,
1564
1567
  address
@@ -1576,7 +1579,7 @@ const fetchWithoutAggregator$1 = async (client, balanceDefs) => {
1576
1579
  value: result.toString(),
1577
1580
  source: MODULE_TYPE$8,
1578
1581
  networkId: parseEvmErc20TokenId(token.id).networkId,
1579
- status: "cache"
1582
+ status: "live"
1580
1583
  };
1581
1584
  return balance;
1582
1585
  } catch (err) {
@@ -1603,7 +1606,6 @@ const fetchWithAggregator$1 = async (client, balanceDefs, erc20BalancesAggregato
1603
1606
  success: [],
1604
1607
  errors: []
1605
1608
  };
1606
- log.debug("fetching %s balances with aggregator", MODULE_TYPE$8, balanceDefs.length);
1607
1609
  try {
1608
1610
  const erc20Balances = await client.readContract({
1609
1611
  abi: erc20BalancesAggregatorAbi,
@@ -1620,7 +1622,7 @@ const fetchWithAggregator$1 = async (client, balanceDefs, erc20BalancesAggregato
1620
1622
  value: erc20Balances[index].toString(),
1621
1623
  source: MODULE_TYPE$8,
1622
1624
  networkId: parseTokenId(balanceDef.token.id).networkId,
1623
- status: "cache"
1625
+ status: "live"
1624
1626
  }));
1625
1627
  return {
1626
1628
  success,
@@ -1766,12 +1768,16 @@ const getTransferCallData$8 = ({
1766
1768
  };
1767
1769
  };
1768
1770
 
1769
- const SUBSCRIPTION_INTERVAL$6 = 6_000;
1771
+ const SUBSCRIPTION_INTERVAL$4 = 6_000;
1770
1772
  const subscribeBalances$8 = ({
1771
1773
  networkId,
1772
1774
  tokensWithAddresses,
1773
1775
  connector
1774
1776
  }) => {
1777
+ if (!tokensWithAddresses.length) return of({
1778
+ success: [],
1779
+ errors: []
1780
+ });
1775
1781
  return new Observable(subscriber => {
1776
1782
  const abortController = new AbortController();
1777
1783
  const poll = async () => {
@@ -1784,7 +1790,7 @@ const subscribeBalances$8 = ({
1784
1790
  });
1785
1791
  if (abortController.signal.aborted) return;
1786
1792
  subscriber.next(balances);
1787
- setTimeout(poll, SUBSCRIPTION_INTERVAL$6);
1793
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$4);
1788
1794
  } catch (error) {
1789
1795
  log.error("Error", {
1790
1796
  module: MODULE_TYPE$8,
@@ -1830,6 +1836,10 @@ const fetchBalances$b = async ({
1830
1836
  tokensWithAddresses,
1831
1837
  connector
1832
1838
  }) => {
1839
+ if (!tokensWithAddresses.length) return {
1840
+ success: [],
1841
+ errors: []
1842
+ };
1833
1843
  const client = await connector.getPublicClientForEvmNetwork(networkId);
1834
1844
  if (!client) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
1835
1845
  for (const [token, addresses] of tokensWithAddresses) {
@@ -1848,7 +1858,6 @@ const fetchWithoutMulticall = async (client, balanceDefs) => {
1848
1858
  success: [],
1849
1859
  errors: []
1850
1860
  };
1851
- log.debug("fetching %s balances without multicall3", MODULE_TYPE$7, balanceDefs.length);
1852
1861
  const results = await Promise.allSettled(balanceDefs.map(async ({
1853
1862
  token,
1854
1863
  address
@@ -1890,7 +1899,6 @@ const fetchWithMulticall = async (client, balanceDefs, multicall3Address) => {
1890
1899
  success: [],
1891
1900
  errors: []
1892
1901
  };
1893
- log.debug("fetching %s balances with multicall3", MODULE_TYPE$7, balanceDefs.length);
1894
1902
  try {
1895
1903
  const callResults = await client.multicall({
1896
1904
  contracts: balanceDefs.map(({
@@ -1980,12 +1988,16 @@ const getTransferCallData$7 = ({
1980
1988
  };
1981
1989
  };
1982
1990
 
1983
- const SUBSCRIPTION_INTERVAL$5 = 6_000;
1991
+ const SUBSCRIPTION_INTERVAL$3 = 6_000;
1984
1992
  const subscribeBalances$7 = ({
1985
1993
  networkId,
1986
1994
  tokensWithAddresses,
1987
1995
  connector
1988
1996
  }) => {
1997
+ if (!tokensWithAddresses.length) return of({
1998
+ success: [],
1999
+ errors: []
2000
+ });
1989
2001
  return new Observable(subscriber => {
1990
2002
  const abortController = new AbortController();
1991
2003
  const poll = async () => {
@@ -1998,7 +2010,7 @@ const subscribeBalances$7 = ({
1998
2010
  });
1999
2011
  if (abortController.signal.aborted) return;
2000
2012
  subscriber.next(balances);
2001
- setTimeout(poll, SUBSCRIPTION_INTERVAL$5);
2013
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$3);
2002
2014
  } catch (error) {
2003
2015
  log.error("Error", {
2004
2016
  module: MODULE_TYPE$7,
@@ -2039,6 +2051,10 @@ const fetchBalances$a = async ({
2039
2051
  tokensWithAddresses,
2040
2052
  connector
2041
2053
  }) => {
2054
+ if (!tokensWithAddresses.length) return {
2055
+ success: [],
2056
+ errors: []
2057
+ };
2042
2058
  const client = await connector.getPublicClientForEvmNetwork(networkId);
2043
2059
  if (!client) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
2044
2060
  for (const [token, addresses] of tokensWithAddresses) {
@@ -2057,7 +2073,6 @@ const fetchWithoutAggregator = async (client, balanceDefs) => {
2057
2073
  success: [],
2058
2074
  errors: []
2059
2075
  };
2060
- log.debug("fetching %s balances without aggregator", MODULE_TYPE$6, balanceDefs.length);
2061
2076
  const results = await Promise.allSettled(balanceDefs.map(async ({
2062
2077
  token,
2063
2078
  address
@@ -2075,7 +2090,7 @@ const fetchWithoutAggregator = async (client, balanceDefs) => {
2075
2090
  value: result.toString(),
2076
2091
  source: MODULE_TYPE$6,
2077
2092
  networkId: parseTokenId(token.id).networkId,
2078
- status: "cache"
2093
+ status: "live"
2079
2094
  };
2080
2095
  return balance;
2081
2096
  } catch (err) {
@@ -2102,7 +2117,6 @@ const fetchWithAggregator = async (client, balanceDefs, erc20BalancesAggregatorA
2102
2117
  success: [],
2103
2118
  errors: []
2104
2119
  };
2105
- log.debug("fetching %s balances with aggregator", MODULE_TYPE$6, balanceDefs.length);
2106
2120
  try {
2107
2121
  const erc20Balances = await client.readContract({
2108
2122
  abi: erc20BalancesAggregatorAbi,
@@ -2119,7 +2133,7 @@ const fetchWithAggregator = async (client, balanceDefs, erc20BalancesAggregatorA
2119
2133
  value: erc20Balances[index].toString(),
2120
2134
  source: MODULE_TYPE$6,
2121
2135
  networkId: parseTokenId(balanceDef.token.id).networkId,
2122
- status: "cache"
2136
+ status: "live"
2123
2137
  }));
2124
2138
  return {
2125
2139
  success,
@@ -2313,12 +2327,16 @@ const getTransferCallData$6 = ({
2313
2327
  };
2314
2328
  };
2315
2329
 
2316
- const SUBSCRIPTION_INTERVAL$4 = 6_000;
2330
+ const SUBSCRIPTION_INTERVAL$2 = 6_000;
2317
2331
  const subscribeBalances$6 = ({
2318
2332
  networkId,
2319
2333
  tokensWithAddresses,
2320
2334
  connector
2321
2335
  }) => {
2336
+ if (!tokensWithAddresses.length) return of({
2337
+ success: [],
2338
+ errors: []
2339
+ });
2322
2340
  return new Observable(subscriber => {
2323
2341
  const abortController = new AbortController();
2324
2342
  const poll = async () => {
@@ -2331,7 +2349,7 @@ const subscribeBalances$6 = ({
2331
2349
  });
2332
2350
  if (abortController.signal.aborted) return;
2333
2351
  subscriber.next(balances);
2334
- setTimeout(poll, SUBSCRIPTION_INTERVAL$4);
2352
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$2);
2335
2353
  } catch (error) {
2336
2354
  log.error("Error", {
2337
2355
  module: MODULE_TYPE$6,
@@ -3195,273 +3213,138 @@ async function getPoolBalance(publicClient, contractAddress, accountAddress) {
3195
3213
  const MODULE_TYPE$5 = SubAssetsTokenSchema.shape.type.value;
3196
3214
  const PLATFORM$5 = SubAssetsTokenSchema.shape.platform.value;
3197
3215
 
3198
- /**
3199
- * Wraps a BalanceModule's fetch/subscribe methods with a single `balances` method.
3200
- * This `balances` method will subscribe if a callback parameter is provided, or otherwise fetch.
3201
- */
3216
+ const fetchRpcQueryPack = async (connector, networkId, queries) => {
3217
+ const allStateKeys = queries.flatMap(({
3218
+ stateKeys
3219
+ }) => stateKeys).filter(isNotNil);
3202
3220
 
3203
- async function balances(balanceModule, addressesByToken, callback) {
3204
- // subscription request
3205
- if (callback !== undefined) return await balanceModule.subscribeBalances({
3206
- addressesByToken
3207
- }, callback);
3221
+ // doing a query without keys would throw an error => return early
3222
+ if (!allStateKeys.length) return queries.map(({
3223
+ stateKeys,
3224
+ decodeResult
3225
+ }) => decodeResult(stateKeys.map(() => null)));
3226
+ const [result] = await connector.send(networkId, "state_queryStorageAt", [allStateKeys]);
3227
+ return decodeRpcQueryPack(queries, result);
3228
+ };
3229
+ const getRpcQueryPack$ = (connector, networkId, queries, timeout = false) => {
3230
+ const allStateKeys = queries.flatMap(({
3231
+ stateKeys
3232
+ }) => stateKeys).filter(isNotNil);
3208
3233
 
3209
- // one-off request
3210
- return await balanceModule.fetchBalances(addressesByToken);
3211
- }
3234
+ // doing a query without keys would throw an error => return early
3235
+ if (!allStateKeys.length) return of(queries.map(({
3236
+ stateKeys,
3237
+ decodeResult
3238
+ }) => decodeResult(stateKeys.map(() => null))));
3239
+ return new Observable(subscriber => {
3240
+ const promUnsub = connector.subscribe(networkId, "state_subscribeStorage", "state_storage", [allStateKeys], (error, result) => {
3241
+ if (error) subscriber.error(error);else subscriber.next(decodeRpcQueryPack(queries, result));
3242
+ }, timeout);
3243
+ return () => {
3244
+ promUnsub.then(unsub => unsub("state_unsubscribeStorage"));
3245
+ };
3246
+ });
3247
+ };
3248
+ const decodeRpcQueryPack = (queries, result) => {
3249
+ return queries.reduce((acc, {
3250
+ stateKeys,
3251
+ decodeResult
3252
+ }) => {
3253
+ const changes = stateKeys.map(stateKey => {
3254
+ if (!stateKey) return null;
3255
+ const change = result.changes.find(([key]) => key === stateKey);
3256
+ if (!change) return null;
3257
+ return change[1];
3258
+ });
3259
+ acc.push(decodeResult(changes));
3260
+ return acc;
3261
+ }, []);
3262
+ };
3212
3263
 
3213
- // TODO remove this one in favor of the network specific one below
3214
- const buildStorageCoders = ({
3215
- chainIds,
3216
- chains,
3217
- miniMetadatas,
3218
- coders
3219
- }) => new Map([...chainIds].flatMap(chainId => {
3220
- const chain = chains[chainId];
3221
- if (!chain) return [];
3222
- const miniMetadata = miniMetadatas.get(chainId); // findMiniMetadata<TBalanceModule>(miniMetadatas, moduleType, chain)
3223
- if (!miniMetadata) return [];
3224
- if (!miniMetadata.data) return [];
3225
- const metadata = unifyMetadata(decAnyMetadata(miniMetadata.data));
3226
- try {
3227
- const scaleBuilder = getDynamicBuilder(getLookupFn(metadata));
3228
- const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
3229
- const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
3230
- chainId
3231
- }) : moduleMethodOrFn;
3232
- try {
3233
- return [[key, scaleBuilder.buildStorage(module, method)]];
3234
- } catch (cause) {
3235
- log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
3236
- return [];
3237
- }
3238
- }));
3239
- return [[chainId, builtCoders]];
3240
- } catch (cause) {
3241
- log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
3242
- return [];
3243
- }
3244
- }));
3245
- // type StorageCoder<TCoders extends NetworkCoders> = ReturnType<ReturnType<typeof getDynamicBuilder>["buildStorage"]>[keyof TCoders]
3264
+ const buildQueries$6 = (networkId, balanceDefs, miniMetadata) => {
3265
+ const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
3266
+ storage: ["Assets", "Account"]
3267
+ });
3268
+ return balanceDefs.map(({
3269
+ token,
3270
+ address
3271
+ }) => {
3272
+ const scaleCoder = networkStorageCoders?.storage;
3273
+ const stateKey = tryEncode$1(scaleCoder, Number(token.assetId), address) ??
3274
+ // Asset Hub
3275
+ tryEncode$1(scaleCoder, BigInt(token.assetId), address); // Astar
3246
3276
 
3247
- const buildNetworkStorageCoders = (chainId, miniMetadata, coders) => {
3248
- if (!miniMetadata.data) return null;
3249
- const metadata = unifyMetadata(decAnyMetadata(miniMetadata.data));
3277
+ if (!stateKey) {
3278
+ log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
3279
+ return null;
3280
+ }
3281
+ const decodeResult = changes => {
3282
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3283
+
3284
+ const decoded = decodeScale(scaleCoder, changes[0], `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
3285
+ balance: 0n,
3286
+ status: {
3287
+ type: "Liquid"
3288
+ }};
3289
+ const isFrozen = decoded?.status?.type === "Frozen";
3290
+ const amount = (decoded?.balance ?? 0n).toString();
3291
+
3292
+ // due to the following balance calculations, which are made in the `Balance` type:
3293
+ //
3294
+ // total balance = (free balance) + (reserved balance)
3295
+ // transferable balance = (free balance) - (frozen balance)
3296
+ //
3297
+ // when `isFrozen` is true we need to set **both** the `free` and `frozen` amounts
3298
+ // of this balance to the value we received from the RPC.
3299
+ //
3300
+ // if we only set the `frozen` amount, then the `total` calculation will be incorrect!
3301
+ const free = amount;
3302
+ const frozen = token.isFrozen || isFrozen ? amount : "0";
3303
+
3304
+ // include balance values even if zero, so that newly-zero values overwrite old values
3305
+ const balanceValues = [{
3306
+ type: "free",
3307
+ label: "free",
3308
+ amount: free.toString()
3309
+ }, {
3310
+ type: "locked",
3311
+ label: "frozen",
3312
+ amount: frozen.toString()
3313
+ }];
3314
+ const balance = {
3315
+ source: "substrate-assets",
3316
+ status: "live",
3317
+ address,
3318
+ networkId,
3319
+ tokenId: token.id,
3320
+ values: balanceValues
3321
+ };
3322
+ return balance;
3323
+ };
3324
+ return {
3325
+ stateKeys: [stateKey],
3326
+ decodeResult
3327
+ };
3328
+ }).filter(isNotNil);
3329
+ };
3330
+ const tryEncode$1 = (scaleCoder, ...args) => {
3250
3331
  try {
3251
- const scaleBuilder = getDynamicBuilder(getLookupFn(metadata));
3252
- const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
3253
- const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
3254
- chainId
3255
- }) : moduleMethodOrFn;
3256
- try {
3257
- return [[key, scaleBuilder.buildStorage(module, method)]];
3258
- } catch (cause) {
3259
- log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
3260
- return [];
3261
- }
3262
- }));
3263
- return builtCoders;
3264
- } catch (cause) {
3265
- log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
3332
+ return scaleCoder?.keys?.enc?.(...args);
3333
+ } catch {
3334
+ return null;
3266
3335
  }
3267
- return null;
3268
3336
  };
3269
3337
 
3270
- /**
3271
- * Decodes & unwraps outputs and errors of a given result, contract, and method.
3272
- * Parsed error message can be found in `decodedOutput` if `isError` is true.
3273
- * SOURCE: https://github.com/paritytech/contracts-ui (GPL-3.0-only)
3274
- */
3275
- function decodeOutput({
3276
- result
3277
- }, registry, abi, method) {
3278
- let output;
3279
- let decodedOutput = "";
3280
- let isError = true;
3281
- if (result.isOk) {
3282
- const flags = result.asOk.flags.toHuman();
3283
- isError = flags.includes("Revert");
3284
- const abiMessage = getAbiMessage(abi, method);
3285
- const returnType = abiMessage.returnType;
3286
- const returnTypeName = getReturnTypeName(returnType);
3287
- const r = returnType ? registry.createTypeUnsafe(returnTypeName, [result.asOk.data]).toHuman() : "()";
3288
- output = isOk(r) ? r.Ok : isErr(r) ? r.Err : r;
3289
- const errorText = isErr(output) ? typeof output.Err === "object" ? JSON.stringify(output.Err, null, 2) : output.Err?.toString() ?? "Error" : output !== "Ok" ? output?.toString() || "Error" : "Error";
3290
- const okText = isOk(r) ? typeof output === "object" ? JSON.stringify(output, null, "\t") : output?.toString() ?? "()" : JSON.stringify(output, null, "\t") ?? "()";
3291
- decodedOutput = isError ? errorText : okText;
3292
- } else if (result.isErr) {
3293
- output = result.toHuman();
3294
- let errorText;
3295
- if (isErr(output) && typeof output.Err === "object" && Object.keys(output.Err || {}).length && typeof Object.values(output.Err || {})[0] === "string") {
3296
- const [errorKey, errorValue] = Object.entries(output.Err || {})[0];
3297
- errorText = `${errorKey}${errorValue}`;
3298
- }
3299
- decodedOutput = errorText || "Error";
3300
- }
3301
- return {
3302
- output,
3303
- decodedOutput,
3304
- isError
3338
+ const fetchBalances$6 = async ({
3339
+ networkId,
3340
+ tokensWithAddresses,
3341
+ connector,
3342
+ miniMetadata
3343
+ }) => {
3344
+ if (!tokensWithAddresses.length) return {
3345
+ success: [],
3346
+ errors: []
3305
3347
  };
3306
- }
3307
-
3308
- /**
3309
- * Helper types & functions
3310
- * SOURCE: https://github.com/paritytech/contracts-ui (GPL-3.0-only)
3311
- */
3312
-
3313
- function isErr(o) {
3314
- return typeof o === "object" && o !== null && "Err" in o;
3315
- }
3316
- function isOk(o) {
3317
- return typeof o === "object" && o !== null && "Ok" in o;
3318
- }
3319
- function getReturnTypeName(type) {
3320
- return type?.lookupName || type?.type || "";
3321
- }
3322
- function getAbiMessage(abi, method) {
3323
- const abiMessage = abi.messages.find(m => stringCamelCase(m.method) === stringCamelCase(method));
3324
- if (!abiMessage) {
3325
- throw new Error(`"${method}" not found in Contract`);
3326
- }
3327
- return abiMessage;
3328
- }
3329
-
3330
- /**
3331
- *
3332
- * Detect Balances::transfer -> Balances::transfer_allow_death migration
3333
- * https://github.com/paritytech/substrate/pull/12951
3334
- *
3335
- * `transfer_allow_death` is the preferred method,
3336
- * so if something goes wrong during detection, we should assume the chain has migrated
3337
- * @param metadataRpc string containing the hashed RPC metadata for the chain
3338
- * @returns
3339
- */
3340
- const detectTransferMethod = metadataRpc => {
3341
- const pjsMetadata = new Metadata(new TypeRegistry(), metadataRpc);
3342
- pjsMetadata.registry.setMetadata(pjsMetadata);
3343
- const balancesPallet = pjsMetadata.asLatest.pallets.find(pallet => pallet.name.eq("Balances"));
3344
- const balancesCallsTypeIndex = balancesPallet?.calls.value.type.toNumber();
3345
- const balancesCallsType = balancesCallsTypeIndex !== undefined ? pjsMetadata.asLatest.lookup.types[balancesCallsTypeIndex] : undefined;
3346
- const hasDeprecatedTransferCall = balancesCallsType?.type.def.asVariant?.variants.find(variant => variant.name.eq("transfer")) !== undefined;
3347
- return hasDeprecatedTransferCall ? "transfer" : "transfer_allow_death";
3348
- };
3349
-
3350
- const getUniqueChainIds = (addressesByToken, tokens) => [...new Set(Object.keys(addressesByToken).map(tokenId => tokens[tokenId]?.networkId).flatMap(chainId => chainId ? [chainId] : []))];
3351
-
3352
- const makeContractCaller = ({
3353
- chainConnector,
3354
- chainId,
3355
- registry
3356
- }) => async (callFrom, contractAddress, inputData) => registry.createType("ContractExecResult", await chainConnector.send(chainId, "state_call", ["ContractsApi_call", u8aToHex(u8aConcatStrict([
3357
- // origin
3358
- registry.createType("AccountId", callFrom).toU8a(),
3359
- // dest
3360
- registry.createType("AccountId", contractAddress).toU8a(),
3361
- // value
3362
- registry.createType("Balance", 0).toU8a(),
3363
- // gasLimit
3364
- registry.createType("Option<WeightV2>").toU8a(),
3365
- // storageDepositLimit
3366
- registry.createType("Option<Balance>").toU8a(),
3367
- // inputData
3368
- inputData instanceof Uint8Array ? inputData : inputData.toU8a()]))]));
3369
-
3370
- /**
3371
- * Pass some these into an `RpcStateQueryHelper` in order to easily batch multiple state queries into the one rpc call.
3372
- */
3373
-
3374
- /**
3375
- * Used by a variety of balance modules to help batch multiple state queries into the one rpc call.
3376
- */
3377
- class RpcStateQueryHelper {
3378
- #chainConnector;
3379
- #queries;
3380
- constructor(chainConnector, queries) {
3381
- this.#chainConnector = chainConnector;
3382
- this.#queries = queries;
3383
- }
3384
- async subscribe(callback, timeout = false, subscribeMethod = "state_subscribeStorage", responseMethod = "state_storage", unsubscribeMethod = "state_unsubscribeStorage") {
3385
- const queriesByChain = groupBy(this.#queries, "chainId");
3386
- const subscriptions = Object.entries(queriesByChain).map(([chainId, queries]) => {
3387
- const params = [queries.map(({
3388
- stateKey
3389
- }) => stateKey)];
3390
- const unsub = this.#chainConnector.subscribe(chainId, subscribeMethod, responseMethod, params, (error, result) => {
3391
- error ? callback(error) : callback(null, this.#distributeChangesToQueryDecoders.call(this, chainId, result));
3392
- }, timeout);
3393
- return () => unsub.then(unsubscribe => unsubscribe(unsubscribeMethod));
3394
- });
3395
- return () => subscriptions.forEach(unsubscribe => unsubscribe());
3396
- }
3397
- async fetch(method = "state_queryStorageAt") {
3398
- const queriesByChain = groupBy(this.#queries, "chainId");
3399
- const resultsByChain = await Promise.all(Object.entries(queriesByChain).map(async ([chainId, queries]) => {
3400
- const params = [queries.map(({
3401
- stateKey
3402
- }) => stateKey)];
3403
- const result = (await this.#chainConnector.send(chainId, method, params))[0];
3404
- return this.#distributeChangesToQueryDecoders.call(this, chainId, result);
3405
- }));
3406
- return resultsByChain.flatMap(result => result);
3407
- }
3408
- #distributeChangesToQueryDecoders(chainId, result) {
3409
- if (typeof result !== "object" || result === null) return [];
3410
- if (!hasOwnProperty(result, "changes") || typeof result.changes !== "object") return [];
3411
- if (!Array.isArray(result.changes)) return [];
3412
- return result.changes.flatMap(([reference, change]) => {
3413
- if (typeof reference !== "string") {
3414
- log.warn(`Received non-string reference in RPC result: ${reference}`);
3415
- return [];
3416
- }
3417
- if (typeof change !== "string" && change !== null) {
3418
- log.warn(`Received non-string and non-null change in RPC result: ${reference} | ${change}`);
3419
- return [];
3420
- }
3421
- const query = this.#queries.find(({
3422
- chainId: cId,
3423
- stateKey
3424
- }) => cId === chainId && stateKey === reference);
3425
- if (!query) {
3426
- log.warn(`Failed to find query:\n${reference} in\n${this.#queries.map(({
3427
- stateKey
3428
- }) => stateKey)}`);
3429
- return [];
3430
- }
3431
- return [query.decodeResult(change)];
3432
- });
3433
- }
3434
- }
3435
-
3436
- const configureStore = (dbTable = db.balancesBlob) => ({
3437
- persistData: async balances => {
3438
- const output = compress(balances);
3439
- await dbTable.clear();
3440
- await dbTable.put({
3441
- data: output,
3442
- id: Date.now().toString()
3443
- });
3444
- },
3445
- retrieveData: async () => {
3446
- const compressedData = await dbTable.toCollection().first();
3447
- if (!compressedData) return [];
3448
- return decompress(compressedData.data);
3449
- }
3450
- });
3451
- const compress = balances => pako.deflate(JSON.stringify(balances));
3452
- const decompress = data => {
3453
- const decompressed = pako.inflate(data, {
3454
- to: "string"
3455
- });
3456
- return JSON.parse(decompressed);
3457
- };
3458
-
3459
- const fetchBalances$6 = async ({
3460
- networkId,
3461
- tokensWithAddresses,
3462
- connector,
3463
- miniMetadata
3464
- }) => {
3465
3348
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
3466
3349
  if (!miniMetadata?.data) {
3467
3350
  log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$5} balances on ${networkId}.`);
@@ -3496,8 +3379,8 @@ const fetchBalances$6 = async ({
3496
3379
  }))
3497
3380
  };
3498
3381
  }
3499
- const queries = buildQueries$7(networkId, balanceDefs, miniMetadata);
3500
- const balances = await new RpcStateQueryHelper(connector, queries).fetch();
3382
+ const queries = buildQueries$6(networkId, balanceDefs, miniMetadata);
3383
+ const balances = await fetchRpcQueryPack(connector, networkId, queries);
3501
3384
  return balanceDefs.reduce((acc, def) => {
3502
3385
  const balance = balances.find(b => b?.address === def.address && b?.tokenId === def.token.id);
3503
3386
  if (balance) acc.success.push(balance);
@@ -3524,80 +3407,6 @@ const fetchBalances$6 = async ({
3524
3407
  errors: []
3525
3408
  });
3526
3409
  };
3527
- const buildQueries$7 = (networkId, balanceDefs, miniMetadata) => {
3528
- const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
3529
- storage: ["Assets", "Account"]
3530
- });
3531
- return balanceDefs.map(({
3532
- token,
3533
- address
3534
- }) => {
3535
- const scaleCoder = networkStorageCoders?.storage;
3536
- const stateKey = tryEncode$2(scaleCoder, Number(token.assetId), address) ??
3537
- // Asset Hub
3538
- tryEncode$2(scaleCoder, BigInt(token.assetId), address); // Astar
3539
-
3540
- if (!stateKey) {
3541
- log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
3542
- return null;
3543
- }
3544
- const decodeResult = change => {
3545
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3546
-
3547
- const decoded = decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
3548
- balance: 0n,
3549
- status: {
3550
- type: "Liquid"
3551
- }};
3552
- const isFrozen = decoded?.status?.type === "Frozen";
3553
- const amount = (decoded?.balance ?? 0n).toString();
3554
-
3555
- // due to the following balance calculations, which are made in the `Balance` type:
3556
- //
3557
- // total balance = (free balance) + (reserved balance)
3558
- // transferable balance = (free balance) - (frozen balance)
3559
- //
3560
- // when `isFrozen` is true we need to set **both** the `free` and `frozen` amounts
3561
- // of this balance to the value we received from the RPC.
3562
- //
3563
- // if we only set the `frozen` amount, then the `total` calculation will be incorrect!
3564
- const free = amount;
3565
- const frozen = token.isFrozen || isFrozen ? amount : "0";
3566
-
3567
- // include balance values even if zero, so that newly-zero values overwrite old values
3568
- const balanceValues = [{
3569
- type: "free",
3570
- label: "free",
3571
- amount: free.toString()
3572
- }, {
3573
- type: "locked",
3574
- label: "frozen",
3575
- amount: frozen.toString()
3576
- }];
3577
- const balance = {
3578
- source: "substrate-assets",
3579
- status: "live",
3580
- address,
3581
- networkId,
3582
- tokenId: token.id,
3583
- values: balanceValues
3584
- };
3585
- return balance;
3586
- };
3587
- return {
3588
- chainId: networkId,
3589
- stateKey,
3590
- decodeResult
3591
- };
3592
- }).filter(isNotNil);
3593
- };
3594
- const tryEncode$2 = (scaleCoder, ...args) => {
3595
- try {
3596
- return scaleCoder?.keys?.enc?.(...args);
3597
- } catch {
3598
- return null;
3599
- }
3600
- };
3601
3410
 
3602
3411
  const fetchTokens$5 = async ({
3603
3412
  networkId,
@@ -3858,93 +3667,335 @@ const tryGetConstantValue = (metadataRpc, pallet, constant) => {
3858
3667
  return codec.dec(encodedValue);
3859
3668
  };
3860
3669
 
3861
- const fetchRpcQueryPack = async (connector, networkId, queries) => {
3862
- const allStateKeys = queries.flatMap(({
3863
- stateKeys
3864
- }) => stateKeys).filter(isNotNil);
3865
-
3866
- // doing a query without keys would throw an error => return early
3867
- if (!allStateKeys.length) return queries.map(({
3868
- stateKeys,
3869
- decodeResult
3870
- }) => decodeResult(stateKeys.map(() => null)));
3871
- const [result] = await connector.send(networkId, "state_queryStorageAt", [allStateKeys]);
3872
- return decodeRpcQueryPack(queries, result);
3670
+ const subscribeBalances$5 = ({
3671
+ networkId,
3672
+ tokensWithAddresses,
3673
+ connector,
3674
+ miniMetadata
3675
+ }) => {
3676
+ if (!tokensWithAddresses.length) return of({
3677
+ success: [],
3678
+ errors: []
3679
+ });
3680
+ const balanceDefs = getBalanceDefs(tokensWithAddresses);
3681
+ const queries = buildQueries$6(networkId, balanceDefs, miniMetadata);
3682
+ return getRpcQueryPack$(connector, networkId, queries).pipe(map(balances => ({
3683
+ success: balances,
3684
+ errors: []
3685
+ })));
3873
3686
  };
3874
- const getRpcQueryPack$ = (connector, networkId, queries, timeout = false) => {
3875
- const allStateKeys = queries.flatMap(({
3876
- stateKeys
3877
- }) => stateKeys).filter(isNotNil);
3878
3687
 
3879
- // doing a query without keys would throw an error => return early
3880
- if (!allStateKeys.length) return of(queries.map(({
3881
- stateKeys,
3882
- decodeResult
3883
- }) => decodeResult(stateKeys.map(() => null))));
3884
- return new Observable(subscriber => {
3885
- const promUnsub = connector.subscribe(networkId, "state_subscribeStorage", "state_storage", [allStateKeys], (error, result) => {
3886
- if (error) subscriber.error(error);else subscriber.next(decodeRpcQueryPack(queries, result));
3887
- }, timeout);
3888
- return () => {
3889
- promUnsub.then(unsub => unsub("state_unsubscribeStorage"));
3890
- };
3891
- });
3688
+ const SubAssetsBalanceModule = {
3689
+ type: MODULE_TYPE$5,
3690
+ platform: PLATFORM$5,
3691
+ getMiniMetadata: getMiniMetadata$6,
3692
+ fetchTokens: fetchTokens$5,
3693
+ fetchBalances: fetchBalances$6,
3694
+ subscribeBalances: subscribeBalances$5,
3695
+ getTransferCallData: getTransferCallData$5
3892
3696
  };
3893
- const decodeRpcQueryPack = (queries, result) => {
3894
- return queries.reduce((acc, {
3895
- stateKeys,
3896
- decodeResult
3897
- }) => {
3898
- const changes = stateKeys.map(stateKey => {
3899
- if (!stateKey) return null;
3900
- const change = result.changes.find(([key]) => key === stateKey);
3901
- if (!change) return null;
3902
- return change[1];
3903
- });
3904
- acc.push(decodeResult(changes));
3905
- return acc;
3906
- }, []);
3697
+
3698
+ // to be used by chaindata too
3699
+ const SubAssetsTokenConfigSchema = z.strictObject({
3700
+ assetId: SubAssetsTokenSchema.shape.assetId,
3701
+ ...TokenConfigBaseSchema.shape
3702
+ });
3703
+
3704
+ const MODULE_TYPE$4 = SubForeignAssetsTokenSchema.shape.type.value;
3705
+ const PLATFORM$4 = SubForeignAssetsTokenSchema.shape.platform.value;
3706
+
3707
+ /**
3708
+ * Wraps a BalanceModule's fetch/subscribe methods with a single `balances` method.
3709
+ * This `balances` method will subscribe if a callback parameter is provided, or otherwise fetch.
3710
+ */
3711
+
3712
+ async function balances(balanceModule, addressesByToken, callback) {
3713
+ // subscription request
3714
+ if (callback !== undefined) return await balanceModule.subscribeBalances({
3715
+ addressesByToken
3716
+ }, callback);
3717
+
3718
+ // one-off request
3719
+ return await balanceModule.fetchBalances(addressesByToken);
3720
+ }
3721
+
3722
+ // TODO remove this one in favor of the network specific one below
3723
+ const buildStorageCoders = ({
3724
+ chainIds,
3725
+ chains,
3726
+ miniMetadatas,
3727
+ coders
3728
+ }) => new Map([...chainIds].flatMap(chainId => {
3729
+ const chain = chains[chainId];
3730
+ if (!chain) return [];
3731
+ const miniMetadata = miniMetadatas.get(chainId); // findMiniMetadata<TBalanceModule>(miniMetadatas, moduleType, chain)
3732
+ if (!miniMetadata) return [];
3733
+ if (!miniMetadata.data) return [];
3734
+ const metadata = unifyMetadata(decAnyMetadata(miniMetadata.data));
3735
+ try {
3736
+ const scaleBuilder = getDynamicBuilder(getLookupFn(metadata));
3737
+ const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
3738
+ const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
3739
+ chainId
3740
+ }) : moduleMethodOrFn;
3741
+ try {
3742
+ return [[key, scaleBuilder.buildStorage(module, method)]];
3743
+ } catch (cause) {
3744
+ log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
3745
+ return [];
3746
+ }
3747
+ }));
3748
+ return [[chainId, builtCoders]];
3749
+ } catch (cause) {
3750
+ log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
3751
+ return [];
3752
+ }
3753
+ }));
3754
+ // type StorageCoder<TCoders extends NetworkCoders> = ReturnType<ReturnType<typeof getDynamicBuilder>["buildStorage"]>[keyof TCoders]
3755
+
3756
+ const buildNetworkStorageCoders = (chainId, miniMetadata, coders) => {
3757
+ if (!miniMetadata.data) return null;
3758
+ const metadata = unifyMetadata(decAnyMetadata(miniMetadata.data));
3759
+ try {
3760
+ const scaleBuilder = getDynamicBuilder(getLookupFn(metadata));
3761
+ const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
3762
+ const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
3763
+ chainId
3764
+ }) : moduleMethodOrFn;
3765
+ try {
3766
+ return [[key, scaleBuilder.buildStorage(module, method)]];
3767
+ } catch (cause) {
3768
+ log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
3769
+ return [];
3770
+ }
3771
+ }));
3772
+ return builtCoders;
3773
+ } catch (cause) {
3774
+ log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
3775
+ }
3776
+ return null;
3907
3777
  };
3908
3778
 
3909
- const subscribeBalances$5 = ({
3910
- networkId,
3911
- tokensWithAddresses,
3912
- connector,
3913
- miniMetadata
3914
- }) => {
3915
- const balanceDefs = getBalanceDefs(tokensWithAddresses);
3916
- const queries = buildQueries$6(networkId, balanceDefs, miniMetadata);
3917
- return getRpcQueryPack$(connector, networkId, queries).pipe(map(balances => ({
3918
- success: balances,
3919
- errors: []
3920
- })));
3779
+ /**
3780
+ * Decodes & unwraps outputs and errors of a given result, contract, and method.
3781
+ * Parsed error message can be found in `decodedOutput` if `isError` is true.
3782
+ * SOURCE: https://github.com/paritytech/contracts-ui (GPL-3.0-only)
3783
+ */
3784
+ function decodeOutput({
3785
+ result
3786
+ }, registry, abi, method) {
3787
+ let output;
3788
+ let decodedOutput = "";
3789
+ let isError = true;
3790
+ if (result.isOk) {
3791
+ const flags = result.asOk.flags.toHuman();
3792
+ isError = flags.includes("Revert");
3793
+ const abiMessage = getAbiMessage(abi, method);
3794
+ const returnType = abiMessage.returnType;
3795
+ const returnTypeName = getReturnTypeName(returnType);
3796
+ const r = returnType ? registry.createTypeUnsafe(returnTypeName, [result.asOk.data]).toHuman() : "()";
3797
+ output = isOk(r) ? r.Ok : isErr(r) ? r.Err : r;
3798
+ const errorText = isErr(output) ? typeof output.Err === "object" ? JSON.stringify(output.Err, null, 2) : output.Err?.toString() ?? "Error" : output !== "Ok" ? output?.toString() || "Error" : "Error";
3799
+ const okText = isOk(r) ? typeof output === "object" ? JSON.stringify(output, null, "\t") : output?.toString() ?? "()" : JSON.stringify(output, null, "\t") ?? "()";
3800
+ decodedOutput = isError ? errorText : okText;
3801
+ } else if (result.isErr) {
3802
+ output = result.toHuman();
3803
+ let errorText;
3804
+ if (isErr(output) && typeof output.Err === "object" && Object.keys(output.Err || {}).length && typeof Object.values(output.Err || {})[0] === "string") {
3805
+ const [errorKey, errorValue] = Object.entries(output.Err || {})[0];
3806
+ errorText = `${errorKey}${errorValue}`;
3807
+ }
3808
+ decodedOutput = errorText || "Error";
3809
+ }
3810
+ return {
3811
+ output,
3812
+ decodedOutput,
3813
+ isError
3814
+ };
3815
+ }
3816
+
3817
+ /**
3818
+ * Helper types & functions
3819
+ * SOURCE: https://github.com/paritytech/contracts-ui (GPL-3.0-only)
3820
+ */
3821
+
3822
+ function isErr(o) {
3823
+ return typeof o === "object" && o !== null && "Err" in o;
3824
+ }
3825
+ function isOk(o) {
3826
+ return typeof o === "object" && o !== null && "Ok" in o;
3827
+ }
3828
+ function getReturnTypeName(type) {
3829
+ return type?.lookupName || type?.type || "";
3830
+ }
3831
+ function getAbiMessage(abi, method) {
3832
+ const abiMessage = abi.messages.find(m => stringCamelCase(m.method) === stringCamelCase(method));
3833
+ if (!abiMessage) {
3834
+ throw new Error(`"${method}" not found in Contract`);
3835
+ }
3836
+ return abiMessage;
3837
+ }
3838
+
3839
+ /**
3840
+ *
3841
+ * Detect Balances::transfer -> Balances::transfer_allow_death migration
3842
+ * https://github.com/paritytech/substrate/pull/12951
3843
+ *
3844
+ * `transfer_allow_death` is the preferred method,
3845
+ * so if something goes wrong during detection, we should assume the chain has migrated
3846
+ * @param metadataRpc string containing the hashed RPC metadata for the chain
3847
+ * @returns
3848
+ */
3849
+ const detectTransferMethod = metadataRpc => {
3850
+ const pjsMetadata = new Metadata(new TypeRegistry(), metadataRpc);
3851
+ pjsMetadata.registry.setMetadata(pjsMetadata);
3852
+ const balancesPallet = pjsMetadata.asLatest.pallets.find(pallet => pallet.name.eq("Balances"));
3853
+ const balancesCallsTypeIndex = balancesPallet?.calls.value.type.toNumber();
3854
+ const balancesCallsType = balancesCallsTypeIndex !== undefined ? pjsMetadata.asLatest.lookup.types[balancesCallsTypeIndex] : undefined;
3855
+ const hasDeprecatedTransferCall = balancesCallsType?.type.def.asVariant?.variants.find(variant => variant.name.eq("transfer")) !== undefined;
3856
+ return hasDeprecatedTransferCall ? "transfer" : "transfer_allow_death";
3921
3857
  };
3922
- const buildQueries$6 = (networkId, balanceDefs, miniMetadata) => {
3858
+
3859
+ const getUniqueChainIds = (addressesByToken, tokens) => [...new Set(Object.keys(addressesByToken).map(tokenId => tokens[tokenId]?.networkId).flatMap(chainId => chainId ? [chainId] : []))];
3860
+
3861
+ const makeContractCaller = ({
3862
+ chainConnector,
3863
+ chainId,
3864
+ registry
3865
+ }) => async (callFrom, contractAddress, inputData) => registry.createType("ContractExecResult", await chainConnector.send(chainId, "state_call", ["ContractsApi_call", u8aToHex(u8aConcatStrict([
3866
+ // origin
3867
+ registry.createType("AccountId", callFrom).toU8a(),
3868
+ // dest
3869
+ registry.createType("AccountId", contractAddress).toU8a(),
3870
+ // value
3871
+ registry.createType("Balance", 0).toU8a(),
3872
+ // gasLimit
3873
+ registry.createType("Option<WeightV2>").toU8a(),
3874
+ // storageDepositLimit
3875
+ registry.createType("Option<Balance>").toU8a(),
3876
+ // inputData
3877
+ inputData instanceof Uint8Array ? inputData : inputData.toU8a()]))]));
3878
+
3879
+ /**
3880
+ * Pass some these into an `RpcStateQueryHelper` in order to easily batch multiple state queries into the one rpc call.
3881
+ */
3882
+
3883
+ /**
3884
+ * Used by a variety of balance modules to help batch multiple state queries into the one rpc call.
3885
+ */
3886
+ class RpcStateQueryHelper {
3887
+ #chainConnector;
3888
+ #queries;
3889
+ constructor(chainConnector, queries) {
3890
+ this.#chainConnector = chainConnector;
3891
+ this.#queries = queries;
3892
+ }
3893
+ async subscribe(callback, timeout = false, subscribeMethod = "state_subscribeStorage", responseMethod = "state_storage", unsubscribeMethod = "state_unsubscribeStorage") {
3894
+ const queriesByChain = groupBy(this.#queries, "chainId");
3895
+ const subscriptions = Object.entries(queriesByChain).map(([chainId, queries]) => {
3896
+ const params = [queries.map(({
3897
+ stateKey
3898
+ }) => stateKey)];
3899
+ const unsub = this.#chainConnector.subscribe(chainId, subscribeMethod, responseMethod, params, (error, result) => {
3900
+ error ? callback(error) : callback(null, this.#distributeChangesToQueryDecoders.call(this, chainId, result));
3901
+ }, timeout);
3902
+ return () => unsub.then(unsubscribe => unsubscribe(unsubscribeMethod));
3903
+ });
3904
+ return () => subscriptions.forEach(unsubscribe => unsubscribe());
3905
+ }
3906
+ async fetch(method = "state_queryStorageAt") {
3907
+ const queriesByChain = groupBy(this.#queries, "chainId");
3908
+ const resultsByChain = await Promise.all(Object.entries(queriesByChain).map(async ([chainId, queries]) => {
3909
+ const params = [queries.map(({
3910
+ stateKey
3911
+ }) => stateKey)];
3912
+ const result = (await this.#chainConnector.send(chainId, method, params))[0];
3913
+ return this.#distributeChangesToQueryDecoders.call(this, chainId, result);
3914
+ }));
3915
+ return resultsByChain.flatMap(result => result);
3916
+ }
3917
+ #distributeChangesToQueryDecoders(chainId, result) {
3918
+ if (typeof result !== "object" || result === null) return [];
3919
+ if (!hasOwnProperty(result, "changes") || typeof result.changes !== "object") return [];
3920
+ if (!Array.isArray(result.changes)) return [];
3921
+ return result.changes.flatMap(([reference, change]) => {
3922
+ if (typeof reference !== "string") {
3923
+ log.warn(`Received non-string reference in RPC result: ${reference}`);
3924
+ return [];
3925
+ }
3926
+ if (typeof change !== "string" && change !== null) {
3927
+ log.warn(`Received non-string and non-null change in RPC result: ${reference} | ${change}`);
3928
+ return [];
3929
+ }
3930
+ const query = this.#queries.find(({
3931
+ chainId: cId,
3932
+ stateKey
3933
+ }) => cId === chainId && stateKey === reference);
3934
+ if (!query) {
3935
+ log.warn(`Failed to find query:\n${reference} in\n${this.#queries.map(({
3936
+ stateKey
3937
+ }) => stateKey)}`);
3938
+ return [];
3939
+ }
3940
+ return [query.decodeResult(change)];
3941
+ });
3942
+ }
3943
+ }
3944
+
3945
+ const configureStore = (dbTable = db.balancesBlob) => ({
3946
+ persistData: async balances => {
3947
+ const output = compress(balances);
3948
+ await dbTable.clear();
3949
+ await dbTable.put({
3950
+ data: output,
3951
+ id: Date.now().toString()
3952
+ });
3953
+ },
3954
+ retrieveData: async () => {
3955
+ const compressedData = await dbTable.toCollection().first();
3956
+ if (!compressedData) return [];
3957
+ return decompress(compressedData.data);
3958
+ }
3959
+ });
3960
+ const compress = balances => pako.deflate(JSON.stringify(balances));
3961
+ const decompress = data => {
3962
+ const decompressed = pako.inflate(data, {
3963
+ to: "string"
3964
+ });
3965
+ return JSON.parse(decompressed);
3966
+ };
3967
+
3968
+ const buildQueries$5 = (networkId, balanceDefs, miniMetadata) => {
3923
3969
  const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
3924
- storage: ["Assets", "Account"]
3970
+ storage: ["ForeignAssets", "Account"]
3925
3971
  });
3926
3972
  return balanceDefs.map(({
3927
3973
  token,
3928
3974
  address
3929
3975
  }) => {
3930
3976
  const scaleCoder = networkStorageCoders?.storage;
3931
- const stateKey = tryEncode$1(scaleCoder, Number(token.assetId), address) ??
3932
- // Asset Hub
3933
- tryEncode$1(scaleCoder, BigInt(token.assetId), address); // Astar
3934
-
3977
+ const getStateKey = onChainId => {
3978
+ try {
3979
+ return scaleCoder?.keys?.enc?.(papiParse(onChainId), address);
3980
+ } catch {
3981
+ return null;
3982
+ }
3983
+ };
3984
+ const stateKey = getStateKey(token.onChainId);
3935
3985
  if (!stateKey) {
3936
- log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
3986
+ log.warn(`Invalid assetId / address in ${networkId} storage query ${token.onChainId} / ${address}`);
3937
3987
  return null;
3938
- } else log.log(`VALID assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
3988
+ }
3939
3989
  const decodeResult = changes => {
3940
3990
  /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3941
3991
 
3942
3992
  const decoded = decodeScale(scaleCoder, changes[0], `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
3943
3993
  balance: 0n,
3994
+ is_frozen: false,
3944
3995
  status: {
3945
3996
  type: "Liquid"
3946
3997
  }};
3947
- const isFrozen = decoded?.status?.type === "Frozen";
3998
+ const isFrozen = decoded.is_frozen ?? decoded?.status?.type === "Frozen";
3948
3999
  const amount = (decoded?.balance ?? 0n).toString();
3949
4000
 
3950
4001
  // due to the following balance calculations, which are made in the `Balance` type:
@@ -3985,32 +4036,6 @@ const buildQueries$6 = (networkId, balanceDefs, miniMetadata) => {
3985
4036
  };
3986
4037
  }).filter(isNotNil);
3987
4038
  };
3988
- const tryEncode$1 = (scaleCoder, ...args) => {
3989
- try {
3990
- return scaleCoder?.keys?.enc?.(...args);
3991
- } catch {
3992
- return null;
3993
- }
3994
- };
3995
-
3996
- const SubAssetsBalanceModule = {
3997
- type: MODULE_TYPE$5,
3998
- platform: PLATFORM$5,
3999
- getMiniMetadata: getMiniMetadata$6,
4000
- fetchTokens: fetchTokens$5,
4001
- fetchBalances: fetchBalances$6,
4002
- subscribeBalances: subscribeBalances$5,
4003
- getTransferCallData: getTransferCallData$5
4004
- };
4005
-
4006
- // to be used by chaindata too
4007
- const SubAssetsTokenConfigSchema = z.strictObject({
4008
- assetId: SubAssetsTokenSchema.shape.assetId,
4009
- ...TokenConfigBaseSchema.shape
4010
- });
4011
-
4012
- const MODULE_TYPE$4 = SubForeignAssetsTokenSchema.shape.type.value;
4013
- const PLATFORM$4 = SubForeignAssetsTokenSchema.shape.platform.value;
4014
4039
 
4015
4040
  const fetchBalances$5 = async ({
4016
4041
  networkId,
@@ -4018,6 +4043,10 @@ const fetchBalances$5 = async ({
4018
4043
  connector,
4019
4044
  miniMetadata
4020
4045
  }) => {
4046
+ if (!tokensWithAddresses.length) return {
4047
+ success: [],
4048
+ errors: []
4049
+ };
4021
4050
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
4022
4051
  if (!miniMetadata?.data) {
4023
4052
  log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$4} balances on ${networkId}.`);
@@ -4053,7 +4082,7 @@ const fetchBalances$5 = async ({
4053
4082
  };
4054
4083
  }
4055
4084
  const queries = buildQueries$5(networkId, balanceDefs, miniMetadata);
4056
- const balances = await new RpcStateQueryHelper(connector, queries).fetch();
4085
+ const balances = await fetchRpcQueryPack(connector, networkId, queries);
4057
4086
  return balanceDefs.reduce((acc, def) => {
4058
4087
  const balance = balances.find(b => b?.address === def.address && b?.tokenId === def.token.id);
4059
4088
  if (balance) acc.success.push(balance);
@@ -4062,95 +4091,23 @@ const fetchBalances$5 = async ({
4062
4091
  address: def.address,
4063
4092
  networkId,
4064
4093
  tokenId: def.token.id,
4065
- source: MODULE_TYPE$4,
4066
- status: "live",
4067
- values: [{
4068
- type: "free",
4069
- label: "free",
4070
- amount: "0"
4071
- }, {
4072
- type: "locked",
4073
- label: "frozen",
4074
- amount: "0"
4075
- }]
4076
- });
4077
- return acc;
4078
- }, {
4079
- success: [],
4080
- errors: []
4081
- });
4082
- };
4083
- const buildQueries$5 = (networkId, balanceDefs, miniMetadata) => {
4084
- const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
4085
- storage: ["ForeignAssets", "Account"]
4086
- });
4087
- return balanceDefs.map(({
4088
- token,
4089
- address
4090
- }) => {
4091
- const scaleCoder = networkStorageCoders?.storage;
4092
- const getStateKey = onChainId => {
4093
- try {
4094
- return scaleCoder?.keys?.enc?.(papiParse(onChainId), address);
4095
- } catch {
4096
- return null;
4097
- }
4098
- };
4099
- const stateKey = getStateKey(token.onChainId);
4100
- if (!stateKey) {
4101
- log.warn(`Invalid assetId / address in ${networkId} storage query ${token.onChainId} / ${address}`);
4102
- return null;
4103
- }
4104
- const decodeResult = change => {
4105
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4106
-
4107
- const decoded = decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
4108
- balance: 0n,
4109
- is_frozen: false,
4110
- status: {
4111
- type: "Liquid"
4112
- }};
4113
- const isFrozen = decoded.is_frozen ?? decoded?.status?.type === "Frozen";
4114
- const amount = (decoded?.balance ?? 0n).toString();
4115
-
4116
- // due to the following balance calculations, which are made in the `Balance` type:
4117
- //
4118
- // total balance = (free balance) + (reserved balance)
4119
- // transferable balance = (free balance) - (frozen balance)
4120
- //
4121
- // when `isFrozen` is true we need to set **both** the `free` and `frozen` amounts
4122
- // of this balance to the value we received from the RPC.
4123
- //
4124
- // if we only set the `frozen` amount, then the `total` calculation will be incorrect!
4125
- const free = amount;
4126
- const frozen = token.isFrozen || isFrozen ? amount : "0";
4127
-
4128
- // include balance values even if zero, so that newly-zero values overwrite old values
4129
- const balanceValues = [{
4130
- type: "free",
4131
- label: "free",
4132
- amount: free.toString()
4133
- }, {
4134
- type: "locked",
4135
- label: "frozen",
4136
- amount: frozen.toString()
4137
- }];
4138
- const balance = {
4139
- source: "substrate-assets",
4140
- status: "live",
4141
- address,
4142
- networkId,
4143
- tokenId: token.id,
4144
- values: balanceValues
4145
- };
4146
- return balance;
4147
- };
4148
- return {
4149
- chainId: networkId,
4150
- stateKey,
4151
- decodeResult
4152
- };
4153
- }).filter(isNotNil);
4094
+ source: MODULE_TYPE$4,
4095
+ status: "live",
4096
+ values: [{
4097
+ type: "free",
4098
+ label: "free",
4099
+ amount: "0"
4100
+ }, {
4101
+ type: "locked",
4102
+ label: "frozen",
4103
+ amount: "0"
4104
+ }]
4105
+ });
4106
+ return acc;
4107
+ }, {
4108
+ success: [],
4109
+ errors: []
4110
+ });
4154
4111
  };
4155
4112
 
4156
4113
  const fetchTokens$4 = async ({
@@ -4349,46 +4306,22 @@ const getTransferAllEncodedArgs$1 = (onChainId, to, codec) => {
4349
4306
  })]);
4350
4307
  };
4351
4308
 
4352
- const SUBSCRIPTION_INTERVAL$3 = 6_000;
4353
4309
  const subscribeBalances$4 = ({
4354
4310
  networkId,
4355
4311
  tokensWithAddresses,
4356
4312
  connector,
4357
4313
  miniMetadata
4358
4314
  }) => {
4359
- return new Observable(subscriber => {
4360
- const abortController = new AbortController();
4361
-
4362
- // on hydration balances are fetched using a runtimeApi, which can't be subscribed to.
4363
- // => poll values for each block
4364
- const poll = async () => {
4365
- try {
4366
- if (abortController.signal.aborted) return;
4367
- const balances = await fetchBalances$5({
4368
- networkId,
4369
- tokensWithAddresses: tokensWithAddresses,
4370
- connector,
4371
- miniMetadata
4372
- });
4373
- if (abortController.signal.aborted) return;
4374
- subscriber.next(balances);
4375
- setTimeout(poll, SUBSCRIPTION_INTERVAL$3);
4376
- } catch (error) {
4377
- log.error("Error", {
4378
- module: MODULE_TYPE$4,
4379
- networkId,
4380
- miniMetadata,
4381
- addressesByToken: tokensWithAddresses,
4382
- error
4383
- });
4384
- subscriber.error(error);
4385
- }
4386
- };
4387
- poll();
4388
- return () => {
4389
- abortController.abort();
4390
- };
4391
- }).pipe(distinctUntilChanged(isEqual));
4315
+ if (!tokensWithAddresses.length) return of({
4316
+ success: [],
4317
+ errors: []
4318
+ });
4319
+ const balanceDefs = getBalanceDefs(tokensWithAddresses);
4320
+ const queries = buildQueries$5(networkId, balanceDefs, miniMetadata);
4321
+ return getRpcQueryPack$(connector, networkId, queries).pipe(map(balances => ({
4322
+ success: balances,
4323
+ errors: []
4324
+ })));
4392
4325
  };
4393
4326
 
4394
4327
  const SubForeignAssetsBalanceModule = {
@@ -4422,9 +4355,15 @@ const fetchBalances$4 = async ({
4422
4355
  connector,
4423
4356
  miniMetadata
4424
4357
  }) => {
4358
+ if (!tokensWithAddresses.length) return {
4359
+ success: [],
4360
+ errors: []
4361
+ };
4425
4362
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
4426
4363
  if (!miniMetadata?.data) {
4427
- log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$3} balances on ${networkId}.`);
4364
+ log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$3} balances on ${networkId}.`, {
4365
+ tokensWithAddresses
4366
+ });
4428
4367
  return {
4429
4368
  success: [],
4430
4369
  errors: balanceDefs.map(def => ({
@@ -4480,7 +4419,7 @@ const fetchBalances$4 = async ({
4480
4419
  networkId,
4481
4420
  tokenId: token.id,
4482
4421
  source: MODULE_TYPE$3,
4483
- status: "cache",
4422
+ status: "live",
4484
4423
  values: [{
4485
4424
  type: "free",
4486
4425
  label: "free",
@@ -4671,13 +4610,17 @@ const getTransferCallData$3 = ({
4671
4610
  };
4672
4611
  };
4673
4612
 
4674
- const SUBSCRIPTION_INTERVAL$2 = 6_000;
4613
+ const SUBSCRIPTION_INTERVAL$1 = 6_000;
4675
4614
  const subscribeBalances$3 = ({
4676
4615
  networkId,
4677
4616
  tokensWithAddresses,
4678
4617
  connector,
4679
4618
  miniMetadata
4680
4619
  }) => {
4620
+ if (!tokensWithAddresses.length) return of({
4621
+ success: [],
4622
+ errors: []
4623
+ });
4681
4624
  return new Observable(subscriber => {
4682
4625
  const abortController = new AbortController();
4683
4626
 
@@ -4694,7 +4637,7 @@ const subscribeBalances$3 = ({
4694
4637
  });
4695
4638
  if (abortController.signal.aborted) return;
4696
4639
  subscriber.next(balances);
4697
- setTimeout(poll, SUBSCRIPTION_INTERVAL$2);
4640
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$1);
4698
4641
  } catch (error) {
4699
4642
  log.error("Error", {
4700
4643
  module: MODULE_TYPE$3,
@@ -5443,6 +5386,10 @@ const fetchBalances$3 = async ({
5443
5386
  connector,
5444
5387
  miniMetadata
5445
5388
  }) => {
5389
+ if (!tokensWithAddresses.length) return {
5390
+ success: [],
5391
+ errors: []
5392
+ };
5446
5393
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
5447
5394
  if (!miniMetadata?.data) {
5448
5395
  log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$2} balances on ${networkId}.`);
@@ -5724,6 +5671,11 @@ const subscribeBalances$2 = ({
5724
5671
  connector,
5725
5672
  miniMetadata
5726
5673
  }) => {
5674
+ if (!tokensWithAddresses.length) return of({
5675
+ success: [],
5676
+ errors: []
5677
+ });
5678
+
5727
5679
  // could be use as shared observable key if we decide to cache the sub
5728
5680
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
5729
5681
  const baseQueries = buildBaseQueries(networkId, balanceDefs, miniMetadata);
@@ -6927,6 +6879,10 @@ const fetchBalances$2 = async ({
6927
6879
  tokensWithAddresses,
6928
6880
  connector
6929
6881
  }) => {
6882
+ if (!tokensWithAddresses.length) return {
6883
+ success: [],
6884
+ errors: []
6885
+ };
6930
6886
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
6931
6887
  if (!balanceDefs.length) return {
6932
6888
  success: [],
@@ -7114,13 +7070,17 @@ const getTransferCallData$1 = async ({
7114
7070
  };
7115
7071
  };
7116
7072
 
7117
- const SUBSCRIPTION_INTERVAL$1 = 6_000;
7073
+ const SUBSCRIPTION_INTERVAL = 6_000;
7118
7074
  const subscribeBalances$1 = ({
7119
7075
  networkId,
7120
7076
  tokensWithAddresses,
7121
7077
  connector,
7122
7078
  miniMetadata
7123
7079
  }) => {
7080
+ if (!tokensWithAddresses.length) return of({
7081
+ success: [],
7082
+ errors: []
7083
+ });
7124
7084
  return new Observable(subscriber => {
7125
7085
  const abortController = new AbortController();
7126
7086
 
@@ -7137,7 +7097,7 @@ const subscribeBalances$1 = ({
7137
7097
  });
7138
7098
  if (abortController.signal.aborted) return;
7139
7099
  subscriber.next(balances);
7140
- setTimeout(poll, SUBSCRIPTION_INTERVAL$1);
7100
+ setTimeout(poll, SUBSCRIPTION_INTERVAL);
7141
7101
  } catch (error) {
7142
7102
  log.error("Error", {
7143
7103
  module: MODULE_TYPE$1,
@@ -7175,12 +7135,77 @@ const SubPsp22TokenConfigSchema = z.strictObject({
7175
7135
  const MODULE_TYPE = SubTokensTokenSchema.shape.type.value;
7176
7136
  const PLATFORM = SubTokensTokenSchema.shape.platform.value;
7177
7137
 
7138
+ const buildQueries$4 = (networkId, balanceDefs, miniMetadata) => {
7139
+ const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
7140
+ storage: [miniMetadata.extra.palletId, "Accounts"]
7141
+ });
7142
+ return balanceDefs.map(({
7143
+ token,
7144
+ address
7145
+ }) => {
7146
+ const scaleCoder = networkStorageCoders?.storage;
7147
+ const getStateKey = onChainId => {
7148
+ try {
7149
+ return scaleCoder.keys.enc(address, papiParse(onChainId));
7150
+ } catch {
7151
+ return null;
7152
+ }
7153
+ };
7154
+ const stateKey = getStateKey(token.onChainId);
7155
+ if (!stateKey) {
7156
+ log.warn(`Invalid assetId / address in ${networkId} storage query ${token.onChainId} / ${address}`);
7157
+ return null;
7158
+ }
7159
+ const decodeResult = changes => {
7160
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
7161
+
7162
+ const decoded = decodeScale(scaleCoder, changes[0], `Failed to decode substrate-tokens balance on chain ${networkId}`) ?? {
7163
+ free: 0n,
7164
+ reserved: 0n,
7165
+ frozen: 0n
7166
+ };
7167
+ const free = (decoded?.free ?? 0n).toString();
7168
+ const reserved = (decoded?.reserved ?? 0n).toString();
7169
+ const frozen = (decoded?.frozen ?? 0n).toString();
7170
+ const balanceValues = [{
7171
+ type: "free",
7172
+ label: "free",
7173
+ amount: free.toString()
7174
+ }, {
7175
+ type: "reserved",
7176
+ label: "reserved",
7177
+ amount: reserved.toString()
7178
+ }, {
7179
+ type: "locked",
7180
+ label: "frozen",
7181
+ amount: frozen.toString()
7182
+ }];
7183
+ return {
7184
+ source: "substrate-tokens",
7185
+ status: "live",
7186
+ address,
7187
+ networkId,
7188
+ tokenId: token.id,
7189
+ values: balanceValues
7190
+ };
7191
+ };
7192
+ return {
7193
+ stateKeys: [stateKey],
7194
+ decodeResult
7195
+ };
7196
+ }).filter(isNotNil);
7197
+ };
7198
+
7178
7199
  const fetchBalances$1 = async ({
7179
7200
  networkId,
7180
7201
  tokensWithAddresses,
7181
7202
  connector,
7182
7203
  miniMetadata
7183
7204
  }) => {
7205
+ if (!tokensWithAddresses.length) return {
7206
+ success: [],
7207
+ errors: []
7208
+ };
7184
7209
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
7185
7210
  if (!miniMetadata?.data) {
7186
7211
  log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE} balances on ${networkId}.`);
@@ -7216,7 +7241,7 @@ const fetchBalances$1 = async ({
7216
7241
  };
7217
7242
  }
7218
7243
  const queries = buildQueries$4(networkId, balanceDefs, miniMetadata);
7219
- const balances = await new RpcStateQueryHelper(connector, queries).fetch();
7244
+ const balances = await fetchRpcQueryPack(connector, networkId, queries);
7220
7245
  return balanceDefs.reduce((acc, def) => {
7221
7246
  const balance = balances.find(b => b?.address === def.address && b?.tokenId === def.token.id);
7222
7247
  if (balance) acc.success.push(balance);
@@ -7243,67 +7268,6 @@ const fetchBalances$1 = async ({
7243
7268
  errors: []
7244
7269
  });
7245
7270
  };
7246
- const buildQueries$4 = (networkId, balanceDefs, miniMetadata) => {
7247
- const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
7248
- storage: [miniMetadata.extra.palletId, "Accounts"]
7249
- });
7250
- return balanceDefs.map(({
7251
- token,
7252
- address
7253
- }) => {
7254
- const scaleCoder = networkStorageCoders?.storage;
7255
- const getStateKey = onChainId => {
7256
- try {
7257
- return scaleCoder.keys.enc(address, papiParse(onChainId));
7258
- } catch {
7259
- return null;
7260
- }
7261
- };
7262
- const stateKey = getStateKey(token.onChainId);
7263
- if (!stateKey) {
7264
- log.warn(`Invalid assetId / address in ${networkId} storage query ${token.onChainId} / ${address}`);
7265
- return null;
7266
- }
7267
- const decodeResult = change => {
7268
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
7269
-
7270
- const decoded = decodeScale(scaleCoder, change, `Failed to decode substrate-tokens balance on chain ${networkId}`) ?? {
7271
- free: 0n,
7272
- reserved: 0n,
7273
- frozen: 0n
7274
- };
7275
- const free = (decoded?.free ?? 0n).toString();
7276
- const reserved = (decoded?.reserved ?? 0n).toString();
7277
- const frozen = (decoded?.frozen ?? 0n).toString();
7278
- const balanceValues = [{
7279
- type: "free",
7280
- label: "free",
7281
- amount: free.toString()
7282
- }, {
7283
- type: "reserved",
7284
- label: "reserved",
7285
- amount: reserved.toString()
7286
- }, {
7287
- type: "locked",
7288
- label: "frozen",
7289
- amount: frozen.toString()
7290
- }];
7291
- return {
7292
- source: "substrate-tokens",
7293
- status: "live",
7294
- address,
7295
- networkId,
7296
- tokenId: token.id,
7297
- values: balanceValues
7298
- };
7299
- };
7300
- return {
7301
- chainId: networkId,
7302
- stateKey,
7303
- decodeResult
7304
- };
7305
- }).filter(isNotNil);
7306
- };
7307
7271
 
7308
7272
  const fetchTokens = async ({
7309
7273
  networkId,
@@ -7504,46 +7468,22 @@ const getCallDataOptions = (to, token, value, type, config) => {
7504
7468
  }] : []));
7505
7469
  };
7506
7470
 
7507
- const SUBSCRIPTION_INTERVAL = 6_000;
7508
7471
  const subscribeBalances = ({
7509
7472
  networkId,
7510
7473
  tokensWithAddresses,
7511
7474
  connector,
7512
7475
  miniMetadata
7513
7476
  }) => {
7514
- return new Observable(subscriber => {
7515
- const abortController = new AbortController();
7516
-
7517
- // on hydration balances are fetched using a runtimeApi, which can't be subscribed to.
7518
- // => poll values for each block
7519
- const poll = async () => {
7520
- try {
7521
- if (abortController.signal.aborted) return;
7522
- const balances = await fetchBalances$1({
7523
- networkId,
7524
- tokensWithAddresses: tokensWithAddresses,
7525
- connector,
7526
- miniMetadata
7527
- });
7528
- if (abortController.signal.aborted) return;
7529
- subscriber.next(balances);
7530
- setTimeout(poll, SUBSCRIPTION_INTERVAL);
7531
- } catch (error) {
7532
- log.error("Error", {
7533
- module: MODULE_TYPE,
7534
- networkId,
7535
- miniMetadata,
7536
- addressesByToken: tokensWithAddresses,
7537
- error
7538
- });
7539
- subscriber.error(error);
7540
- }
7541
- };
7542
- poll();
7543
- return () => {
7544
- abortController.abort();
7545
- };
7546
- }).pipe(distinctUntilChanged(isEqual));
7477
+ if (!tokensWithAddresses.length) return of({
7478
+ success: [],
7479
+ errors: []
7480
+ });
7481
+ const balanceDefs = getBalanceDefs(tokensWithAddresses);
7482
+ const queries = buildQueries$4(networkId, balanceDefs, miniMetadata);
7483
+ return getRpcQueryPack$(connector, networkId, queries).pipe(map(balances => ({
7484
+ success: balances,
7485
+ errors: []
7486
+ })));
7547
7487
  };
7548
7488
 
7549
7489
  const SubTokensBalanceModule = {
@@ -10413,14 +10353,18 @@ class BalancesProvider {
10413
10353
  acc[networkId][tokenId] = addresses;
10414
10354
  return acc;
10415
10355
  }, {});
10416
- return combineLatest(toPairs(addressesByTokenIdByNetworkId).map(([networkId]) => this.getNetworkBalances$(networkId, addressesByTokenIdByNetworkId[networkId]))).pipe(map(results => {
10417
- return {
10418
- status: results.some(({
10419
- status
10420
- }) => status === "initialising") ? "initialising" : "live",
10421
- balances: results.flatMap(result => result.balances)
10422
- };
10423
- }), startWith({
10356
+ return combineLatest({
10357
+ isStale: timer(30_000).pipe(map(() => true), startWith(false)),
10358
+ results: combineLatest(toPairs(addressesByTokenIdByNetworkId).map(([networkId]) => this.getNetworkBalances$(networkId, addressesByTokenIdByNetworkId[networkId])))
10359
+ }).pipe(map(({
10360
+ isStale,
10361
+ results
10362
+ }) => ({
10363
+ status: !isStale && results.some(({
10364
+ status
10365
+ }) => status === "initialising") ? "initialising" : "live",
10366
+ balances: results.flatMap(result => result.balances)
10367
+ })), startWith({
10424
10368
  status: "initialising",
10425
10369
  balances: this.getStoredBalances(addressesByTokenId)
10426
10370
  }), distinctUntilChanged(isEqual));