@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.
@@ -15,10 +15,10 @@ var z = require('zod/v4');
15
15
  var rxjs = require('rxjs');
16
16
  var isEqual = require('lodash/isEqual');
17
17
  var scale = require('@talismn/scale');
18
- var types = require('@polkadot/types');
19
- var groupBy = require('lodash/groupBy');
20
18
  var utils = require('@polkadot-api/utils');
21
19
  var polkadotApi = require('polkadot-api');
20
+ var types = require('@polkadot/types');
21
+ var groupBy = require('lodash/groupBy');
22
22
  var upperFirst = require('lodash/upperFirst');
23
23
  var scaleTs = require('scale-ts');
24
24
  var apiContract = require('@polkadot/api-contract');
@@ -1553,6 +1553,10 @@ const fetchBalances$c = async ({
1553
1553
  tokensWithAddresses,
1554
1554
  connector
1555
1555
  }) => {
1556
+ if (!tokensWithAddresses.length) return {
1557
+ success: [],
1558
+ errors: []
1559
+ };
1556
1560
  const client = await connector.getPublicClientForEvmNetwork(networkId);
1557
1561
  if (!client) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
1558
1562
  for (const [token, addresses] of tokensWithAddresses) {
@@ -1779,12 +1783,16 @@ const getTransferCallData$8 = ({
1779
1783
  };
1780
1784
  };
1781
1785
 
1782
- const SUBSCRIPTION_INTERVAL$7 = 6_000;
1786
+ const SUBSCRIPTION_INTERVAL$4 = 6_000;
1783
1787
  const subscribeBalances$8 = ({
1784
1788
  networkId,
1785
1789
  tokensWithAddresses,
1786
1790
  connector
1787
1791
  }) => {
1792
+ if (!tokensWithAddresses.length) return rxjs.of({
1793
+ success: [],
1794
+ errors: []
1795
+ });
1788
1796
  return new rxjs.Observable(subscriber => {
1789
1797
  const abortController = new AbortController();
1790
1798
  const poll = async () => {
@@ -1797,7 +1805,7 @@ const subscribeBalances$8 = ({
1797
1805
  });
1798
1806
  if (abortController.signal.aborted) return;
1799
1807
  subscriber.next(balances);
1800
- setTimeout(poll, SUBSCRIPTION_INTERVAL$7);
1808
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$4);
1801
1809
  } catch (error) {
1802
1810
  log.error("Error", {
1803
1811
  module: MODULE_TYPE$8,
@@ -1843,6 +1851,10 @@ const fetchBalances$b = async ({
1843
1851
  tokensWithAddresses,
1844
1852
  connector
1845
1853
  }) => {
1854
+ if (!tokensWithAddresses.length) return {
1855
+ success: [],
1856
+ errors: []
1857
+ };
1846
1858
  const client = await connector.getPublicClientForEvmNetwork(networkId);
1847
1859
  if (!client) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
1848
1860
  for (const [token, addresses] of tokensWithAddresses) {
@@ -1993,12 +2005,16 @@ const getTransferCallData$7 = ({
1993
2005
  };
1994
2006
  };
1995
2007
 
1996
- const SUBSCRIPTION_INTERVAL$6 = 6_000;
2008
+ const SUBSCRIPTION_INTERVAL$3 = 6_000;
1997
2009
  const subscribeBalances$7 = ({
1998
2010
  networkId,
1999
2011
  tokensWithAddresses,
2000
2012
  connector
2001
2013
  }) => {
2014
+ if (!tokensWithAddresses.length) return rxjs.of({
2015
+ success: [],
2016
+ errors: []
2017
+ });
2002
2018
  return new rxjs.Observable(subscriber => {
2003
2019
  const abortController = new AbortController();
2004
2020
  const poll = async () => {
@@ -2011,7 +2027,7 @@ const subscribeBalances$7 = ({
2011
2027
  });
2012
2028
  if (abortController.signal.aborted) return;
2013
2029
  subscriber.next(balances);
2014
- setTimeout(poll, SUBSCRIPTION_INTERVAL$6);
2030
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$3);
2015
2031
  } catch (error) {
2016
2032
  log.error("Error", {
2017
2033
  module: MODULE_TYPE$7,
@@ -2052,6 +2068,10 @@ const fetchBalances$a = async ({
2052
2068
  tokensWithAddresses,
2053
2069
  connector
2054
2070
  }) => {
2071
+ if (!tokensWithAddresses.length) return {
2072
+ success: [],
2073
+ errors: []
2074
+ };
2055
2075
  const client = await connector.getPublicClientForEvmNetwork(networkId);
2056
2076
  if (!client) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
2057
2077
  for (const [token, addresses] of tokensWithAddresses) {
@@ -2326,12 +2346,16 @@ const getTransferCallData$6 = ({
2326
2346
  };
2327
2347
  };
2328
2348
 
2329
- const SUBSCRIPTION_INTERVAL$5 = 6_000;
2349
+ const SUBSCRIPTION_INTERVAL$2 = 6_000;
2330
2350
  const subscribeBalances$6 = ({
2331
2351
  networkId,
2332
2352
  tokensWithAddresses,
2333
2353
  connector
2334
2354
  }) => {
2355
+ if (!tokensWithAddresses.length) return rxjs.of({
2356
+ success: [],
2357
+ errors: []
2358
+ });
2335
2359
  return new rxjs.Observable(subscriber => {
2336
2360
  const abortController = new AbortController();
2337
2361
  const poll = async () => {
@@ -2344,7 +2368,7 @@ const subscribeBalances$6 = ({
2344
2368
  });
2345
2369
  if (abortController.signal.aborted) return;
2346
2370
  subscriber.next(balances);
2347
- setTimeout(poll, SUBSCRIPTION_INTERVAL$5);
2371
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$2);
2348
2372
  } catch (error) {
2349
2373
  log.error("Error", {
2350
2374
  module: MODULE_TYPE$6,
@@ -3208,273 +3232,138 @@ async function getPoolBalance(publicClient, contractAddress, accountAddress) {
3208
3232
  const MODULE_TYPE$5 = chaindataProvider.SubAssetsTokenSchema.shape.type.value;
3209
3233
  const PLATFORM$5 = chaindataProvider.SubAssetsTokenSchema.shape.platform.value;
3210
3234
 
3211
- /**
3212
- * Wraps a BalanceModule's fetch/subscribe methods with a single `balances` method.
3213
- * This `balances` method will subscribe if a callback parameter is provided, or otherwise fetch.
3214
- */
3235
+ const fetchRpcQueryPack = async (connector, networkId, queries) => {
3236
+ const allStateKeys = queries.flatMap(({
3237
+ stateKeys
3238
+ }) => stateKeys).filter(util.isNotNil);
3215
3239
 
3216
- async function balances(balanceModule, addressesByToken, callback) {
3217
- // subscription request
3218
- if (callback !== undefined) return await balanceModule.subscribeBalances({
3219
- addressesByToken
3220
- }, callback);
3240
+ // doing a query without keys would throw an error => return early
3241
+ if (!allStateKeys.length) return queries.map(({
3242
+ stateKeys,
3243
+ decodeResult
3244
+ }) => decodeResult(stateKeys.map(() => null)));
3245
+ const [result] = await connector.send(networkId, "state_queryStorageAt", [allStateKeys]);
3246
+ return decodeRpcQueryPack(queries, result);
3247
+ };
3248
+ const getRpcQueryPack$ = (connector, networkId, queries, timeout = false) => {
3249
+ const allStateKeys = queries.flatMap(({
3250
+ stateKeys
3251
+ }) => stateKeys).filter(util.isNotNil);
3221
3252
 
3222
- // one-off request
3223
- return await balanceModule.fetchBalances(addressesByToken);
3224
- }
3253
+ // doing a query without keys would throw an error => return early
3254
+ if (!allStateKeys.length) return rxjs.of(queries.map(({
3255
+ stateKeys,
3256
+ decodeResult
3257
+ }) => decodeResult(stateKeys.map(() => null))));
3258
+ return new rxjs.Observable(subscriber => {
3259
+ const promUnsub = connector.subscribe(networkId, "state_subscribeStorage", "state_storage", [allStateKeys], (error, result) => {
3260
+ if (error) subscriber.error(error);else subscriber.next(decodeRpcQueryPack(queries, result));
3261
+ }, timeout);
3262
+ return () => {
3263
+ promUnsub.then(unsub => unsub("state_unsubscribeStorage"));
3264
+ };
3265
+ });
3266
+ };
3267
+ const decodeRpcQueryPack = (queries, result) => {
3268
+ return queries.reduce((acc, {
3269
+ stateKeys,
3270
+ decodeResult
3271
+ }) => {
3272
+ const changes = stateKeys.map(stateKey => {
3273
+ if (!stateKey) return null;
3274
+ const change = result.changes.find(([key]) => key === stateKey);
3275
+ if (!change) return null;
3276
+ return change[1];
3277
+ });
3278
+ acc.push(decodeResult(changes));
3279
+ return acc;
3280
+ }, []);
3281
+ };
3225
3282
 
3226
- // TODO remove this one in favor of the network specific one below
3227
- const buildStorageCoders = ({
3228
- chainIds,
3229
- chains,
3230
- miniMetadatas,
3231
- coders
3232
- }) => new Map([...chainIds].flatMap(chainId => {
3233
- const chain = chains[chainId];
3234
- if (!chain) return [];
3235
- const miniMetadata = miniMetadatas.get(chainId); // findMiniMetadata<TBalanceModule>(miniMetadatas, moduleType, chain)
3236
- if (!miniMetadata) return [];
3237
- if (!miniMetadata.data) return [];
3238
- const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata.data));
3239
- try {
3240
- const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
3241
- const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
3242
- const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
3243
- chainId
3244
- }) : moduleMethodOrFn;
3245
- try {
3246
- return [[key, scaleBuilder.buildStorage(module, method)]];
3247
- } catch (cause) {
3248
- log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
3249
- return [];
3250
- }
3251
- }));
3252
- return [[chainId, builtCoders]];
3253
- } catch (cause) {
3254
- log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
3255
- return [];
3256
- }
3257
- }));
3258
- // type StorageCoder<TCoders extends NetworkCoders> = ReturnType<ReturnType<typeof getDynamicBuilder>["buildStorage"]>[keyof TCoders]
3283
+ const buildQueries$6 = (networkId, balanceDefs, miniMetadata) => {
3284
+ const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
3285
+ storage: ["Assets", "Account"]
3286
+ });
3287
+ return balanceDefs.map(({
3288
+ token,
3289
+ address
3290
+ }) => {
3291
+ const scaleCoder = networkStorageCoders?.storage;
3292
+ const stateKey = tryEncode$1(scaleCoder, Number(token.assetId), address) ??
3293
+ // Asset Hub
3294
+ tryEncode$1(scaleCoder, BigInt(token.assetId), address); // Astar
3259
3295
 
3260
- const buildNetworkStorageCoders = (chainId, miniMetadata, coders) => {
3261
- if (!miniMetadata.data) return null;
3262
- const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata.data));
3296
+ if (!stateKey) {
3297
+ log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
3298
+ return null;
3299
+ }
3300
+ const decodeResult = changes => {
3301
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3302
+
3303
+ const decoded = scale.decodeScale(scaleCoder, changes[0], `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
3304
+ balance: 0n,
3305
+ status: {
3306
+ type: "Liquid"
3307
+ }};
3308
+ const isFrozen = decoded?.status?.type === "Frozen";
3309
+ const amount = (decoded?.balance ?? 0n).toString();
3310
+
3311
+ // due to the following balance calculations, which are made in the `Balance` type:
3312
+ //
3313
+ // total balance = (free balance) + (reserved balance)
3314
+ // transferable balance = (free balance) - (frozen balance)
3315
+ //
3316
+ // when `isFrozen` is true we need to set **both** the `free` and `frozen` amounts
3317
+ // of this balance to the value we received from the RPC.
3318
+ //
3319
+ // if we only set the `frozen` amount, then the `total` calculation will be incorrect!
3320
+ const free = amount;
3321
+ const frozen = token.isFrozen || isFrozen ? amount : "0";
3322
+
3323
+ // include balance values even if zero, so that newly-zero values overwrite old values
3324
+ const balanceValues = [{
3325
+ type: "free",
3326
+ label: "free",
3327
+ amount: free.toString()
3328
+ }, {
3329
+ type: "locked",
3330
+ label: "frozen",
3331
+ amount: frozen.toString()
3332
+ }];
3333
+ const balance = {
3334
+ source: "substrate-assets",
3335
+ status: "live",
3336
+ address,
3337
+ networkId,
3338
+ tokenId: token.id,
3339
+ values: balanceValues
3340
+ };
3341
+ return balance;
3342
+ };
3343
+ return {
3344
+ stateKeys: [stateKey],
3345
+ decodeResult
3346
+ };
3347
+ }).filter(util.isNotNil);
3348
+ };
3349
+ const tryEncode$1 = (scaleCoder, ...args) => {
3263
3350
  try {
3264
- const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
3265
- const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
3266
- const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
3267
- chainId
3268
- }) : moduleMethodOrFn;
3269
- try {
3270
- return [[key, scaleBuilder.buildStorage(module, method)]];
3271
- } catch (cause) {
3272
- log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
3273
- return [];
3274
- }
3275
- }));
3276
- return builtCoders;
3277
- } catch (cause) {
3278
- log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
3351
+ return scaleCoder?.keys?.enc?.(...args);
3352
+ } catch {
3353
+ return null;
3279
3354
  }
3280
- return null;
3281
3355
  };
3282
3356
 
3283
- /**
3284
- * Decodes & unwraps outputs and errors of a given result, contract, and method.
3285
- * Parsed error message can be found in `decodedOutput` if `isError` is true.
3286
- * SOURCE: https://github.com/paritytech/contracts-ui (GPL-3.0-only)
3287
- */
3288
- function decodeOutput({
3289
- result
3290
- }, registry, abi, method) {
3291
- let output;
3292
- let decodedOutput = "";
3293
- let isError = true;
3294
- if (result.isOk) {
3295
- const flags = result.asOk.flags.toHuman();
3296
- isError = flags.includes("Revert");
3297
- const abiMessage = getAbiMessage(abi, method);
3298
- const returnType = abiMessage.returnType;
3299
- const returnTypeName = getReturnTypeName(returnType);
3300
- const r = returnType ? registry.createTypeUnsafe(returnTypeName, [result.asOk.data]).toHuman() : "()";
3301
- output = isOk(r) ? r.Ok : isErr(r) ? r.Err : r;
3302
- const errorText = isErr(output) ? typeof output.Err === "object" ? JSON.stringify(output.Err, null, 2) : output.Err?.toString() ?? "Error" : output !== "Ok" ? output?.toString() || "Error" : "Error";
3303
- const okText = isOk(r) ? typeof output === "object" ? JSON.stringify(output, null, "\t") : output?.toString() ?? "()" : JSON.stringify(output, null, "\t") ?? "()";
3304
- decodedOutput = isError ? errorText : okText;
3305
- } else if (result.isErr) {
3306
- output = result.toHuman();
3307
- let errorText;
3308
- if (isErr(output) && typeof output.Err === "object" && Object.keys(output.Err || {}).length && typeof Object.values(output.Err || {})[0] === "string") {
3309
- const [errorKey, errorValue] = Object.entries(output.Err || {})[0];
3310
- errorText = `${errorKey}${errorValue}`;
3311
- }
3312
- decodedOutput = errorText || "Error";
3313
- }
3314
- return {
3315
- output,
3316
- decodedOutput,
3317
- isError
3357
+ const fetchBalances$6 = async ({
3358
+ networkId,
3359
+ tokensWithAddresses,
3360
+ connector,
3361
+ miniMetadata
3362
+ }) => {
3363
+ if (!tokensWithAddresses.length) return {
3364
+ success: [],
3365
+ errors: []
3318
3366
  };
3319
- }
3320
-
3321
- /**
3322
- * Helper types & functions
3323
- * SOURCE: https://github.com/paritytech/contracts-ui (GPL-3.0-only)
3324
- */
3325
-
3326
- function isErr(o) {
3327
- return typeof o === "object" && o !== null && "Err" in o;
3328
- }
3329
- function isOk(o) {
3330
- return typeof o === "object" && o !== null && "Ok" in o;
3331
- }
3332
- function getReturnTypeName(type) {
3333
- return type?.lookupName || type?.type || "";
3334
- }
3335
- function getAbiMessage(abi, method) {
3336
- const abiMessage = abi.messages.find(m => util$1.stringCamelCase(m.method) === util$1.stringCamelCase(method));
3337
- if (!abiMessage) {
3338
- throw new Error(`"${method}" not found in Contract`);
3339
- }
3340
- return abiMessage;
3341
- }
3342
-
3343
- /**
3344
- *
3345
- * Detect Balances::transfer -> Balances::transfer_allow_death migration
3346
- * https://github.com/paritytech/substrate/pull/12951
3347
- *
3348
- * `transfer_allow_death` is the preferred method,
3349
- * so if something goes wrong during detection, we should assume the chain has migrated
3350
- * @param metadataRpc string containing the hashed RPC metadata for the chain
3351
- * @returns
3352
- */
3353
- const detectTransferMethod = metadataRpc => {
3354
- const pjsMetadata = new types.Metadata(new types.TypeRegistry(), metadataRpc);
3355
- pjsMetadata.registry.setMetadata(pjsMetadata);
3356
- const balancesPallet = pjsMetadata.asLatest.pallets.find(pallet => pallet.name.eq("Balances"));
3357
- const balancesCallsTypeIndex = balancesPallet?.calls.value.type.toNumber();
3358
- const balancesCallsType = balancesCallsTypeIndex !== undefined ? pjsMetadata.asLatest.lookup.types[balancesCallsTypeIndex] : undefined;
3359
- const hasDeprecatedTransferCall = balancesCallsType?.type.def.asVariant?.variants.find(variant => variant.name.eq("transfer")) !== undefined;
3360
- return hasDeprecatedTransferCall ? "transfer" : "transfer_allow_death";
3361
- };
3362
-
3363
- const getUniqueChainIds = (addressesByToken, tokens) => [...new Set(Object.keys(addressesByToken).map(tokenId => tokens[tokenId]?.networkId).flatMap(chainId => chainId ? [chainId] : []))];
3364
-
3365
- const makeContractCaller = ({
3366
- chainConnector,
3367
- chainId,
3368
- registry
3369
- }) => async (callFrom, contractAddress, inputData) => registry.createType("ContractExecResult", await chainConnector.send(chainId, "state_call", ["ContractsApi_call", util$1.u8aToHex(util$1.u8aConcatStrict([
3370
- // origin
3371
- registry.createType("AccountId", callFrom).toU8a(),
3372
- // dest
3373
- registry.createType("AccountId", contractAddress).toU8a(),
3374
- // value
3375
- registry.createType("Balance", 0).toU8a(),
3376
- // gasLimit
3377
- registry.createType("Option<WeightV2>").toU8a(),
3378
- // storageDepositLimit
3379
- registry.createType("Option<Balance>").toU8a(),
3380
- // inputData
3381
- inputData instanceof Uint8Array ? inputData : inputData.toU8a()]))]));
3382
-
3383
- /**
3384
- * Pass some these into an `RpcStateQueryHelper` in order to easily batch multiple state queries into the one rpc call.
3385
- */
3386
-
3387
- /**
3388
- * Used by a variety of balance modules to help batch multiple state queries into the one rpc call.
3389
- */
3390
- class RpcStateQueryHelper {
3391
- #chainConnector;
3392
- #queries;
3393
- constructor(chainConnector, queries) {
3394
- this.#chainConnector = chainConnector;
3395
- this.#queries = queries;
3396
- }
3397
- async subscribe(callback, timeout = false, subscribeMethod = "state_subscribeStorage", responseMethod = "state_storage", unsubscribeMethod = "state_unsubscribeStorage") {
3398
- const queriesByChain = groupBy__default.default(this.#queries, "chainId");
3399
- const subscriptions = Object.entries(queriesByChain).map(([chainId, queries]) => {
3400
- const params = [queries.map(({
3401
- stateKey
3402
- }) => stateKey)];
3403
- const unsub = this.#chainConnector.subscribe(chainId, subscribeMethod, responseMethod, params, (error, result) => {
3404
- error ? callback(error) : callback(null, this.#distributeChangesToQueryDecoders.call(this, chainId, result));
3405
- }, timeout);
3406
- return () => unsub.then(unsubscribe => unsubscribe(unsubscribeMethod));
3407
- });
3408
- return () => subscriptions.forEach(unsubscribe => unsubscribe());
3409
- }
3410
- async fetch(method = "state_queryStorageAt") {
3411
- const queriesByChain = groupBy__default.default(this.#queries, "chainId");
3412
- const resultsByChain = await Promise.all(Object.entries(queriesByChain).map(async ([chainId, queries]) => {
3413
- const params = [queries.map(({
3414
- stateKey
3415
- }) => stateKey)];
3416
- const result = (await this.#chainConnector.send(chainId, method, params))[0];
3417
- return this.#distributeChangesToQueryDecoders.call(this, chainId, result);
3418
- }));
3419
- return resultsByChain.flatMap(result => result);
3420
- }
3421
- #distributeChangesToQueryDecoders(chainId, result) {
3422
- if (typeof result !== "object" || result === null) return [];
3423
- if (!util.hasOwnProperty(result, "changes") || typeof result.changes !== "object") return [];
3424
- if (!Array.isArray(result.changes)) return [];
3425
- return result.changes.flatMap(([reference, change]) => {
3426
- if (typeof reference !== "string") {
3427
- log.warn(`Received non-string reference in RPC result: ${reference}`);
3428
- return [];
3429
- }
3430
- if (typeof change !== "string" && change !== null) {
3431
- log.warn(`Received non-string and non-null change in RPC result: ${reference} | ${change}`);
3432
- return [];
3433
- }
3434
- const query = this.#queries.find(({
3435
- chainId: cId,
3436
- stateKey
3437
- }) => cId === chainId && stateKey === reference);
3438
- if (!query) {
3439
- log.warn(`Failed to find query:\n${reference} in\n${this.#queries.map(({
3440
- stateKey
3441
- }) => stateKey)}`);
3442
- return [];
3443
- }
3444
- return [query.decodeResult(change)];
3445
- });
3446
- }
3447
- }
3448
-
3449
- const configureStore = (dbTable = db.balancesBlob) => ({
3450
- persistData: async balances => {
3451
- const output = compress(balances);
3452
- await dbTable.clear();
3453
- await dbTable.put({
3454
- data: output,
3455
- id: Date.now().toString()
3456
- });
3457
- },
3458
- retrieveData: async () => {
3459
- const compressedData = await dbTable.toCollection().first();
3460
- if (!compressedData) return [];
3461
- return decompress(compressedData.data);
3462
- }
3463
- });
3464
- const compress = balances => pako__default.default.deflate(JSON.stringify(balances));
3465
- const decompress = data => {
3466
- const decompressed = pako__default.default.inflate(data, {
3467
- to: "string"
3468
- });
3469
- return JSON.parse(decompressed);
3470
- };
3471
-
3472
- const fetchBalances$6 = async ({
3473
- networkId,
3474
- tokensWithAddresses,
3475
- connector,
3476
- miniMetadata
3477
- }) => {
3478
3367
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
3479
3368
  if (!miniMetadata?.data) {
3480
3369
  log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$5} balances on ${networkId}.`);
@@ -3510,7 +3399,7 @@ const fetchBalances$6 = async ({
3510
3399
  };
3511
3400
  }
3512
3401
  const queries = buildQueries$6(networkId, balanceDefs, miniMetadata);
3513
- const balances = await new RpcStateQueryHelper(connector, queries).fetch();
3402
+ const balances = await fetchRpcQueryPack(connector, networkId, queries);
3514
3403
  return balanceDefs.reduce((acc, def) => {
3515
3404
  const balance = balances.find(b => b?.address === def.address && b?.tokenId === def.token.id);
3516
3405
  if (balance) acc.success.push(balance);
@@ -3537,80 +3426,6 @@ const fetchBalances$6 = async ({
3537
3426
  errors: []
3538
3427
  });
3539
3428
  };
3540
- const buildQueries$6 = (networkId, balanceDefs, miniMetadata) => {
3541
- const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
3542
- storage: ["Assets", "Account"]
3543
- });
3544
- return balanceDefs.map(({
3545
- token,
3546
- address
3547
- }) => {
3548
- const scaleCoder = networkStorageCoders?.storage;
3549
- const stateKey = tryEncode$1(scaleCoder, Number(token.assetId), address) ??
3550
- // Asset Hub
3551
- tryEncode$1(scaleCoder, BigInt(token.assetId), address); // Astar
3552
-
3553
- if (!stateKey) {
3554
- log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
3555
- return null;
3556
- }
3557
- const decodeResult = change => {
3558
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3559
-
3560
- const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
3561
- balance: 0n,
3562
- status: {
3563
- type: "Liquid"
3564
- }};
3565
- const isFrozen = decoded?.status?.type === "Frozen";
3566
- const amount = (decoded?.balance ?? 0n).toString();
3567
-
3568
- // due to the following balance calculations, which are made in the `Balance` type:
3569
- //
3570
- // total balance = (free balance) + (reserved balance)
3571
- // transferable balance = (free balance) - (frozen balance)
3572
- //
3573
- // when `isFrozen` is true we need to set **both** the `free` and `frozen` amounts
3574
- // of this balance to the value we received from the RPC.
3575
- //
3576
- // if we only set the `frozen` amount, then the `total` calculation will be incorrect!
3577
- const free = amount;
3578
- const frozen = token.isFrozen || isFrozen ? amount : "0";
3579
-
3580
- // include balance values even if zero, so that newly-zero values overwrite old values
3581
- const balanceValues = [{
3582
- type: "free",
3583
- label: "free",
3584
- amount: free.toString()
3585
- }, {
3586
- type: "locked",
3587
- label: "frozen",
3588
- amount: frozen.toString()
3589
- }];
3590
- const balance = {
3591
- source: "substrate-assets",
3592
- status: "live",
3593
- address,
3594
- networkId,
3595
- tokenId: token.id,
3596
- values: balanceValues
3597
- };
3598
- return balance;
3599
- };
3600
- return {
3601
- chainId: networkId,
3602
- stateKey,
3603
- decodeResult
3604
- };
3605
- }).filter(util.isNotNil);
3606
- };
3607
- const tryEncode$1 = (scaleCoder, ...args) => {
3608
- try {
3609
- return scaleCoder?.keys?.enc?.(...args);
3610
- } catch {
3611
- return null;
3612
- }
3613
- };
3614
3429
 
3615
3430
  const fetchTokens$5 = async ({
3616
3431
  networkId,
@@ -3847,47 +3662,47 @@ const getTransferAllEncodedArgs$2 = (assetId, to, codec) => {
3847
3662
  })]);
3848
3663
  };
3849
3664
 
3850
- const SUBSCRIPTION_INTERVAL$4 = 6_000;
3665
+ const fetchRuntimeCallResult = async (connector, networkId, metadataRpc, apiName, method, args) => {
3666
+ const {
3667
+ builder
3668
+ } = scale.parseMetadataRpc(metadataRpc);
3669
+ const call = builder.buildRuntimeCall(apiName, method);
3670
+ const hex = await connector.send(networkId, "state_call", [`${apiName}_${method}`, scale.toHex(call.args.enc(args))]);
3671
+ return call.value.dec(hex);
3672
+ };
3673
+
3674
+ const tryGetConstantValue = (metadataRpc, pallet, constant) => {
3675
+ const {
3676
+ unifiedMetadata,
3677
+ builder
3678
+ } = scale.parseMetadataRpc(metadataRpc);
3679
+ const encodedValue = unifiedMetadata.pallets.find(({
3680
+ name
3681
+ }) => name === pallet)?.constants.find(({
3682
+ name
3683
+ }) => name === constant)?.value;
3684
+ if (!encodedValue) return null;
3685
+ const codec = builder.buildConstant(pallet, constant);
3686
+ return codec.dec(encodedValue);
3687
+ };
3688
+
3851
3689
  const subscribeBalances$5 = ({
3852
3690
  networkId,
3853
3691
  tokensWithAddresses,
3854
3692
  connector,
3855
3693
  miniMetadata
3856
3694
  }) => {
3857
- return new rxjs.Observable(subscriber => {
3858
- const abortController = new AbortController();
3859
-
3860
- // on hydration balances are fetched using a runtimeApi, which can't be subscribed to.
3861
- // => poll values for each block
3862
- const poll = async () => {
3863
- try {
3864
- if (abortController.signal.aborted) return;
3865
- const balances = await fetchBalances$6({
3866
- networkId,
3867
- tokensWithAddresses: tokensWithAddresses,
3868
- connector,
3869
- miniMetadata
3870
- });
3871
- if (abortController.signal.aborted) return;
3872
- subscriber.next(balances);
3873
- setTimeout(poll, SUBSCRIPTION_INTERVAL$4);
3874
- } catch (error) {
3875
- log.error("Error", {
3876
- module: MODULE_TYPE$5,
3877
- networkId,
3878
- miniMetadata,
3879
- addressesByToken: tokensWithAddresses,
3880
- error
3881
- });
3882
- subscriber.error(error);
3883
- }
3884
- };
3885
- poll();
3886
- return () => {
3887
- abortController.abort();
3888
- };
3889
- }).pipe(rxjs.distinctUntilChanged(lodash.isEqual));
3890
- };
3695
+ if (!tokensWithAddresses.length) return rxjs.of({
3696
+ success: [],
3697
+ errors: []
3698
+ });
3699
+ const balanceDefs = getBalanceDefs(tokensWithAddresses);
3700
+ const queries = buildQueries$6(networkId, balanceDefs, miniMetadata);
3701
+ return getRpcQueryPack$(connector, networkId, queries).pipe(rxjs.map(balances => ({
3702
+ success: balances,
3703
+ errors: []
3704
+ })));
3705
+ };
3891
3706
 
3892
3707
  const SubAssetsBalanceModule = {
3893
3708
  type: MODULE_TYPE$5,
@@ -3908,74 +3723,267 @@ const SubAssetsTokenConfigSchema = z__default.default.strictObject({
3908
3723
  const MODULE_TYPE$4 = chaindataProvider.SubForeignAssetsTokenSchema.shape.type.value;
3909
3724
  const PLATFORM$4 = chaindataProvider.SubForeignAssetsTokenSchema.shape.platform.value;
3910
3725
 
3911
- const fetchBalances$5 = async ({
3912
- networkId,
3913
- tokensWithAddresses,
3914
- connector,
3915
- miniMetadata
3916
- }) => {
3917
- const balanceDefs = getBalanceDefs(tokensWithAddresses);
3918
- if (!miniMetadata?.data) {
3919
- log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$4} balances on ${networkId}.`);
3920
- return {
3921
- success: [],
3922
- errors: balanceDefs.map(def => ({
3923
- tokenId: def.token.id,
3924
- address: def.address,
3925
- error: new Error("Minimetadata is required for fetching balances")
3926
- }))
3927
- };
3726
+ /**
3727
+ * Wraps a BalanceModule's fetch/subscribe methods with a single `balances` method.
3728
+ * This `balances` method will subscribe if a callback parameter is provided, or otherwise fetch.
3729
+ */
3730
+
3731
+ async function balances(balanceModule, addressesByToken, callback) {
3732
+ // subscription request
3733
+ if (callback !== undefined) return await balanceModule.subscribeBalances({
3734
+ addressesByToken
3735
+ }, callback);
3736
+
3737
+ // one-off request
3738
+ return await balanceModule.fetchBalances(addressesByToken);
3739
+ }
3740
+
3741
+ // TODO remove this one in favor of the network specific one below
3742
+ const buildStorageCoders = ({
3743
+ chainIds,
3744
+ chains,
3745
+ miniMetadatas,
3746
+ coders
3747
+ }) => new Map([...chainIds].flatMap(chainId => {
3748
+ const chain = chains[chainId];
3749
+ if (!chain) return [];
3750
+ const miniMetadata = miniMetadatas.get(chainId); // findMiniMetadata<TBalanceModule>(miniMetadatas, moduleType, chain)
3751
+ if (!miniMetadata) return [];
3752
+ if (!miniMetadata.data) return [];
3753
+ const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata.data));
3754
+ try {
3755
+ const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
3756
+ const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
3757
+ const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
3758
+ chainId
3759
+ }) : moduleMethodOrFn;
3760
+ try {
3761
+ return [[key, scaleBuilder.buildStorage(module, method)]];
3762
+ } catch (cause) {
3763
+ log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
3764
+ return [];
3765
+ }
3766
+ }));
3767
+ return [[chainId, builtCoders]];
3768
+ } catch (cause) {
3769
+ log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
3770
+ return [];
3928
3771
  }
3929
- if (miniMetadata.source !== MODULE_TYPE$4) {
3930
- log.warn(`Ignoring miniMetadata with source ${miniMetadata.source} in ${MODULE_TYPE$4}.`);
3931
- return {
3932
- success: [],
3933
- errors: balanceDefs.map(def => ({
3934
- tokenId: def.token.id,
3935
- address: def.address,
3936
- error: new Error(`Invalid request: miniMetadata source is not ${MODULE_TYPE$4}`)
3937
- }))
3938
- };
3772
+ }));
3773
+ // type StorageCoder<TCoders extends NetworkCoders> = ReturnType<ReturnType<typeof getDynamicBuilder>["buildStorage"]>[keyof TCoders]
3774
+
3775
+ const buildNetworkStorageCoders = (chainId, miniMetadata, coders) => {
3776
+ if (!miniMetadata.data) return null;
3777
+ const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata.data));
3778
+ try {
3779
+ const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
3780
+ const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
3781
+ const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
3782
+ chainId
3783
+ }) : moduleMethodOrFn;
3784
+ try {
3785
+ return [[key, scaleBuilder.buildStorage(module, method)]];
3786
+ } catch (cause) {
3787
+ log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
3788
+ return [];
3789
+ }
3790
+ }));
3791
+ return builtCoders;
3792
+ } catch (cause) {
3793
+ log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
3939
3794
  }
3940
- if (miniMetadata.chainId !== networkId) {
3941
- log.warn(`Ignoring miniMetadata with chainId ${miniMetadata.chainId} in ${MODULE_TYPE$4}. Expected chainId is ${networkId}`);
3942
- return {
3943
- success: [],
3944
- errors: balanceDefs.map(def => ({
3945
- tokenId: def.token.id,
3946
- address: def.address,
3947
- error: new Error(`Invalid request: Expected chainId is ${networkId}`)
3948
- }))
3949
- };
3795
+ return null;
3796
+ };
3797
+
3798
+ /**
3799
+ * Decodes & unwraps outputs and errors of a given result, contract, and method.
3800
+ * Parsed error message can be found in `decodedOutput` if `isError` is true.
3801
+ * SOURCE: https://github.com/paritytech/contracts-ui (GPL-3.0-only)
3802
+ */
3803
+ function decodeOutput({
3804
+ result
3805
+ }, registry, abi, method) {
3806
+ let output;
3807
+ let decodedOutput = "";
3808
+ let isError = true;
3809
+ if (result.isOk) {
3810
+ const flags = result.asOk.flags.toHuman();
3811
+ isError = flags.includes("Revert");
3812
+ const abiMessage = getAbiMessage(abi, method);
3813
+ const returnType = abiMessage.returnType;
3814
+ const returnTypeName = getReturnTypeName(returnType);
3815
+ const r = returnType ? registry.createTypeUnsafe(returnTypeName, [result.asOk.data]).toHuman() : "()";
3816
+ output = isOk(r) ? r.Ok : isErr(r) ? r.Err : r;
3817
+ const errorText = isErr(output) ? typeof output.Err === "object" ? JSON.stringify(output.Err, null, 2) : output.Err?.toString() ?? "Error" : output !== "Ok" ? output?.toString() || "Error" : "Error";
3818
+ const okText = isOk(r) ? typeof output === "object" ? JSON.stringify(output, null, "\t") : output?.toString() ?? "()" : JSON.stringify(output, null, "\t") ?? "()";
3819
+ decodedOutput = isError ? errorText : okText;
3820
+ } else if (result.isErr) {
3821
+ output = result.toHuman();
3822
+ let errorText;
3823
+ if (isErr(output) && typeof output.Err === "object" && Object.keys(output.Err || {}).length && typeof Object.values(output.Err || {})[0] === "string") {
3824
+ const [errorKey, errorValue] = Object.entries(output.Err || {})[0];
3825
+ errorText = `${errorKey}${errorValue}`;
3826
+ }
3827
+ decodedOutput = errorText || "Error";
3950
3828
  }
3951
- const queries = buildQueries$5(networkId, balanceDefs, miniMetadata);
3952
- const balances = await new RpcStateQueryHelper(connector, queries).fetch();
3953
- return balanceDefs.reduce((acc, def) => {
3954
- const balance = balances.find(b => b?.address === def.address && b?.tokenId === def.token.id);
3955
- if (balance) acc.success.push(balance);
3956
- //if no entry consider empty balance
3957
- else acc.success.push({
3958
- address: def.address,
3959
- networkId,
3960
- tokenId: def.token.id,
3961
- source: MODULE_TYPE$4,
3962
- status: "live",
3963
- values: [{
3964
- type: "free",
3965
- label: "free",
3966
- amount: "0"
3967
- }, {
3968
- type: "locked",
3969
- label: "frozen",
3970
- amount: "0"
3971
- }]
3829
+ return {
3830
+ output,
3831
+ decodedOutput,
3832
+ isError
3833
+ };
3834
+ }
3835
+
3836
+ /**
3837
+ * Helper types & functions
3838
+ * SOURCE: https://github.com/paritytech/contracts-ui (GPL-3.0-only)
3839
+ */
3840
+
3841
+ function isErr(o) {
3842
+ return typeof o === "object" && o !== null && "Err" in o;
3843
+ }
3844
+ function isOk(o) {
3845
+ return typeof o === "object" && o !== null && "Ok" in o;
3846
+ }
3847
+ function getReturnTypeName(type) {
3848
+ return type?.lookupName || type?.type || "";
3849
+ }
3850
+ function getAbiMessage(abi, method) {
3851
+ const abiMessage = abi.messages.find(m => util$1.stringCamelCase(m.method) === util$1.stringCamelCase(method));
3852
+ if (!abiMessage) {
3853
+ throw new Error(`"${method}" not found in Contract`);
3854
+ }
3855
+ return abiMessage;
3856
+ }
3857
+
3858
+ /**
3859
+ *
3860
+ * Detect Balances::transfer -> Balances::transfer_allow_death migration
3861
+ * https://github.com/paritytech/substrate/pull/12951
3862
+ *
3863
+ * `transfer_allow_death` is the preferred method,
3864
+ * so if something goes wrong during detection, we should assume the chain has migrated
3865
+ * @param metadataRpc string containing the hashed RPC metadata for the chain
3866
+ * @returns
3867
+ */
3868
+ const detectTransferMethod = metadataRpc => {
3869
+ const pjsMetadata = new types.Metadata(new types.TypeRegistry(), metadataRpc);
3870
+ pjsMetadata.registry.setMetadata(pjsMetadata);
3871
+ const balancesPallet = pjsMetadata.asLatest.pallets.find(pallet => pallet.name.eq("Balances"));
3872
+ const balancesCallsTypeIndex = balancesPallet?.calls.value.type.toNumber();
3873
+ const balancesCallsType = balancesCallsTypeIndex !== undefined ? pjsMetadata.asLatest.lookup.types[balancesCallsTypeIndex] : undefined;
3874
+ const hasDeprecatedTransferCall = balancesCallsType?.type.def.asVariant?.variants.find(variant => variant.name.eq("transfer")) !== undefined;
3875
+ return hasDeprecatedTransferCall ? "transfer" : "transfer_allow_death";
3876
+ };
3877
+
3878
+ const getUniqueChainIds = (addressesByToken, tokens) => [...new Set(Object.keys(addressesByToken).map(tokenId => tokens[tokenId]?.networkId).flatMap(chainId => chainId ? [chainId] : []))];
3879
+
3880
+ const makeContractCaller = ({
3881
+ chainConnector,
3882
+ chainId,
3883
+ registry
3884
+ }) => async (callFrom, contractAddress, inputData) => registry.createType("ContractExecResult", await chainConnector.send(chainId, "state_call", ["ContractsApi_call", util$1.u8aToHex(util$1.u8aConcatStrict([
3885
+ // origin
3886
+ registry.createType("AccountId", callFrom).toU8a(),
3887
+ // dest
3888
+ registry.createType("AccountId", contractAddress).toU8a(),
3889
+ // value
3890
+ registry.createType("Balance", 0).toU8a(),
3891
+ // gasLimit
3892
+ registry.createType("Option<WeightV2>").toU8a(),
3893
+ // storageDepositLimit
3894
+ registry.createType("Option<Balance>").toU8a(),
3895
+ // inputData
3896
+ inputData instanceof Uint8Array ? inputData : inputData.toU8a()]))]));
3897
+
3898
+ /**
3899
+ * Pass some these into an `RpcStateQueryHelper` in order to easily batch multiple state queries into the one rpc call.
3900
+ */
3901
+
3902
+ /**
3903
+ * Used by a variety of balance modules to help batch multiple state queries into the one rpc call.
3904
+ */
3905
+ class RpcStateQueryHelper {
3906
+ #chainConnector;
3907
+ #queries;
3908
+ constructor(chainConnector, queries) {
3909
+ this.#chainConnector = chainConnector;
3910
+ this.#queries = queries;
3911
+ }
3912
+ async subscribe(callback, timeout = false, subscribeMethod = "state_subscribeStorage", responseMethod = "state_storage", unsubscribeMethod = "state_unsubscribeStorage") {
3913
+ const queriesByChain = groupBy__default.default(this.#queries, "chainId");
3914
+ const subscriptions = Object.entries(queriesByChain).map(([chainId, queries]) => {
3915
+ const params = [queries.map(({
3916
+ stateKey
3917
+ }) => stateKey)];
3918
+ const unsub = this.#chainConnector.subscribe(chainId, subscribeMethod, responseMethod, params, (error, result) => {
3919
+ error ? callback(error) : callback(null, this.#distributeChangesToQueryDecoders.call(this, chainId, result));
3920
+ }, timeout);
3921
+ return () => unsub.then(unsubscribe => unsubscribe(unsubscribeMethod));
3972
3922
  });
3973
- return acc;
3974
- }, {
3975
- success: [],
3976
- errors: []
3923
+ return () => subscriptions.forEach(unsubscribe => unsubscribe());
3924
+ }
3925
+ async fetch(method = "state_queryStorageAt") {
3926
+ const queriesByChain = groupBy__default.default(this.#queries, "chainId");
3927
+ const resultsByChain = await Promise.all(Object.entries(queriesByChain).map(async ([chainId, queries]) => {
3928
+ const params = [queries.map(({
3929
+ stateKey
3930
+ }) => stateKey)];
3931
+ const result = (await this.#chainConnector.send(chainId, method, params))[0];
3932
+ return this.#distributeChangesToQueryDecoders.call(this, chainId, result);
3933
+ }));
3934
+ return resultsByChain.flatMap(result => result);
3935
+ }
3936
+ #distributeChangesToQueryDecoders(chainId, result) {
3937
+ if (typeof result !== "object" || result === null) return [];
3938
+ if (!util.hasOwnProperty(result, "changes") || typeof result.changes !== "object") return [];
3939
+ if (!Array.isArray(result.changes)) return [];
3940
+ return result.changes.flatMap(([reference, change]) => {
3941
+ if (typeof reference !== "string") {
3942
+ log.warn(`Received non-string reference in RPC result: ${reference}`);
3943
+ return [];
3944
+ }
3945
+ if (typeof change !== "string" && change !== null) {
3946
+ log.warn(`Received non-string and non-null change in RPC result: ${reference} | ${change}`);
3947
+ return [];
3948
+ }
3949
+ const query = this.#queries.find(({
3950
+ chainId: cId,
3951
+ stateKey
3952
+ }) => cId === chainId && stateKey === reference);
3953
+ if (!query) {
3954
+ log.warn(`Failed to find query:\n${reference} in\n${this.#queries.map(({
3955
+ stateKey
3956
+ }) => stateKey)}`);
3957
+ return [];
3958
+ }
3959
+ return [query.decodeResult(change)];
3960
+ });
3961
+ }
3962
+ }
3963
+
3964
+ const configureStore = (dbTable = db.balancesBlob) => ({
3965
+ persistData: async balances => {
3966
+ const output = compress(balances);
3967
+ await dbTable.clear();
3968
+ await dbTable.put({
3969
+ data: output,
3970
+ id: Date.now().toString()
3971
+ });
3972
+ },
3973
+ retrieveData: async () => {
3974
+ const compressedData = await dbTable.toCollection().first();
3975
+ if (!compressedData) return [];
3976
+ return decompress(compressedData.data);
3977
+ }
3978
+ });
3979
+ const compress = balances => pako__default.default.deflate(JSON.stringify(balances));
3980
+ const decompress = data => {
3981
+ const decompressed = pako__default.default.inflate(data, {
3982
+ to: "string"
3977
3983
  });
3984
+ return JSON.parse(decompressed);
3978
3985
  };
3986
+
3979
3987
  const buildQueries$5 = (networkId, balanceDefs, miniMetadata) => {
3980
3988
  const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
3981
3989
  storage: ["ForeignAssets", "Account"]
@@ -3997,10 +4005,10 @@ const buildQueries$5 = (networkId, balanceDefs, miniMetadata) => {
3997
4005
  log.warn(`Invalid assetId / address in ${networkId} storage query ${token.onChainId} / ${address}`);
3998
4006
  return null;
3999
4007
  }
4000
- const decodeResult = change => {
4008
+ const decodeResult = changes => {
4001
4009
  /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
4002
4010
 
4003
- const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
4011
+ const decoded = scale.decodeScale(scaleCoder, changes[0], `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
4004
4012
  balance: 0n,
4005
4013
  is_frozen: false,
4006
4014
  status: {
@@ -4042,13 +4050,85 @@ const buildQueries$5 = (networkId, balanceDefs, miniMetadata) => {
4042
4050
  return balance;
4043
4051
  };
4044
4052
  return {
4045
- chainId: networkId,
4046
- stateKey,
4053
+ stateKeys: [stateKey],
4047
4054
  decodeResult
4048
4055
  };
4049
4056
  }).filter(util.isNotNil);
4050
4057
  };
4051
4058
 
4059
+ const fetchBalances$5 = async ({
4060
+ networkId,
4061
+ tokensWithAddresses,
4062
+ connector,
4063
+ miniMetadata
4064
+ }) => {
4065
+ if (!tokensWithAddresses.length) return {
4066
+ success: [],
4067
+ errors: []
4068
+ };
4069
+ const balanceDefs = getBalanceDefs(tokensWithAddresses);
4070
+ if (!miniMetadata?.data) {
4071
+ log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$4} balances on ${networkId}.`);
4072
+ return {
4073
+ success: [],
4074
+ errors: balanceDefs.map(def => ({
4075
+ tokenId: def.token.id,
4076
+ address: def.address,
4077
+ error: new Error("Minimetadata is required for fetching balances")
4078
+ }))
4079
+ };
4080
+ }
4081
+ if (miniMetadata.source !== MODULE_TYPE$4) {
4082
+ log.warn(`Ignoring miniMetadata with source ${miniMetadata.source} in ${MODULE_TYPE$4}.`);
4083
+ return {
4084
+ success: [],
4085
+ errors: balanceDefs.map(def => ({
4086
+ tokenId: def.token.id,
4087
+ address: def.address,
4088
+ error: new Error(`Invalid request: miniMetadata source is not ${MODULE_TYPE$4}`)
4089
+ }))
4090
+ };
4091
+ }
4092
+ if (miniMetadata.chainId !== networkId) {
4093
+ log.warn(`Ignoring miniMetadata with chainId ${miniMetadata.chainId} in ${MODULE_TYPE$4}. Expected chainId is ${networkId}`);
4094
+ return {
4095
+ success: [],
4096
+ errors: balanceDefs.map(def => ({
4097
+ tokenId: def.token.id,
4098
+ address: def.address,
4099
+ error: new Error(`Invalid request: Expected chainId is ${networkId}`)
4100
+ }))
4101
+ };
4102
+ }
4103
+ const queries = buildQueries$5(networkId, balanceDefs, miniMetadata);
4104
+ const balances = await fetchRpcQueryPack(connector, networkId, queries);
4105
+ return balanceDefs.reduce((acc, def) => {
4106
+ const balance = balances.find(b => b?.address === def.address && b?.tokenId === def.token.id);
4107
+ if (balance) acc.success.push(balance);
4108
+ //if no entry consider empty balance
4109
+ else acc.success.push({
4110
+ address: def.address,
4111
+ networkId,
4112
+ tokenId: def.token.id,
4113
+ source: MODULE_TYPE$4,
4114
+ status: "live",
4115
+ values: [{
4116
+ type: "free",
4117
+ label: "free",
4118
+ amount: "0"
4119
+ }, {
4120
+ type: "locked",
4121
+ label: "frozen",
4122
+ amount: "0"
4123
+ }]
4124
+ });
4125
+ return acc;
4126
+ }, {
4127
+ success: [],
4128
+ errors: []
4129
+ });
4130
+ };
4131
+
4052
4132
  const fetchTokens$4 = async ({
4053
4133
  networkId,
4054
4134
  tokens,
@@ -4245,46 +4325,22 @@ const getTransferAllEncodedArgs$1 = (onChainId, to, codec) => {
4245
4325
  })]);
4246
4326
  };
4247
4327
 
4248
- const SUBSCRIPTION_INTERVAL$3 = 6_000;
4249
4328
  const subscribeBalances$4 = ({
4250
4329
  networkId,
4251
4330
  tokensWithAddresses,
4252
4331
  connector,
4253
4332
  miniMetadata
4254
4333
  }) => {
4255
- return new rxjs.Observable(subscriber => {
4256
- const abortController = new AbortController();
4257
-
4258
- // on hydration balances are fetched using a runtimeApi, which can't be subscribed to.
4259
- // => poll values for each block
4260
- const poll = async () => {
4261
- try {
4262
- if (abortController.signal.aborted) return;
4263
- const balances = await fetchBalances$5({
4264
- networkId,
4265
- tokensWithAddresses: tokensWithAddresses,
4266
- connector,
4267
- miniMetadata
4268
- });
4269
- if (abortController.signal.aborted) return;
4270
- subscriber.next(balances);
4271
- setTimeout(poll, SUBSCRIPTION_INTERVAL$3);
4272
- } catch (error) {
4273
- log.error("Error", {
4274
- module: MODULE_TYPE$4,
4275
- networkId,
4276
- miniMetadata,
4277
- addressesByToken: tokensWithAddresses,
4278
- error
4279
- });
4280
- subscriber.error(error);
4281
- }
4282
- };
4283
- poll();
4284
- return () => {
4285
- abortController.abort();
4286
- };
4287
- }).pipe(rxjs.distinctUntilChanged(lodash.isEqual));
4334
+ if (!tokensWithAddresses.length) return rxjs.of({
4335
+ success: [],
4336
+ errors: []
4337
+ });
4338
+ const balanceDefs = getBalanceDefs(tokensWithAddresses);
4339
+ const queries = buildQueries$5(networkId, balanceDefs, miniMetadata);
4340
+ return getRpcQueryPack$(connector, networkId, queries).pipe(rxjs.map(balances => ({
4341
+ success: balances,
4342
+ errors: []
4343
+ })));
4288
4344
  };
4289
4345
 
4290
4346
  const SubForeignAssetsBalanceModule = {
@@ -4303,48 +4359,30 @@ const SubForeignAssetsTokenConfigSchema = z__default.default.strictObject({
4303
4359
  ...TokenConfigBaseSchema.shape
4304
4360
  });
4305
4361
 
4306
- // to be used by chaindata too
4307
- const SubHydrationTokenConfigSchema = z__default.default.strictObject({
4308
- onChainId: chaindataProvider.SubHydrationTokenSchema.shape.onChainId,
4309
- ...TokenConfigBaseSchema.shape
4310
- });
4311
-
4312
- const MODULE_TYPE$3 = chaindataProvider.SubHydrationTokenSchema.shape.type.value;
4313
- const PLATFORM$3 = chaindataProvider.SubHydrationTokenSchema.shape.platform.value;
4314
-
4315
- const fetchRuntimeCallResult = async (connector, networkId, metadataRpc, apiName, method, args) => {
4316
- const {
4317
- builder
4318
- } = scale.parseMetadataRpc(metadataRpc);
4319
- const call = builder.buildRuntimeCall(apiName, method);
4320
- const hex = await connector.send(networkId, "state_call", [`${apiName}_${method}`, scale.toHex(call.args.enc(args))]);
4321
- return call.value.dec(hex);
4322
- };
4323
-
4324
- const tryGetConstantValue = (metadataRpc, pallet, constant) => {
4325
- const {
4326
- unifiedMetadata,
4327
- builder
4328
- } = scale.parseMetadataRpc(metadataRpc);
4329
- const encodedValue = unifiedMetadata.pallets.find(({
4330
- name
4331
- }) => name === pallet)?.constants.find(({
4332
- name
4333
- }) => name === constant)?.value;
4334
- if (!encodedValue) return null;
4335
- const codec = builder.buildConstant(pallet, constant);
4336
- return codec.dec(encodedValue);
4337
- };
4338
-
4362
+ // to be used by chaindata too
4363
+ const SubHydrationTokenConfigSchema = z__default.default.strictObject({
4364
+ onChainId: chaindataProvider.SubHydrationTokenSchema.shape.onChainId,
4365
+ ...TokenConfigBaseSchema.shape
4366
+ });
4367
+
4368
+ const MODULE_TYPE$3 = chaindataProvider.SubHydrationTokenSchema.shape.type.value;
4369
+ const PLATFORM$3 = chaindataProvider.SubHydrationTokenSchema.shape.platform.value;
4370
+
4339
4371
  const fetchBalances$4 = async ({
4340
4372
  networkId,
4341
4373
  tokensWithAddresses,
4342
4374
  connector,
4343
4375
  miniMetadata
4344
4376
  }) => {
4377
+ if (!tokensWithAddresses.length) return {
4378
+ success: [],
4379
+ errors: []
4380
+ };
4345
4381
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
4346
4382
  if (!miniMetadata?.data) {
4347
- log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$3} balances on ${networkId}.`);
4383
+ log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$3} balances on ${networkId}.`, {
4384
+ tokensWithAddresses
4385
+ });
4348
4386
  return {
4349
4387
  success: [],
4350
4388
  errors: balanceDefs.map(def => ({
@@ -4591,13 +4629,17 @@ const getTransferCallData$3 = ({
4591
4629
  };
4592
4630
  };
4593
4631
 
4594
- const SUBSCRIPTION_INTERVAL$2 = 6_000;
4632
+ const SUBSCRIPTION_INTERVAL$1 = 6_000;
4595
4633
  const subscribeBalances$3 = ({
4596
4634
  networkId,
4597
4635
  tokensWithAddresses,
4598
4636
  connector,
4599
4637
  miniMetadata
4600
4638
  }) => {
4639
+ if (!tokensWithAddresses.length) return rxjs.of({
4640
+ success: [],
4641
+ errors: []
4642
+ });
4601
4643
  return new rxjs.Observable(subscriber => {
4602
4644
  const abortController = new AbortController();
4603
4645
 
@@ -4614,7 +4656,7 @@ const subscribeBalances$3 = ({
4614
4656
  });
4615
4657
  if (abortController.signal.aborted) return;
4616
4658
  subscriber.next(balances);
4617
- setTimeout(poll, SUBSCRIPTION_INTERVAL$2);
4659
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$1);
4618
4660
  } catch (error) {
4619
4661
  log.error("Error", {
4620
4662
  module: MODULE_TYPE$3,
@@ -4646,54 +4688,6 @@ const SubHydrationBalanceModule = {
4646
4688
  const MODULE_TYPE$2 = chaindataProvider.SubNativeTokenSchema.shape.type.value;
4647
4689
  const PLATFORM$2 = chaindataProvider.SubNativeTokenSchema.shape.platform.value;
4648
4690
 
4649
- const fetchRpcQueryPack = async (connector, networkId, queries) => {
4650
- const allStateKeys = queries.flatMap(({
4651
- stateKeys
4652
- }) => stateKeys).filter(util.isNotNil);
4653
-
4654
- // doing a query without keys would throw an error => return early
4655
- if (!allStateKeys.length) return queries.map(({
4656
- stateKeys,
4657
- decodeResult
4658
- }) => decodeResult(stateKeys.map(() => null)));
4659
- const [result] = await connector.send(networkId, "state_queryStorageAt", [allStateKeys]);
4660
- return decodeRpcQueryPack(queries, result);
4661
- };
4662
- const getRpcQueryPack$ = (connector, networkId, queries, timeout = false) => {
4663
- const allStateKeys = queries.flatMap(({
4664
- stateKeys
4665
- }) => stateKeys).filter(util.isNotNil);
4666
-
4667
- // doing a query without keys would throw an error => return early
4668
- if (!allStateKeys.length) return rxjs.of(queries.map(({
4669
- stateKeys,
4670
- decodeResult
4671
- }) => decodeResult(stateKeys.map(() => null))));
4672
- return new rxjs.Observable(subscriber => {
4673
- const promUnsub = connector.subscribe(networkId, "state_subscribeStorage", "state_storage", [allStateKeys], (error, result) => {
4674
- if (error) subscriber.error(error);else subscriber.next(decodeRpcQueryPack(queries, result));
4675
- }, timeout);
4676
- return () => {
4677
- promUnsub.then(unsub => unsub("state_unsubscribeStorage"));
4678
- };
4679
- });
4680
- };
4681
- const decodeRpcQueryPack = (queries, result) => {
4682
- return queries.reduce((acc, {
4683
- stateKeys,
4684
- decodeResult
4685
- }) => {
4686
- const changes = stateKeys.map(stateKey => {
4687
- if (!stateKey) return null;
4688
- const change = result.changes.find(([key]) => key === stateKey);
4689
- if (!change) return null;
4690
- return change[1];
4691
- });
4692
- acc.push(decodeResult(changes));
4693
- return acc;
4694
- }, []);
4695
- };
4696
-
4697
4691
  const SUBTENSOR_ROOT_NETUID$1 = 0;
4698
4692
  const SUBTENSOR_MIN_STAKE_AMOUNT_PLANK$1 = 1000000n;
4699
4693
  const TAO_DECIMALS$1 = 9n;
@@ -5411,6 +5405,10 @@ const fetchBalances$3 = async ({
5411
5405
  connector,
5412
5406
  miniMetadata
5413
5407
  }) => {
5408
+ if (!tokensWithAddresses.length) return {
5409
+ success: [],
5410
+ errors: []
5411
+ };
5414
5412
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
5415
5413
  if (!miniMetadata?.data) {
5416
5414
  log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$2} balances on ${networkId}.`);
@@ -5692,6 +5690,11 @@ const subscribeBalances$2 = ({
5692
5690
  connector,
5693
5691
  miniMetadata
5694
5692
  }) => {
5693
+ if (!tokensWithAddresses.length) return rxjs.of({
5694
+ success: [],
5695
+ errors: []
5696
+ });
5697
+
5695
5698
  // could be use as shared observable key if we decide to cache the sub
5696
5699
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
5697
5700
  const baseQueries = buildBaseQueries(networkId, balanceDefs, miniMetadata);
@@ -6895,6 +6898,10 @@ const fetchBalances$2 = async ({
6895
6898
  tokensWithAddresses,
6896
6899
  connector
6897
6900
  }) => {
6901
+ if (!tokensWithAddresses.length) return {
6902
+ success: [],
6903
+ errors: []
6904
+ };
6898
6905
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
6899
6906
  if (!balanceDefs.length) return {
6900
6907
  success: [],
@@ -7082,13 +7089,17 @@ const getTransferCallData$1 = async ({
7082
7089
  };
7083
7090
  };
7084
7091
 
7085
- const SUBSCRIPTION_INTERVAL$1 = 6_000;
7092
+ const SUBSCRIPTION_INTERVAL = 6_000;
7086
7093
  const subscribeBalances$1 = ({
7087
7094
  networkId,
7088
7095
  tokensWithAddresses,
7089
7096
  connector,
7090
7097
  miniMetadata
7091
7098
  }) => {
7099
+ if (!tokensWithAddresses.length) return rxjs.of({
7100
+ success: [],
7101
+ errors: []
7102
+ });
7092
7103
  return new rxjs.Observable(subscriber => {
7093
7104
  const abortController = new AbortController();
7094
7105
 
@@ -7105,7 +7116,7 @@ const subscribeBalances$1 = ({
7105
7116
  });
7106
7117
  if (abortController.signal.aborted) return;
7107
7118
  subscriber.next(balances);
7108
- setTimeout(poll, SUBSCRIPTION_INTERVAL$1);
7119
+ setTimeout(poll, SUBSCRIPTION_INTERVAL);
7109
7120
  } catch (error) {
7110
7121
  log.error("Error", {
7111
7122
  module: MODULE_TYPE$1,
@@ -7143,12 +7154,77 @@ const SubPsp22TokenConfigSchema = z__default.default.strictObject({
7143
7154
  const MODULE_TYPE = chaindataProvider.SubTokensTokenSchema.shape.type.value;
7144
7155
  const PLATFORM = chaindataProvider.SubTokensTokenSchema.shape.platform.value;
7145
7156
 
7157
+ const buildQueries$4 = (networkId, balanceDefs, miniMetadata) => {
7158
+ const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
7159
+ storage: [miniMetadata.extra.palletId, "Accounts"]
7160
+ });
7161
+ return balanceDefs.map(({
7162
+ token,
7163
+ address
7164
+ }) => {
7165
+ const scaleCoder = networkStorageCoders?.storage;
7166
+ const getStateKey = onChainId => {
7167
+ try {
7168
+ return scaleCoder.keys.enc(address, scale.papiParse(onChainId));
7169
+ } catch {
7170
+ return null;
7171
+ }
7172
+ };
7173
+ const stateKey = getStateKey(token.onChainId);
7174
+ if (!stateKey) {
7175
+ log.warn(`Invalid assetId / address in ${networkId} storage query ${token.onChainId} / ${address}`);
7176
+ return null;
7177
+ }
7178
+ const decodeResult = changes => {
7179
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
7180
+
7181
+ const decoded = scale.decodeScale(scaleCoder, changes[0], `Failed to decode substrate-tokens balance on chain ${networkId}`) ?? {
7182
+ free: 0n,
7183
+ reserved: 0n,
7184
+ frozen: 0n
7185
+ };
7186
+ const free = (decoded?.free ?? 0n).toString();
7187
+ const reserved = (decoded?.reserved ?? 0n).toString();
7188
+ const frozen = (decoded?.frozen ?? 0n).toString();
7189
+ const balanceValues = [{
7190
+ type: "free",
7191
+ label: "free",
7192
+ amount: free.toString()
7193
+ }, {
7194
+ type: "reserved",
7195
+ label: "reserved",
7196
+ amount: reserved.toString()
7197
+ }, {
7198
+ type: "locked",
7199
+ label: "frozen",
7200
+ amount: frozen.toString()
7201
+ }];
7202
+ return {
7203
+ source: "substrate-tokens",
7204
+ status: "live",
7205
+ address,
7206
+ networkId,
7207
+ tokenId: token.id,
7208
+ values: balanceValues
7209
+ };
7210
+ };
7211
+ return {
7212
+ stateKeys: [stateKey],
7213
+ decodeResult
7214
+ };
7215
+ }).filter(util.isNotNil);
7216
+ };
7217
+
7146
7218
  const fetchBalances$1 = async ({
7147
7219
  networkId,
7148
7220
  tokensWithAddresses,
7149
7221
  connector,
7150
7222
  miniMetadata
7151
7223
  }) => {
7224
+ if (!tokensWithAddresses.length) return {
7225
+ success: [],
7226
+ errors: []
7227
+ };
7152
7228
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
7153
7229
  if (!miniMetadata?.data) {
7154
7230
  log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE} balances on ${networkId}.`);
@@ -7184,7 +7260,7 @@ const fetchBalances$1 = async ({
7184
7260
  };
7185
7261
  }
7186
7262
  const queries = buildQueries$4(networkId, balanceDefs, miniMetadata);
7187
- const balances = await new RpcStateQueryHelper(connector, queries).fetch();
7263
+ const balances = await fetchRpcQueryPack(connector, networkId, queries);
7188
7264
  return balanceDefs.reduce((acc, def) => {
7189
7265
  const balance = balances.find(b => b?.address === def.address && b?.tokenId === def.token.id);
7190
7266
  if (balance) acc.success.push(balance);
@@ -7211,67 +7287,6 @@ const fetchBalances$1 = async ({
7211
7287
  errors: []
7212
7288
  });
7213
7289
  };
7214
- const buildQueries$4 = (networkId, balanceDefs, miniMetadata) => {
7215
- const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
7216
- storage: [miniMetadata.extra.palletId, "Accounts"]
7217
- });
7218
- return balanceDefs.map(({
7219
- token,
7220
- address
7221
- }) => {
7222
- const scaleCoder = networkStorageCoders?.storage;
7223
- const getStateKey = onChainId => {
7224
- try {
7225
- return scaleCoder.keys.enc(address, scale.papiParse(onChainId));
7226
- } catch {
7227
- return null;
7228
- }
7229
- };
7230
- const stateKey = getStateKey(token.onChainId);
7231
- if (!stateKey) {
7232
- log.warn(`Invalid assetId / address in ${networkId} storage query ${token.onChainId} / ${address}`);
7233
- return null;
7234
- }
7235
- const decodeResult = change => {
7236
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
7237
-
7238
- const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-tokens balance on chain ${networkId}`) ?? {
7239
- free: 0n,
7240
- reserved: 0n,
7241
- frozen: 0n
7242
- };
7243
- const free = (decoded?.free ?? 0n).toString();
7244
- const reserved = (decoded?.reserved ?? 0n).toString();
7245
- const frozen = (decoded?.frozen ?? 0n).toString();
7246
- const balanceValues = [{
7247
- type: "free",
7248
- label: "free",
7249
- amount: free.toString()
7250
- }, {
7251
- type: "reserved",
7252
- label: "reserved",
7253
- amount: reserved.toString()
7254
- }, {
7255
- type: "locked",
7256
- label: "frozen",
7257
- amount: frozen.toString()
7258
- }];
7259
- return {
7260
- source: "substrate-tokens",
7261
- status: "live",
7262
- address,
7263
- networkId,
7264
- tokenId: token.id,
7265
- values: balanceValues
7266
- };
7267
- };
7268
- return {
7269
- chainId: networkId,
7270
- stateKey,
7271
- decodeResult
7272
- };
7273
- }).filter(util.isNotNil);
7274
- };
7275
7290
 
7276
7291
  const fetchTokens = async ({
7277
7292
  networkId,
@@ -7472,46 +7487,22 @@ const getCallDataOptions = (to, token, value, type, config) => {
7472
7487
  }] : []));
7473
7488
  };
7474
7489
 
7475
- const SUBSCRIPTION_INTERVAL = 6_000;
7476
7490
  const subscribeBalances = ({
7477
7491
  networkId,
7478
7492
  tokensWithAddresses,
7479
7493
  connector,
7480
7494
  miniMetadata
7481
7495
  }) => {
7482
- return new rxjs.Observable(subscriber => {
7483
- const abortController = new AbortController();
7484
-
7485
- // on hydration balances are fetched using a runtimeApi, which can't be subscribed to.
7486
- // => poll values for each block
7487
- const poll = async () => {
7488
- try {
7489
- if (abortController.signal.aborted) return;
7490
- const balances = await fetchBalances$1({
7491
- networkId,
7492
- tokensWithAddresses: tokensWithAddresses,
7493
- connector,
7494
- miniMetadata
7495
- });
7496
- if (abortController.signal.aborted) return;
7497
- subscriber.next(balances);
7498
- setTimeout(poll, SUBSCRIPTION_INTERVAL);
7499
- } catch (error) {
7500
- log.error("Error", {
7501
- module: MODULE_TYPE,
7502
- networkId,
7503
- miniMetadata,
7504
- addressesByToken: tokensWithAddresses,
7505
- error
7506
- });
7507
- subscriber.error(error);
7508
- }
7509
- };
7510
- poll();
7511
- return () => {
7512
- abortController.abort();
7513
- };
7514
- }).pipe(rxjs.distinctUntilChanged(lodash.isEqual));
7496
+ if (!tokensWithAddresses.length) return rxjs.of({
7497
+ success: [],
7498
+ errors: []
7499
+ });
7500
+ const balanceDefs = getBalanceDefs(tokensWithAddresses);
7501
+ const queries = buildQueries$4(networkId, balanceDefs, miniMetadata);
7502
+ return getRpcQueryPack$(connector, networkId, queries).pipe(rxjs.map(balances => ({
7503
+ success: balances,
7504
+ errors: []
7505
+ })));
7515
7506
  };
7516
7507
 
7517
7508
  const SubTokensBalanceModule = {
@@ -10371,9 +10362,17 @@ class BalancesProvider {
10371
10362
  miniMetadatas: lodash.values(miniMetadatas).filter(util.isNotNil)
10372
10363
  })));
10373
10364
  }
10374
- getBalances$(addressesByToken) {
10375
- const networkIds = lodash.uniq(lodash.keys(addressesByToken).map(tokenId => chaindataProvider.parseTokenId(tokenId).networkId));
10376
- return rxjs.combineLatest(networkIds.map(networkId => this.getNetworkBalances$(networkId, addressesByToken))).pipe(rxjs.map(results => {
10365
+
10366
+ // this is the only public method
10367
+ getBalances$(addressesByTokenId) {
10368
+ // split by network
10369
+ const addressesByTokenIdByNetworkId = lodash.toPairs(addressesByTokenId).reduce((acc, [tokenId, addresses]) => {
10370
+ const networkId = chaindataProvider.parseTokenId(tokenId).networkId;
10371
+ if (!acc[networkId]) acc[networkId] = {};
10372
+ acc[networkId][tokenId] = addresses;
10373
+ return acc;
10374
+ }, {});
10375
+ return rxjs.combineLatest(lodash.toPairs(addressesByTokenIdByNetworkId).map(([networkId]) => this.getNetworkBalances$(networkId, addressesByTokenIdByNetworkId[networkId]))).pipe(rxjs.map(results => {
10377
10376
  return {
10378
10377
  status: results.some(({
10379
10378
  status
@@ -10382,9 +10381,17 @@ class BalancesProvider {
10382
10381
  };
10383
10382
  }), rxjs.startWith({
10384
10383
  status: "initialising",
10385
- balances: this.getStoredBalances(addressesByToken)
10384
+ balances: this.getStoredBalances(addressesByTokenId)
10386
10385
  }), rxjs.distinctUntilChanged(lodash.isEqual));
10387
10386
  }
10387
+ fetchBalances(addressesByTokenId) {
10388
+ // TODO: better
10389
+ return rxjs.firstValueFrom(this.getBalances$(addressesByTokenId).pipe(rxjs.filter(({
10390
+ status
10391
+ }) => status === "live"), rxjs.map(({
10392
+ balances
10393
+ }) => balances)));
10394
+ }
10388
10395
  getNetworkBalances$(networkId, addressesByTokenId) {
10389
10396
  const network$ = this.#chaindataProvider.getNetworkById$(networkId);
10390
10397
  const tokensMapById$ = this.#chaindataProvider.getTokensMapById$();
@@ -10456,7 +10463,42 @@ class BalancesProvider {
10456
10463
  }));
10457
10464
  }
10458
10465
  getNetworkMiniMetadatas$(networkId) {
10459
- return this.#chaindataProvider.getNetworkById$(networkId).pipe(rxjs.switchMap(network => chaindataProvider.isNetworkDot(network) && this.#chainConnectors.substrate ? rxjs.from(getMiniMetadatas(this.#chainConnectors.substrate, this.#chaindataProvider, networkId)) : rxjs.of([])));
10466
+ return this.#chaindataProvider.getNetworkById$(networkId).pipe(rxjs.switchMap(network => chaindataProvider.isNetworkDot(network) && this.#chainConnectors.substrate ? rxjs.from(getSpecVersion(this.#chainConnectors.substrate, networkId)).pipe(rxjs.switchMap(specVersion => this.getMiniMetadatas$(networkId, specVersion))) : rxjs.of([])));
10467
+ }
10468
+ getMiniMetadatas$(networkId, specVersion) {
10469
+ return rxjs.combineLatest({
10470
+ defaultMiniMetadatas: this.getDefaultMiniMetadatas$(networkId, specVersion),
10471
+ storedMiniMetadatas: this.getStoredMiniMetadatas$(networkId, specVersion)
10472
+ }).pipe(rxjs.switchMap(({
10473
+ storedMiniMetadatas,
10474
+ defaultMiniMetadatas
10475
+ }) => {
10476
+ if (defaultMiniMetadatas.length) return rxjs.of(defaultMiniMetadatas);
10477
+ if (storedMiniMetadatas.length) return rxjs.of(storedMiniMetadatas);
10478
+ if (!this.#chainConnectors.substrate) return rxjs.of([]);
10479
+ return rxjs.from(
10480
+ // fetch them from the chain
10481
+ getMiniMetadatas(this.#chainConnectors.substrate, this.#chaindataProvider, networkId)).pipe(
10482
+ // and persist in storage for later reuse
10483
+ rxjs.tap(newMiniMetadatas => {
10484
+ if (!newMiniMetadatas.length) return;
10485
+ const storage = this.#storage.getValue();
10486
+ const miniMetadatas = lodash.assign(
10487
+ // keep minimetadatas of other networks
10488
+ lodash.keyBy(lodash.values(storage.miniMetadatas).filter(m => m.chainId !== networkId), m => m.id),
10489
+ // add the ones for our network
10490
+ lodash.keyBy(newMiniMetadatas, m => m.id));
10491
+ this.#storage.next(lodash.assign({}, storage, {
10492
+ miniMetadatas
10493
+ }));
10494
+ }));
10495
+ }));
10496
+ }
10497
+ getStoredMiniMetadatas$(networkId, specVersion) {
10498
+ return this.storage$.pipe(rxjs.map(storage => storage.miniMetadatas.filter(m => m.chainId === networkId && m.specVersion === specVersion && m.version === chaindataProvider.MINIMETADATA_VERSION)), rxjs.distinctUntilChanged(lodash.isEqual));
10499
+ }
10500
+ getDefaultMiniMetadatas$(networkId, specVersion) {
10501
+ return this.#chaindataProvider.miniMetadatas$.pipe(rxjs.map(miniMetadatas => miniMetadatas.filter(m => m.chainId === networkId && m.specVersion === specVersion && m.version === chaindataProvider.MINIMETADATA_VERSION)), rxjs.distinctUntilChanged(lodash.isEqual));
10460
10502
  }
10461
10503
  getStoredBalances(addressesByToken) {
10462
10504
  const balanceDefs = lodash.toPairs(addressesByToken).flatMap(([tokenId, addresses]) => addresses.map(address => [tokenId, address]));
@@ -10467,13 +10509,6 @@ class BalancesProvider {
10467
10509
  }
10468
10510
  }
10469
10511
 
10470
- // const getStoredBalances = (
10471
- // storedBalances: Record<string, IBalance>,
10472
- // addressesByToken: Record<TokenId, Address[]>,
10473
- // ): IBalance[] => {
10474
-
10475
- // }
10476
-
10477
10512
  Object.defineProperty(exports, "MINIMETADATA_VERSION", {
10478
10513
  enumerable: true,
10479
10514
  get: function () { return chaindataProvider.MINIMETADATA_VERSION; }