@metamask-previews/assets-controller 14.0.3-preview-e12b5ece6 → 14.0.3-preview-fe4b3be11

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 (80) hide show
  1. package/CHANGELOG.md +5 -10
  2. package/dist/AssetsController.cjs +592 -273
  3. package/dist/AssetsController.cjs.map +1 -1
  4. package/dist/AssetsController.d.cts +7 -10
  5. package/dist/AssetsController.d.cts.map +1 -1
  6. package/dist/AssetsController.d.mts +7 -10
  7. package/dist/AssetsController.d.mts.map +1 -1
  8. package/dist/AssetsController.mjs +592 -273
  9. package/dist/AssetsController.mjs.map +1 -1
  10. package/dist/data-sources/AbstractDataSource.cjs +23 -0
  11. package/dist/data-sources/AbstractDataSource.cjs.map +1 -1
  12. package/dist/data-sources/AbstractDataSource.d.cts +13 -1
  13. package/dist/data-sources/AbstractDataSource.d.cts.map +1 -1
  14. package/dist/data-sources/AbstractDataSource.d.mts +13 -1
  15. package/dist/data-sources/AbstractDataSource.d.mts.map +1 -1
  16. package/dist/data-sources/AbstractDataSource.mjs +23 -0
  17. package/dist/data-sources/AbstractDataSource.mjs.map +1 -1
  18. package/dist/data-sources/AccountActivityDataSource.cjs +2 -0
  19. package/dist/data-sources/AccountActivityDataSource.cjs.map +1 -1
  20. package/dist/data-sources/AccountActivityDataSource.d.cts.map +1 -1
  21. package/dist/data-sources/AccountActivityDataSource.d.mts.map +1 -1
  22. package/dist/data-sources/AccountActivityDataSource.mjs +2 -0
  23. package/dist/data-sources/AccountActivityDataSource.mjs.map +1 -1
  24. package/dist/data-sources/AccountsApiDataSource.cjs +144 -31
  25. package/dist/data-sources/AccountsApiDataSource.cjs.map +1 -1
  26. package/dist/data-sources/AccountsApiDataSource.d.cts +18 -2
  27. package/dist/data-sources/AccountsApiDataSource.d.cts.map +1 -1
  28. package/dist/data-sources/AccountsApiDataSource.d.mts +18 -2
  29. package/dist/data-sources/AccountsApiDataSource.d.mts.map +1 -1
  30. package/dist/data-sources/AccountsApiDataSource.mjs +145 -32
  31. package/dist/data-sources/AccountsApiDataSource.mjs.map +1 -1
  32. package/dist/data-sources/RpcDataSource.cjs +163 -24
  33. package/dist/data-sources/RpcDataSource.cjs.map +1 -1
  34. package/dist/data-sources/RpcDataSource.d.cts +17 -0
  35. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  36. package/dist/data-sources/RpcDataSource.d.mts +17 -0
  37. package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
  38. package/dist/data-sources/RpcDataSource.mjs +164 -25
  39. package/dist/data-sources/RpcDataSource.mjs.map +1 -1
  40. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.cjs +21 -12
  41. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.cjs.map +1 -1
  42. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.cts +7 -2
  43. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.cts.map +1 -1
  44. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.mts +7 -2
  45. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.mts.map +1 -1
  46. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.mjs +21 -12
  47. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.mjs.map +1 -1
  48. package/dist/data-sources/evm-rpc-services/types/state.cjs.map +1 -1
  49. package/dist/data-sources/evm-rpc-services/types/state.d.cts +3 -2
  50. package/dist/data-sources/evm-rpc-services/types/state.d.cts.map +1 -1
  51. package/dist/data-sources/evm-rpc-services/types/state.d.mts +3 -2
  52. package/dist/data-sources/evm-rpc-services/types/state.d.mts.map +1 -1
  53. package/dist/data-sources/evm-rpc-services/types/state.mjs.map +1 -1
  54. package/dist/middlewares/ParallelMiddleware.cjs +9 -0
  55. package/dist/middlewares/ParallelMiddleware.cjs.map +1 -1
  56. package/dist/middlewares/ParallelMiddleware.d.cts.map +1 -1
  57. package/dist/middlewares/ParallelMiddleware.d.mts.map +1 -1
  58. package/dist/middlewares/ParallelMiddleware.mjs +9 -0
  59. package/dist/middlewares/ParallelMiddleware.mjs.map +1 -1
  60. package/dist/middlewares/RpcFallbackMiddleware.cjs +158 -128
  61. package/dist/middlewares/RpcFallbackMiddleware.cjs.map +1 -1
  62. package/dist/middlewares/RpcFallbackMiddleware.d.cts +10 -14
  63. package/dist/middlewares/RpcFallbackMiddleware.d.cts.map +1 -1
  64. package/dist/middlewares/RpcFallbackMiddleware.d.mts +10 -14
  65. package/dist/middlewares/RpcFallbackMiddleware.d.mts.map +1 -1
  66. package/dist/middlewares/RpcFallbackMiddleware.mjs +158 -128
  67. package/dist/middlewares/RpcFallbackMiddleware.mjs.map +1 -1
  68. package/dist/selectors/balance.cjs +4 -9
  69. package/dist/selectors/balance.cjs.map +1 -1
  70. package/dist/selectors/balance.d.cts.map +1 -1
  71. package/dist/selectors/balance.d.mts.map +1 -1
  72. package/dist/selectors/balance.mjs +4 -9
  73. package/dist/selectors/balance.mjs.map +1 -1
  74. package/dist/types.cjs.map +1 -1
  75. package/dist/types.d.cts +28 -26
  76. package/dist/types.d.cts.map +1 -1
  77. package/dist/types.d.mts +28 -26
  78. package/dist/types.d.mts.map +1 -1
  79. package/dist/types.mjs.map +1 -1
  80. package/package.json +4 -4
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AbstractDataSource = void 0;
4
+ const utils_1 = require("@metamask/utils");
4
5
  // ============================================================================
5
6
  // ABSTRACT DATA SOURCE
6
7
  // ============================================================================
@@ -42,6 +43,28 @@ class AbstractDataSource {
42
43
  getActiveChainsSync() {
43
44
  return this.state.activeChains;
44
45
  }
46
+ /**
47
+ * Claim the pinned assets this source commits to serving. Called during the
48
+ * subscription handoff; claimed assets are not offered to lower-priority
49
+ * sources. Assets a source cannot resolve at fetch time are released via
50
+ * `DataResponse.unprocessedCustomAssets`. Default: claim assets on this
51
+ * source's assigned chains.
52
+ *
53
+ * @param customAssets - Candidate CAIP-19 asset IDs still unclaimed.
54
+ * @param assignedChains - Chains assigned to this source in the handoff.
55
+ * @returns The claimed subset of `customAssets`.
56
+ */
57
+ claimCustomAssets(customAssets, assignedChains) {
58
+ const assigned = new Set(assignedChains);
59
+ return customAssets.filter((assetId) => {
60
+ try {
61
+ return assigned.has((0, utils_1.parseCaipAssetType)(assetId).chainId);
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ });
67
+ }
45
68
  /**
46
69
  * Unsubscribe from updates.
47
70
  *
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractDataSource.cjs","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":";;;AAgEA,+EAA+E;AAC/E,uBAAuB;AACvB,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAsB,kBAAkB;IAYtC,YAAY,IAAU,EAAE,YAAmB;QAJ3C,iCAAiC;QACd,wBAAmB,GACpC,IAAI,GAAG,EAAE,CAAC;QAGV,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC;IAC5B,CAAC;IAED;;;;OAIG;IACH,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,eAAe;QACnB,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;IACjC,CAAC;IAED;;;;OAIG;IACH,mBAAmB;QACjB,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;IACjC,CAAC;IAOD;;;;OAIG;IACH,KAAK,CAAC,WAAW,CAAC,cAAsB;QACtC,MAAM,YAAY,GAAG,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAClE,IAAI,YAAY,EAAE,CAAC;YACjB,YAAY,CAAC,OAAO,EAAE,CAAC;YACvB,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACO,kBAAkB,CAC1B,MAAiB,EACjB,YAAyC;QAEzC,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACxD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAElC,wCAAwC;QACxC,MAAM,UAAU,GACd,cAAc,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI;YACtC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QAErD,sBAAsB;QACtB,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,MAAM,CAAC;QAEjC,iDAAiD;QACjD,IAAI,UAAU,EAAE,CAAC;YACf,YAAY,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACO,cAAc,CACtB,OAAgB,EAChB,YAAyC;QAEzC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/C,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YAChE,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACO,iBAAiB,CACzB,OAAgB,EAChB,YAAyC;QAEzC,IAAI,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CACtD,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,OAAO,CAC7B,CAAC;YACF,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED;;OAEG;IACH,OAAO;QACL,KAAK,MAAM,YAAY,IAAI,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7D,YAAY,CAAC,OAAO,EAAE,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC;IACnC,CAAC;CACF;AApID,gDAoIC","sourcesContent":["import type {\n ChainId,\n DataRequest,\n DataResponse,\n AssetsControllerStateInternal,\n} from '../types.js';\n\n// ============================================================================\n// DATA SOURCE BASE TYPES\n// ============================================================================\n\n/**\n * Subscription request from AssetsController.\n */\nexport type SubscriptionRequest = {\n request: DataRequest;\n subscriptionId: string;\n isUpdate: boolean;\n /** Called when this data source has new asset data. Passed by the controller when subscribing. Optional request describes the update scope (e.g. dataTypes: ['balance'] to skip enrichment). */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n /**\n * Optional state access (e.g. for price/token data sources that need assetsBalance).\n * Provided by the controller when subscribing.\n */\n getAssetsState?: () => AssetsControllerStateInternal;\n /**\n * When true, skip the one-shot fetch that normally runs when a subscription\n * is created. Used after the controller has already force-fetched balances\n * for the same accounts/chains so we do not immediately hit the API again.\n */\n skipInitialFetch?: boolean;\n};\n\n/**\n * Active subscription entry stored by data sources.\n * Data sources can extend this with additional fields as needed.\n */\nexport type ActiveSubscription = {\n /** Cleanup function to unsubscribe */\n cleanup: () => void;\n /** Currently subscribed chains */\n chains: ChainId[];\n /** Original request (for polling data sources to handle account changes) */\n request?: DataRequest;\n /** Account addresses (for WebSocket data sources to detect account changes) */\n addresses?: string[];\n /** Callback to report asset updates (from SubscriptionRequest) */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n};\n\n/**\n * Base state for all data sources.\n */\nexport type DataSourceState = {\n /** Currently active chains (supported AND available) */\n activeChains: ChainId[];\n};\n\n// ============================================================================\n// ABSTRACT DATA SOURCE\n// ============================================================================\n\n/**\n * Abstract base class for data sources.\n *\n * Data sources communicate with AssetsController via Messenger:\n * - Register actions that AssetsController can call\n * - Publish events that AssetsController subscribes to\n */\nexport abstract class AbstractDataSource<\n Name extends string,\n State extends DataSourceState = DataSourceState,\n> {\n protected readonly name: Name;\n\n protected state: State;\n\n /** Active subscriptions by ID */\n protected readonly activeSubscriptions: Map<string, ActiveSubscription> =\n new Map();\n\n constructor(name: Name, initialState: State) {\n this.name = name;\n this.state = initialState;\n }\n\n /**\n * Get the data source name/ID.\n *\n * @returns The name of this data source.\n */\n getName(): Name {\n return this.name;\n }\n\n /**\n * Get currently active chains (supported AND available).\n *\n * @returns Array of currently active chain IDs.\n */\n async getActiveChains(): Promise<ChainId[]> {\n return this.state.activeChains;\n }\n\n /**\n * Get currently active chains synchronously (no state duplication in controller).\n *\n * @returns Array of currently active chain IDs.\n */\n getActiveChainsSync(): ChainId[] {\n return this.state.activeChains;\n }\n\n /**\n * Subscribe to updates for the given request.\n */\n abstract subscribe(request: SubscriptionRequest): Promise<void>;\n\n /**\n * Unsubscribe from updates.\n *\n * @param subscriptionId - The ID of the subscription to cancel.\n */\n async unsubscribe(subscriptionId: string): Promise<void> {\n const subscription = this.activeSubscriptions.get(subscriptionId);\n if (subscription) {\n subscription.cleanup();\n this.activeSubscriptions.delete(subscriptionId);\n }\n }\n\n /**\n * Update active chains and notify listeners only if changed.\n *\n * @param chains - Array of chain IDs to set as active.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected updateActiveChains(\n chains: ChainId[],\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n const previousChains = new Set(this.state.activeChains);\n const newChains = new Set(chains);\n\n // Check if chains have actually changed\n const hasChanges =\n previousChains.size !== newChains.size ||\n chains.some((chain) => !previousChains.has(chain));\n\n // Always update state\n this.state.activeChains = chains;\n\n // Only publish event if there are actual changes\n if (hasChanges) {\n publishEvent(chains);\n }\n }\n\n /**\n * Add a chain to active chains.\n *\n * @param chainId - The chain ID to add.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected addActiveChain(\n chainId: ChainId,\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n if (!this.state.activeChains.includes(chainId)) {\n this.state.activeChains = [...this.state.activeChains, chainId];\n publishEvent(this.state.activeChains);\n }\n }\n\n /**\n * Remove a chain from active chains.\n *\n * @param chainId - The chain ID to remove.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected removeActiveChain(\n chainId: ChainId,\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n if (this.state.activeChains.includes(chainId)) {\n this.state.activeChains = this.state.activeChains.filter(\n (chain) => chain !== chainId,\n );\n publishEvent(this.state.activeChains);\n }\n }\n\n /**\n * Destroy the data source and clean up resources.\n */\n destroy(): void {\n for (const subscription of this.activeSubscriptions.values()) {\n subscription.cleanup();\n }\n this.activeSubscriptions.clear();\n }\n}\n"]}
1
+ {"version":3,"file":"AbstractDataSource.cjs","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import { parseCaipAssetType } from '@metamask/utils';\n\nimport type {\n Caip19AssetId,\n ChainId,\n DataRequest,\n DataResponse,\n AssetsControllerStateInternal,\n} from '../types.js';\n\n// ============================================================================\n// DATA SOURCE BASE TYPES\n// ============================================================================\n\n/**\n * Subscription request from AssetsController.\n */\nexport type SubscriptionRequest = {\n request: DataRequest;\n subscriptionId: string;\n isUpdate: boolean;\n /** Called when this data source has new asset data. Passed by the controller when subscribing. Optional request describes the update scope (e.g. dataTypes: ['balance'] to skip enrichment). */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n /**\n * Optional state access (e.g. for price/token data sources that need assetsBalance).\n * Provided by the controller when subscribing.\n */\n getAssetsState?: () => AssetsControllerStateInternal;\n /**\n * When true, skip the one-shot fetch that normally runs when a subscription\n * is created. Used after the controller has already force-fetched balances\n * for the same accounts/chains so we do not immediately hit the API again.\n */\n skipInitialFetch?: boolean;\n};\n\n/**\n * Active subscription entry stored by data sources.\n * Data sources can extend this with additional fields as needed.\n */\nexport type ActiveSubscription = {\n /** Cleanup function to unsubscribe */\n cleanup: () => void;\n /** Currently subscribed chains */\n chains: ChainId[];\n /** Original request (for polling data sources to handle account changes) */\n request?: DataRequest;\n /** Account addresses (for WebSocket data sources to detect account changes) */\n addresses?: string[];\n /** Callback to report asset updates (from SubscriptionRequest) */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n};\n\n/**\n * Base state for all data sources.\n */\nexport type DataSourceState = {\n /** Currently active chains (supported AND available) */\n activeChains: ChainId[];\n};\n\n// ============================================================================\n// ABSTRACT DATA SOURCE\n// ============================================================================\n\n/**\n * Abstract base class for data sources.\n *\n * Data sources communicate with AssetsController via Messenger:\n * - Register actions that AssetsController can call\n * - Publish events that AssetsController subscribes to\n */\nexport abstract class AbstractDataSource<\n Name extends string,\n State extends DataSourceState = DataSourceState,\n> {\n protected readonly name: Name;\n\n protected state: State;\n\n /** Active subscriptions by ID */\n protected readonly activeSubscriptions: Map<string, ActiveSubscription> =\n new Map();\n\n constructor(name: Name, initialState: State) {\n this.name = name;\n this.state = initialState;\n }\n\n /**\n * Get the data source name/ID.\n *\n * @returns The name of this data source.\n */\n getName(): Name {\n return this.name;\n }\n\n /**\n * Get currently active chains (supported AND available).\n *\n * @returns Array of currently active chain IDs.\n */\n async getActiveChains(): Promise<ChainId[]> {\n return this.state.activeChains;\n }\n\n /**\n * Get currently active chains synchronously (no state duplication in controller).\n *\n * @returns Array of currently active chain IDs.\n */\n getActiveChainsSync(): ChainId[] {\n return this.state.activeChains;\n }\n\n /**\n * Claim the pinned assets this source commits to serving. Called during the\n * subscription handoff; claimed assets are not offered to lower-priority\n * sources. Assets a source cannot resolve at fetch time are released via\n * `DataResponse.unprocessedCustomAssets`. Default: claim assets on this\n * source's assigned chains.\n *\n * @param customAssets - Candidate CAIP-19 asset IDs still unclaimed.\n * @param assignedChains - Chains assigned to this source in the handoff.\n * @returns The claimed subset of `customAssets`.\n */\n claimCustomAssets(\n customAssets: Caip19AssetId[],\n assignedChains: ChainId[],\n ): Caip19AssetId[] {\n const assigned = new Set<ChainId>(assignedChains);\n return customAssets.filter((assetId) => {\n try {\n return assigned.has(parseCaipAssetType(assetId).chainId);\n } catch {\n return false;\n }\n });\n }\n\n /**\n * Subscribe to updates for the given request.\n */\n abstract subscribe(request: SubscriptionRequest): Promise<void>;\n\n /**\n * Unsubscribe from updates.\n *\n * @param subscriptionId - The ID of the subscription to cancel.\n */\n async unsubscribe(subscriptionId: string): Promise<void> {\n const subscription = this.activeSubscriptions.get(subscriptionId);\n if (subscription) {\n subscription.cleanup();\n this.activeSubscriptions.delete(subscriptionId);\n }\n }\n\n /**\n * Update active chains and notify listeners only if changed.\n *\n * @param chains - Array of chain IDs to set as active.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected updateActiveChains(\n chains: ChainId[],\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n const previousChains = new Set(this.state.activeChains);\n const newChains = new Set(chains);\n\n // Check if chains have actually changed\n const hasChanges =\n previousChains.size !== newChains.size ||\n chains.some((chain) => !previousChains.has(chain));\n\n // Always update state\n this.state.activeChains = chains;\n\n // Only publish event if there are actual changes\n if (hasChanges) {\n publishEvent(chains);\n }\n }\n\n /**\n * Add a chain to active chains.\n *\n * @param chainId - The chain ID to add.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected addActiveChain(\n chainId: ChainId,\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n if (!this.state.activeChains.includes(chainId)) {\n this.state.activeChains = [...this.state.activeChains, chainId];\n publishEvent(this.state.activeChains);\n }\n }\n\n /**\n * Remove a chain from active chains.\n *\n * @param chainId - The chain ID to remove.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected removeActiveChain(\n chainId: ChainId,\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n if (this.state.activeChains.includes(chainId)) {\n this.state.activeChains = this.state.activeChains.filter(\n (chain) => chain !== chainId,\n );\n publishEvent(this.state.activeChains);\n }\n }\n\n /**\n * Destroy the data source and clean up resources.\n */\n destroy(): void {\n for (const subscription of this.activeSubscriptions.values()) {\n subscription.cleanup();\n }\n this.activeSubscriptions.clear();\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { ChainId, DataRequest, DataResponse, AssetsControllerStateInternal } from "../types.cjs";
1
+ import type { Caip19AssetId, ChainId, DataRequest, DataResponse, AssetsControllerStateInternal } from "../types.cjs";
2
2
  /**
3
3
  * Subscription request from AssetsController.
4
4
  */
@@ -74,6 +74,18 @@ export declare abstract class AbstractDataSource<Name extends string, State exte
74
74
  * @returns Array of currently active chain IDs.
75
75
  */
76
76
  getActiveChainsSync(): ChainId[];
77
+ /**
78
+ * Claim the pinned assets this source commits to serving. Called during the
79
+ * subscription handoff; claimed assets are not offered to lower-priority
80
+ * sources. Assets a source cannot resolve at fetch time are released via
81
+ * `DataResponse.unprocessedCustomAssets`. Default: claim assets on this
82
+ * source's assigned chains.
83
+ *
84
+ * @param customAssets - Candidate CAIP-19 asset IDs still unclaimed.
85
+ * @param assignedChains - Chains assigned to this source in the handoff.
86
+ * @returns The claimed subset of `customAssets`.
87
+ */
88
+ claimCustomAssets(customAssets: Caip19AssetId[], assignedChains: ChainId[]): Caip19AssetId[];
77
89
  /**
78
90
  * Subscribe to updates for the given request.
79
91
  */
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractDataSource.d.cts","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,WAAW,EACX,YAAY,EACZ,6BAA6B,EAC9B,qBAAoB;AAMrB;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,WAAW,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,gMAAgM;IAChM,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,6BAA6B,CAAC;IACrD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sCAAsC;IACtC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,kCAAkC;IAClC,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,kEAAkE;IAClE,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,wDAAwD;IACxD,YAAY,EAAE,OAAO,EAAE,CAAC;CACzB,CAAC;AAMF;;;;;;GAMG;AACH,8BAAsB,kBAAkB,CACtC,IAAI,SAAS,MAAM,EACnB,KAAK,SAAS,eAAe,GAAG,eAAe;IAE/C,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAE9B,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;IAEvB,iCAAiC;IACjC,SAAS,CAAC,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAC3D;gBAEA,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK;IAK3C;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAIf;;;;OAIG;IACG,eAAe,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAI3C;;;;OAIG;IACH,mBAAmB,IAAI,OAAO,EAAE;IAIhC;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/D;;;;OAIG;IACG,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQxD;;;;;OAKG;IACH,SAAS,CAAC,kBAAkB,CAC1B,MAAM,EAAE,OAAO,EAAE,EACjB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI;IAkBP;;;;;OAKG;IACH,SAAS,CAAC,cAAc,CACtB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI;IAOP;;;;;OAKG;IACH,SAAS,CAAC,iBAAiB,CACzB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI;IASP;;OAEG;IACH,OAAO,IAAI,IAAI;CAMhB"}
1
+ {"version":3,"file":"AbstractDataSource.d.cts","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,OAAO,EACP,WAAW,EACX,YAAY,EACZ,6BAA6B,EAC9B,qBAAoB;AAMrB;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,WAAW,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,gMAAgM;IAChM,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,6BAA6B,CAAC;IACrD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sCAAsC;IACtC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,kCAAkC;IAClC,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,kEAAkE;IAClE,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,wDAAwD;IACxD,YAAY,EAAE,OAAO,EAAE,CAAC;CACzB,CAAC;AAMF;;;;;;GAMG;AACH,8BAAsB,kBAAkB,CACtC,IAAI,SAAS,MAAM,EACnB,KAAK,SAAS,eAAe,GAAG,eAAe;IAE/C,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAE9B,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;IAEvB,iCAAiC;IACjC,SAAS,CAAC,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAC3D;gBAEA,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK;IAK3C;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAIf;;;;OAIG;IACG,eAAe,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAI3C;;;;OAIG;IACH,mBAAmB,IAAI,OAAO,EAAE;IAIhC;;;;;;;;;;OAUG;IACH,iBAAiB,CACf,YAAY,EAAE,aAAa,EAAE,EAC7B,cAAc,EAAE,OAAO,EAAE,GACxB,aAAa,EAAE;IAWlB;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/D;;;;OAIG;IACG,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQxD;;;;;OAKG;IACH,SAAS,CAAC,kBAAkB,CAC1B,MAAM,EAAE,OAAO,EAAE,EACjB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI;IAkBP;;;;;OAKG;IACH,SAAS,CAAC,cAAc,CACtB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI;IAOP;;;;;OAKG;IACH,SAAS,CAAC,iBAAiB,CACzB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI;IASP;;OAEG;IACH,OAAO,IAAI,IAAI;CAMhB"}
@@ -1,4 +1,4 @@
1
- import type { ChainId, DataRequest, DataResponse, AssetsControllerStateInternal } from "../types.mjs";
1
+ import type { Caip19AssetId, ChainId, DataRequest, DataResponse, AssetsControllerStateInternal } from "../types.mjs";
2
2
  /**
3
3
  * Subscription request from AssetsController.
4
4
  */
@@ -74,6 +74,18 @@ export declare abstract class AbstractDataSource<Name extends string, State exte
74
74
  * @returns Array of currently active chain IDs.
75
75
  */
76
76
  getActiveChainsSync(): ChainId[];
77
+ /**
78
+ * Claim the pinned assets this source commits to serving. Called during the
79
+ * subscription handoff; claimed assets are not offered to lower-priority
80
+ * sources. Assets a source cannot resolve at fetch time are released via
81
+ * `DataResponse.unprocessedCustomAssets`. Default: claim assets on this
82
+ * source's assigned chains.
83
+ *
84
+ * @param customAssets - Candidate CAIP-19 asset IDs still unclaimed.
85
+ * @param assignedChains - Chains assigned to this source in the handoff.
86
+ * @returns The claimed subset of `customAssets`.
87
+ */
88
+ claimCustomAssets(customAssets: Caip19AssetId[], assignedChains: ChainId[]): Caip19AssetId[];
77
89
  /**
78
90
  * Subscribe to updates for the given request.
79
91
  */
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractDataSource.d.mts","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,WAAW,EACX,YAAY,EACZ,6BAA6B,EAC9B,qBAAoB;AAMrB;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,WAAW,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,gMAAgM;IAChM,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,6BAA6B,CAAC;IACrD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sCAAsC;IACtC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,kCAAkC;IAClC,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,kEAAkE;IAClE,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,wDAAwD;IACxD,YAAY,EAAE,OAAO,EAAE,CAAC;CACzB,CAAC;AAMF;;;;;;GAMG;AACH,8BAAsB,kBAAkB,CACtC,IAAI,SAAS,MAAM,EACnB,KAAK,SAAS,eAAe,GAAG,eAAe;IAE/C,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAE9B,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;IAEvB,iCAAiC;IACjC,SAAS,CAAC,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAC3D;gBAEA,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK;IAK3C;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAIf;;;;OAIG;IACG,eAAe,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAI3C;;;;OAIG;IACH,mBAAmB,IAAI,OAAO,EAAE;IAIhC;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/D;;;;OAIG;IACG,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQxD;;;;;OAKG;IACH,SAAS,CAAC,kBAAkB,CAC1B,MAAM,EAAE,OAAO,EAAE,EACjB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI;IAkBP;;;;;OAKG;IACH,SAAS,CAAC,cAAc,CACtB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI;IAOP;;;;;OAKG;IACH,SAAS,CAAC,iBAAiB,CACzB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI;IASP;;OAEG;IACH,OAAO,IAAI,IAAI;CAMhB"}
1
+ {"version":3,"file":"AbstractDataSource.d.mts","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,OAAO,EACP,WAAW,EACX,YAAY,EACZ,6BAA6B,EAC9B,qBAAoB;AAMrB;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,WAAW,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,gMAAgM;IAChM,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,6BAA6B,CAAC;IACrD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sCAAsC;IACtC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,kCAAkC;IAClC,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,kEAAkE;IAClE,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,wDAAwD;IACxD,YAAY,EAAE,OAAO,EAAE,CAAC;CACzB,CAAC;AAMF;;;;;;GAMG;AACH,8BAAsB,kBAAkB,CACtC,IAAI,SAAS,MAAM,EACnB,KAAK,SAAS,eAAe,GAAG,eAAe;IAE/C,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAE9B,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;IAEvB,iCAAiC;IACjC,SAAS,CAAC,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAC3D;gBAEA,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK;IAK3C;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAIf;;;;OAIG;IACG,eAAe,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAI3C;;;;OAIG;IACH,mBAAmB,IAAI,OAAO,EAAE;IAIhC;;;;;;;;;;OAUG;IACH,iBAAiB,CACf,YAAY,EAAE,aAAa,EAAE,EAC7B,cAAc,EAAE,OAAO,EAAE,GACxB,aAAa,EAAE;IAWlB;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/D;;;;OAIG;IACG,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQxD;;;;;OAKG;IACH,SAAS,CAAC,kBAAkB,CAC1B,MAAM,EAAE,OAAO,EAAE,EACjB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI;IAkBP;;;;;OAKG;IACH,SAAS,CAAC,cAAc,CACtB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI;IAOP;;;;;OAKG;IACH,SAAS,CAAC,iBAAiB,CACzB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI;IASP;;OAEG;IACH,OAAO,IAAI,IAAI;CAMhB"}
@@ -1,3 +1,4 @@
1
+ import { parseCaipAssetType } from "@metamask/utils";
1
2
  // ============================================================================
2
3
  // ABSTRACT DATA SOURCE
3
4
  // ============================================================================
@@ -39,6 +40,28 @@ export class AbstractDataSource {
39
40
  getActiveChainsSync() {
40
41
  return this.state.activeChains;
41
42
  }
43
+ /**
44
+ * Claim the pinned assets this source commits to serving. Called during the
45
+ * subscription handoff; claimed assets are not offered to lower-priority
46
+ * sources. Assets a source cannot resolve at fetch time are released via
47
+ * `DataResponse.unprocessedCustomAssets`. Default: claim assets on this
48
+ * source's assigned chains.
49
+ *
50
+ * @param customAssets - Candidate CAIP-19 asset IDs still unclaimed.
51
+ * @param assignedChains - Chains assigned to this source in the handoff.
52
+ * @returns The claimed subset of `customAssets`.
53
+ */
54
+ claimCustomAssets(customAssets, assignedChains) {
55
+ const assigned = new Set(assignedChains);
56
+ return customAssets.filter((assetId) => {
57
+ try {
58
+ return assigned.has(parseCaipAssetType(assetId).chainId);
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ });
64
+ }
42
65
  /**
43
66
  * Unsubscribe from updates.
44
67
  *
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractDataSource.mjs","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"AAgEA,+EAA+E;AAC/E,uBAAuB;AACvB,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,OAAgB,kBAAkB;IAYtC,YAAY,IAAU,EAAE,YAAmB;QAJ3C,iCAAiC;QACd,wBAAmB,GACpC,IAAI,GAAG,EAAE,CAAC;QAGV,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC;IAC5B,CAAC;IAED;;;;OAIG;IACH,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,eAAe;QACnB,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;IACjC,CAAC;IAED;;;;OAIG;IACH,mBAAmB;QACjB,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC;IACjC,CAAC;IAOD;;;;OAIG;IACH,KAAK,CAAC,WAAW,CAAC,cAAsB;QACtC,MAAM,YAAY,GAAG,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAClE,IAAI,YAAY,EAAE,CAAC;YACjB,YAAY,CAAC,OAAO,EAAE,CAAC;YACvB,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACO,kBAAkB,CAC1B,MAAiB,EACjB,YAAyC;QAEzC,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACxD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAElC,wCAAwC;QACxC,MAAM,UAAU,GACd,cAAc,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI;YACtC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QAErD,sBAAsB;QACtB,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,MAAM,CAAC;QAEjC,iDAAiD;QACjD,IAAI,UAAU,EAAE,CAAC;YACf,YAAY,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACO,cAAc,CACtB,OAAgB,EAChB,YAAyC;QAEzC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/C,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YAChE,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACO,iBAAiB,CACzB,OAAgB,EAChB,YAAyC;QAEzC,IAAI,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CACtD,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,OAAO,CAC7B,CAAC;YACF,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED;;OAEG;IACH,OAAO;QACL,KAAK,MAAM,YAAY,IAAI,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7D,YAAY,CAAC,OAAO,EAAE,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,CAAC;IACnC,CAAC;CACF","sourcesContent":["import type {\n ChainId,\n DataRequest,\n DataResponse,\n AssetsControllerStateInternal,\n} from '../types.js';\n\n// ============================================================================\n// DATA SOURCE BASE TYPES\n// ============================================================================\n\n/**\n * Subscription request from AssetsController.\n */\nexport type SubscriptionRequest = {\n request: DataRequest;\n subscriptionId: string;\n isUpdate: boolean;\n /** Called when this data source has new asset data. Passed by the controller when subscribing. Optional request describes the update scope (e.g. dataTypes: ['balance'] to skip enrichment). */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n /**\n * Optional state access (e.g. for price/token data sources that need assetsBalance).\n * Provided by the controller when subscribing.\n */\n getAssetsState?: () => AssetsControllerStateInternal;\n /**\n * When true, skip the one-shot fetch that normally runs when a subscription\n * is created. Used after the controller has already force-fetched balances\n * for the same accounts/chains so we do not immediately hit the API again.\n */\n skipInitialFetch?: boolean;\n};\n\n/**\n * Active subscription entry stored by data sources.\n * Data sources can extend this with additional fields as needed.\n */\nexport type ActiveSubscription = {\n /** Cleanup function to unsubscribe */\n cleanup: () => void;\n /** Currently subscribed chains */\n chains: ChainId[];\n /** Original request (for polling data sources to handle account changes) */\n request?: DataRequest;\n /** Account addresses (for WebSocket data sources to detect account changes) */\n addresses?: string[];\n /** Callback to report asset updates (from SubscriptionRequest) */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n};\n\n/**\n * Base state for all data sources.\n */\nexport type DataSourceState = {\n /** Currently active chains (supported AND available) */\n activeChains: ChainId[];\n};\n\n// ============================================================================\n// ABSTRACT DATA SOURCE\n// ============================================================================\n\n/**\n * Abstract base class for data sources.\n *\n * Data sources communicate with AssetsController via Messenger:\n * - Register actions that AssetsController can call\n * - Publish events that AssetsController subscribes to\n */\nexport abstract class AbstractDataSource<\n Name extends string,\n State extends DataSourceState = DataSourceState,\n> {\n protected readonly name: Name;\n\n protected state: State;\n\n /** Active subscriptions by ID */\n protected readonly activeSubscriptions: Map<string, ActiveSubscription> =\n new Map();\n\n constructor(name: Name, initialState: State) {\n this.name = name;\n this.state = initialState;\n }\n\n /**\n * Get the data source name/ID.\n *\n * @returns The name of this data source.\n */\n getName(): Name {\n return this.name;\n }\n\n /**\n * Get currently active chains (supported AND available).\n *\n * @returns Array of currently active chain IDs.\n */\n async getActiveChains(): Promise<ChainId[]> {\n return this.state.activeChains;\n }\n\n /**\n * Get currently active chains synchronously (no state duplication in controller).\n *\n * @returns Array of currently active chain IDs.\n */\n getActiveChainsSync(): ChainId[] {\n return this.state.activeChains;\n }\n\n /**\n * Subscribe to updates for the given request.\n */\n abstract subscribe(request: SubscriptionRequest): Promise<void>;\n\n /**\n * Unsubscribe from updates.\n *\n * @param subscriptionId - The ID of the subscription to cancel.\n */\n async unsubscribe(subscriptionId: string): Promise<void> {\n const subscription = this.activeSubscriptions.get(subscriptionId);\n if (subscription) {\n subscription.cleanup();\n this.activeSubscriptions.delete(subscriptionId);\n }\n }\n\n /**\n * Update active chains and notify listeners only if changed.\n *\n * @param chains - Array of chain IDs to set as active.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected updateActiveChains(\n chains: ChainId[],\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n const previousChains = new Set(this.state.activeChains);\n const newChains = new Set(chains);\n\n // Check if chains have actually changed\n const hasChanges =\n previousChains.size !== newChains.size ||\n chains.some((chain) => !previousChains.has(chain));\n\n // Always update state\n this.state.activeChains = chains;\n\n // Only publish event if there are actual changes\n if (hasChanges) {\n publishEvent(chains);\n }\n }\n\n /**\n * Add a chain to active chains.\n *\n * @param chainId - The chain ID to add.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected addActiveChain(\n chainId: ChainId,\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n if (!this.state.activeChains.includes(chainId)) {\n this.state.activeChains = [...this.state.activeChains, chainId];\n publishEvent(this.state.activeChains);\n }\n }\n\n /**\n * Remove a chain from active chains.\n *\n * @param chainId - The chain ID to remove.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected removeActiveChain(\n chainId: ChainId,\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n if (this.state.activeChains.includes(chainId)) {\n this.state.activeChains = this.state.activeChains.filter(\n (chain) => chain !== chainId,\n );\n publishEvent(this.state.activeChains);\n }\n }\n\n /**\n * Destroy the data source and clean up resources.\n */\n destroy(): void {\n for (const subscription of this.activeSubscriptions.values()) {\n subscription.cleanup();\n }\n this.activeSubscriptions.clear();\n }\n}\n"]}
1
+ {"version":3,"file":"AbstractDataSource.mjs","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import { parseCaipAssetType } from '@metamask/utils';\n\nimport type {\n Caip19AssetId,\n ChainId,\n DataRequest,\n DataResponse,\n AssetsControllerStateInternal,\n} from '../types.js';\n\n// ============================================================================\n// DATA SOURCE BASE TYPES\n// ============================================================================\n\n/**\n * Subscription request from AssetsController.\n */\nexport type SubscriptionRequest = {\n request: DataRequest;\n subscriptionId: string;\n isUpdate: boolean;\n /** Called when this data source has new asset data. Passed by the controller when subscribing. Optional request describes the update scope (e.g. dataTypes: ['balance'] to skip enrichment). */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n /**\n * Optional state access (e.g. for price/token data sources that need assetsBalance).\n * Provided by the controller when subscribing.\n */\n getAssetsState?: () => AssetsControllerStateInternal;\n /**\n * When true, skip the one-shot fetch that normally runs when a subscription\n * is created. Used after the controller has already force-fetched balances\n * for the same accounts/chains so we do not immediately hit the API again.\n */\n skipInitialFetch?: boolean;\n};\n\n/**\n * Active subscription entry stored by data sources.\n * Data sources can extend this with additional fields as needed.\n */\nexport type ActiveSubscription = {\n /** Cleanup function to unsubscribe */\n cleanup: () => void;\n /** Currently subscribed chains */\n chains: ChainId[];\n /** Original request (for polling data sources to handle account changes) */\n request?: DataRequest;\n /** Account addresses (for WebSocket data sources to detect account changes) */\n addresses?: string[];\n /** Callback to report asset updates (from SubscriptionRequest) */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n};\n\n/**\n * Base state for all data sources.\n */\nexport type DataSourceState = {\n /** Currently active chains (supported AND available) */\n activeChains: ChainId[];\n};\n\n// ============================================================================\n// ABSTRACT DATA SOURCE\n// ============================================================================\n\n/**\n * Abstract base class for data sources.\n *\n * Data sources communicate with AssetsController via Messenger:\n * - Register actions that AssetsController can call\n * - Publish events that AssetsController subscribes to\n */\nexport abstract class AbstractDataSource<\n Name extends string,\n State extends DataSourceState = DataSourceState,\n> {\n protected readonly name: Name;\n\n protected state: State;\n\n /** Active subscriptions by ID */\n protected readonly activeSubscriptions: Map<string, ActiveSubscription> =\n new Map();\n\n constructor(name: Name, initialState: State) {\n this.name = name;\n this.state = initialState;\n }\n\n /**\n * Get the data source name/ID.\n *\n * @returns The name of this data source.\n */\n getName(): Name {\n return this.name;\n }\n\n /**\n * Get currently active chains (supported AND available).\n *\n * @returns Array of currently active chain IDs.\n */\n async getActiveChains(): Promise<ChainId[]> {\n return this.state.activeChains;\n }\n\n /**\n * Get currently active chains synchronously (no state duplication in controller).\n *\n * @returns Array of currently active chain IDs.\n */\n getActiveChainsSync(): ChainId[] {\n return this.state.activeChains;\n }\n\n /**\n * Claim the pinned assets this source commits to serving. Called during the\n * subscription handoff; claimed assets are not offered to lower-priority\n * sources. Assets a source cannot resolve at fetch time are released via\n * `DataResponse.unprocessedCustomAssets`. Default: claim assets on this\n * source's assigned chains.\n *\n * @param customAssets - Candidate CAIP-19 asset IDs still unclaimed.\n * @param assignedChains - Chains assigned to this source in the handoff.\n * @returns The claimed subset of `customAssets`.\n */\n claimCustomAssets(\n customAssets: Caip19AssetId[],\n assignedChains: ChainId[],\n ): Caip19AssetId[] {\n const assigned = new Set<ChainId>(assignedChains);\n return customAssets.filter((assetId) => {\n try {\n return assigned.has(parseCaipAssetType(assetId).chainId);\n } catch {\n return false;\n }\n });\n }\n\n /**\n * Subscribe to updates for the given request.\n */\n abstract subscribe(request: SubscriptionRequest): Promise<void>;\n\n /**\n * Unsubscribe from updates.\n *\n * @param subscriptionId - The ID of the subscription to cancel.\n */\n async unsubscribe(subscriptionId: string): Promise<void> {\n const subscription = this.activeSubscriptions.get(subscriptionId);\n if (subscription) {\n subscription.cleanup();\n this.activeSubscriptions.delete(subscriptionId);\n }\n }\n\n /**\n * Update active chains and notify listeners only if changed.\n *\n * @param chains - Array of chain IDs to set as active.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected updateActiveChains(\n chains: ChainId[],\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n const previousChains = new Set(this.state.activeChains);\n const newChains = new Set(chains);\n\n // Check if chains have actually changed\n const hasChanges =\n previousChains.size !== newChains.size ||\n chains.some((chain) => !previousChains.has(chain));\n\n // Always update state\n this.state.activeChains = chains;\n\n // Only publish event if there are actual changes\n if (hasChanges) {\n publishEvent(chains);\n }\n }\n\n /**\n * Add a chain to active chains.\n *\n * @param chainId - The chain ID to add.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected addActiveChain(\n chainId: ChainId,\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n if (!this.state.activeChains.includes(chainId)) {\n this.state.activeChains = [...this.state.activeChains, chainId];\n publishEvent(this.state.activeChains);\n }\n }\n\n /**\n * Remove a chain from active chains.\n *\n * @param chainId - The chain ID to remove.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected removeActiveChain(\n chainId: ChainId,\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n if (this.state.activeChains.includes(chainId)) {\n this.state.activeChains = this.state.activeChains.filter(\n (chain) => chain !== chainId,\n );\n publishEvent(this.state.activeChains);\n }\n }\n\n /**\n * Destroy the data source and clean up resources.\n */\n destroy(): void {\n for (const subscription of this.activeSubscriptions.values()) {\n subscription.cleanup();\n }\n this.activeSubscriptions.clear();\n }\n}\n"]}
@@ -178,6 +178,8 @@ class AccountActivityDataSource extends AbstractDataSource_js_1.AbstractDataSour
178
178
  // ============================================================================
179
179
  // SUBSCRIBE / UNSUBSCRIBE
180
180
  // ============================================================================
181
+ // Pins on active chains are claimed via the inherited `claimCustomAssets`:
182
+ // the activity stream pushes all activity for the address, pins included.
181
183
  /**
182
184
  * AADS is event-driven and chain-agnostic: it never participates in the
183
185
  * controller's subscribe/unsubscribe handoff. Incoming `balanceUpdated`
@@ -1 +1 @@
1
- {"version":3,"file":"AccountActivityDataSource.cjs","sourceRoot":"","sources":["../../src/data-sources/AccountActivityDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountActivityServiceBalanceUpdatedEvent,\n AccountActivityServiceStatusChangedEvent,\n BalanceUpdate,\n} from '@metamask/core-backend';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport { isCaipChainId } from '@metamask/utils';\nimport BigNumberJS from 'bignumber.js';\n\nimport type { AssetsControllerMessenger } from '../AssetsController.js';\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport type {\n AssetBalance,\n AssetMetadata,\n ChainId,\n Caip19AssetId,\n DataRequest,\n DataResponse,\n} from '../types.js';\nimport { safeNormalizeAssetId } from '../utils/index.js';\nimport { AbstractDataSource } from './AbstractDataSource.js';\nimport type { DataSourceState } from './AbstractDataSource.js';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'AccountActivityDataSource';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// BALANCE UPDATE PROCESSING\n// ============================================================================\n\n/**\n * Convert AccountActivityMessage balance updates into a {@link DataResponse}\n * for AssetsController.\n *\n * @param updates - Balance updates from account-activity websocket payload.\n * @param accountId - Internal account UUID.\n * @param getAssetType - Resolver for asset metadata type.\n * @returns DataResponse with merge mode when balances are present.\n */\nfunction processAccountActivityBalanceUpdates(\n updates: BalanceUpdate[],\n accountId: string,\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl',\n): DataResponse {\n const assetsBalance = Object.create(null) as Record<\n string,\n Record<Caip19AssetId, AssetBalance>\n >;\n assetsBalance[accountId] = Object.create(null) as Record<\n Caip19AssetId,\n AssetBalance\n >;\n const assetsMetadata = Object.create(null) as Record<\n Caip19AssetId,\n AssetMetadata\n >;\n\n for (const update of updates) {\n const { asset, postBalance } = update;\n\n if (!asset || !postBalance) {\n continue;\n }\n\n // The websocket sends lower-case ERC-20 addresses, while state and the\n // rest of the pipeline key assets by their checksummed ID. Normalizing\n // here keeps middleware comparisons (detection, custom-asset graduation,\n // occurrence filtering) from treating an existing holding as brand new.\n const assetId = safeNormalizeAssetId(asset.type as Caip19AssetId);\n\n if (asset.decimals === undefined) {\n continue;\n }\n\n const rawBalanceStr = postBalance.amount.startsWith('0x')\n ? BigInt(postBalance.amount).toString()\n : postBalance.amount;\n\n const humanReadableAmount = new BigNumberJS(rawBalanceStr)\n .dividedBy(new BigNumberJS(10).pow(asset.decimals))\n .toFixed();\n\n assetsBalance[accountId][assetId] = {\n amount: humanReadableAmount,\n };\n\n assetsMetadata[assetId] = {\n type: getAssetType(assetId),\n symbol: asset.unit,\n name: asset.unit,\n decimals: asset.decimals,\n };\n }\n\n const response: DataResponse = { updateMode: 'merge' };\n if (Object.keys(assetsBalance[accountId]).length > 0) {\n response.assetsBalance = assetsBalance;\n response.assetsInfo = assetsMetadata;\n }\n\n return response;\n}\n\n// ============================================================================\n// MESSENGER TYPES\n// ============================================================================\n\n/** Allowed events that AccountActivityDataSource subscribes to. */\nexport type AccountActivityDataSourceAllowedEvents =\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceStatusChangedEvent;\n\n// ============================================================================\n// STATE\n// ============================================================================\n\nexport type AccountActivityDataSourceState = DataSourceState;\n\nconst defaultState: AccountActivityDataSourceState = {\n activeChains: [],\n};\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport type AccountActivityDataSourceOptions = {\n /** The AssetsController messenger (shared by all data sources). */\n messenger: AssetsControllerMessenger;\n /** Called when active chains are updated. Pass dataSourceName so the controller knows the source. */\n onActiveChainsUpdated: (\n dataSourceName: string,\n chains: ChainId[],\n previousChains: ChainId[],\n ) => void;\n /** Returns the asset type ('native' | 'erc20' | 'spl') for a given CAIP-19 asset ID. */\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl';\n /**\n * Pushes decoded balance updates to the controller (bound to\n * `AssetsController.handleAssetsUpdate`). AADS is event-driven and never\n * takes part in the controller's subscribe handoff, so it receives this\n * callback directly instead of via a `SubscriptionRequest`.\n */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n state?: Partial<AccountActivityDataSourceState>;\n};\n\n// ============================================================================\n// ACCOUNT ACTIVITY DATA SOURCE\n// ============================================================================\n\n/**\n * Data source that consumes real-time updates from `AccountActivityService`.\n *\n * `AccountActivityService` owns the WebSocket connection and channel\n * subscriptions; this data source is a thin consumer of the two high-level\n * events that it publishes:\n *\n * - `AccountActivityService:balanceUpdated` — post-transaction balances for the\n * subscribed account(s). The address is resolved against the wallet's\n * selected account group, transformed into a {@link DataResponse} (via\n * {@link processAccountActivityBalanceUpdates}), and pushed to the controller\n * through the injected `onAssetsUpdate` callback.\n * - `AccountActivityService:statusChanged` — per-chain \"up\"/\"down\" notifications.\n * A chain reported \"up\" is claimed as an active chain (WebSocket is providing\n * real-time data); a chain reported \"down\" is released so polling data sources\n * can take over. Each change is applied directly via `updateActiveChains`,\n * which notifies the controller through `onActiveChainsUpdated`.\n *\n * This data source does NOT debounce, jitter, or gate chain updates itself.\n * Coalescing (collapsing bursts) and jitter (staggering the WS-subscribe herd\n * across clients) live in `AssetsController`, where the expensive re-subscribe\n * happens and where updates from all data sources converge. `activeChains` are\n * never seeded from the accounts API; they only ever reflect live\n * `statusChanged` events (the service flushes all tracked chains as \"down\" when\n * the WebSocket disconnects, releasing them).\n *\n * It subscribes to these events in its constructor (the same way\n * `TokenBalancesController` does). Unlike the polling data sources it does not\n * take part in the controller's subscribe/unsubscribe handoff: `subscribe` is a\n * no-op, balance updates are routed by resolving the address against the\n * wallet, and updates are pushed through the injected `onAssetsUpdate` callback.\n */\nexport class AccountActivityDataSource extends AbstractDataSource<\n typeof CONTROLLER_NAME,\n AccountActivityDataSourceState\n> {\n readonly #messenger: AssetsControllerMessenger;\n\n readonly #onActiveChainsUpdated: (\n dataSourceName: string,\n chains: ChainId[],\n previousChains: ChainId[],\n ) => void;\n\n readonly #getAssetType: (\n assetId: Caip19AssetId,\n ) => 'native' | 'erc20' | 'spl';\n\n readonly #onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n\n readonly #onBalanceUpdatedBound: (event: {\n address: string;\n chain: string;\n updates: BalanceUpdate[];\n }) => void;\n\n constructor(options: AccountActivityDataSourceOptions) {\n super(CONTROLLER_NAME, {\n ...defaultState,\n ...options.state,\n });\n\n this.#messenger = options.messenger;\n this.#onActiveChainsUpdated = options.onActiveChainsUpdated;\n this.#getAssetType = options.getAssetType;\n this.#onAssetsUpdate = options.onAssetsUpdate;\n this.#onBalanceUpdatedBound = this.#onBalanceUpdated.bind(this);\n\n this.#subscribeToEvents();\n }\n\n // ============================================================================\n // EVENT SUBSCRIPTIONS\n // ============================================================================\n\n #subscribeToEvents(): void {\n this.#messenger.subscribe(\n 'AccountActivityService:balanceUpdated',\n this.#onBalanceUpdatedBound,\n );\n this.#messenger.subscribe(\n 'AccountActivityService:statusChanged',\n this.#onAccountActivityStatusChanged,\n );\n }\n\n // ============================================================================\n // SUBSCRIBE / UNSUBSCRIBE\n // ============================================================================\n\n /**\n * AADS is event-driven and chain-agnostic: it never participates in the\n * controller's subscribe/unsubscribe handoff. Incoming `balanceUpdated`\n * events are routed by resolving the address against the wallet's accounts\n * (see `#onBalanceUpdated`), and updates are pushed through the injected\n * `#onAssetsUpdate` callback. This override exists only to satisfy the\n * abstract contract and is intentionally a no-op.\n */\n async subscribe(): Promise<void> {\n // Intentionally empty — see method doc.\n }\n\n // ============================================================================\n // BALANCE UPDATES\n // ============================================================================\n\n #onBalanceUpdated({\n address,\n chain,\n updates,\n }: {\n address: string;\n chain: string;\n updates: BalanceUpdate[];\n }): void {\n try {\n if (!address || !chain || !updates || updates.length === 0) {\n return;\n }\n\n const account = this.#findAccountForAddress(address);\n if (!account) {\n return;\n }\n\n const chainId = chain as ChainId;\n\n const response = processAccountActivityBalanceUpdates(\n updates,\n account.id,\n (assetId) => this.#getAssetType(assetId),\n );\n\n if (!response.assetsBalance) {\n return;\n }\n\n const request: DataRequest = {\n accountsWithSupportedChains: [{ account, supportedChains: [chainId] }],\n chainIds: [chainId],\n dataTypes: ['balance', 'metadata'],\n };\n\n Promise.resolve(this.#onAssetsUpdate(response, request)).catch(\n (error) => {\n log('Failed to report balance update', { error });\n },\n );\n } catch (error) {\n log('Error handling balance update', error);\n }\n }\n\n /**\n * Find the wallet account matching the given activity address. EVM addresses\n * are matched case-insensitively; other namespaces are matched exactly.\n *\n * The candidate set is the accounts of the selected account group, resolved\n * from the wallet at event time (rather than from a stored subscription), so\n * AADS does not need to be re-subscribed whenever the account set changes.\n *\n * @param address - The account address from the activity message.\n * @returns The matching account, or null.\n */\n #findAccountForAddress(address: string): InternalAccount | null {\n const isEvm = address.startsWith('0x');\n\n return (\n this.#getWalletAccounts().find((candidate) =>\n isEvm\n ? candidate.address.toLowerCase() === address.toLowerCase()\n : candidate.address === address,\n ) ?? null\n );\n }\n\n /**\n * Resolve the accounts of the selected account group from the wallet,\n * mirroring `AssetsController.#getSelectedAccounts`.\n *\n * @returns The accounts of the selected account group.\n */\n #getWalletAccounts(): InternalAccount[] {\n const accounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n if (accounts.length > 0) {\n return accounts;\n }\n\n const selectedAccount = this.#messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n return selectedAccount ? [selectedAccount] : [];\n }\n\n // ============================================================================\n // STATUS CHANGES\n // ============================================================================\n\n /**\n * Handle a `statusChanged` notification. Chains reported \"up\" are claimed as\n * active (WebSocket provides real-time data); chains reported \"down\" are\n * released so polling data sources take over. The change is applied directly;\n * coalescing and jitter are handled by `AssetsController`.\n *\n * @param options - The status change notification.\n * @param options.chainIds - The CAIP-2 chain IDs whose status changed.\n * @param options.status - Whether the chains are `up` or `down`.\n * @param options.timestamp - Optional timestamp of the status change.\n */\n readonly #onAccountActivityStatusChanged = ({\n chainIds,\n status,\n }: {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n }): void => {\n try {\n // Act on every namespace (eip155, solana, etc.); AssetsController is\n // multichain. Only skip identifiers that are not valid CAIP-2 chain IDs.\n const validChains = chainIds.filter((chainId) =>\n isCaipChainId(chainId),\n ) as ChainId[];\n\n if (validChains.length === 0) {\n return;\n }\n\n const next = new Set(this.state.activeChains);\n if (status === 'up') {\n for (const chainId of validChains) {\n next.add(chainId);\n }\n } else {\n for (const chainId of validChains) {\n next.delete(chainId);\n }\n }\n\n const previous = [...this.state.activeChains];\n this.updateActiveChains(Array.from(next), (updatedChains) =>\n this.#onActiveChainsUpdated(this.getName(), updatedChains, previous),\n );\n } catch (error) {\n log('Error handling status change', error);\n }\n };\n\n // ============================================================================\n // CLEANUP\n // ============================================================================\n\n destroy(): void {\n try {\n this.#messenger.unsubscribe(\n 'AccountActivityService:balanceUpdated',\n this.#onBalanceUpdatedBound,\n );\n } catch (error) {\n log('Failed to unsubscribe from balanceUpdated', { error });\n }\n\n try {\n this.#messenger.unsubscribe(\n 'AccountActivityService:statusChanged',\n this.#onAccountActivityStatusChanged,\n );\n } catch (error) {\n log('Failed to unsubscribe from statusChanged', { error });\n }\n\n super.destroy();\n }\n}\n\n// ============================================================================\n// FACTORY FUNCTION\n// ============================================================================\n\n/**\n * Creates an AccountActivityDataSource instance.\n *\n * @param options - Configuration options for the data source.\n * @returns A new AccountActivityDataSource instance.\n */\nexport function createAccountActivityDataSource(\n options: AccountActivityDataSourceOptions,\n): AccountActivityDataSource {\n return new AccountActivityDataSource(options);\n}\n"]}
1
+ {"version":3,"file":"AccountActivityDataSource.cjs","sourceRoot":"","sources":["../../src/data-sources/AccountActivityDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountActivityServiceBalanceUpdatedEvent,\n AccountActivityServiceStatusChangedEvent,\n BalanceUpdate,\n} from '@metamask/core-backend';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport { isCaipChainId } from '@metamask/utils';\nimport BigNumberJS from 'bignumber.js';\n\nimport type { AssetsControllerMessenger } from '../AssetsController.js';\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport type {\n AssetBalance,\n AssetMetadata,\n ChainId,\n Caip19AssetId,\n DataRequest,\n DataResponse,\n} from '../types.js';\nimport { safeNormalizeAssetId } from '../utils/index.js';\nimport { AbstractDataSource } from './AbstractDataSource.js';\nimport type { DataSourceState } from './AbstractDataSource.js';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'AccountActivityDataSource';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// BALANCE UPDATE PROCESSING\n// ============================================================================\n\n/**\n * Convert AccountActivityMessage balance updates into a {@link DataResponse}\n * for AssetsController.\n *\n * @param updates - Balance updates from account-activity websocket payload.\n * @param accountId - Internal account UUID.\n * @param getAssetType - Resolver for asset metadata type.\n * @returns DataResponse with merge mode when balances are present.\n */\nfunction processAccountActivityBalanceUpdates(\n updates: BalanceUpdate[],\n accountId: string,\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl',\n): DataResponse {\n const assetsBalance = Object.create(null) as Record<\n string,\n Record<Caip19AssetId, AssetBalance>\n >;\n assetsBalance[accountId] = Object.create(null) as Record<\n Caip19AssetId,\n AssetBalance\n >;\n const assetsMetadata = Object.create(null) as Record<\n Caip19AssetId,\n AssetMetadata\n >;\n\n for (const update of updates) {\n const { asset, postBalance } = update;\n\n if (!asset || !postBalance) {\n continue;\n }\n\n // The websocket sends lower-case ERC-20 addresses, while state and the\n // rest of the pipeline key assets by their checksummed ID. Normalizing\n // here keeps middleware comparisons (detection, custom-asset graduation,\n // occurrence filtering) from treating an existing holding as brand new.\n const assetId = safeNormalizeAssetId(asset.type as Caip19AssetId);\n\n if (asset.decimals === undefined) {\n continue;\n }\n\n const rawBalanceStr = postBalance.amount.startsWith('0x')\n ? BigInt(postBalance.amount).toString()\n : postBalance.amount;\n\n const humanReadableAmount = new BigNumberJS(rawBalanceStr)\n .dividedBy(new BigNumberJS(10).pow(asset.decimals))\n .toFixed();\n\n assetsBalance[accountId][assetId] = {\n amount: humanReadableAmount,\n };\n\n assetsMetadata[assetId] = {\n type: getAssetType(assetId),\n symbol: asset.unit,\n name: asset.unit,\n decimals: asset.decimals,\n };\n }\n\n const response: DataResponse = { updateMode: 'merge' };\n if (Object.keys(assetsBalance[accountId]).length > 0) {\n response.assetsBalance = assetsBalance;\n response.assetsInfo = assetsMetadata;\n }\n\n return response;\n}\n\n// ============================================================================\n// MESSENGER TYPES\n// ============================================================================\n\n/** Allowed events that AccountActivityDataSource subscribes to. */\nexport type AccountActivityDataSourceAllowedEvents =\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceStatusChangedEvent;\n\n// ============================================================================\n// STATE\n// ============================================================================\n\nexport type AccountActivityDataSourceState = DataSourceState;\n\nconst defaultState: AccountActivityDataSourceState = {\n activeChains: [],\n};\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport type AccountActivityDataSourceOptions = {\n /** The AssetsController messenger (shared by all data sources). */\n messenger: AssetsControllerMessenger;\n /** Called when active chains are updated. Pass dataSourceName so the controller knows the source. */\n onActiveChainsUpdated: (\n dataSourceName: string,\n chains: ChainId[],\n previousChains: ChainId[],\n ) => void;\n /** Returns the asset type ('native' | 'erc20' | 'spl') for a given CAIP-19 asset ID. */\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl';\n /**\n * Pushes decoded balance updates to the controller (bound to\n * `AssetsController.handleAssetsUpdate`). AADS is event-driven and never\n * takes part in the controller's subscribe handoff, so it receives this\n * callback directly instead of via a `SubscriptionRequest`.\n */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n state?: Partial<AccountActivityDataSourceState>;\n};\n\n// ============================================================================\n// ACCOUNT ACTIVITY DATA SOURCE\n// ============================================================================\n\n/**\n * Data source that consumes real-time updates from `AccountActivityService`.\n *\n * `AccountActivityService` owns the WebSocket connection and channel\n * subscriptions; this data source is a thin consumer of the two high-level\n * events that it publishes:\n *\n * - `AccountActivityService:balanceUpdated` — post-transaction balances for the\n * subscribed account(s). The address is resolved against the wallet's\n * selected account group, transformed into a {@link DataResponse} (via\n * {@link processAccountActivityBalanceUpdates}), and pushed to the controller\n * through the injected `onAssetsUpdate` callback.\n * - `AccountActivityService:statusChanged` — per-chain \"up\"/\"down\" notifications.\n * A chain reported \"up\" is claimed as an active chain (WebSocket is providing\n * real-time data); a chain reported \"down\" is released so polling data sources\n * can take over. Each change is applied directly via `updateActiveChains`,\n * which notifies the controller through `onActiveChainsUpdated`.\n *\n * This data source does NOT debounce, jitter, or gate chain updates itself.\n * Coalescing (collapsing bursts) and jitter (staggering the WS-subscribe herd\n * across clients) live in `AssetsController`, where the expensive re-subscribe\n * happens and where updates from all data sources converge. `activeChains` are\n * never seeded from the accounts API; they only ever reflect live\n * `statusChanged` events (the service flushes all tracked chains as \"down\" when\n * the WebSocket disconnects, releasing them).\n *\n * It subscribes to these events in its constructor (the same way\n * `TokenBalancesController` does). Unlike the polling data sources it does not\n * take part in the controller's subscribe/unsubscribe handoff: `subscribe` is a\n * no-op, balance updates are routed by resolving the address against the\n * wallet, and updates are pushed through the injected `onAssetsUpdate` callback.\n */\nexport class AccountActivityDataSource extends AbstractDataSource<\n typeof CONTROLLER_NAME,\n AccountActivityDataSourceState\n> {\n readonly #messenger: AssetsControllerMessenger;\n\n readonly #onActiveChainsUpdated: (\n dataSourceName: string,\n chains: ChainId[],\n previousChains: ChainId[],\n ) => void;\n\n readonly #getAssetType: (\n assetId: Caip19AssetId,\n ) => 'native' | 'erc20' | 'spl';\n\n readonly #onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n\n readonly #onBalanceUpdatedBound: (event: {\n address: string;\n chain: string;\n updates: BalanceUpdate[];\n }) => void;\n\n constructor(options: AccountActivityDataSourceOptions) {\n super(CONTROLLER_NAME, {\n ...defaultState,\n ...options.state,\n });\n\n this.#messenger = options.messenger;\n this.#onActiveChainsUpdated = options.onActiveChainsUpdated;\n this.#getAssetType = options.getAssetType;\n this.#onAssetsUpdate = options.onAssetsUpdate;\n this.#onBalanceUpdatedBound = this.#onBalanceUpdated.bind(this);\n\n this.#subscribeToEvents();\n }\n\n // ============================================================================\n // EVENT SUBSCRIPTIONS\n // ============================================================================\n\n #subscribeToEvents(): void {\n this.#messenger.subscribe(\n 'AccountActivityService:balanceUpdated',\n this.#onBalanceUpdatedBound,\n );\n this.#messenger.subscribe(\n 'AccountActivityService:statusChanged',\n this.#onAccountActivityStatusChanged,\n );\n }\n\n // ============================================================================\n // SUBSCRIBE / UNSUBSCRIBE\n // ============================================================================\n\n // Pins on active chains are claimed via the inherited `claimCustomAssets`:\n // the activity stream pushes all activity for the address, pins included.\n\n /**\n * AADS is event-driven and chain-agnostic: it never participates in the\n * controller's subscribe/unsubscribe handoff. Incoming `balanceUpdated`\n * events are routed by resolving the address against the wallet's accounts\n * (see `#onBalanceUpdated`), and updates are pushed through the injected\n * `#onAssetsUpdate` callback. This override exists only to satisfy the\n * abstract contract and is intentionally a no-op.\n */\n async subscribe(): Promise<void> {\n // Intentionally empty — see method doc.\n }\n\n // ============================================================================\n // BALANCE UPDATES\n // ============================================================================\n\n #onBalanceUpdated({\n address,\n chain,\n updates,\n }: {\n address: string;\n chain: string;\n updates: BalanceUpdate[];\n }): void {\n try {\n if (!address || !chain || !updates || updates.length === 0) {\n return;\n }\n\n const account = this.#findAccountForAddress(address);\n if (!account) {\n return;\n }\n\n const chainId = chain as ChainId;\n\n const response = processAccountActivityBalanceUpdates(\n updates,\n account.id,\n (assetId) => this.#getAssetType(assetId),\n );\n\n if (!response.assetsBalance) {\n return;\n }\n\n const request: DataRequest = {\n accountsWithSupportedChains: [{ account, supportedChains: [chainId] }],\n chainIds: [chainId],\n dataTypes: ['balance', 'metadata'],\n };\n\n Promise.resolve(this.#onAssetsUpdate(response, request)).catch(\n (error) => {\n log('Failed to report balance update', { error });\n },\n );\n } catch (error) {\n log('Error handling balance update', error);\n }\n }\n\n /**\n * Find the wallet account matching the given activity address. EVM addresses\n * are matched case-insensitively; other namespaces are matched exactly.\n *\n * The candidate set is the accounts of the selected account group, resolved\n * from the wallet at event time (rather than from a stored subscription), so\n * AADS does not need to be re-subscribed whenever the account set changes.\n *\n * @param address - The account address from the activity message.\n * @returns The matching account, or null.\n */\n #findAccountForAddress(address: string): InternalAccount | null {\n const isEvm = address.startsWith('0x');\n\n return (\n this.#getWalletAccounts().find((candidate) =>\n isEvm\n ? candidate.address.toLowerCase() === address.toLowerCase()\n : candidate.address === address,\n ) ?? null\n );\n }\n\n /**\n * Resolve the accounts of the selected account group from the wallet,\n * mirroring `AssetsController.#getSelectedAccounts`.\n *\n * @returns The accounts of the selected account group.\n */\n #getWalletAccounts(): InternalAccount[] {\n const accounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n if (accounts.length > 0) {\n return accounts;\n }\n\n const selectedAccount = this.#messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n return selectedAccount ? [selectedAccount] : [];\n }\n\n // ============================================================================\n // STATUS CHANGES\n // ============================================================================\n\n /**\n * Handle a `statusChanged` notification. Chains reported \"up\" are claimed as\n * active (WebSocket provides real-time data); chains reported \"down\" are\n * released so polling data sources take over. The change is applied directly;\n * coalescing and jitter are handled by `AssetsController`.\n *\n * @param options - The status change notification.\n * @param options.chainIds - The CAIP-2 chain IDs whose status changed.\n * @param options.status - Whether the chains are `up` or `down`.\n * @param options.timestamp - Optional timestamp of the status change.\n */\n readonly #onAccountActivityStatusChanged = ({\n chainIds,\n status,\n }: {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n }): void => {\n try {\n // Act on every namespace (eip155, solana, etc.); AssetsController is\n // multichain. Only skip identifiers that are not valid CAIP-2 chain IDs.\n const validChains = chainIds.filter((chainId) =>\n isCaipChainId(chainId),\n ) as ChainId[];\n\n if (validChains.length === 0) {\n return;\n }\n\n const next = new Set(this.state.activeChains);\n if (status === 'up') {\n for (const chainId of validChains) {\n next.add(chainId);\n }\n } else {\n for (const chainId of validChains) {\n next.delete(chainId);\n }\n }\n\n const previous = [...this.state.activeChains];\n this.updateActiveChains(Array.from(next), (updatedChains) =>\n this.#onActiveChainsUpdated(this.getName(), updatedChains, previous),\n );\n } catch (error) {\n log('Error handling status change', error);\n }\n };\n\n // ============================================================================\n // CLEANUP\n // ============================================================================\n\n destroy(): void {\n try {\n this.#messenger.unsubscribe(\n 'AccountActivityService:balanceUpdated',\n this.#onBalanceUpdatedBound,\n );\n } catch (error) {\n log('Failed to unsubscribe from balanceUpdated', { error });\n }\n\n try {\n this.#messenger.unsubscribe(\n 'AccountActivityService:statusChanged',\n this.#onAccountActivityStatusChanged,\n );\n } catch (error) {\n log('Failed to unsubscribe from statusChanged', { error });\n }\n\n super.destroy();\n }\n}\n\n// ============================================================================\n// FACTORY FUNCTION\n// ============================================================================\n\n/**\n * Creates an AccountActivityDataSource instance.\n *\n * @param options - Configuration options for the data source.\n * @returns A new AccountActivityDataSource instance.\n */\nexport function createAccountActivityDataSource(\n options: AccountActivityDataSourceOptions,\n): AccountActivityDataSource {\n return new AccountActivityDataSource(options);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"AccountActivityDataSource.d.cts","sourceRoot":"","sources":["../../src/data-sources/AccountActivityDataSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yCAAyC,EACzC,wCAAwC,EAEzC,+BAA+B;AAKhC,OAAO,KAAK,EAAE,yBAAyB,EAAE,gCAA+B;AAExE,OAAO,KAAK,EAGV,OAAO,EACP,aAAa,EACb,WAAW,EACX,YAAY,EACb,qBAAoB;AAErB,OAAO,EAAE,kBAAkB,EAAE,iCAAgC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,iCAAgC;AAM/D,QAAA,MAAM,eAAe,8BAA8B,CAAC;AAqFpD,mEAAmE;AACnE,MAAM,MAAM,sCAAsC,GAC9C,yCAAyC,GACzC,wCAAwC,CAAC;AAM7C,MAAM,MAAM,8BAA8B,GAAG,eAAe,CAAC;AAU7D,MAAM,MAAM,gCAAgC,GAAG;IAC7C,mEAAmE;IACnE,SAAS,EAAE,yBAAyB,CAAC;IACrC,qGAAqG;IACrG,qBAAqB,EAAE,CACrB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,OAAO,EAAE,EACjB,cAAc,EAAE,OAAO,EAAE,KACtB,IAAI,CAAC;IACV,wFAAwF;IACxF,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;IACrE;;;;;OAKG;IACH,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC,8BAA8B,CAAC,CAAC;CACjD,CAAC;AAMF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,yBAA0B,SAAQ,kBAAkB,CAC/D,OAAO,eAAe,EACtB,8BAA8B,CAC/B;;gBAwBa,OAAO,EAAE,gCAAgC;IAkCrD;;;;;;;OAOG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IA4JhC,OAAO,IAAI,IAAI;CAqBhB;AAMD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,yBAAyB,CAE3B"}
1
+ {"version":3,"file":"AccountActivityDataSource.d.cts","sourceRoot":"","sources":["../../src/data-sources/AccountActivityDataSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yCAAyC,EACzC,wCAAwC,EAEzC,+BAA+B;AAKhC,OAAO,KAAK,EAAE,yBAAyB,EAAE,gCAA+B;AAExE,OAAO,KAAK,EAGV,OAAO,EACP,aAAa,EACb,WAAW,EACX,YAAY,EACb,qBAAoB;AAErB,OAAO,EAAE,kBAAkB,EAAE,iCAAgC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,iCAAgC;AAM/D,QAAA,MAAM,eAAe,8BAA8B,CAAC;AAqFpD,mEAAmE;AACnE,MAAM,MAAM,sCAAsC,GAC9C,yCAAyC,GACzC,wCAAwC,CAAC;AAM7C,MAAM,MAAM,8BAA8B,GAAG,eAAe,CAAC;AAU7D,MAAM,MAAM,gCAAgC,GAAG;IAC7C,mEAAmE;IACnE,SAAS,EAAE,yBAAyB,CAAC;IACrC,qGAAqG;IACrG,qBAAqB,EAAE,CACrB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,OAAO,EAAE,EACjB,cAAc,EAAE,OAAO,EAAE,KACtB,IAAI,CAAC;IACV,wFAAwF;IACxF,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;IACrE;;;;;OAKG;IACH,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC,8BAA8B,CAAC,CAAC;CACjD,CAAC;AAMF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,yBAA0B,SAAQ,kBAAkB,CAC/D,OAAO,eAAe,EACtB,8BAA8B,CAC/B;;gBAwBa,OAAO,EAAE,gCAAgC;IAqCrD;;;;;;;OAOG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IA4JhC,OAAO,IAAI,IAAI;CAqBhB;AAMD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,yBAAyB,CAE3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"AccountActivityDataSource.d.mts","sourceRoot":"","sources":["../../src/data-sources/AccountActivityDataSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yCAAyC,EACzC,wCAAwC,EAEzC,+BAA+B;AAKhC,OAAO,KAAK,EAAE,yBAAyB,EAAE,gCAA+B;AAExE,OAAO,KAAK,EAGV,OAAO,EACP,aAAa,EACb,WAAW,EACX,YAAY,EACb,qBAAoB;AAErB,OAAO,EAAE,kBAAkB,EAAE,iCAAgC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,iCAAgC;AAM/D,QAAA,MAAM,eAAe,8BAA8B,CAAC;AAqFpD,mEAAmE;AACnE,MAAM,MAAM,sCAAsC,GAC9C,yCAAyC,GACzC,wCAAwC,CAAC;AAM7C,MAAM,MAAM,8BAA8B,GAAG,eAAe,CAAC;AAU7D,MAAM,MAAM,gCAAgC,GAAG;IAC7C,mEAAmE;IACnE,SAAS,EAAE,yBAAyB,CAAC;IACrC,qGAAqG;IACrG,qBAAqB,EAAE,CACrB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,OAAO,EAAE,EACjB,cAAc,EAAE,OAAO,EAAE,KACtB,IAAI,CAAC;IACV,wFAAwF;IACxF,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;IACrE;;;;;OAKG;IACH,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC,8BAA8B,CAAC,CAAC;CACjD,CAAC;AAMF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,yBAA0B,SAAQ,kBAAkB,CAC/D,OAAO,eAAe,EACtB,8BAA8B,CAC/B;;gBAwBa,OAAO,EAAE,gCAAgC;IAkCrD;;;;;;;OAOG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IA4JhC,OAAO,IAAI,IAAI;CAqBhB;AAMD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,yBAAyB,CAE3B"}
1
+ {"version":3,"file":"AccountActivityDataSource.d.mts","sourceRoot":"","sources":["../../src/data-sources/AccountActivityDataSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yCAAyC,EACzC,wCAAwC,EAEzC,+BAA+B;AAKhC,OAAO,KAAK,EAAE,yBAAyB,EAAE,gCAA+B;AAExE,OAAO,KAAK,EAGV,OAAO,EACP,aAAa,EACb,WAAW,EACX,YAAY,EACb,qBAAoB;AAErB,OAAO,EAAE,kBAAkB,EAAE,iCAAgC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,iCAAgC;AAM/D,QAAA,MAAM,eAAe,8BAA8B,CAAC;AAqFpD,mEAAmE;AACnE,MAAM,MAAM,sCAAsC,GAC9C,yCAAyC,GACzC,wCAAwC,CAAC;AAM7C,MAAM,MAAM,8BAA8B,GAAG,eAAe,CAAC;AAU7D,MAAM,MAAM,gCAAgC,GAAG;IAC7C,mEAAmE;IACnE,SAAS,EAAE,yBAAyB,CAAC;IACrC,qGAAqG;IACrG,qBAAqB,EAAE,CACrB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,OAAO,EAAE,EACjB,cAAc,EAAE,OAAO,EAAE,KACtB,IAAI,CAAC;IACV,wFAAwF;IACxF,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;IACrE;;;;;OAKG;IACH,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC,8BAA8B,CAAC,CAAC;CACjD,CAAC;AAMF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,yBAA0B,SAAQ,kBAAkB,CAC/D,OAAO,eAAe,EACtB,8BAA8B,CAC/B;;gBAwBa,OAAO,EAAE,gCAAgC;IAqCrD;;;;;;;OAOG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IA4JhC,OAAO,IAAI,IAAI;CAqBhB;AAMD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,yBAAyB,CAE3B"}
@@ -172,6 +172,8 @@ export class AccountActivityDataSource extends AbstractDataSource {
172
172
  // ============================================================================
173
173
  // SUBSCRIBE / UNSUBSCRIBE
174
174
  // ============================================================================
175
+ // Pins on active chains are claimed via the inherited `claimCustomAssets`:
176
+ // the activity stream pushes all activity for the address, pins included.
175
177
  /**
176
178
  * AADS is event-driven and chain-agnostic: it never participates in the
177
179
  * controller's subscribe/unsubscribe handoff. Incoming `balanceUpdated`
@@ -1 +1 @@
1
- {"version":3,"file":"AccountActivityDataSource.mjs","sourceRoot":"","sources":["../../src/data-sources/AccountActivityDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountActivityServiceBalanceUpdatedEvent,\n AccountActivityServiceStatusChangedEvent,\n BalanceUpdate,\n} from '@metamask/core-backend';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport { isCaipChainId } from '@metamask/utils';\nimport BigNumberJS from 'bignumber.js';\n\nimport type { AssetsControllerMessenger } from '../AssetsController.js';\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport type {\n AssetBalance,\n AssetMetadata,\n ChainId,\n Caip19AssetId,\n DataRequest,\n DataResponse,\n} from '../types.js';\nimport { safeNormalizeAssetId } from '../utils/index.js';\nimport { AbstractDataSource } from './AbstractDataSource.js';\nimport type { DataSourceState } from './AbstractDataSource.js';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'AccountActivityDataSource';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// BALANCE UPDATE PROCESSING\n// ============================================================================\n\n/**\n * Convert AccountActivityMessage balance updates into a {@link DataResponse}\n * for AssetsController.\n *\n * @param updates - Balance updates from account-activity websocket payload.\n * @param accountId - Internal account UUID.\n * @param getAssetType - Resolver for asset metadata type.\n * @returns DataResponse with merge mode when balances are present.\n */\nfunction processAccountActivityBalanceUpdates(\n updates: BalanceUpdate[],\n accountId: string,\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl',\n): DataResponse {\n const assetsBalance = Object.create(null) as Record<\n string,\n Record<Caip19AssetId, AssetBalance>\n >;\n assetsBalance[accountId] = Object.create(null) as Record<\n Caip19AssetId,\n AssetBalance\n >;\n const assetsMetadata = Object.create(null) as Record<\n Caip19AssetId,\n AssetMetadata\n >;\n\n for (const update of updates) {\n const { asset, postBalance } = update;\n\n if (!asset || !postBalance) {\n continue;\n }\n\n // The websocket sends lower-case ERC-20 addresses, while state and the\n // rest of the pipeline key assets by their checksummed ID. Normalizing\n // here keeps middleware comparisons (detection, custom-asset graduation,\n // occurrence filtering) from treating an existing holding as brand new.\n const assetId = safeNormalizeAssetId(asset.type as Caip19AssetId);\n\n if (asset.decimals === undefined) {\n continue;\n }\n\n const rawBalanceStr = postBalance.amount.startsWith('0x')\n ? BigInt(postBalance.amount).toString()\n : postBalance.amount;\n\n const humanReadableAmount = new BigNumberJS(rawBalanceStr)\n .dividedBy(new BigNumberJS(10).pow(asset.decimals))\n .toFixed();\n\n assetsBalance[accountId][assetId] = {\n amount: humanReadableAmount,\n };\n\n assetsMetadata[assetId] = {\n type: getAssetType(assetId),\n symbol: asset.unit,\n name: asset.unit,\n decimals: asset.decimals,\n };\n }\n\n const response: DataResponse = { updateMode: 'merge' };\n if (Object.keys(assetsBalance[accountId]).length > 0) {\n response.assetsBalance = assetsBalance;\n response.assetsInfo = assetsMetadata;\n }\n\n return response;\n}\n\n// ============================================================================\n// MESSENGER TYPES\n// ============================================================================\n\n/** Allowed events that AccountActivityDataSource subscribes to. */\nexport type AccountActivityDataSourceAllowedEvents =\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceStatusChangedEvent;\n\n// ============================================================================\n// STATE\n// ============================================================================\n\nexport type AccountActivityDataSourceState = DataSourceState;\n\nconst defaultState: AccountActivityDataSourceState = {\n activeChains: [],\n};\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport type AccountActivityDataSourceOptions = {\n /** The AssetsController messenger (shared by all data sources). */\n messenger: AssetsControllerMessenger;\n /** Called when active chains are updated. Pass dataSourceName so the controller knows the source. */\n onActiveChainsUpdated: (\n dataSourceName: string,\n chains: ChainId[],\n previousChains: ChainId[],\n ) => void;\n /** Returns the asset type ('native' | 'erc20' | 'spl') for a given CAIP-19 asset ID. */\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl';\n /**\n * Pushes decoded balance updates to the controller (bound to\n * `AssetsController.handleAssetsUpdate`). AADS is event-driven and never\n * takes part in the controller's subscribe handoff, so it receives this\n * callback directly instead of via a `SubscriptionRequest`.\n */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n state?: Partial<AccountActivityDataSourceState>;\n};\n\n// ============================================================================\n// ACCOUNT ACTIVITY DATA SOURCE\n// ============================================================================\n\n/**\n * Data source that consumes real-time updates from `AccountActivityService`.\n *\n * `AccountActivityService` owns the WebSocket connection and channel\n * subscriptions; this data source is a thin consumer of the two high-level\n * events that it publishes:\n *\n * - `AccountActivityService:balanceUpdated` — post-transaction balances for the\n * subscribed account(s). The address is resolved against the wallet's\n * selected account group, transformed into a {@link DataResponse} (via\n * {@link processAccountActivityBalanceUpdates}), and pushed to the controller\n * through the injected `onAssetsUpdate` callback.\n * - `AccountActivityService:statusChanged` — per-chain \"up\"/\"down\" notifications.\n * A chain reported \"up\" is claimed as an active chain (WebSocket is providing\n * real-time data); a chain reported \"down\" is released so polling data sources\n * can take over. Each change is applied directly via `updateActiveChains`,\n * which notifies the controller through `onActiveChainsUpdated`.\n *\n * This data source does NOT debounce, jitter, or gate chain updates itself.\n * Coalescing (collapsing bursts) and jitter (staggering the WS-subscribe herd\n * across clients) live in `AssetsController`, where the expensive re-subscribe\n * happens and where updates from all data sources converge. `activeChains` are\n * never seeded from the accounts API; they only ever reflect live\n * `statusChanged` events (the service flushes all tracked chains as \"down\" when\n * the WebSocket disconnects, releasing them).\n *\n * It subscribes to these events in its constructor (the same way\n * `TokenBalancesController` does). Unlike the polling data sources it does not\n * take part in the controller's subscribe/unsubscribe handoff: `subscribe` is a\n * no-op, balance updates are routed by resolving the address against the\n * wallet, and updates are pushed through the injected `onAssetsUpdate` callback.\n */\nexport class AccountActivityDataSource extends AbstractDataSource<\n typeof CONTROLLER_NAME,\n AccountActivityDataSourceState\n> {\n readonly #messenger: AssetsControllerMessenger;\n\n readonly #onActiveChainsUpdated: (\n dataSourceName: string,\n chains: ChainId[],\n previousChains: ChainId[],\n ) => void;\n\n readonly #getAssetType: (\n assetId: Caip19AssetId,\n ) => 'native' | 'erc20' | 'spl';\n\n readonly #onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n\n readonly #onBalanceUpdatedBound: (event: {\n address: string;\n chain: string;\n updates: BalanceUpdate[];\n }) => void;\n\n constructor(options: AccountActivityDataSourceOptions) {\n super(CONTROLLER_NAME, {\n ...defaultState,\n ...options.state,\n });\n\n this.#messenger = options.messenger;\n this.#onActiveChainsUpdated = options.onActiveChainsUpdated;\n this.#getAssetType = options.getAssetType;\n this.#onAssetsUpdate = options.onAssetsUpdate;\n this.#onBalanceUpdatedBound = this.#onBalanceUpdated.bind(this);\n\n this.#subscribeToEvents();\n }\n\n // ============================================================================\n // EVENT SUBSCRIPTIONS\n // ============================================================================\n\n #subscribeToEvents(): void {\n this.#messenger.subscribe(\n 'AccountActivityService:balanceUpdated',\n this.#onBalanceUpdatedBound,\n );\n this.#messenger.subscribe(\n 'AccountActivityService:statusChanged',\n this.#onAccountActivityStatusChanged,\n );\n }\n\n // ============================================================================\n // SUBSCRIBE / UNSUBSCRIBE\n // ============================================================================\n\n /**\n * AADS is event-driven and chain-agnostic: it never participates in the\n * controller's subscribe/unsubscribe handoff. Incoming `balanceUpdated`\n * events are routed by resolving the address against the wallet's accounts\n * (see `#onBalanceUpdated`), and updates are pushed through the injected\n * `#onAssetsUpdate` callback. This override exists only to satisfy the\n * abstract contract and is intentionally a no-op.\n */\n async subscribe(): Promise<void> {\n // Intentionally empty — see method doc.\n }\n\n // ============================================================================\n // BALANCE UPDATES\n // ============================================================================\n\n #onBalanceUpdated({\n address,\n chain,\n updates,\n }: {\n address: string;\n chain: string;\n updates: BalanceUpdate[];\n }): void {\n try {\n if (!address || !chain || !updates || updates.length === 0) {\n return;\n }\n\n const account = this.#findAccountForAddress(address);\n if (!account) {\n return;\n }\n\n const chainId = chain as ChainId;\n\n const response = processAccountActivityBalanceUpdates(\n updates,\n account.id,\n (assetId) => this.#getAssetType(assetId),\n );\n\n if (!response.assetsBalance) {\n return;\n }\n\n const request: DataRequest = {\n accountsWithSupportedChains: [{ account, supportedChains: [chainId] }],\n chainIds: [chainId],\n dataTypes: ['balance', 'metadata'],\n };\n\n Promise.resolve(this.#onAssetsUpdate(response, request)).catch(\n (error) => {\n log('Failed to report balance update', { error });\n },\n );\n } catch (error) {\n log('Error handling balance update', error);\n }\n }\n\n /**\n * Find the wallet account matching the given activity address. EVM addresses\n * are matched case-insensitively; other namespaces are matched exactly.\n *\n * The candidate set is the accounts of the selected account group, resolved\n * from the wallet at event time (rather than from a stored subscription), so\n * AADS does not need to be re-subscribed whenever the account set changes.\n *\n * @param address - The account address from the activity message.\n * @returns The matching account, or null.\n */\n #findAccountForAddress(address: string): InternalAccount | null {\n const isEvm = address.startsWith('0x');\n\n return (\n this.#getWalletAccounts().find((candidate) =>\n isEvm\n ? candidate.address.toLowerCase() === address.toLowerCase()\n : candidate.address === address,\n ) ?? null\n );\n }\n\n /**\n * Resolve the accounts of the selected account group from the wallet,\n * mirroring `AssetsController.#getSelectedAccounts`.\n *\n * @returns The accounts of the selected account group.\n */\n #getWalletAccounts(): InternalAccount[] {\n const accounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n if (accounts.length > 0) {\n return accounts;\n }\n\n const selectedAccount = this.#messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n return selectedAccount ? [selectedAccount] : [];\n }\n\n // ============================================================================\n // STATUS CHANGES\n // ============================================================================\n\n /**\n * Handle a `statusChanged` notification. Chains reported \"up\" are claimed as\n * active (WebSocket provides real-time data); chains reported \"down\" are\n * released so polling data sources take over. The change is applied directly;\n * coalescing and jitter are handled by `AssetsController`.\n *\n * @param options - The status change notification.\n * @param options.chainIds - The CAIP-2 chain IDs whose status changed.\n * @param options.status - Whether the chains are `up` or `down`.\n * @param options.timestamp - Optional timestamp of the status change.\n */\n readonly #onAccountActivityStatusChanged = ({\n chainIds,\n status,\n }: {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n }): void => {\n try {\n // Act on every namespace (eip155, solana, etc.); AssetsController is\n // multichain. Only skip identifiers that are not valid CAIP-2 chain IDs.\n const validChains = chainIds.filter((chainId) =>\n isCaipChainId(chainId),\n ) as ChainId[];\n\n if (validChains.length === 0) {\n return;\n }\n\n const next = new Set(this.state.activeChains);\n if (status === 'up') {\n for (const chainId of validChains) {\n next.add(chainId);\n }\n } else {\n for (const chainId of validChains) {\n next.delete(chainId);\n }\n }\n\n const previous = [...this.state.activeChains];\n this.updateActiveChains(Array.from(next), (updatedChains) =>\n this.#onActiveChainsUpdated(this.getName(), updatedChains, previous),\n );\n } catch (error) {\n log('Error handling status change', error);\n }\n };\n\n // ============================================================================\n // CLEANUP\n // ============================================================================\n\n destroy(): void {\n try {\n this.#messenger.unsubscribe(\n 'AccountActivityService:balanceUpdated',\n this.#onBalanceUpdatedBound,\n );\n } catch (error) {\n log('Failed to unsubscribe from balanceUpdated', { error });\n }\n\n try {\n this.#messenger.unsubscribe(\n 'AccountActivityService:statusChanged',\n this.#onAccountActivityStatusChanged,\n );\n } catch (error) {\n log('Failed to unsubscribe from statusChanged', { error });\n }\n\n super.destroy();\n }\n}\n\n// ============================================================================\n// FACTORY FUNCTION\n// ============================================================================\n\n/**\n * Creates an AccountActivityDataSource instance.\n *\n * @param options - Configuration options for the data source.\n * @returns A new AccountActivityDataSource instance.\n */\nexport function createAccountActivityDataSource(\n options: AccountActivityDataSourceOptions,\n): AccountActivityDataSource {\n return new AccountActivityDataSource(options);\n}\n"]}
1
+ {"version":3,"file":"AccountActivityDataSource.mjs","sourceRoot":"","sources":["../../src/data-sources/AccountActivityDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountActivityServiceBalanceUpdatedEvent,\n AccountActivityServiceStatusChangedEvent,\n BalanceUpdate,\n} from '@metamask/core-backend';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport { isCaipChainId } from '@metamask/utils';\nimport BigNumberJS from 'bignumber.js';\n\nimport type { AssetsControllerMessenger } from '../AssetsController.js';\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport type {\n AssetBalance,\n AssetMetadata,\n ChainId,\n Caip19AssetId,\n DataRequest,\n DataResponse,\n} from '../types.js';\nimport { safeNormalizeAssetId } from '../utils/index.js';\nimport { AbstractDataSource } from './AbstractDataSource.js';\nimport type { DataSourceState } from './AbstractDataSource.js';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'AccountActivityDataSource';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// BALANCE UPDATE PROCESSING\n// ============================================================================\n\n/**\n * Convert AccountActivityMessage balance updates into a {@link DataResponse}\n * for AssetsController.\n *\n * @param updates - Balance updates from account-activity websocket payload.\n * @param accountId - Internal account UUID.\n * @param getAssetType - Resolver for asset metadata type.\n * @returns DataResponse with merge mode when balances are present.\n */\nfunction processAccountActivityBalanceUpdates(\n updates: BalanceUpdate[],\n accountId: string,\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl',\n): DataResponse {\n const assetsBalance = Object.create(null) as Record<\n string,\n Record<Caip19AssetId, AssetBalance>\n >;\n assetsBalance[accountId] = Object.create(null) as Record<\n Caip19AssetId,\n AssetBalance\n >;\n const assetsMetadata = Object.create(null) as Record<\n Caip19AssetId,\n AssetMetadata\n >;\n\n for (const update of updates) {\n const { asset, postBalance } = update;\n\n if (!asset || !postBalance) {\n continue;\n }\n\n // The websocket sends lower-case ERC-20 addresses, while state and the\n // rest of the pipeline key assets by their checksummed ID. Normalizing\n // here keeps middleware comparisons (detection, custom-asset graduation,\n // occurrence filtering) from treating an existing holding as brand new.\n const assetId = safeNormalizeAssetId(asset.type as Caip19AssetId);\n\n if (asset.decimals === undefined) {\n continue;\n }\n\n const rawBalanceStr = postBalance.amount.startsWith('0x')\n ? BigInt(postBalance.amount).toString()\n : postBalance.amount;\n\n const humanReadableAmount = new BigNumberJS(rawBalanceStr)\n .dividedBy(new BigNumberJS(10).pow(asset.decimals))\n .toFixed();\n\n assetsBalance[accountId][assetId] = {\n amount: humanReadableAmount,\n };\n\n assetsMetadata[assetId] = {\n type: getAssetType(assetId),\n symbol: asset.unit,\n name: asset.unit,\n decimals: asset.decimals,\n };\n }\n\n const response: DataResponse = { updateMode: 'merge' };\n if (Object.keys(assetsBalance[accountId]).length > 0) {\n response.assetsBalance = assetsBalance;\n response.assetsInfo = assetsMetadata;\n }\n\n return response;\n}\n\n// ============================================================================\n// MESSENGER TYPES\n// ============================================================================\n\n/** Allowed events that AccountActivityDataSource subscribes to. */\nexport type AccountActivityDataSourceAllowedEvents =\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceStatusChangedEvent;\n\n// ============================================================================\n// STATE\n// ============================================================================\n\nexport type AccountActivityDataSourceState = DataSourceState;\n\nconst defaultState: AccountActivityDataSourceState = {\n activeChains: [],\n};\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport type AccountActivityDataSourceOptions = {\n /** The AssetsController messenger (shared by all data sources). */\n messenger: AssetsControllerMessenger;\n /** Called when active chains are updated. Pass dataSourceName so the controller knows the source. */\n onActiveChainsUpdated: (\n dataSourceName: string,\n chains: ChainId[],\n previousChains: ChainId[],\n ) => void;\n /** Returns the asset type ('native' | 'erc20' | 'spl') for a given CAIP-19 asset ID. */\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl';\n /**\n * Pushes decoded balance updates to the controller (bound to\n * `AssetsController.handleAssetsUpdate`). AADS is event-driven and never\n * takes part in the controller's subscribe handoff, so it receives this\n * callback directly instead of via a `SubscriptionRequest`.\n */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n state?: Partial<AccountActivityDataSourceState>;\n};\n\n// ============================================================================\n// ACCOUNT ACTIVITY DATA SOURCE\n// ============================================================================\n\n/**\n * Data source that consumes real-time updates from `AccountActivityService`.\n *\n * `AccountActivityService` owns the WebSocket connection and channel\n * subscriptions; this data source is a thin consumer of the two high-level\n * events that it publishes:\n *\n * - `AccountActivityService:balanceUpdated` — post-transaction balances for the\n * subscribed account(s). The address is resolved against the wallet's\n * selected account group, transformed into a {@link DataResponse} (via\n * {@link processAccountActivityBalanceUpdates}), and pushed to the controller\n * through the injected `onAssetsUpdate` callback.\n * - `AccountActivityService:statusChanged` — per-chain \"up\"/\"down\" notifications.\n * A chain reported \"up\" is claimed as an active chain (WebSocket is providing\n * real-time data); a chain reported \"down\" is released so polling data sources\n * can take over. Each change is applied directly via `updateActiveChains`,\n * which notifies the controller through `onActiveChainsUpdated`.\n *\n * This data source does NOT debounce, jitter, or gate chain updates itself.\n * Coalescing (collapsing bursts) and jitter (staggering the WS-subscribe herd\n * across clients) live in `AssetsController`, where the expensive re-subscribe\n * happens and where updates from all data sources converge. `activeChains` are\n * never seeded from the accounts API; they only ever reflect live\n * `statusChanged` events (the service flushes all tracked chains as \"down\" when\n * the WebSocket disconnects, releasing them).\n *\n * It subscribes to these events in its constructor (the same way\n * `TokenBalancesController` does). Unlike the polling data sources it does not\n * take part in the controller's subscribe/unsubscribe handoff: `subscribe` is a\n * no-op, balance updates are routed by resolving the address against the\n * wallet, and updates are pushed through the injected `onAssetsUpdate` callback.\n */\nexport class AccountActivityDataSource extends AbstractDataSource<\n typeof CONTROLLER_NAME,\n AccountActivityDataSourceState\n> {\n readonly #messenger: AssetsControllerMessenger;\n\n readonly #onActiveChainsUpdated: (\n dataSourceName: string,\n chains: ChainId[],\n previousChains: ChainId[],\n ) => void;\n\n readonly #getAssetType: (\n assetId: Caip19AssetId,\n ) => 'native' | 'erc20' | 'spl';\n\n readonly #onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n\n readonly #onBalanceUpdatedBound: (event: {\n address: string;\n chain: string;\n updates: BalanceUpdate[];\n }) => void;\n\n constructor(options: AccountActivityDataSourceOptions) {\n super(CONTROLLER_NAME, {\n ...defaultState,\n ...options.state,\n });\n\n this.#messenger = options.messenger;\n this.#onActiveChainsUpdated = options.onActiveChainsUpdated;\n this.#getAssetType = options.getAssetType;\n this.#onAssetsUpdate = options.onAssetsUpdate;\n this.#onBalanceUpdatedBound = this.#onBalanceUpdated.bind(this);\n\n this.#subscribeToEvents();\n }\n\n // ============================================================================\n // EVENT SUBSCRIPTIONS\n // ============================================================================\n\n #subscribeToEvents(): void {\n this.#messenger.subscribe(\n 'AccountActivityService:balanceUpdated',\n this.#onBalanceUpdatedBound,\n );\n this.#messenger.subscribe(\n 'AccountActivityService:statusChanged',\n this.#onAccountActivityStatusChanged,\n );\n }\n\n // ============================================================================\n // SUBSCRIBE / UNSUBSCRIBE\n // ============================================================================\n\n // Pins on active chains are claimed via the inherited `claimCustomAssets`:\n // the activity stream pushes all activity for the address, pins included.\n\n /**\n * AADS is event-driven and chain-agnostic: it never participates in the\n * controller's subscribe/unsubscribe handoff. Incoming `balanceUpdated`\n * events are routed by resolving the address against the wallet's accounts\n * (see `#onBalanceUpdated`), and updates are pushed through the injected\n * `#onAssetsUpdate` callback. This override exists only to satisfy the\n * abstract contract and is intentionally a no-op.\n */\n async subscribe(): Promise<void> {\n // Intentionally empty — see method doc.\n }\n\n // ============================================================================\n // BALANCE UPDATES\n // ============================================================================\n\n #onBalanceUpdated({\n address,\n chain,\n updates,\n }: {\n address: string;\n chain: string;\n updates: BalanceUpdate[];\n }): void {\n try {\n if (!address || !chain || !updates || updates.length === 0) {\n return;\n }\n\n const account = this.#findAccountForAddress(address);\n if (!account) {\n return;\n }\n\n const chainId = chain as ChainId;\n\n const response = processAccountActivityBalanceUpdates(\n updates,\n account.id,\n (assetId) => this.#getAssetType(assetId),\n );\n\n if (!response.assetsBalance) {\n return;\n }\n\n const request: DataRequest = {\n accountsWithSupportedChains: [{ account, supportedChains: [chainId] }],\n chainIds: [chainId],\n dataTypes: ['balance', 'metadata'],\n };\n\n Promise.resolve(this.#onAssetsUpdate(response, request)).catch(\n (error) => {\n log('Failed to report balance update', { error });\n },\n );\n } catch (error) {\n log('Error handling balance update', error);\n }\n }\n\n /**\n * Find the wallet account matching the given activity address. EVM addresses\n * are matched case-insensitively; other namespaces are matched exactly.\n *\n * The candidate set is the accounts of the selected account group, resolved\n * from the wallet at event time (rather than from a stored subscription), so\n * AADS does not need to be re-subscribed whenever the account set changes.\n *\n * @param address - The account address from the activity message.\n * @returns The matching account, or null.\n */\n #findAccountForAddress(address: string): InternalAccount | null {\n const isEvm = address.startsWith('0x');\n\n return (\n this.#getWalletAccounts().find((candidate) =>\n isEvm\n ? candidate.address.toLowerCase() === address.toLowerCase()\n : candidate.address === address,\n ) ?? null\n );\n }\n\n /**\n * Resolve the accounts of the selected account group from the wallet,\n * mirroring `AssetsController.#getSelectedAccounts`.\n *\n * @returns The accounts of the selected account group.\n */\n #getWalletAccounts(): InternalAccount[] {\n const accounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n if (accounts.length > 0) {\n return accounts;\n }\n\n const selectedAccount = this.#messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n return selectedAccount ? [selectedAccount] : [];\n }\n\n // ============================================================================\n // STATUS CHANGES\n // ============================================================================\n\n /**\n * Handle a `statusChanged` notification. Chains reported \"up\" are claimed as\n * active (WebSocket provides real-time data); chains reported \"down\" are\n * released so polling data sources take over. The change is applied directly;\n * coalescing and jitter are handled by `AssetsController`.\n *\n * @param options - The status change notification.\n * @param options.chainIds - The CAIP-2 chain IDs whose status changed.\n * @param options.status - Whether the chains are `up` or `down`.\n * @param options.timestamp - Optional timestamp of the status change.\n */\n readonly #onAccountActivityStatusChanged = ({\n chainIds,\n status,\n }: {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n }): void => {\n try {\n // Act on every namespace (eip155, solana, etc.); AssetsController is\n // multichain. Only skip identifiers that are not valid CAIP-2 chain IDs.\n const validChains = chainIds.filter((chainId) =>\n isCaipChainId(chainId),\n ) as ChainId[];\n\n if (validChains.length === 0) {\n return;\n }\n\n const next = new Set(this.state.activeChains);\n if (status === 'up') {\n for (const chainId of validChains) {\n next.add(chainId);\n }\n } else {\n for (const chainId of validChains) {\n next.delete(chainId);\n }\n }\n\n const previous = [...this.state.activeChains];\n this.updateActiveChains(Array.from(next), (updatedChains) =>\n this.#onActiveChainsUpdated(this.getName(), updatedChains, previous),\n );\n } catch (error) {\n log('Error handling status change', error);\n }\n };\n\n // ============================================================================\n // CLEANUP\n // ============================================================================\n\n destroy(): void {\n try {\n this.#messenger.unsubscribe(\n 'AccountActivityService:balanceUpdated',\n this.#onBalanceUpdatedBound,\n );\n } catch (error) {\n log('Failed to unsubscribe from balanceUpdated', { error });\n }\n\n try {\n this.#messenger.unsubscribe(\n 'AccountActivityService:statusChanged',\n this.#onAccountActivityStatusChanged,\n );\n } catch (error) {\n log('Failed to unsubscribe from statusChanged', { error });\n }\n\n super.destroy();\n }\n}\n\n// ============================================================================\n// FACTORY FUNCTION\n// ============================================================================\n\n/**\n * Creates an AccountActivityDataSource instance.\n *\n * @param options - Configuration options for the data source.\n * @returns A new AccountActivityDataSource instance.\n */\nexport function createAccountActivityDataSource(\n options: AccountActivityDataSourceOptions,\n): AccountActivityDataSource {\n return new AccountActivityDataSource(options);\n}\n"]}