@talismn/balances 0.0.0-pr2075-20250709135256 → 0.0.0-pr2075-20250710032705

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.
Files changed (24) hide show
  1. package/dist/declarations/src/BalancesProvider.d.ts +3 -1
  2. package/dist/declarations/src/{getMiniMetadata/getMiniMetadatas.d.ts → getMiniMetadatas/index.d.ts} +1 -0
  3. package/dist/declarations/src/index.d.ts +0 -1
  4. package/dist/declarations/src/modules/evm-erc20/types.d.ts +1 -1
  5. package/dist/declarations/src/modules/evm-native/types.d.ts +1 -1
  6. package/dist/declarations/src/modules/evm-uniswapv2/types.d.ts +1 -1
  7. package/dist/declarations/src/modules/index.d.ts +9 -9
  8. package/dist/declarations/src/modules/substrate-assets/types.d.ts +1 -1
  9. package/dist/declarations/src/modules/substrate-foreignassets/types.d.ts +1 -1
  10. package/dist/declarations/src/modules/substrate-hydration/types.d.ts +1 -1
  11. package/dist/declarations/src/modules/substrate-native/types.d.ts +1 -1
  12. package/dist/declarations/src/modules/substrate-psp22/types.d.ts +1 -1
  13. package/dist/declarations/src/modules/substrate-tokens/types.d.ts +1 -1
  14. package/dist/declarations/src/types/tokens.d.ts +1 -1
  15. package/dist/talismn-balances.cjs.dev.js +1602 -1657
  16. package/dist/talismn-balances.cjs.prod.js +1602 -1657
  17. package/dist/talismn-balances.esm.js +1603 -1656
  18. package/package.json +8 -8
  19. package/dist/declarations/src/TalismanBalancesDatabase.d.ts +0 -11
  20. package/dist/declarations/src/upgrades/2024-01-25-upgradeRemoveSymbolFromNativeTokenId.d.ts +0 -2
  21. package/dist/declarations/src/upgrades/2024-03-19-upgradeBalancesDataBlob.d.ts +0 -2
  22. package/dist/declarations/src/upgrades/index.d.ts +0 -2
  23. /package/dist/declarations/src/{getMiniMetadata → getMiniMetadatas}/getMetadataRpc.d.ts +0 -0
  24. /package/dist/declarations/src/{getMiniMetadata → getMiniMetadatas}/getSpecVersion.d.ts +0 -0
@@ -1,17 +1,15 @@
1
1
  'use strict';
2
2
 
3
- var dexie = require('dexie');
4
- var anylogger = require('anylogger');
5
3
  var chaindataProvider = require('@talismn/chaindata-provider');
6
- var tokenRates = require('@talismn/token-rates');
7
4
  var util = require('@talismn/util');
8
- var BigNumber = require('bignumber.js');
9
- var scale = require('@talismn/scale');
10
- var pako = require('pako');
11
5
  var viem = require('viem');
12
6
  var lodash = require('lodash');
13
7
  var z = require('zod/v4');
8
+ var anylogger = require('anylogger');
14
9
  var rxjs = require('rxjs');
10
+ var scale = require('@talismn/scale');
11
+ var tokenRates = require('@talismn/token-rates');
12
+ var BigNumber = require('bignumber.js');
15
13
  var utils = require('@polkadot-api/utils');
16
14
  var polkadotApi = require('polkadot-api');
17
15
  var upperFirst = require('lodash/upperFirst');
@@ -24,883 +22,18 @@ var sapi = require('@talismn/sapi');
24
22
 
25
23
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
26
24
 
25
+ var z__default = /*#__PURE__*/_interopDefault(z);
27
26
  var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
28
27
  var BigNumber__default = /*#__PURE__*/_interopDefault(BigNumber);
29
- var pako__default = /*#__PURE__*/_interopDefault(pako);
30
- var z__default = /*#__PURE__*/_interopDefault(z);
31
28
  var upperFirst__default = /*#__PURE__*/_interopDefault(upperFirst);
32
29
  var PQueue__default = /*#__PURE__*/_interopDefault(PQueue);
33
30
 
34
- var packageJson = {
35
- name: "@talismn/balances"};
36
-
37
- var log = anylogger__default.default(packageJson.name);
38
-
39
- function excludeFromTransferableAmount(locks) {
40
- if (typeof locks === "string") return BigInt(locks);
41
- if (!Array.isArray(locks)) locks = [locks];
42
- return locks.filter(lock => lock.includeInTransferable !== true).map(lock => lock.amount.planck).reduce((max, lock) => util.BigMath.max(max, lock), 0n);
43
- }
44
- function excludeFromFeePayableLocks(locks) {
45
- if (typeof locks === "string") return [];
46
- if (!Array.isArray(locks)) locks = [locks];
47
- return locks.filter(lock => lock.excludeFromFeePayable);
48
- }
49
- function includeInTotalExtraAmount(extra) {
50
- if (!extra) return 0n;
51
- if (!Array.isArray(extra)) extra = [extra];
52
- return extra.filter(extra => extra.includeInTotal).map(extra => extra.amount.planck).reduce((a, b) => a + b, 0n);
53
- }
54
-
55
- /**
56
- * Have the importing library define its Token and BalanceJson enums (as a sum type of all plugins) and pass them into some
57
- * internal global typescript context, which is then picked up on by this module.
58
- */
59
-
60
- /** A utility type used to extract the underlying `BalanceType` of a specific source from a generalised `BalanceJson` */
61
-
62
- /** TODO: Remove this in favour of a frontend-friendly `ChaindataProvider` */
63
-
64
- /**
65
- * A collection of balances.
66
- */
67
- class Balances {
68
- //
69
- // Properties
70
- //
71
-
72
- #balances = [];
73
-
74
- //
75
- // Methods
76
- //
77
-
78
- constructor(balances, hydrate) {
79
- // handle Balances (convert to Balance[])
80
- if (balances instanceof Balances) return new Balances(balances.each, hydrate);
81
-
82
- // handle Balance (convert to Balance[])
83
- if (balances instanceof Balance) return new Balances([balances], hydrate);
84
-
85
- // handle BalanceJsonList (the only remaining non-array type of balances) (convert to BalanceJson[])
86
- if (!Array.isArray(balances)) return new Balances(Object.values(balances), hydrate);
87
-
88
- // handle no balances
89
- if (balances.length === 0) return this;
90
-
91
- // handle BalanceJson[]
92
- if (!util.isArrayOf(balances, Balance)) return new Balances(balances.map(storage => new Balance(storage)), hydrate);
93
-
94
- // handle Balance[]
95
- this.#balances = balances;
96
- if (hydrate !== undefined) this.hydrate(hydrate);
97
- }
98
-
99
- /**
100
- * Calling toJSON on a collection of balances will return the underlying BalanceJsonList.
101
- */
102
- toJSON = () => Object.fromEntries(this.#balances.map(balance => {
103
- try {
104
- return [balance.id, balance.toJSON()];
105
- } catch (error) {
106
- log.error("Failed to convert balance to JSON", error, {
107
- id: balance.id,
108
- balance
109
- });
110
- return null;
111
- }
112
- }).filter(Array.isArray));
113
-
114
- /**
115
- * Allows the collection to be iterated over.
116
- *
117
- * @example
118
- * [...balances].forEach(balance => { // do something // })
119
- *
120
- * @example
121
- * for (const balance of balances) {
122
- * // do something
123
- * }
124
- */
125
- [Symbol.iterator] = () =>
126
- // Create an array of the balances in this collection and return the result of its iterator.
127
- this.#balances[Symbol.iterator]();
128
-
129
- /**
130
- * Hydrates all balances in this collection.
131
- *
132
- * @param sources - The sources to hydrate from.
133
- */
134
- hydrate = sources => {
135
- this.#balances.map(balance => balance.hydrate(sources));
136
- };
137
-
138
- /**
139
- * Retrieve a balance from this collection by id.
140
- *
141
- * @param id - The id of the balance to fetch.
142
- * @returns The balance if one exists, or none.
143
- */
144
- get = id => this.#balances.find(balance => balance.id === id) ?? null;
145
-
146
- /**
147
- * Retrieve balances from this collection by search query.
148
- *
149
- * @param query - The search query.
150
- * @returns All balances which match the query.
151
- */
152
- find = query => {
153
- // construct filter
154
- const queryArray = Array.isArray(query) ? query : [query];
155
- const orQueries = queryArray.map(query => typeof query === "function" ? query : Object.entries(query));
156
-
157
- // filter balances
158
- const filter = balance => orQueries.some(query => typeof query === "function" ? query(balance) : query.every(([key, value]) => balance[key] === value));
159
-
160
- // return filter matches
161
- return new Balances([...this].filter(filter));
162
- };
163
-
164
- /**
165
- * Filters this collection to exclude token balances where the token has a `mirrorOf` field
166
- * and another balance exists in this collection for the token specified by the `mirrorOf` field.
167
- */
168
- filterMirrorTokens = () => new Balances([...this].filter(filterMirrorTokens));
169
-
170
- /**
171
- * Filters this collection to only include balances which are not zero AND have a fiat conversion rate.
172
- */
173
- filterNonZeroFiat = (type, currency) => {
174
- const filter = balance => (balance[type].fiat(currency) ?? 0) > 0;
175
- return this.find(filter);
176
- };
177
-
178
- /**
179
- * Add some balances to this collection.
180
- * Added balances take priority over existing balances.
181
- * The aggregation of the two collections is returned.
182
- * The original collection is not mutated.
183
- *
184
- * @param balances - Either a balance or collection of balances to add.
185
- * @returns The new collection of balances.
186
- */
187
- add = balances => {
188
- // handle single balance
189
- if (balances instanceof Balance) return this.add(new Balances(balances));
190
-
191
- // merge balances
192
- const mergedBalances = Object.fromEntries(this.#balances.map(balance => [balance.id, balance]));
193
- balances.each.forEach(balance => mergedBalances[balance.id] = balance);
194
-
195
- // return new balances
196
- return new Balances(Object.values(mergedBalances));
197
- };
198
-
199
- /**
200
- * Remove balances from this collection by id.
201
- * A new collection without these balances is returned.
202
- * The original collection is not mutated.
203
- *
204
- * @param ids - The id(s) of the balances to remove.
205
- * @returns The new collection of balances.
206
- */
207
- remove = ids => {
208
- // handle single id
209
- if (!Array.isArray(ids)) return this.remove([ids]);
210
-
211
- // merge and return new balances
212
- return new Balances(this.#balances.filter(balance => !ids.includes(balance.id)));
213
- };
214
-
215
- // TODO: Add some more useful aggregator methods
216
-
217
- get each() {
218
- return [...this];
219
- }
220
-
221
- /** @deprecated use each instead */
222
- get sorted() {
223
- return this.each;
224
- }
225
-
226
- /**
227
- * Get the number of balances in this collection.
228
- *
229
- * @returns The number of balances in this collection.
230
- */
231
- get count() {
232
- return [...this].length;
233
- }
234
-
235
- /**
236
- * Get the summed value of balances in this collection.
237
- * TODO: Sum up token amounts AND fiat amounts
238
- *
239
- * @example
240
- * // Get the sum of all transferable balances in usd.
241
- * balances.sum.fiat('usd').transferable
242
- */
243
- get sum() {
244
- return new SumBalancesFormatter(this);
245
- }
246
- }
247
- const getBalanceId = balance => {
248
- const {
249
- address,
250
- tokenId
251
- } = balance;
252
- return [address, tokenId].join("::");
253
- };
254
-
255
- /**
256
- * An individual balance.
257
- */
258
- class Balance {
259
- //
260
- // Properties
261
- //
31
+ const MODULE_TYPE$8 = chaindataProvider.EvmErc20TokenSchema.shape.type.value;
32
+ const PLATFORM$8 = chaindataProvider.EvmErc20TokenSchema.shape.platform.value;
262
33
 
263
- /** The underlying data for this balance */
264
- #storage;
265
- #valueGetter;
266
- #db = null;
34
+ 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)"]);
267
35
 
268
- //
269
- // Methods
270
- //
271
-
272
- constructor(storage, hydrate) {
273
- this.#storage = storage;
274
- this.#valueGetter = new BalanceValueGetter(this.#storage);
275
- if (hydrate !== undefined) this.hydrate(hydrate);
276
- }
277
- toJSON = () => this.#storage;
278
- isSource = source => this.#storage.source === source;
279
- // // TODO: Fix this method, the types don't work with our plugin architecture.
280
- // // Specifically, the `BalanceJson` type is compiled down to `IBalance` in the following way:
281
- // //
282
- // // toJSON: () => BalanceJson // works
283
- // // isSource: (source: BalanceSource) => boolean // works
284
- // // asSource: <P extends string>(source: P) => NarrowBalanceType<IBalance, P> | null // Doesn't work! IBalance should just be BalanceJson!
285
- // //
286
- // // `IBalance` won't match the type of `BalanceSource` after `PluginBalanceTypes` has been extended by balance plugins.
287
- // // As a result, typescript will think that the returned #storage is not a BalanceJson.
288
- // asSource = <P extends BalanceSource>(source: P): NarrowBalanceType<BalanceJson, P> | null => {
289
- // if (this.#storage.source === source) return this.#storage as NarrowBalanceType<BalanceJson, P>
290
- // return null
291
- // }
292
-
293
- hydrate = hydrate => {
294
- if (hydrate !== undefined) this.#db = hydrate;
295
- };
296
- #format = balance => new BalanceFormatter(util.isBigInt(balance) ? balance.toString() : balance, this.decimals || undefined, this.rates);
297
-
298
- //
299
- // Accessors
300
- //
301
-
302
- get id() {
303
- return getBalanceId(this.#storage);
304
- }
305
- get source() {
306
- return this.#storage.source;
307
- }
308
- get status() {
309
- return this.#storage.status;
310
- }
311
- get address() {
312
- return this.#storage.address;
313
- }
314
- get networkId() {
315
- return this.#storage.networkId;
316
- }
317
- get network() {
318
- return this.#db?.networks?.[this.networkId] || null;
319
- }
320
- get tokenId() {
321
- return this.#storage.tokenId;
322
- }
323
- get token() {
324
- return this.#db?.tokens?.[this.tokenId] || null;
325
- }
326
- get decimals() {
327
- return this.token?.decimals || null;
328
- }
329
- get rates() {
330
- // uniswap v2 lp tokens need the rates from the underlying pool assets
331
- //
332
- // To note: `@talismn/token-rates` knows to fetch the `coingeckoId0` and `coingeckoId1` rates for evm-uniswapv2 tokens.
333
- // They are then stored in `this.#db.tokenRates` using the `tokenId0` and `tokenId1` keys.
334
- //
335
- // This means that those rates are always available for calculating the uniswapv2 rates,
336
- // regardless of whether or not the underlying erc20s are actually in chaindata and enabled.
337
- if (this.isSource("evm-uniswapv2") && this.token?.type === "evm-uniswapv2") {
338
- const tokenId0 = chaindataProvider.evmErc20TokenId(this.networkId, this.token.tokenAddress0);
339
- const tokenId1 = chaindataProvider.evmErc20TokenId(this.networkId, this.token.tokenAddress1);
340
- const decimals = this.token.decimals;
341
- const decimals0 = this.token.decimals0;
342
- const decimals1 = this.token.decimals1;
343
- const rates0 = this.#db?.tokenRates && this.#db.tokenRates[tokenId0];
344
- const rates1 = this.#db?.tokenRates && this.#db.tokenRates[tokenId1];
345
- if (rates0 === undefined || rates1 === undefined) return null;
346
- const extra = this.#valueGetter.get("extra");
347
- const extras = Array.isArray(extra) ? extra : extra !== undefined ? [extra] : [];
348
- const totalSupply = extras.find(extra => extra.label === "totalSupply")?.amount ?? "0";
349
- const reserve0 = extras.find(extra => extra.label === "reserve0")?.amount ?? "0";
350
- const reserve1 = extras.find(extra => extra.label === "reserve1")?.amount ?? "0";
351
- const totalSupplyTokens = BigNumber__default.default(totalSupply).times(Math.pow(10, -1 * decimals));
352
- const reserve0Tokens = BigNumber__default.default(reserve0).times(Math.pow(10, -1 * decimals0));
353
- const reserve1Tokens = BigNumber__default.default(reserve1).times(Math.pow(10, -1 * decimals1));
354
- const rates0Currencies = new Set(Object.keys(rates0));
355
- const rates1Currencies = new Set(Object.keys(rates1));
356
- // `Set.prototype.intersection` can eventually replace this
357
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/intersection
358
- const currencies = [...rates0Currencies].filter(c => rates1Currencies.has(c));
359
- const totalValueLocked = currencies.map(currency => [currency,
360
- // tvl (in a given currency) == reserve0*currencyRate0 + reserve1*currencyRate1
361
- BigNumber__default.default.sum(reserve0Tokens.times(rates0[currency]?.price ?? 0), reserve1Tokens.times(rates1[currency]?.price ?? 0))]);
362
- const lpTokenRates = tokenRates.newTokenRates();
363
- totalValueLocked.forEach(([currency, tvl]) => {
364
- // divide `the value of all lp tokens` by `the number of lp tokens` to get `the value per token`
365
- if (!totalSupplyTokens.eq(0)) lpTokenRates[currency] = {
366
- price: tvl.div(totalSupplyTokens).toNumber()
367
- };
368
- });
369
- return lpTokenRates;
370
- }
371
-
372
- // other tokens can just pick from the tokenRates db using the tokenId
373
- return this.#db?.tokenRates && this.#db.tokenRates[this.tokenId] || null;
374
- }
375
-
376
- /**
377
- * A general method to get formatted values matching a certain type from this balance.
378
- * @param valueType - The type of value to get.
379
- * @returns An array of the values matching the type with formatted amounts.
380
- */
381
- getValue(valueType) {
382
- return this.getRawValue(valueType).map(value => ({
383
- ...value,
384
- amount: this.#format(value.amount)
385
- }));
386
- }
387
-
388
- /**
389
- * A general method to get values matching a certain type from this balance.
390
- * @param valueType - The type of value to get.
391
- * @returns An array of the values matching the type.
392
- */
393
- getRawValue(valueType) {
394
- return this.#valueGetter.get(valueType);
395
- }
396
-
397
- /**
398
- * A general method to add a value to the array of values for this balance.
399
- * @param valueType - The type of value to add.
400
- * @returns A function which can be used to add a value to the array of values for this balance.
401
- */
402
- addValue(valueType) {
403
- return value => this.#valueGetter.add(valueType, value);
404
- }
405
- /**
406
- * The total balance of this token.
407
- * Includes the free and the reserved amount.
408
- * The balance will be reaped if this goes below the existential deposit.
409
- */
410
- get total() {
411
- const extra = this.getValue("extra");
412
-
413
- // if there is a DelegatedStaking hold (new model: polkadot, kusama), nom pool staked amount is included in reserved
414
- // if not (old model: vara, avail, cere), staked amount is not in the account and it needs to be added to the total
415
- const nomPoolStakedPlancks = this.locks.some(lock => lock.source === "substrate-native-holds" && lock.label === "DelegatedStaking") ? 0n : this.nompools.map(({
416
- amount
417
- }) => amount.planck).reduce((a, b) => a + b, 0n);
418
- return this.#format(this.free.planck + this.reserved.planck + nomPoolStakedPlancks + this.subtensor.map(({
419
- amount
420
- }) => amount.planck).reduce((a, b) => a + b, 0n) + includeInTotalExtraAmount(extra));
421
- }
422
- /** The non-reserved balance of this token. Includes the frozen amount. Is included in the total. */
423
- get free() {
424
- // for simple balances
425
- if ("value" in this.#storage && this.#storage.value) return this.#format(this.#storage.value);
426
-
427
- // for complex balances
428
- const freeValues = this.getValue("free");
429
- const totalFree = freeValues.map(({
430
- amount
431
- }) => amount.planck).reduce((a, b) => a + b, 0n);
432
- return this.#format(totalFree);
433
- }
434
- /** The reserved balance of this token. Is included in the total. */
435
- get reserved() {
436
- const reservedValues = this.getValue("reserved");
437
- if (reservedValues.length === 0) return this.#format(0n);
438
- return this.#format(reservedValues.map(({
439
- amount
440
- }) => amount.planck).reduce((a, b) => a + b, 0n));
441
- }
442
- get reserves() {
443
- return this.getValue("reserved");
444
- }
445
- /** The frozen balance of this token. Is included in the free amount. */
446
- get locked() {
447
- return this.#format(this.locks.map(({
448
- amount
449
- }) => amount.planck).reduce((a, b) => util.BigMath.max(a, b), 0n));
450
- }
451
- get locks() {
452
- return this.getValue("locked");
453
- }
454
- get nompools() {
455
- return this.getValue("nompool");
456
- }
457
- get subtensor() {
458
- return this.getValue("subtensor");
459
- }
460
-
461
- /** The extra balance of this token */
462
- get extra() {
463
- const extra = this.getRawValue("extra");
464
- if (extra.length > 0) return extra;
465
- return undefined;
466
- }
467
-
468
- /** @deprecated Use balance.locked */
469
- get frozen() {
470
- return this.locked;
471
- }
472
- /** The transferable balance of this token. Is generally the free amount - the miscFrozen amount. */
473
- get transferable() {
474
- /**
475
- * As you can see here, `locked` is subtracted from `free` in order to derive `transferable`.
476
- *
477
- * |--------------------------total--------------------------|
478
- * |-------------------free-------------------|---reserved---|
479
- * |----locked-----|-------transferable-------|
480
- */
481
- const oldTransferableCalculation = () => {
482
- // if no locks exist, transferable is equal to the free amount
483
- if (this.locks.length === 0) return this.free;
484
-
485
- // find the largest lock (but ignore any locks which are marked as `includeInTransferable`)
486
- const excludeAmount = excludeFromTransferableAmount(this.locks);
487
-
488
- // subtract the lock from the free amount (but don't go below 0)
489
- return this.#format(util.BigMath.max(this.free.planck - excludeAmount, 0n));
490
- };
491
-
492
- /**
493
- * As you can see here, `locked` is subtracted from `free + reserved` in order to derive `transferable`.
494
- *
495
- * Alternatively, `reserved` is subtracted from `locked` in order to derive `untouchable`,
496
- * which is then subtracted from `free` in order to derive `transferable`.
497
- *
498
- * |--------------------------total--------------------------|
499
- * |---reserved---|-------------------free-------------------|
500
- * |--untouchable--|
501
- * |------------locked------------|-------transferable-------|
502
- */
503
- const newTransferableCalculation = () => {
504
- // if no locks exist, transferable is equal to the free amount
505
- if (this.locks.length === 0) return this.free;
506
-
507
- // find the largest lock (but ignore any locks which are marked as `includeInTransferable`)
508
- // subtract the reserved amount, because locks now act upon the total balance - not just the free balance
509
- const untouchableAmount = util.BigMath.max(excludeFromTransferableAmount(this.locks) - this.reserved.planck, 0n);
510
-
511
- // subtract the untouchable amount from the free amount (but don't go below 0)
512
- return this.#format(util.BigMath.max(this.free.planck - untouchableAmount, 0n));
513
- };
514
- if (this.#storage.useLegacyTransferableCalculation) return oldTransferableCalculation();
515
- return newTransferableCalculation();
516
- }
517
- /**
518
- * The unavailable balance of this token.
519
- * Prior to the Fungible trait, this was the locked amount + the reserved amount, i.e. `locked + reserved`.
520
- * Now, it is the bigger of the locked amount and the reserved amounts, i.e. `max(locked, reserved)`.
521
- */
522
- get unavailable() {
523
- const oldCalculation = () => this.locked.planck + this.reserved.planck;
524
- const newCalculation = () => util.BigMath.max(this.locked.planck, this.reserved.planck);
525
- const baseUnavailable = this.#storage.useLegacyTransferableCalculation ? oldCalculation() : newCalculation();
526
-
527
- // if there is a DelegatedStaking hold (new model: polkadot, kusama), nom pool staked amount is included in reserved
528
- // if not (old model: vara, avail, cere), staked amount is not in the account and it needs to be added to the total
529
- const nomPoolStakedPlancks = this.locks.some(lock => lock.source === "substrate-native-holds" && lock.label === "DelegatedStaking") ? 0n : this.nompools.map(({
530
- amount
531
- }) => amount.planck).reduce((a, b) => a + b, 0n);
532
- const otherUnavailable = nomPoolStakedPlancks + this.subtensor.reduce((total, each) => total + each.amount.planck, 0n);
533
- return this.#format(baseUnavailable + otherUnavailable);
534
- }
535
-
536
- /** The feePayable balance of this token. Is generally the free amount - the feeFrozen amount. */
537
- get feePayable() {
538
- // if no locks exist, feePayable is equal to the free amount
539
- if (this.locks.length === 0) return this.free;
540
-
541
- // find the largest lock which can't be used to pay tx fees
542
- const excludeAmount = excludeFromFeePayableLocks(this.locked.planck.toString()).map(lock => BigInt(lock.amount)).reduce((max, lock) => util.BigMath.max(max, lock), 0n);
543
-
544
- // subtract the lock from the free amount (but don't go below 0)
545
- return this.#format(util.BigMath.max(this.free.planck - excludeAmount, 0n));
546
- }
547
- }
548
- class BalanceValueGetter {
549
- #storage;
550
- constructor(storage) {
551
- this.#storage = storage;
552
- }
553
- get(valueType) {
554
- if ("values" in this.#storage && this.#storage.values) return this.#storage.values.filter(({
555
- type
556
- }) => type === valueType);
557
- return [];
558
- }
559
- add(valueType, amount) {
560
- if ("values" in this.#storage && this.#storage.values) this.#storage.values.push({
561
- type: valueType,
562
- ...amount
563
- });
564
- }
565
- }
566
- class BalanceFormatter {
567
- #planck;
568
- #decimals;
569
- #tokenRates;
570
- constructor(planck, decimals, fiatRatios) {
571
- this.#planck = util.isBigInt(planck) ? planck.toString() : planck ?? "0";
572
- this.#decimals = decimals || 0;
573
- this.#tokenRates = fiatRatios || null;
574
- }
575
- toJSON = () => this.#planck;
576
- get planck() {
577
- return BigInt(this.#planck);
578
- }
579
- get tokens() {
580
- return util.planckToTokens(this.#planck, this.#decimals);
581
- }
582
- fiat(currency) {
583
- if (!this.#tokenRates) return null;
584
- const ratio = this.#tokenRates[currency];
585
- if (!ratio) return null;
586
- return parseFloat(this.tokens) * ratio.price;
587
- }
588
- }
589
- class PlanckSumBalancesFormatter {
590
- #balances;
591
- constructor(balances) {
592
- this.#balances = balances;
593
- }
594
- #sum = balanceField => {
595
- // a function to get a planck amount from a balance
596
- const planck = balance => balance[balanceField].planck ?? 0n;
597
- return this.#balances.filterMirrorTokens().each.reduce(
598
- // add the total amount to the planck amount of each balance
599
- (total, balance) => total + planck(balance),
600
- // start with a total of 0
601
- 0n);
602
- };
603
-
604
- /**
605
- * The total balance of these tokens. Includes the free and the reserved amount.
606
- */
607
- get total() {
608
- return this.#sum("total");
609
- }
610
- /** The non-reserved balance of these tokens. Includes the frozen amount. Is included in the total. */
611
- get free() {
612
- return this.#sum("free");
613
- }
614
- /** The reserved balance of these tokens. Is included in the total. */
615
- get reserved() {
616
- return this.#sum("reserved");
617
- }
618
- /** The frozen balance of these tokens. Is included in the free amount. */
619
- get locked() {
620
- return this.#sum("locked");
621
- }
622
- /** @deprecated Use balances.locked */
623
- get frozen() {
624
- return this.locked;
625
- }
626
- /** The transferable balance of these tokens. Is generally the free amount - the miscFrozen amount. */
627
- get transferable() {
628
- return this.#sum("transferable");
629
- }
630
- /** The unavailable balance of these tokens. */
631
- get unavailable() {
632
- return this.#sum("unavailable");
633
- }
634
-
635
- /** The feePayable balance of these tokens. Is generally the free amount - the feeFrozen amount. */
636
- get feePayable() {
637
- return this.#sum("feePayable");
638
- }
639
- }
640
- class FiatSumBalancesFormatter {
641
- #balances;
642
- #currency;
643
- constructor(balances, currency) {
644
- this.#balances = balances;
645
- this.#currency = currency;
646
- }
647
- #sum = balanceField => {
648
- // a function to get a fiat amount from a balance
649
- const fiat = balance => balance[balanceField].fiat(this.#currency) ?? 0;
650
- return this.#balances.filterMirrorTokens().each.reduce(
651
- // add the total amount to the fiat amount of each balance
652
- (total, balance) => total + fiat(balance),
653
- // start with a total of 0
654
- 0);
655
- };
656
-
657
- /**
658
- * The total balance of these tokens. Includes the free and the reserved amount.
659
- */
660
- get total() {
661
- return this.#sum("total");
662
- }
663
- /** The non-reserved balance of these tokens. Includes the frozen amount. Is included in the total. */
664
- get free() {
665
- return this.#sum("free");
666
- }
667
- /** The reserved balance of these tokens. Is included in the total. */
668
- get reserved() {
669
- return this.#sum("reserved");
670
- }
671
- /** The frozen balance of these tokens. Is included in the free amount. */
672
- get locked() {
673
- return this.#sum("locked");
674
- }
675
- /** @deprecated Use balances.locked */
676
- get frozen() {
677
- return this.locked;
678
- }
679
- /** The transferable balance of these tokens. Is generally the free amount - the miscFrozen amount. */
680
- get transferable() {
681
- return this.#sum("transferable");
682
- }
683
- /** The unavailable balance of these tokens. */
684
- get unavailable() {
685
- return this.#sum("unavailable");
686
- }
687
- /** The feePayable balance of these tokens. Is generally the free amount - the feeFrozen amount. */
688
- get feePayable() {
689
- return this.#sum("feePayable");
690
- }
691
- }
692
- class SumBalancesFormatter {
693
- #balances;
694
- constructor(balances) {
695
- this.#balances = balances;
696
- }
697
- get planck() {
698
- return new PlanckSumBalancesFormatter(this.#balances);
699
- }
700
- fiat(currency) {
701
- return new FiatSumBalancesFormatter(this.#balances, currency);
702
- }
703
- change24h(currency) {
704
- return new Change24hCurrencyFormatter(this.#balances, currency);
705
- }
706
- }
707
- class Change24hCurrencyFormatter {
708
- #balances;
709
- #currency;
710
- constructor(balances, currency) {
711
- this.#balances = balances;
712
- this.#currency = currency;
713
- }
714
- #change24h = balanceField => {
715
- const output = this.#balances.filterMirrorTokens().each.reduce(
716
- // add the total amount to the fiat amount of each balance
717
- (acc, balance) => {
718
- const change24h = balance.rates?.[this.#currency]?.change24h;
719
- if (typeof change24h !== "number") return acc;
720
- const fiat = balance[balanceField].fiat(this.#currency);
721
- if (!fiat) return acc;
722
- return {
723
- totalFiatDiff: acc.totalFiatDiff + fiat * change24h,
724
- totalFiat: acc.totalFiat + fiat
725
- };
726
- },
727
- // start with a total of 0
728
- {
729
- totalFiatDiff: 0,
730
- totalFiat: 0
731
- });
732
- return output.totalFiat === 0 ? null : {
733
- diff: output.totalFiatDiff / 100,
734
- ratio: output.totalFiatDiff / output.totalFiat
735
- };
736
- };
737
- get total() {
738
- return this.#change24h("total");
739
- }
740
- get free() {
741
- return this.#change24h("free");
742
- }
743
- get reserved() {
744
- return this.#change24h("reserved");
745
- }
746
- get locked() {
747
- return this.#change24h("locked");
748
- }
749
- get frozen() {
750
- return this.#change24h("frozen");
751
- }
752
- get transferable() {
753
- return this.#change24h("transferable");
754
- }
755
- get unavailable() {
756
- return this.#change24h("unavailable");
757
- }
758
- get feePayable() {
759
- return this.#change24h("feePayable");
760
- }
761
- }
762
- const filterMirrorTokens = (balance, i, balances) => {
763
- const mirrorOf = balance.token?.mirrorOf;
764
- return !mirrorOf || !balances.find(b => b.tokenId === mirrorOf);
765
- };
766
-
767
- /** `IBalance` is a common interface which all balance types must implement. */
768
-
769
- /** A collection of `BalanceJson` objects */
770
-
771
- /** An unlabelled amount of a balance */
772
-
773
- /** A labelled amount of a balance */
774
-
775
- const getValueId = amount => {
776
- const getMetaId = () => {
777
- const meta = amount.meta;
778
- if (!meta) return "";
779
- if (amount.type === "nompool") return meta.poolId?.toString() ?? "";
780
- if (amount.type === "subtensor") {
781
- const {
782
- hotkey,
783
- netuid
784
- } = meta;
785
- if (hotkey && netuid !== undefined) return `${hotkey.toString()}${netuid.toString()}`;
786
- }
787
- return "";
788
- };
789
- return [amount.label, amount.type, amount.source, getMetaId()].join("::");
790
- };
791
-
792
- /** A labelled locked amount of a balance */
793
-
794
- /** A labelled extra amount of a balance */
795
-
796
- /** Used by plugins to help define their custom `BalanceType` */
797
-
798
- /** For fast db access, you can calculate the primary key for a miniMetadata using this method */
799
- const deriveMiniMetadataId = ({
800
- source,
801
- chainId,
802
- specVersion
803
- }) => scale.toHex(scale.Twox64Concat(new TextEncoder().encode(`${source}${chainId}${specVersion}${chaindataProvider.MINIMETADATA_VERSION}`))).slice(-64);
804
-
805
- // for DB version 3, Wallet version 1.21.0
806
- const upgradeRemoveSymbolFromNativeTokenId = async tx => {
807
- const balancesTable = tx.table("balances");
808
- try {
809
- await balancesTable.toCollection().modify(balance => {
810
- if (balance?.tokenId?.includes?.("-substrate-native-")) {
811
- balance.tokenId = balance.tokenId.replace(/-substrate-native-.+$/, "-substrate-native");
812
- balance.id = new Balance(balance).id;
813
- }
814
- if (balance?.tokenId?.includes?.("-evm-native-")) {
815
- balance.tokenId = balance.tokenId.replace(/-evm-native-.+$/, "-evm-native");
816
- balance.id = new Balance(balance).id;
817
- }
818
- });
819
- } catch (err) {
820
- // error most likely due to duplicate ids => clear the db
821
- log.error("Failed to upgrade balances native token ids", {
822
- err
823
- });
824
- await balancesTable.clear();
825
- }
826
- };
827
-
828
- // "locks": [{"label":"fees","amount":"0","includeInTransferable":true,"excludeFromFeePayable":true},{"label":"misc","amount":"0"}]
829
- // "reserves":[{"label":"reserved","amount":"0"}]
830
-
831
- // for DB version 4, Wallet version 1.26.0
832
- // converts the structured balances data to a single compressed string
833
- const upgradeBalancesDataBlob = async tx => {
834
- const balancesTable = tx.table("balances");
835
- const balancesData = await balancesTable.toCollection().toArray();
836
- // migrate to new format
837
- const complexBalanceTypes = ["substrate-native", "substrate-tokens", "substrate-assets"];
838
- const migratedData = balancesData.map(balance => {
839
- if (complexBalanceTypes.includes(balance.source)) {
840
- // too complicated to migrate subsource balances, so just clear them
841
- if ("subSource" in balance) return false;
842
- if (balance.free && balance.free !== "0") balance.values = [{
843
- amount: balance.free,
844
- type: "free",
845
- label: "free"
846
- }];else return false;
847
- } else {
848
- if (balance.free && balance.free !== "0") balance.value = balance.free;else return false;
849
- }
850
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
851
- const {
852
- locks,
853
- reserves,
854
- extra,
855
- ...stuffToKeep
856
- } = balance;
857
- return stuffToKeep;
858
- }).filter(util.isTruthy);
859
- const output = pako__default.default.deflate(JSON.stringify(migratedData));
860
- // now write the compressed data back to the db and clear the old one
861
- await tx.table("balancesBlob").put({
862
- data: output,
863
- id: Date.now().toString()
864
- });
865
- await tx.table("balances").clear();
866
- };
867
-
868
- class TalismanBalancesDatabase extends dexie.Dexie {
869
- constructor() {
870
- super("TalismanBalances");
871
- // https://dexie.org/docs/Tutorial/Design#database-versioning
872
- // You only need to specify properties that you wish to index.
873
-
874
- // The object store will allow any properties on your stored objects but you can only query them by indexed properties
875
- // https://dexie.org/docs/API-Reference#declare-database
876
- //
877
- // Never index properties containing images, movies or large (huge) strings. Store them in IndexedDB, yes! but just don’t index them!
878
- // https://dexie.org/docs/Version/Version.stores()#warning
879
-
880
- this.version(5).stores({
881
- balancesBlob: "id",
882
- miniMetadatas: "id, source, chainId, specName, specVersion",
883
- balances: null
884
- });
885
- this.version(4).stores({
886
- balances: "id",
887
- balancesBlob: "id",
888
- miniMetadatas: "id, source, chainId, specName, specVersion"
889
- }).upgrade(upgradeBalancesDataBlob);
890
- this.version(3).stores({
891
- balances: "id, source, address, tokenId",
892
- miniMetadatas: "id, source, chainId, specName, specVersion"
893
- }).upgrade(upgradeRemoveSymbolFromNativeTokenId);
894
- }
895
- }
896
- const db = new TalismanBalancesDatabase();
897
-
898
- const MODULE_TYPE$8 = chaindataProvider.EvmErc20TokenSchema.shape.type.value;
899
- const PLATFORM$8 = chaindataProvider.EvmErc20TokenSchema.shape.platform.value;
900
-
901
- 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)"]);
902
-
903
- const erc20BalancesAggregatorAbi = viem.parseAbi(["struct AccountToken {address account; address token;}", "function balances(AccountToken[] memory accountTokens) public view returns (uint256[] memory)"]);
36
+ const erc20BalancesAggregatorAbi = viem.parseAbi(["struct AccountToken {address account; address token;}", "function balances(AccountToken[] memory accountTokens) public view returns (uint256[] memory)"]);
904
37
 
905
38
  const uniswapV2PairAbi = [{
906
39
  anonymous: false,
@@ -1594,6 +727,11 @@ const fetchWithAggregator$1 = async (client, balanceDefs, erc20BalancesAggregato
1594
727
  }
1595
728
  };
1596
729
 
730
+ var packageJson = {
731
+ name: "@talismn/balances"};
732
+
733
+ var log = anylogger__default.default(packageJson.name);
734
+
1597
735
  const getErc20ContractData$1 = async (client, contractAddress) => {
1598
736
  try {
1599
737
  const contract = getTypedContract$1(client, viem.erc20Abi, contractAddress);
@@ -1709,20 +847,240 @@ const getTransferCallData$8 = ({
1709
847
  if (!chaindataProvider.isTokenOfType(token, MODULE_TYPE$8)) throw new Error(`Token type ${token.type} is not ${MODULE_TYPE$8}.`);
1710
848
  if (!util.isEthereumAddress(from)) throw new Error("Invalid from address");
1711
849
  if (!util.isEthereumAddress(to)) throw new Error("Invalid to address");
1712
- const data = viem.encodeFunctionData({
1713
- abi: viem.erc20Abi,
1714
- functionName: "transfer",
1715
- args: [to, BigInt(value)]
1716
- });
850
+ const data = viem.encodeFunctionData({
851
+ abi: viem.erc20Abi,
852
+ functionName: "transfer",
853
+ args: [to, BigInt(value)]
854
+ });
855
+ return {
856
+ from,
857
+ to: token.contractAddress,
858
+ data
859
+ };
860
+ };
861
+
862
+ const SUBSCRIPTION_INTERVAL$4 = 6_000;
863
+ const subscribeBalances$8 = ({
864
+ networkId,
865
+ tokensWithAddresses,
866
+ connector
867
+ }) => {
868
+ if (!tokensWithAddresses.length) return rxjs.of({
869
+ success: [],
870
+ errors: []
871
+ });
872
+ return new rxjs.Observable(subscriber => {
873
+ const abortController = new AbortController();
874
+ const poll = async () => {
875
+ try {
876
+ if (abortController.signal.aborted) return;
877
+ const balances = await fetchBalances$8({
878
+ networkId,
879
+ tokensWithAddresses: tokensWithAddresses,
880
+ connector
881
+ });
882
+ if (abortController.signal.aborted) return;
883
+ subscriber.next(balances);
884
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$4);
885
+ } catch (error) {
886
+ log.error("Error", {
887
+ module: MODULE_TYPE$8,
888
+ networkId,
889
+ addressesByToken: tokensWithAddresses,
890
+ error
891
+ });
892
+ subscriber.error(error);
893
+ }
894
+ };
895
+ poll();
896
+ return () => {
897
+ abortController.abort();
898
+ };
899
+ }).pipe(rxjs.distinctUntilChanged(lodash.isEqual));
900
+ };
901
+
902
+ const EvmErc20BalanceModule = {
903
+ type: MODULE_TYPE$8,
904
+ platform: PLATFORM$8,
905
+ getMiniMetadata: getMiniMetadata$8,
906
+ fetchTokens: fetchTokens$8,
907
+ fetchBalances: fetchBalances$8,
908
+ subscribeBalances: subscribeBalances$8,
909
+ getTransferCallData: getTransferCallData$8
910
+ };
911
+
912
+ const TokenConfigBaseSchema = chaindataProvider.TokenBaseSchema.partial().omit({
913
+ id: true
914
+ });
915
+
916
+ // to be used by chaindata too
917
+ const EvmErc20TokenConfigSchema = z__default.default.strictObject({
918
+ contractAddress: chaindataProvider.EvmErc20TokenSchema.shape.contractAddress,
919
+ ...TokenConfigBaseSchema.shape
920
+ });
921
+
922
+ const MODULE_TYPE$7 = chaindataProvider.EvmNativeTokenSchema.shape.type.value;
923
+ const PLATFORM$7 = chaindataProvider.EvmNativeTokenSchema.shape.platform.value;
924
+
925
+ const fetchBalances$7 = async ({
926
+ networkId,
927
+ tokensWithAddresses,
928
+ connector
929
+ }) => {
930
+ if (!tokensWithAddresses.length) return {
931
+ success: [],
932
+ errors: []
933
+ };
934
+ const client = await connector.getPublicClientForEvmNetwork(networkId);
935
+ if (!client) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
936
+ for (const [token, addresses] of tokensWithAddresses) {
937
+ if (token.type !== MODULE_TYPE$7 || token.networkId !== networkId) throw new Error(`Invalid token type or networkId for EVM ERC20 balance module: ${token.type} on ${token.networkId}`);
938
+ for (const address of addresses) if (!util.isEthereumAddress(address)) throw new Error(`Invalid ethereum address for EVM ERC20 balance module: ${address} for token ${token.id}`);
939
+ }
940
+ const balanceDefs = getBalanceDefs(tokensWithAddresses);
941
+ if (client.chain?.contracts?.multicall3 && balanceDefs.length > 1) {
942
+ const multicall3 = client.chain.contracts.multicall3;
943
+ return fetchWithMulticall(client, balanceDefs, multicall3.address);
944
+ }
945
+ return fetchWithoutMulticall(client, balanceDefs);
946
+ };
947
+ const fetchWithoutMulticall = async (client, balanceDefs) => {
948
+ if (balanceDefs.length === 0) return {
949
+ success: [],
950
+ errors: []
951
+ };
952
+ const results = await Promise.allSettled(balanceDefs.map(async ({
953
+ token,
954
+ address
955
+ }) => {
956
+ try {
957
+ const result = await client.getBalance({
958
+ address
959
+ });
960
+ const balance = {
961
+ address,
962
+ tokenId: token.id,
963
+ value: result.toString(),
964
+ source: MODULE_TYPE$7,
965
+ networkId: chaindataProvider.parseTokenId(token.id).networkId,
966
+ status: "live"
967
+ };
968
+ return balance;
969
+ } catch (err) {
970
+ throw new BalanceFetchError(`Failed to get balance for token ${token.id} and address ${address} on chain ${client.chain?.id}`, token.id, address, err);
971
+ }
972
+ }));
973
+ return results.reduce((acc, result) => {
974
+ if (result.status === "fulfilled") acc.success.push(result.value);else {
975
+ const error = result.reason;
976
+ acc.errors.push({
977
+ tokenId: error.tokenId,
978
+ address: error.address,
979
+ error
980
+ });
981
+ }
982
+ return acc;
983
+ }, {
984
+ success: [],
985
+ errors: []
986
+ });
987
+ };
988
+ const fetchWithMulticall = async (client, balanceDefs, multicall3Address) => {
989
+ if (balanceDefs.length === 0) return {
990
+ success: [],
991
+ errors: []
992
+ };
993
+ try {
994
+ const callResults = await client.multicall({
995
+ contracts: balanceDefs.map(({
996
+ address
997
+ }) => ({
998
+ address: multicall3Address,
999
+ abi: abiMulticall,
1000
+ functionName: "getEthBalance",
1001
+ args: [address]
1002
+ }))
1003
+ });
1004
+ return callResults.reduce((acc, result, index) => {
1005
+ if (result.status === "success") {
1006
+ acc.success.push({
1007
+ address: balanceDefs[index].address,
1008
+ tokenId: balanceDefs[index].token.id,
1009
+ value: result.result.toString(),
1010
+ source: MODULE_TYPE$7,
1011
+ networkId: chaindataProvider.parseTokenId(balanceDefs[index].token.id).networkId,
1012
+ status: "live"
1013
+ });
1014
+ }
1015
+ if (result.status === "failure") {
1016
+ acc.errors.push({
1017
+ tokenId: balanceDefs[index].token.id,
1018
+ address: balanceDefs[index].address,
1019
+ error: new BalanceFetchError(`Failed to get balance for token ${balanceDefs[index].token.id} and address ${balanceDefs[index].address} on chain ${client.chain?.id}`, balanceDefs[index].token.id, balanceDefs[index].address, result.error)
1020
+ });
1021
+ }
1022
+ return acc;
1023
+ }, {
1024
+ success: [],
1025
+ errors: []
1026
+ });
1027
+ } catch (err) {
1028
+ const errors = balanceDefs.map(balanceDef => ({
1029
+ tokenId: balanceDef.token.id,
1030
+ address: balanceDef.address,
1031
+ error: new BalanceFetchNetworkError(`Failed to get balances for evm-erc20 tokens on chain ${client.chain?.id}`, String(client.chain?.id), err)
1032
+ }));
1033
+ return {
1034
+ success: [],
1035
+ errors
1036
+ };
1037
+ }
1038
+ };
1039
+
1040
+ const fetchTokens$7 = async ({
1041
+ networkId,
1042
+ tokens
1043
+ }) => {
1044
+ // assume there is one and only one token in the array
1045
+ if (tokens.length !== 1) throw new Error("EVM Native module expects the nativeCurrency to be passed as a single token in the array");
1046
+ const token = lodash.assign({
1047
+ id: chaindataProvider.evmNativeTokenId(networkId),
1048
+ type: MODULE_TYPE$7,
1049
+ platform: PLATFORM$7,
1050
+ networkId,
1051
+ isDefault: true
1052
+ }, tokens[0]);
1053
+ const parsed = chaindataProvider.EvmNativeTokenSchema.safeParse(token);
1054
+ if (!parsed.success) {
1055
+ log.warn("Ignoring token with invalid schema", token);
1056
+ return [];
1057
+ }
1058
+ return [parsed.data];
1059
+ };
1060
+
1061
+ const getMiniMetadata$7 = () => {
1062
+ throw new Error("MiniMetadata is not supported for ethereum tokens");
1063
+ };
1064
+
1065
+ const getTransferCallData$7 = ({
1066
+ from,
1067
+ to,
1068
+ value,
1069
+ token
1070
+ }) => {
1071
+ if (!chaindataProvider.isTokenOfType(token, MODULE_TYPE$7)) throw new Error(`Token type ${token.type} is not ${MODULE_TYPE$7}.`);
1072
+ if (!util.isEthereumAddress(from)) throw new Error("Invalid from address");
1073
+ if (!util.isEthereumAddress(to)) throw new Error("Invalid to address");
1717
1074
  return {
1718
1075
  from,
1719
- to: token.contractAddress,
1720
- data
1076
+ to,
1077
+ value,
1078
+ data: "0x"
1721
1079
  };
1722
1080
  };
1723
1081
 
1724
- const SUBSCRIPTION_INTERVAL$4 = 6_000;
1725
- const subscribeBalances$8 = ({
1082
+ const SUBSCRIPTION_INTERVAL$3 = 6_000;
1083
+ const subscribeBalances$7 = ({
1726
1084
  networkId,
1727
1085
  tokensWithAddresses,
1728
1086
  connector
@@ -1736,17 +1094,17 @@ const subscribeBalances$8 = ({
1736
1094
  const poll = async () => {
1737
1095
  try {
1738
1096
  if (abortController.signal.aborted) return;
1739
- const balances = await fetchBalances$8({
1097
+ const balances = await fetchBalances$7({
1740
1098
  networkId,
1741
1099
  tokensWithAddresses: tokensWithAddresses,
1742
1100
  connector
1743
1101
  });
1744
1102
  if (abortController.signal.aborted) return;
1745
1103
  subscriber.next(balances);
1746
- setTimeout(poll, SUBSCRIPTION_INTERVAL$4);
1104
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$3);
1747
1105
  } catch (error) {
1748
1106
  log.error("Error", {
1749
- module: MODULE_TYPE$8,
1107
+ module: MODULE_TYPE$7,
1750
1108
  networkId,
1751
1109
  addressesByToken: tokensWithAddresses,
1752
1110
  error
@@ -1761,30 +1119,25 @@ const subscribeBalances$8 = ({
1761
1119
  }).pipe(rxjs.distinctUntilChanged(lodash.isEqual));
1762
1120
  };
1763
1121
 
1764
- const EvmErc20BalanceModule = {
1765
- type: MODULE_TYPE$8,
1766
- platform: PLATFORM$8,
1767
- getMiniMetadata: getMiniMetadata$8,
1768
- fetchTokens: fetchTokens$8,
1769
- fetchBalances: fetchBalances$8,
1770
- subscribeBalances: subscribeBalances$8,
1771
- getTransferCallData: getTransferCallData$8
1122
+ const EvmNativeBalanceModule = {
1123
+ type: MODULE_TYPE$7,
1124
+ platform: PLATFORM$7,
1125
+ getMiniMetadata: getMiniMetadata$7,
1126
+ fetchTokens: fetchTokens$7,
1127
+ fetchBalances: fetchBalances$7,
1128
+ subscribeBalances: subscribeBalances$7,
1129
+ getTransferCallData: getTransferCallData$7
1772
1130
  };
1773
1131
 
1774
- const TokenConfigBaseSchema = chaindataProvider.TokenBaseSchema.partial().omit({
1775
- id: true
1776
- });
1777
-
1778
1132
  // to be used by chaindata too
1779
- const EvmErc20TokenConfigSchema = z__default.default.strictObject({
1780
- contractAddress: chaindataProvider.EvmErc20TokenSchema.shape.contractAddress,
1133
+ const EvmNativeTokenConfigSchema = z__default.default.strictObject({
1781
1134
  ...TokenConfigBaseSchema.shape
1782
1135
  });
1783
1136
 
1784
- const MODULE_TYPE$7 = chaindataProvider.EvmNativeTokenSchema.shape.type.value;
1785
- const PLATFORM$7 = chaindataProvider.EvmNativeTokenSchema.shape.platform.value;
1137
+ const MODULE_TYPE$6 = chaindataProvider.EvmUniswapV2TokenSchema.shape.type.value;
1138
+ const PLATFORM$6 = chaindataProvider.EvmUniswapV2TokenSchema.shape.platform.value;
1786
1139
 
1787
- const fetchBalances$7 = async ({
1140
+ const fetchBalances$6 = async ({
1788
1141
  networkId,
1789
1142
  tokensWithAddresses,
1790
1143
  connector
@@ -1796,17 +1149,17 @@ const fetchBalances$7 = async ({
1796
1149
  const client = await connector.getPublicClientForEvmNetwork(networkId);
1797
1150
  if (!client) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
1798
1151
  for (const [token, addresses] of tokensWithAddresses) {
1799
- if (token.type !== MODULE_TYPE$7 || token.networkId !== networkId) throw new Error(`Invalid token type or networkId for EVM ERC20 balance module: ${token.type} on ${token.networkId}`);
1152
+ if (token.type !== MODULE_TYPE$6 || token.networkId !== networkId) throw new Error(`Invalid token type or networkId for EVM ERC20 balance module: ${token.type} on ${token.networkId}`);
1800
1153
  for (const address of addresses) if (!util.isEthereumAddress(address)) throw new Error(`Invalid ethereum address for EVM ERC20 balance module: ${address} for token ${token.id}`);
1801
1154
  }
1802
1155
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
1803
- if (client.chain?.contracts?.multicall3 && balanceDefs.length > 1) {
1804
- const multicall3 = client.chain.contracts.multicall3;
1805
- return fetchWithMulticall(client, balanceDefs, multicall3.address);
1156
+ if (client.chain?.contracts?.erc20Aggregator && balanceDefs.length > 1) {
1157
+ const erc20Aggregator = client.chain.contracts.erc20Aggregator;
1158
+ return fetchWithAggregator(client, balanceDefs, erc20Aggregator.address);
1806
1159
  }
1807
- return fetchWithoutMulticall(client, balanceDefs);
1160
+ return fetchWithoutAggregator(client, balanceDefs);
1808
1161
  };
1809
- const fetchWithoutMulticall = async (client, balanceDefs) => {
1162
+ const fetchWithoutAggregator = async (client, balanceDefs) => {
1810
1163
  if (balanceDefs.length === 0) return {
1811
1164
  success: [],
1812
1165
  errors: []
@@ -1816,14 +1169,17 @@ const fetchWithoutMulticall = async (client, balanceDefs) => {
1816
1169
  address
1817
1170
  }) => {
1818
1171
  try {
1819
- const result = await client.getBalance({
1820
- address
1172
+ const result = await client.readContract({
1173
+ abi: viem.erc20Abi,
1174
+ address: token.contractAddress,
1175
+ functionName: "balanceOf",
1176
+ args: [address]
1821
1177
  });
1822
1178
  const balance = {
1823
1179
  address,
1824
1180
  tokenId: token.id,
1825
1181
  value: result.toString(),
1826
- source: MODULE_TYPE$7,
1182
+ source: MODULE_TYPE$6,
1827
1183
  networkId: chaindataProvider.parseTokenId(token.id).networkId,
1828
1184
  status: "live"
1829
1185
  };
@@ -1847,45 +1203,33 @@ const fetchWithoutMulticall = async (client, balanceDefs) => {
1847
1203
  errors: []
1848
1204
  });
1849
1205
  };
1850
- const fetchWithMulticall = async (client, balanceDefs, multicall3Address) => {
1206
+ const fetchWithAggregator = async (client, balanceDefs, erc20BalancesAggregatorAddress) => {
1851
1207
  if (balanceDefs.length === 0) return {
1852
1208
  success: [],
1853
1209
  errors: []
1854
1210
  };
1855
1211
  try {
1856
- const callResults = await client.multicall({
1857
- contracts: balanceDefs.map(({
1858
- address
1859
- }) => ({
1860
- address: multicall3Address,
1861
- abi: abiMulticall,
1862
- functionName: "getEthBalance",
1863
- args: [address]
1864
- }))
1212
+ const erc20Balances = await client.readContract({
1213
+ abi: erc20BalancesAggregatorAbi,
1214
+ address: erc20BalancesAggregatorAddress,
1215
+ functionName: "balances",
1216
+ args: [balanceDefs.map(b => ({
1217
+ account: b.address,
1218
+ token: b.token.contractAddress
1219
+ }))]
1865
1220
  });
1866
- return callResults.reduce((acc, result, index) => {
1867
- if (result.status === "success") {
1868
- acc.success.push({
1869
- address: balanceDefs[index].address,
1870
- tokenId: balanceDefs[index].token.id,
1871
- value: result.result.toString(),
1872
- source: MODULE_TYPE$7,
1873
- networkId: chaindataProvider.parseTokenId(balanceDefs[index].token.id).networkId,
1874
- status: "live"
1875
- });
1876
- }
1877
- if (result.status === "failure") {
1878
- acc.errors.push({
1879
- tokenId: balanceDefs[index].token.id,
1880
- address: balanceDefs[index].address,
1881
- error: new BalanceFetchError(`Failed to get balance for token ${balanceDefs[index].token.id} and address ${balanceDefs[index].address} on chain ${client.chain?.id}`, balanceDefs[index].token.id, balanceDefs[index].address, result.error)
1882
- });
1883
- }
1884
- return acc;
1885
- }, {
1886
- success: [],
1221
+ const success = balanceDefs.map((balanceDef, index) => ({
1222
+ address: balanceDef.address,
1223
+ tokenId: balanceDef.token.id,
1224
+ value: erc20Balances[index].toString(),
1225
+ source: MODULE_TYPE$6,
1226
+ networkId: chaindataProvider.parseTokenId(balanceDef.token.id).networkId,
1227
+ status: "live"
1228
+ }));
1229
+ return {
1230
+ success,
1887
1231
  errors: []
1888
- });
1232
+ };
1889
1233
  } catch (err) {
1890
1234
  const errors = balanceDefs.map(balanceDef => ({
1891
1235
  tokenId: balanceDef.token.id,
@@ -1899,50 +1243,183 @@ const fetchWithMulticall = async (client, balanceDefs, multicall3Address) => {
1899
1243
  }
1900
1244
  };
1901
1245
 
1902
- const fetchTokens$7 = async ({
1903
- networkId,
1904
- tokens
1905
- }) => {
1906
- // assume there is one and only one token in the array
1907
- if (tokens.length !== 1) throw new Error("EVM Native module expects the nativeCurrency to be passed as a single token in the array");
1908
- const token = lodash.assign({
1909
- id: chaindataProvider.evmNativeTokenId(networkId),
1910
- type: MODULE_TYPE$7,
1911
- platform: PLATFORM$7,
1912
- networkId,
1913
- isDefault: true
1914
- }, tokens[0]);
1915
- const parsed = chaindataProvider.EvmNativeTokenSchema.safeParse(token);
1916
- if (!parsed.success) {
1917
- log.warn("Ignoring token with invalid schema", token);
1918
- return [];
1919
- }
1920
- return [parsed.data];
1921
- };
1922
-
1923
- const getMiniMetadata$7 = () => {
1246
+ const getErc20ContractData = async (client, contractAddress) => {
1247
+ try {
1248
+ const contract = getTypedContract(client, viem.erc20Abi, contractAddress);
1249
+
1250
+ // eslint-disable-next-line no-var
1251
+ var [symbol, decimals, name] = await Promise.all([contract.read.symbol(), contract.read.decimals(), contract.read.name()]);
1252
+ } catch (e) {
1253
+ if (e instanceof viem.ContractFunctionExecutionError) {
1254
+ // try to perform the contract read with bytes32 symbol
1255
+ const contract = getTypedContract(client, viem.erc20Abi_bytes32, contractAddress);
1256
+
1257
+ // eslint-disable-next-line no-var
1258
+ var [bytesSymbol, decimals, nameSymbol] = await Promise.all([contract.read.symbol(), contract.read.decimals(), contract.read.name()]);
1259
+ symbol = viem.hexToString(bytesSymbol).replace(/\0/g, "").trim(); // remove NULL characters
1260
+ name = viem.hexToString(nameSymbol).replace(/\0/g, "").trim(); // remove NULL characters
1261
+ } else throw e;
1262
+ }
1263
+ return {
1264
+ symbol,
1265
+ decimals,
1266
+ name
1267
+ };
1268
+ };
1269
+ const getTypedContract = (client, abi, contractAddress) => viem.getContract({
1270
+ address: contractAddress,
1271
+ abi,
1272
+ client: {
1273
+ public: client
1274
+ }
1275
+ });
1276
+ const getUniswapV2PairContractData = async (client, contractAddress) => {
1277
+ const contract = getTypedContract(client, uniswapV2PairAbi, contractAddress);
1278
+
1279
+ // eslint-disable-next-line no-var
1280
+ var [token0, token1, decimals, name] = await Promise.all([contract.read.token0(), contract.read.token1(), contract.read.decimals(), contract.read.name()]);
1281
+ return {
1282
+ token0,
1283
+ token1,
1284
+ decimals,
1285
+ name
1286
+ };
1287
+ };
1288
+
1289
+ // const TokenCacheSchema = EvmUniswapV2TokenSchema.pick({
1290
+ // id: true,
1291
+ // symbol: true,
1292
+ // decimals: true,
1293
+ // name: true,
1294
+ // tokenAddress0: true,
1295
+ // tokenAddress1: true,
1296
+ // decimals0: true,
1297
+ // decimals1: true,
1298
+ // symbol0: true,
1299
+ // symbol1: true,
1300
+ // })
1301
+
1302
+ const TokenCacheSchema = z__default.default.discriminatedUnion("isValid", [z__default.default.strictObject({
1303
+ id: chaindataProvider.EvmUniswapV2TokenSchema.shape.id,
1304
+ isValid: z__default.default.literal(true),
1305
+ ...chaindataProvider.EvmUniswapV2TokenSchema.pick({
1306
+ symbol: true,
1307
+ decimals: true,
1308
+ name: true,
1309
+ tokenAddress0: true,
1310
+ tokenAddress1: true,
1311
+ decimals0: true,
1312
+ decimals1: true,
1313
+ symbol0: true,
1314
+ symbol1: true
1315
+ }).shape
1316
+ }), z__default.default.strictObject({
1317
+ id: chaindataProvider.EvmUniswapV2TokenSchema.shape.id,
1318
+ isValid: z__default.default.literal(false)
1319
+ })]);
1320
+ const fetchTokens$6 = async ({
1321
+ networkId,
1322
+ tokens,
1323
+ connector,
1324
+ cache
1325
+ }) => {
1326
+ const result = [];
1327
+ for (const tokenConfig of tokens) {
1328
+ const tokenId = chaindataProvider.evmUniswapV2TokenId(networkId, tokenConfig.contractAddress);
1329
+ const cached = cache[tokenId] && TokenCacheSchema.safeParse(cache[tokenId]).data;
1330
+ if (!cached) {
1331
+ const client = await connector.getPublicClientForEvmNetwork(networkId);
1332
+ if (!client) {
1333
+ log.warn(`No client found for network ${networkId} while fetching EVM ERC20 tokens`);
1334
+ continue;
1335
+ }
1336
+ try {
1337
+ const {
1338
+ token0,
1339
+ token1,
1340
+ name,
1341
+ decimals
1342
+ } = await getUniswapV2PairContractData(client, tokenConfig.contractAddress);
1343
+ const {
1344
+ symbol: symbol0,
1345
+ decimals: decimals0
1346
+ } = await getErc20ContractData(client, token0);
1347
+ const {
1348
+ symbol: symbol1,
1349
+ decimals: decimals1
1350
+ } = await getErc20ContractData(client, token1);
1351
+ cache[tokenId] = TokenCacheSchema.parse({
1352
+ id: tokenId,
1353
+ symbol: `${symbol0}/${symbol1}`,
1354
+ decimals,
1355
+ name,
1356
+ tokenAddress0: token0,
1357
+ tokenAddress1: token1,
1358
+ decimals0,
1359
+ decimals1,
1360
+ symbol0,
1361
+ symbol1,
1362
+ isValid: true
1363
+ });
1364
+ } catch (err) {
1365
+ const msg = err.shortMessage;
1366
+ if (msg.includes("returned no data") || msg.includes("is out of bounds") || msg.includes("reverted")) {
1367
+ cache[tokenId] = {
1368
+ id: tokenId,
1369
+ isValid: false
1370
+ };
1371
+ } else {
1372
+ log.warn(`Failed to fetch UniswapV2 token data for ${tokenConfig.contractAddress}`, err.shortMessage);
1373
+ }
1374
+ continue;
1375
+ }
1376
+ }
1377
+ const base = {
1378
+ id: tokenId,
1379
+ type: MODULE_TYPE$6,
1380
+ platform: PLATFORM$6,
1381
+ networkId
1382
+ };
1383
+ const cached2 = cache[tokenId] && TokenCacheSchema.safeParse(cache[tokenId]).data;
1384
+ if (cached2?.isValid === false) continue;
1385
+ const token = lodash.assign(base, cached2?.isValid ? lodash.omit(cached2, ["isValid"]) : {}, tokenConfig);
1386
+ const parsed = chaindataProvider.EvmUniswapV2TokenSchema.safeParse(token);
1387
+ if (!parsed.success) {
1388
+ log.warn("Ignoring token with invalid schema", token);
1389
+ continue;
1390
+ }
1391
+ result.push(parsed.data);
1392
+ }
1393
+ return result;
1394
+ };
1395
+
1396
+ const getMiniMetadata$6 = () => {
1924
1397
  throw new Error("MiniMetadata is not supported for ethereum tokens");
1925
1398
  };
1926
1399
 
1927
- const getTransferCallData$7 = ({
1400
+ const getTransferCallData$6 = ({
1928
1401
  from,
1929
1402
  to,
1930
1403
  value,
1931
1404
  token
1932
1405
  }) => {
1933
- if (!chaindataProvider.isTokenOfType(token, MODULE_TYPE$7)) throw new Error(`Token type ${token.type} is not ${MODULE_TYPE$7}.`);
1406
+ if (!chaindataProvider.isTokenOfType(token, MODULE_TYPE$6)) throw new Error(`Token type ${token.type} is not ${MODULE_TYPE$6}.`);
1934
1407
  if (!util.isEthereumAddress(from)) throw new Error("Invalid from address");
1935
1408
  if (!util.isEthereumAddress(to)) throw new Error("Invalid to address");
1409
+ const data = viem.encodeFunctionData({
1410
+ abi: viem.erc20Abi,
1411
+ functionName: "transfer",
1412
+ args: [to, BigInt(value)]
1413
+ });
1936
1414
  return {
1937
1415
  from,
1938
- to,
1939
- value,
1940
- data: "0x"
1416
+ to: token.contractAddress,
1417
+ data
1941
1418
  };
1942
1419
  };
1943
1420
 
1944
- const SUBSCRIPTION_INTERVAL$3 = 6_000;
1945
- const subscribeBalances$7 = ({
1421
+ const SUBSCRIPTION_INTERVAL$2 = 6_000;
1422
+ const subscribeBalances$6 = ({
1946
1423
  networkId,
1947
1424
  tokensWithAddresses,
1948
1425
  connector
@@ -1956,17 +1433,17 @@ const subscribeBalances$7 = ({
1956
1433
  const poll = async () => {
1957
1434
  try {
1958
1435
  if (abortController.signal.aborted) return;
1959
- const balances = await fetchBalances$7({
1436
+ const balances = await fetchBalances$6({
1960
1437
  networkId,
1961
1438
  tokensWithAddresses: tokensWithAddresses,
1962
1439
  connector
1963
1440
  });
1964
1441
  if (abortController.signal.aborted) return;
1965
1442
  subscriber.next(balances);
1966
- setTimeout(poll, SUBSCRIPTION_INTERVAL$3);
1443
+ setTimeout(poll, SUBSCRIPTION_INTERVAL$2);
1967
1444
  } catch (error) {
1968
1445
  log.error("Error", {
1969
- module: MODULE_TYPE$7,
1446
+ module: MODULE_TYPE$6,
1970
1447
  networkId,
1971
1448
  addressesByToken: tokensWithAddresses,
1972
1449
  error
@@ -1974,715 +1451,1142 @@ const subscribeBalances$7 = ({
1974
1451
  subscriber.error(error);
1975
1452
  }
1976
1453
  };
1977
- poll();
1978
- return () => {
1979
- abortController.abort();
1454
+ poll();
1455
+ return () => {
1456
+ abortController.abort();
1457
+ };
1458
+ }).pipe(rxjs.distinctUntilChanged(lodash.isEqual));
1459
+ };
1460
+
1461
+ const EvmUniswapV2BalanceModule = {
1462
+ type: MODULE_TYPE$6,
1463
+ platform: PLATFORM$6,
1464
+ getMiniMetadata: getMiniMetadata$6,
1465
+ fetchTokens: fetchTokens$6,
1466
+ fetchBalances: fetchBalances$6,
1467
+ subscribeBalances: subscribeBalances$6,
1468
+ getTransferCallData: getTransferCallData$6
1469
+ };
1470
+
1471
+ // to be used by chaindata too
1472
+ const EvmUniswapV2TokenConfigSchema = z__default.default.strictObject({
1473
+ contractAddress: chaindataProvider.EvmUniswapV2TokenSchema.shape.contractAddress,
1474
+ ...TokenConfigBaseSchema.shape
1475
+ });
1476
+
1477
+ const MODULE_TYPE$5 = chaindataProvider.SubAssetsTokenSchema.shape.type.value;
1478
+ const PLATFORM$5 = chaindataProvider.SubAssetsTokenSchema.shape.platform.value;
1479
+
1480
+ const fetchRpcQueryPack = async (connector, networkId, queries) => {
1481
+ const allStateKeys = queries.flatMap(({
1482
+ stateKeys
1483
+ }) => stateKeys).filter(util.isNotNil);
1484
+
1485
+ // doing a query without keys would throw an error => return early
1486
+ if (!allStateKeys.length) return queries.map(({
1487
+ stateKeys,
1488
+ decodeResult
1489
+ }) => decodeResult(stateKeys.map(() => null)));
1490
+ const [result] = await connector.send(networkId, "state_queryStorageAt", [allStateKeys]);
1491
+ return decodeRpcQueryPack(queries, result);
1492
+ };
1493
+ const getRpcQueryPack$ = (connector, networkId, queries, timeout = false) => {
1494
+ const allStateKeys = queries.flatMap(({
1495
+ stateKeys
1496
+ }) => stateKeys).filter(util.isNotNil);
1497
+
1498
+ // doing a query without keys would throw an error => return early
1499
+ if (!allStateKeys.length) return rxjs.of(queries.map(({
1500
+ stateKeys,
1501
+ decodeResult
1502
+ }) => decodeResult(stateKeys.map(() => null))));
1503
+ return new rxjs.Observable(subscriber => {
1504
+ const promUnsub = connector.subscribe(networkId, "state_subscribeStorage", "state_storage", [allStateKeys], (error, result) => {
1505
+ if (error) subscriber.error(error);else subscriber.next(decodeRpcQueryPack(queries, result));
1506
+ }, timeout);
1507
+ return () => {
1508
+ promUnsub.then(unsub => unsub("state_unsubscribeStorage"));
1509
+ };
1510
+ });
1511
+ };
1512
+ const decodeRpcQueryPack = (queries, result) => {
1513
+ return queries.reduce((acc, {
1514
+ stateKeys,
1515
+ decodeResult
1516
+ }) => {
1517
+ const changes = stateKeys.map(stateKey => {
1518
+ if (!stateKey) return null;
1519
+ const change = result.changes.find(([key]) => key === stateKey);
1520
+ if (!change) return null;
1521
+ return change[1];
1522
+ });
1523
+ acc.push(decodeResult(changes));
1524
+ return acc;
1525
+ }, []);
1526
+ };
1527
+
1528
+ const fetchRuntimeCallResult = async (connector, networkId, metadataRpc, apiName, method, args) => {
1529
+ const {
1530
+ builder
1531
+ } = scale.parseMetadataRpc(metadataRpc);
1532
+ const call = builder.buildRuntimeCall(apiName, method);
1533
+ const hex = await connector.send(networkId, "state_call", [`${apiName}_${method}`, scale.toHex(call.args.enc(args))]);
1534
+ return call.value.dec(hex);
1535
+ };
1536
+
1537
+ const getConstantValue = (metadataRpc, pallet, constant) => {
1538
+ const {
1539
+ unifiedMetadata,
1540
+ builder
1541
+ } = scale.parseMetadataRpc(metadataRpc);
1542
+ const codec = builder.buildConstant(pallet, constant);
1543
+ const encodedValue = unifiedMetadata.pallets.find(({
1544
+ name
1545
+ }) => name === pallet)?.constants.find(({
1546
+ name
1547
+ }) => name === constant)?.value;
1548
+ if (!encodedValue) throw new Error(`Constant ${pallet}.${constant} not found`);
1549
+ return codec.dec(encodedValue);
1550
+ };
1551
+
1552
+ const tryGetConstantValue = (metadataRpc, pallet, constant) => {
1553
+ const {
1554
+ unifiedMetadata,
1555
+ builder
1556
+ } = scale.parseMetadataRpc(metadataRpc);
1557
+ const encodedValue = unifiedMetadata.pallets.find(({
1558
+ name
1559
+ }) => name === pallet)?.constants.find(({
1560
+ name
1561
+ }) => name === constant)?.value;
1562
+ if (!encodedValue) return null;
1563
+ const codec = builder.buildConstant(pallet, constant);
1564
+ return codec.dec(encodedValue);
1565
+ };
1566
+
1567
+ const hasStorageItem = (metadata, palletName, itemName) => {
1568
+ const pallet = metadata.pallets.find(p => p.name === palletName);
1569
+ if (!pallet || !pallet.storage) return false;
1570
+ return pallet.storage.items.some(item => item.name === itemName);
1571
+ };
1572
+ const hasStorageItems = (metadata, palletName, itemNames) => {
1573
+ const pallet = metadata.pallets.find(p => p.name === palletName);
1574
+ if (!pallet || !pallet.storage) return false;
1575
+ return itemNames.every(itemName => pallet.storage?.items.some(item => item.name === itemName));
1576
+ };
1577
+ const hasRuntimeApi = (metadata, apiName, method) => {
1578
+ const api = metadata.apis.find(api => api.name === apiName);
1579
+ if (!api || !api.methods) return false;
1580
+ return api.methods.some(m => m.name === method);
1581
+ };
1582
+
1583
+ const buildNetworkStorageCoders = (chainId, miniMetadata, coders) => {
1584
+ if (!miniMetadata.data) return null;
1585
+ const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata.data));
1586
+ try {
1587
+ const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
1588
+ const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
1589
+ const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
1590
+ chainId
1591
+ }) : moduleMethodOrFn;
1592
+ try {
1593
+ return [[key, scaleBuilder.buildStorage(module, method)]];
1594
+ } catch (cause) {
1595
+ log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
1596
+ return [];
1597
+ }
1598
+ }));
1599
+ return builtCoders;
1600
+ } catch (cause) {
1601
+ log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
1602
+ }
1603
+ return null;
1604
+ };
1605
+
1606
+ const buildQueries$2 = (networkId, balanceDefs, miniMetadata) => {
1607
+ const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
1608
+ storage: ["Assets", "Account"]
1609
+ });
1610
+ return balanceDefs.map(({
1611
+ token,
1612
+ address
1613
+ }) => {
1614
+ const scaleCoder = networkStorageCoders?.storage;
1615
+ const stateKey = tryEncode(scaleCoder, Number(token.assetId), address) ??
1616
+ // Asset Hub
1617
+ tryEncode(scaleCoder, BigInt(token.assetId), address); // Astar
1618
+
1619
+ if (!stateKey) {
1620
+ log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
1621
+ return null;
1622
+ }
1623
+ const decodeResult = changes => {
1624
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
1625
+
1626
+ const decoded = scale.decodeScale(scaleCoder, changes[0], `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
1627
+ balance: 0n,
1628
+ status: {
1629
+ type: "Liquid"
1630
+ }};
1631
+ const isFrozen = decoded?.status?.type === "Frozen";
1632
+ const amount = (decoded?.balance ?? 0n).toString();
1633
+
1634
+ // due to the following balance calculations, which are made in the `Balance` type:
1635
+ //
1636
+ // total balance = (free balance) + (reserved balance)
1637
+ // transferable balance = (free balance) - (frozen balance)
1638
+ //
1639
+ // when `isFrozen` is true we need to set **both** the `free` and `frozen` amounts
1640
+ // of this balance to the value we received from the RPC.
1641
+ //
1642
+ // if we only set the `frozen` amount, then the `total` calculation will be incorrect!
1643
+ const free = amount;
1644
+ const frozen = token.isFrozen || isFrozen ? amount : "0";
1645
+
1646
+ // include balance values even if zero, so that newly-zero values overwrite old values
1647
+ const balanceValues = [{
1648
+ type: "free",
1649
+ label: "free",
1650
+ amount: free.toString()
1651
+ }, {
1652
+ type: "locked",
1653
+ label: "frozen",
1654
+ amount: frozen.toString()
1655
+ }];
1656
+ const balance = {
1657
+ source: "substrate-assets",
1658
+ status: "live",
1659
+ address,
1660
+ networkId,
1661
+ tokenId: token.id,
1662
+ values: balanceValues
1663
+ };
1664
+ return balance;
1665
+ };
1666
+ return {
1667
+ stateKeys: [stateKey],
1668
+ decodeResult
1980
1669
  };
1981
- }).pipe(rxjs.distinctUntilChanged(lodash.isEqual));
1670
+ }).filter(util.isNotNil);
1982
1671
  };
1983
-
1984
- const EvmNativeBalanceModule = {
1985
- type: MODULE_TYPE$7,
1986
- platform: PLATFORM$7,
1987
- getMiniMetadata: getMiniMetadata$7,
1988
- fetchTokens: fetchTokens$7,
1989
- fetchBalances: fetchBalances$7,
1990
- subscribeBalances: subscribeBalances$7,
1991
- getTransferCallData: getTransferCallData$7
1672
+ const tryEncode = (scaleCoder, ...args) => {
1673
+ try {
1674
+ return scaleCoder?.keys?.enc?.(...args);
1675
+ } catch {
1676
+ return null;
1677
+ }
1992
1678
  };
1993
1679
 
1994
- // to be used by chaindata too
1995
- const EvmNativeTokenConfigSchema = z__default.default.strictObject({
1996
- ...TokenConfigBaseSchema.shape
1997
- });
1998
-
1999
- const MODULE_TYPE$6 = chaindataProvider.EvmUniswapV2TokenSchema.shape.type.value;
2000
- const PLATFORM$6 = chaindataProvider.EvmUniswapV2TokenSchema.shape.platform.value;
2001
-
2002
- const fetchBalances$6 = async ({
1680
+ const fetchBalances$5 = async ({
2003
1681
  networkId,
2004
1682
  tokensWithAddresses,
2005
- connector
1683
+ connector,
1684
+ miniMetadata
2006
1685
  }) => {
2007
1686
  if (!tokensWithAddresses.length) return {
2008
1687
  success: [],
2009
1688
  errors: []
2010
1689
  };
2011
- const client = await connector.getPublicClientForEvmNetwork(networkId);
2012
- if (!client) throw new Error(`Could not get rpc provider for evm network ${networkId}`);
2013
- for (const [token, addresses] of tokensWithAddresses) {
2014
- if (token.type !== MODULE_TYPE$6 || token.networkId !== networkId) throw new Error(`Invalid token type or networkId for EVM ERC20 balance module: ${token.type} on ${token.networkId}`);
2015
- for (const address of addresses) if (!util.isEthereumAddress(address)) throw new Error(`Invalid ethereum address for EVM ERC20 balance module: ${address} for token ${token.id}`);
2016
- }
2017
1690
  const balanceDefs = getBalanceDefs(tokensWithAddresses);
2018
- if (client.chain?.contracts?.erc20Aggregator && balanceDefs.length > 1) {
2019
- const erc20Aggregator = client.chain.contracts.erc20Aggregator;
2020
- return fetchWithAggregator(client, balanceDefs, erc20Aggregator.address);
1691
+ if (!miniMetadata?.data) {
1692
+ log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$5} balances on ${networkId}.`);
1693
+ return {
1694
+ success: [],
1695
+ errors: balanceDefs.map(def => ({
1696
+ tokenId: def.token.id,
1697
+ address: def.address,
1698
+ error: new Error("Minimetadata is required for fetching balances")
1699
+ }))
1700
+ };
2021
1701
  }
2022
- return fetchWithoutAggregator(client, balanceDefs);
2023
- };
2024
- const fetchWithoutAggregator = async (client, balanceDefs) => {
2025
- if (balanceDefs.length === 0) return {
2026
- success: [],
2027
- errors: []
2028
- };
2029
- const results = await Promise.allSettled(balanceDefs.map(async ({
2030
- token,
2031
- address
2032
- }) => {
2033
- try {
2034
- const result = await client.readContract({
2035
- abi: viem.erc20Abi,
2036
- address: token.contractAddress,
2037
- functionName: "balanceOf",
2038
- args: [address]
2039
- });
2040
- const balance = {
2041
- address,
2042
- tokenId: token.id,
2043
- value: result.toString(),
2044
- source: MODULE_TYPE$6,
2045
- networkId: chaindataProvider.parseTokenId(token.id).networkId,
2046
- status: "live"
2047
- };
2048
- return balance;
2049
- } catch (err) {
2050
- throw new BalanceFetchError(`Failed to get balance for token ${token.id} and address ${address} on chain ${client.chain?.id}`, token.id, address, err);
2051
- }
2052
- }));
2053
- return results.reduce((acc, result) => {
2054
- if (result.status === "fulfilled") acc.success.push(result.value);else {
2055
- const error = result.reason;
2056
- acc.errors.push({
2057
- tokenId: error.tokenId,
2058
- address: error.address,
2059
- error
2060
- });
2061
- }
1702
+ if (miniMetadata.source !== MODULE_TYPE$5) {
1703
+ log.warn(`Ignoring miniMetadata with source ${miniMetadata.source} in ${MODULE_TYPE$5}.`);
1704
+ return {
1705
+ success: [],
1706
+ errors: balanceDefs.map(def => ({
1707
+ tokenId: def.token.id,
1708
+ address: def.address,
1709
+ error: new Error(`Invalid request: miniMetadata source is not ${MODULE_TYPE$5}`)
1710
+ }))
1711
+ };
1712
+ }
1713
+ if (miniMetadata.chainId !== networkId) {
1714
+ log.warn(`Ignoring miniMetadata with chainId ${miniMetadata.chainId} in ${MODULE_TYPE$5}. Expected chainId is ${networkId}`);
1715
+ return {
1716
+ success: [],
1717
+ errors: balanceDefs.map(def => ({
1718
+ tokenId: def.token.id,
1719
+ address: def.address,
1720
+ error: new Error(`Invalid request: Expected chainId is ${networkId}`)
1721
+ }))
1722
+ };
1723
+ }
1724
+ const queries = buildQueries$2(networkId, balanceDefs, miniMetadata);
1725
+ const balances = await fetchRpcQueryPack(connector, networkId, queries);
1726
+ return balanceDefs.reduce((acc, def) => {
1727
+ const balance = balances.find(b => b?.address === def.address && b?.tokenId === def.token.id);
1728
+ if (balance) acc.success.push(balance);
1729
+ //if no entry consider empty balance
1730
+ else acc.success.push({
1731
+ address: def.address,
1732
+ networkId,
1733
+ tokenId: def.token.id,
1734
+ source: MODULE_TYPE$5,
1735
+ status: "live",
1736
+ values: [{
1737
+ type: "free",
1738
+ label: "free",
1739
+ amount: "0"
1740
+ }, {
1741
+ type: "locked",
1742
+ label: "frozen",
1743
+ amount: "0"
1744
+ }]
1745
+ });
2062
1746
  return acc;
2063
1747
  }, {
2064
1748
  success: [],
2065
1749
  errors: []
2066
1750
  });
2067
1751
  };
2068
- const fetchWithAggregator = async (client, balanceDefs, erc20BalancesAggregatorAddress) => {
2069
- if (balanceDefs.length === 0) return {
2070
- success: [],
2071
- errors: []
2072
- };
2073
- try {
2074
- const erc20Balances = await client.readContract({
2075
- abi: erc20BalancesAggregatorAbi,
2076
- address: erc20BalancesAggregatorAddress,
2077
- functionName: "balances",
2078
- args: [balanceDefs.map(b => ({
2079
- account: b.address,
2080
- token: b.token.contractAddress
2081
- }))]
2082
- });
2083
- const success = balanceDefs.map((balanceDef, index) => ({
2084
- address: balanceDef.address,
2085
- tokenId: balanceDef.token.id,
2086
- value: erc20Balances[index].toString(),
2087
- source: MODULE_TYPE$6,
2088
- networkId: chaindataProvider.parseTokenId(balanceDef.token.id).networkId,
2089
- status: "live"
2090
- }));
1752
+
1753
+ const fetchTokens$5 = async ({
1754
+ networkId,
1755
+ tokens,
1756
+ connector,
1757
+ miniMetadata
1758
+ }) => {
1759
+ const anyMiniMetadata = miniMetadata;
1760
+ if (!anyMiniMetadata?.data) return [];
1761
+ const {
1762
+ builder
1763
+ } = scale.parseMetadataRpc(anyMiniMetadata.data);
1764
+ const assetCodec = builder.buildStorage("Assets", "Asset");
1765
+ const metadataCodec = builder.buildStorage("Assets", "Metadata");
1766
+ const [allAssetStorageKeys, allMetadataStorageKeys] = await Promise.all([connector.send(networkId, "state_getKeys", [scale.getStorageKeyPrefix("Assets", "Asset")]), connector.send(networkId, "state_getKeys", [scale.getStorageKeyPrefix("Assets", "Metadata")])]);
1767
+ const [assetStorageResults, metadataStorageResults] = await Promise.all([connector.send(networkId, "state_queryStorageAt", [allAssetStorageKeys]), connector.send(networkId, "state_queryStorageAt", [allMetadataStorageKeys])]);
1768
+ const assetStorageEntries = assetStorageResults[0].changes;
1769
+ const metadataStorageEntries = metadataStorageResults[0].changes;
1770
+ const assetByAssetId = lodash.keyBy(assetStorageEntries.map(([key, value]) => {
1771
+ const [assetId] = assetCodec.keys.dec(key);
1772
+ const asset = assetCodec.value.dec(value);
2091
1773
  return {
2092
- success,
2093
- errors: []
1774
+ assetId,
1775
+ existentialDeposit: asset.min_balance,
1776
+ isSufficient: asset.is_sufficient
2094
1777
  };
2095
- } catch (err) {
2096
- const errors = balanceDefs.map(balanceDef => ({
2097
- tokenId: balanceDef.token.id,
2098
- address: balanceDef.address,
2099
- error: new BalanceFetchNetworkError(`Failed to get balances for evm-erc20 tokens on chain ${client.chain?.id}`, String(client.chain?.id), err)
2100
- }));
1778
+ }), a => a.assetId);
1779
+ const metadataByAssetId = lodash.keyBy(metadataStorageEntries.map(([key, value]) => {
1780
+ const [assetId] = metadataCodec.keys.dec(key);
1781
+ const metadata = metadataCodec.value.dec(value);
2101
1782
  return {
2102
- success: [],
2103
- errors
1783
+ assetId,
1784
+ decimals: metadata.decimals,
1785
+ isFrozen: metadata.is_frozen,
1786
+ name: metadata.name?.asText(),
1787
+ symbol: metadata.symbol?.asText()
2104
1788
  };
2105
- }
1789
+ }), a => a.assetId);
1790
+ const allTokens = lodash.keys(assetByAssetId).map(assetId => lodash.assign({}, assetByAssetId[assetId], metadataByAssetId[assetId] ?? undefined));
1791
+ const configTokenByAssetId = lodash.keyBy(tokens, t => t.assetId);
1792
+ return allTokens.map(asset => ({
1793
+ id: chaindataProvider.subAssetTokenId(networkId, String(asset.assetId)),
1794
+ type: MODULE_TYPE$5,
1795
+ platform: "polkadot",
1796
+ networkId,
1797
+ assetId: String(asset.assetId),
1798
+ isSufficient: asset.isSufficient,
1799
+ isFrozen: asset.isFrozen,
1800
+ name: asset.name,
1801
+ symbol: asset.symbol,
1802
+ decimals: asset.decimals ?? 0,
1803
+ existentialDeposit: String(asset.existentialDeposit),
1804
+ isDefault: true
1805
+ }))
1806
+ // keep all tokens listed in the config + all tokens marked as sufficient
1807
+ .filter(token => {
1808
+ const configToken = configTokenByAssetId[token.assetId];
1809
+ return configToken || token.isSufficient;
1810
+ })
1811
+ // apply config overrides
1812
+ .map(token => {
1813
+ const configToken = configTokenByAssetId[token.assetId];
1814
+ return configToken ? lodash.assign({}, token, configToken) : token;
1815
+ })
1816
+ // validate results
1817
+ .filter(t => {
1818
+ const parsed = chaindataProvider.SubAssetsTokenSchema.safeParse(t);
1819
+ if (!parsed.success) log.warn(`Ignoring invalid token ${MODULE_TYPE$5}`, t);
1820
+ return parsed.success;
1821
+ });
2106
1822
  };
2107
1823
 
2108
- const getErc20ContractData = async (client, contractAddress) => {
2109
- try {
2110
- const contract = getTypedContract(client, viem.erc20Abi, contractAddress);
1824
+ function excludeFromTransferableAmount(locks) {
1825
+ if (typeof locks === "string") return BigInt(locks);
1826
+ if (!Array.isArray(locks)) locks = [locks];
1827
+ return locks.filter(lock => lock.includeInTransferable !== true).map(lock => lock.amount.planck).reduce((max, lock) => util.BigMath.max(max, lock), 0n);
1828
+ }
1829
+ function excludeFromFeePayableLocks(locks) {
1830
+ if (typeof locks === "string") return [];
1831
+ if (!Array.isArray(locks)) locks = [locks];
1832
+ return locks.filter(lock => lock.excludeFromFeePayable);
1833
+ }
1834
+ function includeInTotalExtraAmount(extra) {
1835
+ if (!extra) return 0n;
1836
+ if (!Array.isArray(extra)) extra = [extra];
1837
+ return extra.filter(extra => extra.includeInTotal).map(extra => extra.amount.planck).reduce((a, b) => a + b, 0n);
1838
+ }
2111
1839
 
2112
- // eslint-disable-next-line no-var
2113
- var [symbol, decimals, name] = await Promise.all([contract.read.symbol(), contract.read.decimals(), contract.read.name()]);
2114
- } catch (e) {
2115
- if (e instanceof viem.ContractFunctionExecutionError) {
2116
- // try to perform the contract read with bytes32 symbol
2117
- const contract = getTypedContract(client, viem.erc20Abi_bytes32, contractAddress);
1840
+ /**
1841
+ * Have the importing library define its Token and BalanceJson enums (as a sum type of all plugins) and pass them into some
1842
+ * internal global typescript context, which is then picked up on by this module.
1843
+ */
2118
1844
 
2119
- // eslint-disable-next-line no-var
2120
- var [bytesSymbol, decimals, nameSymbol] = await Promise.all([contract.read.symbol(), contract.read.decimals(), contract.read.name()]);
2121
- symbol = viem.hexToString(bytesSymbol).replace(/\0/g, "").trim(); // remove NULL characters
2122
- name = viem.hexToString(nameSymbol).replace(/\0/g, "").trim(); // remove NULL characters
2123
- } else throw e;
2124
- }
2125
- return {
2126
- symbol,
2127
- decimals,
2128
- name
2129
- };
2130
- };
2131
- const getTypedContract = (client, abi, contractAddress) => viem.getContract({
2132
- address: contractAddress,
2133
- abi,
2134
- client: {
2135
- public: client
1845
+ /** A utility type used to extract the underlying `BalanceType` of a specific source from a generalised `BalanceJson` */
1846
+
1847
+ /** TODO: Remove this in favour of a frontend-friendly `ChaindataProvider` */
1848
+
1849
+ /**
1850
+ * A collection of balances.
1851
+ */
1852
+ class Balances {
1853
+ //
1854
+ // Properties
1855
+ //
1856
+
1857
+ #balances = [];
1858
+
1859
+ //
1860
+ // Methods
1861
+ //
1862
+
1863
+ constructor(balances, hydrate) {
1864
+ // handle Balances (convert to Balance[])
1865
+ if (balances instanceof Balances) return new Balances(balances.each, hydrate);
1866
+
1867
+ // handle Balance (convert to Balance[])
1868
+ if (balances instanceof Balance) return new Balances([balances], hydrate);
1869
+
1870
+ // handle BalanceJsonList (the only remaining non-array type of balances) (convert to BalanceJson[])
1871
+ if (!Array.isArray(balances)) return new Balances(Object.values(balances), hydrate);
1872
+
1873
+ // handle no balances
1874
+ if (balances.length === 0) return this;
1875
+
1876
+ // handle BalanceJson[]
1877
+ if (!util.isArrayOf(balances, Balance)) return new Balances(balances.map(storage => new Balance(storage)), hydrate);
1878
+
1879
+ // handle Balance[]
1880
+ this.#balances = balances;
1881
+ if (hydrate !== undefined) this.hydrate(hydrate);
2136
1882
  }
2137
- });
2138
- const getUniswapV2PairContractData = async (client, contractAddress) => {
2139
- const contract = getTypedContract(client, uniswapV2PairAbi, contractAddress);
2140
1883
 
2141
- // eslint-disable-next-line no-var
2142
- var [token0, token1, decimals, name] = await Promise.all([contract.read.token0(), contract.read.token1(), contract.read.decimals(), contract.read.name()]);
2143
- return {
2144
- token0,
2145
- token1,
2146
- decimals,
2147
- name
1884
+ /**
1885
+ * Calling toJSON on a collection of balances will return the underlying BalanceJsonList.
1886
+ */
1887
+ toJSON = () => Object.fromEntries(this.#balances.map(balance => {
1888
+ try {
1889
+ return [balance.id, balance.toJSON()];
1890
+ } catch (error) {
1891
+ log.error("Failed to convert balance to JSON", error, {
1892
+ id: balance.id,
1893
+ balance
1894
+ });
1895
+ return null;
1896
+ }
1897
+ }).filter(Array.isArray));
1898
+
1899
+ /**
1900
+ * Allows the collection to be iterated over.
1901
+ *
1902
+ * @example
1903
+ * [...balances].forEach(balance => { // do something // })
1904
+ *
1905
+ * @example
1906
+ * for (const balance of balances) {
1907
+ * // do something
1908
+ * }
1909
+ */
1910
+ [Symbol.iterator] = () =>
1911
+ // Create an array of the balances in this collection and return the result of its iterator.
1912
+ this.#balances[Symbol.iterator]();
1913
+
1914
+ /**
1915
+ * Hydrates all balances in this collection.
1916
+ *
1917
+ * @param sources - The sources to hydrate from.
1918
+ */
1919
+ hydrate = sources => {
1920
+ this.#balances.map(balance => balance.hydrate(sources));
2148
1921
  };
2149
- };
2150
1922
 
2151
- // const TokenCacheSchema = EvmUniswapV2TokenSchema.pick({
2152
- // id: true,
2153
- // symbol: true,
2154
- // decimals: true,
2155
- // name: true,
2156
- // tokenAddress0: true,
2157
- // tokenAddress1: true,
2158
- // decimals0: true,
2159
- // decimals1: true,
2160
- // symbol0: true,
2161
- // symbol1: true,
2162
- // })
1923
+ /**
1924
+ * Retrieve a balance from this collection by id.
1925
+ *
1926
+ * @param id - The id of the balance to fetch.
1927
+ * @returns The balance if one exists, or none.
1928
+ */
1929
+ get = id => this.#balances.find(balance => balance.id === id) ?? null;
2163
1930
 
2164
- const TokenCacheSchema = z__default.default.discriminatedUnion("isValid", [z__default.default.strictObject({
2165
- id: chaindataProvider.EvmUniswapV2TokenSchema.shape.id,
2166
- isValid: z__default.default.literal(true),
2167
- ...chaindataProvider.EvmUniswapV2TokenSchema.pick({
2168
- symbol: true,
2169
- decimals: true,
2170
- name: true,
2171
- tokenAddress0: true,
2172
- tokenAddress1: true,
2173
- decimals0: true,
2174
- decimals1: true,
2175
- symbol0: true,
2176
- symbol1: true
2177
- }).shape
2178
- }), z__default.default.strictObject({
2179
- id: chaindataProvider.EvmUniswapV2TokenSchema.shape.id,
2180
- isValid: z__default.default.literal(false)
2181
- })]);
2182
- const fetchTokens$6 = async ({
2183
- networkId,
2184
- tokens,
2185
- connector,
2186
- cache
2187
- }) => {
2188
- const result = [];
2189
- for (const tokenConfig of tokens) {
2190
- const tokenId = chaindataProvider.evmUniswapV2TokenId(networkId, tokenConfig.contractAddress);
2191
- const cached = cache[tokenId] && TokenCacheSchema.safeParse(cache[tokenId]).data;
2192
- if (!cached) {
2193
- const client = await connector.getPublicClientForEvmNetwork(networkId);
2194
- if (!client) {
2195
- log.warn(`No client found for network ${networkId} while fetching EVM ERC20 tokens`);
2196
- continue;
2197
- }
2198
- try {
2199
- const {
2200
- token0,
2201
- token1,
2202
- name,
2203
- decimals
2204
- } = await getUniswapV2PairContractData(client, tokenConfig.contractAddress);
2205
- const {
2206
- symbol: symbol0,
2207
- decimals: decimals0
2208
- } = await getErc20ContractData(client, token0);
2209
- const {
2210
- symbol: symbol1,
2211
- decimals: decimals1
2212
- } = await getErc20ContractData(client, token1);
2213
- cache[tokenId] = TokenCacheSchema.parse({
2214
- id: tokenId,
2215
- symbol: `${symbol0}/${symbol1}`,
2216
- decimals,
2217
- name,
2218
- tokenAddress0: token0,
2219
- tokenAddress1: token1,
2220
- decimals0,
2221
- decimals1,
2222
- symbol0,
2223
- symbol1,
2224
- isValid: true
2225
- });
2226
- } catch (err) {
2227
- const msg = err.shortMessage;
2228
- if (msg.includes("returned no data") || msg.includes("is out of bounds") || msg.includes("reverted")) {
2229
- cache[tokenId] = {
2230
- id: tokenId,
2231
- isValid: false
2232
- };
2233
- } else {
2234
- log.warn(`Failed to fetch UniswapV2 token data for ${tokenConfig.contractAddress}`, err.shortMessage);
2235
- }
2236
- continue;
2237
- }
2238
- }
2239
- const base = {
2240
- id: tokenId,
2241
- type: MODULE_TYPE$6,
2242
- platform: PLATFORM$6,
2243
- networkId
2244
- };
2245
- const cached2 = cache[tokenId] && TokenCacheSchema.safeParse(cache[tokenId]).data;
2246
- if (cached2?.isValid === false) continue;
2247
- const token = lodash.assign(base, cached2?.isValid ? lodash.omit(cached2, ["isValid"]) : {}, tokenConfig);
2248
- const parsed = chaindataProvider.EvmUniswapV2TokenSchema.safeParse(token);
2249
- if (!parsed.success) {
2250
- log.warn("Ignoring token with invalid schema", token);
2251
- continue;
2252
- }
2253
- result.push(parsed.data);
1931
+ /**
1932
+ * Retrieve balances from this collection by search query.
1933
+ *
1934
+ * @param query - The search query.
1935
+ * @returns All balances which match the query.
1936
+ */
1937
+ find = query => {
1938
+ // construct filter
1939
+ const queryArray = Array.isArray(query) ? query : [query];
1940
+ const orQueries = queryArray.map(query => typeof query === "function" ? query : Object.entries(query));
1941
+
1942
+ // filter balances
1943
+ const filter = balance => orQueries.some(query => typeof query === "function" ? query(balance) : query.every(([key, value]) => balance[key] === value));
1944
+
1945
+ // return filter matches
1946
+ return new Balances([...this].filter(filter));
1947
+ };
1948
+
1949
+ /**
1950
+ * Filters this collection to exclude token balances where the token has a `mirrorOf` field
1951
+ * and another balance exists in this collection for the token specified by the `mirrorOf` field.
1952
+ */
1953
+ filterMirrorTokens = () => new Balances([...this].filter(filterMirrorTokens));
1954
+
1955
+ /**
1956
+ * Filters this collection to only include balances which are not zero AND have a fiat conversion rate.
1957
+ */
1958
+ filterNonZeroFiat = (type, currency) => {
1959
+ const filter = balance => (balance[type].fiat(currency) ?? 0) > 0;
1960
+ return this.find(filter);
1961
+ };
1962
+
1963
+ /**
1964
+ * Add some balances to this collection.
1965
+ * Added balances take priority over existing balances.
1966
+ * The aggregation of the two collections is returned.
1967
+ * The original collection is not mutated.
1968
+ *
1969
+ * @param balances - Either a balance or collection of balances to add.
1970
+ * @returns The new collection of balances.
1971
+ */
1972
+ add = balances => {
1973
+ // handle single balance
1974
+ if (balances instanceof Balance) return this.add(new Balances(balances));
1975
+
1976
+ // merge balances
1977
+ const mergedBalances = Object.fromEntries(this.#balances.map(balance => [balance.id, balance]));
1978
+ balances.each.forEach(balance => mergedBalances[balance.id] = balance);
1979
+
1980
+ // return new balances
1981
+ return new Balances(Object.values(mergedBalances));
1982
+ };
1983
+
1984
+ /**
1985
+ * Remove balances from this collection by id.
1986
+ * A new collection without these balances is returned.
1987
+ * The original collection is not mutated.
1988
+ *
1989
+ * @param ids - The id(s) of the balances to remove.
1990
+ * @returns The new collection of balances.
1991
+ */
1992
+ remove = ids => {
1993
+ // handle single id
1994
+ if (!Array.isArray(ids)) return this.remove([ids]);
1995
+
1996
+ // merge and return new balances
1997
+ return new Balances(this.#balances.filter(balance => !ids.includes(balance.id)));
1998
+ };
1999
+
2000
+ // TODO: Add some more useful aggregator methods
2001
+
2002
+ get each() {
2003
+ return [...this];
2254
2004
  }
2255
- return result;
2256
- };
2257
2005
 
2258
- const getMiniMetadata$6 = () => {
2259
- throw new Error("MiniMetadata is not supported for ethereum tokens");
2006
+ /** @deprecated use each instead */
2007
+ get sorted() {
2008
+ return this.each;
2009
+ }
2010
+
2011
+ /**
2012
+ * Get the number of balances in this collection.
2013
+ *
2014
+ * @returns The number of balances in this collection.
2015
+ */
2016
+ get count() {
2017
+ return [...this].length;
2018
+ }
2019
+
2020
+ /**
2021
+ * Get the summed value of balances in this collection.
2022
+ * TODO: Sum up token amounts AND fiat amounts
2023
+ *
2024
+ * @example
2025
+ * // Get the sum of all transferable balances in usd.
2026
+ * balances.sum.fiat('usd').transferable
2027
+ */
2028
+ get sum() {
2029
+ return new SumBalancesFormatter(this);
2030
+ }
2031
+ }
2032
+ const getBalanceId = balance => {
2033
+ const {
2034
+ address,
2035
+ tokenId
2036
+ } = balance;
2037
+ return [address, tokenId].join("::");
2260
2038
  };
2261
2039
 
2262
- const getTransferCallData$6 = ({
2263
- from,
2264
- to,
2265
- value,
2266
- token
2267
- }) => {
2268
- if (!chaindataProvider.isTokenOfType(token, MODULE_TYPE$6)) throw new Error(`Token type ${token.type} is not ${MODULE_TYPE$6}.`);
2269
- if (!util.isEthereumAddress(from)) throw new Error("Invalid from address");
2270
- if (!util.isEthereumAddress(to)) throw new Error("Invalid to address");
2271
- const data = viem.encodeFunctionData({
2272
- abi: viem.erc20Abi,
2273
- functionName: "transfer",
2274
- args: [to, BigInt(value)]
2275
- });
2276
- return {
2277
- from,
2278
- to: token.contractAddress,
2279
- data
2040
+ /**
2041
+ * An individual balance.
2042
+ */
2043
+ class Balance {
2044
+ //
2045
+ // Properties
2046
+ //
2047
+
2048
+ /** The underlying data for this balance */
2049
+ #storage;
2050
+ #valueGetter;
2051
+ #db = null;
2052
+
2053
+ //
2054
+ // Methods
2055
+ //
2056
+
2057
+ constructor(storage, hydrate) {
2058
+ this.#storage = storage;
2059
+ this.#valueGetter = new BalanceValueGetter(this.#storage);
2060
+ if (hydrate !== undefined) this.hydrate(hydrate);
2061
+ }
2062
+ toJSON = () => this.#storage;
2063
+ isSource = source => this.#storage.source === source;
2064
+ // // TODO: Fix this method, the types don't work with our plugin architecture.
2065
+ // // Specifically, the `BalanceJson` type is compiled down to `IBalance` in the following way:
2066
+ // //
2067
+ // // toJSON: () => BalanceJson // works
2068
+ // // isSource: (source: BalanceSource) => boolean // works
2069
+ // // asSource: <P extends string>(source: P) => NarrowBalanceType<IBalance, P> | null // Doesn't work! IBalance should just be BalanceJson!
2070
+ // //
2071
+ // // `IBalance` won't match the type of `BalanceSource` after `PluginBalanceTypes` has been extended by balance plugins.
2072
+ // // As a result, typescript will think that the returned #storage is not a BalanceJson.
2073
+ // asSource = <P extends BalanceSource>(source: P): NarrowBalanceType<BalanceJson, P> | null => {
2074
+ // if (this.#storage.source === source) return this.#storage as NarrowBalanceType<BalanceJson, P>
2075
+ // return null
2076
+ // }
2077
+
2078
+ hydrate = hydrate => {
2079
+ if (hydrate !== undefined) this.#db = hydrate;
2280
2080
  };
2281
- };
2081
+ #format = balance => new BalanceFormatter(util.isBigInt(balance) ? balance.toString() : balance, this.decimals || undefined, this.rates);
2282
2082
 
2283
- const SUBSCRIPTION_INTERVAL$2 = 6_000;
2284
- const subscribeBalances$6 = ({
2285
- networkId,
2286
- tokensWithAddresses,
2287
- connector
2288
- }) => {
2289
- if (!tokensWithAddresses.length) return rxjs.of({
2290
- success: [],
2291
- errors: []
2292
- });
2293
- return new rxjs.Observable(subscriber => {
2294
- const abortController = new AbortController();
2295
- const poll = async () => {
2296
- try {
2297
- if (abortController.signal.aborted) return;
2298
- const balances = await fetchBalances$6({
2299
- networkId,
2300
- tokensWithAddresses: tokensWithAddresses,
2301
- connector
2302
- });
2303
- if (abortController.signal.aborted) return;
2304
- subscriber.next(balances);
2305
- setTimeout(poll, SUBSCRIPTION_INTERVAL$2);
2306
- } catch (error) {
2307
- log.error("Error", {
2308
- module: MODULE_TYPE$6,
2309
- networkId,
2310
- addressesByToken: tokensWithAddresses,
2311
- error
2312
- });
2313
- subscriber.error(error);
2314
- }
2315
- };
2316
- poll();
2317
- return () => {
2318
- abortController.abort();
2319
- };
2320
- }).pipe(rxjs.distinctUntilChanged(lodash.isEqual));
2321
- };
2083
+ //
2084
+ // Accessors
2085
+ //
2086
+
2087
+ get id() {
2088
+ return getBalanceId(this.#storage);
2089
+ }
2090
+ get source() {
2091
+ return this.#storage.source;
2092
+ }
2093
+ get status() {
2094
+ return this.#storage.status;
2095
+ }
2096
+ get address() {
2097
+ return this.#storage.address;
2098
+ }
2099
+ get networkId() {
2100
+ return this.#storage.networkId;
2101
+ }
2102
+ get network() {
2103
+ return this.#db?.networks?.[this.networkId] || null;
2104
+ }
2105
+ get tokenId() {
2106
+ return this.#storage.tokenId;
2107
+ }
2108
+ get token() {
2109
+ return this.#db?.tokens?.[this.tokenId] || null;
2110
+ }
2111
+ get decimals() {
2112
+ return this.token?.decimals || null;
2113
+ }
2114
+ get rates() {
2115
+ // uniswap v2 lp tokens need the rates from the underlying pool assets
2116
+ //
2117
+ // To note: `@talismn/token-rates` knows to fetch the `coingeckoId0` and `coingeckoId1` rates for evm-uniswapv2 tokens.
2118
+ // They are then stored in `this.#db.tokenRates` using the `tokenId0` and `tokenId1` keys.
2119
+ //
2120
+ // This means that those rates are always available for calculating the uniswapv2 rates,
2121
+ // regardless of whether or not the underlying erc20s are actually in chaindata and enabled.
2122
+ if (this.isSource("evm-uniswapv2") && this.token?.type === "evm-uniswapv2") {
2123
+ const tokenId0 = chaindataProvider.evmErc20TokenId(this.networkId, this.token.tokenAddress0);
2124
+ const tokenId1 = chaindataProvider.evmErc20TokenId(this.networkId, this.token.tokenAddress1);
2125
+ const decimals = this.token.decimals;
2126
+ const decimals0 = this.token.decimals0;
2127
+ const decimals1 = this.token.decimals1;
2128
+ const rates0 = this.#db?.tokenRates && this.#db.tokenRates[tokenId0];
2129
+ const rates1 = this.#db?.tokenRates && this.#db.tokenRates[tokenId1];
2130
+ if (rates0 === undefined || rates1 === undefined) return null;
2131
+ const extra = this.#valueGetter.get("extra");
2132
+ const extras = Array.isArray(extra) ? extra : extra !== undefined ? [extra] : [];
2133
+ const totalSupply = extras.find(extra => extra.label === "totalSupply")?.amount ?? "0";
2134
+ const reserve0 = extras.find(extra => extra.label === "reserve0")?.amount ?? "0";
2135
+ const reserve1 = extras.find(extra => extra.label === "reserve1")?.amount ?? "0";
2136
+ const totalSupplyTokens = BigNumber__default.default(totalSupply).times(Math.pow(10, -1 * decimals));
2137
+ const reserve0Tokens = BigNumber__default.default(reserve0).times(Math.pow(10, -1 * decimals0));
2138
+ const reserve1Tokens = BigNumber__default.default(reserve1).times(Math.pow(10, -1 * decimals1));
2139
+ const rates0Currencies = new Set(Object.keys(rates0));
2140
+ const rates1Currencies = new Set(Object.keys(rates1));
2141
+ // `Set.prototype.intersection` can eventually replace this
2142
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/intersection
2143
+ const currencies = [...rates0Currencies].filter(c => rates1Currencies.has(c));
2144
+ const totalValueLocked = currencies.map(currency => [currency,
2145
+ // tvl (in a given currency) == reserve0*currencyRate0 + reserve1*currencyRate1
2146
+ BigNumber__default.default.sum(reserve0Tokens.times(rates0[currency]?.price ?? 0), reserve1Tokens.times(rates1[currency]?.price ?? 0))]);
2147
+ const lpTokenRates = tokenRates.newTokenRates();
2148
+ totalValueLocked.forEach(([currency, tvl]) => {
2149
+ // divide `the value of all lp tokens` by `the number of lp tokens` to get `the value per token`
2150
+ if (!totalSupplyTokens.eq(0)) lpTokenRates[currency] = {
2151
+ price: tvl.div(totalSupplyTokens).toNumber()
2152
+ };
2153
+ });
2154
+ return lpTokenRates;
2155
+ }
2322
2156
 
2323
- const EvmUniswapV2BalanceModule = {
2324
- type: MODULE_TYPE$6,
2325
- platform: PLATFORM$6,
2326
- getMiniMetadata: getMiniMetadata$6,
2327
- fetchTokens: fetchTokens$6,
2328
- fetchBalances: fetchBalances$6,
2329
- subscribeBalances: subscribeBalances$6,
2330
- getTransferCallData: getTransferCallData$6
2331
- };
2157
+ // other tokens can just pick from the tokenRates db using the tokenId
2158
+ return this.#db?.tokenRates && this.#db.tokenRates[this.tokenId] || null;
2159
+ }
2332
2160
 
2333
- // to be used by chaindata too
2334
- const EvmUniswapV2TokenConfigSchema = z__default.default.strictObject({
2335
- contractAddress: chaindataProvider.EvmUniswapV2TokenSchema.shape.contractAddress,
2336
- ...TokenConfigBaseSchema.shape
2337
- });
2161
+ /**
2162
+ * A general method to get formatted values matching a certain type from this balance.
2163
+ * @param valueType - The type of value to get.
2164
+ * @returns An array of the values matching the type with formatted amounts.
2165
+ */
2166
+ getValue(valueType) {
2167
+ return this.getRawValue(valueType).map(value => ({
2168
+ ...value,
2169
+ amount: this.#format(value.amount)
2170
+ }));
2171
+ }
2338
2172
 
2339
- const MODULE_TYPE$5 = chaindataProvider.SubAssetsTokenSchema.shape.type.value;
2340
- const PLATFORM$5 = chaindataProvider.SubAssetsTokenSchema.shape.platform.value;
2173
+ /**
2174
+ * A general method to get values matching a certain type from this balance.
2175
+ * @param valueType - The type of value to get.
2176
+ * @returns An array of the values matching the type.
2177
+ */
2178
+ getRawValue(valueType) {
2179
+ return this.#valueGetter.get(valueType);
2180
+ }
2341
2181
 
2342
- const fetchRpcQueryPack = async (connector, networkId, queries) => {
2343
- const allStateKeys = queries.flatMap(({
2344
- stateKeys
2345
- }) => stateKeys).filter(util.isNotNil);
2182
+ /**
2183
+ * A general method to add a value to the array of values for this balance.
2184
+ * @param valueType - The type of value to add.
2185
+ * @returns A function which can be used to add a value to the array of values for this balance.
2186
+ */
2187
+ addValue(valueType) {
2188
+ return value => this.#valueGetter.add(valueType, value);
2189
+ }
2190
+ /**
2191
+ * The total balance of this token.
2192
+ * Includes the free and the reserved amount.
2193
+ * The balance will be reaped if this goes below the existential deposit.
2194
+ */
2195
+ get total() {
2196
+ const extra = this.getValue("extra");
2346
2197
 
2347
- // doing a query without keys would throw an error => return early
2348
- if (!allStateKeys.length) return queries.map(({
2349
- stateKeys,
2350
- decodeResult
2351
- }) => decodeResult(stateKeys.map(() => null)));
2352
- const [result] = await connector.send(networkId, "state_queryStorageAt", [allStateKeys]);
2353
- return decodeRpcQueryPack(queries, result);
2354
- };
2355
- const getRpcQueryPack$ = (connector, networkId, queries, timeout = false) => {
2356
- const allStateKeys = queries.flatMap(({
2357
- stateKeys
2358
- }) => stateKeys).filter(util.isNotNil);
2198
+ // if there is a DelegatedStaking hold (new model: polkadot, kusama), nom pool staked amount is included in reserved
2199
+ // if not (old model: vara, avail, cere), staked amount is not in the account and it needs to be added to the total
2200
+ const nomPoolStakedPlancks = this.locks.some(lock => lock.source === "substrate-native-holds" && lock.label === "DelegatedStaking") ? 0n : this.nompools.map(({
2201
+ amount
2202
+ }) => amount.planck).reduce((a, b) => a + b, 0n);
2203
+ return this.#format(this.free.planck + this.reserved.planck + nomPoolStakedPlancks + this.subtensor.map(({
2204
+ amount
2205
+ }) => amount.planck).reduce((a, b) => a + b, 0n) + includeInTotalExtraAmount(extra));
2206
+ }
2207
+ /** The non-reserved balance of this token. Includes the frozen amount. Is included in the total. */
2208
+ get free() {
2209
+ // for simple balances
2210
+ if ("value" in this.#storage && this.#storage.value) return this.#format(this.#storage.value);
2359
2211
 
2360
- // doing a query without keys would throw an error => return early
2361
- if (!allStateKeys.length) return rxjs.of(queries.map(({
2362
- stateKeys,
2363
- decodeResult
2364
- }) => decodeResult(stateKeys.map(() => null))));
2365
- return new rxjs.Observable(subscriber => {
2366
- const promUnsub = connector.subscribe(networkId, "state_subscribeStorage", "state_storage", [allStateKeys], (error, result) => {
2367
- if (error) subscriber.error(error);else subscriber.next(decodeRpcQueryPack(queries, result));
2368
- }, timeout);
2369
- return () => {
2370
- promUnsub.then(unsub => unsub("state_unsubscribeStorage"));
2371
- };
2372
- });
2373
- };
2374
- const decodeRpcQueryPack = (queries, result) => {
2375
- return queries.reduce((acc, {
2376
- stateKeys,
2377
- decodeResult
2378
- }) => {
2379
- const changes = stateKeys.map(stateKey => {
2380
- if (!stateKey) return null;
2381
- const change = result.changes.find(([key]) => key === stateKey);
2382
- if (!change) return null;
2383
- return change[1];
2384
- });
2385
- acc.push(decodeResult(changes));
2386
- return acc;
2387
- }, []);
2388
- };
2212
+ // for complex balances
2213
+ const freeValues = this.getValue("free");
2214
+ const totalFree = freeValues.map(({
2215
+ amount
2216
+ }) => amount.planck).reduce((a, b) => a + b, 0n);
2217
+ return this.#format(totalFree);
2218
+ }
2219
+ /** The reserved balance of this token. Is included in the total. */
2220
+ get reserved() {
2221
+ const reservedValues = this.getValue("reserved");
2222
+ if (reservedValues.length === 0) return this.#format(0n);
2223
+ return this.#format(reservedValues.map(({
2224
+ amount
2225
+ }) => amount.planck).reduce((a, b) => a + b, 0n));
2226
+ }
2227
+ get reserves() {
2228
+ return this.getValue("reserved");
2229
+ }
2230
+ /** The frozen balance of this token. Is included in the free amount. */
2231
+ get locked() {
2232
+ return this.#format(this.locks.map(({
2233
+ amount
2234
+ }) => amount.planck).reduce((a, b) => util.BigMath.max(a, b), 0n));
2235
+ }
2236
+ get locks() {
2237
+ return this.getValue("locked");
2238
+ }
2239
+ get nompools() {
2240
+ return this.getValue("nompool");
2241
+ }
2242
+ get subtensor() {
2243
+ return this.getValue("subtensor");
2244
+ }
2389
2245
 
2390
- const fetchRuntimeCallResult = async (connector, networkId, metadataRpc, apiName, method, args) => {
2391
- const {
2392
- builder
2393
- } = scale.parseMetadataRpc(metadataRpc);
2394
- const call = builder.buildRuntimeCall(apiName, method);
2395
- const hex = await connector.send(networkId, "state_call", [`${apiName}_${method}`, scale.toHex(call.args.enc(args))]);
2396
- return call.value.dec(hex);
2397
- };
2246
+ /** The extra balance of this token */
2247
+ get extra() {
2248
+ const extra = this.getRawValue("extra");
2249
+ if (extra.length > 0) return extra;
2250
+ return undefined;
2251
+ }
2398
2252
 
2399
- const getConstantValue = (metadataRpc, pallet, constant) => {
2400
- const {
2401
- unifiedMetadata,
2402
- builder
2403
- } = scale.parseMetadataRpc(metadataRpc);
2404
- const codec = builder.buildConstant(pallet, constant);
2405
- const encodedValue = unifiedMetadata.pallets.find(({
2406
- name
2407
- }) => name === pallet)?.constants.find(({
2408
- name
2409
- }) => name === constant)?.value;
2410
- if (!encodedValue) throw new Error(`Constant ${pallet}.${constant} not found`);
2411
- return codec.dec(encodedValue);
2412
- };
2253
+ /** @deprecated Use balance.locked */
2254
+ get frozen() {
2255
+ return this.locked;
2256
+ }
2257
+ /** The transferable balance of this token. Is generally the free amount - the miscFrozen amount. */
2258
+ get transferable() {
2259
+ /**
2260
+ * As you can see here, `locked` is subtracted from `free` in order to derive `transferable`.
2261
+ *
2262
+ * |--------------------------total--------------------------|
2263
+ * |-------------------free-------------------|---reserved---|
2264
+ * |----locked-----|-------transferable-------|
2265
+ */
2266
+ const oldTransferableCalculation = () => {
2267
+ // if no locks exist, transferable is equal to the free amount
2268
+ if (this.locks.length === 0) return this.free;
2413
2269
 
2414
- const tryGetConstantValue = (metadataRpc, pallet, constant) => {
2415
- const {
2416
- unifiedMetadata,
2417
- builder
2418
- } = scale.parseMetadataRpc(metadataRpc);
2419
- const encodedValue = unifiedMetadata.pallets.find(({
2420
- name
2421
- }) => name === pallet)?.constants.find(({
2422
- name
2423
- }) => name === constant)?.value;
2424
- if (!encodedValue) return null;
2425
- const codec = builder.buildConstant(pallet, constant);
2426
- return codec.dec(encodedValue);
2427
- };
2270
+ // find the largest lock (but ignore any locks which are marked as `includeInTransferable`)
2271
+ const excludeAmount = excludeFromTransferableAmount(this.locks);
2428
2272
 
2429
- const hasStorageItem = (metadata, palletName, itemName) => {
2430
- const pallet = metadata.pallets.find(p => p.name === palletName);
2431
- if (!pallet || !pallet.storage) return false;
2432
- return pallet.storage.items.some(item => item.name === itemName);
2433
- };
2434
- const hasStorageItems = (metadata, palletName, itemNames) => {
2435
- const pallet = metadata.pallets.find(p => p.name === palletName);
2436
- if (!pallet || !pallet.storage) return false;
2437
- return itemNames.every(itemName => pallet.storage?.items.some(item => item.name === itemName));
2438
- };
2439
- const hasRuntimeApi = (metadata, apiName, method) => {
2440
- const api = metadata.apis.find(api => api.name === apiName);
2441
- if (!api || !api.methods) return false;
2442
- return api.methods.some(m => m.name === method);
2443
- };
2273
+ // subtract the lock from the free amount (but don't go below 0)
2274
+ return this.#format(util.BigMath.max(this.free.planck - excludeAmount, 0n));
2275
+ };
2276
+
2277
+ /**
2278
+ * As you can see here, `locked` is subtracted from `free + reserved` in order to derive `transferable`.
2279
+ *
2280
+ * Alternatively, `reserved` is subtracted from `locked` in order to derive `untouchable`,
2281
+ * which is then subtracted from `free` in order to derive `transferable`.
2282
+ *
2283
+ * |--------------------------total--------------------------|
2284
+ * |---reserved---|-------------------free-------------------|
2285
+ * |--untouchable--|
2286
+ * |------------locked------------|-------transferable-------|
2287
+ */
2288
+ const newTransferableCalculation = () => {
2289
+ // if no locks exist, transferable is equal to the free amount
2290
+ if (this.locks.length === 0) return this.free;
2291
+
2292
+ // find the largest lock (but ignore any locks which are marked as `includeInTransferable`)
2293
+ // subtract the reserved amount, because locks now act upon the total balance - not just the free balance
2294
+ const untouchableAmount = util.BigMath.max(excludeFromTransferableAmount(this.locks) - this.reserved.planck, 0n);
2295
+
2296
+ // subtract the untouchable amount from the free amount (but don't go below 0)
2297
+ return this.#format(util.BigMath.max(this.free.planck - untouchableAmount, 0n));
2298
+ };
2299
+ if (this.#storage.useLegacyTransferableCalculation) return oldTransferableCalculation();
2300
+ return newTransferableCalculation();
2301
+ }
2302
+ /**
2303
+ * The unavailable balance of this token.
2304
+ * Prior to the Fungible trait, this was the locked amount + the reserved amount, i.e. `locked + reserved`.
2305
+ * Now, it is the bigger of the locked amount and the reserved amounts, i.e. `max(locked, reserved)`.
2306
+ */
2307
+ get unavailable() {
2308
+ const oldCalculation = () => this.locked.planck + this.reserved.planck;
2309
+ const newCalculation = () => util.BigMath.max(this.locked.planck, this.reserved.planck);
2310
+ const baseUnavailable = this.#storage.useLegacyTransferableCalculation ? oldCalculation() : newCalculation();
2444
2311
 
2445
- const buildNetworkStorageCoders = (chainId, miniMetadata, coders) => {
2446
- if (!miniMetadata.data) return null;
2447
- const metadata = scale.unifyMetadata(scale.decAnyMetadata(miniMetadata.data));
2448
- try {
2449
- const scaleBuilder = scale.getDynamicBuilder(scale.getLookupFn(metadata));
2450
- const builtCoders = Object.fromEntries(Object.entries(coders).flatMap(([key, moduleMethodOrFn]) => {
2451
- const [module, method] = typeof moduleMethodOrFn === "function" ? moduleMethodOrFn({
2452
- chainId
2453
- }) : moduleMethodOrFn;
2454
- try {
2455
- return [[key, scaleBuilder.buildStorage(module, method)]];
2456
- } catch (cause) {
2457
- log.trace(`Failed to build SCALE coder for chain ${chainId} (${module}::${method})`, cause);
2458
- return [];
2459
- }
2460
- }));
2461
- return builtCoders;
2462
- } catch (cause) {
2463
- log.error(`Failed to build SCALE coders for chain ${chainId} (${JSON.stringify(coders)})`, cause);
2312
+ // if there is a DelegatedStaking hold (new model: polkadot, kusama), nom pool staked amount is included in reserved
2313
+ // if not (old model: vara, avail, cere), staked amount is not in the account and it needs to be added to the total
2314
+ const nomPoolStakedPlancks = this.locks.some(lock => lock.source === "substrate-native-holds" && lock.label === "DelegatedStaking") ? 0n : this.nompools.map(({
2315
+ amount
2316
+ }) => amount.planck).reduce((a, b) => a + b, 0n);
2317
+ const otherUnavailable = nomPoolStakedPlancks + this.subtensor.reduce((total, each) => total + each.amount.planck, 0n);
2318
+ return this.#format(baseUnavailable + otherUnavailable);
2464
2319
  }
2465
- return null;
2466
- };
2467
2320
 
2468
- const buildQueries$2 = (networkId, balanceDefs, miniMetadata) => {
2469
- const networkStorageCoders = buildNetworkStorageCoders(networkId, miniMetadata, {
2470
- storage: ["Assets", "Account"]
2471
- });
2472
- return balanceDefs.map(({
2473
- token,
2474
- address
2475
- }) => {
2476
- const scaleCoder = networkStorageCoders?.storage;
2477
- const stateKey = tryEncode(scaleCoder, Number(token.assetId), address) ??
2478
- // Asset Hub
2479
- tryEncode(scaleCoder, BigInt(token.assetId), address); // Astar
2321
+ /** The feePayable balance of this token. Is generally the free amount - the feeFrozen amount. */
2322
+ get feePayable() {
2323
+ // if no locks exist, feePayable is equal to the free amount
2324
+ if (this.locks.length === 0) return this.free;
2480
2325
 
2481
- if (!stateKey) {
2482
- log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
2483
- return null;
2484
- }
2485
- const decodeResult = changes => {
2486
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
2326
+ // find the largest lock which can't be used to pay tx fees
2327
+ const excludeAmount = excludeFromFeePayableLocks(this.locked.planck.toString()).map(lock => BigInt(lock.amount)).reduce((max, lock) => util.BigMath.max(max, lock), 0n);
2487
2328
 
2488
- const decoded = scale.decodeScale(scaleCoder, changes[0], `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
2489
- balance: 0n,
2490
- status: {
2491
- type: "Liquid"
2492
- }};
2493
- const isFrozen = decoded?.status?.type === "Frozen";
2494
- const amount = (decoded?.balance ?? 0n).toString();
2329
+ // subtract the lock from the free amount (but don't go below 0)
2330
+ return this.#format(util.BigMath.max(this.free.planck - excludeAmount, 0n));
2331
+ }
2332
+ }
2333
+ class BalanceValueGetter {
2334
+ #storage;
2335
+ constructor(storage) {
2336
+ this.#storage = storage;
2337
+ }
2338
+ get(valueType) {
2339
+ if ("values" in this.#storage && this.#storage.values) return this.#storage.values.filter(({
2340
+ type
2341
+ }) => type === valueType);
2342
+ return [];
2343
+ }
2344
+ add(valueType, amount) {
2345
+ if ("values" in this.#storage && this.#storage.values) this.#storage.values.push({
2346
+ type: valueType,
2347
+ ...amount
2348
+ });
2349
+ }
2350
+ }
2351
+ class BalanceFormatter {
2352
+ #planck;
2353
+ #decimals;
2354
+ #tokenRates;
2355
+ constructor(planck, decimals, fiatRatios) {
2356
+ this.#planck = util.isBigInt(planck) ? planck.toString() : planck ?? "0";
2357
+ this.#decimals = decimals || 0;
2358
+ this.#tokenRates = fiatRatios || null;
2359
+ }
2360
+ toJSON = () => this.#planck;
2361
+ get planck() {
2362
+ return BigInt(this.#planck);
2363
+ }
2364
+ get tokens() {
2365
+ return util.planckToTokens(this.#planck, this.#decimals);
2366
+ }
2367
+ fiat(currency) {
2368
+ if (!this.#tokenRates) return null;
2369
+ const ratio = this.#tokenRates[currency];
2370
+ if (!ratio) return null;
2371
+ return parseFloat(this.tokens) * ratio.price;
2372
+ }
2373
+ }
2374
+ class PlanckSumBalancesFormatter {
2375
+ #balances;
2376
+ constructor(balances) {
2377
+ this.#balances = balances;
2378
+ }
2379
+ #sum = balanceField => {
2380
+ // a function to get a planck amount from a balance
2381
+ const planck = balance => balance[balanceField].planck ?? 0n;
2382
+ return this.#balances.filterMirrorTokens().each.reduce(
2383
+ // add the total amount to the planck amount of each balance
2384
+ (total, balance) => total + planck(balance),
2385
+ // start with a total of 0
2386
+ 0n);
2387
+ };
2495
2388
 
2496
- // due to the following balance calculations, which are made in the `Balance` type:
2497
- //
2498
- // total balance = (free balance) + (reserved balance)
2499
- // transferable balance = (free balance) - (frozen balance)
2500
- //
2501
- // when `isFrozen` is true we need to set **both** the `free` and `frozen` amounts
2502
- // of this balance to the value we received from the RPC.
2503
- //
2504
- // if we only set the `frozen` amount, then the `total` calculation will be incorrect!
2505
- const free = amount;
2506
- const frozen = token.isFrozen || isFrozen ? amount : "0";
2389
+ /**
2390
+ * The total balance of these tokens. Includes the free and the reserved amount.
2391
+ */
2392
+ get total() {
2393
+ return this.#sum("total");
2394
+ }
2395
+ /** The non-reserved balance of these tokens. Includes the frozen amount. Is included in the total. */
2396
+ get free() {
2397
+ return this.#sum("free");
2398
+ }
2399
+ /** The reserved balance of these tokens. Is included in the total. */
2400
+ get reserved() {
2401
+ return this.#sum("reserved");
2402
+ }
2403
+ /** The frozen balance of these tokens. Is included in the free amount. */
2404
+ get locked() {
2405
+ return this.#sum("locked");
2406
+ }
2407
+ /** @deprecated Use balances.locked */
2408
+ get frozen() {
2409
+ return this.locked;
2410
+ }
2411
+ /** The transferable balance of these tokens. Is generally the free amount - the miscFrozen amount. */
2412
+ get transferable() {
2413
+ return this.#sum("transferable");
2414
+ }
2415
+ /** The unavailable balance of these tokens. */
2416
+ get unavailable() {
2417
+ return this.#sum("unavailable");
2418
+ }
2507
2419
 
2508
- // include balance values even if zero, so that newly-zero values overwrite old values
2509
- const balanceValues = [{
2510
- type: "free",
2511
- label: "free",
2512
- amount: free.toString()
2513
- }, {
2514
- type: "locked",
2515
- label: "frozen",
2516
- amount: frozen.toString()
2517
- }];
2518
- const balance = {
2519
- source: "substrate-assets",
2520
- status: "live",
2521
- address,
2522
- networkId,
2523
- tokenId: token.id,
2524
- values: balanceValues
2420
+ /** The feePayable balance of these tokens. Is generally the free amount - the feeFrozen amount. */
2421
+ get feePayable() {
2422
+ return this.#sum("feePayable");
2423
+ }
2424
+ }
2425
+ class FiatSumBalancesFormatter {
2426
+ #balances;
2427
+ #currency;
2428
+ constructor(balances, currency) {
2429
+ this.#balances = balances;
2430
+ this.#currency = currency;
2431
+ }
2432
+ #sum = balanceField => {
2433
+ // a function to get a fiat amount from a balance
2434
+ const fiat = balance => balance[balanceField].fiat(this.#currency) ?? 0;
2435
+ return this.#balances.filterMirrorTokens().each.reduce(
2436
+ // add the total amount to the fiat amount of each balance
2437
+ (total, balance) => total + fiat(balance),
2438
+ // start with a total of 0
2439
+ 0);
2440
+ };
2441
+
2442
+ /**
2443
+ * The total balance of these tokens. Includes the free and the reserved amount.
2444
+ */
2445
+ get total() {
2446
+ return this.#sum("total");
2447
+ }
2448
+ /** The non-reserved balance of these tokens. Includes the frozen amount. Is included in the total. */
2449
+ get free() {
2450
+ return this.#sum("free");
2451
+ }
2452
+ /** The reserved balance of these tokens. Is included in the total. */
2453
+ get reserved() {
2454
+ return this.#sum("reserved");
2455
+ }
2456
+ /** The frozen balance of these tokens. Is included in the free amount. */
2457
+ get locked() {
2458
+ return this.#sum("locked");
2459
+ }
2460
+ /** @deprecated Use balances.locked */
2461
+ get frozen() {
2462
+ return this.locked;
2463
+ }
2464
+ /** The transferable balance of these tokens. Is generally the free amount - the miscFrozen amount. */
2465
+ get transferable() {
2466
+ return this.#sum("transferable");
2467
+ }
2468
+ /** The unavailable balance of these tokens. */
2469
+ get unavailable() {
2470
+ return this.#sum("unavailable");
2471
+ }
2472
+ /** The feePayable balance of these tokens. Is generally the free amount - the feeFrozen amount. */
2473
+ get feePayable() {
2474
+ return this.#sum("feePayable");
2475
+ }
2476
+ }
2477
+ class SumBalancesFormatter {
2478
+ #balances;
2479
+ constructor(balances) {
2480
+ this.#balances = balances;
2481
+ }
2482
+ get planck() {
2483
+ return new PlanckSumBalancesFormatter(this.#balances);
2484
+ }
2485
+ fiat(currency) {
2486
+ return new FiatSumBalancesFormatter(this.#balances, currency);
2487
+ }
2488
+ change24h(currency) {
2489
+ return new Change24hCurrencyFormatter(this.#balances, currency);
2490
+ }
2491
+ }
2492
+ class Change24hCurrencyFormatter {
2493
+ #balances;
2494
+ #currency;
2495
+ constructor(balances, currency) {
2496
+ this.#balances = balances;
2497
+ this.#currency = currency;
2498
+ }
2499
+ #change24h = balanceField => {
2500
+ const output = this.#balances.filterMirrorTokens().each.reduce(
2501
+ // add the total amount to the fiat amount of each balance
2502
+ (acc, balance) => {
2503
+ const change24h = balance.rates?.[this.#currency]?.change24h;
2504
+ if (typeof change24h !== "number") return acc;
2505
+ const fiat = balance[balanceField].fiat(this.#currency);
2506
+ if (!fiat) return acc;
2507
+ return {
2508
+ totalFiatDiff: acc.totalFiatDiff + fiat * change24h,
2509
+ totalFiat: acc.totalFiat + fiat
2525
2510
  };
2526
- return balance;
2527
- };
2528
- return {
2529
- stateKeys: [stateKey],
2530
- decodeResult
2511
+ },
2512
+ // start with a total of 0
2513
+ {
2514
+ totalFiatDiff: 0,
2515
+ totalFiat: 0
2516
+ });
2517
+ return output.totalFiat === 0 ? null : {
2518
+ diff: output.totalFiatDiff / 100,
2519
+ ratio: output.totalFiatDiff / output.totalFiat
2531
2520
  };
2532
- }).filter(util.isNotNil);
2533
- };
2534
- const tryEncode = (scaleCoder, ...args) => {
2535
- try {
2536
- return scaleCoder?.keys?.enc?.(...args);
2537
- } catch {
2538
- return null;
2539
- }
2540
- };
2541
-
2542
- const fetchBalances$5 = async ({
2543
- networkId,
2544
- tokensWithAddresses,
2545
- connector,
2546
- miniMetadata
2547
- }) => {
2548
- if (!tokensWithAddresses.length) return {
2549
- success: [],
2550
- errors: []
2551
2521
  };
2552
- const balanceDefs = getBalanceDefs(tokensWithAddresses);
2553
- if (!miniMetadata?.data) {
2554
- log.warn(`MiniMetadata is required for fetching ${MODULE_TYPE$5} balances on ${networkId}.`);
2555
- return {
2556
- success: [],
2557
- errors: balanceDefs.map(def => ({
2558
- tokenId: def.token.id,
2559
- address: def.address,
2560
- error: new Error("Minimetadata is required for fetching balances")
2561
- }))
2562
- };
2522
+ get total() {
2523
+ return this.#change24h("total");
2563
2524
  }
2564
- if (miniMetadata.source !== MODULE_TYPE$5) {
2565
- log.warn(`Ignoring miniMetadata with source ${miniMetadata.source} in ${MODULE_TYPE$5}.`);
2566
- return {
2567
- success: [],
2568
- errors: balanceDefs.map(def => ({
2569
- tokenId: def.token.id,
2570
- address: def.address,
2571
- error: new Error(`Invalid request: miniMetadata source is not ${MODULE_TYPE$5}`)
2572
- }))
2573
- };
2525
+ get free() {
2526
+ return this.#change24h("free");
2574
2527
  }
2575
- if (miniMetadata.chainId !== networkId) {
2576
- log.warn(`Ignoring miniMetadata with chainId ${miniMetadata.chainId} in ${MODULE_TYPE$5}. Expected chainId is ${networkId}`);
2577
- return {
2578
- success: [],
2579
- errors: balanceDefs.map(def => ({
2580
- tokenId: def.token.id,
2581
- address: def.address,
2582
- error: new Error(`Invalid request: Expected chainId is ${networkId}`)
2583
- }))
2584
- };
2528
+ get reserved() {
2529
+ return this.#change24h("reserved");
2585
2530
  }
2586
- const queries = buildQueries$2(networkId, balanceDefs, miniMetadata);
2587
- const balances = await fetchRpcQueryPack(connector, networkId, queries);
2588
- return balanceDefs.reduce((acc, def) => {
2589
- const balance = balances.find(b => b?.address === def.address && b?.tokenId === def.token.id);
2590
- if (balance) acc.success.push(balance);
2591
- //if no entry consider empty balance
2592
- else acc.success.push({
2593
- address: def.address,
2594
- networkId,
2595
- tokenId: def.token.id,
2596
- source: MODULE_TYPE$5,
2597
- status: "live",
2598
- values: [{
2599
- type: "free",
2600
- label: "free",
2601
- amount: "0"
2602
- }, {
2603
- type: "locked",
2604
- label: "frozen",
2605
- amount: "0"
2606
- }]
2607
- });
2608
- return acc;
2609
- }, {
2610
- success: [],
2611
- errors: []
2612
- });
2531
+ get locked() {
2532
+ return this.#change24h("locked");
2533
+ }
2534
+ get frozen() {
2535
+ return this.#change24h("frozen");
2536
+ }
2537
+ get transferable() {
2538
+ return this.#change24h("transferable");
2539
+ }
2540
+ get unavailable() {
2541
+ return this.#change24h("unavailable");
2542
+ }
2543
+ get feePayable() {
2544
+ return this.#change24h("feePayable");
2545
+ }
2546
+ }
2547
+ const filterMirrorTokens = (balance, i, balances) => {
2548
+ const mirrorOf = balance.token?.mirrorOf;
2549
+ return !mirrorOf || !balances.find(b => b.tokenId === mirrorOf);
2613
2550
  };
2614
2551
 
2615
- const fetchTokens$5 = async ({
2616
- networkId,
2617
- tokens,
2618
- connector,
2619
- miniMetadata
2620
- }) => {
2621
- const anyMiniMetadata = miniMetadata;
2622
- if (!anyMiniMetadata?.data) return [];
2623
- const {
2624
- builder
2625
- } = scale.parseMetadataRpc(anyMiniMetadata.data);
2626
- const assetCodec = builder.buildStorage("Assets", "Asset");
2627
- const metadataCodec = builder.buildStorage("Assets", "Metadata");
2628
- const [allAssetStorageKeys, allMetadataStorageKeys] = await Promise.all([connector.send(networkId, "state_getKeys", [scale.getStorageKeyPrefix("Assets", "Asset")]), connector.send(networkId, "state_getKeys", [scale.getStorageKeyPrefix("Assets", "Metadata")])]);
2629
- const [assetStorageResults, metadataStorageResults] = await Promise.all([connector.send(networkId, "state_queryStorageAt", [allAssetStorageKeys]), connector.send(networkId, "state_queryStorageAt", [allMetadataStorageKeys])]);
2630
- const assetStorageEntries = assetStorageResults[0].changes;
2631
- const metadataStorageEntries = metadataStorageResults[0].changes;
2632
- const assetByAssetId = lodash.keyBy(assetStorageEntries.map(([key, value]) => {
2633
- const [assetId] = assetCodec.keys.dec(key);
2634
- const asset = assetCodec.value.dec(value);
2635
- return {
2636
- assetId,
2637
- existentialDeposit: asset.min_balance,
2638
- isSufficient: asset.is_sufficient
2639
- };
2640
- }), a => a.assetId);
2641
- const metadataByAssetId = lodash.keyBy(metadataStorageEntries.map(([key, value]) => {
2642
- const [assetId] = metadataCodec.keys.dec(key);
2643
- const metadata = metadataCodec.value.dec(value);
2644
- return {
2645
- assetId,
2646
- decimals: metadata.decimals,
2647
- isFrozen: metadata.is_frozen,
2648
- name: metadata.name?.asText(),
2649
- symbol: metadata.symbol?.asText()
2650
- };
2651
- }), a => a.assetId);
2652
- const allTokens = lodash.keys(assetByAssetId).map(assetId => lodash.assign({}, assetByAssetId[assetId], metadataByAssetId[assetId] ?? undefined));
2653
- const configTokenByAssetId = lodash.keyBy(tokens, t => t.assetId);
2654
- return allTokens.map(asset => ({
2655
- id: chaindataProvider.subAssetTokenId(networkId, String(asset.assetId)),
2656
- type: MODULE_TYPE$5,
2657
- platform: "polkadot",
2658
- networkId,
2659
- assetId: String(asset.assetId),
2660
- isSufficient: asset.isSufficient,
2661
- isFrozen: asset.isFrozen,
2662
- name: asset.name,
2663
- symbol: asset.symbol,
2664
- decimals: asset.decimals ?? 0,
2665
- existentialDeposit: String(asset.existentialDeposit),
2666
- isDefault: true
2667
- }))
2668
- // keep all tokens listed in the config + all tokens marked as sufficient
2669
- .filter(token => {
2670
- const configToken = configTokenByAssetId[token.assetId];
2671
- return configToken || token.isSufficient;
2672
- })
2673
- // apply config overrides
2674
- .map(token => {
2675
- const configToken = configTokenByAssetId[token.assetId];
2676
- return configToken ? lodash.assign({}, token, configToken) : token;
2677
- })
2678
- // validate results
2679
- .filter(t => {
2680
- const parsed = chaindataProvider.SubAssetsTokenSchema.safeParse(t);
2681
- if (!parsed.success) log.warn(`Ignoring invalid token ${MODULE_TYPE$5}`, t);
2682
- return parsed.success;
2683
- });
2552
+ /** `IBalance` is a common interface which all balance types must implement. */
2553
+
2554
+ /** A collection of `BalanceJson` objects */
2555
+
2556
+ /** An unlabelled amount of a balance */
2557
+
2558
+ /** A labelled amount of a balance */
2559
+
2560
+ const getValueId = amount => {
2561
+ const getMetaId = () => {
2562
+ const meta = amount.meta;
2563
+ if (!meta) return "";
2564
+ if (amount.type === "nompool") return meta.poolId?.toString() ?? "";
2565
+ if (amount.type === "subtensor") {
2566
+ const {
2567
+ hotkey,
2568
+ netuid
2569
+ } = meta;
2570
+ if (hotkey && netuid !== undefined) return `${hotkey.toString()}${netuid.toString()}`;
2571
+ }
2572
+ return "";
2573
+ };
2574
+ return [amount.label, amount.type, amount.source, getMetaId()].join("::");
2684
2575
  };
2685
2576
 
2577
+ /** A labelled locked amount of a balance */
2578
+
2579
+ /** A labelled extra amount of a balance */
2580
+
2581
+ /** Used by plugins to help define their custom `BalanceType` */
2582
+
2583
+ /** For fast db access, you can calculate the primary key for a miniMetadata using this method */
2584
+ const deriveMiniMetadataId = ({
2585
+ source,
2586
+ chainId,
2587
+ specVersion
2588
+ }) => scale.toHex(scale.Twox64Concat(new TextEncoder().encode(`${source}${chainId}${specVersion}${chaindataProvider.MINIMETADATA_VERSION}`))).slice(-64);
2589
+
2686
2590
  const getMiniMetadata$5 = ({
2687
2591
  networkId,
2688
2592
  specVersion,
@@ -6494,7 +6398,7 @@ const POOL = new PQueue__default.default({
6494
6398
  });
6495
6399
  const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, specVersion, signal) => {
6496
6400
  if (CACHE.has(networkId)) return CACHE.get(networkId);
6497
- log.warn("[miniMetadata] getMiniMetadatas called without signal, this may hang the updates", new Error("No signal provided") // this will show the stack trace
6401
+ 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 so the culprit can fix it
6498
6402
  );
6499
6403
  if (specVersion === undefined) specVersion = await getSpecVersion(chainConnector, networkId);
6500
6404
  const pResult = POOL.add(() => fetchMiniMetadatas(chainConnector, chaindataProvider, networkId, specVersion), {
@@ -6563,24 +6467,31 @@ class BalancesProvider {
6563
6467
  // TODO move the getSharedObservable caching down to this.getNetworkBalances$ to prevent network-level subscriptions to restart when enabling/disabling other networks
6564
6468
  // this will require addressesByTokenId arg to be normalized/sorted so the cache key can be compared properly, seems a bit random atm
6565
6469
  return util.getSharedObservable("BalancesProvider.getBalances$", addressesByTokenId, () => {
6470
+ return this.cleanupAddressesByTokenId(addressesByTokenId).pipe(rxjs.map(
6566
6471
  // split by network
6567
- const addressesByTokenIdByNetworkId = lodash.toPairs(addressesByTokenId).reduce((acc, [tokenId, addresses]) => {
6472
+ addressesByTokenId => lodash.toPairs(addressesByTokenId).reduce((acc, [tokenId, addresses]) => {
6568
6473
  const networkId = chaindataProvider.parseTokenId(tokenId).networkId;
6569
6474
  if (!acc[networkId]) acc[networkId] = {};
6570
6475
  acc[networkId][tokenId] = addresses;
6571
6476
  return acc;
6572
- }, {});
6573
- return rxjs.combineLatest({
6477
+ }, {})), rxjs.switchMap(addressesByTokenIdByNetworkId =>
6478
+ // fetch balances and start a 30s timer to mark the whole subscription live after 30s
6479
+ rxjs.combineLatest({
6574
6480
  isStale: rxjs.timer(30_000).pipe(rxjs.map(() => true), rxjs.startWith(false)),
6575
6481
  results: rxjs.combineLatest(lodash.toPairs(addressesByTokenIdByNetworkId).map(([networkId]) => this.getNetworkBalances$(networkId, addressesByTokenIdByNetworkId[networkId])))
6576
- }).pipe(rxjs.map(({
6482
+ })), rxjs.map(
6483
+ // combine
6484
+ ({
6577
6485
  isStale,
6578
6486
  results
6579
6487
  }) => ({
6580
6488
  status: !isStale && results.some(({
6581
6489
  status
6582
6490
  }) => status === "initialising") ? "initialising" : "live",
6583
- balances: results.flatMap(result => result.balances).sort((a, b) => getBalanceId(a).localeCompare(getBalanceId(b)))
6491
+ balances: results.flatMap(result => result.balances.map(b => isStale && b.status !== "live" ? {
6492
+ ...b,
6493
+ status: "stale"
6494
+ } : b)).sort((a, b) => getBalanceId(a).localeCompare(getBalanceId(b)))
6584
6495
  })), rxjs.startWith({
6585
6496
  status: "initialising",
6586
6497
  balances: this.getStoredBalances(addressesByTokenId)
@@ -6622,7 +6533,12 @@ class BalancesProvider {
6622
6533
  const storage = this.#storage.getValue();
6623
6534
  const balances = lodash.assign({}, storage.balances,
6624
6535
  // delete all balances expected in the result set. because if they are not present it means they are empty.
6625
- lodash.fromPairs(balanceIds.map(balanceId => [balanceId, undefined])), lodash.keyBy(results.balances, b => getBalanceId(b)));
6536
+ lodash.fromPairs(balanceIds.map(balanceId => [balanceId, undefined])), lodash.keyBy(
6537
+ // storage balances must have status "cache", because they are used as start value when initialising subsequent subscriptions
6538
+ results.balances.map(b => ({
6539
+ ...b,
6540
+ status: "cache"
6541
+ })), b => getBalanceId(b)));
6626
6542
  this.#storage.next(lodash.assign({}, storage, {
6627
6543
  balances
6628
6544
  }));
@@ -6665,39 +6581,49 @@ class BalancesProvider {
6665
6581
  }) => status === "initialising") ? "initialising" : "live",
6666
6582
  balances: results.flatMap(result => result.balances)
6667
6583
  };
6584
+ }), rxjs.startWith({
6585
+ status: "initialising",
6586
+ balances: this.getStoredBalances(addressesByTokenId)
6668
6587
  }));
6669
6588
  }
6670
6589
  getNetworkMiniMetadatas$(networkId) {
6671
6590
  return this.#chaindataProvider.getNetworkById$(networkId).pipe(rxjs.switchMap(network => chaindataProvider.isNetworkDot(network) && this.#chainConnectors.substrate ? rxjs.from(getSpecVersion(this.#chainConnectors.substrate, networkId)).pipe(rxjs.switchMap(specVersion => this.getMiniMetadatas$(networkId, specVersion))) : rxjs.of([])));
6672
6591
  }
6673
6592
  getMiniMetadatas$(networkId, specVersion) {
6674
- return rxjs.combineLatest({
6675
- defaultMiniMetadatas: this.getDefaultMiniMetadatas$(networkId, specVersion),
6676
- storedMiniMetadatas: this.getStoredMiniMetadatas$(networkId, specVersion)
6677
- }).pipe(rxjs.switchMap(({
6678
- storedMiniMetadatas,
6679
- defaultMiniMetadatas
6680
- }) => {
6681
- if (defaultMiniMetadatas.length) return rxjs.of(defaultMiniMetadatas);
6682
- if (storedMiniMetadatas.length) return rxjs.of(storedMiniMetadatas);
6683
- if (!this.#chainConnectors.substrate) return rxjs.of([]);
6684
- return rxjs.from(
6685
- // fetch them from the chain
6686
- getMiniMetadatas(this.#chainConnectors.substrate, this.#chaindataProvider, networkId)).pipe(
6687
- // and persist in storage for later reuse
6688
- rxjs.tap(newMiniMetadatas => {
6689
- if (!newMiniMetadatas.length) return;
6690
- const storage = this.#storage.getValue();
6691
- const miniMetadatas = lodash.assign(
6692
- // keep minimetadatas of other networks
6693
- lodash.keyBy(lodash.values(storage.miniMetadatas).filter(m => m.chainId !== networkId), m => m.id),
6694
- // add the ones for our network
6695
- lodash.keyBy(newMiniMetadatas, m => m.id));
6696
- this.#storage.next(lodash.assign({}, storage, {
6697
- miniMetadatas
6593
+ return new rxjs.Observable(subscriber => {
6594
+ const controller = new AbortController();
6595
+ const subscription = rxjs.combineLatest({
6596
+ defaultMiniMetadatas: this.getDefaultMiniMetadatas$(networkId, specVersion),
6597
+ storedMiniMetadatas: this.getStoredMiniMetadatas$(networkId, specVersion)
6598
+ }).pipe(rxjs.switchMap(({
6599
+ storedMiniMetadatas,
6600
+ defaultMiniMetadatas
6601
+ }) => {
6602
+ if (defaultMiniMetadatas.length) return rxjs.of(defaultMiniMetadatas);
6603
+ if (storedMiniMetadatas.length) return rxjs.of(storedMiniMetadatas);
6604
+ if (!this.#chainConnectors.substrate) return rxjs.of([]);
6605
+ return rxjs.from(
6606
+ // fetch them from the chain
6607
+ getMiniMetadatas(this.#chainConnectors.substrate, this.#chaindataProvider, networkId, specVersion, controller.signal)).pipe(
6608
+ // and persist in storage for later reuse
6609
+ rxjs.tap(newMiniMetadatas => {
6610
+ if (!newMiniMetadatas.length) return;
6611
+ const storage = this.#storage.getValue();
6612
+ const miniMetadatas = lodash.assign(
6613
+ // keep minimetadatas of other networks
6614
+ lodash.keyBy(lodash.values(storage.miniMetadatas).filter(m => m.chainId !== networkId), m => m.id),
6615
+ // add the ones for our network
6616
+ lodash.keyBy(newMiniMetadatas, m => m.id));
6617
+ this.#storage.next(lodash.assign({}, storage, {
6618
+ miniMetadatas
6619
+ }));
6698
6620
  }));
6699
- }));
6700
- }));
6621
+ })).subscribe(subscriber);
6622
+ return () => {
6623
+ subscription.unsubscribe();
6624
+ controller.abort();
6625
+ };
6626
+ });
6701
6627
  }
6702
6628
  getStoredMiniMetadatas$(networkId, specVersion) {
6703
6629
  return this.storage$.pipe(rxjs.map(storage => storage.miniMetadatas.filter(m => m.chainId === networkId && m.specVersion === specVersion && m.version === chaindataProvider.MINIMETADATA_VERSION)), rxjs.distinctUntilChanged(lodash.isEqual));
@@ -6712,7 +6638,27 @@ class BalancesProvider {
6712
6638
  tokenId
6713
6639
  })]).filter(util.isNotNil).sort((a, b) => getBalanceId(a).localeCompare(getBalanceId(b)));
6714
6640
  }
6641
+ cleanupAddressesByTokenId(addressesByTokenId) {
6642
+ return this.#chaindataProvider.getNetworksMapById$().pipe(rxjs.map(networksById => {
6643
+ return lodash.fromPairs(lodash.toPairs(addressesByTokenId).map(([tokenId, addresses]) => {
6644
+ const networkId = chaindataProvider.parseTokenId(tokenId).networkId;
6645
+ const network = networksById[networkId];
6646
+ return [tokenId, addresses.filter(address => network && isAddressCompatibleWithNetwork(network, address))];
6647
+ }).filter(([, addresses]) => addresses.length > 0));
6648
+ }));
6649
+ }
6715
6650
  }
6651
+ const isAddressCompatibleWithNetwork = (network, address) => {
6652
+ switch (network.platform) {
6653
+ case "ethereum":
6654
+ return util.isEthereumAddress(address);
6655
+ case "polkadot":
6656
+ return util.isEthereumAddress(address) ? network.account === "secp256k1" : network.account !== "secp256k1";
6657
+ default:
6658
+ log.warn("Unsupported network platform", network);
6659
+ throw new Error("Unsupported network platform");
6660
+ }
6661
+ };
6716
6662
 
6717
6663
  Object.defineProperty(exports, "MINIMETADATA_VERSION", {
6718
6664
  enumerable: true,
@@ -6754,12 +6700,10 @@ exports.SubTokensMiniMetadataExtraSchema = SubTokensMiniMetadataExtraSchema;
6754
6700
  exports.SubTokensModuleConfigSchema = SubTokensModuleConfigSchema;
6755
6701
  exports.SubTokensTokenConfigSchema = SubTokensTokenConfigSchema;
6756
6702
  exports.SumBalancesFormatter = SumBalancesFormatter;
6757
- exports.TalismanBalancesDatabase = TalismanBalancesDatabase;
6758
6703
  exports.abiMulticall = abiMulticall;
6759
6704
  exports.calculateAlphaPrice = calculateAlphaPrice;
6760
6705
  exports.calculateTaoAmountFromAlpha = calculateTaoAmountFromAlpha;
6761
6706
  exports.calculateTaoFromDynamicInfo = calculateTaoFromDynamicInfo;
6762
- exports.db = db;
6763
6707
  exports.deriveMiniMetadataId = deriveMiniMetadataId;
6764
6708
  exports.erc20BalancesAggregatorAbi = erc20BalancesAggregatorAbi;
6765
6709
  exports.excludeFromFeePayableLocks = excludeFromFeePayableLocks;
@@ -6771,4 +6715,5 @@ exports.getLockTitle = getLockTitle;
6771
6715
  exports.getLockedType = getLockedType;
6772
6716
  exports.getValueId = getValueId;
6773
6717
  exports.includeInTotalExtraAmount = includeInTotalExtraAmount;
6718
+ exports.isAddressCompatibleWithNetwork = isAddressCompatibleWithNetwork;
6774
6719
  exports.uniswapV2PairAbi = uniswapV2PairAbi;