@metamask-previews/assets-controllers 111.1.1-preview-bbf9454b2 → 111.1.2-preview-9a81434

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 (22) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.cjs.map +1 -1
  3. package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.d.cts +13 -13
  4. package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.d.mts +13 -13
  5. package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.mjs.map +1 -1
  6. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.cjs +26 -25
  7. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.cjs.map +1 -1
  8. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.d.cts +22 -21
  9. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.d.cts.map +1 -1
  10. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.d.mts +22 -21
  11. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.d.mts.map +1 -1
  12. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.mjs +26 -25
  13. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.mjs.map +1 -1
  14. package/dist/DeFiPositionsController/group-defi-positions-v6.cjs +56 -52
  15. package/dist/DeFiPositionsController/group-defi-positions-v6.cjs.map +1 -1
  16. package/dist/DeFiPositionsController/group-defi-positions-v6.d.cts +3 -2
  17. package/dist/DeFiPositionsController/group-defi-positions-v6.d.cts.map +1 -1
  18. package/dist/DeFiPositionsController/group-defi-positions-v6.d.mts +3 -2
  19. package/dist/DeFiPositionsController/group-defi-positions-v6.d.mts.map +1 -1
  20. package/dist/DeFiPositionsController/group-defi-positions-v6.mjs +56 -52
  21. package/dist/DeFiPositionsController/group-defi-positions-v6.mjs.map +1 -1
  22. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -7,10 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [111.1.2]
11
+
10
12
  ### Changed
11
13
 
14
+ - Bump `@metamask/phishing-controller` from `^17.3.1` to `^17.4.0` ([#9957](https://github.com/MetaMask/core/pull/9957))
12
15
  - Bump `@metamask/network-enablement-controller` from `^6.0.3` to `^6.0.4` ([#9923](https://github.com/MetaMask/core/pull/9923))
13
16
  - Bump `@metamask/remote-feature-flag-controller` from `^5.0.0` to `^6.0.0` ([#9945](https://github.com/MetaMask/core/pull/9945))
17
+ - Bump `@metamask/core-backend` from `^8.1.2` to `^9.0.0` ([#9960](https://github.com/MetaMask/core/pull/9960))
18
+ - Bump `@metamask/transaction-controller` from `^69.5.2` to `^69.6.0` ([#9960](https://github.com/MetaMask/core/pull/9960))
19
+
20
+ ### Fixed
21
+
22
+ - Read DeFi balances from the flat Accounts API v6 `balances` response ([#9911](https://github.com/MetaMask/core/pull/9911))
14
23
 
15
24
  ## [111.1.1]
16
25
 
@@ -3430,7 +3439,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3430
3439
 
3431
3440
  - Use Ethers for AssetsContractController ([#845](https://github.com/MetaMask/core/pull/845))
3432
3441
 
3433
- [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@111.1.1...HEAD
3442
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@111.1.2...HEAD
3443
+ [111.1.2]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@111.1.1...@metamask/assets-controllers@111.1.2
3434
3444
  [111.1.1]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@111.1.0...@metamask/assets-controllers@111.1.1
3435
3445
  [111.1.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@111.0.0...@metamask/assets-controllers@111.1.0
3436
3446
  [111.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@110.1.1...@metamask/assets-controllers@111.0.0
@@ -1 +1 @@
1
- {"version":3,"file":"DeFiPositionsControllerV2-method-action-types.cjs","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { DeFiPositionsControllerV2 } from './DeFiPositionsControllerV2.js';\n\n/**\n * Fetches DeFi positions for the selected account group. State is updated only\n * when every selected account in the response is ready (none report\n * `processingDefiPositions`); each account key in that response replaces that\n * account's state (other accounts stay). While any account is still indexing,\n * prior state is kept and the method polls (invalidating the balances cache\n * between attempts) until all selected accounts are ready, the attempt limit\n * is reached, or a request fails. Concurrent calls for the same selected\n * accounts and `vsCurrency` share one in-flight promise; calls for a different\n * selection or fiat currency start a new fetch and leave prior polls running\n * so a later switch back can join them. When a successful ready response\n * required more than one attempt, or when polling hits the attempt limit\n * while still processing, reports to Sentry via `messenger.captureException`\n * (error names `DeFiPositionsV2FetchAttempts` /\n * `DeFiPositionsV2ProcessingPollExhausted`) so poll limits can be tuned.\n * No-ops when disabled or when the group has no\n * supported accounts. Caching / spam prevention is handled by the apiClient\n * TanStack Query cache (keyed by accounts + query options including\n * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the\n * first attempt (e.g. pull-to-refresh).\n *\n * @param options - Optional fetch modifiers.\n * @param options.forceRefresh - When true, bypass the apiClient cache on the\n * first attempt and fetch immediately.\n * @returns Resolves when the fetch (and any processing polls) finish.\n */\nexport type DeFiPositionsControllerV2FetchDeFiPositionsAction = {\n type: `DeFiPositionsControllerV2:fetchDeFiPositions`;\n handler: DeFiPositionsControllerV2['fetchDeFiPositions'];\n};\n\n/**\n * Union of all DeFiPositionsControllerV2 action types.\n */\nexport type DeFiPositionsControllerV2MethodActions =\n DeFiPositionsControllerV2FetchDeFiPositionsAction;\n"]}
1
+ {"version":3,"file":"DeFiPositionsControllerV2-method-action-types.cjs","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { DeFiPositionsControllerV2 } from './DeFiPositionsControllerV2.js';\n\n/**\n * Fetches DeFi positions for the selected account group. State is updated\n * only when the response lists no accounts in its `processingDefiPositions`\n * array; each resolved account then has its state replaced (other accounts\n * stay). While any account is still indexing, prior state is kept and the\n * method polls (invalidating the balances cache between attempts) until no\n * accounts are processing, the attempt limit is reached, or a request fails.\n * Concurrent calls for the same selected accounts and `vsCurrency` share one\n * in-flight promise; calls for a different selection or fiat currency start\n * a new fetch and leave prior polls running so a later switch back can join\n * them. When a successful ready response required more than one attempt, or\n * when polling hits the attempt limit while still processing, reports to\n * Sentry via `messenger.captureException` (error names\n * `DeFiPositionsV2FetchAttempts` /\n * `DeFiPositionsV2ProcessingPollExhausted`) so poll limits can be tuned.\n * No-ops when disabled or when the group has no\n * supported accounts. Caching / spam prevention is handled by the apiClient\n * TanStack Query cache (keyed by accounts + query options including\n * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the\n * first attempt (e.g. pull-to-refresh).\n *\n * @param options - Optional fetch modifiers.\n * @param options.forceRefresh - When true, bypass the apiClient cache on the\n * first attempt and fetch immediately.\n * @returns Resolves when the fetch (and any processing polls) finish.\n */\nexport type DeFiPositionsControllerV2FetchDeFiPositionsAction = {\n type: `DeFiPositionsControllerV2:fetchDeFiPositions`;\n handler: DeFiPositionsControllerV2['fetchDeFiPositions'];\n};\n\n/**\n * Union of all DeFiPositionsControllerV2 action types.\n */\nexport type DeFiPositionsControllerV2MethodActions =\n DeFiPositionsControllerV2FetchDeFiPositionsAction;\n"]}
@@ -4,19 +4,19 @@
4
4
  */
5
5
  import type { DeFiPositionsControllerV2 } from "./DeFiPositionsControllerV2.cjs";
6
6
  /**
7
- * Fetches DeFi positions for the selected account group. State is updated only
8
- * when every selected account in the response is ready (none report
9
- * `processingDefiPositions`); each account key in that response replaces that
10
- * account's state (other accounts stay). While any account is still indexing,
11
- * prior state is kept and the method polls (invalidating the balances cache
12
- * between attempts) until all selected accounts are ready, the attempt limit
13
- * is reached, or a request fails. Concurrent calls for the same selected
14
- * accounts and `vsCurrency` share one in-flight promise; calls for a different
15
- * selection or fiat currency start a new fetch and leave prior polls running
16
- * so a later switch back can join them. When a successful ready response
17
- * required more than one attempt, or when polling hits the attempt limit
18
- * while still processing, reports to Sentry via `messenger.captureException`
19
- * (error names `DeFiPositionsV2FetchAttempts` /
7
+ * Fetches DeFi positions for the selected account group. State is updated
8
+ * only when the response lists no accounts in its `processingDefiPositions`
9
+ * array; each resolved account then has its state replaced (other accounts
10
+ * stay). While any account is still indexing, prior state is kept and the
11
+ * method polls (invalidating the balances cache between attempts) until no
12
+ * accounts are processing, the attempt limit is reached, or a request fails.
13
+ * Concurrent calls for the same selected accounts and `vsCurrency` share one
14
+ * in-flight promise; calls for a different selection or fiat currency start
15
+ * a new fetch and leave prior polls running so a later switch back can join
16
+ * them. When a successful ready response required more than one attempt, or
17
+ * when polling hits the attempt limit while still processing, reports to
18
+ * Sentry via `messenger.captureException` (error names
19
+ * `DeFiPositionsV2FetchAttempts` /
20
20
  * `DeFiPositionsV2ProcessingPollExhausted`) so poll limits can be tuned.
21
21
  * No-ops when disabled or when the group has no
22
22
  * supported accounts. Caching / spam prevention is handled by the apiClient
@@ -4,19 +4,19 @@
4
4
  */
5
5
  import type { DeFiPositionsControllerV2 } from "./DeFiPositionsControllerV2.mjs";
6
6
  /**
7
- * Fetches DeFi positions for the selected account group. State is updated only
8
- * when every selected account in the response is ready (none report
9
- * `processingDefiPositions`); each account key in that response replaces that
10
- * account's state (other accounts stay). While any account is still indexing,
11
- * prior state is kept and the method polls (invalidating the balances cache
12
- * between attempts) until all selected accounts are ready, the attempt limit
13
- * is reached, or a request fails. Concurrent calls for the same selected
14
- * accounts and `vsCurrency` share one in-flight promise; calls for a different
15
- * selection or fiat currency start a new fetch and leave prior polls running
16
- * so a later switch back can join them. When a successful ready response
17
- * required more than one attempt, or when polling hits the attempt limit
18
- * while still processing, reports to Sentry via `messenger.captureException`
19
- * (error names `DeFiPositionsV2FetchAttempts` /
7
+ * Fetches DeFi positions for the selected account group. State is updated
8
+ * only when the response lists no accounts in its `processingDefiPositions`
9
+ * array; each resolved account then has its state replaced (other accounts
10
+ * stay). While any account is still indexing, prior state is kept and the
11
+ * method polls (invalidating the balances cache between attempts) until no
12
+ * accounts are processing, the attempt limit is reached, or a request fails.
13
+ * Concurrent calls for the same selected accounts and `vsCurrency` share one
14
+ * in-flight promise; calls for a different selection or fiat currency start
15
+ * a new fetch and leave prior polls running so a later switch back can join
16
+ * them. When a successful ready response required more than one attempt, or
17
+ * when polling hits the attempt limit while still processing, reports to
18
+ * Sentry via `messenger.captureException` (error names
19
+ * `DeFiPositionsV2FetchAttempts` /
20
20
  * `DeFiPositionsV2ProcessingPollExhausted`) so poll limits can be tuned.
21
21
  * No-ops when disabled or when the group has no
22
22
  * supported accounts. Caching / spam prevention is handled by the apiClient
@@ -1 +1 @@
1
- {"version":3,"file":"DeFiPositionsControllerV2-method-action-types.mjs","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { DeFiPositionsControllerV2 } from './DeFiPositionsControllerV2.js';\n\n/**\n * Fetches DeFi positions for the selected account group. State is updated only\n * when every selected account in the response is ready (none report\n * `processingDefiPositions`); each account key in that response replaces that\n * account's state (other accounts stay). While any account is still indexing,\n * prior state is kept and the method polls (invalidating the balances cache\n * between attempts) until all selected accounts are ready, the attempt limit\n * is reached, or a request fails. Concurrent calls for the same selected\n * accounts and `vsCurrency` share one in-flight promise; calls for a different\n * selection or fiat currency start a new fetch and leave prior polls running\n * so a later switch back can join them. When a successful ready response\n * required more than one attempt, or when polling hits the attempt limit\n * while still processing, reports to Sentry via `messenger.captureException`\n * (error names `DeFiPositionsV2FetchAttempts` /\n * `DeFiPositionsV2ProcessingPollExhausted`) so poll limits can be tuned.\n * No-ops when disabled or when the group has no\n * supported accounts. Caching / spam prevention is handled by the apiClient\n * TanStack Query cache (keyed by accounts + query options including\n * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the\n * first attempt (e.g. pull-to-refresh).\n *\n * @param options - Optional fetch modifiers.\n * @param options.forceRefresh - When true, bypass the apiClient cache on the\n * first attempt and fetch immediately.\n * @returns Resolves when the fetch (and any processing polls) finish.\n */\nexport type DeFiPositionsControllerV2FetchDeFiPositionsAction = {\n type: `DeFiPositionsControllerV2:fetchDeFiPositions`;\n handler: DeFiPositionsControllerV2['fetchDeFiPositions'];\n};\n\n/**\n * Union of all DeFiPositionsControllerV2 action types.\n */\nexport type DeFiPositionsControllerV2MethodActions =\n DeFiPositionsControllerV2FetchDeFiPositionsAction;\n"]}
1
+ {"version":3,"file":"DeFiPositionsControllerV2-method-action-types.mjs","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { DeFiPositionsControllerV2 } from './DeFiPositionsControllerV2.js';\n\n/**\n * Fetches DeFi positions for the selected account group. State is updated\n * only when the response lists no accounts in its `processingDefiPositions`\n * array; each resolved account then has its state replaced (other accounts\n * stay). While any account is still indexing, prior state is kept and the\n * method polls (invalidating the balances cache between attempts) until no\n * accounts are processing, the attempt limit is reached, or a request fails.\n * Concurrent calls for the same selected accounts and `vsCurrency` share one\n * in-flight promise; calls for a different selection or fiat currency start\n * a new fetch and leave prior polls running so a later switch back can join\n * them. When a successful ready response required more than one attempt, or\n * when polling hits the attempt limit while still processing, reports to\n * Sentry via `messenger.captureException` (error names\n * `DeFiPositionsV2FetchAttempts` /\n * `DeFiPositionsV2ProcessingPollExhausted`) so poll limits can be tuned.\n * No-ops when disabled or when the group has no\n * supported accounts. Caching / spam prevention is handled by the apiClient\n * TanStack Query cache (keyed by accounts + query options including\n * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the\n * first attempt (e.g. pull-to-refresh).\n *\n * @param options - Optional fetch modifiers.\n * @param options.forceRefresh - When true, bypass the apiClient cache on the\n * first attempt and fetch immediately.\n * @returns Resolves when the fetch (and any processing polls) finish.\n */\nexport type DeFiPositionsControllerV2FetchDeFiPositionsAction = {\n type: `DeFiPositionsControllerV2:fetchDeFiPositions`;\n handler: DeFiPositionsControllerV2['fetchDeFiPositions'];\n};\n\n/**\n * Union of all DeFiPositionsControllerV2 action types.\n */\nexport type DeFiPositionsControllerV2MethodActions =\n DeFiPositionsControllerV2FetchDeFiPositionsAction;\n"]}
@@ -50,14 +50,15 @@ exports.getDefaultDeFiPositionsControllerV2State = getDefaultDeFiPositionsContro
50
50
  * `{ forceRefresh: true }` to bypass that cache on the first attempt (e.g.
51
51
  * pull-to-refresh); later processing polls always bypass the cache.
52
52
  *
53
- * When the API reports `processingDefiPositions` for any account, this
54
- * controller polls until indexing finishes or the attempt limit is reached,
55
- * and only then processes and writes state — mixed ready/processing responses
56
- * leave prior state untouched. Concurrent calls for the same selected accounts
57
- * and `vsCurrency` share one in-flight promise so the UI can treat the pending
58
- * promise as a loading signal. Calls for a different selection or fiat currency
59
- * start their own fetch and leave any prior poll running, so switching back can
60
- * join an in-flight fetch for that group and currency.
53
+ * When the API reports account IDs in the response-level
54
+ * `processingDefiPositions` array, this controller polls until indexing
55
+ * finishes or the attempt limit is reached, and only then processes and writes
56
+ * state — responses that still list processing accounts leave prior state
57
+ * untouched. Concurrent calls for the same selected accounts and `vsCurrency`
58
+ * share one in-flight promise so the UI can treat the pending promise as a
59
+ * loading signal. Calls for a different selection or fiat currency start their
60
+ * own fetch and leave any prior poll running, so switching back can join an
61
+ * in-flight fetch for that group and currency.
61
62
  *
62
63
  * Processing-poll `maxAttempts` / `pollInterval` are read via
63
64
  * {@link getProcessingPollConfig} from the `defiControllerV2` remote feature
@@ -101,19 +102,19 @@ class DeFiPositionsControllerV2 extends base_controller_1.BaseController {
101
102
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
102
103
  }
103
104
  /**
104
- * Fetches DeFi positions for the selected account group. State is updated only
105
- * when every selected account in the response is ready (none report
106
- * `processingDefiPositions`); each account key in that response replaces that
107
- * account's state (other accounts stay). While any account is still indexing,
108
- * prior state is kept and the method polls (invalidating the balances cache
109
- * between attempts) until all selected accounts are ready, the attempt limit
110
- * is reached, or a request fails. Concurrent calls for the same selected
111
- * accounts and `vsCurrency` share one in-flight promise; calls for a different
112
- * selection or fiat currency start a new fetch and leave prior polls running
113
- * so a later switch back can join them. When a successful ready response
114
- * required more than one attempt, or when polling hits the attempt limit
115
- * while still processing, reports to Sentry via `messenger.captureException`
116
- * (error names `DeFiPositionsV2FetchAttempts` /
105
+ * Fetches DeFi positions for the selected account group. State is updated
106
+ * only when the response lists no accounts in its `processingDefiPositions`
107
+ * array; each resolved account then has its state replaced (other accounts
108
+ * stay). While any account is still indexing, prior state is kept and the
109
+ * method polls (invalidating the balances cache between attempts) until no
110
+ * accounts are processing, the attempt limit is reached, or a request fails.
111
+ * Concurrent calls for the same selected accounts and `vsCurrency` share one
112
+ * in-flight promise; calls for a different selection or fiat currency start
113
+ * a new fetch and leave prior polls running so a later switch back can join
114
+ * them. When a successful ready response required more than one attempt, or
115
+ * when polling hits the attempt limit while still processing, reports to
116
+ * Sentry via `messenger.captureException` (error names
117
+ * `DeFiPositionsV2FetchAttempts` /
117
118
  * `DeFiPositionsV2ProcessingPollExhausted`) so poll limits can be tuned.
118
119
  * No-ops when disabled or when the group has no
119
120
  * supported accounts. Caching / spam prevention is handled by the apiClient
@@ -184,10 +185,10 @@ _DeFiPositionsControllerV2_apiClient = new WeakMap(), _DeFiPositionsControllerV2
184
185
  console.error('Failed to fetch DeFi positions', error);
185
186
  return;
186
187
  }
187
- const stillProcessing = response.accounts.some((account) => account.processingDefiPositions);
188
- // Only process and write when every account is ready so a partial
189
- // response cannot clear or overwrite positions for accounts that are
190
- // still indexing.
188
+ const stillProcessing = (response.processingDefiPositions?.length ?? 0) > 0;
189
+ // Only process and write when no account is still indexing, so a
190
+ // partial response (whose DeFi rows for processing accounts are
191
+ // omitted) cannot clear or overwrite positions for those accounts.
191
192
  if (!stillProcessing) {
192
193
  const positionsByAccount = (0, group_defi_positions_v6_js_1.groupDeFiPositionsV6)(response, internalAccountIdByCaip);
193
194
  this.update((state) => {
@@ -1 +1 @@
1
- {"version":3,"file":"DeFiPositionsControllerV2.cjs","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountTreeControllerGetAccountsFromSelectedAccountGroupAction } from '@metamask/account-tree-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport type { ApiPlatformClient } from '@metamask/core-backend';\nimport type { Messenger } from '@metamask/messenger';\nimport type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller';\n\nimport { buildDeFiBalancesQuery } from './build-defi-balances-query.js';\nimport type { DeFiBalancesQuery } from './build-defi-balances-query.js';\nimport { getProcessingPollConfig } from './defi-controller-v2-feature-flag.js';\nimport type { DeFiPositionsControllerV2MethodActions } from './DeFiPositionsControllerV2-method-action-types.js';\nimport type { DeFiPositionsByAccount } from './group-defi-positions-v6.js';\nimport { groupDeFiPositionsV6 } from './group-defi-positions-v6.js';\n\nconst controllerName = 'DeFiPositionsControllerV2';\n\nconst MESSENGER_EXPOSED_METHODS = ['fetchDeFiPositions'] as const;\n\n/**\n * @param ms - Milliseconds to wait.\n * @returns A promise that resolves after `ms`.\n */\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport type DeFiPositionsControllerV2State = {\n /**\n * DeFi positions keyed by internal MetaMask account ID (`InternalAccount.id`,\n * the same key AssetsController uses). Each account maps to a flat list of\n * protocol groups shown in the DeFi tab, each carrying its own `chainId` for\n * filtering plus the details-page sections embedded inside it. This is\n * exactly the shape the client consumes, so no further transformation is\n * needed on read.\n *\n * Named `allDeFiPositionsV2` (rather than `allDeFiPositions`) so it can live\n * alongside the legacy `DeFiPositionsController` in clients that flatten every\n * controller's state into a single object (e.g. the extension background),\n * without colliding on the shared `allDeFiPositions` key.\n */\n allDeFiPositionsV2: DeFiPositionsByAccount;\n};\n\nconst controllerMetadata: StateMetadata<DeFiPositionsControllerV2State> = {\n allDeFiPositionsV2: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n};\n\nexport const getDefaultDeFiPositionsControllerV2State =\n (): DeFiPositionsControllerV2State => {\n return {\n allDeFiPositionsV2: {},\n };\n };\n\nexport type DeFiPositionsControllerV2GetStateAction = ControllerGetStateAction<\n typeof controllerName,\n DeFiPositionsControllerV2State\n>;\n\nexport type DeFiPositionsControllerV2Actions =\n | DeFiPositionsControllerV2GetStateAction\n | DeFiPositionsControllerV2MethodActions;\n\nexport type DeFiPositionsControllerV2StateChangedEvent =\n ControllerStateChangedEvent<\n typeof controllerName,\n DeFiPositionsControllerV2State\n >;\n\nexport type DeFiPositionsControllerV2Events =\n DeFiPositionsControllerV2StateChangedEvent;\n\n/**\n * The external actions available to the {@link DeFiPositionsControllerV2}.\n */\nexport type AllowedActions =\n | AccountTreeControllerGetAccountsFromSelectedAccountGroupAction\n | RemoteFeatureFlagControllerGetStateAction;\n\n/**\n * The external events available to the {@link DeFiPositionsControllerV2}.\n *\n * None yet — clients must call `fetchDeFiPositions` (and optionally\n * `{ forceRefresh: true }`) on their own triggers. Likely future subscriptions:\n * `AccountTreeController:selectedAccountGroupChange`,\n * `TransactionController:transactionConfirmed`, and `KeyringController:lock`.\n */\nexport type AllowedEvents = never;\n\nexport type DeFiPositionsControllerV2Messenger = Messenger<\n typeof controllerName,\n DeFiPositionsControllerV2Actions | AllowedActions,\n DeFiPositionsControllerV2Events | AllowedEvents\n>;\n\n/**\n * Controller that fetches DeFi positions for the selected account group from\n * the Accounts API (v6 multiaccount balances) and stores them in the shape the\n * client consumes directly.\n *\n * Deduplication and freshness are handled by the shared TanStack Query cache on\n * {@link ApiPlatformClient} (balances default `staleTime` is 1 minute). Pass\n * `{ forceRefresh: true }` to bypass that cache on the first attempt (e.g.\n * pull-to-refresh); later processing polls always bypass the cache.\n *\n * When the API reports `processingDefiPositions` for any account, this\n * controller polls until indexing finishes or the attempt limit is reached,\n * and only then processes and writes state — mixed ready/processing responses\n * leave prior state untouched. Concurrent calls for the same selected accounts\n * and `vsCurrency` share one in-flight promise so the UI can treat the pending\n * promise as a loading signal. Calls for a different selection or fiat currency\n * start their own fetch and leave any prior poll running, so switching back can\n * join an in-flight fetch for that group and currency.\n *\n * Processing-poll `maxAttempts` / `pollInterval` are read via\n * {@link getProcessingPollConfig} from the `defiControllerV2` remote feature\n * flag (`RemoteFeatureFlagController:getState`), falling back to built-in\n * defaults when unset or invalid. Clients must delegate that action to this\n * controller's messenger.\n */\nexport class DeFiPositionsControllerV2 extends BaseController<\n typeof controllerName,\n DeFiPositionsControllerV2State,\n DeFiPositionsControllerV2Messenger\n> {\n readonly #apiClient: ApiPlatformClient;\n\n readonly #isEnabled: () => boolean;\n\n readonly #getVsCurrency: () => string;\n\n /**\n * In-flight fetches keyed by selected DeFi-queryable account IDs plus\n * `vsCurrency`. Concurrent callers for the same selection and currency share\n * a promise; different selections or fiat currencies keep independent\n * fetches so fast switching can join an earlier matching poll.\n */\n readonly #inFlightFetches = new Map<string, Promise<void>>();\n\n /**\n * @param options - Constructor options.\n * @param options.messenger - The controller messenger.\n * @param options.apiClient - Accounts API client used to fetch balances/positions. Auth is handled by the client.\n * @param options.isEnabled - Returns whether fetching is enabled (default: () => false).\n * @param options.getVsCurrency - Returns the fiat currency for prices (default: () => 'usd').\n * @param options.state - Initial controller state.\n */\n constructor({\n messenger,\n apiClient,\n isEnabled,\n getVsCurrency,\n state,\n }: {\n messenger: DeFiPositionsControllerV2Messenger;\n apiClient: ApiPlatformClient;\n isEnabled: () => boolean;\n getVsCurrency: () => string;\n state?: Partial<DeFiPositionsControllerV2State>;\n }) {\n super({\n name: controllerName,\n metadata: controllerMetadata,\n messenger,\n state: {\n ...getDefaultDeFiPositionsControllerV2State(),\n ...state,\n },\n });\n\n this.#apiClient = apiClient;\n this.#isEnabled = isEnabled;\n this.#getVsCurrency = getVsCurrency;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Fetches DeFi positions for the selected account group. State is updated only\n * when every selected account in the response is ready (none report\n * `processingDefiPositions`); each account key in that response replaces that\n * account's state (other accounts stay). While any account is still indexing,\n * prior state is kept and the method polls (invalidating the balances cache\n * between attempts) until all selected accounts are ready, the attempt limit\n * is reached, or a request fails. Concurrent calls for the same selected\n * accounts and `vsCurrency` share one in-flight promise; calls for a different\n * selection or fiat currency start a new fetch and leave prior polls running\n * so a later switch back can join them. When a successful ready response\n * required more than one attempt, or when polling hits the attempt limit\n * while still processing, reports to Sentry via `messenger.captureException`\n * (error names `DeFiPositionsV2FetchAttempts` /\n * `DeFiPositionsV2ProcessingPollExhausted`) so poll limits can be tuned.\n * No-ops when disabled or when the group has no\n * supported accounts. Caching / spam prevention is handled by the apiClient\n * TanStack Query cache (keyed by accounts + query options including\n * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the\n * first attempt (e.g. pull-to-refresh).\n *\n * @param options - Optional fetch modifiers.\n * @param options.forceRefresh - When true, bypass the apiClient cache on the\n * first attempt and fetch immediately.\n * @returns Resolves when the fetch (and any processing polls) finish.\n */\n async fetchDeFiPositions(options?: {\n forceRefresh?: boolean;\n }): Promise<void> {\n if (!this.#isEnabled()) {\n return;\n }\n\n const selectedAccounts = this.messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n const query = buildDeFiBalancesQuery(selectedAccounts);\n const vsCurrency = this.#getVsCurrency().toLowerCase();\n // Include vsCurrency so a fiat change does not join a poll priced in the\n // previous currency (TanStack also keys the balances cache on vsCurrency).\n const inFlightKey = [\n ...[...query.internalAccountIdByCaip.keys()].sort(),\n vsCurrency,\n ].join('\\0');\n\n const existing = this.#inFlightFetches.get(inFlightKey);\n if (existing) {\n await existing;\n return;\n }\n\n // Same-key callers join this promise instead of replacing it, so cleanup\n // can always delete without checking identity.\n const fetchPromise = this.#fetchDeFiPositions(\n options,\n query,\n vsCurrency,\n ).finally(() => {\n this.#inFlightFetches.delete(inFlightKey);\n });\n this.#inFlightFetches.set(inFlightKey, fetchPromise);\n\n await fetchPromise;\n }\n\n async #fetchDeFiPositions(\n options: { forceRefresh?: boolean } | undefined,\n { networks, internalAccountIdByCaip }: DeFiBalancesQuery,\n vsCurrency: string,\n ): Promise<void> {\n if (internalAccountIdByCaip.size === 0 || networks.length === 0) {\n return;\n }\n\n const accountIds = [...internalAccountIdByCaip.keys()];\n const queryOptions = {\n networks,\n includeDeFiBalances: true,\n forceFetchDeFiPositions: true,\n includePrices: true,\n vsCurrency,\n };\n\n // Resolve once per fetch so a mid-poll remote-flag change cannot stretch\n // or shrink this poll sequence inconsistently.\n const { maxAttempts, pollInterval } = getProcessingPollConfig(\n this.messenger,\n );\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n // First attempt respects forceRefresh; later polls always bypass cache\n // so we do not spin on a stale processing snapshot.\n const fetchOptions = {\n ...(options?.forceRefresh || attempt > 0 ? { staleTime: 0 } : {}),\n };\n\n let response;\n try {\n response = await this.#apiClient.accounts.fetchV6MultiAccountBalances(\n accountIds,\n queryOptions,\n fetchOptions,\n );\n } catch (error) {\n // Soft-fail so prior state stays and the in-flight promise settles.\n console.error('Failed to fetch DeFi positions', error);\n return;\n }\n\n const stillProcessing = response.accounts.some(\n (account) => account.processingDefiPositions,\n );\n\n // Only process and write when every account is ready so a partial\n // response cannot clear or overwrite positions for accounts that are\n // still indexing.\n if (!stillProcessing) {\n const positionsByAccount = groupDeFiPositionsV6(\n response,\n internalAccountIdByCaip,\n );\n\n this.update((state) => {\n for (const [accountId, positions] of Object.entries(\n positionsByAccount,\n )) {\n state.allDeFiPositionsV2[accountId] = positions;\n }\n });\n\n // Report how many attempts were needed (only when polling was\n // required) so Sentry can inform remote-flag / default poll-limit\n // tuning without flooding on first-try successes.\n const attemptsTaken = attempt + 1;\n if (attemptsTaken > 1) {\n const multipleAttemptsError = new Error(\n `DeFiPositionsControllerV2: positions ready after ${attemptsTaken} attempt(s)`,\n );\n multipleAttemptsError.name = 'DeFiPositionsV2FetchAttempts';\n this.messenger.captureException?.(multipleAttemptsError);\n }\n return;\n }\n\n const { queryKey } =\n this.#apiClient.accounts.getV6MultiAccountBalancesQueryOptions(\n accountIds,\n queryOptions,\n fetchOptions,\n );\n await this.#apiClient.accounts.queryClient.invalidateQueries({\n queryKey,\n });\n\n const isLastAttempt = attempt >= maxAttempts - 1;\n if (isLastAttempt) {\n // Report exhausted polls so Sentry can inform remote-flag / default\n // poll-limit tuning when indexing never finishes in time.\n const multipleAttemptsError = new Error(\n `DeFiPositionsControllerV2: still processing after ${maxAttempts} attempt(s)`,\n );\n multipleAttemptsError.name = 'DeFiPositionsV2ProcessingPollExhausted';\n this.messenger.captureException?.(multipleAttemptsError);\n return;\n }\n\n await delay(pollInterval);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"DeFiPositionsControllerV2.cjs","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountTreeControllerGetAccountsFromSelectedAccountGroupAction } from '@metamask/account-tree-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport type { ApiPlatformClient } from '@metamask/core-backend';\nimport type { Messenger } from '@metamask/messenger';\nimport type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller';\n\nimport { buildDeFiBalancesQuery } from './build-defi-balances-query.js';\nimport type { DeFiBalancesQuery } from './build-defi-balances-query.js';\nimport { getProcessingPollConfig } from './defi-controller-v2-feature-flag.js';\nimport type { DeFiPositionsControllerV2MethodActions } from './DeFiPositionsControllerV2-method-action-types.js';\nimport type { DeFiPositionsByAccount } from './group-defi-positions-v6.js';\nimport { groupDeFiPositionsV6 } from './group-defi-positions-v6.js';\n\nconst controllerName = 'DeFiPositionsControllerV2';\n\nconst MESSENGER_EXPOSED_METHODS = ['fetchDeFiPositions'] as const;\n\n/**\n * @param ms - Milliseconds to wait.\n * @returns A promise that resolves after `ms`.\n */\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport type DeFiPositionsControllerV2State = {\n /**\n * DeFi positions keyed by internal MetaMask account ID (`InternalAccount.id`,\n * the same key AssetsController uses). Each account maps to a flat list of\n * protocol groups shown in the DeFi tab, each carrying its own `chainId` for\n * filtering plus the details-page sections embedded inside it. This is\n * exactly the shape the client consumes, so no further transformation is\n * needed on read.\n *\n * Named `allDeFiPositionsV2` (rather than `allDeFiPositions`) so it can live\n * alongside the legacy `DeFiPositionsController` in clients that flatten every\n * controller's state into a single object (e.g. the extension background),\n * without colliding on the shared `allDeFiPositions` key.\n */\n allDeFiPositionsV2: DeFiPositionsByAccount;\n};\n\nconst controllerMetadata: StateMetadata<DeFiPositionsControllerV2State> = {\n allDeFiPositionsV2: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n};\n\nexport const getDefaultDeFiPositionsControllerV2State =\n (): DeFiPositionsControllerV2State => {\n return {\n allDeFiPositionsV2: {},\n };\n };\n\nexport type DeFiPositionsControllerV2GetStateAction = ControllerGetStateAction<\n typeof controllerName,\n DeFiPositionsControllerV2State\n>;\n\nexport type DeFiPositionsControllerV2Actions =\n | DeFiPositionsControllerV2GetStateAction\n | DeFiPositionsControllerV2MethodActions;\n\nexport type DeFiPositionsControllerV2StateChangedEvent =\n ControllerStateChangedEvent<\n typeof controllerName,\n DeFiPositionsControllerV2State\n >;\n\nexport type DeFiPositionsControllerV2Events =\n DeFiPositionsControllerV2StateChangedEvent;\n\n/**\n * The external actions available to the {@link DeFiPositionsControllerV2}.\n */\nexport type AllowedActions =\n | AccountTreeControllerGetAccountsFromSelectedAccountGroupAction\n | RemoteFeatureFlagControllerGetStateAction;\n\n/**\n * The external events available to the {@link DeFiPositionsControllerV2}.\n *\n * None yet — clients must call `fetchDeFiPositions` (and optionally\n * `{ forceRefresh: true }`) on their own triggers. Likely future subscriptions:\n * `AccountTreeController:selectedAccountGroupChange`,\n * `TransactionController:transactionConfirmed`, and `KeyringController:lock`.\n */\nexport type AllowedEvents = never;\n\nexport type DeFiPositionsControllerV2Messenger = Messenger<\n typeof controllerName,\n DeFiPositionsControllerV2Actions | AllowedActions,\n DeFiPositionsControllerV2Events | AllowedEvents\n>;\n\n/**\n * Controller that fetches DeFi positions for the selected account group from\n * the Accounts API (v6 multiaccount balances) and stores them in the shape the\n * client consumes directly.\n *\n * Deduplication and freshness are handled by the shared TanStack Query cache on\n * {@link ApiPlatformClient} (balances default `staleTime` is 1 minute). Pass\n * `{ forceRefresh: true }` to bypass that cache on the first attempt (e.g.\n * pull-to-refresh); later processing polls always bypass the cache.\n *\n * When the API reports account IDs in the response-level\n * `processingDefiPositions` array, this controller polls until indexing\n * finishes or the attempt limit is reached, and only then processes and writes\n * state — responses that still list processing accounts leave prior state\n * untouched. Concurrent calls for the same selected accounts and `vsCurrency`\n * share one in-flight promise so the UI can treat the pending promise as a\n * loading signal. Calls for a different selection or fiat currency start their\n * own fetch and leave any prior poll running, so switching back can join an\n * in-flight fetch for that group and currency.\n *\n * Processing-poll `maxAttempts` / `pollInterval` are read via\n * {@link getProcessingPollConfig} from the `defiControllerV2` remote feature\n * flag (`RemoteFeatureFlagController:getState`), falling back to built-in\n * defaults when unset or invalid. Clients must delegate that action to this\n * controller's messenger.\n */\nexport class DeFiPositionsControllerV2 extends BaseController<\n typeof controllerName,\n DeFiPositionsControllerV2State,\n DeFiPositionsControllerV2Messenger\n> {\n readonly #apiClient: ApiPlatformClient;\n\n readonly #isEnabled: () => boolean;\n\n readonly #getVsCurrency: () => string;\n\n /**\n * In-flight fetches keyed by selected DeFi-queryable account IDs plus\n * `vsCurrency`. Concurrent callers for the same selection and currency share\n * a promise; different selections or fiat currencies keep independent\n * fetches so fast switching can join an earlier matching poll.\n */\n readonly #inFlightFetches = new Map<string, Promise<void>>();\n\n /**\n * @param options - Constructor options.\n * @param options.messenger - The controller messenger.\n * @param options.apiClient - Accounts API client used to fetch balances/positions. Auth is handled by the client.\n * @param options.isEnabled - Returns whether fetching is enabled (default: () => false).\n * @param options.getVsCurrency - Returns the fiat currency for prices (default: () => 'usd').\n * @param options.state - Initial controller state.\n */\n constructor({\n messenger,\n apiClient,\n isEnabled,\n getVsCurrency,\n state,\n }: {\n messenger: DeFiPositionsControllerV2Messenger;\n apiClient: ApiPlatformClient;\n isEnabled: () => boolean;\n getVsCurrency: () => string;\n state?: Partial<DeFiPositionsControllerV2State>;\n }) {\n super({\n name: controllerName,\n metadata: controllerMetadata,\n messenger,\n state: {\n ...getDefaultDeFiPositionsControllerV2State(),\n ...state,\n },\n });\n\n this.#apiClient = apiClient;\n this.#isEnabled = isEnabled;\n this.#getVsCurrency = getVsCurrency;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Fetches DeFi positions for the selected account group. State is updated\n * only when the response lists no accounts in its `processingDefiPositions`\n * array; each resolved account then has its state replaced (other accounts\n * stay). While any account is still indexing, prior state is kept and the\n * method polls (invalidating the balances cache between attempts) until no\n * accounts are processing, the attempt limit is reached, or a request fails.\n * Concurrent calls for the same selected accounts and `vsCurrency` share one\n * in-flight promise; calls for a different selection or fiat currency start\n * a new fetch and leave prior polls running so a later switch back can join\n * them. When a successful ready response required more than one attempt, or\n * when polling hits the attempt limit while still processing, reports to\n * Sentry via `messenger.captureException` (error names\n * `DeFiPositionsV2FetchAttempts` /\n * `DeFiPositionsV2ProcessingPollExhausted`) so poll limits can be tuned.\n * No-ops when disabled or when the group has no\n * supported accounts. Caching / spam prevention is handled by the apiClient\n * TanStack Query cache (keyed by accounts + query options including\n * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the\n * first attempt (e.g. pull-to-refresh).\n *\n * @param options - Optional fetch modifiers.\n * @param options.forceRefresh - When true, bypass the apiClient cache on the\n * first attempt and fetch immediately.\n * @returns Resolves when the fetch (and any processing polls) finish.\n */\n async fetchDeFiPositions(options?: {\n forceRefresh?: boolean;\n }): Promise<void> {\n if (!this.#isEnabled()) {\n return;\n }\n\n const selectedAccounts = this.messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n const query = buildDeFiBalancesQuery(selectedAccounts);\n const vsCurrency = this.#getVsCurrency().toLowerCase();\n // Include vsCurrency so a fiat change does not join a poll priced in the\n // previous currency (TanStack also keys the balances cache on vsCurrency).\n const inFlightKey = [\n ...[...query.internalAccountIdByCaip.keys()].sort(),\n vsCurrency,\n ].join('\\0');\n\n const existing = this.#inFlightFetches.get(inFlightKey);\n if (existing) {\n await existing;\n return;\n }\n\n // Same-key callers join this promise instead of replacing it, so cleanup\n // can always delete without checking identity.\n const fetchPromise = this.#fetchDeFiPositions(\n options,\n query,\n vsCurrency,\n ).finally(() => {\n this.#inFlightFetches.delete(inFlightKey);\n });\n this.#inFlightFetches.set(inFlightKey, fetchPromise);\n\n await fetchPromise;\n }\n\n async #fetchDeFiPositions(\n options: { forceRefresh?: boolean } | undefined,\n { networks, internalAccountIdByCaip }: DeFiBalancesQuery,\n vsCurrency: string,\n ): Promise<void> {\n if (internalAccountIdByCaip.size === 0 || networks.length === 0) {\n return;\n }\n\n const accountIds = [...internalAccountIdByCaip.keys()];\n const queryOptions = {\n networks,\n includeDeFiBalances: true,\n forceFetchDeFiPositions: true,\n includePrices: true,\n vsCurrency,\n };\n\n // Resolve once per fetch so a mid-poll remote-flag change cannot stretch\n // or shrink this poll sequence inconsistently.\n const { maxAttempts, pollInterval } = getProcessingPollConfig(\n this.messenger,\n );\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n // First attempt respects forceRefresh; later polls always bypass cache\n // so we do not spin on a stale processing snapshot.\n const fetchOptions = {\n ...(options?.forceRefresh || attempt > 0 ? { staleTime: 0 } : {}),\n };\n\n let response;\n try {\n response = await this.#apiClient.accounts.fetchV6MultiAccountBalances(\n accountIds,\n queryOptions,\n fetchOptions,\n );\n } catch (error) {\n // Soft-fail so prior state stays and the in-flight promise settles.\n console.error('Failed to fetch DeFi positions', error);\n return;\n }\n\n const stillProcessing =\n (response.processingDefiPositions?.length ?? 0) > 0;\n\n // Only process and write when no account is still indexing, so a\n // partial response (whose DeFi rows for processing accounts are\n // omitted) cannot clear or overwrite positions for those accounts.\n if (!stillProcessing) {\n const positionsByAccount = groupDeFiPositionsV6(\n response,\n internalAccountIdByCaip,\n );\n\n this.update((state) => {\n for (const [accountId, positions] of Object.entries(\n positionsByAccount,\n )) {\n state.allDeFiPositionsV2[accountId] = positions;\n }\n });\n\n // Report how many attempts were needed (only when polling was\n // required) so Sentry can inform remote-flag / default poll-limit\n // tuning without flooding on first-try successes.\n const attemptsTaken = attempt + 1;\n if (attemptsTaken > 1) {\n const multipleAttemptsError = new Error(\n `DeFiPositionsControllerV2: positions ready after ${attemptsTaken} attempt(s)`,\n );\n multipleAttemptsError.name = 'DeFiPositionsV2FetchAttempts';\n this.messenger.captureException?.(multipleAttemptsError);\n }\n return;\n }\n\n const { queryKey } =\n this.#apiClient.accounts.getV6MultiAccountBalancesQueryOptions(\n accountIds,\n queryOptions,\n fetchOptions,\n );\n await this.#apiClient.accounts.queryClient.invalidateQueries({\n queryKey,\n });\n\n const isLastAttempt = attempt >= maxAttempts - 1;\n if (isLastAttempt) {\n // Report exhausted polls so Sentry can inform remote-flag / default\n // poll-limit tuning when indexing never finishes in time.\n const multipleAttemptsError = new Error(\n `DeFiPositionsControllerV2: still processing after ${maxAttempts} attempt(s)`,\n );\n multipleAttemptsError.name = 'DeFiPositionsV2ProcessingPollExhausted';\n this.messenger.captureException?.(multipleAttemptsError);\n return;\n }\n\n await delay(pollInterval);\n }\n }\n}\n"]}
@@ -52,14 +52,15 @@ export type DeFiPositionsControllerV2Messenger = Messenger<typeof controllerName
52
52
  * `{ forceRefresh: true }` to bypass that cache on the first attempt (e.g.
53
53
  * pull-to-refresh); later processing polls always bypass the cache.
54
54
  *
55
- * When the API reports `processingDefiPositions` for any account, this
56
- * controller polls until indexing finishes or the attempt limit is reached,
57
- * and only then processes and writes state — mixed ready/processing responses
58
- * leave prior state untouched. Concurrent calls for the same selected accounts
59
- * and `vsCurrency` share one in-flight promise so the UI can treat the pending
60
- * promise as a loading signal. Calls for a different selection or fiat currency
61
- * start their own fetch and leave any prior poll running, so switching back can
62
- * join an in-flight fetch for that group and currency.
55
+ * When the API reports account IDs in the response-level
56
+ * `processingDefiPositions` array, this controller polls until indexing
57
+ * finishes or the attempt limit is reached, and only then processes and writes
58
+ * state — responses that still list processing accounts leave prior state
59
+ * untouched. Concurrent calls for the same selected accounts and `vsCurrency`
60
+ * share one in-flight promise so the UI can treat the pending promise as a
61
+ * loading signal. Calls for a different selection or fiat currency start their
62
+ * own fetch and leave any prior poll running, so switching back can join an
63
+ * in-flight fetch for that group and currency.
63
64
  *
64
65
  * Processing-poll `maxAttempts` / `pollInterval` are read via
65
66
  * {@link getProcessingPollConfig} from the `defiControllerV2` remote feature
@@ -85,19 +86,19 @@ export declare class DeFiPositionsControllerV2 extends BaseController<typeof con
85
86
  state?: Partial<DeFiPositionsControllerV2State>;
86
87
  });
87
88
  /**
88
- * Fetches DeFi positions for the selected account group. State is updated only
89
- * when every selected account in the response is ready (none report
90
- * `processingDefiPositions`); each account key in that response replaces that
91
- * account's state (other accounts stay). While any account is still indexing,
92
- * prior state is kept and the method polls (invalidating the balances cache
93
- * between attempts) until all selected accounts are ready, the attempt limit
94
- * is reached, or a request fails. Concurrent calls for the same selected
95
- * accounts and `vsCurrency` share one in-flight promise; calls for a different
96
- * selection or fiat currency start a new fetch and leave prior polls running
97
- * so a later switch back can join them. When a successful ready response
98
- * required more than one attempt, or when polling hits the attempt limit
99
- * while still processing, reports to Sentry via `messenger.captureException`
100
- * (error names `DeFiPositionsV2FetchAttempts` /
89
+ * Fetches DeFi positions for the selected account group. State is updated
90
+ * only when the response lists no accounts in its `processingDefiPositions`
91
+ * array; each resolved account then has its state replaced (other accounts
92
+ * stay). While any account is still indexing, prior state is kept and the
93
+ * method polls (invalidating the balances cache between attempts) until no
94
+ * accounts are processing, the attempt limit is reached, or a request fails.
95
+ * Concurrent calls for the same selected accounts and `vsCurrency` share one
96
+ * in-flight promise; calls for a different selection or fiat currency start
97
+ * a new fetch and leave prior polls running so a later switch back can join
98
+ * them. When a successful ready response required more than one attempt, or
99
+ * when polling hits the attempt limit while still processing, reports to
100
+ * Sentry via `messenger.captureException` (error names
101
+ * `DeFiPositionsV2FetchAttempts` /
101
102
  * `DeFiPositionsV2ProcessingPollExhausted`) so poll limits can be tuned.
102
103
  * No-ops when disabled or when the group has no
103
104
  * supported accounts. Caching / spam prevention is handled by the apiClient
@@ -1 +1 @@
1
- {"version":3,"file":"DeFiPositionsControllerV2.d.cts","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8DAA8D,EAAE,0CAA0C;AACxH,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAE5B,kCAAkC;AACnC,OAAO,KAAK,EAAE,iBAAiB,EAAE,+BAA+B;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EAAE,yCAAyC,EAAE,iDAAiD;AAK1G,OAAO,KAAK,EAAE,sCAAsC,EAAE,4DAA2D;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,sCAAqC;AAG3E,QAAA,MAAM,cAAc,8BAA8B,CAAC;AAYnD,MAAM,MAAM,8BAA8B,GAAG;IAC3C;;;;;;;;;;;;OAYG;IACH,kBAAkB,EAAE,sBAAsB,CAAC;CAC5C,CAAC;AAWF,eAAO,MAAM,wCAAwC,QAC/C,8BAIH,CAAC;AAEJ,MAAM,MAAM,uCAAuC,GAAG,wBAAwB,CAC5E,OAAO,cAAc,EACrB,8BAA8B,CAC/B,CAAC;AAEF,MAAM,MAAM,gCAAgC,GACxC,uCAAuC,GACvC,sCAAsC,CAAC;AAE3C,MAAM,MAAM,0CAA0C,GACpD,2BAA2B,CACzB,OAAO,cAAc,EACrB,8BAA8B,CAC/B,CAAC;AAEJ,MAAM,MAAM,+BAA+B,GACzC,0CAA0C,CAAC;AAE7C;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,8DAA8D,GAC9D,yCAAyC,CAAC;AAE9C;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,KAAK,CAAC;AAElC,MAAM,MAAM,kCAAkC,GAAG,SAAS,CACxD,OAAO,cAAc,EACrB,gCAAgC,GAAG,cAAc,EACjD,+BAA+B,GAAG,aAAa,CAChD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,yBAA0B,SAAQ,cAAc,CAC3D,OAAO,cAAc,EACrB,8BAA8B,EAC9B,kCAAkC,CACnC;;IAeC;;;;;;;OAOG;gBACS,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,aAAa,EACb,KAAK,GACN,EAAE;QACD,SAAS,EAAE,kCAAkC,CAAC;QAC9C,SAAS,EAAE,iBAAiB,CAAC;QAC7B,SAAS,EAAE,MAAM,OAAO,CAAC;QACzB,aAAa,EAAE,MAAM,MAAM,CAAC;QAC5B,KAAK,CAAC,EAAE,OAAO,CAAC,8BAA8B,CAAC,CAAC;KACjD;IAqBD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACG,kBAAkB,CAAC,OAAO,CAAC,EAAE;QACjC,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC,IAAI,CAAC;CA6IlB"}
1
+ {"version":3,"file":"DeFiPositionsControllerV2.d.cts","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8DAA8D,EAAE,0CAA0C;AACxH,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAE5B,kCAAkC;AACnC,OAAO,KAAK,EAAE,iBAAiB,EAAE,+BAA+B;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EAAE,yCAAyC,EAAE,iDAAiD;AAK1G,OAAO,KAAK,EAAE,sCAAsC,EAAE,4DAA2D;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,sCAAqC;AAG3E,QAAA,MAAM,cAAc,8BAA8B,CAAC;AAYnD,MAAM,MAAM,8BAA8B,GAAG;IAC3C;;;;;;;;;;;;OAYG;IACH,kBAAkB,EAAE,sBAAsB,CAAC;CAC5C,CAAC;AAWF,eAAO,MAAM,wCAAwC,QAC/C,8BAIH,CAAC;AAEJ,MAAM,MAAM,uCAAuC,GAAG,wBAAwB,CAC5E,OAAO,cAAc,EACrB,8BAA8B,CAC/B,CAAC;AAEF,MAAM,MAAM,gCAAgC,GACxC,uCAAuC,GACvC,sCAAsC,CAAC;AAE3C,MAAM,MAAM,0CAA0C,GACpD,2BAA2B,CACzB,OAAO,cAAc,EACrB,8BAA8B,CAC/B,CAAC;AAEJ,MAAM,MAAM,+BAA+B,GACzC,0CAA0C,CAAC;AAE7C;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,8DAA8D,GAC9D,yCAAyC,CAAC;AAE9C;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,KAAK,CAAC;AAElC,MAAM,MAAM,kCAAkC,GAAG,SAAS,CACxD,OAAO,cAAc,EACrB,gCAAgC,GAAG,cAAc,EACjD,+BAA+B,GAAG,aAAa,CAChD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,yBAA0B,SAAQ,cAAc,CAC3D,OAAO,cAAc,EACrB,8BAA8B,EAC9B,kCAAkC,CACnC;;IAeC;;;;;;;OAOG;gBACS,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,aAAa,EACb,KAAK,GACN,EAAE;QACD,SAAS,EAAE,kCAAkC,CAAC;QAC9C,SAAS,EAAE,iBAAiB,CAAC;QAC7B,SAAS,EAAE,MAAM,OAAO,CAAC;QACzB,aAAa,EAAE,MAAM,MAAM,CAAC;QAC5B,KAAK,CAAC,EAAE,OAAO,CAAC,8BAA8B,CAAC,CAAC;KACjD;IAqBD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACG,kBAAkB,CAAC,OAAO,CAAC,EAAE;QACjC,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC,IAAI,CAAC;CA4IlB"}
@@ -52,14 +52,15 @@ export type DeFiPositionsControllerV2Messenger = Messenger<typeof controllerName
52
52
  * `{ forceRefresh: true }` to bypass that cache on the first attempt (e.g.
53
53
  * pull-to-refresh); later processing polls always bypass the cache.
54
54
  *
55
- * When the API reports `processingDefiPositions` for any account, this
56
- * controller polls until indexing finishes or the attempt limit is reached,
57
- * and only then processes and writes state — mixed ready/processing responses
58
- * leave prior state untouched. Concurrent calls for the same selected accounts
59
- * and `vsCurrency` share one in-flight promise so the UI can treat the pending
60
- * promise as a loading signal. Calls for a different selection or fiat currency
61
- * start their own fetch and leave any prior poll running, so switching back can
62
- * join an in-flight fetch for that group and currency.
55
+ * When the API reports account IDs in the response-level
56
+ * `processingDefiPositions` array, this controller polls until indexing
57
+ * finishes or the attempt limit is reached, and only then processes and writes
58
+ * state — responses that still list processing accounts leave prior state
59
+ * untouched. Concurrent calls for the same selected accounts and `vsCurrency`
60
+ * share one in-flight promise so the UI can treat the pending promise as a
61
+ * loading signal. Calls for a different selection or fiat currency start their
62
+ * own fetch and leave any prior poll running, so switching back can join an
63
+ * in-flight fetch for that group and currency.
63
64
  *
64
65
  * Processing-poll `maxAttempts` / `pollInterval` are read via
65
66
  * {@link getProcessingPollConfig} from the `defiControllerV2` remote feature
@@ -85,19 +86,19 @@ export declare class DeFiPositionsControllerV2 extends BaseController<typeof con
85
86
  state?: Partial<DeFiPositionsControllerV2State>;
86
87
  });
87
88
  /**
88
- * Fetches DeFi positions for the selected account group. State is updated only
89
- * when every selected account in the response is ready (none report
90
- * `processingDefiPositions`); each account key in that response replaces that
91
- * account's state (other accounts stay). While any account is still indexing,
92
- * prior state is kept and the method polls (invalidating the balances cache
93
- * between attempts) until all selected accounts are ready, the attempt limit
94
- * is reached, or a request fails. Concurrent calls for the same selected
95
- * accounts and `vsCurrency` share one in-flight promise; calls for a different
96
- * selection or fiat currency start a new fetch and leave prior polls running
97
- * so a later switch back can join them. When a successful ready response
98
- * required more than one attempt, or when polling hits the attempt limit
99
- * while still processing, reports to Sentry via `messenger.captureException`
100
- * (error names `DeFiPositionsV2FetchAttempts` /
89
+ * Fetches DeFi positions for the selected account group. State is updated
90
+ * only when the response lists no accounts in its `processingDefiPositions`
91
+ * array; each resolved account then has its state replaced (other accounts
92
+ * stay). While any account is still indexing, prior state is kept and the
93
+ * method polls (invalidating the balances cache between attempts) until no
94
+ * accounts are processing, the attempt limit is reached, or a request fails.
95
+ * Concurrent calls for the same selected accounts and `vsCurrency` share one
96
+ * in-flight promise; calls for a different selection or fiat currency start
97
+ * a new fetch and leave prior polls running so a later switch back can join
98
+ * them. When a successful ready response required more than one attempt, or
99
+ * when polling hits the attempt limit while still processing, reports to
100
+ * Sentry via `messenger.captureException` (error names
101
+ * `DeFiPositionsV2FetchAttempts` /
101
102
  * `DeFiPositionsV2ProcessingPollExhausted`) so poll limits can be tuned.
102
103
  * No-ops when disabled or when the group has no
103
104
  * supported accounts. Caching / spam prevention is handled by the apiClient
@@ -1 +1 @@
1
- {"version":3,"file":"DeFiPositionsControllerV2.d.mts","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8DAA8D,EAAE,0CAA0C;AACxH,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAE5B,kCAAkC;AACnC,OAAO,KAAK,EAAE,iBAAiB,EAAE,+BAA+B;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EAAE,yCAAyC,EAAE,iDAAiD;AAK1G,OAAO,KAAK,EAAE,sCAAsC,EAAE,4DAA2D;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,sCAAqC;AAG3E,QAAA,MAAM,cAAc,8BAA8B,CAAC;AAYnD,MAAM,MAAM,8BAA8B,GAAG;IAC3C;;;;;;;;;;;;OAYG;IACH,kBAAkB,EAAE,sBAAsB,CAAC;CAC5C,CAAC;AAWF,eAAO,MAAM,wCAAwC,QAC/C,8BAIH,CAAC;AAEJ,MAAM,MAAM,uCAAuC,GAAG,wBAAwB,CAC5E,OAAO,cAAc,EACrB,8BAA8B,CAC/B,CAAC;AAEF,MAAM,MAAM,gCAAgC,GACxC,uCAAuC,GACvC,sCAAsC,CAAC;AAE3C,MAAM,MAAM,0CAA0C,GACpD,2BAA2B,CACzB,OAAO,cAAc,EACrB,8BAA8B,CAC/B,CAAC;AAEJ,MAAM,MAAM,+BAA+B,GACzC,0CAA0C,CAAC;AAE7C;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,8DAA8D,GAC9D,yCAAyC,CAAC;AAE9C;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,KAAK,CAAC;AAElC,MAAM,MAAM,kCAAkC,GAAG,SAAS,CACxD,OAAO,cAAc,EACrB,gCAAgC,GAAG,cAAc,EACjD,+BAA+B,GAAG,aAAa,CAChD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,yBAA0B,SAAQ,cAAc,CAC3D,OAAO,cAAc,EACrB,8BAA8B,EAC9B,kCAAkC,CACnC;;IAeC;;;;;;;OAOG;gBACS,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,aAAa,EACb,KAAK,GACN,EAAE;QACD,SAAS,EAAE,kCAAkC,CAAC;QAC9C,SAAS,EAAE,iBAAiB,CAAC;QAC7B,SAAS,EAAE,MAAM,OAAO,CAAC;QACzB,aAAa,EAAE,MAAM,MAAM,CAAC;QAC5B,KAAK,CAAC,EAAE,OAAO,CAAC,8BAA8B,CAAC,CAAC;KACjD;IAqBD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACG,kBAAkB,CAAC,OAAO,CAAC,EAAE;QACjC,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC,IAAI,CAAC;CA6IlB"}
1
+ {"version":3,"file":"DeFiPositionsControllerV2.d.mts","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8DAA8D,EAAE,0CAA0C;AACxH,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAE5B,kCAAkC;AACnC,OAAO,KAAK,EAAE,iBAAiB,EAAE,+BAA+B;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EAAE,yCAAyC,EAAE,iDAAiD;AAK1G,OAAO,KAAK,EAAE,sCAAsC,EAAE,4DAA2D;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,sCAAqC;AAG3E,QAAA,MAAM,cAAc,8BAA8B,CAAC;AAYnD,MAAM,MAAM,8BAA8B,GAAG;IAC3C;;;;;;;;;;;;OAYG;IACH,kBAAkB,EAAE,sBAAsB,CAAC;CAC5C,CAAC;AAWF,eAAO,MAAM,wCAAwC,QAC/C,8BAIH,CAAC;AAEJ,MAAM,MAAM,uCAAuC,GAAG,wBAAwB,CAC5E,OAAO,cAAc,EACrB,8BAA8B,CAC/B,CAAC;AAEF,MAAM,MAAM,gCAAgC,GACxC,uCAAuC,GACvC,sCAAsC,CAAC;AAE3C,MAAM,MAAM,0CAA0C,GACpD,2BAA2B,CACzB,OAAO,cAAc,EACrB,8BAA8B,CAC/B,CAAC;AAEJ,MAAM,MAAM,+BAA+B,GACzC,0CAA0C,CAAC;AAE7C;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,8DAA8D,GAC9D,yCAAyC,CAAC;AAE9C;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,KAAK,CAAC;AAElC,MAAM,MAAM,kCAAkC,GAAG,SAAS,CACxD,OAAO,cAAc,EACrB,gCAAgC,GAAG,cAAc,EACjD,+BAA+B,GAAG,aAAa,CAChD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,yBAA0B,SAAQ,cAAc,CAC3D,OAAO,cAAc,EACrB,8BAA8B,EAC9B,kCAAkC,CACnC;;IAeC;;;;;;;OAOG;gBACS,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,aAAa,EACb,KAAK,GACN,EAAE;QACD,SAAS,EAAE,kCAAkC,CAAC;QAC9C,SAAS,EAAE,iBAAiB,CAAC;QAC7B,SAAS,EAAE,MAAM,OAAO,CAAC;QACzB,aAAa,EAAE,MAAM,MAAM,CAAC;QAC5B,KAAK,CAAC,EAAE,OAAO,CAAC,8BAA8B,CAAC,CAAC;KACjD;IAqBD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACG,kBAAkB,CAAC,OAAO,CAAC,EAAE;QACjC,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC,IAAI,CAAC;CA4IlB"}
@@ -46,14 +46,15 @@ export const getDefaultDeFiPositionsControllerV2State = () => {
46
46
  * `{ forceRefresh: true }` to bypass that cache on the first attempt (e.g.
47
47
  * pull-to-refresh); later processing polls always bypass the cache.
48
48
  *
49
- * When the API reports `processingDefiPositions` for any account, this
50
- * controller polls until indexing finishes or the attempt limit is reached,
51
- * and only then processes and writes state — mixed ready/processing responses
52
- * leave prior state untouched. Concurrent calls for the same selected accounts
53
- * and `vsCurrency` share one in-flight promise so the UI can treat the pending
54
- * promise as a loading signal. Calls for a different selection or fiat currency
55
- * start their own fetch and leave any prior poll running, so switching back can
56
- * join an in-flight fetch for that group and currency.
49
+ * When the API reports account IDs in the response-level
50
+ * `processingDefiPositions` array, this controller polls until indexing
51
+ * finishes or the attempt limit is reached, and only then processes and writes
52
+ * state — responses that still list processing accounts leave prior state
53
+ * untouched. Concurrent calls for the same selected accounts and `vsCurrency`
54
+ * share one in-flight promise so the UI can treat the pending promise as a
55
+ * loading signal. Calls for a different selection or fiat currency start their
56
+ * own fetch and leave any prior poll running, so switching back can join an
57
+ * in-flight fetch for that group and currency.
57
58
  *
58
59
  * Processing-poll `maxAttempts` / `pollInterval` are read via
59
60
  * {@link getProcessingPollConfig} from the `defiControllerV2` remote feature
@@ -97,19 +98,19 @@ export class DeFiPositionsControllerV2 extends BaseController {
97
98
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
98
99
  }
99
100
  /**
100
- * Fetches DeFi positions for the selected account group. State is updated only
101
- * when every selected account in the response is ready (none report
102
- * `processingDefiPositions`); each account key in that response replaces that
103
- * account's state (other accounts stay). While any account is still indexing,
104
- * prior state is kept and the method polls (invalidating the balances cache
105
- * between attempts) until all selected accounts are ready, the attempt limit
106
- * is reached, or a request fails. Concurrent calls for the same selected
107
- * accounts and `vsCurrency` share one in-flight promise; calls for a different
108
- * selection or fiat currency start a new fetch and leave prior polls running
109
- * so a later switch back can join them. When a successful ready response
110
- * required more than one attempt, or when polling hits the attempt limit
111
- * while still processing, reports to Sentry via `messenger.captureException`
112
- * (error names `DeFiPositionsV2FetchAttempts` /
101
+ * Fetches DeFi positions for the selected account group. State is updated
102
+ * only when the response lists no accounts in its `processingDefiPositions`
103
+ * array; each resolved account then has its state replaced (other accounts
104
+ * stay). While any account is still indexing, prior state is kept and the
105
+ * method polls (invalidating the balances cache between attempts) until no
106
+ * accounts are processing, the attempt limit is reached, or a request fails.
107
+ * Concurrent calls for the same selected accounts and `vsCurrency` share one
108
+ * in-flight promise; calls for a different selection or fiat currency start
109
+ * a new fetch and leave prior polls running so a later switch back can join
110
+ * them. When a successful ready response required more than one attempt, or
111
+ * when polling hits the attempt limit while still processing, reports to
112
+ * Sentry via `messenger.captureException` (error names
113
+ * `DeFiPositionsV2FetchAttempts` /
113
114
  * `DeFiPositionsV2ProcessingPollExhausted`) so poll limits can be tuned.
114
115
  * No-ops when disabled or when the group has no
115
116
  * supported accounts. Caching / spam prevention is handled by the apiClient
@@ -179,10 +180,10 @@ _DeFiPositionsControllerV2_apiClient = new WeakMap(), _DeFiPositionsControllerV2
179
180
  console.error('Failed to fetch DeFi positions', error);
180
181
  return;
181
182
  }
182
- const stillProcessing = response.accounts.some((account) => account.processingDefiPositions);
183
- // Only process and write when every account is ready so a partial
184
- // response cannot clear or overwrite positions for accounts that are
185
- // still indexing.
183
+ const stillProcessing = (response.processingDefiPositions?.length ?? 0) > 0;
184
+ // Only process and write when no account is still indexing, so a
185
+ // partial response (whose DeFi rows for processing accounts are
186
+ // omitted) cannot clear or overwrite positions for those accounts.
186
187
  if (!stillProcessing) {
187
188
  const positionsByAccount = groupDeFiPositionsV6(response, internalAccountIdByCaip);
188
189
  this.update((state) => {