@talismn/balances 0.0.0-pr2075-20250708125508 → 0.0.0-pr2075-20250708151500

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, timer, switchMap, from, firstValueFrom, combineLatest, map, scan, share, switchAll, mergeMap, toArray, interval, startWith, exhaustMap, BehaviorSubject, debounceTime, takeUntil, withLatestFrom, concatMap, 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, papiParse, papiStringify, toHex, 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) {
@@ -1766,12 +1770,16 @@ const getTransferCallData$8 = ({
1766
1770
  };
1767
1771
  };
1768
1772
 
1769
- const SUBSCRIPTION_INTERVAL$7 = 6_000;
1773
+ const SUBSCRIPTION_INTERVAL$4 = 6_000;
1770
1774
  const subscribeBalances$8 = ({
1771
1775
  networkId,
1772
1776
  tokensWithAddresses,
1773
1777
  connector
1774
1778
  }) => {
1779
+ if (!tokensWithAddresses.length) return of({
1780
+ success: [],
1781
+ errors: []
1782
+ });
1775
1783
  return new Observable(subscriber => {
1776
1784
  const abortController = new AbortController();
1777
1785
  const poll = async () => {
@@ -1784,7 +1792,7 @@ const subscribeBalances$8 = ({
1784
1792
  });
1785
1793
  if (abortController.signal.aborted) return;
1786
1794
  subscriber.next(balances);
1787
- setTimeout(poll, SUBSCRIPTION_INTERVAL$7);
1795
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$4);
1788
1796
  } catch (error) {
1789
1797
  log.error("Error", {
1790
1798
  module: MODULE_TYPE$8,
@@ -1830,6 +1838,10 @@ const fetchBalances$b = async ({
1830
1838
  tokensWithAddresses,
1831
1839
  connector
1832
1840
  }) => {
1841
+ if (!tokensWithAddresses.length) return {
1842
+ success: [],
1843
+ errors: []
1844
+ };
1833
1845
  const client = await connector.getPublicClientForEvmNetwork(networkId);
1834
1846
  if (!client) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
1835
1847
  for (const [token, addresses] of tokensWithAddresses) {
@@ -1980,12 +1992,16 @@ const getTransferCallData$7 = ({
1980
1992
  };
1981
1993
  };
1982
1994
 
1983
- const SUBSCRIPTION_INTERVAL$6 = 6_000;
1995
+ const SUBSCRIPTION_INTERVAL$3 = 6_000;
1984
1996
  const subscribeBalances$7 = ({
1985
1997
  networkId,
1986
1998
  tokensWithAddresses,
1987
1999
  connector
1988
2000
  }) => {
2001
+ if (!tokensWithAddresses.length) return of({
2002
+ success: [],
2003
+ errors: []
2004
+ });
1989
2005
  return new Observable(subscriber => {
1990
2006
  const abortController = new AbortController();
1991
2007
  const poll = async () => {
@@ -1998,7 +2014,7 @@ const subscribeBalances$7 = ({
1998
2014
  });
1999
2015
  if (abortController.signal.aborted) return;
2000
2016
  subscriber.next(balances);
2001
- setTimeout(poll, SUBSCRIPTION_INTERVAL$6);
2017
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$3);
2002
2018
  } catch (error) {
2003
2019
  log.error("Error", {
2004
2020
  module: MODULE_TYPE$7,
@@ -2039,6 +2055,10 @@ const fetchBalances$a = async ({
2039
2055
  tokensWithAddresses,
2040
2056
  connector
2041
2057
  }) => {
2058
+ if (!tokensWithAddresses.length) return {
2059
+ success: [],
2060
+ errors: []
2061
+ };
2042
2062
  const client = await connector.getPublicClientForEvmNetwork(networkId);
2043
2063
  if (!client) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
2044
2064
  for (const [token, addresses] of tokensWithAddresses) {
@@ -2313,12 +2333,16 @@ const getTransferCallData$6 = ({
2313
2333
  };
2314
2334
  };
2315
2335
 
2316
- const SUBSCRIPTION_INTERVAL$5 = 6_000;
2336
+ const SUBSCRIPTION_INTERVAL$2 = 6_000;
2317
2337
  const subscribeBalances$6 = ({
2318
2338
  networkId,
2319
2339
  tokensWithAddresses,
2320
2340
  connector
2321
2341
  }) => {
2342
+ if (!tokensWithAddresses.length) return of({
2343
+ success: [],
2344
+ errors: []
2345
+ });
2322
2346
  return new Observable(subscriber => {
2323
2347
  const abortController = new AbortController();
2324
2348
  const poll = async () => {
@@ -2331,7 +2355,7 @@ const subscribeBalances$6 = ({
2331
2355
  });
2332
2356
  if (abortController.signal.aborted) return;
2333
2357
  subscriber.next(balances);
2334
- setTimeout(poll, SUBSCRIPTION_INTERVAL$5);
2358
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$2);
2335
2359
  } catch (error) {
2336
2360
  log.error("Error", {
2337
2361
  module: MODULE_TYPE$6,
@@ -3195,273 +3219,138 @@ async function getPoolBalance(publicClient, contractAddress, accountAddress) {
3195
3219
  const MODULE_TYPE$5 = SubAssetsTokenSchema.shape.type.value;
3196
3220
  const PLATFORM$5 = SubAssetsTokenSchema.shape.platform.value;
3197
3221
 
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
- */
3222
+ const fetchRpcQueryPack = async (connector, networkId, queries) => {
3223
+ const allStateKeys = queries.flatMap(({
3224
+ stateKeys
3225
+ }) => stateKeys).filter(isNotNil);
3202
3226
 
3203
- async function balances(balanceModule, addressesByToken, callback) {
3204
- // subscription request
3205
- if (callback !== undefined) return await balanceModule.subscribeBalances({
3206
- addressesByToken
3207
- }, callback);
3227
+ // doing a query without keys would throw an error => return early
3228
+ if (!allStateKeys.length) return queries.map(({
3229
+ stateKeys,
3230
+ decodeResult
3231
+ }) => decodeResult(stateKeys.map(() => null)));
3232
+ const [result] = await connector.send(networkId, "state_queryStorageAt", [allStateKeys]);
3233
+ return decodeRpcQueryPack(queries, result);
3234
+ };
3235
+ const getRpcQueryPack$ = (connector, networkId, queries, timeout = false) => {
3236
+ const allStateKeys = queries.flatMap(({
3237
+ stateKeys
3238
+ }) => stateKeys).filter(isNotNil);
3208
3239
 
3209
- // one-off request
3210
- return await balanceModule.fetchBalances(addressesByToken);
3211
- }
3240
+ // doing a query without keys would throw an error => return early
3241
+ if (!allStateKeys.length) return of(queries.map(({
3242
+ stateKeys,
3243
+ decodeResult
3244
+ }) => decodeResult(stateKeys.map(() => null))));
3245
+ return new Observable(subscriber => {
3246
+ const promUnsub = connector.subscribe(networkId, "state_subscribeStorage", "state_storage", [allStateKeys], (error, result) => {
3247
+ if (error) subscriber.error(error);else subscriber.next(decodeRpcQueryPack(queries, result));
3248
+ }, timeout);
3249
+ return () => {
3250
+ promUnsub.then(unsub => unsub("state_unsubscribeStorage"));
3251
+ };
3252
+ });
3253
+ };
3254
+ const decodeRpcQueryPack = (queries, result) => {
3255
+ return queries.reduce((acc, {
3256
+ stateKeys,
3257
+ decodeResult
3258
+ }) => {
3259
+ const changes = stateKeys.map(stateKey => {
3260
+ if (!stateKey) return null;
3261
+ const change = result.changes.find(([key]) => key === stateKey);
3262
+ if (!change) return null;
3263
+ return change[1];
3264
+ });
3265
+ acc.push(decodeResult(changes));
3266
+ return acc;
3267
+ }, []);
3268
+ };
3212
3269
 
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]
3270
+ const buildQueries$6 = (networkId, balanceDefs, miniMetadata) => {
3271
+ const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
3272
+ storage: ["Assets", "Account"]
3273
+ });
3274
+ return balanceDefs.map(({
3275
+ token,
3276
+ address
3277
+ }) => {
3278
+ const scaleCoder = networkStorageCoders?.storage;
3279
+ const stateKey = tryEncode$1(scaleCoder, Number(token.assetId), address) ??
3280
+ // Asset Hub
3281
+ tryEncode$1(scaleCoder, BigInt(token.assetId), address); // Astar
3246
3282
 
3247
- const buildNetworkStorageCoders = (chainId, miniMetadata, coders) => {
3248
- if (!miniMetadata.data) return null;
3249
- const metadata = unifyMetadata(decAnyMetadata(miniMetadata.data));
3283
+ if (!stateKey) {
3284
+ log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
3285
+ return null;
3286
+ }
3287
+ const decodeResult = changes => {
3288
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3289
+
3290
+ const decoded = decodeScale(scaleCoder, changes[0], `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
3291
+ balance: 0n,
3292
+ status: {
3293
+ type: "Liquid"
3294
+ }};
3295
+ const isFrozen = decoded?.status?.type === "Frozen";
3296
+ const amount = (decoded?.balance ?? 0n).toString();
3297
+
3298
+ // due to the following balance calculations, which are made in the `Balance` type:
3299
+ //
3300
+ // total balance = (free balance) + (reserved balance)
3301
+ // transferable balance = (free balance) - (frozen balance)
3302
+ //
3303
+ // when `isFrozen` is true we need to set **both** the `free` and `frozen` amounts
3304
+ // of this balance to the value we received from the RPC.
3305
+ //
3306
+ // if we only set the `frozen` amount, then the `total` calculation will be incorrect!
3307
+ const free = amount;
3308
+ const frozen = token.isFrozen || isFrozen ? amount : "0";
3309
+
3310
+ // include balance values even if zero, so that newly-zero values overwrite old values
3311
+ const balanceValues = [{
3312
+ type: "free",
3313
+ label: "free",
3314
+ amount: free.toString()
3315
+ }, {
3316
+ type: "locked",
3317
+ label: "frozen",
3318
+ amount: frozen.toString()
3319
+ }];
3320
+ const balance = {
3321
+ source: "substrate-assets",
3322
+ status: "live",
3323
+ address,
3324
+ networkId,
3325
+ tokenId: token.id,
3326
+ values: balanceValues
3327
+ };
3328
+ return balance;
3329
+ };
3330
+ return {
3331
+ stateKeys: [stateKey],
3332
+ decodeResult
3333
+ };
3334
+ }).filter(isNotNil);
3335
+ };
3336
+ const tryEncode$1 = (scaleCoder, ...args) => {
3250
3337
  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);
3338
+ return scaleCoder?.keys?.enc?.(...args);
3339
+ } catch {
3340
+ return null;
3266
3341
  }
3267
- return null;
3268
3342
  };
3269
3343
 
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
3344
+ const fetchBalances$6 = async ({
3345
+ networkId,
3346
+ tokensWithAddresses,
3347
+ connector,
3348
+ miniMetadata
3349
+ }) => {
3350
+ if (!tokensWithAddresses.length) return {
3351
+ success: [],
3352
+ errors: []
3305
3353
  };
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
3354
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
3466
3355
  if (!miniMetadata?.data) {
3467
3356
  log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$5} balances on ${networkId}.`);
@@ -3497,7 +3386,7 @@ const fetchBalances$6 = async ({
3497
3386
  };
3498
3387
  }
3499
3388
  const queries = buildQueries$6(networkId, balanceDefs, miniMetadata);
3500
- const balances = await new RpcStateQueryHelper(connector, queries).fetch();
3389
+ const balances = await fetchRpcQueryPack(connector, networkId, queries);
3501
3390
  return balanceDefs.reduce((acc, def) => {
3502
3391
  const balance = balances.find(b => b?.address === def.address && b?.tokenId === def.token.id);
3503
3392
  if (balance) acc.success.push(balance);
@@ -3524,80 +3413,6 @@ const fetchBalances$6 = async ({
3524
3413
  errors: []
3525
3414
  });
3526
3415
  };
3527
- const buildQueries$6 = (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$1(scaleCoder, Number(token.assetId), address) ??
3537
- // Asset Hub
3538
- tryEncode$1(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$1 = (scaleCoder, ...args) => {
3595
- try {
3596
- return scaleCoder?.keys?.enc?.(...args);
3597
- } catch {
3598
- return null;
3599
- }
3600
- };
3601
3416
 
3602
3417
  const fetchTokens$5 = async ({
3603
3418
  networkId,
@@ -3834,47 +3649,47 @@ const getTransferAllEncodedArgs$2 = (assetId, to, codec) => {
3834
3649
  })]);
3835
3650
  };
3836
3651
 
3837
- const SUBSCRIPTION_INTERVAL$4 = 6_000;
3652
+ const fetchRuntimeCallResult = async (connector, networkId, metadataRpc, apiName, method, args) => {
3653
+ const {
3654
+ builder
3655
+ } = parseMetadataRpc(metadataRpc);
3656
+ const call = builder.buildRuntimeCall(apiName, method);
3657
+ const hex = await connector.send(networkId, "state_call", [`${apiName}_${method}`, toHex(call.args.enc(args))]);
3658
+ return call.value.dec(hex);
3659
+ };
3660
+
3661
+ const tryGetConstantValue = (metadataRpc, pallet, constant) => {
3662
+ const {
3663
+ unifiedMetadata,
3664
+ builder
3665
+ } = parseMetadataRpc(metadataRpc);
3666
+ const encodedValue = unifiedMetadata.pallets.find(({
3667
+ name
3668
+ }) => name === pallet)?.constants.find(({
3669
+ name
3670
+ }) => name === constant)?.value;
3671
+ if (!encodedValue) return null;
3672
+ const codec = builder.buildConstant(pallet, constant);
3673
+ return codec.dec(encodedValue);
3674
+ };
3675
+
3838
3676
  const subscribeBalances$5 = ({
3839
3677
  networkId,
3840
3678
  tokensWithAddresses,
3841
3679
  connector,
3842
3680
  miniMetadata
3843
3681
  }) => {
3844
- return new Observable(subscriber => {
3845
- const abortController = new AbortController();
3846
-
3847
- // on hydration balances are fetched using a runtimeApi, which can't be subscribed to.
3848
- // => poll values for each block
3849
- const poll = async () => {
3850
- try {
3851
- if (abortController.signal.aborted) return;
3852
- const balances = await fetchBalances$6({
3853
- networkId,
3854
- tokensWithAddresses: tokensWithAddresses,
3855
- connector,
3856
- miniMetadata
3857
- });
3858
- if (abortController.signal.aborted) return;
3859
- subscriber.next(balances);
3860
- setTimeout(poll, SUBSCRIPTION_INTERVAL$4);
3861
- } catch (error) {
3862
- log.error("Error", {
3863
- module: MODULE_TYPE$5,
3864
- networkId,
3865
- miniMetadata,
3866
- addressesByToken: tokensWithAddresses,
3867
- error
3868
- });
3869
- subscriber.error(error);
3870
- }
3871
- };
3872
- poll();
3873
- return () => {
3874
- abortController.abort();
3875
- };
3876
- }).pipe(distinctUntilChanged(isEqual));
3877
- };
3682
+ if (!tokensWithAddresses.length) return of({
3683
+ success: [],
3684
+ errors: []
3685
+ });
3686
+ const balanceDefs = getBalanceDefs(tokensWithAddresses);
3687
+ const queries = buildQueries$6(networkId, balanceDefs, miniMetadata);
3688
+ return getRpcQueryPack$(connector, networkId, queries).pipe(map(balances => ({
3689
+ success: balances,
3690
+ errors: []
3691
+ })));
3692
+ };
3878
3693
 
3879
3694
  const SubAssetsBalanceModule = {
3880
3695
  type: MODULE_TYPE$5,
@@ -3895,74 +3710,267 @@ const SubAssetsTokenConfigSchema = z.strictObject({
3895
3710
  const MODULE_TYPE$4 = SubForeignAssetsTokenSchema.shape.type.value;
3896
3711
  const PLATFORM$4 = SubForeignAssetsTokenSchema.shape.platform.value;
3897
3712
 
3898
- const fetchBalances$5 = async ({
3899
- networkId,
3900
- tokensWithAddresses,
3901
- connector,
3902
- miniMetadata
3903
- }) => {
3904
- const balanceDefs = getBalanceDefs(tokensWithAddresses);
3905
- if (!miniMetadata?.data) {
3906
- log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$4} balances on ${networkId}.`);
3907
- return {
3908
- success: [],
3909
- errors: balanceDefs.map(def => ({
3910
- tokenId: def.token.id,
3911
- address: def.address,
3912
- error: new Error("Minimetadata is required for fetching balances")
3913
- }))
3914
- };
3713
+ /**
3714
+ * Wraps a BalanceModule's fetch/subscribe methods with a single `balances` method.
3715
+ * This `balances` method will subscribe if a callback parameter is provided, or otherwise fetch.
3716
+ */
3717
+
3718
+ async function balances(balanceModule, addressesByToken, callback) {
3719
+ // subscription request
3720
+ if (callback !== undefined) return await balanceModule.subscribeBalances({
3721
+ addressesByToken
3722
+ }, callback);
3723
+
3724
+ // one-off request
3725
+ return await balanceModule.fetchBalances(addressesByToken);
3726
+ }
3727
+
3728
+ // TODO remove this one in favor of the network specific one below
3729
+ const buildStorageCoders = ({
3730
+ chainIds,
3731
+ chains,
3732
+ miniMetadatas,
3733
+ coders
3734
+ }) => new Map([...chainIds].flatMap(chainId => {
3735
+ const chain = chains[chainId];
3736
+ if (!chain) return [];
3737
+ const miniMetadata = miniMetadatas.get(chainId); // findMiniMetadata<TBalanceModule>(miniMetadatas, moduleType, chain)
3738
+ if (!miniMetadata) return [];
3739
+ if (!miniMetadata.data) return [];
3740
+ const metadata = unifyMetadata(decAnyMetadata(miniMetadata.data));
3741
+ try {
3742
+ const scaleBuilder = getDynamicBuilder(getLookupFn(metadata));
3743
+ const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
3744
+ const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
3745
+ chainId
3746
+ }) : moduleMethodOrFn;
3747
+ try {
3748
+ return [[key, scaleBuilder.buildStorage(module, method)]];
3749
+ } catch (cause) {
3750
+ log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
3751
+ return [];
3752
+ }
3753
+ }));
3754
+ return [[chainId, builtCoders]];
3755
+ } catch (cause) {
3756
+ log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
3757
+ return [];
3915
3758
  }
3916
- if (miniMetadata.source !== MODULE_TYPE$4) {
3917
- log.warn(`Ignoring miniMetadata with source ${miniMetadata.source} in ${MODULE_TYPE$4}.`);
3918
- return {
3919
- success: [],
3920
- errors: balanceDefs.map(def => ({
3921
- tokenId: def.token.id,
3922
- address: def.address,
3923
- error: new Error(`Invalid request: miniMetadata source is not ${MODULE_TYPE$4}`)
3924
- }))
3925
- };
3759
+ }));
3760
+ // type StorageCoder<TCoders extends NetworkCoders> = ReturnType<ReturnType<typeof getDynamicBuilder>["buildStorage"]>[keyof TCoders]
3761
+
3762
+ const buildNetworkStorageCoders = (chainId, miniMetadata, coders) => {
3763
+ if (!miniMetadata.data) return null;
3764
+ const metadata = unifyMetadata(decAnyMetadata(miniMetadata.data));
3765
+ try {
3766
+ const scaleBuilder = getDynamicBuilder(getLookupFn(metadata));
3767
+ const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
3768
+ const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
3769
+ chainId
3770
+ }) : moduleMethodOrFn;
3771
+ try {
3772
+ return [[key, scaleBuilder.buildStorage(module, method)]];
3773
+ } catch (cause) {
3774
+ log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
3775
+ return [];
3776
+ }
3777
+ }));
3778
+ return builtCoders;
3779
+ } catch (cause) {
3780
+ log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
3926
3781
  }
3927
- if (miniMetadata.chainId !== networkId) {
3928
- log.warn(`Ignoring miniMetadata with chainId ${miniMetadata.chainId} in ${MODULE_TYPE$4}. Expected chainId is ${networkId}`);
3929
- return {
3930
- success: [],
3931
- errors: balanceDefs.map(def => ({
3932
- tokenId: def.token.id,
3933
- address: def.address,
3934
- error: new Error(`Invalid request: Expected chainId is ${networkId}`)
3935
- }))
3936
- };
3782
+ return null;
3783
+ };
3784
+
3785
+ /**
3786
+ * Decodes & unwraps outputs and errors of a given result, contract, and method.
3787
+ * Parsed error message can be found in `decodedOutput` if `isError` is true.
3788
+ * SOURCE: https://github.com/paritytech/contracts-ui (GPL-3.0-only)
3789
+ */
3790
+ function decodeOutput({
3791
+ result
3792
+ }, registry, abi, method) {
3793
+ let output;
3794
+ let decodedOutput = "";
3795
+ let isError = true;
3796
+ if (result.isOk) {
3797
+ const flags = result.asOk.flags.toHuman();
3798
+ isError = flags.includes("Revert");
3799
+ const abiMessage = getAbiMessage(abi, method);
3800
+ const returnType = abiMessage.returnType;
3801
+ const returnTypeName = getReturnTypeName(returnType);
3802
+ const r = returnType ? registry.createTypeUnsafe(returnTypeName, [result.asOk.data]).toHuman() : "()";
3803
+ output = isOk(r) ? r.Ok : isErr(r) ? r.Err : r;
3804
+ const errorText = isErr(output) ? typeof output.Err === "object" ? JSON.stringify(output.Err, null, 2) : output.Err?.toString() ?? "Error" : output !== "Ok" ? output?.toString() || "Error" : "Error";
3805
+ const okText = isOk(r) ? typeof output === "object" ? JSON.stringify(output, null, "\t") : output?.toString() ?? "()" : JSON.stringify(output, null, "\t") ?? "()";
3806
+ decodedOutput = isError ? errorText : okText;
3807
+ } else if (result.isErr) {
3808
+ output = result.toHuman();
3809
+ let errorText;
3810
+ if (isErr(output) && typeof output.Err === "object" && Object.keys(output.Err || {}).length && typeof Object.values(output.Err || {})[0] === "string") {
3811
+ const [errorKey, errorValue] = Object.entries(output.Err || {})[0];
3812
+ errorText = `${errorKey}${errorValue}`;
3813
+ }
3814
+ decodedOutput = errorText || "Error";
3937
3815
  }
3938
- const queries = buildQueries$5(networkId, balanceDefs, miniMetadata);
3939
- const balances = await new RpcStateQueryHelper(connector, queries).fetch();
3940
- return balanceDefs.reduce((acc, def) => {
3941
- const balance = balances.find(b => b?.address === def.address && b?.tokenId === def.token.id);
3942
- if (balance) acc.success.push(balance);
3943
- //if no entry consider empty balance
3944
- else acc.success.push({
3945
- address: def.address,
3946
- networkId,
3947
- tokenId: def.token.id,
3948
- source: MODULE_TYPE$4,
3949
- status: "live",
3950
- values: [{
3951
- type: "free",
3952
- label: "free",
3953
- amount: "0"
3954
- }, {
3955
- type: "locked",
3956
- label: "frozen",
3957
- amount: "0"
3958
- }]
3816
+ return {
3817
+ output,
3818
+ decodedOutput,
3819
+ isError
3820
+ };
3821
+ }
3822
+
3823
+ /**
3824
+ * Helper types & functions
3825
+ * SOURCE: https://github.com/paritytech/contracts-ui (GPL-3.0-only)
3826
+ */
3827
+
3828
+ function isErr(o) {
3829
+ return typeof o === "object" && o !== null && "Err" in o;
3830
+ }
3831
+ function isOk(o) {
3832
+ return typeof o === "object" && o !== null && "Ok" in o;
3833
+ }
3834
+ function getReturnTypeName(type) {
3835
+ return type?.lookupName || type?.type || "";
3836
+ }
3837
+ function getAbiMessage(abi, method) {
3838
+ const abiMessage = abi.messages.find(m => stringCamelCase(m.method) === stringCamelCase(method));
3839
+ if (!abiMessage) {
3840
+ throw new Error(`"${method}" not found in Contract`);
3841
+ }
3842
+ return abiMessage;
3843
+ }
3844
+
3845
+ /**
3846
+ *
3847
+ * Detect Balances::transfer -> Balances::transfer_allow_death migration
3848
+ * https://github.com/paritytech/substrate/pull/12951
3849
+ *
3850
+ * `transfer_allow_death` is the preferred method,
3851
+ * so if something goes wrong during detection, we should assume the chain has migrated
3852
+ * @param metadataRpc string containing the hashed RPC metadata for the chain
3853
+ * @returns
3854
+ */
3855
+ const detectTransferMethod = metadataRpc => {
3856
+ const pjsMetadata = new Metadata(new TypeRegistry(), metadataRpc);
3857
+ pjsMetadata.registry.setMetadata(pjsMetadata);
3858
+ const balancesPallet = pjsMetadata.asLatest.pallets.find(pallet => pallet.name.eq("Balances"));
3859
+ const balancesCallsTypeIndex = balancesPallet?.calls.value.type.toNumber();
3860
+ const balancesCallsType = balancesCallsTypeIndex !== undefined ? pjsMetadata.asLatest.lookup.types[balancesCallsTypeIndex] : undefined;
3861
+ const hasDeprecatedTransferCall = balancesCallsType?.type.def.asVariant?.variants.find(variant => variant.name.eq("transfer")) !== undefined;
3862
+ return hasDeprecatedTransferCall ? "transfer" : "transfer_allow_death";
3863
+ };
3864
+
3865
+ const getUniqueChainIds = (addressesByToken, tokens) => [...new Set(Object.keys(addressesByToken).map(tokenId => tokens[tokenId]?.networkId).flatMap(chainId => chainId ? [chainId] : []))];
3866
+
3867
+ const makeContractCaller = ({
3868
+ chainConnector,
3869
+ chainId,
3870
+ registry
3871
+ }) => async (callFrom, contractAddress, inputData) => registry.createType("ContractExecResult", await chainConnector.send(chainId, "state_call", ["ContractsApi_call", u8aToHex(u8aConcatStrict([
3872
+ // origin
3873
+ registry.createType("AccountId", callFrom).toU8a(),
3874
+ // dest
3875
+ registry.createType("AccountId", contractAddress).toU8a(),
3876
+ // value
3877
+ registry.createType("Balance", 0).toU8a(),
3878
+ // gasLimit
3879
+ registry.createType("Option<WeightV2>").toU8a(),
3880
+ // storageDepositLimit
3881
+ registry.createType("Option<Balance>").toU8a(),
3882
+ // inputData
3883
+ inputData instanceof Uint8Array ? inputData : inputData.toU8a()]))]));
3884
+
3885
+ /**
3886
+ * Pass some these into an `RpcStateQueryHelper` in order to easily batch multiple state queries into the one rpc call.
3887
+ */
3888
+
3889
+ /**
3890
+ * Used by a variety of balance modules to help batch multiple state queries into the one rpc call.
3891
+ */
3892
+ class RpcStateQueryHelper {
3893
+ #chainConnector;
3894
+ #queries;
3895
+ constructor(chainConnector, queries) {
3896
+ this.#chainConnector = chainConnector;
3897
+ this.#queries = queries;
3898
+ }
3899
+ async subscribe(callback, timeout = false, subscribeMethod = "state_subscribeStorage", responseMethod = "state_storage", unsubscribeMethod = "state_unsubscribeStorage") {
3900
+ const queriesByChain = groupBy(this.#queries, "chainId");
3901
+ const subscriptions = Object.entries(queriesByChain).map(([chainId, queries]) => {
3902
+ const params = [queries.map(({
3903
+ stateKey
3904
+ }) => stateKey)];
3905
+ const unsub = this.#chainConnector.subscribe(chainId, subscribeMethod, responseMethod, params, (error, result) => {
3906
+ error ? callback(error) : callback(null, this.#distributeChangesToQueryDecoders.call(this, chainId, result));
3907
+ }, timeout);
3908
+ return () => unsub.then(unsubscribe => unsubscribe(unsubscribeMethod));
3959
3909
  });
3960
- return acc;
3961
- }, {
3962
- success: [],
3963
- errors: []
3910
+ return () => subscriptions.forEach(unsubscribe => unsubscribe());
3911
+ }
3912
+ async fetch(method = "state_queryStorageAt") {
3913
+ const queriesByChain = groupBy(this.#queries, "chainId");
3914
+ const resultsByChain = await Promise.all(Object.entries(queriesByChain).map(async ([chainId, queries]) => {
3915
+ const params = [queries.map(({
3916
+ stateKey
3917
+ }) => stateKey)];
3918
+ const result = (await this.#chainConnector.send(chainId, method, params))[0];
3919
+ return this.#distributeChangesToQueryDecoders.call(this, chainId, result);
3920
+ }));
3921
+ return resultsByChain.flatMap(result => result);
3922
+ }
3923
+ #distributeChangesToQueryDecoders(chainId, result) {
3924
+ if (typeof result !== "object" || result === null) return [];
3925
+ if (!hasOwnProperty(result, "changes") || typeof result.changes !== "object") return [];
3926
+ if (!Array.isArray(result.changes)) return [];
3927
+ return result.changes.flatMap(([reference, change]) => {
3928
+ if (typeof reference !== "string") {
3929
+ log.warn(`Received non-string reference in RPC result: ${reference}`);
3930
+ return [];
3931
+ }
3932
+ if (typeof change !== "string" && change !== null) {
3933
+ log.warn(`Received non-string and non-null change in RPC result: ${reference} | ${change}`);
3934
+ return [];
3935
+ }
3936
+ const query = this.#queries.find(({
3937
+ chainId: cId,
3938
+ stateKey
3939
+ }) => cId === chainId && stateKey === reference);
3940
+ if (!query) {
3941
+ log.warn(`Failed to find query:\n${reference} in\n${this.#queries.map(({
3942
+ stateKey
3943
+ }) => stateKey)}`);
3944
+ return [];
3945
+ }
3946
+ return [query.decodeResult(change)];
3947
+ });
3948
+ }
3949
+ }
3950
+
3951
+ const configureStore = (dbTable = db.balancesBlob) => ({
3952
+ persistData: async balances => {
3953
+ const output = compress(balances);
3954
+ await dbTable.clear();
3955
+ await dbTable.put({
3956
+ data: output,
3957
+ id: Date.now().toString()
3958
+ });
3959
+ },
3960
+ retrieveData: async () => {
3961
+ const compressedData = await dbTable.toCollection().first();
3962
+ if (!compressedData) return [];
3963
+ return decompress(compressedData.data);
3964
+ }
3965
+ });
3966
+ const compress = balances => pako.deflate(JSON.stringify(balances));
3967
+ const decompress = data => {
3968
+ const decompressed = pako.inflate(data, {
3969
+ to: "string"
3964
3970
  });
3971
+ return JSON.parse(decompressed);
3965
3972
  };
3973
+
3966
3974
  const buildQueries$5 = (networkId, balanceDefs, miniMetadata) => {
3967
3975
  const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
3968
3976
  storage: ["ForeignAssets", "Account"]
@@ -3984,10 +3992,10 @@ const buildQueries$5 = (networkId, balanceDefs, miniMetadata) => {
3984
3992
  log.warn(`Invalid assetId / address in ${networkId} storage query ${token.onChainId} / ${address}`);
3985
3993
  return null;
3986
3994
  }
3987
- const decodeResult = change => {
3995
+ const decodeResult = changes => {
3988
3996
  /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3989
3997
 
3990
- const decoded = decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
3998
+ const decoded = decodeScale(scaleCoder, changes[0], `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
3991
3999
  balance: 0n,
3992
4000
  is_frozen: false,
3993
4001
  status: {
@@ -4029,13 +4037,85 @@ const buildQueries$5 = (networkId, balanceDefs, miniMetadata) => {
4029
4037
  return balance;
4030
4038
  };
4031
4039
  return {
4032
- chainId: networkId,
4033
- stateKey,
4040
+ stateKeys: [stateKey],
4034
4041
  decodeResult
4035
4042
  };
4036
4043
  }).filter(isNotNil);
4037
4044
  };
4038
4045
 
4046
+ const fetchBalances$5 = async ({
4047
+ networkId,
4048
+ tokensWithAddresses,
4049
+ connector,
4050
+ miniMetadata
4051
+ }) => {
4052
+ if (!tokensWithAddresses.length) return {
4053
+ success: [],
4054
+ errors: []
4055
+ };
4056
+ const balanceDefs = getBalanceDefs(tokensWithAddresses);
4057
+ if (!miniMetadata?.data) {
4058
+ log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$4} balances on ${networkId}.`);
4059
+ return {
4060
+ success: [],
4061
+ errors: balanceDefs.map(def => ({
4062
+ tokenId: def.token.id,
4063
+ address: def.address,
4064
+ error: new Error("Minimetadata is required for fetching balances")
4065
+ }))
4066
+ };
4067
+ }
4068
+ if (miniMetadata.source !== MODULE_TYPE$4) {
4069
+ log.warn(`Ignoring miniMetadata with source ${miniMetadata.source} in ${MODULE_TYPE$4}.`);
4070
+ return {
4071
+ success: [],
4072
+ errors: balanceDefs.map(def => ({
4073
+ tokenId: def.token.id,
4074
+ address: def.address,
4075
+ error: new Error(`Invalid request: miniMetadata source is not ${MODULE_TYPE$4}`)
4076
+ }))
4077
+ };
4078
+ }
4079
+ if (miniMetadata.chainId !== networkId) {
4080
+ log.warn(`Ignoring miniMetadata with chainId ${miniMetadata.chainId} in ${MODULE_TYPE$4}. Expected chainId is ${networkId}`);
4081
+ return {
4082
+ success: [],
4083
+ errors: balanceDefs.map(def => ({
4084
+ tokenId: def.token.id,
4085
+ address: def.address,
4086
+ error: new Error(`Invalid request: Expected chainId is ${networkId}`)
4087
+ }))
4088
+ };
4089
+ }
4090
+ const queries = buildQueries$5(networkId, balanceDefs, miniMetadata);
4091
+ const balances = await fetchRpcQueryPack(connector, networkId, queries);
4092
+ return balanceDefs.reduce((acc, def) => {
4093
+ const balance = balances.find(b => b?.address === def.address && b?.tokenId === def.token.id);
4094
+ if (balance) acc.success.push(balance);
4095
+ //if no entry consider empty balance
4096
+ else acc.success.push({
4097
+ address: def.address,
4098
+ networkId,
4099
+ tokenId: def.token.id,
4100
+ source: MODULE_TYPE$4,
4101
+ status: "live",
4102
+ values: [{
4103
+ type: "free",
4104
+ label: "free",
4105
+ amount: "0"
4106
+ }, {
4107
+ type: "locked",
4108
+ label: "frozen",
4109
+ amount: "0"
4110
+ }]
4111
+ });
4112
+ return acc;
4113
+ }, {
4114
+ success: [],
4115
+ errors: []
4116
+ });
4117
+ };
4118
+
4039
4119
  const fetchTokens$4 = async ({
4040
4120
  networkId,
4041
4121
  tokens,
@@ -4232,46 +4312,22 @@ const getTransferAllEncodedArgs$1 = (onChainId, to, codec) => {
4232
4312
  })]);
4233
4313
  };
4234
4314
 
4235
- const SUBSCRIPTION_INTERVAL$3 = 6_000;
4236
4315
  const subscribeBalances$4 = ({
4237
4316
  networkId,
4238
4317
  tokensWithAddresses,
4239
4318
  connector,
4240
4319
  miniMetadata
4241
4320
  }) => {
4242
- return new Observable(subscriber => {
4243
- const abortController = new AbortController();
4244
-
4245
- // on hydration balances are fetched using a runtimeApi, which can't be subscribed to.
4246
- // => poll values for each block
4247
- const poll = async () => {
4248
- try {
4249
- if (abortController.signal.aborted) return;
4250
- const balances = await fetchBalances$5({
4251
- networkId,
4252
- tokensWithAddresses: tokensWithAddresses,
4253
- connector,
4254
- miniMetadata
4255
- });
4256
- if (abortController.signal.aborted) return;
4257
- subscriber.next(balances);
4258
- setTimeout(poll, SUBSCRIPTION_INTERVAL$3);
4259
- } catch (error) {
4260
- log.error("Error", {
4261
- module: MODULE_TYPE$4,
4262
- networkId,
4263
- miniMetadata,
4264
- addressesByToken: tokensWithAddresses,
4265
- error
4266
- });
4267
- subscriber.error(error);
4268
- }
4269
- };
4270
- poll();
4271
- return () => {
4272
- abortController.abort();
4273
- };
4274
- }).pipe(distinctUntilChanged(isEqual));
4321
+ if (!tokensWithAddresses.length) return of({
4322
+ success: [],
4323
+ errors: []
4324
+ });
4325
+ const balanceDefs = getBalanceDefs(tokensWithAddresses);
4326
+ const queries = buildQueries$5(networkId, balanceDefs, miniMetadata);
4327
+ return getRpcQueryPack$(connector, networkId, queries).pipe(map(balances => ({
4328
+ success: balances,
4329
+ errors: []
4330
+ })));
4275
4331
  };
4276
4332
 
4277
4333
  const SubForeignAssetsBalanceModule = {
@@ -4290,48 +4346,30 @@ const SubForeignAssetsTokenConfigSchema = z.strictObject({
4290
4346
  ...TokenConfigBaseSchema.shape
4291
4347
  });
4292
4348
 
4293
- // to be used by chaindata too
4294
- const SubHydrationTokenConfigSchema = z.strictObject({
4295
- onChainId: SubHydrationTokenSchema.shape.onChainId,
4296
- ...TokenConfigBaseSchema.shape
4297
- });
4298
-
4299
- const MODULE_TYPE$3 = SubHydrationTokenSchema.shape.type.value;
4300
- const PLATFORM$3 = SubHydrationTokenSchema.shape.platform.value;
4301
-
4302
- const fetchRuntimeCallResult = async (connector, networkId, metadataRpc, apiName, method, args) => {
4303
- const {
4304
- builder
4305
- } = parseMetadataRpc(metadataRpc);
4306
- const call = builder.buildRuntimeCall(apiName, method);
4307
- const hex = await connector.send(networkId, "state_call", [`${apiName}_${method}`, toHex(call.args.enc(args))]);
4308
- return call.value.dec(hex);
4309
- };
4310
-
4311
- const tryGetConstantValue = (metadataRpc, pallet, constant) => {
4312
- const {
4313
- unifiedMetadata,
4314
- builder
4315
- } = parseMetadataRpc(metadataRpc);
4316
- const encodedValue = unifiedMetadata.pallets.find(({
4317
- name
4318
- }) => name === pallet)?.constants.find(({
4319
- name
4320
- }) => name === constant)?.value;
4321
- if (!encodedValue) return null;
4322
- const codec = builder.buildConstant(pallet, constant);
4323
- return codec.dec(encodedValue);
4324
- };
4325
-
4349
+ // to be used by chaindata too
4350
+ const SubHydrationTokenConfigSchema = z.strictObject({
4351
+ onChainId: SubHydrationTokenSchema.shape.onChainId,
4352
+ ...TokenConfigBaseSchema.shape
4353
+ });
4354
+
4355
+ const MODULE_TYPE$3 = SubHydrationTokenSchema.shape.type.value;
4356
+ const PLATFORM$3 = SubHydrationTokenSchema.shape.platform.value;
4357
+
4326
4358
  const fetchBalances$4 = async ({
4327
4359
  networkId,
4328
4360
  tokensWithAddresses,
4329
4361
  connector,
4330
4362
  miniMetadata
4331
4363
  }) => {
4364
+ if (!tokensWithAddresses.length) return {
4365
+ success: [],
4366
+ errors: []
4367
+ };
4332
4368
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
4333
4369
  if (!miniMetadata?.data) {
4334
- log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$3} balances on ${networkId}.`);
4370
+ log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$3} balances on ${networkId}.`, {
4371
+ tokensWithAddresses
4372
+ });
4335
4373
  return {
4336
4374
  success: [],
4337
4375
  errors: balanceDefs.map(def => ({
@@ -4578,13 +4616,17 @@ const getTransferCallData$3 = ({
4578
4616
  };
4579
4617
  };
4580
4618
 
4581
- const SUBSCRIPTION_INTERVAL$2 = 6_000;
4619
+ const SUBSCRIPTION_INTERVAL$1 = 6_000;
4582
4620
  const subscribeBalances$3 = ({
4583
4621
  networkId,
4584
4622
  tokensWithAddresses,
4585
4623
  connector,
4586
4624
  miniMetadata
4587
4625
  }) => {
4626
+ if (!tokensWithAddresses.length) return of({
4627
+ success: [],
4628
+ errors: []
4629
+ });
4588
4630
  return new Observable(subscriber => {
4589
4631
  const abortController = new AbortController();
4590
4632
 
@@ -4601,7 +4643,7 @@ const subscribeBalances$3 = ({
4601
4643
  });
4602
4644
  if (abortController.signal.aborted) return;
4603
4645
  subscriber.next(balances);
4604
- setTimeout(poll, SUBSCRIPTION_INTERVAL$2);
4646
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$1);
4605
4647
  } catch (error) {
4606
4648
  log.error("Error", {
4607
4649
  module: MODULE_TYPE$3,
@@ -4633,54 +4675,6 @@ const SubHydrationBalanceModule = {
4633
4675
  const MODULE_TYPE$2 = SubNativeTokenSchema.shape.type.value;
4634
4676
  const PLATFORM$2 = SubNativeTokenSchema.shape.platform.value;
4635
4677
 
4636
- const fetchRpcQueryPack = async (connector, networkId, queries) => {
4637
- const allStateKeys = queries.flatMap(({
4638
- stateKeys
4639
- }) => stateKeys).filter(isNotNil);
4640
-
4641
- // doing a query without keys would throw an error => return early
4642
- if (!allStateKeys.length) return queries.map(({
4643
- stateKeys,
4644
- decodeResult
4645
- }) => decodeResult(stateKeys.map(() => null)));
4646
- const [result] = await connector.send(networkId, "state_queryStorageAt", [allStateKeys]);
4647
- return decodeRpcQueryPack(queries, result);
4648
- };
4649
- const getRpcQueryPack$ = (connector, networkId, queries, timeout = false) => {
4650
- const allStateKeys = queries.flatMap(({
4651
- stateKeys
4652
- }) => stateKeys).filter(isNotNil);
4653
-
4654
- // doing a query without keys would throw an error => return early
4655
- if (!allStateKeys.length) return of(queries.map(({
4656
- stateKeys,
4657
- decodeResult
4658
- }) => decodeResult(stateKeys.map(() => null))));
4659
- return new Observable(subscriber => {
4660
- const promUnsub = connector.subscribe(networkId, "state_subscribeStorage", "state_storage", [allStateKeys], (error, result) => {
4661
- if (error) subscriber.error(error);else subscriber.next(decodeRpcQueryPack(queries, result));
4662
- }, timeout);
4663
- return () => {
4664
- promUnsub.then(unsub => unsub("state_unsubscribeStorage"));
4665
- };
4666
- });
4667
- };
4668
- const decodeRpcQueryPack = (queries, result) => {
4669
- return queries.reduce((acc, {
4670
- stateKeys,
4671
- decodeResult
4672
- }) => {
4673
- const changes = stateKeys.map(stateKey => {
4674
- if (!stateKey) return null;
4675
- const change = result.changes.find(([key]) => key === stateKey);
4676
- if (!change) return null;
4677
- return change[1];
4678
- });
4679
- acc.push(decodeResult(changes));
4680
- return acc;
4681
- }, []);
4682
- };
4683
-
4684
4678
  const SUBTENSOR_ROOT_NETUID$1 = 0;
4685
4679
  const SUBTENSOR_MIN_STAKE_AMOUNT_PLANK$1 = 1000000n;
4686
4680
  const TAO_DECIMALS$1 = 9n;
@@ -5398,6 +5392,10 @@ const fetchBalances$3 = async ({
5398
5392
  connector,
5399
5393
  miniMetadata
5400
5394
  }) => {
5395
+ if (!tokensWithAddresses.length) return {
5396
+ success: [],
5397
+ errors: []
5398
+ };
5401
5399
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
5402
5400
  if (!miniMetadata?.data) {
5403
5401
  log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$2} balances on ${networkId}.`);
@@ -5679,6 +5677,11 @@ const subscribeBalances$2 = ({
5679
5677
  connector,
5680
5678
  miniMetadata
5681
5679
  }) => {
5680
+ if (!tokensWithAddresses.length) return of({
5681
+ success: [],
5682
+ errors: []
5683
+ });
5684
+
5682
5685
  // could be use as shared observable key if we decide to cache the sub
5683
5686
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
5684
5687
  const baseQueries = buildBaseQueries(networkId, balanceDefs, miniMetadata);
@@ -6882,6 +6885,10 @@ const fetchBalances$2 = async ({
6882
6885
  tokensWithAddresses,
6883
6886
  connector
6884
6887
  }) => {
6888
+ if (!tokensWithAddresses.length) return {
6889
+ success: [],
6890
+ errors: []
6891
+ };
6885
6892
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
6886
6893
  if (!balanceDefs.length) return {
6887
6894
  success: [],
@@ -7069,13 +7076,17 @@ const getTransferCallData$1 = async ({
7069
7076
  };
7070
7077
  };
7071
7078
 
7072
- const SUBSCRIPTION_INTERVAL$1 = 6_000;
7079
+ const SUBSCRIPTION_INTERVAL = 6_000;
7073
7080
  const subscribeBalances$1 = ({
7074
7081
  networkId,
7075
7082
  tokensWithAddresses,
7076
7083
  connector,
7077
7084
  miniMetadata
7078
7085
  }) => {
7086
+ if (!tokensWithAddresses.length) return of({
7087
+ success: [],
7088
+ errors: []
7089
+ });
7079
7090
  return new Observable(subscriber => {
7080
7091
  const abortController = new AbortController();
7081
7092
 
@@ -7092,7 +7103,7 @@ const subscribeBalances$1 = ({
7092
7103
  });
7093
7104
  if (abortController.signal.aborted) return;
7094
7105
  subscriber.next(balances);
7095
- setTimeout(poll, SUBSCRIPTION_INTERVAL$1);
7106
+ setTimeout(poll, SUBSCRIPTION_INTERVAL);
7096
7107
  } catch (error) {
7097
7108
  log.error("Error", {
7098
7109
  module: MODULE_TYPE$1,
@@ -7130,12 +7141,77 @@ const SubPsp22TokenConfigSchema = z.strictObject({
7130
7141
  const MODULE_TYPE = SubTokensTokenSchema.shape.type.value;
7131
7142
  const PLATFORM = SubTokensTokenSchema.shape.platform.value;
7132
7143
 
7144
+ const buildQueries$4 = (networkId, balanceDefs, miniMetadata) => {
7145
+ const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
7146
+ storage: [miniMetadata.extra.palletId, "Accounts"]
7147
+ });
7148
+ return balanceDefs.map(({
7149
+ token,
7150
+ address
7151
+ }) => {
7152
+ const scaleCoder = networkStorageCoders?.storage;
7153
+ const getStateKey = onChainId => {
7154
+ try {
7155
+ return scaleCoder.keys.enc(address, papiParse(onChainId));
7156
+ } catch {
7157
+ return null;
7158
+ }
7159
+ };
7160
+ const stateKey = getStateKey(token.onChainId);
7161
+ if (!stateKey) {
7162
+ log.warn(`Invalid assetId / address in ${networkId} storage query ${token.onChainId} / ${address}`);
7163
+ return null;
7164
+ }
7165
+ const decodeResult = changes => {
7166
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
7167
+
7168
+ const decoded = decodeScale(scaleCoder, changes[0], `Failed to decode substrate-tokens balance on chain ${networkId}`) ?? {
7169
+ free: 0n,
7170
+ reserved: 0n,
7171
+ frozen: 0n
7172
+ };
7173
+ const free = (decoded?.free ?? 0n).toString();
7174
+ const reserved = (decoded?.reserved ?? 0n).toString();
7175
+ const frozen = (decoded?.frozen ?? 0n).toString();
7176
+ const balanceValues = [{
7177
+ type: "free",
7178
+ label: "free",
7179
+ amount: free.toString()
7180
+ }, {
7181
+ type: "reserved",
7182
+ label: "reserved",
7183
+ amount: reserved.toString()
7184
+ }, {
7185
+ type: "locked",
7186
+ label: "frozen",
7187
+ amount: frozen.toString()
7188
+ }];
7189
+ return {
7190
+ source: "substrate-tokens",
7191
+ status: "live",
7192
+ address,
7193
+ networkId,
7194
+ tokenId: token.id,
7195
+ values: balanceValues
7196
+ };
7197
+ };
7198
+ return {
7199
+ stateKeys: [stateKey],
7200
+ decodeResult
7201
+ };
7202
+ }).filter(isNotNil);
7203
+ };
7204
+
7133
7205
  const fetchBalances$1 = async ({
7134
7206
  networkId,
7135
7207
  tokensWithAddresses,
7136
7208
  connector,
7137
7209
  miniMetadata
7138
7210
  }) => {
7211
+ if (!tokensWithAddresses.length) return {
7212
+ success: [],
7213
+ errors: []
7214
+ };
7139
7215
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
7140
7216
  if (!miniMetadata?.data) {
7141
7217
  log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE} balances on ${networkId}.`);
@@ -7171,7 +7247,7 @@ const fetchBalances$1 = async ({
7171
7247
  };
7172
7248
  }
7173
7249
  const queries = buildQueries$4(networkId, balanceDefs, miniMetadata);
7174
- const balances = await new RpcStateQueryHelper(connector, queries).fetch();
7250
+ const balances = await fetchRpcQueryPack(connector, networkId, queries);
7175
7251
  return balanceDefs.reduce((acc, def) => {
7176
7252
  const balance = balances.find(b => b?.address === def.address && b?.tokenId === def.token.id);
7177
7253
  if (balance) acc.success.push(balance);
@@ -7198,67 +7274,6 @@ const fetchBalances$1 = async ({
7198
7274
  errors: []
7199
7275
  });
7200
7276
  };
7201
- const buildQueries$4 = (networkId, balanceDefs, miniMetadata) => {
7202
- const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
7203
- storage: [miniMetadata.extra.palletId, "Accounts"]
7204
- });
7205
- return balanceDefs.map(({
7206
- token,
7207
- address
7208
- }) => {
7209
- const scaleCoder = networkStorageCoders?.storage;
7210
- const getStateKey = onChainId => {
7211
- try {
7212
- return scaleCoder.keys.enc(address, papiParse(onChainId));
7213
- } catch {
7214
- return null;
7215
- }
7216
- };
7217
- const stateKey = getStateKey(token.onChainId);
7218
- if (!stateKey) {
7219
- log.warn(`Invalid assetId / address in ${networkId} storage query ${token.onChainId} / ${address}`);
7220
- return null;
7221
- }
7222
- const decodeResult = change => {
7223
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
7224
-
7225
- const decoded = decodeScale(scaleCoder, change, `Failed to decode substrate-tokens balance on chain ${networkId}`) ?? {
7226
- free: 0n,
7227
- reserved: 0n,
7228
- frozen: 0n
7229
- };
7230
- const free = (decoded?.free ?? 0n).toString();
7231
- const reserved = (decoded?.reserved ?? 0n).toString();
7232
- const frozen = (decoded?.frozen ?? 0n).toString();
7233
- const balanceValues = [{
7234
- type: "free",
7235
- label: "free",
7236
- amount: free.toString()
7237
- }, {
7238
- type: "reserved",
7239
- label: "reserved",
7240
- amount: reserved.toString()
7241
- }, {
7242
- type: "locked",
7243
- label: "frozen",
7244
- amount: frozen.toString()
7245
- }];
7246
- return {
7247
- source: "substrate-tokens",
7248
- status: "live",
7249
- address,
7250
- networkId,
7251
- tokenId: token.id,
7252
- values: balanceValues
7253
- };
7254
- };
7255
- return {
7256
- chainId: networkId,
7257
- stateKey,
7258
- decodeResult
7259
- };
7260
- }).filter(isNotNil);
7261
- };
7262
7277
 
7263
7278
  const fetchTokens = async ({
7264
7279
  networkId,
@@ -7459,46 +7474,22 @@ const getCallDataOptions = (to, token, value, type, config) => {
7459
7474
  }] : []));
7460
7475
  };
7461
7476
 
7462
- const SUBSCRIPTION_INTERVAL = 6_000;
7463
7477
  const subscribeBalances = ({
7464
7478
  networkId,
7465
7479
  tokensWithAddresses,
7466
7480
  connector,
7467
7481
  miniMetadata
7468
7482
  }) => {
7469
- return new Observable(subscriber => {
7470
- const abortController = new AbortController();
7471
-
7472
- // on hydration balances are fetched using a runtimeApi, which can't be subscribed to.
7473
- // => poll values for each block
7474
- const poll = async () => {
7475
- try {
7476
- if (abortController.signal.aborted) return;
7477
- const balances = await fetchBalances$1({
7478
- networkId,
7479
- tokensWithAddresses: tokensWithAddresses,
7480
- connector,
7481
- miniMetadata
7482
- });
7483
- if (abortController.signal.aborted) return;
7484
- subscriber.next(balances);
7485
- setTimeout(poll, SUBSCRIPTION_INTERVAL);
7486
- } catch (error) {
7487
- log.error("Error", {
7488
- module: MODULE_TYPE,
7489
- networkId,
7490
- miniMetadata,
7491
- addressesByToken: tokensWithAddresses,
7492
- error
7493
- });
7494
- subscriber.error(error);
7495
- }
7496
- };
7497
- poll();
7498
- return () => {
7499
- abortController.abort();
7500
- };
7501
- }).pipe(distinctUntilChanged(isEqual));
7483
+ if (!tokensWithAddresses.length) return of({
7484
+ success: [],
7485
+ errors: []
7486
+ });
7487
+ const balanceDefs = getBalanceDefs(tokensWithAddresses);
7488
+ const queries = buildQueries$4(networkId, balanceDefs, miniMetadata);
7489
+ return getRpcQueryPack$(connector, networkId, queries).pipe(map(balances => ({
7490
+ success: balances,
7491
+ errors: []
7492
+ })));
7502
7493
  };
7503
7494
 
7504
7495
  const SubTokensBalanceModule = {
@@ -10358,9 +10349,17 @@ class BalancesProvider {
10358
10349
  miniMetadatas: values(miniMetadatas).filter(isNotNil)
10359
10350
  })));
10360
10351
  }
10361
- getBalances$(addressesByToken) {
10362
- const networkIds = uniq(keys(addressesByToken).map(tokenId => parseTokenId(tokenId).networkId));
10363
- return combineLatest(networkIds.map(networkId => this.getNetworkBalances$(networkId, addressesByToken))).pipe(map(results => {
10352
+
10353
+ // this is the only public method
10354
+ getBalances$(addressesByTokenId) {
10355
+ // split by network
10356
+ const addressesByTokenIdByNetworkId = toPairs(addressesByTokenId).reduce((acc, [tokenId, addresses]) => {
10357
+ const networkId = parseTokenId(tokenId).networkId;
10358
+ if (!acc[networkId]) acc[networkId] = {};
10359
+ acc[networkId][tokenId] = addresses;
10360
+ return acc;
10361
+ }, {});
10362
+ return combineLatest(toPairs(addressesByTokenIdByNetworkId).map(([networkId]) => this.getNetworkBalances$(networkId, addressesByTokenIdByNetworkId[networkId]))).pipe(map(results => {
10364
10363
  return {
10365
10364
  status: results.some(({
10366
10365
  status
@@ -10369,9 +10368,17 @@ class BalancesProvider {
10369
10368
  };
10370
10369
  }), startWith({
10371
10370
  status: "initialising",
10372
- balances: this.getStoredBalances(addressesByToken)
10371
+ balances: this.getStoredBalances(addressesByTokenId)
10373
10372
  }), distinctUntilChanged(isEqual));
10374
10373
  }
10374
+ fetchBalances(addressesByTokenId) {
10375
+ // TODO: better
10376
+ return firstValueFrom(this.getBalances$(addressesByTokenId).pipe(filter(({
10377
+ status
10378
+ }) => status === "live"), map(({
10379
+ balances
10380
+ }) => balances)));
10381
+ }
10375
10382
  getNetworkBalances$(networkId, addressesByTokenId) {
10376
10383
  const network$ = this.#chaindataProvider.getNetworkById$(networkId);
10377
10384
  const tokensMapById$ = this.#chaindataProvider.getTokensMapById$();
@@ -10443,7 +10450,42 @@ class BalancesProvider {
10443
10450
  }));
10444
10451
  }
10445
10452
  getNetworkMiniMetadatas$(networkId) {
10446
- return this.#chaindataProvider.getNetworkById$(networkId).pipe(switchMap(network => isNetworkDot(network) && this.#chainConnectors.substrate ? from(getMiniMetadatas(this.#chainConnectors.substrate, this.#chaindataProvider, networkId)) : of([])));
10453
+ return this.#chaindataProvider.getNetworkById$(networkId).pipe(switchMap(network => isNetworkDot(network) && this.#chainConnectors.substrate ? from(getSpecVersion(this.#chainConnectors.substrate, networkId)).pipe(switchMap(specVersion => this.getMiniMetadatas$(networkId, specVersion))) : of([])));
10454
+ }
10455
+ getMiniMetadatas$(networkId, specVersion) {
10456
+ return combineLatest({
10457
+ defaultMiniMetadatas: this.getDefaultMiniMetadatas$(networkId, specVersion),
10458
+ storedMiniMetadatas: this.getStoredMiniMetadatas$(networkId, specVersion)
10459
+ }).pipe(switchMap(({
10460
+ storedMiniMetadatas,
10461
+ defaultMiniMetadatas
10462
+ }) => {
10463
+ if (defaultMiniMetadatas.length) return of(defaultMiniMetadatas);
10464
+ if (storedMiniMetadatas.length) return of(storedMiniMetadatas);
10465
+ if (!this.#chainConnectors.substrate) return of([]);
10466
+ return from(
10467
+ // fetch them from the chain
10468
+ getMiniMetadatas(this.#chainConnectors.substrate, this.#chaindataProvider, networkId)).pipe(
10469
+ // and persist in storage for later reuse
10470
+ tap(newMiniMetadatas => {
10471
+ if (!newMiniMetadatas.length) return;
10472
+ const storage = this.#storage.getValue();
10473
+ const miniMetadatas = assign(
10474
+ // keep minimetadatas of other networks
10475
+ keyBy(values(storage.miniMetadatas).filter(m => m.chainId !== networkId), m => m.id),
10476
+ // add the ones for our network
10477
+ keyBy(newMiniMetadatas, m => m.id));
10478
+ this.#storage.next(assign({}, storage, {
10479
+ miniMetadatas
10480
+ }));
10481
+ }));
10482
+ }));
10483
+ }
10484
+ getStoredMiniMetadatas$(networkId, specVersion) {
10485
+ return this.storage$.pipe(map(storage => storage.miniMetadatas.filter(m => m.chainId === networkId && m.specVersion === specVersion && m.version === MINIMETADATA_VERSION)), distinctUntilChanged(isEqual));
10486
+ }
10487
+ getDefaultMiniMetadatas$(networkId, specVersion) {
10488
+ return this.#chaindataProvider.miniMetadatas$.pipe(map(miniMetadatas => miniMetadatas.filter(m => m.chainId === networkId && m.specVersion === specVersion && m.version === MINIMETADATA_VERSION)), distinctUntilChanged(isEqual));
10447
10489
  }
10448
10490
  getStoredBalances(addressesByToken) {
10449
10491
  const balanceDefs = toPairs(addressesByToken).flatMap(([tokenId, addresses]) => addresses.map(address => [tokenId, address]));
@@ -10454,11 +10496,4 @@ class BalancesProvider {
10454
10496
  }
10455
10497
  }
10456
10498
 
10457
- // const getStoredBalances = (
10458
- // storedBalances: Record<string, IBalance>,
10459
- // addressesByToken: Record<TokenId, Address[]>,
10460
- // ): IBalance[] => {
10461
-
10462
- // }
10463
-
10464
10499
  export { BALANCE_MODULES, Balance, BalanceFormatter, BalanceValueGetter, Balances, BalancesProvider, Change24hCurrencyFormatter, DefaultBalanceModule, EvmErc20BalanceModule, EvmErc20TokenConfigSchema, EvmNativeBalanceModule, EvmNativeTokenConfigSchema, EvmUniswapV2BalanceModule, EvmUniswapV2TokenConfigSchema, FiatSumBalancesFormatter, ONE_ALPHA_TOKEN$1 as ONE_ALPHA_TOKEN, PlanckSumBalancesFormatter, RpcStateQueryHelper, SCALE_FACTOR$1 as SCALE_FACTOR, SUBTENSOR_MIN_STAKE_AMOUNT_PLANK$1 as SUBTENSOR_MIN_STAKE_AMOUNT_PLANK, SUBTENSOR_ROOT_NETUID$1 as SUBTENSOR_ROOT_NETUID, SubAssetsBalanceModule, SubAssetsTokenConfigSchema, SubForeignAssetsBalanceModule, SubForeignAssetsTokenConfigSchema, SubHydrationBalanceModule, SubHydrationTokenConfigSchema, SubNativeBalanceModule, SubNativeMiniMetadataExtraSchema, SubNativeModuleConfigSchema, SubNativeTokenConfigSchema, SubPsp22BalanceModule, SubPsp22TokenConfigSchema, SubTokensBalanceModule, SubTokensMiniMetadataExtraSchema, SubTokensModuleConfigSchema, SubTokensTokenConfigSchema, SumBalancesFormatter, TalismanBalancesDatabase, abiMulticall, balances, buildNetworkStorageCoders, buildStorageCoders, calculateAlphaPrice$1 as calculateAlphaPrice, calculateTaoAmountFromAlpha$1 as calculateTaoAmountFromAlpha, calculateTaoFromDynamicInfo$1 as calculateTaoFromDynamicInfo, compress, configureStore, db, decodeOutput, decompress, defaultBalanceModules, deriveMiniMetadataId, detectTransferMethod, erc20BalancesAggregatorAbi, excludeFromFeePayableLocks, excludeFromTransferableAmount, filterBaseLocks, filterMirrorTokens, getBalanceId, getLockTitle, getLockedType$1 as getLockedType, getUniqueChainIds, getValueId, includeInTotalExtraAmount, makeContractCaller, uniswapV2PairAbi };