@metamask-previews/assets-controller 0.0.0-preview-e09bf49f → 0.0.0-preview-52f4a2ca

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
@@ -10,13 +10,17 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  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");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- 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;
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ 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;
14
17
  Object.defineProperty(exports, "__esModule", { value: true });
15
18
  exports.createRpcDataSource = exports.RpcDataSource = exports.caipChainIdToHex = void 0;
16
19
  const providers_1 = require("@ethersproject/providers");
17
20
  const base_controller_1 = require("@metamask/base-controller");
18
21
  const controller_utils_1 = require("@metamask/controller-utils");
19
22
  const utils_1 = require("@metamask/utils");
23
+ const bignumber_js_1 = __importDefault(require("bignumber.js"));
20
24
  const evm_rpc_services_1 = require("./evm-rpc-services/index.cjs");
21
25
  const logger_1 = require("../logger.cjs");
22
26
  const CONTROLLER_NAME = 'RpcDataSource';
@@ -94,24 +98,25 @@ class RpcDataSource extends base_controller_1.BaseController {
94
98
  __classPrivateFieldSet(this, _RpcDataSource_multicallClient, new evm_rpc_services_1.MulticallClient((hexChainId) => {
95
99
  return __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_getMulticallProvider).call(this, hexChainId);
96
100
  }), "f");
97
- // Create state getters using messenger
98
- const getTokenListState = () => {
99
- return this.messenger.call('TokenListController:getState');
101
+ // Create messenger adapters for BalanceFetcher and TokenDetector
102
+ const balanceFetcherMessenger = {
103
+ call: (_action) => {
104
+ const state = this.messenger.call('AssetsController:getState');
105
+ return {
106
+ assetsBalance: (state.assetsBalance ?? {}),
107
+ };
108
+ },
100
109
  };
101
- const getUserTokensState = () => {
102
- return this.messenger.call('AssetsController:getState');
110
+ const tokenDetectorMessenger = {
111
+ call: (_action) => {
112
+ return this.messenger.call('TokenListController:getState');
113
+ },
103
114
  };
104
115
  // Initialize BalanceFetcher with polling interval
105
- __classPrivateFieldSet(this, _RpcDataSource_balanceFetcher, new evm_rpc_services_1.BalanceFetcher(__classPrivateFieldGet(this, _RpcDataSource_multicallClient, "f"), {
106
- pollingInterval: balanceInterval,
107
- }), "f");
108
- __classPrivateFieldGet(this, _RpcDataSource_balanceFetcher, "f").setUserTokensStateGetter(getUserTokensState);
116
+ __classPrivateFieldSet(this, _RpcDataSource_balanceFetcher, new evm_rpc_services_1.BalanceFetcher(__classPrivateFieldGet(this, _RpcDataSource_multicallClient, "f"), balanceFetcherMessenger, { pollingInterval: balanceInterval }), "f");
109
117
  __classPrivateFieldGet(this, _RpcDataSource_balanceFetcher, "f").setOnBalanceUpdate(__classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_handleBalanceUpdate).bind(this));
110
118
  // Initialize TokenDetector with polling interval
111
- __classPrivateFieldSet(this, _RpcDataSource_tokenDetector, new evm_rpc_services_1.TokenDetector(__classPrivateFieldGet(this, _RpcDataSource_multicallClient, "f"), {
112
- pollingInterval: detectionInterval,
113
- }), "f");
114
- __classPrivateFieldGet(this, _RpcDataSource_tokenDetector, "f").setTokenListStateGetter(getTokenListState);
119
+ __classPrivateFieldSet(this, _RpcDataSource_tokenDetector, new evm_rpc_services_1.TokenDetector(__classPrivateFieldGet(this, _RpcDataSource_multicallClient, "f"), tokenDetectorMessenger, { pollingInterval: detectionInterval }), "f");
115
120
  __classPrivateFieldGet(this, _RpcDataSource_tokenDetector, "f").setOnDetectionUpdate(__classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_handleDetectionUpdate).bind(this));
116
121
  __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_registerActionHandlers).call(this);
117
122
  __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_subscribeToNetworkController).call(this);
@@ -197,6 +202,7 @@ class RpcDataSource extends base_controller_1.BaseController {
197
202
  return response;
198
203
  }
199
204
  const assetsBalance = {};
205
+ const assetsMetadata = {};
200
206
  const failedChains = [];
201
207
  // Fetch balances for each chain using BalanceFetcher
202
208
  for (const chainId of chainsToFetch) {
@@ -213,10 +219,17 @@ class RpcDataSource extends base_controller_1.BaseController {
213
219
  if (!assetsBalance[accountId]) {
214
220
  assetsBalance[accountId] = {};
215
221
  }
216
- // Convert balances to response format
222
+ // Collect metadata for all balances
223
+ const balanceMetadata = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_collectMetadataForBalances).call(this, result.balances, chainId);
224
+ Object.assign(assetsMetadata, balanceMetadata);
225
+ // Convert balances to human-readable format
217
226
  for (const balance of result.balances) {
227
+ const metadata = assetsMetadata[balance.assetId];
228
+ // Default to 18 decimals (ERC20 standard) for consistent human-readable format
229
+ const decimals = metadata?.decimals ?? 18;
230
+ const humanReadableAmount = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_convertToHumanReadable).call(this, balance.balance, decimals);
218
231
  assetsBalance[accountId][balance.assetId] = {
219
- amount: balance.balance,
232
+ amount: humanReadableAmount,
220
233
  };
221
234
  }
222
235
  }
@@ -227,6 +240,16 @@ class RpcDataSource extends base_controller_1.BaseController {
227
240
  }
228
241
  const nativeAssetId = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_buildNativeAssetId).call(this, chainId);
229
242
  assetsBalance[accountId][nativeAssetId] = { amount: '0' };
243
+ // Even on error, include native token metadata
244
+ const chainStatus = __classPrivateFieldGet(this, _RpcDataSource_chainStatuses, "f")[chainId];
245
+ if (chainStatus) {
246
+ assetsMetadata[nativeAssetId] = {
247
+ type: 'native',
248
+ symbol: chainStatus.nativeCurrency,
249
+ name: chainStatus.nativeCurrency,
250
+ decimals: 18,
251
+ };
252
+ }
230
253
  if (!failedChains.includes(chainId)) {
231
254
  failedChains.push(chainId);
232
255
  }
@@ -250,6 +273,10 @@ class RpcDataSource extends base_controller_1.BaseController {
250
273
  });
251
274
  }
252
275
  response.assetsBalance = assetsBalance;
276
+ // Include metadata for native tokens if we have any
277
+ if (Object.keys(assetsMetadata).length > 0) {
278
+ response.assetsMetadata = assetsMetadata;
279
+ }
253
280
  return response;
254
281
  }
255
282
  /**
@@ -279,10 +306,27 @@ class RpcDataSource extends base_controller_1.BaseController {
279
306
  });
280
307
  // Convert detected assets to DataResponse format
281
308
  const balances = {};
282
- // Add balances for detected tokens
309
+ const assetsMetadata = {};
310
+ // Build metadata from detected assets
311
+ for (const asset of result.detectedAssets) {
312
+ if (asset.symbol && asset.decimals !== undefined) {
313
+ assetsMetadata[asset.assetId] = {
314
+ type: 'erc20',
315
+ symbol: asset.symbol,
316
+ name: asset.name ?? asset.symbol,
317
+ decimals: asset.decimals,
318
+ image: asset.image,
319
+ };
320
+ }
321
+ }
322
+ // Add balances for detected tokens (converted to human-readable format)
283
323
  for (const balance of result.detectedBalances) {
324
+ const detectedAsset = result.detectedAssets.find((asset) => asset.assetId === balance.assetId);
325
+ // Default to 18 decimals (ERC20 standard) for consistent human-readable format
326
+ const decimals = detectedAsset?.decimals ?? 18;
327
+ const humanReadableAmount = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_convertToHumanReadable).call(this, balance.balance, decimals);
284
328
  balances[balance.assetId] = {
285
- amount: balance.balance,
329
+ amount: humanReadableAmount,
286
330
  };
287
331
  }
288
332
  const response = {
@@ -293,6 +337,10 @@ class RpcDataSource extends base_controller_1.BaseController {
293
337
  [accountId]: balances,
294
338
  },
295
339
  };
340
+ // Include metadata if we have any
341
+ if (Object.keys(assetsMetadata).length > 0) {
342
+ response.assetsMetadata = assetsMetadata;
343
+ }
296
344
  return response;
297
345
  }
298
346
  catch (error) {
@@ -302,7 +350,7 @@ class RpcDataSource extends base_controller_1.BaseController {
302
350
  }
303
351
  get assetsMiddleware() {
304
352
  return async (context, next) => {
305
- var _a, _b;
353
+ var _a, _b, _c;
306
354
  const { request } = context;
307
355
  const supportedChains = request.chainIds.filter((chainId) => __classPrivateFieldGet(this, _RpcDataSource_activeChains, "f").includes(chainId));
308
356
  if (supportedChains.length === 0) {
@@ -327,6 +375,13 @@ class RpcDataSource extends base_controller_1.BaseController {
327
375
  };
328
376
  }
329
377
  }
378
+ if (response.assetsMetadata) {
379
+ (_c = context.response).assetsMetadata ?? (_c.assetsMetadata = {});
380
+ context.response.assetsMetadata = {
381
+ ...context.response.assetsMetadata,
382
+ ...response.assetsMetadata,
383
+ };
384
+ }
330
385
  const failedChains = new Set(Object.keys(response.errors ?? {}));
331
386
  successfullyHandledChains = supportedChains.filter((chainId) => !failedChains.has(chainId));
332
387
  if (successfullyHandledChains.length > 0) {
@@ -361,20 +416,19 @@ class RpcDataSource extends base_controller_1.BaseController {
361
416
  log('No active chains to subscribe');
362
417
  return;
363
418
  }
364
- // Handle subscription update
419
+ // Handle subscription update - restart polling for new chains
365
420
  if (isUpdate) {
366
421
  const existing = __classPrivateFieldGet(this, _RpcDataSource_activeSubscriptions, "f").get(subscriptionId);
367
422
  if (existing) {
368
- log('Updating existing subscription', {
423
+ log('Updating existing subscription - restarting polling', {
369
424
  subscriptionId,
370
- chainsToSubscribe,
425
+ existingChains: existing.chains,
426
+ newChains: chainsToSubscribe,
371
427
  });
372
- existing.chains = chainsToSubscribe;
373
- existing.accounts = request.accounts;
374
- return;
428
+ // Don't return early - continue to unsubscribe and restart polling
375
429
  }
376
430
  }
377
- // Clean up existing subscription
431
+ // Clean up existing subscription (stops old polling)
378
432
  await this.unsubscribe(subscriptionId);
379
433
  // Start polling through BalanceFetcher and TokenDetector
380
434
  const balancePollingTokens = [];
@@ -455,18 +509,82 @@ class RpcDataSource extends base_controller_1.BaseController {
455
509
  }
456
510
  }
457
511
  exports.RpcDataSource = RpcDataSource;
458
- _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) {
459
- const accountBalances = {};
512
+ _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) {
513
+ const rawAmount = new bignumber_js_1.default(rawBalance);
514
+ const divisor = new bignumber_js_1.default(10).pow(decimals);
515
+ return rawAmount.dividedBy(divisor).toString();
516
+ }, _RpcDataSource_collectMetadataForBalances = function _RpcDataSource_collectMetadataForBalances(balances, chainId) {
517
+ const assetsMetadata = {};
518
+ const existingMetadata = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_getExistingAssetsMetadata).call(this);
519
+ for (const balance of balances) {
520
+ const isNative = balance.assetId.includes('/slip44:');
521
+ if (isNative) {
522
+ const chainStatus = __classPrivateFieldGet(this, _RpcDataSource_chainStatuses, "f")[chainId];
523
+ if (chainStatus) {
524
+ assetsMetadata[balance.assetId] = {
525
+ type: 'native',
526
+ symbol: chainStatus.nativeCurrency,
527
+ name: chainStatus.nativeCurrency,
528
+ decimals: 18,
529
+ };
530
+ }
531
+ }
532
+ else {
533
+ // For ERC20 tokens, try existing metadata from state first
534
+ const existingMeta = existingMetadata[balance.assetId];
535
+ if (existingMeta) {
536
+ assetsMetadata[balance.assetId] = existingMeta;
537
+ }
538
+ else {
539
+ // Fallback to token list if not in state
540
+ const tokenListMeta = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_getTokenMetadataFromTokenList).call(this, balance.assetId);
541
+ if (tokenListMeta) {
542
+ assetsMetadata[balance.assetId] = tokenListMeta;
543
+ }
544
+ else {
545
+ // Default metadata for unknown ERC20 tokens.
546
+ // Use 18 decimals (the standard for most ERC20 tokens)
547
+ // to ensure consistent human-readable balance format.
548
+ assetsMetadata[balance.assetId] = {
549
+ type: 'erc20',
550
+ symbol: '',
551
+ name: '',
552
+ decimals: 18,
553
+ };
554
+ }
555
+ }
556
+ }
557
+ }
558
+ return assetsMetadata;
559
+ }, _RpcDataSource_handleBalanceUpdate = function _RpcDataSource_handleBalanceUpdate(result) {
560
+ const newBalances = {};
561
+ // Convert hex chain ID to CAIP-2 format
562
+ const chainIdDecimal = parseInt(result.chainId, 16);
563
+ const caipChainId = `eip155:${chainIdDecimal}`;
564
+ // Collect metadata for all balances
565
+ const assetsMetadata = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_collectMetadataForBalances).call(this, result.balances, caipChainId);
566
+ // Convert balances to human-readable format using metadata
460
567
  for (const balance of result.balances) {
461
- accountBalances[balance.assetId] = {
462
- amount: balance.balance,
568
+ const metadata = assetsMetadata[balance.assetId];
569
+ // Default to 18 decimals (ERC20 standard) for consistent human-readable format
570
+ const decimals = metadata?.decimals ?? 18;
571
+ const humanReadableAmount = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_convertToHumanReadable).call(this, balance.balance, decimals);
572
+ newBalances[balance.assetId] = {
573
+ amount: humanReadableAmount,
463
574
  };
464
575
  }
576
+ // Only send new data to AssetsController - it handles merging atomically
577
+ // to avoid race conditions when concurrent updates occur for the same account
465
578
  const response = {
466
579
  assetsBalance: {
467
- [result.accountId]: accountBalances,
580
+ [result.accountId]: newBalances,
468
581
  },
582
+ assetsMetadata,
469
583
  };
584
+ log('Balance update response', {
585
+ accountId: result.accountId,
586
+ newBalanceCount: Object.keys(newBalances).length,
587
+ });
470
588
  this.messenger
471
589
  .call('AssetsController:assetsUpdate', response, CONTROLLER_NAME)
472
590
  .catch((error) => {
@@ -476,22 +594,47 @@ _RpcDataSource_timeout = new WeakMap(), _RpcDataSource_tokenDetectionEnabled = n
476
594
  log('Detected new tokens', {
477
595
  count: result.detectedAssets.length,
478
596
  });
479
- const response = {
480
- detectedAssets: {
481
- [result.accountId]: result.detectedAssets.map((asset) => asset.assetId),
482
- },
483
- };
484
- // Add balances for detected tokens
597
+ // Build new metadata from detected assets
598
+ const newMetadata = {};
599
+ if (result.detectedAssets.length > 0) {
600
+ for (const asset of result.detectedAssets) {
601
+ // Only include if we have metadata (symbol and decimals at minimum)
602
+ if (asset.symbol && asset.decimals !== undefined) {
603
+ newMetadata[asset.assetId] = {
604
+ type: 'erc20',
605
+ symbol: asset.symbol,
606
+ name: asset.name ?? asset.symbol,
607
+ decimals: asset.decimals,
608
+ image: asset.image,
609
+ };
610
+ }
611
+ }
612
+ }
613
+ // Build new balances from detected tokens
614
+ const newBalances = {};
485
615
  if (result.detectedBalances.length > 0) {
486
- response.assetsBalance = {
487
- [result.accountId]: {},
488
- };
489
616
  for (const balance of result.detectedBalances) {
490
- response.assetsBalance[result.accountId][balance.assetId] = {
491
- amount: balance.balance,
617
+ // Get decimals from the detected asset metadata
618
+ const detectedAsset = result.detectedAssets.find((asset) => asset.assetId === balance.assetId);
619
+ // Default to 18 decimals (ERC20 standard) for consistent human-readable format
620
+ const decimals = detectedAsset?.decimals ?? 18;
621
+ const humanReadableAmount = __classPrivateFieldGet(this, _RpcDataSource_instances, "m", _RpcDataSource_convertToHumanReadable).call(this, balance.balance, decimals);
622
+ newBalances[balance.assetId] = {
623
+ amount: humanReadableAmount,
492
624
  };
493
625
  }
494
626
  }
627
+ // Only send new data to AssetsController - it handles merging atomically
628
+ // to avoid race conditions when concurrent updates occur for the same account
629
+ const response = {
630
+ detectedAssets: {
631
+ [result.accountId]: result.detectedAssets.map((asset) => asset.assetId),
632
+ },
633
+ assetsMetadata: newMetadata,
634
+ assetsBalance: {
635
+ [result.accountId]: newBalances,
636
+ },
637
+ };
495
638
  this.messenger
496
639
  .call('AssetsController:assetsUpdate', response, CONTROLLER_NAME)
497
640
  .catch((error) => {
@@ -631,6 +774,53 @@ _RpcDataSource_timeout = new WeakMap(), _RpcDataSource_tokenDetectionEnabled = n
631
774
  const { nativeAssetIdentifiers } = this.messenger.call('NetworkEnablementController:getState');
632
775
  return (nativeAssetIdentifiers[chainId] ??
633
776
  `${chainId}/slip44:60`);
777
+ }, _RpcDataSource_getExistingAssetsMetadata = function _RpcDataSource_getExistingAssetsMetadata() {
778
+ try {
779
+ const state = this.messenger.call('AssetsController:getState');
780
+ return (state.assetsMetadata ?? {});
781
+ }
782
+ catch {
783
+ // If AssetsController:getState fails, return empty metadata
784
+ return {};
785
+ }
786
+ }, _RpcDataSource_getTokenMetadataFromTokenList = function _RpcDataSource_getTokenMetadataFromTokenList(assetId) {
787
+ try {
788
+ // Parse asset ID to get chain and token address
789
+ // Format: eip155:{chainId}/erc20:{address}
790
+ const [chainPart, assetPart] = assetId.split('/');
791
+ if (!assetPart?.startsWith('erc20:')) {
792
+ return undefined;
793
+ }
794
+ const tokenAddress = assetPart.slice(6); // Remove 'erc20:' prefix
795
+ const chainIdDecimal = chainPart.split(':')[1];
796
+ const hexChainId = `0x${parseInt(chainIdDecimal, 10).toString(16)}`;
797
+ const tokenListState = this.messenger.call('TokenListController:getState');
798
+ const chainCacheEntry = tokenListState.tokensChainsCache[hexChainId];
799
+ const chainTokenList = chainCacheEntry?.data;
800
+ if (!chainTokenList) {
801
+ return undefined;
802
+ }
803
+ // Look up token by address (case-insensitive)
804
+ const lowerAddress = tokenAddress.toLowerCase();
805
+ for (const [address, tokenData] of Object.entries(chainTokenList)) {
806
+ if (address.toLowerCase() === lowerAddress) {
807
+ const token = tokenData;
808
+ if (token.symbol && token.decimals !== undefined) {
809
+ return {
810
+ type: 'erc20',
811
+ symbol: token.symbol,
812
+ name: token.name ?? token.symbol,
813
+ decimals: token.decimals,
814
+ image: token.iconUrl,
815
+ };
816
+ }
817
+ }
818
+ }
819
+ return undefined;
820
+ }
821
+ catch {
822
+ return undefined;
823
+ }
634
824
  };
635
825
  function createRpcDataSource(options) {
636
826
  return new RpcDataSource(options);