@metamask-previews/assets-controller 16.1.2-preview-ff2ab0583 → 17.0.0-preview-c4789fdf4

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 (100) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/dist/AssetsController-method-action-types.d.ts +5 -1
  3. package/dist/AssetsController-method-action-types.d.ts.map +1 -1
  4. package/dist/AssetsController-method-action-types.js.map +1 -1
  5. package/dist/AssetsController.d.ts +8 -40
  6. package/dist/AssetsController.d.ts.map +1 -1
  7. package/dist/AssetsController.js +583 -239
  8. package/dist/AssetsController.js.map +1 -1
  9. package/dist/data-sources/AbstractDataSource.d.ts +1 -6
  10. package/dist/data-sources/AbstractDataSource.d.ts.map +1 -1
  11. package/dist/data-sources/AbstractDataSource.js.map +1 -1
  12. package/dist/data-sources/AccountsApiDataSource.d.ts +17 -3
  13. package/dist/data-sources/AccountsApiDataSource.d.ts.map +1 -1
  14. package/dist/data-sources/AccountsApiDataSource.js +275 -125
  15. package/dist/data-sources/AccountsApiDataSource.js.map +1 -1
  16. package/dist/data-sources/PriceDataSource.d.ts +7 -6
  17. package/dist/data-sources/PriceDataSource.d.ts.map +1 -1
  18. package/dist/data-sources/PriceDataSource.js +15 -20
  19. package/dist/data-sources/PriceDataSource.js.map +1 -1
  20. package/dist/data-sources/RpcDataSource.d.ts +18 -4
  21. package/dist/data-sources/RpcDataSource.d.ts.map +1 -1
  22. package/dist/data-sources/RpcDataSource.js +520 -121
  23. package/dist/data-sources/RpcDataSource.js.map +1 -1
  24. package/dist/data-sources/SnapDataSource.d.ts +11 -1
  25. package/dist/data-sources/SnapDataSource.d.ts.map +1 -1
  26. package/dist/data-sources/SnapDataSource.js +155 -0
  27. package/dist/data-sources/SnapDataSource.js.map +1 -1
  28. package/dist/data-sources/TokenDataSource.d.ts +3 -1
  29. package/dist/data-sources/TokenDataSource.d.ts.map +1 -1
  30. package/dist/data-sources/TokenDataSource.js +4 -2
  31. package/dist/data-sources/TokenDataSource.js.map +1 -1
  32. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.ts +11 -7
  33. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.ts.map +1 -1
  34. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.js +51 -6
  35. package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.js.map +1 -1
  36. package/dist/data-sources/evm-rpc-services/services/index.d.ts +1 -1
  37. package/dist/data-sources/evm-rpc-services/services/index.d.ts.map +1 -1
  38. package/dist/data-sources/evm-rpc-services/services/index.js.map +1 -1
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/middlewares/CustomAssetGraduationMiddleware.d.ts +3 -1
  43. package/dist/middlewares/CustomAssetGraduationMiddleware.d.ts.map +1 -1
  44. package/dist/middlewares/CustomAssetGraduationMiddleware.js +3 -1
  45. package/dist/middlewares/CustomAssetGraduationMiddleware.js.map +1 -1
  46. package/dist/middlewares/DetectionMiddleware.d.ts +10 -2
  47. package/dist/middlewares/DetectionMiddleware.d.ts.map +1 -1
  48. package/dist/middlewares/DetectionMiddleware.js +5 -23
  49. package/dist/middlewares/DetectionMiddleware.js.map +1 -1
  50. package/dist/middlewares/ParallelMiddleware.d.ts.map +1 -1
  51. package/dist/middlewares/ParallelMiddleware.js +0 -3
  52. package/dist/middlewares/ParallelMiddleware.js.map +1 -1
  53. package/dist/middlewares/RpcFallbackMiddleware.d.ts +16 -15
  54. package/dist/middlewares/RpcFallbackMiddleware.d.ts.map +1 -1
  55. package/dist/middlewares/RpcFallbackMiddleware.js +125 -107
  56. package/dist/middlewares/RpcFallbackMiddleware.js.map +1 -1
  57. package/dist/middlewares/index.d.ts +1 -0
  58. package/dist/middlewares/index.d.ts.map +1 -1
  59. package/dist/middlewares/index.js.map +1 -1
  60. package/dist/migrations/healAssetsInfoMetadata.d.ts +5 -5
  61. package/dist/migrations/healAssetsInfoMetadata.d.ts.map +1 -1
  62. package/dist/migrations/healAssetsInfoMetadata.js.map +1 -1
  63. package/dist/pipeline/buildFastFetchSources.d.ts +5 -0
  64. package/dist/pipeline/buildFastFetchSources.d.ts.map +1 -1
  65. package/dist/pipeline/buildFastFetchSources.js +7 -1
  66. package/dist/pipeline/buildFastFetchSources.js.map +1 -1
  67. package/dist/pipeline/executeAssetsPipeline.d.ts +1 -3
  68. package/dist/pipeline/executeAssetsPipeline.d.ts.map +1 -1
  69. package/dist/pipeline/executeAssetsPipeline.js +1 -2
  70. package/dist/pipeline/executeAssetsPipeline.js.map +1 -1
  71. package/dist/selectors/balance.js +1 -1
  72. package/dist/selectors/balance.js.map +1 -1
  73. package/dist/types.d.ts +30 -24
  74. package/dist/types.d.ts.map +1 -1
  75. package/dist/types.js.map +1 -1
  76. package/dist/utils/assetVisibility.d.ts +30 -0
  77. package/dist/utils/assetVisibility.d.ts.map +1 -0
  78. package/dist/utils/assetVisibility.js +84 -0
  79. package/dist/utils/assetVisibility.js.map +1 -0
  80. package/dist/utils/constants.d.ts +9 -0
  81. package/dist/utils/constants.d.ts.map +1 -1
  82. package/dist/utils/constants.js +9 -0
  83. package/dist/utils/constants.js.map +1 -1
  84. package/dist/utils/filterFailedChainBalances.d.ts +11 -0
  85. package/dist/utils/filterFailedChainBalances.d.ts.map +1 -0
  86. package/dist/utils/filterFailedChainBalances.js +22 -0
  87. package/dist/utils/filterFailedChainBalances.js.map +1 -0
  88. package/dist/utils/getZeroAssetBalance.d.ts +56 -0
  89. package/dist/utils/getZeroAssetBalance.d.ts.map +1 -0
  90. package/dist/utils/getZeroAssetBalance.js +99 -0
  91. package/dist/utils/getZeroAssetBalance.js.map +1 -0
  92. package/dist/utils/index.d.ts +2 -1
  93. package/dist/utils/index.d.ts.map +1 -1
  94. package/dist/utils/index.js +2 -1
  95. package/dist/utils/index.js.map +1 -1
  96. package/dist/utils/native-assets.d.ts +6 -19
  97. package/dist/utils/native-assets.d.ts.map +1 -1
  98. package/dist/utils/native-assets.js +8 -29
  99. package/dist/utils/native-assets.js.map +1 -1
  100. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- import type { ChainId, DataRequest, DataResponse, AssetsControllerStateInternal } from '../types.js';
1
+ import type { ChainId, DataRequest, DataResponse } from '../types.js';
2
2
  /**
3
3
  * Subscription request from AssetsController.
4
4
  */
@@ -8,11 +8,6 @@ export type SubscriptionRequest = {
8
8
  isUpdate: boolean;
9
9
  /** 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). */
10
10
  onAssetsUpdate: (response: DataResponse, request?: DataRequest) => void | Promise<void>;
11
- /**
12
- * Optional state access (e.g. for price/token data sources that need assetsBalance).
13
- * Provided by the controller when subscribing.
14
- */
15
- getAssetsState?: () => AssetsControllerStateInternal;
16
11
  /**
17
12
  * When true, skip the one-shot fetch that normally runs when a subscription
18
13
  * is created. Used after the controller has already force-fetched balances
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractDataSource.d.ts","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,WAAW,EACX,YAAY,EACZ,6BAA6B,EAC9B,MAAM,aAAa,CAAC;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;IAEZ,YAAY,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAG1C;IAED;;;;OAIG;IACH,OAAO,IAAI,IAAI,CAEd;IAED;;;;OAIG;IACG,eAAe,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAE1C;IAED;;;;OAIG;IACH,mBAAmB,IAAI,OAAO,EAAE,CAE/B;IAED;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhE;;;;OAIG;IACG,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMvD;IAED;;;;;OAKG;IACH,SAAS,CAAC,kBAAkB,CAC1B,MAAM,EAAE,OAAO,EAAE,EACjB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI,CAgBN;IAED;;;;;OAKG;IACH,SAAS,CAAC,cAAc,CACtB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI,CAKN;IAED;;;;;OAKG;IACH,SAAS,CAAC,iBAAiB,CACzB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI,CAON;IAED;;OAEG;IACH,OAAO,IAAI,IAAI,CAKd;CACF"}
1
+ {"version":3,"file":"AbstractDataSource.d.ts","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMtE;;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;;;;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;IAEZ,YAAY,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAG1C;IAED;;;;OAIG;IACH,OAAO,IAAI,IAAI,CAEd;IAED;;;;OAIG;IACG,eAAe,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAE1C;IAED;;;;OAIG;IACH,mBAAmB,IAAI,OAAO,EAAE,CAE/B;IAED;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhE;;;;OAIG;IACG,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMvD;IAED;;;;;OAKG;IACH,SAAS,CAAC,kBAAkB,CAC1B,MAAM,EAAE,OAAO,EAAE,EACjB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI,CAgBN;IAED;;;;;OAKG;IACH,SAAS,CAAC,cAAc,CACtB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI,CAKN;IAED;;;;;OAKG;IACH,SAAS,CAAC,iBAAiB,CACzB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI,CAON;IAED;;OAEG;IACH,OAAO,IAAI,IAAI,CAKd;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractDataSource.js","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"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","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.js","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"AAsDA,+EAA+E;AAC/E,uBAAuB;AACvB,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,OAAgB,kBAAkB;IAInB,IAAI,CAAO;IAEpB,KAAK,CAAQ;IAEvB,iCAAiC;IACd,mBAAmB,GACpC,IAAI,GAAG,EAAE,CAAC;IAEZ,YAAY,IAAU,EAAE,YAAmB;QACzC,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 { ChainId, DataRequest, DataResponse } 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 * 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,7 +1,8 @@
1
1
  import { ApiPlatformClient } from '@metamask/core-backend';
2
2
  import type { RemoteFeatureFlagControllerGetStateAction, RemoteFeatureFlagControllerStateChangeEvent } from '@metamask/remote-feature-flag-controller';
3
3
  import type { AssetsControllerMessenger } from '../AssetsController.js';
4
- import type { ChainId, DataRequest, DataResponse, Middleware, AssetsControllerStateInternal } from '../types.js';
4
+ import type { ChainId, DataRequest, DataResponse, Middleware, AssetsControllerState } from '../types.js';
5
+ import type { GetAssetVisibility } from '../utils/assetVisibility.js';
5
6
  import type { DataSourceState, SubscriptionRequest } from './AbstractDataSource.js';
6
7
  import { AbstractDataSource } from './AbstractDataSource.js';
7
8
  declare const CONTROLLER_NAME = "AccountsApiDataSource";
@@ -28,13 +29,26 @@ export type AccountsApiDataSourceConfig = {
28
29
  export type AccountsApiDataSourceOptions = AccountsApiDataSourceConfig & {
29
30
  /**
30
31
  * The AssetsController messenger (shared by all data sources). Used to read
31
- * the `assetsAccountsApiV6` remote feature flag.
32
+ * Snaps → AssetsController migration flags and subscribe to flag changes.
32
33
  */
33
34
  messenger: AssetsControllerMessenger;
34
35
  /** ApiPlatformClient for API calls with caching */
35
36
  queryApiClient: ApiPlatformClient;
36
37
  /** Called when active chains are updated. Pass dataSourceName so the controller knows the source. */
37
38
  onActiveChainsUpdated: (dataSourceName: string, chains: ChainId[], previousChains: ChainId[]) => void;
39
+ /**
40
+ * Whether to use the Accounts API v6 balances endpoint (and `includeAssetIds`).
41
+ * Injected by AssetsController so the flag is read in one place. Defaults to
42
+ * `false` (v5). Read on demand, not cached.
43
+ */
44
+ isBalanceV6Enabled?: () => boolean;
45
+ /**
46
+ * Current AssetsController state. Used for v6 include/exclude asset IDs and
47
+ * for filtering when token detection is off.
48
+ */
49
+ getAssetsState: () => AssetsControllerState;
50
+ /** Returns shared visible/hidden assets for an account/chain scope. */
51
+ getAssetVisibility: GetAssetVisibility;
38
52
  state?: Partial<AccountsApiDataSourceState>;
39
53
  };
40
54
  /**
@@ -45,7 +59,7 @@ export type AccountsApiDataSourceOptions = AccountsApiDataSourceConfig & {
45
59
  * @param assetsState - Current assets controller state to check existing balances against.
46
60
  * @returns A new response with only known asset balances.
47
61
  */
48
- export declare function filterResponseToKnownAssets(response: DataResponse, assetsState: AssetsControllerStateInternal): DataResponse;
62
+ export declare function filterResponseToKnownAssets(response: DataResponse, assetsState: AssetsControllerState): DataResponse;
49
63
  /**
50
64
  * Data source for fetching balances from the MetaMask Accounts API.
51
65
  *
@@ -1 +1 @@
1
- {"version":3,"file":"AccountsApiDataSource.d.ts","sourceRoot":"","sources":["../../src/data-sources/AccountsApiDataSource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EACV,yCAAyC,EACzC,2CAA2C,EAC5C,MAAM,0CAA0C,CAAC;AAQlD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAExE,OAAO,KAAK,EACV,OAAO,EAGP,WAAW,EACX,YAAY,EACZ,UAAU,EACV,6BAA6B,EAC9B,MAAM,aAAa,CAAC;AAMrB,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAO7D,QAAA,MAAM,eAAe,0BAA0B,CAAC;AAehD,MAAM,MAAM,mCAAmC,GAC7C,yCAAyC,CAAC;AAI5C,MAAM,MAAM,kCAAkC,GAC5C,2CAA2C,CAAC;AAM9C,MAAM,MAAM,0BAA0B,GAAG,eAAe,CAAC;AAUzD,wDAAwD;AACxD,MAAM,MAAM,2BAA2B,GAAG;IACxC,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,OAAO,CAAC;IACtC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG,2BAA2B,GAAG;IACvE;;;OAGG;IACH,SAAS,EAAE,yBAAyB,CAAC;IACrC,mDAAmD;IACnD,cAAc,EAAE,iBAAiB,CAAC;IAClC,qGAAqG;IACrG,qBAAqB,EAAE,CACrB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,OAAO,EAAE,EACjB,cAAc,EAAE,OAAO,EAAE,KACtB,IAAI,CAAC;IACV,KAAK,CAAC,EAAE,OAAO,CAAC,0BAA0B,CAAC,CAAC;CAC7C,CAAC;AAgEF;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,YAAY,EACtB,WAAW,EAAE,6BAA6B,GACzC,YAAY,CAqCd;AAMD;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,kBAAkB,CAC3D,OAAO,eAAe,EACtB,0BAA0B,CAC3B;;IA0BC,YAAY,OAAO,EAAE,4BAA4B,EA+BhD;IA8FD;;;;;;OAMG;IACH,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnC;IA0BK,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CA4FvD;IA2OD;;;;;;;;;OASG;IACH,IAAI,gBAAgB,IAAI,UAAU,CAuEjC;IAMK,SAAS,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoGvE;IAMD,OAAO,IAAI,IAAI,CAMd;CACF"}
1
+ {"version":3,"file":"AccountsApiDataSource.d.ts","sourceRoot":"","sources":["../../src/data-sources/AccountsApiDataSource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EACV,yCAAyC,EACzC,2CAA2C,EAC5C,MAAM,0CAA0C,CAAC;AASlD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAExE,OAAO,KAAK,EACV,OAAO,EAGP,WAAW,EACX,YAAY,EACZ,UAAU,EACV,qBAAqB,EACtB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAOtE,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAO7D,QAAA,MAAM,eAAe,0BAA0B,CAAC;AAehD,MAAM,MAAM,mCAAmC,GAC7C,yCAAyC,CAAC;AAI5C,MAAM,MAAM,kCAAkC,GAC5C,2CAA2C,CAAC;AAM9C,MAAM,MAAM,0BAA0B,GAAG,eAAe,CAAC;AAUzD,wDAAwD;AACxD,MAAM,MAAM,2BAA2B,GAAG;IACxC,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,OAAO,CAAC;IACtC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG,2BAA2B,GAAG;IACvE;;;OAGG;IACH,SAAS,EAAE,yBAAyB,CAAC;IACrC,mDAAmD;IACnD,cAAc,EAAE,iBAAiB,CAAC;IAClC,qGAAqG;IACrG,qBAAqB,EAAE,CACrB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,OAAO,EAAE,EACjB,cAAc,EAAE,OAAO,EAAE,KACtB,IAAI,CAAC;IACV;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC;IACnC;;;OAGG;IACH,cAAc,EAAE,MAAM,qBAAqB,CAAC;IAC5C,uEAAuE;IACvE,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,KAAK,CAAC,EAAE,OAAO,CAAC,0BAA0B,CAAC,CAAC;CAC7C,CAAC;AAgEF;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,YAAY,EACtB,WAAW,EAAE,qBAAqB,GACjC,YAAY,CAqCd;AAMD;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,kBAAkB,CAC3D,OAAO,eAAe,EACtB,0BAA0B,CAC3B;;IA8BC,YAAY,OAAO,EAAE,4BAA4B,EAmChD;IAiED;;;;;;OAMG;IACH,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnC;IA0BK,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAMvD;IAueD;;;;;;;;;OASG;IACH,IAAI,gBAAgB,IAAI,UAAU,CAMjC;IA2IK,SAAS,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+FvE;IAMD,OAAO,IAAI,IAAI,CAMd;CACF"}
@@ -1,5 +1,6 @@
1
- import { isCaipChainId, KnownCaipNamespace, toCaipChainId, } from '@metamask/utils';
1
+ import { isCaipChainId, KnownCaipNamespace, parseCaipAssetType, toCaipChainId, } from '@metamask/utils';
2
2
  import { projectLogger, createModuleLogger } from '../logger.js';
3
+ import { filterFailedChainBalances } from '../utils/filterFailedChainBalances.js';
3
4
  import { fetchWithTimeout, normalizeAssetId } from '../utils/index.js';
4
5
  import { getMigrationStages, shouldSupportChain, } from '../utils/snaps-assets-migration.js';
5
6
  import { AbstractDataSource } from './AbstractDataSource.js';
@@ -116,12 +117,14 @@ export class AccountsApiDataSource extends AbstractDataSource {
116
117
  #tokenDetectionEnabled;
117
118
  /** Shared AssetsController messenger, used to read remote feature flags. */
118
119
  #messenger;
120
+ /** Injected by AssetsController; `true` when the v6 balances endpoint should be used. */
121
+ #isBalanceV6Enabled;
122
+ #getAssetsState;
123
+ #getAssetVisibility;
119
124
  /** ApiPlatformClient for cached API calls */
120
125
  #apiClient;
121
126
  /** Chains refresh timer */
122
127
  #chainsRefreshTimer = null;
123
- /** State accessor from subscriptions (for filtering when tokenDetectionEnabled is false) */
124
- #getAssetsState;
125
128
  constructor(options) {
126
129
  super(CONTROLLER_NAME, {
127
130
  ...defaultState,
@@ -133,6 +136,10 @@ export class AccountsApiDataSource extends AbstractDataSource {
133
136
  this.#tokenDetectionEnabled =
134
137
  options.tokenDetectionEnabled ?? (() => true);
135
138
  this.#messenger = options.messenger;
139
+ this.#isBalanceV6Enabled =
140
+ options.isBalanceV6Enabled ?? (() => false);
141
+ this.#getAssetsState = options.getAssetsState;
142
+ this.#getAssetVisibility = options.getAssetVisibility;
136
143
  this.#apiClient = options.queryApiClient;
137
144
  // The Snaps → AssetsController migration flags gate which migration networks
138
145
  // (Solana, Stellar, Tron) are surfaced as active chains (see
@@ -165,31 +172,6 @@ export class AccountsApiDataSource extends AbstractDataSource {
165
172
  });
166
173
  }
167
174
  }
168
- /**
169
- * Whether the Accounts API v6 balances endpoint is enabled, read from the
170
- * RemoteFeatureFlagController (`assetsAccountsApiV6`). Read on demand (per
171
- * fetch) rather than cached so the value can be toggled at runtime, and behaves
172
- * the same across clients (extension, mobile). Defaults to `false` when the
173
- * flag is unset or the controller is unavailable.
174
- *
175
- * The flag is a LaunchDarkly JSON variation shaped `{ value: boolean }` (same
176
- * shape as `backendWebSocketConnection`), so the nested `value` is read rather
177
- * than treating the flag as a plain boolean.
178
- *
179
- * @returns `true` when the v6 balances endpoint should be used.
180
- */
181
- #isBalanceV6Enabled() {
182
- try {
183
- const { remoteFeatureFlags } = this.#messenger.call('RemoteFeatureFlagController:getState');
184
- const flag = remoteFeatureFlags?.assetsAccountsApiV6;
185
- return (typeof flag === 'object' &&
186
- flag !== null &&
187
- Boolean(flag.value));
188
- }
189
- catch {
190
- return false;
191
- }
192
- }
193
175
  // ============================================================================
194
176
  // INITIALIZATION
195
177
  // ============================================================================
@@ -251,45 +233,33 @@ export class AccountsApiDataSource extends AbstractDataSource {
251
233
  // FETCH
252
234
  // ============================================================================
253
235
  async fetch(request) {
236
+ if (this.#isBalanceV6Enabled()) {
237
+ return this.#fetchV6(request);
238
+ }
239
+ return this.#fetchV5(request);
240
+ }
241
+ /**
242
+ * v5 fetch. Unchanged from the pre-v6 handler; delete the v6 sibling first
243
+ * if `assetsAccountsApiV6` is rolled back.
244
+ *
245
+ * @param request - The data request.
246
+ * @returns Balances stamped `merge`, plus errors for chains the API could
247
+ * not process.
248
+ */
249
+ async #fetchV5(request) {
254
250
  let response = {};
255
- // Filter to only chains supported by Accounts API
256
- const supportedChains = new Set(this.state.activeChains);
257
- const chainsToFetch = request.chainIds.filter((chainId) => supportedChains.has(chainId));
251
+ const chainsToFetch = this.#getChainsToFetch(request);
258
252
  if (chainsToFetch.length === 0) {
259
- // Mark unsupported chains as errors so they pass to next middleware
260
- for (const chainId of request.chainIds) {
261
- if (!supportedChains.has(chainId)) {
262
- response.errors = response.errors ?? {};
263
- response.errors[chainId] = 'Chain not supported by Accounts API';
264
- }
265
- }
253
+ this.#markUnsupportedChains(response, request);
266
254
  return response;
267
255
  }
268
256
  try {
269
- // Build CAIP-10 account IDs (e.g., "eip155:1:0x1234...")
270
- // Use pre-computed supportedChains per account from the request
271
- const accountIds = request.accountsWithSupportedChains.flatMap(({ account, supportedChains: accountChains }) => chainsToFetch
272
- .filter((chainId) => accountChains.includes(chainId))
273
- .map((chainId) => `${chainId}:${account.address}`));
257
+ const accountIds = this.#buildAccountIds(request, chainsToFetch);
274
258
  // Skip API call if no valid account-chain combinations
275
259
  if (accountIds.length === 0) {
276
260
  return response;
277
261
  }
278
- const fetchOptions = request.forceUpdate || request.bypassServerCache
279
- ? {
280
- staleTime: 0,
281
- gcTime: 0,
282
- // Also defeats the API's server-side cache (via a random
283
- // bypassServerCache query param) so a post-transaction refresh cannot
284
- // be answered with a pre-transaction snapshot.
285
- ...(request.bypassServerCache ? { bypassServerCache: true } : {}),
286
- }
287
- : undefined;
288
- // Feature-flagged: v6 endpoint with a fallback to legacy v5. The flag is
289
- // read here (not cached) so a runtime toggle can revert v6 -> v5.
290
- const { unprocessedNetworks, assetsBalance } = this.#isBalanceV6Enabled()
291
- ? await this.#fetchV6Balances(accountIds, fetchOptions, request)
292
- : await this.#fetchV5Balances(accountIds, fetchOptions, request);
262
+ const { unprocessedNetworks, assetsBalance } = await this.#fetchV5Balances(accountIds, this.#buildFetchOptions(request), request);
293
263
  // Handle unprocessed networks - these will be passed to next middleware
294
264
  if (unprocessedNetworks.length > 0) {
295
265
  const unprocessedChainIds = unprocessedNetworks.map(caipChainIdToChainId);
@@ -303,26 +273,145 @@ export class AccountsApiDataSource extends AbstractDataSource {
303
273
  response.updateMode = 'merge';
304
274
  }
305
275
  catch (error) {
306
- log('Fetch FAILED', { error, chains: chainsToFetch });
307
- // On error, mark all chains as errors so they can be handled by next middleware
308
- response.errors = response.errors ?? {};
309
- for (const chainId of chainsToFetch) {
310
- response.errors[chainId] =
311
- `Fetch failed: ${error instanceof Error ? error.message : String(error)}`;
276
+ this.#markFetchFailure(response, chainsToFetch, error);
277
+ }
278
+ this.#markUnsupportedChains(response, request);
279
+ // When token detection is disabled, filter out tokens not already in state
280
+ if (!this.#tokenDetectionEnabled()) {
281
+ response = filterResponseToKnownAssets(response, this.#getAssetsState());
282
+ }
283
+ return response;
284
+ }
285
+ /**
286
+ * v6 fetch. Reads visibility from state, sends it as `includeAssetIds` /
287
+ * `excludeAssetIds`, and stamps `full`. A chain the API left unprocessed, or
288
+ * answered without every requested `includeAssetId`, is reported in `errors`
289
+ * and contributes no balances, so the RPC fallback can recover it.
290
+ *
291
+ * @param request - The data request.
292
+ * @returns An authoritative snapshot for the chains that succeeded.
293
+ */
294
+ async #fetchV6(request) {
295
+ const response = {};
296
+ const chainsToFetch = this.#getChainsToFetch(request);
297
+ if (chainsToFetch.length === 0) {
298
+ this.#markUnsupportedChains(response, request);
299
+ return response;
300
+ }
301
+ try {
302
+ const accountIds = this.#buildAccountIds(request, chainsToFetch);
303
+ if (accountIds.length === 0) {
304
+ return response;
305
+ }
306
+ const { visibleAssetIds, hiddenAssetIds } = this.#getAssetVisibility(request.accountsWithSupportedChains.map(({ account }) => account.id), chainsToFetch);
307
+ const { unprocessedNetworks, unprocessedIncludeAssetIds, assetsBalance } = await this.#fetchV6Balances(accountIds, this.#buildFetchOptions(request), request, visibleAssetIds.length > 0 ? visibleAssetIds : undefined, hiddenAssetIds.length > 0 ? hiddenAssetIds : undefined);
308
+ response.updateMode = 'full';
309
+ const unprocessedChainIds = unprocessedNetworks.map(caipChainIdToChainId);
310
+ // Chains answered without every requested `includeAssetIds`. The
311
+ // snapshot is incomplete, so the chain counts as failed rather than
312
+ // silently dropping the assets it left out.
313
+ const incompleteChainIds = this.#getChainIdsForAssetIds(unprocessedIncludeAssetIds);
314
+ const failedChainIds = new Set([
315
+ ...unprocessedChainIds,
316
+ ...incompleteChainIds,
317
+ ]);
318
+ // Errors hand these chains to the next middleware (the RPC fallback).
319
+ if (failedChainIds.size > 0) {
320
+ response.errors = response.errors ?? {};
321
+ for (const chainId of unprocessedChainIds) {
322
+ response.errors[chainId] = 'Unprocessed by Accounts API';
323
+ }
324
+ for (const chainId of incompleteChainIds) {
325
+ response.errors[chainId] ??= 'Unresolved includeAssetIds';
326
+ }
312
327
  }
328
+ // A failed chain contributes nothing, so its balances stay as they are.
329
+ response.assetsBalance = filterFailedChainBalances(assetsBalance, failedChainIds);
313
330
  }
314
- // Mark unsupported chains as errors so they pass to next middleware
331
+ catch (error) {
332
+ this.#markFetchFailure(response, chainsToFetch, error);
333
+ }
334
+ this.#markUnsupportedChains(response, request);
335
+ return response;
336
+ }
337
+ /**
338
+ * Requested chains the Accounts API currently supports.
339
+ *
340
+ * @param request - The data request being fetched.
341
+ * @returns The subset of `request.chainIds` this source can answer.
342
+ */
343
+ #getChainsToFetch(request) {
344
+ const supportedChains = new Set(this.state.activeChains);
345
+ return request.chainIds.filter((chainId) => supportedChains.has(chainId));
346
+ }
347
+ /**
348
+ * Mark unsupported chains as errors so they pass to next middleware.
349
+ *
350
+ * @param response - Response being built; mutated in place.
351
+ * @param request - The data request being fetched.
352
+ */
353
+ #markUnsupportedChains(response, request) {
354
+ const supportedChains = new Set(this.state.activeChains);
315
355
  for (const chainId of request.chainIds) {
316
356
  if (!supportedChains.has(chainId)) {
317
357
  response.errors = response.errors ?? {};
318
358
  response.errors[chainId] = 'Chain not supported by Accounts API';
319
359
  }
320
360
  }
321
- // When token detection is disabled, filter out tokens not already in state
322
- if (!this.#tokenDetectionEnabled() && this.#getAssetsState) {
323
- response = filterResponseToKnownAssets(response, this.#getAssetsState());
361
+ }
362
+ /**
363
+ * Build CAIP-10 account IDs (e.g., "eip155:1:0x1234...") from the
364
+ * pre-computed supportedChains per account on the request.
365
+ *
366
+ * @param request - The data request being fetched.
367
+ * @param chainsToFetch - Chains this source will ask the API for.
368
+ * @returns One CAIP-10 ID per account-chain combination.
369
+ */
370
+ #buildAccountIds(request, chainsToFetch) {
371
+ return request.accountsWithSupportedChains.flatMap(({ account, supportedChains: accountChains }) => chainsToFetch
372
+ .filter((chainId) => accountChains.includes(chainId))
373
+ .map((chainId) => `${chainId}:${account.address}`));
374
+ }
375
+ #buildFetchOptions(request) {
376
+ if (!request.forceUpdate && !request.bypassServerCache) {
377
+ return undefined;
324
378
  }
325
- return response;
379
+ return {
380
+ staleTime: 0,
381
+ gcTime: 0,
382
+ // Also defeats the API's server-side cache (via a random
383
+ // bypassServerCache query param) so a post-transaction refresh cannot
384
+ // be answered with a pre-transaction snapshot.
385
+ ...(request.bypassServerCache ? { bypassServerCache: true } : {}),
386
+ };
387
+ }
388
+ /**
389
+ * On error, mark all chains as errors so the next middleware handles them.
390
+ *
391
+ * @param response - Response being built; mutated in place.
392
+ * @param chainsToFetch - Chains the failed request covered.
393
+ * @param error - The thrown error.
394
+ */
395
+ #markFetchFailure(response, chainsToFetch, error) {
396
+ log('Fetch FAILED', { error, chains: chainsToFetch });
397
+ response.errors = response.errors ?? {};
398
+ for (const chainId of chainsToFetch) {
399
+ response.errors[chainId] =
400
+ `Fetch failed: ${error instanceof Error ? error.message : String(error)}`;
401
+ }
402
+ }
403
+ #getChainIdsForAssetIds(assetIds) {
404
+ const chainIds = new Set();
405
+ for (const assetId of assetIds) {
406
+ try {
407
+ chainIds.add(parseCaipAssetType(assetId).chainId);
408
+ }
409
+ catch {
410
+ // An unparseable ID cannot be attributed to a chain, so it cannot
411
+ // invalidate one either.
412
+ }
413
+ }
414
+ return chainIds;
326
415
  }
327
416
  /**
328
417
  * Fetch balances from the legacy v5 endpoint and process them.
@@ -337,6 +426,8 @@ export class AccountsApiDataSource extends AbstractDataSource {
337
426
  const { assetsBalance } = this.#processV5Balances(apiResponse.balances, request);
338
427
  return {
339
428
  unprocessedNetworks: apiResponse.unprocessedNetworks,
429
+ // v5 has no `includeAssetIds` support.
430
+ unprocessedIncludeAssetIds: [],
340
431
  assetsBalance,
341
432
  };
342
433
  }
@@ -346,13 +437,23 @@ export class AccountsApiDataSource extends AbstractDataSource {
346
437
  * @param accountIds - CAIP-10 account IDs to fetch balances for.
347
438
  * @param fetchOptions - Cache/fetch options (e.g. force update settings).
348
439
  * @param request - The original data request containing accounts to map.
349
- * @returns Unprocessed networks and processed asset balances by account.
440
+ * @param includeAssetIds - Pinned asset IDs the backend must always return.
441
+ * @param excludeAssetIds - Hidden asset IDs the backend must drop.
442
+ * @returns Unprocessed networks, unprocessed pinned assets, and processed
443
+ * asset balances by account.
350
444
  */
351
- async #fetchV6Balances(accountIds, fetchOptions, request) {
352
- const apiResponse = await fetchWithTimeout(() => this.#apiClient.accounts.fetchV6MultiAccountBalances(accountIds, undefined, fetchOptions), this.#fetchTimeoutMs);
445
+ async #fetchV6Balances(accountIds, fetchOptions, request, includeAssetIds, excludeAssetIds) {
446
+ const params = includeAssetIds || excludeAssetIds
447
+ ? {
448
+ ...(includeAssetIds && { includeAssetIds }),
449
+ ...(excludeAssetIds && { excludeAssetIds }),
450
+ }
451
+ : undefined;
452
+ const apiResponse = await fetchWithTimeout(() => this.#apiClient.accounts.fetchV6MultiAccountBalances(accountIds, params, fetchOptions), this.#fetchTimeoutMs);
353
453
  const { assetsBalance } = this.#processV6Balances(apiResponse.balances, request);
354
454
  return {
355
455
  unprocessedNetworks: apiResponse.unprocessedNetworks,
456
+ unprocessedIncludeAssetIds: apiResponse.unprocessedIncludeAssetIds,
356
457
  assetsBalance,
357
458
  };
358
459
  }
@@ -482,69 +583,118 @@ export class AccountsApiDataSource extends AbstractDataSource {
482
583
  * @returns The middleware function for the assets pipeline.
483
584
  */
484
585
  get assetsMiddleware() {
485
- return async (context, next) => {
486
- const { request } = context;
487
- // Price/metadata-only requests must not hit the Accounts API.
488
- if (!request.dataTypes.includes('balance')) {
489
- return next(context);
490
- }
491
- // If no chains requested, skip to next middleware
492
- if (request.chainIds.length === 0) {
493
- return next(context);
494
- }
495
- let successfullyHandledChains = [];
496
- try {
497
- const response = await this.fetch(request);
498
- // Merge response into context
499
- if (response.assetsBalance) {
500
- context.response.assetsBalance ??= {};
501
- for (const [accountId, accountBalances] of Object.entries(response.assetsBalance)) {
502
- context.response.assetsBalance[accountId] = {
503
- ...context.response.assetsBalance[accountId],
504
- ...accountBalances,
505
- };
506
- }
507
- }
508
- // Determine successfully handled chains (exclude unprocessed/error chains)
509
- const unprocessedChains = new Set(Object.keys(response.errors ?? {}));
510
- successfullyHandledChains = request.chainIds.filter((chainId) => !unprocessedChains.has(chainId));
511
- // When token detection is off and we filtered out all balance data (e.g. new
512
- // account with empty state), do not claim any chain as handled so that RPC
513
- // middleware can still process them and fetch native balances (ETH, MATIC, etc.).
514
- if (!this.#tokenDetectionEnabled() &&
515
- (!response.assetsBalance ||
516
- Object.keys(response.assetsBalance).length === 0)) {
517
- successfullyHandledChains = [];
586
+ if (this.#isBalanceV6Enabled()) {
587
+ return this.#assetsMiddlewareV6;
588
+ }
589
+ return this.#assetsMiddlewareV5;
590
+ }
591
+ #assetsMiddlewareV5 = async (context, next) => {
592
+ const { request } = context;
593
+ // Price/metadata-only requests must not hit the Accounts API.
594
+ if (!request.dataTypes.includes('balance')) {
595
+ return next(context);
596
+ }
597
+ // If no chains requested, skip to next middleware
598
+ if (request.chainIds.length === 0) {
599
+ return next(context);
600
+ }
601
+ let successfullyHandledChains = [];
602
+ try {
603
+ const response = await this.fetch(request);
604
+ // Merge response into context
605
+ if (response.assetsBalance) {
606
+ context.response.assetsBalance ??= {};
607
+ for (const [accountId, accountBalances] of Object.entries(response.assetsBalance)) {
608
+ context.response.assetsBalance[accountId] = {
609
+ ...context.response.assetsBalance[accountId],
610
+ ...accountBalances,
611
+ };
518
612
  }
519
613
  }
520
- catch (error) {
521
- log('Middleware fetch failed', { error });
614
+ // Determine successfully handled chains (exclude unprocessed/error chains)
615
+ const unprocessedChains = new Set(Object.keys(response.errors ?? {}));
616
+ successfullyHandledChains = request.chainIds.filter((chainId) => !unprocessedChains.has(chainId));
617
+ // When token detection is off and we filtered out all balance data (e.g. new
618
+ // account with empty state), do not claim any chain as handled so that RPC
619
+ // middleware can still process them and fetch native balances (ETH, MATIC, etc.).
620
+ if (!this.#tokenDetectionEnabled() &&
621
+ (!response.assetsBalance ||
622
+ Object.keys(response.assetsBalance).length === 0)) {
522
623
  successfullyHandledChains = [];
523
624
  }
524
- // Remove successfully handled chains from request for next middleware
525
- if (successfullyHandledChains.length > 0) {
526
- const remainingChains = request.chainIds.filter((chainId) => !successfullyHandledChains.includes(chainId));
527
- return next({
528
- ...context,
529
- request: {
530
- ...request,
531
- chainIds: remainingChains,
532
- },
533
- });
534
- }
535
- // No chains handled - pass context unchanged
625
+ }
626
+ catch (error) {
627
+ log('Middleware fetch failed', { error });
628
+ successfullyHandledChains = [];
629
+ }
630
+ // Remove successfully handled chains from request for next middleware
631
+ if (successfullyHandledChains.length > 0) {
632
+ const remainingChains = request.chainIds.filter((chainId) => !successfullyHandledChains.includes(chainId));
633
+ return next({
634
+ ...context,
635
+ request: {
636
+ ...request,
637
+ chainIds: remainingChains,
638
+ },
639
+ });
640
+ }
641
+ // No chains handled - pass context unchanged
642
+ return next(context);
643
+ };
644
+ #assetsMiddlewareV6 = async (context, next) => {
645
+ const { request } = context;
646
+ // Price/metadata-only requests must not hit the Accounts API.
647
+ if (!request.dataTypes.includes('balance')) {
536
648
  return next(context);
537
- };
538
- }
649
+ }
650
+ // If no chains requested, skip to next middleware
651
+ if (request.chainIds.length === 0) {
652
+ return next(context);
653
+ }
654
+ let remainingChains = request.chainIds;
655
+ try {
656
+ const response = await this.fetch(request);
657
+ // Merge response into context
658
+ if (response.assetsBalance) {
659
+ context.response.assetsBalance ??= {};
660
+ for (const [accountId, accountBalances] of Object.entries(response.assetsBalance)) {
661
+ context.response.assetsBalance[accountId] = {
662
+ ...context.response.assetsBalance[accountId],
663
+ ...accountBalances,
664
+ };
665
+ }
666
+ }
667
+ context.response = {
668
+ ...context.response,
669
+ updateMode: 'full',
670
+ };
671
+ const errors = response.errors ?? {};
672
+ remainingChains = request.chainIds.filter((chainId) => errors[chainId]);
673
+ // Remaining chains are exactly the failed ones; RPC fallback retries them.
674
+ if (remainingChains.length > 0) {
675
+ context.response.errors = {
676
+ ...context.response.errors,
677
+ ...Object.fromEntries(remainingChains.map((chainId) => [chainId, errors[chainId]])),
678
+ };
679
+ }
680
+ }
681
+ catch (error) {
682
+ log('Middleware fetch failed', { error });
683
+ remainingChains = request.chainIds;
684
+ }
685
+ return next({
686
+ ...context,
687
+ request: {
688
+ ...request,
689
+ chainIds: remainingChains,
690
+ },
691
+ });
692
+ };
539
693
  // ============================================================================
540
694
  // SUBSCRIBE
541
695
  // ============================================================================
542
696
  async subscribe(subscriptionRequest) {
543
697
  const { request, subscriptionId, isUpdate, skipInitialFetch } = subscriptionRequest;
544
- // Store state accessor for filtering when tokenDetectionEnabled is false
545
- if (subscriptionRequest.getAssetsState) {
546
- this.#getAssetsState = subscriptionRequest.getAssetsState;
547
- }
548
698
  // Try all requested chains - API will handle unsupported ones via unprocessedNetworks
549
699
  const chainsToSubscribe = request.chainIds;
550
700
  if (chainsToSubscribe.length === 0) {