@metamask-previews/assets-controllers 110.0.2-preview-d77c814bb → 110.0.2-preview-48f22dbe8
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 +0 -4
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.cjs.map +1 -1
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.d.cts +6 -12
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.d.cts.map +1 -1
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.d.mts +6 -12
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.d.mts.map +1 -1
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.mjs.map +1 -1
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2.cjs +34 -107
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2.cjs.map +1 -1
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2.d.cts +6 -28
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2.d.cts.map +1 -1
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2.d.mts +6 -28
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2.d.mts.map +1 -1
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2.mjs +32 -105
- package/dist/DeFiPositionsController/DeFiPositionsControllerV2.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,10 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
### Changed
|
|
11
|
-
|
|
12
|
-
- `DeFiPositionsControllerV2.fetchDeFiPositions` now polls while any selected account has `processingDefiPositions: true`, applying ready accounts immediately, invalidating the balances cache between attempts, sharing one in-flight promise per selected-account + `vsCurrency` key (so fast switches can join an earlier matching poll), and stopping on request failure or the max attempt limit ([#9711](https://github.com/MetaMask/core/pull/9711))
|
|
13
|
-
|
|
14
10
|
## [110.0.2]
|
|
15
11
|
|
|
16
12
|
### Changed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeFiPositionsControllerV2-method-action-types.cjs","sourceRoot":"","sources":["../../src/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { DeFiPositionsControllerV2 } from './DeFiPositionsControllerV2.js';\n\n/**\n * Fetches DeFi positions for the selected account group. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`)
|
|
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"]}
|
|
@@ -6,22 +6,16 @@ import type { DeFiPositionsControllerV2 } from "./DeFiPositionsControllerV2.cjs"
|
|
|
6
6
|
/**
|
|
7
7
|
* Fetches DeFi positions for the selected account group. Each account key in
|
|
8
8
|
* a ready response replaces that account's state (other accounts stay).
|
|
9
|
-
* Accounts still indexing (`processingDefiPositions`)
|
|
10
|
-
*
|
|
11
|
-
* selected accounts are ready, the attempt limit is reached, or a request
|
|
12
|
-
* fails. Concurrent calls for the same selected accounts and `vsCurrency`
|
|
13
|
-
* share one in-flight promise; calls for a different selection or fiat
|
|
14
|
-
* currency start a new fetch and leave prior polls running so a later
|
|
15
|
-
* switch back can join them. No-ops when disabled or when the group has no
|
|
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
|
|
16
11
|
* supported accounts. Caching / spam prevention is handled by the apiClient
|
|
17
12
|
* TanStack Query cache (keyed by accounts + query options including
|
|
18
|
-
* `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache
|
|
19
|
-
*
|
|
13
|
+
* `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache (e.g.
|
|
14
|
+
* pull-to-refresh).
|
|
20
15
|
*
|
|
21
16
|
* @param options - Optional fetch modifiers.
|
|
22
|
-
* @param options.forceRefresh - When true, bypass the apiClient cache
|
|
23
|
-
*
|
|
24
|
-
* @returns Resolves when the fetch (and any processing polls) finish.
|
|
17
|
+
* @param options.forceRefresh - When true, bypass the apiClient cache and
|
|
18
|
+
* fetch immediately.
|
|
25
19
|
*/
|
|
26
20
|
export type DeFiPositionsControllerV2FetchDeFiPositionsAction = {
|
|
27
21
|
type: `DeFiPositionsControllerV2:fetchDeFiPositions`;
|
package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.d.cts.map
CHANGED
|
@@ -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
|
|
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"}
|
|
@@ -6,22 +6,16 @@ import type { DeFiPositionsControllerV2 } from "./DeFiPositionsControllerV2.mjs"
|
|
|
6
6
|
/**
|
|
7
7
|
* Fetches DeFi positions for the selected account group. Each account key in
|
|
8
8
|
* a ready response replaces that account's state (other accounts stay).
|
|
9
|
-
* Accounts still indexing (`processingDefiPositions`)
|
|
10
|
-
*
|
|
11
|
-
* selected accounts are ready, the attempt limit is reached, or a request
|
|
12
|
-
* fails. Concurrent calls for the same selected accounts and `vsCurrency`
|
|
13
|
-
* share one in-flight promise; calls for a different selection or fiat
|
|
14
|
-
* currency start a new fetch and leave prior polls running so a later
|
|
15
|
-
* switch back can join them. No-ops when disabled or when the group has no
|
|
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
|
|
16
11
|
* supported accounts. Caching / spam prevention is handled by the apiClient
|
|
17
12
|
* TanStack Query cache (keyed by accounts + query options including
|
|
18
|
-
* `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache
|
|
19
|
-
*
|
|
13
|
+
* `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache (e.g.
|
|
14
|
+
* pull-to-refresh).
|
|
20
15
|
*
|
|
21
16
|
* @param options - Optional fetch modifiers.
|
|
22
|
-
* @param options.forceRefresh - When true, bypass the apiClient cache
|
|
23
|
-
*
|
|
24
|
-
* @returns Resolves when the fetch (and any processing polls) finish.
|
|
17
|
+
* @param options.forceRefresh - When true, bypass the apiClient cache and
|
|
18
|
+
* fetch immediately.
|
|
25
19
|
*/
|
|
26
20
|
export type DeFiPositionsControllerV2FetchDeFiPositionsAction = {
|
|
27
21
|
type: `DeFiPositionsControllerV2:fetchDeFiPositions`;
|
package/dist/DeFiPositionsController/DeFiPositionsControllerV2-method-action-types.d.mts.map
CHANGED
|
@@ -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
|
|
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. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`)
|
|
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
|
|
13
|
+
var _DeFiPositionsControllerV2_apiClient, _DeFiPositionsControllerV2_isEnabled, _DeFiPositionsControllerV2_getVsCurrency;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.DeFiPositionsControllerV2 = exports.getDefaultDeFiPositionsControllerV2State =
|
|
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 and keeps
|
|
26
|
-
* prior state for accounts that never became ready.
|
|
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,14 +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
|
-
* Concurrent calls for the same selected accounts and `vsCurrency` share one
|
|
62
|
-
* in-flight promise so the UI can treat the pending promise as a loading
|
|
63
|
-
* signal. Calls for a different selection or fiat currency start their own
|
|
64
|
-
* fetch and leave any prior poll running, so switching back can join an
|
|
65
|
-
* in-flight fetch for that group and currency.
|
|
66
43
|
*/
|
|
67
44
|
class DeFiPositionsControllerV2 extends base_controller_1.BaseController {
|
|
68
45
|
/**
|
|
@@ -83,17 +60,9 @@ class DeFiPositionsControllerV2 extends base_controller_1.BaseController {
|
|
|
83
60
|
...state,
|
|
84
61
|
},
|
|
85
62
|
});
|
|
86
|
-
_DeFiPositionsControllerV2_instances.add(this);
|
|
87
63
|
_DeFiPositionsControllerV2_apiClient.set(this, void 0);
|
|
88
64
|
_DeFiPositionsControllerV2_isEnabled.set(this, void 0);
|
|
89
65
|
_DeFiPositionsControllerV2_getVsCurrency.set(this, void 0);
|
|
90
|
-
/**
|
|
91
|
-
* In-flight fetches keyed by selected DeFi-queryable account IDs plus
|
|
92
|
-
* `vsCurrency`. Concurrent callers for the same selection and currency share
|
|
93
|
-
* a promise; different selections or fiat currencies keep independent
|
|
94
|
-
* fetches so fast switching can join an earlier matching poll.
|
|
95
|
-
*/
|
|
96
|
-
_DeFiPositionsControllerV2_inFlightFetches.set(this, new Map());
|
|
97
66
|
__classPrivateFieldSet(this, _DeFiPositionsControllerV2_apiClient, apiClient, "f");
|
|
98
67
|
__classPrivateFieldSet(this, _DeFiPositionsControllerV2_isEnabled, isEnabled, "f");
|
|
99
68
|
__classPrivateFieldSet(this, _DeFiPositionsControllerV2_getVsCurrency, getVsCurrency, "f");
|
|
@@ -102,100 +71,58 @@ class DeFiPositionsControllerV2 extends base_controller_1.BaseController {
|
|
|
102
71
|
/**
|
|
103
72
|
* Fetches DeFi positions for the selected account group. Each account key in
|
|
104
73
|
* a ready response replaces that account's state (other accounts stay).
|
|
105
|
-
* Accounts still indexing (`processingDefiPositions`)
|
|
106
|
-
*
|
|
107
|
-
* selected accounts are ready, the attempt limit is reached, or a request
|
|
108
|
-
* fails. Concurrent calls for the same selected accounts and `vsCurrency`
|
|
109
|
-
* share one in-flight promise; calls for a different selection or fiat
|
|
110
|
-
* currency start a new fetch and leave prior polls running so a later
|
|
111
|
-
* switch back can join them. No-ops when disabled or when the group has no
|
|
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
|
|
112
76
|
* supported accounts. Caching / spam prevention is handled by the apiClient
|
|
113
77
|
* TanStack Query cache (keyed by accounts + query options including
|
|
114
|
-
* `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache
|
|
115
|
-
*
|
|
78
|
+
* `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache (e.g.
|
|
79
|
+
* pull-to-refresh).
|
|
116
80
|
*
|
|
117
81
|
* @param options - Optional fetch modifiers.
|
|
118
|
-
* @param options.forceRefresh - When true, bypass the apiClient cache
|
|
119
|
-
*
|
|
120
|
-
* @returns Resolves when the fetch (and any processing polls) finish.
|
|
82
|
+
* @param options.forceRefresh - When true, bypass the apiClient cache and
|
|
83
|
+
* fetch immediately.
|
|
121
84
|
*/
|
|
122
85
|
async fetchDeFiPositions(options) {
|
|
123
86
|
if (!__classPrivateFieldGet(this, _DeFiPositionsControllerV2_isEnabled, "f").call(this)) {
|
|
124
87
|
return;
|
|
125
88
|
}
|
|
126
89
|
const selectedAccounts = this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
// Include vsCurrency so a fiat change does not join a poll priced in the
|
|
130
|
-
// previous currency (TanStack also keys the balances cache on vsCurrency).
|
|
131
|
-
const inFlightKey = [
|
|
132
|
-
...[...query.internalAccountIdByCaip.keys()].sort(),
|
|
133
|
-
vsCurrency,
|
|
134
|
-
].join('\0');
|
|
135
|
-
const existing = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").get(inFlightKey);
|
|
136
|
-
if (existing) {
|
|
137
|
-
await existing;
|
|
90
|
+
const { networks, internalAccountIdByCaip } = (0, build_defi_balances_query_js_1.buildDeFiBalancesQuery)(selectedAccounts);
|
|
91
|
+
if (internalAccountIdByCaip.size === 0 || networks.length === 0) {
|
|
138
92
|
return;
|
|
139
93
|
}
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
__classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").delete(inFlightKey);
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
__classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").set(inFlightKey, fetchPromise);
|
|
146
|
-
await fetchPromise;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
exports.DeFiPositionsControllerV2 = DeFiPositionsControllerV2;
|
|
150
|
-
_DeFiPositionsControllerV2_apiClient = new WeakMap(), _DeFiPositionsControllerV2_isEnabled = new WeakMap(), _DeFiPositionsControllerV2_getVsCurrency = new WeakMap(), _DeFiPositionsControllerV2_inFlightFetches = new WeakMap(), _DeFiPositionsControllerV2_instances = new WeakSet(), _DeFiPositionsControllerV2_fetchDeFiPositions = async function _DeFiPositionsControllerV2_fetchDeFiPositions(options, { networks, internalAccountIdByCaip }, vsCurrency) {
|
|
151
|
-
if (internalAccountIdByCaip.size === 0 || networks.length === 0) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
const accountIds = [...internalAccountIdByCaip.keys()];
|
|
155
|
-
const queryOptions = {
|
|
156
|
-
networks,
|
|
157
|
-
includeDeFiBalances: true,
|
|
158
|
-
forceFetchDeFiPositions: true,
|
|
159
|
-
includePrices: true,
|
|
160
|
-
vsCurrency,
|
|
161
|
-
};
|
|
162
|
-
for (let attempt = 0; attempt < exports.DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS; attempt++) {
|
|
163
|
-
// First attempt respects forceRefresh; later polls always bypass cache
|
|
164
|
-
// so we do not spin on a stale processing snapshot.
|
|
165
|
-
const fetchOptions = {
|
|
166
|
-
...(options?.forceRefresh || attempt > 0 ? { staleTime: 0 } : {}),
|
|
167
|
-
};
|
|
94
|
+
const accountIds = [...internalAccountIdByCaip.keys()];
|
|
95
|
+
const vsCurrency = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_getVsCurrency, "f").call(this).toLowerCase();
|
|
168
96
|
try {
|
|
169
|
-
const response = await __classPrivateFieldGet(this, _DeFiPositionsControllerV2_apiClient, "f").accounts.fetchV6MultiAccountBalances(accountIds,
|
|
170
|
-
|
|
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.
|
|
171
108
|
const readyAccounts = response.accounts.filter((account) => !account.processingDefiPositions);
|
|
172
|
-
if (readyAccounts.length
|
|
173
|
-
const positionsByAccount = (0, group_defi_positions_v6_js_1.groupDeFiPositionsV6)({ ...response, accounts: readyAccounts }, internalAccountIdByCaip);
|
|
174
|
-
// Last valid response wins per ready account; still-indexing accounts
|
|
175
|
-
// stay untouched.
|
|
176
|
-
this.update((state) => {
|
|
177
|
-
for (const [accountId, positions] of Object.entries(positionsByAccount)) {
|
|
178
|
-
state.allDeFiPositionsV2[accountId] = positions;
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
if (processingAccounts.length === 0) {
|
|
109
|
+
if (readyAccounts.length === 0) {
|
|
183
110
|
return;
|
|
184
111
|
}
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
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
|
+
}
|
|
188
119
|
});
|
|
189
|
-
const isLastAttempt = attempt >= exports.DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS - 1;
|
|
190
|
-
if (isLastAttempt) {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
await delay(exports.DEFAULT_PROCESSING_POLL_INTERVAL_MS);
|
|
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 and keeps\n * prior state for accounts that never became ready.\n */\nexport const DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS = 5;\n\n/**\n * @param ms - Milliseconds to wait.\n * @returns A promise that resolves after `ms`.\n */\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport type DeFiPositionsControllerV2State = {\n /**\n * DeFi positions keyed by internal MetaMask account ID (`InternalAccount.id`,\n * the same key AssetsController uses). Each account maps to a flat list of\n * protocol groups shown in the DeFi tab, each carrying its own `chainId` for\n * filtering plus the details-page sections embedded inside it. This is\n * exactly the shape the client consumes, so no further transformation is\n * needed on read.\n *\n * Named `allDeFiPositionsV2` (rather than `allDeFiPositions`) so it can live\n * alongside the legacy `DeFiPositionsController` in clients that flatten every\n * controller's state into a single object (e.g. the extension background),\n * without colliding on the shared `allDeFiPositions` key.\n */\n allDeFiPositionsV2: DeFiPositionsByAccount;\n};\n\nconst controllerMetadata: StateMetadata<DeFiPositionsControllerV2State> = {\n allDeFiPositionsV2: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n};\n\nexport const getDefaultDeFiPositionsControllerV2State =\n (): DeFiPositionsControllerV2State => {\n return {\n allDeFiPositionsV2: {},\n };\n };\n\nexport type DeFiPositionsControllerV2GetStateAction = ControllerGetStateAction<\n typeof controllerName,\n DeFiPositionsControllerV2State\n>;\n\nexport type DeFiPositionsControllerV2Actions =\n | DeFiPositionsControllerV2GetStateAction\n | DeFiPositionsControllerV2MethodActions;\n\nexport type DeFiPositionsControllerV2StateChangedEvent =\n ControllerStateChangedEvent<\n typeof controllerName,\n DeFiPositionsControllerV2State\n >;\n\nexport type DeFiPositionsControllerV2Events =\n DeFiPositionsControllerV2StateChangedEvent;\n\n/**\n * The external actions available to the {@link DeFiPositionsControllerV2}.\n */\nexport type AllowedActions =\n AccountTreeControllerGetAccountsFromSelectedAccountGroupAction;\n\n/**\n * The external events available to the {@link DeFiPositionsControllerV2}.\n *\n * None yet — clients must call `fetchDeFiPositions` (and optionally\n * `{ forceRefresh: true }`) on their own triggers. Likely future subscriptions:\n * `AccountTreeController:selectedAccountGroupChange`,\n * `TransactionController:transactionConfirmed`, and `KeyringController:lock`.\n */\nexport type AllowedEvents = never;\n\nexport type DeFiPositionsControllerV2Messenger = Messenger<\n typeof controllerName,\n DeFiPositionsControllerV2Actions | AllowedActions,\n DeFiPositionsControllerV2Events | AllowedEvents\n>;\n\n/**\n * Controller that fetches DeFi positions for the selected account group from\n * the Accounts API (v6 multiaccount balances) and stores them in the shape the\n * client consumes directly.\n *\n * Deduplication and freshness are handled by the shared TanStack Query cache on\n * {@link ApiPlatformClient} (balances default `staleTime` is 1 minute). Pass\n * `{ forceRefresh: true }` to bypass that cache for pull-to-refresh.\n *\n * When the API reports `processingDefiPositions` for any account, this\n * controller polls until indexing finishes or the attempt limit is reached.\n * Concurrent calls for the same selected accounts and `vsCurrency` share one\n * in-flight promise so the UI can treat the pending promise as a loading\n * signal. Calls for a different selection or fiat currency start their own\n * fetch and leave any prior poll running, so switching back can join an\n * in-flight fetch for that group and currency.\n */\nexport class DeFiPositionsControllerV2 extends BaseController<\n typeof controllerName,\n DeFiPositionsControllerV2State,\n DeFiPositionsControllerV2Messenger\n> {\n readonly #apiClient: ApiPlatformClient;\n\n readonly #isEnabled: () => boolean;\n\n readonly #getVsCurrency: () => string;\n\n /**\n * In-flight fetches keyed by selected DeFi-queryable account IDs plus\n * `vsCurrency`. Concurrent callers for the same selection and currency share\n * a promise; different selections or fiat currencies keep independent\n * fetches so fast switching can join an earlier matching poll.\n */\n readonly #inFlightFetches = new Map<string, Promise<void>>();\n\n /**\n * @param options - Constructor options.\n * @param options.messenger - The controller messenger.\n * @param options.apiClient - Accounts API client used to fetch balances/positions. Auth is handled by the client.\n * @param options.isEnabled - Returns whether fetching is enabled (default: () => false).\n * @param options.getVsCurrency - Returns the fiat currency for prices (default: () => 'usd').\n * @param options.state - Initial controller state.\n */\n constructor({\n messenger,\n apiClient,\n isEnabled,\n getVsCurrency,\n state,\n }: {\n messenger: DeFiPositionsControllerV2Messenger;\n apiClient: ApiPlatformClient;\n isEnabled: () => boolean;\n getVsCurrency: () => string;\n state?: Partial<DeFiPositionsControllerV2State>;\n }) {\n super({\n name: controllerName,\n metadata: controllerMetadata,\n messenger,\n state: {\n ...getDefaultDeFiPositionsControllerV2State(),\n ...state,\n },\n });\n\n this.#apiClient = apiClient;\n this.#isEnabled = isEnabled;\n this.#getVsCurrency = getVsCurrency;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Fetches DeFi positions for the selected account group. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`) keep prior state; the\n * method polls (invalidating the balances cache between attempts) until all\n * selected accounts are ready, the attempt limit is reached, or a request\n * fails. Concurrent calls for the same selected accounts and `vsCurrency`\n * share one in-flight promise; calls for a different selection or fiat\n * currency start a new fetch and leave prior polls running so a later\n * switch back can join them. No-ops when disabled or when the group has no\n * supported accounts. Caching / spam prevention is handled by the apiClient\n * TanStack Query cache (keyed by accounts + query options including\n * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the\n * first attempt (e.g. pull-to-refresh).\n *\n * @param options - Optional fetch modifiers.\n * @param options.forceRefresh - When true, bypass the apiClient cache on the\n * first attempt and fetch immediately.\n * @returns Resolves when the fetch (and any processing polls) finish.\n */\n async fetchDeFiPositions(options?: {\n forceRefresh?: boolean;\n }): Promise<void> {\n if (!this.#isEnabled()) {\n return;\n }\n\n const selectedAccounts = this.messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n const query = buildDeFiBalancesQuery(selectedAccounts);\n const vsCurrency = this.#getVsCurrency().toLowerCase();\n // Include vsCurrency so a fiat change does not join a poll priced in the\n // previous currency (TanStack also keys the balances cache on vsCurrency).\n const inFlightKey = [\n ...[...query.internalAccountIdByCaip.keys()].sort(),\n vsCurrency,\n ].join('\\0');\n\n const existing = this.#inFlightFetches.get(inFlightKey);\n if (existing) {\n await existing;\n return;\n }\n\n const fetchPromise = this.#fetchDeFiPositions(\n options,\n query,\n vsCurrency,\n ).finally(() => {\n if (this.#inFlightFetches.get(inFlightKey) === fetchPromise) {\n this.#inFlightFetches.delete(inFlightKey);\n }\n });\n this.#inFlightFetches.set(inFlightKey, fetchPromise);\n\n await fetchPromise;\n }\n\n async #fetchDeFiPositions(\n options: { forceRefresh?: boolean } | undefined,\n { networks, internalAccountIdByCaip }: DeFiBalancesQuery,\n vsCurrency: string,\n ): Promise<void> {\n if (internalAccountIdByCaip.size === 0 || networks.length === 0) {\n return;\n }\n\n const accountIds = [...internalAccountIdByCaip.keys()];\n const queryOptions = {\n networks,\n includeDeFiBalances: true,\n forceFetchDeFiPositions: true,\n includePrices: true,\n vsCurrency,\n };\n\n for (\n let attempt = 0;\n attempt < DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS;\n attempt++\n ) {\n // First attempt respects forceRefresh; later polls always bypass cache\n // so we do not spin on a stale processing snapshot.\n const fetchOptions = {\n ...(options?.forceRefresh || attempt > 0 ? { staleTime: 0 } : {}),\n };\n\n try {\n const response =\n await this.#apiClient.accounts.fetchV6MultiAccountBalances(\n accountIds,\n queryOptions,\n fetchOptions,\n );\n\n const processingAccounts = response.accounts.filter(\n (account) => account.processingDefiPositions,\n );\n const readyAccounts = response.accounts.filter(\n (account) => !account.processingDefiPositions,\n );\n\n if (readyAccounts.length > 0) {\n const positionsByAccount = groupDeFiPositionsV6(\n { ...response, accounts: readyAccounts },\n internalAccountIdByCaip,\n );\n\n // Last valid response wins per ready account; still-indexing accounts\n // stay untouched.\n this.update((state) => {\n for (const [accountId, positions] of Object.entries(\n positionsByAccount,\n )) {\n state.allDeFiPositionsV2[accountId] = positions;\n }\n });\n }\n\n if (processingAccounts.length === 0) {\n return;\n }\n\n const { queryKey } =\n this.#apiClient.accounts.getV6MultiAccountBalancesQueryOptions(\n accountIds,\n queryOptions,\n fetchOptions,\n );\n await this.#apiClient.accounts.queryClient.invalidateQueries({\n queryKey,\n });\n\n const isLastAttempt =\n attempt >= DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS - 1;\n if (isLastAttempt) {\n return;\n }\n\n await delay(DEFAULT_PROCESSING_POLL_INTERVAL_MS);\n } catch (error) {\n console.error('Failed to fetch DeFi positions', error);\n return;\n }\n }\n }\n}\n"]}
|
|
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 and keeps
|
|
14
|
-
* prior state for accounts that never became ready.
|
|
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,14 +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
|
-
* Concurrent calls for the same selected accounts and `vsCurrency` share one
|
|
64
|
-
* in-flight promise so the UI can treat the pending promise as a loading
|
|
65
|
-
* signal. Calls for a different selection or fiat currency start their own
|
|
66
|
-
* fetch and leave any prior poll running, so switching back can join an
|
|
67
|
-
* in-flight fetch for that group and currency.
|
|
68
52
|
*/
|
|
69
53
|
export declare class DeFiPositionsControllerV2 extends BaseController<typeof controllerName, DeFiPositionsControllerV2State, DeFiPositionsControllerV2Messenger> {
|
|
70
54
|
#private;
|
|
@@ -86,22 +70,16 @@ export declare class DeFiPositionsControllerV2 extends BaseController<typeof con
|
|
|
86
70
|
/**
|
|
87
71
|
* Fetches DeFi positions for the selected account group. Each account key in
|
|
88
72
|
* a ready response replaces that account's state (other accounts stay).
|
|
89
|
-
* Accounts still indexing (`processingDefiPositions`)
|
|
90
|
-
*
|
|
91
|
-
* selected accounts are ready, the attempt limit is reached, or a request
|
|
92
|
-
* fails. Concurrent calls for the same selected accounts and `vsCurrency`
|
|
93
|
-
* share one in-flight promise; calls for a different selection or fiat
|
|
94
|
-
* currency start a new fetch and leave prior polls running so a later
|
|
95
|
-
* switch back can join them. No-ops when disabled or when the group has no
|
|
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
|
|
96
75
|
* supported accounts. Caching / spam prevention is handled by the apiClient
|
|
97
76
|
* TanStack Query cache (keyed by accounts + query options including
|
|
98
|
-
* `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache
|
|
99
|
-
*
|
|
77
|
+
* `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache (e.g.
|
|
78
|
+
* pull-to-refresh).
|
|
100
79
|
*
|
|
101
80
|
* @param options - Optional fetch modifiers.
|
|
102
|
-
* @param options.forceRefresh - When true, bypass the apiClient cache
|
|
103
|
-
*
|
|
104
|
-
* @returns Resolves when the fetch (and any processing polls) finish.
|
|
81
|
+
* @param options.forceRefresh - When true, bypass the apiClient cache and
|
|
82
|
+
* fetch immediately.
|
|
105
83
|
*/
|
|
106
84
|
fetchDeFiPositions(options?: {
|
|
107
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;
|
|
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 and keeps
|
|
14
|
-
* prior state for accounts that never became ready.
|
|
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,14 +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
|
-
* Concurrent calls for the same selected accounts and `vsCurrency` share one
|
|
64
|
-
* in-flight promise so the UI can treat the pending promise as a loading
|
|
65
|
-
* signal. Calls for a different selection or fiat currency start their own
|
|
66
|
-
* fetch and leave any prior poll running, so switching back can join an
|
|
67
|
-
* in-flight fetch for that group and currency.
|
|
68
52
|
*/
|
|
69
53
|
export declare class DeFiPositionsControllerV2 extends BaseController<typeof controllerName, DeFiPositionsControllerV2State, DeFiPositionsControllerV2Messenger> {
|
|
70
54
|
#private;
|
|
@@ -86,22 +70,16 @@ export declare class DeFiPositionsControllerV2 extends BaseController<typeof con
|
|
|
86
70
|
/**
|
|
87
71
|
* Fetches DeFi positions for the selected account group. Each account key in
|
|
88
72
|
* a ready response replaces that account's state (other accounts stay).
|
|
89
|
-
* Accounts still indexing (`processingDefiPositions`)
|
|
90
|
-
*
|
|
91
|
-
* selected accounts are ready, the attempt limit is reached, or a request
|
|
92
|
-
* fails. Concurrent calls for the same selected accounts and `vsCurrency`
|
|
93
|
-
* share one in-flight promise; calls for a different selection or fiat
|
|
94
|
-
* currency start a new fetch and leave prior polls running so a later
|
|
95
|
-
* switch back can join them. No-ops when disabled or when the group has no
|
|
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
|
|
96
75
|
* supported accounts. Caching / spam prevention is handled by the apiClient
|
|
97
76
|
* TanStack Query cache (keyed by accounts + query options including
|
|
98
|
-
* `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache
|
|
99
|
-
*
|
|
77
|
+
* `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache (e.g.
|
|
78
|
+
* pull-to-refresh).
|
|
100
79
|
*
|
|
101
80
|
* @param options - Optional fetch modifiers.
|
|
102
|
-
* @param options.forceRefresh - When true, bypass the apiClient cache
|
|
103
|
-
*
|
|
104
|
-
* @returns Resolves when the fetch (and any processing polls) finish.
|
|
81
|
+
* @param options.forceRefresh - When true, bypass the apiClient cache and
|
|
82
|
+
* fetch immediately.
|
|
105
83
|
*/
|
|
106
84
|
fetchDeFiPositions(options?: {
|
|
107
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;
|
|
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
|
|
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 and keeps
|
|
23
|
-
* prior state for accounts that never became ready.
|
|
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,14 +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
|
-
* Concurrent calls for the same selected accounts and `vsCurrency` share one
|
|
58
|
-
* in-flight promise so the UI can treat the pending promise as a loading
|
|
59
|
-
* signal. Calls for a different selection or fiat currency start their own
|
|
60
|
-
* fetch and leave any prior poll running, so switching back can join an
|
|
61
|
-
* in-flight fetch for that group and currency.
|
|
62
39
|
*/
|
|
63
40
|
export class DeFiPositionsControllerV2 extends BaseController {
|
|
64
41
|
/**
|
|
@@ -79,17 +56,9 @@ export class DeFiPositionsControllerV2 extends BaseController {
|
|
|
79
56
|
...state,
|
|
80
57
|
},
|
|
81
58
|
});
|
|
82
|
-
_DeFiPositionsControllerV2_instances.add(this);
|
|
83
59
|
_DeFiPositionsControllerV2_apiClient.set(this, void 0);
|
|
84
60
|
_DeFiPositionsControllerV2_isEnabled.set(this, void 0);
|
|
85
61
|
_DeFiPositionsControllerV2_getVsCurrency.set(this, void 0);
|
|
86
|
-
/**
|
|
87
|
-
* In-flight fetches keyed by selected DeFi-queryable account IDs plus
|
|
88
|
-
* `vsCurrency`. Concurrent callers for the same selection and currency share
|
|
89
|
-
* a promise; different selections or fiat currencies keep independent
|
|
90
|
-
* fetches so fast switching can join an earlier matching poll.
|
|
91
|
-
*/
|
|
92
|
-
_DeFiPositionsControllerV2_inFlightFetches.set(this, new Map());
|
|
93
62
|
__classPrivateFieldSet(this, _DeFiPositionsControllerV2_apiClient, apiClient, "f");
|
|
94
63
|
__classPrivateFieldSet(this, _DeFiPositionsControllerV2_isEnabled, isEnabled, "f");
|
|
95
64
|
__classPrivateFieldSet(this, _DeFiPositionsControllerV2_getVsCurrency, getVsCurrency, "f");
|
|
@@ -98,99 +67,57 @@ export class DeFiPositionsControllerV2 extends BaseController {
|
|
|
98
67
|
/**
|
|
99
68
|
* Fetches DeFi positions for the selected account group. Each account key in
|
|
100
69
|
* a ready response replaces that account's state (other accounts stay).
|
|
101
|
-
* Accounts still indexing (`processingDefiPositions`)
|
|
102
|
-
*
|
|
103
|
-
* selected accounts are ready, the attempt limit is reached, or a request
|
|
104
|
-
* fails. Concurrent calls for the same selected accounts and `vsCurrency`
|
|
105
|
-
* share one in-flight promise; calls for a different selection or fiat
|
|
106
|
-
* currency start a new fetch and leave prior polls running so a later
|
|
107
|
-
* switch back can join them. No-ops when disabled or when the group has no
|
|
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
|
|
108
72
|
* supported accounts. Caching / spam prevention is handled by the apiClient
|
|
109
73
|
* TanStack Query cache (keyed by accounts + query options including
|
|
110
|
-
* `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache
|
|
111
|
-
*
|
|
74
|
+
* `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache (e.g.
|
|
75
|
+
* pull-to-refresh).
|
|
112
76
|
*
|
|
113
77
|
* @param options - Optional fetch modifiers.
|
|
114
|
-
* @param options.forceRefresh - When true, bypass the apiClient cache
|
|
115
|
-
*
|
|
116
|
-
* @returns Resolves when the fetch (and any processing polls) finish.
|
|
78
|
+
* @param options.forceRefresh - When true, bypass the apiClient cache and
|
|
79
|
+
* fetch immediately.
|
|
117
80
|
*/
|
|
118
81
|
async fetchDeFiPositions(options) {
|
|
119
82
|
if (!__classPrivateFieldGet(this, _DeFiPositionsControllerV2_isEnabled, "f").call(this)) {
|
|
120
83
|
return;
|
|
121
84
|
}
|
|
122
85
|
const selectedAccounts = this.messenger.call('AccountTreeController:getAccountsFromSelectedAccountGroup');
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
// Include vsCurrency so a fiat change does not join a poll priced in the
|
|
126
|
-
// previous currency (TanStack also keys the balances cache on vsCurrency).
|
|
127
|
-
const inFlightKey = [
|
|
128
|
-
...[...query.internalAccountIdByCaip.keys()].sort(),
|
|
129
|
-
vsCurrency,
|
|
130
|
-
].join('\0');
|
|
131
|
-
const existing = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").get(inFlightKey);
|
|
132
|
-
if (existing) {
|
|
133
|
-
await existing;
|
|
86
|
+
const { networks, internalAccountIdByCaip } = buildDeFiBalancesQuery(selectedAccounts);
|
|
87
|
+
if (internalAccountIdByCaip.size === 0 || networks.length === 0) {
|
|
134
88
|
return;
|
|
135
89
|
}
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
__classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").delete(inFlightKey);
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
__classPrivateFieldGet(this, _DeFiPositionsControllerV2_inFlightFetches, "f").set(inFlightKey, fetchPromise);
|
|
142
|
-
await fetchPromise;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
_DeFiPositionsControllerV2_apiClient = new WeakMap(), _DeFiPositionsControllerV2_isEnabled = new WeakMap(), _DeFiPositionsControllerV2_getVsCurrency = new WeakMap(), _DeFiPositionsControllerV2_inFlightFetches = new WeakMap(), _DeFiPositionsControllerV2_instances = new WeakSet(), _DeFiPositionsControllerV2_fetchDeFiPositions = async function _DeFiPositionsControllerV2_fetchDeFiPositions(options, { networks, internalAccountIdByCaip }, vsCurrency) {
|
|
146
|
-
if (internalAccountIdByCaip.size === 0 || networks.length === 0) {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
const accountIds = [...internalAccountIdByCaip.keys()];
|
|
150
|
-
const queryOptions = {
|
|
151
|
-
networks,
|
|
152
|
-
includeDeFiBalances: true,
|
|
153
|
-
forceFetchDeFiPositions: true,
|
|
154
|
-
includePrices: true,
|
|
155
|
-
vsCurrency,
|
|
156
|
-
};
|
|
157
|
-
for (let attempt = 0; attempt < DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS; attempt++) {
|
|
158
|
-
// First attempt respects forceRefresh; later polls always bypass cache
|
|
159
|
-
// so we do not spin on a stale processing snapshot.
|
|
160
|
-
const fetchOptions = {
|
|
161
|
-
...(options?.forceRefresh || attempt > 0 ? { staleTime: 0 } : {}),
|
|
162
|
-
};
|
|
90
|
+
const accountIds = [...internalAccountIdByCaip.keys()];
|
|
91
|
+
const vsCurrency = __classPrivateFieldGet(this, _DeFiPositionsControllerV2_getVsCurrency, "f").call(this).toLowerCase();
|
|
163
92
|
try {
|
|
164
|
-
const response = await __classPrivateFieldGet(this, _DeFiPositionsControllerV2_apiClient, "f").accounts.fetchV6MultiAccountBalances(accountIds,
|
|
165
|
-
|
|
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.
|
|
166
104
|
const readyAccounts = response.accounts.filter((account) => !account.processingDefiPositions);
|
|
167
|
-
if (readyAccounts.length
|
|
168
|
-
const positionsByAccount = groupDeFiPositionsV6({ ...response, accounts: readyAccounts }, internalAccountIdByCaip);
|
|
169
|
-
// Last valid response wins per ready account; still-indexing accounts
|
|
170
|
-
// stay untouched.
|
|
171
|
-
this.update((state) => {
|
|
172
|
-
for (const [accountId, positions] of Object.entries(positionsByAccount)) {
|
|
173
|
-
state.allDeFiPositionsV2[accountId] = positions;
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
if (processingAccounts.length === 0) {
|
|
105
|
+
if (readyAccounts.length === 0) {
|
|
178
106
|
return;
|
|
179
107
|
}
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
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
|
+
}
|
|
183
115
|
});
|
|
184
|
-
const isLastAttempt = attempt >= DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS - 1;
|
|
185
|
-
if (isLastAttempt) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
await delay(DEFAULT_PROCESSING_POLL_INTERVAL_MS);
|
|
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":"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","sourcesContent":["import type { AccountTreeControllerGetAccountsFromSelectedAccountGroupAction } from '@metamask/account-tree-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport type { ApiPlatformClient } from '@metamask/core-backend';\nimport type { Messenger } from '@metamask/messenger';\n\nimport { buildDeFiBalancesQuery } from './build-defi-balances-query.js';\nimport type { DeFiBalancesQuery } from './build-defi-balances-query.js';\nimport type { DeFiPositionsControllerV2MethodActions } from './DeFiPositionsControllerV2-method-action-types.js';\nimport type { DeFiPositionsByAccount } from './group-defi-positions-v6.js';\nimport { groupDeFiPositionsV6 } from './group-defi-positions-v6.js';\n\nconst controllerName = 'DeFiPositionsControllerV2';\n\nconst MESSENGER_EXPOSED_METHODS = ['fetchDeFiPositions'] as const;\n\n/** Delay between polls while Accounts API reports DeFi indexing in progress. */\nexport const DEFAULT_PROCESSING_POLL_INTERVAL_MS = 5_000;\n\n/**\n * Maximum fetch attempts (including the first) while any account still has\n * `processingDefiPositions: true`. After this, the call resolves and keeps\n * prior state for accounts that never became ready.\n */\nexport const DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS = 5;\n\n/**\n * @param ms - Milliseconds to wait.\n * @returns A promise that resolves after `ms`.\n */\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport type DeFiPositionsControllerV2State = {\n /**\n * DeFi positions keyed by internal MetaMask account ID (`InternalAccount.id`,\n * the same key AssetsController uses). Each account maps to a flat list of\n * protocol groups shown in the DeFi tab, each carrying its own `chainId` for\n * filtering plus the details-page sections embedded inside it. This is\n * exactly the shape the client consumes, so no further transformation is\n * needed on read.\n *\n * Named `allDeFiPositionsV2` (rather than `allDeFiPositions`) so it can live\n * alongside the legacy `DeFiPositionsController` in clients that flatten every\n * controller's state into a single object (e.g. the extension background),\n * without colliding on the shared `allDeFiPositions` key.\n */\n allDeFiPositionsV2: DeFiPositionsByAccount;\n};\n\nconst controllerMetadata: StateMetadata<DeFiPositionsControllerV2State> = {\n allDeFiPositionsV2: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n};\n\nexport const getDefaultDeFiPositionsControllerV2State =\n (): DeFiPositionsControllerV2State => {\n return {\n allDeFiPositionsV2: {},\n };\n };\n\nexport type DeFiPositionsControllerV2GetStateAction = ControllerGetStateAction<\n typeof controllerName,\n DeFiPositionsControllerV2State\n>;\n\nexport type DeFiPositionsControllerV2Actions =\n | DeFiPositionsControllerV2GetStateAction\n | DeFiPositionsControllerV2MethodActions;\n\nexport type DeFiPositionsControllerV2StateChangedEvent =\n ControllerStateChangedEvent<\n typeof controllerName,\n DeFiPositionsControllerV2State\n >;\n\nexport type DeFiPositionsControllerV2Events =\n DeFiPositionsControllerV2StateChangedEvent;\n\n/**\n * The external actions available to the {@link DeFiPositionsControllerV2}.\n */\nexport type AllowedActions =\n AccountTreeControllerGetAccountsFromSelectedAccountGroupAction;\n\n/**\n * The external events available to the {@link DeFiPositionsControllerV2}.\n *\n * None yet — clients must call `fetchDeFiPositions` (and optionally\n * `{ forceRefresh: true }`) on their own triggers. Likely future subscriptions:\n * `AccountTreeController:selectedAccountGroupChange`,\n * `TransactionController:transactionConfirmed`, and `KeyringController:lock`.\n */\nexport type AllowedEvents = never;\n\nexport type DeFiPositionsControllerV2Messenger = Messenger<\n typeof controllerName,\n DeFiPositionsControllerV2Actions | AllowedActions,\n DeFiPositionsControllerV2Events | AllowedEvents\n>;\n\n/**\n * Controller that fetches DeFi positions for the selected account group from\n * the Accounts API (v6 multiaccount balances) and stores them in the shape the\n * client consumes directly.\n *\n * Deduplication and freshness are handled by the shared TanStack Query cache on\n * {@link ApiPlatformClient} (balances default `staleTime` is 1 minute). Pass\n * `{ forceRefresh: true }` to bypass that cache for pull-to-refresh.\n *\n * When the API reports `processingDefiPositions` for any account, this\n * controller polls until indexing finishes or the attempt limit is reached.\n * Concurrent calls for the same selected accounts and `vsCurrency` share one\n * in-flight promise so the UI can treat the pending promise as a loading\n * signal. Calls for a different selection or fiat currency start their own\n * fetch and leave any prior poll running, so switching back can join an\n * in-flight fetch for that group and currency.\n */\nexport class DeFiPositionsControllerV2 extends BaseController<\n typeof controllerName,\n DeFiPositionsControllerV2State,\n DeFiPositionsControllerV2Messenger\n> {\n readonly #apiClient: ApiPlatformClient;\n\n readonly #isEnabled: () => boolean;\n\n readonly #getVsCurrency: () => string;\n\n /**\n * In-flight fetches keyed by selected DeFi-queryable account IDs plus\n * `vsCurrency`. Concurrent callers for the same selection and currency share\n * a promise; different selections or fiat currencies keep independent\n * fetches so fast switching can join an earlier matching poll.\n */\n readonly #inFlightFetches = new Map<string, Promise<void>>();\n\n /**\n * @param options - Constructor options.\n * @param options.messenger - The controller messenger.\n * @param options.apiClient - Accounts API client used to fetch balances/positions. Auth is handled by the client.\n * @param options.isEnabled - Returns whether fetching is enabled (default: () => false).\n * @param options.getVsCurrency - Returns the fiat currency for prices (default: () => 'usd').\n * @param options.state - Initial controller state.\n */\n constructor({\n messenger,\n apiClient,\n isEnabled,\n getVsCurrency,\n state,\n }: {\n messenger: DeFiPositionsControllerV2Messenger;\n apiClient: ApiPlatformClient;\n isEnabled: () => boolean;\n getVsCurrency: () => string;\n state?: Partial<DeFiPositionsControllerV2State>;\n }) {\n super({\n name: controllerName,\n metadata: controllerMetadata,\n messenger,\n state: {\n ...getDefaultDeFiPositionsControllerV2State(),\n ...state,\n },\n });\n\n this.#apiClient = apiClient;\n this.#isEnabled = isEnabled;\n this.#getVsCurrency = getVsCurrency;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Fetches DeFi positions for the selected account group. Each account key in\n * a ready response replaces that account's state (other accounts stay).\n * Accounts still indexing (`processingDefiPositions`) keep prior state; the\n * method polls (invalidating the balances cache between attempts) until all\n * selected accounts are ready, the attempt limit is reached, or a request\n * fails. Concurrent calls for the same selected accounts and `vsCurrency`\n * share one in-flight promise; calls for a different selection or fiat\n * currency start a new fetch and leave prior polls running so a later\n * switch back can join them. No-ops when disabled or when the group has no\n * supported accounts. Caching / spam prevention is handled by the apiClient\n * TanStack Query cache (keyed by accounts + query options including\n * `vsCurrency`). Pass `{ forceRefresh: true }` to bypass the cache on the\n * first attempt (e.g. pull-to-refresh).\n *\n * @param options - Optional fetch modifiers.\n * @param options.forceRefresh - When true, bypass the apiClient cache on the\n * first attempt and fetch immediately.\n * @returns Resolves when the fetch (and any processing polls) finish.\n */\n async fetchDeFiPositions(options?: {\n forceRefresh?: boolean;\n }): Promise<void> {\n if (!this.#isEnabled()) {\n return;\n }\n\n const selectedAccounts = this.messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n const query = buildDeFiBalancesQuery(selectedAccounts);\n const vsCurrency = this.#getVsCurrency().toLowerCase();\n // Include vsCurrency so a fiat change does not join a poll priced in the\n // previous currency (TanStack also keys the balances cache on vsCurrency).\n const inFlightKey = [\n ...[...query.internalAccountIdByCaip.keys()].sort(),\n vsCurrency,\n ].join('\\0');\n\n const existing = this.#inFlightFetches.get(inFlightKey);\n if (existing) {\n await existing;\n return;\n }\n\n const fetchPromise = this.#fetchDeFiPositions(\n options,\n query,\n vsCurrency,\n ).finally(() => {\n if (this.#inFlightFetches.get(inFlightKey) === fetchPromise) {\n this.#inFlightFetches.delete(inFlightKey);\n }\n });\n this.#inFlightFetches.set(inFlightKey, fetchPromise);\n\n await fetchPromise;\n }\n\n async #fetchDeFiPositions(\n options: { forceRefresh?: boolean } | undefined,\n { networks, internalAccountIdByCaip }: DeFiBalancesQuery,\n vsCurrency: string,\n ): Promise<void> {\n if (internalAccountIdByCaip.size === 0 || networks.length === 0) {\n return;\n }\n\n const accountIds = [...internalAccountIdByCaip.keys()];\n const queryOptions = {\n networks,\n includeDeFiBalances: true,\n forceFetchDeFiPositions: true,\n includePrices: true,\n vsCurrency,\n };\n\n for (\n let attempt = 0;\n attempt < DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS;\n attempt++\n ) {\n // First attempt respects forceRefresh; later polls always bypass cache\n // so we do not spin on a stale processing snapshot.\n const fetchOptions = {\n ...(options?.forceRefresh || attempt > 0 ? { staleTime: 0 } : {}),\n };\n\n try {\n const response =\n await this.#apiClient.accounts.fetchV6MultiAccountBalances(\n accountIds,\n queryOptions,\n fetchOptions,\n );\n\n const processingAccounts = response.accounts.filter(\n (account) => account.processingDefiPositions,\n );\n const readyAccounts = response.accounts.filter(\n (account) => !account.processingDefiPositions,\n );\n\n if (readyAccounts.length > 0) {\n const positionsByAccount = groupDeFiPositionsV6(\n { ...response, accounts: readyAccounts },\n internalAccountIdByCaip,\n );\n\n // Last valid response wins per ready account; still-indexing accounts\n // stay untouched.\n this.update((state) => {\n for (const [accountId, positions] of Object.entries(\n positionsByAccount,\n )) {\n state.allDeFiPositionsV2[accountId] = positions;\n }\n });\n }\n\n if (processingAccounts.length === 0) {\n return;\n }\n\n const { queryKey } =\n this.#apiClient.accounts.getV6MultiAccountBalancesQueryOptions(\n accountIds,\n queryOptions,\n fetchOptions,\n );\n await this.#apiClient.accounts.queryClient.invalidateQueries({\n queryKey,\n });\n\n const isLastAttempt =\n attempt >= DEFAULT_PROCESSING_POLL_MAX_ATTEMPTS - 1;\n if (isLastAttempt) {\n return;\n }\n\n await delay(DEFAULT_PROCESSING_POLL_INTERVAL_MS);\n } catch (error) {\n console.error('Failed to fetch DeFi positions', error);\n return;\n }\n }\n }\n}\n"]}
|
|
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;AAGxE,OAAO,EAAE,oBAAoB,EAAE,sCAAqC;AAEpE,MAAM,cAAc,GAAG,2BAA2B,CAAC;AAEnD,MAAM,yBAAyB,GAAG,CAAC,oBAAoB,CAAU,CAAC;AAmBlE,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;;;;;;;;GAQG;AACH,MAAM,OAAO,yBAA0B,SAAQ,cAI9C;IAOC;;;;;;;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;QAnCI,uDAA8B;QAE9B,uDAA0B;QAE1B,2DAA6B;QAiCpC,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;;;;;;;;;;;;;OAaG;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;QAEF,MAAM,EAAE,QAAQ,EAAE,uBAAuB,EAAE,GACzC,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;QAE3C,IAAI,uBAAuB,CAAC,IAAI,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChE,OAAO;QACT,CAAC;QAED,MAAM,UAAU,GAAG,CAAC,GAAG,uBAAuB,CAAC,IAAI,EAAE,CAAC,CAAC;QACvD,MAAM,UAAU,GAAG,uBAAA,IAAI,gDAAe,MAAnB,IAAI,CAAiB,CAAC,WAAW,EAAE,CAAC;QAEvD,IAAI,CAAC;YACH,MAAM,QAAQ,GACZ,MAAM,uBAAA,IAAI,4CAAW,CAAC,QAAQ,CAAC,2BAA2B,CACxD,UAAU,EACV;gBACE,QAAQ;gBACR,mBAAmB,EAAE,IAAI;gBACzB,uBAAuB,EAAE,IAAI;gBAC7B,aAAa,EAAE,IAAI;gBACnB,UAAU;aACX,EACD;gBACE,sEAAsE;gBACtE,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnD,CACF,CAAC;YAEJ,0EAA0E;YAC1E,MAAM,aAAa,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAC5C,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAC9C,CAAC;YACF,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC/B,OAAO;YACT,CAAC;YAED,MAAM,kBAAkB,GAAG,oBAAoB,CAC7C,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,EACxC,uBAAuB,CACxB,CAAC;YAEF,0EAA0E;YAC1E,kBAAkB;YAClB,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;gBACpB,KAAK,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CACjD,kBAAkB,CACnB,EAAE,CAAC;oBACF,KAAK,CAAC,kBAAkB,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;gBAClD,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;CACF","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.0.2-preview-
|
|
3
|
+
"version": "110.0.2-preview-48f22dbe8",
|
|
4
4
|
"description": "Controllers which manage interactions involving ERC-20, ERC-721, and ERC-1155 tokens (including NFTs)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Ethereum",
|