@metamask/earn-controller 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0]
11
+
12
+ ### Added
13
+
14
+ - Initial release ([#5271](https://github.com/MetaMask/core/pull/5271))
15
+
16
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/earn-controller@0.1.0...HEAD
17
+ [0.1.0]: https://github.com/MetaMask/core/releases/tag/@metamask/earn-controller@0.1.0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 MetaMask
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # `@metamask/earn-controller`
2
+
3
+ Manages state for earning features and coordinates interactions between staking services, SDK integrations, and other controllers to enable users to participate in various earning opportunities.
4
+
5
+ ## Installation
6
+
7
+ `yarn add @metamask/earn-controller`
8
+
9
+ or
10
+
11
+ `npm install @metamask/earn-controller`
12
+
13
+ ## Contributing
14
+
15
+ This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme).
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
4
+ 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");
5
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
6
+ };
7
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
8
+ if (kind === "m") throw new TypeError("Private method is not writable");
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
10
+ 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");
11
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
12
+ };
13
+ var _EarnController_instances, _EarnController_stakeSDK, _EarnController_selectedNetworkClientId, _EarnController_stakingApiService, _EarnController_initializeSDK, _EarnController_getCurrentAccount, _EarnController_getCurrentChainId;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.EarnController = exports.getDefaultEarnControllerState = exports.controllerName = void 0;
16
+ const providers_1 = require("@ethersproject/providers");
17
+ const base_controller_1 = require("@metamask/base-controller");
18
+ const controller_utils_1 = require("@metamask/controller-utils");
19
+ const stake_sdk_1 = require("@metamask/stake-sdk");
20
+ exports.controllerName = 'EarnController';
21
+ /**
22
+ * Metadata for the EarnController.
23
+ */
24
+ const earnControllerMetadata = {
25
+ pooled_staking: {
26
+ persist: true,
27
+ anonymous: false,
28
+ },
29
+ stablecoin_lending: {
30
+ persist: true,
31
+ anonymous: false,
32
+ },
33
+ lastUpdated: {
34
+ persist: false,
35
+ anonymous: true,
36
+ },
37
+ };
38
+ // === Default State ===
39
+ const DEFAULT_STABLECOIN_VAULT = {
40
+ symbol: '',
41
+ name: '',
42
+ chainId: 0,
43
+ tokenAddress: '',
44
+ vaultAddress: '',
45
+ currentAPY: '0',
46
+ supply: '0',
47
+ liquidity: '0',
48
+ };
49
+ /**
50
+ * Gets the default state for the EarnController.
51
+ *
52
+ * @returns The default EarnController state.
53
+ */
54
+ function getDefaultEarnControllerState() {
55
+ return {
56
+ pooled_staking: {
57
+ pooledStakes: {
58
+ account: '',
59
+ lifetimeRewards: '0',
60
+ assets: '0',
61
+ exitRequests: [],
62
+ },
63
+ exchangeRate: '1',
64
+ vaultData: {
65
+ apy: '0',
66
+ capacity: '0',
67
+ feePercent: 0,
68
+ totalAssets: '0',
69
+ vaultAddress: '0x0000000000000000000000000000000000000000',
70
+ },
71
+ isEligible: false,
72
+ },
73
+ stablecoin_lending: {
74
+ vaults: [DEFAULT_STABLECOIN_VAULT],
75
+ },
76
+ lastUpdated: 0,
77
+ };
78
+ }
79
+ exports.getDefaultEarnControllerState = getDefaultEarnControllerState;
80
+ // === CONTROLLER DEFINITION ===
81
+ /**
82
+ * EarnController manages DeFi earning opportunities across different protocols and chains.
83
+ */
84
+ class EarnController extends base_controller_1.BaseController {
85
+ constructor({ messenger, state = {}, }) {
86
+ super({
87
+ name: exports.controllerName,
88
+ metadata: earnControllerMetadata,
89
+ messenger,
90
+ state: {
91
+ ...getDefaultEarnControllerState(),
92
+ ...state,
93
+ },
94
+ });
95
+ _EarnController_instances.add(this);
96
+ _EarnController_stakeSDK.set(this, null);
97
+ _EarnController_selectedNetworkClientId.set(this, void 0);
98
+ _EarnController_stakingApiService.set(this, new stake_sdk_1.StakingApiService());
99
+ __classPrivateFieldGet(this, _EarnController_instances, "m", _EarnController_initializeSDK).call(this);
100
+ this.refreshPooledStakingData().catch(console.error);
101
+ const { selectedNetworkClientId } = this.messagingSystem.call('NetworkController:getState');
102
+ __classPrivateFieldSet(this, _EarnController_selectedNetworkClientId, selectedNetworkClientId, "f");
103
+ this.messagingSystem.subscribe('NetworkController:stateChange', (networkControllerState) => {
104
+ if (networkControllerState.selectedNetworkClientId !==
105
+ __classPrivateFieldGet(this, _EarnController_selectedNetworkClientId, "f")) {
106
+ __classPrivateFieldGet(this, _EarnController_instances, "m", _EarnController_initializeSDK).call(this, networkControllerState.selectedNetworkClientId);
107
+ this.refreshPooledStakingData().catch(console.error);
108
+ }
109
+ __classPrivateFieldSet(this, _EarnController_selectedNetworkClientId, networkControllerState.selectedNetworkClientId, "f");
110
+ });
111
+ // Listen for account changes
112
+ this.messagingSystem.subscribe('AccountsController:selectedAccountChange', () => {
113
+ this.refreshPooledStakingData().catch(console.error);
114
+ });
115
+ }
116
+ /**
117
+ * Refreshes the pooled stakes data for the current account.
118
+ * Fetches updated stake information including lifetime rewards, assets, and exit requests
119
+ * from the staking API service and updates the state.
120
+ *
121
+ * @returns A promise that resolves when the stakes data has been updated
122
+ */
123
+ async refreshPooledStakes() {
124
+ const currentAccount = __classPrivateFieldGet(this, _EarnController_instances, "m", _EarnController_getCurrentAccount).call(this);
125
+ if (!currentAccount?.address) {
126
+ return;
127
+ }
128
+ const chainId = __classPrivateFieldGet(this, _EarnController_instances, "m", _EarnController_getCurrentChainId).call(this);
129
+ const { accounts, exchangeRate } = await __classPrivateFieldGet(this, _EarnController_stakingApiService, "f").getPooledStakes([currentAccount.address], chainId);
130
+ this.update((state) => {
131
+ state.pooled_staking.pooledStakes = accounts[0];
132
+ state.pooled_staking.exchangeRate = exchangeRate;
133
+ });
134
+ }
135
+ /**
136
+ * Refreshes the staking eligibility status for the current account.
137
+ * Updates the eligibility status in the controller state based on the location and address blocklist for compliance.
138
+ *
139
+ * @returns A promise that resolves when the eligibility status has been updated
140
+ */
141
+ async refreshStakingEligibility() {
142
+ const currentAccount = __classPrivateFieldGet(this, _EarnController_instances, "m", _EarnController_getCurrentAccount).call(this);
143
+ if (!currentAccount?.address) {
144
+ return;
145
+ }
146
+ const { eligible: isEligible } = await __classPrivateFieldGet(this, _EarnController_stakingApiService, "f").getPooledStakingEligibility([
147
+ currentAccount.address,
148
+ ]);
149
+ this.update((state) => {
150
+ state.pooled_staking.isEligible = isEligible;
151
+ });
152
+ }
153
+ /**
154
+ * Refreshes vault data for the current chain.
155
+ * Updates the vault data in the controller state including APY, capacity,
156
+ * fee percentage, total assets, and vault address.
157
+ *
158
+ * @returns A promise that resolves when the vault data has been updated
159
+ */
160
+ async refreshVaultData() {
161
+ const chainId = __classPrivateFieldGet(this, _EarnController_instances, "m", _EarnController_getCurrentChainId).call(this);
162
+ const vaultData = await __classPrivateFieldGet(this, _EarnController_stakingApiService, "f").getVaultData(chainId);
163
+ this.update((state) => {
164
+ state.pooled_staking.vaultData = vaultData;
165
+ });
166
+ }
167
+ /**
168
+ * Refreshes all pooled staking related data including stakes, eligibility, and vault data.
169
+ * This method allows partial success, meaning some data may update while other requests fail.
170
+ * All errors are collected and thrown as a single error message.
171
+ *
172
+ * @returns A promise that resolves when all possible data has been updated
173
+ * @throws {Error} If any of the refresh operations fail, with concatenated error messages
174
+ */
175
+ async refreshPooledStakingData() {
176
+ const errors = [];
177
+ await Promise.all([
178
+ this.refreshPooledStakes().catch((error) => {
179
+ errors.push(error);
180
+ }),
181
+ this.refreshStakingEligibility().catch((error) => {
182
+ errors.push(error);
183
+ }),
184
+ this.refreshVaultData().catch((error) => {
185
+ errors.push(error);
186
+ }),
187
+ ]);
188
+ if (errors.length > 0) {
189
+ throw new Error(`Failed to refresh some staking data: ${errors
190
+ .map((e) => e.message)
191
+ .join(', ')}`);
192
+ }
193
+ }
194
+ }
195
+ exports.EarnController = EarnController;
196
+ _EarnController_stakeSDK = new WeakMap(), _EarnController_selectedNetworkClientId = new WeakMap(), _EarnController_stakingApiService = new WeakMap(), _EarnController_instances = new WeakSet(), _EarnController_initializeSDK = function _EarnController_initializeSDK(networkClientId) {
197
+ const { selectedNetworkClientId } = networkClientId
198
+ ? { selectedNetworkClientId: networkClientId }
199
+ : this.messagingSystem.call('NetworkController:getState');
200
+ const networkClient = this.messagingSystem.call('NetworkController:getNetworkClientById', selectedNetworkClientId);
201
+ if (!networkClient?.provider) {
202
+ __classPrivateFieldSet(this, _EarnController_stakeSDK, null, "f");
203
+ return;
204
+ }
205
+ const provider = new providers_1.Web3Provider(networkClient.provider);
206
+ const { chainId } = networkClient.configuration;
207
+ // Initialize appropriate contracts based on chainId
208
+ const config = {
209
+ chainId: (0, controller_utils_1.convertHexToDecimal)(chainId),
210
+ };
211
+ try {
212
+ __classPrivateFieldSet(this, _EarnController_stakeSDK, stake_sdk_1.StakeSdk.create(config), "f");
213
+ __classPrivateFieldGet(this, _EarnController_stakeSDK, "f").pooledStakingContract.connectSignerOrProvider(provider);
214
+ }
215
+ catch (error) {
216
+ __classPrivateFieldSet(this, _EarnController_stakeSDK, null, "f");
217
+ // Only log unexpected errors, not unsupported chain errors
218
+ if (!(error instanceof Error &&
219
+ error.message.includes('Unsupported chainId'))) {
220
+ console.error('Stake SDK initialization failed:', error);
221
+ }
222
+ }
223
+ }, _EarnController_getCurrentAccount = function _EarnController_getCurrentAccount() {
224
+ return this.messagingSystem.call('AccountsController:getSelectedAccount');
225
+ }, _EarnController_getCurrentChainId = function _EarnController_getCurrentChainId() {
226
+ const { selectedNetworkClientId } = this.messagingSystem.call('NetworkController:getState');
227
+ const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', selectedNetworkClientId);
228
+ return (0, controller_utils_1.convertHexToDecimal)(chainId);
229
+ };
230
+ //# sourceMappingURL=EarnController.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EarnController.cjs","sourceRoot":"","sources":["../src/EarnController.ts"],"names":[],"mappings":"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","sourcesContent":["import { Web3Provider } from '@ethersproject/providers';\nimport type {\n AccountsControllerGetSelectedAccountAction,\n AccountsControllerSelectedAccountChangeEvent,\n} from '@metamask/accounts-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n RestrictedMessenger,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport { convertHexToDecimal } from '@metamask/controller-utils';\nimport type { NetworkControllerStateChangeEvent } from '@metamask/network-controller';\nimport type {\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetStateAction,\n} from '@metamask/network-controller';\nimport {\n StakeSdk,\n StakingApiService,\n type PooledStake,\n type StakeSdkConfig,\n type VaultData,\n} from '@metamask/stake-sdk';\n\nexport const controllerName = 'EarnController';\n\nexport type PooledStakingState = {\n pooledStakes: PooledStake;\n exchangeRate: string;\n vaultData: VaultData;\n isEligible: boolean;\n};\n\nexport type StablecoinLendingState = {\n vaults: StablecoinVault[];\n};\n\nexport type StablecoinVault = {\n symbol: string;\n name: string;\n chainId: number;\n tokenAddress: string;\n vaultAddress: string;\n currentAPY: string;\n supply: string;\n liquidity: string;\n};\n\n/**\n * Metadata for the EarnController.\n */\nconst earnControllerMetadata: StateMetadata<EarnControllerState> = {\n pooled_staking: {\n persist: true,\n anonymous: false,\n },\n stablecoin_lending: {\n persist: true,\n anonymous: false,\n },\n lastUpdated: {\n persist: false,\n anonymous: true,\n },\n};\n\n// === State Types ===\nexport type EarnControllerState = {\n pooled_staking: PooledStakingState;\n stablecoin_lending?: StablecoinLendingState;\n lastUpdated: number;\n};\n\n// === Default State ===\nconst DEFAULT_STABLECOIN_VAULT: StablecoinVault = {\n symbol: '',\n name: '',\n chainId: 0,\n tokenAddress: '',\n vaultAddress: '',\n currentAPY: '0',\n supply: '0',\n liquidity: '0',\n};\n\n/**\n * Gets the default state for the EarnController.\n *\n * @returns The default EarnController state.\n */\nexport function getDefaultEarnControllerState(): EarnControllerState {\n return {\n pooled_staking: {\n pooledStakes: {\n account: '',\n lifetimeRewards: '0',\n assets: '0',\n exitRequests: [],\n },\n exchangeRate: '1',\n vaultData: {\n apy: '0',\n capacity: '0',\n feePercent: 0,\n totalAssets: '0',\n vaultAddress: '0x0000000000000000000000000000000000000000',\n },\n isEligible: false,\n },\n stablecoin_lending: {\n vaults: [DEFAULT_STABLECOIN_VAULT],\n },\n lastUpdated: 0,\n };\n}\n\n// === MESSENGER ===\n\n/**\n * The action which can be used to retrieve the state of the EarnController.\n */\nexport type EarnControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n EarnControllerState\n>;\n\n/**\n * All actions that EarnController registers, to be called externally.\n */\nexport type EarnControllerActions = EarnControllerGetStateAction;\n\n/**\n * All actions that EarnController calls internally.\n */\nexport type AllowedActions =\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetStateAction\n | AccountsControllerGetSelectedAccountAction;\n\n/**\n * The event that EarnController publishes when updating state.\n */\nexport type EarnControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n EarnControllerState\n>;\n\n/**\n * All events that EarnController publishes, to be subscribed to externally.\n */\nexport type EarnControllerEvents = EarnControllerStateChangeEvent;\n\n/**\n * All events that EarnController subscribes to internally.\n */\nexport type AllowedEvents =\n | AccountsControllerSelectedAccountChangeEvent\n | NetworkControllerStateChangeEvent;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * EarnController.\n */\nexport type EarnControllerMessenger = RestrictedMessenger<\n typeof controllerName,\n EarnControllerActions | AllowedActions,\n EarnControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\n// === CONTROLLER DEFINITION ===\n\n/**\n * EarnController manages DeFi earning opportunities across different protocols and chains.\n */\nexport class EarnController extends BaseController<\n typeof controllerName,\n EarnControllerState,\n EarnControllerMessenger\n> {\n #stakeSDK: StakeSdk | null = null;\n\n #selectedNetworkClientId?: string;\n\n readonly #stakingApiService: StakingApiService = new StakingApiService();\n\n constructor({\n messenger,\n state = {},\n }: {\n messenger: EarnControllerMessenger;\n state?: Partial<EarnControllerState>;\n }) {\n super({\n name: controllerName,\n metadata: earnControllerMetadata,\n messenger,\n state: {\n ...getDefaultEarnControllerState(),\n ...state,\n },\n });\n\n this.#initializeSDK();\n this.refreshPooledStakingData().catch(console.error);\n\n const { selectedNetworkClientId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n this.#selectedNetworkClientId = selectedNetworkClientId;\n\n this.messagingSystem.subscribe(\n 'NetworkController:stateChange',\n (networkControllerState) => {\n if (\n networkControllerState.selectedNetworkClientId !==\n this.#selectedNetworkClientId\n ) {\n this.#initializeSDK(networkControllerState.selectedNetworkClientId);\n this.refreshPooledStakingData().catch(console.error);\n }\n this.#selectedNetworkClientId =\n networkControllerState.selectedNetworkClientId;\n },\n );\n\n // Listen for account changes\n this.messagingSystem.subscribe(\n 'AccountsController:selectedAccountChange',\n () => {\n this.refreshPooledStakingData().catch(console.error);\n },\n );\n }\n\n #initializeSDK(networkClientId?: string) {\n const { selectedNetworkClientId } = networkClientId\n ? { selectedNetworkClientId: networkClientId }\n : this.messagingSystem.call('NetworkController:getState');\n\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n selectedNetworkClientId,\n );\n\n if (!networkClient?.provider) {\n this.#stakeSDK = null;\n return;\n }\n\n const provider = new Web3Provider(networkClient.provider);\n const { chainId } = networkClient.configuration;\n\n // Initialize appropriate contracts based on chainId\n const config: StakeSdkConfig = {\n chainId: convertHexToDecimal(chainId),\n };\n\n try {\n this.#stakeSDK = StakeSdk.create(config);\n this.#stakeSDK.pooledStakingContract.connectSignerOrProvider(provider);\n } catch (error) {\n this.#stakeSDK = null;\n // Only log unexpected errors, not unsupported chain errors\n if (\n !(\n error instanceof Error &&\n error.message.includes('Unsupported chainId')\n )\n ) {\n console.error('Stake SDK initialization failed:', error);\n }\n }\n }\n\n #getCurrentAccount() {\n return this.messagingSystem.call('AccountsController:getSelectedAccount');\n }\n\n #getCurrentChainId(): number {\n const { selectedNetworkClientId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n const {\n configuration: { chainId },\n } = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n selectedNetworkClientId,\n );\n return convertHexToDecimal(chainId);\n }\n\n /**\n * Refreshes the pooled stakes data for the current account.\n * Fetches updated stake information including lifetime rewards, assets, and exit requests\n * from the staking API service and updates the state.\n *\n * @returns A promise that resolves when the stakes data has been updated\n */\n async refreshPooledStakes(): Promise<void> {\n const currentAccount = this.#getCurrentAccount();\n if (!currentAccount?.address) {\n return;\n }\n\n const chainId = this.#getCurrentChainId();\n\n const { accounts, exchangeRate } =\n await this.#stakingApiService.getPooledStakes(\n [currentAccount.address],\n chainId,\n );\n\n this.update((state) => {\n state.pooled_staking.pooledStakes = accounts[0];\n state.pooled_staking.exchangeRate = exchangeRate;\n });\n }\n\n /**\n * Refreshes the staking eligibility status for the current account.\n * Updates the eligibility status in the controller state based on the location and address blocklist for compliance.\n *\n * @returns A promise that resolves when the eligibility status has been updated\n */\n async refreshStakingEligibility(): Promise<void> {\n const currentAccount = this.#getCurrentAccount();\n if (!currentAccount?.address) {\n return;\n }\n\n const { eligible: isEligible } =\n await this.#stakingApiService.getPooledStakingEligibility([\n currentAccount.address,\n ]);\n\n this.update((state) => {\n state.pooled_staking.isEligible = isEligible;\n });\n }\n\n /**\n * Refreshes vault data for the current chain.\n * Updates the vault data in the controller state including APY, capacity,\n * fee percentage, total assets, and vault address.\n *\n * @returns A promise that resolves when the vault data has been updated\n */\n async refreshVaultData(): Promise<void> {\n const chainId = this.#getCurrentChainId();\n const vaultData = await this.#stakingApiService.getVaultData(chainId);\n\n this.update((state) => {\n state.pooled_staking.vaultData = vaultData;\n });\n }\n\n /**\n * Refreshes all pooled staking related data including stakes, eligibility, and vault data.\n * This method allows partial success, meaning some data may update while other requests fail.\n * All errors are collected and thrown as a single error message.\n *\n * @returns A promise that resolves when all possible data has been updated\n * @throws {Error} If any of the refresh operations fail, with concatenated error messages\n */\n async refreshPooledStakingData(): Promise<void> {\n const errors: Error[] = [];\n\n await Promise.all([\n this.refreshPooledStakes().catch((error) => {\n errors.push(error);\n }),\n this.refreshStakingEligibility().catch((error) => {\n errors.push(error);\n }),\n this.refreshVaultData().catch((error) => {\n errors.push(error);\n }),\n ]);\n\n if (errors.length > 0) {\n throw new Error(\n `Failed to refresh some staking data: ${errors\n .map((e) => e.message)\n .join(', ')}`,\n );\n }\n }\n}\n"]}
@@ -0,0 +1,109 @@
1
+ import type { AccountsControllerGetSelectedAccountAction, AccountsControllerSelectedAccountChangeEvent } from "@metamask/accounts-controller";
2
+ import type { ControllerGetStateAction, ControllerStateChangeEvent, RestrictedMessenger } from "@metamask/base-controller";
3
+ import { BaseController } from "@metamask/base-controller";
4
+ import type { NetworkControllerStateChangeEvent } from "@metamask/network-controller";
5
+ import type { NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction } from "@metamask/network-controller";
6
+ import { type PooledStake, type VaultData } from "@metamask/stake-sdk";
7
+ export declare const controllerName = "EarnController";
8
+ export type PooledStakingState = {
9
+ pooledStakes: PooledStake;
10
+ exchangeRate: string;
11
+ vaultData: VaultData;
12
+ isEligible: boolean;
13
+ };
14
+ export type StablecoinLendingState = {
15
+ vaults: StablecoinVault[];
16
+ };
17
+ export type StablecoinVault = {
18
+ symbol: string;
19
+ name: string;
20
+ chainId: number;
21
+ tokenAddress: string;
22
+ vaultAddress: string;
23
+ currentAPY: string;
24
+ supply: string;
25
+ liquidity: string;
26
+ };
27
+ export type EarnControllerState = {
28
+ pooled_staking: PooledStakingState;
29
+ stablecoin_lending?: StablecoinLendingState;
30
+ lastUpdated: number;
31
+ };
32
+ /**
33
+ * Gets the default state for the EarnController.
34
+ *
35
+ * @returns The default EarnController state.
36
+ */
37
+ export declare function getDefaultEarnControllerState(): EarnControllerState;
38
+ /**
39
+ * The action which can be used to retrieve the state of the EarnController.
40
+ */
41
+ export type EarnControllerGetStateAction = ControllerGetStateAction<typeof controllerName, EarnControllerState>;
42
+ /**
43
+ * All actions that EarnController registers, to be called externally.
44
+ */
45
+ export type EarnControllerActions = EarnControllerGetStateAction;
46
+ /**
47
+ * All actions that EarnController calls internally.
48
+ */
49
+ export type AllowedActions = NetworkControllerGetNetworkClientByIdAction | NetworkControllerGetStateAction | AccountsControllerGetSelectedAccountAction;
50
+ /**
51
+ * The event that EarnController publishes when updating state.
52
+ */
53
+ export type EarnControllerStateChangeEvent = ControllerStateChangeEvent<typeof controllerName, EarnControllerState>;
54
+ /**
55
+ * All events that EarnController publishes, to be subscribed to externally.
56
+ */
57
+ export type EarnControllerEvents = EarnControllerStateChangeEvent;
58
+ /**
59
+ * All events that EarnController subscribes to internally.
60
+ */
61
+ export type AllowedEvents = AccountsControllerSelectedAccountChangeEvent | NetworkControllerStateChangeEvent;
62
+ /**
63
+ * The messenger which is restricted to actions and events accessed by
64
+ * EarnController.
65
+ */
66
+ export type EarnControllerMessenger = RestrictedMessenger<typeof controllerName, EarnControllerActions | AllowedActions, EarnControllerEvents | AllowedEvents, AllowedActions['type'], AllowedEvents['type']>;
67
+ /**
68
+ * EarnController manages DeFi earning opportunities across different protocols and chains.
69
+ */
70
+ export declare class EarnController extends BaseController<typeof controllerName, EarnControllerState, EarnControllerMessenger> {
71
+ #private;
72
+ constructor({ messenger, state, }: {
73
+ messenger: EarnControllerMessenger;
74
+ state?: Partial<EarnControllerState>;
75
+ });
76
+ /**
77
+ * Refreshes the pooled stakes data for the current account.
78
+ * Fetches updated stake information including lifetime rewards, assets, and exit requests
79
+ * from the staking API service and updates the state.
80
+ *
81
+ * @returns A promise that resolves when the stakes data has been updated
82
+ */
83
+ refreshPooledStakes(): Promise<void>;
84
+ /**
85
+ * Refreshes the staking eligibility status for the current account.
86
+ * Updates the eligibility status in the controller state based on the location and address blocklist for compliance.
87
+ *
88
+ * @returns A promise that resolves when the eligibility status has been updated
89
+ */
90
+ refreshStakingEligibility(): Promise<void>;
91
+ /**
92
+ * Refreshes vault data for the current chain.
93
+ * Updates the vault data in the controller state including APY, capacity,
94
+ * fee percentage, total assets, and vault address.
95
+ *
96
+ * @returns A promise that resolves when the vault data has been updated
97
+ */
98
+ refreshVaultData(): Promise<void>;
99
+ /**
100
+ * Refreshes all pooled staking related data including stakes, eligibility, and vault data.
101
+ * This method allows partial success, meaning some data may update while other requests fail.
102
+ * All errors are collected and thrown as a single error message.
103
+ *
104
+ * @returns A promise that resolves when all possible data has been updated
105
+ * @throws {Error} If any of the refresh operations fail, with concatenated error messages
106
+ */
107
+ refreshPooledStakingData(): Promise<void>;
108
+ }
109
+ //# sourceMappingURL=EarnController.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EarnController.d.cts","sourceRoot":"","sources":["../src/EarnController.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,0CAA0C,EAC1C,4CAA4C,EAC7C,sCAAsC;AACvC,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAC1B,mBAAmB,EAEpB,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAE3D,OAAO,KAAK,EAAE,iCAAiC,EAAE,qCAAqC;AACtF,OAAO,KAAK,EACV,2CAA2C,EAC3C,+BAA+B,EAChC,qCAAqC;AACtC,OAAO,EAGL,KAAK,WAAW,EAEhB,KAAK,SAAS,EACf,4BAA4B;AAE7B,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,WAAW,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAqBF,MAAM,MAAM,mBAAmB,GAAG;IAChC,cAAc,EAAE,kBAAkB,CAAC;IACnC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IAC5C,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAcF;;;;GAIG;AACH,wBAAgB,6BAA6B,IAAI,mBAAmB,CAwBnE;AAID;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,wBAAwB,CACjE,OAAO,cAAc,EACrB,mBAAmB,CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,4BAA4B,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,2CAA2C,GAC3C,+BAA+B,GAC/B,0CAA0C,CAAC;AAE/C;;GAEG;AACH,MAAM,MAAM,8BAA8B,GAAG,0BAA0B,CACrE,OAAO,cAAc,EACrB,mBAAmB,CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,8BAA8B,CAAC;AAElE;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB,4CAA4C,GAC5C,iCAAiC,CAAC;AAEtC;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,mBAAmB,CACvD,OAAO,cAAc,EACrB,qBAAqB,GAAG,cAAc,EACtC,oBAAoB,GAAG,aAAa,EACpC,cAAc,CAAC,MAAM,CAAC,EACtB,aAAa,CAAC,MAAM,CAAC,CACtB,CAAC;AAIF;;GAEG;AACH,qBAAa,cAAe,SAAQ,cAAc,CAChD,OAAO,cAAc,EACrB,mBAAmB,EACnB,uBAAuB,CACxB;;gBAOa,EACV,SAAS,EACT,KAAU,GACX,EAAE;QACD,SAAS,EAAE,uBAAuB,CAAC;QACnC,KAAK,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;KACtC;IAoGD;;;;;;OAMG;IACG,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB1C;;;;;OAKG;IACG,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBhD;;;;;;OAMG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IASvC;;;;;;;OAOG;IACG,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;CAuBhD"}
@@ -0,0 +1,109 @@
1
+ import type { AccountsControllerGetSelectedAccountAction, AccountsControllerSelectedAccountChangeEvent } from "@metamask/accounts-controller";
2
+ import type { ControllerGetStateAction, ControllerStateChangeEvent, RestrictedMessenger } from "@metamask/base-controller";
3
+ import { BaseController } from "@metamask/base-controller";
4
+ import type { NetworkControllerStateChangeEvent } from "@metamask/network-controller";
5
+ import type { NetworkControllerGetNetworkClientByIdAction, NetworkControllerGetStateAction } from "@metamask/network-controller";
6
+ import { type PooledStake, type VaultData } from "@metamask/stake-sdk";
7
+ export declare const controllerName = "EarnController";
8
+ export type PooledStakingState = {
9
+ pooledStakes: PooledStake;
10
+ exchangeRate: string;
11
+ vaultData: VaultData;
12
+ isEligible: boolean;
13
+ };
14
+ export type StablecoinLendingState = {
15
+ vaults: StablecoinVault[];
16
+ };
17
+ export type StablecoinVault = {
18
+ symbol: string;
19
+ name: string;
20
+ chainId: number;
21
+ tokenAddress: string;
22
+ vaultAddress: string;
23
+ currentAPY: string;
24
+ supply: string;
25
+ liquidity: string;
26
+ };
27
+ export type EarnControllerState = {
28
+ pooled_staking: PooledStakingState;
29
+ stablecoin_lending?: StablecoinLendingState;
30
+ lastUpdated: number;
31
+ };
32
+ /**
33
+ * Gets the default state for the EarnController.
34
+ *
35
+ * @returns The default EarnController state.
36
+ */
37
+ export declare function getDefaultEarnControllerState(): EarnControllerState;
38
+ /**
39
+ * The action which can be used to retrieve the state of the EarnController.
40
+ */
41
+ export type EarnControllerGetStateAction = ControllerGetStateAction<typeof controllerName, EarnControllerState>;
42
+ /**
43
+ * All actions that EarnController registers, to be called externally.
44
+ */
45
+ export type EarnControllerActions = EarnControllerGetStateAction;
46
+ /**
47
+ * All actions that EarnController calls internally.
48
+ */
49
+ export type AllowedActions = NetworkControllerGetNetworkClientByIdAction | NetworkControllerGetStateAction | AccountsControllerGetSelectedAccountAction;
50
+ /**
51
+ * The event that EarnController publishes when updating state.
52
+ */
53
+ export type EarnControllerStateChangeEvent = ControllerStateChangeEvent<typeof controllerName, EarnControllerState>;
54
+ /**
55
+ * All events that EarnController publishes, to be subscribed to externally.
56
+ */
57
+ export type EarnControllerEvents = EarnControllerStateChangeEvent;
58
+ /**
59
+ * All events that EarnController subscribes to internally.
60
+ */
61
+ export type AllowedEvents = AccountsControllerSelectedAccountChangeEvent | NetworkControllerStateChangeEvent;
62
+ /**
63
+ * The messenger which is restricted to actions and events accessed by
64
+ * EarnController.
65
+ */
66
+ export type EarnControllerMessenger = RestrictedMessenger<typeof controllerName, EarnControllerActions | AllowedActions, EarnControllerEvents | AllowedEvents, AllowedActions['type'], AllowedEvents['type']>;
67
+ /**
68
+ * EarnController manages DeFi earning opportunities across different protocols and chains.
69
+ */
70
+ export declare class EarnController extends BaseController<typeof controllerName, EarnControllerState, EarnControllerMessenger> {
71
+ #private;
72
+ constructor({ messenger, state, }: {
73
+ messenger: EarnControllerMessenger;
74
+ state?: Partial<EarnControllerState>;
75
+ });
76
+ /**
77
+ * Refreshes the pooled stakes data for the current account.
78
+ * Fetches updated stake information including lifetime rewards, assets, and exit requests
79
+ * from the staking API service and updates the state.
80
+ *
81
+ * @returns A promise that resolves when the stakes data has been updated
82
+ */
83
+ refreshPooledStakes(): Promise<void>;
84
+ /**
85
+ * Refreshes the staking eligibility status for the current account.
86
+ * Updates the eligibility status in the controller state based on the location and address blocklist for compliance.
87
+ *
88
+ * @returns A promise that resolves when the eligibility status has been updated
89
+ */
90
+ refreshStakingEligibility(): Promise<void>;
91
+ /**
92
+ * Refreshes vault data for the current chain.
93
+ * Updates the vault data in the controller state including APY, capacity,
94
+ * fee percentage, total assets, and vault address.
95
+ *
96
+ * @returns A promise that resolves when the vault data has been updated
97
+ */
98
+ refreshVaultData(): Promise<void>;
99
+ /**
100
+ * Refreshes all pooled staking related data including stakes, eligibility, and vault data.
101
+ * This method allows partial success, meaning some data may update while other requests fail.
102
+ * All errors are collected and thrown as a single error message.
103
+ *
104
+ * @returns A promise that resolves when all possible data has been updated
105
+ * @throws {Error} If any of the refresh operations fail, with concatenated error messages
106
+ */
107
+ refreshPooledStakingData(): Promise<void>;
108
+ }
109
+ //# sourceMappingURL=EarnController.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EarnController.d.mts","sourceRoot":"","sources":["../src/EarnController.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,0CAA0C,EAC1C,4CAA4C,EAC7C,sCAAsC;AACvC,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAC1B,mBAAmB,EAEpB,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAE3D,OAAO,KAAK,EAAE,iCAAiC,EAAE,qCAAqC;AACtF,OAAO,KAAK,EACV,2CAA2C,EAC3C,+BAA+B,EAChC,qCAAqC;AACtC,OAAO,EAGL,KAAK,WAAW,EAEhB,KAAK,SAAS,EACf,4BAA4B;AAE7B,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,WAAW,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAqBF,MAAM,MAAM,mBAAmB,GAAG;IAChC,cAAc,EAAE,kBAAkB,CAAC;IACnC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IAC5C,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAcF;;;;GAIG;AACH,wBAAgB,6BAA6B,IAAI,mBAAmB,CAwBnE;AAID;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,wBAAwB,CACjE,OAAO,cAAc,EACrB,mBAAmB,CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,4BAA4B,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,2CAA2C,GAC3C,+BAA+B,GAC/B,0CAA0C,CAAC;AAE/C;;GAEG;AACH,MAAM,MAAM,8BAA8B,GAAG,0BAA0B,CACrE,OAAO,cAAc,EACrB,mBAAmB,CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,8BAA8B,CAAC;AAElE;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB,4CAA4C,GAC5C,iCAAiC,CAAC;AAEtC;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,mBAAmB,CACvD,OAAO,cAAc,EACrB,qBAAqB,GAAG,cAAc,EACtC,oBAAoB,GAAG,aAAa,EACpC,cAAc,CAAC,MAAM,CAAC,EACtB,aAAa,CAAC,MAAM,CAAC,CACtB,CAAC;AAIF;;GAEG;AACH,qBAAa,cAAe,SAAQ,cAAc,CAChD,OAAO,cAAc,EACrB,mBAAmB,EACnB,uBAAuB,CACxB;;gBAOa,EACV,SAAS,EACT,KAAU,GACX,EAAE;QACD,SAAS,EAAE,uBAAuB,CAAC;QACnC,KAAK,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;KACtC;IAoGD;;;;;;OAMG;IACG,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB1C;;;;;OAKG;IACG,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBhD;;;;;;OAMG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IASvC;;;;;;;OAOG;IACG,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;CAuBhD"}
@@ -0,0 +1,225 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ 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");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
+ if (kind === "m") throw new TypeError("Private method is not writable");
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
+ 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");
10
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
+ };
12
+ var _EarnController_instances, _EarnController_stakeSDK, _EarnController_selectedNetworkClientId, _EarnController_stakingApiService, _EarnController_initializeSDK, _EarnController_getCurrentAccount, _EarnController_getCurrentChainId;
13
+ import { Web3Provider } from "@ethersproject/providers";
14
+ import { BaseController } from "@metamask/base-controller";
15
+ import { convertHexToDecimal } from "@metamask/controller-utils";
16
+ import { StakeSdk, StakingApiService } from "@metamask/stake-sdk";
17
+ export const controllerName = 'EarnController';
18
+ /**
19
+ * Metadata for the EarnController.
20
+ */
21
+ const earnControllerMetadata = {
22
+ pooled_staking: {
23
+ persist: true,
24
+ anonymous: false,
25
+ },
26
+ stablecoin_lending: {
27
+ persist: true,
28
+ anonymous: false,
29
+ },
30
+ lastUpdated: {
31
+ persist: false,
32
+ anonymous: true,
33
+ },
34
+ };
35
+ // === Default State ===
36
+ const DEFAULT_STABLECOIN_VAULT = {
37
+ symbol: '',
38
+ name: '',
39
+ chainId: 0,
40
+ tokenAddress: '',
41
+ vaultAddress: '',
42
+ currentAPY: '0',
43
+ supply: '0',
44
+ liquidity: '0',
45
+ };
46
+ /**
47
+ * Gets the default state for the EarnController.
48
+ *
49
+ * @returns The default EarnController state.
50
+ */
51
+ export function getDefaultEarnControllerState() {
52
+ return {
53
+ pooled_staking: {
54
+ pooledStakes: {
55
+ account: '',
56
+ lifetimeRewards: '0',
57
+ assets: '0',
58
+ exitRequests: [],
59
+ },
60
+ exchangeRate: '1',
61
+ vaultData: {
62
+ apy: '0',
63
+ capacity: '0',
64
+ feePercent: 0,
65
+ totalAssets: '0',
66
+ vaultAddress: '0x0000000000000000000000000000000000000000',
67
+ },
68
+ isEligible: false,
69
+ },
70
+ stablecoin_lending: {
71
+ vaults: [DEFAULT_STABLECOIN_VAULT],
72
+ },
73
+ lastUpdated: 0,
74
+ };
75
+ }
76
+ // === CONTROLLER DEFINITION ===
77
+ /**
78
+ * EarnController manages DeFi earning opportunities across different protocols and chains.
79
+ */
80
+ export class EarnController extends BaseController {
81
+ constructor({ messenger, state = {}, }) {
82
+ super({
83
+ name: controllerName,
84
+ metadata: earnControllerMetadata,
85
+ messenger,
86
+ state: {
87
+ ...getDefaultEarnControllerState(),
88
+ ...state,
89
+ },
90
+ });
91
+ _EarnController_instances.add(this);
92
+ _EarnController_stakeSDK.set(this, null);
93
+ _EarnController_selectedNetworkClientId.set(this, void 0);
94
+ _EarnController_stakingApiService.set(this, new StakingApiService());
95
+ __classPrivateFieldGet(this, _EarnController_instances, "m", _EarnController_initializeSDK).call(this);
96
+ this.refreshPooledStakingData().catch(console.error);
97
+ const { selectedNetworkClientId } = this.messagingSystem.call('NetworkController:getState');
98
+ __classPrivateFieldSet(this, _EarnController_selectedNetworkClientId, selectedNetworkClientId, "f");
99
+ this.messagingSystem.subscribe('NetworkController:stateChange', (networkControllerState) => {
100
+ if (networkControllerState.selectedNetworkClientId !==
101
+ __classPrivateFieldGet(this, _EarnController_selectedNetworkClientId, "f")) {
102
+ __classPrivateFieldGet(this, _EarnController_instances, "m", _EarnController_initializeSDK).call(this, networkControllerState.selectedNetworkClientId);
103
+ this.refreshPooledStakingData().catch(console.error);
104
+ }
105
+ __classPrivateFieldSet(this, _EarnController_selectedNetworkClientId, networkControllerState.selectedNetworkClientId, "f");
106
+ });
107
+ // Listen for account changes
108
+ this.messagingSystem.subscribe('AccountsController:selectedAccountChange', () => {
109
+ this.refreshPooledStakingData().catch(console.error);
110
+ });
111
+ }
112
+ /**
113
+ * Refreshes the pooled stakes data for the current account.
114
+ * Fetches updated stake information including lifetime rewards, assets, and exit requests
115
+ * from the staking API service and updates the state.
116
+ *
117
+ * @returns A promise that resolves when the stakes data has been updated
118
+ */
119
+ async refreshPooledStakes() {
120
+ const currentAccount = __classPrivateFieldGet(this, _EarnController_instances, "m", _EarnController_getCurrentAccount).call(this);
121
+ if (!currentAccount?.address) {
122
+ return;
123
+ }
124
+ const chainId = __classPrivateFieldGet(this, _EarnController_instances, "m", _EarnController_getCurrentChainId).call(this);
125
+ const { accounts, exchangeRate } = await __classPrivateFieldGet(this, _EarnController_stakingApiService, "f").getPooledStakes([currentAccount.address], chainId);
126
+ this.update((state) => {
127
+ state.pooled_staking.pooledStakes = accounts[0];
128
+ state.pooled_staking.exchangeRate = exchangeRate;
129
+ });
130
+ }
131
+ /**
132
+ * Refreshes the staking eligibility status for the current account.
133
+ * Updates the eligibility status in the controller state based on the location and address blocklist for compliance.
134
+ *
135
+ * @returns A promise that resolves when the eligibility status has been updated
136
+ */
137
+ async refreshStakingEligibility() {
138
+ const currentAccount = __classPrivateFieldGet(this, _EarnController_instances, "m", _EarnController_getCurrentAccount).call(this);
139
+ if (!currentAccount?.address) {
140
+ return;
141
+ }
142
+ const { eligible: isEligible } = await __classPrivateFieldGet(this, _EarnController_stakingApiService, "f").getPooledStakingEligibility([
143
+ currentAccount.address,
144
+ ]);
145
+ this.update((state) => {
146
+ state.pooled_staking.isEligible = isEligible;
147
+ });
148
+ }
149
+ /**
150
+ * Refreshes vault data for the current chain.
151
+ * Updates the vault data in the controller state including APY, capacity,
152
+ * fee percentage, total assets, and vault address.
153
+ *
154
+ * @returns A promise that resolves when the vault data has been updated
155
+ */
156
+ async refreshVaultData() {
157
+ const chainId = __classPrivateFieldGet(this, _EarnController_instances, "m", _EarnController_getCurrentChainId).call(this);
158
+ const vaultData = await __classPrivateFieldGet(this, _EarnController_stakingApiService, "f").getVaultData(chainId);
159
+ this.update((state) => {
160
+ state.pooled_staking.vaultData = vaultData;
161
+ });
162
+ }
163
+ /**
164
+ * Refreshes all pooled staking related data including stakes, eligibility, and vault data.
165
+ * This method allows partial success, meaning some data may update while other requests fail.
166
+ * All errors are collected and thrown as a single error message.
167
+ *
168
+ * @returns A promise that resolves when all possible data has been updated
169
+ * @throws {Error} If any of the refresh operations fail, with concatenated error messages
170
+ */
171
+ async refreshPooledStakingData() {
172
+ const errors = [];
173
+ await Promise.all([
174
+ this.refreshPooledStakes().catch((error) => {
175
+ errors.push(error);
176
+ }),
177
+ this.refreshStakingEligibility().catch((error) => {
178
+ errors.push(error);
179
+ }),
180
+ this.refreshVaultData().catch((error) => {
181
+ errors.push(error);
182
+ }),
183
+ ]);
184
+ if (errors.length > 0) {
185
+ throw new Error(`Failed to refresh some staking data: ${errors
186
+ .map((e) => e.message)
187
+ .join(', ')}`);
188
+ }
189
+ }
190
+ }
191
+ _EarnController_stakeSDK = new WeakMap(), _EarnController_selectedNetworkClientId = new WeakMap(), _EarnController_stakingApiService = new WeakMap(), _EarnController_instances = new WeakSet(), _EarnController_initializeSDK = function _EarnController_initializeSDK(networkClientId) {
192
+ const { selectedNetworkClientId } = networkClientId
193
+ ? { selectedNetworkClientId: networkClientId }
194
+ : this.messagingSystem.call('NetworkController:getState');
195
+ const networkClient = this.messagingSystem.call('NetworkController:getNetworkClientById', selectedNetworkClientId);
196
+ if (!networkClient?.provider) {
197
+ __classPrivateFieldSet(this, _EarnController_stakeSDK, null, "f");
198
+ return;
199
+ }
200
+ const provider = new Web3Provider(networkClient.provider);
201
+ const { chainId } = networkClient.configuration;
202
+ // Initialize appropriate contracts based on chainId
203
+ const config = {
204
+ chainId: convertHexToDecimal(chainId),
205
+ };
206
+ try {
207
+ __classPrivateFieldSet(this, _EarnController_stakeSDK, StakeSdk.create(config), "f");
208
+ __classPrivateFieldGet(this, _EarnController_stakeSDK, "f").pooledStakingContract.connectSignerOrProvider(provider);
209
+ }
210
+ catch (error) {
211
+ __classPrivateFieldSet(this, _EarnController_stakeSDK, null, "f");
212
+ // Only log unexpected errors, not unsupported chain errors
213
+ if (!(error instanceof Error &&
214
+ error.message.includes('Unsupported chainId'))) {
215
+ console.error('Stake SDK initialization failed:', error);
216
+ }
217
+ }
218
+ }, _EarnController_getCurrentAccount = function _EarnController_getCurrentAccount() {
219
+ return this.messagingSystem.call('AccountsController:getSelectedAccount');
220
+ }, _EarnController_getCurrentChainId = function _EarnController_getCurrentChainId() {
221
+ const { selectedNetworkClientId } = this.messagingSystem.call('NetworkController:getState');
222
+ const { configuration: { chainId }, } = this.messagingSystem.call('NetworkController:getNetworkClientById', selectedNetworkClientId);
223
+ return convertHexToDecimal(chainId);
224
+ };
225
+ //# sourceMappingURL=EarnController.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EarnController.mjs","sourceRoot":"","sources":["../src/EarnController.ts"],"names":[],"mappings":"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","sourcesContent":["import { Web3Provider } from '@ethersproject/providers';\nimport type {\n AccountsControllerGetSelectedAccountAction,\n AccountsControllerSelectedAccountChangeEvent,\n} from '@metamask/accounts-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n RestrictedMessenger,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport { convertHexToDecimal } from '@metamask/controller-utils';\nimport type { NetworkControllerStateChangeEvent } from '@metamask/network-controller';\nimport type {\n NetworkControllerGetNetworkClientByIdAction,\n NetworkControllerGetStateAction,\n} from '@metamask/network-controller';\nimport {\n StakeSdk,\n StakingApiService,\n type PooledStake,\n type StakeSdkConfig,\n type VaultData,\n} from '@metamask/stake-sdk';\n\nexport const controllerName = 'EarnController';\n\nexport type PooledStakingState = {\n pooledStakes: PooledStake;\n exchangeRate: string;\n vaultData: VaultData;\n isEligible: boolean;\n};\n\nexport type StablecoinLendingState = {\n vaults: StablecoinVault[];\n};\n\nexport type StablecoinVault = {\n symbol: string;\n name: string;\n chainId: number;\n tokenAddress: string;\n vaultAddress: string;\n currentAPY: string;\n supply: string;\n liquidity: string;\n};\n\n/**\n * Metadata for the EarnController.\n */\nconst earnControllerMetadata: StateMetadata<EarnControllerState> = {\n pooled_staking: {\n persist: true,\n anonymous: false,\n },\n stablecoin_lending: {\n persist: true,\n anonymous: false,\n },\n lastUpdated: {\n persist: false,\n anonymous: true,\n },\n};\n\n// === State Types ===\nexport type EarnControllerState = {\n pooled_staking: PooledStakingState;\n stablecoin_lending?: StablecoinLendingState;\n lastUpdated: number;\n};\n\n// === Default State ===\nconst DEFAULT_STABLECOIN_VAULT: StablecoinVault = {\n symbol: '',\n name: '',\n chainId: 0,\n tokenAddress: '',\n vaultAddress: '',\n currentAPY: '0',\n supply: '0',\n liquidity: '0',\n};\n\n/**\n * Gets the default state for the EarnController.\n *\n * @returns The default EarnController state.\n */\nexport function getDefaultEarnControllerState(): EarnControllerState {\n return {\n pooled_staking: {\n pooledStakes: {\n account: '',\n lifetimeRewards: '0',\n assets: '0',\n exitRequests: [],\n },\n exchangeRate: '1',\n vaultData: {\n apy: '0',\n capacity: '0',\n feePercent: 0,\n totalAssets: '0',\n vaultAddress: '0x0000000000000000000000000000000000000000',\n },\n isEligible: false,\n },\n stablecoin_lending: {\n vaults: [DEFAULT_STABLECOIN_VAULT],\n },\n lastUpdated: 0,\n };\n}\n\n// === MESSENGER ===\n\n/**\n * The action which can be used to retrieve the state of the EarnController.\n */\nexport type EarnControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n EarnControllerState\n>;\n\n/**\n * All actions that EarnController registers, to be called externally.\n */\nexport type EarnControllerActions = EarnControllerGetStateAction;\n\n/**\n * All actions that EarnController calls internally.\n */\nexport type AllowedActions =\n | NetworkControllerGetNetworkClientByIdAction\n | NetworkControllerGetStateAction\n | AccountsControllerGetSelectedAccountAction;\n\n/**\n * The event that EarnController publishes when updating state.\n */\nexport type EarnControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n EarnControllerState\n>;\n\n/**\n * All events that EarnController publishes, to be subscribed to externally.\n */\nexport type EarnControllerEvents = EarnControllerStateChangeEvent;\n\n/**\n * All events that EarnController subscribes to internally.\n */\nexport type AllowedEvents =\n | AccountsControllerSelectedAccountChangeEvent\n | NetworkControllerStateChangeEvent;\n\n/**\n * The messenger which is restricted to actions and events accessed by\n * EarnController.\n */\nexport type EarnControllerMessenger = RestrictedMessenger<\n typeof controllerName,\n EarnControllerActions | AllowedActions,\n EarnControllerEvents | AllowedEvents,\n AllowedActions['type'],\n AllowedEvents['type']\n>;\n\n// === CONTROLLER DEFINITION ===\n\n/**\n * EarnController manages DeFi earning opportunities across different protocols and chains.\n */\nexport class EarnController extends BaseController<\n typeof controllerName,\n EarnControllerState,\n EarnControllerMessenger\n> {\n #stakeSDK: StakeSdk | null = null;\n\n #selectedNetworkClientId?: string;\n\n readonly #stakingApiService: StakingApiService = new StakingApiService();\n\n constructor({\n messenger,\n state = {},\n }: {\n messenger: EarnControllerMessenger;\n state?: Partial<EarnControllerState>;\n }) {\n super({\n name: controllerName,\n metadata: earnControllerMetadata,\n messenger,\n state: {\n ...getDefaultEarnControllerState(),\n ...state,\n },\n });\n\n this.#initializeSDK();\n this.refreshPooledStakingData().catch(console.error);\n\n const { selectedNetworkClientId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n this.#selectedNetworkClientId = selectedNetworkClientId;\n\n this.messagingSystem.subscribe(\n 'NetworkController:stateChange',\n (networkControllerState) => {\n if (\n networkControllerState.selectedNetworkClientId !==\n this.#selectedNetworkClientId\n ) {\n this.#initializeSDK(networkControllerState.selectedNetworkClientId);\n this.refreshPooledStakingData().catch(console.error);\n }\n this.#selectedNetworkClientId =\n networkControllerState.selectedNetworkClientId;\n },\n );\n\n // Listen for account changes\n this.messagingSystem.subscribe(\n 'AccountsController:selectedAccountChange',\n () => {\n this.refreshPooledStakingData().catch(console.error);\n },\n );\n }\n\n #initializeSDK(networkClientId?: string) {\n const { selectedNetworkClientId } = networkClientId\n ? { selectedNetworkClientId: networkClientId }\n : this.messagingSystem.call('NetworkController:getState');\n\n const networkClient = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n selectedNetworkClientId,\n );\n\n if (!networkClient?.provider) {\n this.#stakeSDK = null;\n return;\n }\n\n const provider = new Web3Provider(networkClient.provider);\n const { chainId } = networkClient.configuration;\n\n // Initialize appropriate contracts based on chainId\n const config: StakeSdkConfig = {\n chainId: convertHexToDecimal(chainId),\n };\n\n try {\n this.#stakeSDK = StakeSdk.create(config);\n this.#stakeSDK.pooledStakingContract.connectSignerOrProvider(provider);\n } catch (error) {\n this.#stakeSDK = null;\n // Only log unexpected errors, not unsupported chain errors\n if (\n !(\n error instanceof Error &&\n error.message.includes('Unsupported chainId')\n )\n ) {\n console.error('Stake SDK initialization failed:', error);\n }\n }\n }\n\n #getCurrentAccount() {\n return this.messagingSystem.call('AccountsController:getSelectedAccount');\n }\n\n #getCurrentChainId(): number {\n const { selectedNetworkClientId } = this.messagingSystem.call(\n 'NetworkController:getState',\n );\n const {\n configuration: { chainId },\n } = this.messagingSystem.call(\n 'NetworkController:getNetworkClientById',\n selectedNetworkClientId,\n );\n return convertHexToDecimal(chainId);\n }\n\n /**\n * Refreshes the pooled stakes data for the current account.\n * Fetches updated stake information including lifetime rewards, assets, and exit requests\n * from the staking API service and updates the state.\n *\n * @returns A promise that resolves when the stakes data has been updated\n */\n async refreshPooledStakes(): Promise<void> {\n const currentAccount = this.#getCurrentAccount();\n if (!currentAccount?.address) {\n return;\n }\n\n const chainId = this.#getCurrentChainId();\n\n const { accounts, exchangeRate } =\n await this.#stakingApiService.getPooledStakes(\n [currentAccount.address],\n chainId,\n );\n\n this.update((state) => {\n state.pooled_staking.pooledStakes = accounts[0];\n state.pooled_staking.exchangeRate = exchangeRate;\n });\n }\n\n /**\n * Refreshes the staking eligibility status for the current account.\n * Updates the eligibility status in the controller state based on the location and address blocklist for compliance.\n *\n * @returns A promise that resolves when the eligibility status has been updated\n */\n async refreshStakingEligibility(): Promise<void> {\n const currentAccount = this.#getCurrentAccount();\n if (!currentAccount?.address) {\n return;\n }\n\n const { eligible: isEligible } =\n await this.#stakingApiService.getPooledStakingEligibility([\n currentAccount.address,\n ]);\n\n this.update((state) => {\n state.pooled_staking.isEligible = isEligible;\n });\n }\n\n /**\n * Refreshes vault data for the current chain.\n * Updates the vault data in the controller state including APY, capacity,\n * fee percentage, total assets, and vault address.\n *\n * @returns A promise that resolves when the vault data has been updated\n */\n async refreshVaultData(): Promise<void> {\n const chainId = this.#getCurrentChainId();\n const vaultData = await this.#stakingApiService.getVaultData(chainId);\n\n this.update((state) => {\n state.pooled_staking.vaultData = vaultData;\n });\n }\n\n /**\n * Refreshes all pooled staking related data including stakes, eligibility, and vault data.\n * This method allows partial success, meaning some data may update while other requests fail.\n * All errors are collected and thrown as a single error message.\n *\n * @returns A promise that resolves when all possible data has been updated\n * @throws {Error} If any of the refresh operations fail, with concatenated error messages\n */\n async refreshPooledStakingData(): Promise<void> {\n const errors: Error[] = [];\n\n await Promise.all([\n this.refreshPooledStakes().catch((error) => {\n errors.push(error);\n }),\n this.refreshStakingEligibility().catch((error) => {\n errors.push(error);\n }),\n this.refreshVaultData().catch((error) => {\n errors.push(error);\n }),\n ]);\n\n if (errors.length > 0) {\n throw new Error(\n `Failed to refresh some staking data: ${errors\n .map((e) => e.message)\n .join(', ')}`,\n );\n }\n }\n}\n"]}
package/dist/index.cjs ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EarnController = exports.getDefaultEarnControllerState = exports.controllerName = void 0;
4
+ var EarnController_1 = require("./EarnController.cjs");
5
+ Object.defineProperty(exports, "controllerName", { enumerable: true, get: function () { return EarnController_1.controllerName; } });
6
+ Object.defineProperty(exports, "getDefaultEarnControllerState", { enumerable: true, get: function () { return EarnController_1.getDefaultEarnControllerState; } });
7
+ Object.defineProperty(exports, "EarnController", { enumerable: true, get: function () { return EarnController_1.EarnController; } });
8
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAYA,uDAI0B;AAHxB,gHAAA,cAAc,OAAA;AACd,+HAAA,6BAA6B,OAAA;AAC7B,gHAAA,cAAc,OAAA","sourcesContent":["export type {\n PooledStakingState,\n StablecoinLendingState,\n StablecoinVault,\n EarnControllerState,\n EarnControllerGetStateAction,\n EarnControllerStateChangeEvent,\n EarnControllerActions,\n EarnControllerEvents,\n EarnControllerMessenger,\n} from './EarnController';\n\nexport {\n controllerName,\n getDefaultEarnControllerState,\n EarnController,\n} from './EarnController';\n"]}
@@ -0,0 +1,3 @@
1
+ export type { PooledStakingState, StablecoinLendingState, StablecoinVault, EarnControllerState, EarnControllerGetStateAction, EarnControllerStateChangeEvent, EarnControllerActions, EarnControllerEvents, EarnControllerMessenger, } from "./EarnController.cjs";
2
+ export { controllerName, getDefaultEarnControllerState, EarnController, } from "./EarnController.cjs";
3
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,kBAAkB,EAClB,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EACnB,4BAA4B,EAC5B,8BAA8B,EAC9B,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,GACxB,6BAAyB;AAE1B,OAAO,EACL,cAAc,EACd,6BAA6B,EAC7B,cAAc,GACf,6BAAyB"}
@@ -0,0 +1,3 @@
1
+ export type { PooledStakingState, StablecoinLendingState, StablecoinVault, EarnControllerState, EarnControllerGetStateAction, EarnControllerStateChangeEvent, EarnControllerActions, EarnControllerEvents, EarnControllerMessenger, } from "./EarnController.mjs";
2
+ export { controllerName, getDefaultEarnControllerState, EarnController, } from "./EarnController.mjs";
3
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,kBAAkB,EAClB,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EACnB,4BAA4B,EAC5B,8BAA8B,EAC9B,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,GACxB,6BAAyB;AAE1B,OAAO,EACL,cAAc,EACd,6BAA6B,EAC7B,cAAc,GACf,6BAAyB"}
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ export { controllerName, getDefaultEarnControllerState, EarnController } from "./EarnController.mjs";
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,OAAO,EACL,cAAc,EACd,6BAA6B,EAC7B,cAAc,EACf,6BAAyB","sourcesContent":["export type {\n PooledStakingState,\n StablecoinLendingState,\n StablecoinVault,\n EarnControllerState,\n EarnControllerGetStateAction,\n EarnControllerStateChangeEvent,\n EarnControllerActions,\n EarnControllerEvents,\n EarnControllerMessenger,\n} from './EarnController';\n\nexport {\n controllerName,\n getDefaultEarnControllerState,\n EarnController,\n} from './EarnController';\n"]}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@metamask/earn-controller",
3
+ "version": "0.1.0",
4
+ "description": "Manages state for earning features and coordinates interactions between staking services, SDK integrations, and other controllers to enable users to participate in various earning opportunities",
5
+ "keywords": [
6
+ "MetaMask",
7
+ "Ethereum"
8
+ ],
9
+ "homepage": "https://github.com/MetaMask/core/tree/main/packages/earn-controller#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/MetaMask/core/issues"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/MetaMask/core.git"
16
+ },
17
+ "license": "MIT",
18
+ "sideEffects": false,
19
+ "exports": {
20
+ ".": {
21
+ "import": {
22
+ "types": "./dist/index.d.mts",
23
+ "default": "./dist/index.mjs"
24
+ },
25
+ "require": {
26
+ "types": "./dist/index.d.cts",
27
+ "default": "./dist/index.cjs"
28
+ }
29
+ },
30
+ "./package.json": "./package.json"
31
+ },
32
+ "main": "./dist/index.cjs",
33
+ "types": "./dist/index.d.cts",
34
+ "files": [
35
+ "dist/"
36
+ ],
37
+ "scripts": {
38
+ "build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references",
39
+ "build:docs": "typedoc",
40
+ "changelog:validate": "../../scripts/validate-changelog.sh @metamask/earn-controller",
41
+ "changelog:update": "../../scripts/update-changelog.sh @metamask/earn-controller",
42
+ "publish:preview": "yarn npm publish --tag preview",
43
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
44
+ "test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
45
+ "test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
46
+ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
47
+ "since-latest-release": "../../scripts/since-latest-release.sh"
48
+ },
49
+ "dependencies": {
50
+ "@ethersproject/providers": "^5.7.0",
51
+ "@metamask/base-controller": "^7.1.1",
52
+ "@metamask/controller-utils": "^11.4.5",
53
+ "@metamask/stake-sdk": "^1.0.0"
54
+ },
55
+ "devDependencies": {
56
+ "@metamask/accounts-controller": "^22.0.0",
57
+ "@metamask/auto-changelog": "^3.4.4",
58
+ "@metamask/network-controller": "^22.1.1",
59
+ "@types/jest": "^27.4.1",
60
+ "deepmerge": "^4.2.2",
61
+ "jest": "^27.5.1",
62
+ "ts-jest": "^27.1.4",
63
+ "typedoc": "^0.24.8",
64
+ "typedoc-plugin-missing-exports": "^2.0.0",
65
+ "typescript": "~5.2.2"
66
+ },
67
+ "peerDependencies": {
68
+ "@metamask/accounts-controller": "^22.0.0",
69
+ "@metamask/network-controller": "^22.1.1"
70
+ },
71
+ "engines": {
72
+ "node": "^18.18 || >=20"
73
+ },
74
+ "publishConfig": {
75
+ "access": "public",
76
+ "registry": "https://registry.npmjs.org/"
77
+ }
78
+ }