@metamask-previews/assets-controllers 109.4.1-preview-30b493585 → 109.4.1-preview-8abc30aca

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.cjs +7 -0
  3. package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.cjs.map +1 -0
  4. package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.d.cts +28 -0
  5. package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.d.cts.map +1 -0
  6. package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.d.mts +28 -0
  7. package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.d.mts.map +1 -0
  8. package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.mjs +6 -0
  9. package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.mjs.map +1 -0
  10. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.cjs +128 -0
  11. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.cjs.map +1 -0
  12. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.d.cts +89 -0
  13. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.d.cts.map +1 -0
  14. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.d.mts +89 -0
  15. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.d.mts.map +1 -0
  16. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.mjs +123 -0
  17. package/dist/DeFiPositionsController/DeFiPositionsControllerV2.mjs.map +1 -0
  18. package/dist/DeFiPositionsController/build-defi-balances-query.cjs +73 -0
  19. package/dist/DeFiPositionsController/build-defi-balances-query.cjs.map +1 -0
  20. package/dist/DeFiPositionsController/build-defi-balances-query.d.cts +34 -0
  21. package/dist/DeFiPositionsController/build-defi-balances-query.d.cts.map +1 -0
  22. package/dist/DeFiPositionsController/build-defi-balances-query.d.mts +34 -0
  23. package/dist/DeFiPositionsController/build-defi-balances-query.d.mts.map +1 -0
  24. package/dist/DeFiPositionsController/build-defi-balances-query.mjs +69 -0
  25. package/dist/DeFiPositionsController/build-defi-balances-query.mjs.map +1 -0
  26. package/dist/DeFiPositionsController/group-defi-positions-v6.cjs +235 -0
  27. package/dist/DeFiPositionsController/group-defi-positions-v6.cjs.map +1 -0
  28. package/dist/DeFiPositionsController/group-defi-positions-v6.d.cts +117 -0
  29. package/dist/DeFiPositionsController/group-defi-positions-v6.d.cts.map +1 -0
  30. package/dist/DeFiPositionsController/group-defi-positions-v6.d.mts +117 -0
  31. package/dist/DeFiPositionsController/group-defi-positions-v6.d.mts.map +1 -0
  32. package/dist/DeFiPositionsController/group-defi-positions-v6.mjs +231 -0
  33. package/dist/DeFiPositionsController/group-defi-positions-v6.mjs.map +1 -0
  34. package/dist/DeFiPositionsController/merge-positions-for-accounts.cjs +71 -0
  35. package/dist/DeFiPositionsController/merge-positions-for-accounts.cjs.map +1 -0
  36. package/dist/DeFiPositionsController/merge-positions-for-accounts.d.cts +16 -0
  37. package/dist/DeFiPositionsController/merge-positions-for-accounts.d.cts.map +1 -0
  38. package/dist/DeFiPositionsController/merge-positions-for-accounts.d.mts +16 -0
  39. package/dist/DeFiPositionsController/merge-positions-for-accounts.d.mts.map +1 -0
  40. package/dist/DeFiPositionsController/merge-positions-for-accounts.mjs +67 -0
  41. package/dist/DeFiPositionsController/merge-positions-for-accounts.mjs.map +1 -0
  42. package/dist/index.cjs +9 -1
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +6 -0
  45. package/dist/index.d.cts.map +1 -1
  46. package/dist/index.d.mts +6 -0
  47. package/dist/index.d.mts.map +1 -1
  48. package/dist/index.mjs +3 -0
  49. package/dist/index.mjs.map +1 -1
  50. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Added
11
11
 
12
+ - Add `DeFiPositionsControllerV2`, which fetches DeFi positions from the Accounts API v6 multiaccount balances endpoint and stores them in a client-ready shape under `allDeFiPositionsV2` ([#9503](https://github.com/MetaMask/core/pull/9503))
13
+ - Export `DeFiPositionsControllerV2` and supporting types
12
14
  - Add `isDeprecated` option to `TokenDetectionController` constructor ([#9362](https://github.com/MetaMask/core/pull/9362))
13
15
  - When `isDeprecated()` returns `true`, no network requests are sent and entry points bail early at `start`, `detectTokens`, `_executePoll`, `addDetectedTokensViaWs`, and `addDetectedTokensViaPolling`, so no token detection work runs while the controller is disabled.
14
16
  - The function is re-evaluated on each entry point so it can be toggled at runtime without reconstructing the controller.
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * This file is auto generated.
4
+ * Do not edit manually.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=DeFiPositionsControllerV2-method-action-types.cjs.map
@@ -0,0 +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';\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"]}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * This file is auto generated.
3
+ * Do not edit manually.
4
+ */
5
+ import type { DeFiPositionsControllerV2 } from "./DeFiPositionsControllerV2.cjs";
6
+ /**
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).
15
+ *
16
+ * @param options - Optional fetch modifiers.
17
+ * @param options.forceRefresh - When true, bypass the apiClient cache and
18
+ * fetch immediately.
19
+ */
20
+ export type DeFiPositionsControllerV2FetchDeFiPositionsAction = {
21
+ type: `DeFiPositionsControllerV2:fetchDeFiPositions`;
22
+ handler: DeFiPositionsControllerV2['fetchDeFiPositions'];
23
+ };
24
+ /**
25
+ * Union of all DeFiPositionsControllerV2 action types.
26
+ */
27
+ export type DeFiPositionsControllerV2MethodActions = DeFiPositionsControllerV2FetchDeFiPositionsAction;
28
+ //# sourceMappingURL=DeFiPositionsControllerV2-method-action-types.d.cts.map
@@ -0,0 +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,wCAAoC;AAE7E;;;;;;;;;;;;;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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * This file is auto generated.
3
+ * Do not edit manually.
4
+ */
5
+ import type { DeFiPositionsControllerV2 } from "./DeFiPositionsControllerV2.mjs";
6
+ /**
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).
15
+ *
16
+ * @param options - Optional fetch modifiers.
17
+ * @param options.forceRefresh - When true, bypass the apiClient cache and
18
+ * fetch immediately.
19
+ */
20
+ export type DeFiPositionsControllerV2FetchDeFiPositionsAction = {
21
+ type: `DeFiPositionsControllerV2:fetchDeFiPositions`;
22
+ handler: DeFiPositionsControllerV2['fetchDeFiPositions'];
23
+ };
24
+ /**
25
+ * Union of all DeFiPositionsControllerV2 action types.
26
+ */
27
+ export type DeFiPositionsControllerV2MethodActions = DeFiPositionsControllerV2FetchDeFiPositionsAction;
28
+ //# sourceMappingURL=DeFiPositionsControllerV2-method-action-types.d.mts.map
@@ -0,0 +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,wCAAoC;AAE7E;;;;;;;;;;;;;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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * This file is auto generated.
3
+ * Do not edit manually.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=DeFiPositionsControllerV2-method-action-types.mjs.map
@@ -0,0 +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';\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"]}
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
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
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _DeFiPositionsControllerV2_apiClient, _DeFiPositionsControllerV2_isEnabled, _DeFiPositionsControllerV2_getVsCurrency;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.DeFiPositionsControllerV2 = exports.getDefaultDeFiPositionsControllerV2State = void 0;
16
+ const base_controller_1 = require("@metamask/base-controller");
17
+ const build_defi_balances_query_1 = require("./build-defi-balances-query.cjs");
18
+ const group_defi_positions_v6_1 = require("./group-defi-positions-v6.cjs");
19
+ const controllerName = 'DeFiPositionsControllerV2';
20
+ const MESSENGER_EXPOSED_METHODS = ['fetchDeFiPositions'];
21
+ const controllerMetadata = {
22
+ allDeFiPositionsV2: {
23
+ includeInStateLogs: false,
24
+ persist: true,
25
+ includeInDebugSnapshot: false,
26
+ usedInUi: true,
27
+ },
28
+ };
29
+ const getDefaultDeFiPositionsControllerV2State = () => {
30
+ return {
31
+ allDeFiPositionsV2: {},
32
+ };
33
+ };
34
+ exports.getDefaultDeFiPositionsControllerV2State = getDefaultDeFiPositionsControllerV2State;
35
+ /**
36
+ * Controller that fetches DeFi positions for the selected account group from
37
+ * the Accounts API (v6 multiaccount balances) and stores them in the shape the
38
+ * client consumes directly.
39
+ *
40
+ * Deduplication and freshness are handled by the shared TanStack Query cache on
41
+ * {@link ApiPlatformClient} (balances default `staleTime` is 1 minute). Pass
42
+ * `{ forceRefresh: true }` to bypass that cache for pull-to-refresh.
43
+ */
44
+ class DeFiPositionsControllerV2 extends base_controller_1.BaseController {
45
+ /**
46
+ * @param options - Constructor options.
47
+ * @param options.messenger - The controller messenger.
48
+ * @param options.apiClient - Accounts API client used to fetch balances/positions. Auth is handled by the client.
49
+ * @param options.isEnabled - Returns whether fetching is enabled (default: () => false).
50
+ * @param options.getVsCurrency - Returns the fiat currency for prices (default: () => 'usd').
51
+ * @param options.state - Initial controller state.
52
+ */
53
+ constructor({ messenger, apiClient, isEnabled, getVsCurrency, state, }) {
54
+ super({
55
+ name: controllerName,
56
+ metadata: controllerMetadata,
57
+ messenger,
58
+ state: {
59
+ ...(0, exports.getDefaultDeFiPositionsControllerV2State)(),
60
+ ...state,
61
+ },
62
+ });
63
+ _DeFiPositionsControllerV2_apiClient.set(this, void 0);
64
+ _DeFiPositionsControllerV2_isEnabled.set(this, void 0);
65
+ _DeFiPositionsControllerV2_getVsCurrency.set(this, void 0);
66
+ __classPrivateFieldSet(this, _DeFiPositionsControllerV2_apiClient, apiClient, "f");
67
+ __classPrivateFieldSet(this, _DeFiPositionsControllerV2_isEnabled, isEnabled, "f");
68
+ __classPrivateFieldSet(this, _DeFiPositionsControllerV2_getVsCurrency, getVsCurrency, "f");
69
+ this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
70
+ }
71
+ /**
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).
80
+ *
81
+ * @param options - Optional fetch modifiers.
82
+ * @param options.forceRefresh - When true, bypass the apiClient cache and
83
+ * fetch immediately.
84
+ */
85
+ async fetchDeFiPositions(options) {
86
+ if (!__classPrivateFieldGet(this, _DeFiPositionsControllerV2_isEnabled, "f").call(this)) {
87
+ return;
88
+ }
89
+ const selectedAccounts = this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
90
+ const { networks, internalAccountIdByCaip } = (0, build_defi_balances_query_1.buildDeFiBalancesQuery)(selectedAccounts);
91
+ if (internalAccountIdByCaip.size === 0 || networks.length === 0) {
92
+ return;
93
+ }
94
+ const accountIds = [...internalAccountIdByCaip.keys()];
95
+ const vsCurrency = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_getVsCurrency, "f").call(this).toLowerCase();
96
+ try {
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 } : {}),
106
+ });
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) {
110
+ return;
111
+ }
112
+ const positionsByAccount = (0, group_defi_positions_v6_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
+ });
120
+ }
121
+ catch (error) {
122
+ console.error('Failed to fetch DeFi positions', error);
123
+ }
124
+ }
125
+ }
126
+ exports.DeFiPositionsControllerV2 = DeFiPositionsControllerV2;
127
+ _DeFiPositionsControllerV2_apiClient = new WeakMap(), _DeFiPositionsControllerV2_isEnabled = new WeakMap(), _DeFiPositionsControllerV2_getVsCurrency = new WeakMap();
128
+ //# sourceMappingURL=DeFiPositionsControllerV2.cjs.map
@@ -0,0 +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';\nimport type { DeFiPositionsControllerV2MethodActions } from './DeFiPositionsControllerV2-method-action-types';\nimport type { DeFiPositionsByAccount } from './group-defi-positions-v6';\nimport { groupDeFiPositionsV6 } from './group-defi-positions-v6';\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"]}
@@ -0,0 +1,89 @@
1
+ import type { AccountTreeControllerGetAccountsFromSelectedAccountGroupAction } from "@metamask/account-tree-controller";
2
+ import { BaseController } from "@metamask/base-controller";
3
+ import type { ControllerGetStateAction, ControllerStateChangedEvent } from "@metamask/base-controller";
4
+ import type { ApiPlatformClient } from "@metamask/core-backend";
5
+ import type { Messenger } from "@metamask/messenger";
6
+ import type { DeFiPositionsControllerV2MethodActions } from "./DeFiPositionsControllerV2-method-action-types.cjs";
7
+ import type { DeFiPositionsByAccount } from "./group-defi-positions-v6.cjs";
8
+ declare const controllerName = "DeFiPositionsControllerV2";
9
+ export type DeFiPositionsControllerV2State = {
10
+ /**
11
+ * DeFi positions keyed by internal MetaMask account ID (`InternalAccount.id`,
12
+ * the same key AssetsController uses). Each account maps to a flat list of
13
+ * protocol groups shown in the DeFi tab, each carrying its own `chainId` for
14
+ * filtering plus the details-page sections embedded inside it. This is
15
+ * exactly the shape the client consumes, so no further transformation is
16
+ * needed on read.
17
+ *
18
+ * Named `allDeFiPositionsV2` (rather than `allDeFiPositions`) so it can live
19
+ * alongside the legacy `DeFiPositionsController` in clients that flatten every
20
+ * controller's state into a single object (e.g. the extension background),
21
+ * without colliding on the shared `allDeFiPositions` key.
22
+ */
23
+ allDeFiPositionsV2: DeFiPositionsByAccount;
24
+ };
25
+ export declare const getDefaultDeFiPositionsControllerV2State: () => DeFiPositionsControllerV2State;
26
+ export type DeFiPositionsControllerV2GetStateAction = ControllerGetStateAction<typeof controllerName, DeFiPositionsControllerV2State>;
27
+ export type DeFiPositionsControllerV2Actions = DeFiPositionsControllerV2GetStateAction | DeFiPositionsControllerV2MethodActions;
28
+ export type DeFiPositionsControllerV2StateChangedEvent = ControllerStateChangedEvent<typeof controllerName, DeFiPositionsControllerV2State>;
29
+ export type DeFiPositionsControllerV2Events = DeFiPositionsControllerV2StateChangedEvent;
30
+ /**
31
+ * The external actions available to the {@link DeFiPositionsControllerV2}.
32
+ */
33
+ export type AllowedActions = AccountTreeControllerGetAccountsFromSelectedAccountGroupAction;
34
+ /**
35
+ * The external events available to the {@link DeFiPositionsControllerV2}.
36
+ *
37
+ * None yet — clients must call `fetchDeFiPositions` (and optionally
38
+ * `{ forceRefresh: true }`) on their own triggers. Likely future subscriptions:
39
+ * `AccountTreeController:selectedAccountGroupChange`,
40
+ * `TransactionController:transactionConfirmed`, and `KeyringController:lock`.
41
+ */
42
+ export type AllowedEvents = never;
43
+ export type DeFiPositionsControllerV2Messenger = Messenger<typeof controllerName, DeFiPositionsControllerV2Actions | AllowedActions, DeFiPositionsControllerV2Events | AllowedEvents>;
44
+ /**
45
+ * Controller that fetches DeFi positions for the selected account group from
46
+ * the Accounts API (v6 multiaccount balances) and stores them in the shape the
47
+ * client consumes directly.
48
+ *
49
+ * Deduplication and freshness are handled by the shared TanStack Query cache on
50
+ * {@link ApiPlatformClient} (balances default `staleTime` is 1 minute). Pass
51
+ * `{ forceRefresh: true }` to bypass that cache for pull-to-refresh.
52
+ */
53
+ export declare class DeFiPositionsControllerV2 extends BaseController<typeof controllerName, DeFiPositionsControllerV2State, DeFiPositionsControllerV2Messenger> {
54
+ #private;
55
+ /**
56
+ * @param options - Constructor options.
57
+ * @param options.messenger - The controller messenger.
58
+ * @param options.apiClient - Accounts API client used to fetch balances/positions. Auth is handled by the client.
59
+ * @param options.isEnabled - Returns whether fetching is enabled (default: () => false).
60
+ * @param options.getVsCurrency - Returns the fiat currency for prices (default: () => 'usd').
61
+ * @param options.state - Initial controller state.
62
+ */
63
+ constructor({ messenger, apiClient, isEnabled, getVsCurrency, state, }: {
64
+ messenger: DeFiPositionsControllerV2Messenger;
65
+ apiClient: ApiPlatformClient;
66
+ isEnabled: () => boolean;
67
+ getVsCurrency: () => string;
68
+ state?: Partial<DeFiPositionsControllerV2State>;
69
+ });
70
+ /**
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).
79
+ *
80
+ * @param options - Optional fetch modifiers.
81
+ * @param options.forceRefresh - When true, bypass the apiClient cache and
82
+ * fetch immediately.
83
+ */
84
+ fetchDeFiPositions(options?: {
85
+ forceRefresh?: boolean;
86
+ }): Promise<void>;
87
+ }
88
+ export {};
89
+ //# sourceMappingURL=DeFiPositionsControllerV2.d.cts.map
@@ -0,0 +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;AAGrD,OAAO,KAAK,EAAE,sCAAsC,EAAE,4DAAwD;AAC9G,OAAO,KAAK,EAAE,sBAAsB,EAAE,sCAAkC;AAGxE,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"}
@@ -0,0 +1,89 @@
1
+ import type { AccountTreeControllerGetAccountsFromSelectedAccountGroupAction } from "@metamask/account-tree-controller";
2
+ import { BaseController } from "@metamask/base-controller";
3
+ import type { ControllerGetStateAction, ControllerStateChangedEvent } from "@metamask/base-controller";
4
+ import type { ApiPlatformClient } from "@metamask/core-backend";
5
+ import type { Messenger } from "@metamask/messenger";
6
+ import type { DeFiPositionsControllerV2MethodActions } from "./DeFiPositionsControllerV2-method-action-types.mjs";
7
+ import type { DeFiPositionsByAccount } from "./group-defi-positions-v6.mjs";
8
+ declare const controllerName = "DeFiPositionsControllerV2";
9
+ export type DeFiPositionsControllerV2State = {
10
+ /**
11
+ * DeFi positions keyed by internal MetaMask account ID (`InternalAccount.id`,
12
+ * the same key AssetsController uses). Each account maps to a flat list of
13
+ * protocol groups shown in the DeFi tab, each carrying its own `chainId` for
14
+ * filtering plus the details-page sections embedded inside it. This is
15
+ * exactly the shape the client consumes, so no further transformation is
16
+ * needed on read.
17
+ *
18
+ * Named `allDeFiPositionsV2` (rather than `allDeFiPositions`) so it can live
19
+ * alongside the legacy `DeFiPositionsController` in clients that flatten every
20
+ * controller's state into a single object (e.g. the extension background),
21
+ * without colliding on the shared `allDeFiPositions` key.
22
+ */
23
+ allDeFiPositionsV2: DeFiPositionsByAccount;
24
+ };
25
+ export declare const getDefaultDeFiPositionsControllerV2State: () => DeFiPositionsControllerV2State;
26
+ export type DeFiPositionsControllerV2GetStateAction = ControllerGetStateAction<typeof controllerName, DeFiPositionsControllerV2State>;
27
+ export type DeFiPositionsControllerV2Actions = DeFiPositionsControllerV2GetStateAction | DeFiPositionsControllerV2MethodActions;
28
+ export type DeFiPositionsControllerV2StateChangedEvent = ControllerStateChangedEvent<typeof controllerName, DeFiPositionsControllerV2State>;
29
+ export type DeFiPositionsControllerV2Events = DeFiPositionsControllerV2StateChangedEvent;
30
+ /**
31
+ * The external actions available to the {@link DeFiPositionsControllerV2}.
32
+ */
33
+ export type AllowedActions = AccountTreeControllerGetAccountsFromSelectedAccountGroupAction;
34
+ /**
35
+ * The external events available to the {@link DeFiPositionsControllerV2}.
36
+ *
37
+ * None yet — clients must call `fetchDeFiPositions` (and optionally
38
+ * `{ forceRefresh: true }`) on their own triggers. Likely future subscriptions:
39
+ * `AccountTreeController:selectedAccountGroupChange`,
40
+ * `TransactionController:transactionConfirmed`, and `KeyringController:lock`.
41
+ */
42
+ export type AllowedEvents = never;
43
+ export type DeFiPositionsControllerV2Messenger = Messenger<typeof controllerName, DeFiPositionsControllerV2Actions | AllowedActions, DeFiPositionsControllerV2Events | AllowedEvents>;
44
+ /**
45
+ * Controller that fetches DeFi positions for the selected account group from
46
+ * the Accounts API (v6 multiaccount balances) and stores them in the shape the
47
+ * client consumes directly.
48
+ *
49
+ * Deduplication and freshness are handled by the shared TanStack Query cache on
50
+ * {@link ApiPlatformClient} (balances default `staleTime` is 1 minute). Pass
51
+ * `{ forceRefresh: true }` to bypass that cache for pull-to-refresh.
52
+ */
53
+ export declare class DeFiPositionsControllerV2 extends BaseController<typeof controllerName, DeFiPositionsControllerV2State, DeFiPositionsControllerV2Messenger> {
54
+ #private;
55
+ /**
56
+ * @param options - Constructor options.
57
+ * @param options.messenger - The controller messenger.
58
+ * @param options.apiClient - Accounts API client used to fetch balances/positions. Auth is handled by the client.
59
+ * @param options.isEnabled - Returns whether fetching is enabled (default: () => false).
60
+ * @param options.getVsCurrency - Returns the fiat currency for prices (default: () => 'usd').
61
+ * @param options.state - Initial controller state.
62
+ */
63
+ constructor({ messenger, apiClient, isEnabled, getVsCurrency, state, }: {
64
+ messenger: DeFiPositionsControllerV2Messenger;
65
+ apiClient: ApiPlatformClient;
66
+ isEnabled: () => boolean;
67
+ getVsCurrency: () => string;
68
+ state?: Partial<DeFiPositionsControllerV2State>;
69
+ });
70
+ /**
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).
79
+ *
80
+ * @param options - Optional fetch modifiers.
81
+ * @param options.forceRefresh - When true, bypass the apiClient cache and
82
+ * fetch immediately.
83
+ */
84
+ fetchDeFiPositions(options?: {
85
+ forceRefresh?: boolean;
86
+ }): Promise<void>;
87
+ }
88
+ export {};
89
+ //# sourceMappingURL=DeFiPositionsControllerV2.d.mts.map
@@ -0,0 +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;AAGrD,OAAO,KAAK,EAAE,sCAAsC,EAAE,4DAAwD;AAC9G,OAAO,KAAK,EAAE,sBAAsB,EAAE,sCAAkC;AAGxE,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"}
@@ -0,0 +1,123 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
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
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _DeFiPositionsControllerV2_apiClient, _DeFiPositionsControllerV2_isEnabled, _DeFiPositionsControllerV2_getVsCurrency;
13
+ import { BaseController } from "@metamask/base-controller";
14
+ import { buildDeFiBalancesQuery } from "./build-defi-balances-query.mjs";
15
+ import { groupDeFiPositionsV6 } from "./group-defi-positions-v6.mjs";
16
+ const controllerName = 'DeFiPositionsControllerV2';
17
+ const MESSENGER_EXPOSED_METHODS = ['fetchDeFiPositions'];
18
+ const controllerMetadata = {
19
+ allDeFiPositionsV2: {
20
+ includeInStateLogs: false,
21
+ persist: true,
22
+ includeInDebugSnapshot: false,
23
+ usedInUi: true,
24
+ },
25
+ };
26
+ export const getDefaultDeFiPositionsControllerV2State = () => {
27
+ return {
28
+ allDeFiPositionsV2: {},
29
+ };
30
+ };
31
+ /**
32
+ * Controller that fetches DeFi positions for the selected account group from
33
+ * the Accounts API (v6 multiaccount balances) and stores them in the shape the
34
+ * client consumes directly.
35
+ *
36
+ * Deduplication and freshness are handled by the shared TanStack Query cache on
37
+ * {@link ApiPlatformClient} (balances default `staleTime` is 1 minute). Pass
38
+ * `{ forceRefresh: true }` to bypass that cache for pull-to-refresh.
39
+ */
40
+ export class DeFiPositionsControllerV2 extends BaseController {
41
+ /**
42
+ * @param options - Constructor options.
43
+ * @param options.messenger - The controller messenger.
44
+ * @param options.apiClient - Accounts API client used to fetch balances/positions. Auth is handled by the client.
45
+ * @param options.isEnabled - Returns whether fetching is enabled (default: () => false).
46
+ * @param options.getVsCurrency - Returns the fiat currency for prices (default: () => 'usd').
47
+ * @param options.state - Initial controller state.
48
+ */
49
+ constructor({ messenger, apiClient, isEnabled, getVsCurrency, state, }) {
50
+ super({
51
+ name: controllerName,
52
+ metadata: controllerMetadata,
53
+ messenger,
54
+ state: {
55
+ ...getDefaultDeFiPositionsControllerV2State(),
56
+ ...state,
57
+ },
58
+ });
59
+ _DeFiPositionsControllerV2_apiClient.set(this, void 0);
60
+ _DeFiPositionsControllerV2_isEnabled.set(this, void 0);
61
+ _DeFiPositionsControllerV2_getVsCurrency.set(this, void 0);
62
+ __classPrivateFieldSet(this, _DeFiPositionsControllerV2_apiClient, apiClient, "f");
63
+ __classPrivateFieldSet(this, _DeFiPositionsControllerV2_isEnabled, isEnabled, "f");
64
+ __classPrivateFieldSet(this, _DeFiPositionsControllerV2_getVsCurrency, getVsCurrency, "f");
65
+ this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
66
+ }
67
+ /**
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).
76
+ *
77
+ * @param options - Optional fetch modifiers.
78
+ * @param options.forceRefresh - When true, bypass the apiClient cache and
79
+ * fetch immediately.
80
+ */
81
+ async fetchDeFiPositions(options) {
82
+ if (!__classPrivateFieldGet(this, _DeFiPositionsControllerV2_isEnabled, "f").call(this)) {
83
+ return;
84
+ }
85
+ const selectedAccounts = this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
86
+ const { networks, internalAccountIdByCaip } = buildDeFiBalancesQuery(selectedAccounts);
87
+ if (internalAccountIdByCaip.size === 0 || networks.length === 0) {
88
+ return;
89
+ }
90
+ const accountIds = [...internalAccountIdByCaip.keys()];
91
+ const vsCurrency = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_getVsCurrency, "f").call(this).toLowerCase();
92
+ try {
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 } : {}),
102
+ });
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) {
106
+ return;
107
+ }
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
+ });
116
+ }
117
+ catch (error) {
118
+ console.error('Failed to fetch DeFi positions', error);
119
+ }
120
+ }
121
+ }
122
+ _DeFiPositionsControllerV2_apiClient = new WeakMap(), _DeFiPositionsControllerV2_isEnabled = new WeakMap(), _DeFiPositionsControllerV2_getVsCurrency = new WeakMap();
123
+ //# sourceMappingURL=DeFiPositionsControllerV2.mjs.map
@@ -0,0 +1 @@
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';\nimport type { DeFiPositionsControllerV2MethodActions } from './DeFiPositionsControllerV2-method-action-types';\nimport type { DeFiPositionsByAccount } from './group-defi-positions-v6';\nimport { groupDeFiPositionsV6 } from './group-defi-positions-v6';\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"]}