@talismn/balances 0.0.0-pr2043-20250618091117 → 0.0.0-pr2043-20250619003406
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 +1 -1
- package/dist/declarations/src/getMiniMetadata/getMiniMetadatas.d.ts +1 -1
- package/dist/declarations/src/getMiniMetadata/getUpdatedMiniMetadatas.d.ts +1 -1
- package/dist/declarations/src/getMiniMetadata/index.d.ts +1 -1
- package/dist/declarations/src/index.d.ts +0 -2
- package/dist/declarations/src/modules/SubstrateNativeModule/types.d.ts +0 -4
- package/dist/declarations/src/modules/SubstrateNativeModule/util/systemProperties.d.ts +22 -0
- package/dist/declarations/src/modules/util/buildStorageCoders.d.ts +10 -0
- package/dist/talismn-balances.cjs.dev.js +410 -713
- package/dist/talismn-balances.cjs.prod.js +410 -713
- package/dist/talismn-balances.esm.js +411 -713
- package/package.json +9 -7
- package/dist/declarations/src/MiniMetadataUpdater.d.ts +0 -43
- package/dist/declarations/src/util/hydrateChaindata.d.ts +0 -8
- package/dist/declarations/src/util/index.d.ts +0 -1
@@ -1,8 +1,6 @@
|
|
1
1
|
'use strict';
|
2
2
|
|
3
|
-
var chaindataProvider = require('@talismn/chaindata-provider');
|
4
3
|
var dexie = require('dexie');
|
5
|
-
var rxjs = require('rxjs');
|
6
4
|
var anylogger = require('anylogger');
|
7
5
|
var tokenRates = require('@talismn/token-rates');
|
8
6
|
var util = require('@talismn/util');
|
@@ -10,12 +8,14 @@ var BigNumber = require('bignumber.js');
|
|
10
8
|
var util$1 = require('@polkadot/util');
|
11
9
|
var utilCrypto = require('@polkadot/util-crypto');
|
12
10
|
var pako = require('pako');
|
11
|
+
var chaindataProvider = require('@talismn/chaindata-provider');
|
13
12
|
var viem = require('viem');
|
14
13
|
var isEqual = require('lodash/isEqual');
|
15
14
|
var txwrapperCore = require('@substrate/txwrapper-core');
|
16
15
|
var scale = require('@talismn/scale');
|
17
16
|
var lodash = require('lodash');
|
18
17
|
var camelCase = require('lodash/camelCase');
|
18
|
+
var PQueue = require('p-queue');
|
19
19
|
var sapi = require('@talismn/sapi');
|
20
20
|
var types = require('@polkadot/types');
|
21
21
|
var groupBy = require('lodash/groupBy');
|
@@ -23,8 +23,10 @@ var utils = require('@polkadot-api/utils');
|
|
23
23
|
var polkadotApi = require('polkadot-api');
|
24
24
|
var PromisePool = require('@supercharge/promise-pool');
|
25
25
|
var chainConnector = require('@talismn/chain-connector');
|
26
|
+
var rxjs = require('rxjs');
|
26
27
|
var scaleTs = require('scale-ts');
|
27
28
|
var upperFirst = require('lodash/upperFirst');
|
29
|
+
var z = require('zod/v4');
|
28
30
|
var apiContract = require('@polkadot/api-contract');
|
29
31
|
|
30
32
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
@@ -34,9 +36,11 @@ var BigNumber__default = /*#__PURE__*/_interopDefault(BigNumber);
|
|
34
36
|
var pako__default = /*#__PURE__*/_interopDefault(pako);
|
35
37
|
var isEqual__default = /*#__PURE__*/_interopDefault(isEqual);
|
36
38
|
var camelCase__default = /*#__PURE__*/_interopDefault(camelCase);
|
39
|
+
var PQueue__default = /*#__PURE__*/_interopDefault(PQueue);
|
37
40
|
var groupBy__default = /*#__PURE__*/_interopDefault(groupBy);
|
38
41
|
var PromisePool__default = /*#__PURE__*/_interopDefault(PromisePool);
|
39
42
|
var upperFirst__default = /*#__PURE__*/_interopDefault(upperFirst);
|
43
|
+
var z__default = /*#__PURE__*/_interopDefault(z);
|
40
44
|
|
41
45
|
// TODO: Document default balances module purpose/usage
|
42
46
|
const DefaultBalanceModule = type => ({
|
@@ -120,13 +124,11 @@ class EvmTokenFetcher {
|
|
120
124
|
// }
|
121
125
|
}
|
122
126
|
|
123
|
-
var
|
127
|
+
var pkg = {
|
124
128
|
name: "@talismn/balances",
|
125
|
-
version: "0.0.0-pr2043-
|
126
|
-
|
127
|
-
const libVersion = packageJson.version;
|
129
|
+
version: "0.0.0-pr2043-20250619003406"};
|
128
130
|
|
129
|
-
var log = anylogger__default.default(
|
131
|
+
var log = anylogger__default.default(pkg.name);
|
130
132
|
|
131
133
|
function excludeFromTransferableAmount(locks) {
|
132
134
|
if (typeof locks === "string") return BigInt(locks);
|
@@ -336,20 +338,13 @@ class Balances {
|
|
336
338
|
return new SumBalancesFormatter(this);
|
337
339
|
}
|
338
340
|
}
|
339
|
-
|
340
|
-
// type BalanceJsonEvm = BalanceJson & { evmNetworkId: string }
|
341
|
-
|
342
|
-
// const isBalanceEvm = (balance: BalanceJson): balance is BalanceJsonEvm => "evmNetworkId" in balance
|
343
|
-
|
344
341
|
const getBalanceId = balance => {
|
345
342
|
const {
|
346
343
|
source,
|
347
344
|
address,
|
348
|
-
tokenId
|
349
|
-
networkId
|
345
|
+
tokenId
|
350
346
|
} = balance;
|
351
|
-
|
352
|
-
return [source, address, networkId, tokenId].filter(util.isTruthy).join("::");
|
347
|
+
return [source, address, tokenId].join("::");
|
353
348
|
};
|
354
349
|
|
355
350
|
/**
|
@@ -1061,257 +1056,6 @@ class TalismanBalancesDatabase extends dexie.Dexie {
|
|
1061
1056
|
}
|
1062
1057
|
const db = new TalismanBalancesDatabase();
|
1063
1058
|
|
1064
|
-
const minimumHydrationInterval = 300_000; // 300_000ms = 300s = 5 minutes
|
1065
|
-
|
1066
|
-
/**
|
1067
|
-
* A substrate dapp needs access to a set of types when it wants to communicate with a blockchain node.
|
1068
|
-
*
|
1069
|
-
* These types are used to encode requests & decode responses via the SCALE codec.
|
1070
|
-
* Each chain generally has its own set of types.
|
1071
|
-
*
|
1072
|
-
* Substrate provides a construct to retrieve these types from a blockchain node.
|
1073
|
-
* The chain metadata.
|
1074
|
-
*
|
1075
|
-
* The metadata includes the types required for any communication with the chain,
|
1076
|
-
* including lots of methods which are not relevant to balance fetching.
|
1077
|
-
*
|
1078
|
-
* As such, the metadata can clock in at around 1-2MB per chain, which is a lot of storage
|
1079
|
-
* for browser-based dapps which want to connect to lots of chains.
|
1080
|
-
*
|
1081
|
-
* By utilizing the wonderful [scale-ts](https://github.com/unstoppablejs/unstoppablejs/tree/main/packages/scale-ts#readme) library,
|
1082
|
-
* we can trim the chain metadata down so that it only includes the types we need for balance fetching.
|
1083
|
-
*
|
1084
|
-
* Each balance module has a function to do just that, `BalanceModule::fetchSubstrateChainMeta`.
|
1085
|
-
*
|
1086
|
-
* But, we only want to run this operation when necessary.
|
1087
|
-
*
|
1088
|
-
* The purpose of this class, `MiniMetadataUpdater`, is to maintain a local cache of
|
1089
|
-
* trimmed-down metadatas, which we'll refer to as `MiniMetadatas`.
|
1090
|
-
*/
|
1091
|
-
class MiniMetadataUpdater {
|
1092
|
-
#lastHydratedMiniMetadatasAt = 0;
|
1093
|
-
#lastHydratedCustomChainsAt = 0;
|
1094
|
-
#chainConnectors;
|
1095
|
-
#chaindataProvider;
|
1096
|
-
#balanceModules;
|
1097
|
-
constructor(chainConnectors, chaindataProvider, balanceModules) {
|
1098
|
-
this.#chainConnectors = chainConnectors;
|
1099
|
-
this.#chaindataProvider = chaindataProvider;
|
1100
|
-
this.#balanceModules = balanceModules;
|
1101
|
-
}
|
1102
|
-
|
1103
|
-
/** Subscribe to the metadata for a chain */
|
1104
|
-
subscribe(chainId) {
|
1105
|
-
return rxjs.from(dexie.liveQuery(() => db.miniMetadatas.filter(m => m.chainId === chainId).toArray().then(array => array[0])));
|
1106
|
-
}
|
1107
|
-
async update(chainIds) {
|
1108
|
-
await this.updateSubstrateChains(chainIds);
|
1109
|
-
}
|
1110
|
-
async statuses(chains) {
|
1111
|
-
const ids = await db.miniMetadatas.orderBy("id").primaryKeys();
|
1112
|
-
const wantedIdsByChain = new Map(chains.flatMap(({
|
1113
|
-
id: chainId,
|
1114
|
-
specName,
|
1115
|
-
specVersion
|
1116
|
-
}) => {
|
1117
|
-
if (specName === null) return [];
|
1118
|
-
if (specVersion === null) return [];
|
1119
|
-
return [[chainId, this.#balanceModules.filter(m => m.type.startsWith("substrate-")).map(({
|
1120
|
-
type: source
|
1121
|
-
}) => deriveMiniMetadataId({
|
1122
|
-
source,
|
1123
|
-
chainId,
|
1124
|
-
specVersion,
|
1125
|
-
libVersion
|
1126
|
-
}))]];
|
1127
|
-
}));
|
1128
|
-
const statusesByChain = new Map(Array.from(wantedIdsByChain.entries()).map(([chainId, wantedIds]) => [chainId, wantedIds.every(wantedId => ids.includes(wantedId)) ? "good" : "none"]));
|
1129
|
-
return {
|
1130
|
-
wantedIdsByChain,
|
1131
|
-
statusesByChain
|
1132
|
-
};
|
1133
|
-
}
|
1134
|
-
async hydrateFromChaindata() {
|
1135
|
-
// TODO review this. feels unnecessary to fetch them all
|
1136
|
-
|
1137
|
-
const now = Date.now();
|
1138
|
-
if (now - this.#lastHydratedMiniMetadatasAt < minimumHydrationInterval) return false;
|
1139
|
-
const dbHasMiniMetadatas = (await db.miniMetadatas.count()) > 0;
|
1140
|
-
try {
|
1141
|
-
try {
|
1142
|
-
var miniMetadatas = await this.#chaindataProvider.miniMetadatas(); // eslint-disable-line no-var
|
1143
|
-
if (miniMetadatas.length <= 0) throw new Error("Ignoring empty chaindata miniMetadatas response");
|
1144
|
-
} catch (error) {
|
1145
|
-
if (dbHasMiniMetadatas) throw error;
|
1146
|
-
log.warn("Failed to fetch miniMetadatas from chaindata", error);
|
1147
|
-
// On first start-up (db is empty), if we fail to fetch miniMetadatas then we should
|
1148
|
-
// initialize the DB with the list of miniMetadatas inside our init/mini-metadatas.json file.
|
1149
|
-
// This data will represent a relatively recent copy of what's in chaindata,
|
1150
|
-
// which will be better for our users than to have nothing at all.
|
1151
|
-
var miniMetadatas = await chaindataProvider.fetchInitMiniMetadatas(); // eslint-disable-line no-var
|
1152
|
-
}
|
1153
|
-
await db.miniMetadatas.bulkPut(miniMetadatas);
|
1154
|
-
this.#lastHydratedMiniMetadatasAt = now;
|
1155
|
-
return true;
|
1156
|
-
} catch (error) {
|
1157
|
-
log.warn(`Failed to hydrate miniMetadatas from chaindata`, error);
|
1158
|
-
return false;
|
1159
|
-
}
|
1160
|
-
}
|
1161
|
-
async hydrateCustomChains() {
|
1162
|
-
// TODO
|
1163
|
-
// const now = Date.now()
|
1164
|
-
// if (now - this.#lastHydratedCustomChainsAt < minimumHydrationInterval) return false
|
1165
|
-
// const chains = await this.#chaindataProvider.chains()
|
1166
|
-
// const customChains = chains.filter(
|
1167
|
-
// (chain): chain is CustomChain => "isCustom" in chain && chain.isCustom,
|
1168
|
-
// )
|
1169
|
-
// const updatedCustomChains: Array<CustomChain> = []
|
1170
|
-
// const concurrency = 4
|
1171
|
-
// ;(
|
1172
|
-
// await PromisePool.withConcurrency(concurrency)
|
1173
|
-
// .for(customChains)
|
1174
|
-
// .process(async (customChain) => {
|
1175
|
-
// const send = (method: string, params: unknown[]) =>
|
1176
|
-
// this.#chainConnectors.substrate?.send(customChain.id, method, params)
|
1177
|
-
// const [genesisHash, runtimeVersion, chainName, chainType] = await Promise.all([
|
1178
|
-
// send("chain_getBlockHash", [0]),
|
1179
|
-
// send("state_getRuntimeVersion", []),
|
1180
|
-
// send("system_chain", []),
|
1181
|
-
// send("system_chainType", []),
|
1182
|
-
// ])
|
1183
|
-
// // deconstruct rpc data
|
1184
|
-
// const { specName, implName } = runtimeVersion
|
1185
|
-
// const specVersion = String(runtimeVersion.specVersion)
|
1186
|
-
// const changed =
|
1187
|
-
// customChain.genesisHash !== genesisHash ||
|
1188
|
-
// customChain.chainName !== chainName ||
|
1189
|
-
// !isEqual(customChain.chainType, chainType) ||
|
1190
|
-
// customChain.implName !== implName ||
|
1191
|
-
// customChain.specName !== specName ||
|
1192
|
-
// customChain.specVersion !== specVersion
|
1193
|
-
// if (!changed) return
|
1194
|
-
// customChain.genesisHash = genesisHash
|
1195
|
-
// customChain.chainName = chainName
|
1196
|
-
// customChain.chainType = chainType
|
1197
|
-
// customChain.implName = implName
|
1198
|
-
// customChain.specName = specName
|
1199
|
-
// customChain.specVersion = specVersion
|
1200
|
-
// updatedCustomChains.push(customChain)
|
1201
|
-
// })
|
1202
|
-
// ).errors.forEach((error) => log.error("Error hydrating custom chains", error))
|
1203
|
-
// if (updatedCustomChains.length > 0) {
|
1204
|
-
// await this.#chaindataProvider.transaction("rw", ["chains"], async () => {
|
1205
|
-
// for (const updatedCustomChain of updatedCustomChains) {
|
1206
|
-
// await this.#chaindataProvider.removeCustomChain(updatedCustomChain.id)
|
1207
|
-
// await this.#chaindataProvider.addCustomChain(updatedCustomChain)
|
1208
|
-
// }
|
1209
|
-
// })
|
1210
|
-
// }
|
1211
|
-
// if (updatedCustomChains.length > 0) this.#lastHydratedCustomChainsAt = now
|
1212
|
-
// return true
|
1213
|
-
}
|
1214
|
-
async updateSubstrateChains(_chainIds) {
|
1215
|
-
// const chains = new Map(
|
1216
|
-
// (await this.#chaindataProvider.chains()).map((chain) => [chain.id, chain]),
|
1217
|
-
// )
|
1218
|
-
// const filteredChains = chainIds.flatMap((chainId) => chains.get(chainId) ?? [])
|
1219
|
-
// const ids = await balancesDb.miniMetadatas.orderBy("id").primaryKeys()
|
1220
|
-
// const { wantedIdsByChain, statusesByChain } = await this.statuses(filteredChains)
|
1221
|
-
// // clean up store
|
1222
|
-
// const wantedIds = Array.from(wantedIdsByChain.values()).flatMap((ids) => ids)
|
1223
|
-
// const unwantedIds = ids.filter((id) => !wantedIds.includes(id))
|
1224
|
-
// if (unwantedIds.length > 0) {
|
1225
|
-
// const chainIds = Array.from(
|
1226
|
-
// new Set((await balancesDb.miniMetadatas.bulkGet(unwantedIds)).map((m) => m?.chainId)),
|
1227
|
-
// )
|
1228
|
-
// log.info(`Pruning ${unwantedIds.length} miniMetadatas on chains ${chainIds.join(", ")}`)
|
1229
|
-
// await balancesDb.miniMetadatas.bulkDelete(unwantedIds)
|
1230
|
-
// }
|
1231
|
-
// const needUpdates = Array.from(statusesByChain.entries())
|
1232
|
-
// .filter(([, status]) => status !== "good")
|
1233
|
-
// .map(([chainId]) => chainId)
|
1234
|
-
// if (needUpdates.length > 0)
|
1235
|
-
// log.info(`${needUpdates.length} miniMetadatas need updates (${needUpdates.join(", ")})`)
|
1236
|
-
// const availableTokenLogos = await availableTokenLogoFilenames().catch((error) => {
|
1237
|
-
// log.error("Failed to fetch available token logos", error)
|
1238
|
-
// return []
|
1239
|
-
// })
|
1240
|
-
// const concurrency = 12
|
1241
|
-
// ;(
|
1242
|
-
// await PromisePool.withConcurrency(concurrency)
|
1243
|
-
// .for(needUpdates)
|
1244
|
-
// .process(async (chainId) => {
|
1245
|
-
// log.info(`Updating metadata for chain ${chainId}`)
|
1246
|
-
// const chain = chains.get(chainId)
|
1247
|
-
// if (!chain) return
|
1248
|
-
// const { specName, specVersion } = chain
|
1249
|
-
// if (specName === null) return
|
1250
|
-
// if (specVersion === null) return
|
1251
|
-
// const fetchMetadata = async () => {
|
1252
|
-
// try {
|
1253
|
-
// return await fetchBestMetadata(
|
1254
|
-
// (method, params, isCacheable) => {
|
1255
|
-
// if (!this.#chainConnectors.substrate)
|
1256
|
-
// throw new Error("Substrate connector is not available")
|
1257
|
-
// return this.#chainConnectors.substrate.send(chainId, method, params, isCacheable)
|
1258
|
-
// },
|
1259
|
-
// true, // allow v14 fallback
|
1260
|
-
// )
|
1261
|
-
// } catch (err) {
|
1262
|
-
// log.warn(`Failed to fetch metadata for chain ${chainId}`)
|
1263
|
-
// return undefined
|
1264
|
-
// }
|
1265
|
-
// }
|
1266
|
-
// const [metadataRpc, systemProperties] = await Promise.all([
|
1267
|
-
// fetchMetadata(),
|
1268
|
-
// this.#chainConnectors.substrate?.send(chainId, "system_properties", []),
|
1269
|
-
// ])
|
1270
|
-
// for (const mod of this.#balanceModules.filter((m) => m.type.startsWith("substrate-"))) {
|
1271
|
-
// const balancesConfig = (chain.balancesConfig ?? []).find(
|
1272
|
-
// ({ moduleType }) => moduleType === mod.type,
|
1273
|
-
// )
|
1274
|
-
// const moduleConfig = balancesConfig?.moduleConfig ?? {}
|
1275
|
-
// const chainMeta = await mod.fetchSubstrateChainMeta(
|
1276
|
-
// chainId,
|
1277
|
-
// moduleConfig,
|
1278
|
-
// metadataRpc,
|
1279
|
-
// systemProperties,
|
1280
|
-
// )
|
1281
|
-
// const tokens = await mod.fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig)
|
1282
|
-
// // update tokens in chaindata
|
1283
|
-
// await this.#chaindataProvider.updateChainTokens(
|
1284
|
-
// chainId,
|
1285
|
-
// mod.type,
|
1286
|
-
// Object.values(tokens),
|
1287
|
-
// availableTokenLogos,
|
1288
|
-
// )
|
1289
|
-
// // update miniMetadatas
|
1290
|
-
// const { miniMetadata: data, metadataVersion: version, ...extra } = chainMeta ?? {}
|
1291
|
-
// await balancesDb.miniMetadatas.put({
|
1292
|
-
// id: deriveMiniMetadataId({
|
1293
|
-
// source: mod.type,
|
1294
|
-
// chainId,
|
1295
|
-
// specName,
|
1296
|
-
// specVersion,
|
1297
|
-
// balancesConfig: JSON.stringify(moduleConfig),
|
1298
|
-
// }),
|
1299
|
-
// source: mod.type,
|
1300
|
-
// chainId,
|
1301
|
-
// specName,
|
1302
|
-
// specVersion,
|
1303
|
-
// balancesConfig: JSON.stringify(moduleConfig),
|
1304
|
-
// // TODO: Standardise return value from `fetchSubstrateChainMeta`
|
1305
|
-
// version,
|
1306
|
-
// data,
|
1307
|
-
// extra: JSON.stringify(extra),
|
1308
|
-
// })
|
1309
|
-
// }
|
1310
|
-
// })
|
1311
|
-
// ).errors.forEach((error) => log.error("Error updating chain metadata", error))
|
1312
|
-
}
|
1313
|
-
}
|
1314
|
-
|
1315
1059
|
const erc20Abi = [{
|
1316
1060
|
constant: true,
|
1317
1061
|
inputs: [],
|
@@ -2953,12 +2697,14 @@ async function getPoolBalance(publicClient, contractAddress, accountAddress) {
|
|
2953
2697
|
}
|
2954
2698
|
}
|
2955
2699
|
|
2700
|
+
const libVersion = pkg.version;
|
2701
|
+
|
2956
2702
|
// cache the promise so it can be shared across multiple calls
|
2957
2703
|
const CACHE_GET_SPEC_VERSION = new Map();
|
2958
2704
|
const fetchSpecVersion = async (chainConnector, networkId) => {
|
2959
2705
|
const {
|
2960
2706
|
specVersion
|
2961
|
-
} = await chainConnector.send(networkId, "state_getRuntimeVersion", [true
|
2707
|
+
} = await chainConnector.send(networkId, "state_getRuntimeVersion", [], true);
|
2962
2708
|
return specVersion;
|
2963
2709
|
};
|
2964
2710
|
|
@@ -3000,9 +2746,16 @@ const getMetadataRpc = async (chainConnector, networkId) => {
|
|
3000
2746
|
|
3001
2747
|
// share requests as all modules will call this at once
|
3002
2748
|
const CACHE = new Map();
|
3003
|
-
|
2749
|
+
|
2750
|
+
// ensures we dont fetch miniMetadatas on more than 4 chains at once
|
2751
|
+
const POOL = new PQueue__default.default({
|
2752
|
+
concurrency: 4
|
2753
|
+
});
|
2754
|
+
const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, specVersion, signal) => {
|
3004
2755
|
if (CACHE.has(networkId)) return CACHE.get(networkId);
|
3005
|
-
const pResult = fetchMiniMetadatas(chainConnector, chaindataProvider, networkId, specVersion)
|
2756
|
+
const pResult = POOL.add(() => fetchMiniMetadatas(chainConnector, chaindataProvider, networkId, specVersion), {
|
2757
|
+
signal
|
2758
|
+
});
|
3006
2759
|
CACHE.set(networkId, pResult);
|
3007
2760
|
try {
|
3008
2761
|
return await pResult;
|
@@ -3014,49 +2767,57 @@ const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, sp
|
|
3014
2767
|
CACHE.delete(networkId);
|
3015
2768
|
}
|
3016
2769
|
};
|
3017
|
-
const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion) => {
|
3018
|
-
const
|
3019
|
-
|
3020
|
-
|
3021
|
-
|
3022
|
-
|
3023
|
-
chainConnectors
|
3024
|
-
|
3025
|
-
|
3026
|
-
|
3027
|
-
const
|
3028
|
-
|
3029
|
-
|
3030
|
-
|
2770
|
+
const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion, signal) => {
|
2771
|
+
const start = performance.now();
|
2772
|
+
log.debug("[miniMetadata] fetching minimetadatas for %s", chainId);
|
2773
|
+
try {
|
2774
|
+
const metadataRpc = await getMetadataRpc(chainConnector, chainId);
|
2775
|
+
signal?.throwIfAborted();
|
2776
|
+
const chainConnectors = {
|
2777
|
+
substrate: chainConnector,
|
2778
|
+
evm: {} // wont be used but workarounds error for module creation
|
2779
|
+
};
|
2780
|
+
const modules = defaultBalanceModules.map(mod => mod({
|
2781
|
+
chainConnectors,
|
2782
|
+
chaindataProvider
|
2783
|
+
})).filter(mod => mod.type.startsWith("substrate-"));
|
2784
|
+
return Promise.all(modules.map(async mod => {
|
2785
|
+
const source = mod.type;
|
2786
|
+
const chainMeta = await mod.fetchSubstrateChainMeta(chainId, {}, metadataRpc);
|
2787
|
+
return {
|
2788
|
+
id: deriveMiniMetadataId({
|
2789
|
+
source,
|
2790
|
+
chainId,
|
2791
|
+
specVersion,
|
2792
|
+
libVersion
|
2793
|
+
}),
|
3031
2794
|
source,
|
3032
2795
|
chainId,
|
3033
2796
|
specVersion,
|
3034
|
-
libVersion
|
3035
|
-
|
3036
|
-
|
3037
|
-
|
3038
|
-
|
3039
|
-
|
3040
|
-
|
3041
|
-
};
|
3042
|
-
}));
|
2797
|
+
libVersion,
|
2798
|
+
data: chainMeta?.miniMetadata ?? null
|
2799
|
+
};
|
2800
|
+
}));
|
2801
|
+
} finally {
|
2802
|
+
log.debug("[miniMetadata] updated miniMetadatas for %s in %sms", chainId, performance.now() - start);
|
2803
|
+
}
|
3043
2804
|
};
|
3044
2805
|
|
3045
|
-
const getUpdatedMiniMetadatas = async (chainConnector, chaindataProvider,
|
3046
|
-
const miniMetadatas = await getMiniMetadatas(chainConnector, chaindataProvider,
|
2806
|
+
const getUpdatedMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion, signal) => {
|
2807
|
+
const miniMetadatas = await getMiniMetadatas(chainConnector, chaindataProvider, chainId, specVersion, signal);
|
2808
|
+
signal?.throwIfAborted();
|
3047
2809
|
await db.transaction("readwrite", "miniMetadatas", async tx => {
|
3048
2810
|
await tx.miniMetadatas.where({
|
3049
|
-
|
2811
|
+
chainId
|
3050
2812
|
}).delete();
|
3051
2813
|
await tx.miniMetadatas.bulkPut(miniMetadatas);
|
3052
2814
|
});
|
3053
2815
|
return miniMetadatas;
|
3054
2816
|
};
|
3055
2817
|
|
3056
|
-
const getMiniMetadata = async (chaindataProvider, chainConnector, chainId, source) => {
|
2818
|
+
const getMiniMetadata = async (chaindataProvider, chainConnector, chainId, source, signal) => {
|
3057
2819
|
const specVersion = await getSpecVersion(chainConnector, chainId);
|
3058
|
-
|
3059
|
-
// TODO when working a chaindata branch, need a way to pass the libVersion used to derive the miniMetadataId got github
|
2820
|
+
signal?.throwIfAborted();
|
3060
2821
|
const miniMetadataId = deriveMiniMetadataId({
|
3061
2822
|
source,
|
3062
2823
|
chainId,
|
@@ -3066,11 +2827,13 @@ const getMiniMetadata = async (chaindataProvider, chainConnector, chainId, sourc
|
|
3066
2827
|
|
3067
2828
|
// lookup local ones
|
3068
2829
|
const [dbMiniMetadata, ghMiniMetadata] = await Promise.all([db.miniMetadatas.get(miniMetadataId), chaindataProvider.miniMetadataById(miniMetadataId)]);
|
2830
|
+
signal?.throwIfAborted();
|
3069
2831
|
const miniMetadata = dbMiniMetadata ?? ghMiniMetadata;
|
3070
2832
|
if (miniMetadata) return miniMetadata;
|
3071
2833
|
|
3072
2834
|
// update from live chain metadata and persist locally
|
3073
|
-
const miniMetadatas = await getUpdatedMiniMetadatas(chainConnector, chaindataProvider, chainId, specVersion);
|
2835
|
+
const miniMetadatas = await getUpdatedMiniMetadatas(chainConnector, chaindataProvider, chainId, specVersion, signal);
|
2836
|
+
signal?.throwIfAborted();
|
3074
2837
|
const found = miniMetadatas.find(m => m.id === miniMetadataId);
|
3075
2838
|
if (!found) {
|
3076
2839
|
log.warn("MiniMetadata not found in updated miniMetadatas", {
|
@@ -3157,6 +2920,28 @@ const buildStorageCoders = ({
|
|
3157
2920
|
return [];
|
3158
2921
|
}
|
3159
2922
|
}));
|
2923
|
+
const buildNetworkStorageCoders = (chainId, miniMetadata, coders) => {
|
2924
|
+
if (!miniMetadata.data) return null;
|
2925
|
+
const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata.data));
|
2926
|
+
try {
|
2927
|
+
const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
|
2928
|
+
const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
|
2929
|
+
const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
|
2930
|
+
chainId
|
2931
|
+
}) : moduleMethodOrFn;
|
2932
|
+
try {
|
2933
|
+
return [[key, scaleBuilder.buildStorage(module, method)]];
|
2934
|
+
} catch (cause) {
|
2935
|
+
log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
|
2936
|
+
return [];
|
2937
|
+
}
|
2938
|
+
}));
|
2939
|
+
return builtCoders;
|
2940
|
+
} catch (cause) {
|
2941
|
+
log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
|
2942
|
+
}
|
2943
|
+
return null;
|
2944
|
+
};
|
3160
2945
|
|
3161
2946
|
/**
|
3162
2947
|
* Decodes & unwraps outputs and errors of a given result, contract, and method.
|
@@ -3357,15 +3142,9 @@ const SubAssetsModule = hydrate => {
|
|
3357
3142
|
util$1.assert(chainConnector, "This module requires a substrate chain connector");
|
3358
3143
|
return {
|
3359
3144
|
...DefaultBalanceModule(moduleType$4),
|
3145
|
+
// TODO make synchronous at the module definition level ?
|
3360
3146
|
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
|
3361
|
-
|
3362
|
-
if (metadataRpc === undefined) return {
|
3363
|
-
isTestnet
|
3364
|
-
};
|
3365
|
-
if ((moduleConfig?.tokens ?? []).length < 1) return {
|
3366
|
-
isTestnet
|
3367
|
-
};
|
3368
|
-
const metadataVersion = scale.getMetadataVersion(metadataRpc);
|
3147
|
+
if (!metadataRpc) return {};
|
3369
3148
|
const metadata = scale.decAnyMetadata(metadataRpc);
|
3370
3149
|
scale.compactMetadata(metadata, [{
|
3371
3150
|
pallet: "Assets",
|
@@ -3373,9 +3152,7 @@ const SubAssetsModule = hydrate => {
|
|
3373
3152
|
}]);
|
3374
3153
|
const miniMetadata = scale.encodeMetadata(metadata);
|
3375
3154
|
return {
|
3376
|
-
|
3377
|
-
miniMetadata,
|
3378
|
-
metadataVersion
|
3155
|
+
miniMetadata
|
3379
3156
|
};
|
3380
3157
|
},
|
3381
3158
|
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
|
@@ -3440,44 +3217,32 @@ const SubAssetsModule = hydrate => {
|
|
3440
3217
|
return acc;
|
3441
3218
|
}, {});
|
3442
3219
|
const controller = new AbortController();
|
3443
|
-
|
3444
|
-
|
3445
|
-
|
3446
|
-
|
3447
|
-
|
3448
|
-
|
3449
|
-
|
3450
|
-
|
3451
|
-
|
3452
|
-
|
3453
|
-
|
3454
|
-
|
3455
|
-
|
3456
|
-
|
3220
|
+
const pUnsubs = Promise.all(lodash.toPairs(byNetwork).map(async ([networkId, addressesByToken]) => {
|
3221
|
+
try {
|
3222
|
+
const queries = await buildNetworkQueries$1(networkId, chainConnector, chaindataProvider$1, addressesByToken, controller.signal);
|
3223
|
+
if (controller.signal.aborted) return () => {};
|
3224
|
+
const stateHelper = new RpcStateQueryHelper(chainConnector, queries);
|
3225
|
+
return await stateHelper.subscribe((error, result) => {
|
3226
|
+
// console.log("SubstrateAssetsModule.callback", { error, result })
|
3227
|
+
if (error) return callback(error);
|
3228
|
+
const balances = result?.filter(b => b !== null) ?? [];
|
3229
|
+
if (balances.length > 0) callback(null, new Balances(balances));
|
3230
|
+
});
|
3231
|
+
} catch (err) {
|
3232
|
+
if (!controller.signal.aborted) log.error(`Failed to subscribe balances for network ${networkId}`, err);
|
3233
|
+
return () => {};
|
3234
|
+
}
|
3457
3235
|
}));
|
3458
|
-
|
3459
|
-
// const networkIds = uniq(uniq(keys(addressesByToken)).map((tokenId) => parseSubAssetTokenId(tokenId).networkId))
|
3460
|
-
// const
|
3461
|
-
|
3462
|
-
//console.log("SubstrateAssetsModule.subscribeBalances 1", { addressesByToken })
|
3463
|
-
// const queries = await buildQueries(chaindataProvider, addressesByToken)
|
3464
|
-
// //console.log("SubstrateAssetsModule.subscribeBalances 2", { queries, addressesByToken })
|
3465
|
-
// const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe(
|
3466
|
-
// (error, result) => {
|
3467
|
-
// // console.log("SubstrateAssetsModule.callback", { error, result })
|
3468
|
-
// if (error) return callback(error)
|
3469
|
-
// const balances = result?.filter((b): b is SubAssetsBalance => b !== null) ?? []
|
3470
|
-
// if (balances.length > 0) callback(null, new Balances(balances))
|
3471
|
-
// },
|
3472
|
-
// )
|
3473
|
-
|
3474
3236
|
return () => {
|
3475
3237
|
controller.abort();
|
3238
|
+
pUnsubs.then(unsubs => {
|
3239
|
+
unsubs.forEach(unsubscribe => unsubscribe());
|
3240
|
+
});
|
3476
3241
|
};
|
3477
3242
|
},
|
3478
3243
|
async fetchBalances(addressesByToken) {
|
3479
3244
|
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
3480
|
-
const queries = await buildQueries$3(chaindataProvider$1, addressesByToken);
|
3245
|
+
const queries = await buildQueries$3(chainConnector, chaindataProvider$1, addressesByToken);
|
3481
3246
|
const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
|
3482
3247
|
const balances = result?.filter(b => b !== null) ?? [];
|
3483
3248
|
return new Balances(balances);
|
@@ -3547,23 +3312,14 @@ const SubAssetsModule = hydrate => {
|
|
3547
3312
|
}
|
3548
3313
|
};
|
3549
3314
|
};
|
3550
|
-
async function buildNetworkQueries(networkId, chainConnector, chaindataProvider, addressesByToken) {
|
3551
|
-
const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType$4);
|
3552
|
-
|
3315
|
+
async function buildNetworkQueries$1(networkId, chainConnector, chaindataProvider, addressesByToken, signal) {
|
3316
|
+
const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType$4, signal);
|
3317
|
+
// console.log("Fetched miniMetadata for network", networkId, { miniMetadata })
|
3318
|
+
const chain = await chaindataProvider.chainById(networkId);
|
3553
3319
|
const tokensById = await chaindataProvider.tokensById();
|
3554
|
-
|
3555
|
-
const
|
3556
|
-
[
|
3557
|
-
} : {};
|
3558
|
-
const miniMetadatas = new Map([[miniMetadata.id, miniMetadata]]);
|
3559
|
-
const chainStorageCoders = buildStorageCoders({
|
3560
|
-
chainIds,
|
3561
|
-
chains,
|
3562
|
-
miniMetadatas,
|
3563
|
-
moduleType: moduleType$4,
|
3564
|
-
coders: {
|
3565
|
-
storage: ["Assets", "Account"]
|
3566
|
-
}
|
3320
|
+
signal?.throwIfAborted();
|
3321
|
+
const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
|
3322
|
+
storage: ["Assets", "Account"]
|
3567
3323
|
});
|
3568
3324
|
return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
|
3569
3325
|
const token = tokensById[tokenId];
|
@@ -3575,19 +3331,14 @@ async function buildNetworkQueries(networkId, chainConnector, chaindataProvider,
|
|
3575
3331
|
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
3576
3332
|
return [];
|
3577
3333
|
}
|
3578
|
-
|
3579
|
-
if (!networkId) {
|
3580
|
-
log.warn(`Token ${tokenId} has no chain`);
|
3581
|
-
return [];
|
3582
|
-
}
|
3583
|
-
const chain = chains[networkId];
|
3334
|
+
//
|
3584
3335
|
if (!chain) {
|
3585
3336
|
log.warn(`Chain ${networkId} for token ${tokenId} not found`);
|
3586
3337
|
return [];
|
3587
3338
|
}
|
3588
3339
|
return addresses.flatMap(address => {
|
3589
|
-
const scaleCoder =
|
3590
|
-
const stateKey = tryEncode(scaleCoder, BigInt(token.assetId), address) ?? tryEncode(scaleCoder, token.assetId, address);
|
3340
|
+
const scaleCoder = networkStorageCoders?.storage;
|
3341
|
+
const stateKey = tryEncode(scaleCoder, BigInt(token.assetId), address) ?? tryEncode(scaleCoder, Number(token.assetId), address);
|
3591
3342
|
if (!stateKey) {
|
3592
3343
|
log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
|
3593
3344
|
return [];
|
@@ -3642,102 +3393,16 @@ async function buildNetworkQueries(networkId, chainConnector, chaindataProvider,
|
|
3642
3393
|
});
|
3643
3394
|
});
|
3644
3395
|
}
|
3645
|
-
async function buildQueries$3(chaindataProvider, addressesByToken) {
|
3646
|
-
const
|
3647
|
-
|
3648
|
-
|
3649
|
-
|
3650
|
-
|
3651
|
-
|
3652
|
-
|
3653
|
-
|
3654
|
-
|
3655
|
-
const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
|
3656
|
-
const chainStorageCoders = buildStorageCoders({
|
3657
|
-
chainIds: uniqueChainIds,
|
3658
|
-
chains,
|
3659
|
-
miniMetadatas,
|
3660
|
-
moduleType: "substrate-assets",
|
3661
|
-
coders: {
|
3662
|
-
storage: ["Assets", "Account"]
|
3663
|
-
}
|
3664
|
-
});
|
3665
|
-
return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
|
3666
|
-
const token = tokens[tokenId];
|
3667
|
-
if (!token) {
|
3668
|
-
log.warn(`Token ${tokenId} not found`);
|
3669
|
-
return [];
|
3670
|
-
}
|
3671
|
-
if (token.type !== "substrate-assets") {
|
3672
|
-
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
3673
|
-
return [];
|
3674
|
-
}
|
3675
|
-
const networkId = token.networkId;
|
3676
|
-
if (!networkId) {
|
3677
|
-
log.warn(`Token ${tokenId} has no chain`);
|
3678
|
-
return [];
|
3679
|
-
}
|
3680
|
-
const chain = chains[networkId];
|
3681
|
-
if (!chain) {
|
3682
|
-
log.warn(`Chain ${networkId} for token ${tokenId} not found`);
|
3683
|
-
return [];
|
3684
|
-
}
|
3685
|
-
return addresses.flatMap(address => {
|
3686
|
-
const scaleCoder = chainStorageCoders.get(networkId)?.storage;
|
3687
|
-
const stateKey = tryEncode(scaleCoder, BigInt(token.assetId), address) ?? tryEncode(scaleCoder, token.assetId, address);
|
3688
|
-
if (!stateKey) {
|
3689
|
-
log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
|
3690
|
-
return [];
|
3691
|
-
}
|
3692
|
-
const decodeResult = change => {
|
3693
|
-
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
3694
|
-
|
3695
|
-
const decoded = scale.decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
|
3696
|
-
balance: 0n,
|
3697
|
-
status: {
|
3698
|
-
type: "Liquid"
|
3699
|
-
}};
|
3700
|
-
const isFrozen = decoded?.status?.type === "Frozen";
|
3701
|
-
const amount = (decoded?.balance ?? 0n).toString();
|
3702
|
-
|
3703
|
-
// due to the following balance calculations, which are made in the `Balance` type:
|
3704
|
-
//
|
3705
|
-
// total balance = (free balance) + (reserved balance)
|
3706
|
-
// transferable balance = (free balance) - (frozen balance)
|
3707
|
-
//
|
3708
|
-
// when `isFrozen` is true we need to set **both** the `free` and `frozen` amounts
|
3709
|
-
// of this balance to the value we received from the RPC.
|
3710
|
-
//
|
3711
|
-
// if we only set the `frozen` amount, then the `total` calculation will be incorrect!
|
3712
|
-
const free = amount;
|
3713
|
-
const frozen = token.isFrozen || isFrozen ? amount : "0";
|
3714
|
-
|
3715
|
-
// include balance values even if zero, so that newly-zero values overwrite old values
|
3716
|
-
const balanceValues = [{
|
3717
|
-
type: "free",
|
3718
|
-
label: "free",
|
3719
|
-
amount: free.toString()
|
3720
|
-
}, {
|
3721
|
-
type: "locked",
|
3722
|
-
label: "frozen",
|
3723
|
-
amount: frozen.toString()
|
3724
|
-
}];
|
3725
|
-
return {
|
3726
|
-
source: "substrate-assets",
|
3727
|
-
status: "live",
|
3728
|
-
address,
|
3729
|
-
networkId,
|
3730
|
-
tokenId: token.id,
|
3731
|
-
values: balanceValues
|
3732
|
-
};
|
3733
|
-
};
|
3734
|
-
return {
|
3735
|
-
chainId: networkId,
|
3736
|
-
stateKey,
|
3737
|
-
decodeResult
|
3738
|
-
};
|
3739
|
-
});
|
3740
|
-
});
|
3396
|
+
async function buildQueries$3(chainConnector, chaindataProvider$1, addressesByToken, signal) {
|
3397
|
+
const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
|
3398
|
+
const networkId = chaindataProvider.parseSubAssetTokenId(tokenId).networkId;
|
3399
|
+
if (!acc[networkId]) acc[networkId] = {};
|
3400
|
+
acc[networkId][tokenId] = addressesByToken[tokenId];
|
3401
|
+
return acc;
|
3402
|
+
}, {});
|
3403
|
+
return (await Promise.all(lodash.toPairs(byNetwork).map(([networkId, addressesByToken]) => {
|
3404
|
+
return buildNetworkQueries$1(networkId, chainConnector, chaindataProvider$1, addressesByToken, signal);
|
3405
|
+
}))).flat();
|
3741
3406
|
}
|
3742
3407
|
// NOTE: Different chains need different formats for assetId when encoding the stateKey
|
3743
3408
|
// E.g. Polkadot Asset Hub needs it to be a string, Astar needs it to be a bigint
|
@@ -5413,8 +5078,23 @@ class SubNativeBalanceError extends Error {
|
|
5413
5078
|
}
|
5414
5079
|
}
|
5415
5080
|
|
5416
|
-
const
|
5417
|
-
|
5081
|
+
const DotNetworkPropertiesSimple = z__default.default.object({
|
5082
|
+
tokenDecimals: z__default.default.number().optional().default(0),
|
5083
|
+
tokenSymbol: z__default.default.string().optional().default("Unit")
|
5084
|
+
});
|
5085
|
+
const DotNetworkPropertiesArray = z__default.default.object({
|
5086
|
+
tokenDecimals: z__default.default.array(z__default.default.number()).nonempty(),
|
5087
|
+
tokenSymbol: z__default.default.array(z__default.default.string()).nonempty()
|
5088
|
+
});
|
5089
|
+
const DotNetworkProperties = z__default.default.union([DotNetworkPropertiesSimple, DotNetworkPropertiesArray]).transform(val => ({
|
5090
|
+
tokenDecimals: Array.isArray(val.tokenDecimals) ? val.tokenDecimals[0] : val.tokenDecimals,
|
5091
|
+
tokenSymbol: Array.isArray(val.tokenSymbol) ? val.tokenSymbol[0] : val.tokenSymbol
|
5092
|
+
}));
|
5093
|
+
const getChainProperties = async (chainConnector, networkId) => {
|
5094
|
+
const properties = await chainConnector.send(networkId, "system_properties", [], true);
|
5095
|
+
return DotNetworkProperties.parse(properties);
|
5096
|
+
};
|
5097
|
+
|
5418
5098
|
const POLLING_WINDOW_SIZE = 20;
|
5419
5099
|
const MAX_SUBSCRIPTION_SIZE = 40;
|
5420
5100
|
const SubNativeModule = hydrate => {
|
@@ -5428,29 +5108,187 @@ const SubNativeModule = hydrate => {
|
|
5428
5108
|
const getModuleTokens = async () => {
|
5429
5109
|
return await chaindataProvider$1.tokensByIdForType(moduleType$2);
|
5430
5110
|
};
|
5431
|
-
return {
|
5432
|
-
...DefaultBalanceModule(moduleType$2),
|
5433
|
-
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc, systemProperties) {
|
5434
|
-
const isTestnet = (await chaindataProvider$1.chainById(chainId))?.isTestnet || false;
|
5435
|
-
if (moduleConfig?.disable === true || metadataRpc === undefined) return {
|
5436
|
-
isTestnet
|
5437
|
-
};
|
5438
5111
|
|
5439
|
-
|
5440
|
-
|
5441
|
-
|
5112
|
+
// subscribeBalances was split by network to prevent all subs to wait for all minimetadatas to be ready.
|
5113
|
+
// 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
|
5114
|
+
// TODO refactor this be actually network specific
|
5115
|
+
const subscribeChainBalances = async (chainId, opts, callback) => {
|
5116
|
+
const {
|
5117
|
+
addressesByToken,
|
5118
|
+
initialBalances
|
5119
|
+
} = opts;
|
5120
|
+
// full record of balances for this module
|
5121
|
+
const subNativeBalances = new rxjs.BehaviorSubject(Object.fromEntries(initialBalances?.map(b => [getBalanceId(b), b]) ?? []));
|
5122
|
+
// tokens which have a known positive balance
|
5123
|
+
const positiveBalanceTokens = subNativeBalances.pipe(rxjs.map(balances => Array.from(new Set(Object.values(balances).map(b => b.tokenId)))), rxjs.share());
|
5124
|
+
|
5125
|
+
// tokens that will be subscribed to, simply a slice of the positive balance tokens of size MAX_SUBSCRIPTION_SIZE
|
5126
|
+
const subscriptionTokens = positiveBalanceTokens.pipe(rxjs.map(tokens => tokens.sort(sortChains).slice(0, MAX_SUBSCRIPTION_SIZE)));
|
5127
|
+
|
5128
|
+
// an initialised balance is one where we have received a response for any type of 'subsource',
|
5129
|
+
// until then they are initialising. We only need to maintain one map of tokens to addresses for this
|
5130
|
+
const initialisingBalances = Object.entries(addressesByToken).reduce((acc, [tokenId, addresses]) => {
|
5131
|
+
acc.set(tokenId, new Set(addresses));
|
5132
|
+
return acc;
|
5133
|
+
}, new Map());
|
5134
|
+
|
5135
|
+
// after thirty seconds, we need to kill the initialising balances
|
5136
|
+
const initBalancesTimeout = setTimeout(() => {
|
5137
|
+
initialisingBalances.clear();
|
5138
|
+
// manually call the callback to ensure the caller gets the correct status
|
5139
|
+
callback(null, {
|
5140
|
+
status: "live",
|
5141
|
+
data: Object.values(subNativeBalances.getValue())
|
5142
|
+
});
|
5143
|
+
}, 30_000);
|
5144
|
+
const _callbackSub = subNativeBalances.pipe(rxjs.debounceTime(100)).subscribe({
|
5145
|
+
next: balances => {
|
5146
|
+
callback(null, {
|
5147
|
+
status: initialisingBalances.size > 0 ? "initialising" : "live",
|
5148
|
+
data: Object.values(balances)
|
5149
|
+
});
|
5150
|
+
},
|
5151
|
+
error: error => callback(error),
|
5152
|
+
complete: () => {
|
5153
|
+
initialisingBalances.clear();
|
5154
|
+
clearTimeout(initBalancesTimeout);
|
5155
|
+
}
|
5156
|
+
});
|
5157
|
+
const unsubDeferred = util.Deferred();
|
5158
|
+
// we return this to the caller so that they can let us know when they're no longer interested in this subscription
|
5159
|
+
const callerUnsubscribe = () => {
|
5160
|
+
subNativeBalances.complete();
|
5161
|
+
_callbackSub.unsubscribe();
|
5162
|
+
return unsubDeferred.reject(new Error(`Caller unsubscribed`));
|
5163
|
+
};
|
5164
|
+
// we queue up our work to clean up our subscription when this promise rejects
|
5165
|
+
const callerUnsubscribed = unsubDeferred.promise;
|
5166
|
+
|
5167
|
+
// The update handler is to allow us to merge balances with the same id, and manage initialising and positive balances state for each
|
5168
|
+
// balance type and network
|
5169
|
+
const handleUpdateForSource = source => (error, result) => {
|
5170
|
+
if (result) {
|
5171
|
+
const currentBalances = subNativeBalances.getValue();
|
5172
|
+
|
5173
|
+
// first merge any balances with the same id within the result
|
5174
|
+
const accumulatedUpdates = result.filter(b => b.values.length > 0).reduce((acc, b) => {
|
5175
|
+
const bId = getBalanceId(b);
|
5176
|
+
acc[bId] = mergeBalances(acc[bId], b, source, false);
|
5177
|
+
return acc;
|
5178
|
+
}, {});
|
5179
|
+
|
5180
|
+
// then merge these with the current balances
|
5181
|
+
const mergedBalances = {};
|
5182
|
+
Object.entries(accumulatedUpdates).forEach(([bId, b]) => {
|
5183
|
+
// merge the values from the new balance into the existing balance, if there is one
|
5184
|
+
mergedBalances[bId] = mergeBalances(currentBalances[bId], b, source, true);
|
5185
|
+
|
5186
|
+
// update initialisingBalances to remove balances which have been updated
|
5187
|
+
const intialisingForToken = initialisingBalances.get(b.tokenId);
|
5188
|
+
if (intialisingForToken) {
|
5189
|
+
intialisingForToken.delete(b.address);
|
5190
|
+
if (intialisingForToken.size === 0) initialisingBalances.delete(b.tokenId);else initialisingBalances.set(b.tokenId, intialisingForToken);
|
5191
|
+
}
|
5192
|
+
});
|
5193
|
+
subNativeBalances.next({
|
5194
|
+
...currentBalances,
|
5195
|
+
...mergedBalances
|
5196
|
+
});
|
5197
|
+
}
|
5198
|
+
if (error) {
|
5199
|
+
if (error instanceof SubNativeBalanceError) {
|
5200
|
+
// this type of error doesn't need to be handled by the caller
|
5201
|
+
initialisingBalances.delete(error.tokenId);
|
5202
|
+
} else return callback(error);
|
5203
|
+
}
|
5204
|
+
};
|
5442
5205
|
|
5443
|
-
|
5444
|
-
|
5445
|
-
|
5446
|
-
|
5447
|
-
|
5448
|
-
|
5206
|
+
// subscribe to addresses and tokens for which we have a known positive balance
|
5207
|
+
const positiveSub = subscriptionTokens.pipe(rxjs.debounceTime(1000), rxjs.takeUntil(callerUnsubscribed), rxjs.map(tokenIds => tokenIds.reduce((acc, tokenId) => {
|
5208
|
+
acc[tokenId] = addressesByToken[tokenId];
|
5209
|
+
return acc;
|
5210
|
+
}, {})), rxjs.distinctUntilChanged(isEqual__default.default), rxjs.switchMap(newAddressesByToken => {
|
5211
|
+
return rxjs.from(queryCache.getQueries(newAddressesByToken)).pipe(rxjs.switchMap(baseQueries => {
|
5212
|
+
return new rxjs.Observable(subscriber => {
|
5213
|
+
if (!chainConnectors.substrate) return;
|
5214
|
+
const unsubSubtensorStaking = subscribeSubtensorStaking(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("subtensor-staking"));
|
5215
|
+
const unsubNompoolStaking = subscribeNompoolStaking(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("nompools-staking"));
|
5216
|
+
const unsubCrowdloans = subscribeCrowdloans(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("crowdloan"));
|
5217
|
+
const unsubBase = subscribeBase(baseQueries, chainConnectors.substrate, handleUpdateForSource("base"));
|
5218
|
+
subscriber.add(async () => (await unsubSubtensorStaking)());
|
5219
|
+
subscriber.add(async () => (await unsubNompoolStaking)());
|
5220
|
+
subscriber.add(async () => (await unsubCrowdloans)());
|
5221
|
+
subscriber.add(async () => (await unsubBase)());
|
5222
|
+
});
|
5223
|
+
}));
|
5224
|
+
})).subscribe();
|
5225
|
+
|
5226
|
+
// for chains where we don't have a known positive balance, poll rather than subscribe
|
5227
|
+
const poll = async (addressesByToken = {}) => {
|
5228
|
+
const handleUpdate = handleUpdateForSource("base");
|
5229
|
+
try {
|
5230
|
+
const balances = await fetchBalances(addressesByToken);
|
5231
|
+
handleUpdate(null, Object.values(balances.toJSON()));
|
5232
|
+
} catch (error) {
|
5233
|
+
if (error instanceof chainConnector.ChainConnectionError) {
|
5234
|
+
// coerce ChainConnection errors into SubNativeBalance errors
|
5235
|
+
const errorChainId = error.chainId;
|
5236
|
+
Object.entries(await getModuleTokens()).filter(([, token]) => token.networkId === errorChainId).forEach(([tokenId]) => {
|
5237
|
+
const wrappedError = new SubNativeBalanceError(tokenId, error.message);
|
5238
|
+
handleUpdate(wrappedError);
|
5239
|
+
});
|
5240
|
+
} else {
|
5241
|
+
log.error("unknown substrate native balance error", error);
|
5242
|
+
handleUpdate(error);
|
5243
|
+
}
|
5244
|
+
}
|
5245
|
+
};
|
5246
|
+
// do one poll to get things started
|
5247
|
+
const currentBalances = subNativeBalances.getValue();
|
5248
|
+
const currentTokens = new Set(Object.values(currentBalances).map(b => b.tokenId));
|
5249
|
+
const nonCurrentTokens = Object.keys(addressesByToken).filter(tokenId => !currentTokens.has(tokenId)).sort(sortChains);
|
5250
|
+
|
5251
|
+
// break nonCurrentTokens into chunks of POLLING_WINDOW_SIZE
|
5252
|
+
await PromisePool__default.default.withConcurrency(POLLING_WINDOW_SIZE).for(nonCurrentTokens).process(async nonCurrentTokenId => await poll({
|
5253
|
+
[nonCurrentTokenId]: addressesByToken[nonCurrentTokenId]
|
5254
|
+
}));
|
5255
|
+
|
5256
|
+
// now poll every 30s on chains which are not subscriptionTokens
|
5257
|
+
// we chunk this observable into batches of positive token ids, to prevent eating all the websocket connections
|
5258
|
+
const pollingSub = rxjs.interval(30_000) // emit values every 30 seconds
|
5259
|
+
.pipe(rxjs.takeUntil(callerUnsubscribed), rxjs.withLatestFrom(subscriptionTokens),
|
5260
|
+
// Combine latest value from subscriptionTokens with each interval tick
|
5261
|
+
rxjs.map(([, subscribedTokenIds]) =>
|
5262
|
+
// Filter out tokens that are not subscribed
|
5263
|
+
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 => {
|
5264
|
+
// tokenChunk is a chunk of tokenIds with size POLLING_WINDOW_SIZE
|
5265
|
+
const pollingTokenAddresses = Object.fromEntries(tokenChunk.map(tokenId => [tokenId, addressesByToken[tokenId]]));
|
5266
|
+
await poll(pollingTokenAddresses);
|
5267
|
+
return true;
|
5268
|
+
})))).subscribe();
|
5269
|
+
return () => {
|
5270
|
+
callerUnsubscribe();
|
5271
|
+
positiveSub.unsubscribe();
|
5272
|
+
pollingSub.unsubscribe();
|
5273
|
+
};
|
5274
|
+
};
|
5275
|
+
const fetchBalances = async addressesByToken => {
|
5276
|
+
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
5277
|
+
const queries = await queryCache.getQueries(addressesByToken);
|
5278
|
+
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
5279
|
+
const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
|
5280
|
+
return new Balances(result ?? []);
|
5281
|
+
};
|
5282
|
+
return {
|
5283
|
+
...DefaultBalanceModule(moduleType$2),
|
5284
|
+
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
|
5285
|
+
if (!metadataRpc) return {};
|
5449
5286
|
|
5450
5287
|
//
|
5451
5288
|
// process metadata into SCALE encoders/decoders
|
5452
5289
|
//
|
5453
5290
|
const metadataVersion = scale.getMetadataVersion(metadataRpc);
|
5291
|
+
if (metadataVersion < 14) return {};
|
5454
5292
|
const metadata = scale.decAnyMetadata(metadataRpc);
|
5455
5293
|
const unifiedMetadata = scale.unifyMetadata(metadata);
|
5456
5294
|
|
@@ -5517,16 +5355,15 @@ const SubNativeModule = hydrate => {
|
|
5517
5355
|
}) => name === "Freezes"));
|
5518
5356
|
const useLegacyTransferableCalculation = !hasFreezesItem;
|
5519
5357
|
const chainMeta = {
|
5520
|
-
isTestnet,
|
5358
|
+
// isTestnet,
|
5521
5359
|
useLegacyTransferableCalculation,
|
5522
|
-
symbol,
|
5523
|
-
decimals,
|
5360
|
+
// symbol,
|
5361
|
+
// decimals,
|
5524
5362
|
existentialDeposit,
|
5525
5363
|
nominationPoolsPalletId,
|
5526
5364
|
crowdloanPalletId,
|
5527
5365
|
hasSubtensorPallet,
|
5528
|
-
miniMetadata
|
5529
|
-
metadataVersion
|
5366
|
+
miniMetadata
|
5530
5367
|
};
|
5531
5368
|
if (!useLegacyTransferableCalculation) delete chainMeta.useLegacyTransferableCalculation;
|
5532
5369
|
if (!hasSubtensorPallet) delete chainMeta.hasSubtensorPallet;
|
@@ -5535,9 +5372,10 @@ const SubNativeModule = hydrate => {
|
|
5535
5372
|
async fetchSubstrateChainTokens(chainId, chainMeta, moduleConfig) {
|
5536
5373
|
if (moduleConfig?.disable === true) return {};
|
5537
5374
|
const {
|
5538
|
-
|
5539
|
-
|
5540
|
-
|
5375
|
+
tokenSymbol: symbol,
|
5376
|
+
tokenDecimals: decimals
|
5377
|
+
} = await getChainProperties(chainConnector$1, chainId);
|
5378
|
+
const {
|
5541
5379
|
existentialDeposit
|
5542
5380
|
} = chainMeta;
|
5543
5381
|
const id = chaindataProvider.subNativeTokenId(chainId);
|
@@ -5545,11 +5383,10 @@ const SubNativeModule = hydrate => {
|
|
5545
5383
|
id,
|
5546
5384
|
type: "substrate-native",
|
5547
5385
|
platform: "polkadot",
|
5548
|
-
isTestnet,
|
5549
5386
|
isDefault: moduleConfig?.isDefault ?? true,
|
5550
|
-
symbol: symbol
|
5551
|
-
name: moduleConfig?.name ?? symbol
|
5552
|
-
decimals: decimals
|
5387
|
+
symbol: symbol,
|
5388
|
+
name: moduleConfig?.name ?? symbol,
|
5389
|
+
decimals: decimals,
|
5553
5390
|
logo: moduleConfig?.logo,
|
5554
5391
|
existentialDeposit: existentialDeposit ?? "0",
|
5555
5392
|
networkId: chainId
|
@@ -5566,169 +5403,43 @@ const SubNativeModule = hydrate => {
|
|
5566
5403
|
initialBalances
|
5567
5404
|
}, callback) {
|
5568
5405
|
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
5569
|
-
|
5570
|
-
|
5571
|
-
|
5572
|
-
|
5573
|
-
const positiveBalanceTokens = subNativeBalances.pipe(rxjs.map(balances => Array.from(new Set(Object.values(balances).map(b => b.tokenId)))), rxjs.share());
|
5574
|
-
|
5575
|
-
// tokens that will be subscribed to, simply a slice of the positive balance tokens of size MAX_SUBSCRIPTION_SIZE
|
5576
|
-
const subscriptionTokens = positiveBalanceTokens.pipe(rxjs.map(tokens => tokens.sort(sortChains).slice(0, MAX_SUBSCRIPTION_SIZE)));
|
5577
|
-
|
5578
|
-
// an initialised balance is one where we have received a response for any type of 'subsource',
|
5579
|
-
// until then they are initialising. We only need to maintain one map of tokens to addresses for this
|
5580
|
-
const initialisingBalances = Object.entries(addressesByToken).reduce((acc, [tokenId, addresses]) => {
|
5581
|
-
acc.set(tokenId, new Set(addresses));
|
5406
|
+
const addressesByTokenByNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
|
5407
|
+
const networkId = chaindataProvider.parseSubNativeTokenId(tokenId).networkId;
|
5408
|
+
if (!acc[networkId]) acc[networkId] = {};
|
5409
|
+
acc[networkId][tokenId] = addressesByToken[tokenId];
|
5582
5410
|
return acc;
|
5583
|
-
},
|
5584
|
-
|
5585
|
-
|
5586
|
-
|
5587
|
-
|
5588
|
-
|
5589
|
-
|
5590
|
-
|
5591
|
-
|
5592
|
-
|
5593
|
-
}, 30_000);
|
5594
|
-
const _callbackSub = subNativeBalances.pipe(rxjs.debounceTime(100)).subscribe({
|
5595
|
-
next: balances => {
|
5596
|
-
callback(null, {
|
5597
|
-
status: initialisingBalances.size > 0 ? "initialising" : "live",
|
5598
|
-
data: Object.values(balances)
|
5599
|
-
});
|
5600
|
-
},
|
5601
|
-
error: error => callback(error),
|
5602
|
-
complete: () => {
|
5603
|
-
initialisingBalances.clear();
|
5604
|
-
clearTimeout(initBalancesTimeout);
|
5605
|
-
}
|
5606
|
-
});
|
5607
|
-
const unsubDeferred = util.Deferred();
|
5608
|
-
// we return this to the caller so that they can let us know when they're no longer interested in this subscription
|
5609
|
-
const callerUnsubscribe = () => {
|
5610
|
-
subNativeBalances.complete();
|
5611
|
-
_callbackSub.unsubscribe();
|
5612
|
-
return unsubDeferred.reject(new Error(`Caller unsubscribed`));
|
5613
|
-
};
|
5614
|
-
// we queue up our work to clean up our subscription when this promise rejects
|
5615
|
-
const callerUnsubscribed = unsubDeferred.promise;
|
5616
|
-
|
5617
|
-
// The update handler is to allow us to merge balances with the same id, and manage initialising and positive balances state for each
|
5618
|
-
// balance type and network
|
5619
|
-
const handleUpdateForSource = source => (error, result) => {
|
5620
|
-
if (result) {
|
5621
|
-
const currentBalances = subNativeBalances.getValue();
|
5622
|
-
|
5623
|
-
// first merge any balances with the same id within the result
|
5624
|
-
const accumulatedUpdates = result.filter(b => b.values.length > 0).reduce((acc, b) => {
|
5625
|
-
const bId = getBalanceId(b);
|
5626
|
-
acc[bId] = mergeBalances(acc[bId], b, source, false);
|
5627
|
-
return acc;
|
5628
|
-
}, {});
|
5629
|
-
|
5630
|
-
// then merge these with the current balances
|
5631
|
-
const mergedBalances = {};
|
5632
|
-
Object.entries(accumulatedUpdates).forEach(([bId, b]) => {
|
5633
|
-
// merge the values from the new balance into the existing balance, if there is one
|
5634
|
-
mergedBalances[bId] = mergeBalances(currentBalances[bId], b, source, true);
|
5635
|
-
|
5636
|
-
// update initialisingBalances to remove balances which have been updated
|
5637
|
-
const intialisingForToken = initialisingBalances.get(b.tokenId);
|
5638
|
-
if (intialisingForToken) {
|
5639
|
-
intialisingForToken.delete(b.address);
|
5640
|
-
if (intialisingForToken.size === 0) initialisingBalances.delete(b.tokenId);else initialisingBalances.set(b.tokenId, intialisingForToken);
|
5641
|
-
}
|
5642
|
-
});
|
5643
|
-
subNativeBalances.next({
|
5644
|
-
...currentBalances,
|
5645
|
-
...mergedBalances
|
5646
|
-
});
|
5647
|
-
}
|
5648
|
-
if (error) {
|
5649
|
-
if (error instanceof SubNativeBalanceError) {
|
5650
|
-
// this type of error doesn't need to be handled by the caller
|
5651
|
-
initialisingBalances.delete(error.tokenId);
|
5652
|
-
} else return callback(error);
|
5653
|
-
}
|
5411
|
+
}, {});
|
5412
|
+
const initialBalancesByNetwork = lodash.groupBy(initialBalances ?? [], "networkId");
|
5413
|
+
const {
|
5414
|
+
abort,
|
5415
|
+
signal
|
5416
|
+
} = new AbortController();
|
5417
|
+
const safeCallback = (error, result) => {
|
5418
|
+
if (signal.aborted) return;
|
5419
|
+
// typescript isnt happy with fowarding parameters as is
|
5420
|
+
return error ? callback(error, undefined) : callback(error, result);
|
5654
5421
|
};
|
5655
|
-
|
5656
|
-
// subscribe to addresses and tokens for which we have a known positive balance
|
5657
|
-
const positiveSub = subscriptionTokens.pipe(rxjs.debounceTime(1000), rxjs.takeUntil(callerUnsubscribed), rxjs.map(tokenIds => tokenIds.reduce((acc, tokenId) => {
|
5658
|
-
acc[tokenId] = addressesByToken[tokenId];
|
5659
|
-
return acc;
|
5660
|
-
}, {})), rxjs.distinctUntilChanged(isEqual__default.default), rxjs.switchMap(newAddressesByToken => {
|
5661
|
-
return rxjs.from(queryCache.getQueries(newAddressesByToken)).pipe(rxjs.switchMap(baseQueries => {
|
5662
|
-
return new rxjs.Observable(subscriber => {
|
5663
|
-
if (!chainConnectors.substrate) return;
|
5664
|
-
const unsubSubtensorStaking = subscribeSubtensorStaking(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("subtensor-staking"));
|
5665
|
-
const unsubNompoolStaking = subscribeNompoolStaking(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("nompools-staking"));
|
5666
|
-
const unsubCrowdloans = subscribeCrowdloans(chaindataProvider$1, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("crowdloan"));
|
5667
|
-
const unsubBase = subscribeBase(baseQueries, chainConnectors.substrate, handleUpdateForSource("base"));
|
5668
|
-
subscriber.add(async () => (await unsubSubtensorStaking)());
|
5669
|
-
subscriber.add(async () => (await unsubNompoolStaking)());
|
5670
|
-
subscriber.add(async () => (await unsubCrowdloans)());
|
5671
|
-
subscriber.add(async () => (await unsubBase)());
|
5672
|
-
});
|
5673
|
-
}));
|
5674
|
-
})).subscribe();
|
5675
|
-
|
5676
|
-
// for chains where we don't have a known positive balance, poll rather than subscribe
|
5677
|
-
const poll = async (addressesByToken = {}) => {
|
5678
|
-
const handleUpdate = handleUpdateForSource("base");
|
5422
|
+
const unsubsribeFns = Promise.all(lodash.keys(addressesByTokenByNetwork).map(async networkId => {
|
5679
5423
|
try {
|
5680
|
-
|
5681
|
-
|
5682
|
-
|
5683
|
-
|
5684
|
-
|
5685
|
-
|
5686
|
-
Object.entries(await getModuleTokens()).filter(([, token]) => token.networkId === errorChainId).forEach(([tokenId]) => {
|
5687
|
-
const wrappedError = new SubNativeBalanceError(tokenId, error.message);
|
5688
|
-
handleUpdate(wrappedError);
|
5689
|
-
});
|
5690
|
-
} else {
|
5691
|
-
log.error("unknown substrate native balance error", error);
|
5692
|
-
handleUpdate(error);
|
5693
|
-
}
|
5424
|
+
// this is what we want to be done separately for each network
|
5425
|
+
// this will update the DB so minimetadata will be available when it's used, veeeeery far down the tree of subscribeChainBalances
|
5426
|
+
await getMiniMetadata(chaindataProvider$1, chainConnector$1, networkId, moduleType$2, signal);
|
5427
|
+
} catch (err) {
|
5428
|
+
if (!signal.aborted) log.warn("Failed to get native token miniMetadata for network", networkId, err);
|
5429
|
+
return () => {};
|
5694
5430
|
}
|
5695
|
-
|
5696
|
-
|
5697
|
-
|
5698
|
-
|
5699
|
-
|
5700
|
-
|
5701
|
-
// break nonCurrentTokens into chunks of POLLING_WINDOW_SIZE
|
5702
|
-
await PromisePool__default.default.withConcurrency(POLLING_WINDOW_SIZE).for(nonCurrentTokens).process(async nonCurrentTokenId => await poll({
|
5703
|
-
[nonCurrentTokenId]: addressesByToken[nonCurrentTokenId]
|
5431
|
+
if (signal.aborted) return () => {};
|
5432
|
+
return subscribeChainBalances(networkId, {
|
5433
|
+
addressesByToken: addressesByTokenByNetwork[networkId] ?? {},
|
5434
|
+
initialBalances: initialBalancesByNetwork[networkId] ?? []
|
5435
|
+
}, safeCallback);
|
5704
5436
|
}));
|
5705
|
-
|
5706
|
-
// now poll every 30s on chains which are not subscriptionTokens
|
5707
|
-
// we chunk this observable into batches of positive token ids, to prevent eating all the websocket connections
|
5708
|
-
const pollingSub = rxjs.interval(30_000) // emit values every 30 seconds
|
5709
|
-
.pipe(rxjs.takeUntil(callerUnsubscribed), rxjs.withLatestFrom(subscriptionTokens),
|
5710
|
-
// Combine latest value from subscriptionTokens with each interval tick
|
5711
|
-
rxjs.map(([, subscribedTokenIds]) =>
|
5712
|
-
// Filter out tokens that are not subscribed
|
5713
|
-
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 => {
|
5714
|
-
// tokenChunk is a chunk of tokenIds with size POLLING_WINDOW_SIZE
|
5715
|
-
const pollingTokenAddresses = Object.fromEntries(tokenChunk.map(tokenId => [tokenId, addressesByToken[tokenId]]));
|
5716
|
-
await poll(pollingTokenAddresses);
|
5717
|
-
return true;
|
5718
|
-
})))).subscribe();
|
5719
5437
|
return () => {
|
5720
|
-
|
5721
|
-
|
5722
|
-
pollingSub.unsubscribe();
|
5438
|
+
abort();
|
5439
|
+
unsubsribeFns.then(fns => fns.forEach(unsubscribe => unsubscribe()));
|
5723
5440
|
};
|
5724
5441
|
},
|
5725
|
-
|
5726
|
-
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
5727
|
-
const queries = await queryCache.getQueries(addressesByToken);
|
5728
|
-
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
5729
|
-
const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
|
5730
|
-
return new Balances(result ?? []);
|
5731
|
-
},
|
5442
|
+
fetchBalances,
|
5732
5443
|
async transferToken({
|
5733
5444
|
tokenId,
|
5734
5445
|
from,
|
@@ -7200,7 +6911,6 @@ const SubTokensModule = hydrate => {
|
|
7200
6911
|
...DefaultBalanceModule(moduleType),
|
7201
6912
|
async fetchSubstrateChainMeta(chainId, moduleConfig, metadataRpc) {
|
7202
6913
|
if (metadataRpc === undefined) return {};
|
7203
|
-
if ((moduleConfig?.tokens ?? []).length < 1) return {};
|
7204
6914
|
const metadata = scale.decAnyMetadata(metadataRpc);
|
7205
6915
|
const palletId = moduleConfig?.palletId ?? defaultPalletId;
|
7206
6916
|
scale.compactMetadata(metadata, [{
|
@@ -7252,17 +6962,39 @@ const SubTokensModule = hydrate => {
|
|
7252
6962
|
async subscribeBalances({
|
7253
6963
|
addressesByToken
|
7254
6964
|
}, callback) {
|
7255
|
-
const
|
7256
|
-
|
7257
|
-
if (
|
7258
|
-
|
7259
|
-
|
7260
|
-
});
|
7261
|
-
|
6965
|
+
const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
|
6966
|
+
const networkId = chaindataProvider.parseSubTokensTokenId(tokenId).networkId;
|
6967
|
+
if (!acc[networkId]) acc[networkId] = {};
|
6968
|
+
acc[networkId][tokenId] = addressesByToken[tokenId];
|
6969
|
+
return acc;
|
6970
|
+
}, {});
|
6971
|
+
const controller = new AbortController();
|
6972
|
+
const pUnsubs = Promise.all(lodash.toPairs(byNetwork).map(async ([networkId, addressesByToken]) => {
|
6973
|
+
try {
|
6974
|
+
const queries = await buildNetworkQueries(networkId, chainConnector, chaindataProvider$1, addressesByToken, controller.signal);
|
6975
|
+
if (controller.signal.aborted) return () => {};
|
6976
|
+
const stateHelper = new RpcStateQueryHelper(chainConnector, queries);
|
6977
|
+
return await stateHelper.subscribe((error, result) => {
|
6978
|
+
// console.log("SubstrateAssetsModule.callback", { error, result })
|
6979
|
+
if (error) return callback(error);
|
6980
|
+
const balances = result?.filter(b => b !== null) ?? [];
|
6981
|
+
if (balances.length > 0) callback(null, new Balances(balances));
|
6982
|
+
});
|
6983
|
+
} catch (err) {
|
6984
|
+
if (!controller.signal.aborted) log.error(`Failed to subscribe balances for network ${networkId}`, err);
|
6985
|
+
return () => {};
|
6986
|
+
}
|
6987
|
+
}));
|
6988
|
+
return () => {
|
6989
|
+
controller.abort();
|
6990
|
+
pUnsubs.then(unsubs => {
|
6991
|
+
unsubs.forEach(unsubscribe => unsubscribe());
|
6992
|
+
});
|
6993
|
+
};
|
7262
6994
|
},
|
7263
6995
|
async fetchBalances(addressesByToken) {
|
7264
6996
|
util$1.assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
7265
|
-
const queries = await buildQueries(chaindataProvider$1, addressesByToken);
|
6997
|
+
const queries = await buildQueries(chainConnector, chaindataProvider$1, addressesByToken);
|
7266
6998
|
const result = await new RpcStateQueryHelper(chainConnectors.substrate, queries).fetch();
|
7267
6999
|
const balances = result?.filter(b => b !== null) ?? [];
|
7268
7000
|
return new Balances(balances);
|
@@ -7381,23 +7113,16 @@ const SubTokensModule = hydrate => {
|
|
7381
7113
|
}
|
7382
7114
|
};
|
7383
7115
|
};
|
7384
|
-
async function
|
7385
|
-
const
|
7116
|
+
async function buildNetworkQueries(networkId, chainConnector, chaindataProvider, addressesByToken, signal) {
|
7117
|
+
const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType, signal);
|
7118
|
+
const chain = await chaindataProvider.chainById(networkId);
|
7386
7119
|
const tokens = await chaindataProvider.tokensById();
|
7387
|
-
|
7388
|
-
|
7389
|
-
const
|
7390
|
-
const
|
7391
|
-
const
|
7392
|
-
|
7393
|
-
chains,
|
7394
|
-
miniMetadatas,
|
7395
|
-
moduleType: "substrate-tokens",
|
7396
|
-
coders: {
|
7397
|
-
storage: ({
|
7398
|
-
chainId
|
7399
|
-
}) => [tokensPalletByChain.get(chainId) ?? defaultPalletId, "Accounts"]
|
7400
|
-
}
|
7120
|
+
if (!chain) return [];
|
7121
|
+
signal?.throwIfAborted();
|
7122
|
+
const tokensMetadata = miniMetadata;
|
7123
|
+
const palletId = tokensMetadata.palletId ?? defaultPalletId;
|
7124
|
+
const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
|
7125
|
+
storage: [palletId, "Accounts"]
|
7401
7126
|
});
|
7402
7127
|
return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
|
7403
7128
|
const token = tokens[tokenId];
|
@@ -7409,18 +7134,8 @@ async function buildQueries(chaindataProvider, addressesByToken) {
|
|
7409
7134
|
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
7410
7135
|
return [];
|
7411
7136
|
}
|
7412
|
-
const networkId = token.networkId;
|
7413
|
-
if (!networkId) {
|
7414
|
-
log.warn(`Token ${tokenId} has no chain`);
|
7415
|
-
return [];
|
7416
|
-
}
|
7417
|
-
const chain = chains[networkId];
|
7418
|
-
if (!chain) {
|
7419
|
-
log.warn(`Chain ${networkId} for token ${tokenId} not found`);
|
7420
|
-
return [];
|
7421
|
-
}
|
7422
7137
|
return addresses.flatMap(address => {
|
7423
|
-
const scaleCoder =
|
7138
|
+
const scaleCoder = networkStorageCoders?.storage;
|
7424
7139
|
const onChainId = (() => {
|
7425
7140
|
try {
|
7426
7141
|
return scale.papiParse(token.onChainId);
|
@@ -7471,35 +7186,20 @@ async function buildQueries(chaindataProvider, addressesByToken) {
|
|
7471
7186
|
});
|
7472
7187
|
});
|
7473
7188
|
}
|
7189
|
+
async function buildQueries(chainConnector, chaindataProvider$1, addressesByToken, signal) {
|
7190
|
+
const byNetwork = lodash.keys(addressesByToken).reduce((acc, tokenId) => {
|
7191
|
+
const networkId = chaindataProvider.parseSubTokensTokenId(tokenId).networkId;
|
7192
|
+
if (!acc[networkId]) acc[networkId] = {};
|
7193
|
+
acc[networkId][tokenId] = addressesByToken[tokenId];
|
7194
|
+
return acc;
|
7195
|
+
}, {});
|
7196
|
+
return (await Promise.all(lodash.toPairs(byNetwork).map(([networkId, addressesByToken]) => {
|
7197
|
+
return buildNetworkQueries(networkId, chainConnector, chaindataProvider$1, addressesByToken, signal);
|
7198
|
+
}))).flat();
|
7199
|
+
}
|
7474
7200
|
|
7475
7201
|
const defaultBalanceModules = [EvmErc20Module, EvmNativeModule, EvmUniswapV2Module, SubAssetsModule, SubForeignAssetsModule, SubNativeModule, SubPsp22Module, SubTokensModule];
|
7476
7202
|
|
7477
|
-
/** Pulls the latest chaindata from https://github.com/TalismanSociety/chaindata */
|
7478
|
-
const hydrateChaindataAndMiniMetadata = async (chaindataProvider, miniMetadataUpdater) => {
|
7479
|
-
// need chains to be provisioned first, or substrate balances won't fetch on first subscription
|
7480
|
-
await chaindataProvider.hydrate();
|
7481
|
-
await Promise.all([miniMetadataUpdater.hydrateFromChaindata(), miniMetadataUpdater.hydrateCustomChains()]);
|
7482
|
-
const chains = await chaindataProvider.chains();
|
7483
|
-
const {
|
7484
|
-
statusesByChain
|
7485
|
-
} = await miniMetadataUpdater.statuses(chains);
|
7486
|
-
const goodChains = [...statusesByChain.entries()].flatMap(([chainId, status]) => status === "good" ? chainId : []);
|
7487
|
-
await chaindataProvider.hydrateSubstrateTokens(goodChains);
|
7488
|
-
};
|
7489
|
-
|
7490
|
-
/** Builds any missing miniMetadatas (e.g. for the user's custom substrate chains) */
|
7491
|
-
const updateCustomMiniMetadata = async (chaindataProvider, miniMetadataUpdater) => {
|
7492
|
-
const chainIds = await chaindataProvider.chainIds();
|
7493
|
-
await miniMetadataUpdater.update(chainIds);
|
7494
|
-
};
|
7495
|
-
|
7496
|
-
/** Fetches any missing Evm Tokens */
|
7497
|
-
const updateEvmTokens = async (chaindataProvider, evmTokenFetcher) => {
|
7498
|
-
await chaindataProvider.hydrate();
|
7499
|
-
const evmNetworkIds = await chaindataProvider.evmNetworkIds();
|
7500
|
-
await evmTokenFetcher.update(evmNetworkIds);
|
7501
|
-
};
|
7502
|
-
|
7503
7203
|
exports.Balance = Balance;
|
7504
7204
|
exports.BalanceFormatter = BalanceFormatter;
|
7505
7205
|
exports.BalanceValueGetter = BalanceValueGetter;
|
@@ -7511,7 +7211,6 @@ exports.EvmNativeModule = EvmNativeModule;
|
|
7511
7211
|
exports.EvmTokenFetcher = EvmTokenFetcher;
|
7512
7212
|
exports.EvmUniswapV2Module = EvmUniswapV2Module;
|
7513
7213
|
exports.FiatSumBalancesFormatter = FiatSumBalancesFormatter;
|
7514
|
-
exports.MiniMetadataUpdater = MiniMetadataUpdater;
|
7515
7214
|
exports.ONE_ALPHA_TOKEN = ONE_ALPHA_TOKEN;
|
7516
7215
|
exports.PlanckSumBalancesFormatter = PlanckSumBalancesFormatter;
|
7517
7216
|
exports.RpcStateQueryHelper = RpcStateQueryHelper;
|
@@ -7527,6 +7226,7 @@ exports.SumBalancesFormatter = SumBalancesFormatter;
|
|
7527
7226
|
exports.TalismanBalancesDatabase = TalismanBalancesDatabase;
|
7528
7227
|
exports.abiMulticall = abiMulticall;
|
7529
7228
|
exports.balances = balances;
|
7229
|
+
exports.buildNetworkStorageCoders = buildNetworkStorageCoders;
|
7530
7230
|
exports.buildStorageCoders = buildStorageCoders;
|
7531
7231
|
exports.calculateAlphaPrice = calculateAlphaPrice;
|
7532
7232
|
exports.calculateTaoAmountFromAlpha = calculateTaoAmountFromAlpha;
|
@@ -7550,9 +7250,6 @@ exports.getBalanceId = getBalanceId;
|
|
7550
7250
|
exports.getLockTitle = getLockTitle;
|
7551
7251
|
exports.getUniqueChainIds = getUniqueChainIds;
|
7552
7252
|
exports.getValueId = getValueId;
|
7553
|
-
exports.hydrateChaindataAndMiniMetadata = hydrateChaindataAndMiniMetadata;
|
7554
7253
|
exports.includeInTotalExtraAmount = includeInTotalExtraAmount;
|
7555
7254
|
exports.makeContractCaller = makeContractCaller;
|
7556
7255
|
exports.uniswapV2PairAbi = uniswapV2PairAbi;
|
7557
|
-
exports.updateCustomMiniMetadata = updateCustomMiniMetadata;
|
7558
|
-
exports.updateEvmTokens = updateEvmTokens;
|