@talismn/balances 0.0.0-pr2067-20250630033435 → 0.0.0-pr2075-20250703111149
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/BalanceModule.d.ts +23 -12
- package/dist/declarations/src/getMiniMetadata/getMetadataRpc.d.ts +3 -0
- package/dist/declarations/src/getMiniMetadata/getMiniMetadatas.d.ts +4 -0
- package/dist/declarations/src/getMiniMetadata/getSpecVersion.d.ts +6 -0
- package/dist/declarations/src/getMiniMetadata/getUpdatedMiniMetadatas.d.ts +4 -0
- package/dist/declarations/src/getMiniMetadata/index.d.ts +5 -0
- package/dist/declarations/src/index.d.ts +0 -3
- package/dist/declarations/src/libVersion.d.ts +1 -0
- package/dist/declarations/src/modules/EvmErc20Module.d.ts +20 -23
- package/dist/declarations/src/modules/EvmNativeModule.d.ts +19 -18
- package/dist/declarations/src/modules/EvmUniswapV2Module.d.ts +28 -30
- package/dist/declarations/src/modules/SubstrateAssetsModule.d.ts +20 -18
- package/dist/declarations/src/modules/SubstrateForeignAssetsModule.d.ts +20 -18
- package/dist/declarations/src/modules/SubstrateNativeModule/index.d.ts +17 -4
- package/dist/declarations/src/modules/SubstrateNativeModule/subscribeNompoolStaking.d.ts +3 -3
- package/dist/declarations/src/modules/SubstrateNativeModule/subscribeSubtensorStaking.d.ts +3 -3
- package/dist/declarations/src/modules/SubstrateNativeModule/types.d.ts +6 -23
- package/dist/declarations/src/modules/SubstrateNativeModule/util/QueryCache.d.ts +6 -6
- package/dist/declarations/src/modules/SubstrateNativeModule/util/buildQueries.d.ts +5 -4
- package/dist/declarations/src/modules/SubstrateNativeModule/util/sortChains.d.ts +1 -1
- package/dist/declarations/src/modules/SubstrateNativeModule/util/systemProperties.d.ts +5 -0
- package/dist/declarations/src/modules/SubstratePsp22Module.d.ts +20 -19
- package/dist/declarations/src/modules/SubstrateTokensModule.d.ts +22 -22
- package/dist/declarations/src/modules/index.d.ts +213 -38
- package/dist/declarations/src/modules/util/InferBalanceModuleTypes.d.ts +5 -3
- package/dist/declarations/src/modules/util/buildStorageCoders.d.ts +16 -7
- package/dist/declarations/src/modules/util/getAddresssesByTokenByNetwork.d.ts +3 -0
- package/dist/declarations/src/modules/util/getUniqueChainIds.d.ts +2 -2
- package/dist/declarations/src/modules/util/index.d.ts +0 -1
- package/dist/declarations/src/types/balances.d.ts +220 -72
- package/dist/declarations/src/types/balancetypes.d.ts +8 -18
- package/dist/declarations/src/types/minimetadatas.d.ts +6 -24
- package/dist/declarations/src/types/tokens.d.ts +11 -0
- package/dist/talismn-balances.cjs.dev.js +1415 -2090
- package/dist/talismn-balances.cjs.prod.js +1415 -2090
- package/dist/talismn-balances.esm.js +1417 -2086
- package/package.json +9 -8
- package/dist/declarations/src/EvmTokenFetcher.d.ts +0 -11
- package/dist/declarations/src/MiniMetadataUpdater.d.ts +0 -43
- package/dist/declarations/src/modules/SubstrateEquilibriumModule.d.ts +0 -39
- package/dist/declarations/src/modules/SubstrateNativeModule/subscribeCrowdloans.d.ts +0 -5
- package/dist/declarations/src/modules/SubstrateNativeModule/util/crowdloanFundContributionsChildKey.d.ts +0 -4
- package/dist/declarations/src/modules/SubstrateNativeModule/util/detectMiniMetadataChanges.d.ts +0 -2
- package/dist/declarations/src/modules/util/findChainMeta.d.ts +0 -8
- package/dist/declarations/src/util/hydrateChaindata.d.ts +0 -8
- package/dist/declarations/src/util/index.d.ts +0 -1
@@ -1,40 +1,42 @@
|
|
1
1
|
'use strict';
|
2
2
|
|
3
|
-
var PromisePool = require('@supercharge/promise-pool');
|
4
|
-
var chaindataProvider = require('@talismn/chaindata-provider');
|
5
|
-
var sapi = require('@talismn/sapi');
|
6
3
|
var dexie = require('dexie');
|
7
|
-
var isEqual = require('lodash/isEqual');
|
8
|
-
var rxjs = require('rxjs');
|
9
4
|
var anylogger = require('anylogger');
|
5
|
+
var chaindataProvider = require('@talismn/chaindata-provider');
|
10
6
|
var tokenRates = require('@talismn/token-rates');
|
11
7
|
var util = require('@talismn/util');
|
12
8
|
var BigNumber = require('bignumber.js');
|
13
9
|
var util$1 = require('@polkadot/util');
|
14
10
|
var utilCrypto = require('@polkadot/util-crypto');
|
15
11
|
var pako = require('pako');
|
12
|
+
var z = require('zod/v4');
|
16
13
|
var viem = require('viem');
|
14
|
+
var lodash = require('lodash');
|
15
|
+
var isEqual = require('lodash/isEqual');
|
17
16
|
var txwrapperCore = require('@substrate/txwrapper-core');
|
18
17
|
var scale = require('@talismn/scale');
|
19
18
|
var camelCase = require('lodash/camelCase');
|
19
|
+
var PQueue = require('p-queue');
|
20
|
+
var sapi = require('@talismn/sapi');
|
20
21
|
var types = require('@polkadot/types');
|
21
22
|
var groupBy = require('lodash/groupBy');
|
22
23
|
var utils = require('@polkadot-api/utils');
|
23
24
|
var polkadotApi = require('polkadot-api');
|
24
25
|
var chainConnector = require('@talismn/chain-connector');
|
26
|
+
var rxjs = require('rxjs');
|
25
27
|
var scaleTs = require('scale-ts');
|
26
28
|
var upperFirst = require('lodash/upperFirst');
|
27
29
|
var apiContract = require('@polkadot/api-contract');
|
28
|
-
var lzString = require('lz-string');
|
29
30
|
|
30
31
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
31
32
|
|
32
|
-
var PromisePool__default = /*#__PURE__*/_interopDefault(PromisePool);
|
33
|
-
var isEqual__default = /*#__PURE__*/_interopDefault(isEqual);
|
34
33
|
var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
|
35
34
|
var BigNumber__default = /*#__PURE__*/_interopDefault(BigNumber);
|
36
35
|
var pako__default = /*#__PURE__*/_interopDefault(pako);
|
36
|
+
var z__default = /*#__PURE__*/_interopDefault(z);
|
37
|
+
var isEqual__default = /*#__PURE__*/_interopDefault(isEqual);
|
37
38
|
var camelCase__default = /*#__PURE__*/_interopDefault(camelCase);
|
39
|
+
var PQueue__default = /*#__PURE__*/_interopDefault(PQueue);
|
38
40
|
var groupBy__default = /*#__PURE__*/_interopDefault(groupBy);
|
39
41
|
var upperFirst__default = /*#__PURE__*/_interopDefault(upperFirst);
|
40
42
|
|
@@ -71,48 +73,11 @@ const DefaultBalanceModule = type => ({
|
|
71
73
|
// internal
|
72
74
|
//
|
73
75
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
class EvmTokenFetcher {
|
78
|
-
#chaindataProvider;
|
79
|
-
#balanceModules;
|
80
|
-
constructor(chaindataProvider, balanceModules) {
|
81
|
-
this.#chaindataProvider = chaindataProvider;
|
82
|
-
this.#balanceModules = balanceModules;
|
83
|
-
}
|
84
|
-
async update(evmNetworkIds) {
|
85
|
-
await this.updateEvmNetworks(evmNetworkIds);
|
86
|
-
}
|
87
|
-
async updateEvmNetworks(evmNetworkIds) {
|
88
|
-
const evmNetworks = new Map((await this.#chaindataProvider.evmNetworks()).map(evmNetwork => [evmNetwork.id, evmNetwork]));
|
89
|
-
const allEvmTokens = {};
|
90
|
-
const evmNetworkConcurrency = 10;
|
91
|
-
await PromisePool.PromisePool.withConcurrency(evmNetworkConcurrency).for(evmNetworkIds).process(async evmNetworkId => {
|
92
|
-
const evmNetwork = evmNetworks.get(evmNetworkId);
|
93
|
-
if (!evmNetwork) return;
|
94
|
-
for (const mod of this.#balanceModules.filter(m => m.type.startsWith("evm-"))) {
|
95
|
-
const balancesConfig = (evmNetwork.balancesConfig ?? []).find(({
|
96
|
-
moduleType
|
97
|
-
}) => moduleType === mod.type);
|
98
|
-
const moduleConfig = balancesConfig?.moduleConfig ?? {};
|
99
|
-
|
100
|
-
// chainMeta arg only needs the isTestnet property, let's save a db roundtrip for now
|
101
|
-
const isTestnet = evmNetwork.isTestnet ?? false;
|
102
|
-
const tokens = await mod.fetchEvmChainTokens(evmNetworkId, {
|
103
|
-
isTestnet
|
104
|
-
}, moduleConfig);
|
105
|
-
for (const [tokenId, token] of Object.entries(tokens)) allEvmTokens[tokenId] = token;
|
106
|
-
}
|
107
|
-
});
|
108
|
-
await this.#chaindataProvider.updateEvmNetworkTokens(Object.values(allEvmTokens));
|
109
|
-
}
|
110
|
-
}
|
111
|
-
|
112
|
-
var packageJson = {
|
113
|
-
name: "@talismn/balances"};
|
76
|
+
var pkg = {
|
77
|
+
name: "@talismn/balances",
|
78
|
+
version: "0.0.0-pr2075-20250703111149"};
|
114
79
|
|
115
|
-
var log = anylogger__default.default(
|
80
|
+
var log = anylogger__default.default(pkg.name);
|
116
81
|
|
117
82
|
function excludeFromTransferableAmount(locks) {
|
118
83
|
if (typeof locks === "string") return BigInt(locks);
|
@@ -322,15 +287,13 @@ class Balances {
|
|
322
287
|
return new SumBalancesFormatter(this);
|
323
288
|
}
|
324
289
|
}
|
325
|
-
const isBalanceEvm = balance => "evmNetworkId" in balance;
|
326
290
|
const getBalanceId = balance => {
|
327
291
|
const {
|
328
292
|
source,
|
329
293
|
address,
|
330
294
|
tokenId
|
331
295
|
} = balance;
|
332
|
-
|
333
|
-
return [source, address, locationId, tokenId].filter(util.isTruthy).join("::");
|
296
|
+
return [source, address, tokenId].join("::");
|
334
297
|
};
|
335
298
|
|
336
299
|
/**
|
@@ -392,23 +355,17 @@ class Balance {
|
|
392
355
|
get address() {
|
393
356
|
return this.#storage.address;
|
394
357
|
}
|
395
|
-
get
|
396
|
-
return
|
397
|
-
}
|
398
|
-
get chain() {
|
399
|
-
return this.#db?.chains && this.chainId && this.#db?.chains[this.chainId] || null;
|
358
|
+
get networkId() {
|
359
|
+
return this.#storage.networkId;
|
400
360
|
}
|
401
|
-
get
|
402
|
-
return
|
403
|
-
}
|
404
|
-
get evmNetwork() {
|
405
|
-
return this.#db?.evmNetworks && this.evmNetworkId && this.#db?.evmNetworks[this.evmNetworkId] || null;
|
361
|
+
get network() {
|
362
|
+
return this.#db?.networks?.[this.networkId] || null;
|
406
363
|
}
|
407
364
|
get tokenId() {
|
408
365
|
return this.#storage.tokenId;
|
409
366
|
}
|
410
367
|
get token() {
|
411
|
-
return this.#db?.tokens
|
368
|
+
return this.#db?.tokens?.[this.tokenId] || null;
|
412
369
|
}
|
413
370
|
get decimals() {
|
414
371
|
return this.token?.decimals || null;
|
@@ -421,9 +378,9 @@ class Balance {
|
|
421
378
|
//
|
422
379
|
// This means that those rates are always available for calculating the uniswapv2 rates,
|
423
380
|
// regardless of whether or not the underlying erc20s are actually in chaindata and enabled.
|
424
|
-
if (this.isSource("evm-uniswapv2") && this.token?.type === "evm-uniswapv2"
|
425
|
-
const tokenId0 = evmErc20TokenId
|
426
|
-
const tokenId1 = evmErc20TokenId
|
381
|
+
if (this.isSource("evm-uniswapv2") && this.token?.type === "evm-uniswapv2") {
|
382
|
+
const tokenId0 = chaindataProvider.evmErc20TokenId(this.networkId, this.token.tokenAddress0);
|
383
|
+
const tokenId1 = chaindataProvider.evmErc20TokenId(this.networkId, this.token.tokenAddress1);
|
427
384
|
const decimals = this.token.decimals;
|
428
385
|
const decimals0 = this.token.decimals0;
|
429
386
|
const decimals1 = this.token.decimals1;
|
@@ -502,9 +459,7 @@ class Balance {
|
|
502
459
|
const nomPoolStakedPlancks = this.locks.some(lock => lock.source === "substrate-native-holds" && lock.label === "DelegatedStaking") ? 0n : this.nompools.map(({
|
503
460
|
amount
|
504
461
|
}) => amount.planck).reduce((a, b) => a + b, 0n);
|
505
|
-
return this.#format(this.free.planck + this.reserved.planck + nomPoolStakedPlancks + this.
|
506
|
-
amount
|
507
|
-
}) => amount.planck).reduce((a, b) => a + b, 0n) + this.subtensor.map(({
|
462
|
+
return this.#format(this.free.planck + this.reserved.planck + nomPoolStakedPlancks + this.subtensor.map(({
|
508
463
|
amount
|
509
464
|
}) => amount.planck).reduce((a, b) => a + b, 0n) + includeInTotalExtraAmount(extra));
|
510
465
|
}
|
@@ -540,9 +495,6 @@ class Balance {
|
|
540
495
|
get locks() {
|
541
496
|
return this.getValue("locked");
|
542
497
|
}
|
543
|
-
get crowdloans() {
|
544
|
-
return this.getValue("crowdloan");
|
545
|
-
}
|
546
498
|
get nompools() {
|
547
499
|
return this.getValue("nompool");
|
548
500
|
}
|
@@ -621,7 +573,7 @@ class Balance {
|
|
621
573
|
const nomPoolStakedPlancks = this.locks.some(lock => lock.source === "substrate-native-holds" && lock.label === "DelegatedStaking") ? 0n : this.nompools.map(({
|
622
574
|
amount
|
623
575
|
}) => amount.planck).reduce((a, b) => a + b, 0n);
|
624
|
-
const otherUnavailable = nomPoolStakedPlancks + this.
|
576
|
+
const otherUnavailable = nomPoolStakedPlancks + this.subtensor.reduce((total, each) => total + each.amount.planck, 0n);
|
625
577
|
return this.#format(baseUnavailable + otherUnavailable);
|
626
578
|
}
|
627
579
|
|
@@ -856,10 +808,6 @@ const filterMirrorTokens = (balance, i, balances) => {
|
|
856
808
|
return !mirrorOf || !balances.find(b => b.tokenId === mirrorOf);
|
857
809
|
};
|
858
810
|
|
859
|
-
// TODO: Move this into a common module which can then be imported both here and into EvmErc20Module
|
860
|
-
// We can't import this directly from EvmErc20Module because then we'd have a circular dependency
|
861
|
-
const evmErc20TokenId$1 = (chainId, tokenContractAddress) => `${chainId}-evm-erc20-${tokenContractAddress}`.toLowerCase();
|
862
|
-
|
863
811
|
/**
|
864
812
|
* `BalanceTypes` is an automatically determined sub-selection of `PluginBalanceTypes`.
|
865
813
|
*
|
@@ -890,7 +838,6 @@ const getValueId = amount => {
|
|
890
838
|
const getMetaId = () => {
|
891
839
|
const meta = amount.meta;
|
892
840
|
if (!meta) return "";
|
893
|
-
if (amount.type === "crowdloan") return meta.paraId?.toString() ?? "";
|
894
841
|
if (amount.type === "nompool") return meta.poolId?.toString() ?? "";
|
895
842
|
if (amount.type === "subtensor") {
|
896
843
|
const {
|
@@ -914,10 +861,9 @@ const getValueId = amount => {
|
|
914
861
|
const deriveMiniMetadataId = ({
|
915
862
|
source,
|
916
863
|
chainId,
|
917
|
-
specName,
|
918
864
|
specVersion,
|
919
|
-
|
920
|
-
}) => util$1.u8aToHex(utilCrypto.xxhashAsU8a(new TextEncoder().encode(`${source}${chainId}${
|
865
|
+
libVersion
|
866
|
+
}) => util$1.u8aToHex(utilCrypto.xxhashAsU8a(new TextEncoder().encode(`${source}${chainId}${specVersion}${libVersion}`), 64), undefined, false);
|
921
867
|
|
922
868
|
// for DB version 3, Wallet version 1.21.0
|
923
869
|
const upgradeRemoveSymbolFromNativeTokenId = async tx => {
|
@@ -1012,230 +958,9 @@ class TalismanBalancesDatabase extends dexie.Dexie {
|
|
1012
958
|
}
|
1013
959
|
const db = new TalismanBalancesDatabase();
|
1014
960
|
|
1015
|
-
const
|
1016
|
-
|
1017
|
-
|
1018
|
-
* A substrate dapp needs access to a set of types when it wants to communicate with a blockchain node.
|
1019
|
-
*
|
1020
|
-
* These types are used to encode requests & decode responses via the SCALE codec.
|
1021
|
-
* Each chain generally has its own set of types.
|
1022
|
-
*
|
1023
|
-
* Substrate provides a construct to retrieve these types from a blockchain node.
|
1024
|
-
* The chain metadata.
|
1025
|
-
*
|
1026
|
-
* The metadata includes the types required for any communication with the chain,
|
1027
|
-
* including lots of methods which are not relevant to balance fetching.
|
1028
|
-
*
|
1029
|
-
* As such, the metadata can clock in at around 1-2MB per chain, which is a lot of storage
|
1030
|
-
* for browser-based dapps which want to connect to lots of chains.
|
1031
|
-
*
|
1032
|
-
* By utilizing the wonderful [scale-ts](https://github.com/unstoppablejs/unstoppablejs/tree/main/packages/scale-ts#readme) library,
|
1033
|
-
* we can trim the chain metadata down so that it only includes the types we need for balance fetching.
|
1034
|
-
*
|
1035
|
-
* Each balance module has a function to do just that, `BalanceModule::fetchSubstrateChainMeta`.
|
1036
|
-
*
|
1037
|
-
* But, we only want to run this operation when necessary.
|
1038
|
-
*
|
1039
|
-
* The purpose of this class, `MiniMetadataUpdater`, is to maintain a local cache of
|
1040
|
-
* trimmed-down metadatas, which we'll refer to as `MiniMetadatas`.
|
1041
|
-
*/
|
1042
|
-
class MiniMetadataUpdater {
|
1043
|
-
#lastHydratedMiniMetadatasAt = 0;
|
1044
|
-
#lastHydratedCustomChainsAt = 0;
|
1045
|
-
#chainConnectors;
|
1046
|
-
#chaindataProvider;
|
1047
|
-
#balanceModules;
|
1048
|
-
constructor(chainConnectors, chaindataProvider, balanceModules) {
|
1049
|
-
this.#chainConnectors = chainConnectors;
|
1050
|
-
this.#chaindataProvider = chaindataProvider;
|
1051
|
-
this.#balanceModules = balanceModules;
|
1052
|
-
}
|
1053
|
-
|
1054
|
-
/** Subscribe to the metadata for a chain */
|
1055
|
-
subscribe(chainId) {
|
1056
|
-
return rxjs.from(dexie.liveQuery(() => db.miniMetadatas.filter(m => m.chainId === chainId).toArray().then(array => array[0])));
|
1057
|
-
}
|
1058
|
-
async update(chainIds) {
|
1059
|
-
await this.updateSubstrateChains(chainIds);
|
1060
|
-
}
|
1061
|
-
async statuses(chains) {
|
1062
|
-
const ids = await db.miniMetadatas.orderBy("id").primaryKeys();
|
1063
|
-
const wantedIdsByChain = new Map(chains.flatMap(({
|
1064
|
-
id: chainId,
|
1065
|
-
specName,
|
1066
|
-
specVersion,
|
1067
|
-
balancesConfig
|
1068
|
-
}) => {
|
1069
|
-
if (specName === null) return [];
|
1070
|
-
if (specVersion === null) return [];
|
1071
|
-
return [[chainId, this.#balanceModules.filter(m => m.type.startsWith("substrate-")).map(({
|
1072
|
-
type: source
|
1073
|
-
}) => deriveMiniMetadataId({
|
1074
|
-
source,
|
1075
|
-
chainId: chainId,
|
1076
|
-
specName: specName,
|
1077
|
-
specVersion: specVersion,
|
1078
|
-
balancesConfig: JSON.stringify((balancesConfig ?? []).find(({
|
1079
|
-
moduleType
|
1080
|
-
}) => moduleType === source)?.moduleConfig ?? {})
|
1081
|
-
}))]];
|
1082
|
-
}));
|
1083
|
-
const statusesByChain = new Map(Array.from(wantedIdsByChain.entries()).map(([chainId, wantedIds]) => [chainId, wantedIds.every(wantedId => ids.includes(wantedId)) ? "good" : "none"]));
|
1084
|
-
return {
|
1085
|
-
wantedIdsByChain,
|
1086
|
-
statusesByChain
|
1087
|
-
};
|
1088
|
-
}
|
1089
|
-
async hydrateFromChaindata() {
|
1090
|
-
const now = Date.now();
|
1091
|
-
if (now - this.#lastHydratedMiniMetadatasAt < minimumHydrationInterval) return false;
|
1092
|
-
const dbHasMiniMetadatas = (await db.miniMetadatas.count()) > 0;
|
1093
|
-
try {
|
1094
|
-
try {
|
1095
|
-
// TODO: Move `fetchMiniMetadatas` into this package,
|
1096
|
-
// so that we don't have a circular import between `@talismn/balances` and `@talismn/chaindata-provider`.
|
1097
|
-
var miniMetadatas = await chaindataProvider.fetchMiniMetadatas(); // eslint-disable-line no-var
|
1098
|
-
if (miniMetadatas.length <= 0) throw new Error("Ignoring empty chaindata miniMetadatas response");
|
1099
|
-
} catch (error) {
|
1100
|
-
if (dbHasMiniMetadatas) throw error;
|
1101
|
-
// On first start-up (db is empty), if we fail to fetch miniMetadatas then we should
|
1102
|
-
// initialize the DB with the list of miniMetadatas inside our init/mini-metadatas.json file.
|
1103
|
-
// This data will represent a relatively recent copy of what's in chaindata,
|
1104
|
-
// which will be better for our users than to have nothing at all.
|
1105
|
-
var miniMetadatas = await chaindataProvider.fetchInitMiniMetadatas(); // eslint-disable-line no-var
|
1106
|
-
}
|
1107
|
-
await db.miniMetadatas.bulkPut(miniMetadatas);
|
1108
|
-
this.#lastHydratedMiniMetadatasAt = now;
|
1109
|
-
return true;
|
1110
|
-
} catch (error) {
|
1111
|
-
log.warn(`Failed to hydrate miniMetadatas from chaindata`, error);
|
1112
|
-
return false;
|
1113
|
-
}
|
1114
|
-
}
|
1115
|
-
async hydrateCustomChains() {
|
1116
|
-
const now = Date.now();
|
1117
|
-
if (now - this.#lastHydratedCustomChainsAt < minimumHydrationInterval) return false;
|
1118
|
-
const chains = await this.#chaindataProvider.chains();
|
1119
|
-
const customChains = chains.filter(chain => "isCustom" in chain && chain.isCustom);
|
1120
|
-
const updatedCustomChains = [];
|
1121
|
-
const concurrency = 4;
|
1122
|
-
(await PromisePool.PromisePool.withConcurrency(concurrency).for(customChains).process(async customChain => {
|
1123
|
-
const send = (method, params) => this.#chainConnectors.substrate?.send(customChain.id, method, params);
|
1124
|
-
const [genesisHash, runtimeVersion, chainName, chainType] = await Promise.all([send("chain_getBlockHash", [0]), send("state_getRuntimeVersion", []), send("system_chain", []), send("system_chainType", [])]);
|
1125
|
-
|
1126
|
-
// deconstruct rpc data
|
1127
|
-
const {
|
1128
|
-
specName,
|
1129
|
-
implName
|
1130
|
-
} = runtimeVersion;
|
1131
|
-
const specVersion = String(runtimeVersion.specVersion);
|
1132
|
-
const changed = customChain.genesisHash !== genesisHash || customChain.chainName !== chainName || !isEqual__default.default(customChain.chainType, chainType) || customChain.implName !== implName || customChain.specName !== specName || customChain.specVersion !== specVersion;
|
1133
|
-
if (!changed) return;
|
1134
|
-
customChain.genesisHash = genesisHash;
|
1135
|
-
customChain.chainName = chainName;
|
1136
|
-
customChain.chainType = chainType;
|
1137
|
-
customChain.implName = implName;
|
1138
|
-
customChain.specName = specName;
|
1139
|
-
customChain.specVersion = specVersion;
|
1140
|
-
updatedCustomChains.push(customChain);
|
1141
|
-
})).errors.forEach(error => log.error("Error hydrating custom chains", error));
|
1142
|
-
if (updatedCustomChains.length > 0) {
|
1143
|
-
await this.#chaindataProvider.transaction("rw", ["chains"], async () => {
|
1144
|
-
for (const updatedCustomChain of updatedCustomChains) {
|
1145
|
-
await this.#chaindataProvider.removeCustomChain(updatedCustomChain.id);
|
1146
|
-
await this.#chaindataProvider.addCustomChain(updatedCustomChain);
|
1147
|
-
}
|
1148
|
-
});
|
1149
|
-
}
|
1150
|
-
if (updatedCustomChains.length > 0) this.#lastHydratedCustomChainsAt = now;
|
1151
|
-
return true;
|
1152
|
-
}
|
1153
|
-
async updateSubstrateChains(chainIds) {
|
1154
|
-
const chains = new Map((await this.#chaindataProvider.chains()).map(chain => [chain.id, chain]));
|
1155
|
-
const filteredChains = chainIds.flatMap(chainId => chains.get(chainId) ?? []);
|
1156
|
-
const ids = await db.miniMetadatas.orderBy("id").primaryKeys();
|
1157
|
-
const {
|
1158
|
-
wantedIdsByChain,
|
1159
|
-
statusesByChain
|
1160
|
-
} = await this.statuses(filteredChains);
|
1161
|
-
|
1162
|
-
// clean up store
|
1163
|
-
const wantedIds = Array.from(wantedIdsByChain.values()).flatMap(ids => ids);
|
1164
|
-
const unwantedIds = ids.filter(id => !wantedIds.includes(id));
|
1165
|
-
if (unwantedIds.length > 0) {
|
1166
|
-
const chainIds = Array.from(new Set((await db.miniMetadatas.bulkGet(unwantedIds)).map(m => m?.chainId)));
|
1167
|
-
log.info(`Pruning ${unwantedIds.length} miniMetadatas on chains ${chainIds.join(", ")}`);
|
1168
|
-
await db.miniMetadatas.bulkDelete(unwantedIds);
|
1169
|
-
}
|
1170
|
-
const needUpdates = Array.from(statusesByChain.entries()).filter(([, status]) => status !== "good").map(([chainId]) => chainId);
|
1171
|
-
if (needUpdates.length > 0) log.info(`${needUpdates.length} miniMetadatas need updates (${needUpdates.join(", ")})`);
|
1172
|
-
const availableTokenLogos = await chaindataProvider.availableTokenLogoFilenames().catch(error => {
|
1173
|
-
log.error("Failed to fetch available token logos", error);
|
1174
|
-
return [];
|
1175
|
-
});
|
1176
|
-
const concurrency = 12;
|
1177
|
-
(await PromisePool.PromisePool.withConcurrency(concurrency).for(needUpdates).process(async chainId => {
|
1178
|
-
log.info(`Updating metadata for chain ${chainId}`);
|
1179
|
-
const chain = chains.get(chainId);
|
1180
|
-
if (!chain) return;
|
1181
|
-
const {
|
1182
|
-
specName,
|
1183
|
-
specVersion
|
1184
|
-
} = chain;
|
1185
|
-
if (specName === null) return;
|
1186
|
-
if (specVersion === null) return;
|
1187
|
-
const fetchMetadata = async () => {
|
1188
|
-
try {
|
1189
|
-
return await sapi.fetchBestMetadata((method, params, isCacheable) => {
|
1190
|
-
if (!this.#chainConnectors.substrate) throw new Error("Substrate connector is not available");
|
1191
|
-
return this.#chainConnectors.substrate.send(chainId, method, params, isCacheable);
|
1192
|
-
}, true // allow v14 fallback
|
1193
|
-
);
|
1194
|
-
} catch (err) {
|
1195
|
-
log.warn(`Failed to fetch metadata for chain ${chainId}`);
|
1196
|
-
return undefined;
|
1197
|
-
}
|
1198
|
-
};
|
1199
|
-
const [metadataRpc, systemProperties] = await Promise.all([fetchMetadata(), this.#chainConnectors.substrate?.send(chainId, "system_properties", [])]);
|
1200
|
-
for (const mod of this.#balanceModules.filter(m => m.type.startsWith("substrate-"))) {
|
1201
|
-
const balancesConfig = (chain.balancesConfig ?? []).find(({
|
1202
|
-
moduleType
|
1203
|
-
}) => moduleType === mod.type);
|
1204
|
-
const moduleConfig = balancesConfig?.moduleConfig ?? {};
|
1205
|
-
const chainMeta = await mod.fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc, systemProperties);
|
1206
|
-
const tokens = await mod.fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig);
|
1207
|
-
|
1208
|
-
// update tokens in chaindata
|
1209
|
-
await this.#chaindataProvider.updateChainTokens(chainId, mod.type, Object.values(tokens), availableTokenLogos);
|
1210
|
-
|
1211
|
-
// update miniMetadatas
|
1212
|
-
const {
|
1213
|
-
miniMetadata: data,
|
1214
|
-
metadataVersion: version,
|
1215
|
-
...extra
|
1216
|
-
} = chainMeta ?? {};
|
1217
|
-
await db.miniMetadatas.put({
|
1218
|
-
id: deriveMiniMetadataId({
|
1219
|
-
source: mod.type,
|
1220
|
-
chainId,
|
1221
|
-
specName,
|
1222
|
-
specVersion,
|
1223
|
-
balancesConfig: JSON.stringify(moduleConfig)
|
1224
|
-
}),
|
1225
|
-
source: mod.type,
|
1226
|
-
chainId,
|
1227
|
-
specName,
|
1228
|
-
specVersion,
|
1229
|
-
balancesConfig: JSON.stringify(moduleConfig),
|
1230
|
-
// TODO: Standardise return value from `fetchSubstrateChainMeta`
|
1231
|
-
version,
|
1232
|
-
data,
|
1233
|
-
extra: JSON.stringify(extra)
|
1234
|
-
});
|
1235
|
-
}
|
1236
|
-
})).errors.forEach(error => log.error("Error updating chain metadata", error));
|
1237
|
-
}
|
1238
|
-
}
|
961
|
+
const TokenConfigBaseSchema = chaindataProvider.TokenBaseSchema.partial().omit({
|
962
|
+
id: true
|
963
|
+
});
|
1239
964
|
|
1240
965
|
const erc20Abi = [{
|
1241
966
|
constant: true,
|
@@ -1467,8 +1192,11 @@ const erc20Abi = [{
|
|
1467
1192
|
|
1468
1193
|
const erc20BalancesAggregatorAbi = viem.parseAbi(["struct AccountToken {address account; address token;}", "function balances(AccountToken[] memory accountTokens) public view returns (uint256[] memory)"]);
|
1469
1194
|
|
1470
|
-
const moduleType$
|
1471
|
-
const
|
1195
|
+
const moduleType$7 = "evm-erc20";
|
1196
|
+
const EvmErc20TokenConfigSchema = z__default.default.strictObject({
|
1197
|
+
contractAddress: chaindataProvider.EvmErc20TokenSchema.shape.contractAddress,
|
1198
|
+
...TokenConfigBaseSchema.shape
|
1199
|
+
});
|
1472
1200
|
const EvmErc20Module = hydrate => {
|
1473
1201
|
const {
|
1474
1202
|
chainConnectors,
|
@@ -1492,38 +1220,36 @@ const EvmErc20Module = hydrate => {
|
|
1492
1220
|
}, {});
|
1493
1221
|
};
|
1494
1222
|
const getModuleTokens = async () => {
|
1495
|
-
return await chaindataProvider$1.
|
1223
|
+
return await chaindataProvider$1.getTokensMapById(moduleType$7);
|
1496
1224
|
};
|
1497
1225
|
const getErc20Aggregators = async () => {
|
1498
|
-
const evmNetworks = await chaindataProvider$1.
|
1499
|
-
return Object.fromEntries(evmNetworks.filter(n => n.
|
1226
|
+
const evmNetworks = await chaindataProvider$1.getNetworks("ethereum");
|
1227
|
+
return Object.fromEntries(evmNetworks.filter(n => n.contracts?.Erc20Aggregator).map(n => [n.id, n.contracts.Erc20Aggregator]));
|
1500
1228
|
};
|
1501
1229
|
return {
|
1502
|
-
...DefaultBalanceModule(moduleType$
|
1230
|
+
...DefaultBalanceModule(moduleType$7),
|
1503
1231
|
/**
|
1504
1232
|
* This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L280-L284).
|
1505
1233
|
* In a future version of the balance libraries, we may build some kind of async scheduling system which will keep the chainmeta for each chain up to date without relying on a squid.
|
1506
1234
|
*/
|
1507
|
-
async fetchEvmChainMeta(
|
1508
|
-
const isTestnet = (await chaindataProvider$1.evmNetworkById(chainId))?.isTestnet || false;
|
1235
|
+
async fetchEvmChainMeta(_chainId) {
|
1509
1236
|
return {
|
1510
|
-
|
1237
|
+
miniMetadata: null,
|
1238
|
+
extra: null
|
1511
1239
|
};
|
1512
1240
|
},
|
1513
1241
|
/**
|
1514
1242
|
* This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L338-L343).
|
1515
1243
|
* In a future version of the balance libraries, we may build some kind of async scheduling system which will keep the list of tokens for each chain up to date without relying on a squid.
|
1516
1244
|
*/
|
1517
|
-
async fetchEvmChainTokens(chainId,
|
1518
|
-
const {
|
1519
|
-
isTestnet
|
1520
|
-
} = chainMeta;
|
1245
|
+
async fetchEvmChainTokens(chainId, _chainMeta, moduleConfig, tokens) {
|
1521
1246
|
const chainTokens = {};
|
1522
|
-
for (const tokenConfig of
|
1247
|
+
for (const tokenConfig of tokens ?? []) {
|
1523
1248
|
const {
|
1524
1249
|
contractAddress,
|
1525
1250
|
symbol: contractSymbol,
|
1526
|
-
decimals: contractDecimals
|
1251
|
+
decimals: contractDecimals,
|
1252
|
+
name: contractName
|
1527
1253
|
} = tokenConfig;
|
1528
1254
|
// TODO : in chaindata's build, filter out all tokens that don't have any of these
|
1529
1255
|
if (!contractAddress || !contractSymbol || contractDecimals === undefined) {
|
@@ -1533,26 +1259,29 @@ const EvmErc20Module = hydrate => {
|
|
1533
1259
|
const symbol = tokenConfig?.symbol ?? contractSymbol ?? "ETH";
|
1534
1260
|
const decimals = typeof tokenConfig?.decimals === "number" ? tokenConfig.decimals : typeof contractDecimals === "number" ? contractDecimals : 18;
|
1535
1261
|
if (!symbol || typeof decimals !== "number") continue;
|
1536
|
-
const id = evmErc20TokenId(chainId, contractAddress);
|
1262
|
+
const id = chaindataProvider.evmErc20TokenId(chainId, contractAddress);
|
1537
1263
|
const token = {
|
1538
1264
|
id,
|
1539
1265
|
type: "evm-erc20",
|
1540
|
-
|
1266
|
+
platform: "ethereum",
|
1541
1267
|
isDefault: tokenConfig.isDefault ?? true,
|
1542
1268
|
symbol,
|
1543
1269
|
decimals,
|
1544
|
-
|
1270
|
+
name: contractName ?? symbol,
|
1271
|
+
logo: tokenConfig?.logo,
|
1545
1272
|
contractAddress,
|
1546
|
-
|
1547
|
-
id: chainId
|
1548
|
-
}
|
1273
|
+
networkId: chainId
|
1549
1274
|
};
|
1550
1275
|
if (tokenConfig?.symbol) token.symbol = tokenConfig?.symbol;
|
1551
1276
|
if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
|
1552
|
-
if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
|
1553
1277
|
if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
|
1554
1278
|
if (tokenConfig?.noDiscovery) token.noDiscovery = tokenConfig?.noDiscovery;
|
1555
|
-
|
1279
|
+
const validation = chaindataProvider.EvmErc20TokenSchema.safeParse(token);
|
1280
|
+
if (validation.success) {
|
1281
|
+
chainTokens[token.id] = token;
|
1282
|
+
} else {
|
1283
|
+
log.warn("Ignoring invalid token", token.id, validation.error.message, validation.error.issues);
|
1284
|
+
}
|
1556
1285
|
}
|
1557
1286
|
return chainTokens;
|
1558
1287
|
},
|
@@ -1564,18 +1293,18 @@ const EvmErc20Module = hydrate => {
|
|
1564
1293
|
const subscriptionInterval = 6_000; // 6_000ms == 6 seconds
|
1565
1294
|
const initDelay = 1_500; // 1_500ms == 1.5 seconds
|
1566
1295
|
const initialisingBalances = new Set();
|
1567
|
-
const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.
|
1296
|
+
const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.networkId));
|
1568
1297
|
const tokens = await getModuleTokens();
|
1569
1298
|
|
1570
1299
|
// for chains with a zero balance we only call fetchBalances once every 5 subscriptionIntervals
|
1571
1300
|
// if subscriptionInterval is 6 seconds, this means we only poll chains with a zero balance every 30 seconds
|
1572
1301
|
let zeroBalanceSubscriptionIntervalCounter = 0;
|
1573
|
-
const evmNetworks = await chaindataProvider$1.
|
1302
|
+
const evmNetworks = await chaindataProvider$1.getNetworksMapById("ethereum");
|
1574
1303
|
const ethAddressesByToken = Object.fromEntries(Object.entries(addressesByToken).map(([tokenId, addresses]) => {
|
1575
1304
|
const ethAddresses = addresses.filter(util.isEthereumAddress);
|
1576
1305
|
if (ethAddresses.length === 0) return null;
|
1577
1306
|
const token = tokens[tokenId];
|
1578
|
-
const evmNetworkId = token.
|
1307
|
+
const evmNetworkId = token.networkId;
|
1579
1308
|
if (!evmNetworkId) return null;
|
1580
1309
|
return [tokenId, ethAddresses];
|
1581
1310
|
}).filter(x => Boolean(x)));
|
@@ -1701,20 +1430,17 @@ const fetchBalances$3 = async (evmChainConnector, tokenAddressesByNetwork, erc20
|
|
1701
1430
|
results: [],
|
1702
1431
|
errors: []
|
1703
1432
|
};
|
1704
|
-
await Promise.all(Object.entries(tokenAddressesByNetwork).map(async ([
|
1705
|
-
const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(
|
1706
|
-
if (!publicClient) throw new EvmErc20NetworkError(`Could not get rpc provider for evm network ${
|
1707
|
-
const balances = await getEvmTokenBalances(publicClient, networkParams, result.errors, erc20Aggregators[
|
1433
|
+
await Promise.all(Object.entries(tokenAddressesByNetwork).map(async ([networkId, networkParams]) => {
|
1434
|
+
const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(networkId);
|
1435
|
+
if (!publicClient) throw new EvmErc20NetworkError(`Could not get rpc provider for evm network ${networkId}`, networkId);
|
1436
|
+
const balances = await getEvmTokenBalances(publicClient, networkParams, result.errors, erc20Aggregators[networkId]);
|
1708
1437
|
|
1709
1438
|
// consider only non null balances in the results
|
1710
1439
|
result.results.push(...balances.filter(util.isTruthy).map((free, i) => ({
|
1711
1440
|
source: "evm-erc20",
|
1712
1441
|
status: "live",
|
1713
1442
|
address: networkParams[i].address,
|
1714
|
-
|
1715
|
-
evmChainId: evmNetworkId
|
1716
|
-
},
|
1717
|
-
evmNetworkId,
|
1443
|
+
networkId,
|
1718
1444
|
tokenId: networkParams[i].token.id,
|
1719
1445
|
value: free
|
1720
1446
|
})));
|
@@ -1785,7 +1511,7 @@ function groupAddressesByTokenByEvmNetwork$1(addressesByToken, tokens) {
|
|
1785
1511
|
log.error(`Token ${tokenId} not found`);
|
1786
1512
|
return byChain;
|
1787
1513
|
}
|
1788
|
-
const chainId = token.
|
1514
|
+
const chainId = token.networkId;
|
1789
1515
|
if (!chainId) {
|
1790
1516
|
log.error(`Token ${tokenId} has no evm network`);
|
1791
1517
|
return byChain;
|
@@ -1798,67 +1524,38 @@ function groupAddressesByTokenByEvmNetwork$1(addressesByToken, tokens) {
|
|
1798
1524
|
|
1799
1525
|
const abiMulticall = viem.parseAbi(["struct Call { address target; bytes callData; }", "struct Call3 { address target; bool allowFailure; bytes callData; }", "struct Call3Value { address target; bool allowFailure; uint256 value; bytes callData; }", "struct Result { bool success; bytes returnData; }", "function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData)", "function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData)", "function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData)", "function blockAndAggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData)", "function getBasefee() view returns (uint256 basefee)", "function getBlockHash(uint256 blockNumber) view returns (bytes32 blockHash)", "function getBlockNumber() view returns (uint256 blockNumber)", "function getChainId() view returns (uint256 chainid)", "function getCurrentBlockCoinbase() view returns (address coinbase)", "function getCurrentBlockDifficulty() view returns (uint256 difficulty)", "function getCurrentBlockGasLimit() view returns (uint256 gaslimit)", "function getCurrentBlockTimestamp() view returns (uint256 timestamp)", "function getEthBalance(address addr) view returns (uint256 balance)", "function getLastBlockHash() view returns (bytes32 blockHash)", "function tryAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (Result[] memory returnData)", "function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData)"]);
|
1800
1526
|
|
1801
|
-
const moduleType$
|
1802
|
-
const
|
1803
|
-
const getEvmNetworkIdFromTokenId = tokenId => {
|
1804
|
-
const evmNetworkId = tokenId.split("-")[0];
|
1805
|
-
if (!evmNetworkId) throw new Error(`Can't detect chainId for token ${tokenId}`);
|
1806
|
-
return evmNetworkId;
|
1807
|
-
};
|
1527
|
+
const moduleType$6 = "evm-native";
|
1528
|
+
const EvmNativeTokenConfigSchema = TokenConfigBaseSchema;
|
1808
1529
|
const EvmNativeModule = hydrate => {
|
1809
1530
|
const {
|
1810
1531
|
chainConnectors,
|
1811
1532
|
chaindataProvider: chaindataProvider$1
|
1812
1533
|
} = hydrate;
|
1813
1534
|
const getModuleTokens = async () => {
|
1814
|
-
return await chaindataProvider$1.
|
1535
|
+
return await chaindataProvider$1.getTokensMapById(moduleType$6);
|
1815
1536
|
};
|
1816
1537
|
return {
|
1817
|
-
...DefaultBalanceModule(moduleType$
|
1538
|
+
...DefaultBalanceModule(moduleType$6),
|
1818
1539
|
get tokens() {
|
1819
|
-
return chaindataProvider$1.
|
1540
|
+
return chaindataProvider$1.getTokensMapById(moduleType$6);
|
1820
1541
|
},
|
1821
1542
|
/**
|
1822
1543
|
* This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L280-L284).
|
1823
1544
|
* In a future version of the balance libraries, we may build some kind of async scheduling system which will keep the chainmeta for each chain up to date without relying on a squid.
|
1824
1545
|
*/
|
1825
|
-
async fetchEvmChainMeta(
|
1826
|
-
const isTestnet = (await chaindataProvider$1.evmNetworkById(chainId))?.isTestnet || false;
|
1546
|
+
async fetchEvmChainMeta(_chainId) {
|
1827
1547
|
return {
|
1828
|
-
|
1548
|
+
miniMetadata: null,
|
1549
|
+
extra: null
|
1829
1550
|
};
|
1830
1551
|
},
|
1831
1552
|
/**
|
1832
1553
|
* This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L338-L343).
|
1833
1554
|
* In a future version of the balance libraries, we may build some kind of async scheduling system which will keep the list of tokens for each chain up to date without relying on a squid.
|
1834
1555
|
*/
|
1835
|
-
async fetchEvmChainTokens(
|
1836
|
-
|
1837
|
-
|
1838
|
-
} = chainMeta;
|
1839
|
-
const symbol = moduleConfig?.symbol ?? "ETH";
|
1840
|
-
const decimals = typeof moduleConfig?.decimals === "number" ? moduleConfig.decimals : 18;
|
1841
|
-
const id = evmNativeTokenId(chainId);
|
1842
|
-
const nativeToken = {
|
1843
|
-
id,
|
1844
|
-
type: "evm-native",
|
1845
|
-
isTestnet,
|
1846
|
-
isDefault: true,
|
1847
|
-
symbol,
|
1848
|
-
decimals,
|
1849
|
-
logo: moduleConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
|
1850
|
-
evmNetwork: {
|
1851
|
-
id: chainId
|
1852
|
-
}
|
1853
|
-
};
|
1854
|
-
if (moduleConfig?.symbol) nativeToken.symbol = moduleConfig?.symbol;
|
1855
|
-
if (moduleConfig?.coingeckoId) nativeToken.coingeckoId = moduleConfig?.coingeckoId;
|
1856
|
-
if (moduleConfig?.dcentName) nativeToken.dcentName = moduleConfig?.dcentName;
|
1857
|
-
if (moduleConfig?.mirrorOf) nativeToken.mirrorOf = moduleConfig?.mirrorOf;
|
1858
|
-
if (moduleConfig?.noDiscovery) nativeToken.noDiscovery = moduleConfig?.noDiscovery;
|
1859
|
-
return {
|
1860
|
-
[nativeToken.id]: nativeToken
|
1861
|
-
};
|
1556
|
+
async fetchEvmChainTokens() {
|
1557
|
+
// token is currently generated in chaindata from the EthNetworkConfig["nativeCurrency"] field
|
1558
|
+
return {};
|
1862
1559
|
},
|
1863
1560
|
async subscribeBalances({
|
1864
1561
|
addressesByToken,
|
@@ -1869,11 +1566,11 @@ const EvmNativeModule = hydrate => {
|
|
1869
1566
|
const initDelay = 500; // 500ms == 0.5 seconds
|
1870
1567
|
|
1871
1568
|
const tokens = await getModuleTokens();
|
1872
|
-
const ethAddressesByToken =
|
1569
|
+
const ethAddressesByToken = lodash.fromPairs(lodash.toPairs(addressesByToken).map(([tokenId, addresses]) => {
|
1873
1570
|
const ethAddresses = addresses.filter(util.isEthereumAddress);
|
1874
1571
|
if (ethAddresses.length === 0) return null;
|
1875
1572
|
const token = tokens[tokenId];
|
1876
|
-
const evmNetworkId = token.
|
1573
|
+
const evmNetworkId = token.networkId;
|
1877
1574
|
if (!evmNetworkId) return null;
|
1878
1575
|
return [tokenId, ethAddresses];
|
1879
1576
|
}).filter(x => Boolean(x)));
|
@@ -1883,16 +1580,16 @@ const EvmNativeModule = hydrate => {
|
|
1883
1580
|
let zeroBalanceSubscriptionIntervalCounter = 0;
|
1884
1581
|
|
1885
1582
|
// setup initialising balances for all active evm networks
|
1886
|
-
const activeEvmNetworkIds =
|
1583
|
+
const activeEvmNetworkIds = lodash.keys(ethAddressesByToken).map(chaindataProvider.networkIdFromTokenId);
|
1887
1584
|
const initialisingBalances = new Set(activeEvmNetworkIds);
|
1888
|
-
const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.
|
1585
|
+
const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.networkId));
|
1889
1586
|
const poll = async () => {
|
1890
1587
|
if (!subscriptionActive) return;
|
1891
1588
|
zeroBalanceSubscriptionIntervalCounter = (zeroBalanceSubscriptionIntervalCounter + 1) % 5;
|
1892
1589
|
try {
|
1893
1590
|
// fetch balance for each network sequentially to prevent creating a big queue of http requests (browser can only handle 2 at a time)
|
1894
1591
|
for (const [tokenId, addresses] of Object.entries(ethAddressesByToken)) {
|
1895
|
-
const evmNetworkId =
|
1592
|
+
const evmNetworkId = chaindataProvider.networkIdFromTokenId(tokenId);
|
1896
1593
|
|
1897
1594
|
// a zero balance network is one that has initialised and does not have a positive balance
|
1898
1595
|
const isZeroBalanceNetwork = !initialisingBalances.has(evmNetworkId) && !positiveBalanceNetworks.has(evmNetworkId);
|
@@ -1912,10 +1609,10 @@ const EvmNativeModule = hydrate => {
|
|
1912
1609
|
log.error(balance.message, balance.networkId);
|
1913
1610
|
initialisingBalances.delete(balance.networkId);
|
1914
1611
|
} else {
|
1915
|
-
if (balance.
|
1916
|
-
initialisingBalances.delete(balance.
|
1612
|
+
if (balance.networkId) {
|
1613
|
+
initialisingBalances.delete(balance.networkId);
|
1917
1614
|
if (BigInt(balance.value) > 0n) {
|
1918
|
-
positiveBalanceNetworks.add(balance.
|
1615
|
+
positiveBalanceNetworks.add(balance.networkId);
|
1919
1616
|
}
|
1920
1617
|
resultBalances.push(balance);
|
1921
1618
|
}
|
@@ -1965,23 +1662,20 @@ const fetchBalances$2 = async (evmChainConnector, addressesByToken, tokens) => {
|
|
1965
1662
|
if (!evmChainConnector) throw new Error(`This module requires an evm chain connector`);
|
1966
1663
|
return Promise.all(Object.entries(addressesByToken).map(async ([tokenId, addresses]) => {
|
1967
1664
|
const token = tokens[tokenId];
|
1968
|
-
const
|
1969
|
-
if (!
|
1970
|
-
const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(
|
1971
|
-
if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${
|
1665
|
+
const networkId = token.networkId;
|
1666
|
+
if (!networkId) throw new Error(`Token ${token.id} has no evm network`);
|
1667
|
+
const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(networkId);
|
1668
|
+
if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
|
1972
1669
|
|
1973
1670
|
// fetch all balances
|
1974
1671
|
const freeBalances = await getFreeBalances(publicClient, addresses);
|
1975
1672
|
const balanceResults = addresses.map((address, i) => {
|
1976
|
-
if (freeBalances[i] === "error") return new EvmNativeBalanceError("Could not fetch balance ",
|
1673
|
+
if (freeBalances[i] === "error") return new EvmNativeBalanceError("Could not fetch balance ", networkId);
|
1977
1674
|
return {
|
1978
1675
|
source: "evm-native",
|
1979
1676
|
status: "live",
|
1980
1677
|
address: address,
|
1981
|
-
|
1982
|
-
evmChainId: evmNetworkId
|
1983
|
-
},
|
1984
|
-
evmNetworkId,
|
1678
|
+
networkId,
|
1985
1679
|
tokenId,
|
1986
1680
|
value: freeBalances[i].toString()
|
1987
1681
|
};
|
@@ -2034,7 +1728,7 @@ async function getFreeBalances(publicClient, addresses) {
|
|
2034
1728
|
});
|
2035
1729
|
} catch (err) {
|
2036
1730
|
const errorMessage = util.hasOwnProperty(err, "shortMessage") ? err.shortMessage : util.hasOwnProperty(err, "message") ? err.message : err;
|
2037
|
-
log.warn(`Failed to get balance from chain ${publicClient.chain
|
1731
|
+
log.warn(`Failed to get balance from chain ${publicClient.chain.id} for ${ethAddresses.length} addresses: ${errorMessage}`);
|
2038
1732
|
return ethAddresses.map(() => "error");
|
2039
1733
|
}
|
2040
1734
|
}
|
@@ -2602,8 +2296,20 @@ const uniswapV2PairAbi = [{
|
|
2602
2296
|
type: "function"
|
2603
2297
|
}];
|
2604
2298
|
|
2605
|
-
const moduleType$
|
2606
|
-
const
|
2299
|
+
const moduleType$5 = "evm-uniswapv2";
|
2300
|
+
const EvmUniswapV2TokenConfigSchema = z__default.default.strictObject({
|
2301
|
+
contractAddress: chaindataProvider.EvmUniswapV2TokenSchema.shape.contractAddress,
|
2302
|
+
...TokenConfigBaseSchema.shape,
|
2303
|
+
// on chaindata side these are fetched by a dedicated task
|
2304
|
+
symbol0: chaindataProvider.EvmUniswapV2TokenSchema.shape.symbol0.optional(),
|
2305
|
+
symbol1: chaindataProvider.EvmUniswapV2TokenSchema.shape.symbol1.optional(),
|
2306
|
+
decimals0: chaindataProvider.EvmUniswapV2TokenSchema.shape.decimals0.optional(),
|
2307
|
+
decimals1: chaindataProvider.EvmUniswapV2TokenSchema.shape.decimals1.optional(),
|
2308
|
+
tokenAddress0: chaindataProvider.EvmUniswapV2TokenSchema.shape.tokenAddress0.optional(),
|
2309
|
+
tokenAddress1: chaindataProvider.EvmUniswapV2TokenSchema.shape.tokenAddress1.optional(),
|
2310
|
+
coingeckoId0: chaindataProvider.EvmUniswapV2TokenSchema.shape.coingeckoId0.optional(),
|
2311
|
+
coingeckoId1: chaindataProvider.EvmUniswapV2TokenSchema.shape.coingeckoId1.optional()
|
2312
|
+
});
|
2607
2313
|
const EvmUniswapV2Module = hydrate => {
|
2608
2314
|
const {
|
2609
2315
|
chainConnectors,
|
@@ -2612,19 +2318,16 @@ const EvmUniswapV2Module = hydrate => {
|
|
2612
2318
|
const chainConnector = chainConnectors.evm;
|
2613
2319
|
util$1.assert(chainConnector, "This module requires an evm chain connector");
|
2614
2320
|
return {
|
2615
|
-
...DefaultBalanceModule(moduleType$
|
2616
|
-
async fetchEvmChainMeta(
|
2617
|
-
const isTestnet = (await chaindataProvider$1.evmNetworkById(chainId))?.isTestnet || false;
|
2321
|
+
...DefaultBalanceModule(moduleType$5),
|
2322
|
+
async fetchEvmChainMeta(_chainId) {
|
2618
2323
|
return {
|
2619
|
-
|
2324
|
+
miniMetadata: null,
|
2325
|
+
extra: null
|
2620
2326
|
};
|
2621
2327
|
},
|
2622
|
-
async fetchEvmChainTokens(chainId,
|
2623
|
-
const {
|
2624
|
-
isTestnet
|
2625
|
-
} = chainMeta;
|
2328
|
+
async fetchEvmChainTokens(chainId, _chainMeta, moduleConfig, pools) {
|
2626
2329
|
const tokens = {};
|
2627
|
-
for (const tokenConfig of
|
2330
|
+
for (const tokenConfig of pools ?? []) {
|
2628
2331
|
const {
|
2629
2332
|
contractAddress,
|
2630
2333
|
decimals,
|
@@ -2635,21 +2338,23 @@ const EvmUniswapV2Module = hydrate => {
|
|
2635
2338
|
tokenAddress0,
|
2636
2339
|
tokenAddress1,
|
2637
2340
|
coingeckoId0,
|
2638
|
-
coingeckoId1
|
2341
|
+
coingeckoId1,
|
2342
|
+
name
|
2639
2343
|
} = tokenConfig;
|
2640
2344
|
if (!contractAddress || decimals === undefined || symbol0 === undefined || decimals0 === undefined || symbol1 === undefined || decimals1 === undefined || tokenAddress0 === undefined || tokenAddress1 === undefined) {
|
2641
2345
|
log.warn("ignoring token on chain %s", chainId, tokenConfig);
|
2642
2346
|
continue;
|
2643
2347
|
}
|
2644
|
-
const id = evmUniswapV2TokenId(chainId, contractAddress);
|
2348
|
+
const id = chaindataProvider.evmUniswapV2TokenId(chainId, contractAddress);
|
2645
2349
|
const token = {
|
2646
2350
|
id,
|
2647
2351
|
type: "evm-uniswapv2",
|
2648
|
-
|
2352
|
+
platform: "ethereum",
|
2649
2353
|
isDefault: tokenConfig.isDefault ?? false,
|
2650
2354
|
symbol: `${symbol0 ?? "UNKNOWN"}/${symbol1 ?? "UNKNOWN"}`,
|
2355
|
+
name: name ?? `${symbol0 ?? "UNKNOWN"}/${symbol1 ?? "UNKNOWN"}`,
|
2651
2356
|
decimals,
|
2652
|
-
logo: tokenConfig?.logo || chaindataProvider.
|
2357
|
+
logo: tokenConfig?.logo || chaindataProvider.getGithubTokenLogoUrl("uniswap"),
|
2653
2358
|
symbol0,
|
2654
2359
|
decimals0,
|
2655
2360
|
symbol1,
|
@@ -2659,13 +2364,10 @@ const EvmUniswapV2Module = hydrate => {
|
|
2659
2364
|
tokenAddress1,
|
2660
2365
|
coingeckoId0,
|
2661
2366
|
coingeckoId1,
|
2662
|
-
|
2663
|
-
id: chainId
|
2664
|
-
}
|
2367
|
+
networkId: chainId
|
2665
2368
|
};
|
2666
2369
|
if (tokenConfig?.symbol) token.symbol = tokenConfig?.symbol;
|
2667
2370
|
if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
|
2668
|
-
if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
|
2669
2371
|
if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
|
2670
2372
|
if (tokenConfig?.noDiscovery) token.noDiscovery = tokenConfig?.noDiscovery;
|
2671
2373
|
tokens[token.id] = token;
|
@@ -2681,9 +2383,9 @@ const EvmUniswapV2Module = hydrate => {
|
|
2681
2383
|
const initDelay = 1_500; // 1_500ms == 1.5 seconds
|
2682
2384
|
|
2683
2385
|
const initialBalancesByNetwork = initialBalances?.reduce((result, b) => {
|
2684
|
-
if (!b.
|
2685
|
-
if (!result[b.
|
2686
|
-
result[b.
|
2386
|
+
if (!b.networkId) return result;
|
2387
|
+
if (!result[b.networkId]) result[b.networkId] = {};
|
2388
|
+
result[b.networkId][getBalanceId(b)] = b;
|
2687
2389
|
return result;
|
2688
2390
|
}, {});
|
2689
2391
|
const cache = new Map(Object.entries(initialBalancesByNetwork ?? {}));
|
@@ -2691,8 +2393,8 @@ const EvmUniswapV2Module = hydrate => {
|
|
2691
2393
|
// for chains with a zero balance we only call fetchBalances once every 5 subscriptionIntervals
|
2692
2394
|
// if subscriptionInterval is 6 seconds, this means we only poll chains with a zero balance every 30 seconds
|
2693
2395
|
let zeroBalanceSubscriptionIntervalCounter = 0;
|
2694
|
-
const evmNetworks = await chaindataProvider$1.
|
2695
|
-
const tokens = await chaindataProvider$1.
|
2396
|
+
const evmNetworks = await chaindataProvider$1.getNetworksMapById("ethereum");
|
2397
|
+
const tokens = await chaindataProvider$1.getTokensMapById();
|
2696
2398
|
const poll = async () => {
|
2697
2399
|
if (!subscriptionActive) return;
|
2698
2400
|
zeroBalanceSubscriptionIntervalCounter = (zeroBalanceSubscriptionIntervalCounter + 1) % 5;
|
@@ -2735,20 +2437,20 @@ const EvmUniswapV2Module = hydrate => {
|
|
2735
2437
|
},
|
2736
2438
|
async fetchBalances(addressesByToken) {
|
2737
2439
|
if (!chainConnectors.evm) throw new Error(`This module requires an evm chain connector`);
|
2738
|
-
const evmNetworks = await chaindataProvider$1.
|
2739
|
-
const tokens = await chaindataProvider$1.
|
2440
|
+
const evmNetworks = await chaindataProvider$1.getNetworksMapById("ethereum");
|
2441
|
+
const tokens = await chaindataProvider$1.getTokensMapById();
|
2740
2442
|
return fetchBalances$1(chainConnectors.evm, evmNetworks, tokens, addressesByToken);
|
2741
2443
|
}
|
2742
2444
|
};
|
2743
2445
|
};
|
2744
2446
|
const fetchBalances$1 = async (evmChainConnector, evmNetworks, tokens, addressesByToken) => {
|
2745
2447
|
const addressesByTokenGroupedByEvmNetwork = groupAddressesByTokenByEvmNetwork(addressesByToken, tokens);
|
2746
|
-
const balances = (await Promise.allSettled(Object.entries(addressesByTokenGroupedByEvmNetwork).map(async ([
|
2448
|
+
const balances = (await Promise.allSettled(Object.entries(addressesByTokenGroupedByEvmNetwork).map(async ([networkId, addressesByToken]) => {
|
2747
2449
|
if (!evmChainConnector) throw new Error(`This module requires an evm chain connector`);
|
2748
|
-
const evmNetwork = evmNetworks[
|
2749
|
-
if (!evmNetwork) throw new Error(`Evm network ${
|
2750
|
-
const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(
|
2751
|
-
if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${
|
2450
|
+
const evmNetwork = evmNetworks[networkId];
|
2451
|
+
if (!evmNetwork) throw new Error(`Evm network ${networkId} not found`);
|
2452
|
+
const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(networkId);
|
2453
|
+
if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
|
2752
2454
|
const tokensAndAddresses = Object.entries(addressesByToken).reduce((tokensAndAddresses, [tokenId, addresses]) => {
|
2753
2455
|
const token = tokens[tokenId];
|
2754
2456
|
if (!token) {
|
@@ -2770,10 +2472,7 @@ const fetchBalances$1 = async (evmChainConnector, evmNetworks, tokens, addresses
|
|
2770
2472
|
source: "evm-uniswapv2",
|
2771
2473
|
status: "live",
|
2772
2474
|
address: address,
|
2773
|
-
|
2774
|
-
evmChainId: evmNetwork.id
|
2775
|
-
},
|
2776
|
-
evmNetworkId,
|
2475
|
+
networkId,
|
2777
2476
|
tokenId: token.id,
|
2778
2477
|
values: await getPoolBalance(publicClient, token.contractAddress, address)
|
2779
2478
|
}));
|
@@ -2809,7 +2508,7 @@ function groupAddressesByTokenByEvmNetwork(addressesByToken, tokens) {
|
|
2809
2508
|
log.error(`Token ${tokenId} not found`);
|
2810
2509
|
return byChain;
|
2811
2510
|
}
|
2812
|
-
const chainId = token.
|
2511
|
+
const chainId = token.networkId;
|
2813
2512
|
if (!chainId) {
|
2814
2513
|
log.error(`Token ${tokenId} has no evm network`);
|
2815
2514
|
return byChain;
|
@@ -2884,6 +2583,170 @@ async function getPoolBalance(publicClient, contractAddress, accountAddress) {
|
|
2884
2583
|
}
|
2885
2584
|
}
|
2886
2585
|
|
2586
|
+
const libVersion = pkg.version;
|
2587
|
+
|
2588
|
+
// cache the promise so it can be shared across multiple calls
|
2589
|
+
const CACHE_GET_SPEC_VERSION = new Map();
|
2590
|
+
const fetchSpecVersion = async (chainConnector, networkId) => {
|
2591
|
+
const {
|
2592
|
+
specVersion
|
2593
|
+
} = await chainConnector.send(networkId, "state_getRuntimeVersion", [], true);
|
2594
|
+
return specVersion;
|
2595
|
+
};
|
2596
|
+
|
2597
|
+
/**
|
2598
|
+
* fetches the spec version of a network. current request is cached in case of multiple calls (all balance modules will kick in at once)
|
2599
|
+
*/
|
2600
|
+
const getSpecVersion = async (chainConnector, networkId) => {
|
2601
|
+
if (CACHE_GET_SPEC_VERSION.has(networkId)) return CACHE_GET_SPEC_VERSION.get(networkId);
|
2602
|
+
const pResult = fetchSpecVersion(chainConnector, networkId);
|
2603
|
+
CACHE_GET_SPEC_VERSION.set(networkId, pResult);
|
2604
|
+
try {
|
2605
|
+
return await pResult;
|
2606
|
+
} catch (cause) {
|
2607
|
+
throw new Error(`Failed to fetch specVersion for network ${networkId}`, {
|
2608
|
+
cause
|
2609
|
+
});
|
2610
|
+
} finally {
|
2611
|
+
CACHE_GET_SPEC_VERSION.delete(networkId);
|
2612
|
+
}
|
2613
|
+
};
|
2614
|
+
|
2615
|
+
// share requests as all modules will call this at once
|
2616
|
+
const CACHE$1 = new Map();
|
2617
|
+
const getMetadataRpc = async (chainConnector, networkId) => {
|
2618
|
+
if (CACHE$1.has(networkId)) return CACHE$1.get(networkId);
|
2619
|
+
const pResult = sapi.fetchBestMetadata((method, params, isCacheable) => chainConnector.send(networkId, method, params, isCacheable, {
|
2620
|
+
expectErrors: true
|
2621
|
+
}), true // allow fallback to 14 as modules dont use any v15 or v16 specifics yet
|
2622
|
+
);
|
2623
|
+
CACHE$1.set(networkId, pResult);
|
2624
|
+
try {
|
2625
|
+
return await pResult;
|
2626
|
+
} catch (cause) {
|
2627
|
+
if (util.isAbortError(cause)) throw cause;
|
2628
|
+
throw new Error(`Failed to fetch metadataRpc for network ${networkId}`, {
|
2629
|
+
cause
|
2630
|
+
});
|
2631
|
+
} finally {
|
2632
|
+
CACHE$1.delete(networkId);
|
2633
|
+
}
|
2634
|
+
};
|
2635
|
+
|
2636
|
+
// share requests as all modules will call this at once
|
2637
|
+
const CACHE = new Map();
|
2638
|
+
|
2639
|
+
// ensures we dont fetch miniMetadatas on more than 4 chains at once
|
2640
|
+
const POOL = new PQueue__default.default({
|
2641
|
+
concurrency: 4
|
2642
|
+
});
|
2643
|
+
const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, specVersion, signal) => {
|
2644
|
+
if (CACHE.has(networkId)) return CACHE.get(networkId);
|
2645
|
+
if (!signal) log.warn("[miniMetadata] getMiniMetadatas called without signal, this may hang the updates", new Error("No signal provided") // this will show the stack trace
|
2646
|
+
);
|
2647
|
+
const pResult = POOL.add(() => fetchMiniMetadatas(chainConnector, chaindataProvider, networkId, specVersion), {
|
2648
|
+
signal
|
2649
|
+
});
|
2650
|
+
CACHE.set(networkId, pResult);
|
2651
|
+
try {
|
2652
|
+
return await pResult;
|
2653
|
+
} catch (cause) {
|
2654
|
+
if (util.isAbortError(cause)) throw cause;
|
2655
|
+
throw new Error(`Failed to fetch metadataRpc for network ${networkId}`, {
|
2656
|
+
cause
|
2657
|
+
});
|
2658
|
+
} finally {
|
2659
|
+
CACHE.delete(networkId);
|
2660
|
+
}
|
2661
|
+
};
|
2662
|
+
const DotBalanceModuleTypeSchema = z__default.default.keyof(chaindataProvider.DotNetworkBalancesConfigSchema);
|
2663
|
+
const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion, signal) => {
|
2664
|
+
const start = performance.now();
|
2665
|
+
log.info("[miniMetadata] fetching minimetadatas for %s", chainId);
|
2666
|
+
try {
|
2667
|
+
const metadataRpc = await getMetadataRpc(chainConnector, chainId);
|
2668
|
+
signal?.throwIfAborted();
|
2669
|
+
const chainConnectors = {
|
2670
|
+
substrate: chainConnector,
|
2671
|
+
evm: {} // wont be used but workarounds error for module creation
|
2672
|
+
};
|
2673
|
+
const modules = defaultBalanceModules.map(mod => mod({
|
2674
|
+
chainConnectors,
|
2675
|
+
chaindataProvider
|
2676
|
+
})).filter(mod => DotBalanceModuleTypeSchema.safeParse(mod.type).success);
|
2677
|
+
return Promise.all(modules.map(async mod => {
|
2678
|
+
const source = mod.type;
|
2679
|
+
const chain = await chaindataProvider.getNetworkById(chainId, "polkadot");
|
2680
|
+
const balancesConfig = chain?.balancesConfig?.[mod.type];
|
2681
|
+
const chainMeta = await mod.fetchSubstrateChainMeta(chainId, balancesConfig,
|
2682
|
+
// TODO better typing
|
2683
|
+
metadataRpc);
|
2684
|
+
return {
|
2685
|
+
id: deriveMiniMetadataId({
|
2686
|
+
source,
|
2687
|
+
chainId,
|
2688
|
+
specVersion,
|
2689
|
+
libVersion
|
2690
|
+
}),
|
2691
|
+
source,
|
2692
|
+
chainId,
|
2693
|
+
specVersion,
|
2694
|
+
libVersion,
|
2695
|
+
data: chainMeta?.miniMetadata ?? null,
|
2696
|
+
extra: chainMeta?.extra ?? null
|
2697
|
+
};
|
2698
|
+
}));
|
2699
|
+
} finally {
|
2700
|
+
log.debug("[miniMetadata] updated miniMetadatas for %s in %sms", chainId, (performance.now() - start).toFixed(2));
|
2701
|
+
}
|
2702
|
+
};
|
2703
|
+
|
2704
|
+
const getUpdatedMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion, signal) => {
|
2705
|
+
const miniMetadatas = await getMiniMetadatas(chainConnector, chaindataProvider, chainId, specVersion, signal);
|
2706
|
+
signal?.throwIfAborted();
|
2707
|
+
await db.transaction("readwrite", "miniMetadatas", async tx => {
|
2708
|
+
await tx.miniMetadatas.where({
|
2709
|
+
chainId
|
2710
|
+
}).delete();
|
2711
|
+
await tx.miniMetadatas.bulkPut(miniMetadatas);
|
2712
|
+
});
|
2713
|
+
return miniMetadatas;
|
2714
|
+
};
|
2715
|
+
|
2716
|
+
const getMiniMetadata = async (chaindataProvider, chainConnector, chainId, source, signal) => {
|
2717
|
+
const specVersion = await getSpecVersion(chainConnector, chainId);
|
2718
|
+
signal?.throwIfAborted();
|
2719
|
+
const miniMetadataId = deriveMiniMetadataId({
|
2720
|
+
source,
|
2721
|
+
chainId,
|
2722
|
+
specVersion,
|
2723
|
+
libVersion
|
2724
|
+
});
|
2725
|
+
|
2726
|
+
// lookup local ones
|
2727
|
+
const [dbMiniMetadata, ghMiniMetadata] = await Promise.all([db.miniMetadatas.get(miniMetadataId), chaindataProvider.miniMetadataById(miniMetadataId)]);
|
2728
|
+
signal?.throwIfAborted();
|
2729
|
+
const miniMetadata = dbMiniMetadata ?? ghMiniMetadata;
|
2730
|
+
if (miniMetadata) return miniMetadata;
|
2731
|
+
|
2732
|
+
// update from live chain metadata and persist locally
|
2733
|
+
const miniMetadatas = await getUpdatedMiniMetadatas(chainConnector, chaindataProvider, chainId, specVersion, signal);
|
2734
|
+
signal?.throwIfAborted();
|
2735
|
+
const found = miniMetadatas.find(m => m.id === miniMetadataId);
|
2736
|
+
if (!found) {
|
2737
|
+
log.warn("MiniMetadata not found in updated miniMetadatas", {
|
2738
|
+
source,
|
2739
|
+
chainId,
|
2740
|
+
specVersion,
|
2741
|
+
libVersion,
|
2742
|
+
miniMetadataId,
|
2743
|
+
miniMetadatas
|
2744
|
+
});
|
2745
|
+
throw new Error(`MiniMetadata not found for ${source} on ${chainId}`);
|
2746
|
+
}
|
2747
|
+
return found;
|
2748
|
+
};
|
2749
|
+
|
2887
2750
|
/**
|
2888
2751
|
* Wraps a BalanceModule's fetch/subscribe methods with a single `balances` method.
|
2889
2752
|
* This `balances` method will subscribe if a callback parameter is provided, or otherwise fetch.
|
@@ -2899,44 +2762,16 @@ async function balances(balanceModule, addressesByToken, callback) {
|
|
2899
2762
|
return await balanceModule.fetchBalances(addressesByToken);
|
2900
2763
|
}
|
2901
2764
|
|
2902
|
-
|
2903
|
-
* Given a `moduleType` and a `chain` from a chaindataProvider, this function will find the chainMeta
|
2904
|
-
* associated with the given balanceModule for the given chain.
|
2905
|
-
*/
|
2906
|
-
const findChainMeta = (miniMetadatas, moduleType, chain) => {
|
2907
|
-
if (!chain) return [undefined, undefined];
|
2908
|
-
if (!chain.specName) return [undefined, undefined];
|
2909
|
-
if (!chain.specVersion) return [undefined, undefined];
|
2910
|
-
|
2911
|
-
// TODO: This is spaghetti to import this here, it should be injected into each balance module or something.
|
2912
|
-
const metadataId = deriveMiniMetadataId({
|
2913
|
-
source: moduleType,
|
2914
|
-
chainId: chain.id,
|
2915
|
-
specName: chain.specName,
|
2916
|
-
specVersion: chain.specVersion,
|
2917
|
-
balancesConfig: JSON.stringify(chain.balancesConfig?.find(config => config.moduleType === moduleType)?.moduleConfig ?? {})
|
2918
|
-
});
|
2919
|
-
|
2920
|
-
// TODO: Fix this (needs to fetch miniMetadata without being async)
|
2921
|
-
const miniMetadata = miniMetadatas.get(metadataId);
|
2922
|
-
const chainMeta = miniMetadata ? {
|
2923
|
-
miniMetadata: miniMetadata.data,
|
2924
|
-
metadataVersion: miniMetadata.version,
|
2925
|
-
...JSON.parse(miniMetadata.extra)
|
2926
|
-
} : undefined;
|
2927
|
-
return [chainMeta, miniMetadata];
|
2928
|
-
};
|
2929
|
-
|
2765
|
+
// TODO remove this one in favor of the network specific one below
|
2930
2766
|
const buildStorageCoders = ({
|
2931
2767
|
chainIds,
|
2932
2768
|
chains,
|
2933
2769
|
miniMetadatas,
|
2934
|
-
moduleType,
|
2935
2770
|
coders
|
2936
2771
|
}) => new Map([...chainIds].flatMap(chainId => {
|
2937
2772
|
const chain = chains[chainId];
|
2938
2773
|
if (!chain) return [];
|
2939
|
-
const
|
2774
|
+
const miniMetadata = miniMetadatas.get(chainId); // findMiniMetadata<TBalanceModule>(miniMetadatas, moduleType, chain)
|
2940
2775
|
if (!miniMetadata) return [];
|
2941
2776
|
if (!miniMetadata.data) return [];
|
2942
2777
|
const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata.data));
|
@@ -2959,6 +2794,28 @@ const buildStorageCoders = ({
|
|
2959
2794
|
return [];
|
2960
2795
|
}
|
2961
2796
|
}));
|
2797
|
+
const buildNetworkStorageCoders = (chainId, miniMetadata, coders) => {
|
2798
|
+
if (!miniMetadata.data) return null;
|
2799
|
+
const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata.data));
|
2800
|
+
try {
|
2801
|
+
const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
|
2802
|
+
const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
|
2803
|
+
const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
|
2804
|
+
chainId
|
2805
|
+
}) : moduleMethodOrFn;
|
2806
|
+
try {
|
2807
|
+
return [[key, scaleBuilder.buildStorage(module, method)]];
|
2808
|
+
} catch (cause) {
|
2809
|
+
log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
|
2810
|
+
return [];
|
2811
|
+
}
|
2812
|
+
}));
|
2813
|
+
return builtCoders;
|
2814
|
+
} catch (cause) {
|
2815
|
+
log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
|
2816
|
+
}
|
2817
|
+
return null;
|
2818
|
+
};
|
2962
2819
|
|
2963
2820
|
/**
|
2964
2821
|
* Decodes & unwraps outputs and errors of a given result, contract, and method.
|
@@ -3040,7 +2897,7 @@ const detectTransferMethod = metadataRpc => {
|
|
3040
2897
|
return hasDeprecatedTransferCall ? "transfer" : "transfer_allow_death";
|
3041
2898
|
};
|
3042
2899
|
|
3043
|
-
const getUniqueChainIds = (addressesByToken, tokens) => [...new Set(Object.keys(addressesByToken).map(tokenId => tokens[tokenId]?.
|
2900
|
+
const getUniqueChainIds = (addressesByToken, tokens) => [...new Set(Object.keys(addressesByToken).map(tokenId => tokens[tokenId]?.networkId).flatMap(chainId => chainId ? [chainId] : []))];
|
3044
2901
|
|
3045
2902
|
const makeContractCaller = ({
|
3046
2903
|
chainConnector,
|
@@ -3149,8 +3006,15 @@ const decompress = data => {
|
|
3149
3006
|
return JSON.parse(decompressed);
|
3150
3007
|
};
|
3151
3008
|
|
3152
|
-
const moduleType$
|
3153
|
-
const
|
3009
|
+
const moduleType$4 = "substrate-assets";
|
3010
|
+
const SubAssetsTokenConfigSchema = z__default.default.strictObject({
|
3011
|
+
assetId: chaindataProvider.SubAssetsTokenSchema.shape.assetId,
|
3012
|
+
...TokenConfigBaseSchema.shape
|
3013
|
+
});
|
3014
|
+
const UNSUPPORTED_CHAIN_META$2 = {
|
3015
|
+
miniMetadata: null,
|
3016
|
+
extra: null
|
3017
|
+
};
|
3154
3018
|
const SubAssetsModule = hydrate => {
|
3155
3019
|
const {
|
3156
3020
|
chainConnectors,
|
@@ -3159,16 +3023,10 @@ const SubAssetsModule = hydrate => {
|
|
3159
3023
|
const chainConnector = chainConnectors.substrate;
|
3160
3024
|
util$1.assert(chainConnector, "This module requires a substrate chain connector");
|
3161
3025
|
return {
|
3162
|
-
...DefaultBalanceModule(moduleType$
|
3026
|
+
...DefaultBalanceModule(moduleType$4),
|
3027
|
+
// TODO make synchronous at the module definition level ?
|
3163
3028
|
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
|
3164
|
-
|
3165
|
-
if (metadataRpc === undefined) return {
|
3166
|
-
isTestnet
|
3167
|
-
};
|
3168
|
-
if ((moduleConfig?.tokens ?? []).length < 1) return {
|
3169
|
-
isTestnet
|
3170
|
-
};
|
3171
|
-
const metadataVersion = scale.getMetadataVersion(metadataRpc);
|
3029
|
+
if (!metadataRpc) return UNSUPPORTED_CHAIN_META$2;
|
3172
3030
|
const metadata = scale.decAnyMetadata(metadataRpc);
|
3173
3031
|
scale.compactMetadata(metadata, [{
|
3174
3032
|
pallet: "Assets",
|
@@ -3176,82 +3034,98 @@ const SubAssetsModule = hydrate => {
|
|
3176
3034
|
}]);
|
3177
3035
|
const miniMetadata = scale.encodeMetadata(metadata);
|
3178
3036
|
return {
|
3179
|
-
isTestnet,
|
3180
3037
|
miniMetadata,
|
3181
|
-
|
3038
|
+
extra: null
|
3182
3039
|
};
|
3183
3040
|
},
|
3184
|
-
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
|
3185
|
-
if (
|
3041
|
+
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
|
3042
|
+
if (!tokens?.length) return {};
|
3186
3043
|
const {
|
3187
|
-
|
3188
|
-
miniMetadata,
|
3189
|
-
metadataVersion
|
3044
|
+
miniMetadata
|
3190
3045
|
} = chainMeta;
|
3191
|
-
if (miniMetadata
|
3192
|
-
if (metadataVersion < 14) return {};
|
3046
|
+
if (!miniMetadata) return {};
|
3193
3047
|
const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata));
|
3194
3048
|
const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
|
3195
3049
|
const assetCoder = scaleBuilder.buildStorage("Assets", "Asset");
|
3196
3050
|
const metadataCoder = scaleBuilder.buildStorage("Assets", "Metadata");
|
3197
|
-
const
|
3198
|
-
for (const tokenConfig of
|
3051
|
+
const tokenList = {};
|
3052
|
+
for (const tokenConfig of tokens ?? []) {
|
3199
3053
|
try {
|
3200
|
-
const assetId =
|
3054
|
+
const assetId = String(tokenConfig.assetId);
|
3201
3055
|
const assetStateKey = tryEncode(assetCoder, BigInt(assetId)) ?? tryEncode(assetCoder, assetId);
|
3202
3056
|
const metadataStateKey = tryEncode(metadataCoder, BigInt(assetId)) ?? tryEncode(metadataCoder, assetId);
|
3203
3057
|
if (assetStateKey === null || metadataStateKey === null) throw new Error(`Failed to encode stateKey for asset ${assetId} on chain ${chainId}`);
|
3204
3058
|
const [assetsAsset, assetsMetadata] = await Promise.all([chainConnector.send(chainId, "state_getStorage", [assetStateKey]).then(result => assetCoder.value.dec(result) ?? null), chainConnector.send(chainId, "state_getStorage", [metadataStateKey]).then(result => metadataCoder.value.dec(result) ?? null)]);
|
3205
3059
|
const existentialDeposit = assetsAsset?.min_balance?.toString?.() ?? "0";
|
3206
3060
|
const symbol = assetsMetadata?.symbol?.asText?.() ?? "Unit";
|
3061
|
+
const name = assetsMetadata?.name?.asText?.() ?? symbol;
|
3207
3062
|
const decimals = assetsMetadata?.decimals ?? 0;
|
3208
3063
|
const isFrozen = assetsMetadata?.is_frozen ?? false;
|
3209
|
-
const id = subAssetTokenId(chainId, assetId
|
3064
|
+
const id = chaindataProvider.subAssetTokenId(chainId, assetId);
|
3210
3065
|
const token = {
|
3211
3066
|
id,
|
3212
3067
|
type: "substrate-assets",
|
3213
|
-
|
3068
|
+
platform: "polkadot",
|
3214
3069
|
isDefault: tokenConfig?.isDefault ?? true,
|
3215
|
-
symbol,
|
3070
|
+
symbol: tokenConfig?.symbol ?? symbol,
|
3071
|
+
name: tokenConfig?.name ?? name,
|
3216
3072
|
decimals,
|
3217
|
-
logo: tokenConfig?.logo
|
3073
|
+
logo: tokenConfig?.logo,
|
3218
3074
|
existentialDeposit,
|
3219
3075
|
assetId,
|
3220
3076
|
isFrozen,
|
3221
|
-
|
3222
|
-
id: chainId
|
3223
|
-
}
|
3077
|
+
networkId: chainId
|
3224
3078
|
};
|
3225
3079
|
if (tokenConfig?.symbol) {
|
3226
3080
|
token.symbol = tokenConfig?.symbol;
|
3227
|
-
token.id = subAssetTokenId(chainId, assetId
|
3081
|
+
token.id = chaindataProvider.subAssetTokenId(chainId, assetId);
|
3228
3082
|
}
|
3229
3083
|
if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
|
3230
|
-
if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
|
3231
3084
|
if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
|
3232
|
-
|
3085
|
+
tokenList[token.id] = token;
|
3233
3086
|
} catch (error) {
|
3234
3087
|
log.error(`Failed to build substrate-assets token ${tokenConfig.assetId} (${tokenConfig.symbol}) on ${chainId}`, error);
|
3235
3088
|
continue;
|
3236
3089
|
}
|
3237
3090
|
}
|
3238
|
-
return
|
3091
|
+
return tokenList;
|
3239
3092
|
},
|
3240
3093
|
// TODO: Don't create empty subscriptions
|
3241
3094
|
async subscribeBalances({
|
3242
3095
|
addressesByToken
|
3243
3096
|
}, callback) {
|
3244
|
-
const
|
3245
|
-
|
3246
|
-
if (
|
3247
|
-
|
3248
|
-
|
3249
|
-
});
|
3250
|
-
|
3097
|
+
const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
|
3098
|
+
const networkId = chaindataProvider.parseSubAssetTokenId(tokenId).networkId;
|
3099
|
+
if (!acc[networkId]) acc[networkId] = {};
|
3100
|
+
acc[networkId][tokenId] = addressesByToken[tokenId];
|
3101
|
+
return acc;
|
3102
|
+
}, {});
|
3103
|
+
const controller = new AbortController();
|
3104
|
+
const pUnsubs = Promise.all(lodash.toPairs(byNetwork).map(async ([networkId, addressesByToken]) => {
|
3105
|
+
try {
|
3106
|
+
const queries = await buildNetworkQueries$2(networkId, chainConnector, chaindataProvider$1, addressesByToken, controller.signal);
|
3107
|
+
if (controller.signal.aborted) return () => {};
|
3108
|
+
const stateHelper = new RpcStateQueryHelper(chainConnector, queries);
|
3109
|
+
return await stateHelper.subscribe((error, result) => {
|
3110
|
+
if (error) return callback(error);
|
3111
|
+
const balances = result?.filter(b => b !== null) ?? [];
|
3112
|
+
if (balances.length > 0) callback(null, new Balances(balances));
|
3113
|
+
});
|
3114
|
+
} catch (err) {
|
3115
|
+
if (!util.isAbortError(err)) log.error(`Failed to subscribe balances for network ${networkId}`, err);
|
3116
|
+
return () => {};
|
3117
|
+
}
|
3118
|
+
}));
|
3119
|
+
return () => {
|
3120
|
+
pUnsubs.then(unsubs => {
|
3121
|
+
unsubs.forEach(unsubscribe => unsubscribe());
|
3122
|
+
});
|
3123
|
+
controller.abort();
|
3124
|
+
};
|
3251
3125
|
},
|
3252
3126
|
async fetchBalances(addressesByToken) {
|
3253
3127
|
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
3254
|
-
const queries = await buildQueries$
|
3128
|
+
const queries = await buildQueries$3(chainConnector, chaindataProvider$1, addressesByToken);
|
3255
3129
|
const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
|
3256
3130
|
const balances = result?.filter(b => b !== null) ?? [];
|
3257
3131
|
return new Balances(balances);
|
@@ -3272,11 +3146,10 @@ const SubAssetsModule = hydrate => {
|
|
3272
3146
|
transferMethod,
|
3273
3147
|
userExtensions
|
3274
3148
|
}) {
|
3275
|
-
const token = await chaindataProvider$1.
|
3149
|
+
const token = await chaindataProvider$1.getTokenById(tokenId, "substrate-assets");
|
3276
3150
|
util$1.assert(token, `Token ${tokenId} not found in store`);
|
3277
|
-
|
3278
|
-
const
|
3279
|
-
const chain = await chaindataProvider$1.chainById(chainId);
|
3151
|
+
const chainId = token.networkId;
|
3152
|
+
const chain = await chaindataProvider$1.getNetworkById(chainId, "polkadot");
|
3280
3153
|
util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
|
3281
3154
|
const {
|
3282
3155
|
genesisHash
|
@@ -3321,23 +3194,16 @@ const SubAssetsModule = hydrate => {
|
|
3321
3194
|
}
|
3322
3195
|
};
|
3323
3196
|
};
|
3324
|
-
async function
|
3325
|
-
const
|
3326
|
-
const
|
3327
|
-
const
|
3328
|
-
|
3329
|
-
const
|
3330
|
-
|
3331
|
-
chainIds: uniqueChainIds,
|
3332
|
-
chains,
|
3333
|
-
miniMetadatas,
|
3334
|
-
moduleType: "substrate-assets",
|
3335
|
-
coders: {
|
3336
|
-
storage: ["Assets", "Account"]
|
3337
|
-
}
|
3197
|
+
async function buildNetworkQueries$2(networkId, chainConnector, chaindataProvider, addressesByToken, signal) {
|
3198
|
+
const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType$4, signal);
|
3199
|
+
const chain = await chaindataProvider.getNetworkById(networkId, "polkadot");
|
3200
|
+
const tokensById = await chaindataProvider.getTokensMapById();
|
3201
|
+
signal?.throwIfAborted();
|
3202
|
+
const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
|
3203
|
+
storage: ["Assets", "Account"]
|
3338
3204
|
});
|
3339
3205
|
return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
|
3340
|
-
const token =
|
3206
|
+
const token = tokensById[tokenId];
|
3341
3207
|
if (!token) {
|
3342
3208
|
log.warn(`Token ${tokenId} not found`);
|
3343
3209
|
return [];
|
@@ -3346,27 +3212,22 @@ async function buildQueries$4(chaindataProvider, addressesByToken) {
|
|
3346
3212
|
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
3347
3213
|
return [];
|
3348
3214
|
}
|
3349
|
-
|
3350
|
-
if (!chainId) {
|
3351
|
-
log.warn(`Token ${tokenId} has no chain`);
|
3352
|
-
return [];
|
3353
|
-
}
|
3354
|
-
const chain = chains[chainId];
|
3215
|
+
//
|
3355
3216
|
if (!chain) {
|
3356
|
-
log.warn(`Chain ${
|
3217
|
+
log.warn(`Chain ${networkId} for token ${tokenId} not found`);
|
3357
3218
|
return [];
|
3358
3219
|
}
|
3359
3220
|
return addresses.flatMap(address => {
|
3360
|
-
const scaleCoder =
|
3361
|
-
const stateKey = tryEncode(scaleCoder,
|
3221
|
+
const scaleCoder = networkStorageCoders?.storage;
|
3222
|
+
const stateKey = tryEncode(scaleCoder, Number(token.assetId), address) ?? tryEncode(scaleCoder, BigInt(token.assetId), address);
|
3362
3223
|
if (!stateKey) {
|
3363
|
-
log.warn(`Invalid assetId / address in ${
|
3224
|
+
log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
|
3364
3225
|
return [];
|
3365
3226
|
}
|
3366
3227
|
const decodeResult = change => {
|
3367
3228
|
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
3368
3229
|
|
3369
|
-
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${
|
3230
|
+
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
|
3370
3231
|
balance: 0n,
|
3371
3232
|
status: {
|
3372
3233
|
type: "Liquid"
|
@@ -3400,22 +3261,30 @@ async function buildQueries$4(chaindataProvider, addressesByToken) {
|
|
3400
3261
|
source: "substrate-assets",
|
3401
3262
|
status: "live",
|
3402
3263
|
address,
|
3403
|
-
|
3404
|
-
subChainId: chainId
|
3405
|
-
},
|
3406
|
-
chainId,
|
3264
|
+
networkId,
|
3407
3265
|
tokenId: token.id,
|
3408
3266
|
values: balanceValues
|
3409
3267
|
};
|
3410
3268
|
};
|
3411
3269
|
return {
|
3412
|
-
chainId,
|
3270
|
+
chainId: networkId,
|
3413
3271
|
stateKey,
|
3414
3272
|
decodeResult
|
3415
3273
|
};
|
3416
3274
|
});
|
3417
3275
|
});
|
3418
3276
|
}
|
3277
|
+
async function buildQueries$3(chainConnector, chaindataProvider$1, addressesByToken, signal) {
|
3278
|
+
const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
|
3279
|
+
const networkId = chaindataProvider.parseSubAssetTokenId(tokenId).networkId;
|
3280
|
+
if (!acc[networkId]) acc[networkId] = {};
|
3281
|
+
acc[networkId][tokenId] = addressesByToken[tokenId];
|
3282
|
+
return acc;
|
3283
|
+
}, {});
|
3284
|
+
return (await Promise.all(lodash.toPairs(byNetwork).map(([networkId, addressesByToken]) => {
|
3285
|
+
return buildNetworkQueries$2(networkId, chainConnector, chaindataProvider$1, addressesByToken, signal);
|
3286
|
+
}))).flat();
|
3287
|
+
}
|
3419
3288
|
// NOTE: Different chains need different formats for assetId when encoding the stateKey
|
3420
3289
|
// E.g. Polkadot Asset Hub needs it to be a string, Astar needs it to be a bigint
|
3421
3290
|
//
|
@@ -3428,9 +3297,16 @@ const tryEncode = (scaleCoder, ...args) => {
|
|
3428
3297
|
}
|
3429
3298
|
};
|
3430
3299
|
|
3431
|
-
const moduleType$
|
3432
|
-
const
|
3433
|
-
|
3300
|
+
const moduleType$3 = "substrate-foreignassets";
|
3301
|
+
const UNSUPPORTED_CHAIN_META$1 = {
|
3302
|
+
miniMetadata: null,
|
3303
|
+
extra: null
|
3304
|
+
};
|
3305
|
+
const SubForeignAssetsTokenConfigSchema = z__default.default.strictObject({
|
3306
|
+
onChainId: chaindataProvider.SubForeignAssetsTokenSchema.shape.onChainId,
|
3307
|
+
...TokenConfigBaseSchema.shape
|
3308
|
+
});
|
3309
|
+
const SubForeignAssetsModule = hydrate => {
|
3434
3310
|
const {
|
3435
3311
|
chainConnectors,
|
3436
3312
|
chaindataProvider: chaindataProvider$1
|
@@ -3438,312 +3314,35 @@ const SubEquilibriumModule = hydrate => {
|
|
3438
3314
|
const chainConnector = chainConnectors.substrate;
|
3439
3315
|
util$1.assert(chainConnector, "This module requires a substrate chain connector");
|
3440
3316
|
return {
|
3441
|
-
...DefaultBalanceModule(moduleType$
|
3317
|
+
...DefaultBalanceModule(moduleType$3),
|
3442
3318
|
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
|
3443
|
-
|
3444
|
-
if (metadataRpc === undefined) return {
|
3445
|
-
isTestnet
|
3446
|
-
};
|
3447
|
-
if (moduleConfig?.disable !== false) return {
|
3448
|
-
isTestnet
|
3449
|
-
}; // default to disabled
|
3450
|
-
|
3319
|
+
if (metadataRpc === undefined) return UNSUPPORTED_CHAIN_META$1;
|
3451
3320
|
const metadataVersion = scale.getMetadataVersion(metadataRpc);
|
3321
|
+
if (metadataVersion < 14) return UNSUPPORTED_CHAIN_META$1;
|
3452
3322
|
const metadata = scale.decAnyMetadata(metadataRpc);
|
3453
3323
|
scale.compactMetadata(metadata, [{
|
3454
|
-
pallet: "
|
3455
|
-
items: ["
|
3456
|
-
}, {
|
3457
|
-
pallet: "System",
|
3458
|
-
items: ["Account"]
|
3324
|
+
pallet: "ForeignAssets",
|
3325
|
+
items: ["Account", "Asset", "Metadata"]
|
3459
3326
|
}]);
|
3460
3327
|
const miniMetadata = scale.encodeMetadata(metadata);
|
3461
3328
|
return {
|
3462
|
-
isTestnet,
|
3463
3329
|
miniMetadata,
|
3464
|
-
|
3330
|
+
extra: null
|
3465
3331
|
};
|
3466
3332
|
},
|
3467
|
-
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
|
3468
|
-
|
3469
|
-
if (moduleConfig?.disable !== false) return {};
|
3333
|
+
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
|
3334
|
+
if (!tokens?.length) return {};
|
3470
3335
|
const {
|
3471
|
-
|
3472
|
-
miniMetadata,
|
3473
|
-
metadataVersion
|
3336
|
+
miniMetadata
|
3474
3337
|
} = chainMeta;
|
3475
|
-
if (miniMetadata
|
3476
|
-
if (metadataVersion < 14) return {};
|
3477
|
-
try {
|
3478
|
-
const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata));
|
3479
|
-
const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
|
3480
|
-
const assetsCoder = scaleBuilder.buildStorage("EqAssets", "Assets");
|
3481
|
-
const stateKey = assetsCoder.keys.enc();
|
3482
|
-
|
3483
|
-
/** NOTE: Just a guideline, the RPC can return whatever it wants */
|
3484
|
-
|
3485
|
-
const assetsResult = await chainConnector.send(chainId, "state_getStorage", [stateKey]).then(result => assetsCoder.value.dec(result) ?? null);
|
3486
|
-
const tokens = (Array.isArray(assetsResult) ? assetsResult : []).flatMap(asset => {
|
3487
|
-
if (!asset) return [];
|
3488
|
-
if (!asset?.id) return [];
|
3489
|
-
const assetId = asset.id.toString(10);
|
3490
|
-
const symbol = tokenSymbolFromU64Id(asset.id);
|
3491
|
-
const id = subEquilibriumTokenId(chainId, symbol);
|
3492
|
-
const decimals = DEFAULT_DECIMALS$1;
|
3493
|
-
const tokenConfig = (moduleConfig?.tokens ?? []).find(token => token.assetId === assetId);
|
3494
|
-
const token = {
|
3495
|
-
id,
|
3496
|
-
type: "substrate-equilibrium",
|
3497
|
-
isTestnet,
|
3498
|
-
isDefault: tokenConfig?.isDefault ?? true,
|
3499
|
-
symbol,
|
3500
|
-
decimals,
|
3501
|
-
logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
|
3502
|
-
// TODO: Fetch the ED
|
3503
|
-
existentialDeposit: "0",
|
3504
|
-
assetId,
|
3505
|
-
chain: {
|
3506
|
-
id: chainId
|
3507
|
-
}
|
3508
|
-
};
|
3509
|
-
if (tokenConfig?.symbol) {
|
3510
|
-
token.symbol = tokenConfig?.symbol;
|
3511
|
-
token.id = subEquilibriumTokenId(chainId, token.symbol);
|
3512
|
-
}
|
3513
|
-
if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
|
3514
|
-
if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
|
3515
|
-
if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
|
3516
|
-
return [[token.id, token]];
|
3517
|
-
});
|
3518
|
-
return Object.fromEntries(tokens);
|
3519
|
-
} catch (error) {
|
3520
|
-
log.error(`Failed to build substrate-equilibrium tokens on ${chainId}`, error?.message ?? error);
|
3521
|
-
return {};
|
3522
|
-
}
|
3523
|
-
},
|
3524
|
-
// TODO: Don't create empty subscriptions
|
3525
|
-
async subscribeBalances({
|
3526
|
-
addressesByToken
|
3527
|
-
}, callback) {
|
3528
|
-
const queries = await buildQueries$3(chaindataProvider$1, addressesByToken);
|
3529
|
-
const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
|
3530
|
-
if (error) return callback(error);
|
3531
|
-
const balances = result?.flatMap(balances => balances) ?? [];
|
3532
|
-
if (balances.length > 0) callback(null, new Balances(balances));
|
3533
|
-
});
|
3534
|
-
return unsubscribe;
|
3535
|
-
},
|
3536
|
-
async fetchBalances(addressesByToken) {
|
3537
|
-
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
3538
|
-
const queries = await buildQueries$3(chaindataProvider$1, addressesByToken);
|
3539
|
-
const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
|
3540
|
-
const balances = result?.flatMap(balances => balances) ?? [];
|
3541
|
-
return new Balances(balances);
|
3542
|
-
},
|
3543
|
-
async transferToken({
|
3544
|
-
tokenId,
|
3545
|
-
from,
|
3546
|
-
to,
|
3547
|
-
amount,
|
3548
|
-
registry,
|
3549
|
-
metadataRpc,
|
3550
|
-
blockHash,
|
3551
|
-
blockNumber,
|
3552
|
-
nonce,
|
3553
|
-
specVersion,
|
3554
|
-
transactionVersion,
|
3555
|
-
tip,
|
3556
|
-
transferMethod,
|
3557
|
-
userExtensions
|
3558
|
-
}) {
|
3559
|
-
const token = await chaindataProvider$1.tokenById(tokenId);
|
3560
|
-
util$1.assert(token, `Token ${tokenId} not found in store`);
|
3561
|
-
if (token.type !== "substrate-equilibrium") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
|
3562
|
-
const chainId = token.chain.id;
|
3563
|
-
const chain = await chaindataProvider$1.chainById(chainId);
|
3564
|
-
util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
|
3565
|
-
const {
|
3566
|
-
genesisHash
|
3567
|
-
} = chain;
|
3568
|
-
const {
|
3569
|
-
assetId
|
3570
|
-
} = token;
|
3571
|
-
const pallet = "EqBalances";
|
3572
|
-
const method = transferMethod === "transfer_all" ?
|
3573
|
-
// the eqBalances pallet has no transfer_all method
|
3574
|
-
"transfer" : transferMethod === "transfer_keep_alive" ?
|
3575
|
-
// the eqBalances pallet has no transfer_keep_alive method
|
3576
|
-
"transfer" : "transfer";
|
3577
|
-
const args = {
|
3578
|
-
asset: assetId,
|
3579
|
-
to,
|
3580
|
-
value: amount
|
3581
|
-
};
|
3582
|
-
const unsigned = txwrapperCore.defineMethod({
|
3583
|
-
method: {
|
3584
|
-
pallet: camelCase__default.default(pallet),
|
3585
|
-
name: camelCase__default.default(method),
|
3586
|
-
args
|
3587
|
-
},
|
3588
|
-
address: from,
|
3589
|
-
blockHash,
|
3590
|
-
blockNumber,
|
3591
|
-
eraPeriod: 64,
|
3592
|
-
genesisHash,
|
3593
|
-
metadataRpc,
|
3594
|
-
nonce,
|
3595
|
-
specVersion,
|
3596
|
-
tip: tip ? Number(tip) : 0,
|
3597
|
-
transactionVersion
|
3598
|
-
}, {
|
3599
|
-
metadataRpc,
|
3600
|
-
registry,
|
3601
|
-
userExtensions
|
3602
|
-
});
|
3603
|
-
return {
|
3604
|
-
type: "substrate",
|
3605
|
-
callData: unsigned.method
|
3606
|
-
};
|
3607
|
-
}
|
3608
|
-
};
|
3609
|
-
};
|
3610
|
-
async function buildQueries$3(chaindataProvider, addressesByToken) {
|
3611
|
-
const allChains = await chaindataProvider.chainsById();
|
3612
|
-
const tokens = await chaindataProvider.tokensById();
|
3613
|
-
const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
|
3614
|
-
const uniqueChainIds = getUniqueChainIds(addressesByToken, tokens);
|
3615
|
-
const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
|
3616
|
-
const chainStorageCoders = buildStorageCoders({
|
3617
|
-
chainIds: uniqueChainIds,
|
3618
|
-
chains,
|
3619
|
-
miniMetadatas,
|
3620
|
-
moduleType: "substrate-equilibrium",
|
3621
|
-
coders: {
|
3622
|
-
storage: ["System", "Account"]
|
3623
|
-
}
|
3624
|
-
});
|
3625
|
-
|
3626
|
-
// equilibrium returns all chain tokens for each address in the one query
|
3627
|
-
// so, we only need to make one query per address, rather than one query per token per address
|
3628
|
-
const addressesByChain = new Map();
|
3629
|
-
const tokensByAddress = new Map();
|
3630
|
-
Object.entries(addressesByToken).map(([tokenId, addresses]) => {
|
3631
|
-
const token = tokens[tokenId];
|
3632
|
-
if (!token) return log.warn(`Token ${tokenId} not found`);
|
3633
|
-
if (token.type !== "substrate-equilibrium") return log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
3634
|
-
const chainId = token?.chain?.id;
|
3635
|
-
if (!chainId) return log.warn(`Token ${tokenId} has no chain`);
|
3636
|
-
const byChain = addressesByChain.get(chainId) ?? new Set();
|
3637
|
-
addresses.forEach(address => {
|
3638
|
-
byChain?.add(address);
|
3639
|
-
tokensByAddress.set(address, (tokensByAddress.get(address) ?? new Set()).add(token));
|
3640
|
-
});
|
3641
|
-
addressesByChain.set(chainId, byChain);
|
3642
|
-
});
|
3643
|
-
return Array.from(addressesByChain).flatMap(([chainId, addresses]) => {
|
3644
|
-
const chain = chains[chainId];
|
3645
|
-
if (!chain) {
|
3646
|
-
log.warn(`Chain ${chainId} not found`);
|
3647
|
-
return [];
|
3648
|
-
}
|
3649
|
-
return Array.from(addresses).flatMap(address => {
|
3650
|
-
const scaleCoder = chainStorageCoders.get(chainId)?.storage;
|
3651
|
-
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address in ${chainId} storage query ${address}`, address);
|
3652
|
-
if (!stateKey) return [];
|
3653
|
-
const decodeResult = change => {
|
3654
|
-
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
3655
|
-
|
3656
|
-
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode eqBalances on chain ${chainId}`);
|
3657
|
-
const tokenBalances = Object.fromEntries((decoded?.data?.value?.balance ?? []).map(balance => ({
|
3658
|
-
id: (balance?.[0] ?? 0n)?.toString?.(),
|
3659
|
-
free: balance?.[1]?.type === "Positive" ? (balance?.[1]?.value ?? 0n).toString() : balance?.[1]?.type === "Negative" ? ((balance?.[1]?.value ?? 0n) * -1n).toString() : "0"
|
3660
|
-
})).map(({
|
3661
|
-
id,
|
3662
|
-
free
|
3663
|
-
}) => [id, free]).filter(([id, free]) => id !== undefined && free !== undefined));
|
3664
|
-
const result = Array.from(tokensByAddress.get(address) ?? []).filter(t => t.chain.id === chainId).map(token => {
|
3665
|
-
const value = tokenBalances[token.assetId];
|
3666
|
-
return {
|
3667
|
-
source: "substrate-equilibrium",
|
3668
|
-
status: "live",
|
3669
|
-
address,
|
3670
|
-
multiChainId: {
|
3671
|
-
subChainId: chainId
|
3672
|
-
},
|
3673
|
-
chainId,
|
3674
|
-
tokenId: token.id,
|
3675
|
-
value
|
3676
|
-
};
|
3677
|
-
}).filter(b => b !== undefined);
|
3678
|
-
return result;
|
3679
|
-
};
|
3680
|
-
return {
|
3681
|
-
chainId,
|
3682
|
-
stateKey,
|
3683
|
-
decodeResult
|
3684
|
-
};
|
3685
|
-
});
|
3686
|
-
});
|
3687
|
-
}
|
3688
|
-
const DEFAULT_DECIMALS$1 = 9;
|
3689
|
-
const tokenSymbolFromU64Id = u64 => {
|
3690
|
-
const bytes = [];
|
3691
|
-
let num = typeof u64 === "number" ? BigInt(u64) : util.isBigInt(u64) ? u64 : u64.toBigInt();
|
3692
|
-
do {
|
3693
|
-
bytes.unshift(Number(num % 256n));
|
3694
|
-
num = num / 256n;
|
3695
|
-
} while (num > 0);
|
3696
|
-
return new TextDecoder("utf-8").decode(new Uint8Array(bytes)).toUpperCase();
|
3697
|
-
};
|
3698
|
-
|
3699
|
-
const moduleType$3 = "substrate-foreignassets";
|
3700
|
-
const subForeignAssetTokenId = (chainId, tokenSymbol) => `${chainId}-substrate-foreignassets-${tokenSymbol}`.toLowerCase().replace(/ /g, "-");
|
3701
|
-
const SubForeignAssetsModule = hydrate => {
|
3702
|
-
const {
|
3703
|
-
chainConnectors,
|
3704
|
-
chaindataProvider: chaindataProvider$1
|
3705
|
-
} = hydrate;
|
3706
|
-
const chainConnector = chainConnectors.substrate;
|
3707
|
-
util$1.assert(chainConnector, "This module requires a substrate chain connector");
|
3708
|
-
return {
|
3709
|
-
...DefaultBalanceModule(moduleType$3),
|
3710
|
-
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
|
3711
|
-
const isTestnet = (await chaindataProvider$1.chainById(chainId))?.isTestnet || false;
|
3712
|
-
if (metadataRpc === undefined) return {
|
3713
|
-
isTestnet
|
3714
|
-
};
|
3715
|
-
if ((moduleConfig?.tokens ?? []).length < 1) return {
|
3716
|
-
isTestnet
|
3717
|
-
};
|
3718
|
-
const metadataVersion = scale.getMetadataVersion(metadataRpc);
|
3719
|
-
const metadata = scale.decAnyMetadata(metadataRpc);
|
3720
|
-
scale.compactMetadata(metadata, [{
|
3721
|
-
pallet: "ForeignAssets",
|
3722
|
-
items: ["Account", "Asset", "Metadata"]
|
3723
|
-
}]);
|
3724
|
-
const miniMetadata = scale.encodeMetadata(metadata);
|
3725
|
-
return {
|
3726
|
-
isTestnet,
|
3727
|
-
miniMetadata,
|
3728
|
-
metadataVersion
|
3729
|
-
};
|
3730
|
-
},
|
3731
|
-
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
|
3732
|
-
if ((moduleConfig?.tokens ?? []).length < 1) return {};
|
3733
|
-
const {
|
3734
|
-
isTestnet,
|
3735
|
-
miniMetadata,
|
3736
|
-
metadataVersion
|
3737
|
-
} = chainMeta;
|
3738
|
-
if (miniMetadata === undefined || metadataVersion === undefined) return {};
|
3739
|
-
if (metadataVersion < 14) return {};
|
3338
|
+
if (!miniMetadata) return {};
|
3740
3339
|
const metadata = scale.decAnyMetadata(miniMetadata);
|
3741
3340
|
const unifiedMetadata = scale.unifyMetadata(metadata);
|
3742
3341
|
const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(unifiedMetadata));
|
3743
3342
|
const assetCoder = scaleBuilder.buildStorage("ForeignAssets", "Asset");
|
3744
3343
|
const metadataCoder = scaleBuilder.buildStorage("ForeignAssets", "Metadata");
|
3745
|
-
const
|
3746
|
-
for (const tokenConfig of
|
3344
|
+
const tokenList = {};
|
3345
|
+
for (const tokenConfig of tokens ?? []) {
|
3747
3346
|
try {
|
3748
3347
|
const onChainId = (() => {
|
3749
3348
|
try {
|
@@ -3758,54 +3357,70 @@ const SubForeignAssetsModule = hydrate => {
|
|
3758
3357
|
const [assetsAsset, assetsMetadata] = await Promise.all([chainConnector.send(chainId, "state_getStorage", [assetStateKey]).then(result => assetCoder.value.dec(result) ?? null), chainConnector.send(chainId, "state_getStorage", [metadataStateKey]).then(result => metadataCoder.value.dec(result) ?? null)]);
|
3759
3358
|
const existentialDeposit = assetsAsset?.min_balance?.toString?.() ?? "0";
|
3760
3359
|
const symbol = assetsMetadata?.symbol?.asText?.() ?? "Unit";
|
3360
|
+
const name = assetsMetadata?.name?.asText?.() ?? symbol;
|
3761
3361
|
const decimals = assetsMetadata?.decimals ?? 0;
|
3762
3362
|
const isFrozen = assetsMetadata?.is_frozen ?? false;
|
3763
|
-
const id = subForeignAssetTokenId(chainId,
|
3363
|
+
const id = chaindataProvider.subForeignAssetTokenId(chainId, tokenConfig.onChainId);
|
3764
3364
|
const token = {
|
3765
3365
|
id,
|
3766
3366
|
type: "substrate-foreignassets",
|
3767
|
-
|
3367
|
+
platform: "polkadot",
|
3768
3368
|
isDefault: tokenConfig?.isDefault ?? true,
|
3769
3369
|
symbol,
|
3770
3370
|
decimals,
|
3771
|
-
|
3371
|
+
name: tokenConfig?.name ?? name,
|
3372
|
+
logo: tokenConfig?.logo,
|
3772
3373
|
existentialDeposit,
|
3773
3374
|
onChainId: tokenConfig.onChainId,
|
3774
3375
|
isFrozen,
|
3775
|
-
|
3776
|
-
id: chainId
|
3777
|
-
}
|
3376
|
+
networkId: chainId
|
3778
3377
|
};
|
3779
|
-
if (tokenConfig?.symbol) {
|
3780
|
-
token.symbol = tokenConfig?.symbol;
|
3781
|
-
token.id = subForeignAssetTokenId(chainId, token.symbol);
|
3782
|
-
}
|
3783
3378
|
if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
|
3784
|
-
if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
|
3785
3379
|
if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
|
3786
|
-
|
3380
|
+
tokenList[token.id] = token;
|
3787
3381
|
} catch (error) {
|
3788
3382
|
log.error(`Failed to build substrate-foreignassets token ${tokenConfig.onChainId} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
|
3789
3383
|
continue;
|
3790
3384
|
}
|
3791
3385
|
}
|
3792
|
-
return
|
3386
|
+
return tokenList;
|
3793
3387
|
},
|
3794
3388
|
// TODO: Don't create empty subscriptions
|
3795
3389
|
async subscribeBalances({
|
3796
3390
|
addressesByToken
|
3797
3391
|
}, callback) {
|
3798
|
-
const
|
3799
|
-
|
3800
|
-
if (
|
3801
|
-
|
3802
|
-
|
3803
|
-
});
|
3804
|
-
|
3392
|
+
const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
|
3393
|
+
const networkId = chaindataProvider.parseSubForeignAssetTokenId(tokenId).networkId;
|
3394
|
+
if (!acc[networkId]) acc[networkId] = {};
|
3395
|
+
acc[networkId][tokenId] = addressesByToken[tokenId];
|
3396
|
+
return acc;
|
3397
|
+
}, {});
|
3398
|
+
const controller = new AbortController();
|
3399
|
+
const pUnsubs = Promise.all(lodash.toPairs(byNetwork).map(async ([networkId, addressesByToken]) => {
|
3400
|
+
try {
|
3401
|
+
const queries = await buildNetworkQueries$1(networkId, chainConnector, chaindataProvider$1, addressesByToken, controller.signal);
|
3402
|
+
if (controller.signal.aborted) return () => {};
|
3403
|
+
const stateHelper = new RpcStateQueryHelper(chainConnector, queries);
|
3404
|
+
return await stateHelper.subscribe((error, result) => {
|
3405
|
+
if (error) return callback(error);
|
3406
|
+
const balances = result?.filter(b => b !== null) ?? [];
|
3407
|
+
if (balances.length > 0) callback(null, new Balances(balances));
|
3408
|
+
});
|
3409
|
+
} catch (err) {
|
3410
|
+
if (!util.isAbortError(err)) log.error(`Failed to subscribe ${moduleType$3} balances for network ${networkId}`, err);
|
3411
|
+
return () => {};
|
3412
|
+
}
|
3413
|
+
}));
|
3414
|
+
return () => {
|
3415
|
+
pUnsubs.then(unsubs => {
|
3416
|
+
unsubs.forEach(unsubscribe => unsubscribe());
|
3417
|
+
});
|
3418
|
+
controller.abort();
|
3419
|
+
};
|
3805
3420
|
},
|
3806
3421
|
async fetchBalances(addressesByToken) {
|
3807
3422
|
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
3808
|
-
const queries = await buildQueries$2(chaindataProvider$1, addressesByToken);
|
3423
|
+
const queries = await buildQueries$2(chainConnector, chaindataProvider$1, addressesByToken);
|
3809
3424
|
const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
|
3810
3425
|
const balances = result?.filter(b => b !== null) ?? [];
|
3811
3426
|
return new Balances(balances);
|
@@ -3817,11 +3432,10 @@ const SubForeignAssetsModule = hydrate => {
|
|
3817
3432
|
transferMethod,
|
3818
3433
|
metadataRpc
|
3819
3434
|
}) {
|
3820
|
-
const token = await chaindataProvider$1.
|
3435
|
+
const token = await chaindataProvider$1.getTokenById(tokenId, "substrate-foreignassets");
|
3821
3436
|
util$1.assert(token, `Token ${tokenId} not found in store`);
|
3822
|
-
|
3823
|
-
const
|
3824
|
-
const chain = await chaindataProvider$1.chainById(chainId);
|
3437
|
+
const chainId = token.networkId;
|
3438
|
+
const chain = await chaindataProvider$1.getNetworkById(chainId, "polkadot");
|
3825
3439
|
util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
|
3826
3440
|
const onChainId = (() => {
|
3827
3441
|
try {
|
@@ -3861,23 +3475,16 @@ const SubForeignAssetsModule = hydrate => {
|
|
3861
3475
|
}
|
3862
3476
|
};
|
3863
3477
|
};
|
3864
|
-
async function
|
3865
|
-
const
|
3866
|
-
const
|
3867
|
-
const
|
3868
|
-
|
3869
|
-
const
|
3870
|
-
|
3871
|
-
chainIds: uniqueChainIds,
|
3872
|
-
chains,
|
3873
|
-
miniMetadatas,
|
3874
|
-
moduleType: "substrate-foreignassets",
|
3875
|
-
coders: {
|
3876
|
-
storage: ["ForeignAssets", "Account"]
|
3877
|
-
}
|
3478
|
+
async function buildNetworkQueries$1(networkId, chainConnector, chaindataProvider, addressesByToken, signal) {
|
3479
|
+
const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType$3, signal);
|
3480
|
+
const chain = await chaindataProvider.getNetworkById(networkId, "polkadot");
|
3481
|
+
const tokensById = await chaindataProvider.getTokensMapById();
|
3482
|
+
signal?.throwIfAborted();
|
3483
|
+
const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
|
3484
|
+
storage: ["ForeignAssets", "Account"]
|
3878
3485
|
});
|
3879
3486
|
return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
|
3880
|
-
const token =
|
3487
|
+
const token = tokensById[tokenId];
|
3881
3488
|
if (!token) {
|
3882
3489
|
log.warn(`Token ${tokenId} not found`);
|
3883
3490
|
return [];
|
@@ -3886,18 +3493,12 @@ async function buildQueries$2(chaindataProvider, addressesByToken) {
|
|
3886
3493
|
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
3887
3494
|
return [];
|
3888
3495
|
}
|
3889
|
-
const chainId = token.chain?.id;
|
3890
|
-
if (!chainId) {
|
3891
|
-
log.warn(`Token ${tokenId} has no chain`);
|
3892
|
-
return [];
|
3893
|
-
}
|
3894
|
-
const chain = chains[chainId];
|
3895
3496
|
if (!chain) {
|
3896
|
-
log.warn(`Chain ${
|
3497
|
+
log.warn(`Chain ${networkId} for token ${tokenId} not found`);
|
3897
3498
|
return [];
|
3898
3499
|
}
|
3899
3500
|
return addresses.flatMap(address => {
|
3900
|
-
const scaleCoder =
|
3501
|
+
const scaleCoder = networkStorageCoders?.storage;
|
3901
3502
|
const onChainId = (() => {
|
3902
3503
|
try {
|
3903
3504
|
return scale.papiParse(token.onChainId);
|
@@ -3905,12 +3506,12 @@ async function buildQueries$2(chaindataProvider, addressesByToken) {
|
|
3905
3506
|
return token.onChainId;
|
3906
3507
|
}
|
3907
3508
|
})();
|
3908
|
-
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${
|
3509
|
+
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${networkId} storage query ${address} / ${token.onChainId}`, onChainId, address);
|
3909
3510
|
if (!stateKey) return [];
|
3910
3511
|
const decodeResult = change => {
|
3911
3512
|
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
3912
3513
|
|
3913
|
-
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-foreignassets balance on chain ${
|
3514
|
+
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-foreignassets balance on chain ${networkId}`) ?? {
|
3914
3515
|
balance: 0n,
|
3915
3516
|
status: {
|
3916
3517
|
type: "Liquid"
|
@@ -3944,29 +3545,54 @@ async function buildQueries$2(chaindataProvider, addressesByToken) {
|
|
3944
3545
|
source: "substrate-foreignassets",
|
3945
3546
|
status: "live",
|
3946
3547
|
address,
|
3947
|
-
|
3948
|
-
subChainId: chainId
|
3949
|
-
},
|
3950
|
-
chainId,
|
3548
|
+
networkId,
|
3951
3549
|
tokenId: token.id,
|
3952
3550
|
values: balanceValues
|
3953
3551
|
};
|
3954
3552
|
};
|
3955
3553
|
return {
|
3956
|
-
chainId,
|
3554
|
+
chainId: networkId,
|
3957
3555
|
stateKey,
|
3958
3556
|
decodeResult
|
3959
3557
|
};
|
3960
3558
|
});
|
3961
3559
|
});
|
3962
3560
|
}
|
3561
|
+
async function buildQueries$2(chainConnector, chaindataProvider$1, addressesByToken, signal) {
|
3562
|
+
const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
|
3563
|
+
const networkId = chaindataProvider.parseSubForeignAssetTokenId(tokenId).networkId;
|
3564
|
+
if (!acc[networkId]) acc[networkId] = {};
|
3565
|
+
acc[networkId][tokenId] = addressesByToken[tokenId];
|
3566
|
+
return acc;
|
3567
|
+
}, {});
|
3568
|
+
return (await Promise.all(lodash.toPairs(byNetwork).map(([networkId, addressesByToken]) => {
|
3569
|
+
return buildNetworkQueries$1(networkId, chainConnector, chaindataProvider$1, addressesByToken, signal);
|
3570
|
+
}))).flat();
|
3571
|
+
}
|
3572
|
+
|
3573
|
+
const getAddresssesByTokenByNetwork = addressesByToken => {
|
3574
|
+
const addressesByTokenByNetwork = lodash.toPairs(addressesByToken).reduce((acc, [tokenId, addresses]) => {
|
3575
|
+
const networkId = chaindataProvider.parseTokenId(tokenId).networkId;
|
3576
|
+
if (!acc[networkId]) acc[networkId] = {};
|
3577
|
+
acc[networkId][tokenId] = addresses;
|
3578
|
+
return acc;
|
3579
|
+
}, {});
|
3580
|
+
return addressesByTokenByNetwork;
|
3581
|
+
};
|
3963
3582
|
|
3964
3583
|
async function subscribeBase(queries, chainConnector, callback) {
|
3965
|
-
|
3966
|
-
|
3967
|
-
|
3968
|
-
|
3969
|
-
|
3584
|
+
try {
|
3585
|
+
const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
|
3586
|
+
if (error) callback(error);
|
3587
|
+
if (result && result.length > 0) callback(null, result);
|
3588
|
+
});
|
3589
|
+
return unsubscribe;
|
3590
|
+
} catch (err) {
|
3591
|
+
if (!util.isAbortError(err)) log.error("Error subscribing to base queries", {
|
3592
|
+
err
|
3593
|
+
});
|
3594
|
+
return () => {};
|
3595
|
+
}
|
3970
3596
|
}
|
3971
3597
|
|
3972
3598
|
/**
|
@@ -3982,262 +3608,6 @@ const asObservable = handler => (...args) => new rxjs.Observable(subscriber => {
|
|
3982
3608
|
return unsubscribe;
|
3983
3609
|
});
|
3984
3610
|
|
3985
|
-
/**
|
3986
|
-
* Crowdloan contributions are stored in the `childstate` key returned by this function.
|
3987
|
-
*/
|
3988
|
-
const crowdloanFundContributionsChildKey = fundIndex => util$1.u8aToHex(util$1.u8aConcat(":child_storage:default:", utilCrypto.blake2AsU8a(util$1.u8aConcat("crowdloan", scaleTs.u32.enc(fundIndex)))));
|
3989
|
-
|
3990
|
-
async function subscribeCrowdloans(chaindataProvider, chainConnector, addressesByToken, callback) {
|
3991
|
-
const allChains = await chaindataProvider.chainsById();
|
3992
|
-
const tokens = await chaindataProvider.tokensById();
|
3993
|
-
const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
|
3994
|
-
const crowdloanTokenIds = Object.entries(tokens).filter(([, token]) => {
|
3995
|
-
// ignore non-native tokens
|
3996
|
-
if (token.type !== "substrate-native") return;
|
3997
|
-
// ignore tokens on chains with no crowdloans pallet
|
3998
|
-
const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", allChains[token.chain.id]);
|
3999
|
-
return typeof chainMeta?.crowdloanPalletId === "string";
|
4000
|
-
}).map(([tokenId]) => tokenId);
|
4001
|
-
|
4002
|
-
// crowdloan contributions can only be done by the native token on chains with the crowdloan pallet
|
4003
|
-
const addressesByCrowdloanToken = Object.fromEntries(Object.entries(addressesByToken)
|
4004
|
-
// remove ethereum addresses
|
4005
|
-
.map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !util.isEthereumAddress(address))])
|
4006
|
-
// remove tokens which aren't crowdloan tokens
|
4007
|
-
.filter(([tokenId]) => crowdloanTokenIds.includes(tokenId)));
|
4008
|
-
const uniqueChainIds = getUniqueChainIds(addressesByCrowdloanToken, tokens);
|
4009
|
-
const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
|
4010
|
-
const chainStorageCoders = buildStorageCoders({
|
4011
|
-
chainIds: uniqueChainIds,
|
4012
|
-
chains,
|
4013
|
-
miniMetadatas,
|
4014
|
-
moduleType: "substrate-native",
|
4015
|
-
coders: {
|
4016
|
-
parachains: ["Paras", "Parachains"],
|
4017
|
-
funds: ["Crowdloan", "Funds"]
|
4018
|
-
}
|
4019
|
-
});
|
4020
|
-
const tokenSubscriptions = [];
|
4021
|
-
for (const [tokenId, addresses] of Object.entries(addressesByCrowdloanToken)) {
|
4022
|
-
const token = tokens[tokenId];
|
4023
|
-
if (!token) {
|
4024
|
-
log.warn(`Token ${tokenId} not found`);
|
4025
|
-
continue;
|
4026
|
-
}
|
4027
|
-
if (token.type !== "substrate-native") {
|
4028
|
-
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
4029
|
-
continue;
|
4030
|
-
}
|
4031
|
-
const chainId = token.chain?.id;
|
4032
|
-
if (!chainId) {
|
4033
|
-
log.warn(`Token ${tokenId} has no chain`);
|
4034
|
-
continue;
|
4035
|
-
}
|
4036
|
-
const chain = chains[chainId];
|
4037
|
-
if (!chain) {
|
4038
|
-
log.warn(`Chain ${chainId} for token ${tokenId} not found`);
|
4039
|
-
continue;
|
4040
|
-
}
|
4041
|
-
const subscribeParaIds = callback => {
|
4042
|
-
const scaleCoder = chainStorageCoders.get(chainId)?.parachains;
|
4043
|
-
const queries = [0].flatMap(() => {
|
4044
|
-
const stateKey = scale.encodeStateKey(scaleCoder);
|
4045
|
-
if (!stateKey) return [];
|
4046
|
-
const decodeResult = change => {
|
4047
|
-
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
4048
|
-
|
4049
|
-
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode parachains on chain ${chainId}`);
|
4050
|
-
const paraIds = decoded ?? [];
|
4051
|
-
return paraIds;
|
4052
|
-
};
|
4053
|
-
return {
|
4054
|
-
chainId,
|
4055
|
-
stateKey,
|
4056
|
-
decodeResult
|
4057
|
-
};
|
4058
|
-
});
|
4059
|
-
const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
|
4060
|
-
return () => subscription.then(unsubscribe => unsubscribe());
|
4061
|
-
};
|
4062
|
-
const subscribeParaFundIndexes = (paraIds, callback) => {
|
4063
|
-
const scaleCoder = chainStorageCoders.get(chainId)?.funds;
|
4064
|
-
const queries = paraIds.flatMap(paraId => {
|
4065
|
-
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid paraId in ${chainId} funds query ${paraId}`, paraId);
|
4066
|
-
if (!stateKey) return [];
|
4067
|
-
const decodeResult = change => {
|
4068
|
-
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
4069
|
-
|
4070
|
-
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode paras on chain ${chainId}`);
|
4071
|
-
const firstPeriod = decoded?.first_period?.toString?.() ?? "";
|
4072
|
-
const lastPeriod = decoded?.last_period?.toString?.() ?? "";
|
4073
|
-
const fundPeriod = `${firstPeriod}-${lastPeriod}`;
|
4074
|
-
const fundIndex = decoded?.fund_index ?? decoded?.trie_index;
|
4075
|
-
return {
|
4076
|
-
paraId,
|
4077
|
-
fundPeriod,
|
4078
|
-
fundIndex
|
4079
|
-
};
|
4080
|
-
};
|
4081
|
-
return {
|
4082
|
-
chainId,
|
4083
|
-
stateKey,
|
4084
|
-
decodeResult
|
4085
|
-
};
|
4086
|
-
});
|
4087
|
-
const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
|
4088
|
-
return () => subscription.then(unsubscribe => unsubscribe());
|
4089
|
-
};
|
4090
|
-
const subscribeFundContributions = (funds, addresses, callback) => {
|
4091
|
-
// TODO: Watch system_events in order to subscribe to changes, then redo the contributions query when changes are detected:
|
4092
|
-
// https://github.com/polkadot-js/api/blob/8fe02a14345b57e6abb8f7f2c2b624cf70c51b23/packages/api-derive/src/crowdloan/ownContributions.ts#L32-L47
|
4093
|
-
//
|
4094
|
-
// For now we just re-fetch all contributions on a timer and then only send them to the subscription callback when they have changed
|
4095
|
-
|
4096
|
-
const queries = funds.map(({
|
4097
|
-
paraId,
|
4098
|
-
fundIndex
|
4099
|
-
}) => ({
|
4100
|
-
paraId,
|
4101
|
-
fundIndex,
|
4102
|
-
addresses,
|
4103
|
-
childKey: crowdloanFundContributionsChildKey(fundIndex),
|
4104
|
-
storageKeys: addresses.map(address => util$1.u8aToHex(util.decodeAnyAddress(address)))
|
4105
|
-
}));
|
4106
|
-
|
4107
|
-
// track whether our caller is still subscribed
|
4108
|
-
let subscriptionActive = true;
|
4109
|
-
let previousContributions = null;
|
4110
|
-
const fetchContributions = async () => {
|
4111
|
-
try {
|
4112
|
-
const results = await Promise.all(queries.map(async ({
|
4113
|
-
paraId,
|
4114
|
-
fundIndex,
|
4115
|
-
addresses,
|
4116
|
-
childKey,
|
4117
|
-
storageKeys
|
4118
|
-
}) => ({
|
4119
|
-
paraId,
|
4120
|
-
fundIndex,
|
4121
|
-
addresses,
|
4122
|
-
result: await chainConnector.send(chainId, "childstate_getStorageEntries", [childKey, storageKeys])
|
4123
|
-
})));
|
4124
|
-
const contributions = results.flatMap(queryResult => {
|
4125
|
-
const {
|
4126
|
-
paraId,
|
4127
|
-
fundIndex,
|
4128
|
-
addresses,
|
4129
|
-
result
|
4130
|
-
} = queryResult;
|
4131
|
-
return (Array.isArray(result) ? result : []).flatMap((encoded, index) => {
|
4132
|
-
const amount = (() => {
|
4133
|
-
try {
|
4134
|
-
return typeof encoded === "string" ? scaleTs.u128.dec(encoded) ?? 0n : 0n;
|
4135
|
-
} catch {
|
4136
|
-
return 0n;
|
4137
|
-
}
|
4138
|
-
})().toString();
|
4139
|
-
return {
|
4140
|
-
paraId,
|
4141
|
-
fundIndex,
|
4142
|
-
address: addresses[index],
|
4143
|
-
amount
|
4144
|
-
};
|
4145
|
-
});
|
4146
|
-
});
|
4147
|
-
|
4148
|
-
// ignore these results if our caller has tried to close this subscription
|
4149
|
-
if (!subscriptionActive) return;
|
4150
|
-
|
4151
|
-
// ignore these results if they're the same as what we previously fetched
|
4152
|
-
if (isEqual__default.default(previousContributions, contributions)) return;
|
4153
|
-
previousContributions = contributions;
|
4154
|
-
callback(null, contributions);
|
4155
|
-
} catch (error) {
|
4156
|
-
callback(error);
|
4157
|
-
}
|
4158
|
-
};
|
4159
|
-
|
4160
|
-
// set up polling for contributions
|
4161
|
-
const crowdloanContributionsPollInterval = 60_000; // 60_000ms === 1 minute
|
4162
|
-
const pollContributions = async () => {
|
4163
|
-
if (!subscriptionActive) return;
|
4164
|
-
try {
|
4165
|
-
await fetchContributions();
|
4166
|
-
} catch (error) {
|
4167
|
-
// log any errors, but don't cancel the poll for contributions when one fetch fails
|
4168
|
-
log.error(error);
|
4169
|
-
}
|
4170
|
-
if (!subscriptionActive) return;
|
4171
|
-
setTimeout(pollContributions, crowdloanContributionsPollInterval);
|
4172
|
-
};
|
4173
|
-
|
4174
|
-
// start polling
|
4175
|
-
pollContributions();
|
4176
|
-
return () => {
|
4177
|
-
// stop polling
|
4178
|
-
subscriptionActive = false;
|
4179
|
-
};
|
4180
|
-
};
|
4181
|
-
const paraIds$ = asObservable(subscribeParaIds)().pipe(rxjs.scan((_, next) => Array.from(new Set(next.flatMap(paraIds => paraIds))), []), rxjs.share());
|
4182
|
-
const fundIndexesByParaId$ = paraIds$.pipe(rxjs.map(paraIds => asObservable(subscribeParaFundIndexes)(paraIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
|
4183
|
-
for (const fund of next) {
|
4184
|
-
const {
|
4185
|
-
paraId,
|
4186
|
-
fundIndex
|
4187
|
-
} = fund;
|
4188
|
-
if (typeof fundIndex === "number") {
|
4189
|
-
state.set(paraId, (state.get(paraId) ?? new Set()).add(fundIndex));
|
4190
|
-
}
|
4191
|
-
}
|
4192
|
-
return state;
|
4193
|
-
}, new Map()));
|
4194
|
-
const contributionsByAddress$ = fundIndexesByParaId$.pipe(rxjs.map(fundIndexesByParaId => Array.from(fundIndexesByParaId).flatMap(([paraId, fundIndexes]) => Array.from(fundIndexes).map(fundIndex => ({
|
4195
|
-
paraId,
|
4196
|
-
fundIndex
|
4197
|
-
})))), rxjs.map(funds => asObservable(subscribeFundContributions)(funds, addresses)), rxjs.switchAll(), rxjs.scan((state, next) => {
|
4198
|
-
for (const contribution of next) {
|
4199
|
-
const {
|
4200
|
-
address
|
4201
|
-
} = contribution;
|
4202
|
-
state.set(address, (state.get(address) ?? new Set()).add(contribution));
|
4203
|
-
}
|
4204
|
-
return state;
|
4205
|
-
}, new Map()));
|
4206
|
-
const subscription = contributionsByAddress$.subscribe({
|
4207
|
-
next: contributionsByAddress => {
|
4208
|
-
const balances = Array.from(contributionsByAddress).map(([address, contributions]) => {
|
4209
|
-
return {
|
4210
|
-
source: "substrate-native",
|
4211
|
-
status: "live",
|
4212
|
-
address,
|
4213
|
-
multiChainId: {
|
4214
|
-
subChainId: chainId
|
4215
|
-
},
|
4216
|
-
chainId,
|
4217
|
-
tokenId,
|
4218
|
-
values: Array.from(contributions).map(({
|
4219
|
-
amount,
|
4220
|
-
paraId
|
4221
|
-
}) => ({
|
4222
|
-
type: "crowdloan",
|
4223
|
-
label: "crowdloan",
|
4224
|
-
source: "crowdloan",
|
4225
|
-
amount: amount,
|
4226
|
-
meta: {
|
4227
|
-
paraId
|
4228
|
-
}
|
4229
|
-
}))
|
4230
|
-
};
|
4231
|
-
});
|
4232
|
-
if (balances.length > 0) callback(null, balances);
|
4233
|
-
},
|
4234
|
-
error: error => callback(error)
|
4235
|
-
});
|
4236
|
-
tokenSubscriptions.push(() => subscription.unsubscribe());
|
4237
|
-
}
|
4238
|
-
return () => tokenSubscriptions.forEach(unsub => unsub());
|
4239
|
-
}
|
4240
|
-
|
4241
3611
|
/**
|
4242
3612
|
* Each nominationPool in the nominationPools pallet has access to some accountIds which have no
|
4243
3613
|
* associated private key. Instead, they are derived from this function.
|
@@ -4257,280 +3627,293 @@ const nompoolAccountId = (palletId, poolId, index) => {
|
|
4257
3627
|
/** The stash account for the nomination pool */
|
4258
3628
|
const nompoolStashAccountId = (palletId, poolId) => nompoolAccountId(palletId, poolId, 0);
|
4259
3629
|
|
4260
|
-
|
4261
|
-
|
4262
|
-
|
4263
|
-
|
4264
|
-
|
4265
|
-
|
4266
|
-
|
4267
|
-
|
4268
|
-
const
|
4269
|
-
|
4270
|
-
|
4271
|
-
|
4272
|
-
// staking can only be done by the native token on chains with the staking pallet
|
4273
|
-
const addressesByNomPoolToken = Object.fromEntries(Object.entries(addressesByToken)
|
4274
|
-
// remove ethereum addresses
|
4275
|
-
.map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !util.isEthereumAddress(address))])
|
4276
|
-
// remove tokens which aren't nom pool tokens
|
4277
|
-
.filter(([tokenId]) => nomPoolTokenIds.includes(tokenId)));
|
4278
|
-
const uniqueChainIds = getUniqueChainIds(addressesByNomPoolToken, tokens);
|
4279
|
-
const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
|
4280
|
-
const chainStorageCoders = buildStorageCoders({
|
4281
|
-
chainIds: uniqueChainIds,
|
4282
|
-
chains,
|
4283
|
-
miniMetadatas,
|
4284
|
-
moduleType: "substrate-native",
|
4285
|
-
coders: {
|
4286
|
-
poolMembers: ["NominationPools", "PoolMembers"],
|
4287
|
-
bondedPools: ["NominationPools", "BondedPools"],
|
4288
|
-
ledger: ["Staking", "Ledger"],
|
4289
|
-
metadata: ["NominationPools", "Metadata"]
|
4290
|
-
}
|
4291
|
-
});
|
4292
|
-
const resultUnsubscribes = [];
|
4293
|
-
for (const [tokenId, addresses] of Object.entries(addressesByNomPoolToken)) {
|
4294
|
-
const token = tokens[tokenId];
|
4295
|
-
if (!token) {
|
4296
|
-
log.warn(`Token ${tokenId} not found`);
|
4297
|
-
continue;
|
4298
|
-
}
|
4299
|
-
if (token.type !== "substrate-native") {
|
4300
|
-
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
4301
|
-
continue;
|
4302
|
-
}
|
4303
|
-
const chainId = token.chain?.id;
|
4304
|
-
if (!chainId) {
|
4305
|
-
log.warn(`Token ${tokenId} has no chain`);
|
4306
|
-
continue;
|
4307
|
-
}
|
4308
|
-
const chain = chains[chainId];
|
4309
|
-
if (!chain) {
|
4310
|
-
log.warn(`Chain ${chainId} for token ${tokenId} not found`);
|
4311
|
-
continue;
|
3630
|
+
// TODO make this method chain-specific
|
3631
|
+
async function subscribeNompoolStaking(chaindataProvider$1, chainConnector, addressesByToken, callback, signal) {
|
3632
|
+
try {
|
3633
|
+
const allChains = await chaindataProvider$1.getNetworksMapById("polkadot");
|
3634
|
+
const tokens = await chaindataProvider$1.getTokensMapById();
|
3635
|
+
|
3636
|
+
// there should be only one network here when subscribing to balances, we've split it up by network at the top level
|
3637
|
+
const networkIds = lodash.keys(addressesByToken).map(tokenId => chaindataProvider.parseTokenId(tokenId).networkId);
|
3638
|
+
const miniMetadatas = new Map();
|
3639
|
+
for (const networkId of networkIds) {
|
3640
|
+
const miniMetadata = await getMiniMetadata(chaindataProvider$1, chainConnector, networkId, "substrate-native");
|
3641
|
+
miniMetadatas.set(networkId, miniMetadata);
|
4312
3642
|
}
|
4313
|
-
|
4314
|
-
const {
|
4315
|
-
|
4316
|
-
|
4317
|
-
|
4318
|
-
|
4319
|
-
const
|
4320
|
-
|
4321
|
-
|
4322
|
-
|
4323
|
-
|
4324
|
-
|
4325
|
-
|
4326
|
-
|
4327
|
-
|
4328
|
-
|
4329
|
-
|
4330
|
-
|
4331
|
-
|
4332
|
-
|
4333
|
-
|
3643
|
+
signal?.throwIfAborted();
|
3644
|
+
const nomPoolTokenIds = Object.entries(tokens).filter(([, token]) => {
|
3645
|
+
// ignore non-native tokens
|
3646
|
+
if (token.type !== "substrate-native") return false;
|
3647
|
+
|
3648
|
+
// ignore tokens on chains with no nompools pallet
|
3649
|
+
const miniMetadata = miniMetadatas.get(token.networkId);
|
3650
|
+
return typeof miniMetadata?.extra?.nominationPoolsPalletId === "string";
|
3651
|
+
}).map(([tokenId]) => tokenId);
|
3652
|
+
|
3653
|
+
// staking can only be done by the native token on chains with the staking pallet
|
3654
|
+
const addressesByNomPoolToken = Object.fromEntries(Object.entries(addressesByToken)
|
3655
|
+
// remove ethereum addresses
|
3656
|
+
.map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !util.isEthereumAddress(address))])
|
3657
|
+
// remove tokens which aren't nom pool tokens
|
3658
|
+
.filter(([tokenId]) => nomPoolTokenIds.includes(tokenId)));
|
3659
|
+
const uniqueChainIds = getUniqueChainIds(addressesByNomPoolToken, tokens);
|
3660
|
+
const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
|
3661
|
+
const chainStorageCoders = buildStorageCoders({
|
3662
|
+
chainIds: uniqueChainIds,
|
3663
|
+
chains,
|
3664
|
+
miniMetadatas,
|
3665
|
+
coders: {
|
3666
|
+
poolMembers: ["NominationPools", "PoolMembers"],
|
3667
|
+
bondedPools: ["NominationPools", "BondedPools"],
|
3668
|
+
ledger: ["Staking", "Ledger"],
|
3669
|
+
metadata: ["NominationPools", "Metadata"]
|
3670
|
+
}
|
3671
|
+
});
|
3672
|
+
const resultUnsubscribes = [];
|
3673
|
+
for (const [tokenId, addresses] of Object.entries(addressesByNomPoolToken)) {
|
3674
|
+
const token = tokens[tokenId];
|
3675
|
+
if (!token) {
|
3676
|
+
log.warn(`Token ${tokenId} not found`);
|
3677
|
+
continue;
|
3678
|
+
}
|
3679
|
+
if (token.type !== "substrate-native") {
|
3680
|
+
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
3681
|
+
continue;
|
3682
|
+
}
|
3683
|
+
const chainId = token.networkId;
|
3684
|
+
if (!chainId) {
|
3685
|
+
log.warn(`Token ${tokenId} has no chain`);
|
3686
|
+
continue;
|
3687
|
+
}
|
3688
|
+
const chain = chains[chainId];
|
3689
|
+
if (!chain) {
|
3690
|
+
log.warn(`Chain ${chainId} for token ${tokenId} not found`);
|
3691
|
+
continue;
|
3692
|
+
}
|
3693
|
+
const miniMetadata = miniMetadatas.get(chainId);
|
3694
|
+
const {
|
3695
|
+
nominationPoolsPalletId
|
3696
|
+
} = miniMetadata?.extra ?? {};
|
3697
|
+
const subscribePoolMembers = (addresses, callback) => {
|
3698
|
+
const scaleCoder = chainStorageCoders.get(chainId)?.poolMembers;
|
3699
|
+
const queries = addresses.flatMap(address => {
|
3700
|
+
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address in ${chainId} poolMembers query ${address}`, address);
|
3701
|
+
if (!stateKey) return [];
|
3702
|
+
const decodeResult = change => {
|
3703
|
+
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
3704
|
+
|
3705
|
+
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode poolMembers on chain ${chainId}`);
|
3706
|
+
const poolId = decoded?.pool_id?.toString?.();
|
3707
|
+
const points = decoded?.points?.toString?.();
|
3708
|
+
const unbondingEras = Array.from(decoded?.unbonding_eras ?? []).flatMap(entry => {
|
3709
|
+
if (entry === undefined) return [];
|
3710
|
+
const [key, value] = Array.from(entry);
|
3711
|
+
const era = key?.toString?.();
|
3712
|
+
const amount = value?.toString?.();
|
3713
|
+
if (typeof era !== "string" || typeof amount !== "string") return [];
|
3714
|
+
return {
|
3715
|
+
era,
|
3716
|
+
amount
|
3717
|
+
};
|
3718
|
+
});
|
4334
3719
|
return {
|
4335
|
-
|
4336
|
-
|
3720
|
+
tokenId,
|
3721
|
+
address,
|
3722
|
+
poolId,
|
3723
|
+
points,
|
3724
|
+
unbondingEras
|
4337
3725
|
};
|
4338
|
-
}
|
3726
|
+
};
|
4339
3727
|
return {
|
4340
|
-
|
4341
|
-
|
3728
|
+
chainId,
|
3729
|
+
stateKey,
|
3730
|
+
decodeResult
|
3731
|
+
};
|
3732
|
+
});
|
3733
|
+
const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
|
3734
|
+
return () => subscription.then(unsubscribe => unsubscribe());
|
3735
|
+
};
|
3736
|
+
const subscribePoolPoints = (poolIds, callback) => {
|
3737
|
+
if (poolIds.length === 0) callback(null, []);
|
3738
|
+
const scaleCoder = chainStorageCoders.get(chainId)?.bondedPools;
|
3739
|
+
const queries = poolIds.flatMap(poolId => {
|
3740
|
+
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} bondedPools query ${poolId}`, poolId);
|
3741
|
+
if (!stateKey) return [];
|
3742
|
+
const decodeResult = change => {
|
3743
|
+
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
3744
|
+
|
3745
|
+
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode bondedPools on chain ${chainId}`);
|
3746
|
+
const points = decoded?.points?.toString?.();
|
3747
|
+
return {
|
3748
|
+
poolId,
|
3749
|
+
points
|
3750
|
+
};
|
3751
|
+
};
|
3752
|
+
return {
|
3753
|
+
chainId,
|
3754
|
+
stateKey,
|
3755
|
+
decodeResult
|
3756
|
+
};
|
3757
|
+
});
|
3758
|
+
const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
|
3759
|
+
return () => subscription.then(unsubscribe => unsubscribe());
|
3760
|
+
};
|
3761
|
+
const subscribePoolStake = (poolIds, callback) => {
|
3762
|
+
if (poolIds.length === 0) callback(null, []);
|
3763
|
+
const scaleCoder = chainStorageCoders.get(chainId)?.ledger;
|
3764
|
+
const queries = poolIds.flatMap(poolId => {
|
3765
|
+
if (!nominationPoolsPalletId) return [];
|
3766
|
+
const stashAddress = nompoolStashAccountId(nominationPoolsPalletId, poolId);
|
3767
|
+
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address in ${chainId} ledger query ${stashAddress}`, stashAddress);
|
3768
|
+
if (!stateKey) return [];
|
3769
|
+
const decodeResult = change => {
|
3770
|
+
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
3771
|
+
|
3772
|
+
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode ledger on chain ${chainId}`);
|
3773
|
+
const activeStake = decoded?.active?.toString?.();
|
3774
|
+
return {
|
3775
|
+
poolId,
|
3776
|
+
activeStake
|
3777
|
+
};
|
3778
|
+
};
|
3779
|
+
return {
|
3780
|
+
chainId,
|
3781
|
+
stateKey,
|
3782
|
+
decodeResult
|
3783
|
+
};
|
3784
|
+
});
|
3785
|
+
const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
|
3786
|
+
return () => subscription.then(unsubscribe => unsubscribe());
|
3787
|
+
};
|
3788
|
+
const subscribePoolMetadata = (poolIds, callback) => {
|
3789
|
+
if (poolIds.length === 0) callback(null, []);
|
3790
|
+
const scaleCoder = chainStorageCoders.get(chainId)?.metadata;
|
3791
|
+
const queries = poolIds.flatMap(poolId => {
|
3792
|
+
if (!nominationPoolsPalletId) return [];
|
3793
|
+
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} metadata query ${poolId}`, poolId);
|
3794
|
+
if (!stateKey) return [];
|
3795
|
+
const decodeResult = change => {
|
3796
|
+
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
3797
|
+
|
3798
|
+
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode metadata on chain ${chainId}`);
|
3799
|
+
const metadata = decoded?.asText?.();
|
3800
|
+
return {
|
3801
|
+
poolId,
|
3802
|
+
metadata
|
3803
|
+
};
|
3804
|
+
};
|
3805
|
+
return {
|
3806
|
+
chainId,
|
3807
|
+
stateKey,
|
3808
|
+
decodeResult
|
3809
|
+
};
|
3810
|
+
});
|
3811
|
+
const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
|
3812
|
+
return () => subscription.then(unsubscribe => unsubscribe());
|
3813
|
+
};
|
3814
|
+
const poolMembersByAddress$ = asObservable(subscribePoolMembers)(addresses).pipe(rxjs.scan((state, next) => {
|
3815
|
+
for (const poolMembers of next) {
|
3816
|
+
const {
|
4342
3817
|
poolId,
|
4343
3818
|
points,
|
4344
3819
|
unbondingEras
|
4345
|
-
};
|
4346
|
-
|
4347
|
-
|
4348
|
-
|
4349
|
-
|
4350
|
-
|
4351
|
-
}
|
4352
|
-
|
4353
|
-
|
4354
|
-
|
4355
|
-
|
4356
|
-
|
4357
|
-
|
4358
|
-
const
|
4359
|
-
|
4360
|
-
|
4361
|
-
if (!stateKey) return [];
|
4362
|
-
const decodeResult = change => {
|
4363
|
-
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
4364
|
-
|
4365
|
-
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode bondedPools on chain ${chainId}`);
|
4366
|
-
const points = decoded?.points?.toString?.();
|
4367
|
-
return {
|
3820
|
+
} = poolMembers;
|
3821
|
+
if (typeof poolId === "string" && typeof points === "string") state.set(poolMembers.address, {
|
3822
|
+
poolId,
|
3823
|
+
points,
|
3824
|
+
unbondingEras
|
3825
|
+
});else state.set(poolMembers.address, null);
|
3826
|
+
}
|
3827
|
+
return state;
|
3828
|
+
}, new Map()), rxjs.share());
|
3829
|
+
const poolIdByAddress$ = poolMembersByAddress$.pipe(rxjs.map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.poolId ?? null]))));
|
3830
|
+
const pointsByAddress$ = poolMembersByAddress$.pipe(rxjs.map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.points ?? null]))));
|
3831
|
+
const unbondingErasByAddress$ = poolMembersByAddress$.pipe(rxjs.map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.unbondingEras ?? null]))));
|
3832
|
+
const poolIds$ = poolIdByAddress$.pipe(rxjs.map(byAddress => [...new Set(Array.from(byAddress.values()).flatMap(poolId => poolId ?? []))]));
|
3833
|
+
const pointsByPool$ = poolIds$.pipe(rxjs.map(poolIds => asObservable(subscribePoolPoints)(poolIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
|
3834
|
+
for (const poolPoints of next) {
|
3835
|
+
const {
|
4368
3836
|
poolId,
|
4369
3837
|
points
|
4370
|
-
};
|
4371
|
-
|
4372
|
-
|
4373
|
-
|
4374
|
-
|
4375
|
-
|
4376
|
-
|
4377
|
-
|
4378
|
-
const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
|
4379
|
-
return () => subscription.then(unsubscribe => unsubscribe());
|
4380
|
-
};
|
4381
|
-
const subscribePoolStake = (poolIds, callback) => {
|
4382
|
-
if (poolIds.length === 0) callback(null, []);
|
4383
|
-
const scaleCoder = chainStorageCoders.get(chainId)?.ledger;
|
4384
|
-
const queries = poolIds.flatMap(poolId => {
|
4385
|
-
if (!nominationPoolsPalletId) return [];
|
4386
|
-
const stashAddress = nompoolStashAccountId(nominationPoolsPalletId, poolId);
|
4387
|
-
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address in ${chainId} ledger query ${stashAddress}`, stashAddress);
|
4388
|
-
if (!stateKey) return [];
|
4389
|
-
const decodeResult = change => {
|
4390
|
-
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
4391
|
-
|
4392
|
-
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode ledger on chain ${chainId}`);
|
4393
|
-
const activeStake = decoded?.active?.toString?.();
|
4394
|
-
return {
|
3838
|
+
} = poolPoints;
|
3839
|
+
if (typeof points === "string") state.set(poolId, points);else state.delete(poolId);
|
3840
|
+
}
|
3841
|
+
return state;
|
3842
|
+
}, new Map()));
|
3843
|
+
const stakeByPool$ = poolIds$.pipe(rxjs.map(poolIds => asObservable(subscribePoolStake)(poolIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
|
3844
|
+
for (const poolStake of next) {
|
3845
|
+
const {
|
4395
3846
|
poolId,
|
4396
3847
|
activeStake
|
4397
|
-
};
|
4398
|
-
|
4399
|
-
|
4400
|
-
|
4401
|
-
|
4402
|
-
|
4403
|
-
|
4404
|
-
|
4405
|
-
const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
|
4406
|
-
return () => subscription.then(unsubscribe => unsubscribe());
|
4407
|
-
};
|
4408
|
-
const subscribePoolMetadata = (poolIds, callback) => {
|
4409
|
-
if (poolIds.length === 0) callback(null, []);
|
4410
|
-
const scaleCoder = chainStorageCoders.get(chainId)?.metadata;
|
4411
|
-
const queries = poolIds.flatMap(poolId => {
|
4412
|
-
if (!nominationPoolsPalletId) return [];
|
4413
|
-
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} metadata query ${poolId}`, poolId);
|
4414
|
-
if (!stateKey) return [];
|
4415
|
-
const decodeResult = change => {
|
4416
|
-
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
4417
|
-
|
4418
|
-
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode metadata on chain ${chainId}`);
|
4419
|
-
const metadata = decoded?.asText?.();
|
4420
|
-
return {
|
3848
|
+
} = poolStake;
|
3849
|
+
if (typeof activeStake === "string") state.set(poolId, activeStake);else state.delete(poolId);
|
3850
|
+
}
|
3851
|
+
return state;
|
3852
|
+
}, new Map()));
|
3853
|
+
const metadataByPool$ = poolIds$.pipe(rxjs.map(poolIds => asObservable(subscribePoolMetadata)(poolIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
|
3854
|
+
for (const poolMetadata of next) {
|
3855
|
+
const {
|
4421
3856
|
poolId,
|
4422
3857
|
metadata
|
4423
|
-
};
|
4424
|
-
|
4425
|
-
|
4426
|
-
|
4427
|
-
|
4428
|
-
|
4429
|
-
|
4430
|
-
|
4431
|
-
|
4432
|
-
|
4433
|
-
|
4434
|
-
|
4435
|
-
|
4436
|
-
|
4437
|
-
|
4438
|
-
|
4439
|
-
|
4440
|
-
|
4441
|
-
|
4442
|
-
|
4443
|
-
|
4444
|
-
|
4445
|
-
|
4446
|
-
|
4447
|
-
|
4448
|
-
}, new Map()), rxjs.share());
|
4449
|
-
const poolIdByAddress$ = poolMembersByAddress$.pipe(rxjs.map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.poolId ?? null]))));
|
4450
|
-
const pointsByAddress$ = poolMembersByAddress$.pipe(rxjs.map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.points ?? null]))));
|
4451
|
-
const unbondingErasByAddress$ = poolMembersByAddress$.pipe(rxjs.map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.unbondingEras ?? null]))));
|
4452
|
-
const poolIds$ = poolIdByAddress$.pipe(rxjs.map(byAddress => [...new Set(Array.from(byAddress.values()).flatMap(poolId => poolId ?? []))]));
|
4453
|
-
const pointsByPool$ = poolIds$.pipe(rxjs.map(poolIds => asObservable(subscribePoolPoints)(poolIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
|
4454
|
-
for (const poolPoints of next) {
|
4455
|
-
const {
|
4456
|
-
poolId,
|
4457
|
-
points
|
4458
|
-
} = poolPoints;
|
4459
|
-
if (typeof points === "string") state.set(poolId, points);else state.delete(poolId);
|
4460
|
-
}
|
4461
|
-
return state;
|
4462
|
-
}, new Map()));
|
4463
|
-
const stakeByPool$ = poolIds$.pipe(rxjs.map(poolIds => asObservable(subscribePoolStake)(poolIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
|
4464
|
-
for (const poolStake of next) {
|
4465
|
-
const {
|
4466
|
-
poolId,
|
4467
|
-
activeStake
|
4468
|
-
} = poolStake;
|
4469
|
-
if (typeof activeStake === "string") state.set(poolId, activeStake);else state.delete(poolId);
|
4470
|
-
}
|
4471
|
-
return state;
|
4472
|
-
}, new Map()));
|
4473
|
-
const metadataByPool$ = poolIds$.pipe(rxjs.map(poolIds => asObservable(subscribePoolMetadata)(poolIds)), rxjs.switchAll(), rxjs.scan((state, next) => {
|
4474
|
-
for (const poolMetadata of next) {
|
4475
|
-
const {
|
4476
|
-
poolId,
|
4477
|
-
metadata
|
4478
|
-
} = poolMetadata;
|
4479
|
-
if (typeof metadata === "string") state.set(poolId, metadata);else state.delete(poolId);
|
4480
|
-
}
|
4481
|
-
return state;
|
4482
|
-
}, new Map()));
|
4483
|
-
const subscription = rxjs.combineLatest([poolIdByAddress$, pointsByAddress$, unbondingErasByAddress$, pointsByPool$, stakeByPool$, metadataByPool$]).subscribe({
|
4484
|
-
next: ([poolIdByAddress, pointsByAddress, unbondingErasByAddress, pointsByPool, stakeByPool, metadataByPool]) => {
|
4485
|
-
const balances = Array.from(poolIdByAddress).map(([address, poolId]) => {
|
4486
|
-
const parsedPoolId = poolId === null ? undefined : parseInt(poolId);
|
4487
|
-
const points = pointsByAddress.get(address) ?? "0";
|
4488
|
-
const poolPoints = pointsByPool.get(poolId ?? "") ?? "0";
|
4489
|
-
const poolStake = stakeByPool.get(poolId ?? "") ?? "0";
|
4490
|
-
const poolMetadata = poolId ? metadataByPool.get(poolId) ?? `Pool ${poolId}` : undefined;
|
4491
|
-
const amount = points === "0" || poolPoints === "0" || poolStake === "0" ? 0n : BigInt(poolStake) * BigInt(points) / BigInt(poolPoints);
|
4492
|
-
const unbondingAmount = (unbondingErasByAddress.get(address) ?? []).reduce((total, {
|
4493
|
-
amount
|
4494
|
-
}) => total + BigInt(amount ?? "0"), 0n);
|
4495
|
-
return {
|
4496
|
-
source: "substrate-native",
|
4497
|
-
status: "live",
|
4498
|
-
address,
|
4499
|
-
multiChainId: {
|
4500
|
-
subChainId: chainId
|
4501
|
-
},
|
4502
|
-
chainId,
|
4503
|
-
tokenId,
|
4504
|
-
values: [{
|
4505
|
-
source: "nompools-staking",
|
4506
|
-
type: "nompool",
|
4507
|
-
label: "nompools-staking",
|
4508
|
-
amount: amount.toString(),
|
4509
|
-
meta: {
|
3858
|
+
} = poolMetadata;
|
3859
|
+
if (typeof metadata === "string") state.set(poolId, metadata);else state.delete(poolId);
|
3860
|
+
}
|
3861
|
+
return state;
|
3862
|
+
}, new Map()));
|
3863
|
+
const subscription = rxjs.combineLatest([poolIdByAddress$, pointsByAddress$, unbondingErasByAddress$, pointsByPool$, stakeByPool$, metadataByPool$]).subscribe({
|
3864
|
+
next: ([poolIdByAddress, pointsByAddress, unbondingErasByAddress, pointsByPool, stakeByPool, metadataByPool]) => {
|
3865
|
+
const balances = Array.from(poolIdByAddress).map(([address, poolId]) => {
|
3866
|
+
const parsedPoolId = poolId === null ? undefined : parseInt(poolId);
|
3867
|
+
const points = pointsByAddress.get(address) ?? "0";
|
3868
|
+
const poolPoints = pointsByPool.get(poolId ?? "") ?? "0";
|
3869
|
+
const poolStake = stakeByPool.get(poolId ?? "") ?? "0";
|
3870
|
+
const poolMetadata = poolId ? metadataByPool.get(poolId) ?? `Pool ${poolId}` : undefined;
|
3871
|
+
const amount = points === "0" || poolPoints === "0" || poolStake === "0" ? 0n : BigInt(poolStake) * BigInt(points) / BigInt(poolPoints);
|
3872
|
+
const unbondingAmount = (unbondingErasByAddress.get(address) ?? []).reduce((total, {
|
3873
|
+
amount
|
3874
|
+
}) => total + BigInt(amount ?? "0"), 0n);
|
3875
|
+
return {
|
3876
|
+
source: "substrate-native",
|
3877
|
+
status: "live",
|
3878
|
+
address,
|
3879
|
+
networkId: chainId,
|
3880
|
+
tokenId,
|
3881
|
+
values: [{
|
3882
|
+
source: "nompools-staking",
|
4510
3883
|
type: "nompool",
|
4511
|
-
|
4512
|
-
|
4513
|
-
|
4514
|
-
|
4515
|
-
|
4516
|
-
|
4517
|
-
|
4518
|
-
|
4519
|
-
|
4520
|
-
|
4521
|
-
|
4522
|
-
|
4523
|
-
|
4524
|
-
|
4525
|
-
|
4526
|
-
|
4527
|
-
|
4528
|
-
|
4529
|
-
|
3884
|
+
label: "nompools-staking",
|
3885
|
+
amount: amount.toString(),
|
3886
|
+
meta: {
|
3887
|
+
type: "nompool",
|
3888
|
+
poolId: parsedPoolId,
|
3889
|
+
description: poolMetadata
|
3890
|
+
}
|
3891
|
+
}, {
|
3892
|
+
source: "nompools-staking",
|
3893
|
+
type: "nompool",
|
3894
|
+
label: "nompools-unbonding",
|
3895
|
+
amount: unbondingAmount.toString(),
|
3896
|
+
meta: {
|
3897
|
+
poolId: parsedPoolId,
|
3898
|
+
description: poolMetadata,
|
3899
|
+
unbonding: true
|
3900
|
+
}
|
3901
|
+
}]
|
3902
|
+
};
|
3903
|
+
}).filter(util.isNotNil);
|
3904
|
+
if (balances.length > 0) callback(null, balances);
|
3905
|
+
},
|
3906
|
+
error: error => callback(error)
|
3907
|
+
});
|
3908
|
+
resultUnsubscribes.push(() => subscription.unsubscribe());
|
3909
|
+
}
|
3910
|
+
return () => resultUnsubscribes.forEach(unsub => unsub());
|
3911
|
+
} catch (err) {
|
3912
|
+
if (!util.isAbortError(err)) log.error("Error subscribing to nom pool staking", {
|
3913
|
+
err
|
4530
3914
|
});
|
4531
|
-
|
3915
|
+
return () => {};
|
4532
3916
|
}
|
4533
|
-
return () => resultUnsubscribes.forEach(unsub => unsub());
|
4534
3917
|
}
|
4535
3918
|
|
4536
3919
|
const SUBTENSOR_ROOT_NETUID = 0;
|
@@ -4567,230 +3950,241 @@ const calculateTaoFromDynamicInfo = ({
|
|
4567
3950
|
dynamicInfo
|
4568
3951
|
});
|
4569
3952
|
return calculateTaoAmountFromAlpha({
|
4570
|
-
alphaPrice,
|
4571
|
-
alphaStaked
|
4572
|
-
});
|
4573
|
-
};
|
4574
|
-
|
4575
|
-
|
4576
|
-
|
4577
|
-
|
4578
|
-
|
4579
|
-
|
4580
|
-
|
4581
|
-
|
4582
|
-
|
4583
|
-
const
|
4584
|
-
|
4585
|
-
|
4586
|
-
|
4587
|
-
// staking can only be done by the native token on chains with the subtensor pallet
|
4588
|
-
const addressesBySubtensorToken = Object.fromEntries(Object.entries(addressesByToken)
|
4589
|
-
// remove ethereum addresses
|
4590
|
-
.map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !util.isEthereumAddress(address))])
|
4591
|
-
// remove tokens which aren't subtensor staking tokens
|
4592
|
-
.filter(([tokenId]) => subtensorTokenIds.includes(tokenId)));
|
4593
|
-
const uniqueChainIds = getUniqueChainIds(addressesBySubtensorToken, tokens);
|
4594
|
-
const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
|
4595
|
-
const abortController = new AbortController();
|
4596
|
-
for (const [tokenId, addresses] of Object.entries(addressesBySubtensorToken)) {
|
4597
|
-
const token = tokens[tokenId];
|
4598
|
-
if (!token) {
|
4599
|
-
log.warn(`Token ${tokenId} not found`);
|
4600
|
-
continue;
|
4601
|
-
}
|
4602
|
-
if (token.type !== "substrate-native") {
|
4603
|
-
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
4604
|
-
continue;
|
4605
|
-
}
|
4606
|
-
const chainId = token.chain?.id;
|
4607
|
-
if (!chainId) {
|
4608
|
-
log.warn(`Token ${tokenId} has no chain`);
|
4609
|
-
continue;
|
4610
|
-
}
|
4611
|
-
const chain = chains[chainId];
|
4612
|
-
if (!chain) {
|
4613
|
-
log.warn(`Chain ${chainId} for token ${tokenId} not found`);
|
4614
|
-
continue;
|
4615
|
-
}
|
4616
|
-
const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", chain);
|
4617
|
-
if (!chainMeta?.miniMetadata) {
|
4618
|
-
log.warn(`MiniMetadata for chain ${chainId} not found`);
|
4619
|
-
continue;
|
3953
|
+
alphaPrice,
|
3954
|
+
alphaStaked
|
3955
|
+
});
|
3956
|
+
};
|
3957
|
+
|
3958
|
+
// TODO make this method chain-specific
|
3959
|
+
async function subscribeSubtensorStaking(chaindataProvider$1, chainConnector, addressesByToken, callback, signal) {
|
3960
|
+
try {
|
3961
|
+
const allChains = await chaindataProvider$1.getNetworksMapById("polkadot");
|
3962
|
+
const tokens = await chaindataProvider$1.getTokensMapById();
|
3963
|
+
|
3964
|
+
// there should be only one network here when subscribing to balances, we've split it up by network at the top level
|
3965
|
+
const networkIds = lodash.keys(addressesByToken).map(tokenId => chaindataProvider.parseTokenId(tokenId).networkId);
|
3966
|
+
const miniMetadatas = new Map();
|
3967
|
+
for (const networkId of networkIds) {
|
3968
|
+
const miniMetadata = await getMiniMetadata(chaindataProvider$1, chainConnector, networkId, "substrate-native", signal);
|
3969
|
+
miniMetadatas.set(networkId, miniMetadata);
|
4620
3970
|
}
|
4621
|
-
|
4622
|
-
|
4623
|
-
|
4624
|
-
|
4625
|
-
|
4626
|
-
)
|
4627
|
-
|
4628
|
-
|
4629
|
-
|
4630
|
-
|
4631
|
-
|
4632
|
-
|
4633
|
-
|
4634
|
-
|
4635
|
-
|
4636
|
-
|
4637
|
-
|
4638
|
-
|
4639
|
-
|
4640
|
-
|
4641
|
-
|
4642
|
-
|
4643
|
-
|
4644
|
-
|
4645
|
-
|
4646
|
-
|
4647
|
-
|
4648
|
-
|
4649
|
-
|
4650
|
-
|
3971
|
+
signal?.throwIfAborted();
|
3972
|
+
const subtensorTokenIds = Object.entries(tokens).filter(([, token]) => {
|
3973
|
+
// ignore non-native tokens
|
3974
|
+
if (token.type !== "substrate-native") return false;
|
3975
|
+
// ignore tokens on chains with no subtensor pallet
|
3976
|
+
const miniMetadata = miniMetadatas.get(token.networkId);
|
3977
|
+
return miniMetadata?.extra?.hasSubtensorPallet === true;
|
3978
|
+
}).map(([tokenId]) => tokenId);
|
3979
|
+
|
3980
|
+
// staking can only be done by the native token on chains with the subtensor pallet
|
3981
|
+
const addressesBySubtensorToken = Object.fromEntries(Object.entries(addressesByToken)
|
3982
|
+
// remove ethereum addresses
|
3983
|
+
.map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !util.isEthereumAddress(address))])
|
3984
|
+
// remove tokens which aren't subtensor staking tokens
|
3985
|
+
.filter(([tokenId]) => subtensorTokenIds.includes(tokenId)));
|
3986
|
+
const uniqueChainIds = getUniqueChainIds(addressesBySubtensorToken, tokens);
|
3987
|
+
const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
|
3988
|
+
const abortController = new AbortController();
|
3989
|
+
for (const [tokenId, addresses] of Object.entries(addressesBySubtensorToken)) {
|
3990
|
+
const token = tokens[tokenId];
|
3991
|
+
if (!token) {
|
3992
|
+
log.warn(`Token ${tokenId} not found`);
|
3993
|
+
continue;
|
3994
|
+
}
|
3995
|
+
if (token.type !== "substrate-native") {
|
3996
|
+
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
3997
|
+
continue;
|
3998
|
+
}
|
3999
|
+
const chainId = token.networkId;
|
4000
|
+
const chain = chains[chainId];
|
4001
|
+
if (!chain) {
|
4002
|
+
log.warn(`Chain ${chainId} for token ${tokenId} not found`);
|
4003
|
+
continue;
|
4004
|
+
}
|
4005
|
+
const miniMetadata = miniMetadatas.get(token.networkId);
|
4006
|
+
if (!miniMetadata?.data) {
|
4007
|
+
log.warn(`MiniMetadata for chain ${chainId} not found`);
|
4008
|
+
continue;
|
4009
|
+
}
|
4010
|
+
const scaleApi = sapi.getScaleApi({
|
4011
|
+
chainId,
|
4012
|
+
send: (...args) => chainConnector.send(chainId, ...args, {
|
4013
|
+
expectErrors: true
|
4014
|
+
} // don't pollute the wallet logs when this request fails
|
4015
|
+
)
|
4016
|
+
}, miniMetadata.data, token, chain.hasCheckMetadataHash, chain.signedExtensions, chain.registryTypes);
|
4017
|
+
|
4018
|
+
// sets the number of addresses to query in parallel (per chain, since each chain runs in parallel to the others)
|
4019
|
+
const concurrency = 4;
|
4020
|
+
// In-memory cache for successful dynamic info results
|
4021
|
+
const dynamicInfoCache = new Map();
|
4022
|
+
const fetchDynamicInfoForNetuids = async uniqueNetuids => {
|
4023
|
+
const MAX_RETRIES = 3;
|
4024
|
+
const RETRY_DELAY_MS = 500;
|
4025
|
+
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
4026
|
+
const fetchInfo = async netuid => {
|
4027
|
+
if (netuid === 0) return null;
|
4028
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
4029
|
+
try {
|
4030
|
+
const params = [netuid];
|
4031
|
+
const result = await scaleApi.getRuntimeCallValue("SubnetInfoRuntimeApi", "get_dynamic_info", params);
|
4032
|
+
dynamicInfoCache.set(netuid, result); // Cache successful response
|
4033
|
+
return result;
|
4034
|
+
} catch (error) {
|
4035
|
+
log.trace(`Attempt ${attempt} failed for netuid ${netuid}:`, error);
|
4036
|
+
if (attempt < MAX_RETRIES) {
|
4037
|
+
const backoffTime = RETRY_DELAY_MS * 2 ** (attempt - 1);
|
4038
|
+
log.trace(`Retrying in ${backoffTime}ms...`);
|
4039
|
+
await delay(backoffTime);
|
4040
|
+
}
|
4651
4041
|
}
|
4652
4042
|
}
|
4653
|
-
|
4654
|
-
|
4655
|
-
|
4656
|
-
|
4657
|
-
|
4658
|
-
|
4043
|
+
if (dynamicInfoCache.has(netuid)) {
|
4044
|
+
return dynamicInfoCache.get(netuid); // Use cached value on failure
|
4045
|
+
}
|
4046
|
+
log.trace(`Failed to fetch dynamic info for netuid ${netuid} after ${MAX_RETRIES} attempts.`);
|
4047
|
+
return null;
|
4048
|
+
};
|
4049
|
+
return Promise.all(uniqueNetuids.map(fetchInfo));
|
4659
4050
|
};
|
4660
|
-
|
4661
|
-
|
4662
|
-
|
4663
|
-
|
4664
|
-
|
4665
|
-
|
4666
|
-
|
4667
|
-
|
4668
|
-
|
4669
|
-
|
4670
|
-
|
4671
|
-
|
4672
|
-
const stakes = result?.map(({
|
4673
|
-
coldkey,
|
4674
|
-
hotkey,
|
4675
|
-
netuid,
|
4676
|
-
stake
|
4677
|
-
}) => {
|
4678
|
-
return {
|
4679
|
-
address: coldkey,
|
4051
|
+
const subtensorQueries = rxjs.from(addresses).pipe(
|
4052
|
+
// mergeMap lets us run N concurrent queries, where N is the value of `concurrency`
|
4053
|
+
rxjs.mergeMap(async address => {
|
4054
|
+
const queryMethods = [async () => {
|
4055
|
+
if (chain.isTestnet) return [];
|
4056
|
+
const params = [address];
|
4057
|
+
const result = await scaleApi.getRuntimeCallValue("StakeInfoRuntimeApi", "get_stake_info_for_coldkey", params);
|
4058
|
+
if (!Array.isArray(result)) return [];
|
4059
|
+
const uniqueNetuids = Array.from(new Set(result.map(item => Number(item.netuid)).filter(netuid => netuid !== SUBTENSOR_ROOT_NETUID)));
|
4060
|
+
await fetchDynamicInfoForNetuids(uniqueNetuids);
|
4061
|
+
const stakes = result?.map(({
|
4062
|
+
coldkey,
|
4680
4063
|
hotkey,
|
4681
|
-
netuid
|
4682
|
-
stake
|
4683
|
-
|
4684
|
-
|
4685
|
-
|
4686
|
-
|
4687
|
-
|
4688
|
-
|
4689
|
-
|
4690
|
-
|
4691
|
-
|
4692
|
-
|
4693
|
-
|
4694
|
-
return
|
4695
|
-
}
|
4696
|
-
|
4697
|
-
|
4064
|
+
netuid,
|
4065
|
+
stake
|
4066
|
+
}) => {
|
4067
|
+
return {
|
4068
|
+
address: coldkey,
|
4069
|
+
hotkey,
|
4070
|
+
netuid: Number(netuid),
|
4071
|
+
stake: BigInt(stake),
|
4072
|
+
dynamicInfo: dynamicInfoCache.get(Number(netuid))
|
4073
|
+
};
|
4074
|
+
}).filter(({
|
4075
|
+
stake
|
4076
|
+
}) => stake >= SUBTENSOR_MIN_STAKE_AMOUNT_PLANK);
|
4077
|
+
return stakes;
|
4078
|
+
}];
|
4079
|
+
const errors = [];
|
4080
|
+
for (const queryMethod of queryMethods) {
|
4081
|
+
try {
|
4082
|
+
// try each query method
|
4083
|
+
return await queryMethod();
|
4084
|
+
} catch (cause) {
|
4085
|
+
// if it fails, keep track of the error and try the next one
|
4086
|
+
errors.push(cause);
|
4087
|
+
}
|
4698
4088
|
}
|
4699
|
-
}
|
4700
4089
|
|
4701
|
-
|
4702
|
-
|
4703
|
-
|
4704
|
-
|
4705
|
-
|
4706
|
-
|
4707
|
-
|
4708
|
-
|
4709
|
-
|
4710
|
-
|
4711
|
-
address,
|
4712
|
-
hotkey,
|
4713
|
-
stake,
|
4714
|
-
netuid,
|
4715
|
-
dynamicInfo
|
4716
|
-
}) => {
|
4717
|
-
const {
|
4718
|
-
token_symbol,
|
4719
|
-
subnet_name,
|
4720
|
-
subnet_identity
|
4721
|
-
} = dynamicInfo ?? {};
|
4722
|
-
const tokenSymbol = new TextDecoder().decode(Uint8Array.from(token_symbol ?? []));
|
4723
|
-
const subnetName = new TextDecoder().decode(Uint8Array.from(subnet_name ?? []));
|
4724
|
-
|
4725
|
-
/** Map from Record<string, Binary> to Record<string, string> */
|
4726
|
-
const binaryToText = input => Object.entries(input).reduce((acc, [key, value]) => {
|
4727
|
-
acc[key] = value.asText();
|
4728
|
-
return acc;
|
4729
|
-
}, {});
|
4730
|
-
const subnetIdentity = subnet_identity ? binaryToText(subnet_identity) : undefined;
|
4731
|
-
|
4732
|
-
// Add 1n balance if failed to fetch dynamic info, so the position is not ignored by Balance lib and is displayed in the UI.
|
4733
|
-
const alphaStakedInTao = dynamicInfo ? calculateTaoFromDynamicInfo({
|
4734
|
-
dynamicInfo,
|
4735
|
-
alphaStaked: stake
|
4736
|
-
}) : 1n;
|
4737
|
-
const alphaToTaoRate = calculateTaoFromDynamicInfo({
|
4738
|
-
dynamicInfo: dynamicInfo ?? null,
|
4739
|
-
alphaStaked: ONE_ALPHA_TOKEN
|
4740
|
-
}).toString();
|
4741
|
-
const stakeByNetuid = Number(netuid) === SUBTENSOR_ROOT_NETUID ? stake : alphaStakedInTao;
|
4742
|
-
return {
|
4743
|
-
source: "substrate-native",
|
4744
|
-
status: "live",
|
4090
|
+
// if we get to here, that means that all query methods failed
|
4091
|
+
// let's throw the errors back to the native balance module
|
4092
|
+
throw new Error([`Failed to fetch ${tokenId} subtensor staked balance for ${address}:`, ...errors.map(error => String(error))].join("\n\t"));
|
4093
|
+
}, concurrency),
|
4094
|
+
// instead of emitting each balance as it's fetched, toArray waits for them all to fetch and then it collects them into an array
|
4095
|
+
rxjs.toArray(),
|
4096
|
+
// this mergeMap flattens our Array<Array<Stakes>> into just an Array<Stakes>
|
4097
|
+
rxjs.mergeMap(stakes => stakes),
|
4098
|
+
// convert our Array<Stakes> into Array<Balances>, which we can then return to the native balance module
|
4099
|
+
rxjs.map(stakes => stakes.map(({
|
4745
4100
|
address,
|
4746
|
-
|
4747
|
-
|
4748
|
-
|
4749
|
-
|
4750
|
-
|
4751
|
-
|
4752
|
-
|
4753
|
-
|
4754
|
-
|
4755
|
-
|
4756
|
-
|
4757
|
-
|
4758
|
-
|
4759
|
-
|
4760
|
-
|
4761
|
-
|
4762
|
-
|
4763
|
-
|
4764
|
-
|
4765
|
-
|
4766
|
-
|
4767
|
-
|
4101
|
+
hotkey,
|
4102
|
+
stake,
|
4103
|
+
netuid,
|
4104
|
+
dynamicInfo
|
4105
|
+
}) => {
|
4106
|
+
const {
|
4107
|
+
token_symbol,
|
4108
|
+
subnet_name,
|
4109
|
+
subnet_identity
|
4110
|
+
} = dynamicInfo ?? {};
|
4111
|
+
const tokenSymbol = new TextDecoder().decode(Uint8Array.from(token_symbol ?? []));
|
4112
|
+
const subnetName = new TextDecoder().decode(Uint8Array.from(subnet_name ?? []));
|
4113
|
+
|
4114
|
+
/** Map from Record<string, Binary> to Record<string, string> */
|
4115
|
+
const binaryToText = input => Object.entries(input).reduce((acc, [key, value]) => {
|
4116
|
+
acc[key] = value.asText();
|
4117
|
+
return acc;
|
4118
|
+
}, {});
|
4119
|
+
const subnetIdentity = subnet_identity ? binaryToText(subnet_identity) : undefined;
|
4120
|
+
|
4121
|
+
// Add 1n balance if failed to fetch dynamic info, so the position is not ignored by Balance lib and is displayed in the UI.
|
4122
|
+
const alphaStakedInTao = dynamicInfo ? calculateTaoFromDynamicInfo({
|
4123
|
+
dynamicInfo,
|
4124
|
+
alphaStaked: stake
|
4125
|
+
}) : 1n;
|
4126
|
+
const alphaToTaoRate = calculateTaoFromDynamicInfo({
|
4127
|
+
dynamicInfo: dynamicInfo ?? null,
|
4128
|
+
alphaStaked: ONE_ALPHA_TOKEN
|
4129
|
+
}).toString();
|
4130
|
+
const stakeByNetuid = Number(netuid) === SUBTENSOR_ROOT_NETUID ? stake : alphaStakedInTao;
|
4131
|
+
return {
|
4132
|
+
source: "substrate-native",
|
4133
|
+
status: "live",
|
4134
|
+
address,
|
4135
|
+
networkId: chainId,
|
4136
|
+
tokenId,
|
4137
|
+
values: [{
|
4138
|
+
source: "subtensor-staking",
|
4139
|
+
type: "subtensor",
|
4140
|
+
label: "subtensor-staking",
|
4141
|
+
amount: stakeByNetuid.toString(),
|
4142
|
+
meta: {
|
4143
|
+
type: "subtensor-staking",
|
4144
|
+
hotkey,
|
4145
|
+
netuid,
|
4146
|
+
amountStaked: stake.toString(),
|
4147
|
+
alphaToTaoRate,
|
4148
|
+
dynamicInfo: {
|
4149
|
+
tokenSymbol,
|
4150
|
+
subnetName,
|
4151
|
+
subnetIdentity: {
|
4152
|
+
...subnetIdentity,
|
4153
|
+
subnetName: subnetIdentity?.subnet_name || subnetName
|
4154
|
+
}
|
4768
4155
|
}
|
4769
4156
|
}
|
4770
|
-
}
|
4771
|
-
}
|
4772
|
-
};
|
4773
|
-
|
4157
|
+
}]
|
4158
|
+
};
|
4159
|
+
})));
|
4160
|
+
|
4161
|
+
// This observable will run the subtensorQueries on a 30s (30_000ms) interval.
|
4162
|
+
// However, if the last run has not yet completed (e.g. its been 30s but we're still fetching some balances),
|
4163
|
+
// then exhaustMap will wait until the next interval (so T: 60s, T: 90s, T: 120s, etc) before re-executing the subtensorQueries.
|
4164
|
+
const subtensorQueriesInterval = rxjs.interval(30_000).pipe(rxjs.startWith(0),
|
4165
|
+
// start immediately
|
4166
|
+
rxjs.exhaustMap(() => {
|
4167
|
+
return subtensorQueries;
|
4168
|
+
}));
|
4774
4169
|
|
4775
|
-
|
4776
|
-
|
4777
|
-
|
4778
|
-
|
4779
|
-
|
4780
|
-
rxjs.exhaustMap(() => {
|
4781
|
-
return subtensorQueries;
|
4782
|
-
}));
|
4170
|
+
// subscribe to the balances
|
4171
|
+
const subscription = subtensorQueriesInterval.subscribe({
|
4172
|
+
next: balances => callback(null, balances),
|
4173
|
+
error: error => callback(error)
|
4174
|
+
});
|
4783
4175
|
|
4784
|
-
|
4785
|
-
|
4786
|
-
|
4787
|
-
|
4176
|
+
// use the abortController to tear the subscription down when we don't need it anymore
|
4177
|
+
abortController.signal.addEventListener("abort", () => {
|
4178
|
+
subscription.unsubscribe();
|
4179
|
+
});
|
4180
|
+
}
|
4181
|
+
return () => abortController.abort();
|
4182
|
+
} catch (err) {
|
4183
|
+
if (!util.isAbortError(err)) log.error("Error subscribing to subtensor staking", {
|
4184
|
+
err
|
4788
4185
|
});
|
4789
|
-
|
4790
|
-
// use the abortController to tear the subscription down when we don't need it anymore
|
4791
|
-
abortController.signal.onabort = () => subscription.unsubscribe();
|
4186
|
+
return () => {};
|
4792
4187
|
}
|
4793
|
-
return () => abortController.abort();
|
4794
4188
|
}
|
4795
4189
|
|
4796
4190
|
const getOtherType = input => `other-${input}`;
|
@@ -4846,18 +4240,14 @@ const filterBaseLocks = locks => {
|
|
4846
4240
|
};
|
4847
4241
|
|
4848
4242
|
// TODO: Make these titles translatable
|
4849
|
-
const getLockTitle = (lock,
|
4243
|
+
const getLockTitle = (lock,
|
4244
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
4245
|
+
{
|
4850
4246
|
balance
|
4851
4247
|
} = {}) => {
|
4852
4248
|
if (!lock.label) return lock.label;
|
4853
4249
|
if (lock.label === "democracy") return "Governance";
|
4854
|
-
if (lock.label === "crowdloan")
|
4855
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
4856
|
-
const paraId = lock.meta?.paraId;
|
4857
|
-
if (!paraId) return "Crowdloan";
|
4858
|
-
const name = balance?.chain?.parathreads?.find(parathread => parathread?.paraId === paraId)?.name;
|
4859
|
-
return `${name ? name : `Parachain ${paraId}`} Crowdloan`;
|
4860
|
-
}
|
4250
|
+
if (lock.label === "crowdloan") return "Crowdloan";
|
4861
4251
|
if (lock.label === "nompools-staking") return "Pooled Staking";
|
4862
4252
|
if (lock.label === "nompools-unbonding") return "Pooled Staking";
|
4863
4253
|
if (lock.label === "subtensor-staking") return "Root Staking";
|
@@ -4869,7 +4259,6 @@ const getLockTitle = (lock, {
|
|
4869
4259
|
};
|
4870
4260
|
|
4871
4261
|
const moduleType$2 = "substrate-native";
|
4872
|
-
const subNativeTokenId = chainId => `${chainId}-substrate-native`.toLowerCase().replace(/ /g, "-");
|
4873
4262
|
|
4874
4263
|
/**
|
4875
4264
|
* Function to merge two 'sub sources' of the same balance together, or
|
@@ -4947,7 +4336,7 @@ async function buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas,
|
|
4947
4336
|
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
4948
4337
|
return outerResult;
|
4949
4338
|
}
|
4950
|
-
const chainId = token.
|
4339
|
+
const chainId = token.networkId;
|
4951
4340
|
if (!chainId) {
|
4952
4341
|
log.warn(`Token ${tokenId} has no chain`);
|
4953
4342
|
return outerResult;
|
@@ -4957,10 +4346,10 @@ async function buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas,
|
|
4957
4346
|
log.warn(`Chain ${chainId} for token ${tokenId} not found`);
|
4958
4347
|
return outerResult;
|
4959
4348
|
}
|
4960
|
-
const
|
4349
|
+
const miniMetadata = miniMetadatas.get(chainId);
|
4961
4350
|
const {
|
4962
4351
|
useLegacyTransferableCalculation
|
4963
|
-
} =
|
4352
|
+
} = miniMetadata?.extra ?? {};
|
4964
4353
|
addresses.flat().forEach(address => {
|
4965
4354
|
const queryKey = `${tokenId}-${address}`;
|
4966
4355
|
// We share this balanceJson between the base and the lock query for this address
|
@@ -4968,10 +4357,7 @@ async function buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas,
|
|
4968
4357
|
source: "substrate-native",
|
4969
4358
|
status: "live",
|
4970
4359
|
address,
|
4971
|
-
|
4972
|
-
subChainId: chainId
|
4973
|
-
},
|
4974
|
-
chainId,
|
4360
|
+
networkId: chainId,
|
4975
4361
|
tokenId,
|
4976
4362
|
values: []
|
4977
4363
|
};
|
@@ -5243,75 +4629,19 @@ const updateStakingLocksUsingUnbondingLocks = values => {
|
|
5243
4629
|
return [...otherValues, ...stakingLocks];
|
5244
4630
|
};
|
5245
4631
|
|
5246
|
-
const detectMiniMetadataChanges = () => {
|
5247
|
-
let previousMap = null;
|
5248
|
-
return rxjs.pipe(rxjs.map(currMap => {
|
5249
|
-
if (!currMap) return null;
|
5250
|
-
const changes = new Set();
|
5251
|
-
if (previousMap) {
|
5252
|
-
// Check for added or changed keys/values
|
5253
|
-
for (const [key, value] of currMap) {
|
5254
|
-
if (!previousMap.has(key) || !isEqual__default.default(previousMap.get(key), value)) {
|
5255
|
-
changes.add(value.chainId);
|
5256
|
-
}
|
5257
|
-
}
|
5258
|
-
|
5259
|
-
// Check for removed keys
|
5260
|
-
for (const [key, value] of previousMap) {
|
5261
|
-
if (!currMap.has(key)) {
|
5262
|
-
changes.add(value.chainId);
|
5263
|
-
}
|
5264
|
-
}
|
5265
|
-
}
|
5266
|
-
previousMap = currMap;
|
5267
|
-
return changes.size > 0 ? changes : null;
|
5268
|
-
}),
|
5269
|
-
// Filter out null emissions (no changes)
|
5270
|
-
rxjs.filter(changes => changes !== null));
|
5271
|
-
};
|
5272
|
-
|
5273
|
-
// NOTE: `liveQuery` is not initialized until commonMetadataObservable is subscribed to.
|
5274
|
-
const commonMetadataObservable = rxjs.from(dexie.liveQuery(() => db.miniMetadatas.where("source").equals("substrate-native").toArray())).pipe(rxjs.map(items => new Map(items.map(item => [item.id, item]))),
|
5275
|
-
// `refCount: true` will unsubscribe from the DB when commonMetadataObservable has no more subscribers
|
5276
|
-
rxjs.shareReplay({
|
5277
|
-
bufferSize: 1,
|
5278
|
-
refCount: true
|
5279
|
-
}));
|
5280
4632
|
class QueryCache {
|
4633
|
+
#chaindataProvider;
|
4634
|
+
#chainConnector;
|
4635
|
+
miniMetadatas = new Map();
|
5281
4636
|
balanceQueryCache = new Map();
|
5282
|
-
|
5283
|
-
constructor(chaindataProvider) {
|
4637
|
+
constructor(chaindataProvider, chainConnector) {
|
5284
4638
|
this.chaindataProvider = chaindataProvider;
|
5285
|
-
|
5286
|
-
|
5287
|
-
if (this.metadataSub) return;
|
5288
|
-
this.metadataSub = commonMetadataObservable.pipe(util.firstThenDebounce(500), detectMiniMetadataChanges(), rxjs.combineLatestWith(this.chaindataProvider.tokensObservable), rxjs.distinctUntilChanged()).subscribe(([miniMetadataChanges, tokens]) => {
|
5289
|
-
// invalidate cache entries for any chains with new metadata
|
5290
|
-
const tokensByChainId = tokens.filter(token => token.type === "substrate-native").reduce((result, token) => {
|
5291
|
-
if (!token.chain?.id) return result;
|
5292
|
-
result[token.chain.id] ? result[token.chain.id].push(token) : result[token.chain.id] = [token];
|
5293
|
-
return result;
|
5294
|
-
}, {});
|
5295
|
-
miniMetadataChanges.forEach(chainId => {
|
5296
|
-
const chainTokens = tokensByChainId[chainId];
|
5297
|
-
if (!chainTokens) return;
|
5298
|
-
chainTokens.forEach(token => {
|
5299
|
-
const tokenId = token.id;
|
5300
|
-
const cacheKeys = this.balanceQueryCache.keys();
|
5301
|
-
for (const key of cacheKeys) {
|
5302
|
-
if (key.startsWith(`${tokenId}-`)) this.balanceQueryCache.delete(key);
|
5303
|
-
}
|
5304
|
-
});
|
5305
|
-
});
|
5306
|
-
});
|
5307
|
-
}
|
5308
|
-
destroy() {
|
5309
|
-
this.metadataSub?.unsubscribe();
|
4639
|
+
this.#chaindataProvider = chaindataProvider;
|
4640
|
+
this.#chainConnector = chainConnector;
|
5310
4641
|
}
|
5311
4642
|
async getQueries(addressesByToken) {
|
5312
|
-
this.
|
5313
|
-
const
|
5314
|
-
const tokens = await this.chaindataProvider.tokensById();
|
4643
|
+
const chains = await this.chaindataProvider.getNetworksMapById("polkadot");
|
4644
|
+
const tokens = await this.chaindataProvider.getTokensMapById();
|
5315
4645
|
const queryResults = Object.entries(addressesByToken).reduce((result, [tokenId, addresses]) => {
|
5316
4646
|
addresses.forEach(address => {
|
5317
4647
|
const key = `${tokenId}-${address}`;
|
@@ -5327,15 +4657,19 @@ class QueryCache {
|
|
5327
4657
|
existing: [],
|
5328
4658
|
newAddressesByToken: {}
|
5329
4659
|
});
|
4660
|
+
const byNetwork = getAddresssesByTokenByNetwork(addressesByToken);
|
4661
|
+
for (const networkId of lodash.keys(byNetwork)) {
|
4662
|
+
if (this.miniMetadatas.has(networkId)) continue;
|
4663
|
+
const miniMetadata = await getMiniMetadata(this.#chaindataProvider, this.#chainConnector, networkId, "substrate-native");
|
4664
|
+
this.miniMetadatas.set(networkId, miniMetadata);
|
4665
|
+
}
|
5330
4666
|
|
5331
4667
|
// build queries for token/address pairs which have not been queried before
|
5332
|
-
const
|
5333
|
-
const uniqueChainIds = getUniqueChainIds(queryResults.newAddressesByToken, tokens);
|
4668
|
+
const uniqueChainIds = lodash.keys(byNetwork); // getUniqueChainIds(queryResults.newAddressesByToken, tokens)
|
5334
4669
|
const chainStorageCoders = buildStorageCoders({
|
5335
4670
|
chainIds: uniqueChainIds,
|
5336
4671
|
chains,
|
5337
|
-
miniMetadatas,
|
5338
|
-
moduleType: "substrate-native",
|
4672
|
+
miniMetadatas: this.miniMetadatas,
|
5339
4673
|
coders: {
|
5340
4674
|
base: ["System", "Account"],
|
5341
4675
|
stakingLedger: ["Staking", "Ledger"],
|
@@ -5345,7 +4679,7 @@ class QueryCache {
|
|
5345
4679
|
freezes: ["Balances", "Freezes"]
|
5346
4680
|
}
|
5347
4681
|
});
|
5348
|
-
const queries = await buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas, queryResults.newAddressesByToken);
|
4682
|
+
const queries = await buildQueries$1(chains, tokens, chainStorageCoders, this.miniMetadatas, queryResults.newAddressesByToken);
|
5349
4683
|
// now update the cache
|
5350
4684
|
Object.entries(queries).forEach(([key, query]) => {
|
5351
4685
|
this.balanceQueryCache.set(key, query);
|
@@ -5354,14 +4688,11 @@ class QueryCache {
|
|
5354
4688
|
}
|
5355
4689
|
}
|
5356
4690
|
|
5357
|
-
const
|
5358
|
-
const
|
5359
|
-
const sortChains = (a, b) => {
|
4691
|
+
const IMPORTANT_TOKENS = [chaindataProvider.subNativeTokenId("polkadot"), chaindataProvider.subNativeTokenId("kusama"), chaindataProvider.subNativeTokenId("polkadot-asset-hub"), chaindataProvider.subNativeTokenId("kusama-asset-hub"), chaindataProvider.subNativeTokenId("bittensor")];
|
4692
|
+
const sortChainsNativeTokensByPriority = (a, b) => {
|
5360
4693
|
// polkadot and kusama should be checked first
|
5361
|
-
if (
|
5362
|
-
if (
|
5363
|
-
if (PUBLIC_GOODS_TOKENS.includes(a)) return -1;
|
5364
|
-
if (PUBLIC_GOODS_TOKENS.includes(b)) return 1;
|
4694
|
+
if (IMPORTANT_TOKENS.includes(a)) return -1;
|
4695
|
+
if (IMPORTANT_TOKENS.includes(b)) return 1;
|
5365
4696
|
return 0;
|
5366
4697
|
};
|
5367
4698
|
|
@@ -5377,10 +4708,30 @@ class SubNativeBalanceError extends Error {
|
|
5377
4708
|
}
|
5378
4709
|
}
|
5379
4710
|
|
5380
|
-
const
|
5381
|
-
|
4711
|
+
const DotNetworkPropertiesSimple = z__default.default.object({
|
4712
|
+
tokenDecimals: z__default.default.number().optional().default(0),
|
4713
|
+
tokenSymbol: z__default.default.string().optional().default("Unit")
|
4714
|
+
});
|
4715
|
+
const DotNetworkPropertiesArray = z__default.default.object({
|
4716
|
+
tokenDecimals: z__default.default.array(z__default.default.number()).nonempty(),
|
4717
|
+
tokenSymbol: z__default.default.array(z__default.default.string()).nonempty()
|
4718
|
+
});
|
4719
|
+
const DotNetworkPropertiesSchema = z__default.default.union([DotNetworkPropertiesSimple, DotNetworkPropertiesArray]).transform(val => ({
|
4720
|
+
tokenDecimals: Array.isArray(val.tokenDecimals) ? val.tokenDecimals[0] : val.tokenDecimals,
|
4721
|
+
tokenSymbol: Array.isArray(val.tokenSymbol) ? val.tokenSymbol[0] : val.tokenSymbol
|
4722
|
+
}));
|
4723
|
+
const getChainProperties = async (chainConnector, networkId) => {
|
4724
|
+
const properties = await chainConnector.send(networkId, "system_properties", [], true);
|
4725
|
+
return DotNetworkPropertiesSchema.parse(properties);
|
4726
|
+
};
|
4727
|
+
|
5382
4728
|
const POLLING_WINDOW_SIZE = 20;
|
5383
4729
|
const MAX_SUBSCRIPTION_SIZE = 40;
|
4730
|
+
const EMPTY_CHAIN_META = {
|
4731
|
+
miniMetadata: null,
|
4732
|
+
extra: null
|
4733
|
+
};
|
4734
|
+
const SubNativeTokenConfigSchema = TokenConfigBaseSchema;
|
5384
4735
|
const SubNativeModule = hydrate => {
|
5385
4736
|
const {
|
5386
4737
|
chainConnectors,
|
@@ -5388,33 +4739,198 @@ const SubNativeModule = hydrate => {
|
|
5388
4739
|
} = hydrate;
|
5389
4740
|
const chainConnector$1 = chainConnectors.substrate;
|
5390
4741
|
util$1.assert(chainConnector$1, "This module requires a substrate chain connector");
|
5391
|
-
const queryCache = new QueryCache(chaindataProvider$1);
|
4742
|
+
const queryCache = new QueryCache(chaindataProvider$1, chainConnector$1);
|
5392
4743
|
const getModuleTokens = async () => {
|
5393
|
-
return await chaindataProvider$1.
|
4744
|
+
return await chaindataProvider$1.getTokensMapById(moduleType$2);
|
5394
4745
|
};
|
5395
|
-
return {
|
5396
|
-
...DefaultBalanceModule(moduleType$2),
|
5397
|
-
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc, systemProperties) {
|
5398
|
-
const isTestnet = (await chaindataProvider$1.chainById(chainId))?.isTestnet || false;
|
5399
|
-
if (moduleConfig?.disable === true || metadataRpc === undefined) return {
|
5400
|
-
isTestnet
|
5401
|
-
};
|
5402
4746
|
|
5403
|
-
|
5404
|
-
|
5405
|
-
|
4747
|
+
// subscribeBalances was split by network to prevent all subs to wait for all minimetadatas to be ready.
|
4748
|
+
// however the multichain logic in there is so deep in the function below that i had to keep it as-is, and call it by per-network chunks
|
4749
|
+
// TODO refactor this be actually network specific
|
4750
|
+
// Note: had to extract this function from the result object or this.subscribeBalances wouldn't be typed correctly
|
4751
|
+
const subscribeChainBalances = (chainId, opts, callback, signal) => {
|
4752
|
+
const {
|
4753
|
+
addressesByToken,
|
4754
|
+
initialBalances
|
4755
|
+
} = opts;
|
4756
|
+
// full record of balances for this module
|
4757
|
+
const subNativeBalances = new rxjs.BehaviorSubject(Object.fromEntries(initialBalances?.map(b => [getBalanceId(b), b]) ?? []));
|
4758
|
+
// tokens which have a known positive balance
|
4759
|
+
const positiveBalanceTokens = subNativeBalances.pipe(rxjs.map(balances => Array.from(new Set(Object.values(balances).map(b => b.tokenId)))), rxjs.share());
|
4760
|
+
|
4761
|
+
// tokens that will be subscribed to, simply a slice of the positive balance tokens of size MAX_SUBSCRIPTION_SIZE
|
4762
|
+
const subscriptionTokens = positiveBalanceTokens.pipe(rxjs.map(tokens => tokens.sort(sortChainsNativeTokensByPriority).slice(0, MAX_SUBSCRIPTION_SIZE)));
|
4763
|
+
|
4764
|
+
// an initialised balance is one where we have received a response for any type of 'subsource',
|
4765
|
+
// until then they are initialising. We only need to maintain one map of tokens to addresses for this
|
4766
|
+
const initialisingBalances = Object.entries(addressesByToken).reduce((acc, [tokenId, addresses]) => {
|
4767
|
+
acc.set(tokenId, new Set(addresses));
|
4768
|
+
return acc;
|
4769
|
+
}, new Map());
|
4770
|
+
|
4771
|
+
// after thirty seconds, we need to kill the initialising balances
|
4772
|
+
const initBalancesTimeout = setTimeout(() => {
|
4773
|
+
initialisingBalances.clear();
|
4774
|
+
// manually call the callback to ensure the caller gets the correct status
|
4775
|
+
callback(null, {
|
4776
|
+
status: "live",
|
4777
|
+
data: Object.values(subNativeBalances.getValue())
|
4778
|
+
});
|
4779
|
+
}, 30_000);
|
4780
|
+
const _callbackSub = subNativeBalances.pipe(rxjs.debounceTime(100)).subscribe({
|
4781
|
+
next: balances => {
|
4782
|
+
callback(null, {
|
4783
|
+
status: initialisingBalances.size > 0 ? "initialising" : "live",
|
4784
|
+
data: Object.values(balances)
|
4785
|
+
});
|
4786
|
+
},
|
4787
|
+
error: error => callback(error),
|
4788
|
+
complete: () => {
|
4789
|
+
initialisingBalances.clear();
|
4790
|
+
clearTimeout(initBalancesTimeout);
|
4791
|
+
}
|
4792
|
+
});
|
4793
|
+
const unsubDeferred = util.Deferred();
|
4794
|
+
// we return this to the caller so that they can let us know when they're no longer interested in this subscription
|
4795
|
+
const callerUnsubscribe = () => {
|
4796
|
+
subNativeBalances.complete();
|
4797
|
+
_callbackSub.unsubscribe();
|
4798
|
+
return unsubDeferred.reject(new Error(`Caller unsubscribed`));
|
4799
|
+
};
|
4800
|
+
// we queue up our work to clean up our subscription when this promise rejects
|
4801
|
+
const callerUnsubscribed = unsubDeferred.promise;
|
4802
|
+
|
4803
|
+
// The update handler is to allow us to merge balances with the same id, and manage initialising and positive balances state for each
|
4804
|
+
// balance type and network
|
4805
|
+
const handleUpdateForSource = source => (error, result) => {
|
4806
|
+
if (result) {
|
4807
|
+
const currentBalances = subNativeBalances.getValue();
|
4808
|
+
|
4809
|
+
// first merge any balances with the same id within the result
|
4810
|
+
const accumulatedUpdates = result.filter(b => b.values.length > 0).reduce((acc, b) => {
|
4811
|
+
const bId = getBalanceId(b);
|
4812
|
+
acc[bId] = mergeBalances(acc[bId], b, source, false);
|
4813
|
+
return acc;
|
4814
|
+
}, {});
|
4815
|
+
|
4816
|
+
// then merge these with the current balances
|
4817
|
+
const mergedBalances = {};
|
4818
|
+
Object.entries(accumulatedUpdates).forEach(([bId, b]) => {
|
4819
|
+
// merge the values from the new balance into the existing balance, if there is one
|
4820
|
+
mergedBalances[bId] = mergeBalances(currentBalances[bId], b, source, true);
|
4821
|
+
|
4822
|
+
// update initialisingBalances to remove balances which have been updated
|
4823
|
+
const intialisingForToken = initialisingBalances.get(b.tokenId);
|
4824
|
+
if (intialisingForToken) {
|
4825
|
+
intialisingForToken.delete(b.address);
|
4826
|
+
if (intialisingForToken.size === 0) initialisingBalances.delete(b.tokenId);else initialisingBalances.set(b.tokenId, intialisingForToken);
|
4827
|
+
}
|
4828
|
+
});
|
4829
|
+
subNativeBalances.next({
|
4830
|
+
...currentBalances,
|
4831
|
+
...mergedBalances
|
4832
|
+
});
|
4833
|
+
}
|
4834
|
+
if (error) {
|
4835
|
+
if (error instanceof SubNativeBalanceError) {
|
4836
|
+
// this type of error doesn't need to be handled by the caller
|
4837
|
+
initialisingBalances.delete(error.tokenId);
|
4838
|
+
} else return callback(error);
|
4839
|
+
}
|
4840
|
+
};
|
5406
4841
|
|
5407
|
-
|
5408
|
-
|
5409
|
-
|
5410
|
-
|
5411
|
-
|
5412
|
-
|
4842
|
+
// subscribe to addresses and tokens for which we have a known positive balance
|
4843
|
+
const positiveSub = subscriptionTokens.pipe(rxjs.debounceTime(1000), rxjs.takeUntil(callerUnsubscribed), rxjs.map(tokenIds => tokenIds.reduce((acc, tokenId) => {
|
4844
|
+
acc[tokenId] = addressesByToken[tokenId];
|
4845
|
+
return acc;
|
4846
|
+
}, {})), rxjs.distinctUntilChanged(isEqual__default.default), rxjs.switchMap(newAddressesByToken => {
|
4847
|
+
return rxjs.from(queryCache.getQueries(newAddressesByToken)).pipe(rxjs.switchMap(baseQueries => {
|
4848
|
+
return new rxjs.Observable(subscriber => {
|
4849
|
+
if (!chainConnectors.substrate) return;
|
4850
|
+
const unsubSubtensorStaking = subscribeSubtensorStaking(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("subtensor-staking"), signal);
|
4851
|
+
const unsubNompoolStaking = subscribeNompoolStaking(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("nompools-staking"), signal);
|
4852
|
+
const unsubBase = subscribeBase(baseQueries, chainConnectors.substrate, handleUpdateForSource("base"));
|
4853
|
+
subscriber.add(async () => (await unsubSubtensorStaking)());
|
4854
|
+
subscriber.add(async () => (await unsubNompoolStaking)());
|
4855
|
+
subscriber.add(async () => (await unsubBase)());
|
4856
|
+
});
|
4857
|
+
}));
|
4858
|
+
})).subscribe();
|
4859
|
+
|
4860
|
+
// for chains where we don't have a known positive balance, poll rather than subscribe
|
4861
|
+
const poll = async (addressesByToken = {}) => {
|
4862
|
+
const handleUpdate = handleUpdateForSource("base");
|
4863
|
+
try {
|
4864
|
+
const balances = await fetchBalances(addressesByToken);
|
4865
|
+
handleUpdate(null, Object.values(balances.toJSON()));
|
4866
|
+
} catch (error) {
|
4867
|
+
if (error instanceof chainConnector.ChainConnectionError) {
|
4868
|
+
// coerce ChainConnection errors into SubNativeBalance errors
|
4869
|
+
const errorChainId = error.chainId;
|
4870
|
+
Object.entries(await getModuleTokens()).filter(([, token]) => token.networkId === errorChainId).forEach(([tokenId]) => {
|
4871
|
+
const wrappedError = new SubNativeBalanceError(tokenId, error.message);
|
4872
|
+
handleUpdate(wrappedError);
|
4873
|
+
});
|
4874
|
+
} else {
|
4875
|
+
log.error("unknown substrate native balance error", error);
|
4876
|
+
handleUpdate(error);
|
4877
|
+
}
|
4878
|
+
}
|
4879
|
+
};
|
4880
|
+
// do one poll to get things started
|
4881
|
+
const currentBalances = subNativeBalances.getValue();
|
4882
|
+
const currentTokens = new Set(Object.values(currentBalances).map(b => b.tokenId));
|
4883
|
+
const nonCurrentTokens = Object.keys(addressesByToken).filter(tokenId => !currentTokens.has(tokenId)).sort(sortChainsNativeTokensByPriority);
|
4884
|
+
|
4885
|
+
// break nonCurrentTokens into chunks of POLLING_WINDOW_SIZE
|
4886
|
+
const pool = new PQueue__default.default({
|
4887
|
+
concurrency: POLLING_WINDOW_SIZE
|
4888
|
+
});
|
4889
|
+
nonCurrentTokens.forEach(nonCurrentTokenId => pool.add(() => poll({
|
4890
|
+
[nonCurrentTokenId]: addressesByToken[nonCurrentTokenId]
|
4891
|
+
}), {
|
4892
|
+
signal
|
4893
|
+
}));
|
4894
|
+
|
4895
|
+
// now poll every 30s on chains which are not subscriptionTokens
|
4896
|
+
// we chunk this observable into batches of positive token ids, to prevent eating all the websocket connections
|
4897
|
+
const pollingSub = rxjs.interval(30_000) // emit values every 30 seconds
|
4898
|
+
.pipe(rxjs.takeUntil(callerUnsubscribed), rxjs.withLatestFrom(subscriptionTokens),
|
4899
|
+
// Combine latest value from subscriptionTokens with each interval tick
|
4900
|
+
rxjs.map(([, subscribedTokenIds]) =>
|
4901
|
+
// Filter out tokens that are not subscribed
|
4902
|
+
Object.keys(addressesByToken).filter(tokenId => !subscribedTokenIds.includes(tokenId))), rxjs.exhaustMap(tokenIds => rxjs.from(util$1.arrayChunk(tokenIds, POLLING_WINDOW_SIZE)).pipe(rxjs.concatMap(async tokenChunk => {
|
4903
|
+
// tokenChunk is a chunk of tokenIds with size POLLING_WINDOW_SIZE
|
4904
|
+
const pollingTokenAddresses = Object.fromEntries(tokenChunk.map(tokenId => [tokenId, addressesByToken[tokenId]]));
|
4905
|
+
await pool.add(() => poll(pollingTokenAddresses), {
|
4906
|
+
signal
|
4907
|
+
});
|
4908
|
+
return true;
|
4909
|
+
})))).subscribe();
|
4910
|
+
return () => {
|
4911
|
+
callerUnsubscribe();
|
4912
|
+
positiveSub.unsubscribe();
|
4913
|
+
pollingSub.unsubscribe();
|
4914
|
+
};
|
4915
|
+
};
|
4916
|
+
const fetchBalances = async addressesByToken => {
|
4917
|
+
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
4918
|
+
const queries = await queryCache.getQueries(addressesByToken);
|
4919
|
+
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
4920
|
+
const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
|
4921
|
+
return new Balances(result ?? []);
|
4922
|
+
};
|
4923
|
+
return {
|
4924
|
+
...DefaultBalanceModule(moduleType$2),
|
4925
|
+
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
|
4926
|
+
if (moduleConfig?.disable) return EMPTY_CHAIN_META;
|
4927
|
+
if (!metadataRpc) return EMPTY_CHAIN_META;
|
5413
4928
|
|
5414
4929
|
//
|
5415
4930
|
// process metadata into SCALE encoders/decoders
|
5416
4931
|
//
|
5417
4932
|
const metadataVersion = scale.getMetadataVersion(metadataRpc);
|
4933
|
+
if (metadataVersion < 14) return EMPTY_CHAIN_META;
|
5418
4934
|
const metadata = scale.decAnyMetadata(metadataRpc);
|
5419
4935
|
const unifiedMetadata = scale.unifyMetadata(metadata);
|
5420
4936
|
|
@@ -5434,7 +4950,6 @@ const SubNativeModule = hydrate => {
|
|
5434
4950
|
};
|
5435
4951
|
const existentialDeposit = getConstantValue("Balances", "ExistentialDeposit")?.toString();
|
5436
4952
|
const nominationPoolsPalletId = getConstantValue("NominationPools", "PalletId")?.asText();
|
5437
|
-
const crowdloanPalletId = getConstantValue("Crowdloan", "PalletId")?.asText();
|
5438
4953
|
const hasSubtensorPallet = getConstantValue("SubtensorModule", "KeySwapCost") !== undefined;
|
5439
4954
|
|
5440
4955
|
//
|
@@ -5454,15 +4969,10 @@ const SubNativeModule = hydrate => {
|
|
5454
4969
|
}, {
|
5455
4970
|
pallet: "Staking",
|
5456
4971
|
items: ["Ledger"]
|
5457
|
-
}, {
|
5458
|
-
pallet: "Crowdloan",
|
5459
|
-
items: ["Funds"]
|
5460
|
-
}, {
|
5461
|
-
pallet: "Paras",
|
5462
|
-
items: ["Parachains"]
|
5463
4972
|
},
|
5464
4973
|
// TotalColdkeyStake is used until v.2.2.1, then it is replaced by StakingHotkeys+Stake
|
5465
4974
|
// Need to keep TotalColdkeyStake for a while so chaindata keeps including it in miniMetadatas, so it doesnt break old versions of the wallet
|
4975
|
+
// TODO: Since chaindata v4 this is safe to now delete
|
5466
4976
|
{
|
5467
4977
|
pallet: "SubtensorModule",
|
5468
4978
|
items: ["TotalColdkeyStake", "StakingHotkeys", "Stake"]
|
@@ -5481,47 +4991,40 @@ const SubNativeModule = hydrate => {
|
|
5481
4991
|
}) => name === "Freezes"));
|
5482
4992
|
const useLegacyTransferableCalculation = !hasFreezesItem;
|
5483
4993
|
const chainMeta = {
|
5484
|
-
isTestnet,
|
5485
|
-
useLegacyTransferableCalculation,
|
5486
|
-
symbol,
|
5487
|
-
decimals,
|
5488
|
-
existentialDeposit,
|
5489
|
-
nominationPoolsPalletId,
|
5490
|
-
crowdloanPalletId,
|
5491
|
-
hasSubtensorPallet,
|
5492
4994
|
miniMetadata,
|
5493
|
-
|
4995
|
+
extra: {
|
4996
|
+
useLegacyTransferableCalculation,
|
4997
|
+
existentialDeposit,
|
4998
|
+
nominationPoolsPalletId,
|
4999
|
+
hasSubtensorPallet
|
5000
|
+
}
|
5494
5001
|
};
|
5495
|
-
if (!useLegacyTransferableCalculation) delete chainMeta.useLegacyTransferableCalculation;
|
5496
|
-
if (!hasSubtensorPallet) delete chainMeta.hasSubtensorPallet;
|
5002
|
+
if (!useLegacyTransferableCalculation) delete chainMeta.extra?.useLegacyTransferableCalculation;
|
5003
|
+
if (!hasSubtensorPallet) delete chainMeta.extra?.hasSubtensorPallet;
|
5497
5004
|
return chainMeta;
|
5498
5005
|
},
|
5499
5006
|
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
|
5500
5007
|
if (moduleConfig?.disable === true) return {};
|
5501
5008
|
const {
|
5502
|
-
|
5503
|
-
|
5504
|
-
|
5009
|
+
tokenSymbol: symbol,
|
5010
|
+
tokenDecimals: decimals
|
5011
|
+
} = await getChainProperties(chainConnector$1, chainId);
|
5012
|
+
const {
|
5505
5013
|
existentialDeposit
|
5506
|
-
} = chainMeta;
|
5507
|
-
|
5014
|
+
} = chainMeta.extra ?? {};
|
5015
|
+
if (existentialDeposit === undefined) log.warn("Substrate native module: existentialDeposit is undefined for %s, using 0", chainId);
|
5016
|
+
const id = chaindataProvider.subNativeTokenId(chainId);
|
5508
5017
|
const nativeToken = {
|
5509
5018
|
id,
|
5510
5019
|
type: "substrate-native",
|
5511
|
-
|
5512
|
-
isDefault:
|
5513
|
-
symbol: symbol
|
5514
|
-
|
5515
|
-
|
5020
|
+
platform: "polkadot",
|
5021
|
+
isDefault: true,
|
5022
|
+
symbol: symbol,
|
5023
|
+
name: symbol,
|
5024
|
+
decimals: decimals,
|
5516
5025
|
existentialDeposit: existentialDeposit ?? "0",
|
5517
|
-
|
5518
|
-
id: chainId
|
5519
|
-
}
|
5026
|
+
networkId: chainId
|
5520
5027
|
};
|
5521
|
-
if (moduleConfig?.symbol) nativeToken.symbol = moduleConfig?.symbol;
|
5522
|
-
if (moduleConfig?.coingeckoId) nativeToken.coingeckoId = moduleConfig?.coingeckoId;
|
5523
|
-
if (moduleConfig?.dcentName) nativeToken.dcentName = moduleConfig?.dcentName;
|
5524
|
-
if (moduleConfig?.mirrorOf) nativeToken.mirrorOf = moduleConfig?.mirrorOf;
|
5525
5028
|
return {
|
5526
5029
|
[nativeToken.id]: nativeToken
|
5527
5030
|
};
|
@@ -5531,169 +5034,36 @@ const SubNativeModule = hydrate => {
|
|
5531
5034
|
initialBalances
|
5532
5035
|
}, callback) {
|
5533
5036
|
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
5534
|
-
|
5535
|
-
|
5536
|
-
const
|
5537
|
-
|
5538
|
-
|
5539
|
-
|
5540
|
-
|
5541
|
-
|
5542
|
-
|
5543
|
-
// an initialised balance is one where we have received a response for any type of 'subsource',
|
5544
|
-
// until then they are initialising. We only need to maintain one map of tokens to addresses for this
|
5545
|
-
const initialisingBalances = Object.entries(addressesByToken).reduce((acc, [tokenId, addresses]) => {
|
5546
|
-
acc.set(tokenId, new Set(addresses));
|
5547
|
-
return acc;
|
5548
|
-
}, new Map());
|
5549
|
-
|
5550
|
-
// after thirty seconds, we need to kill the initialising balances
|
5551
|
-
const initBalancesTimeout = setTimeout(() => {
|
5552
|
-
initialisingBalances.clear();
|
5553
|
-
// manually call the callback to ensure the caller gets the correct status
|
5554
|
-
callback(null, {
|
5555
|
-
status: "live",
|
5556
|
-
data: Object.values(subNativeBalances.getValue())
|
5557
|
-
});
|
5558
|
-
}, 30_000);
|
5559
|
-
const _callbackSub = subNativeBalances.pipe(rxjs.debounceTime(100)).subscribe({
|
5560
|
-
next: balances => {
|
5561
|
-
callback(null, {
|
5562
|
-
status: initialisingBalances.size > 0 ? "initialising" : "live",
|
5563
|
-
data: Object.values(balances)
|
5564
|
-
});
|
5565
|
-
},
|
5566
|
-
error: error => callback(error),
|
5567
|
-
complete: () => {
|
5568
|
-
initialisingBalances.clear();
|
5569
|
-
clearTimeout(initBalancesTimeout);
|
5570
|
-
}
|
5571
|
-
});
|
5572
|
-
const unsubDeferred = util.Deferred();
|
5573
|
-
// we return this to the caller so that they can let us know when they're no longer interested in this subscription
|
5574
|
-
const callerUnsubscribe = () => {
|
5575
|
-
subNativeBalances.complete();
|
5576
|
-
_callbackSub.unsubscribe();
|
5577
|
-
return unsubDeferred.reject(new Error(`Caller unsubscribed`));
|
5037
|
+
const addressesByTokenByNetwork = getAddresssesByTokenByNetwork(addressesByToken);
|
5038
|
+
const initialBalancesByNetwork = lodash.groupBy(initialBalances ?? [], "networkId");
|
5039
|
+
const controller = new AbortController();
|
5040
|
+
const safeCallback = (error, result) => {
|
5041
|
+
if (controller.signal.aborted) return;
|
5042
|
+
if (util.isAbortError(error)) return;
|
5043
|
+
// typescript isnt happy with fowarding parameters as is
|
5044
|
+
return error ? callback(error, undefined) : callback(error, result);
|
5578
5045
|
};
|
5579
|
-
|
5580
|
-
const callerUnsubscribed = unsubDeferred.promise;
|
5581
|
-
|
5582
|
-
// The update handler is to allow us to merge balances with the same id, and manage initialising and positive balances state for each
|
5583
|
-
// balance type and network
|
5584
|
-
const handleUpdateForSource = source => (error, result) => {
|
5585
|
-
if (result) {
|
5586
|
-
const currentBalances = subNativeBalances.getValue();
|
5587
|
-
|
5588
|
-
// first merge any balances with the same id within the result
|
5589
|
-
const accumulatedUpdates = result.filter(b => b.values.length > 0).reduce((acc, b) => {
|
5590
|
-
const bId = getBalanceId(b);
|
5591
|
-
acc[bId] = mergeBalances(acc[bId], b, source, false);
|
5592
|
-
return acc;
|
5593
|
-
}, {});
|
5594
|
-
|
5595
|
-
// then merge these with the current balances
|
5596
|
-
const mergedBalances = {};
|
5597
|
-
Object.entries(accumulatedUpdates).forEach(([bId, b]) => {
|
5598
|
-
// merge the values from the new balance into the existing balance, if there is one
|
5599
|
-
mergedBalances[bId] = mergeBalances(currentBalances[bId], b, source, true);
|
5600
|
-
|
5601
|
-
// update initialisingBalances to remove balances which have been updated
|
5602
|
-
const intialisingForToken = initialisingBalances.get(b.tokenId);
|
5603
|
-
if (intialisingForToken) {
|
5604
|
-
intialisingForToken.delete(b.address);
|
5605
|
-
if (intialisingForToken.size === 0) initialisingBalances.delete(b.tokenId);else initialisingBalances.set(b.tokenId, intialisingForToken);
|
5606
|
-
}
|
5607
|
-
});
|
5608
|
-
subNativeBalances.next({
|
5609
|
-
...currentBalances,
|
5610
|
-
...mergedBalances
|
5611
|
-
});
|
5612
|
-
}
|
5613
|
-
if (error) {
|
5614
|
-
if (error instanceof SubNativeBalanceError) {
|
5615
|
-
// this type of error doesn't need to be handled by the caller
|
5616
|
-
initialisingBalances.delete(error.tokenId);
|
5617
|
-
} else return callback(error);
|
5618
|
-
}
|
5619
|
-
};
|
5620
|
-
|
5621
|
-
// subscribe to addresses and tokens for which we have a known positive balance
|
5622
|
-
const positiveSub = subscriptionTokens.pipe(rxjs.debounceTime(1000), rxjs.takeUntil(callerUnsubscribed), rxjs.map(tokenIds => tokenIds.reduce((acc, tokenId) => {
|
5623
|
-
acc[tokenId] = addressesByToken[tokenId];
|
5624
|
-
return acc;
|
5625
|
-
}, {})), rxjs.distinctUntilChanged(isEqual__default.default), rxjs.switchMap(newAddressesByToken => {
|
5626
|
-
return rxjs.from(queryCache.getQueries(newAddressesByToken)).pipe(rxjs.switchMap(baseQueries => {
|
5627
|
-
return new rxjs.Observable(subscriber => {
|
5628
|
-
if (!chainConnectors.substrate) return;
|
5629
|
-
const unsubSubtensorStaking = subscribeSubtensorStaking(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("subtensor-staking"));
|
5630
|
-
const unsubNompoolStaking = subscribeNompoolStaking(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("nompools-staking"));
|
5631
|
-
const unsubCrowdloans = subscribeCrowdloans(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("crowdloan"));
|
5632
|
-
const unsubBase = subscribeBase(baseQueries, chainConnectors.substrate, handleUpdateForSource("base"));
|
5633
|
-
subscriber.add(async () => (await unsubSubtensorStaking)());
|
5634
|
-
subscriber.add(async () => (await unsubNompoolStaking)());
|
5635
|
-
subscriber.add(async () => (await unsubCrowdloans)());
|
5636
|
-
subscriber.add(async () => (await unsubBase)());
|
5637
|
-
});
|
5638
|
-
}));
|
5639
|
-
})).subscribe();
|
5640
|
-
|
5641
|
-
// for chains where we don't have a known positive balance, poll rather than subscribe
|
5642
|
-
const poll = async (addressesByToken = {}) => {
|
5643
|
-
const handleUpdate = handleUpdateForSource("base");
|
5046
|
+
const unsubsribeFns = Promise.all(lodash.keys(addressesByTokenByNetwork).map(async networkId => {
|
5644
5047
|
try {
|
5645
|
-
|
5646
|
-
|
5647
|
-
|
5648
|
-
|
5649
|
-
|
5650
|
-
|
5651
|
-
Object.entries(await getModuleTokens()).filter(([, token]) => token.chain?.id === errorChainId).forEach(([tokenId]) => {
|
5652
|
-
const wrappedError = new SubNativeBalanceError(tokenId, error.message);
|
5653
|
-
handleUpdate(wrappedError);
|
5654
|
-
});
|
5655
|
-
} else {
|
5656
|
-
log.error("unknown substrate native balance error", error);
|
5657
|
-
handleUpdate(error);
|
5658
|
-
}
|
5048
|
+
// this is what we want to be done separately for each network
|
5049
|
+
// this will update the DB so minimetadata will be available when it's used, everywhere else down the tree of subscribeChainBalances
|
5050
|
+
await getMiniMetadata(chaindataProvider$1, chainConnector$1, networkId, moduleType$2, controller.signal);
|
5051
|
+
} catch (err) {
|
5052
|
+
if (!util.isAbortError(err)) log.warn("Failed to get native token miniMetadata for network", networkId, err);
|
5053
|
+
return () => {};
|
5659
5054
|
}
|
5660
|
-
|
5661
|
-
|
5662
|
-
|
5663
|
-
|
5664
|
-
|
5665
|
-
|
5666
|
-
// break nonCurrentTokens into chunks of POLLING_WINDOW_SIZE
|
5667
|
-
await PromisePool__default.default.withConcurrency(POLLING_WINDOW_SIZE).for(nonCurrentTokens).process(async nonCurrentTokenId => await poll({
|
5668
|
-
[nonCurrentTokenId]: addressesByToken[nonCurrentTokenId]
|
5055
|
+
if (controller.signal.aborted) return () => {};
|
5056
|
+
return subscribeChainBalances(networkId, {
|
5057
|
+
addressesByToken: addressesByTokenByNetwork[networkId] ?? {},
|
5058
|
+
initialBalances: initialBalancesByNetwork[networkId] ?? []
|
5059
|
+
}, safeCallback, controller.signal);
|
5669
5060
|
}));
|
5670
|
-
|
5671
|
-
// now poll every 30s on chains which are not subscriptionTokens
|
5672
|
-
// we chunk this observable into batches of positive token ids, to prevent eating all the websocket connections
|
5673
|
-
const pollingSub = rxjs.interval(30_000) // emit values every 30 seconds
|
5674
|
-
.pipe(rxjs.takeUntil(callerUnsubscribed), rxjs.withLatestFrom(subscriptionTokens),
|
5675
|
-
// Combine latest value from subscriptionTokens with each interval tick
|
5676
|
-
rxjs.map(([, subscribedTokenIds]) =>
|
5677
|
-
// Filter out tokens that are not subscribed
|
5678
|
-
Object.keys(addressesByToken).filter(tokenId => !subscribedTokenIds.includes(tokenId))), rxjs.exhaustMap(tokenIds => rxjs.from(util$1.arrayChunk(tokenIds, POLLING_WINDOW_SIZE)).pipe(rxjs.concatMap(async tokenChunk => {
|
5679
|
-
// tokenChunk is a chunk of tokenIds with size POLLING_WINDOW_SIZE
|
5680
|
-
const pollingTokenAddresses = Object.fromEntries(tokenChunk.map(tokenId => [tokenId, addressesByToken[tokenId]]));
|
5681
|
-
await poll(pollingTokenAddresses);
|
5682
|
-
return true;
|
5683
|
-
})))).subscribe();
|
5684
5061
|
return () => {
|
5685
|
-
|
5686
|
-
|
5687
|
-
pollingSub.unsubscribe();
|
5062
|
+
unsubsribeFns.then(fns => fns.forEach(unsubscribe => unsubscribe()));
|
5063
|
+
controller.abort();
|
5688
5064
|
};
|
5689
5065
|
},
|
5690
|
-
|
5691
|
-
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
5692
|
-
const queries = await queryCache.getQueries(addressesByToken);
|
5693
|
-
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
5694
|
-
const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
|
5695
|
-
return new Balances(result ?? []);
|
5696
|
-
},
|
5066
|
+
fetchBalances,
|
5697
5067
|
async transferToken({
|
5698
5068
|
tokenId,
|
5699
5069
|
from,
|
@@ -5710,11 +5080,10 @@ const SubNativeModule = hydrate => {
|
|
5710
5080
|
transferMethod,
|
5711
5081
|
userExtensions
|
5712
5082
|
}) {
|
5713
|
-
const token = await chaindataProvider$1.
|
5083
|
+
const token = await chaindataProvider$1.getTokenById(tokenId, "substrate-native");
|
5714
5084
|
util$1.assert(token, `Token ${tokenId} not found in store`);
|
5715
|
-
|
5716
|
-
const
|
5717
|
-
const chain = await chaindataProvider$1.chainById(chainId);
|
5085
|
+
const chainId = token.networkId;
|
5086
|
+
const chain = await chaindataProvider$1.getNetworkById(chainId, "polkadot");
|
5718
5087
|
util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
|
5719
5088
|
const {
|
5720
5089
|
genesisHash
|
@@ -6908,7 +6277,10 @@ var psp22Abi = {
|
|
6908
6277
|
};
|
6909
6278
|
|
6910
6279
|
const moduleType$1 = "substrate-psp22";
|
6911
|
-
const
|
6280
|
+
const SubPsp22TokenConfigSchema = z__default.default.strictObject({
|
6281
|
+
contractAddress: chaindataProvider.SubPsp22TokenSchema.shape.contractAddress,
|
6282
|
+
...TokenConfigBaseSchema.shape
|
6283
|
+
});
|
6912
6284
|
const SubPsp22Module = hydrate => {
|
6913
6285
|
const {
|
6914
6286
|
chainConnectors,
|
@@ -6918,16 +6290,15 @@ const SubPsp22Module = hydrate => {
|
|
6918
6290
|
util$1.assert(chainConnector, "This module requires a substrate chain connector");
|
6919
6291
|
return {
|
6920
6292
|
...DefaultBalanceModule(moduleType$1),
|
6921
|
-
async fetchSubstrateChainMeta(
|
6922
|
-
|
6293
|
+
async fetchSubstrateChainMeta(_chainId) {
|
6294
|
+
// we dont need anything
|
6923
6295
|
return {
|
6924
|
-
|
6296
|
+
miniMetadata: null,
|
6297
|
+
extra: null
|
6925
6298
|
};
|
6926
6299
|
},
|
6927
|
-
async fetchSubstrateChainTokens(chainId,
|
6928
|
-
|
6929
|
-
isTestnet
|
6930
|
-
} = chainMeta;
|
6300
|
+
async fetchSubstrateChainTokens(chainId, _chainMeta, moduleConfig, tokens) {
|
6301
|
+
if (!tokens?.length) return {};
|
6931
6302
|
const registry = new types.TypeRegistry();
|
6932
6303
|
const Psp22Abi = new apiContract.Abi(psp22Abi);
|
6933
6304
|
|
@@ -6937,12 +6308,11 @@ const SubPsp22Module = hydrate => {
|
|
6937
6308
|
chainId,
|
6938
6309
|
registry
|
6939
6310
|
});
|
6940
|
-
const
|
6941
|
-
for (const tokenConfig of
|
6311
|
+
const tokenList = {};
|
6312
|
+
for (const tokenConfig of tokens ?? []) {
|
6942
6313
|
try {
|
6943
6314
|
let symbol = tokenConfig?.symbol ?? "Unit";
|
6944
6315
|
let decimals = tokenConfig?.decimals ?? 0;
|
6945
|
-
const existentialDeposit = tokenConfig?.ed ?? "0";
|
6946
6316
|
const contractAddress = tokenConfig?.contractAddress ?? undefined;
|
6947
6317
|
if (contractAddress === undefined) continue;
|
6948
6318
|
await (async () => {
|
@@ -6958,35 +6328,28 @@ const SubPsp22Module = hydrate => {
|
|
6958
6328
|
const decimalsData = decimalsResult.toJSON()?.result?.ok?.data;
|
6959
6329
|
decimals = typeof decimalsData === "string" && decimalsData.startsWith("0x") ? util$1.hexToNumber(decimalsData) : decimals;
|
6960
6330
|
})();
|
6961
|
-
const id = subPsp22TokenId(chainId,
|
6331
|
+
const id = chaindataProvider.subPsp22TokenId(chainId, contractAddress);
|
6962
6332
|
const token = {
|
6963
6333
|
id,
|
6964
6334
|
type: "substrate-psp22",
|
6965
|
-
|
6335
|
+
platform: "polkadot",
|
6966
6336
|
isDefault: tokenConfig.isDefault ?? true,
|
6967
6337
|
symbol,
|
6968
6338
|
decimals,
|
6969
|
-
|
6970
|
-
|
6339
|
+
name: tokenConfig?.name || symbol,
|
6340
|
+
logo: tokenConfig?.logo,
|
6971
6341
|
contractAddress,
|
6972
|
-
|
6973
|
-
id: chainId
|
6974
|
-
}
|
6342
|
+
networkId: chainId
|
6975
6343
|
};
|
6976
|
-
if (tokenConfig?.symbol) {
|
6977
|
-
token.symbol = tokenConfig?.symbol;
|
6978
|
-
token.id = subPsp22TokenId(chainId, token.symbol);
|
6979
|
-
}
|
6980
6344
|
if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
|
6981
|
-
if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
|
6982
6345
|
if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
|
6983
|
-
|
6346
|
+
tokenList[token.id] = token;
|
6984
6347
|
} catch (error) {
|
6985
6348
|
log.error(`Failed to build substrate-psp22 token ${tokenConfig.contractAddress} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
|
6986
6349
|
continue;
|
6987
6350
|
}
|
6988
6351
|
}
|
6989
|
-
return
|
6352
|
+
return tokenList;
|
6990
6353
|
},
|
6991
6354
|
// TODO: Don't create empty subscriptions
|
6992
6355
|
async subscribeBalances({
|
@@ -6996,7 +6359,7 @@ const SubPsp22Module = hydrate => {
|
|
6996
6359
|
const subscriptionInterval = 12_000; // 12_000ms == 12 seconds
|
6997
6360
|
const initDelay = 3_000; // 3000ms == 3 seconds
|
6998
6361
|
const cache = new Map();
|
6999
|
-
const tokens = await chaindataProvider$1.
|
6362
|
+
const tokens = await chaindataProvider$1.getTokensMapById();
|
7000
6363
|
const poll = async () => {
|
7001
6364
|
if (!subscriptionActive) return;
|
7002
6365
|
try {
|
@@ -7023,7 +6386,7 @@ const SubPsp22Module = hydrate => {
|
|
7023
6386
|
},
|
7024
6387
|
async fetchBalances(addressesByToken) {
|
7025
6388
|
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
7026
|
-
const tokens = await chaindataProvider$1.
|
6389
|
+
const tokens = await chaindataProvider$1.getTokensMapById();
|
7027
6390
|
return fetchBalances(chainConnectors.substrate, tokens, addressesByToken);
|
7028
6391
|
},
|
7029
6392
|
async transferToken({
|
@@ -7041,11 +6404,11 @@ const SubPsp22Module = hydrate => {
|
|
7041
6404
|
tip,
|
7042
6405
|
userExtensions
|
7043
6406
|
}) {
|
7044
|
-
const token = await chaindataProvider$1.
|
6407
|
+
const token = await chaindataProvider$1.getTokenById(tokenId, "substrate-psp22");
|
7045
6408
|
util$1.assert(token, `Token ${tokenId} not found in store`);
|
7046
6409
|
if (token.type !== "substrate-psp22") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
|
7047
|
-
const chainId = token.
|
7048
|
-
const chain = await chaindataProvider$1.
|
6410
|
+
const chainId = token.networkId;
|
6411
|
+
const chain = await chaindataProvider$1.getNetworkById(chainId, "polkadot");
|
7049
6412
|
util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
|
7050
6413
|
const {
|
7051
6414
|
genesisHash
|
@@ -7121,7 +6484,7 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
|
|
7121
6484
|
// TODO: Use `decodeOutput` from `./util/decodeOutput`
|
7122
6485
|
const contractCall = makeContractCaller({
|
7123
6486
|
chainConnector,
|
7124
|
-
chainId: token.
|
6487
|
+
chainId: token.networkId,
|
7125
6488
|
registry
|
7126
6489
|
});
|
7127
6490
|
if (token.contractAddress === undefined) {
|
@@ -7138,10 +6501,7 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
|
|
7138
6501
|
source: "substrate-psp22",
|
7139
6502
|
status: "live",
|
7140
6503
|
address,
|
7141
|
-
|
7142
|
-
subChainId: token.chain.id
|
7143
|
-
},
|
7144
|
-
chainId: token.chain.id,
|
6504
|
+
networkId: token.networkId,
|
7145
6505
|
tokenId,
|
7146
6506
|
value: balance
|
7147
6507
|
};
|
@@ -7164,8 +6524,16 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
|
|
7164
6524
|
};
|
7165
6525
|
|
7166
6526
|
const moduleType = "substrate-tokens";
|
6527
|
+
const SubTokensTokenConfigSchema = z__default.default.strictObject({
|
6528
|
+
onChainId: chaindataProvider.SubTokensTokenSchema.shape.onChainId,
|
6529
|
+
...TokenConfigBaseSchema.shape,
|
6530
|
+
existentialDeposit: chaindataProvider.SubTokensTokenSchema.shape.existentialDeposit.optional()
|
6531
|
+
});
|
7167
6532
|
const defaultPalletId = "Tokens";
|
7168
|
-
const
|
6533
|
+
const UNSUPPORTED_CHAIN_META = {
|
6534
|
+
miniMetadata: null,
|
6535
|
+
extra: {}
|
6536
|
+
};
|
7169
6537
|
const SubTokensModule = hydrate => {
|
7170
6538
|
const {
|
7171
6539
|
chainConnectors,
|
@@ -7176,14 +6544,7 @@ const SubTokensModule = hydrate => {
|
|
7176
6544
|
return {
|
7177
6545
|
...DefaultBalanceModule(moduleType),
|
7178
6546
|
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
|
7179
|
-
|
7180
|
-
if (metadataRpc === undefined) return {
|
7181
|
-
isTestnet
|
7182
|
-
};
|
7183
|
-
if ((moduleConfig?.tokens ?? []).length < 1) return {
|
7184
|
-
isTestnet
|
7185
|
-
};
|
7186
|
-
const metadataVersion = scale.getMetadataVersion(metadataRpc);
|
6547
|
+
if (metadataRpc === undefined) return UNSUPPORTED_CHAIN_META;
|
7187
6548
|
const metadata = scale.decAnyMetadata(metadataRpc);
|
7188
6549
|
const palletId = moduleConfig?.palletId ?? defaultPalletId;
|
7189
6550
|
scale.compactMetadata(metadata, [{
|
@@ -7191,74 +6552,83 @@ const SubTokensModule = hydrate => {
|
|
7191
6552
|
items: ["Accounts"]
|
7192
6553
|
}]);
|
7193
6554
|
const miniMetadata = scale.encodeMetadata(metadata);
|
7194
|
-
return
|
7195
|
-
isTestnet,
|
7196
|
-
miniMetadata,
|
7197
|
-
metadataVersion
|
7198
|
-
} : {
|
7199
|
-
isTestnet,
|
7200
|
-
palletId,
|
6555
|
+
return {
|
7201
6556
|
miniMetadata,
|
7202
|
-
|
6557
|
+
extra: {
|
6558
|
+
palletId
|
6559
|
+
}
|
7203
6560
|
};
|
7204
6561
|
},
|
7205
|
-
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
|
7206
|
-
const {
|
7207
|
-
|
7208
|
-
} = chainMeta;
|
7209
|
-
const tokens = {};
|
7210
|
-
for (const tokenConfig of moduleConfig?.tokens ?? []) {
|
6562
|
+
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig, tokens) {
|
6563
|
+
const tokenList = {};
|
6564
|
+
for (const tokenConfig of tokens ?? []) {
|
7211
6565
|
try {
|
6566
|
+
// TODO fetch metadata from chain, like we do for assets
|
7212
6567
|
const symbol = tokenConfig?.symbol ?? "Unit";
|
7213
6568
|
const decimals = tokenConfig?.decimals ?? 0;
|
7214
|
-
const existentialDeposit = tokenConfig?.
|
6569
|
+
const existentialDeposit = tokenConfig?.existentialDeposit ?? "0";
|
7215
6570
|
const onChainId = tokenConfig?.onChainId ?? undefined;
|
7216
6571
|
if (onChainId === undefined) continue;
|
7217
|
-
const id = subTokensTokenId(chainId, onChainId);
|
6572
|
+
const id = chaindataProvider.subTokensTokenId(chainId, onChainId);
|
7218
6573
|
const token = {
|
7219
6574
|
id,
|
7220
6575
|
type: "substrate-tokens",
|
7221
|
-
|
6576
|
+
platform: "polkadot",
|
7222
6577
|
isDefault: tokenConfig.isDefault ?? true,
|
7223
6578
|
symbol,
|
7224
6579
|
decimals,
|
7225
|
-
|
6580
|
+
name: tokenConfig?.name ?? symbol,
|
6581
|
+
logo: tokenConfig?.logo,
|
7226
6582
|
existentialDeposit,
|
7227
6583
|
onChainId,
|
7228
|
-
|
7229
|
-
id: chainId
|
7230
|
-
}
|
6584
|
+
networkId: chainId
|
7231
6585
|
};
|
7232
|
-
if (tokenConfig?.symbol) {
|
7233
|
-
token.symbol = tokenConfig?.symbol;
|
7234
|
-
token.id = subTokensTokenId(chainId, token.onChainId);
|
7235
|
-
}
|
7236
6586
|
if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
|
7237
|
-
if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
|
7238
6587
|
if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
|
7239
|
-
|
6588
|
+
tokenList[token.id] = token;
|
7240
6589
|
} catch (error) {
|
7241
6590
|
log.error(`Failed to build substrate-tokens token ${tokenConfig.onChainId} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
|
7242
6591
|
continue;
|
7243
6592
|
}
|
7244
6593
|
}
|
7245
|
-
return
|
6594
|
+
return tokenList;
|
7246
6595
|
},
|
7247
6596
|
// TODO: Don't create empty subscriptions
|
7248
6597
|
async subscribeBalances({
|
7249
6598
|
addressesByToken
|
7250
6599
|
}, callback) {
|
7251
|
-
const
|
7252
|
-
|
7253
|
-
if (
|
7254
|
-
|
7255
|
-
|
7256
|
-
});
|
7257
|
-
|
6600
|
+
const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
|
6601
|
+
const networkId = chaindataProvider.parseSubTokensTokenId(tokenId).networkId;
|
6602
|
+
if (!acc[networkId]) acc[networkId] = {};
|
6603
|
+
acc[networkId][tokenId] = addressesByToken[tokenId];
|
6604
|
+
return acc;
|
6605
|
+
}, {});
|
6606
|
+
const controller = new AbortController();
|
6607
|
+
const pUnsubs = Promise.all(lodash.toPairs(byNetwork).map(async ([networkId, addressesByToken]) => {
|
6608
|
+
try {
|
6609
|
+
const queries = await buildNetworkQueries(networkId, chainConnector, chaindataProvider$1, addressesByToken, controller.signal);
|
6610
|
+
if (controller.signal.aborted) return () => {};
|
6611
|
+
const stateHelper = new RpcStateQueryHelper(chainConnector, queries);
|
6612
|
+
return await stateHelper.subscribe((error, result) => {
|
6613
|
+
if (error) return callback(error);
|
6614
|
+
const balances = result?.filter(b => b !== null) ?? [];
|
6615
|
+
if (balances.length > 0) callback(null, new Balances(balances));
|
6616
|
+
});
|
6617
|
+
} catch (err) {
|
6618
|
+
if (!util.isAbortError(err)) log.error(`Failed to subscribe balances for network ${networkId}`, err);
|
6619
|
+
return () => {};
|
6620
|
+
}
|
6621
|
+
}));
|
6622
|
+
return () => {
|
6623
|
+
pUnsubs.then(unsubs => {
|
6624
|
+
unsubs.forEach(unsubscribe => unsubscribe());
|
6625
|
+
});
|
6626
|
+
controller.abort();
|
6627
|
+
};
|
7258
6628
|
},
|
7259
6629
|
async fetchBalances(addressesByToken) {
|
7260
6630
|
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
7261
|
-
const queries = await buildQueries(chaindataProvider$1, addressesByToken);
|
6631
|
+
const queries = await buildQueries(chainConnector, chaindataProvider$1, addressesByToken);
|
7262
6632
|
const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
|
7263
6633
|
const balances = result?.filter(b => b !== null) ?? [];
|
7264
6634
|
return new Balances(balances);
|
@@ -7270,15 +6640,13 @@ const SubTokensModule = hydrate => {
|
|
7270
6640
|
transferMethod,
|
7271
6641
|
metadataRpc
|
7272
6642
|
}) {
|
7273
|
-
const token = await chaindataProvider$1.
|
6643
|
+
const token = await chaindataProvider$1.getTokenById(tokenId, "substrate-tokens");
|
7274
6644
|
util$1.assert(token, `Token ${tokenId} not found in store`);
|
7275
|
-
|
7276
|
-
const
|
7277
|
-
const chain = await chaindataProvider$1.chainById(chainId);
|
6645
|
+
const chainId = token.networkId;
|
6646
|
+
const chain = await chaindataProvider$1.getNetworkById(chainId, "polkadot");
|
7278
6647
|
util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
|
7279
|
-
const
|
7280
|
-
const
|
7281
|
-
const tokensPallet = chainMeta?.palletId ?? defaultPalletId;
|
6648
|
+
const miniMetadata = await getMiniMetadata(chaindataProvider$1, chainConnector, chainId, moduleType);
|
6649
|
+
const tokensPallet = miniMetadata?.extra?.palletId ?? defaultPalletId;
|
7282
6650
|
const onChainId = (() => {
|
7283
6651
|
try {
|
7284
6652
|
return scale.papiParse(token.onChainId);
|
@@ -7377,23 +6745,15 @@ const SubTokensModule = hydrate => {
|
|
7377
6745
|
}
|
7378
6746
|
};
|
7379
6747
|
};
|
7380
|
-
async function
|
7381
|
-
const
|
7382
|
-
const
|
7383
|
-
const
|
7384
|
-
|
7385
|
-
|
7386
|
-
const
|
7387
|
-
const
|
7388
|
-
|
7389
|
-
chains,
|
7390
|
-
miniMetadatas,
|
7391
|
-
moduleType: "substrate-tokens",
|
7392
|
-
coders: {
|
7393
|
-
storage: ({
|
7394
|
-
chainId
|
7395
|
-
}) => [tokensPalletByChain.get(chainId) ?? defaultPalletId, "Accounts"]
|
7396
|
-
}
|
6748
|
+
async function buildNetworkQueries(networkId, chainConnector, chaindataProvider, addressesByToken, signal) {
|
6749
|
+
const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType, signal);
|
6750
|
+
const chain = await chaindataProvider.getNetworkById(networkId, "polkadot");
|
6751
|
+
const tokens = await chaindataProvider.getTokensMapById();
|
6752
|
+
if (!chain) return [];
|
6753
|
+
signal?.throwIfAborted();
|
6754
|
+
const palletId = miniMetadata.extra.palletId ?? defaultPalletId;
|
6755
|
+
const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
|
6756
|
+
storage: [palletId, "Accounts"]
|
7397
6757
|
});
|
7398
6758
|
return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
|
7399
6759
|
const token = tokens[tokenId];
|
@@ -7405,18 +6765,8 @@ async function buildQueries(chaindataProvider, addressesByToken) {
|
|
7405
6765
|
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
7406
6766
|
return [];
|
7407
6767
|
}
|
7408
|
-
const chainId = token.chain?.id;
|
7409
|
-
if (!chainId) {
|
7410
|
-
log.warn(`Token ${tokenId} has no chain`);
|
7411
|
-
return [];
|
7412
|
-
}
|
7413
|
-
const chain = chains[chainId];
|
7414
|
-
if (!chain) {
|
7415
|
-
log.warn(`Chain ${chainId} for token ${tokenId} not found`);
|
7416
|
-
return [];
|
7417
|
-
}
|
7418
6768
|
return addresses.flatMap(address => {
|
7419
|
-
const scaleCoder =
|
6769
|
+
const scaleCoder = networkStorageCoders?.storage;
|
7420
6770
|
const onChainId = (() => {
|
7421
6771
|
try {
|
7422
6772
|
return scale.papiParse(token.onChainId);
|
@@ -7424,12 +6774,12 @@ async function buildQueries(chaindataProvider, addressesByToken) {
|
|
7424
6774
|
return token.onChainId;
|
7425
6775
|
}
|
7426
6776
|
})();
|
7427
|
-
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${
|
6777
|
+
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${networkId} storage query ${address} / ${token.onChainId}`, address, onChainId);
|
7428
6778
|
if (!stateKey) return [];
|
7429
6779
|
const decodeResult = change => {
|
7430
6780
|
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
7431
6781
|
|
7432
|
-
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-tokens balance on chain ${
|
6782
|
+
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-tokens balance on chain ${networkId}`) ?? {
|
7433
6783
|
free: 0n,
|
7434
6784
|
reserved: 0n,
|
7435
6785
|
frozen: 0n
|
@@ -7454,50 +6804,32 @@ async function buildQueries(chaindataProvider, addressesByToken) {
|
|
7454
6804
|
source: "substrate-tokens",
|
7455
6805
|
status: "live",
|
7456
6806
|
address,
|
7457
|
-
|
7458
|
-
subChainId: chainId
|
7459
|
-
},
|
7460
|
-
chainId,
|
6807
|
+
networkId,
|
7461
6808
|
tokenId: token.id,
|
7462
6809
|
values: balanceValues
|
7463
6810
|
};
|
7464
6811
|
};
|
7465
6812
|
return {
|
7466
|
-
chainId,
|
6813
|
+
chainId: networkId,
|
7467
6814
|
stateKey,
|
7468
6815
|
decodeResult
|
7469
6816
|
};
|
7470
6817
|
});
|
7471
6818
|
});
|
7472
6819
|
}
|
6820
|
+
async function buildQueries(chainConnector, chaindataProvider$1, addressesByToken, signal) {
|
6821
|
+
const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
|
6822
|
+
const networkId = chaindataProvider.parseSubTokensTokenId(tokenId).networkId;
|
6823
|
+
if (!acc[networkId]) acc[networkId] = {};
|
6824
|
+
acc[networkId][tokenId] = addressesByToken[tokenId];
|
6825
|
+
return acc;
|
6826
|
+
}, {});
|
6827
|
+
return (await Promise.all(lodash.toPairs(byNetwork).map(([networkId, addressesByToken]) => {
|
6828
|
+
return buildNetworkQueries(networkId, chainConnector, chaindataProvider$1, addressesByToken, signal);
|
6829
|
+
}))).flat();
|
6830
|
+
}
|
7473
6831
|
|
7474
|
-
const defaultBalanceModules = [EvmErc20Module, EvmNativeModule, EvmUniswapV2Module, SubAssetsModule,
|
7475
|
-
|
7476
|
-
/** Pulls the latest chaindata from https://github.com/TalismanSociety/chaindata */
|
7477
|
-
const hydrateChaindataAndMiniMetadata = async (chaindataProvider, miniMetadataUpdater) => {
|
7478
|
-
// need chains to be provisioned first, or substrate balances won't fetch on first subscription
|
7479
|
-
await chaindataProvider.hydrateChains();
|
7480
|
-
await Promise.all([miniMetadataUpdater.hydrateFromChaindata(), miniMetadataUpdater.hydrateCustomChains()]);
|
7481
|
-
const chains = await chaindataProvider.chains();
|
7482
|
-
const {
|
7483
|
-
statusesByChain
|
7484
|
-
} = await miniMetadataUpdater.statuses(chains);
|
7485
|
-
const goodChains = [...statusesByChain.entries()].flatMap(([chainId, status]) => status === "good" ? chainId : []);
|
7486
|
-
await chaindataProvider.hydrateSubstrateTokens(goodChains);
|
7487
|
-
};
|
7488
|
-
|
7489
|
-
/** Builds any missing miniMetadatas (e.g. for the user's custom substrate chains) */
|
7490
|
-
const updateCustomMiniMetadata = async (chaindataProvider, miniMetadataUpdater) => {
|
7491
|
-
const chainIds = await chaindataProvider.chainIds();
|
7492
|
-
await miniMetadataUpdater.update(chainIds);
|
7493
|
-
};
|
7494
|
-
|
7495
|
-
/** Fetches any missing Evm Tokens */
|
7496
|
-
const updateEvmTokens = async (chaindataProvider, evmTokenFetcher) => {
|
7497
|
-
await chaindataProvider.hydrateEvmNetworks();
|
7498
|
-
const evmNetworkIds = await chaindataProvider.evmNetworkIds();
|
7499
|
-
await evmTokenFetcher.update(evmNetworkIds);
|
7500
|
-
};
|
6832
|
+
const defaultBalanceModules = [EvmErc20Module, EvmNativeModule, EvmUniswapV2Module, SubAssetsModule, SubForeignAssetsModule, SubNativeModule, SubPsp22Module, SubTokensModule];
|
7501
6833
|
|
7502
6834
|
exports.Balance = Balance;
|
7503
6835
|
exports.BalanceFormatter = BalanceFormatter;
|
@@ -7506,11 +6838,12 @@ exports.Balances = Balances;
|
|
7506
6838
|
exports.Change24hCurrencyFormatter = Change24hCurrencyFormatter;
|
7507
6839
|
exports.DefaultBalanceModule = DefaultBalanceModule;
|
7508
6840
|
exports.EvmErc20Module = EvmErc20Module;
|
6841
|
+
exports.EvmErc20TokenConfigSchema = EvmErc20TokenConfigSchema;
|
7509
6842
|
exports.EvmNativeModule = EvmNativeModule;
|
7510
|
-
exports.
|
6843
|
+
exports.EvmNativeTokenConfigSchema = EvmNativeTokenConfigSchema;
|
7511
6844
|
exports.EvmUniswapV2Module = EvmUniswapV2Module;
|
6845
|
+
exports.EvmUniswapV2TokenConfigSchema = EvmUniswapV2TokenConfigSchema;
|
7512
6846
|
exports.FiatSumBalancesFormatter = FiatSumBalancesFormatter;
|
7513
|
-
exports.MiniMetadataUpdater = MiniMetadataUpdater;
|
7514
6847
|
exports.ONE_ALPHA_TOKEN = ONE_ALPHA_TOKEN;
|
7515
6848
|
exports.PlanckSumBalancesFormatter = PlanckSumBalancesFormatter;
|
7516
6849
|
exports.RpcStateQueryHelper = RpcStateQueryHelper;
|
@@ -7518,15 +6851,20 @@ exports.SCALE_FACTOR = SCALE_FACTOR;
|
|
7518
6851
|
exports.SUBTENSOR_MIN_STAKE_AMOUNT_PLANK = SUBTENSOR_MIN_STAKE_AMOUNT_PLANK;
|
7519
6852
|
exports.SUBTENSOR_ROOT_NETUID = SUBTENSOR_ROOT_NETUID;
|
7520
6853
|
exports.SubAssetsModule = SubAssetsModule;
|
7521
|
-
exports.
|
6854
|
+
exports.SubAssetsTokenConfigSchema = SubAssetsTokenConfigSchema;
|
7522
6855
|
exports.SubForeignAssetsModule = SubForeignAssetsModule;
|
6856
|
+
exports.SubForeignAssetsTokenConfigSchema = SubForeignAssetsTokenConfigSchema;
|
7523
6857
|
exports.SubNativeModule = SubNativeModule;
|
6858
|
+
exports.SubNativeTokenConfigSchema = SubNativeTokenConfigSchema;
|
7524
6859
|
exports.SubPsp22Module = SubPsp22Module;
|
6860
|
+
exports.SubPsp22TokenConfigSchema = SubPsp22TokenConfigSchema;
|
7525
6861
|
exports.SubTokensModule = SubTokensModule;
|
6862
|
+
exports.SubTokensTokenConfigSchema = SubTokensTokenConfigSchema;
|
7526
6863
|
exports.SumBalancesFormatter = SumBalancesFormatter;
|
7527
6864
|
exports.TalismanBalancesDatabase = TalismanBalancesDatabase;
|
7528
6865
|
exports.abiMulticall = abiMulticall;
|
7529
6866
|
exports.balances = balances;
|
6867
|
+
exports.buildNetworkStorageCoders = buildNetworkStorageCoders;
|
7530
6868
|
exports.buildStorageCoders = buildStorageCoders;
|
7531
6869
|
exports.calculateAlphaPrice = calculateAlphaPrice;
|
7532
6870
|
exports.calculateTaoAmountFromAlpha = calculateTaoAmountFromAlpha;
|
@@ -7541,27 +6879,14 @@ exports.deriveMiniMetadataId = deriveMiniMetadataId;
|
|
7541
6879
|
exports.detectTransferMethod = detectTransferMethod;
|
7542
6880
|
exports.erc20Abi = erc20Abi;
|
7543
6881
|
exports.erc20BalancesAggregatorAbi = erc20BalancesAggregatorAbi;
|
7544
|
-
exports.evmErc20TokenId = evmErc20TokenId;
|
7545
|
-
exports.evmNativeTokenId = evmNativeTokenId;
|
7546
|
-
exports.evmUniswapV2TokenId = evmUniswapV2TokenId;
|
7547
6882
|
exports.excludeFromFeePayableLocks = excludeFromFeePayableLocks;
|
7548
6883
|
exports.excludeFromTransferableAmount = excludeFromTransferableAmount;
|
7549
6884
|
exports.filterBaseLocks = filterBaseLocks;
|
7550
6885
|
exports.filterMirrorTokens = filterMirrorTokens;
|
7551
|
-
exports.findChainMeta = findChainMeta;
|
7552
6886
|
exports.getBalanceId = getBalanceId;
|
7553
6887
|
exports.getLockTitle = getLockTitle;
|
7554
6888
|
exports.getUniqueChainIds = getUniqueChainIds;
|
7555
6889
|
exports.getValueId = getValueId;
|
7556
|
-
exports.hydrateChaindataAndMiniMetadata = hydrateChaindataAndMiniMetadata;
|
7557
6890
|
exports.includeInTotalExtraAmount = includeInTotalExtraAmount;
|
7558
6891
|
exports.makeContractCaller = makeContractCaller;
|
7559
|
-
exports.subAssetTokenId = subAssetTokenId;
|
7560
|
-
exports.subEquilibriumTokenId = subEquilibriumTokenId;
|
7561
|
-
exports.subForeignAssetTokenId = subForeignAssetTokenId;
|
7562
|
-
exports.subNativeTokenId = subNativeTokenId;
|
7563
|
-
exports.subPsp22TokenId = subPsp22TokenId;
|
7564
|
-
exports.subTokensTokenId = subTokensTokenId;
|
7565
6892
|
exports.uniswapV2PairAbi = uniswapV2PairAbi;
|
7566
|
-
exports.updateCustomMiniMetadata = updateCustomMiniMetadata;
|
7567
|
-
exports.updateEvmTokens = updateEvmTokens;
|