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