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