@metamask-previews/assets-controllers 110.1.0-preview-beea66146 → 110.1.0-preview-0efc7ef01

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,7 +9,6 @@ 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 account has `processingDefiPositions: true`, updating state only when every account is ready, 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))
13
12
  - Bump `@metamask/network-controller` from `^35.0.0` to `^35.0.1` ([#9758](https://github.com/MetaMask/core/pull/9758))
14
13
  - Bump `@metamask/phishing-controller` from `^17.3.0` to `^17.3.1` ([#9746](https://github.com/MetaMask/core/pull/9746))
15
14
 
@@ -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. No-ops when disabled or when the group\n * has no supported accounts. Caching / spam prevention is handled by the\n * apiClient 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. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`) are skipped so prior\n * state is kept for 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 (e.g.\n * pull-to-refresh).\n *\n * @param options - Optional fetch modifiers.\n * @param options.forceRefresh - When true, bypass the apiClient cache and\n * fetch immediately.\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,25 +4,18 @@
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. No-ops when disabled or when the group
17
- * has no supported accounts. Caching / spam prevention is handled by the
18
- * apiClient TanStack Query cache (keyed by accounts + query options including
19
- * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the
20
- * first attempt (e.g. pull-to-refresh).
7
+ * Fetches DeFi positions for the selected account group. Each account key in
8
+ * a ready response replaces that account's state (other accounts stay).
9
+ * Accounts still indexing (`processingDefiPositions`) are skipped so prior
10
+ * state is kept for them. No-ops when disabled or when the group has no
11
+ * supported accounts. Caching / spam prevention is handled by the apiClient
12
+ * TanStack Query cache (keyed by accounts + query options including
13
+ * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache (e.g.
14
+ * pull-to-refresh).
21
15
  *
22
16
  * @param options - Optional fetch modifiers.
23
- * @param options.forceRefresh - When true, bypass the apiClient cache on the
24
- * first attempt and fetch immediately.
25
- * @returns Resolves when the fetch (and any processing polls) finish.
17
+ * @param options.forceRefresh - When true, bypass the apiClient cache and
18
+ * fetch immediately.
26
19
  */
27
20
  export type DeFiPositionsControllerV2FetchDeFiPositionsAction = {
28
21
  type: `DeFiPositionsControllerV2:fetchDeFiPositions`;
@@ -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;;;;;;;;;;;;;;;;;;;;GAoBG;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;;;;;;;;;;;;;GAaG;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"}
@@ -4,25 +4,18 @@
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. No-ops when disabled or when the group
17
- * has no supported accounts. Caching / spam prevention is handled by the
18
- * apiClient TanStack Query cache (keyed by accounts + query options including
19
- * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the
20
- * first attempt (e.g. pull-to-refresh).
7
+ * Fetches DeFi positions for the selected account group. Each account key in
8
+ * a ready response replaces that account's state (other accounts stay).
9
+ * Accounts still indexing (`processingDefiPositions`) are skipped so prior
10
+ * state is kept for them. No-ops when disabled or when the group has no
11
+ * supported accounts. Caching / spam prevention is handled by the apiClient
12
+ * TanStack Query cache (keyed by accounts + query options including
13
+ * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache (e.g.
14
+ * pull-to-refresh).
21
15
  *
22
16
  * @param options - Optional fetch modifiers.
23
- * @param options.forceRefresh - When true, bypass the apiClient cache on the
24
- * first attempt and fetch immediately.
25
- * @returns Resolves when the fetch (and any processing polls) finish.
17
+ * @param options.forceRefresh - When true, bypass the apiClient cache and
18
+ * fetch immediately.
26
19
  */
27
20
  export type DeFiPositionsControllerV2FetchDeFiPositionsAction = {
28
21
  type: `DeFiPositionsControllerV2:fetchDeFiPositions`;
@@ -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;;;;;;;;;;;;;;;;;;;;GAoBG;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;;;;;;;;;;;;;GAaG;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. 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. No-ops when disabled or when the group\n * has no supported accounts. Caching / spam prevention is handled by the\n * apiClient 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. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`) are skipped so prior\n * state is kept for 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 (e.g.\n * pull-to-refresh).\n *\n * @param options - Optional fetch modifiers.\n * @param options.forceRefresh - When true, bypass the apiClient cache and\n * fetch immediately.\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"]}
@@ -10,29 +10,14 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _DeFiPositionsControllerV2_instances, _DeFiPositionsControllerV2_apiClient, _DeFiPositionsControllerV2_isEnabled, _DeFiPositionsControllerV2_getVsCurrency, _DeFiPositionsControllerV2_inFlightFetches, _DeFiPositionsControllerV2_fetchDeFiPositions;
13
+ var _DeFiPositionsControllerV2_apiClient, _DeFiPositionsControllerV2_isEnabled, _DeFiPositionsControllerV2_getVsCurrency;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.DeFiPositionsControllerV2 = exports.getDefaultDeFiPositionsControllerV2State = exports.DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS = exports.DEFAULT_PROCESSING_POLL_INTERVAL_MS = void 0;
15
+ exports.DeFiPositionsControllerV2 = exports.getDefaultDeFiPositionsControllerV2State = void 0;
16
16
  const base_controller_1 = require("@metamask/base-controller");
17
17
  const build_defi_balances_query_js_1 = require("./build-defi-balances-query.cjs");
18
18
  const group_defi_positions_v6_js_1 = require("./group-defi-positions-v6.cjs");
19
19
  const controllerName = 'DeFiPositionsControllerV2';
20
20
  const MESSENGER_EXPOSED_METHODS = ['fetchDeFiPositions'];
21
- /** Delay between polls while Accounts API reports DeFi indexing in progress. */
22
- exports.DEFAULT_PROCESSING_POLL_INTERVAL_MS = 5000;
23
- /**
24
- * Maximum fetch attempts (including the first) while any account still has
25
- * `processingDefiPositions: true`. After this, the call resolves without
26
- * updating state, so prior positions are kept for every selected account.
27
- */
28
- exports.DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS = 5;
29
- /**
30
- * @param ms - Milliseconds to wait.
31
- * @returns A promise that resolves after `ms`.
32
- */
33
- function delay(ms) {
34
- return new Promise((resolve) => setTimeout(resolve, ms));
35
- }
36
21
  const controllerMetadata = {
37
22
  allDeFiPositionsV2: {
38
23
  includeInStateLogs: false,
@@ -55,15 +40,6 @@ exports.getDefaultDeFiPositionsControllerV2State = getDefaultDeFiPositionsContro
55
40
  * Deduplication and freshness are handled by the shared TanStack Query cache on
56
41
  * {@link ApiPlatformClient} (balances default `staleTime` is 1 minute). Pass
57
42
  * `{ forceRefresh: true }` to bypass that cache for pull-to-refresh.
58
- *
59
- * When the API reports `processingDefiPositions` for any account, this
60
- * controller polls until indexing finishes or the attempt limit is reached,
61
- * and only then processes and writes state — mixed ready/processing responses
62
- * leave prior state untouched. Concurrent calls for the same selected accounts
63
- * and `vsCurrency` share one in-flight promise so the UI can treat the pending
64
- * promise as a loading signal. Calls for a different selection or fiat currency
65
- * start their own fetch and leave any prior poll running, so switching back can
66
- * join an in-flight fetch for that group and currency.
67
43
  */
68
44
  class DeFiPositionsControllerV2 extends base_controller_1.BaseController {
69
45
  /**
@@ -84,118 +60,69 @@ class DeFiPositionsControllerV2 extends base_controller_1.BaseController {
84
60
  ...state,
85
61
  },
86
62
  });
87
- _DeFiPositionsControllerV2_instances.add(this);
88
63
  _DeFiPositionsControllerV2_apiClient.set(this, void 0);
89
64
  _DeFiPositionsControllerV2_isEnabled.set(this, void 0);
90
65
  _DeFiPositionsControllerV2_getVsCurrency.set(this, void 0);
91
- /**
92
- * In-flight fetches keyed by selected DeFi-queryable account IDs plus
93
- * `vsCurrency`. Concurrent callers for the same selection and currency share
94
- * a promise; different selections or fiat currencies keep independent
95
- * fetches so fast switching can join an earlier matching poll.
96
- */
97
- _DeFiPositionsControllerV2_inFlightFetches.set(this, new Map());
98
66
  __classPrivateFieldSet(this, _DeFiPositionsControllerV2_apiClient, apiClient, "f");
99
67
  __classPrivateFieldSet(this, _DeFiPositionsControllerV2_isEnabled, isEnabled, "f");
100
68
  __classPrivateFieldSet(this, _DeFiPositionsControllerV2_getVsCurrency, getVsCurrency, "f");
101
69
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
102
70
  }
103
71
  /**
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. No-ops when disabled or when the group
114
- * has no supported accounts. Caching / spam prevention is handled by the
115
- * apiClient TanStack Query cache (keyed by accounts + query options including
116
- * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the
117
- * first attempt (e.g. pull-to-refresh).
72
+ * Fetches DeFi positions for the selected account group. Each account key in
73
+ * a ready response replaces that account's state (other accounts stay).
74
+ * Accounts still indexing (`processingDefiPositions`) are skipped so prior
75
+ * state is kept for them. No-ops when disabled or when the group has no
76
+ * supported accounts. Caching / spam prevention is handled by the apiClient
77
+ * TanStack Query cache (keyed by accounts + query options including
78
+ * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache (e.g.
79
+ * pull-to-refresh).
118
80
  *
119
81
  * @param options - Optional fetch modifiers.
120
- * @param options.forceRefresh - When true, bypass the apiClient cache on the
121
- * first attempt and fetch immediately.
122
- * @returns Resolves when the fetch (and any processing polls) finish.
82
+ * @param options.forceRefresh - When true, bypass the apiClient cache and
83
+ * fetch immediately.
123
84
  */
124
85
  async fetchDeFiPositions(options) {
125
86
  if (!__classPrivateFieldGet(this, _DeFiPositionsControllerV2_isEnabled, "f").call(this)) {
126
87
  return;
127
88
  }
128
89
  const selectedAccounts = this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
129
- const query = (0, build_defi_balances_query_js_1.buildDeFiBalancesQuery)(selectedAccounts);
130
- const vsCurrency = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_getVsCurrency, "f").call(this).toLowerCase();
131
- // Include vsCurrency so a fiat change does not join a poll priced in the
132
- // previous currency (TanStack also keys the balances cache on vsCurrency).
133
- const inFlightKey = [
134
- ...[...query.internalAccountIdByCaip.keys()].sort(),
135
- vsCurrency,
136
- ].join('\0');
137
- const existing = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").get(inFlightKey);
138
- if (existing) {
139
- await existing;
90
+ const { networks, internalAccountIdByCaip } = (0, build_defi_balances_query_js_1.buildDeFiBalancesQuery)(selectedAccounts);
91
+ if (internalAccountIdByCaip.size === 0 || networks.length === 0) {
140
92
  return;
141
93
  }
142
- // Same-key callers join this promise instead of replacing it, so cleanup
143
- // can always delete without checking identity.
144
- const fetchPromise = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_instances, "m", _DeFiPositionsControllerV2_fetchDeFiPositions).call(this, options, query, vsCurrency).finally(() => {
145
- __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").delete(inFlightKey);
146
- });
147
- __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").set(inFlightKey, fetchPromise);
148
- await fetchPromise;
149
- }
150
- }
151
- exports.DeFiPositionsControllerV2 = DeFiPositionsControllerV2;
152
- _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) {
153
- if (internalAccountIdByCaip.size === 0 || networks.length === 0) {
154
- return;
155
- }
156
- const accountIds = [...internalAccountIdByCaip.keys()];
157
- const queryOptions = {
158
- networks,
159
- includeDeFiBalances: true,
160
- forceFetchDeFiPositions: true,
161
- includePrices: true,
162
- vsCurrency,
163
- };
164
- for (let attempt = 0; attempt < exports.DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS; attempt++) {
165
- // First attempt respects forceRefresh; later polls always bypass cache
166
- // so we do not spin on a stale processing snapshot.
167
- const fetchOptions = {
168
- ...(options?.forceRefresh || attempt > 0 ? { staleTime: 0 } : {}),
169
- };
94
+ const accountIds = [...internalAccountIdByCaip.keys()];
95
+ const vsCurrency = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_getVsCurrency, "f").call(this).toLowerCase();
170
96
  try {
171
- const response = await __classPrivateFieldGet(this, _DeFiPositionsControllerV2_apiClient, "f").accounts.fetchV6MultiAccountBalances(accountIds, queryOptions, fetchOptions);
172
- const stillProcessing = response.accounts.some((account) => account.processingDefiPositions);
173
- // Only process and write when every account is ready so a partial
174
- // response cannot clear or overwrite positions for accounts that are
175
- // still indexing.
176
- if (!stillProcessing) {
177
- const positionsByAccount = (0, group_defi_positions_v6_js_1.groupDeFiPositionsV6)(response, internalAccountIdByCaip);
178
- this.update((state) => {
179
- for (const [accountId, positions] of Object.entries(positionsByAccount)) {
180
- state.allDeFiPositionsV2[accountId] = positions;
181
- }
182
- });
183
- return;
184
- }
185
- const { queryKey } = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_apiClient, "f").accounts.getV6MultiAccountBalancesQueryOptions(accountIds, queryOptions, fetchOptions);
186
- await __classPrivateFieldGet(this, _DeFiPositionsControllerV2_apiClient, "f").accounts.queryClient.invalidateQueries({
187
- queryKey,
97
+ const response = await __classPrivateFieldGet(this, _DeFiPositionsControllerV2_apiClient, "f").accounts.fetchV6MultiAccountBalances(accountIds, {
98
+ networks,
99
+ includeDeFiBalances: true,
100
+ forceFetchDeFiPositions: true,
101
+ includePrices: true,
102
+ vsCurrency,
103
+ }, {
104
+ // staleTime: 0 makes TanStack treat the cache as stale for this call.
105
+ ...(options?.forceRefresh ? { staleTime: 0 } : {}),
188
106
  });
189
- const isLastAttempt = attempt >= exports.DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS - 1;
190
- if (isLastAttempt) {
107
+ // Skip accounts still indexing — their balances are not a valid snapshot.
108
+ const readyAccounts = response.accounts.filter((account) => !account.processingDefiPositions);
109
+ if (readyAccounts.length === 0) {
191
110
  return;
192
111
  }
193
- await delay(exports.DEFAULT_PROCESSING_POLL_INTERVAL_MS);
112
+ const positionsByAccount = (0, group_defi_positions_v6_js_1.groupDeFiPositionsV6)({ ...response, accounts: readyAccounts }, internalAccountIdByCaip);
113
+ // Last valid response wins per ready account; processing / other accounts
114
+ // stay untouched.
115
+ this.update((state) => {
116
+ for (const [accountId, positions] of Object.entries(positionsByAccount)) {
117
+ state.allDeFiPositionsV2[accountId] = positions;
118
+ }
119
+ });
194
120
  }
195
121
  catch (error) {
196
122
  console.error('Failed to fetch DeFi positions', error);
197
- return;
198
123
  }
199
124
  }
200
- };
125
+ }
126
+ exports.DeFiPositionsControllerV2 = DeFiPositionsControllerV2;
127
+ _DeFiPositionsControllerV2_apiClient = new WeakMap(), _DeFiPositionsControllerV2_isEnabled = new WeakMap(), _DeFiPositionsControllerV2_getVsCurrency = new WeakMap();
201
128
  //# sourceMappingURL=DeFiPositionsControllerV2.cjs.map
@@ -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 without\n * updating state, so prior positions are kept for every selected account.\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 * 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 */\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. No-ops when disabled or when the group\n * has no supported accounts. Caching / spam prevention is handled by the\n * apiClient 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 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 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 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 { 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\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 */\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 * @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`) are skipped so prior\n * state is kept for 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 (e.g.\n * pull-to-refresh).\n *\n * @param options - Optional fetch modifiers.\n * @param options.forceRefresh - When true, bypass the apiClient cache and\n * fetch immediately.\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\n const { networks, internalAccountIdByCaip } =\n buildDeFiBalancesQuery(selectedAccounts);\n\n if (internalAccountIdByCaip.size === 0 || networks.length === 0) {\n return;\n }\n\n const accountIds = [...internalAccountIdByCaip.keys()];\n const vsCurrency = this.#getVsCurrency().toLowerCase();\n\n try {\n const response =\n await this.#apiClient.accounts.fetchV6MultiAccountBalances(\n accountIds,\n {\n networks,\n includeDeFiBalances: true,\n forceFetchDeFiPositions: true,\n includePrices: true,\n vsCurrency,\n },\n {\n // staleTime: 0 makes TanStack treat the cache as stale for this call.\n ...(options?.forceRefresh ? { staleTime: 0 } : {}),\n },\n );\n\n // Skip accounts still indexing — their balances are not a valid snapshot.\n const readyAccounts = response.accounts.filter(\n (account) => !account.processingDefiPositions,\n );\n if (readyAccounts.length === 0) {\n return;\n }\n\n const positionsByAccount = groupDeFiPositionsV6(\n { ...response, accounts: readyAccounts },\n internalAccountIdByCaip,\n );\n\n // Last valid response wins per ready account; processing / other 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 } catch (error) {\n console.error('Failed to fetch DeFi positions', error);\n }\n }\n}\n"]}
@@ -6,14 +6,6 @@ import type { Messenger } from "@metamask/messenger";
6
6
  import type { DeFiPositionsControllerV2MethodActions } from "./DeFiPositionsControllerV2-method-action-types.cjs";
7
7
  import type { DeFiPositionsByAccount } from "./group-defi-positions-v6.cjs";
8
8
  declare const controllerName = "DeFiPositionsControllerV2";
9
- /** Delay between polls while Accounts API reports DeFi indexing in progress. */
10
- export declare const DEFAULT_PROCESSING_POLL_INTERVAL_MS = 5000;
11
- /**
12
- * Maximum fetch attempts (including the first) while any account still has
13
- * `processingDefiPositions: true`. After this, the call resolves without
14
- * updating state, so prior positions are kept for every selected account.
15
- */
16
- export declare const DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS = 5;
17
9
  export type DeFiPositionsControllerV2State = {
18
10
  /**
19
11
  * DeFi positions keyed by internal MetaMask account ID (`InternalAccount.id`,
@@ -57,15 +49,6 @@ export type DeFiPositionsControllerV2Messenger = Messenger<typeof controllerName
57
49
  * Deduplication and freshness are handled by the shared TanStack Query cache on
58
50
  * {@link ApiPlatformClient} (balances default `staleTime` is 1 minute). Pass
59
51
  * `{ forceRefresh: true }` to bypass that cache for pull-to-refresh.
60
- *
61
- * When the API reports `processingDefiPositions` for any account, this
62
- * controller polls until indexing finishes or the attempt limit is reached,
63
- * and only then processes and writes state — mixed ready/processing responses
64
- * leave prior state untouched. Concurrent calls for the same selected accounts
65
- * and `vsCurrency` share one in-flight promise so the UI can treat the pending
66
- * promise as a loading signal. Calls for a different selection or fiat currency
67
- * start their own fetch and leave any prior poll running, so switching back can
68
- * join an in-flight fetch for that group and currency.
69
52
  */
70
53
  export declare class DeFiPositionsControllerV2 extends BaseController<typeof controllerName, DeFiPositionsControllerV2State, DeFiPositionsControllerV2Messenger> {
71
54
  #private;
@@ -85,25 +68,18 @@ export declare class DeFiPositionsControllerV2 extends BaseController<typeof con
85
68
  state?: Partial<DeFiPositionsControllerV2State>;
86
69
  });
87
70
  /**
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. No-ops when disabled or when the group
98
- * has no supported accounts. Caching / spam prevention is handled by the
99
- * apiClient TanStack Query cache (keyed by accounts + query options including
100
- * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the
101
- * first attempt (e.g. pull-to-refresh).
71
+ * Fetches DeFi positions for the selected account group. Each account key in
72
+ * a ready response replaces that account's state (other accounts stay).
73
+ * Accounts still indexing (`processingDefiPositions`) are skipped so prior
74
+ * state is kept for them. No-ops when disabled or when the group has no
75
+ * supported accounts. Caching / spam prevention is handled by the apiClient
76
+ * TanStack Query cache (keyed by accounts + query options including
77
+ * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache (e.g.
78
+ * pull-to-refresh).
102
79
  *
103
80
  * @param options - Optional fetch modifiers.
104
- * @param options.forceRefresh - When true, bypass the apiClient cache on the
105
- * first attempt and fetch immediately.
106
- * @returns Resolves when the fetch (and any processing polls) finish.
81
+ * @param options.forceRefresh - When true, bypass the apiClient cache and
82
+ * fetch immediately.
107
83
  */
108
84
  fetchDeFiPositions(options?: {
109
85
  forceRefresh?: boolean;
@@ -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;;;;;;;;;;;;;;;;;GAiBG;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;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,kBAAkB,CAAC,OAAO,CAAC,EAAE;QACjC,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC,IAAI,CAAC;CAwHlB"}
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;AAGrD,OAAO,KAAK,EAAE,sCAAsC,EAAE,4DAA2D;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,sCAAqC;AAG3E,QAAA,MAAM,cAAc,8BAA8B,CAAC;AAInD,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;;;;;;;;GAQG;AACH,qBAAa,yBAA0B,SAAQ,cAAc,CAC3D,OAAO,cAAc,EACrB,8BAA8B,EAC9B,kCAAkC,CACnC;;IAOC;;;;;;;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;;;;;;;;;;;;;OAaG;IACG,kBAAkB,CAAC,OAAO,CAAC,EAAE;QACjC,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC,IAAI,CAAC;CA8DlB"}
@@ -6,14 +6,6 @@ import type { Messenger } from "@metamask/messenger";
6
6
  import type { DeFiPositionsControllerV2MethodActions } from "./DeFiPositionsControllerV2-method-action-types.mjs";
7
7
  import type { DeFiPositionsByAccount } from "./group-defi-positions-v6.mjs";
8
8
  declare const controllerName = "DeFiPositionsControllerV2";
9
- /** Delay between polls while Accounts API reports DeFi indexing in progress. */
10
- export declare const DEFAULT_PROCESSING_POLL_INTERVAL_MS = 5000;
11
- /**
12
- * Maximum fetch attempts (including the first) while any account still has
13
- * `processingDefiPositions: true`. After this, the call resolves without
14
- * updating state, so prior positions are kept for every selected account.
15
- */
16
- export declare const DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS = 5;
17
9
  export type DeFiPositionsControllerV2State = {
18
10
  /**
19
11
  * DeFi positions keyed by internal MetaMask account ID (`InternalAccount.id`,
@@ -57,15 +49,6 @@ export type DeFiPositionsControllerV2Messenger = Messenger<typeof controllerName
57
49
  * Deduplication and freshness are handled by the shared TanStack Query cache on
58
50
  * {@link ApiPlatformClient} (balances default `staleTime` is 1 minute). Pass
59
51
  * `{ forceRefresh: true }` to bypass that cache for pull-to-refresh.
60
- *
61
- * When the API reports `processingDefiPositions` for any account, this
62
- * controller polls until indexing finishes or the attempt limit is reached,
63
- * and only then processes and writes state — mixed ready/processing responses
64
- * leave prior state untouched. Concurrent calls for the same selected accounts
65
- * and `vsCurrency` share one in-flight promise so the UI can treat the pending
66
- * promise as a loading signal. Calls for a different selection or fiat currency
67
- * start their own fetch and leave any prior poll running, so switching back can
68
- * join an in-flight fetch for that group and currency.
69
52
  */
70
53
  export declare class DeFiPositionsControllerV2 extends BaseController<typeof controllerName, DeFiPositionsControllerV2State, DeFiPositionsControllerV2Messenger> {
71
54
  #private;
@@ -85,25 +68,18 @@ export declare class DeFiPositionsControllerV2 extends BaseController<typeof con
85
68
  state?: Partial<DeFiPositionsControllerV2State>;
86
69
  });
87
70
  /**
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. No-ops when disabled or when the group
98
- * has no supported accounts. Caching / spam prevention is handled by the
99
- * apiClient TanStack Query cache (keyed by accounts + query options including
100
- * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the
101
- * first attempt (e.g. pull-to-refresh).
71
+ * Fetches DeFi positions for the selected account group. Each account key in
72
+ * a ready response replaces that account's state (other accounts stay).
73
+ * Accounts still indexing (`processingDefiPositions`) are skipped so prior
74
+ * state is kept for them. No-ops when disabled or when the group has no
75
+ * supported accounts. Caching / spam prevention is handled by the apiClient
76
+ * TanStack Query cache (keyed by accounts + query options including
77
+ * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache (e.g.
78
+ * pull-to-refresh).
102
79
  *
103
80
  * @param options - Optional fetch modifiers.
104
- * @param options.forceRefresh - When true, bypass the apiClient cache on the
105
- * first attempt and fetch immediately.
106
- * @returns Resolves when the fetch (and any processing polls) finish.
81
+ * @param options.forceRefresh - When true, bypass the apiClient cache and
82
+ * fetch immediately.
107
83
  */
108
84
  fetchDeFiPositions(options?: {
109
85
  forceRefresh?: boolean;
@@ -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;;;;;;;;;;;;;;;;;GAiBG;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;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,kBAAkB,CAAC,OAAO,CAAC,EAAE;QACjC,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC,IAAI,CAAC;CAwHlB"}
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;AAGrD,OAAO,KAAK,EAAE,sCAAsC,EAAE,4DAA2D;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,sCAAqC;AAG3E,QAAA,MAAM,cAAc,8BAA8B,CAAC;AAInD,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;;;;;;;;GAQG;AACH,qBAAa,yBAA0B,SAAQ,cAAc,CAC3D,OAAO,cAAc,EACrB,8BAA8B,EAC9B,kCAAkC,CACnC;;IAOC;;;;;;;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;;;;;;;;;;;;;OAaG;IACG,kBAAkB,CAAC,OAAO,CAAC,EAAE;QACjC,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,OAAO,CAAC,IAAI,CAAC;CA8DlB"}
@@ -9,27 +9,12 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _DeFiPositionsControllerV2_instances, _DeFiPositionsControllerV2_apiClient, _DeFiPositionsControllerV2_isEnabled, _DeFiPositionsControllerV2_getVsCurrency, _DeFiPositionsControllerV2_inFlightFetches, _DeFiPositionsControllerV2_fetchDeFiPositions;
12
+ var _DeFiPositionsControllerV2_apiClient, _DeFiPositionsControllerV2_isEnabled, _DeFiPositionsControllerV2_getVsCurrency;
13
13
  import { BaseController } from "@metamask/base-controller";
14
14
  import { buildDeFiBalancesQuery } from "./build-defi-balances-query.mjs";
15
15
  import { groupDeFiPositionsV6 } from "./group-defi-positions-v6.mjs";
16
16
  const controllerName = 'DeFiPositionsControllerV2';
17
17
  const MESSENGER_EXPOSED_METHODS = ['fetchDeFiPositions'];
18
- /** Delay between polls while Accounts API reports DeFi indexing in progress. */
19
- export const DEFAULT_PROCESSING_POLL_INTERVAL_MS = 5000;
20
- /**
21
- * Maximum fetch attempts (including the first) while any account still has
22
- * `processingDefiPositions: true`. After this, the call resolves without
23
- * updating state, so prior positions are kept for every selected account.
24
- */
25
- export const DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS = 5;
26
- /**
27
- * @param ms - Milliseconds to wait.
28
- * @returns A promise that resolves after `ms`.
29
- */
30
- function delay(ms) {
31
- return new Promise((resolve) => setTimeout(resolve, ms));
32
- }
33
18
  const controllerMetadata = {
34
19
  allDeFiPositionsV2: {
35
20
  includeInStateLogs: false,
@@ -51,15 +36,6 @@ export const getDefaultDeFiPositionsControllerV2State = () => {
51
36
  * Deduplication and freshness are handled by the shared TanStack Query cache on
52
37
  * {@link ApiPlatformClient} (balances default `staleTime` is 1 minute). Pass
53
38
  * `{ forceRefresh: true }` to bypass that cache for pull-to-refresh.
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.
63
39
  */
64
40
  export class DeFiPositionsControllerV2 extends BaseController {
65
41
  /**
@@ -80,117 +56,68 @@ export class DeFiPositionsControllerV2 extends BaseController {
80
56
  ...state,
81
57
  },
82
58
  });
83
- _DeFiPositionsControllerV2_instances.add(this);
84
59
  _DeFiPositionsControllerV2_apiClient.set(this, void 0);
85
60
  _DeFiPositionsControllerV2_isEnabled.set(this, void 0);
86
61
  _DeFiPositionsControllerV2_getVsCurrency.set(this, void 0);
87
- /**
88
- * In-flight fetches keyed by selected DeFi-queryable account IDs plus
89
- * `vsCurrency`. Concurrent callers for the same selection and currency share
90
- * a promise; different selections or fiat currencies keep independent
91
- * fetches so fast switching can join an earlier matching poll.
92
- */
93
- _DeFiPositionsControllerV2_inFlightFetches.set(this, new Map());
94
62
  __classPrivateFieldSet(this, _DeFiPositionsControllerV2_apiClient, apiClient, "f");
95
63
  __classPrivateFieldSet(this, _DeFiPositionsControllerV2_isEnabled, isEnabled, "f");
96
64
  __classPrivateFieldSet(this, _DeFiPositionsControllerV2_getVsCurrency, getVsCurrency, "f");
97
65
  this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
98
66
  }
99
67
  /**
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. No-ops when disabled or when the group
110
- * has no supported accounts. Caching / spam prevention is handled by the
111
- * apiClient TanStack Query cache (keyed by accounts + query options including
112
- * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the
113
- * first attempt (e.g. pull-to-refresh).
68
+ * Fetches DeFi positions for the selected account group. Each account key in
69
+ * a ready response replaces that account's state (other accounts stay).
70
+ * Accounts still indexing (`processingDefiPositions`) are skipped so prior
71
+ * state is kept for them. No-ops when disabled or when the group has no
72
+ * supported accounts. Caching / spam prevention is handled by the apiClient
73
+ * TanStack Query cache (keyed by accounts + query options including
74
+ * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache (e.g.
75
+ * pull-to-refresh).
114
76
  *
115
77
  * @param options - Optional fetch modifiers.
116
- * @param options.forceRefresh - When true, bypass the apiClient cache on the
117
- * first attempt and fetch immediately.
118
- * @returns Resolves when the fetch (and any processing polls) finish.
78
+ * @param options.forceRefresh - When true, bypass the apiClient cache and
79
+ * fetch immediately.
119
80
  */
120
81
  async fetchDeFiPositions(options) {
121
82
  if (!__classPrivateFieldGet(this, _DeFiPositionsControllerV2_isEnabled, "f").call(this)) {
122
83
  return;
123
84
  }
124
85
  const selectedAccounts = this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
125
- const query = buildDeFiBalancesQuery(selectedAccounts);
126
- const vsCurrency = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_getVsCurrency, "f").call(this).toLowerCase();
127
- // Include vsCurrency so a fiat change does not join a poll priced in the
128
- // previous currency (TanStack also keys the balances cache on vsCurrency).
129
- const inFlightKey = [
130
- ...[...query.internalAccountIdByCaip.keys()].sort(),
131
- vsCurrency,
132
- ].join('\0');
133
- const existing = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").get(inFlightKey);
134
- if (existing) {
135
- await existing;
86
+ const { networks, internalAccountIdByCaip } = buildDeFiBalancesQuery(selectedAccounts);
87
+ if (internalAccountIdByCaip.size === 0 || networks.length === 0) {
136
88
  return;
137
89
  }
138
- // Same-key callers join this promise instead of replacing it, so cleanup
139
- // can always delete without checking identity.
140
- const fetchPromise = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_instances, "m", _DeFiPositionsControllerV2_fetchDeFiPositions).call(this, options, query, vsCurrency).finally(() => {
141
- __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").delete(inFlightKey);
142
- });
143
- __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").set(inFlightKey, fetchPromise);
144
- await fetchPromise;
145
- }
146
- }
147
- _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) {
148
- if (internalAccountIdByCaip.size === 0 || networks.length === 0) {
149
- return;
150
- }
151
- const accountIds = [...internalAccountIdByCaip.keys()];
152
- const queryOptions = {
153
- networks,
154
- includeDeFiBalances: true,
155
- forceFetchDeFiPositions: true,
156
- includePrices: true,
157
- vsCurrency,
158
- };
159
- for (let attempt = 0; attempt < DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS; attempt++) {
160
- // First attempt respects forceRefresh; later polls always bypass cache
161
- // so we do not spin on a stale processing snapshot.
162
- const fetchOptions = {
163
- ...(options?.forceRefresh || attempt > 0 ? { staleTime: 0 } : {}),
164
- };
90
+ const accountIds = [...internalAccountIdByCaip.keys()];
91
+ const vsCurrency = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_getVsCurrency, "f").call(this).toLowerCase();
165
92
  try {
166
- const response = await __classPrivateFieldGet(this, _DeFiPositionsControllerV2_apiClient, "f").accounts.fetchV6MultiAccountBalances(accountIds, queryOptions, fetchOptions);
167
- const stillProcessing = response.accounts.some((account) => account.processingDefiPositions);
168
- // Only process and write when every account is ready so a partial
169
- // response cannot clear or overwrite positions for accounts that are
170
- // still indexing.
171
- if (!stillProcessing) {
172
- const positionsByAccount = groupDeFiPositionsV6(response, internalAccountIdByCaip);
173
- this.update((state) => {
174
- for (const [accountId, positions] of Object.entries(positionsByAccount)) {
175
- state.allDeFiPositionsV2[accountId] = positions;
176
- }
177
- });
178
- return;
179
- }
180
- const { queryKey } = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_apiClient, "f").accounts.getV6MultiAccountBalancesQueryOptions(accountIds, queryOptions, fetchOptions);
181
- await __classPrivateFieldGet(this, _DeFiPositionsControllerV2_apiClient, "f").accounts.queryClient.invalidateQueries({
182
- queryKey,
93
+ const response = await __classPrivateFieldGet(this, _DeFiPositionsControllerV2_apiClient, "f").accounts.fetchV6MultiAccountBalances(accountIds, {
94
+ networks,
95
+ includeDeFiBalances: true,
96
+ forceFetchDeFiPositions: true,
97
+ includePrices: true,
98
+ vsCurrency,
99
+ }, {
100
+ // staleTime: 0 makes TanStack treat the cache as stale for this call.
101
+ ...(options?.forceRefresh ? { staleTime: 0 } : {}),
183
102
  });
184
- const isLastAttempt = attempt >= DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS - 1;
185
- if (isLastAttempt) {
103
+ // Skip accounts still indexing — their balances are not a valid snapshot.
104
+ const readyAccounts = response.accounts.filter((account) => !account.processingDefiPositions);
105
+ if (readyAccounts.length === 0) {
186
106
  return;
187
107
  }
188
- await delay(DEFAULT_PROCESSING_POLL_INTERVAL_MS);
108
+ const positionsByAccount = groupDeFiPositionsV6({ ...response, accounts: readyAccounts }, internalAccountIdByCaip);
109
+ // Last valid response wins per ready account; processing / other accounts
110
+ // stay untouched.
111
+ this.update((state) => {
112
+ for (const [accountId, positions] of Object.entries(positionsByAccount)) {
113
+ state.allDeFiPositionsV2[accountId] = positions;
114
+ }
115
+ });
189
116
  }
190
117
  catch (error) {
191
118
  console.error('Failed to fetch DeFi positions', error);
192
- return;
193
119
  }
194
120
  }
195
- };
121
+ }
122
+ _DeFiPositionsControllerV2_apiClient = new WeakMap(), _DeFiPositionsControllerV2_isEnabled = new WeakMap(), _DeFiPositionsControllerV2_getVsCurrency = new WeakMap();
196
123
  //# sourceMappingURL=DeFiPositionsControllerV2.mjs.map
@@ -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;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,yBAA0B,SAAQ,cAI9C;IAeC;;;;;;;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;;QA3CI,uDAA8B;QAE9B,uDAA0B;QAE1B,2DAA6B;QAEtC;;;;;WAKG;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;;;;;;;;;;;;;;;;;;;;OAoBG;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,UAAU,GAAG,uBAAA,IAAI,gDAAe,MAAnB,IAAI,CAAiB,CAAC,WAAW,EAAE,CAAC;QACvD,yEAAyE;QACzE,2EAA2E;QAC3E,MAAM,WAAW,GAAG;YAClB,GAAG,CAAC,GAAG,KAAK,CAAC,uBAAuB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;YACnD,UAAU;SACX,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEb,MAAM,QAAQ,GAAG,uBAAA,IAAI,kDAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACxD,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,QAAQ,CAAC;YACf,OAAO;QACT,CAAC;QAED,yEAAyE;QACzE,+CAA+C;QAC/C,MAAM,YAAY,GAAG,uBAAA,IAAI,2FAAoB,MAAxB,IAAI,EACvB,OAAO,EACP,KAAK,EACL,UAAU,CACX,CAAC,OAAO,CAAC,GAAG,EAAE;YACb,uBAAA,IAAI,kDAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QACH,uBAAA,IAAI,kDAAiB,CAAC,GAAG,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;QAErD,MAAM,YAAY,CAAC;IACrB,CAAC;CAqFF;wUAnFC,KAAK,wDACH,OAA+C,EAC/C,EAAE,QAAQ,EAAE,uBAAuB,EAAqB,EACxD,UAAkB;IAElB,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,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,eAAe,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAC5C,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,uBAAuB,CAC7C,CAAC;YAEF,kEAAkE;YAClE,qEAAqE;YACrE,kBAAkB;YAClB,IAAI,CAAC,eAAe,EAAE,CAAC;gBACrB,MAAM,kBAAkB,GAAG,oBAAoB,CAC7C,QAAQ,EACR,uBAAuB,CACxB,CAAC;gBAEF,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;gBACH,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 without\n * updating state, so prior positions are kept for every selected account.\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 * 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 */\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. No-ops when disabled or when the group\n * has no supported accounts. Caching / spam prevention is handled by the\n * apiClient 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 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 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 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 { 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\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 */\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 * @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`) are skipped so prior\n * state is kept for 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 (e.g.\n * pull-to-refresh).\n *\n * @param options - Optional fetch modifiers.\n * @param options.forceRefresh - When true, bypass the apiClient cache and\n * fetch immediately.\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\n const { networks, internalAccountIdByCaip } =\n buildDeFiBalancesQuery(selectedAccounts);\n\n if (internalAccountIdByCaip.size === 0 || networks.length === 0) {\n return;\n }\n\n const accountIds = [...internalAccountIdByCaip.keys()];\n const vsCurrency = this.#getVsCurrency().toLowerCase();\n\n try {\n const response =\n await this.#apiClient.accounts.fetchV6MultiAccountBalances(\n accountIds,\n {\n networks,\n includeDeFiBalances: true,\n forceFetchDeFiPositions: true,\n includePrices: true,\n vsCurrency,\n },\n {\n // staleTime: 0 makes TanStack treat the cache as stale for this call.\n ...(options?.forceRefresh ? { staleTime: 0 } : {}),\n },\n );\n\n // Skip accounts still indexing — their balances are not a valid snapshot.\n const readyAccounts = response.accounts.filter(\n (account) => !account.processingDefiPositions,\n );\n if (readyAccounts.length === 0) {\n return;\n }\n\n const positionsByAccount = groupDeFiPositionsV6(\n { ...response, accounts: readyAccounts },\n internalAccountIdByCaip,\n );\n\n // Last valid response wins per ready account; processing / other 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 } catch (error) {\n console.error('Failed to fetch DeFi positions', error);\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/assets-controllers",
3
- "version": "110.1.0-preview-beea66146",
3
+ "version": "110.1.0-preview-0efc7ef01",
4
4
  "description": "Controllers which manage interactions involving ERC-20, ERC-721, and ERC-1155 tokens (including NFTs)",
5
5
  "keywords": [
6
6
  "Ethereum",