@metamask-previews/assets-controllers 110.0.2-preview-f48a87ac7 → 110.0.2-preview-d77c814bb

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.
package/CHANGELOG.md CHANGED
@@ -9,11 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Changed
11
11
 
12
- - `DeFiPositionsControllerV2.fetchDeFiPositions` now polls while any selected
13
- account has `processingDefiPositions: true`, applying ready accounts
14
- immediately, invalidating the balances cache between attempts, sharing one
15
- in-flight promise per selected-account key (so fast switches can join an
16
- earlier poll), and stopping on request failure or the max attempt limit
12
+ - `DeFiPositionsControllerV2.fetchDeFiPositions` now polls while any selected account has `processingDefiPositions: true`, applying ready accounts immediately, invalidating the balances cache between attempts, sharing one in-flight promise per selected-account + `vsCurrency` key (so fast switches can join an earlier matching poll), and stopping on request failure or the max attempt limit ([#9711](https://github.com/MetaMask/core/pull/9711))
17
13
 
18
14
  ## [110.0.2]
19
15
 
@@ -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. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`) keep prior state; the\n * method polls (invalidating the balances cache between attempts) until all\n * selected accounts are ready, the attempt limit is reached, or a request\n * fails. Concurrent calls for the same selected accounts share one in-flight\n * promise; calls for a different selection start a new fetch and leave prior\n * polls running so a later switch back can join them. No-ops when disabled\n * or when the group has no supported accounts. Caching / spam prevention is\n * handled by the apiClient TanStack Query cache (keyed by accounts + query\n * options including `vsCurrency`). Pass `{ forceRefresh: true }` to bypass\n * the cache on the 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. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`) keep prior state; the\n * method polls (invalidating the balances cache between attempts) until all\n * selected accounts are ready, the attempt limit is reached, or a request\n * fails. Concurrent calls for the same selected accounts and `vsCurrency`\n * share one in-flight promise; calls for a different selection or fiat\n * currency start a new fetch and leave prior polls running so a later\n * switch back can join them. 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"]}
@@ -9,13 +9,14 @@ import type { DeFiPositionsControllerV2 } from "./DeFiPositionsControllerV2.cjs"
9
9
  * Accounts still indexing (`processingDefiPositions`) keep prior state; the
10
10
  * method polls (invalidating the balances cache between attempts) until all
11
11
  * selected accounts are ready, the attempt limit is reached, or a request
12
- * fails. Concurrent calls for the same selected accounts share one in-flight
13
- * promise; calls for a different selection start a new fetch and leave prior
14
- * polls running so a later switch back can join them. No-ops when disabled
15
- * or when the group has no supported accounts. Caching / spam prevention is
16
- * handled by the apiClient TanStack Query cache (keyed by accounts + query
17
- * options including `vsCurrency`). Pass `{ forceRefresh: true }` to bypass
18
- * the cache on the first attempt (e.g. pull-to-refresh).
12
+ * fails. Concurrent calls for the same selected accounts and `vsCurrency`
13
+ * share one in-flight promise; calls for a different selection or fiat
14
+ * currency start a new fetch and leave prior polls running so a later
15
+ * switch back can join them. No-ops when disabled or when the group has no
16
+ * supported accounts. Caching / spam prevention is handled by the apiClient
17
+ * TanStack Query cache (keyed by accounts + query options including
18
+ * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the
19
+ * first attempt (e.g. pull-to-refresh).
19
20
  *
20
21
  * @param options - Optional fetch modifiers.
21
22
  * @param options.forceRefresh - When true, bypass the apiClient cache on the
@@ -1 +1 @@
1
- {"version":3,"file":"DeFiPositionsControllerV2-method-action-types.d.cts","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,yBAAyB,EAAE,wCAAuC;AAEhF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,iDAAiD,GAAG;IAC9D,IAAI,EAAE,8CAA8C,CAAC;IACrD,OAAO,EAAE,yBAAyB,CAAC,oBAAoB,CAAC,CAAC;CAC1D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAChD,iDAAiD,CAAC"}
1
+ {"version":3,"file":"DeFiPositionsControllerV2-method-action-types.d.cts","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,yBAAyB,EAAE,wCAAuC;AAEhF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,iDAAiD,GAAG;IAC9D,IAAI,EAAE,8CAA8C,CAAC;IACrD,OAAO,EAAE,yBAAyB,CAAC,oBAAoB,CAAC,CAAC;CAC1D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAChD,iDAAiD,CAAC"}
@@ -9,13 +9,14 @@ import type { DeFiPositionsControllerV2 } from "./DeFiPositionsControllerV2.mjs"
9
9
  * Accounts still indexing (`processingDefiPositions`) keep prior state; the
10
10
  * method polls (invalidating the balances cache between attempts) until all
11
11
  * selected accounts are ready, the attempt limit is reached, or a request
12
- * fails. Concurrent calls for the same selected accounts share one in-flight
13
- * promise; calls for a different selection start a new fetch and leave prior
14
- * polls running so a later switch back can join them. No-ops when disabled
15
- * or when the group has no supported accounts. Caching / spam prevention is
16
- * handled by the apiClient TanStack Query cache (keyed by accounts + query
17
- * options including `vsCurrency`). Pass `{ forceRefresh: true }` to bypass
18
- * the cache on the first attempt (e.g. pull-to-refresh).
12
+ * fails. Concurrent calls for the same selected accounts and `vsCurrency`
13
+ * share one in-flight promise; calls for a different selection or fiat
14
+ * currency start a new fetch and leave prior polls running so a later
15
+ * switch back can join them. No-ops when disabled or when the group has no
16
+ * supported accounts. Caching / spam prevention is handled by the apiClient
17
+ * TanStack Query cache (keyed by accounts + query options including
18
+ * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the
19
+ * first attempt (e.g. pull-to-refresh).
19
20
  *
20
21
  * @param options - Optional fetch modifiers.
21
22
  * @param options.forceRefresh - When true, bypass the apiClient cache on the
@@ -1 +1 @@
1
- {"version":3,"file":"DeFiPositionsControllerV2-method-action-types.d.mts","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,yBAAyB,EAAE,wCAAuC;AAEhF;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,iDAAiD,GAAG;IAC9D,IAAI,EAAE,8CAA8C,CAAC;IACrD,OAAO,EAAE,yBAAyB,CAAC,oBAAoB,CAAC,CAAC;CAC1D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAChD,iDAAiD,CAAC"}
1
+ {"version":3,"file":"DeFiPositionsControllerV2-method-action-types.d.mts","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,yBAAyB,EAAE,wCAAuC;AAEhF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,iDAAiD,GAAG;IAC9D,IAAI,EAAE,8CAA8C,CAAC;IACrD,OAAO,EAAE,yBAAyB,CAAC,oBAAoB,CAAC,CAAC;CAC1D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAChD,iDAAiD,CAAC"}
@@ -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. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`) keep prior state; the\n * method polls (invalidating the balances cache between attempts) until all\n * selected accounts are ready, the attempt limit is reached, or a request\n * fails. Concurrent calls for the same selected accounts share one in-flight\n * promise; calls for a different selection start a new fetch and leave prior\n * polls running so a later switch back can join them. No-ops when disabled\n * or when the group has no supported accounts. Caching / spam prevention is\n * handled by the apiClient TanStack Query cache (keyed by accounts + query\n * options including `vsCurrency`). Pass `{ forceRefresh: true }` to bypass\n * the cache on the 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. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`) keep prior state; the\n * method polls (invalidating the balances cache between attempts) until all\n * selected accounts are ready, the attempt limit is reached, or a request\n * fails. Concurrent calls for the same selected accounts and `vsCurrency`\n * share one in-flight promise; calls for a different selection or fiat\n * currency start a new fetch and leave prior polls running so a later\n * switch back can join them. 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"]}
@@ -58,10 +58,11 @@ exports.getDefaultDeFiPositionsControllerV2State = getDefaultDeFiPositionsContro
58
58
  *
59
59
  * When the API reports `processingDefiPositions` for any account, this
60
60
  * controller polls until indexing finishes or the attempt limit is reached.
61
- * Concurrent calls for the same selected accounts share one in-flight promise
62
- * so the UI can treat the pending promise as a loading signal. Calls for a
63
- * different selection start their own fetch and leave any prior poll running,
64
- * so switching back can join an in-flight fetch for that group.
61
+ * Concurrent calls for the same selected accounts and `vsCurrency` share one
62
+ * in-flight promise so the UI can treat the pending promise as a loading
63
+ * signal. Calls for a different selection or fiat currency start their own
64
+ * fetch and leave any prior poll running, so switching back can join an
65
+ * in-flight fetch for that group and currency.
65
66
  */
66
67
  class DeFiPositionsControllerV2 extends base_controller_1.BaseController {
67
68
  /**
@@ -87,9 +88,10 @@ class DeFiPositionsControllerV2 extends base_controller_1.BaseController {
87
88
  _DeFiPositionsControllerV2_isEnabled.set(this, void 0);
88
89
  _DeFiPositionsControllerV2_getVsCurrency.set(this, void 0);
89
90
  /**
90
- * In-flight fetches keyed by selected DeFi-queryable account IDs. Concurrent
91
- * callers for the same selection share a promise; different selections keep
92
- * independent fetches so fast account switching can join an earlier poll.
91
+ * In-flight fetches keyed by selected DeFi-queryable account IDs plus
92
+ * `vsCurrency`. Concurrent callers for the same selection and currency share
93
+ * a promise; different selections or fiat currencies keep independent
94
+ * fetches so fast switching can join an earlier matching poll.
93
95
  */
94
96
  _DeFiPositionsControllerV2_inFlightFetches.set(this, new Map());
95
97
  __classPrivateFieldSet(this, _DeFiPositionsControllerV2_apiClient, apiClient, "f");
@@ -103,13 +105,14 @@ class DeFiPositionsControllerV2 extends base_controller_1.BaseController {
103
105
  * Accounts still indexing (`processingDefiPositions`) keep prior state; the
104
106
  * method polls (invalidating the balances cache between attempts) until all
105
107
  * selected accounts are ready, the attempt limit is reached, or a request
106
- * fails. Concurrent calls for the same selected accounts share one in-flight
107
- * promise; calls for a different selection start a new fetch and leave prior
108
- * polls running so a later switch back can join them. No-ops when disabled
109
- * or when the group has no supported accounts. Caching / spam prevention is
110
- * handled by the apiClient TanStack Query cache (keyed by accounts + query
111
- * options including `vsCurrency`). Pass `{ forceRefresh: true }` to bypass
112
- * the cache on the first attempt (e.g. pull-to-refresh).
108
+ * fails. Concurrent calls for the same selected accounts and `vsCurrency`
109
+ * share one in-flight promise; calls for a different selection or fiat
110
+ * currency start a new fetch and leave prior polls running so a later
111
+ * switch back can join them. No-ops when disabled or when the group has no
112
+ * supported accounts. Caching / spam prevention is handled by the apiClient
113
+ * TanStack Query cache (keyed by accounts + query options including
114
+ * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the
115
+ * first attempt (e.g. pull-to-refresh).
113
116
  *
114
117
  * @param options - Optional fetch modifiers.
115
118
  * @param options.forceRefresh - When true, bypass the apiClient cache on the
@@ -122,30 +125,33 @@ class DeFiPositionsControllerV2 extends base_controller_1.BaseController {
122
125
  }
123
126
  const selectedAccounts = this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
124
127
  const query = (0, build_defi_balances_query_js_1.buildDeFiBalancesQuery)(selectedAccounts);
125
- const accountIdsKey = [...query.internalAccountIdByCaip.keys()]
126
- .sort()
127
- .join('\0');
128
- const existing = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").get(accountIdsKey);
128
+ const vsCurrency = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_getVsCurrency, "f").call(this).toLowerCase();
129
+ // Include vsCurrency so a fiat change does not join a poll priced in the
130
+ // previous currency (TanStack also keys the balances cache on vsCurrency).
131
+ const inFlightKey = [
132
+ ...[...query.internalAccountIdByCaip.keys()].sort(),
133
+ vsCurrency,
134
+ ].join('\0');
135
+ const existing = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").get(inFlightKey);
129
136
  if (existing) {
130
137
  await existing;
131
138
  return;
132
139
  }
133
- const fetchPromise = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_instances, "m", _DeFiPositionsControllerV2_fetchDeFiPositions).call(this, options, query).finally(() => {
134
- if (__classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").get(accountIdsKey) === fetchPromise) {
135
- __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").delete(accountIdsKey);
140
+ const fetchPromise = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_instances, "m", _DeFiPositionsControllerV2_fetchDeFiPositions).call(this, options, query, vsCurrency).finally(() => {
141
+ if (__classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").get(inFlightKey) === fetchPromise) {
142
+ __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").delete(inFlightKey);
136
143
  }
137
144
  });
138
- __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").set(accountIdsKey, fetchPromise);
145
+ __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").set(inFlightKey, fetchPromise);
139
146
  await fetchPromise;
140
147
  }
141
148
  }
142
149
  exports.DeFiPositionsControllerV2 = DeFiPositionsControllerV2;
143
- _DeFiPositionsControllerV2_apiClient = new WeakMap(), _DeFiPositionsControllerV2_isEnabled = new WeakMap(), _DeFiPositionsControllerV2_getVsCurrency = new WeakMap(), _DeFiPositionsControllerV2_inFlightFetches = new WeakMap(), _DeFiPositionsControllerV2_instances = new WeakSet(), _DeFiPositionsControllerV2_fetchDeFiPositions = async function _DeFiPositionsControllerV2_fetchDeFiPositions(options, { networks, internalAccountIdByCaip }) {
150
+ _DeFiPositionsControllerV2_apiClient = new WeakMap(), _DeFiPositionsControllerV2_isEnabled = new WeakMap(), _DeFiPositionsControllerV2_getVsCurrency = new WeakMap(), _DeFiPositionsControllerV2_inFlightFetches = new WeakMap(), _DeFiPositionsControllerV2_instances = new WeakSet(), _DeFiPositionsControllerV2_fetchDeFiPositions = async function _DeFiPositionsControllerV2_fetchDeFiPositions(options, { networks, internalAccountIdByCaip }, vsCurrency) {
144
151
  if (internalAccountIdByCaip.size === 0 || networks.length === 0) {
145
152
  return;
146
153
  }
147
154
  const accountIds = [...internalAccountIdByCaip.keys()];
148
- const vsCurrency = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_getVsCurrency, "f").call(this).toLowerCase();
149
155
  const queryOptions = {
150
156
  networks,
151
157
  includeDeFiBalances: true,
@@ -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';\n\nimport { buildDeFiBalancesQuery } from './build-defi-balances-query.js';\nimport type { DeFiBalancesQuery } from './build-defi-balances-query.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/** Delay between polls while Accounts API reports DeFi indexing in progress. */\nexport const DEFAULT_PROCESSING_POLL_INTERVAL_MS = 5_000;\n\n/**\n * Maximum fetch attempts (including the first) while any account still has\n * `processingDefiPositions: true`. After this, the call resolves and keeps\n * prior state for accounts that never became ready.\n */\nexport const DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS = 5;\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\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 for pull-to-refresh.\n *\n * When the API reports `processingDefiPositions` for any account, this\n * controller polls until indexing finishes or the attempt limit is reached.\n * Concurrent calls for the same selected accounts share one in-flight promise\n * so the UI can treat the pending promise as a loading signal. Calls for a\n * different selection start their own fetch and leave any prior poll running,\n * so switching back can join an in-flight fetch for that group.\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. Concurrent\n * callers for the same selection share a promise; different selections keep\n * independent fetches so fast account switching can join an earlier 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. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`) keep prior state; the\n * method polls (invalidating the balances cache between attempts) until all\n * selected accounts are ready, the attempt limit is reached, or a request\n * fails. Concurrent calls for the same selected accounts share one in-flight\n * promise; calls for a different selection start a new fetch and leave prior\n * polls running so a later switch back can join them. No-ops when disabled\n * or when the group has no supported accounts. Caching / spam prevention is\n * handled by the apiClient TanStack Query cache (keyed by accounts + query\n * options including `vsCurrency`). Pass `{ forceRefresh: true }` to bypass\n * the cache on the 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 accountIdsKey = [...query.internalAccountIdByCaip.keys()]\n .sort()\n .join('\\0');\n\n const existing = this.#inFlightFetches.get(accountIdsKey);\n if (existing) {\n await existing;\n return;\n }\n\n const fetchPromise = this.#fetchDeFiPositions(options, query).finally(\n () => {\n if (this.#inFlightFetches.get(accountIdsKey) === fetchPromise) {\n this.#inFlightFetches.delete(accountIdsKey);\n }\n },\n );\n this.#inFlightFetches.set(accountIdsKey, fetchPromise);\n\n await fetchPromise;\n }\n\n async #fetchDeFiPositions(\n options: { forceRefresh?: boolean } | undefined,\n { networks, internalAccountIdByCaip }: DeFiBalancesQuery,\n ): Promise<void> {\n if (internalAccountIdByCaip.size === 0 || networks.length === 0) {\n return;\n }\n\n const accountIds = [...internalAccountIdByCaip.keys()];\n const vsCurrency = this.#getVsCurrency().toLowerCase();\n const queryOptions = {\n networks,\n includeDeFiBalances: true,\n forceFetchDeFiPositions: true,\n includePrices: true,\n vsCurrency,\n };\n\n for (\n let attempt = 0;\n attempt < DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS;\n attempt++\n ) {\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 try {\n const response =\n await this.#apiClient.accounts.fetchV6MultiAccountBalances(\n accountIds,\n queryOptions,\n fetchOptions,\n );\n\n const processingAccounts = response.accounts.filter(\n (account) => account.processingDefiPositions,\n );\n const readyAccounts = response.accounts.filter(\n (account) => !account.processingDefiPositions,\n );\n\n if (readyAccounts.length > 0) {\n const positionsByAccount = groupDeFiPositionsV6(\n { ...response, accounts: readyAccounts },\n internalAccountIdByCaip,\n );\n\n // Last valid response wins per ready account; still-indexing accounts\n // stay untouched.\n this.update((state) => {\n for (const [accountId, positions] of Object.entries(\n positionsByAccount,\n )) {\n state.allDeFiPositionsV2[accountId] = positions;\n }\n });\n }\n\n if (processingAccounts.length === 0) {\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 =\n attempt >= DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS - 1;\n if (isLastAttempt) {\n return;\n }\n\n await delay(DEFAULT_PROCESSING_POLL_INTERVAL_MS);\n } catch (error) {\n console.error('Failed to fetch DeFi positions', error);\n return;\n }\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';\n\nimport { buildDeFiBalancesQuery } from './build-defi-balances-query.js';\nimport type { DeFiBalancesQuery } from './build-defi-balances-query.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/** Delay between polls while Accounts API reports DeFi indexing in progress. */\nexport const DEFAULT_PROCESSING_POLL_INTERVAL_MS = 5_000;\n\n/**\n * Maximum fetch attempts (including the first) while any account still has\n * `processingDefiPositions: true`. After this, the call resolves and keeps\n * prior state for accounts that never became ready.\n */\nexport const DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS = 5;\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\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 for pull-to-refresh.\n *\n * When the API reports `processingDefiPositions` for any account, this\n * controller polls until indexing finishes or the attempt limit is reached.\n * Concurrent calls for the same selected accounts and `vsCurrency` share one\n * in-flight promise so the UI can treat the pending promise as a loading\n * signal. Calls for a different selection or fiat currency start their own\n * fetch and leave any prior poll running, so switching back can join an\n * in-flight fetch for that group and currency.\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. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`) keep prior state; the\n * method polls (invalidating the balances cache between attempts) until all\n * selected accounts are ready, the attempt limit is reached, or a request\n * fails. Concurrent calls for the same selected accounts and `vsCurrency`\n * share one in-flight promise; calls for a different selection or fiat\n * currency start a new fetch and leave prior polls running so a later\n * switch back can join them. 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 const fetchPromise = this.#fetchDeFiPositions(\n options,\n query,\n vsCurrency,\n ).finally(() => {\n if (this.#inFlightFetches.get(inFlightKey) === fetchPromise) {\n this.#inFlightFetches.delete(inFlightKey);\n }\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 for (\n let attempt = 0;\n attempt < DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS;\n attempt++\n ) {\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 try {\n const response =\n await this.#apiClient.accounts.fetchV6MultiAccountBalances(\n accountIds,\n queryOptions,\n fetchOptions,\n );\n\n const processingAccounts = response.accounts.filter(\n (account) => account.processingDefiPositions,\n );\n const readyAccounts = response.accounts.filter(\n (account) => !account.processingDefiPositions,\n );\n\n if (readyAccounts.length > 0) {\n const positionsByAccount = groupDeFiPositionsV6(\n { ...response, accounts: readyAccounts },\n internalAccountIdByCaip,\n );\n\n // Last valid response wins per ready account; still-indexing accounts\n // stay untouched.\n this.update((state) => {\n for (const [accountId, positions] of Object.entries(\n positionsByAccount,\n )) {\n state.allDeFiPositionsV2[accountId] = positions;\n }\n });\n }\n\n if (processingAccounts.length === 0) {\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 =\n attempt >= DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS - 1;\n if (isLastAttempt) {\n return;\n }\n\n await delay(DEFAULT_PROCESSING_POLL_INTERVAL_MS);\n } catch (error) {\n console.error('Failed to fetch DeFi positions', error);\n return;\n }\n }\n }\n}\n"]}
@@ -60,10 +60,11 @@ export type DeFiPositionsControllerV2Messenger = Messenger<typeof controllerName
60
60
  *
61
61
  * When the API reports `processingDefiPositions` for any account, this
62
62
  * controller polls until indexing finishes or the attempt limit is reached.
63
- * Concurrent calls for the same selected accounts share one in-flight promise
64
- * so the UI can treat the pending promise as a loading signal. Calls for a
65
- * different selection start their own fetch and leave any prior poll running,
66
- * so switching back can join an in-flight fetch for that group.
63
+ * Concurrent calls for the same selected accounts and `vsCurrency` share one
64
+ * in-flight promise so the UI can treat the pending promise as a loading
65
+ * signal. Calls for a different selection or fiat currency start their own
66
+ * fetch and leave any prior poll running, so switching back can join an
67
+ * in-flight fetch for that group and currency.
67
68
  */
68
69
  export declare class DeFiPositionsControllerV2 extends BaseController<typeof controllerName, DeFiPositionsControllerV2State, DeFiPositionsControllerV2Messenger> {
69
70
  #private;
@@ -88,13 +89,14 @@ export declare class DeFiPositionsControllerV2 extends BaseController<typeof con
88
89
  * Accounts still indexing (`processingDefiPositions`) keep prior state; the
89
90
  * method polls (invalidating the balances cache between attempts) until all
90
91
  * selected accounts are ready, the attempt limit is reached, or a request
91
- * fails. Concurrent calls for the same selected accounts share one in-flight
92
- * promise; calls for a different selection start a new fetch and leave prior
93
- * polls running so a later switch back can join them. No-ops when disabled
94
- * or when the group has no supported accounts. Caching / spam prevention is
95
- * handled by the apiClient TanStack Query cache (keyed by accounts + query
96
- * options including `vsCurrency`). Pass `{ forceRefresh: true }` to bypass
97
- * the cache on the first attempt (e.g. pull-to-refresh).
92
+ * fails. Concurrent calls for the same selected accounts and `vsCurrency`
93
+ * share one in-flight promise; calls for a different selection or fiat
94
+ * currency start a new fetch and leave prior polls running so a later
95
+ * switch back can join them. No-ops when disabled or when the group has no
96
+ * supported accounts. Caching / spam prevention is handled by the apiClient
97
+ * TanStack Query cache (keyed by accounts + query options including
98
+ * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the
99
+ * first attempt (e.g. pull-to-refresh).
98
100
  *
99
101
  * @param options - Optional fetch modifiers.
100
102
  * @param options.forceRefresh - When true, bypass the apiClient cache on the
@@ -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;AAIrD,OAAO,KAAK,EAAE,sCAAsC,EAAE,4DAA2D;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,sCAAqC;AAG3E,QAAA,MAAM,cAAc,8BAA8B,CAAC;AAInD,gFAAgF;AAChF,eAAO,MAAM,mCAAmC,OAAQ,CAAC;AAEzD;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,IAAI,CAAC;AAUtD,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,GACxB,8DAA8D,CAAC;AAEjE;;;;;;;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;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,yBAA0B,SAAQ,cAAc,CAC3D,OAAO,cAAc,EACrB,8BAA8B,EAC9B,kCAAkC,CACnC;;IAcC;;;;;;;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;;;;;;;;;;;;;;;;;;OAkBG;IACG,kBAAkB,CAAC,OAAO,CAAC,EAAE;QACjC,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC,IAAI,CAAC;CAuHlB"}
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;AAIrD,OAAO,KAAK,EAAE,sCAAsC,EAAE,4DAA2D;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,sCAAqC;AAG3E,QAAA,MAAM,cAAc,8BAA8B,CAAC;AAInD,gFAAgF;AAChF,eAAO,MAAM,mCAAmC,OAAQ,CAAC;AAEzD;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,IAAI,CAAC;AAUtD,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,GACxB,8DAA8D,CAAC;AAEjE;;;;;;;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;;;;;;;;;;;;;;;;GAgBG;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;;;;;;;;;;;;;;;;;;;OAmBG;IACG,kBAAkB,CAAC,OAAO,CAAC,EAAE;QACjC,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC,IAAI,CAAC;CA6HlB"}
@@ -60,10 +60,11 @@ export type DeFiPositionsControllerV2Messenger = Messenger<typeof controllerName
60
60
  *
61
61
  * When the API reports `processingDefiPositions` for any account, this
62
62
  * controller polls until indexing finishes or the attempt limit is reached.
63
- * Concurrent calls for the same selected accounts share one in-flight promise
64
- * so the UI can treat the pending promise as a loading signal. Calls for a
65
- * different selection start their own fetch and leave any prior poll running,
66
- * so switching back can join an in-flight fetch for that group.
63
+ * Concurrent calls for the same selected accounts and `vsCurrency` share one
64
+ * in-flight promise so the UI can treat the pending promise as a loading
65
+ * signal. Calls for a different selection or fiat currency start their own
66
+ * fetch and leave any prior poll running, so switching back can join an
67
+ * in-flight fetch for that group and currency.
67
68
  */
68
69
  export declare class DeFiPositionsControllerV2 extends BaseController<typeof controllerName, DeFiPositionsControllerV2State, DeFiPositionsControllerV2Messenger> {
69
70
  #private;
@@ -88,13 +89,14 @@ export declare class DeFiPositionsControllerV2 extends BaseController<typeof con
88
89
  * Accounts still indexing (`processingDefiPositions`) keep prior state; the
89
90
  * method polls (invalidating the balances cache between attempts) until all
90
91
  * selected accounts are ready, the attempt limit is reached, or a request
91
- * fails. Concurrent calls for the same selected accounts share one in-flight
92
- * promise; calls for a different selection start a new fetch and leave prior
93
- * polls running so a later switch back can join them. No-ops when disabled
94
- * or when the group has no supported accounts. Caching / spam prevention is
95
- * handled by the apiClient TanStack Query cache (keyed by accounts + query
96
- * options including `vsCurrency`). Pass `{ forceRefresh: true }` to bypass
97
- * the cache on the first attempt (e.g. pull-to-refresh).
92
+ * fails. Concurrent calls for the same selected accounts and `vsCurrency`
93
+ * share one in-flight promise; calls for a different selection or fiat
94
+ * currency start a new fetch and leave prior polls running so a later
95
+ * switch back can join them. No-ops when disabled or when the group has no
96
+ * supported accounts. Caching / spam prevention is handled by the apiClient
97
+ * TanStack Query cache (keyed by accounts + query options including
98
+ * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the
99
+ * first attempt (e.g. pull-to-refresh).
98
100
  *
99
101
  * @param options - Optional fetch modifiers.
100
102
  * @param options.forceRefresh - When true, bypass the apiClient cache on the
@@ -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;AAIrD,OAAO,KAAK,EAAE,sCAAsC,EAAE,4DAA2D;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,sCAAqC;AAG3E,QAAA,MAAM,cAAc,8BAA8B,CAAC;AAInD,gFAAgF;AAChF,eAAO,MAAM,mCAAmC,OAAQ,CAAC;AAEzD;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,IAAI,CAAC;AAUtD,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,GACxB,8DAA8D,CAAC;AAEjE;;;;;;;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;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,yBAA0B,SAAQ,cAAc,CAC3D,OAAO,cAAc,EACrB,8BAA8B,EAC9B,kCAAkC,CACnC;;IAcC;;;;;;;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;;;;;;;;;;;;;;;;;;OAkBG;IACG,kBAAkB,CAAC,OAAO,CAAC,EAAE;QACjC,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC,IAAI,CAAC;CAuHlB"}
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;AAIrD,OAAO,KAAK,EAAE,sCAAsC,EAAE,4DAA2D;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,sCAAqC;AAG3E,QAAA,MAAM,cAAc,8BAA8B,CAAC;AAInD,gFAAgF;AAChF,eAAO,MAAM,mCAAmC,OAAQ,CAAC;AAEzD;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,IAAI,CAAC;AAUtD,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,GACxB,8DAA8D,CAAC;AAEjE;;;;;;;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;;;;;;;;;;;;;;;;GAgBG;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;;;;;;;;;;;;;;;;;;;OAmBG;IACG,kBAAkB,CAAC,OAAO,CAAC,EAAE;QACjC,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC,IAAI,CAAC;CA6HlB"}
@@ -54,10 +54,11 @@ export const getDefaultDeFiPositionsControllerV2State = () => {
54
54
  *
55
55
  * When the API reports `processingDefiPositions` for any account, this
56
56
  * controller polls until indexing finishes or the attempt limit is reached.
57
- * Concurrent calls for the same selected accounts share one in-flight promise
58
- * so the UI can treat the pending promise as a loading signal. Calls for a
59
- * different selection start their own fetch and leave any prior poll running,
60
- * so switching back can join an in-flight fetch for that group.
57
+ * Concurrent calls for the same selected accounts and `vsCurrency` share one
58
+ * in-flight promise so the UI can treat the pending promise as a loading
59
+ * signal. Calls for a different selection or fiat currency start their own
60
+ * 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
  export class DeFiPositionsControllerV2 extends BaseController {
63
64
  /**
@@ -83,9 +84,10 @@ export class DeFiPositionsControllerV2 extends BaseController {
83
84
  _DeFiPositionsControllerV2_isEnabled.set(this, void 0);
84
85
  _DeFiPositionsControllerV2_getVsCurrency.set(this, void 0);
85
86
  /**
86
- * In-flight fetches keyed by selected DeFi-queryable account IDs. Concurrent
87
- * callers for the same selection share a promise; different selections keep
88
- * independent fetches so fast account switching can join an earlier poll.
87
+ * In-flight fetches keyed by selected DeFi-queryable account IDs plus
88
+ * `vsCurrency`. Concurrent callers for the same selection and currency share
89
+ * a promise; different selections or fiat currencies keep independent
90
+ * fetches so fast switching can join an earlier matching poll.
89
91
  */
90
92
  _DeFiPositionsControllerV2_inFlightFetches.set(this, new Map());
91
93
  __classPrivateFieldSet(this, _DeFiPositionsControllerV2_apiClient, apiClient, "f");
@@ -99,13 +101,14 @@ export class DeFiPositionsControllerV2 extends BaseController {
99
101
  * Accounts still indexing (`processingDefiPositions`) keep prior state; the
100
102
  * method polls (invalidating the balances cache between attempts) until all
101
103
  * selected accounts are ready, the attempt limit is reached, or a request
102
- * fails. Concurrent calls for the same selected accounts share one in-flight
103
- * promise; calls for a different selection start a new fetch and leave prior
104
- * polls running so a later switch back can join them. No-ops when disabled
105
- * or when the group has no supported accounts. Caching / spam prevention is
106
- * handled by the apiClient TanStack Query cache (keyed by accounts + query
107
- * options including `vsCurrency`). Pass `{ forceRefresh: true }` to bypass
108
- * the cache on the first attempt (e.g. pull-to-refresh).
104
+ * fails. Concurrent calls for the same selected accounts and `vsCurrency`
105
+ * share one in-flight promise; calls for a different selection or fiat
106
+ * currency start a new fetch and leave prior polls running so a later
107
+ * switch back can join them. No-ops when disabled or when the group has no
108
+ * supported accounts. Caching / spam prevention is handled by the apiClient
109
+ * TanStack Query cache (keyed by accounts + query options including
110
+ * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the
111
+ * first attempt (e.g. pull-to-refresh).
109
112
  *
110
113
  * @param options - Optional fetch modifiers.
111
114
  * @param options.forceRefresh - When true, bypass the apiClient cache on the
@@ -118,29 +121,32 @@ export class DeFiPositionsControllerV2 extends BaseController {
118
121
  }
119
122
  const selectedAccounts = this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
120
123
  const query = buildDeFiBalancesQuery(selectedAccounts);
121
- const accountIdsKey = [...query.internalAccountIdByCaip.keys()]
122
- .sort()
123
- .join('\0');
124
- const existing = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").get(accountIdsKey);
124
+ const vsCurrency = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_getVsCurrency, "f").call(this).toLowerCase();
125
+ // Include vsCurrency so a fiat change does not join a poll priced in the
126
+ // previous currency (TanStack also keys the balances cache on vsCurrency).
127
+ const inFlightKey = [
128
+ ...[...query.internalAccountIdByCaip.keys()].sort(),
129
+ vsCurrency,
130
+ ].join('\0');
131
+ const existing = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").get(inFlightKey);
125
132
  if (existing) {
126
133
  await existing;
127
134
  return;
128
135
  }
129
- const fetchPromise = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_instances, "m", _DeFiPositionsControllerV2_fetchDeFiPositions).call(this, options, query).finally(() => {
130
- if (__classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").get(accountIdsKey) === fetchPromise) {
131
- __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").delete(accountIdsKey);
136
+ const fetchPromise = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_instances, "m", _DeFiPositionsControllerV2_fetchDeFiPositions).call(this, options, query, vsCurrency).finally(() => {
137
+ if (__classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").get(inFlightKey) === fetchPromise) {
138
+ __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").delete(inFlightKey);
132
139
  }
133
140
  });
134
- __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").set(accountIdsKey, fetchPromise);
141
+ __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").set(inFlightKey, fetchPromise);
135
142
  await fetchPromise;
136
143
  }
137
144
  }
138
- _DeFiPositionsControllerV2_apiClient = new WeakMap(), _DeFiPositionsControllerV2_isEnabled = new WeakMap(), _DeFiPositionsControllerV2_getVsCurrency = new WeakMap(), _DeFiPositionsControllerV2_inFlightFetches = new WeakMap(), _DeFiPositionsControllerV2_instances = new WeakSet(), _DeFiPositionsControllerV2_fetchDeFiPositions = async function _DeFiPositionsControllerV2_fetchDeFiPositions(options, { networks, internalAccountIdByCaip }) {
145
+ _DeFiPositionsControllerV2_apiClient = new WeakMap(), _DeFiPositionsControllerV2_isEnabled = new WeakMap(), _DeFiPositionsControllerV2_getVsCurrency = new WeakMap(), _DeFiPositionsControllerV2_inFlightFetches = new WeakMap(), _DeFiPositionsControllerV2_instances = new WeakSet(), _DeFiPositionsControllerV2_fetchDeFiPositions = async function _DeFiPositionsControllerV2_fetchDeFiPositions(options, { networks, internalAccountIdByCaip }, vsCurrency) {
139
146
  if (internalAccountIdByCaip.size === 0 || networks.length === 0) {
140
147
  return;
141
148
  }
142
149
  const accountIds = [...internalAccountIdByCaip.keys()];
143
- const vsCurrency = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_getVsCurrency, "f").call(this).toLowerCase();
144
150
  const queryOptions = {
145
151
  networks,
146
152
  includeDeFiBalances: true,
@@ -1 +1 @@
1
- {"version":3,"file":"DeFiPositionsControllerV2.mjs","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAS3D,OAAO,EAAE,sBAAsB,EAAE,wCAAuC;AAIxE,OAAO,EAAE,oBAAoB,EAAE,sCAAqC;AAEpE,MAAM,cAAc,GAAG,2BAA2B,CAAC;AAEnD,MAAM,yBAAyB,GAAG,CAAC,oBAAoB,CAAU,CAAC;AAElE,gFAAgF;AAChF,MAAM,CAAC,MAAM,mCAAmC,GAAG,IAAK,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,oCAAoC,GAAG,CAAC,CAAC;AAEtD;;;GAGG;AACH,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAmBD,MAAM,kBAAkB,GAAkD;IACxE,kBAAkB,EAAE;QAClB,kBAAkB,EAAE,KAAK;QACzB,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,KAAK;QAC7B,QAAQ,EAAE,IAAI;KACf;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,wCAAwC,GACnD,GAAmC,EAAE;IACnC,OAAO;QACL,kBAAkB,EAAE,EAAE;KACvB,CAAC;AACJ,CAAC,CAAC;AA0CJ;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,yBAA0B,SAAQ,cAI9C;IAcC;;;;;;;OAOG;IACH,YAAY,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,aAAa,EACb,KAAK,GAON;QACC,KAAK,CAAC;YACJ,IAAI,EAAE,cAAc;YACpB,QAAQ,EAAE,kBAAkB;YAC5B,SAAS;YACT,KAAK,EAAE;gBACL,GAAG,wCAAwC,EAAE;gBAC7C,GAAG,KAAK;aACT;SACF,CAAC,CAAC;;QA1CI,uDAA8B;QAE9B,uDAA0B;QAE1B,2DAA6B;QAEtC;;;;WAIG;QACM,qDAAmB,IAAI,GAAG,EAAyB,EAAC;QAiC3D,uBAAA,IAAI,wCAAc,SAAS,MAAA,CAAC;QAC5B,uBAAA,IAAI,wCAAc,SAAS,MAAA,CAAC;QAC5B,uBAAA,IAAI,4CAAkB,aAAa,MAAA,CAAC;QAEpC,IAAI,CAAC,SAAS,CAAC,4BAA4B,CACzC,IAAI,EACJ,yBAAyB,CAC1B,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,KAAK,CAAC,kBAAkB,CAAC,OAExB;QACC,IAAI,CAAC,uBAAA,IAAI,4CAAW,MAAf,IAAI,CAAa,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAC1C,2DAA2D,CAC5D,CAAC;QACF,MAAM,KAAK,GAAG,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;QACvD,MAAM,aAAa,GAAG,CAAC,GAAG,KAAK,CAAC,uBAAuB,CAAC,IAAI,EAAE,CAAC;aAC5D,IAAI,EAAE;aACN,IAAI,CAAC,IAAI,CAAC,CAAC;QAEd,MAAM,QAAQ,GAAG,uBAAA,IAAI,kDAAiB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QAC1D,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,QAAQ,CAAC;YACf,OAAO;QACT,CAAC;QAED,MAAM,YAAY,GAAG,uBAAA,IAAI,2FAAoB,MAAxB,IAAI,EAAqB,OAAO,EAAE,KAAK,CAAC,CAAC,OAAO,CACnE,GAAG,EAAE;YACH,IAAI,uBAAA,IAAI,kDAAiB,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,YAAY,EAAE,CAAC;gBAC9D,uBAAA,IAAI,kDAAiB,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC,CACF,CAAC;QACF,uBAAA,IAAI,kDAAiB,CAAC,GAAG,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;QAEvD,MAAM,YAAY,CAAC;IACrB,CAAC;CA0FF;wUAxFC,KAAK,wDACH,OAA+C,EAC/C,EAAE,QAAQ,EAAE,uBAAuB,EAAqB;IAExD,IAAI,uBAAuB,CAAC,IAAI,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChE,OAAO;IACT,CAAC;IAED,MAAM,UAAU,GAAG,CAAC,GAAG,uBAAuB,CAAC,IAAI,EAAE,CAAC,CAAC;IACvD,MAAM,UAAU,GAAG,uBAAA,IAAI,gDAAe,MAAnB,IAAI,CAAiB,CAAC,WAAW,EAAE,CAAC;IACvD,MAAM,YAAY,GAAG;QACnB,QAAQ;QACR,mBAAmB,EAAE,IAAI;QACzB,uBAAuB,EAAE,IAAI;QAC7B,aAAa,EAAE,IAAI;QACnB,UAAU;KACX,CAAC;IAEF,KACE,IAAI,OAAO,GAAG,CAAC,EACf,OAAO,GAAG,oCAAoC,EAC9C,OAAO,EAAE,EACT,CAAC;QACD,uEAAuE;QACvE,oDAAoD;QACpD,MAAM,YAAY,GAAG;YACnB,GAAG,CAAC,OAAO,EAAE,YAAY,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,QAAQ,GACZ,MAAM,uBAAA,IAAI,4CAAW,CAAC,QAAQ,CAAC,2BAA2B,CACxD,UAAU,EACV,YAAY,EACZ,YAAY,CACb,CAAC;YAEJ,MAAM,kBAAkB,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CACjD,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,uBAAuB,CAC7C,CAAC;YACF,MAAM,aAAa,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAC5C,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAC9C,CAAC;YAEF,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC7B,MAAM,kBAAkB,GAAG,oBAAoB,CAC7C,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,EACxC,uBAAuB,CACxB,CAAC;gBAEF,sEAAsE;gBACtE,kBAAkB;gBAClB,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;oBACpB,KAAK,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CACjD,kBAAkB,CACnB,EAAE,CAAC;wBACF,KAAK,CAAC,kBAAkB,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;oBAClD,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC;YAED,IAAI,kBAAkB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACpC,OAAO;YACT,CAAC;YAED,MAAM,EAAE,QAAQ,EAAE,GAChB,uBAAA,IAAI,4CAAW,CAAC,QAAQ,CAAC,qCAAqC,CAC5D,UAAU,EACV,YAAY,EACZ,YAAY,CACb,CAAC;YACJ,MAAM,uBAAA,IAAI,4CAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,iBAAiB,CAAC;gBAC3D,QAAQ;aACT,CAAC,CAAC;YAEH,MAAM,aAAa,GACjB,OAAO,IAAI,oCAAoC,GAAG,CAAC,CAAC;YACtD,IAAI,aAAa,EAAE,CAAC;gBAClB,OAAO;YACT,CAAC;YAED,MAAM,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAC;YACvD,OAAO;QACT,CAAC;IACH,CAAC;AACH,CAAC","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';\n\nimport { buildDeFiBalancesQuery } from './build-defi-balances-query.js';\nimport type { DeFiBalancesQuery } from './build-defi-balances-query.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/** Delay between polls while Accounts API reports DeFi indexing in progress. */\nexport const DEFAULT_PROCESSING_POLL_INTERVAL_MS = 5_000;\n\n/**\n * Maximum fetch attempts (including the first) while any account still has\n * `processingDefiPositions: true`. After this, the call resolves and keeps\n * prior state for accounts that never became ready.\n */\nexport const DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS = 5;\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\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 for pull-to-refresh.\n *\n * When the API reports `processingDefiPositions` for any account, this\n * controller polls until indexing finishes or the attempt limit is reached.\n * Concurrent calls for the same selected accounts share one in-flight promise\n * so the UI can treat the pending promise as a loading signal. Calls for a\n * different selection start their own fetch and leave any prior poll running,\n * so switching back can join an in-flight fetch for that group.\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. Concurrent\n * callers for the same selection share a promise; different selections keep\n * independent fetches so fast account switching can join an earlier 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. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`) keep prior state; the\n * method polls (invalidating the balances cache between attempts) until all\n * selected accounts are ready, the attempt limit is reached, or a request\n * fails. Concurrent calls for the same selected accounts share one in-flight\n * promise; calls for a different selection start a new fetch and leave prior\n * polls running so a later switch back can join them. No-ops when disabled\n * or when the group has no supported accounts. Caching / spam prevention is\n * handled by the apiClient TanStack Query cache (keyed by accounts + query\n * options including `vsCurrency`). Pass `{ forceRefresh: true }` to bypass\n * the cache on the 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 accountIdsKey = [...query.internalAccountIdByCaip.keys()]\n .sort()\n .join('\\0');\n\n const existing = this.#inFlightFetches.get(accountIdsKey);\n if (existing) {\n await existing;\n return;\n }\n\n const fetchPromise = this.#fetchDeFiPositions(options, query).finally(\n () => {\n if (this.#inFlightFetches.get(accountIdsKey) === fetchPromise) {\n this.#inFlightFetches.delete(accountIdsKey);\n }\n },\n );\n this.#inFlightFetches.set(accountIdsKey, fetchPromise);\n\n await fetchPromise;\n }\n\n async #fetchDeFiPositions(\n options: { forceRefresh?: boolean } | undefined,\n { networks, internalAccountIdByCaip }: DeFiBalancesQuery,\n ): Promise<void> {\n if (internalAccountIdByCaip.size === 0 || networks.length === 0) {\n return;\n }\n\n const accountIds = [...internalAccountIdByCaip.keys()];\n const vsCurrency = this.#getVsCurrency().toLowerCase();\n const queryOptions = {\n networks,\n includeDeFiBalances: true,\n forceFetchDeFiPositions: true,\n includePrices: true,\n vsCurrency,\n };\n\n for (\n let attempt = 0;\n attempt < DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS;\n attempt++\n ) {\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 try {\n const response =\n await this.#apiClient.accounts.fetchV6MultiAccountBalances(\n accountIds,\n queryOptions,\n fetchOptions,\n );\n\n const processingAccounts = response.accounts.filter(\n (account) => account.processingDefiPositions,\n );\n const readyAccounts = response.accounts.filter(\n (account) => !account.processingDefiPositions,\n );\n\n if (readyAccounts.length > 0) {\n const positionsByAccount = groupDeFiPositionsV6(\n { ...response, accounts: readyAccounts },\n internalAccountIdByCaip,\n );\n\n // Last valid response wins per ready account; still-indexing accounts\n // stay untouched.\n this.update((state) => {\n for (const [accountId, positions] of Object.entries(\n positionsByAccount,\n )) {\n state.allDeFiPositionsV2[accountId] = positions;\n }\n });\n }\n\n if (processingAccounts.length === 0) {\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 =\n attempt >= DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS - 1;\n if (isLastAttempt) {\n return;\n }\n\n await delay(DEFAULT_PROCESSING_POLL_INTERVAL_MS);\n } catch (error) {\n console.error('Failed to fetch DeFi positions', error);\n return;\n }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"DeFiPositionsControllerV2.mjs","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';\n\nimport { buildDeFiBalancesQuery } from './build-defi-balances-query.js';\nimport type { DeFiBalancesQuery } from './build-defi-balances-query.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/** Delay between polls while Accounts API reports DeFi indexing in progress. */\nexport const DEFAULT_PROCESSING_POLL_INTERVAL_MS = 5_000;\n\n/**\n * Maximum fetch attempts (including the first) while any account still has\n * `processingDefiPositions: true`. After this, the call resolves and keeps\n * prior state for accounts that never became ready.\n */\nexport const DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS = 5;\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\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 for pull-to-refresh.\n *\n * When the API reports `processingDefiPositions` for any account, this\n * controller polls until indexing finishes or the attempt limit is reached.\n * Concurrent calls for the same selected accounts and `vsCurrency` share one\n * in-flight promise so the UI can treat the pending promise as a loading\n * signal. Calls for a different selection or fiat currency start their own\n * fetch and leave any prior poll running, so switching back can join an\n * in-flight fetch for that group and currency.\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. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`) keep prior state; the\n * method polls (invalidating the balances cache between attempts) until all\n * selected accounts are ready, the attempt limit is reached, or a request\n * fails. Concurrent calls for the same selected accounts and `vsCurrency`\n * share one in-flight promise; calls for a different selection or fiat\n * currency start a new fetch and leave prior polls running so a later\n * switch back can join them. 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 const fetchPromise = this.#fetchDeFiPositions(\n options,\n query,\n vsCurrency,\n ).finally(() => {\n if (this.#inFlightFetches.get(inFlightKey) === fetchPromise) {\n this.#inFlightFetches.delete(inFlightKey);\n }\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 for (\n let attempt = 0;\n attempt < DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS;\n attempt++\n ) {\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 try {\n const response =\n await this.#apiClient.accounts.fetchV6MultiAccountBalances(\n accountIds,\n queryOptions,\n fetchOptions,\n );\n\n const processingAccounts = response.accounts.filter(\n (account) => account.processingDefiPositions,\n );\n const readyAccounts = response.accounts.filter(\n (account) => !account.processingDefiPositions,\n );\n\n if (readyAccounts.length > 0) {\n const positionsByAccount = groupDeFiPositionsV6(\n { ...response, accounts: readyAccounts },\n internalAccountIdByCaip,\n );\n\n // Last valid response wins per ready account; still-indexing accounts\n // stay untouched.\n this.update((state) => {\n for (const [accountId, positions] of Object.entries(\n positionsByAccount,\n )) {\n state.allDeFiPositionsV2[accountId] = positions;\n }\n });\n }\n\n if (processingAccounts.length === 0) {\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 =\n attempt >= DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS - 1;\n if (isLastAttempt) {\n return;\n }\n\n await delay(DEFAULT_PROCESSING_POLL_INTERVAL_MS);\n } catch (error) {\n console.error('Failed to fetch DeFi positions', error);\n return;\n }\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/assets-controllers",
3
- "version": "110.0.2-preview-f48a87ac7",
3
+ "version": "110.0.2-preview-d77c814bb",
4
4
  "description": "Controllers which manage interactions involving ERC-20, ERC-721, and ERC-1155 tokens (including NFTs)",
5
5
  "keywords": [
6
6
  "Ethereum",