@metamask-previews/core-backend 6.5.0-preview-6756b9ebc → 6.5.0-preview-75ce9748b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,24 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Changed
11
11
 
12
- - **BREAKING:** `AccountActivityService` now determines which non-EVM chains to subscribe to from remote feature flags instead of a bundled list ([#9379](https://github.com/MetaMask/core/pull/9379))
13
- - The `AccountActivityServiceMessenger` now requires the following delegate actions and events:
14
- - `RemoteFeatureFlagController:getState` action and `RemoteFeatureFlagController:stateChange` event
15
- - `AccountTreeController:getAccountsFromSelectedAccountGroup` action and `AccountTreeController:selectedAccountGroupChange` event
16
- - EVM (`eip155`) subscriptions are always enabled. Solana, Tron, and Stellar subscriptions are enabled when the corresponding `networkAssetsSnapsMigrationSolana` / `networkAssetsSnapsMigrationTron` / `networkAssetsSnapsMigrationStellar` feature flag has `stage >= 1`, and disabled otherwise.
17
- - Accounts whose scopes do not match an enabled chain are no longer subscribed at all.
18
- - When the enabled chains change via a feature flag update while the websocket is connected, the service automatically resubscribes the selected account.
19
- - Add `@metamask/remote-feature-flag-controller` `^4.2.2` as a dependency ([#9379](https://github.com/MetaMask/core/pull/9379))
20
12
  - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392))
21
13
  - Bump `@metamask/profile-sync-controller` from `^28.2.0` to `^28.3.0` ([#9463](https://github.com/MetaMask/core/pull/9463))
22
-
23
- ### Removed
24
-
25
- - **BREAKING:** `AccountActivityService.subscribe` and `AccountActivityService.unsubscribe` methods have been removed ([#9531](https://github.com/MetaMask/core/pull/9531))
26
-
27
- ### Fixed
28
-
29
- - `AccountActivityService` subscribes to all supported scopes for a given account ([#9379](https://github.com/MetaMask/core/pull/9379))
14
+ - Bump `@metamask/accounts-controller` from `^39.0.4` to `^39.0.5` ([#9470](https://github.com/MetaMask/core/pull/9470))
30
15
 
31
16
  ## [6.5.0]
32
17
 
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * This file is auto generated.
4
+ * Do not edit manually.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=AccountActivityService-method-action-types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccountActivityService-method-action-types.cjs","sourceRoot":"","sources":["../../src/ws/AccountActivityService-method-action-types.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { AccountActivityService } from './AccountActivityService';\n\n/**\n * Subscribe to account activity (transactions and balance updates)\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address\n */\nexport type AccountActivityServiceSubscribeAction = {\n type: `AccountActivityService:subscribe`;\n handler: AccountActivityService['subscribe'];\n};\n\n/**\n * Unsubscribe from account activity for specified address\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address to unsubscribe\n */\nexport type AccountActivityServiceUnsubscribeAction = {\n type: `AccountActivityService:unsubscribe`;\n handler: AccountActivityService['unsubscribe'];\n};\n\n/**\n * Union of all AccountActivityService action types.\n */\nexport type AccountActivityServiceMethodActions =\n | AccountActivityServiceSubscribeAction\n | AccountActivityServiceUnsubscribeAction;\n"]}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * This file is auto generated.
3
+ * Do not edit manually.
4
+ */
5
+ import type { AccountActivityService } from "./AccountActivityService.cjs";
6
+ /**
7
+ * Subscribe to account activity (transactions and balance updates)
8
+ * Address should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
9
+ *
10
+ * @param subscription - Account subscription configuration with address
11
+ */
12
+ export type AccountActivityServiceSubscribeAction = {
13
+ type: `AccountActivityService:subscribe`;
14
+ handler: AccountActivityService['subscribe'];
15
+ };
16
+ /**
17
+ * Unsubscribe from account activity for specified address
18
+ * Address should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
19
+ *
20
+ * @param subscription - Account subscription configuration with address to unsubscribe
21
+ */
22
+ export type AccountActivityServiceUnsubscribeAction = {
23
+ type: `AccountActivityService:unsubscribe`;
24
+ handler: AccountActivityService['unsubscribe'];
25
+ };
26
+ /**
27
+ * Union of all AccountActivityService action types.
28
+ */
29
+ export type AccountActivityServiceMethodActions = AccountActivityServiceSubscribeAction | AccountActivityServiceUnsubscribeAction;
30
+ //# sourceMappingURL=AccountActivityService-method-action-types.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccountActivityService-method-action-types.d.cts","sourceRoot":"","sources":["../../src/ws/AccountActivityService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,qCAAiC;AAEvE;;;;;GAKG;AACH,MAAM,MAAM,qCAAqC,GAAG;IAClD,IAAI,EAAE,kCAAkC,CAAC;IACzC,OAAO,EAAE,sBAAsB,CAAC,WAAW,CAAC,CAAC;CAC9C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,uCAAuC,GAAG;IACpD,IAAI,EAAE,oCAAoC,CAAC;IAC3C,OAAO,EAAE,sBAAsB,CAAC,aAAa,CAAC,CAAC;CAChD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mCAAmC,GAC3C,qCAAqC,GACrC,uCAAuC,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * This file is auto generated.
3
+ * Do not edit manually.
4
+ */
5
+ import type { AccountActivityService } from "./AccountActivityService.mjs";
6
+ /**
7
+ * Subscribe to account activity (transactions and balance updates)
8
+ * Address should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
9
+ *
10
+ * @param subscription - Account subscription configuration with address
11
+ */
12
+ export type AccountActivityServiceSubscribeAction = {
13
+ type: `AccountActivityService:subscribe`;
14
+ handler: AccountActivityService['subscribe'];
15
+ };
16
+ /**
17
+ * Unsubscribe from account activity for specified address
18
+ * Address should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
19
+ *
20
+ * @param subscription - Account subscription configuration with address to unsubscribe
21
+ */
22
+ export type AccountActivityServiceUnsubscribeAction = {
23
+ type: `AccountActivityService:unsubscribe`;
24
+ handler: AccountActivityService['unsubscribe'];
25
+ };
26
+ /**
27
+ * Union of all AccountActivityService action types.
28
+ */
29
+ export type AccountActivityServiceMethodActions = AccountActivityServiceSubscribeAction | AccountActivityServiceUnsubscribeAction;
30
+ //# sourceMappingURL=AccountActivityService-method-action-types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccountActivityService-method-action-types.d.mts","sourceRoot":"","sources":["../../src/ws/AccountActivityService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,qCAAiC;AAEvE;;;;;GAKG;AACH,MAAM,MAAM,qCAAqC,GAAG;IAClD,IAAI,EAAE,kCAAkC,CAAC;IACzC,OAAO,EAAE,sBAAsB,CAAC,WAAW,CAAC,CAAC;CAC9C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,uCAAuC,GAAG;IACpD,IAAI,EAAE,oCAAoC,CAAC;IAC3C,OAAO,EAAE,sBAAsB,CAAC,aAAa,CAAC,CAAC;CAChD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mCAAmC,GAC3C,qCAAqC,GACrC,uCAAuC,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * This file is auto generated.
3
+ * Do not edit manually.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=AccountActivityService-method-action-types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccountActivityService-method-action-types.mjs","sourceRoot":"","sources":["../../src/ws/AccountActivityService-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { AccountActivityService } from './AccountActivityService';\n\n/**\n * Subscribe to account activity (transactions and balance updates)\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address\n */\nexport type AccountActivityServiceSubscribeAction = {\n type: `AccountActivityService:subscribe`;\n handler: AccountActivityService['subscribe'];\n};\n\n/**\n * Unsubscribe from account activity for specified address\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address to unsubscribe\n */\nexport type AccountActivityServiceUnsubscribeAction = {\n type: `AccountActivityService:unsubscribe`;\n handler: AccountActivityService['unsubscribe'];\n};\n\n/**\n * Union of all AccountActivityService action types.\n */\nexport type AccountActivityServiceMethodActions =\n | AccountActivityServiceSubscribeAction\n | AccountActivityServiceUnsubscribeAction;\n"]}
@@ -16,29 +16,18 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
16
16
  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");
17
17
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
18
18
  };
19
- var _AccountActivityService_instances, _AccountActivityService_messenger, _AccountActivityService_options, _AccountActivityService_trace, _AccountActivityService_chainsUp, _AccountActivityService_subscribe, _AccountActivityService_handleAccountActivityUpdate, _AccountActivityService_handleSelectedAccountChange, _AccountActivityService_handleSystemNotification, _AccountActivityService_handleWebSocketStateChange, _AccountActivityService_subscribeToSelectedAccount, _AccountActivityService_unsubscribeFromAllAccountActivity, _AccountActivityService_convertToCaip10Addresses, _AccountActivityService_getSupportedChainPrefixes, _AccountActivityService_handleFeatureFlagsStateChange, _AccountActivityService_forceReconnection;
19
+ var _AccountActivityService_instances, _AccountActivityService_messenger, _AccountActivityService_options, _AccountActivityService_trace, _AccountActivityService_chainsUp, _AccountActivityService_handleAccountActivityUpdate, _AccountActivityService_handleSelectedAccountChange, _AccountActivityService_handleSystemNotification, _AccountActivityService_handleWebSocketStateChange, _AccountActivityService_subscribeToSelectedAccount, _AccountActivityService_unsubscribeFromAllAccountActivity, _AccountActivityService_convertToCaip10Address, _AccountActivityService_forceReconnection;
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.AccountActivityService = exports.ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = exports.ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = void 0;
22
- const utils_1 = require("@metamask/utils");
23
22
  const logger_1 = require("../logger.cjs");
24
23
  const BackendWebSocketService_1 = require("./BackendWebSocketService.cjs");
25
24
  const SERVICE_NAME = 'AccountActivityService';
26
25
  const log = (0, logger_1.createModuleLogger)(logger_1.projectLogger, SERVICE_NAME);
27
- const MESSENGER_EXPOSED_METHODS = [];
26
+ const MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'];
28
27
  const SUBSCRIPTION_NAMESPACE = 'account-activity.v1';
29
- // EVM subscriptions are always enabled.
30
- const ALWAYS_SUPPORTED_CHAIN_PREFIXES = ['eip155'];
31
- // Non-EVM chains are gated behind the
32
- // per-network snaps-migration remote feature flags: a chain is
33
- // enabled when its flag payload has `stage >= 1`.
34
- const CHAIN_PREFIX_FEATURE_FLAGS = {
35
- solana: 'networkAssetsSnapsMigrationSolana',
36
- tron: 'networkAssetsSnapsMigrationTron',
37
- stellar: 'networkAssetsSnapsMigrationStellar',
38
- };
39
28
  // Allowed actions that AccountActivityService can call on other controllers
40
29
  exports.ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [
41
- 'AccountTreeController:getAccountsFromSelectedAccountGroup',
30
+ 'AccountsController:getSelectedAccount',
42
31
  'BackendWebSocketService:connect',
43
32
  'BackendWebSocketService:forceReconnection',
44
33
  'BackendWebSocketService:subscribe',
@@ -48,13 +37,11 @@ exports.ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [
48
37
  'BackendWebSocketService:findSubscriptionsByChannelPrefix',
49
38
  'BackendWebSocketService:addChannelCallback',
50
39
  'BackendWebSocketService:removeChannelCallback',
51
- 'RemoteFeatureFlagController:getState',
52
40
  ];
53
41
  // Allowed events that AccountActivityService can listen to
54
42
  exports.ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [
55
- 'AccountTreeController:selectedAccountGroupChange',
43
+ 'AccountsController:selectedAccountChange',
56
44
  'BackendWebSocketService:connectionStateChanged',
57
- 'RemoteFeatureFlagController:stateChange',
58
45
  ];
59
46
  // =============================================================================
60
47
  // Main Service Class
@@ -123,30 +110,78 @@ class AccountActivityService {
123
110
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
111
  ((_request, fn) => fn?.()), "f");
125
112
  __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
126
- __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('AccountTreeController:selectedAccountGroupChange',
113
+ __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('AccountsController:selectedAccountChange',
127
114
  // Promise result intentionally not awaited
128
115
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
129
- async () => await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSelectedAccountChange).call(this));
116
+ async (account) => await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSelectedAccountChange).call(this, account));
130
117
  __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('BackendWebSocketService:connectionStateChanged',
131
118
  // Promise result intentionally not awaited
132
119
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
133
120
  (connectionInfo) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleWebSocketStateChange).call(this, connectionInfo));
134
- __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe(
135
- // eslint-disable-next-line no-restricted-syntax
136
- 'RemoteFeatureFlagController:stateChange',
137
- // Promise result intentionally not awaited
138
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
139
- async () => await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleFeatureFlagsStateChange).call(this),
140
- // Only react to changes in the set of enabled chain prefixes. The
141
- // messenger compares selector results with strict equality, so the
142
- // selector must return a primitive rather than a fresh object.
143
- (state) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_getSupportedChainPrefixes).call(this, state.remoteFeatureFlags).join(','));
144
121
  __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:addChannelCallback', {
145
122
  channelName: `system-notifications.v1.${__classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace}`,
146
123
  callback: (notification) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSystemNotification).call(this, notification),
147
124
  });
148
125
  }
149
126
  // =============================================================================
127
+ // Account Subscription Methods
128
+ // =============================================================================
129
+ /**
130
+ * Subscribe to account activity (transactions and balance updates)
131
+ * Address should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
132
+ *
133
+ * @param subscription - Account subscription configuration with address
134
+ */
135
+ async subscribe(subscription) {
136
+ try {
137
+ await __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:connect');
138
+ // Create channel name from address
139
+ const channel = `${__classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace}.${subscription.address}`;
140
+ // Check if already subscribed
141
+ if (__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:channelHasSubscription', channel)) {
142
+ return;
143
+ }
144
+ // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)
145
+ await __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:subscribe', {
146
+ channels: [channel],
147
+ channelType: __classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace, // e.g., 'account-activity.v1'
148
+ callback: (notification) => {
149
+ __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleAccountActivityUpdate).call(this, notification.data);
150
+ },
151
+ });
152
+ }
153
+ catch (error) {
154
+ log('Subscription failed, forcing reconnection', { error });
155
+ await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_forceReconnection).call(this);
156
+ }
157
+ }
158
+ /**
159
+ * Unsubscribe from account activity for specified address
160
+ * Address should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
161
+ *
162
+ * @param subscription - Account subscription configuration with address to unsubscribe
163
+ */
164
+ async unsubscribe(subscription) {
165
+ const { address } = subscription;
166
+ try {
167
+ // Find channel for the specified address
168
+ const channel = `${__classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace}.${address}`;
169
+ const subscriptions = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:getSubscriptionsByChannel', channel);
170
+ if (subscriptions.length === 0) {
171
+ return;
172
+ }
173
+ // Fast path: Direct unsubscribe using stored unsubscribe function
174
+ // Unsubscribe from all matching subscriptions
175
+ for (const subscriptionInfo of subscriptions) {
176
+ await subscriptionInfo.unsubscribe();
177
+ }
178
+ }
179
+ catch (error) {
180
+ log('Unsubscription failed, forcing reconnection', { error });
181
+ await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_forceReconnection).call(this);
182
+ }
183
+ }
184
+ // =============================================================================
150
185
  // Public Methods - Cleanup
151
186
  // =============================================================================
152
187
  /**
@@ -159,40 +194,7 @@ class AccountActivityService {
159
194
  }
160
195
  }
161
196
  exports.AccountActivityService = AccountActivityService;
162
- _AccountActivityService_messenger = new WeakMap(), _AccountActivityService_options = new WeakMap(), _AccountActivityService_trace = new WeakMap(), _AccountActivityService_chainsUp = new WeakMap(), _AccountActivityService_instances = new WeakSet(), _AccountActivityService_subscribe =
163
- /**
164
- * Subscribe to account activity (transactions and balance updates)
165
- * Addresses should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
166
- *
167
- * @param subscription - The subscription configuration
168
- * @param subscription.addresses - Array of addresses to subscribe to, each in CAIP-10 format
169
- * or an `addresses` array for batch subscription
170
- */
171
- async function _AccountActivityService_subscribe({ addresses }) {
172
- try {
173
- await __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:connect');
174
- // Derive new subscriptions to be created from the provided addresses,
175
- // filtering out any channels that already have an active subscription
176
- const channels = addresses
177
- .map((address) => `${__classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace}.${address}`)
178
- .filter((channel) => !__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:channelHasSubscription', channel));
179
- if (channels.length === 0) {
180
- return;
181
- }
182
- // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)
183
- await __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:subscribe', {
184
- channels,
185
- channelType: __classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace, // e.g., 'account-activity.v1'
186
- callback: (notification) => {
187
- __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleAccountActivityUpdate).call(this, notification.data);
188
- },
189
- });
190
- }
191
- catch (error) {
192
- log('Subscription failed, forcing reconnection', { error });
193
- await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_forceReconnection).call(this);
194
- }
195
- }, _AccountActivityService_handleAccountActivityUpdate = function _AccountActivityService_handleAccountActivityUpdate(payload) {
197
+ _AccountActivityService_messenger = new WeakMap(), _AccountActivityService_options = new WeakMap(), _AccountActivityService_trace = new WeakMap(), _AccountActivityService_chainsUp = new WeakMap(), _AccountActivityService_instances = new WeakSet(), _AccountActivityService_handleAccountActivityUpdate = function _AccountActivityService_handleAccountActivityUpdate(payload) {
196
198
  const { address, tx, updates } = payload;
197
199
  // Calculate time elapsed between transaction time and message receipt
198
200
  const txTimestampMs = tx.timestamp * 1000; // Convert Unix timestamp (seconds) to milliseconds
@@ -229,16 +231,20 @@ async function _AccountActivityService_subscribe({ addresses }) {
229
231
  }, _AccountActivityService_handleSelectedAccountChange =
230
232
  /**
231
233
  * Handle selected account change event
234
+ *
235
+ * @param newAccount - The newly selected account
232
236
  */
233
- async function _AccountActivityService_handleSelectedAccountChange() {
234
- const selectedAccounts = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('AccountTreeController:getAccountsFromSelectedAccountGroup');
237
+ async function _AccountActivityService_handleSelectedAccountChange(newAccount) {
238
+ if (!newAccount?.address) {
239
+ return;
240
+ }
235
241
  try {
242
+ // Convert new account to CAIP-10 format
243
+ const newAddress = __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_convertToCaip10Address).call(this, newAccount);
236
244
  // First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions
237
245
  await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_unsubscribeFromAllAccountActivity).call(this);
238
- // Subscribe to the new selected accounts in CAIP-10 format
239
- await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_subscribe).call(this, {
240
- addresses: __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_convertToCaip10Addresses).call(this, selectedAccounts),
241
- });
246
+ // Then, subscribe to the new selected account
247
+ await this.subscribe({ address: newAddress });
242
248
  }
243
249
  catch (error) {
244
250
  log('Account change failed', { error });
@@ -310,10 +316,13 @@ async function _AccountActivityService_handleWebSocketStateChange(connectionInfo
310
316
  * Subscribe to the currently selected account only
311
317
  */
312
318
  async function _AccountActivityService_subscribeToSelectedAccount() {
313
- const selectedAccounts = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('AccountTreeController:getAccountsFromSelectedAccountGroup');
314
- await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_subscribe).call(this, {
315
- addresses: __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_convertToCaip10Addresses).call(this, selectedAccounts),
316
- });
319
+ const selectedAccount = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('AccountsController:getSelectedAccount');
320
+ if (!selectedAccount?.address) {
321
+ return;
322
+ }
323
+ // Convert to CAIP-10 format and subscribe
324
+ const address = __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_convertToCaip10Address).call(this, selectedAccount);
325
+ await this.subscribe({ address });
317
326
  }, _AccountActivityService_unsubscribeFromAllAccountActivity =
318
327
  /**
319
328
  * Unsubscribe from all account activity subscriptions for this service
@@ -325,47 +334,19 @@ async function _AccountActivityService_unsubscribeFromAllAccountActivity() {
325
334
  for (const subscription of accountActivitySubscriptions) {
326
335
  await subscription.unsubscribe();
327
336
  }
328
- }, _AccountActivityService_convertToCaip10Addresses = function _AccountActivityService_convertToCaip10Addresses(accounts) {
329
- const supportedChainPrefixes = __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_getSupportedChainPrefixes).call(this);
330
- return accounts.reduce((result, account) => {
331
- const accountPrefix = supportedChainPrefixes.find((prefix) => account.scopes.some((scope) => scope.startsWith(`${prefix}:`)));
332
- if (!accountPrefix) {
333
- // Skip unsupported accounts
334
- return result;
335
- }
336
- result.push(`${accountPrefix}:0:${account.address}`);
337
- return result;
338
- }, []);
339
- }, _AccountActivityService_getSupportedChainPrefixes = function _AccountActivityService_getSupportedChainPrefixes(remoteFeatureFlags = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('RemoteFeatureFlagController:getState').remoteFeatureFlags) {
340
- const prefixes = [...ALWAYS_SUPPORTED_CHAIN_PREFIXES];
341
- for (const [prefix, flagName] of Object.entries(CHAIN_PREFIX_FEATURE_FLAGS)) {
342
- const flagValue = remoteFeatureFlags[flagName];
343
- if ((0, utils_1.isObject)(flagValue) &&
344
- typeof flagValue.stage === 'number' &&
345
- flagValue.stage >= 1) {
346
- prefixes.push(prefix);
347
- }
348
- }
349
- return prefixes;
350
- }, _AccountActivityService_handleFeatureFlagsStateChange =
351
- /**
352
- * Handle remote feature flag changes: if the set of enabled chain prefixes
353
- * changed while connected, resubscribe the selected account so new chains
354
- * are picked up and disabled ones are dropped.
355
- */
356
- async function _AccountActivityService_handleFeatureFlagsStateChange() {
357
- try {
358
- const { state } = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:getConnectionInfo');
359
- if (state !== BackendWebSocketService_1.WebSocketState.CONNECTED) {
360
- // Not connected: the next connection will subscribe with fresh flags
361
- return;
362
- }
363
- await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_unsubscribeFromAllAccountActivity).call(this);
364
- await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_subscribeToSelectedAccount).call(this);
337
+ }, _AccountActivityService_convertToCaip10Address = function _AccountActivityService_convertToCaip10Address(account) {
338
+ // Check if account has EVM scopes
339
+ if (account.scopes.some((scope) => scope.startsWith('eip155:'))) {
340
+ // CAIP-10 format: eip155:0:address (subscribe to all EVM chains)
341
+ return `eip155:0:${account.address}`;
365
342
  }
366
- catch (error) {
367
- log('Feature flag change handling failed', { error });
343
+ // Check if account has Solana scopes
344
+ if (account.scopes.some((scope) => scope.startsWith('solana:'))) {
345
+ // CAIP-10 format: solana:0:address (subscribe to all Solana chains)
346
+ return `solana:0:${account.address}`;
368
347
  }
348
+ // For other chains or unknown scopes, return raw address
349
+ return account.address;
369
350
  }, _AccountActivityService_forceReconnection =
370
351
  /**
371
352
  * Force WebSocket reconnection to clean up subscription state
@@ -1 +1 @@
1
- {"version":3,"file":"AccountActivityService.cjs","sourceRoot":"","sources":["../../src/ws/AccountActivityService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Account Activity Service for monitoring account transactions and balance changes\n *\n * This service subscribes to account activity and receives all transactions\n * and balance updates for those accounts via the comprehensive AccountActivityMessage format.\n */\n\nimport type {\n AccountTreeControllerSelectedAccountGroupChangeEvent,\n AccountTreeControllerGetAccountsFromSelectedAccountGroupAction,\n} from '@metamask/account-tree-controller';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n FeatureFlags,\n RemoteFeatureFlagControllerGetStateAction,\n RemoteFeatureFlagControllerStateChangeEvent,\n} from '@metamask/remote-feature-flag-controller';\nimport { isObject } from '@metamask/utils';\n\nimport { projectLogger, createModuleLogger } from '../logger';\nimport type {\n Transaction,\n AccountActivityMessage,\n BalanceUpdate,\n} from '../types';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from './BackendWebSocketService';\nimport { WebSocketState } from './BackendWebSocketService';\nimport type { BackendWebSocketServiceMethodActions } from './BackendWebSocketService-method-action-types';\n\n// =============================================================================\n// Types and Constants\n// =============================================================================\n\n/**\n * System notification data for chain status updates\n */\nexport type SystemNotificationData = {\n /** Array of chain IDs affected (e.g., ['eip155:137', 'eip155:1']) */\n chainIds: string[];\n /** Status of the chains: 'down' or 'up' */\n status: 'down' | 'up';\n /** Timestamp of the notification */\n timestamp?: number;\n};\n\nconst SERVICE_NAME = 'AccountActivityService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = [] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'account-activity.v1';\n\n// EVM subscriptions are always enabled.\nconst ALWAYS_SUPPORTED_CHAIN_PREFIXES = ['eip155'] as const;\n\n// Non-EVM chains are gated behind the\n// per-network snaps-migration remote feature flags: a chain is\n// enabled when its flag payload has `stage >= 1`.\nconst CHAIN_PREFIX_FEATURE_FLAGS = {\n solana: 'networkAssetsSnapsMigrationSolana',\n tron: 'networkAssetsSnapsMigrationTron',\n stellar: 'networkAssetsSnapsMigrationStellar',\n} as const;\n\n/**\n * Account subscription options\n */\nexport type SubscriptionOptions = {\n /**\n * Array of addresses to subscribe to, each in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n */\n addresses: string[];\n};\n\n/**\n * Configuration options for the account activity service\n */\nexport type AccountActivityServiceOptions = {\n /** Custom subscription namespace (default: 'account-activity.v1') */\n subscriptionNamespace?: string;\n /** Optional callback to trace performance of account activity operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\n// Action types for the messaging system\nexport type AccountActivityServiceActions = never;\n\n// Allowed actions that AccountActivityService can call on other controllers\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n 'BackendWebSocketService:connect',\n 'BackendWebSocketService:forceReconnection',\n 'BackendWebSocketService:subscribe',\n 'BackendWebSocketService:getConnectionInfo',\n 'BackendWebSocketService:channelHasSubscription',\n 'BackendWebSocketService:getSubscriptionsByChannel',\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n 'BackendWebSocketService:addChannelCallback',\n 'BackendWebSocketService:removeChannelCallback',\n 'RemoteFeatureFlagController:getState',\n] as const;\n\n// Allowed events that AccountActivityService can listen to\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [\n 'AccountTreeController:selectedAccountGroupChange',\n 'BackendWebSocketService:connectionStateChanged',\n 'RemoteFeatureFlagController:stateChange',\n] as const;\n\nexport type AllowedActions =\n | AccountTreeControllerGetAccountsFromSelectedAccountGroupAction\n | BackendWebSocketServiceMethodActions\n | RemoteFeatureFlagControllerGetStateAction;\n\n// Event types for the messaging system\n\nexport type AccountActivityServiceTransactionUpdatedEvent = {\n type: `AccountActivityService:transactionUpdated`;\n payload: [Transaction];\n};\n\nexport type AccountActivityServiceBalanceUpdatedEvent = {\n type: `AccountActivityService:balanceUpdated`;\n payload: [{ address: string; chain: string; updates: BalanceUpdate[] }];\n};\n\nexport type AccountActivityServiceSubscriptionErrorEvent = {\n type: `AccountActivityService:subscriptionError`;\n payload: [{ addresses: string[]; error: string; operation: string }];\n};\n\nexport type AccountActivityServiceStatusChangedEvent = {\n type: `AccountActivityService:statusChanged`;\n payload: [\n {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n },\n ];\n};\n\nexport type AccountActivityServiceEvents =\n | AccountActivityServiceTransactionUpdatedEvent\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceSubscriptionErrorEvent\n | AccountActivityServiceStatusChangedEvent;\n\nexport type AllowedEvents =\n | AccountTreeControllerSelectedAccountGroupChangeEvent\n | BackendWebSocketServiceConnectionStateChangedEvent\n | RemoteFeatureFlagControllerStateChangeEvent;\n\nexport type AccountActivityServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n AccountActivityServiceActions | AllowedActions,\n AccountActivityServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * High-performance service for real-time account activity monitoring using optimized\n * WebSocket subscriptions with direct callback routing. Automatically subscribes to\n * the currently selected account and switches subscriptions when the selected account changes.\n * Receives transactions and balance updates using the comprehensive AccountActivityMessage format.\n *\n * Performance Features:\n * - Direct callback routing (no EventEmitter overhead)\n * - Minimal subscription tracking (no duplication with BackendWebSocketService)\n * - Optimized cleanup for mobile environments\n * - Single-account subscription (only selected account)\n * - Comprehensive balance updates with transfer tracking\n *\n * Architecture:\n * - Uses messenger pattern to communicate with BackendWebSocketService\n * - AccountActivityService tracks channel-to-subscriptionId mappings via messenger calls\n * - Automatically subscribes to selected account on initialization\n * - Switches subscriptions when selected account changes\n * - No direct dependency on BackendWebSocketService (uses messenger instead)\n *\n * @example\n * ```typescript\n * const service = new AccountActivityService({\n * messenger: activityMessenger,\n * });\n *\n * // Service automatically subscribes to the currently selected account\n * // When user switches accounts, service automatically resubscribes\n *\n * // All transactions and balance updates are received via optimized\n * // WebSocket callbacks and processed with zero-allocation routing\n * // Balance updates include comprehensive transfer details and post-transaction balances\n * ```\n */\nexport class AccountActivityService {\n /**\n * The name of the service.\n */\n readonly name = SERVICE_NAME;\n\n readonly #messenger: AccountActivityServiceMessenger;\n\n readonly #options: Required<Omit<AccountActivityServiceOptions, 'traceFn'>>;\n\n readonly #trace: TraceCallback;\n\n // Track chains that are currently up (based on system notifications)\n readonly #chainsUp: Set<string> = new Set();\n\n // =============================================================================\n // Constructor and Initialization\n // =============================================================================\n\n /**\n * Creates a new Account Activity service instance\n *\n * @param options - Configuration options including messenger\n */\n constructor(\n options: AccountActivityServiceOptions & {\n messenger: AccountActivityServiceMessenger;\n },\n ) {\n this.#messenger = options.messenger;\n\n // Set configuration with defaults\n this.#options = {\n subscriptionNamespace:\n options.subscriptionNamespace ?? SUBSCRIPTION_NAMESPACE,\n };\n\n // Default to no-op trace function to keep core platform-agnostic\n this.#trace =\n options.traceFn ??\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (((_request: any, fn?: any) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n this.#messenger.subscribe(\n 'AccountTreeController:selectedAccountGroupChange',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async () => await this.#handleSelectedAccountChange(),\n );\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n this.#messenger.subscribe(\n // eslint-disable-next-line no-restricted-syntax\n 'RemoteFeatureFlagController:stateChange',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async () => await this.#handleFeatureFlagsStateChange(),\n // Only react to changes in the set of enabled chain prefixes. The\n // messenger compares selector results with strict equality, so the\n // selector must return a primitive rather than a fresh object.\n (state) =>\n this.#getSupportedChainPrefixes(state.remoteFeatureFlags).join(','),\n );\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n /**\n * Subscribe to account activity (transactions and balance updates)\n * Addresses should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - The subscription configuration\n * @param subscription.addresses - Array of addresses to subscribe to, each in CAIP-10 format\n * or an `addresses` array for batch subscription\n */\n async #subscribe({ addresses }: SubscriptionOptions): Promise<void> {\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n // Derive new subscriptions to be created from the provided addresses,\n // filtering out any channels that already have an active subscription\n const channels = addresses\n .map((address) => `${this.#options.subscriptionNamespace}.${address}`)\n .filter(\n (channel) =>\n !this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n ),\n );\n\n if (channels.length === 0) {\n return;\n }\n\n // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels,\n channelType: this.#options.subscriptionNamespace, // e.g., 'account-activity.v1'\n callback: (notification: ServerNotificationMessage) => {\n this.#handleAccountActivityUpdate(\n notification.data as AccountActivityMessage,\n );\n },\n });\n } catch (error) {\n log('Subscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n /**\n * Handle account activity updates (transactions + balance changes)\n * Processes the comprehensive AccountActivityMessage format with detailed balance updates and transfers\n *\n * @param payload - The account activity message containing transaction and balance updates\n * @example AccountActivityMessage format handling:\n * Input: {\n * address: \"0xd14b52362b5b777ffa754c666ddec6722aaeee08\",\n * tx: { id: \"0x1cde...\", chain: \"eip155:8453\", status: \"confirmed\", timestamp: 1760099871, ... },\n * updates: [{\n * asset: { fungible: true, type: \"eip155:8453/erc20:0x833...\", unit: \"USDC\", decimals: 6 },\n * postBalance: { amount: \"0xc350\" },\n * transfers: [{ from: \"0x7b07...\", to: \"0xd14b...\", amount: \"0x2710\" }]\n * }]\n * }\n * Output: Transaction and balance updates published separately\n */\n #handleAccountActivityUpdate(payload: AccountActivityMessage): void {\n const { address, tx, updates } = payload;\n\n // Calculate time elapsed between transaction time and message receipt\n const txTimestampMs = tx.timestamp * 1000; // Convert Unix timestamp (seconds) to milliseconds\n const elapsedMs = Date.now() - txTimestampMs;\n\n log('Handling account activity update', {\n address,\n updateCount: updates.length,\n elapsedMs,\n });\n\n // Trace message receipt with latency from transaction time to now\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Transaction Message`,\n data: {\n chain: tx.chain,\n status: tx.status,\n elapsed_ms: elapsedMs,\n },\n tags: {\n service: SERVICE_NAME,\n notification_type: this.#options.subscriptionNamespace,\n },\n },\n () => {\n // Process transaction update\n this.#messenger.publish(\n `AccountActivityService:transactionUpdated`,\n tx,\n );\n\n // Publish comprehensive balance updates with transfer details\n this.#messenger.publish(`AccountActivityService:balanceUpdated`, {\n address,\n chain: tx.chain,\n updates,\n });\n },\n );\n }\n\n /**\n * Handle selected account change event\n */\n async #handleSelectedAccountChange(): Promise<void> {\n const selectedAccounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n\n try {\n // First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions\n await this.#unsubscribeFromAllAccountActivity();\n\n // Subscribe to the new selected accounts in CAIP-10 format\n await this.#subscribe({\n addresses: this.#convertToCaip10Addresses(selectedAccounts),\n });\n } catch (error) {\n log('Account change failed', { error });\n }\n }\n\n /**\n * Handle system notification for chain status changes\n * Publishes only the status change (delta) for affected chains\n *\n * @param notification - Server notification message containing chain status updates and timestamp\n */\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as SystemNotificationData;\n const { timestamp } = notification;\n\n // Validate required fields\n if (!data.chainIds || !Array.isArray(data.chainIds) || !data.status) {\n throw new Error(\n 'Invalid system notification data: missing chainIds or status',\n );\n }\n\n // Track chain status\n if (data.status === 'up') {\n for (const chainId of data.chainIds) {\n this.#chainsUp.add(chainId);\n }\n } else {\n for (const chainId of data.chainIds) {\n this.#chainsUp.delete(chainId);\n }\n }\n\n // Publish status change directly (delta update)\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n\n log(\n `WebSocket status change - Published tracked chains as ${data.status}`,\n {\n count: data.chainIds.length,\n chains: data.chainIds,\n status: data.status,\n },\n );\n }\n\n /**\n * Handle WebSocket connection state changes for fallback polling and resubscription\n *\n * @param connectionInfo - WebSocket connection state information\n */\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n // WebSocket connected - resubscribe to selected account\n // The system notification will automatically provide the list of chains that are up\n await this.#subscribeToSelectedAccount();\n } else if (state === WebSocketState.DISCONNECTED) {\n // On disconnect, flush all tracked chains as down\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log('WebSocket disconnection - Published tracked chains as down', {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n });\n\n // Clear the tracking set since all chains are now down\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private Methods - Subscription Management\n // =============================================================================\n\n /**\n * Subscribe to the currently selected account only\n */\n async #subscribeToSelectedAccount(): Promise<void> {\n const selectedAccounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n\n await this.#subscribe({\n addresses: this.#convertToCaip10Addresses(selectedAccounts),\n });\n }\n\n /**\n * Unsubscribe from all account activity subscriptions for this service\n * Finds all channels matching the service's namespace and unsubscribes from them\n */\n async #unsubscribeFromAllAccountActivity(): Promise<void> {\n const accountActivitySubscriptions = this.#messenger.call(\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n this.#options.subscriptionNamespace,\n );\n\n // Unsubscribe from all matching subscriptions\n for (const subscription of accountActivitySubscriptions) {\n await subscription.unsubscribe();\n }\n }\n\n // =============================================================================\n // Private Methods - Utility Functions\n // =============================================================================\n\n /**\n * Convert a list of InternalAccount addresses to CAIP-10 format, using the first\n * supported chain prefix matching the account's scopes\n *\n * @param accounts - The internal accounts to convert\n * @returns The CAIP-10 formatted addresses (e.g. [`eip155:0:address`], meaning\n * all chains of that namespace), or an empty array if none of the account's\n * scopes are supported\n */\n #convertToCaip10Addresses(accounts: InternalAccount[]): string[] {\n const supportedChainPrefixes = this.#getSupportedChainPrefixes();\n return accounts.reduce<string[]>((result, account) => {\n const accountPrefix = supportedChainPrefixes.find((prefix) =>\n account.scopes.some((scope) => scope.startsWith(`${prefix}:`)),\n );\n\n if (!accountPrefix) {\n // Skip unsupported accounts\n return result;\n }\n\n result.push(`${accountPrefix}:0:${account.address}`);\n return result;\n }, []);\n }\n\n /**\n * Get the chain prefixes currently enabled for subscriptions: EVM is always\n * enabled, while other chains are gated behind their per-network remote\n * feature flag (enabled when the flag payload has `stage >= 1`).\n *\n * @param remoteFeatureFlags - The remote feature flags state to check for enabled chains.\n * @returns An array of enabled CAIP-2 namespace prefixes (e.g. `['eip155', 'solana']`)\n */\n #getSupportedChainPrefixes(\n remoteFeatureFlags: FeatureFlags = this.#messenger.call(\n 'RemoteFeatureFlagController:getState',\n ).remoteFeatureFlags,\n ): string[] {\n const prefixes: string[] = [...ALWAYS_SUPPORTED_CHAIN_PREFIXES];\n for (const [prefix, flagName] of Object.entries(\n CHAIN_PREFIX_FEATURE_FLAGS,\n )) {\n const flagValue = remoteFeatureFlags[flagName];\n if (\n isObject(flagValue) &&\n typeof flagValue.stage === 'number' &&\n flagValue.stage >= 1\n ) {\n prefixes.push(prefix);\n }\n }\n return prefixes;\n }\n\n /**\n * Handle remote feature flag changes: if the set of enabled chain prefixes\n * changed while connected, resubscribe the selected account so new chains\n * are picked up and disabled ones are dropped.\n */\n async #handleFeatureFlagsStateChange(): Promise<void> {\n try {\n const { state } = this.#messenger.call(\n 'BackendWebSocketService:getConnectionInfo',\n );\n if (state !== WebSocketState.CONNECTED) {\n // Not connected: the next connection will subscribe with fresh flags\n return;\n }\n\n await this.#unsubscribeFromAllAccountActivity();\n await this.#subscribeToSelectedAccount();\n } catch (error) {\n log('Feature flag change handling failed', { error });\n }\n }\n\n /**\n * Force WebSocket reconnection to clean up subscription state\n */\n async #forceReconnection(): Promise<void> {\n log('Forcing WebSocket reconnection to clean up subscription state');\n\n // Use the dedicated forceReconnection method which performs a controlled\n // disconnect-then-connect sequence to clean up subscription state\n await this.#messenger.call('BackendWebSocketService:forceReconnection');\n }\n\n // =============================================================================\n // Public Methods - Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources\n * Optimized for fast cleanup during service destruction or mobile app termination\n */\n destroy(): void {\n // Clean up system notification callback\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"AccountActivityService.cjs","sourceRoot":"","sources":["../../src/ws/AccountActivityService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Account Activity Service for monitoring account transactions and balance changes\n *\n * This service subscribes to account activity and receives all transactions\n * and balance updates for those accounts via the comprehensive AccountActivityMessage format.\n */\n\nimport type {\n AccountsControllerGetSelectedAccountAction,\n AccountsControllerSelectedAccountChangeEvent,\n} from '@metamask/accounts-controller';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\n\nimport { projectLogger, createModuleLogger } from '../logger';\nimport type {\n Transaction,\n AccountActivityMessage,\n BalanceUpdate,\n} from '../types';\nimport type { AccountActivityServiceMethodActions } from './AccountActivityService-method-action-types';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from './BackendWebSocketService';\nimport { WebSocketState } from './BackendWebSocketService';\nimport type { BackendWebSocketServiceMethodActions } from './BackendWebSocketService-method-action-types';\n\n// =============================================================================\n// Types and Constants\n// =============================================================================\n\n/**\n * System notification data for chain status updates\n */\nexport type SystemNotificationData = {\n /** Array of chain IDs affected (e.g., ['eip155:137', 'eip155:1']) */\n chainIds: string[];\n /** Status of the chains: 'down' or 'up' */\n status: 'down' | 'up';\n /** Timestamp of the notification */\n timestamp?: number;\n};\n\nconst SERVICE_NAME = 'AccountActivityService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'account-activity.v1';\n\n/**\n * Account subscription options\n */\nexport type SubscriptionOptions = {\n address: string; // Should be in CAIP-10 format, e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\"\n};\n\n/**\n * Configuration options for the account activity service\n */\nexport type AccountActivityServiceOptions = {\n /** Custom subscription namespace (default: 'account-activity.v1') */\n subscriptionNamespace?: string;\n /** Optional callback to trace performance of account activity operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\n// Action types for the messaging system - using generated method actions\nexport type AccountActivityServiceActions = AccountActivityServiceMethodActions;\n\n// Allowed actions that AccountActivityService can call on other controllers\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [\n 'AccountsController:getSelectedAccount',\n 'BackendWebSocketService:connect',\n 'BackendWebSocketService:forceReconnection',\n 'BackendWebSocketService:subscribe',\n 'BackendWebSocketService:getConnectionInfo',\n 'BackendWebSocketService:channelHasSubscription',\n 'BackendWebSocketService:getSubscriptionsByChannel',\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n 'BackendWebSocketService:addChannelCallback',\n 'BackendWebSocketService:removeChannelCallback',\n] as const;\n\n// Allowed events that AccountActivityService can listen to\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [\n 'AccountsController:selectedAccountChange',\n 'BackendWebSocketService:connectionStateChanged',\n] as const;\n\nexport type AllowedActions =\n | AccountsControllerGetSelectedAccountAction\n | BackendWebSocketServiceMethodActions;\n\n// Event types for the messaging system\n\nexport type AccountActivityServiceTransactionUpdatedEvent = {\n type: `AccountActivityService:transactionUpdated`;\n payload: [Transaction];\n};\n\nexport type AccountActivityServiceBalanceUpdatedEvent = {\n type: `AccountActivityService:balanceUpdated`;\n payload: [{ address: string; chain: string; updates: BalanceUpdate[] }];\n};\n\nexport type AccountActivityServiceSubscriptionErrorEvent = {\n type: `AccountActivityService:subscriptionError`;\n payload: [{ addresses: string[]; error: string; operation: string }];\n};\n\nexport type AccountActivityServiceStatusChangedEvent = {\n type: `AccountActivityService:statusChanged`;\n payload: [\n {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n },\n ];\n};\n\nexport type AccountActivityServiceEvents =\n | AccountActivityServiceTransactionUpdatedEvent\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceSubscriptionErrorEvent\n | AccountActivityServiceStatusChangedEvent;\n\nexport type AllowedEvents =\n | AccountsControllerSelectedAccountChangeEvent\n | BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type AccountActivityServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n AccountActivityServiceActions | AllowedActions,\n AccountActivityServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * High-performance service for real-time account activity monitoring using optimized\n * WebSocket subscriptions with direct callback routing. Automatically subscribes to\n * the currently selected account and switches subscriptions when the selected account changes.\n * Receives transactions and balance updates using the comprehensive AccountActivityMessage format.\n *\n * Performance Features:\n * - Direct callback routing (no EventEmitter overhead)\n * - Minimal subscription tracking (no duplication with BackendWebSocketService)\n * - Optimized cleanup for mobile environments\n * - Single-account subscription (only selected account)\n * - Comprehensive balance updates with transfer tracking\n *\n * Architecture:\n * - Uses messenger pattern to communicate with BackendWebSocketService\n * - AccountActivityService tracks channel-to-subscriptionId mappings via messenger calls\n * - Automatically subscribes to selected account on initialization\n * - Switches subscriptions when selected account changes\n * - No direct dependency on BackendWebSocketService (uses messenger instead)\n *\n * @example\n * ```typescript\n * const service = new AccountActivityService({\n * messenger: activityMessenger,\n * });\n *\n * // Service automatically subscribes to the currently selected account\n * // When user switches accounts, service automatically resubscribes\n *\n * // All transactions and balance updates are received via optimized\n * // WebSocket callbacks and processed with zero-allocation routing\n * // Balance updates include comprehensive transfer details and post-transaction balances\n * ```\n */\nexport class AccountActivityService {\n /**\n * The name of the service.\n */\n readonly name = SERVICE_NAME;\n\n readonly #messenger: AccountActivityServiceMessenger;\n\n readonly #options: Required<Omit<AccountActivityServiceOptions, 'traceFn'>>;\n\n readonly #trace: TraceCallback;\n\n // Track chains that are currently up (based on system notifications)\n readonly #chainsUp: Set<string> = new Set();\n\n // =============================================================================\n // Constructor and Initialization\n // =============================================================================\n\n /**\n * Creates a new Account Activity service instance\n *\n * @param options - Configuration options including messenger\n */\n constructor(\n options: AccountActivityServiceOptions & {\n messenger: AccountActivityServiceMessenger;\n },\n ) {\n this.#messenger = options.messenger;\n\n // Set configuration with defaults\n this.#options = {\n subscriptionNamespace:\n options.subscriptionNamespace ?? SUBSCRIPTION_NAMESPACE,\n };\n\n // Default to no-op trace function to keep core platform-agnostic\n this.#trace =\n options.traceFn ??\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (((_request: any, fn?: any) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n this.#messenger.subscribe(\n 'AccountsController:selectedAccountChange',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async (account: InternalAccount) =>\n await this.#handleSelectedAccountChange(account),\n );\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n // =============================================================================\n // Account Subscription Methods\n // =============================================================================\n\n /**\n * Subscribe to account activity (transactions and balance updates)\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address\n */\n async subscribe(subscription: SubscriptionOptions): Promise<void> {\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n // Create channel name from address\n const channel = `${this.#options.subscriptionNamespace}.${subscription.address}`;\n\n // Check if already subscribed\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n return;\n }\n\n // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: this.#options.subscriptionNamespace, // e.g., 'account-activity.v1'\n callback: (notification: ServerNotificationMessage) => {\n this.#handleAccountActivityUpdate(\n notification.data as AccountActivityMessage,\n );\n },\n });\n } catch (error) {\n log('Subscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n /**\n * Unsubscribe from account activity for specified address\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address to unsubscribe\n */\n async unsubscribe(subscription: SubscriptionOptions): Promise<void> {\n const { address } = subscription;\n try {\n // Find channel for the specified address\n const channel = `${this.#options.subscriptionNamespace}.${address}`;\n const subscriptions = this.#messenger.call(\n 'BackendWebSocketService:getSubscriptionsByChannel',\n channel,\n );\n\n if (subscriptions.length === 0) {\n return;\n }\n\n // Fast path: Direct unsubscribe using stored unsubscribe function\n // Unsubscribe from all matching subscriptions\n for (const subscriptionInfo of subscriptions) {\n await subscriptionInfo.unsubscribe();\n }\n } catch (error) {\n log('Unsubscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n // =============================================================================\n // Private Methods - Event Handlers\n // =============================================================================\n\n /**\n * Handle account activity updates (transactions + balance changes)\n * Processes the comprehensive AccountActivityMessage format with detailed balance updates and transfers\n *\n * @param payload - The account activity message containing transaction and balance updates\n * @example AccountActivityMessage format handling:\n * Input: {\n * address: \"0xd14b52362b5b777ffa754c666ddec6722aaeee08\",\n * tx: { id: \"0x1cde...\", chain: \"eip155:8453\", status: \"confirmed\", timestamp: 1760099871, ... },\n * updates: [{\n * asset: { fungible: true, type: \"eip155:8453/erc20:0x833...\", unit: \"USDC\", decimals: 6 },\n * postBalance: { amount: \"0xc350\" },\n * transfers: [{ from: \"0x7b07...\", to: \"0xd14b...\", amount: \"0x2710\" }]\n * }]\n * }\n * Output: Transaction and balance updates published separately\n */\n #handleAccountActivityUpdate(payload: AccountActivityMessage): void {\n const { address, tx, updates } = payload;\n\n // Calculate time elapsed between transaction time and message receipt\n const txTimestampMs = tx.timestamp * 1000; // Convert Unix timestamp (seconds) to milliseconds\n const elapsedMs = Date.now() - txTimestampMs;\n\n log('Handling account activity update', {\n address,\n updateCount: updates.length,\n elapsedMs,\n });\n\n // Trace message receipt with latency from transaction time to now\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Transaction Message`,\n data: {\n chain: tx.chain,\n status: tx.status,\n elapsed_ms: elapsedMs,\n },\n tags: {\n service: SERVICE_NAME,\n notification_type: this.#options.subscriptionNamespace,\n },\n },\n () => {\n // Process transaction update\n this.#messenger.publish(\n `AccountActivityService:transactionUpdated`,\n tx,\n );\n\n // Publish comprehensive balance updates with transfer details\n this.#messenger.publish(`AccountActivityService:balanceUpdated`, {\n address,\n chain: tx.chain,\n updates,\n });\n },\n );\n }\n\n /**\n * Handle selected account change event\n *\n * @param newAccount - The newly selected account\n */\n async #handleSelectedAccountChange(\n newAccount: InternalAccount | null,\n ): Promise<void> {\n if (!newAccount?.address) {\n return;\n }\n\n try {\n // Convert new account to CAIP-10 format\n const newAddress = this.#convertToCaip10Address(newAccount);\n\n // First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions\n await this.#unsubscribeFromAllAccountActivity();\n\n // Then, subscribe to the new selected account\n await this.subscribe({ address: newAddress });\n } catch (error) {\n log('Account change failed', { error });\n }\n }\n\n /**\n * Handle system notification for chain status changes\n * Publishes only the status change (delta) for affected chains\n *\n * @param notification - Server notification message containing chain status updates and timestamp\n */\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as SystemNotificationData;\n const { timestamp } = notification;\n\n // Validate required fields\n if (!data.chainIds || !Array.isArray(data.chainIds) || !data.status) {\n throw new Error(\n 'Invalid system notification data: missing chainIds or status',\n );\n }\n\n // Track chain status\n if (data.status === 'up') {\n for (const chainId of data.chainIds) {\n this.#chainsUp.add(chainId);\n }\n } else {\n for (const chainId of data.chainIds) {\n this.#chainsUp.delete(chainId);\n }\n }\n\n // Publish status change directly (delta update)\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n\n log(\n `WebSocket status change - Published tracked chains as ${data.status}`,\n {\n count: data.chainIds.length,\n chains: data.chainIds,\n status: data.status,\n },\n );\n }\n\n /**\n * Handle WebSocket connection state changes for fallback polling and resubscription\n *\n * @param connectionInfo - WebSocket connection state information\n */\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n // WebSocket connected - resubscribe to selected account\n // The system notification will automatically provide the list of chains that are up\n await this.#subscribeToSelectedAccount();\n } else if (state === WebSocketState.DISCONNECTED) {\n // On disconnect, flush all tracked chains as down\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log('WebSocket disconnection - Published tracked chains as down', {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n });\n\n // Clear the tracking set since all chains are now down\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private Methods - Subscription Management\n // =============================================================================\n\n /**\n * Subscribe to the currently selected account only\n */\n async #subscribeToSelectedAccount(): Promise<void> {\n const selectedAccount = this.#messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n\n if (!selectedAccount?.address) {\n return;\n }\n\n // Convert to CAIP-10 format and subscribe\n const address = this.#convertToCaip10Address(selectedAccount);\n await this.subscribe({ address });\n }\n\n /**\n * Unsubscribe from all account activity subscriptions for this service\n * Finds all channels matching the service's namespace and unsubscribes from them\n */\n async #unsubscribeFromAllAccountActivity(): Promise<void> {\n const accountActivitySubscriptions = this.#messenger.call(\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n this.#options.subscriptionNamespace,\n );\n\n // Unsubscribe from all matching subscriptions\n for (const subscription of accountActivitySubscriptions) {\n await subscription.unsubscribe();\n }\n }\n\n // =============================================================================\n // Private Methods - Utility Functions\n // =============================================================================\n\n /**\n * Convert an InternalAccount address to CAIP-10 format or raw address\n *\n * @param account - The internal account to convert\n * @returns The CAIP-10 formatted address or raw address\n */\n #convertToCaip10Address(account: InternalAccount): string {\n // Check if account has EVM scopes\n if (account.scopes.some((scope) => scope.startsWith('eip155:'))) {\n // CAIP-10 format: eip155:0:address (subscribe to all EVM chains)\n return `eip155:0:${account.address}`;\n }\n\n // Check if account has Solana scopes\n if (account.scopes.some((scope) => scope.startsWith('solana:'))) {\n // CAIP-10 format: solana:0:address (subscribe to all Solana chains)\n return `solana:0:${account.address}`;\n }\n\n // For other chains or unknown scopes, return raw address\n return account.address;\n }\n\n /**\n * Force WebSocket reconnection to clean up subscription state\n */\n async #forceReconnection(): Promise<void> {\n log('Forcing WebSocket reconnection to clean up subscription state');\n\n // Use the dedicated forceReconnection method which performs a controlled\n // disconnect-then-connect sequence to clean up subscription state\n await this.#messenger.call('BackendWebSocketService:forceReconnection');\n }\n\n // =============================================================================\n // Public Methods - Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources\n * Optimized for fast cleanup during service destruction or mobile app termination\n */\n destroy(): void {\n // Clean up system notification callback\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n );\n }\n}\n"]}
@@ -4,11 +4,11 @@
4
4
  * This service subscribes to account activity and receives all transactions
5
5
  * and balance updates for those accounts via the comprehensive AccountActivityMessage format.
6
6
  */
7
- import type { AccountTreeControllerSelectedAccountGroupChangeEvent, AccountTreeControllerGetAccountsFromSelectedAccountGroupAction } from "@metamask/account-tree-controller";
7
+ import type { AccountsControllerGetSelectedAccountAction, AccountsControllerSelectedAccountChangeEvent } from "@metamask/accounts-controller";
8
8
  import type { TraceCallback } from "@metamask/controller-utils";
9
9
  import type { Messenger } from "@metamask/messenger";
10
- import type { RemoteFeatureFlagControllerGetStateAction, RemoteFeatureFlagControllerStateChangeEvent } from "@metamask/remote-feature-flag-controller";
11
10
  import type { Transaction, BalanceUpdate } from "../types.cjs";
11
+ import type { AccountActivityServiceMethodActions } from "./AccountActivityService-method-action-types.cjs";
12
12
  import type { BackendWebSocketServiceConnectionStateChangedEvent } from "./BackendWebSocketService.cjs";
13
13
  import type { BackendWebSocketServiceMethodActions } from "./BackendWebSocketService-method-action-types.cjs";
14
14
  /**
@@ -27,10 +27,7 @@ declare const SERVICE_NAME = "AccountActivityService";
27
27
  * Account subscription options
28
28
  */
29
29
  export type SubscriptionOptions = {
30
- /**
31
- * Array of addresses to subscribe to, each in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
32
- */
33
- addresses: string[];
30
+ address: string;
34
31
  };
35
32
  /**
36
33
  * Configuration options for the account activity service
@@ -41,10 +38,10 @@ export type AccountActivityServiceOptions = {
41
38
  /** Optional callback to trace performance of account activity operations (default: no-op) */
42
39
  traceFn?: TraceCallback;
43
40
  };
44
- export type AccountActivityServiceActions = never;
45
- export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS: readonly ["AccountTreeController:getAccountsFromSelectedAccountGroup", "BackendWebSocketService:connect", "BackendWebSocketService:forceReconnection", "BackendWebSocketService:subscribe", "BackendWebSocketService:getConnectionInfo", "BackendWebSocketService:channelHasSubscription", "BackendWebSocketService:getSubscriptionsByChannel", "BackendWebSocketService:findSubscriptionsByChannelPrefix", "BackendWebSocketService:addChannelCallback", "BackendWebSocketService:removeChannelCallback", "RemoteFeatureFlagController:getState"];
46
- export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS: readonly ["AccountTreeController:selectedAccountGroupChange", "BackendWebSocketService:connectionStateChanged", "RemoteFeatureFlagController:stateChange"];
47
- export type AllowedActions = AccountTreeControllerGetAccountsFromSelectedAccountGroupAction | BackendWebSocketServiceMethodActions | RemoteFeatureFlagControllerGetStateAction;
41
+ export type AccountActivityServiceActions = AccountActivityServiceMethodActions;
42
+ export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS: readonly ["AccountsController:getSelectedAccount", "BackendWebSocketService:connect", "BackendWebSocketService:forceReconnection", "BackendWebSocketService:subscribe", "BackendWebSocketService:getConnectionInfo", "BackendWebSocketService:channelHasSubscription", "BackendWebSocketService:getSubscriptionsByChannel", "BackendWebSocketService:findSubscriptionsByChannelPrefix", "BackendWebSocketService:addChannelCallback", "BackendWebSocketService:removeChannelCallback"];
43
+ export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS: readonly ["AccountsController:selectedAccountChange", "BackendWebSocketService:connectionStateChanged"];
44
+ export type AllowedActions = AccountsControllerGetSelectedAccountAction | BackendWebSocketServiceMethodActions;
48
45
  export type AccountActivityServiceTransactionUpdatedEvent = {
49
46
  type: `AccountActivityService:transactionUpdated`;
50
47
  payload: [Transaction];
@@ -76,7 +73,7 @@ export type AccountActivityServiceStatusChangedEvent = {
76
73
  ];
77
74
  };
78
75
  export type AccountActivityServiceEvents = AccountActivityServiceTransactionUpdatedEvent | AccountActivityServiceBalanceUpdatedEvent | AccountActivityServiceSubscriptionErrorEvent | AccountActivityServiceStatusChangedEvent;
79
- export type AllowedEvents = AccountTreeControllerSelectedAccountGroupChangeEvent | BackendWebSocketServiceConnectionStateChangedEvent | RemoteFeatureFlagControllerStateChangeEvent;
76
+ export type AllowedEvents = AccountsControllerSelectedAccountChangeEvent | BackendWebSocketServiceConnectionStateChangedEvent;
80
77
  export type AccountActivityServiceMessenger = Messenger<typeof SERVICE_NAME, AccountActivityServiceActions | AllowedActions, AccountActivityServiceEvents | AllowedEvents>;
81
78
  /**
82
79
  * High-performance service for real-time account activity monitoring using optimized
@@ -126,6 +123,20 @@ export declare class AccountActivityService {
126
123
  constructor(options: AccountActivityServiceOptions & {
127
124
  messenger: AccountActivityServiceMessenger;
128
125
  });
126
+ /**
127
+ * Subscribe to account activity (transactions and balance updates)
128
+ * Address should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
129
+ *
130
+ * @param subscription - Account subscription configuration with address
131
+ */
132
+ subscribe(subscription: SubscriptionOptions): Promise<void>;
133
+ /**
134
+ * Unsubscribe from account activity for specified address
135
+ * Address should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
136
+ *
137
+ * @param subscription - Account subscription configuration with address to unsubscribe
138
+ */
139
+ unsubscribe(subscription: SubscriptionOptions): Promise<void>;
129
140
  /**
130
141
  * Destroy the service and clean up all resources
131
142
  * Optimized for fast cleanup during service destruction or mobile app termination
@@ -1 +1 @@
1
- {"version":3,"file":"AccountActivityService.d.cts","sourceRoot":"","sources":["../../src/ws/AccountActivityService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AccountActivityService.d.cts","sourceRoot":"","sources":["../../src/ws/AccountActivityService.ts"],"names":[],"mappings":"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"}
@@ -4,11 +4,11 @@
4
4
  * This service subscribes to account activity and receives all transactions
5
5
  * and balance updates for those accounts via the comprehensive AccountActivityMessage format.
6
6
  */
7
- import type { AccountTreeControllerSelectedAccountGroupChangeEvent, AccountTreeControllerGetAccountsFromSelectedAccountGroupAction } from "@metamask/account-tree-controller";
7
+ import type { AccountsControllerGetSelectedAccountAction, AccountsControllerSelectedAccountChangeEvent } from "@metamask/accounts-controller";
8
8
  import type { TraceCallback } from "@metamask/controller-utils";
9
9
  import type { Messenger } from "@metamask/messenger";
10
- import type { RemoteFeatureFlagControllerGetStateAction, RemoteFeatureFlagControllerStateChangeEvent } from "@metamask/remote-feature-flag-controller";
11
10
  import type { Transaction, BalanceUpdate } from "../types.mjs";
11
+ import type { AccountActivityServiceMethodActions } from "./AccountActivityService-method-action-types.mjs";
12
12
  import type { BackendWebSocketServiceConnectionStateChangedEvent } from "./BackendWebSocketService.mjs";
13
13
  import type { BackendWebSocketServiceMethodActions } from "./BackendWebSocketService-method-action-types.mjs";
14
14
  /**
@@ -27,10 +27,7 @@ declare const SERVICE_NAME = "AccountActivityService";
27
27
  * Account subscription options
28
28
  */
29
29
  export type SubscriptionOptions = {
30
- /**
31
- * Array of addresses to subscribe to, each in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
32
- */
33
- addresses: string[];
30
+ address: string;
34
31
  };
35
32
  /**
36
33
  * Configuration options for the account activity service
@@ -41,10 +38,10 @@ export type AccountActivityServiceOptions = {
41
38
  /** Optional callback to trace performance of account activity operations (default: no-op) */
42
39
  traceFn?: TraceCallback;
43
40
  };
44
- export type AccountActivityServiceActions = never;
45
- export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS: readonly ["AccountTreeController:getAccountsFromSelectedAccountGroup", "BackendWebSocketService:connect", "BackendWebSocketService:forceReconnection", "BackendWebSocketService:subscribe", "BackendWebSocketService:getConnectionInfo", "BackendWebSocketService:channelHasSubscription", "BackendWebSocketService:getSubscriptionsByChannel", "BackendWebSocketService:findSubscriptionsByChannelPrefix", "BackendWebSocketService:addChannelCallback", "BackendWebSocketService:removeChannelCallback", "RemoteFeatureFlagController:getState"];
46
- export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS: readonly ["AccountTreeController:selectedAccountGroupChange", "BackendWebSocketService:connectionStateChanged", "RemoteFeatureFlagController:stateChange"];
47
- export type AllowedActions = AccountTreeControllerGetAccountsFromSelectedAccountGroupAction | BackendWebSocketServiceMethodActions | RemoteFeatureFlagControllerGetStateAction;
41
+ export type AccountActivityServiceActions = AccountActivityServiceMethodActions;
42
+ export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS: readonly ["AccountsController:getSelectedAccount", "BackendWebSocketService:connect", "BackendWebSocketService:forceReconnection", "BackendWebSocketService:subscribe", "BackendWebSocketService:getConnectionInfo", "BackendWebSocketService:channelHasSubscription", "BackendWebSocketService:getSubscriptionsByChannel", "BackendWebSocketService:findSubscriptionsByChannelPrefix", "BackendWebSocketService:addChannelCallback", "BackendWebSocketService:removeChannelCallback"];
43
+ export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS: readonly ["AccountsController:selectedAccountChange", "BackendWebSocketService:connectionStateChanged"];
44
+ export type AllowedActions = AccountsControllerGetSelectedAccountAction | BackendWebSocketServiceMethodActions;
48
45
  export type AccountActivityServiceTransactionUpdatedEvent = {
49
46
  type: `AccountActivityService:transactionUpdated`;
50
47
  payload: [Transaction];
@@ -76,7 +73,7 @@ export type AccountActivityServiceStatusChangedEvent = {
76
73
  ];
77
74
  };
78
75
  export type AccountActivityServiceEvents = AccountActivityServiceTransactionUpdatedEvent | AccountActivityServiceBalanceUpdatedEvent | AccountActivityServiceSubscriptionErrorEvent | AccountActivityServiceStatusChangedEvent;
79
- export type AllowedEvents = AccountTreeControllerSelectedAccountGroupChangeEvent | BackendWebSocketServiceConnectionStateChangedEvent | RemoteFeatureFlagControllerStateChangeEvent;
76
+ export type AllowedEvents = AccountsControllerSelectedAccountChangeEvent | BackendWebSocketServiceConnectionStateChangedEvent;
80
77
  export type AccountActivityServiceMessenger = Messenger<typeof SERVICE_NAME, AccountActivityServiceActions | AllowedActions, AccountActivityServiceEvents | AllowedEvents>;
81
78
  /**
82
79
  * High-performance service for real-time account activity monitoring using optimized
@@ -126,6 +123,20 @@ export declare class AccountActivityService {
126
123
  constructor(options: AccountActivityServiceOptions & {
127
124
  messenger: AccountActivityServiceMessenger;
128
125
  });
126
+ /**
127
+ * Subscribe to account activity (transactions and balance updates)
128
+ * Address should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
129
+ *
130
+ * @param subscription - Account subscription configuration with address
131
+ */
132
+ subscribe(subscription: SubscriptionOptions): Promise<void>;
133
+ /**
134
+ * Unsubscribe from account activity for specified address
135
+ * Address should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
136
+ *
137
+ * @param subscription - Account subscription configuration with address to unsubscribe
138
+ */
139
+ unsubscribe(subscription: SubscriptionOptions): Promise<void>;
129
140
  /**
130
141
  * Destroy the service and clean up all resources
131
142
  * Optimized for fast cleanup during service destruction or mobile app termination
@@ -1 +1 @@
1
- {"version":3,"file":"AccountActivityService.d.mts","sourceRoot":"","sources":["../../src/ws/AccountActivityService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AccountActivityService.d.mts","sourceRoot":"","sources":["../../src/ws/AccountActivityService.ts"],"names":[],"mappings":"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"}
@@ -15,27 +15,16 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
15
15
  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");
16
16
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
17
17
  };
18
- var _AccountActivityService_instances, _AccountActivityService_messenger, _AccountActivityService_options, _AccountActivityService_trace, _AccountActivityService_chainsUp, _AccountActivityService_subscribe, _AccountActivityService_handleAccountActivityUpdate, _AccountActivityService_handleSelectedAccountChange, _AccountActivityService_handleSystemNotification, _AccountActivityService_handleWebSocketStateChange, _AccountActivityService_subscribeToSelectedAccount, _AccountActivityService_unsubscribeFromAllAccountActivity, _AccountActivityService_convertToCaip10Addresses, _AccountActivityService_getSupportedChainPrefixes, _AccountActivityService_handleFeatureFlagsStateChange, _AccountActivityService_forceReconnection;
19
- import { isObject } from "@metamask/utils";
18
+ var _AccountActivityService_instances, _AccountActivityService_messenger, _AccountActivityService_options, _AccountActivityService_trace, _AccountActivityService_chainsUp, _AccountActivityService_handleAccountActivityUpdate, _AccountActivityService_handleSelectedAccountChange, _AccountActivityService_handleSystemNotification, _AccountActivityService_handleWebSocketStateChange, _AccountActivityService_subscribeToSelectedAccount, _AccountActivityService_unsubscribeFromAllAccountActivity, _AccountActivityService_convertToCaip10Address, _AccountActivityService_forceReconnection;
20
19
  import { projectLogger, createModuleLogger } from "../logger.mjs";
21
20
  import { WebSocketState } from "./BackendWebSocketService.mjs";
22
21
  const SERVICE_NAME = 'AccountActivityService';
23
22
  const log = createModuleLogger(projectLogger, SERVICE_NAME);
24
- const MESSENGER_EXPOSED_METHODS = [];
23
+ const MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'];
25
24
  const SUBSCRIPTION_NAMESPACE = 'account-activity.v1';
26
- // EVM subscriptions are always enabled.
27
- const ALWAYS_SUPPORTED_CHAIN_PREFIXES = ['eip155'];
28
- // Non-EVM chains are gated behind the
29
- // per-network snaps-migration remote feature flags: a chain is
30
- // enabled when its flag payload has `stage >= 1`.
31
- const CHAIN_PREFIX_FEATURE_FLAGS = {
32
- solana: 'networkAssetsSnapsMigrationSolana',
33
- tron: 'networkAssetsSnapsMigrationTron',
34
- stellar: 'networkAssetsSnapsMigrationStellar',
35
- };
36
25
  // Allowed actions that AccountActivityService can call on other controllers
37
26
  export const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [
38
- 'AccountTreeController:getAccountsFromSelectedAccountGroup',
27
+ 'AccountsController:getSelectedAccount',
39
28
  'BackendWebSocketService:connect',
40
29
  'BackendWebSocketService:forceReconnection',
41
30
  'BackendWebSocketService:subscribe',
@@ -45,13 +34,11 @@ export const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [
45
34
  'BackendWebSocketService:findSubscriptionsByChannelPrefix',
46
35
  'BackendWebSocketService:addChannelCallback',
47
36
  'BackendWebSocketService:removeChannelCallback',
48
- 'RemoteFeatureFlagController:getState',
49
37
  ];
50
38
  // Allowed events that AccountActivityService can listen to
51
39
  export const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [
52
- 'AccountTreeController:selectedAccountGroupChange',
40
+ 'AccountsController:selectedAccountChange',
53
41
  'BackendWebSocketService:connectionStateChanged',
54
- 'RemoteFeatureFlagController:stateChange',
55
42
  ];
56
43
  // =============================================================================
57
44
  // Main Service Class
@@ -120,30 +107,78 @@ export class AccountActivityService {
120
107
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
121
108
  ((_request, fn) => fn?.()), "f");
122
109
  __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
123
- __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('AccountTreeController:selectedAccountGroupChange',
110
+ __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('AccountsController:selectedAccountChange',
124
111
  // Promise result intentionally not awaited
125
112
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
126
- async () => await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSelectedAccountChange).call(this));
113
+ async (account) => await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSelectedAccountChange).call(this, account));
127
114
  __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('BackendWebSocketService:connectionStateChanged',
128
115
  // Promise result intentionally not awaited
129
116
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
130
117
  (connectionInfo) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleWebSocketStateChange).call(this, connectionInfo));
131
- __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe(
132
- // eslint-disable-next-line no-restricted-syntax
133
- 'RemoteFeatureFlagController:stateChange',
134
- // Promise result intentionally not awaited
135
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
136
- async () => await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleFeatureFlagsStateChange).call(this),
137
- // Only react to changes in the set of enabled chain prefixes. The
138
- // messenger compares selector results with strict equality, so the
139
- // selector must return a primitive rather than a fresh object.
140
- (state) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_getSupportedChainPrefixes).call(this, state.remoteFeatureFlags).join(','));
141
118
  __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:addChannelCallback', {
142
119
  channelName: `system-notifications.v1.${__classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace}`,
143
120
  callback: (notification) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSystemNotification).call(this, notification),
144
121
  });
145
122
  }
146
123
  // =============================================================================
124
+ // Account Subscription Methods
125
+ // =============================================================================
126
+ /**
127
+ * Subscribe to account activity (transactions and balance updates)
128
+ * Address should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
129
+ *
130
+ * @param subscription - Account subscription configuration with address
131
+ */
132
+ async subscribe(subscription) {
133
+ try {
134
+ await __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:connect');
135
+ // Create channel name from address
136
+ const channel = `${__classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace}.${subscription.address}`;
137
+ // Check if already subscribed
138
+ if (__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:channelHasSubscription', channel)) {
139
+ return;
140
+ }
141
+ // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)
142
+ await __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:subscribe', {
143
+ channels: [channel],
144
+ channelType: __classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace, // e.g., 'account-activity.v1'
145
+ callback: (notification) => {
146
+ __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleAccountActivityUpdate).call(this, notification.data);
147
+ },
148
+ });
149
+ }
150
+ catch (error) {
151
+ log('Subscription failed, forcing reconnection', { error });
152
+ await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_forceReconnection).call(this);
153
+ }
154
+ }
155
+ /**
156
+ * Unsubscribe from account activity for specified address
157
+ * Address should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
158
+ *
159
+ * @param subscription - Account subscription configuration with address to unsubscribe
160
+ */
161
+ async unsubscribe(subscription) {
162
+ const { address } = subscription;
163
+ try {
164
+ // Find channel for the specified address
165
+ const channel = `${__classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace}.${address}`;
166
+ const subscriptions = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:getSubscriptionsByChannel', channel);
167
+ if (subscriptions.length === 0) {
168
+ return;
169
+ }
170
+ // Fast path: Direct unsubscribe using stored unsubscribe function
171
+ // Unsubscribe from all matching subscriptions
172
+ for (const subscriptionInfo of subscriptions) {
173
+ await subscriptionInfo.unsubscribe();
174
+ }
175
+ }
176
+ catch (error) {
177
+ log('Unsubscription failed, forcing reconnection', { error });
178
+ await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_forceReconnection).call(this);
179
+ }
180
+ }
181
+ // =============================================================================
147
182
  // Public Methods - Cleanup
148
183
  // =============================================================================
149
184
  /**
@@ -155,40 +190,7 @@ export class AccountActivityService {
155
190
  __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:removeChannelCallback', `system-notifications.v1.${__classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace}`);
156
191
  }
157
192
  }
158
- _AccountActivityService_messenger = new WeakMap(), _AccountActivityService_options = new WeakMap(), _AccountActivityService_trace = new WeakMap(), _AccountActivityService_chainsUp = new WeakMap(), _AccountActivityService_instances = new WeakSet(), _AccountActivityService_subscribe =
159
- /**
160
- * Subscribe to account activity (transactions and balance updates)
161
- * Addresses should be in CAIP-10 format (e.g., "eip155:0:0x1234..." or "solana:0:ABC123...")
162
- *
163
- * @param subscription - The subscription configuration
164
- * @param subscription.addresses - Array of addresses to subscribe to, each in CAIP-10 format
165
- * or an `addresses` array for batch subscription
166
- */
167
- async function _AccountActivityService_subscribe({ addresses }) {
168
- try {
169
- await __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:connect');
170
- // Derive new subscriptions to be created from the provided addresses,
171
- // filtering out any channels that already have an active subscription
172
- const channels = addresses
173
- .map((address) => `${__classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace}.${address}`)
174
- .filter((channel) => !__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:channelHasSubscription', channel));
175
- if (channels.length === 0) {
176
- return;
177
- }
178
- // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)
179
- await __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:subscribe', {
180
- channels,
181
- channelType: __classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace, // e.g., 'account-activity.v1'
182
- callback: (notification) => {
183
- __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleAccountActivityUpdate).call(this, notification.data);
184
- },
185
- });
186
- }
187
- catch (error) {
188
- log('Subscription failed, forcing reconnection', { error });
189
- await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_forceReconnection).call(this);
190
- }
191
- }, _AccountActivityService_handleAccountActivityUpdate = function _AccountActivityService_handleAccountActivityUpdate(payload) {
193
+ _AccountActivityService_messenger = new WeakMap(), _AccountActivityService_options = new WeakMap(), _AccountActivityService_trace = new WeakMap(), _AccountActivityService_chainsUp = new WeakMap(), _AccountActivityService_instances = new WeakSet(), _AccountActivityService_handleAccountActivityUpdate = function _AccountActivityService_handleAccountActivityUpdate(payload) {
192
194
  const { address, tx, updates } = payload;
193
195
  // Calculate time elapsed between transaction time and message receipt
194
196
  const txTimestampMs = tx.timestamp * 1000; // Convert Unix timestamp (seconds) to milliseconds
@@ -225,16 +227,20 @@ async function _AccountActivityService_subscribe({ addresses }) {
225
227
  }, _AccountActivityService_handleSelectedAccountChange =
226
228
  /**
227
229
  * Handle selected account change event
230
+ *
231
+ * @param newAccount - The newly selected account
228
232
  */
229
- async function _AccountActivityService_handleSelectedAccountChange() {
230
- const selectedAccounts = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('AccountTreeController:getAccountsFromSelectedAccountGroup');
233
+ async function _AccountActivityService_handleSelectedAccountChange(newAccount) {
234
+ if (!newAccount?.address) {
235
+ return;
236
+ }
231
237
  try {
238
+ // Convert new account to CAIP-10 format
239
+ const newAddress = __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_convertToCaip10Address).call(this, newAccount);
232
240
  // First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions
233
241
  await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_unsubscribeFromAllAccountActivity).call(this);
234
- // Subscribe to the new selected accounts in CAIP-10 format
235
- await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_subscribe).call(this, {
236
- addresses: __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_convertToCaip10Addresses).call(this, selectedAccounts),
237
- });
242
+ // Then, subscribe to the new selected account
243
+ await this.subscribe({ address: newAddress });
238
244
  }
239
245
  catch (error) {
240
246
  log('Account change failed', { error });
@@ -306,10 +312,13 @@ async function _AccountActivityService_handleWebSocketStateChange(connectionInfo
306
312
  * Subscribe to the currently selected account only
307
313
  */
308
314
  async function _AccountActivityService_subscribeToSelectedAccount() {
309
- const selectedAccounts = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('AccountTreeController:getAccountsFromSelectedAccountGroup');
310
- await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_subscribe).call(this, {
311
- addresses: __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_convertToCaip10Addresses).call(this, selectedAccounts),
312
- });
315
+ const selectedAccount = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('AccountsController:getSelectedAccount');
316
+ if (!selectedAccount?.address) {
317
+ return;
318
+ }
319
+ // Convert to CAIP-10 format and subscribe
320
+ const address = __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_convertToCaip10Address).call(this, selectedAccount);
321
+ await this.subscribe({ address });
313
322
  }, _AccountActivityService_unsubscribeFromAllAccountActivity =
314
323
  /**
315
324
  * Unsubscribe from all account activity subscriptions for this service
@@ -321,47 +330,19 @@ async function _AccountActivityService_unsubscribeFromAllAccountActivity() {
321
330
  for (const subscription of accountActivitySubscriptions) {
322
331
  await subscription.unsubscribe();
323
332
  }
324
- }, _AccountActivityService_convertToCaip10Addresses = function _AccountActivityService_convertToCaip10Addresses(accounts) {
325
- const supportedChainPrefixes = __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_getSupportedChainPrefixes).call(this);
326
- return accounts.reduce((result, account) => {
327
- const accountPrefix = supportedChainPrefixes.find((prefix) => account.scopes.some((scope) => scope.startsWith(`${prefix}:`)));
328
- if (!accountPrefix) {
329
- // Skip unsupported accounts
330
- return result;
331
- }
332
- result.push(`${accountPrefix}:0:${account.address}`);
333
- return result;
334
- }, []);
335
- }, _AccountActivityService_getSupportedChainPrefixes = function _AccountActivityService_getSupportedChainPrefixes(remoteFeatureFlags = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('RemoteFeatureFlagController:getState').remoteFeatureFlags) {
336
- const prefixes = [...ALWAYS_SUPPORTED_CHAIN_PREFIXES];
337
- for (const [prefix, flagName] of Object.entries(CHAIN_PREFIX_FEATURE_FLAGS)) {
338
- const flagValue = remoteFeatureFlags[flagName];
339
- if (isObject(flagValue) &&
340
- typeof flagValue.stage === 'number' &&
341
- flagValue.stage >= 1) {
342
- prefixes.push(prefix);
343
- }
344
- }
345
- return prefixes;
346
- }, _AccountActivityService_handleFeatureFlagsStateChange =
347
- /**
348
- * Handle remote feature flag changes: if the set of enabled chain prefixes
349
- * changed while connected, resubscribe the selected account so new chains
350
- * are picked up and disabled ones are dropped.
351
- */
352
- async function _AccountActivityService_handleFeatureFlagsStateChange() {
353
- try {
354
- const { state } = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:getConnectionInfo');
355
- if (state !== WebSocketState.CONNECTED) {
356
- // Not connected: the next connection will subscribe with fresh flags
357
- return;
358
- }
359
- await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_unsubscribeFromAllAccountActivity).call(this);
360
- await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_subscribeToSelectedAccount).call(this);
333
+ }, _AccountActivityService_convertToCaip10Address = function _AccountActivityService_convertToCaip10Address(account) {
334
+ // Check if account has EVM scopes
335
+ if (account.scopes.some((scope) => scope.startsWith('eip155:'))) {
336
+ // CAIP-10 format: eip155:0:address (subscribe to all EVM chains)
337
+ return `eip155:0:${account.address}`;
361
338
  }
362
- catch (error) {
363
- log('Feature flag change handling failed', { error });
339
+ // Check if account has Solana scopes
340
+ if (account.scopes.some((scope) => scope.startsWith('solana:'))) {
341
+ // CAIP-10 format: solana:0:address (subscribe to all Solana chains)
342
+ return `solana:0:${account.address}`;
364
343
  }
344
+ // For other chains or unknown scopes, return raw address
345
+ return account.address;
365
346
  }, _AccountActivityService_forceReconnection =
366
347
  /**
367
348
  * Force WebSocket reconnection to clean up subscription state
@@ -1 +1 @@
1
- {"version":3,"file":"AccountActivityService.mjs","sourceRoot":"","sources":["../../src/ws/AccountActivityService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Account Activity Service for monitoring account transactions and balance changes\n *\n * This service subscribes to account activity and receives all transactions\n * and balance updates for those accounts via the comprehensive AccountActivityMessage format.\n */\n\nimport type {\n AccountTreeControllerSelectedAccountGroupChangeEvent,\n AccountTreeControllerGetAccountsFromSelectedAccountGroupAction,\n} from '@metamask/account-tree-controller';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n FeatureFlags,\n RemoteFeatureFlagControllerGetStateAction,\n RemoteFeatureFlagControllerStateChangeEvent,\n} from '@metamask/remote-feature-flag-controller';\nimport { isObject } from '@metamask/utils';\n\nimport { projectLogger, createModuleLogger } from '../logger';\nimport type {\n Transaction,\n AccountActivityMessage,\n BalanceUpdate,\n} from '../types';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from './BackendWebSocketService';\nimport { WebSocketState } from './BackendWebSocketService';\nimport type { BackendWebSocketServiceMethodActions } from './BackendWebSocketService-method-action-types';\n\n// =============================================================================\n// Types and Constants\n// =============================================================================\n\n/**\n * System notification data for chain status updates\n */\nexport type SystemNotificationData = {\n /** Array of chain IDs affected (e.g., ['eip155:137', 'eip155:1']) */\n chainIds: string[];\n /** Status of the chains: 'down' or 'up' */\n status: 'down' | 'up';\n /** Timestamp of the notification */\n timestamp?: number;\n};\n\nconst SERVICE_NAME = 'AccountActivityService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = [] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'account-activity.v1';\n\n// EVM subscriptions are always enabled.\nconst ALWAYS_SUPPORTED_CHAIN_PREFIXES = ['eip155'] as const;\n\n// Non-EVM chains are gated behind the\n// per-network snaps-migration remote feature flags: a chain is\n// enabled when its flag payload has `stage >= 1`.\nconst CHAIN_PREFIX_FEATURE_FLAGS = {\n solana: 'networkAssetsSnapsMigrationSolana',\n tron: 'networkAssetsSnapsMigrationTron',\n stellar: 'networkAssetsSnapsMigrationStellar',\n} as const;\n\n/**\n * Account subscription options\n */\nexport type SubscriptionOptions = {\n /**\n * Array of addresses to subscribe to, each in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n */\n addresses: string[];\n};\n\n/**\n * Configuration options for the account activity service\n */\nexport type AccountActivityServiceOptions = {\n /** Custom subscription namespace (default: 'account-activity.v1') */\n subscriptionNamespace?: string;\n /** Optional callback to trace performance of account activity operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\n// Action types for the messaging system\nexport type AccountActivityServiceActions = never;\n\n// Allowed actions that AccountActivityService can call on other controllers\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n 'BackendWebSocketService:connect',\n 'BackendWebSocketService:forceReconnection',\n 'BackendWebSocketService:subscribe',\n 'BackendWebSocketService:getConnectionInfo',\n 'BackendWebSocketService:channelHasSubscription',\n 'BackendWebSocketService:getSubscriptionsByChannel',\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n 'BackendWebSocketService:addChannelCallback',\n 'BackendWebSocketService:removeChannelCallback',\n 'RemoteFeatureFlagController:getState',\n] as const;\n\n// Allowed events that AccountActivityService can listen to\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [\n 'AccountTreeController:selectedAccountGroupChange',\n 'BackendWebSocketService:connectionStateChanged',\n 'RemoteFeatureFlagController:stateChange',\n] as const;\n\nexport type AllowedActions =\n | AccountTreeControllerGetAccountsFromSelectedAccountGroupAction\n | BackendWebSocketServiceMethodActions\n | RemoteFeatureFlagControllerGetStateAction;\n\n// Event types for the messaging system\n\nexport type AccountActivityServiceTransactionUpdatedEvent = {\n type: `AccountActivityService:transactionUpdated`;\n payload: [Transaction];\n};\n\nexport type AccountActivityServiceBalanceUpdatedEvent = {\n type: `AccountActivityService:balanceUpdated`;\n payload: [{ address: string; chain: string; updates: BalanceUpdate[] }];\n};\n\nexport type AccountActivityServiceSubscriptionErrorEvent = {\n type: `AccountActivityService:subscriptionError`;\n payload: [{ addresses: string[]; error: string; operation: string }];\n};\n\nexport type AccountActivityServiceStatusChangedEvent = {\n type: `AccountActivityService:statusChanged`;\n payload: [\n {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n },\n ];\n};\n\nexport type AccountActivityServiceEvents =\n | AccountActivityServiceTransactionUpdatedEvent\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceSubscriptionErrorEvent\n | AccountActivityServiceStatusChangedEvent;\n\nexport type AllowedEvents =\n | AccountTreeControllerSelectedAccountGroupChangeEvent\n | BackendWebSocketServiceConnectionStateChangedEvent\n | RemoteFeatureFlagControllerStateChangeEvent;\n\nexport type AccountActivityServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n AccountActivityServiceActions | AllowedActions,\n AccountActivityServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * High-performance service for real-time account activity monitoring using optimized\n * WebSocket subscriptions with direct callback routing. Automatically subscribes to\n * the currently selected account and switches subscriptions when the selected account changes.\n * Receives transactions and balance updates using the comprehensive AccountActivityMessage format.\n *\n * Performance Features:\n * - Direct callback routing (no EventEmitter overhead)\n * - Minimal subscription tracking (no duplication with BackendWebSocketService)\n * - Optimized cleanup for mobile environments\n * - Single-account subscription (only selected account)\n * - Comprehensive balance updates with transfer tracking\n *\n * Architecture:\n * - Uses messenger pattern to communicate with BackendWebSocketService\n * - AccountActivityService tracks channel-to-subscriptionId mappings via messenger calls\n * - Automatically subscribes to selected account on initialization\n * - Switches subscriptions when selected account changes\n * - No direct dependency on BackendWebSocketService (uses messenger instead)\n *\n * @example\n * ```typescript\n * const service = new AccountActivityService({\n * messenger: activityMessenger,\n * });\n *\n * // Service automatically subscribes to the currently selected account\n * // When user switches accounts, service automatically resubscribes\n *\n * // All transactions and balance updates are received via optimized\n * // WebSocket callbacks and processed with zero-allocation routing\n * // Balance updates include comprehensive transfer details and post-transaction balances\n * ```\n */\nexport class AccountActivityService {\n /**\n * The name of the service.\n */\n readonly name = SERVICE_NAME;\n\n readonly #messenger: AccountActivityServiceMessenger;\n\n readonly #options: Required<Omit<AccountActivityServiceOptions, 'traceFn'>>;\n\n readonly #trace: TraceCallback;\n\n // Track chains that are currently up (based on system notifications)\n readonly #chainsUp: Set<string> = new Set();\n\n // =============================================================================\n // Constructor and Initialization\n // =============================================================================\n\n /**\n * Creates a new Account Activity service instance\n *\n * @param options - Configuration options including messenger\n */\n constructor(\n options: AccountActivityServiceOptions & {\n messenger: AccountActivityServiceMessenger;\n },\n ) {\n this.#messenger = options.messenger;\n\n // Set configuration with defaults\n this.#options = {\n subscriptionNamespace:\n options.subscriptionNamespace ?? SUBSCRIPTION_NAMESPACE,\n };\n\n // Default to no-op trace function to keep core platform-agnostic\n this.#trace =\n options.traceFn ??\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (((_request: any, fn?: any) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n this.#messenger.subscribe(\n 'AccountTreeController:selectedAccountGroupChange',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async () => await this.#handleSelectedAccountChange(),\n );\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n this.#messenger.subscribe(\n // eslint-disable-next-line no-restricted-syntax\n 'RemoteFeatureFlagController:stateChange',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async () => await this.#handleFeatureFlagsStateChange(),\n // Only react to changes in the set of enabled chain prefixes. The\n // messenger compares selector results with strict equality, so the\n // selector must return a primitive rather than a fresh object.\n (state) =>\n this.#getSupportedChainPrefixes(state.remoteFeatureFlags).join(','),\n );\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n /**\n * Subscribe to account activity (transactions and balance updates)\n * Addresses should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - The subscription configuration\n * @param subscription.addresses - Array of addresses to subscribe to, each in CAIP-10 format\n * or an `addresses` array for batch subscription\n */\n async #subscribe({ addresses }: SubscriptionOptions): Promise<void> {\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n // Derive new subscriptions to be created from the provided addresses,\n // filtering out any channels that already have an active subscription\n const channels = addresses\n .map((address) => `${this.#options.subscriptionNamespace}.${address}`)\n .filter(\n (channel) =>\n !this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n ),\n );\n\n if (channels.length === 0) {\n return;\n }\n\n // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels,\n channelType: this.#options.subscriptionNamespace, // e.g., 'account-activity.v1'\n callback: (notification: ServerNotificationMessage) => {\n this.#handleAccountActivityUpdate(\n notification.data as AccountActivityMessage,\n );\n },\n });\n } catch (error) {\n log('Subscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n /**\n * Handle account activity updates (transactions + balance changes)\n * Processes the comprehensive AccountActivityMessage format with detailed balance updates and transfers\n *\n * @param payload - The account activity message containing transaction and balance updates\n * @example AccountActivityMessage format handling:\n * Input: {\n * address: \"0xd14b52362b5b777ffa754c666ddec6722aaeee08\",\n * tx: { id: \"0x1cde...\", chain: \"eip155:8453\", status: \"confirmed\", timestamp: 1760099871, ... },\n * updates: [{\n * asset: { fungible: true, type: \"eip155:8453/erc20:0x833...\", unit: \"USDC\", decimals: 6 },\n * postBalance: { amount: \"0xc350\" },\n * transfers: [{ from: \"0x7b07...\", to: \"0xd14b...\", amount: \"0x2710\" }]\n * }]\n * }\n * Output: Transaction and balance updates published separately\n */\n #handleAccountActivityUpdate(payload: AccountActivityMessage): void {\n const { address, tx, updates } = payload;\n\n // Calculate time elapsed between transaction time and message receipt\n const txTimestampMs = tx.timestamp * 1000; // Convert Unix timestamp (seconds) to milliseconds\n const elapsedMs = Date.now() - txTimestampMs;\n\n log('Handling account activity update', {\n address,\n updateCount: updates.length,\n elapsedMs,\n });\n\n // Trace message receipt with latency from transaction time to now\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Transaction Message`,\n data: {\n chain: tx.chain,\n status: tx.status,\n elapsed_ms: elapsedMs,\n },\n tags: {\n service: SERVICE_NAME,\n notification_type: this.#options.subscriptionNamespace,\n },\n },\n () => {\n // Process transaction update\n this.#messenger.publish(\n `AccountActivityService:transactionUpdated`,\n tx,\n );\n\n // Publish comprehensive balance updates with transfer details\n this.#messenger.publish(`AccountActivityService:balanceUpdated`, {\n address,\n chain: tx.chain,\n updates,\n });\n },\n );\n }\n\n /**\n * Handle selected account change event\n */\n async #handleSelectedAccountChange(): Promise<void> {\n const selectedAccounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n\n try {\n // First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions\n await this.#unsubscribeFromAllAccountActivity();\n\n // Subscribe to the new selected accounts in CAIP-10 format\n await this.#subscribe({\n addresses: this.#convertToCaip10Addresses(selectedAccounts),\n });\n } catch (error) {\n log('Account change failed', { error });\n }\n }\n\n /**\n * Handle system notification for chain status changes\n * Publishes only the status change (delta) for affected chains\n *\n * @param notification - Server notification message containing chain status updates and timestamp\n */\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as SystemNotificationData;\n const { timestamp } = notification;\n\n // Validate required fields\n if (!data.chainIds || !Array.isArray(data.chainIds) || !data.status) {\n throw new Error(\n 'Invalid system notification data: missing chainIds or status',\n );\n }\n\n // Track chain status\n if (data.status === 'up') {\n for (const chainId of data.chainIds) {\n this.#chainsUp.add(chainId);\n }\n } else {\n for (const chainId of data.chainIds) {\n this.#chainsUp.delete(chainId);\n }\n }\n\n // Publish status change directly (delta update)\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n\n log(\n `WebSocket status change - Published tracked chains as ${data.status}`,\n {\n count: data.chainIds.length,\n chains: data.chainIds,\n status: data.status,\n },\n );\n }\n\n /**\n * Handle WebSocket connection state changes for fallback polling and resubscription\n *\n * @param connectionInfo - WebSocket connection state information\n */\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n // WebSocket connected - resubscribe to selected account\n // The system notification will automatically provide the list of chains that are up\n await this.#subscribeToSelectedAccount();\n } else if (state === WebSocketState.DISCONNECTED) {\n // On disconnect, flush all tracked chains as down\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log('WebSocket disconnection - Published tracked chains as down', {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n });\n\n // Clear the tracking set since all chains are now down\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private Methods - Subscription Management\n // =============================================================================\n\n /**\n * Subscribe to the currently selected account only\n */\n async #subscribeToSelectedAccount(): Promise<void> {\n const selectedAccounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n\n await this.#subscribe({\n addresses: this.#convertToCaip10Addresses(selectedAccounts),\n });\n }\n\n /**\n * Unsubscribe from all account activity subscriptions for this service\n * Finds all channels matching the service's namespace and unsubscribes from them\n */\n async #unsubscribeFromAllAccountActivity(): Promise<void> {\n const accountActivitySubscriptions = this.#messenger.call(\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n this.#options.subscriptionNamespace,\n );\n\n // Unsubscribe from all matching subscriptions\n for (const subscription of accountActivitySubscriptions) {\n await subscription.unsubscribe();\n }\n }\n\n // =============================================================================\n // Private Methods - Utility Functions\n // =============================================================================\n\n /**\n * Convert a list of InternalAccount addresses to CAIP-10 format, using the first\n * supported chain prefix matching the account's scopes\n *\n * @param accounts - The internal accounts to convert\n * @returns The CAIP-10 formatted addresses (e.g. [`eip155:0:address`], meaning\n * all chains of that namespace), or an empty array if none of the account's\n * scopes are supported\n */\n #convertToCaip10Addresses(accounts: InternalAccount[]): string[] {\n const supportedChainPrefixes = this.#getSupportedChainPrefixes();\n return accounts.reduce<string[]>((result, account) => {\n const accountPrefix = supportedChainPrefixes.find((prefix) =>\n account.scopes.some((scope) => scope.startsWith(`${prefix}:`)),\n );\n\n if (!accountPrefix) {\n // Skip unsupported accounts\n return result;\n }\n\n result.push(`${accountPrefix}:0:${account.address}`);\n return result;\n }, []);\n }\n\n /**\n * Get the chain prefixes currently enabled for subscriptions: EVM is always\n * enabled, while other chains are gated behind their per-network remote\n * feature flag (enabled when the flag payload has `stage >= 1`).\n *\n * @param remoteFeatureFlags - The remote feature flags state to check for enabled chains.\n * @returns An array of enabled CAIP-2 namespace prefixes (e.g. `['eip155', 'solana']`)\n */\n #getSupportedChainPrefixes(\n remoteFeatureFlags: FeatureFlags = this.#messenger.call(\n 'RemoteFeatureFlagController:getState',\n ).remoteFeatureFlags,\n ): string[] {\n const prefixes: string[] = [...ALWAYS_SUPPORTED_CHAIN_PREFIXES];\n for (const [prefix, flagName] of Object.entries(\n CHAIN_PREFIX_FEATURE_FLAGS,\n )) {\n const flagValue = remoteFeatureFlags[flagName];\n if (\n isObject(flagValue) &&\n typeof flagValue.stage === 'number' &&\n flagValue.stage >= 1\n ) {\n prefixes.push(prefix);\n }\n }\n return prefixes;\n }\n\n /**\n * Handle remote feature flag changes: if the set of enabled chain prefixes\n * changed while connected, resubscribe the selected account so new chains\n * are picked up and disabled ones are dropped.\n */\n async #handleFeatureFlagsStateChange(): Promise<void> {\n try {\n const { state } = this.#messenger.call(\n 'BackendWebSocketService:getConnectionInfo',\n );\n if (state !== WebSocketState.CONNECTED) {\n // Not connected: the next connection will subscribe with fresh flags\n return;\n }\n\n await this.#unsubscribeFromAllAccountActivity();\n await this.#subscribeToSelectedAccount();\n } catch (error) {\n log('Feature flag change handling failed', { error });\n }\n }\n\n /**\n * Force WebSocket reconnection to clean up subscription state\n */\n async #forceReconnection(): Promise<void> {\n log('Forcing WebSocket reconnection to clean up subscription state');\n\n // Use the dedicated forceReconnection method which performs a controlled\n // disconnect-then-connect sequence to clean up subscription state\n await this.#messenger.call('BackendWebSocketService:forceReconnection');\n }\n\n // =============================================================================\n // Public Methods - Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources\n * Optimized for fast cleanup during service destruction or mobile app termination\n */\n destroy(): void {\n // Clean up system notification callback\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"AccountActivityService.mjs","sourceRoot":"","sources":["../../src/ws/AccountActivityService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Account Activity Service for monitoring account transactions and balance changes\n *\n * This service subscribes to account activity and receives all transactions\n * and balance updates for those accounts via the comprehensive AccountActivityMessage format.\n */\n\nimport type {\n AccountsControllerGetSelectedAccountAction,\n AccountsControllerSelectedAccountChangeEvent,\n} from '@metamask/accounts-controller';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { Messenger } from '@metamask/messenger';\n\nimport { projectLogger, createModuleLogger } from '../logger';\nimport type {\n Transaction,\n AccountActivityMessage,\n BalanceUpdate,\n} from '../types';\nimport type { AccountActivityServiceMethodActions } from './AccountActivityService-method-action-types';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from './BackendWebSocketService';\nimport { WebSocketState } from './BackendWebSocketService';\nimport type { BackendWebSocketServiceMethodActions } from './BackendWebSocketService-method-action-types';\n\n// =============================================================================\n// Types and Constants\n// =============================================================================\n\n/**\n * System notification data for chain status updates\n */\nexport type SystemNotificationData = {\n /** Array of chain IDs affected (e.g., ['eip155:137', 'eip155:1']) */\n chainIds: string[];\n /** Status of the chains: 'down' or 'up' */\n status: 'down' | 'up';\n /** Timestamp of the notification */\n timestamp?: number;\n};\n\nconst SERVICE_NAME = 'AccountActivityService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'account-activity.v1';\n\n/**\n * Account subscription options\n */\nexport type SubscriptionOptions = {\n address: string; // Should be in CAIP-10 format, e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\"\n};\n\n/**\n * Configuration options for the account activity service\n */\nexport type AccountActivityServiceOptions = {\n /** Custom subscription namespace (default: 'account-activity.v1') */\n subscriptionNamespace?: string;\n /** Optional callback to trace performance of account activity operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\n// Action types for the messaging system - using generated method actions\nexport type AccountActivityServiceActions = AccountActivityServiceMethodActions;\n\n// Allowed actions that AccountActivityService can call on other controllers\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [\n 'AccountsController:getSelectedAccount',\n 'BackendWebSocketService:connect',\n 'BackendWebSocketService:forceReconnection',\n 'BackendWebSocketService:subscribe',\n 'BackendWebSocketService:getConnectionInfo',\n 'BackendWebSocketService:channelHasSubscription',\n 'BackendWebSocketService:getSubscriptionsByChannel',\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n 'BackendWebSocketService:addChannelCallback',\n 'BackendWebSocketService:removeChannelCallback',\n] as const;\n\n// Allowed events that AccountActivityService can listen to\nexport const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [\n 'AccountsController:selectedAccountChange',\n 'BackendWebSocketService:connectionStateChanged',\n] as const;\n\nexport type AllowedActions =\n | AccountsControllerGetSelectedAccountAction\n | BackendWebSocketServiceMethodActions;\n\n// Event types for the messaging system\n\nexport type AccountActivityServiceTransactionUpdatedEvent = {\n type: `AccountActivityService:transactionUpdated`;\n payload: [Transaction];\n};\n\nexport type AccountActivityServiceBalanceUpdatedEvent = {\n type: `AccountActivityService:balanceUpdated`;\n payload: [{ address: string; chain: string; updates: BalanceUpdate[] }];\n};\n\nexport type AccountActivityServiceSubscriptionErrorEvent = {\n type: `AccountActivityService:subscriptionError`;\n payload: [{ addresses: string[]; error: string; operation: string }];\n};\n\nexport type AccountActivityServiceStatusChangedEvent = {\n type: `AccountActivityService:statusChanged`;\n payload: [\n {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n },\n ];\n};\n\nexport type AccountActivityServiceEvents =\n | AccountActivityServiceTransactionUpdatedEvent\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceSubscriptionErrorEvent\n | AccountActivityServiceStatusChangedEvent;\n\nexport type AllowedEvents =\n | AccountsControllerSelectedAccountChangeEvent\n | BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type AccountActivityServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n AccountActivityServiceActions | AllowedActions,\n AccountActivityServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * High-performance service for real-time account activity monitoring using optimized\n * WebSocket subscriptions with direct callback routing. Automatically subscribes to\n * the currently selected account and switches subscriptions when the selected account changes.\n * Receives transactions and balance updates using the comprehensive AccountActivityMessage format.\n *\n * Performance Features:\n * - Direct callback routing (no EventEmitter overhead)\n * - Minimal subscription tracking (no duplication with BackendWebSocketService)\n * - Optimized cleanup for mobile environments\n * - Single-account subscription (only selected account)\n * - Comprehensive balance updates with transfer tracking\n *\n * Architecture:\n * - Uses messenger pattern to communicate with BackendWebSocketService\n * - AccountActivityService tracks channel-to-subscriptionId mappings via messenger calls\n * - Automatically subscribes to selected account on initialization\n * - Switches subscriptions when selected account changes\n * - No direct dependency on BackendWebSocketService (uses messenger instead)\n *\n * @example\n * ```typescript\n * const service = new AccountActivityService({\n * messenger: activityMessenger,\n * });\n *\n * // Service automatically subscribes to the currently selected account\n * // When user switches accounts, service automatically resubscribes\n *\n * // All transactions and balance updates are received via optimized\n * // WebSocket callbacks and processed with zero-allocation routing\n * // Balance updates include comprehensive transfer details and post-transaction balances\n * ```\n */\nexport class AccountActivityService {\n /**\n * The name of the service.\n */\n readonly name = SERVICE_NAME;\n\n readonly #messenger: AccountActivityServiceMessenger;\n\n readonly #options: Required<Omit<AccountActivityServiceOptions, 'traceFn'>>;\n\n readonly #trace: TraceCallback;\n\n // Track chains that are currently up (based on system notifications)\n readonly #chainsUp: Set<string> = new Set();\n\n // =============================================================================\n // Constructor and Initialization\n // =============================================================================\n\n /**\n * Creates a new Account Activity service instance\n *\n * @param options - Configuration options including messenger\n */\n constructor(\n options: AccountActivityServiceOptions & {\n messenger: AccountActivityServiceMessenger;\n },\n ) {\n this.#messenger = options.messenger;\n\n // Set configuration with defaults\n this.#options = {\n subscriptionNamespace:\n options.subscriptionNamespace ?? SUBSCRIPTION_NAMESPACE,\n };\n\n // Default to no-op trace function to keep core platform-agnostic\n this.#trace =\n options.traceFn ??\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (((_request: any, fn?: any) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n this.#messenger.subscribe(\n 'AccountsController:selectedAccountChange',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n async (account: InternalAccount) =>\n await this.#handleSelectedAccountChange(account),\n );\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n // =============================================================================\n // Account Subscription Methods\n // =============================================================================\n\n /**\n * Subscribe to account activity (transactions and balance updates)\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address\n */\n async subscribe(subscription: SubscriptionOptions): Promise<void> {\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n // Create channel name from address\n const channel = `${this.#options.subscriptionNamespace}.${subscription.address}`;\n\n // Check if already subscribed\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n return;\n }\n\n // Create subscription using the proper subscribe method (this will be stored in WebSocketService's internal tracking)\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: this.#options.subscriptionNamespace, // e.g., 'account-activity.v1'\n callback: (notification: ServerNotificationMessage) => {\n this.#handleAccountActivityUpdate(\n notification.data as AccountActivityMessage,\n );\n },\n });\n } catch (error) {\n log('Subscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n /**\n * Unsubscribe from account activity for specified address\n * Address should be in CAIP-10 format (e.g., \"eip155:0:0x1234...\" or \"solana:0:ABC123...\")\n *\n * @param subscription - Account subscription configuration with address to unsubscribe\n */\n async unsubscribe(subscription: SubscriptionOptions): Promise<void> {\n const { address } = subscription;\n try {\n // Find channel for the specified address\n const channel = `${this.#options.subscriptionNamespace}.${address}`;\n const subscriptions = this.#messenger.call(\n 'BackendWebSocketService:getSubscriptionsByChannel',\n channel,\n );\n\n if (subscriptions.length === 0) {\n return;\n }\n\n // Fast path: Direct unsubscribe using stored unsubscribe function\n // Unsubscribe from all matching subscriptions\n for (const subscriptionInfo of subscriptions) {\n await subscriptionInfo.unsubscribe();\n }\n } catch (error) {\n log('Unsubscription failed, forcing reconnection', { error });\n await this.#forceReconnection();\n }\n }\n\n // =============================================================================\n // Private Methods - Event Handlers\n // =============================================================================\n\n /**\n * Handle account activity updates (transactions + balance changes)\n * Processes the comprehensive AccountActivityMessage format with detailed balance updates and transfers\n *\n * @param payload - The account activity message containing transaction and balance updates\n * @example AccountActivityMessage format handling:\n * Input: {\n * address: \"0xd14b52362b5b777ffa754c666ddec6722aaeee08\",\n * tx: { id: \"0x1cde...\", chain: \"eip155:8453\", status: \"confirmed\", timestamp: 1760099871, ... },\n * updates: [{\n * asset: { fungible: true, type: \"eip155:8453/erc20:0x833...\", unit: \"USDC\", decimals: 6 },\n * postBalance: { amount: \"0xc350\" },\n * transfers: [{ from: \"0x7b07...\", to: \"0xd14b...\", amount: \"0x2710\" }]\n * }]\n * }\n * Output: Transaction and balance updates published separately\n */\n #handleAccountActivityUpdate(payload: AccountActivityMessage): void {\n const { address, tx, updates } = payload;\n\n // Calculate time elapsed between transaction time and message receipt\n const txTimestampMs = tx.timestamp * 1000; // Convert Unix timestamp (seconds) to milliseconds\n const elapsedMs = Date.now() - txTimestampMs;\n\n log('Handling account activity update', {\n address,\n updateCount: updates.length,\n elapsedMs,\n });\n\n // Trace message receipt with latency from transaction time to now\n // Promise result intentionally not awaited\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Transaction Message`,\n data: {\n chain: tx.chain,\n status: tx.status,\n elapsed_ms: elapsedMs,\n },\n tags: {\n service: SERVICE_NAME,\n notification_type: this.#options.subscriptionNamespace,\n },\n },\n () => {\n // Process transaction update\n this.#messenger.publish(\n `AccountActivityService:transactionUpdated`,\n tx,\n );\n\n // Publish comprehensive balance updates with transfer details\n this.#messenger.publish(`AccountActivityService:balanceUpdated`, {\n address,\n chain: tx.chain,\n updates,\n });\n },\n );\n }\n\n /**\n * Handle selected account change event\n *\n * @param newAccount - The newly selected account\n */\n async #handleSelectedAccountChange(\n newAccount: InternalAccount | null,\n ): Promise<void> {\n if (!newAccount?.address) {\n return;\n }\n\n try {\n // Convert new account to CAIP-10 format\n const newAddress = this.#convertToCaip10Address(newAccount);\n\n // First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions\n await this.#unsubscribeFromAllAccountActivity();\n\n // Then, subscribe to the new selected account\n await this.subscribe({ address: newAddress });\n } catch (error) {\n log('Account change failed', { error });\n }\n }\n\n /**\n * Handle system notification for chain status changes\n * Publishes only the status change (delta) for affected chains\n *\n * @param notification - Server notification message containing chain status updates and timestamp\n */\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as SystemNotificationData;\n const { timestamp } = notification;\n\n // Validate required fields\n if (!data.chainIds || !Array.isArray(data.chainIds) || !data.status) {\n throw new Error(\n 'Invalid system notification data: missing chainIds or status',\n );\n }\n\n // Track chain status\n if (data.status === 'up') {\n for (const chainId of data.chainIds) {\n this.#chainsUp.add(chainId);\n }\n } else {\n for (const chainId of data.chainIds) {\n this.#chainsUp.delete(chainId);\n }\n }\n\n // Publish status change directly (delta update)\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n\n log(\n `WebSocket status change - Published tracked chains as ${data.status}`,\n {\n count: data.chainIds.length,\n chains: data.chainIds,\n status: data.status,\n },\n );\n }\n\n /**\n * Handle WebSocket connection state changes for fallback polling and resubscription\n *\n * @param connectionInfo - WebSocket connection state information\n */\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n // WebSocket connected - resubscribe to selected account\n // The system notification will automatically provide the list of chains that are up\n await this.#subscribeToSelectedAccount();\n } else if (state === WebSocketState.DISCONNECTED) {\n // On disconnect, flush all tracked chains as down\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish(`AccountActivityService:statusChanged`, {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log('WebSocket disconnection - Published tracked chains as down', {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n });\n\n // Clear the tracking set since all chains are now down\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private Methods - Subscription Management\n // =============================================================================\n\n /**\n * Subscribe to the currently selected account only\n */\n async #subscribeToSelectedAccount(): Promise<void> {\n const selectedAccount = this.#messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n\n if (!selectedAccount?.address) {\n return;\n }\n\n // Convert to CAIP-10 format and subscribe\n const address = this.#convertToCaip10Address(selectedAccount);\n await this.subscribe({ address });\n }\n\n /**\n * Unsubscribe from all account activity subscriptions for this service\n * Finds all channels matching the service's namespace and unsubscribes from them\n */\n async #unsubscribeFromAllAccountActivity(): Promise<void> {\n const accountActivitySubscriptions = this.#messenger.call(\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n this.#options.subscriptionNamespace,\n );\n\n // Unsubscribe from all matching subscriptions\n for (const subscription of accountActivitySubscriptions) {\n await subscription.unsubscribe();\n }\n }\n\n // =============================================================================\n // Private Methods - Utility Functions\n // =============================================================================\n\n /**\n * Convert an InternalAccount address to CAIP-10 format or raw address\n *\n * @param account - The internal account to convert\n * @returns The CAIP-10 formatted address or raw address\n */\n #convertToCaip10Address(account: InternalAccount): string {\n // Check if account has EVM scopes\n if (account.scopes.some((scope) => scope.startsWith('eip155:'))) {\n // CAIP-10 format: eip155:0:address (subscribe to all EVM chains)\n return `eip155:0:${account.address}`;\n }\n\n // Check if account has Solana scopes\n if (account.scopes.some((scope) => scope.startsWith('solana:'))) {\n // CAIP-10 format: solana:0:address (subscribe to all Solana chains)\n return `solana:0:${account.address}`;\n }\n\n // For other chains or unknown scopes, return raw address\n return account.address;\n }\n\n /**\n * Force WebSocket reconnection to clean up subscription state\n */\n async #forceReconnection(): Promise<void> {\n log('Forcing WebSocket reconnection to clean up subscription state');\n\n // Use the dedicated forceReconnection method which performs a controlled\n // disconnect-then-connect sequence to clean up subscription state\n await this.#messenger.call('BackendWebSocketService:forceReconnection');\n }\n\n // =============================================================================\n // Public Methods - Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources\n * Optimized for fast cleanup during service destruction or mobile app termination\n */\n destroy(): void {\n // Clean up system notification callback\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n `system-notifications.v1.${this.#options.subscriptionNamespace}`,\n );\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/core-backend",
3
- "version": "6.5.0-preview-6756b9ebc",
3
+ "version": "6.5.0-preview-75ce9748b",
4
4
  "description": "Core backend services for MetaMask",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -55,12 +55,11 @@
55
55
  "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
56
56
  },
57
57
  "dependencies": {
58
- "@metamask/account-tree-controller": "^7.5.5",
58
+ "@metamask/accounts-controller": "^39.0.5",
59
59
  "@metamask/controller-utils": "^12.3.0",
60
60
  "@metamask/keyring-controller": "^27.1.0",
61
61
  "@metamask/messenger": "^2.0.0",
62
62
  "@metamask/profile-sync-controller": "^28.3.0",
63
- "@metamask/remote-feature-flag-controller": "^4.2.2",
64
63
  "@metamask/utils": "^11.11.0",
65
64
  "@tanstack/query-core": "^5.62.16",
66
65
  "async-mutex": "^0.5.0",