@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.
- package/dist/declarations/src/BalancesProvider.d.ts +3 -1
- package/dist/declarations/src/{getMiniMetadata/getMiniMetadatas.d.ts → getMiniMetadatas/index.d.ts} +1 -0
- package/dist/declarations/src/index.d.ts +0 -1
- package/dist/declarations/src/modules/evm-erc20/types.d.ts +1 -1
- package/dist/declarations/src/modules/evm-native/types.d.ts +1 -1
- package/dist/declarations/src/modules/evm-uniswapv2/types.d.ts +1 -1
- package/dist/declarations/src/modules/index.d.ts +9 -9
- package/dist/declarations/src/modules/substrate-assets/types.d.ts +1 -1
- package/dist/declarations/src/modules/substrate-foreignassets/types.d.ts +1 -1
- package/dist/declarations/src/modules/substrate-hydration/types.d.ts +1 -1
- package/dist/declarations/src/modules/substrate-native/types.d.ts +1 -1
- package/dist/declarations/src/modules/substrate-psp22/types.d.ts +1 -1
- package/dist/declarations/src/modules/substrate-tokens/types.d.ts +1 -1
- package/dist/declarations/src/types/tokens.d.ts +1 -1
- package/dist/talismn-balances.cjs.dev.js +1602 -1657
- package/dist/talismn-balances.cjs.prod.js +1602 -1657
- package/dist/talismn-balances.esm.js +1603 -1656
- package/package.json +8 -8
- package/dist/declarations/src/TalismanBalancesDatabase.d.ts +0 -11
- package/dist/declarations/src/upgrades/2024-01-25-upgradeRemoveSymbolFromNativeTokenId.d.ts +0 -2
- package/dist/declarations/src/upgrades/2024-03-19-upgradeBalancesDataBlob.d.ts +0 -2
- package/dist/declarations/src/upgrades/index.d.ts +0 -2
- /package/dist/declarations/src/{getMiniMetadata → getMiniMetadatas}/getMetadataRpc.d.ts +0 -0
- /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
|
-
|
35
|
-
|
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
|
-
|
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
|
1720
|
-
|
1076
|
+
to,
|
1077
|
+
value,
|
1078
|
+
data: "0x"
|
1721
1079
|
};
|
1722
1080
|
};
|
1723
1081
|
|
1724
|
-
const SUBSCRIPTION_INTERVAL$
|
1725
|
-
const subscribeBalances$
|
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$
|
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$
|
1104
|
+
setTimeout(poll, SUBSCRIPTION_INTERVAL$3);
|
1747
1105
|
} catch (error) {
|
1748
1106
|
log.error("Error", {
|
1749
|
-
module: MODULE_TYPE$
|
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
|
1765
|
-
type: MODULE_TYPE$
|
1766
|
-
platform: PLATFORM$
|
1767
|
-
getMiniMetadata: getMiniMetadata$
|
1768
|
-
fetchTokens: fetchTokens$
|
1769
|
-
fetchBalances: fetchBalances$
|
1770
|
-
subscribeBalances: subscribeBalances$
|
1771
|
-
getTransferCallData: getTransferCallData$
|
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
|
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$
|
1785
|
-
const PLATFORM$
|
1137
|
+
const MODULE_TYPE$6 = chaindataProvider.EvmUniswapV2TokenSchema.shape.type.value;
|
1138
|
+
const PLATFORM$6 = chaindataProvider.EvmUniswapV2TokenSchema.shape.platform.value;
|
1786
1139
|
|
1787
|
-
const fetchBalances$
|
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$
|
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?.
|
1804
|
-
const
|
1805
|
-
return
|
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
|
1160
|
+
return fetchWithoutAggregator(client, balanceDefs);
|
1808
1161
|
};
|
1809
|
-
const
|
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.
|
1820
|
-
|
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$
|
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
|
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
|
1857
|
-
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1861
|
-
|
1862
|
-
|
1863
|
-
|
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
|
-
|
1867
|
-
|
1868
|
-
|
1869
|
-
|
1870
|
-
|
1871
|
-
|
1872
|
-
|
1873
|
-
|
1874
|
-
|
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
|
1903
|
-
|
1904
|
-
|
1905
|
-
|
1906
|
-
|
1907
|
-
|
1908
|
-
|
1909
|
-
|
1910
|
-
|
1911
|
-
|
1912
|
-
|
1913
|
-
|
1914
|
-
|
1915
|
-
|
1916
|
-
|
1917
|
-
|
1918
|
-
|
1919
|
-
|
1920
|
-
|
1921
|
-
|
1922
|
-
|
1923
|
-
|
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$
|
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$
|
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
|
-
|
1940
|
-
data: "0x"
|
1416
|
+
to: token.contractAddress,
|
1417
|
+
data
|
1941
1418
|
};
|
1942
1419
|
};
|
1943
1420
|
|
1944
|
-
const SUBSCRIPTION_INTERVAL$
|
1945
|
-
const subscribeBalances$
|
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$
|
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$
|
1443
|
+
setTimeout(poll, SUBSCRIPTION_INTERVAL$2);
|
1967
1444
|
} catch (error) {
|
1968
1445
|
log.error("Error", {
|
1969
|
-
module: MODULE_TYPE$
|
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
|
-
}).
|
1670
|
+
}).filter(util.isNotNil);
|
1982
1671
|
};
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
1987
|
-
|
1988
|
-
|
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
|
-
|
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 (
|
2019
|
-
|
2020
|
-
return
|
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
|
-
|
2023
|
-
};
|
2024
|
-
|
2025
|
-
|
2026
|
-
|
2027
|
-
|
2028
|
-
|
2029
|
-
|
2030
|
-
|
2031
|
-
|
2032
|
-
}
|
2033
|
-
|
2034
|
-
|
2035
|
-
|
2036
|
-
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2040
|
-
|
2041
|
-
|
2042
|
-
|
2043
|
-
|
2044
|
-
|
2045
|
-
|
2046
|
-
|
2047
|
-
|
2048
|
-
|
2049
|
-
|
2050
|
-
|
2051
|
-
|
2052
|
-
|
2053
|
-
|
2054
|
-
|
2055
|
-
|
2056
|
-
|
2057
|
-
|
2058
|
-
|
2059
|
-
|
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
|
-
|
2069
|
-
|
2070
|
-
|
2071
|
-
|
2072
|
-
|
2073
|
-
|
2074
|
-
|
2075
|
-
|
2076
|
-
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
2080
|
-
|
2081
|
-
|
2082
|
-
|
2083
|
-
|
2084
|
-
|
2085
|
-
|
2086
|
-
|
2087
|
-
|
2088
|
-
|
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
|
-
|
2093
|
-
|
1774
|
+
assetId,
|
1775
|
+
existentialDeposit: asset.min_balance,
|
1776
|
+
isSufficient: asset.is_sufficient
|
2094
1777
|
};
|
2095
|
-
}
|
2096
|
-
|
2097
|
-
|
2098
|
-
|
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
|
-
|
2103
|
-
|
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
|
-
|
2109
|
-
|
2110
|
-
|
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
|
-
|
2113
|
-
|
2114
|
-
|
2115
|
-
|
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
|
-
|
2120
|
-
|
2121
|
-
|
2122
|
-
|
2123
|
-
|
2124
|
-
|
2125
|
-
|
2126
|
-
|
2127
|
-
|
2128
|
-
|
2129
|
-
|
2130
|
-
|
2131
|
-
|
2132
|
-
|
2133
|
-
|
2134
|
-
|
2135
|
-
|
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
|
-
|
2142
|
-
|
2143
|
-
|
2144
|
-
|
2145
|
-
|
2146
|
-
|
2147
|
-
|
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
|
-
|
2152
|
-
|
2153
|
-
|
2154
|
-
|
2155
|
-
|
2156
|
-
|
2157
|
-
|
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
|
-
|
2165
|
-
|
2166
|
-
|
2167
|
-
|
2168
|
-
|
2169
|
-
|
2170
|
-
|
2171
|
-
|
2172
|
-
|
2173
|
-
|
2174
|
-
|
2175
|
-
|
2176
|
-
|
2177
|
-
|
2178
|
-
|
2179
|
-
|
2180
|
-
|
2181
|
-
|
2182
|
-
|
2183
|
-
|
2184
|
-
|
2185
|
-
|
2186
|
-
|
2187
|
-
|
2188
|
-
|
2189
|
-
|
2190
|
-
|
2191
|
-
|
2192
|
-
|
2193
|
-
|
2194
|
-
|
2195
|
-
|
2196
|
-
|
2197
|
-
|
2198
|
-
|
2199
|
-
|
2200
|
-
|
2201
|
-
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2205
|
-
|
2206
|
-
|
2207
|
-
|
2208
|
-
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2212
|
-
|
2213
|
-
|
2214
|
-
|
2215
|
-
|
2216
|
-
|
2217
|
-
|
2218
|
-
|
2219
|
-
|
2220
|
-
|
2221
|
-
|
2222
|
-
|
2223
|
-
|
2224
|
-
|
2225
|
-
|
2226
|
-
|
2227
|
-
|
2228
|
-
|
2229
|
-
|
2230
|
-
|
2231
|
-
|
2232
|
-
|
2233
|
-
|
2234
|
-
|
2235
|
-
|
2236
|
-
|
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
|
-
|
2259
|
-
|
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
|
-
|
2263
|
-
|
2264
|
-
|
2265
|
-
|
2266
|
-
|
2267
|
-
|
2268
|
-
|
2269
|
-
|
2270
|
-
|
2271
|
-
|
2272
|
-
|
2273
|
-
|
2274
|
-
|
2275
|
-
|
2276
|
-
|
2277
|
-
|
2278
|
-
|
2279
|
-
|
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
|
-
|
2284
|
-
|
2285
|
-
|
2286
|
-
|
2287
|
-
|
2288
|
-
|
2289
|
-
|
2290
|
-
|
2291
|
-
|
2292
|
-
}
|
2293
|
-
|
2294
|
-
|
2295
|
-
|
2296
|
-
|
2297
|
-
|
2298
|
-
|
2299
|
-
|
2300
|
-
|
2301
|
-
|
2302
|
-
|
2303
|
-
|
2304
|
-
|
2305
|
-
|
2306
|
-
|
2307
|
-
|
2308
|
-
|
2309
|
-
|
2310
|
-
|
2311
|
-
|
2312
|
-
|
2313
|
-
|
2314
|
-
|
2315
|
-
|
2316
|
-
|
2317
|
-
|
2318
|
-
|
2319
|
-
|
2320
|
-
|
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
|
-
|
2324
|
-
|
2325
|
-
|
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
|
-
|
2334
|
-
|
2335
|
-
|
2336
|
-
|
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
|
-
|
2340
|
-
|
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
|
-
|
2343
|
-
|
2344
|
-
|
2345
|
-
|
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
|
-
|
2348
|
-
|
2349
|
-
|
2350
|
-
|
2351
|
-
|
2352
|
-
|
2353
|
-
|
2354
|
-
};
|
2355
|
-
|
2356
|
-
|
2357
|
-
|
2358
|
-
|
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
|
-
|
2361
|
-
|
2362
|
-
|
2363
|
-
|
2364
|
-
|
2365
|
-
|
2366
|
-
|
2367
|
-
|
2368
|
-
|
2369
|
-
|
2370
|
-
|
2371
|
-
|
2372
|
-
|
2373
|
-
};
|
2374
|
-
|
2375
|
-
|
2376
|
-
|
2377
|
-
|
2378
|
-
|
2379
|
-
|
2380
|
-
|
2381
|
-
|
2382
|
-
|
2383
|
-
|
2384
|
-
|
2385
|
-
|
2386
|
-
|
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
|
-
|
2391
|
-
|
2392
|
-
|
2393
|
-
|
2394
|
-
|
2395
|
-
|
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
|
-
|
2400
|
-
|
2401
|
-
|
2402
|
-
|
2403
|
-
|
2404
|
-
|
2405
|
-
|
2406
|
-
|
2407
|
-
|
2408
|
-
|
2409
|
-
|
2410
|
-
|
2411
|
-
|
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
|
-
|
2415
|
-
|
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
|
-
|
2430
|
-
|
2431
|
-
|
2432
|
-
|
2433
|
-
|
2434
|
-
|
2435
|
-
|
2436
|
-
|
2437
|
-
|
2438
|
-
|
2439
|
-
|
2440
|
-
|
2441
|
-
|
2442
|
-
|
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
|
-
|
2446
|
-
|
2447
|
-
|
2448
|
-
|
2449
|
-
|
2450
|
-
const
|
2451
|
-
|
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
|
-
|
2469
|
-
|
2470
|
-
|
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
|
-
|
2482
|
-
|
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
|
-
|
2489
|
-
|
2490
|
-
|
2491
|
-
|
2492
|
-
|
2493
|
-
|
2494
|
-
|
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
|
-
|
2497
|
-
|
2498
|
-
|
2499
|
-
|
2500
|
-
|
2501
|
-
|
2502
|
-
|
2503
|
-
|
2504
|
-
|
2505
|
-
|
2506
|
-
|
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
|
-
|
2509
|
-
|
2510
|
-
|
2511
|
-
|
2512
|
-
|
2513
|
-
|
2514
|
-
|
2515
|
-
|
2516
|
-
|
2517
|
-
|
2518
|
-
|
2519
|
-
|
2520
|
-
|
2521
|
-
|
2522
|
-
|
2523
|
-
|
2524
|
-
|
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
|
-
|
2527
|
-
|
2528
|
-
|
2529
|
-
|
2530
|
-
|
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
|
-
|
2553
|
-
|
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
|
-
|
2565
|
-
|
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
|
-
|
2576
|
-
|
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
|
-
|
2587
|
-
|
2588
|
-
|
2589
|
-
|
2590
|
-
|
2591
|
-
|
2592
|
-
|
2593
|
-
|
2594
|
-
|
2595
|
-
|
2596
|
-
|
2597
|
-
|
2598
|
-
|
2599
|
-
|
2600
|
-
|
2601
|
-
|
2602
|
-
|
2603
|
-
|
2604
|
-
|
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
|
-
|
2616
|
-
|
2617
|
-
|
2618
|
-
|
2619
|
-
|
2620
|
-
|
2621
|
-
|
2622
|
-
|
2623
|
-
|
2624
|
-
|
2625
|
-
|
2626
|
-
|
2627
|
-
|
2628
|
-
|
2629
|
-
|
2630
|
-
|
2631
|
-
|
2632
|
-
|
2633
|
-
|
2634
|
-
|
2635
|
-
return
|
2636
|
-
|
2637
|
-
|
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
|
-
|
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
|
-
|
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
|
-
})
|
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
|
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(
|
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.
|
6675
|
-
|
6676
|
-
|
6677
|
-
|
6678
|
-
|
6679
|
-
|
6680
|
-
|
6681
|
-
|
6682
|
-
|
6683
|
-
|
6684
|
-
|
6685
|
-
|
6686
|
-
|
6687
|
-
|
6688
|
-
|
6689
|
-
|
6690
|
-
|
6691
|
-
|
6692
|
-
|
6693
|
-
|
6694
|
-
|
6695
|
-
|
6696
|
-
|
6697
|
-
|
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;
|