@metamask-previews/assets-controller 16.1.0-preview-b1e33e9f1 → 16.1.0-preview-d2ea3cc
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/CHANGELOG.md +0 -28
- package/dist/AssetsController-method-action-types.d.ts +1 -5
- package/dist/AssetsController-method-action-types.d.ts.map +1 -1
- package/dist/AssetsController-method-action-types.js.map +1 -1
- package/dist/AssetsController.d.ts +40 -8
- package/dist/AssetsController.d.ts.map +1 -1
- package/dist/AssetsController.js +235 -570
- package/dist/AssetsController.js.map +1 -1
- package/dist/data-sources/AbstractDataSource.d.ts +6 -1
- package/dist/data-sources/AbstractDataSource.d.ts.map +1 -1
- package/dist/data-sources/AbstractDataSource.js.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.d.ts +3 -17
- package/dist/data-sources/AccountsApiDataSource.d.ts.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.js +125 -275
- package/dist/data-sources/AccountsApiDataSource.js.map +1 -1
- package/dist/data-sources/PriceDataSource.d.ts +6 -7
- package/dist/data-sources/PriceDataSource.d.ts.map +1 -1
- package/dist/data-sources/PriceDataSource.js +20 -15
- package/dist/data-sources/PriceDataSource.js.map +1 -1
- package/dist/data-sources/RpcDataSource.d.ts +1 -15
- package/dist/data-sources/RpcDataSource.d.ts.map +1 -1
- package/dist/data-sources/RpcDataSource.js +107 -398
- package/dist/data-sources/RpcDataSource.js.map +1 -1
- package/dist/data-sources/SnapDataSource.d.ts +0 -5
- package/dist/data-sources/SnapDataSource.d.ts.map +1 -1
- package/dist/data-sources/SnapDataSource.js +0 -114
- package/dist/data-sources/SnapDataSource.js.map +1 -1
- package/dist/data-sources/TokenDataSource.d.ts +1 -3
- package/dist/data-sources/TokenDataSource.d.ts.map +1 -1
- package/dist/data-sources/TokenDataSource.js +2 -4
- package/dist/data-sources/TokenDataSource.js.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.ts +0 -8
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.ts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.js +2 -47
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/middlewares/CustomAssetGraduationMiddleware.d.ts +1 -3
- package/dist/middlewares/CustomAssetGraduationMiddleware.d.ts.map +1 -1
- package/dist/middlewares/CustomAssetGraduationMiddleware.js +1 -3
- package/dist/middlewares/CustomAssetGraduationMiddleware.js.map +1 -1
- package/dist/middlewares/DetectionMiddleware.d.ts +2 -10
- package/dist/middlewares/DetectionMiddleware.d.ts.map +1 -1
- package/dist/middlewares/DetectionMiddleware.js +23 -5
- package/dist/middlewares/DetectionMiddleware.js.map +1 -1
- package/dist/middlewares/ParallelMiddleware.d.ts.map +1 -1
- package/dist/middlewares/ParallelMiddleware.js +3 -0
- package/dist/middlewares/ParallelMiddleware.js.map +1 -1
- package/dist/middlewares/RpcFallbackMiddleware.d.ts +15 -16
- package/dist/middlewares/RpcFallbackMiddleware.d.ts.map +1 -1
- package/dist/middlewares/RpcFallbackMiddleware.js +107 -125
- package/dist/middlewares/RpcFallbackMiddleware.js.map +1 -1
- package/dist/middlewares/index.d.ts +0 -1
- package/dist/middlewares/index.d.ts.map +1 -1
- package/dist/middlewares/index.js.map +1 -1
- package/dist/migrations/healAssetsInfoMetadata.d.ts +5 -5
- package/dist/migrations/healAssetsInfoMetadata.d.ts.map +1 -1
- package/dist/migrations/healAssetsInfoMetadata.js.map +1 -1
- package/dist/pipeline/buildFastFetchSources.d.ts +0 -5
- package/dist/pipeline/buildFastFetchSources.d.ts.map +1 -1
- package/dist/pipeline/buildFastFetchSources.js +1 -7
- package/dist/pipeline/buildFastFetchSources.js.map +1 -1
- package/dist/pipeline/executeAssetsPipeline.d.ts +3 -1
- package/dist/pipeline/executeAssetsPipeline.d.ts.map +1 -1
- package/dist/pipeline/executeAssetsPipeline.js +2 -1
- package/dist/pipeline/executeAssetsPipeline.js.map +1 -1
- package/dist/selectors/balance.js +1 -1
- package/dist/selectors/balance.js.map +1 -1
- package/dist/types.d.ts +23 -29
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/dist/utils/assetVisibility.d.ts +0 -29
- package/dist/utils/assetVisibility.d.ts.map +0 -1
- package/dist/utils/assetVisibility.js +0 -80
- package/dist/utils/assetVisibility.js.map +0 -1
- package/dist/utils/filterFailedChainBalances.d.ts +0 -11
- package/dist/utils/filterFailedChainBalances.d.ts.map +0 -1
- package/dist/utils/filterFailedChainBalances.js +0 -22
- package/dist/utils/filterFailedChainBalances.js.map +0 -1
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { Web3Provider } from '@ethersproject/providers';
|
|
2
2
|
import { toHex } from '@metamask/controller-utils';
|
|
3
|
-
import { isStrictHexString, isCaipChainId,
|
|
3
|
+
import { isStrictHexString, isCaipChainId, parseCaipAssetType, parseCaipChainId, hexToNumber, toCaipChainId, KnownCaipNamespace, } from '@metamask/utils';
|
|
4
4
|
import { BigNumber as BigNumberJS } from 'bignumber.js';
|
|
5
5
|
import { projectLogger, createModuleLogger } from '../logger.js';
|
|
6
6
|
import { ZERO_ADDRESS } from '../utils/constants.js';
|
|
7
|
-
import { filterFailedChainBalances } from '../utils/filterFailedChainBalances.js';
|
|
8
7
|
import { normalizeAssetId } from '../utils/index.js';
|
|
9
8
|
import { AbstractDataSource } from './AbstractDataSource.js';
|
|
10
|
-
import { BalanceFetcher,
|
|
9
|
+
import { BalanceFetcher, MulticallClient, TokenDetector, TokensApiClient, } from './evm-rpc-services/index.js';
|
|
11
10
|
import { shouldSkipNativeForCaipChainId } from './evm-rpc-services/utils/assets.js';
|
|
12
11
|
const CONTROLLER_NAME = 'RpcDataSource';
|
|
13
12
|
const DEFAULT_BALANCE_INTERVAL = 30_000; // 30 seconds
|
|
@@ -48,7 +47,6 @@ export const caipChainIdToHex = (chainId) => {
|
|
|
48
47
|
*/
|
|
49
48
|
export class RpcDataSource extends AbstractDataSource {
|
|
50
49
|
#messenger;
|
|
51
|
-
#getAssetsState;
|
|
52
50
|
#onActiveChainsUpdated;
|
|
53
51
|
#getNativeAssetForChain;
|
|
54
52
|
#timeout;
|
|
@@ -69,12 +67,9 @@ export class RpcDataSource extends AbstractDataSource {
|
|
|
69
67
|
#balanceFetcher;
|
|
70
68
|
#tokenDetector;
|
|
71
69
|
#getAssetType;
|
|
72
|
-
#isBalanceV6Enabled;
|
|
73
|
-
#getAssetVisibility;
|
|
74
70
|
constructor(options) {
|
|
75
71
|
super(CONTROLLER_NAME, { activeChains: [] });
|
|
76
72
|
this.#messenger = options.messenger;
|
|
77
|
-
this.#getAssetsState = options.getAssetsState;
|
|
78
73
|
this.#onActiveChainsUpdated = options.onActiveChainsUpdated;
|
|
79
74
|
this.#getNativeAssetForChain = options.getNativeAssetForChain;
|
|
80
75
|
this.#getAssetType = options.getAssetType;
|
|
@@ -84,9 +79,6 @@ export class RpcDataSource extends AbstractDataSource {
|
|
|
84
79
|
this.#useExternalService =
|
|
85
80
|
options.useExternalService ?? (() => true);
|
|
86
81
|
this.#isOnboarded = options.isOnboarded ?? (() => true);
|
|
87
|
-
this.#isBalanceV6Enabled =
|
|
88
|
-
options.isBalanceV6Enabled ?? (() => false);
|
|
89
|
-
this.#getAssetVisibility = options.getAssetVisibility;
|
|
90
82
|
const balanceInterval = options.balanceInterval ?? DEFAULT_BALANCE_INTERVAL;
|
|
91
83
|
const detectionInterval = options.detectionInterval ?? DEFAULT_DETECTION_INTERVAL;
|
|
92
84
|
log('Initializing RpcDataSource', {
|
|
@@ -126,8 +118,6 @@ export class RpcDataSource extends AbstractDataSource {
|
|
|
126
118
|
const nativeId = this.#getNativeAssetForChain(chainId);
|
|
127
119
|
return nativeId?.toLowerCase() === assetId.toLowerCase();
|
|
128
120
|
},
|
|
129
|
-
isBalanceV6Enabled: () => this.#isBalanceV6Enabled(),
|
|
130
|
-
getAssetVisibility: this.#getAssetVisibility,
|
|
131
121
|
});
|
|
132
122
|
// Polling controller awaits this callback; rejections must not become unhandled.
|
|
133
123
|
this.#balanceFetcher.setOnBalanceUpdate(async (result) => {
|
|
@@ -273,19 +263,6 @@ export class RpcDataSource extends AbstractDataSource {
|
|
|
273
263
|
* @param result - The balance fetch result.
|
|
274
264
|
*/
|
|
275
265
|
async #handleBalanceUpdate(result) {
|
|
276
|
-
if (this.#isBalanceV6Enabled()) {
|
|
277
|
-
await this.#handleBalanceUpdateV6(result);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
await this.#handleBalanceUpdateV5(result);
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* v5 poll path. Unchanged from the pre-v6 handler; delete the v6 sibling
|
|
284
|
-
* first if `assetsAccountsApiV6` is rolled back.
|
|
285
|
-
*
|
|
286
|
-
* @param result - The balance fetch result.
|
|
287
|
-
*/
|
|
288
|
-
async #handleBalanceUpdateV5(result) {
|
|
289
266
|
const newBalances = {};
|
|
290
267
|
// Convert hex chain ID to CAIP-2 format
|
|
291
268
|
const chainIdDecimal = parseInt(result.chainId, 16);
|
|
@@ -336,75 +313,6 @@ export class RpcDataSource extends AbstractDataSource {
|
|
|
336
313
|
});
|
|
337
314
|
}
|
|
338
315
|
}
|
|
339
|
-
/**
|
|
340
|
-
* v6 poll path: a complete chain snapshot, or nothing. Delete with the rest
|
|
341
|
-
* of the v6 path if `assetsAccountsApiV6` is rolled back.
|
|
342
|
-
*
|
|
343
|
-
* @param result - The balance fetch result.
|
|
344
|
-
*/
|
|
345
|
-
async #handleBalanceUpdateV6(result) {
|
|
346
|
-
if (result.failedAddresses.length > 0) {
|
|
347
|
-
log('Skipping incomplete v6 poll snapshot', {
|
|
348
|
-
accountId: result.accountId,
|
|
349
|
-
chainId: result.chainId,
|
|
350
|
-
failedAddresses: result.failedAddresses,
|
|
351
|
-
});
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
const newBalances = {};
|
|
355
|
-
// Convert hex chain ID to CAIP-2 format
|
|
356
|
-
const chainIdDecimal = parseInt(result.chainId, 16);
|
|
357
|
-
const caipChainId = `eip155:${chainIdDecimal}`;
|
|
358
|
-
// Normalize asset IDs from BalanceFetcher (lowercase) to checksummed form
|
|
359
|
-
const normalizedBalances = result.balances.map((b) => ({
|
|
360
|
-
...b,
|
|
361
|
-
assetId: normalizeAssetId(b.assetId),
|
|
362
|
-
}));
|
|
363
|
-
// Collect metadata for all balances
|
|
364
|
-
const assetsInfo = this.#collectMetadataForBalances(normalizedBalances, caipChainId);
|
|
365
|
-
// Convert balances to human-readable format.
|
|
366
|
-
// Unknown decimals mean the snapshot is incomplete: drop the chain
|
|
367
|
-
// instead of publishing a partial `full` result.
|
|
368
|
-
const existingMetadata = this.#getExistingAssetsMetadata();
|
|
369
|
-
for (const balance of normalizedBalances) {
|
|
370
|
-
const stateMetadata = existingMetadata[balance.assetId];
|
|
371
|
-
const pipelineMetadata = assetsInfo[balance.assetId];
|
|
372
|
-
const decimals = this.#pickValidDecimals(stateMetadata, pipelineMetadata);
|
|
373
|
-
if (decimals === undefined) {
|
|
374
|
-
log('Skipping incomplete v6 poll snapshot', {
|
|
375
|
-
accountId: result.accountId,
|
|
376
|
-
chainId: result.chainId,
|
|
377
|
-
assetId: balance.assetId,
|
|
378
|
-
});
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
const humanReadableAmount = this.#convertToHumanReadable(balance.balance, decimals);
|
|
382
|
-
newBalances[balance.assetId] = {
|
|
383
|
-
amount: humanReadableAmount,
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
const response = {
|
|
387
|
-
assetsBalance: {
|
|
388
|
-
[result.accountId]: newBalances,
|
|
389
|
-
},
|
|
390
|
-
assetsInfo,
|
|
391
|
-
updateMode: 'full',
|
|
392
|
-
};
|
|
393
|
-
const request = {
|
|
394
|
-
accountsWithSupportedChains: [],
|
|
395
|
-
chainIds: [caipChainId],
|
|
396
|
-
dataTypes: ['balance'],
|
|
397
|
-
};
|
|
398
|
-
log('Balance update response', {
|
|
399
|
-
accountId: result.accountId,
|
|
400
|
-
newBalanceCount: Object.keys(newBalances).length,
|
|
401
|
-
});
|
|
402
|
-
for (const subscription of this.#activeSubscriptions.values()) {
|
|
403
|
-
subscription.onAssetsUpdate(response, request)?.catch((error) => {
|
|
404
|
-
log('Failed to update assets', { error });
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
316
|
/**
|
|
409
317
|
* Handle detection update from TokenDetector.
|
|
410
318
|
*
|
|
@@ -753,80 +661,21 @@ export class RpcDataSource extends AbstractDataSource {
|
|
|
753
661
|
log('Skipping fetch - onboarding not complete');
|
|
754
662
|
return {};
|
|
755
663
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* v5 fetch. Overlays tokens that succeed, stamps `merge`. Delete with the
|
|
763
|
-
* rest of the v5 path if `assetsAccountsApiV6` is rolled back.
|
|
764
|
-
*
|
|
765
|
-
* @param request - The data request.
|
|
766
|
-
* @returns Balance and metadata for the requested chains.
|
|
767
|
-
*/
|
|
768
|
-
async #fetchV5(request) {
|
|
769
|
-
const chainsToFetch = this.#getChainsToFetch(request);
|
|
770
|
-
if (chainsToFetch.length === 0) {
|
|
771
|
-
log('No active chains to fetch');
|
|
772
|
-
return {};
|
|
773
|
-
}
|
|
774
|
-
const assetsBalance = {};
|
|
775
|
-
const assetsInfo = {};
|
|
776
|
-
const failedChains = [];
|
|
777
|
-
for (const { account, supportedChains, } of request.accountsWithSupportedChains) {
|
|
778
|
-
const chainsForAccount = chainsToFetch.filter((chain) => supportedChains.includes(chain));
|
|
779
|
-
if (chainsForAccount.length === 0) {
|
|
780
|
-
continue;
|
|
781
|
-
}
|
|
782
|
-
const { address, id: accountId } = account;
|
|
783
|
-
for (const chainId of chainsForAccount) {
|
|
784
|
-
const hexChainId = caipChainIdToHex(chainId);
|
|
785
|
-
const nativeAssetId = this.#getNativeAssetForChain(chainId);
|
|
786
|
-
const shouldSkipNative = shouldSkipNativeForCaipChainId(chainId);
|
|
787
|
-
const assetsToFetch = this.#getAssetsToFetchV5(request, chainId, shouldSkipNative);
|
|
788
|
-
try {
|
|
789
|
-
const result = await this.#balanceFetcher.fetchBalancesForAssets(hexChainId, accountId, address, assetsToFetch);
|
|
790
|
-
await this.#ingestFetchedBalances(result, accountId, chainId, assetsBalance, assetsInfo);
|
|
791
|
-
}
|
|
792
|
-
catch (error) {
|
|
793
|
-
this.#recordFetchChainFailure({
|
|
794
|
-
address,
|
|
795
|
-
chainId,
|
|
796
|
-
error,
|
|
797
|
-
accountId,
|
|
798
|
-
nativeAssetId,
|
|
799
|
-
shouldSkipNative,
|
|
800
|
-
assetsBalance,
|
|
801
|
-
assetsInfo,
|
|
802
|
-
failedChains,
|
|
803
|
-
});
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
return this.#completeFetchResponse({
|
|
808
|
-
assetsBalance,
|
|
809
|
-
assetsInfo,
|
|
810
|
-
failedChains,
|
|
664
|
+
const response = {};
|
|
665
|
+
const chainsToFetch = request.chainIds.filter((chainId) => this.#activeChains.includes(chainId));
|
|
666
|
+
log('Fetch requested', {
|
|
667
|
+
accounts: request.accountsWithSupportedChains.map((a) => a.account.id),
|
|
668
|
+
requestedChains: request.chainIds,
|
|
811
669
|
chainsToFetch,
|
|
812
|
-
updateMode: 'merge',
|
|
813
670
|
});
|
|
814
|
-
}
|
|
815
|
-
/**
|
|
816
|
-
* v6 fetch. Visibility-scoped snapshot, atomic per chain, stamps `full`.
|
|
817
|
-
*
|
|
818
|
-
* @param request - The data request.
|
|
819
|
-
* @returns Balance and metadata for successful chains only.
|
|
820
|
-
*/
|
|
821
|
-
async #fetchV6(request) {
|
|
822
|
-
const chainsToFetch = this.#getChainsToFetch(request);
|
|
823
671
|
if (chainsToFetch.length === 0) {
|
|
824
672
|
log('No active chains to fetch');
|
|
825
|
-
return
|
|
673
|
+
return response;
|
|
826
674
|
}
|
|
827
675
|
const assetsBalance = {};
|
|
828
676
|
const assetsInfo = {};
|
|
829
677
|
const failedChains = [];
|
|
678
|
+
// Fetch balances for each account and its supported chains (pre-computed in request)
|
|
830
679
|
for (const { account, supportedChains, } of request.accountsWithSupportedChains) {
|
|
831
680
|
const chainsForAccount = chainsToFetch.filter((chain) => supportedChains.includes(chain));
|
|
832
681
|
if (chainsForAccount.length === 0) {
|
|
@@ -837,133 +686,100 @@ export class RpcDataSource extends AbstractDataSource {
|
|
|
837
686
|
const hexChainId = caipChainIdToHex(chainId);
|
|
838
687
|
const nativeAssetId = this.#getNativeAssetForChain(chainId);
|
|
839
688
|
const shouldSkipNative = shouldSkipNativeForCaipChainId(chainId);
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
//
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
689
|
+
const assetsToFetch = [];
|
|
690
|
+
if (!shouldSkipNative) {
|
|
691
|
+
// Build a single AssetFetchEntry[] for native + custom ERC-20s
|
|
692
|
+
assetsToFetch.push({ assetId: nativeAssetId, address: ZERO_ADDRESS });
|
|
693
|
+
}
|
|
694
|
+
if (request.customAssets) {
|
|
695
|
+
const existingMetadata = this.#getExistingAssetsMetadata();
|
|
696
|
+
for (const assetId of request.customAssets) {
|
|
697
|
+
try {
|
|
698
|
+
const parsed = parseCaipAssetType(assetId);
|
|
699
|
+
const assetChainId = `${parsed.chain.namespace}:${parsed.chain.reference}`;
|
|
700
|
+
if (assetChainId === chainId &&
|
|
701
|
+
this.#getAssetType(assetId) === 'erc20') {
|
|
702
|
+
const tokenAddress = parsed.assetReference.toLowerCase();
|
|
703
|
+
const normalizedId = normalizeAssetId(assetId);
|
|
704
|
+
const decimals = existingMetadata[normalizedId]?.decimals;
|
|
705
|
+
assetsToFetch.push({
|
|
706
|
+
assetId,
|
|
707
|
+
address: tokenAddress,
|
|
708
|
+
decimals,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
848
711
|
}
|
|
849
|
-
|
|
712
|
+
catch {
|
|
713
|
+
// Skip unparseable asset IDs
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
try {
|
|
718
|
+
const result = await this.#balanceFetcher.fetchBalancesForAssets(hexChainId, accountId, address, assetsToFetch);
|
|
719
|
+
if (!assetsBalance[accountId]) {
|
|
720
|
+
assetsBalance[accountId] = {};
|
|
850
721
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
722
|
+
// Normalize asset IDs from BalanceFetcher (which uses lowercase
|
|
723
|
+
// addresses) to checksummed form so they match assetsInfo state keys.
|
|
724
|
+
const normalizedBalances = result.balances.map((b) => ({
|
|
725
|
+
...b,
|
|
726
|
+
assetId: normalizeAssetId(b.assetId),
|
|
727
|
+
}));
|
|
728
|
+
// Collect metadata for all balances
|
|
729
|
+
const balanceMetadata = this.#collectMetadataForBalances(normalizedBalances, chainId);
|
|
730
|
+
Object.assign(assetsInfo, balanceMetadata);
|
|
731
|
+
// Convert balances to human-readable format using decimals from
|
|
732
|
+
// assetsInfo state (which includes pendingMetadata from addCustomAsset).
|
|
733
|
+
// Resolution: state → pipeline metadata → RPC `decimals()`; omit balance if still unknown.
|
|
734
|
+
const existingMetadata = this.#getExistingAssetsMetadata();
|
|
735
|
+
for (const balance of normalizedBalances) {
|
|
736
|
+
const stateMetadata = existingMetadata[balance.assetId];
|
|
737
|
+
const pipelineMetadata = assetsInfo[balance.assetId];
|
|
738
|
+
let decimals = this.#pickValidDecimals(stateMetadata, pipelineMetadata);
|
|
739
|
+
if (decimals === undefined) {
|
|
740
|
+
const parsed = parseCaipAssetType(balance.assetId);
|
|
741
|
+
if (this.#getAssetType(balance.assetId) === 'erc20') {
|
|
742
|
+
decimals = await this.#fetchDecimalsViaRpc(chainId, parsed.assetReference);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if (decimals === undefined) {
|
|
746
|
+
continue;
|
|
855
747
|
}
|
|
748
|
+
const humanReadableAmount = this.#convertToHumanReadable(balance.balance, decimals);
|
|
749
|
+
assetsBalance[accountId][balance.assetId] = {
|
|
750
|
+
amount: humanReadableAmount,
|
|
751
|
+
};
|
|
856
752
|
}
|
|
857
753
|
}
|
|
858
754
|
catch (error) {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
nativeAssetId
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
755
|
+
log('Failed to fetch balance', { address, chainId, error });
|
|
756
|
+
if (!assetsBalance[accountId]) {
|
|
757
|
+
assetsBalance[accountId] = {};
|
|
758
|
+
}
|
|
759
|
+
if (!shouldSkipNative) {
|
|
760
|
+
assetsBalance[accountId][nativeAssetId] = { amount: '0' };
|
|
761
|
+
}
|
|
762
|
+
// On error, emit a stub only when no valid metadata exists in state
|
|
763
|
+
// yet. Re-emitting existing metadata would overwrite richer entries
|
|
764
|
+
// (e.g. image/description added by AccountsAPI) with a simpler stub.
|
|
765
|
+
const existingNativeMeta = this.#getExistingAssetsMetadata()[nativeAssetId];
|
|
766
|
+
if (!this.#hasValidDecimals(existingNativeMeta)) {
|
|
767
|
+
const chainStatus = this.#chainStatuses[chainId];
|
|
768
|
+
if (chainStatus) {
|
|
769
|
+
assetsInfo[nativeAssetId] = {
|
|
770
|
+
type: 'native',
|
|
771
|
+
symbol: chainStatus.nativeCurrency,
|
|
772
|
+
name: chainStatus.nativeCurrency,
|
|
773
|
+
decimals: 18,
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (!failedChains.includes(chainId)) {
|
|
778
|
+
failedChains.push(chainId);
|
|
779
|
+
}
|
|
870
780
|
}
|
|
871
781
|
}
|
|
872
782
|
}
|
|
873
|
-
return this.#completeFetchResponse({
|
|
874
|
-
// A failed chain contributes nothing, so its balances stay as they are.
|
|
875
|
-
assetsBalance: filterFailedChainBalances(assetsBalance, new Set(failedChains)),
|
|
876
|
-
assetsInfo,
|
|
877
|
-
failedChains,
|
|
878
|
-
chainsToFetch,
|
|
879
|
-
updateMode: 'full',
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
#getChainsToFetch(request) {
|
|
883
|
-
const chainsToFetch = request.chainIds.filter((chainId) => this.#activeChains.includes(chainId));
|
|
884
|
-
log('Fetch requested', {
|
|
885
|
-
accounts: request.accountsWithSupportedChains.map((a) => a.account.id),
|
|
886
|
-
requestedChains: request.chainIds,
|
|
887
|
-
chainsToFetch,
|
|
888
|
-
});
|
|
889
|
-
return chainsToFetch;
|
|
890
|
-
}
|
|
891
|
-
/**
|
|
892
|
-
* Convert fetched raw balances into human-readable amounts.
|
|
893
|
-
*
|
|
894
|
-
* v5 overlays tokens that resolve and skips the rest (`merge`). v6 must not
|
|
895
|
-
* publish a partial `full` snapshot: when `failOnUnresolvedDecimals` is set,
|
|
896
|
-
* unresolved decimals abort ingest so the caller can fail the chain.
|
|
897
|
-
*
|
|
898
|
-
* @param result - The balance fetch result.
|
|
899
|
-
* @param accountId - The account the balances belong to.
|
|
900
|
-
* @param chainId - The CAIP-2 chain ID.
|
|
901
|
-
* @param assetsBalance - Accumulator for converted balances.
|
|
902
|
-
* @param assetsInfo - Accumulator for metadata collected from this fetch.
|
|
903
|
-
* @param options - Ingest options.
|
|
904
|
-
* @param options.failOnUnresolvedDecimals - When true, stop ingesting and
|
|
905
|
-
* return false if any token's decimals cannot be resolved.
|
|
906
|
-
* @returns `false` when ingest was aborted for unresolved decimals; otherwise `true`.
|
|
907
|
-
*/
|
|
908
|
-
async #ingestFetchedBalances(result, accountId, chainId, assetsBalance, assetsInfo, { failOnUnresolvedDecimals = false, } = {}) {
|
|
909
|
-
assetsBalance[accountId] ??= {};
|
|
910
|
-
const normalizedBalances = result.balances.map((balance) => ({
|
|
911
|
-
...balance,
|
|
912
|
-
assetId: normalizeAssetId(balance.assetId),
|
|
913
|
-
}));
|
|
914
|
-
Object.assign(assetsInfo, this.#collectMetadataForBalances(normalizedBalances, chainId));
|
|
915
|
-
const existingMetadata = this.#getExistingAssetsMetadata();
|
|
916
|
-
for (const balance of normalizedBalances) {
|
|
917
|
-
const stateMetadata = existingMetadata[balance.assetId];
|
|
918
|
-
const pipelineMetadata = assetsInfo[balance.assetId];
|
|
919
|
-
let decimals = this.#pickValidDecimals(stateMetadata, pipelineMetadata);
|
|
920
|
-
if (decimals === undefined) {
|
|
921
|
-
const parsed = parseCaipAssetType(balance.assetId);
|
|
922
|
-
if (this.#getAssetType(balance.assetId) === 'erc20') {
|
|
923
|
-
decimals = await this.#fetchDecimalsViaRpc(chainId, parsed.assetReference);
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
if (decimals === undefined) {
|
|
927
|
-
if (failOnUnresolvedDecimals) {
|
|
928
|
-
log('Skipping incomplete v6 fetch snapshot', {
|
|
929
|
-
accountId,
|
|
930
|
-
chainId,
|
|
931
|
-
assetId: balance.assetId,
|
|
932
|
-
});
|
|
933
|
-
return false;
|
|
934
|
-
}
|
|
935
|
-
continue;
|
|
936
|
-
}
|
|
937
|
-
assetsBalance[accountId][balance.assetId] = {
|
|
938
|
-
amount: this.#convertToHumanReadable(balance.balance, decimals),
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
return true;
|
|
942
|
-
}
|
|
943
|
-
#recordFetchChainFailure({ address, chainId, error, accountId, nativeAssetId, shouldSkipNative, assetsBalance, assetsInfo, failedChains, }) {
|
|
944
|
-
log('Failed to fetch balance', { address, chainId, error });
|
|
945
|
-
assetsBalance[accountId] ??= {};
|
|
946
|
-
if (!shouldSkipNative) {
|
|
947
|
-
assetsBalance[accountId][nativeAssetId] = { amount: '0' };
|
|
948
|
-
}
|
|
949
|
-
const existingNativeMeta = this.#getExistingAssetsMetadata()[nativeAssetId];
|
|
950
|
-
if (!this.#hasValidDecimals(existingNativeMeta)) {
|
|
951
|
-
const chainStatus = this.#chainStatuses[chainId];
|
|
952
|
-
if (chainStatus) {
|
|
953
|
-
assetsInfo[nativeAssetId] = {
|
|
954
|
-
type: 'native',
|
|
955
|
-
symbol: chainStatus.nativeCurrency,
|
|
956
|
-
name: chainStatus.nativeCurrency,
|
|
957
|
-
decimals: 18,
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
if (!failedChains.includes(chainId)) {
|
|
962
|
-
failedChains.push(chainId);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
#completeFetchResponse({ assetsBalance, assetsInfo, failedChains, chainsToFetch, updateMode, }) {
|
|
966
|
-
const response = { updateMode };
|
|
967
783
|
if (failedChains.length > 0) {
|
|
968
784
|
log('Fetch PARTIAL - some chains failed', {
|
|
969
785
|
successChains: chainsToFetch.filter((chain) => !failedChains.includes(chain)),
|
|
@@ -977,13 +793,15 @@ export class RpcDataSource extends AbstractDataSource {
|
|
|
977
793
|
else {
|
|
978
794
|
log('Fetch SUCCESS', {
|
|
979
795
|
chains: chainsToFetch,
|
|
980
|
-
accountCount: Object.keys(assetsBalance
|
|
796
|
+
accountCount: Object.keys(assetsBalance).length,
|
|
981
797
|
});
|
|
982
798
|
}
|
|
983
799
|
response.assetsBalance = assetsBalance;
|
|
800
|
+
// Include metadata for native tokens if we have any
|
|
984
801
|
if (Object.keys(assetsInfo).length > 0) {
|
|
985
802
|
response.assetsInfo = assetsInfo;
|
|
986
803
|
}
|
|
804
|
+
response.updateMode = 'merge';
|
|
987
805
|
return response;
|
|
988
806
|
}
|
|
989
807
|
/**
|
|
@@ -1101,12 +919,6 @@ export class RpcDataSource extends AbstractDataSource {
|
|
|
1101
919
|
...response.errors,
|
|
1102
920
|
};
|
|
1103
921
|
}
|
|
1104
|
-
if (response.updateMode) {
|
|
1105
|
-
context.response = {
|
|
1106
|
-
...context.response,
|
|
1107
|
-
updateMode: response.updateMode,
|
|
1108
|
-
};
|
|
1109
|
-
}
|
|
1110
922
|
const failedChains = new Set(Object.keys(response.errors ?? {}));
|
|
1111
923
|
successfullyHandledChains = supportedChains.filter((chainId) => !failedChains.has(chainId));
|
|
1112
924
|
if (successfullyHandledChains.length > 0) {
|
|
@@ -1186,6 +998,8 @@ export class RpcDataSource extends AbstractDataSource {
|
|
|
1186
998
|
};
|
|
1187
999
|
const balanceToken = this.#balanceFetcher.startPolling(balanceInput);
|
|
1188
1000
|
balancePollingTokens.push(balanceToken);
|
|
1001
|
+
// Token detection is only relevant for "regular" subscriptions —
|
|
1002
|
+
// a customAssetsOnly subscription should never run detection.
|
|
1189
1003
|
if (request.customAssetsOnly !== true &&
|
|
1190
1004
|
this.#tokenDetectionEnabled() &&
|
|
1191
1005
|
this.#useExternalService()) {
|
|
@@ -1235,118 +1049,6 @@ export class RpcDataSource extends AbstractDataSource {
|
|
|
1235
1049
|
log('Unsubscribed and stopped polling', { subscriptionId });
|
|
1236
1050
|
}
|
|
1237
1051
|
}
|
|
1238
|
-
/**
|
|
1239
|
-
* v5 entries for one account-chain fetch: native plus the ERC-20s the
|
|
1240
|
-
* caller scoped via `request.customAssets`. Unchanged from the pre-v6
|
|
1241
|
-
* behavior; delete with the rest of the v5 path.
|
|
1242
|
-
*
|
|
1243
|
-
* @param request - The data request being fetched.
|
|
1244
|
-
* @param chainId - Chain being fetched.
|
|
1245
|
-
* @param shouldSkipNative - When true, omit the native entry.
|
|
1246
|
-
* @returns Entries to hand to the balance fetcher.
|
|
1247
|
-
*/
|
|
1248
|
-
#getAssetsToFetchV5(request, chainId, shouldSkipNative) {
|
|
1249
|
-
const assetsToFetch = [];
|
|
1250
|
-
if (!shouldSkipNative) {
|
|
1251
|
-
// Build a single AssetFetchEntry[] for native + custom ERC-20s
|
|
1252
|
-
assetsToFetch.push({
|
|
1253
|
-
assetId: this.#getNativeAssetForChain(chainId),
|
|
1254
|
-
address: ZERO_ADDRESS,
|
|
1255
|
-
});
|
|
1256
|
-
}
|
|
1257
|
-
if (request.customAssets) {
|
|
1258
|
-
const existingMetadata = this.#getExistingAssetsMetadata();
|
|
1259
|
-
for (const assetId of request.customAssets) {
|
|
1260
|
-
try {
|
|
1261
|
-
const parsed = parseCaipAssetType(assetId);
|
|
1262
|
-
const assetChainId = `${parsed.chain.namespace}:${parsed.chain.reference}`;
|
|
1263
|
-
if (assetChainId === chainId &&
|
|
1264
|
-
this.#getAssetType(assetId) === 'erc20') {
|
|
1265
|
-
const tokenAddress = parsed.assetReference.toLowerCase();
|
|
1266
|
-
const normalizedId = normalizeAssetId(assetId);
|
|
1267
|
-
const decimals = existingMetadata[normalizedId]?.decimals;
|
|
1268
|
-
assetsToFetch.push({
|
|
1269
|
-
assetId,
|
|
1270
|
-
address: tokenAddress,
|
|
1271
|
-
decimals,
|
|
1272
|
-
});
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
catch {
|
|
1276
|
-
// Skip unparseable asset IDs
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
|
-
return assetsToFetch;
|
|
1281
|
-
}
|
|
1282
|
-
/**
|
|
1283
|
-
* v6 entries for one account-chain fetch: the shared visible set (native,
|
|
1284
|
-
* pins, default tracked) plus already-tracked ERC-20s that are not hidden.
|
|
1285
|
-
* Chains with no native token omit native from `visibleAssetIds`. Hide is
|
|
1286
|
-
* only applied to `assetsBalance` rows. The request is never scoped via
|
|
1287
|
-
* `customAssets` on v6.
|
|
1288
|
-
*
|
|
1289
|
-
* @param accountId - Account being fetched.
|
|
1290
|
-
* @param chainId - Chain being fetched.
|
|
1291
|
-
* @returns Entries to hand to the balance fetcher.
|
|
1292
|
-
*/
|
|
1293
|
-
#getAssetsToFetchV6(accountId, chainId) {
|
|
1294
|
-
const { assetsInfo = {}, assetsBalance = {} } = this.#getAssetsState();
|
|
1295
|
-
const { visibleAssetIds, hiddenAssetIds } = this.#getAssetVisibility([accountId], [chainId]);
|
|
1296
|
-
const hidden = new Set(hiddenAssetIds);
|
|
1297
|
-
const assetsToFetch = [];
|
|
1298
|
-
const seen = new Set();
|
|
1299
|
-
// Fetch visible assets
|
|
1300
|
-
for (const assetId of visibleAssetIds) {
|
|
1301
|
-
try {
|
|
1302
|
-
if (this.#getAssetType(assetId) === 'native') {
|
|
1303
|
-
assetsToFetch.push({ assetId, address: ZERO_ADDRESS });
|
|
1304
|
-
continue;
|
|
1305
|
-
}
|
|
1306
|
-
const parsed = parseCaipAssetType(assetId);
|
|
1307
|
-
const assetChainId = `${parsed.chain.namespace}:${parsed.chain.reference}`;
|
|
1308
|
-
if (assetChainId !== chainId ||
|
|
1309
|
-
this.#getAssetType(assetId) !== 'erc20' ||
|
|
1310
|
-
seen.has(assetId)) {
|
|
1311
|
-
continue;
|
|
1312
|
-
}
|
|
1313
|
-
seen.add(assetId);
|
|
1314
|
-
assetsToFetch.push({
|
|
1315
|
-
assetId,
|
|
1316
|
-
address: parsed.assetReference.toLowerCase(),
|
|
1317
|
-
decimals: assetsInfo[assetId]?.decimals,
|
|
1318
|
-
});
|
|
1319
|
-
}
|
|
1320
|
-
catch {
|
|
1321
|
-
// Skip unparseable asset IDs
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
// Fetch already-tracked ERC-20s that are not hidden
|
|
1325
|
-
for (const assetId of Object.keys(assetsBalance[accountId] ?? {})) {
|
|
1326
|
-
try {
|
|
1327
|
-
const parsed = parseCaipAssetType(assetId);
|
|
1328
|
-
const assetChainId = `${parsed.chain.namespace}:${parsed.chain.reference}`;
|
|
1329
|
-
const normalizedId = normalizeAssetId(assetId);
|
|
1330
|
-
if (hidden.has(normalizedId) ||
|
|
1331
|
-
isStakingContractAssetId(assetId) ||
|
|
1332
|
-
assetChainId !== chainId ||
|
|
1333
|
-
this.#getAssetType(assetId) !== 'erc20' ||
|
|
1334
|
-
seen.has(normalizedId)) {
|
|
1335
|
-
continue;
|
|
1336
|
-
}
|
|
1337
|
-
seen.add(normalizedId);
|
|
1338
|
-
assetsToFetch.push({
|
|
1339
|
-
assetId,
|
|
1340
|
-
address: parsed.assetReference.toLowerCase(),
|
|
1341
|
-
decimals: assetsInfo[normalizedId]?.decimals,
|
|
1342
|
-
});
|
|
1343
|
-
}
|
|
1344
|
-
catch {
|
|
1345
|
-
// Skip unparseable asset IDs
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
return assetsToFetch;
|
|
1349
|
-
}
|
|
1350
1052
|
/**
|
|
1351
1053
|
* Get existing assets metadata from AssetsController state.
|
|
1352
1054
|
* Used to include metadata for ERC20 tokens when returning balance updates.
|
|
@@ -1354,7 +1056,14 @@ export class RpcDataSource extends AbstractDataSource {
|
|
|
1354
1056
|
* @returns Record of asset IDs to their metadata.
|
|
1355
1057
|
*/
|
|
1356
1058
|
#getExistingAssetsMetadata() {
|
|
1357
|
-
|
|
1059
|
+
try {
|
|
1060
|
+
const state = this.#messenger.call('AssetsController:getState');
|
|
1061
|
+
return state.assetsInfo ?? {};
|
|
1062
|
+
}
|
|
1063
|
+
catch (error) {
|
|
1064
|
+
log('Failed to get existing assets metadata', { error });
|
|
1065
|
+
return {};
|
|
1066
|
+
}
|
|
1358
1067
|
}
|
|
1359
1068
|
/**
|
|
1360
1069
|
* Destroy the data source and clean up resources.
|