@talismn/balances 0.0.0-pr2043-20250703063959 → 0.0.0-pr2059-20250627034726
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 +12 -23
- package/dist/declarations/src/EvmTokenFetcher.d.ts +11 -0
- package/dist/declarations/src/MiniMetadataUpdater.d.ts +43 -0
- package/dist/declarations/src/index.d.ts +3 -0
- package/dist/declarations/src/modules/EvmErc20Module.d.ts +23 -20
- package/dist/declarations/src/modules/EvmNativeModule.d.ts +18 -19
- package/dist/declarations/src/modules/EvmUniswapV2Module.d.ts +30 -28
- package/dist/declarations/src/modules/SubstrateAssetsModule.d.ts +18 -20
- package/dist/declarations/src/modules/SubstrateEquilibriumModule.d.ts +39 -0
- package/dist/declarations/src/modules/SubstrateForeignAssetsModule.d.ts +18 -20
- package/dist/declarations/src/modules/SubstrateNativeModule/index.d.ts +4 -17
- package/dist/declarations/src/modules/SubstrateNativeModule/subscribeCrowdloans.d.ts +5 -0
- 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 +23 -6
- package/dist/declarations/src/modules/SubstrateNativeModule/util/QueryCache.d.ts +6 -6
- package/dist/declarations/src/modules/SubstrateNativeModule/util/buildQueries.d.ts +4 -5
- package/dist/declarations/src/modules/SubstrateNativeModule/util/crowdloanFundContributionsChildKey.d.ts +4 -0
- package/dist/declarations/src/modules/SubstrateNativeModule/util/detectMiniMetadataChanges.d.ts +2 -0
- package/dist/declarations/src/modules/SubstrateNativeModule/util/sortChains.d.ts +1 -1
- package/dist/declarations/src/modules/SubstratePsp22Module.d.ts +19 -20
- package/dist/declarations/src/modules/SubstrateTokensModule.d.ts +22 -22
- package/dist/declarations/src/modules/index.d.ts +38 -213
- package/dist/declarations/src/modules/util/InferBalanceModuleTypes.d.ts +3 -5
- package/dist/declarations/src/modules/util/buildStorageCoders.d.ts +7 -16
- package/dist/declarations/src/modules/util/findChainMeta.d.ts +8 -0
- package/dist/declarations/src/modules/util/getUniqueChainIds.d.ts +2 -2
- package/dist/declarations/src/modules/util/index.d.ts +1 -0
- package/dist/declarations/src/types/balances.d.ts +72 -203
- package/dist/declarations/src/types/balancetypes.d.ts +18 -8
- package/dist/declarations/src/types/minimetadatas.d.ts +24 -6
- package/dist/declarations/src/util/hydrateChaindata.d.ts +8 -0
- package/dist/declarations/src/util/index.d.ts +1 -0
- package/dist/talismn-balances.cjs.dev.js +2099 -1424
- package/dist/talismn-balances.cjs.prod.js +2099 -1424
- package/dist/talismn-balances.esm.js +2083 -1414
- package/package.json +8 -9
- package/dist/declarations/src/getMiniMetadata/getMetadataRpc.d.ts +0 -3
- package/dist/declarations/src/getMiniMetadata/getMiniMetadatas.d.ts +0 -4
- package/dist/declarations/src/getMiniMetadata/getSpecVersion.d.ts +0 -6
- package/dist/declarations/src/getMiniMetadata/getUpdatedMiniMetadatas.d.ts +0 -4
- package/dist/declarations/src/getMiniMetadata/index.d.ts +0 -5
- package/dist/declarations/src/libVersion.d.ts +0 -1
- package/dist/declarations/src/modules/SubstrateNativeModule/util/systemProperties.d.ts +0 -5
- package/dist/declarations/src/modules/util/getAddresssesByTokenByNetwork.d.ts +0 -3
- package/dist/declarations/src/types/tokens.d.ts +0 -11
@@ -1,42 +1,40 @@
|
|
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');
|
3
6
|
var dexie = require('dexie');
|
7
|
+
var isEqual = require('lodash/isEqual');
|
8
|
+
var rxjs = require('rxjs');
|
4
9
|
var anylogger = require('anylogger');
|
5
|
-
var chaindataProvider = require('@talismn/chaindata-provider');
|
6
10
|
var tokenRates = require('@talismn/token-rates');
|
7
11
|
var util = require('@talismn/util');
|
8
12
|
var BigNumber = require('bignumber.js');
|
9
13
|
var util$1 = require('@polkadot/util');
|
10
14
|
var utilCrypto = require('@polkadot/util-crypto');
|
11
15
|
var pako = require('pako');
|
12
|
-
var z = require('zod/v4');
|
13
16
|
var viem = require('viem');
|
14
|
-
var lodash = require('lodash');
|
15
|
-
var isEqual = require('lodash/isEqual');
|
16
17
|
var txwrapperCore = require('@substrate/txwrapper-core');
|
17
18
|
var scale = require('@talismn/scale');
|
18
19
|
var camelCase = require('lodash/camelCase');
|
19
|
-
var PQueue = require('p-queue');
|
20
|
-
var sapi = require('@talismn/sapi');
|
21
20
|
var types = require('@polkadot/types');
|
22
21
|
var groupBy = require('lodash/groupBy');
|
23
22
|
var utils = require('@polkadot-api/utils');
|
24
23
|
var polkadotApi = require('polkadot-api');
|
25
24
|
var chainConnector = require('@talismn/chain-connector');
|
26
|
-
var rxjs = require('rxjs');
|
27
25
|
var scaleTs = require('scale-ts');
|
28
26
|
var upperFirst = require('lodash/upperFirst');
|
29
27
|
var apiContract = require('@polkadot/api-contract');
|
28
|
+
var lzString = require('lz-string');
|
30
29
|
|
31
30
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
32
31
|
|
32
|
+
var PromisePool__default = /*#__PURE__*/_interopDefault(PromisePool);
|
33
|
+
var isEqual__default = /*#__PURE__*/_interopDefault(isEqual);
|
33
34
|
var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
|
34
35
|
var BigNumber__default = /*#__PURE__*/_interopDefault(BigNumber);
|
35
36
|
var pako__default = /*#__PURE__*/_interopDefault(pako);
|
36
|
-
var z__default = /*#__PURE__*/_interopDefault(z);
|
37
|
-
var isEqual__default = /*#__PURE__*/_interopDefault(isEqual);
|
38
37
|
var camelCase__default = /*#__PURE__*/_interopDefault(camelCase);
|
39
|
-
var PQueue__default = /*#__PURE__*/_interopDefault(PQueue);
|
40
38
|
var groupBy__default = /*#__PURE__*/_interopDefault(groupBy);
|
41
39
|
var upperFirst__default = /*#__PURE__*/_interopDefault(upperFirst);
|
42
40
|
|
@@ -73,11 +71,48 @@ const DefaultBalanceModule = type => ({
|
|
73
71
|
// internal
|
74
72
|
//
|
75
73
|
|
76
|
-
|
77
|
-
|
78
|
-
|
74
|
+
/**
|
75
|
+
* Fetches tokens for EVM networks.
|
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"};
|
79
114
|
|
80
|
-
var log = anylogger__default.default(
|
115
|
+
var log = anylogger__default.default(packageJson.name);
|
81
116
|
|
82
117
|
function excludeFromTransferableAmount(locks) {
|
83
118
|
if (typeof locks === "string") return BigInt(locks);
|
@@ -287,13 +322,15 @@ class Balances {
|
|
287
322
|
return new SumBalancesFormatter(this);
|
288
323
|
}
|
289
324
|
}
|
325
|
+
const isBalanceEvm = balance => "evmNetworkId" in balance;
|
290
326
|
const getBalanceId = balance => {
|
291
327
|
const {
|
292
328
|
source,
|
293
329
|
address,
|
294
330
|
tokenId
|
295
331
|
} = balance;
|
296
|
-
|
332
|
+
const locationId = isBalanceEvm(balance) ? balance.evmNetworkId : balance.chainId;
|
333
|
+
return [source, address, locationId, tokenId].filter(util.isTruthy).join("::");
|
297
334
|
};
|
298
335
|
|
299
336
|
/**
|
@@ -355,17 +392,23 @@ class Balance {
|
|
355
392
|
get address() {
|
356
393
|
return this.#storage.address;
|
357
394
|
}
|
358
|
-
get
|
359
|
-
return this.#storage.
|
395
|
+
get chainId() {
|
396
|
+
return isBalanceEvm(this.#storage) ? undefined : this.#storage.chainId;
|
397
|
+
}
|
398
|
+
get chain() {
|
399
|
+
return this.#db?.chains && this.chainId && this.#db?.chains[this.chainId] || null;
|
360
400
|
}
|
361
|
-
get
|
362
|
-
return this.#
|
401
|
+
get evmNetworkId() {
|
402
|
+
return isBalanceEvm(this.#storage) ? this.#storage.evmNetworkId : undefined;
|
403
|
+
}
|
404
|
+
get evmNetwork() {
|
405
|
+
return this.#db?.evmNetworks && this.evmNetworkId && this.#db?.evmNetworks[this.evmNetworkId] || null;
|
363
406
|
}
|
364
407
|
get tokenId() {
|
365
408
|
return this.#storage.tokenId;
|
366
409
|
}
|
367
410
|
get token() {
|
368
|
-
return this.#db?.tokens?.[this.tokenId] || null;
|
411
|
+
return this.#db?.tokens && this.#db?.tokens[this.tokenId] || null;
|
369
412
|
}
|
370
413
|
get decimals() {
|
371
414
|
return this.token?.decimals || null;
|
@@ -378,9 +421,9 @@ class Balance {
|
|
378
421
|
//
|
379
422
|
// This means that those rates are always available for calculating the uniswapv2 rates,
|
380
423
|
// regardless of whether or not the underlying erc20s are actually in chaindata and enabled.
|
381
|
-
if (this.isSource("evm-uniswapv2") && this.token?.type === "evm-uniswapv2") {
|
382
|
-
const tokenId0 =
|
383
|
-
const tokenId1 =
|
424
|
+
if (this.isSource("evm-uniswapv2") && this.token?.type === "evm-uniswapv2" && this.evmNetworkId) {
|
425
|
+
const tokenId0 = evmErc20TokenId$1(this.evmNetworkId, this.token.tokenAddress0);
|
426
|
+
const tokenId1 = evmErc20TokenId$1(this.evmNetworkId, this.token.tokenAddress1);
|
384
427
|
const decimals = this.token.decimals;
|
385
428
|
const decimals0 = this.token.decimals0;
|
386
429
|
const decimals1 = this.token.decimals1;
|
@@ -459,7 +502,9 @@ class Balance {
|
|
459
502
|
const nomPoolStakedPlancks = this.locks.some(lock => lock.source === "substrate-native-holds" && lock.label === "DelegatedStaking") ? 0n : this.nompools.map(({
|
460
503
|
amount
|
461
504
|
}) => amount.planck).reduce((a, b) => a + b, 0n);
|
462
|
-
return this.#format(this.free.planck + this.reserved.planck + nomPoolStakedPlancks + this.
|
505
|
+
return this.#format(this.free.planck + this.reserved.planck + nomPoolStakedPlancks + this.crowdloans.map(({
|
506
|
+
amount
|
507
|
+
}) => amount.planck).reduce((a, b) => a + b, 0n) + this.subtensor.map(({
|
463
508
|
amount
|
464
509
|
}) => amount.planck).reduce((a, b) => a + b, 0n) + includeInTotalExtraAmount(extra));
|
465
510
|
}
|
@@ -495,6 +540,9 @@ class Balance {
|
|
495
540
|
get locks() {
|
496
541
|
return this.getValue("locked");
|
497
542
|
}
|
543
|
+
get crowdloans() {
|
544
|
+
return this.getValue("crowdloan");
|
545
|
+
}
|
498
546
|
get nompools() {
|
499
547
|
return this.getValue("nompool");
|
500
548
|
}
|
@@ -573,7 +621,7 @@ class Balance {
|
|
573
621
|
const nomPoolStakedPlancks = this.locks.some(lock => lock.source === "substrate-native-holds" && lock.label === "DelegatedStaking") ? 0n : this.nompools.map(({
|
574
622
|
amount
|
575
623
|
}) => amount.planck).reduce((a, b) => a + b, 0n);
|
576
|
-
const otherUnavailable = nomPoolStakedPlancks + this.subtensor.reduce((total, each) => total + each.amount.planck, 0n);
|
624
|
+
const otherUnavailable = nomPoolStakedPlancks + this.crowdloans.reduce((total, each) => total + each.amount.planck, 0n) + this.subtensor.reduce((total, each) => total + each.amount.planck, 0n);
|
577
625
|
return this.#format(baseUnavailable + otherUnavailable);
|
578
626
|
}
|
579
627
|
|
@@ -808,6 +856,10 @@ const filterMirrorTokens = (balance, i, balances) => {
|
|
808
856
|
return !mirrorOf || !balances.find(b => b.tokenId === mirrorOf);
|
809
857
|
};
|
810
858
|
|
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
|
+
|
811
863
|
/**
|
812
864
|
* `BalanceTypes` is an automatically determined sub-selection of `PluginBalanceTypes`.
|
813
865
|
*
|
@@ -838,6 +890,7 @@ const getValueId = amount => {
|
|
838
890
|
const getMetaId = () => {
|
839
891
|
const meta = amount.meta;
|
840
892
|
if (!meta) return "";
|
893
|
+
if (amount.type === "crowdloan") return meta.paraId?.toString() ?? "";
|
841
894
|
if (amount.type === "nompool") return meta.poolId?.toString() ?? "";
|
842
895
|
if (amount.type === "subtensor") {
|
843
896
|
const {
|
@@ -861,9 +914,10 @@ const getValueId = amount => {
|
|
861
914
|
const deriveMiniMetadataId = ({
|
862
915
|
source,
|
863
916
|
chainId,
|
917
|
+
specName,
|
864
918
|
specVersion,
|
865
|
-
|
866
|
-
}) => util$1.u8aToHex(utilCrypto.xxhashAsU8a(new TextEncoder().encode(`${source}${chainId}${specVersion}${
|
919
|
+
balancesConfig
|
920
|
+
}) => util$1.u8aToHex(utilCrypto.xxhashAsU8a(new TextEncoder().encode(`${source}${chainId}${specName}${specVersion}${balancesConfig}`), 64), undefined, false);
|
867
921
|
|
868
922
|
// for DB version 3, Wallet version 1.21.0
|
869
923
|
const upgradeRemoveSymbolFromNativeTokenId = async tx => {
|
@@ -958,9 +1012,230 @@ class TalismanBalancesDatabase extends dexie.Dexie {
|
|
958
1012
|
}
|
959
1013
|
const db = new TalismanBalancesDatabase();
|
960
1014
|
|
961
|
-
const
|
962
|
-
|
963
|
-
|
1015
|
+
const minimumHydrationInterval = 300_000; // 300_000ms = 300s = 5 minutes
|
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
|
+
}
|
964
1239
|
|
965
1240
|
const erc20Abi = [{
|
966
1241
|
constant: true,
|
@@ -1192,11 +1467,8 @@ const erc20Abi = [{
|
|
1192
1467
|
|
1193
1468
|
const erc20BalancesAggregatorAbi = viem.parseAbi(["struct AccountToken {address account; address token;}", "function balances(AccountToken[] memory accountTokens) public view returns (uint256[] memory)"]);
|
1194
1469
|
|
1195
|
-
const moduleType$
|
1196
|
-
const
|
1197
|
-
contractAddress: chaindataProvider.EvmErc20TokenSchema.shape.contractAddress,
|
1198
|
-
...TokenConfigBaseSchema.shape
|
1199
|
-
});
|
1470
|
+
const moduleType$8 = "evm-erc20";
|
1471
|
+
const evmErc20TokenId = (chainId, tokenContractAddress) => `${chainId}-evm-erc20-${tokenContractAddress}`.toLowerCase();
|
1200
1472
|
const EvmErc20Module = hydrate => {
|
1201
1473
|
const {
|
1202
1474
|
chainConnectors,
|
@@ -1220,36 +1492,38 @@ const EvmErc20Module = hydrate => {
|
|
1220
1492
|
}, {});
|
1221
1493
|
};
|
1222
1494
|
const getModuleTokens = async () => {
|
1223
|
-
return await chaindataProvider$1.
|
1495
|
+
return await chaindataProvider$1.tokensByIdForType(moduleType$8);
|
1224
1496
|
};
|
1225
1497
|
const getErc20Aggregators = async () => {
|
1226
|
-
const evmNetworks = await chaindataProvider$1.
|
1227
|
-
return Object.fromEntries(evmNetworks.filter(n => n.
|
1498
|
+
const evmNetworks = await chaindataProvider$1.evmNetworks();
|
1499
|
+
return Object.fromEntries(evmNetworks.filter(n => n.erc20aggregator).map(n => [n.id, n.erc20aggregator]));
|
1228
1500
|
};
|
1229
1501
|
return {
|
1230
|
-
...DefaultBalanceModule(moduleType$
|
1502
|
+
...DefaultBalanceModule(moduleType$8),
|
1231
1503
|
/**
|
1232
1504
|
* This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L280-L284).
|
1233
1505
|
* 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.
|
1234
1506
|
*/
|
1235
|
-
async fetchEvmChainMeta(
|
1507
|
+
async fetchEvmChainMeta(chainId) {
|
1508
|
+
const isTestnet = (await chaindataProvider$1.evmNetworkById(chainId))?.isTestnet || false;
|
1236
1509
|
return {
|
1237
|
-
|
1238
|
-
extra: null
|
1510
|
+
isTestnet
|
1239
1511
|
};
|
1240
1512
|
},
|
1241
1513
|
/**
|
1242
1514
|
* This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L338-L343).
|
1243
1515
|
* 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.
|
1244
1516
|
*/
|
1245
|
-
async fetchEvmChainTokens(chainId,
|
1517
|
+
async fetchEvmChainTokens(chainId, chainMeta, moduleConfig) {
|
1518
|
+
const {
|
1519
|
+
isTestnet
|
1520
|
+
} = chainMeta;
|
1246
1521
|
const chainTokens = {};
|
1247
|
-
for (const tokenConfig of tokens ?? []) {
|
1522
|
+
for (const tokenConfig of moduleConfig?.tokens ?? []) {
|
1248
1523
|
const {
|
1249
1524
|
contractAddress,
|
1250
1525
|
symbol: contractSymbol,
|
1251
|
-
decimals: contractDecimals
|
1252
|
-
name: contractName
|
1526
|
+
decimals: contractDecimals
|
1253
1527
|
} = tokenConfig;
|
1254
1528
|
// TODO : in chaindata's build, filter out all tokens that don't have any of these
|
1255
1529
|
if (!contractAddress || !contractSymbol || contractDecimals === undefined) {
|
@@ -1259,29 +1533,26 @@ const EvmErc20Module = hydrate => {
|
|
1259
1533
|
const symbol = tokenConfig?.symbol ?? contractSymbol ?? "ETH";
|
1260
1534
|
const decimals = typeof tokenConfig?.decimals === "number" ? tokenConfig.decimals : typeof contractDecimals === "number" ? contractDecimals : 18;
|
1261
1535
|
if (!symbol || typeof decimals !== "number") continue;
|
1262
|
-
const id =
|
1536
|
+
const id = evmErc20TokenId(chainId, contractAddress);
|
1263
1537
|
const token = {
|
1264
1538
|
id,
|
1265
1539
|
type: "evm-erc20",
|
1266
|
-
|
1540
|
+
isTestnet,
|
1267
1541
|
isDefault: tokenConfig.isDefault ?? true,
|
1268
1542
|
symbol,
|
1269
1543
|
decimals,
|
1270
|
-
|
1271
|
-
logo: tokenConfig?.logo,
|
1544
|
+
logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
|
1272
1545
|
contractAddress,
|
1273
|
-
|
1546
|
+
evmNetwork: {
|
1547
|
+
id: chainId
|
1548
|
+
}
|
1274
1549
|
};
|
1275
1550
|
if (tokenConfig?.symbol) token.symbol = tokenConfig?.symbol;
|
1276
1551
|
if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
|
1552
|
+
if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
|
1277
1553
|
if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
|
1278
1554
|
if (tokenConfig?.noDiscovery) token.noDiscovery = tokenConfig?.noDiscovery;
|
1279
|
-
|
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
|
-
}
|
1555
|
+
chainTokens[token.id] = token;
|
1285
1556
|
}
|
1286
1557
|
return chainTokens;
|
1287
1558
|
},
|
@@ -1293,18 +1564,18 @@ const EvmErc20Module = hydrate => {
|
|
1293
1564
|
const subscriptionInterval = 6_000; // 6_000ms == 6 seconds
|
1294
1565
|
const initDelay = 1_500; // 1_500ms == 1.5 seconds
|
1295
1566
|
const initialisingBalances = new Set();
|
1296
|
-
const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.
|
1567
|
+
const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.evmNetworkId));
|
1297
1568
|
const tokens = await getModuleTokens();
|
1298
1569
|
|
1299
1570
|
// for chains with a zero balance we only call fetchBalances once every 5 subscriptionIntervals
|
1300
1571
|
// if subscriptionInterval is 6 seconds, this means we only poll chains with a zero balance every 30 seconds
|
1301
1572
|
let zeroBalanceSubscriptionIntervalCounter = 0;
|
1302
|
-
const evmNetworks = await chaindataProvider$1.
|
1573
|
+
const evmNetworks = await chaindataProvider$1.evmNetworksById();
|
1303
1574
|
const ethAddressesByToken = Object.fromEntries(Object.entries(addressesByToken).map(([tokenId, addresses]) => {
|
1304
1575
|
const ethAddresses = addresses.filter(util.isEthereumAddress);
|
1305
1576
|
if (ethAddresses.length === 0) return null;
|
1306
1577
|
const token = tokens[tokenId];
|
1307
|
-
const evmNetworkId = token.
|
1578
|
+
const evmNetworkId = token.evmNetwork?.id;
|
1308
1579
|
if (!evmNetworkId) return null;
|
1309
1580
|
return [tokenId, ethAddresses];
|
1310
1581
|
}).filter(x => Boolean(x)));
|
@@ -1430,17 +1701,20 @@ const fetchBalances$3 = async (evmChainConnector, tokenAddressesByNetwork, erc20
|
|
1430
1701
|
results: [],
|
1431
1702
|
errors: []
|
1432
1703
|
};
|
1433
|
-
await Promise.all(Object.entries(tokenAddressesByNetwork).map(async ([
|
1434
|
-
const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(
|
1435
|
-
if (!publicClient) throw new EvmErc20NetworkError(`Could not get rpc provider for evm network ${
|
1436
|
-
const balances = await getEvmTokenBalances(publicClient, networkParams, result.errors, erc20Aggregators[
|
1704
|
+
await Promise.all(Object.entries(tokenAddressesByNetwork).map(async ([evmNetworkId, networkParams]) => {
|
1705
|
+
const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(evmNetworkId);
|
1706
|
+
if (!publicClient) throw new EvmErc20NetworkError(`Could not get rpc provider for evm network ${evmNetworkId}`, evmNetworkId);
|
1707
|
+
const balances = await getEvmTokenBalances(publicClient, networkParams, result.errors, erc20Aggregators[evmNetworkId]);
|
1437
1708
|
|
1438
1709
|
// consider only non null balances in the results
|
1439
1710
|
result.results.push(...balances.filter(util.isTruthy).map((free, i) => ({
|
1440
1711
|
source: "evm-erc20",
|
1441
1712
|
status: "live",
|
1442
1713
|
address: networkParams[i].address,
|
1443
|
-
|
1714
|
+
multiChainId: {
|
1715
|
+
evmChainId: evmNetworkId
|
1716
|
+
},
|
1717
|
+
evmNetworkId,
|
1444
1718
|
tokenId: networkParams[i].token.id,
|
1445
1719
|
value: free
|
1446
1720
|
})));
|
@@ -1511,7 +1785,7 @@ function groupAddressesByTokenByEvmNetwork$1(addressesByToken, tokens) {
|
|
1511
1785
|
log.error(`Token ${tokenId} not found`);
|
1512
1786
|
return byChain;
|
1513
1787
|
}
|
1514
|
-
const chainId = token.
|
1788
|
+
const chainId = token.evmNetwork?.id;
|
1515
1789
|
if (!chainId) {
|
1516
1790
|
log.error(`Token ${tokenId} has no evm network`);
|
1517
1791
|
return byChain;
|
@@ -1524,38 +1798,67 @@ function groupAddressesByTokenByEvmNetwork$1(addressesByToken, tokens) {
|
|
1524
1798
|
|
1525
1799
|
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)"]);
|
1526
1800
|
|
1527
|
-
const moduleType$
|
1528
|
-
const
|
1801
|
+
const moduleType$7 = "evm-native";
|
1802
|
+
const evmNativeTokenId = chainId => `${chainId}-evm-native`.toLowerCase().replace(/ /g, "-");
|
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
|
+
};
|
1529
1808
|
const EvmNativeModule = hydrate => {
|
1530
1809
|
const {
|
1531
1810
|
chainConnectors,
|
1532
1811
|
chaindataProvider: chaindataProvider$1
|
1533
1812
|
} = hydrate;
|
1534
1813
|
const getModuleTokens = async () => {
|
1535
|
-
return await chaindataProvider$1.
|
1814
|
+
return await chaindataProvider$1.tokensByIdForType(moduleType$7);
|
1536
1815
|
};
|
1537
1816
|
return {
|
1538
|
-
...DefaultBalanceModule(moduleType$
|
1817
|
+
...DefaultBalanceModule(moduleType$7),
|
1539
1818
|
get tokens() {
|
1540
|
-
return chaindataProvider$1.
|
1819
|
+
return chaindataProvider$1.tokensByIdForType(moduleType$7);
|
1541
1820
|
},
|
1542
1821
|
/**
|
1543
1822
|
* This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L280-L284).
|
1544
1823
|
* 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.
|
1545
1824
|
*/
|
1546
|
-
async fetchEvmChainMeta(
|
1825
|
+
async fetchEvmChainMeta(chainId) {
|
1826
|
+
const isTestnet = (await chaindataProvider$1.evmNetworkById(chainId))?.isTestnet || false;
|
1547
1827
|
return {
|
1548
|
-
|
1549
|
-
extra: null
|
1828
|
+
isTestnet
|
1550
1829
|
};
|
1551
1830
|
},
|
1552
1831
|
/**
|
1553
1832
|
* This method is currently executed on [a squid](https://github.com/TalismanSociety/chaindata-squid/blob/0ee02818bf5caa7362e3f3664e55ef05ec8df078/src/steps/updateEvmNetworksFromGithub.ts#L338-L343).
|
1554
1833
|
* 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.
|
1555
1834
|
*/
|
1556
|
-
async fetchEvmChainTokens() {
|
1557
|
-
|
1558
|
-
|
1835
|
+
async fetchEvmChainTokens(chainId, chainMeta, moduleConfig) {
|
1836
|
+
const {
|
1837
|
+
isTestnet
|
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
|
+
};
|
1559
1862
|
},
|
1560
1863
|
async subscribeBalances({
|
1561
1864
|
addressesByToken,
|
@@ -1566,11 +1869,11 @@ const EvmNativeModule = hydrate => {
|
|
1566
1869
|
const initDelay = 500; // 500ms == 0.5 seconds
|
1567
1870
|
|
1568
1871
|
const tokens = await getModuleTokens();
|
1569
|
-
const ethAddressesByToken =
|
1872
|
+
const ethAddressesByToken = Object.fromEntries(Object.entries(addressesByToken).map(([tokenId, addresses]) => {
|
1570
1873
|
const ethAddresses = addresses.filter(util.isEthereumAddress);
|
1571
1874
|
if (ethAddresses.length === 0) return null;
|
1572
1875
|
const token = tokens[tokenId];
|
1573
|
-
const evmNetworkId = token.
|
1876
|
+
const evmNetworkId = token.evmNetwork?.id;
|
1574
1877
|
if (!evmNetworkId) return null;
|
1575
1878
|
return [tokenId, ethAddresses];
|
1576
1879
|
}).filter(x => Boolean(x)));
|
@@ -1580,16 +1883,16 @@ const EvmNativeModule = hydrate => {
|
|
1580
1883
|
let zeroBalanceSubscriptionIntervalCounter = 0;
|
1581
1884
|
|
1582
1885
|
// setup initialising balances for all active evm networks
|
1583
|
-
const activeEvmNetworkIds =
|
1886
|
+
const activeEvmNetworkIds = Object.keys(ethAddressesByToken).map(getEvmNetworkIdFromTokenId);
|
1584
1887
|
const initialisingBalances = new Set(activeEvmNetworkIds);
|
1585
|
-
const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.
|
1888
|
+
const positiveBalanceNetworks = new Set(initialBalances?.map(b => b.evmNetworkId));
|
1586
1889
|
const poll = async () => {
|
1587
1890
|
if (!subscriptionActive) return;
|
1588
1891
|
zeroBalanceSubscriptionIntervalCounter = (zeroBalanceSubscriptionIntervalCounter + 1) % 5;
|
1589
1892
|
try {
|
1590
1893
|
// fetch balance for each network sequentially to prevent creating a big queue of http requests (browser can only handle 2 at a time)
|
1591
1894
|
for (const [tokenId, addresses] of Object.entries(ethAddressesByToken)) {
|
1592
|
-
const evmNetworkId =
|
1895
|
+
const evmNetworkId = getEvmNetworkIdFromTokenId(tokenId);
|
1593
1896
|
|
1594
1897
|
// a zero balance network is one that has initialised and does not have a positive balance
|
1595
1898
|
const isZeroBalanceNetwork = !initialisingBalances.has(evmNetworkId) && !positiveBalanceNetworks.has(evmNetworkId);
|
@@ -1609,10 +1912,10 @@ const EvmNativeModule = hydrate => {
|
|
1609
1912
|
log.error(balance.message, balance.networkId);
|
1610
1913
|
initialisingBalances.delete(balance.networkId);
|
1611
1914
|
} else {
|
1612
|
-
if (balance.
|
1613
|
-
initialisingBalances.delete(balance.
|
1915
|
+
if (balance.evmNetworkId) {
|
1916
|
+
initialisingBalances.delete(balance.evmNetworkId);
|
1614
1917
|
if (BigInt(balance.value) > 0n) {
|
1615
|
-
positiveBalanceNetworks.add(balance.
|
1918
|
+
positiveBalanceNetworks.add(balance.evmNetworkId);
|
1616
1919
|
}
|
1617
1920
|
resultBalances.push(balance);
|
1618
1921
|
}
|
@@ -1662,20 +1965,23 @@ const fetchBalances$2 = async (evmChainConnector, addressesByToken, tokens) => {
|
|
1662
1965
|
if (!evmChainConnector) throw new Error(`This module requires an evm chain connector`);
|
1663
1966
|
return Promise.all(Object.entries(addressesByToken).map(async ([tokenId, addresses]) => {
|
1664
1967
|
const token = tokens[tokenId];
|
1665
|
-
const
|
1666
|
-
if (!
|
1667
|
-
const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(
|
1668
|
-
if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${
|
1968
|
+
const evmNetworkId = token.evmNetwork?.id;
|
1969
|
+
if (!evmNetworkId) throw new Error(`Token ${token.id} has no evm network`);
|
1970
|
+
const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(evmNetworkId);
|
1971
|
+
if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${evmNetworkId}`);
|
1669
1972
|
|
1670
1973
|
// fetch all balances
|
1671
1974
|
const freeBalances = await getFreeBalances(publicClient, addresses);
|
1672
1975
|
const balanceResults = addresses.map((address, i) => {
|
1673
|
-
if (freeBalances[i] === "error") return new EvmNativeBalanceError("Could not fetch balance ",
|
1976
|
+
if (freeBalances[i] === "error") return new EvmNativeBalanceError("Could not fetch balance ", evmNetworkId);
|
1674
1977
|
return {
|
1675
1978
|
source: "evm-native",
|
1676
1979
|
status: "live",
|
1677
1980
|
address: address,
|
1678
|
-
|
1981
|
+
multiChainId: {
|
1982
|
+
evmChainId: evmNetworkId
|
1983
|
+
},
|
1984
|
+
evmNetworkId,
|
1679
1985
|
tokenId,
|
1680
1986
|
value: freeBalances[i].toString()
|
1681
1987
|
};
|
@@ -1728,7 +2034,7 @@ async function getFreeBalances(publicClient, addresses) {
|
|
1728
2034
|
});
|
1729
2035
|
} catch (err) {
|
1730
2036
|
const errorMessage = util.hasOwnProperty(err, "shortMessage") ? err.shortMessage : util.hasOwnProperty(err, "message") ? err.message : err;
|
1731
|
-
log.warn(`Failed to get balance from chain ${publicClient.chain
|
2037
|
+
log.warn(`Failed to get balance from chain ${publicClient.chain?.id} for ${ethAddresses.length} addresses: ${errorMessage}`);
|
1732
2038
|
return ethAddresses.map(() => "error");
|
1733
2039
|
}
|
1734
2040
|
}
|
@@ -2296,20 +2602,8 @@ const uniswapV2PairAbi = [{
|
|
2296
2602
|
type: "function"
|
2297
2603
|
}];
|
2298
2604
|
|
2299
|
-
const moduleType$
|
2300
|
-
const
|
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
|
-
});
|
2605
|
+
const moduleType$6 = "evm-uniswapv2";
|
2606
|
+
const evmUniswapV2TokenId = (chainId, contractAddress) => `${chainId}-evm-uniswapv2-${contractAddress}`.toLowerCase();
|
2313
2607
|
const EvmUniswapV2Module = hydrate => {
|
2314
2608
|
const {
|
2315
2609
|
chainConnectors,
|
@@ -2318,16 +2612,19 @@ const EvmUniswapV2Module = hydrate => {
|
|
2318
2612
|
const chainConnector = chainConnectors.evm;
|
2319
2613
|
util$1.assert(chainConnector, "This module requires an evm chain connector");
|
2320
2614
|
return {
|
2321
|
-
...DefaultBalanceModule(moduleType$
|
2322
|
-
async fetchEvmChainMeta(
|
2615
|
+
...DefaultBalanceModule(moduleType$6),
|
2616
|
+
async fetchEvmChainMeta(chainId) {
|
2617
|
+
const isTestnet = (await chaindataProvider$1.evmNetworkById(chainId))?.isTestnet || false;
|
2323
2618
|
return {
|
2324
|
-
|
2325
|
-
extra: null
|
2619
|
+
isTestnet
|
2326
2620
|
};
|
2327
2621
|
},
|
2328
|
-
async fetchEvmChainTokens(chainId,
|
2622
|
+
async fetchEvmChainTokens(chainId, chainMeta, moduleConfig) {
|
2623
|
+
const {
|
2624
|
+
isTestnet
|
2625
|
+
} = chainMeta;
|
2329
2626
|
const tokens = {};
|
2330
|
-
for (const tokenConfig of pools ?? []) {
|
2627
|
+
for (const tokenConfig of moduleConfig?.pools ?? []) {
|
2331
2628
|
const {
|
2332
2629
|
contractAddress,
|
2333
2630
|
decimals,
|
@@ -2338,23 +2635,21 @@ const EvmUniswapV2Module = hydrate => {
|
|
2338
2635
|
tokenAddress0,
|
2339
2636
|
tokenAddress1,
|
2340
2637
|
coingeckoId0,
|
2341
|
-
coingeckoId1
|
2342
|
-
name
|
2638
|
+
coingeckoId1
|
2343
2639
|
} = tokenConfig;
|
2344
2640
|
if (!contractAddress || decimals === undefined || symbol0 === undefined || decimals0 === undefined || symbol1 === undefined || decimals1 === undefined || tokenAddress0 === undefined || tokenAddress1 === undefined) {
|
2345
2641
|
log.warn("ignoring token on chain %s", chainId, tokenConfig);
|
2346
2642
|
continue;
|
2347
2643
|
}
|
2348
|
-
const id =
|
2644
|
+
const id = evmUniswapV2TokenId(chainId, contractAddress);
|
2349
2645
|
const token = {
|
2350
2646
|
id,
|
2351
2647
|
type: "evm-uniswapv2",
|
2352
|
-
|
2648
|
+
isTestnet,
|
2353
2649
|
isDefault: tokenConfig.isDefault ?? false,
|
2354
2650
|
symbol: `${symbol0 ?? "UNKNOWN"}/${symbol1 ?? "UNKNOWN"}`,
|
2355
|
-
name: name ?? `${symbol0 ?? "UNKNOWN"}/${symbol1 ?? "UNKNOWN"}`,
|
2356
2651
|
decimals,
|
2357
|
-
logo: tokenConfig?.logo || chaindataProvider.
|
2652
|
+
logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl("uniswap"),
|
2358
2653
|
symbol0,
|
2359
2654
|
decimals0,
|
2360
2655
|
symbol1,
|
@@ -2364,10 +2659,13 @@ const EvmUniswapV2Module = hydrate => {
|
|
2364
2659
|
tokenAddress1,
|
2365
2660
|
coingeckoId0,
|
2366
2661
|
coingeckoId1,
|
2367
|
-
|
2662
|
+
evmNetwork: {
|
2663
|
+
id: chainId
|
2664
|
+
}
|
2368
2665
|
};
|
2369
2666
|
if (tokenConfig?.symbol) token.symbol = tokenConfig?.symbol;
|
2370
2667
|
if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
|
2668
|
+
if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
|
2371
2669
|
if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
|
2372
2670
|
if (tokenConfig?.noDiscovery) token.noDiscovery = tokenConfig?.noDiscovery;
|
2373
2671
|
tokens[token.id] = token;
|
@@ -2383,9 +2681,9 @@ const EvmUniswapV2Module = hydrate => {
|
|
2383
2681
|
const initDelay = 1_500; // 1_500ms == 1.5 seconds
|
2384
2682
|
|
2385
2683
|
const initialBalancesByNetwork = initialBalances?.reduce((result, b) => {
|
2386
|
-
if (!b.
|
2387
|
-
if (!result[b.
|
2388
|
-
result[b.
|
2684
|
+
if (!b.evmNetworkId) return result;
|
2685
|
+
if (!result[b.evmNetworkId]) result[b.evmNetworkId] = {};
|
2686
|
+
result[b.evmNetworkId][getBalanceId(b)] = b;
|
2389
2687
|
return result;
|
2390
2688
|
}, {});
|
2391
2689
|
const cache = new Map(Object.entries(initialBalancesByNetwork ?? {}));
|
@@ -2393,8 +2691,8 @@ const EvmUniswapV2Module = hydrate => {
|
|
2393
2691
|
// for chains with a zero balance we only call fetchBalances once every 5 subscriptionIntervals
|
2394
2692
|
// if subscriptionInterval is 6 seconds, this means we only poll chains with a zero balance every 30 seconds
|
2395
2693
|
let zeroBalanceSubscriptionIntervalCounter = 0;
|
2396
|
-
const evmNetworks = await chaindataProvider$1.
|
2397
|
-
const tokens = await chaindataProvider$1.
|
2694
|
+
const evmNetworks = await chaindataProvider$1.evmNetworksById();
|
2695
|
+
const tokens = await chaindataProvider$1.tokensById();
|
2398
2696
|
const poll = async () => {
|
2399
2697
|
if (!subscriptionActive) return;
|
2400
2698
|
zeroBalanceSubscriptionIntervalCounter = (zeroBalanceSubscriptionIntervalCounter + 1) % 5;
|
@@ -2437,20 +2735,20 @@ const EvmUniswapV2Module = hydrate => {
|
|
2437
2735
|
},
|
2438
2736
|
async fetchBalances(addressesByToken) {
|
2439
2737
|
if (!chainConnectors.evm) throw new Error(`This module requires an evm chain connector`);
|
2440
|
-
const evmNetworks = await chaindataProvider$1.
|
2441
|
-
const tokens = await chaindataProvider$1.
|
2738
|
+
const evmNetworks = await chaindataProvider$1.evmNetworksById();
|
2739
|
+
const tokens = await chaindataProvider$1.tokensById();
|
2442
2740
|
return fetchBalances$1(chainConnectors.evm, evmNetworks, tokens, addressesByToken);
|
2443
2741
|
}
|
2444
2742
|
};
|
2445
2743
|
};
|
2446
2744
|
const fetchBalances$1 = async (evmChainConnector, evmNetworks, tokens, addressesByToken) => {
|
2447
2745
|
const addressesByTokenGroupedByEvmNetwork = groupAddressesByTokenByEvmNetwork(addressesByToken, tokens);
|
2448
|
-
const balances = (await Promise.allSettled(Object.entries(addressesByTokenGroupedByEvmNetwork).map(async ([
|
2746
|
+
const balances = (await Promise.allSettled(Object.entries(addressesByTokenGroupedByEvmNetwork).map(async ([evmNetworkId, addressesByToken]) => {
|
2449
2747
|
if (!evmChainConnector) throw new Error(`This module requires an evm chain connector`);
|
2450
|
-
const evmNetwork = evmNetworks[
|
2451
|
-
if (!evmNetwork) throw new Error(`Evm network ${
|
2452
|
-
const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(
|
2453
|
-
if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${
|
2748
|
+
const evmNetwork = evmNetworks[evmNetworkId];
|
2749
|
+
if (!evmNetwork) throw new Error(`Evm network ${evmNetworkId} not found`);
|
2750
|
+
const publicClient = await evmChainConnector.getPublicClientForEvmNetwork(evmNetworkId);
|
2751
|
+
if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${evmNetworkId}`);
|
2454
2752
|
const tokensAndAddresses = Object.entries(addressesByToken).reduce((tokensAndAddresses, [tokenId, addresses]) => {
|
2455
2753
|
const token = tokens[tokenId];
|
2456
2754
|
if (!token) {
|
@@ -2472,7 +2770,10 @@ const fetchBalances$1 = async (evmChainConnector, evmNetworks, tokens, addresses
|
|
2472
2770
|
source: "evm-uniswapv2",
|
2473
2771
|
status: "live",
|
2474
2772
|
address: address,
|
2475
|
-
|
2773
|
+
multiChainId: {
|
2774
|
+
evmChainId: evmNetwork.id
|
2775
|
+
},
|
2776
|
+
evmNetworkId,
|
2476
2777
|
tokenId: token.id,
|
2477
2778
|
values: await getPoolBalance(publicClient, token.contractAddress, address)
|
2478
2779
|
}));
|
@@ -2508,7 +2809,7 @@ function groupAddressesByTokenByEvmNetwork(addressesByToken, tokens) {
|
|
2508
2809
|
log.error(`Token ${tokenId} not found`);
|
2509
2810
|
return byChain;
|
2510
2811
|
}
|
2511
|
-
const chainId = token.
|
2812
|
+
const chainId = token.evmNetwork?.id;
|
2512
2813
|
if (!chainId) {
|
2513
2814
|
log.error(`Token ${tokenId} has no evm network`);
|
2514
2815
|
return byChain;
|
@@ -2583,170 +2884,6 @@ async function getPoolBalance(publicClient, contractAddress, accountAddress) {
|
|
2583
2884
|
}
|
2584
2885
|
}
|
2585
2886
|
|
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
|
-
|
2750
2887
|
/**
|
2751
2888
|
* Wraps a BalanceModule's fetch/subscribe methods with a single `balances` method.
|
2752
2889
|
* This `balances` method will subscribe if a callback parameter is provided, or otherwise fetch.
|
@@ -2762,16 +2899,44 @@ async function balances(balanceModule, addressesByToken, callback) {
|
|
2762
2899
|
return await balanceModule.fetchBalances(addressesByToken);
|
2763
2900
|
}
|
2764
2901
|
|
2765
|
-
|
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
|
+
|
2766
2930
|
const buildStorageCoders = ({
|
2767
2931
|
chainIds,
|
2768
2932
|
chains,
|
2769
2933
|
miniMetadatas,
|
2934
|
+
moduleType,
|
2770
2935
|
coders
|
2771
2936
|
}) => new Map([...chainIds].flatMap(chainId => {
|
2772
2937
|
const chain = chains[chainId];
|
2773
2938
|
if (!chain) return [];
|
2774
|
-
const miniMetadata =
|
2939
|
+
const [, miniMetadata] = findChainMeta(miniMetadatas, moduleType, chain);
|
2775
2940
|
if (!miniMetadata) return [];
|
2776
2941
|
if (!miniMetadata.data) return [];
|
2777
2942
|
const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata.data));
|
@@ -2794,28 +2959,6 @@ const buildStorageCoders = ({
|
|
2794
2959
|
return [];
|
2795
2960
|
}
|
2796
2961
|
}));
|
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
|
-
};
|
2819
2962
|
|
2820
2963
|
/**
|
2821
2964
|
* Decodes & unwraps outputs and errors of a given result, contract, and method.
|
@@ -2897,7 +3040,7 @@ const detectTransferMethod = metadataRpc => {
|
|
2897
3040
|
return hasDeprecatedTransferCall ? "transfer" : "transfer_allow_death";
|
2898
3041
|
};
|
2899
3042
|
|
2900
|
-
const getUniqueChainIds = (addressesByToken, tokens) => [...new Set(Object.keys(addressesByToken).map(tokenId => tokens[tokenId]?.
|
3043
|
+
const getUniqueChainIds = (addressesByToken, tokens) => [...new Set(Object.keys(addressesByToken).map(tokenId => tokens[tokenId]?.chain?.id).flatMap(chainId => chainId ? [chainId] : []))];
|
2901
3044
|
|
2902
3045
|
const makeContractCaller = ({
|
2903
3046
|
chainConnector,
|
@@ -3006,15 +3149,8 @@ const decompress = data => {
|
|
3006
3149
|
return JSON.parse(decompressed);
|
3007
3150
|
};
|
3008
3151
|
|
3009
|
-
const moduleType$
|
3010
|
-
const
|
3011
|
-
assetId: chaindataProvider.SubAssetsTokenSchema.shape.assetId,
|
3012
|
-
...TokenConfigBaseSchema.shape
|
3013
|
-
});
|
3014
|
-
const UNSUPPORTED_CHAIN_META$2 = {
|
3015
|
-
miniMetadata: null,
|
3016
|
-
extra: null
|
3017
|
-
};
|
3152
|
+
const moduleType$5 = "substrate-assets";
|
3153
|
+
const subAssetTokenId = (chainId, assetId, tokenSymbol) => `${chainId}-substrate-assets-${assetId}-${tokenSymbol}`.toLowerCase().replace(/ /g, "-");
|
3018
3154
|
const SubAssetsModule = hydrate => {
|
3019
3155
|
const {
|
3020
3156
|
chainConnectors,
|
@@ -3023,10 +3159,16 @@ const SubAssetsModule = hydrate => {
|
|
3023
3159
|
const chainConnector = chainConnectors.substrate;
|
3024
3160
|
util$1.assert(chainConnector, "This module requires a substrate chain connector");
|
3025
3161
|
return {
|
3026
|
-
...DefaultBalanceModule(moduleType$
|
3027
|
-
// TODO make synchronous at the module definition level ?
|
3162
|
+
...DefaultBalanceModule(moduleType$5),
|
3028
3163
|
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
|
3029
|
-
|
3164
|
+
const isTestnet = (await chaindataProvider$1.chainById(chainId))?.isTestnet || false;
|
3165
|
+
if (metadataRpc === undefined) return {
|
3166
|
+
isTestnet
|
3167
|
+
};
|
3168
|
+
if ((moduleConfig?.tokens ?? []).length < 1) return {
|
3169
|
+
isTestnet
|
3170
|
+
};
|
3171
|
+
const metadataVersion = scale.getMetadataVersion(metadataRpc);
|
3030
3172
|
const metadata = scale.decAnyMetadata(metadataRpc);
|
3031
3173
|
scale.compactMetadata(metadata, [{
|
3032
3174
|
pallet: "Assets",
|
@@ -3034,98 +3176,82 @@ const SubAssetsModule = hydrate => {
|
|
3034
3176
|
}]);
|
3035
3177
|
const miniMetadata = scale.encodeMetadata(metadata);
|
3036
3178
|
return {
|
3179
|
+
isTestnet,
|
3037
3180
|
miniMetadata,
|
3038
|
-
|
3181
|
+
metadataVersion
|
3039
3182
|
};
|
3040
3183
|
},
|
3041
|
-
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig
|
3042
|
-
if (
|
3184
|
+
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
|
3185
|
+
if ((moduleConfig?.tokens ?? []).length < 1) return {};
|
3043
3186
|
const {
|
3044
|
-
|
3187
|
+
isTestnet,
|
3188
|
+
miniMetadata,
|
3189
|
+
metadataVersion
|
3045
3190
|
} = chainMeta;
|
3046
|
-
if (
|
3191
|
+
if (miniMetadata === undefined || metadataVersion === undefined) return {};
|
3192
|
+
if (metadataVersion < 14) return {};
|
3047
3193
|
const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata));
|
3048
3194
|
const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
|
3049
3195
|
const assetCoder = scaleBuilder.buildStorage("Assets", "Asset");
|
3050
3196
|
const metadataCoder = scaleBuilder.buildStorage("Assets", "Metadata");
|
3051
|
-
const
|
3052
|
-
for (const tokenConfig of tokens ?? []) {
|
3197
|
+
const tokens = {};
|
3198
|
+
for (const tokenConfig of moduleConfig?.tokens ?? []) {
|
3053
3199
|
try {
|
3054
|
-
const assetId =
|
3200
|
+
const assetId = typeof tokenConfig.assetId === "number" ? tokenConfig.assetId.toString() : tokenConfig.assetId;
|
3055
3201
|
const assetStateKey = tryEncode(assetCoder, BigInt(assetId)) ?? tryEncode(assetCoder, assetId);
|
3056
3202
|
const metadataStateKey = tryEncode(metadataCoder, BigInt(assetId)) ?? tryEncode(metadataCoder, assetId);
|
3057
3203
|
if (assetStateKey === null || metadataStateKey === null) throw new Error(`Failed to encode stateKey for asset ${assetId} on chain ${chainId}`);
|
3058
3204
|
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)]);
|
3059
3205
|
const existentialDeposit = assetsAsset?.min_balance?.toString?.() ?? "0";
|
3060
3206
|
const symbol = assetsMetadata?.symbol?.asText?.() ?? "Unit";
|
3061
|
-
const name = assetsMetadata?.name?.asText?.() ?? symbol;
|
3062
3207
|
const decimals = assetsMetadata?.decimals ?? 0;
|
3063
3208
|
const isFrozen = assetsMetadata?.is_frozen ?? false;
|
3064
|
-
const id =
|
3209
|
+
const id = subAssetTokenId(chainId, assetId, symbol);
|
3065
3210
|
const token = {
|
3066
3211
|
id,
|
3067
3212
|
type: "substrate-assets",
|
3068
|
-
|
3213
|
+
isTestnet,
|
3069
3214
|
isDefault: tokenConfig?.isDefault ?? true,
|
3070
|
-
symbol
|
3071
|
-
name: tokenConfig?.name ?? name,
|
3215
|
+
symbol,
|
3072
3216
|
decimals,
|
3073
|
-
logo: tokenConfig?.logo,
|
3217
|
+
logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
|
3074
3218
|
existentialDeposit,
|
3075
3219
|
assetId,
|
3076
3220
|
isFrozen,
|
3077
|
-
|
3221
|
+
chain: {
|
3222
|
+
id: chainId
|
3223
|
+
}
|
3078
3224
|
};
|
3079
3225
|
if (tokenConfig?.symbol) {
|
3080
3226
|
token.symbol = tokenConfig?.symbol;
|
3081
|
-
token.id =
|
3227
|
+
token.id = subAssetTokenId(chainId, assetId, token.symbol);
|
3082
3228
|
}
|
3083
3229
|
if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
|
3230
|
+
if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
|
3084
3231
|
if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
|
3085
|
-
|
3232
|
+
tokens[token.id] = token;
|
3086
3233
|
} catch (error) {
|
3087
3234
|
log.error(`Failed to build substrate-assets token ${tokenConfig.assetId} (${tokenConfig.symbol}) on ${chainId}`, error);
|
3088
3235
|
continue;
|
3089
3236
|
}
|
3090
3237
|
}
|
3091
|
-
return
|
3238
|
+
return tokens;
|
3092
3239
|
},
|
3093
3240
|
// TODO: Don't create empty subscriptions
|
3094
3241
|
async subscribeBalances({
|
3095
3242
|
addressesByToken
|
3096
3243
|
}, callback) {
|
3097
|
-
const
|
3098
|
-
|
3099
|
-
if (
|
3100
|
-
|
3101
|
-
|
3102
|
-
}
|
3103
|
-
|
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
|
-
};
|
3244
|
+
const queries = await buildQueries$4(chaindataProvider$1, addressesByToken);
|
3245
|
+
const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
|
3246
|
+
if (error) return callback(error);
|
3247
|
+
const balances = result?.filter(b => b !== null) ?? [];
|
3248
|
+
if (balances.length > 0) callback(null, new Balances(balances));
|
3249
|
+
});
|
3250
|
+
return unsubscribe;
|
3125
3251
|
},
|
3126
3252
|
async fetchBalances(addressesByToken) {
|
3127
3253
|
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
3128
|
-
const queries = await buildQueries$
|
3254
|
+
const queries = await buildQueries$4(chaindataProvider$1, addressesByToken);
|
3129
3255
|
const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
|
3130
3256
|
const balances = result?.filter(b => b !== null) ?? [];
|
3131
3257
|
return new Balances(balances);
|
@@ -3146,10 +3272,11 @@ const SubAssetsModule = hydrate => {
|
|
3146
3272
|
transferMethod,
|
3147
3273
|
userExtensions
|
3148
3274
|
}) {
|
3149
|
-
const token = await chaindataProvider$1.
|
3275
|
+
const token = await chaindataProvider$1.tokenById(tokenId);
|
3150
3276
|
util$1.assert(token, `Token ${tokenId} not found in store`);
|
3151
|
-
|
3152
|
-
const
|
3277
|
+
if (token.type !== "substrate-assets") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
|
3278
|
+
const chainId = token.chain.id;
|
3279
|
+
const chain = await chaindataProvider$1.chainById(chainId);
|
3153
3280
|
util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
|
3154
3281
|
const {
|
3155
3282
|
genesisHash
|
@@ -3194,16 +3321,23 @@ const SubAssetsModule = hydrate => {
|
|
3194
3321
|
}
|
3195
3322
|
};
|
3196
3323
|
};
|
3197
|
-
async function
|
3198
|
-
const
|
3199
|
-
const
|
3200
|
-
const
|
3201
|
-
|
3202
|
-
const
|
3203
|
-
|
3324
|
+
async function buildQueries$4(chaindataProvider, addressesByToken) {
|
3325
|
+
const allChains = await chaindataProvider.chainsById();
|
3326
|
+
const tokens = await chaindataProvider.tokensById();
|
3327
|
+
const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
|
3328
|
+
const uniqueChainIds = getUniqueChainIds(addressesByToken, tokens);
|
3329
|
+
const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
|
3330
|
+
const chainStorageCoders = buildStorageCoders({
|
3331
|
+
chainIds: uniqueChainIds,
|
3332
|
+
chains,
|
3333
|
+
miniMetadatas,
|
3334
|
+
moduleType: "substrate-assets",
|
3335
|
+
coders: {
|
3336
|
+
storage: ["Assets", "Account"]
|
3337
|
+
}
|
3204
3338
|
});
|
3205
3339
|
return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
|
3206
|
-
const token =
|
3340
|
+
const token = tokens[tokenId];
|
3207
3341
|
if (!token) {
|
3208
3342
|
log.warn(`Token ${tokenId} not found`);
|
3209
3343
|
return [];
|
@@ -3212,22 +3346,27 @@ async function buildNetworkQueries$2(networkId, chainConnector, chaindataProvide
|
|
3212
3346
|
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
3213
3347
|
return [];
|
3214
3348
|
}
|
3215
|
-
|
3349
|
+
const chainId = token.chain?.id;
|
3350
|
+
if (!chainId) {
|
3351
|
+
log.warn(`Token ${tokenId} has no chain`);
|
3352
|
+
return [];
|
3353
|
+
}
|
3354
|
+
const chain = chains[chainId];
|
3216
3355
|
if (!chain) {
|
3217
|
-
log.warn(`Chain ${
|
3356
|
+
log.warn(`Chain ${chainId} for token ${tokenId} not found`);
|
3218
3357
|
return [];
|
3219
3358
|
}
|
3220
3359
|
return addresses.flatMap(address => {
|
3221
|
-
const scaleCoder =
|
3222
|
-
const stateKey = tryEncode(scaleCoder,
|
3360
|
+
const scaleCoder = chainStorageCoders.get(chainId)?.storage;
|
3361
|
+
const stateKey = tryEncode(scaleCoder, BigInt(token.assetId), address) ?? tryEncode(scaleCoder, token.assetId, address);
|
3223
3362
|
if (!stateKey) {
|
3224
|
-
log.warn(`Invalid assetId / address in ${
|
3363
|
+
log.warn(`Invalid assetId / address in ${chainId} storage query ${token.assetId} / ${address}`);
|
3225
3364
|
return [];
|
3226
3365
|
}
|
3227
3366
|
const decodeResult = change => {
|
3228
3367
|
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
3229
3368
|
|
3230
|
-
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${
|
3369
|
+
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${chainId}`) ?? {
|
3231
3370
|
balance: 0n,
|
3232
3371
|
status: {
|
3233
3372
|
type: "Liquid"
|
@@ -3261,30 +3400,22 @@ async function buildNetworkQueries$2(networkId, chainConnector, chaindataProvide
|
|
3261
3400
|
source: "substrate-assets",
|
3262
3401
|
status: "live",
|
3263
3402
|
address,
|
3264
|
-
|
3403
|
+
multiChainId: {
|
3404
|
+
subChainId: chainId
|
3405
|
+
},
|
3406
|
+
chainId,
|
3265
3407
|
tokenId: token.id,
|
3266
3408
|
values: balanceValues
|
3267
3409
|
};
|
3268
3410
|
};
|
3269
3411
|
return {
|
3270
|
-
chainId
|
3412
|
+
chainId,
|
3271
3413
|
stateKey,
|
3272
3414
|
decodeResult
|
3273
3415
|
};
|
3274
3416
|
});
|
3275
3417
|
});
|
3276
3418
|
}
|
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
|
-
}
|
3288
3419
|
// NOTE: Different chains need different formats for assetId when encoding the stateKey
|
3289
3420
|
// E.g. Polkadot Asset Hub needs it to be a string, Astar needs it to be a bigint
|
3290
3421
|
//
|
@@ -3297,16 +3428,9 @@ const tryEncode = (scaleCoder, ...args) => {
|
|
3297
3428
|
}
|
3298
3429
|
};
|
3299
3430
|
|
3300
|
-
const moduleType$
|
3301
|
-
const
|
3302
|
-
|
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 => {
|
3431
|
+
const moduleType$4 = "substrate-equilibrium";
|
3432
|
+
const subEquilibriumTokenId = (chainId, tokenSymbol) => `${chainId}-substrate-equilibrium-${tokenSymbol}`.toLowerCase().replace(/ /g, "-");
|
3433
|
+
const SubEquilibriumModule = hydrate => {
|
3310
3434
|
const {
|
3311
3435
|
chainConnectors,
|
3312
3436
|
chaindataProvider: chaindataProvider$1
|
@@ -3314,113 +3438,374 @@ const SubForeignAssetsModule = hydrate => {
|
|
3314
3438
|
const chainConnector = chainConnectors.substrate;
|
3315
3439
|
util$1.assert(chainConnector, "This module requires a substrate chain connector");
|
3316
3440
|
return {
|
3317
|
-
...DefaultBalanceModule(moduleType$
|
3441
|
+
...DefaultBalanceModule(moduleType$4),
|
3318
3442
|
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
|
3319
|
-
|
3443
|
+
const isTestnet = (await chaindataProvider$1.chainById(chainId))?.isTestnet || false;
|
3444
|
+
if (metadataRpc === undefined) return {
|
3445
|
+
isTestnet
|
3446
|
+
};
|
3447
|
+
if (moduleConfig?.disable !== false) return {
|
3448
|
+
isTestnet
|
3449
|
+
}; // default to disabled
|
3450
|
+
|
3320
3451
|
const metadataVersion = scale.getMetadataVersion(metadataRpc);
|
3321
|
-
if (metadataVersion < 14) return UNSUPPORTED_CHAIN_META$1;
|
3322
3452
|
const metadata = scale.decAnyMetadata(metadataRpc);
|
3323
3453
|
scale.compactMetadata(metadata, [{
|
3324
|
-
pallet: "
|
3325
|
-
items: ["
|
3454
|
+
pallet: "EqAssets",
|
3455
|
+
items: ["Assets"]
|
3456
|
+
}, {
|
3457
|
+
pallet: "System",
|
3458
|
+
items: ["Account"]
|
3326
3459
|
}]);
|
3327
3460
|
const miniMetadata = scale.encodeMetadata(metadata);
|
3328
3461
|
return {
|
3462
|
+
isTestnet,
|
3329
3463
|
miniMetadata,
|
3330
|
-
|
3464
|
+
metadataVersion
|
3331
3465
|
};
|
3332
3466
|
},
|
3333
|
-
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig
|
3334
|
-
|
3467
|
+
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
|
3468
|
+
// default to disabled
|
3469
|
+
if (moduleConfig?.disable !== false) return {};
|
3335
3470
|
const {
|
3336
|
-
|
3471
|
+
isTestnet,
|
3472
|
+
miniMetadata,
|
3473
|
+
metadataVersion
|
3337
3474
|
} = chainMeta;
|
3338
|
-
if (
|
3339
|
-
|
3340
|
-
|
3341
|
-
|
3342
|
-
|
3343
|
-
|
3344
|
-
|
3345
|
-
|
3346
|
-
|
3347
|
-
|
3348
|
-
|
3349
|
-
|
3350
|
-
|
3351
|
-
|
3352
|
-
|
3353
|
-
|
3354
|
-
|
3475
|
+
if (miniMetadata === undefined || metadataVersion === undefined) return {};
|
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 {};
|
3740
|
+
const metadata = scale.decAnyMetadata(miniMetadata);
|
3741
|
+
const unifiedMetadata = scale.unifyMetadata(metadata);
|
3742
|
+
const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(unifiedMetadata));
|
3743
|
+
const assetCoder = scaleBuilder.buildStorage("ForeignAssets", "Asset");
|
3744
|
+
const metadataCoder = scaleBuilder.buildStorage("ForeignAssets", "Metadata");
|
3745
|
+
const tokens = {};
|
3746
|
+
for (const tokenConfig of moduleConfig?.tokens ?? []) {
|
3747
|
+
try {
|
3748
|
+
const onChainId = (() => {
|
3749
|
+
try {
|
3750
|
+
return scale.papiParse(tokenConfig.onChainId);
|
3751
|
+
} catch (error) {
|
3752
|
+
return tokenConfig.onChainId;
|
3753
|
+
}
|
3754
|
+
})();
|
3755
|
+
if (onChainId === undefined) continue;
|
3355
3756
|
const assetStateKey = assetCoder.keys.enc(onChainId);
|
3356
3757
|
const metadataStateKey = metadataCoder.keys.enc(onChainId);
|
3357
3758
|
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)]);
|
3358
3759
|
const existentialDeposit = assetsAsset?.min_balance?.toString?.() ?? "0";
|
3359
3760
|
const symbol = assetsMetadata?.symbol?.asText?.() ?? "Unit";
|
3360
|
-
const name = assetsMetadata?.name?.asText?.() ?? symbol;
|
3361
3761
|
const decimals = assetsMetadata?.decimals ?? 0;
|
3362
3762
|
const isFrozen = assetsMetadata?.is_frozen ?? false;
|
3363
|
-
const id =
|
3763
|
+
const id = subForeignAssetTokenId(chainId, symbol);
|
3364
3764
|
const token = {
|
3365
3765
|
id,
|
3366
3766
|
type: "substrate-foreignassets",
|
3367
|
-
|
3767
|
+
isTestnet,
|
3368
3768
|
isDefault: tokenConfig?.isDefault ?? true,
|
3369
3769
|
symbol,
|
3370
3770
|
decimals,
|
3371
|
-
|
3372
|
-
logo: tokenConfig?.logo,
|
3771
|
+
logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
|
3373
3772
|
existentialDeposit,
|
3374
3773
|
onChainId: tokenConfig.onChainId,
|
3375
3774
|
isFrozen,
|
3376
|
-
|
3775
|
+
chain: {
|
3776
|
+
id: chainId
|
3777
|
+
}
|
3377
3778
|
};
|
3779
|
+
if (tokenConfig?.symbol) {
|
3780
|
+
token.symbol = tokenConfig?.symbol;
|
3781
|
+
token.id = subForeignAssetTokenId(chainId, token.symbol);
|
3782
|
+
}
|
3378
3783
|
if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
|
3784
|
+
if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
|
3379
3785
|
if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
|
3380
|
-
|
3786
|
+
tokens[token.id] = token;
|
3381
3787
|
} catch (error) {
|
3382
3788
|
log.error(`Failed to build substrate-foreignassets token ${tokenConfig.onChainId} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
|
3383
3789
|
continue;
|
3384
3790
|
}
|
3385
3791
|
}
|
3386
|
-
return
|
3792
|
+
return tokens;
|
3387
3793
|
},
|
3388
3794
|
// TODO: Don't create empty subscriptions
|
3389
3795
|
async subscribeBalances({
|
3390
3796
|
addressesByToken
|
3391
3797
|
}, callback) {
|
3392
|
-
const
|
3393
|
-
|
3394
|
-
if (
|
3395
|
-
|
3396
|
-
|
3397
|
-
}
|
3398
|
-
|
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
|
-
};
|
3798
|
+
const queries = await buildQueries$2(chaindataProvider$1, addressesByToken);
|
3799
|
+
const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
|
3800
|
+
if (error) return callback(error);
|
3801
|
+
const balances = result?.filter(b => b !== null) ?? [];
|
3802
|
+
if (balances.length > 0) callback(null, new Balances(balances));
|
3803
|
+
});
|
3804
|
+
return unsubscribe;
|
3420
3805
|
},
|
3421
3806
|
async fetchBalances(addressesByToken) {
|
3422
3807
|
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
3423
|
-
const queries = await buildQueries$2(
|
3808
|
+
const queries = await buildQueries$2(chaindataProvider$1, addressesByToken);
|
3424
3809
|
const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
|
3425
3810
|
const balances = result?.filter(b => b !== null) ?? [];
|
3426
3811
|
return new Balances(balances);
|
@@ -3432,10 +3817,11 @@ const SubForeignAssetsModule = hydrate => {
|
|
3432
3817
|
transferMethod,
|
3433
3818
|
metadataRpc
|
3434
3819
|
}) {
|
3435
|
-
const token = await chaindataProvider$1.
|
3820
|
+
const token = await chaindataProvider$1.tokenById(tokenId);
|
3436
3821
|
util$1.assert(token, `Token ${tokenId} not found in store`);
|
3437
|
-
|
3438
|
-
const
|
3822
|
+
if (token.type !== "substrate-foreignassets") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
|
3823
|
+
const chainId = token.chain.id;
|
3824
|
+
const chain = await chaindataProvider$1.chainById(chainId);
|
3439
3825
|
util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
|
3440
3826
|
const onChainId = (() => {
|
3441
3827
|
try {
|
@@ -3475,16 +3861,23 @@ const SubForeignAssetsModule = hydrate => {
|
|
3475
3861
|
}
|
3476
3862
|
};
|
3477
3863
|
};
|
3478
|
-
async function
|
3479
|
-
const
|
3480
|
-
const
|
3481
|
-
const
|
3482
|
-
|
3483
|
-
const
|
3484
|
-
|
3864
|
+
async function buildQueries$2(chaindataProvider, addressesByToken) {
|
3865
|
+
const allChains = await chaindataProvider.chainsById();
|
3866
|
+
const tokens = await chaindataProvider.tokensById();
|
3867
|
+
const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
|
3868
|
+
const uniqueChainIds = getUniqueChainIds(addressesByToken, tokens);
|
3869
|
+
const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
|
3870
|
+
const chainStorageCoders = buildStorageCoders({
|
3871
|
+
chainIds: uniqueChainIds,
|
3872
|
+
chains,
|
3873
|
+
miniMetadatas,
|
3874
|
+
moduleType: "substrate-foreignassets",
|
3875
|
+
coders: {
|
3876
|
+
storage: ["ForeignAssets", "Account"]
|
3877
|
+
}
|
3485
3878
|
});
|
3486
3879
|
return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
|
3487
|
-
const token =
|
3880
|
+
const token = tokens[tokenId];
|
3488
3881
|
if (!token) {
|
3489
3882
|
log.warn(`Token ${tokenId} not found`);
|
3490
3883
|
return [];
|
@@ -3493,12 +3886,18 @@ async function buildNetworkQueries$1(networkId, chainConnector, chaindataProvide
|
|
3493
3886
|
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
3494
3887
|
return [];
|
3495
3888
|
}
|
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];
|
3496
3895
|
if (!chain) {
|
3497
|
-
log.warn(`Chain ${
|
3896
|
+
log.warn(`Chain ${chainId} for token ${tokenId} not found`);
|
3498
3897
|
return [];
|
3499
3898
|
}
|
3500
3899
|
return addresses.flatMap(address => {
|
3501
|
-
const scaleCoder =
|
3900
|
+
const scaleCoder = chainStorageCoders.get(chainId)?.storage;
|
3502
3901
|
const onChainId = (() => {
|
3503
3902
|
try {
|
3504
3903
|
return scale.papiParse(token.onChainId);
|
@@ -3506,12 +3905,12 @@ async function buildNetworkQueries$1(networkId, chainConnector, chaindataProvide
|
|
3506
3905
|
return token.onChainId;
|
3507
3906
|
}
|
3508
3907
|
})();
|
3509
|
-
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${
|
3908
|
+
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${chainId} storage query ${address} / ${token.onChainId}`, onChainId, address);
|
3510
3909
|
if (!stateKey) return [];
|
3511
3910
|
const decodeResult = change => {
|
3512
3911
|
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
3513
3912
|
|
3514
|
-
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-foreignassets balance on chain ${
|
3913
|
+
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-foreignassets balance on chain ${chainId}`) ?? {
|
3515
3914
|
balance: 0n,
|
3516
3915
|
status: {
|
3517
3916
|
type: "Liquid"
|
@@ -3545,54 +3944,29 @@ async function buildNetworkQueries$1(networkId, chainConnector, chaindataProvide
|
|
3545
3944
|
source: "substrate-foreignassets",
|
3546
3945
|
status: "live",
|
3547
3946
|
address,
|
3548
|
-
|
3947
|
+
multiChainId: {
|
3948
|
+
subChainId: chainId
|
3949
|
+
},
|
3950
|
+
chainId,
|
3549
3951
|
tokenId: token.id,
|
3550
3952
|
values: balanceValues
|
3551
3953
|
};
|
3552
3954
|
};
|
3553
3955
|
return {
|
3554
|
-
chainId
|
3956
|
+
chainId,
|
3555
3957
|
stateKey,
|
3556
3958
|
decodeResult
|
3557
3959
|
};
|
3558
3960
|
});
|
3559
3961
|
});
|
3560
3962
|
}
|
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
|
-
};
|
3582
3963
|
|
3583
3964
|
async function subscribeBase(queries, chainConnector, callback) {
|
3584
|
-
|
3585
|
-
|
3586
|
-
|
3587
|
-
|
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
|
-
}
|
3965
|
+
const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
|
3966
|
+
if (error) callback(error);
|
3967
|
+
if (result && result.length > 0) callback(null, result);
|
3968
|
+
});
|
3969
|
+
return unsubscribe;
|
3596
3970
|
}
|
3597
3971
|
|
3598
3972
|
/**
|
@@ -3608,6 +3982,262 @@ const asObservable = handler => (...args) => new rxjs.Observable(subscriber => {
|
|
3608
3982
|
return unsubscribe;
|
3609
3983
|
});
|
3610
3984
|
|
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
|
+
|
3611
4241
|
/**
|
3612
4242
|
* Each nominationPool in the nominationPools pallet has access to some accountIds which have no
|
3613
4243
|
* associated private key. Instead, they are derived from this function.
|
@@ -3627,293 +4257,280 @@ const nompoolAccountId = (palletId, poolId, index) => {
|
|
3627
4257
|
/** The stash account for the nomination pool */
|
3628
4258
|
const nompoolStashAccountId = (palletId, poolId) => nompoolAccountId(palletId, poolId, 0);
|
3629
4259
|
|
3630
|
-
|
3631
|
-
|
3632
|
-
|
3633
|
-
|
3634
|
-
|
3635
|
-
|
3636
|
-
|
3637
|
-
|
3638
|
-
const
|
3639
|
-
|
3640
|
-
|
3641
|
-
|
4260
|
+
async function subscribeNompoolStaking(chaindataProvider, chainConnector, addressesByToken, callback) {
|
4261
|
+
const allChains = await chaindataProvider.chainsById();
|
4262
|
+
const tokens = await chaindataProvider.tokensById();
|
4263
|
+
const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
|
4264
|
+
const nomPoolTokenIds = Object.entries(tokens).filter(([, token]) => {
|
4265
|
+
// ignore non-native tokens
|
4266
|
+
if (token.type !== "substrate-native") return false;
|
4267
|
+
// ignore tokens on chains with no nompools pallet
|
4268
|
+
const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", allChains[token.chain.id]);
|
4269
|
+
return typeof chainMeta?.nominationPoolsPalletId === "string";
|
4270
|
+
}).map(([tokenId]) => tokenId);
|
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"]
|
3642
4290
|
}
|
3643
|
-
|
3644
|
-
|
3645
|
-
|
3646
|
-
|
3647
|
-
|
3648
|
-
|
3649
|
-
|
3650
|
-
|
3651
|
-
|
3652
|
-
|
3653
|
-
|
3654
|
-
|
3655
|
-
|
3656
|
-
|
3657
|
-
|
3658
|
-
|
3659
|
-
|
3660
|
-
const
|
3661
|
-
|
3662
|
-
|
3663
|
-
|
3664
|
-
|
3665
|
-
|
3666
|
-
|
3667
|
-
|
3668
|
-
|
3669
|
-
|
3670
|
-
|
3671
|
-
|
3672
|
-
|
3673
|
-
|
3674
|
-
|
3675
|
-
|
3676
|
-
|
3677
|
-
|
3678
|
-
|
3679
|
-
|
3680
|
-
|
3681
|
-
|
3682
|
-
|
3683
|
-
|
3684
|
-
|
3685
|
-
|
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
|
-
});
|
3719
|
-
return {
|
3720
|
-
tokenId,
|
3721
|
-
address,
|
3722
|
-
poolId,
|
3723
|
-
points,
|
3724
|
-
unbondingEras
|
3725
|
-
};
|
3726
|
-
};
|
3727
|
-
return {
|
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?.();
|
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;
|
4312
|
+
}
|
4313
|
+
const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", chain);
|
4314
|
+
const {
|
4315
|
+
nominationPoolsPalletId
|
4316
|
+
} = chainMeta ?? {};
|
4317
|
+
const subscribePoolMembers = (addresses, callback) => {
|
4318
|
+
const scaleCoder = chainStorageCoders.get(chainId)?.poolMembers;
|
4319
|
+
const queries = addresses.flatMap(address => {
|
4320
|
+
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address in ${chainId} poolMembers query ${address}`, address);
|
4321
|
+
if (!stateKey) return [];
|
4322
|
+
const decodeResult = change => {
|
4323
|
+
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
4324
|
+
|
4325
|
+
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode poolMembers on chain ${chainId}`);
|
4326
|
+
const poolId = decoded?.pool_id?.toString?.();
|
4327
|
+
const points = decoded?.points?.toString?.();
|
4328
|
+
const unbondingEras = Array.from(decoded?.unbonding_eras ?? []).flatMap(entry => {
|
4329
|
+
if (entry === undefined) return [];
|
4330
|
+
const [key, value] = Array.from(entry);
|
4331
|
+
const era = key?.toString?.();
|
4332
|
+
const amount = value?.toString?.();
|
4333
|
+
if (typeof era !== "string" || typeof amount !== "string") return [];
|
3800
4334
|
return {
|
3801
|
-
|
3802
|
-
|
4335
|
+
era,
|
4336
|
+
amount
|
3803
4337
|
};
|
3804
|
-
};
|
4338
|
+
});
|
3805
4339
|
return {
|
3806
|
-
|
3807
|
-
|
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 {
|
3817
|
-
poolId,
|
3818
|
-
points,
|
3819
|
-
unbondingEras
|
3820
|
-
} = poolMembers;
|
3821
|
-
if (typeof poolId === "string" && typeof points === "string") state.set(poolMembers.address, {
|
4340
|
+
tokenId,
|
4341
|
+
address,
|
3822
4342
|
poolId,
|
3823
4343
|
points,
|
3824
4344
|
unbondingEras
|
3825
|
-
}
|
3826
|
-
}
|
3827
|
-
return
|
3828
|
-
|
3829
|
-
|
3830
|
-
|
3831
|
-
|
3832
|
-
|
3833
|
-
const
|
3834
|
-
|
3835
|
-
|
4345
|
+
};
|
4346
|
+
};
|
4347
|
+
return {
|
4348
|
+
chainId,
|
4349
|
+
stateKey,
|
4350
|
+
decodeResult
|
4351
|
+
};
|
4352
|
+
});
|
4353
|
+
const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
|
4354
|
+
return () => subscription.then(unsubscribe => unsubscribe());
|
4355
|
+
};
|
4356
|
+
const subscribePoolPoints = (poolIds, callback) => {
|
4357
|
+
if (poolIds.length === 0) callback(null, []);
|
4358
|
+
const scaleCoder = chainStorageCoders.get(chainId)?.bondedPools;
|
4359
|
+
const queries = poolIds.flatMap(poolId => {
|
4360
|
+
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} bondedPools query ${poolId}`, poolId);
|
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 {
|
3836
4368
|
poolId,
|
3837
4369
|
points
|
3838
|
-
}
|
3839
|
-
|
3840
|
-
|
3841
|
-
|
3842
|
-
|
3843
|
-
|
3844
|
-
|
3845
|
-
|
4370
|
+
};
|
4371
|
+
};
|
4372
|
+
return {
|
4373
|
+
chainId,
|
4374
|
+
stateKey,
|
4375
|
+
decodeResult
|
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 {
|
3846
4395
|
poolId,
|
3847
4396
|
activeStake
|
3848
|
-
}
|
3849
|
-
|
3850
|
-
|
3851
|
-
|
3852
|
-
|
3853
|
-
|
3854
|
-
|
3855
|
-
|
4397
|
+
};
|
4398
|
+
};
|
4399
|
+
return {
|
4400
|
+
chainId,
|
4401
|
+
stateKey,
|
4402
|
+
decodeResult
|
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 {
|
3856
4421
|
poolId,
|
3857
4422
|
metadata
|
3858
|
-
}
|
3859
|
-
|
3860
|
-
|
3861
|
-
|
3862
|
-
|
3863
|
-
|
3864
|
-
|
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",
|
3883
|
-
type: "nompool",
|
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)
|
4423
|
+
};
|
4424
|
+
};
|
4425
|
+
return {
|
4426
|
+
chainId,
|
4427
|
+
stateKey,
|
4428
|
+
decodeResult
|
4429
|
+
};
|
3907
4430
|
});
|
3908
|
-
|
3909
|
-
|
3910
|
-
|
3911
|
-
|
3912
|
-
|
3913
|
-
|
4431
|
+
const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
|
4432
|
+
return () => subscription.then(unsubscribe => unsubscribe());
|
4433
|
+
};
|
4434
|
+
const poolMembersByAddress$ = asObservable(subscribePoolMembers)(addresses).pipe(rxjs.scan((state, next) => {
|
4435
|
+
for (const poolMembers of next) {
|
4436
|
+
const {
|
4437
|
+
poolId,
|
4438
|
+
points,
|
4439
|
+
unbondingEras
|
4440
|
+
} = poolMembers;
|
4441
|
+
if (typeof poolId === "string" && typeof points === "string") state.set(poolMembers.address, {
|
4442
|
+
poolId,
|
4443
|
+
points,
|
4444
|
+
unbondingEras
|
4445
|
+
});else state.set(poolMembers.address, null);
|
4446
|
+
}
|
4447
|
+
return state;
|
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: {
|
4510
|
+
type: "nompool",
|
4511
|
+
poolId: parsedPoolId,
|
4512
|
+
description: poolMetadata
|
4513
|
+
}
|
4514
|
+
}, {
|
4515
|
+
source: "nompools-staking",
|
4516
|
+
type: "nompool",
|
4517
|
+
label: "nompools-unbonding",
|
4518
|
+
amount: unbondingAmount.toString(),
|
4519
|
+
meta: {
|
4520
|
+
poolId: parsedPoolId,
|
4521
|
+
description: poolMetadata,
|
4522
|
+
unbonding: true
|
4523
|
+
}
|
4524
|
+
}]
|
4525
|
+
};
|
4526
|
+
}).filter(util.isNotNil);
|
4527
|
+
if (balances.length > 0) callback(null, balances);
|
4528
|
+
},
|
4529
|
+
error: error => callback(error)
|
3914
4530
|
});
|
3915
|
-
|
4531
|
+
resultUnsubscribes.push(() => subscription.unsubscribe());
|
3916
4532
|
}
|
4533
|
+
return () => resultUnsubscribes.forEach(unsub => unsub());
|
3917
4534
|
}
|
3918
4535
|
|
3919
4536
|
const SUBTENSOR_ROOT_NETUID = 0;
|
@@ -3955,236 +4572,225 @@ const calculateTaoFromDynamicInfo = ({
|
|
3955
4572
|
});
|
3956
4573
|
};
|
3957
4574
|
|
3958
|
-
|
3959
|
-
|
3960
|
-
|
3961
|
-
|
3962
|
-
|
3963
|
-
|
3964
|
-
|
3965
|
-
|
3966
|
-
const
|
3967
|
-
|
3968
|
-
|
3969
|
-
|
4575
|
+
async function subscribeSubtensorStaking(chaindataProvider, chainConnector, addressesByToken, callback) {
|
4576
|
+
const allChains = await chaindataProvider.chainsById();
|
4577
|
+
const tokens = await chaindataProvider.tokensById();
|
4578
|
+
const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
|
4579
|
+
const subtensorTokenIds = Object.entries(tokens).filter(([, token]) => {
|
4580
|
+
// ignore non-native tokens
|
4581
|
+
if (token.type !== "substrate-native") return false;
|
4582
|
+
// ignore tokens on chains with no subtensor pallet
|
4583
|
+
const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", allChains[token.chain.id]);
|
4584
|
+
return chainMeta?.hasSubtensorPallet === true;
|
4585
|
+
}).map(([tokenId]) => tokenId);
|
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;
|
3970
4610
|
}
|
3971
|
-
|
3972
|
-
|
3973
|
-
|
3974
|
-
|
3975
|
-
|
3976
|
-
|
3977
|
-
|
3978
|
-
|
3979
|
-
|
3980
|
-
|
3981
|
-
const
|
3982
|
-
|
3983
|
-
|
3984
|
-
|
3985
|
-
|
3986
|
-
|
3987
|
-
|
3988
|
-
|
3989
|
-
|
3990
|
-
|
3991
|
-
|
3992
|
-
|
3993
|
-
|
3994
|
-
|
3995
|
-
|
3996
|
-
|
3997
|
-
|
3998
|
-
|
3999
|
-
|
4000
|
-
|
4001
|
-
|
4002
|
-
|
4003
|
-
|
4004
|
-
|
4005
|
-
|
4006
|
-
|
4007
|
-
|
4008
|
-
|
4009
|
-
|
4010
|
-
|
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
|
-
}
|
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;
|
4620
|
+
}
|
4621
|
+
const scaleApi = sapi.getScaleApi({
|
4622
|
+
chainId,
|
4623
|
+
send: (...args) => chainConnector.send(chainId, ...args, {
|
4624
|
+
expectErrors: true
|
4625
|
+
} // don't pollute the wallet logs when this request fails
|
4626
|
+
)
|
4627
|
+
}, chainMeta.miniMetadata, token, chain.hasCheckMetadataHash, chain.signedExtensions, chain.registryTypes);
|
4628
|
+
|
4629
|
+
// sets the number of addresses to query in parallel (per chain, since each chain runs in parallel to the others)
|
4630
|
+
const concurrency = 4;
|
4631
|
+
// In-memory cache for successful dynamic info results
|
4632
|
+
const dynamicInfoCache = new Map();
|
4633
|
+
const fetchDynamicInfoForNetuids = async uniqueNetuids => {
|
4634
|
+
const MAX_RETRIES = 3;
|
4635
|
+
const RETRY_DELAY_MS = 500;
|
4636
|
+
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
4637
|
+
const fetchInfo = async netuid => {
|
4638
|
+
if (netuid === 0) return null;
|
4639
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
4640
|
+
try {
|
4641
|
+
const params = [netuid];
|
4642
|
+
const result = await scaleApi.getRuntimeCallValue("SubnetInfoRuntimeApi", "get_dynamic_info", params);
|
4643
|
+
dynamicInfoCache.set(netuid, result); // Cache successful response
|
4644
|
+
return result;
|
4645
|
+
} catch (error) {
|
4646
|
+
log.trace(`Attempt ${attempt} failed for netuid ${netuid}:`, error);
|
4647
|
+
if (attempt < MAX_RETRIES) {
|
4648
|
+
const backoffTime = RETRY_DELAY_MS * 2 ** (attempt - 1);
|
4649
|
+
log.trace(`Retrying in ${backoffTime}ms...`);
|
4650
|
+
await delay(backoffTime);
|
4041
4651
|
}
|
4042
4652
|
}
|
4043
|
-
|
4044
|
-
|
4045
|
-
|
4046
|
-
|
4047
|
-
|
4048
|
-
|
4049
|
-
return Promise.all(uniqueNetuids.map(fetchInfo));
|
4653
|
+
}
|
4654
|
+
if (dynamicInfoCache.has(netuid)) {
|
4655
|
+
return dynamicInfoCache.get(netuid); // Use cached value on failure
|
4656
|
+
}
|
4657
|
+
log.trace(`Failed to fetch dynamic info for netuid ${netuid} after ${MAX_RETRIES} attempts.`);
|
4658
|
+
return null;
|
4050
4659
|
};
|
4051
|
-
|
4052
|
-
|
4053
|
-
|
4054
|
-
|
4055
|
-
|
4056
|
-
|
4057
|
-
|
4058
|
-
|
4059
|
-
|
4060
|
-
|
4061
|
-
|
4062
|
-
|
4660
|
+
return Promise.all(uniqueNetuids.map(fetchInfo));
|
4661
|
+
};
|
4662
|
+
const subtensorQueries = rxjs.from(addresses).pipe(
|
4663
|
+
// mergeMap lets us run N concurrent queries, where N is the value of `concurrency`
|
4664
|
+
rxjs.mergeMap(async address => {
|
4665
|
+
const queryMethods = [async () => {
|
4666
|
+
if (chain.isTestnet) return [];
|
4667
|
+
const params = [address];
|
4668
|
+
const result = await scaleApi.getRuntimeCallValue("StakeInfoRuntimeApi", "get_stake_info_for_coldkey", params);
|
4669
|
+
if (!Array.isArray(result)) return [];
|
4670
|
+
const uniqueNetuids = Array.from(new Set(result.map(item => Number(item.netuid)).filter(netuid => netuid !== SUBTENSOR_ROOT_NETUID)));
|
4671
|
+
await fetchDynamicInfoForNetuids(uniqueNetuids);
|
4672
|
+
const stakes = result?.map(({
|
4673
|
+
coldkey,
|
4674
|
+
hotkey,
|
4675
|
+
netuid,
|
4676
|
+
stake
|
4677
|
+
}) => {
|
4678
|
+
return {
|
4679
|
+
address: coldkey,
|
4063
4680
|
hotkey,
|
4064
|
-
netuid,
|
4065
|
-
stake
|
4066
|
-
|
4067
|
-
|
4068
|
-
|
4069
|
-
|
4070
|
-
|
4071
|
-
|
4072
|
-
|
4073
|
-
|
4074
|
-
|
4075
|
-
|
4076
|
-
|
4077
|
-
return
|
4078
|
-
}
|
4079
|
-
|
4080
|
-
|
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
|
-
}
|
4681
|
+
netuid: Number(netuid),
|
4682
|
+
stake: BigInt(stake),
|
4683
|
+
dynamicInfo: dynamicInfoCache.get(Number(netuid))
|
4684
|
+
};
|
4685
|
+
}).filter(({
|
4686
|
+
stake
|
4687
|
+
}) => stake >= SUBTENSOR_MIN_STAKE_AMOUNT_PLANK);
|
4688
|
+
return stakes;
|
4689
|
+
}];
|
4690
|
+
const errors = [];
|
4691
|
+
for (const queryMethod of queryMethods) {
|
4692
|
+
try {
|
4693
|
+
// try each query method
|
4694
|
+
return await queryMethod();
|
4695
|
+
} catch (cause) {
|
4696
|
+
// if it fails, keep track of the error and try the next one
|
4697
|
+
errors.push(cause);
|
4088
4698
|
}
|
4699
|
+
}
|
4089
4700
|
|
4090
|
-
|
4091
|
-
|
4092
|
-
|
4093
|
-
|
4094
|
-
|
4095
|
-
|
4096
|
-
|
4097
|
-
|
4098
|
-
|
4099
|
-
|
4701
|
+
// if we get to here, that means that all query methods failed
|
4702
|
+
// let's throw the errors back to the native balance module
|
4703
|
+
throw new Error([`Failed to fetch ${tokenId} subtensor staked balance for ${address}:`, ...errors.map(error => String(error))].join("\n\t"));
|
4704
|
+
}, concurrency),
|
4705
|
+
// instead of emitting each balance as it's fetched, toArray waits for them all to fetch and then it collects them into an array
|
4706
|
+
rxjs.toArray(),
|
4707
|
+
// this mergeMap flattens our Array<Array<Stakes>> into just an Array<Stakes>
|
4708
|
+
rxjs.mergeMap(stakes => stakes),
|
4709
|
+
// convert our Array<Stakes> into Array<Balances>, which we can then return to the native balance module
|
4710
|
+
rxjs.map(stakes => stakes.map(({
|
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",
|
4100
4745
|
address,
|
4101
|
-
|
4102
|
-
|
4103
|
-
|
4104
|
-
|
4105
|
-
|
4106
|
-
|
4107
|
-
|
4108
|
-
|
4109
|
-
|
4110
|
-
|
4111
|
-
|
4112
|
-
|
4113
|
-
|
4114
|
-
|
4115
|
-
|
4116
|
-
|
4117
|
-
|
4118
|
-
|
4119
|
-
|
4120
|
-
|
4121
|
-
|
4122
|
-
|
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
|
-
}
|
4746
|
+
multiChainId: {
|
4747
|
+
subChainId: chainId
|
4748
|
+
},
|
4749
|
+
chainId,
|
4750
|
+
tokenId,
|
4751
|
+
values: [{
|
4752
|
+
source: "subtensor-staking",
|
4753
|
+
type: "subtensor",
|
4754
|
+
label: "subtensor-staking",
|
4755
|
+
amount: stakeByNetuid.toString(),
|
4756
|
+
meta: {
|
4757
|
+
type: "subtensor-staking",
|
4758
|
+
hotkey,
|
4759
|
+
netuid,
|
4760
|
+
amountStaked: stake.toString(),
|
4761
|
+
alphaToTaoRate,
|
4762
|
+
dynamicInfo: {
|
4763
|
+
tokenSymbol,
|
4764
|
+
subnetName,
|
4765
|
+
subnetIdentity: {
|
4766
|
+
...subnetIdentity,
|
4767
|
+
subnetName: subnetIdentity?.subnet_name || subnetName
|
4155
4768
|
}
|
4156
4769
|
}
|
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
|
-
}));
|
4770
|
+
}
|
4771
|
+
}]
|
4772
|
+
};
|
4773
|
+
})));
|
4169
4774
|
|
4170
|
-
|
4171
|
-
|
4172
|
-
|
4173
|
-
|
4174
|
-
|
4775
|
+
// This observable will run the subtensorQueries on a 30s (30_000ms) interval.
|
4776
|
+
// However, if the last run has not yet completed (e.g. its been 30s but we're still fetching some balances),
|
4777
|
+
// then exhaustMap will wait until the next interval (so T: 60s, T: 90s, T: 120s, etc) before re-executing the subtensorQueries.
|
4778
|
+
const subtensorQueriesInterval = rxjs.interval(30_000).pipe(rxjs.startWith(0),
|
4779
|
+
// start immediately
|
4780
|
+
rxjs.exhaustMap(() => {
|
4781
|
+
return subtensorQueries;
|
4782
|
+
}));
|
4175
4783
|
|
4176
|
-
|
4177
|
-
|
4178
|
-
|
4179
|
-
|
4180
|
-
}
|
4181
|
-
return () => abortController.abort();
|
4182
|
-
} catch (err) {
|
4183
|
-
if (!util.isAbortError(err)) log.error("Error subscribing to subtensor staking", {
|
4184
|
-
err
|
4784
|
+
// subscribe to the balances
|
4785
|
+
const subscription = subtensorQueriesInterval.subscribe({
|
4786
|
+
next: balances => callback(null, balances),
|
4787
|
+
error: error => callback(error)
|
4185
4788
|
});
|
4186
|
-
|
4789
|
+
|
4790
|
+
// use the abortController to tear the subscription down when we don't need it anymore
|
4791
|
+
abortController.signal.onabort = () => subscription.unsubscribe();
|
4187
4792
|
}
|
4793
|
+
return () => abortController.abort();
|
4188
4794
|
}
|
4189
4795
|
|
4190
4796
|
const getOtherType = input => `other-${input}`;
|
@@ -4240,14 +4846,18 @@ const filterBaseLocks = locks => {
|
|
4240
4846
|
};
|
4241
4847
|
|
4242
4848
|
// TODO: Make these titles translatable
|
4243
|
-
const getLockTitle = (lock,
|
4244
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
4245
|
-
{
|
4849
|
+
const getLockTitle = (lock, {
|
4246
4850
|
balance
|
4247
4851
|
} = {}) => {
|
4248
4852
|
if (!lock.label) return lock.label;
|
4249
4853
|
if (lock.label === "democracy") return "Governance";
|
4250
|
-
if (lock.label === "crowdloan")
|
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
|
+
}
|
4251
4861
|
if (lock.label === "nompools-staking") return "Pooled Staking";
|
4252
4862
|
if (lock.label === "nompools-unbonding") return "Pooled Staking";
|
4253
4863
|
if (lock.label === "subtensor-staking") return "Root Staking";
|
@@ -4259,6 +4869,7 @@ const getLockTitle = (lock,
|
|
4259
4869
|
};
|
4260
4870
|
|
4261
4871
|
const moduleType$2 = "substrate-native";
|
4872
|
+
const subNativeTokenId = chainId => `${chainId}-substrate-native`.toLowerCase().replace(/ /g, "-");
|
4262
4873
|
|
4263
4874
|
/**
|
4264
4875
|
* Function to merge two 'sub sources' of the same balance together, or
|
@@ -4336,7 +4947,7 @@ async function buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas,
|
|
4336
4947
|
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
4337
4948
|
return outerResult;
|
4338
4949
|
}
|
4339
|
-
const chainId = token.
|
4950
|
+
const chainId = token.chain?.id;
|
4340
4951
|
if (!chainId) {
|
4341
4952
|
log.warn(`Token ${tokenId} has no chain`);
|
4342
4953
|
return outerResult;
|
@@ -4346,10 +4957,10 @@ async function buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas,
|
|
4346
4957
|
log.warn(`Chain ${chainId} for token ${tokenId} not found`);
|
4347
4958
|
return outerResult;
|
4348
4959
|
}
|
4349
|
-
const
|
4960
|
+
const [chainMeta] = findChainMeta(miniMetadatas, "substrate-native", chain);
|
4350
4961
|
const {
|
4351
4962
|
useLegacyTransferableCalculation
|
4352
|
-
} =
|
4963
|
+
} = chainMeta ?? {};
|
4353
4964
|
addresses.flat().forEach(address => {
|
4354
4965
|
const queryKey = `${tokenId}-${address}`;
|
4355
4966
|
// We share this balanceJson between the base and the lock query for this address
|
@@ -4357,7 +4968,10 @@ async function buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas,
|
|
4357
4968
|
source: "substrate-native",
|
4358
4969
|
status: "live",
|
4359
4970
|
address,
|
4360
|
-
|
4971
|
+
multiChainId: {
|
4972
|
+
subChainId: chainId
|
4973
|
+
},
|
4974
|
+
chainId,
|
4361
4975
|
tokenId,
|
4362
4976
|
values: []
|
4363
4977
|
};
|
@@ -4629,19 +5243,75 @@ const updateStakingLocksUsingUnbondingLocks = values => {
|
|
4629
5243
|
return [...otherValues, ...stakingLocks];
|
4630
5244
|
};
|
4631
5245
|
|
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
|
+
}));
|
4632
5280
|
class QueryCache {
|
4633
|
-
#chaindataProvider;
|
4634
|
-
#chainConnector;
|
4635
|
-
miniMetadatas = new Map();
|
4636
5281
|
balanceQueryCache = new Map();
|
4637
|
-
|
5282
|
+
metadataSub = null;
|
5283
|
+
constructor(chaindataProvider) {
|
4638
5284
|
this.chaindataProvider = chaindataProvider;
|
4639
|
-
|
4640
|
-
|
5285
|
+
}
|
5286
|
+
ensureSetup() {
|
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();
|
4641
5310
|
}
|
4642
5311
|
async getQueries(addressesByToken) {
|
4643
|
-
|
4644
|
-
const
|
5312
|
+
this.ensureSetup();
|
5313
|
+
const chains = await this.chaindataProvider.chainsById();
|
5314
|
+
const tokens = await this.chaindataProvider.tokensById();
|
4645
5315
|
const queryResults = Object.entries(addressesByToken).reduce((result, [tokenId, addresses]) => {
|
4646
5316
|
addresses.forEach(address => {
|
4647
5317
|
const key = `${tokenId}-${address}`;
|
@@ -4657,19 +5327,15 @@ class QueryCache {
|
|
4657
5327
|
existing: [],
|
4658
5328
|
newAddressesByToken: {}
|
4659
5329
|
});
|
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
|
-
}
|
4666
5330
|
|
4667
5331
|
// build queries for token/address pairs which have not been queried before
|
4668
|
-
const
|
5332
|
+
const miniMetadatas = await rxjs.firstValueFrom(commonMetadataObservable);
|
5333
|
+
const uniqueChainIds = getUniqueChainIds(queryResults.newAddressesByToken, tokens);
|
4669
5334
|
const chainStorageCoders = buildStorageCoders({
|
4670
5335
|
chainIds: uniqueChainIds,
|
4671
5336
|
chains,
|
4672
|
-
miniMetadatas
|
5337
|
+
miniMetadatas,
|
5338
|
+
moduleType: "substrate-native",
|
4673
5339
|
coders: {
|
4674
5340
|
base: ["System", "Account"],
|
4675
5341
|
stakingLedger: ["Staking", "Ledger"],
|
@@ -4679,7 +5345,7 @@ class QueryCache {
|
|
4679
5345
|
freezes: ["Balances", "Freezes"]
|
4680
5346
|
}
|
4681
5347
|
});
|
4682
|
-
const queries = await buildQueries$1(chains, tokens, chainStorageCoders,
|
5348
|
+
const queries = await buildQueries$1(chains, tokens, chainStorageCoders, miniMetadatas, queryResults.newAddressesByToken);
|
4683
5349
|
// now update the cache
|
4684
5350
|
Object.entries(queries).forEach(([key, query]) => {
|
4685
5351
|
this.balanceQueryCache.set(key, query);
|
@@ -4688,11 +5354,14 @@ class QueryCache {
|
|
4688
5354
|
}
|
4689
5355
|
}
|
4690
5356
|
|
4691
|
-
const
|
4692
|
-
const
|
5357
|
+
const RELAY_TOKENS = ["polkadot-substrate-native", "kusama-substrate-native"];
|
5358
|
+
const PUBLIC_GOODS_TOKENS = ["polkadot-asset-hub-substrate-native", "kusama-asset-hub-substrate-native"];
|
5359
|
+
const sortChains = (a, b) => {
|
4693
5360
|
// polkadot and kusama should be checked first
|
4694
|
-
if (
|
4695
|
-
if (
|
5361
|
+
if (RELAY_TOKENS.includes(a)) return -1;
|
5362
|
+
if (RELAY_TOKENS.includes(b)) return 1;
|
5363
|
+
if (PUBLIC_GOODS_TOKENS.includes(a)) return -1;
|
5364
|
+
if (PUBLIC_GOODS_TOKENS.includes(b)) return 1;
|
4696
5365
|
return 0;
|
4697
5366
|
};
|
4698
5367
|
|
@@ -4708,30 +5377,10 @@ class SubNativeBalanceError extends Error {
|
|
4708
5377
|
}
|
4709
5378
|
}
|
4710
5379
|
|
4711
|
-
const
|
4712
|
-
|
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
|
-
|
5380
|
+
const DEFAULT_SYMBOL = "Unit";
|
5381
|
+
const DEFAULT_DECIMALS = 0;
|
4728
5382
|
const POLLING_WINDOW_SIZE = 20;
|
4729
5383
|
const MAX_SUBSCRIPTION_SIZE = 40;
|
4730
|
-
const EMPTY_CHAIN_META = {
|
4731
|
-
miniMetadata: null,
|
4732
|
-
extra: null
|
4733
|
-
};
|
4734
|
-
const SubNativeTokenConfigSchema = TokenConfigBaseSchema;
|
4735
5384
|
const SubNativeModule = hydrate => {
|
4736
5385
|
const {
|
4737
5386
|
chainConnectors,
|
@@ -4739,198 +5388,33 @@ const SubNativeModule = hydrate => {
|
|
4739
5388
|
} = hydrate;
|
4740
5389
|
const chainConnector$1 = chainConnectors.substrate;
|
4741
5390
|
util$1.assert(chainConnector$1, "This module requires a substrate chain connector");
|
4742
|
-
const queryCache = new QueryCache(chaindataProvider$1
|
5391
|
+
const queryCache = new QueryCache(chaindataProvider$1);
|
4743
5392
|
const getModuleTokens = async () => {
|
4744
|
-
return await chaindataProvider$1.
|
4745
|
-
};
|
4746
|
-
|
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
|
-
};
|
4841
|
-
|
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 ?? []);
|
5393
|
+
return await chaindataProvider$1.tokensByIdForType(moduleType$2);
|
4922
5394
|
};
|
4923
5395
|
return {
|
4924
5396
|
...DefaultBalanceModule(moduleType$2),
|
4925
|
-
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
|
4926
|
-
|
4927
|
-
if (
|
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
|
+
|
5403
|
+
//
|
5404
|
+
// extract system_properties
|
5405
|
+
//
|
5406
|
+
|
5407
|
+
const {
|
5408
|
+
tokenSymbol,
|
5409
|
+
tokenDecimals
|
5410
|
+
} = systemProperties ?? {};
|
5411
|
+
const symbol = (Array.isArray(tokenSymbol) ? tokenSymbol[0] : tokenSymbol) ?? DEFAULT_SYMBOL;
|
5412
|
+
const decimals = (Array.isArray(tokenDecimals) ? tokenDecimals[0] : tokenDecimals) ?? DEFAULT_DECIMALS;
|
4928
5413
|
|
4929
5414
|
//
|
4930
5415
|
// process metadata into SCALE encoders/decoders
|
4931
5416
|
//
|
4932
5417
|
const metadataVersion = scale.getMetadataVersion(metadataRpc);
|
4933
|
-
if (metadataVersion < 14) return EMPTY_CHAIN_META;
|
4934
5418
|
const metadata = scale.decAnyMetadata(metadataRpc);
|
4935
5419
|
const unifiedMetadata = scale.unifyMetadata(metadata);
|
4936
5420
|
|
@@ -4950,6 +5434,7 @@ const SubNativeModule = hydrate => {
|
|
4950
5434
|
};
|
4951
5435
|
const existentialDeposit = getConstantValue("Balances", "ExistentialDeposit")?.toString();
|
4952
5436
|
const nominationPoolsPalletId = getConstantValue("NominationPools", "PalletId")?.asText();
|
5437
|
+
const crowdloanPalletId = getConstantValue("Crowdloan", "PalletId")?.asText();
|
4953
5438
|
const hasSubtensorPallet = getConstantValue("SubtensorModule", "KeySwapCost") !== undefined;
|
4954
5439
|
|
4955
5440
|
//
|
@@ -4969,10 +5454,15 @@ const SubNativeModule = hydrate => {
|
|
4969
5454
|
}, {
|
4970
5455
|
pallet: "Staking",
|
4971
5456
|
items: ["Ledger"]
|
5457
|
+
}, {
|
5458
|
+
pallet: "Crowdloan",
|
5459
|
+
items: ["Funds"]
|
5460
|
+
}, {
|
5461
|
+
pallet: "Paras",
|
5462
|
+
items: ["Parachains"]
|
4972
5463
|
},
|
4973
5464
|
// TotalColdkeyStake is used until v.2.2.1, then it is replaced by StakingHotkeys+Stake
|
4974
5465
|
// 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
|
4976
5466
|
{
|
4977
5467
|
pallet: "SubtensorModule",
|
4978
5468
|
items: ["TotalColdkeyStake", "StakingHotkeys", "Stake"]
|
@@ -4991,40 +5481,47 @@ const SubNativeModule = hydrate => {
|
|
4991
5481
|
}) => name === "Freezes"));
|
4992
5482
|
const useLegacyTransferableCalculation = !hasFreezesItem;
|
4993
5483
|
const chainMeta = {
|
5484
|
+
isTestnet,
|
5485
|
+
useLegacyTransferableCalculation,
|
5486
|
+
symbol,
|
5487
|
+
decimals,
|
5488
|
+
existentialDeposit,
|
5489
|
+
nominationPoolsPalletId,
|
5490
|
+
crowdloanPalletId,
|
5491
|
+
hasSubtensorPallet,
|
4994
5492
|
miniMetadata,
|
4995
|
-
|
4996
|
-
useLegacyTransferableCalculation,
|
4997
|
-
existentialDeposit,
|
4998
|
-
nominationPoolsPalletId,
|
4999
|
-
hasSubtensorPallet
|
5000
|
-
}
|
5493
|
+
metadataVersion
|
5001
5494
|
};
|
5002
|
-
if (!useLegacyTransferableCalculation) delete chainMeta.
|
5003
|
-
if (!hasSubtensorPallet) delete chainMeta.
|
5495
|
+
if (!useLegacyTransferableCalculation) delete chainMeta.useLegacyTransferableCalculation;
|
5496
|
+
if (!hasSubtensorPallet) delete chainMeta.hasSubtensorPallet;
|
5004
5497
|
return chainMeta;
|
5005
5498
|
},
|
5006
5499
|
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
|
5007
5500
|
if (moduleConfig?.disable === true) return {};
|
5008
5501
|
const {
|
5009
|
-
|
5010
|
-
|
5011
|
-
|
5012
|
-
const {
|
5502
|
+
isTestnet,
|
5503
|
+
symbol,
|
5504
|
+
decimals,
|
5013
5505
|
existentialDeposit
|
5014
|
-
} = chainMeta
|
5015
|
-
|
5016
|
-
const id = chaindataProvider.subNativeTokenId(chainId);
|
5506
|
+
} = chainMeta;
|
5507
|
+
const id = subNativeTokenId(chainId);
|
5017
5508
|
const nativeToken = {
|
5018
5509
|
id,
|
5019
5510
|
type: "substrate-native",
|
5020
|
-
|
5021
|
-
isDefault: true,
|
5022
|
-
symbol: symbol,
|
5023
|
-
|
5024
|
-
|
5511
|
+
isTestnet,
|
5512
|
+
isDefault: moduleConfig?.isDefault ?? true,
|
5513
|
+
symbol: symbol ?? DEFAULT_SYMBOL,
|
5514
|
+
decimals: decimals ?? DEFAULT_DECIMALS,
|
5515
|
+
logo: moduleConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
|
5025
5516
|
existentialDeposit: existentialDeposit ?? "0",
|
5026
|
-
|
5517
|
+
chain: {
|
5518
|
+
id: chainId
|
5519
|
+
}
|
5027
5520
|
};
|
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;
|
5028
5525
|
return {
|
5029
5526
|
[nativeToken.id]: nativeToken
|
5030
5527
|
};
|
@@ -5034,36 +5531,169 @@ const SubNativeModule = hydrate => {
|
|
5034
5531
|
initialBalances
|
5035
5532
|
}, callback) {
|
5036
5533
|
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
5037
|
-
|
5038
|
-
|
5039
|
-
const
|
5040
|
-
|
5041
|
-
|
5042
|
-
|
5043
|
-
|
5044
|
-
|
5534
|
+
|
5535
|
+
// full record of balances for this module
|
5536
|
+
const subNativeBalances = new rxjs.BehaviorSubject(Object.fromEntries(initialBalances?.map(b => [getBalanceId(b), b]) ?? []));
|
5537
|
+
// tokens which have a known positive balance
|
5538
|
+
const positiveBalanceTokens = subNativeBalances.pipe(rxjs.map(balances => Array.from(new Set(Object.values(balances).map(b => b.tokenId)))), rxjs.share());
|
5539
|
+
|
5540
|
+
// tokens that will be subscribed to, simply a slice of the positive balance tokens of size MAX_SUBSCRIPTION_SIZE
|
5541
|
+
const subscriptionTokens = positiveBalanceTokens.pipe(rxjs.map(tokens => tokens.sort(sortChains).slice(0, MAX_SUBSCRIPTION_SIZE)));
|
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`));
|
5045
5578
|
};
|
5046
|
-
|
5579
|
+
// we queue up our work to clean up our subscription when this promise rejects
|
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");
|
5047
5644
|
try {
|
5048
|
-
|
5049
|
-
|
5050
|
-
|
5051
|
-
|
5052
|
-
|
5053
|
-
|
5645
|
+
const balances = await this.fetchBalances(addressesByToken);
|
5646
|
+
handleUpdate(null, Object.values(balances.toJSON()));
|
5647
|
+
} catch (error) {
|
5648
|
+
if (error instanceof chainConnector.ChainConnectionError) {
|
5649
|
+
// coerce ChainConnection errors into SubNativeBalance errors
|
5650
|
+
const errorChainId = error.chainId;
|
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
|
+
}
|
5054
5659
|
}
|
5055
|
-
|
5056
|
-
|
5057
|
-
|
5058
|
-
|
5059
|
-
|
5660
|
+
};
|
5661
|
+
// do one poll to get things started
|
5662
|
+
const currentBalances = subNativeBalances.getValue();
|
5663
|
+
const currentTokens = new Set(Object.values(currentBalances).map(b => b.tokenId));
|
5664
|
+
const nonCurrentTokens = Object.keys(addressesByToken).filter(tokenId => !currentTokens.has(tokenId)).sort(sortChains);
|
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]
|
5060
5669
|
}));
|
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();
|
5061
5684
|
return () => {
|
5062
|
-
|
5063
|
-
|
5685
|
+
callerUnsubscribe();
|
5686
|
+
positiveSub.unsubscribe();
|
5687
|
+
pollingSub.unsubscribe();
|
5064
5688
|
};
|
5065
5689
|
},
|
5066
|
-
fetchBalances
|
5690
|
+
async fetchBalances(addressesByToken) {
|
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
|
+
},
|
5067
5697
|
async transferToken({
|
5068
5698
|
tokenId,
|
5069
5699
|
from,
|
@@ -5080,10 +5710,11 @@ const SubNativeModule = hydrate => {
|
|
5080
5710
|
transferMethod,
|
5081
5711
|
userExtensions
|
5082
5712
|
}) {
|
5083
|
-
const token = await chaindataProvider$1.
|
5713
|
+
const token = await chaindataProvider$1.tokenById(tokenId);
|
5084
5714
|
util$1.assert(token, `Token ${tokenId} not found in store`);
|
5085
|
-
|
5086
|
-
const
|
5715
|
+
if (token.type !== "substrate-native") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
|
5716
|
+
const chainId = token.chain.id;
|
5717
|
+
const chain = await chaindataProvider$1.chainById(chainId);
|
5087
5718
|
util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
|
5088
5719
|
const {
|
5089
5720
|
genesisHash
|
@@ -6277,10 +6908,7 @@ var psp22Abi = {
|
|
6277
6908
|
};
|
6278
6909
|
|
6279
6910
|
const moduleType$1 = "substrate-psp22";
|
6280
|
-
const
|
6281
|
-
contractAddress: chaindataProvider.SubPsp22TokenSchema.shape.contractAddress,
|
6282
|
-
...TokenConfigBaseSchema.shape
|
6283
|
-
});
|
6911
|
+
const subPsp22TokenId = (chainId, tokenSymbol) => `${chainId}-substrate-psp22-${tokenSymbol}`.toLowerCase().replace(/ /g, "-");
|
6284
6912
|
const SubPsp22Module = hydrate => {
|
6285
6913
|
const {
|
6286
6914
|
chainConnectors,
|
@@ -6290,15 +6918,16 @@ const SubPsp22Module = hydrate => {
|
|
6290
6918
|
util$1.assert(chainConnector, "This module requires a substrate chain connector");
|
6291
6919
|
return {
|
6292
6920
|
...DefaultBalanceModule(moduleType$1),
|
6293
|
-
async fetchSubstrateChainMeta(
|
6294
|
-
|
6921
|
+
async fetchSubstrateChainMeta(chainId) {
|
6922
|
+
const isTestnet = (await chaindataProvider$1.chainById(chainId))?.isTestnet || false;
|
6295
6923
|
return {
|
6296
|
-
|
6297
|
-
extra: null
|
6924
|
+
isTestnet
|
6298
6925
|
};
|
6299
6926
|
},
|
6300
|
-
async fetchSubstrateChainTokens(chainId,
|
6301
|
-
|
6927
|
+
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
|
6928
|
+
const {
|
6929
|
+
isTestnet
|
6930
|
+
} = chainMeta;
|
6302
6931
|
const registry = new types.TypeRegistry();
|
6303
6932
|
const Psp22Abi = new apiContract.Abi(psp22Abi);
|
6304
6933
|
|
@@ -6308,11 +6937,12 @@ const SubPsp22Module = hydrate => {
|
|
6308
6937
|
chainId,
|
6309
6938
|
registry
|
6310
6939
|
});
|
6311
|
-
const
|
6312
|
-
for (const tokenConfig of tokens ?? []) {
|
6940
|
+
const tokens = {};
|
6941
|
+
for (const tokenConfig of moduleConfig?.tokens ?? []) {
|
6313
6942
|
try {
|
6314
6943
|
let symbol = tokenConfig?.symbol ?? "Unit";
|
6315
6944
|
let decimals = tokenConfig?.decimals ?? 0;
|
6945
|
+
const existentialDeposit = tokenConfig?.ed ?? "0";
|
6316
6946
|
const contractAddress = tokenConfig?.contractAddress ?? undefined;
|
6317
6947
|
if (contractAddress === undefined) continue;
|
6318
6948
|
await (async () => {
|
@@ -6328,28 +6958,35 @@ const SubPsp22Module = hydrate => {
|
|
6328
6958
|
const decimalsData = decimalsResult.toJSON()?.result?.ok?.data;
|
6329
6959
|
decimals = typeof decimalsData === "string" && decimalsData.startsWith("0x") ? util$1.hexToNumber(decimalsData) : decimals;
|
6330
6960
|
})();
|
6331
|
-
const id =
|
6961
|
+
const id = subPsp22TokenId(chainId, symbol);
|
6332
6962
|
const token = {
|
6333
6963
|
id,
|
6334
6964
|
type: "substrate-psp22",
|
6335
|
-
|
6965
|
+
isTestnet,
|
6336
6966
|
isDefault: tokenConfig.isDefault ?? true,
|
6337
6967
|
symbol,
|
6338
6968
|
decimals,
|
6339
|
-
|
6340
|
-
|
6969
|
+
logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
|
6970
|
+
existentialDeposit,
|
6341
6971
|
contractAddress,
|
6342
|
-
|
6972
|
+
chain: {
|
6973
|
+
id: chainId
|
6974
|
+
}
|
6343
6975
|
};
|
6976
|
+
if (tokenConfig?.symbol) {
|
6977
|
+
token.symbol = tokenConfig?.symbol;
|
6978
|
+
token.id = subPsp22TokenId(chainId, token.symbol);
|
6979
|
+
}
|
6344
6980
|
if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
|
6981
|
+
if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
|
6345
6982
|
if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
|
6346
|
-
|
6983
|
+
tokens[token.id] = token;
|
6347
6984
|
} catch (error) {
|
6348
6985
|
log.error(`Failed to build substrate-psp22 token ${tokenConfig.contractAddress} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
|
6349
6986
|
continue;
|
6350
6987
|
}
|
6351
6988
|
}
|
6352
|
-
return
|
6989
|
+
return tokens;
|
6353
6990
|
},
|
6354
6991
|
// TODO: Don't create empty subscriptions
|
6355
6992
|
async subscribeBalances({
|
@@ -6359,7 +6996,7 @@ const SubPsp22Module = hydrate => {
|
|
6359
6996
|
const subscriptionInterval = 12_000; // 12_000ms == 12 seconds
|
6360
6997
|
const initDelay = 3_000; // 3000ms == 3 seconds
|
6361
6998
|
const cache = new Map();
|
6362
|
-
const tokens = await chaindataProvider$1.
|
6999
|
+
const tokens = await chaindataProvider$1.tokensById();
|
6363
7000
|
const poll = async () => {
|
6364
7001
|
if (!subscriptionActive) return;
|
6365
7002
|
try {
|
@@ -6386,7 +7023,7 @@ const SubPsp22Module = hydrate => {
|
|
6386
7023
|
},
|
6387
7024
|
async fetchBalances(addressesByToken) {
|
6388
7025
|
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
6389
|
-
const tokens = await chaindataProvider$1.
|
7026
|
+
const tokens = await chaindataProvider$1.tokensById();
|
6390
7027
|
return fetchBalances(chainConnectors.substrate, tokens, addressesByToken);
|
6391
7028
|
},
|
6392
7029
|
async transferToken({
|
@@ -6404,11 +7041,11 @@ const SubPsp22Module = hydrate => {
|
|
6404
7041
|
tip,
|
6405
7042
|
userExtensions
|
6406
7043
|
}) {
|
6407
|
-
const token = await chaindataProvider$1.
|
7044
|
+
const token = await chaindataProvider$1.tokenById(tokenId);
|
6408
7045
|
util$1.assert(token, `Token ${tokenId} not found in store`);
|
6409
7046
|
if (token.type !== "substrate-psp22") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
|
6410
|
-
const chainId = token.
|
6411
|
-
const chain = await chaindataProvider$1.
|
7047
|
+
const chainId = token.chain.id;
|
7048
|
+
const chain = await chaindataProvider$1.chainById(chainId);
|
6412
7049
|
util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
|
6413
7050
|
const {
|
6414
7051
|
genesisHash
|
@@ -6484,7 +7121,7 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
|
|
6484
7121
|
// TODO: Use `decodeOutput` from `./util/decodeOutput`
|
6485
7122
|
const contractCall = makeContractCaller({
|
6486
7123
|
chainConnector,
|
6487
|
-
chainId: token.
|
7124
|
+
chainId: token.chain.id,
|
6488
7125
|
registry
|
6489
7126
|
});
|
6490
7127
|
if (token.contractAddress === undefined) {
|
@@ -6501,7 +7138,10 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
|
|
6501
7138
|
source: "substrate-psp22",
|
6502
7139
|
status: "live",
|
6503
7140
|
address,
|
6504
|
-
|
7141
|
+
multiChainId: {
|
7142
|
+
subChainId: token.chain.id
|
7143
|
+
},
|
7144
|
+
chainId: token.chain.id,
|
6505
7145
|
tokenId,
|
6506
7146
|
value: balance
|
6507
7147
|
};
|
@@ -6524,16 +7164,8 @@ const fetchBalances = async (chainConnector, tokens, addressesByToken) => {
|
|
6524
7164
|
};
|
6525
7165
|
|
6526
7166
|
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
|
-
});
|
6532
7167
|
const defaultPalletId = "Tokens";
|
6533
|
-
const
|
6534
|
-
miniMetadata: null,
|
6535
|
-
extra: {}
|
6536
|
-
};
|
7168
|
+
const subTokensTokenId = (chainId, onChainId) => `${chainId}-substrate-tokens-${lzString.compressToEncodedURIComponent(String(onChainId))}`;
|
6537
7169
|
const SubTokensModule = hydrate => {
|
6538
7170
|
const {
|
6539
7171
|
chainConnectors,
|
@@ -6544,7 +7176,14 @@ const SubTokensModule = hydrate => {
|
|
6544
7176
|
return {
|
6545
7177
|
...DefaultBalanceModule(moduleType),
|
6546
7178
|
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
|
6547
|
-
|
7179
|
+
const isTestnet = (await chaindataProvider$1.chainById(chainId))?.isTestnet || false;
|
7180
|
+
if (metadataRpc === undefined) return {
|
7181
|
+
isTestnet
|
7182
|
+
};
|
7183
|
+
if ((moduleConfig?.tokens ?? []).length < 1) return {
|
7184
|
+
isTestnet
|
7185
|
+
};
|
7186
|
+
const metadataVersion = scale.getMetadataVersion(metadataRpc);
|
6548
7187
|
const metadata = scale.decAnyMetadata(metadataRpc);
|
6549
7188
|
const palletId = moduleConfig?.palletId ?? defaultPalletId;
|
6550
7189
|
scale.compactMetadata(metadata, [{
|
@@ -6552,83 +7191,74 @@ const SubTokensModule = hydrate => {
|
|
6552
7191
|
items: ["Accounts"]
|
6553
7192
|
}]);
|
6554
7193
|
const miniMetadata = scale.encodeMetadata(metadata);
|
6555
|
-
return {
|
7194
|
+
return palletId === defaultPalletId ? {
|
7195
|
+
isTestnet,
|
6556
7196
|
miniMetadata,
|
6557
|
-
|
6558
|
-
|
6559
|
-
|
7197
|
+
metadataVersion
|
7198
|
+
} : {
|
7199
|
+
isTestnet,
|
7200
|
+
palletId,
|
7201
|
+
miniMetadata,
|
7202
|
+
metadataVersion
|
6560
7203
|
};
|
6561
7204
|
},
|
6562
|
-
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig
|
6563
|
-
const
|
6564
|
-
|
7205
|
+
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
|
7206
|
+
const {
|
7207
|
+
isTestnet
|
7208
|
+
} = chainMeta;
|
7209
|
+
const tokens = {};
|
7210
|
+
for (const tokenConfig of moduleConfig?.tokens ?? []) {
|
6565
7211
|
try {
|
6566
|
-
// TODO fetch metadata from chain, like we do for assets
|
6567
7212
|
const symbol = tokenConfig?.symbol ?? "Unit";
|
6568
7213
|
const decimals = tokenConfig?.decimals ?? 0;
|
6569
|
-
const existentialDeposit = tokenConfig?.
|
7214
|
+
const existentialDeposit = tokenConfig?.ed ?? "0";
|
6570
7215
|
const onChainId = tokenConfig?.onChainId ?? undefined;
|
6571
7216
|
if (onChainId === undefined) continue;
|
6572
|
-
const id =
|
7217
|
+
const id = subTokensTokenId(chainId, onChainId);
|
6573
7218
|
const token = {
|
6574
7219
|
id,
|
6575
7220
|
type: "substrate-tokens",
|
6576
|
-
|
7221
|
+
isTestnet,
|
6577
7222
|
isDefault: tokenConfig.isDefault ?? true,
|
6578
7223
|
symbol,
|
6579
7224
|
decimals,
|
6580
|
-
|
6581
|
-
logo: tokenConfig?.logo,
|
7225
|
+
logo: tokenConfig?.logo || chaindataProvider.githubTokenLogoUrl(id),
|
6582
7226
|
existentialDeposit,
|
6583
7227
|
onChainId,
|
6584
|
-
|
7228
|
+
chain: {
|
7229
|
+
id: chainId
|
7230
|
+
}
|
6585
7231
|
};
|
7232
|
+
if (tokenConfig?.symbol) {
|
7233
|
+
token.symbol = tokenConfig?.symbol;
|
7234
|
+
token.id = subTokensTokenId(chainId, token.onChainId);
|
7235
|
+
}
|
6586
7236
|
if (tokenConfig?.coingeckoId) token.coingeckoId = tokenConfig?.coingeckoId;
|
7237
|
+
if (tokenConfig?.dcentName) token.dcentName = tokenConfig?.dcentName;
|
6587
7238
|
if (tokenConfig?.mirrorOf) token.mirrorOf = tokenConfig?.mirrorOf;
|
6588
|
-
|
7239
|
+
tokens[token.id] = token;
|
6589
7240
|
} catch (error) {
|
6590
7241
|
log.error(`Failed to build substrate-tokens token ${tokenConfig.onChainId} (${tokenConfig.symbol}) on ${chainId}`, error?.message ?? error);
|
6591
7242
|
continue;
|
6592
7243
|
}
|
6593
7244
|
}
|
6594
|
-
return
|
7245
|
+
return tokens;
|
6595
7246
|
},
|
6596
7247
|
// TODO: Don't create empty subscriptions
|
6597
7248
|
async subscribeBalances({
|
6598
7249
|
addressesByToken
|
6599
7250
|
}, callback) {
|
6600
|
-
const
|
6601
|
-
|
6602
|
-
if (
|
6603
|
-
|
6604
|
-
|
6605
|
-
}
|
6606
|
-
|
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
|
-
};
|
7251
|
+
const queries = await buildQueries(chaindataProvider$1, addressesByToken);
|
7252
|
+
const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
|
7253
|
+
if (error) return callback(error);
|
7254
|
+
const balances = result?.filter(b => b !== null) ?? [];
|
7255
|
+
if (balances.length > 0) callback(null, new Balances(balances));
|
7256
|
+
});
|
7257
|
+
return unsubscribe;
|
6628
7258
|
},
|
6629
7259
|
async fetchBalances(addressesByToken) {
|
6630
7260
|
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
6631
|
-
const queries = await buildQueries(
|
7261
|
+
const queries = await buildQueries(chaindataProvider$1, addressesByToken);
|
6632
7262
|
const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
|
6633
7263
|
const balances = result?.filter(b => b !== null) ?? [];
|
6634
7264
|
return new Balances(balances);
|
@@ -6640,13 +7270,15 @@ const SubTokensModule = hydrate => {
|
|
6640
7270
|
transferMethod,
|
6641
7271
|
metadataRpc
|
6642
7272
|
}) {
|
6643
|
-
const token = await chaindataProvider$1.
|
7273
|
+
const token = await chaindataProvider$1.tokenById(tokenId);
|
6644
7274
|
util$1.assert(token, `Token ${tokenId} not found in store`);
|
6645
|
-
|
6646
|
-
const
|
7275
|
+
if (token.type !== "substrate-tokens") throw new Error(`This module doesn't handle tokens of type ${token.type}`);
|
7276
|
+
const chainId = token.chain.id;
|
7277
|
+
const chain = await chaindataProvider$1.chainById(chainId);
|
6647
7278
|
util$1.assert(chain?.genesisHash, `Chain ${chainId} not found in store`);
|
6648
|
-
const
|
6649
|
-
const
|
7279
|
+
const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
|
7280
|
+
const [chainMeta] = findChainMeta(miniMetadatas, moduleType, chain);
|
7281
|
+
const tokensPallet = chainMeta?.palletId ?? defaultPalletId;
|
6650
7282
|
const onChainId = (() => {
|
6651
7283
|
try {
|
6652
7284
|
return scale.papiParse(token.onChainId);
|
@@ -6745,15 +7377,23 @@ const SubTokensModule = hydrate => {
|
|
6745
7377
|
}
|
6746
7378
|
};
|
6747
7379
|
};
|
6748
|
-
async function
|
6749
|
-
const
|
6750
|
-
const
|
6751
|
-
const
|
6752
|
-
|
6753
|
-
|
6754
|
-
const
|
6755
|
-
const
|
6756
|
-
|
7380
|
+
async function buildQueries(chaindataProvider, addressesByToken) {
|
7381
|
+
const allChains = await chaindataProvider.chainsById();
|
7382
|
+
const tokens = await chaindataProvider.tokensById();
|
7383
|
+
const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
|
7384
|
+
const tokensPalletByChain = new Map(Object.values(allChains).map(chain => [chain.id, findChainMeta(miniMetadatas, moduleType, chain)[0]?.palletId]));
|
7385
|
+
const uniqueChainIds = getUniqueChainIds(addressesByToken, tokens);
|
7386
|
+
const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
|
7387
|
+
const chainStorageCoders = buildStorageCoders({
|
7388
|
+
chainIds: uniqueChainIds,
|
7389
|
+
chains,
|
7390
|
+
miniMetadatas,
|
7391
|
+
moduleType: "substrate-tokens",
|
7392
|
+
coders: {
|
7393
|
+
storage: ({
|
7394
|
+
chainId
|
7395
|
+
}) => [tokensPalletByChain.get(chainId) ?? defaultPalletId, "Accounts"]
|
7396
|
+
}
|
6757
7397
|
});
|
6758
7398
|
return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
|
6759
7399
|
const token = tokens[tokenId];
|
@@ -6765,8 +7405,18 @@ async function buildNetworkQueries(networkId, chainConnector, chaindataProvider,
|
|
6765
7405
|
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
6766
7406
|
return [];
|
6767
7407
|
}
|
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
|
+
}
|
6768
7418
|
return addresses.flatMap(address => {
|
6769
|
-
const scaleCoder =
|
7419
|
+
const scaleCoder = chainStorageCoders.get(chainId)?.storage;
|
6770
7420
|
const onChainId = (() => {
|
6771
7421
|
try {
|
6772
7422
|
return scale.papiParse(token.onChainId);
|
@@ -6774,12 +7424,12 @@ async function buildNetworkQueries(networkId, chainConnector, chaindataProvider,
|
|
6774
7424
|
return token.onChainId;
|
6775
7425
|
}
|
6776
7426
|
})();
|
6777
|
-
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${
|
7427
|
+
const stateKey = scale.encodeStateKey(scaleCoder, `Invalid address / token onChainId in ${chainId} storage query ${address} / ${token.onChainId}`, address, onChainId);
|
6778
7428
|
if (!stateKey) return [];
|
6779
7429
|
const decodeResult = change => {
|
6780
7430
|
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
6781
7431
|
|
6782
|
-
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-tokens balance on chain ${
|
7432
|
+
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-tokens balance on chain ${chainId}`) ?? {
|
6783
7433
|
free: 0n,
|
6784
7434
|
reserved: 0n,
|
6785
7435
|
frozen: 0n
|
@@ -6804,32 +7454,50 @@ async function buildNetworkQueries(networkId, chainConnector, chaindataProvider,
|
|
6804
7454
|
source: "substrate-tokens",
|
6805
7455
|
status: "live",
|
6806
7456
|
address,
|
6807
|
-
|
7457
|
+
multiChainId: {
|
7458
|
+
subChainId: chainId
|
7459
|
+
},
|
7460
|
+
chainId,
|
6808
7461
|
tokenId: token.id,
|
6809
7462
|
values: balanceValues
|
6810
7463
|
};
|
6811
7464
|
};
|
6812
7465
|
return {
|
6813
|
-
chainId
|
7466
|
+
chainId,
|
6814
7467
|
stateKey,
|
6815
7468
|
decodeResult
|
6816
7469
|
};
|
6817
7470
|
});
|
6818
7471
|
});
|
6819
7472
|
}
|
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
|
-
}
|
6831
7473
|
|
6832
|
-
const defaultBalanceModules = [EvmErc20Module, EvmNativeModule, EvmUniswapV2Module, SubAssetsModule, SubForeignAssetsModule, SubNativeModule, SubPsp22Module, SubTokensModule];
|
7474
|
+
const defaultBalanceModules = [EvmErc20Module, EvmNativeModule, EvmUniswapV2Module, SubAssetsModule, SubEquilibriumModule, SubForeignAssetsModule, SubNativeModule, SubPsp22Module, SubTokensModule];
|
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
|
+
};
|
6833
7501
|
|
6834
7502
|
exports.Balance = Balance;
|
6835
7503
|
exports.BalanceFormatter = BalanceFormatter;
|
@@ -6838,12 +7506,11 @@ exports.Balances = Balances;
|
|
6838
7506
|
exports.Change24hCurrencyFormatter = Change24hCurrencyFormatter;
|
6839
7507
|
exports.DefaultBalanceModule = DefaultBalanceModule;
|
6840
7508
|
exports.EvmErc20Module = EvmErc20Module;
|
6841
|
-
exports.EvmErc20TokenConfigSchema = EvmErc20TokenConfigSchema;
|
6842
7509
|
exports.EvmNativeModule = EvmNativeModule;
|
6843
|
-
exports.
|
7510
|
+
exports.EvmTokenFetcher = EvmTokenFetcher;
|
6844
7511
|
exports.EvmUniswapV2Module = EvmUniswapV2Module;
|
6845
|
-
exports.EvmUniswapV2TokenConfigSchema = EvmUniswapV2TokenConfigSchema;
|
6846
7512
|
exports.FiatSumBalancesFormatter = FiatSumBalancesFormatter;
|
7513
|
+
exports.MiniMetadataUpdater = MiniMetadataUpdater;
|
6847
7514
|
exports.ONE_ALPHA_TOKEN = ONE_ALPHA_TOKEN;
|
6848
7515
|
exports.PlanckSumBalancesFormatter = PlanckSumBalancesFormatter;
|
6849
7516
|
exports.RpcStateQueryHelper = RpcStateQueryHelper;
|
@@ -6851,20 +7518,15 @@ exports.SCALE_FACTOR = SCALE_FACTOR;
|
|
6851
7518
|
exports.SUBTENSOR_MIN_STAKE_AMOUNT_PLANK = SUBTENSOR_MIN_STAKE_AMOUNT_PLANK;
|
6852
7519
|
exports.SUBTENSOR_ROOT_NETUID = SUBTENSOR_ROOT_NETUID;
|
6853
7520
|
exports.SubAssetsModule = SubAssetsModule;
|
6854
|
-
exports.
|
7521
|
+
exports.SubEquilibriumModule = SubEquilibriumModule;
|
6855
7522
|
exports.SubForeignAssetsModule = SubForeignAssetsModule;
|
6856
|
-
exports.SubForeignAssetsTokenConfigSchema = SubForeignAssetsTokenConfigSchema;
|
6857
7523
|
exports.SubNativeModule = SubNativeModule;
|
6858
|
-
exports.SubNativeTokenConfigSchema = SubNativeTokenConfigSchema;
|
6859
7524
|
exports.SubPsp22Module = SubPsp22Module;
|
6860
|
-
exports.SubPsp22TokenConfigSchema = SubPsp22TokenConfigSchema;
|
6861
7525
|
exports.SubTokensModule = SubTokensModule;
|
6862
|
-
exports.SubTokensTokenConfigSchema = SubTokensTokenConfigSchema;
|
6863
7526
|
exports.SumBalancesFormatter = SumBalancesFormatter;
|
6864
7527
|
exports.TalismanBalancesDatabase = TalismanBalancesDatabase;
|
6865
7528
|
exports.abiMulticall = abiMulticall;
|
6866
7529
|
exports.balances = balances;
|
6867
|
-
exports.buildNetworkStorageCoders = buildNetworkStorageCoders;
|
6868
7530
|
exports.buildStorageCoders = buildStorageCoders;
|
6869
7531
|
exports.calculateAlphaPrice = calculateAlphaPrice;
|
6870
7532
|
exports.calculateTaoAmountFromAlpha = calculateTaoAmountFromAlpha;
|
@@ -6879,14 +7541,27 @@ exports.deriveMiniMetadataId = deriveMiniMetadataId;
|
|
6879
7541
|
exports.detectTransferMethod = detectTransferMethod;
|
6880
7542
|
exports.erc20Abi = erc20Abi;
|
6881
7543
|
exports.erc20BalancesAggregatorAbi = erc20BalancesAggregatorAbi;
|
7544
|
+
exports.evmErc20TokenId = evmErc20TokenId;
|
7545
|
+
exports.evmNativeTokenId = evmNativeTokenId;
|
7546
|
+
exports.evmUniswapV2TokenId = evmUniswapV2TokenId;
|
6882
7547
|
exports.excludeFromFeePayableLocks = excludeFromFeePayableLocks;
|
6883
7548
|
exports.excludeFromTransferableAmount = excludeFromTransferableAmount;
|
6884
7549
|
exports.filterBaseLocks = filterBaseLocks;
|
6885
7550
|
exports.filterMirrorTokens = filterMirrorTokens;
|
7551
|
+
exports.findChainMeta = findChainMeta;
|
6886
7552
|
exports.getBalanceId = getBalanceId;
|
6887
7553
|
exports.getLockTitle = getLockTitle;
|
6888
7554
|
exports.getUniqueChainIds = getUniqueChainIds;
|
6889
7555
|
exports.getValueId = getValueId;
|
7556
|
+
exports.hydrateChaindataAndMiniMetadata = hydrateChaindataAndMiniMetadata;
|
6890
7557
|
exports.includeInTotalExtraAmount = includeInTotalExtraAmount;
|
6891
7558
|
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;
|
6892
7565
|
exports.uniswapV2PairAbi = uniswapV2PairAbi;
|
7566
|
+
exports.updateCustomMiniMetadata = updateCustomMiniMetadata;
|
7567
|
+
exports.updateEvmTokens = updateEvmTokens;
|