@metamask-previews/assets-controller 0.0.0-preview-e09bf49f → 0.0.0-preview-9c68b732

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/dist/AssetsController.cjs +26 -32
  3. package/dist/AssetsController.cjs.map +1 -1
  4. package/dist/AssetsController.d.cts +5 -1
  5. package/dist/AssetsController.d.cts.map +1 -1
  6. package/dist/AssetsController.d.mts +5 -1
  7. package/dist/AssetsController.d.mts.map +1 -1
  8. package/dist/AssetsController.mjs +26 -32
  9. package/dist/AssetsController.mjs.map +1 -1
  10. package/dist/data-sources/PriceDataSource.cjs +12 -19
  11. package/dist/data-sources/PriceDataSource.cjs.map +1 -1
  12. package/dist/data-sources/PriceDataSource.d.cts.map +1 -1
  13. package/dist/data-sources/PriceDataSource.d.mts.map +1 -1
  14. package/dist/data-sources/PriceDataSource.mjs +12 -19
  15. package/dist/data-sources/PriceDataSource.mjs.map +1 -1
  16. package/dist/data-sources/RpcDataSource.cjs +232 -42
  17. package/dist/data-sources/RpcDataSource.cjs.map +1 -1
  18. package/dist/data-sources/RpcDataSource.d.cts +5 -1
  19. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  20. package/dist/data-sources/RpcDataSource.d.mts +5 -1
  21. package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
  22. package/dist/data-sources/RpcDataSource.mjs +229 -42
  23. package/dist/data-sources/RpcDataSource.mjs.map +1 -1
  24. package/dist/data-sources/evm-rpc-services/index.cjs.map +1 -1
  25. package/dist/data-sources/evm-rpc-services/index.d.cts +1 -1
  26. package/dist/data-sources/evm-rpc-services/index.d.cts.map +1 -1
  27. package/dist/data-sources/evm-rpc-services/index.d.mts +1 -1
  28. package/dist/data-sources/evm-rpc-services/index.d.mts.map +1 -1
  29. package/dist/data-sources/evm-rpc-services/index.mjs.map +1 -1
  30. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.cjs +32 -27
  31. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.cjs.map +1 -1
  32. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.cts +12 -5
  33. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.cts.map +1 -1
  34. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.mts +12 -5
  35. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.mts.map +1 -1
  36. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.mjs +32 -27
  37. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.mjs.map +1 -1
  38. package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs +23 -12
  39. package/dist/data-sources/evm-rpc-services/services/TokenDetector.cjs.map +1 -1
  40. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts +8 -3
  41. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.cts.map +1 -1
  42. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts +8 -3
  43. package/dist/data-sources/evm-rpc-services/services/TokenDetector.d.mts.map +1 -1
  44. package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs +23 -12
  45. package/dist/data-sources/evm-rpc-services/services/TokenDetector.mjs.map +1 -1
  46. package/dist/data-sources/evm-rpc-services/services/index.cjs.map +1 -1
  47. package/dist/data-sources/evm-rpc-services/services/index.d.cts +2 -2
  48. package/dist/data-sources/evm-rpc-services/services/index.d.cts.map +1 -1
  49. package/dist/data-sources/evm-rpc-services/services/index.d.mts +2 -2
  50. package/dist/data-sources/evm-rpc-services/services/index.d.mts.map +1 -1
  51. package/dist/data-sources/evm-rpc-services/services/index.mjs.map +1 -1
  52. package/dist/data-sources/evm-rpc-services/types/index.cjs.map +1 -1
  53. package/dist/data-sources/evm-rpc-services/types/index.d.cts +1 -1
  54. package/dist/data-sources/evm-rpc-services/types/index.d.cts.map +1 -1
  55. package/dist/data-sources/evm-rpc-services/types/index.d.mts +1 -1
  56. package/dist/data-sources/evm-rpc-services/types/index.d.mts.map +1 -1
  57. package/dist/data-sources/evm-rpc-services/types/index.mjs.map +1 -1
  58. package/dist/data-sources/evm-rpc-services/types/state.cjs.map +1 -1
  59. package/dist/data-sources/evm-rpc-services/types/state.d.cts +9 -24
  60. package/dist/data-sources/evm-rpc-services/types/state.d.cts.map +1 -1
  61. package/dist/data-sources/evm-rpc-services/types/state.d.mts +9 -24
  62. package/dist/data-sources/evm-rpc-services/types/state.d.mts.map +1 -1
  63. package/dist/data-sources/evm-rpc-services/types/state.mjs.map +1 -1
  64. package/dist/types.cjs.map +1 -1
  65. package/dist/types.d.cts +40 -6
  66. package/dist/types.d.cts.map +1 -1
  67. package/dist/types.d.mts +40 -6
  68. package/dist/types.d.mts.map +1 -1
  69. package/dist/types.mjs.map +1 -1
  70. package/package.json +1 -1
@@ -9,11 +9,12 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _RpcDataSource_instances, _RpcDataSource_timeout, _RpcDataSource_tokenDetectionEnabled, _RpcDataSource_activeChains, _RpcDataSource_chainStatuses, _RpcDataSource_providerCache, _RpcDataSource_activeSubscriptions, _RpcDataSource_multicallClient, _RpcDataSource_balanceFetcher, _RpcDataSource_tokenDetector, _RpcDataSource_handleBalanceUpdate, _RpcDataSource_handleDetectionUpdate, _RpcDataSource_registerActionHandlers, _RpcDataSource_subscribeToNetworkController, _RpcDataSource_initializeFromNetworkController, _RpcDataSource_updateFromNetworkState, _RpcDataSource_getProvider, _RpcDataSource_getMulticallProvider, _RpcDataSource_clearProviderCache, _RpcDataSource_accountSupportsChain, _RpcDataSource_buildNativeAssetId;
12
+ var _RpcDataSource_instances, _RpcDataSource_timeout, _RpcDataSource_tokenDetectionEnabled, _RpcDataSource_activeChains, _RpcDataSource_chainStatuses, _RpcDataSource_providerCache, _RpcDataSource_activeSubscriptions, _RpcDataSource_multicallClient, _RpcDataSource_balanceFetcher, _RpcDataSource_tokenDetector, _RpcDataSource_convertToHumanReadable, _RpcDataSource_collectMetadataForBalances, _RpcDataSource_handleBalanceUpdate, _RpcDataSource_handleDetectionUpdate, _RpcDataSource_registerActionHandlers, _RpcDataSource_subscribeToNetworkController, _RpcDataSource_initializeFromNetworkController, _RpcDataSource_updateFromNetworkState, _RpcDataSource_getProvider, _RpcDataSource_getMulticallProvider, _RpcDataSource_clearProviderCache, _RpcDataSource_accountSupportsChain, _RpcDataSource_buildNativeAssetId, _RpcDataSource_getExistingAssetsMetadata, _RpcDataSource_getTokenMetadataFromTokenList;
13
13
  import { Web3Provider } from "@ethersproject/providers";
14
14
  import { BaseController } from "@metamask/base-controller";
15
15
  import { toHex } from "@metamask/controller-utils";
16
16
  import { isStrictHexString, isCaipChainId, parseCaipChainId } from "@metamask/utils";
17
+ import BigNumberJS from "bignumber.js";
17
18
  import { BalanceFetcher, MulticallClient, TokenDetector } from "./evm-rpc-services/index.mjs";
18
19
  import { projectLogger, createModuleLogger } from "../logger.mjs";
19
20
  const CONTROLLER_NAME = 'RpcDataSource';
@@ -90,24 +91,25 @@ export class RpcDataSource extends BaseController {
90
91
  __classPrivateFieldSet(this, _RpcDataSource_multicallClient, new MulticallClient((hexChainId) => {
91
92
  return __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_getMulticallProvider).call(this, hexChainId);
92
93
  }), "f");
93
- // Create state getters using messenger
94
- const getTokenListState = () => {
95
- return this.messenger.call('TokenListController:getState');
94
+ // Create messenger adapters for BalanceFetcher and TokenDetector
95
+ const balanceFetcherMessenger = {
96
+ call: (_action) => {
97
+ const state = this.messenger.call('AssetsController:getState');
98
+ return {
99
+ assetsBalance: (state.assetsBalance ?? {}),
100
+ };
101
+ },
96
102
  };
97
- const getUserTokensState = () => {
98
- return this.messenger.call('AssetsController:getState');
103
+ const tokenDetectorMessenger = {
104
+ call: (_action) => {
105
+ return this.messenger.call('TokenListController:getState');
106
+ },
99
107
  };
100
108
  // Initialize BalanceFetcher with polling interval
101
- __classPrivateFieldSet(this, _RpcDataSource_balanceFetcher, new BalanceFetcher(__classPrivateFieldGet(this, _RpcDataSource_multicallClient, "f"), {
102
- pollingInterval: balanceInterval,
103
- }), "f");
104
- __classPrivateFieldGet(this, _RpcDataSource_balanceFetcher, "f").setUserTokensStateGetter(getUserTokensState);
109
+ __classPrivateFieldSet(this, _RpcDataSource_balanceFetcher, new BalanceFetcher(__classPrivateFieldGet(this, _RpcDataSource_multicallClient, "f"), balanceFetcherMessenger, { pollingInterval: balanceInterval }), "f");
105
110
  __classPrivateFieldGet(this, _RpcDataSource_balanceFetcher, "f").setOnBalanceUpdate(__classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_handleBalanceUpdate).bind(this));
106
111
  // Initialize TokenDetector with polling interval
107
- __classPrivateFieldSet(this, _RpcDataSource_tokenDetector, new TokenDetector(__classPrivateFieldGet(this, _RpcDataSource_multicallClient, "f"), {
108
- pollingInterval: detectionInterval,
109
- }), "f");
110
- __classPrivateFieldGet(this, _RpcDataSource_tokenDetector, "f").setTokenListStateGetter(getTokenListState);
112
+ __classPrivateFieldSet(this, _RpcDataSource_tokenDetector, new TokenDetector(__classPrivateFieldGet(this, _RpcDataSource_multicallClient, "f"), tokenDetectorMessenger, { pollingInterval: detectionInterval }), "f");
111
113
  __classPrivateFieldGet(this, _RpcDataSource_tokenDetector, "f").setOnDetectionUpdate(__classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_handleDetectionUpdate).bind(this));
112
114
  __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_registerActionHandlers).call(this);
113
115
  __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_subscribeToNetworkController).call(this);
@@ -193,6 +195,7 @@ export class RpcDataSource extends BaseController {
193
195
  return response;
194
196
  }
195
197
  const assetsBalance = {};
198
+ const assetsMetadata = {};
196
199
  const failedChains = [];
197
200
  // Fetch balances for each chain using BalanceFetcher
198
201
  for (const chainId of chainsToFetch) {
@@ -209,10 +212,17 @@ export class RpcDataSource extends BaseController {
209
212
  if (!assetsBalance[accountId]) {
210
213
  assetsBalance[accountId] = {};
211
214
  }
212
- // Convert balances to response format
215
+ // Collect metadata for all balances
216
+ const balanceMetadata = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_collectMetadataForBalances).call(this, result.balances, chainId);
217
+ Object.assign(assetsMetadata, balanceMetadata);
218
+ // Convert balances to human-readable format
213
219
  for (const balance of result.balances) {
220
+ const metadata = assetsMetadata[balance.assetId];
221
+ // Default to 18 decimals (ERC20 standard) for consistent human-readable format
222
+ const decimals = metadata?.decimals ?? 18;
223
+ const humanReadableAmount = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_convertToHumanReadable).call(this, balance.balance, decimals);
214
224
  assetsBalance[accountId][balance.assetId] = {
215
- amount: balance.balance,
225
+ amount: humanReadableAmount,
216
226
  };
217
227
  }
218
228
  }
@@ -223,6 +233,16 @@ export class RpcDataSource extends BaseController {
223
233
  }
224
234
  const nativeAssetId = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_buildNativeAssetId).call(this, chainId);
225
235
  assetsBalance[accountId][nativeAssetId] = { amount: '0' };
236
+ // Even on error, include native token metadata
237
+ const chainStatus = __classPrivateFieldGet(this, _RpcDataSource_chainStatuses, "f")[chainId];
238
+ if (chainStatus) {
239
+ assetsMetadata[nativeAssetId] = {
240
+ type: 'native',
241
+ symbol: chainStatus.nativeCurrency,
242
+ name: chainStatus.nativeCurrency,
243
+ decimals: 18,
244
+ };
245
+ }
226
246
  if (!failedChains.includes(chainId)) {
227
247
  failedChains.push(chainId);
228
248
  }
@@ -246,6 +266,10 @@ export class RpcDataSource extends BaseController {
246
266
  });
247
267
  }
248
268
  response.assetsBalance = assetsBalance;
269
+ // Include metadata for native tokens if we have any
270
+ if (Object.keys(assetsMetadata).length > 0) {
271
+ response.assetsMetadata = assetsMetadata;
272
+ }
249
273
  return response;
250
274
  }
251
275
  /**
@@ -275,10 +299,27 @@ export class RpcDataSource extends BaseController {
275
299
  });
276
300
  // Convert detected assets to DataResponse format
277
301
  const balances = {};
278
- // Add balances for detected tokens
302
+ const assetsMetadata = {};
303
+ // Build metadata from detected assets
304
+ for (const asset of result.detectedAssets) {
305
+ if (asset.symbol && asset.decimals !== undefined) {
306
+ assetsMetadata[asset.assetId] = {
307
+ type: 'erc20',
308
+ symbol: asset.symbol,
309
+ name: asset.name ?? asset.symbol,
310
+ decimals: asset.decimals,
311
+ image: asset.image,
312
+ };
313
+ }
314
+ }
315
+ // Add balances for detected tokens (converted to human-readable format)
279
316
  for (const balance of result.detectedBalances) {
317
+ const detectedAsset = result.detectedAssets.find((asset) => asset.assetId === balance.assetId);
318
+ // Default to 18 decimals (ERC20 standard) for consistent human-readable format
319
+ const decimals = detectedAsset?.decimals ?? 18;
320
+ const humanReadableAmount = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_convertToHumanReadable).call(this, balance.balance, decimals);
280
321
  balances[balance.assetId] = {
281
- amount: balance.balance,
322
+ amount: humanReadableAmount,
282
323
  };
283
324
  }
284
325
  const response = {
@@ -289,6 +330,10 @@ export class RpcDataSource extends BaseController {
289
330
  [accountId]: balances,
290
331
  },
291
332
  };
333
+ // Include metadata if we have any
334
+ if (Object.keys(assetsMetadata).length > 0) {
335
+ response.assetsMetadata = assetsMetadata;
336
+ }
292
337
  return response;
293
338
  }
294
339
  catch (error) {
@@ -298,7 +343,7 @@ export class RpcDataSource extends BaseController {
298
343
  }
299
344
  get assetsMiddleware() {
300
345
  return async (context, next) => {
301
- var _a, _b;
346
+ var _a, _b, _c;
302
347
  const { request } = context;
303
348
  const supportedChains = request.chainIds.filter((chainId) => __classPrivateFieldGet(this, _RpcDataSource_activeChains, "f").includes(chainId));
304
349
  if (supportedChains.length === 0) {
@@ -323,6 +368,13 @@ export class RpcDataSource extends BaseController {
323
368
  };
324
369
  }
325
370
  }
371
+ if (response.assetsMetadata) {
372
+ (_c = context.response).assetsMetadata ?? (_c.assetsMetadata = {});
373
+ context.response.assetsMetadata = {
374
+ ...context.response.assetsMetadata,
375
+ ...response.assetsMetadata,
376
+ };
377
+ }
326
378
  const failedChains = new Set(Object.keys(response.errors ?? {}));
327
379
  successfullyHandledChains = supportedChains.filter((chainId) => !failedChains.has(chainId));
328
380
  if (successfullyHandledChains.length > 0) {
@@ -357,20 +409,19 @@ export class RpcDataSource extends BaseController {
357
409
  log('No active chains to subscribe');
358
410
  return;
359
411
  }
360
- // Handle subscription update
412
+ // Handle subscription update - restart polling for new chains
361
413
  if (isUpdate) {
362
414
  const existing = __classPrivateFieldGet(this, _RpcDataSource_activeSubscriptions, "f").get(subscriptionId);
363
415
  if (existing) {
364
- log('Updating existing subscription', {
416
+ log('Updating existing subscription - restarting polling', {
365
417
  subscriptionId,
366
- chainsToSubscribe,
418
+ existingChains: existing.chains,
419
+ newChains: chainsToSubscribe,
367
420
  });
368
- existing.chains = chainsToSubscribe;
369
- existing.accounts = request.accounts;
370
- return;
421
+ // Don't return early - continue to unsubscribe and restart polling
371
422
  }
372
423
  }
373
- // Clean up existing subscription
424
+ // Clean up existing subscription (stops old polling)
374
425
  await this.unsubscribe(subscriptionId);
375
426
  // Start polling through BalanceFetcher and TokenDetector
376
427
  const balancePollingTokens = [];
@@ -450,18 +501,82 @@ export class RpcDataSource extends BaseController {
450
501
  __classPrivateFieldGet(this, _RpcDataSource_providerCache, "f").clear();
451
502
  }
452
503
  }
453
- _RpcDataSource_timeout = new WeakMap(), _RpcDataSource_tokenDetectionEnabled = new WeakMap(), _RpcDataSource_activeChains = new WeakMap(), _RpcDataSource_chainStatuses = new WeakMap(), _RpcDataSource_providerCache = new WeakMap(), _RpcDataSource_activeSubscriptions = new WeakMap(), _RpcDataSource_multicallClient = new WeakMap(), _RpcDataSource_balanceFetcher = new WeakMap(), _RpcDataSource_tokenDetector = new WeakMap(), _RpcDataSource_instances = new WeakSet(), _RpcDataSource_handleBalanceUpdate = function _RpcDataSource_handleBalanceUpdate(result) {
454
- const accountBalances = {};
504
+ _RpcDataSource_timeout = new WeakMap(), _RpcDataSource_tokenDetectionEnabled = new WeakMap(), _RpcDataSource_activeChains = new WeakMap(), _RpcDataSource_chainStatuses = new WeakMap(), _RpcDataSource_providerCache = new WeakMap(), _RpcDataSource_activeSubscriptions = new WeakMap(), _RpcDataSource_multicallClient = new WeakMap(), _RpcDataSource_balanceFetcher = new WeakMap(), _RpcDataSource_tokenDetector = new WeakMap(), _RpcDataSource_instances = new WeakSet(), _RpcDataSource_convertToHumanReadable = function _RpcDataSource_convertToHumanReadable(rawBalance, decimals) {
505
+ const rawAmount = new BigNumberJS(rawBalance);
506
+ const divisor = new BigNumberJS(10).pow(decimals);
507
+ return rawAmount.dividedBy(divisor).toString();
508
+ }, _RpcDataSource_collectMetadataForBalances = function _RpcDataSource_collectMetadataForBalances(balances, chainId) {
509
+ const assetsMetadata = {};
510
+ const existingMetadata = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_getExistingAssetsMetadata).call(this);
511
+ for (const balance of balances) {
512
+ const isNative = balance.assetId.includes('/slip44:');
513
+ if (isNative) {
514
+ const chainStatus = __classPrivateFieldGet(this, _RpcDataSource_chainStatuses, "f")[chainId];
515
+ if (chainStatus) {
516
+ assetsMetadata[balance.assetId] = {
517
+ type: 'native',
518
+ symbol: chainStatus.nativeCurrency,
519
+ name: chainStatus.nativeCurrency,
520
+ decimals: 18,
521
+ };
522
+ }
523
+ }
524
+ else {
525
+ // For ERC20 tokens, try existing metadata from state first
526
+ const existingMeta = existingMetadata[balance.assetId];
527
+ if (existingMeta) {
528
+ assetsMetadata[balance.assetId] = existingMeta;
529
+ }
530
+ else {
531
+ // Fallback to token list if not in state
532
+ const tokenListMeta = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_getTokenMetadataFromTokenList).call(this, balance.assetId);
533
+ if (tokenListMeta) {
534
+ assetsMetadata[balance.assetId] = tokenListMeta;
535
+ }
536
+ else {
537
+ // Default metadata for unknown ERC20 tokens.
538
+ // Use 18 decimals (the standard for most ERC20 tokens)
539
+ // to ensure consistent human-readable balance format.
540
+ assetsMetadata[balance.assetId] = {
541
+ type: 'erc20',
542
+ symbol: '',
543
+ name: '',
544
+ decimals: 18,
545
+ };
546
+ }
547
+ }
548
+ }
549
+ }
550
+ return assetsMetadata;
551
+ }, _RpcDataSource_handleBalanceUpdate = function _RpcDataSource_handleBalanceUpdate(result) {
552
+ const newBalances = {};
553
+ // Convert hex chain ID to CAIP-2 format
554
+ const chainIdDecimal = parseInt(result.chainId, 16);
555
+ const caipChainId = `eip155:${chainIdDecimal}`;
556
+ // Collect metadata for all balances
557
+ const assetsMetadata = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_collectMetadataForBalances).call(this, result.balances, caipChainId);
558
+ // Convert balances to human-readable format using metadata
455
559
  for (const balance of result.balances) {
456
- accountBalances[balance.assetId] = {
457
- amount: balance.balance,
560
+ const metadata = assetsMetadata[balance.assetId];
561
+ // Default to 18 decimals (ERC20 standard) for consistent human-readable format
562
+ const decimals = metadata?.decimals ?? 18;
563
+ const humanReadableAmount = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_convertToHumanReadable).call(this, balance.balance, decimals);
564
+ newBalances[balance.assetId] = {
565
+ amount: humanReadableAmount,
458
566
  };
459
567
  }
568
+ // Only send new data to AssetsController - it handles merging atomically
569
+ // to avoid race conditions when concurrent updates occur for the same account
460
570
  const response = {
461
571
  assetsBalance: {
462
- [result.accountId]: accountBalances,
572
+ [result.accountId]: newBalances,
463
573
  },
574
+ assetsMetadata,
464
575
  };
576
+ log('Balance update response', {
577
+ accountId: result.accountId,
578
+ newBalanceCount: Object.keys(newBalances).length,
579
+ });
465
580
  this.messenger
466
581
  .call('AssetsController:assetsUpdate', response, CONTROLLER_NAME)
467
582
  .catch((error) => {
@@ -471,22 +586,47 @@ _RpcDataSource_timeout = new WeakMap(), _RpcDataSource_tokenDetectionEnabled = n
471
586
  log('Detected new tokens', {
472
587
  count: result.detectedAssets.length,
473
588
  });
474
- const response = {
475
- detectedAssets: {
476
- [result.accountId]: result.detectedAssets.map((asset) => asset.assetId),
477
- },
478
- };
479
- // Add balances for detected tokens
589
+ // Build new metadata from detected assets
590
+ const newMetadata = {};
591
+ if (result.detectedAssets.length > 0) {
592
+ for (const asset of result.detectedAssets) {
593
+ // Only include if we have metadata (symbol and decimals at minimum)
594
+ if (asset.symbol && asset.decimals !== undefined) {
595
+ newMetadata[asset.assetId] = {
596
+ type: 'erc20',
597
+ symbol: asset.symbol,
598
+ name: asset.name ?? asset.symbol,
599
+ decimals: asset.decimals,
600
+ image: asset.image,
601
+ };
602
+ }
603
+ }
604
+ }
605
+ // Build new balances from detected tokens
606
+ const newBalances = {};
480
607
  if (result.detectedBalances.length > 0) {
481
- response.assetsBalance = {
482
- [result.accountId]: {},
483
- };
484
608
  for (const balance of result.detectedBalances) {
485
- response.assetsBalance[result.accountId][balance.assetId] = {
486
- amount: balance.balance,
609
+ // Get decimals from the detected asset metadata
610
+ const detectedAsset = result.detectedAssets.find((asset) => asset.assetId === balance.assetId);
611
+ // Default to 18 decimals (ERC20 standard) for consistent human-readable format
612
+ const decimals = detectedAsset?.decimals ?? 18;
613
+ const humanReadableAmount = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_convertToHumanReadable).call(this, balance.balance, decimals);
614
+ newBalances[balance.assetId] = {
615
+ amount: humanReadableAmount,
487
616
  };
488
617
  }
489
618
  }
619
+ // Only send new data to AssetsController - it handles merging atomically
620
+ // to avoid race conditions when concurrent updates occur for the same account
621
+ const response = {
622
+ detectedAssets: {
623
+ [result.accountId]: result.detectedAssets.map((asset) => asset.assetId),
624
+ },
625
+ assetsMetadata: newMetadata,
626
+ assetsBalance: {
627
+ [result.accountId]: newBalances,
628
+ },
629
+ };
490
630
  this.messenger
491
631
  .call('AssetsController:assetsUpdate', response, CONTROLLER_NAME)
492
632
  .catch((error) => {
@@ -626,6 +766,53 @@ _RpcDataSource_timeout = new WeakMap(), _RpcDataSource_tokenDetectionEnabled = n
626
766
  const { nativeAssetIdentifiers } = this.messenger.call('NetworkEnablementController:getState');
627
767
  return (nativeAssetIdentifiers[chainId] ??
628
768
  `${chainId}/slip44:60`);
769
+ }, _RpcDataSource_getExistingAssetsMetadata = function _RpcDataSource_getExistingAssetsMetadata() {
770
+ try {
771
+ const state = this.messenger.call('AssetsController:getState');
772
+ return (state.assetsMetadata ?? {});
773
+ }
774
+ catch {
775
+ // If AssetsController:getState fails, return empty metadata
776
+ return {};
777
+ }
778
+ }, _RpcDataSource_getTokenMetadataFromTokenList = function _RpcDataSource_getTokenMetadataFromTokenList(assetId) {
779
+ try {
780
+ // Parse asset ID to get chain and token address
781
+ // Format: eip155:{chainId}/erc20:{address}
782
+ const [chainPart, assetPart] = assetId.split('/');
783
+ if (!assetPart?.startsWith('erc20:')) {
784
+ return undefined;
785
+ }
786
+ const tokenAddress = assetPart.slice(6); // Remove 'erc20:' prefix
787
+ const chainIdDecimal = chainPart.split(':')[1];
788
+ const hexChainId = `0x${parseInt(chainIdDecimal, 10).toString(16)}`;
789
+ const tokenListState = this.messenger.call('TokenListController:getState');
790
+ const chainCacheEntry = tokenListState.tokensChainsCache[hexChainId];
791
+ const chainTokenList = chainCacheEntry?.data;
792
+ if (!chainTokenList) {
793
+ return undefined;
794
+ }
795
+ // Look up token by address (case-insensitive)
796
+ const lowerAddress = tokenAddress.toLowerCase();
797
+ for (const [address, tokenData] of Object.entries(chainTokenList)) {
798
+ if (address.toLowerCase() === lowerAddress) {
799
+ const token = tokenData;
800
+ if (token.symbol && token.decimals !== undefined) {
801
+ return {
802
+ type: 'erc20',
803
+ symbol: token.symbol,
804
+ name: token.name ?? token.symbol,
805
+ decimals: token.decimals,
806
+ image: token.iconUrl,
807
+ };
808
+ }
809
+ }
810
+ }
811
+ return undefined;
812
+ }
813
+ catch {
814
+ return undefined;
815
+ }
629
816
  };
630
817
  export function createRpcDataSource(options) {
631
818
  return new RpcDataSource(options);