@metamask-previews/core-backend 6.5.0-preview-1ccf11bcb → 6.5.0-preview-0f07793cb
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 +10 -0
- package/dist/ws/AccountActivityService.cjs +74 -31
- package/dist/ws/AccountActivityService.cjs.map +1 -1
- package/dist/ws/AccountActivityService.d.cts +6 -5
- package/dist/ws/AccountActivityService.d.cts.map +1 -1
- package/dist/ws/AccountActivityService.d.mts +6 -5
- package/dist/ws/AccountActivityService.d.mts.map +1 -1
- package/dist/ws/AccountActivityService.mjs +74 -31
- package/dist/ws/AccountActivityService.mjs.map +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -9,10 +9,20 @@ 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 `AccountActivityService` messenger must now allow the `RemoteFeatureFlagController:getState` action and the `RemoteFeatureFlagController:stateChange` event.
|
|
14
|
+
- 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 (including when `RemoteFeatureFlagController` is unavailable).
|
|
15
|
+
- Accounts whose scopes do not match an enabled chain are no longer subscribed at all; previously they were subscribed with a raw (non-CAIP-10) address.
|
|
16
|
+
- When the enabled chains change via a feature flag update while the websocket is connected, the service automatically resubscribes the selected account.
|
|
17
|
+
- Add `@metamask/remote-feature-flag-controller` `^4.2.2` as a dependency ([#9379](https://github.com/MetaMask/core/pull/9379))
|
|
12
18
|
- Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392))
|
|
13
19
|
- Bump `@metamask/profile-sync-controller` from `^28.2.0` to `^28.3.0` ([#9463](https://github.com/MetaMask/core/pull/9463))
|
|
14
20
|
- Bump `@metamask/accounts-controller` from `^39.0.4` to `^39.0.5` ([#9470](https://github.com/MetaMask/core/pull/9470))
|
|
15
21
|
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- `AccountActivityService` subscribes to all supported scopes for a given account ([#9379](https://github.com/MetaMask/core/pull/9379))
|
|
25
|
+
|
|
16
26
|
## [6.5.0]
|
|
17
27
|
|
|
18
28
|
### Added
|
|
@@ -16,18 +16,29 @@ 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_handleAccountActivityUpdate, _AccountActivityService_handleSelectedAccountChange, _AccountActivityService_handleSystemNotification, _AccountActivityService_handleWebSocketStateChange, _AccountActivityService_subscribeToSelectedAccount, _AccountActivityService_unsubscribeFromAllAccountActivity, _AccountActivityService_convertToCaip10Address, _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_getSupportedChainPrefixes, _AccountActivityService_handleFeatureFlagsStateChange, _AccountActivityService_getSupportedMultichainCaip10Addresses, _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");
|
|
22
23
|
const logger_1 = require("../logger.cjs");
|
|
23
24
|
const BackendWebSocketService_1 = require("./BackendWebSocketService.cjs");
|
|
24
25
|
const SERVICE_NAME = 'AccountActivityService';
|
|
25
26
|
const log = (0, logger_1.createModuleLogger)(logger_1.projectLogger, SERVICE_NAME);
|
|
26
27
|
const MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'];
|
|
27
28
|
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
|
+
};
|
|
28
39
|
// Allowed actions that AccountActivityService can call on other controllers
|
|
29
40
|
exports.ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [
|
|
30
|
-
'
|
|
41
|
+
'AccountTreeController:getAccountsFromSelectedAccountGroup',
|
|
31
42
|
'BackendWebSocketService:connect',
|
|
32
43
|
'BackendWebSocketService:forceReconnection',
|
|
33
44
|
'BackendWebSocketService:subscribe',
|
|
@@ -37,11 +48,13 @@ exports.ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [
|
|
|
37
48
|
'BackendWebSocketService:findSubscriptionsByChannelPrefix',
|
|
38
49
|
'BackendWebSocketService:addChannelCallback',
|
|
39
50
|
'BackendWebSocketService:removeChannelCallback',
|
|
51
|
+
'RemoteFeatureFlagController:getState',
|
|
40
52
|
];
|
|
41
53
|
// Allowed events that AccountActivityService can listen to
|
|
42
54
|
exports.ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [
|
|
43
|
-
'
|
|
55
|
+
'AccountTreeController:selectedAccountGroupChange',
|
|
44
56
|
'BackendWebSocketService:connectionStateChanged',
|
|
57
|
+
'RemoteFeatureFlagController:stateChange',
|
|
45
58
|
];
|
|
46
59
|
// =============================================================================
|
|
47
60
|
// Main Service Class
|
|
@@ -110,14 +123,27 @@ class AccountActivityService {
|
|
|
110
123
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
124
|
((_request, fn) => fn?.()), "f");
|
|
112
125
|
__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
113
|
-
__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('
|
|
126
|
+
__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('AccountTreeController:selectedAccountGroupChange',
|
|
114
127
|
// Promise result intentionally not awaited
|
|
115
128
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
116
|
-
async (
|
|
129
|
+
async () => await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSelectedAccountChange).call(this));
|
|
117
130
|
__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('BackendWebSocketService:connectionStateChanged',
|
|
118
131
|
// Promise result intentionally not awaited
|
|
119
132
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
120
133
|
(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 relevant feature flags for chain prefixes
|
|
141
|
+
(state) => ({
|
|
142
|
+
remoteFeatureFlags: Object.fromEntries(Object.entries(CHAIN_PREFIX_FEATURE_FLAGS).map(([_prefix, flag]) => [
|
|
143
|
+
flag,
|
|
144
|
+
state.remoteFeatureFlags[flag],
|
|
145
|
+
])),
|
|
146
|
+
}));
|
|
121
147
|
__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:addChannelCallback', {
|
|
122
148
|
channelName: `system-notifications.v1.${__classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace}`,
|
|
123
149
|
callback: (notification) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSystemNotification).call(this, notification),
|
|
@@ -231,20 +257,16 @@ _AccountActivityService_messenger = new WeakMap(), _AccountActivityService_optio
|
|
|
231
257
|
}, _AccountActivityService_handleSelectedAccountChange =
|
|
232
258
|
/**
|
|
233
259
|
* Handle selected account change event
|
|
234
|
-
*
|
|
235
|
-
* @param newAccount - The newly selected account
|
|
236
260
|
*/
|
|
237
|
-
async function _AccountActivityService_handleSelectedAccountChange(
|
|
238
|
-
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
261
|
+
async function _AccountActivityService_handleSelectedAccountChange() {
|
|
262
|
+
const selectedAccounts = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('AccountTreeController:getAccountsFromSelectedAccountGroup');
|
|
241
263
|
try {
|
|
242
|
-
// Convert new account to CAIP-10 format
|
|
243
|
-
const newAddress = __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_convertToCaip10Address).call(this, newAccount);
|
|
244
264
|
// First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions
|
|
245
265
|
await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_unsubscribeFromAllAccountActivity).call(this);
|
|
246
|
-
|
|
247
|
-
|
|
266
|
+
for (const address of __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_getSupportedMultichainCaip10Addresses).call(this, selectedAccounts)) {
|
|
267
|
+
// Subscribe to the new selected account in CAIP-10 format
|
|
268
|
+
await this.subscribe({ address });
|
|
269
|
+
}
|
|
248
270
|
}
|
|
249
271
|
catch (error) {
|
|
250
272
|
log('Account change failed', { error });
|
|
@@ -316,13 +338,10 @@ async function _AccountActivityService_handleWebSocketStateChange(connectionInfo
|
|
|
316
338
|
* Subscribe to the currently selected account only
|
|
317
339
|
*/
|
|
318
340
|
async function _AccountActivityService_subscribeToSelectedAccount() {
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
341
|
+
const selectedAccounts = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('AccountTreeController:getAccountsFromSelectedAccountGroup');
|
|
342
|
+
for (const address of __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_getSupportedMultichainCaip10Addresses).call(this, selectedAccounts)) {
|
|
343
|
+
await this.subscribe({ address });
|
|
322
344
|
}
|
|
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 });
|
|
326
345
|
}, _AccountActivityService_unsubscribeFromAllAccountActivity =
|
|
327
346
|
/**
|
|
328
347
|
* Unsubscribe from all account activity subscriptions for this service
|
|
@@ -335,18 +354,42 @@ async function _AccountActivityService_unsubscribeFromAllAccountActivity() {
|
|
|
335
354
|
await subscription.unsubscribe();
|
|
336
355
|
}
|
|
337
356
|
}, _AccountActivityService_convertToCaip10Address = function _AccountActivityService_convertToCaip10Address(account) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
357
|
+
const chainPrefix = __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_getSupportedChainPrefixes).call(this).find((prefix) => account.scopes.some((scope) => scope.startsWith(`${prefix}:`)));
|
|
358
|
+
return chainPrefix ? `${chainPrefix}:0:${account.address}` : undefined;
|
|
359
|
+
}, _AccountActivityService_getSupportedChainPrefixes = function _AccountActivityService_getSupportedChainPrefixes(remoteFeatureFlags = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('RemoteFeatureFlagController:getState').remoteFeatureFlags) {
|
|
360
|
+
const prefixes = [...ALWAYS_SUPPORTED_CHAIN_PREFIXES];
|
|
361
|
+
for (const [prefix, flagName] of Object.entries(CHAIN_PREFIX_FEATURE_FLAGS)) {
|
|
362
|
+
const flagValue = remoteFeatureFlags[flagName];
|
|
363
|
+
if ((0, utils_1.isObject)(flagValue) &&
|
|
364
|
+
typeof flagValue.stage === 'number' &&
|
|
365
|
+
flagValue.stage >= 1) {
|
|
366
|
+
prefixes.push(prefix);
|
|
367
|
+
}
|
|
342
368
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
369
|
+
return prefixes;
|
|
370
|
+
}, _AccountActivityService_handleFeatureFlagsStateChange =
|
|
371
|
+
/**
|
|
372
|
+
* Handle remote feature flag changes: if the set of enabled chain prefixes
|
|
373
|
+
* changed while connected, resubscribe the selected account so new chains
|
|
374
|
+
* are picked up and disabled ones are dropped.
|
|
375
|
+
*/
|
|
376
|
+
async function _AccountActivityService_handleFeatureFlagsStateChange() {
|
|
377
|
+
try {
|
|
378
|
+
const { state } = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:getConnectionInfo');
|
|
379
|
+
if (state !== BackendWebSocketService_1.WebSocketState.CONNECTED) {
|
|
380
|
+
// Not connected: the next connection will subscribe with fresh flags
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_unsubscribeFromAllAccountActivity).call(this);
|
|
384
|
+
await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_subscribeToSelectedAccount).call(this);
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
log('Feature flag change handling failed', { error });
|
|
347
388
|
}
|
|
348
|
-
|
|
349
|
-
return
|
|
389
|
+
}, _AccountActivityService_getSupportedMultichainCaip10Addresses = function _AccountActivityService_getSupportedMultichainCaip10Addresses(accounts) {
|
|
390
|
+
return accounts
|
|
391
|
+
.map((account) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_convertToCaip10Address).call(this, account))
|
|
392
|
+
.filter((address) => address !== undefined);
|
|
350
393
|
}, _AccountActivityService_forceReconnection =
|
|
351
394
|
/**
|
|
352
395
|
* Force WebSocket reconnection to clean up subscription state
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountActivityService.cjs","sourceRoot":"","sources":["../../src/ws/AccountActivityService.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;;;;AAUH,0CAA8D;AAY9D,2EAA2D;AAmB3D,MAAM,YAAY,GAAG,wBAAwB,CAAC;AAE9C,MAAM,GAAG,GAAG,IAAA,2BAAkB,EAAC,sBAAa,EAAE,YAAY,CAAC,CAAC;AAE5D,MAAM,yBAAyB,GAAG,CAAC,WAAW,EAAE,aAAa,CAAU,CAAC;AAExE,MAAM,sBAAsB,GAAG,qBAAqB,CAAC;AA0BrD,4EAA4E;AAC/D,QAAA,wCAAwC,GAAG;IACtD,uCAAuC;IACvC,iCAAiC;IACjC,2CAA2C;IAC3C,mCAAmC;IACnC,2CAA2C;IAC3C,gDAAgD;IAChD,mDAAmD;IACnD,0DAA0D;IAC1D,4CAA4C;IAC5C,+CAA+C;CACvC,CAAC;AAEX,2DAA2D;AAC9C,QAAA,uCAAuC,GAAG;IACrD,0CAA0C;IAC1C,gDAAgD;CACxC,CAAC;AAkDX,gFAAgF;AAChF,qBAAqB;AACrB,gFAAgF;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAa,sBAAsB;IAejC,gFAAgF;IAChF,iCAAiC;IACjC,gFAAgF;IAEhF;;;;OAIG;IACH,YACE,OAEC;;QA1BH;;WAEG;QACM,SAAI,GAAG,YAAY,CAAC;QAEpB,oDAA4C;QAE5C,kDAAmE;QAEnE,gDAAsB;QAE/B,qEAAqE;QAC5D,2CAAyB,IAAI,GAAG,EAAE,EAAC;QAgB1C,uBAAA,IAAI,qCAAc,OAAO,CAAC,SAAS,MAAA,CAAC;QAEpC,kCAAkC;QAClC,uBAAA,IAAI,mCAAY;YACd,qBAAqB,EACnB,OAAO,CAAC,qBAAqB,IAAI,sBAAsB;SAC1D,MAAA,CAAC;QAEF,iEAAiE;QACjE,uBAAA,IAAI,iCACF,OAAO,CAAC,OAAO;YACf,8DAA8D;YAC7D,CAAC,CAAC,QAAa,EAAE,EAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAmB,MAAA,CAAC;QAE3D,uBAAA,IAAI,yCAAW,CAAC,4BAA4B,CAC1C,IAAI,EACJ,yBAAyB,CAC1B,CAAC;QACF,uBAAA,IAAI,yCAAW,CAAC,SAAS,CACvB,0CAA0C;QAC1C,2CAA2C;QAC3C,kEAAkE;QAClE,KAAK,EAAE,OAAwB,EAAE,EAAE,CACjC,MAAM,uBAAA,IAAI,8FAA6B,MAAjC,IAAI,EAA8B,OAAO,CAAC,CACnD,CAAC;QACF,uBAAA,IAAI,yCAAW,CAAC,SAAS,CACvB,gDAAgD;QAChD,2CAA2C;QAC3C,kEAAkE;QAClE,CAAC,cAAuC,EAAE,EAAE,CAC1C,uBAAA,IAAI,6FAA4B,MAAhC,IAAI,EAA6B,cAAc,CAAC,CACnD,CAAC;QACF,uBAAA,IAAI,yCAAW,CAAC,IAAI,CAAC,4CAA4C,EAAE;YACjE,WAAW,EAAE,2BAA2B,uBAAA,IAAI,uCAAS,CAAC,qBAAqB,EAAE;YAC7E,QAAQ,EAAE,CAAC,YAAuC,EAAE,EAAE,CACpD,uBAAA,IAAI,2FAA0B,MAA9B,IAAI,EAA2B,YAAY,CAAC;SAC/C,CAAC,CAAC;IACL,CAAC;IAED,gFAAgF;IAChF,+BAA+B;IAC/B,gFAAgF;IAEhF;;;;;OAKG;IACH,KAAK,CAAC,SAAS,CAAC,YAAiC;QAC/C,IAAI,CAAC;YACH,MAAM,uBAAA,IAAI,yCAAW,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;YAE9D,mCAAmC;YACnC,MAAM,OAAO,GAAG,GAAG,uBAAA,IAAI,uCAAS,CAAC,qBAAqB,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;YAEjF,8BAA8B;YAC9B,IACE,uBAAA,IAAI,yCAAW,CAAC,IAAI,CAClB,gDAAgD,EAChD,OAAO,CACR,EACD,CAAC;gBACD,OAAO;YACT,CAAC;YAED,sHAAsH;YACtH,MAAM,uBAAA,IAAI,yCAAW,CAAC,IAAI,CAAC,mCAAmC,EAAE;gBAC9D,QAAQ,EAAE,CAAC,OAAO,CAAC;gBACnB,WAAW,EAAE,uBAAA,IAAI,uCAAS,CAAC,qBAAqB,EAAE,8BAA8B;gBAChF,QAAQ,EAAE,CAAC,YAAuC,EAAE,EAAE;oBACpD,uBAAA,IAAI,8FAA6B,MAAjC,IAAI,EACF,YAAY,CAAC,IAA8B,CAC5C,CAAC;gBACJ,CAAC;aACF,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,2CAA2C,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YAC5D,MAAM,uBAAA,IAAI,oFAAmB,MAAvB,IAAI,CAAqB,CAAC;QAClC,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CAAC,YAAiC;QACjD,MAAM,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC;YACH,yCAAyC;YACzC,MAAM,OAAO,GAAG,GAAG,uBAAA,IAAI,uCAAS,CAAC,qBAAqB,IAAI,OAAO,EAAE,CAAC;YACpE,MAAM,aAAa,GAAG,uBAAA,IAAI,yCAAW,CAAC,IAAI,CACxC,mDAAmD,EACnD,OAAO,CACR,CAAC;YAEF,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC/B,OAAO;YACT,CAAC;YAED,kEAAkE;YAClE,8CAA8C;YAC9C,KAAK,MAAM,gBAAgB,IAAI,aAAa,EAAE,CAAC;gBAC7C,MAAM,gBAAgB,CAAC,WAAW,EAAE,CAAC;YACvC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,6CAA6C,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YAC9D,MAAM,uBAAA,IAAI,oFAAmB,MAAvB,IAAI,CAAqB,CAAC;QAClC,CAAC;IACH,CAAC;IA2PD,gFAAgF;IAChF,2BAA2B;IAC3B,gFAAgF;IAEhF;;;OAGG;IACH,OAAO;QACL,wCAAwC;QACxC,uBAAA,IAAI,yCAAW,CAAC,IAAI,CAClB,+CAA+C,EAC/C,2BAA2B,uBAAA,IAAI,uCAAS,CAAC,qBAAqB,EAAE,CACjE,CAAC;IACJ,CAAC;CACF;AAtZD,wDAsZC;2WAnP8B,OAA+B;IAC1D,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAEzC,sEAAsE;IACtE,MAAM,aAAa,GAAG,EAAE,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,mDAAmD;IAC9F,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,CAAC;IAE7C,GAAG,CAAC,kCAAkC,EAAE;QACtC,OAAO;QACP,WAAW,EAAE,OAAO,CAAC,MAAM;QAC3B,SAAS;KACV,CAAC,CAAC;IAEH,kEAAkE;IAClE,2CAA2C;IAC3C,mEAAmE;IACnE,uBAAA,IAAI,qCAAO,MAAX,IAAI,EACF;QACE,IAAI,EAAE,GAAG,YAAY,sBAAsB;QAC3C,IAAI,EAAE;YACJ,KAAK,EAAE,EAAE,CAAC,KAAK;YACf,MAAM,EAAE,EAAE,CAAC,MAAM;YACjB,UAAU,EAAE,SAAS;SACtB;QACD,IAAI,EAAE;YACJ,OAAO,EAAE,YAAY;YACrB,iBAAiB,EAAE,uBAAA,IAAI,uCAAS,CAAC,qBAAqB;SACvD;KACF,EACD,GAAG,EAAE;QACH,6BAA6B;QAC7B,uBAAA,IAAI,yCAAW,CAAC,OAAO,CACrB,2CAA2C,EAC3C,EAAE,CACH,CAAC;QAEF,8DAA8D;QAC9D,uBAAA,IAAI,yCAAW,CAAC,OAAO,CAAC,uCAAuC,EAAE;YAC/D,OAAO;YACP,KAAK,EAAE,EAAE,CAAC,KAAK;YACf,OAAO;SACR,CAAC,CAAC;IACL,CAAC,CACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,KAAK,8DACH,UAAkC;IAElC,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC;QACzB,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,wCAAwC;QACxC,MAAM,UAAU,GAAG,uBAAA,IAAI,yFAAwB,MAA5B,IAAI,EAAyB,UAAU,CAAC,CAAC;QAE5D,qGAAqG;QACrG,MAAM,uBAAA,IAAI,oGAAmC,MAAvC,IAAI,CAAqC,CAAC;QAEhD,8CAA8C;QAC9C,MAAM,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC,+GAQyB,YAAuC;IAC/D,MAAM,IAAI,GAAG,YAAY,CAAC,IAA8B,CAAC;IACzD,MAAM,EAAE,SAAS,EAAE,GAAG,YAAY,CAAC;IAEnC,2BAA2B;IAC3B,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CACb,8DAA8D,CAC/D,CAAC;IACJ,CAAC;IAED,qBAAqB;IACrB,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QACzB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,uBAAA,IAAI,wCAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,uBAAA,IAAI,wCAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,gDAAgD;IAChD,uBAAA,IAAI,yCAAW,CAAC,OAAO,CAAC,sCAAsC,EAAE;QAC9D,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,SAAS;KACV,CAAC,CAAC;IAEH,GAAG,CACD,yDAAyD,IAAI,CAAC,MAAM,EAAE,EACtE;QACE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM;QAC3B,MAAM,EAAE,IAAI,CAAC,QAAQ;QACrB,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,KAAK,6DACH,cAAuC;IAEvC,MAAM,EAAE,KAAK,EAAE,GAAG,cAAc,CAAC;IAEjC,IAAI,KAAK,KAAK,wCAAc,CAAC,SAAS,EAAE,CAAC;QACvC,wDAAwD;QACxD,oFAAoF;QACpF,MAAM,uBAAA,IAAI,6FAA4B,MAAhC,IAAI,CAA8B,CAAC;IAC3C,CAAC;SAAM,IAAI,KAAK,KAAK,wCAAc,CAAC,YAAY,EAAE,CAAC;QACjD,kDAAkD;QAClD,MAAM,gBAAgB,GAAG,KAAK,CAAC,IAAI,CAAC,uBAAA,IAAI,wCAAU,CAAC,CAAC;QAEpD,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,uBAAA,IAAI,yCAAW,CAAC,OAAO,CAAC,sCAAsC,EAAE;gBAC9D,QAAQ,EAAE,gBAAgB;gBAC1B,MAAM,EAAE,MAAM;gBACd,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;aACtB,CAAC,CAAC;YAEH,GAAG,CAAC,4DAA4D,EAAE;gBAChE,KAAK,EAAE,gBAAgB,CAAC,MAAM;gBAC9B,MAAM,EAAE,gBAAgB;aACzB,CAAC,CAAC;YAEH,uDAAuD;YACvD,uBAAA,IAAI,wCAAU,CAAC,KAAK,EAAE,CAAC;QACzB,CAAC;IACH,CAAC;AACH,CAAC;AAED,gFAAgF;AAChF,4CAA4C;AAC5C,gFAAgF;AAEhF;;GAEG;AACH,KAAK;IACH,MAAM,eAAe,GAAG,uBAAA,IAAI,yCAAW,CAAC,IAAI,CAC1C,uCAAuC,CACxC,CAAC;IAEF,IAAI,CAAC,eAAe,EAAE,OAAO,EAAE,CAAC;QAC9B,OAAO;IACT,CAAC;IAED,0CAA0C;IAC1C,MAAM,OAAO,GAAG,uBAAA,IAAI,yFAAwB,MAA5B,IAAI,EAAyB,eAAe,CAAC,CAAC;IAC9D,MAAM,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,KAAK;IACH,MAAM,4BAA4B,GAAG,uBAAA,IAAI,yCAAW,CAAC,IAAI,CACvD,0DAA0D,EAC1D,uBAAA,IAAI,uCAAS,CAAC,qBAAqB,CACpC,CAAC;IAEF,8CAA8C;IAC9C,KAAK,MAAM,YAAY,IAAI,4BAA4B,EAAE,CAAC;QACxD,MAAM,YAAY,CAAC,WAAW,EAAE,CAAC;IACnC,CAAC;AACH,CAAC,2GAYuB,OAAwB;IAC9C,kCAAkC;IAClC,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;QAChE,iEAAiE;QACjE,OAAO,YAAY,OAAO,CAAC,OAAO,EAAE,CAAC;IACvC,CAAC;IAED,qCAAqC;IACrC,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;QAChE,oEAAoE;QACpE,OAAO,YAAY,OAAO,CAAC,OAAO,EAAE,CAAC;IACvC,CAAC;IAED,yDAAyD;IACzD,OAAO,OAAO,CAAC,OAAO,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,KAAK;IACH,GAAG,CAAC,+DAA+D,CAAC,CAAC;IAErE,yEAAyE;IACzE,kEAAkE;IAClE,MAAM,uBAAA,IAAI,yCAAW,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;AAC1E,CAAC","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"]}
|
|
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 { 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// 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 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 '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 relevant feature flags for chain prefixes\n (state) => ({\n remoteFeatureFlags: Object.fromEntries(\n Object.entries(CHAIN_PREFIX_FEATURE_FLAGS).map(([_prefix, flag]) => [\n flag,\n state.remoteFeatureFlags[flag],\n ]),\n ),\n }),\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 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 for (const address of this.#getSupportedMultichainCaip10Addresses(\n selectedAccounts,\n )) {\n // Subscribe to the new selected account in CAIP-10 format\n await this.subscribe({ address });\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 for (const address of this.#getSupportedMultichainCaip10Addresses(\n selectedAccounts,\n )) {\n await this.subscribe({ address });\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 an InternalAccount address to CAIP-10 format, using the first\n * supported chain prefix matching the account's scopes\n *\n * @param account - The internal account to convert\n * @param account.address - The raw address of the account\n * @param account.scopes - The scopes of the account (used to determine chain type)\n * @returns The CAIP-10 formatted address (e.g. `eip155:0:address`, meaning\n * all chains of that namespace), or `undefined` if none of the account's\n * scopes are supported\n */\n #convertToCaip10Address(account: {\n address: InternalAccount['address'];\n scopes: InternalAccount['scopes'];\n }): string | undefined {\n const chainPrefix = this.#getSupportedChainPrefixes().find((prefix) =>\n account.scopes.some((scope) => scope.startsWith(`${prefix}:`)),\n );\n return chainPrefix ? `${chainPrefix}:0:${account.address}` : undefined;\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 * Get all supported multichain CAIP-10 addresses for a given account\n * This is used to determine which accounts to subscribe to for activity updates\n *\n * @param accounts - The internal accounts to get supported addresses for\n * @returns An array of CAIP-10 formatted addresses that are supported for subscription\n */\n #getSupportedMultichainCaip10Addresses(\n accounts: InternalAccount[],\n ): string[] {\n return accounts\n .map((account) => this.#convertToCaip10Address(account))\n .filter((address): address is string => address !== undefined);\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,9 +4,10 @@
|
|
|
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 {
|
|
7
|
+
import type { AccountTreeControllerSelectedAccountGroupChangeEvent, AccountTreeControllerGetAccountsFromSelectedAccountGroupAction } from "@metamask/account-tree-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";
|
|
10
11
|
import type { Transaction, BalanceUpdate } from "../types.cjs";
|
|
11
12
|
import type { AccountActivityServiceMethodActions } from "./AccountActivityService-method-action-types.cjs";
|
|
12
13
|
import type { BackendWebSocketServiceConnectionStateChangedEvent } from "./BackendWebSocketService.cjs";
|
|
@@ -39,9 +40,9 @@ export type AccountActivityServiceOptions = {
|
|
|
39
40
|
traceFn?: TraceCallback;
|
|
40
41
|
};
|
|
41
42
|
export type AccountActivityServiceActions = AccountActivityServiceMethodActions;
|
|
42
|
-
export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS: readonly ["
|
|
43
|
-
export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS: readonly ["
|
|
44
|
-
export type AllowedActions =
|
|
43
|
+
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"];
|
|
44
|
+
export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS: readonly ["AccountTreeController:selectedAccountGroupChange", "BackendWebSocketService:connectionStateChanged", "RemoteFeatureFlagController:stateChange"];
|
|
45
|
+
export type AllowedActions = AccountTreeControllerGetAccountsFromSelectedAccountGroupAction | BackendWebSocketServiceMethodActions | RemoteFeatureFlagControllerGetStateAction;
|
|
45
46
|
export type AccountActivityServiceTransactionUpdatedEvent = {
|
|
46
47
|
type: `AccountActivityService:transactionUpdated`;
|
|
47
48
|
payload: [Transaction];
|
|
@@ -73,7 +74,7 @@ export type AccountActivityServiceStatusChangedEvent = {
|
|
|
73
74
|
];
|
|
74
75
|
};
|
|
75
76
|
export type AccountActivityServiceEvents = AccountActivityServiceTransactionUpdatedEvent | AccountActivityServiceBalanceUpdatedEvent | AccountActivityServiceSubscriptionErrorEvent | AccountActivityServiceStatusChangedEvent;
|
|
76
|
-
export type AllowedEvents =
|
|
77
|
+
export type AllowedEvents = AccountTreeControllerSelectedAccountGroupChangeEvent | BackendWebSocketServiceConnectionStateChangedEvent | RemoteFeatureFlagControllerStateChangeEvent;
|
|
77
78
|
export type AccountActivityServiceMessenger = Messenger<typeof SERVICE_NAME, AccountActivityServiceActions | AllowedActions, AccountActivityServiceEvents | AllowedEvents>;
|
|
78
79
|
/**
|
|
79
80
|
* High-performance service for real-time account activity monitoring using optimized
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountActivityService.d.cts","sourceRoot":"","sources":["../../src/ws/AccountActivityService.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"AccountActivityService.d.cts","sourceRoot":"","sources":["../../src/ws/AccountActivityService.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,9 +4,10 @@
|
|
|
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 {
|
|
7
|
+
import type { AccountTreeControllerSelectedAccountGroupChangeEvent, AccountTreeControllerGetAccountsFromSelectedAccountGroupAction } from "@metamask/account-tree-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";
|
|
10
11
|
import type { Transaction, BalanceUpdate } from "../types.mjs";
|
|
11
12
|
import type { AccountActivityServiceMethodActions } from "./AccountActivityService-method-action-types.mjs";
|
|
12
13
|
import type { BackendWebSocketServiceConnectionStateChangedEvent } from "./BackendWebSocketService.mjs";
|
|
@@ -39,9 +40,9 @@ export type AccountActivityServiceOptions = {
|
|
|
39
40
|
traceFn?: TraceCallback;
|
|
40
41
|
};
|
|
41
42
|
export type AccountActivityServiceActions = AccountActivityServiceMethodActions;
|
|
42
|
-
export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS: readonly ["
|
|
43
|
-
export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS: readonly ["
|
|
44
|
-
export type AllowedActions =
|
|
43
|
+
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"];
|
|
44
|
+
export declare const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS: readonly ["AccountTreeController:selectedAccountGroupChange", "BackendWebSocketService:connectionStateChanged", "RemoteFeatureFlagController:stateChange"];
|
|
45
|
+
export type AllowedActions = AccountTreeControllerGetAccountsFromSelectedAccountGroupAction | BackendWebSocketServiceMethodActions | RemoteFeatureFlagControllerGetStateAction;
|
|
45
46
|
export type AccountActivityServiceTransactionUpdatedEvent = {
|
|
46
47
|
type: `AccountActivityService:transactionUpdated`;
|
|
47
48
|
payload: [Transaction];
|
|
@@ -73,7 +74,7 @@ export type AccountActivityServiceStatusChangedEvent = {
|
|
|
73
74
|
];
|
|
74
75
|
};
|
|
75
76
|
export type AccountActivityServiceEvents = AccountActivityServiceTransactionUpdatedEvent | AccountActivityServiceBalanceUpdatedEvent | AccountActivityServiceSubscriptionErrorEvent | AccountActivityServiceStatusChangedEvent;
|
|
76
|
-
export type AllowedEvents =
|
|
77
|
+
export type AllowedEvents = AccountTreeControllerSelectedAccountGroupChangeEvent | BackendWebSocketServiceConnectionStateChangedEvent | RemoteFeatureFlagControllerStateChangeEvent;
|
|
77
78
|
export type AccountActivityServiceMessenger = Messenger<typeof SERVICE_NAME, AccountActivityServiceActions | AllowedActions, AccountActivityServiceEvents | AllowedEvents>;
|
|
78
79
|
/**
|
|
79
80
|
* High-performance service for real-time account activity monitoring using optimized
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountActivityService.d.mts","sourceRoot":"","sources":["../../src/ws/AccountActivityService.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"AccountActivityService.d.mts","sourceRoot":"","sources":["../../src/ws/AccountActivityService.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,16 +15,27 @@ 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_handleAccountActivityUpdate, _AccountActivityService_handleSelectedAccountChange, _AccountActivityService_handleSystemNotification, _AccountActivityService_handleWebSocketStateChange, _AccountActivityService_subscribeToSelectedAccount, _AccountActivityService_unsubscribeFromAllAccountActivity, _AccountActivityService_convertToCaip10Address, _AccountActivityService_forceReconnection;
|
|
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_getSupportedChainPrefixes, _AccountActivityService_handleFeatureFlagsStateChange, _AccountActivityService_getSupportedMultichainCaip10Addresses, _AccountActivityService_forceReconnection;
|
|
19
|
+
import { isObject } from "@metamask/utils";
|
|
19
20
|
import { projectLogger, createModuleLogger } from "../logger.mjs";
|
|
20
21
|
import { WebSocketState } from "./BackendWebSocketService.mjs";
|
|
21
22
|
const SERVICE_NAME = 'AccountActivityService';
|
|
22
23
|
const log = createModuleLogger(projectLogger, SERVICE_NAME);
|
|
23
24
|
const MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'];
|
|
24
25
|
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
|
+
};
|
|
25
36
|
// Allowed actions that AccountActivityService can call on other controllers
|
|
26
37
|
export const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [
|
|
27
|
-
'
|
|
38
|
+
'AccountTreeController:getAccountsFromSelectedAccountGroup',
|
|
28
39
|
'BackendWebSocketService:connect',
|
|
29
40
|
'BackendWebSocketService:forceReconnection',
|
|
30
41
|
'BackendWebSocketService:subscribe',
|
|
@@ -34,11 +45,13 @@ export const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS = [
|
|
|
34
45
|
'BackendWebSocketService:findSubscriptionsByChannelPrefix',
|
|
35
46
|
'BackendWebSocketService:addChannelCallback',
|
|
36
47
|
'BackendWebSocketService:removeChannelCallback',
|
|
48
|
+
'RemoteFeatureFlagController:getState',
|
|
37
49
|
];
|
|
38
50
|
// Allowed events that AccountActivityService can listen to
|
|
39
51
|
export const ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS = [
|
|
40
|
-
'
|
|
52
|
+
'AccountTreeController:selectedAccountGroupChange',
|
|
41
53
|
'BackendWebSocketService:connectionStateChanged',
|
|
54
|
+
'RemoteFeatureFlagController:stateChange',
|
|
42
55
|
];
|
|
43
56
|
// =============================================================================
|
|
44
57
|
// Main Service Class
|
|
@@ -107,14 +120,27 @@ export class AccountActivityService {
|
|
|
107
120
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
121
|
((_request, fn) => fn?.()), "f");
|
|
109
122
|
__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
|
|
110
|
-
__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('
|
|
123
|
+
__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('AccountTreeController:selectedAccountGroupChange',
|
|
111
124
|
// Promise result intentionally not awaited
|
|
112
125
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
113
|
-
async (
|
|
126
|
+
async () => await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSelectedAccountChange).call(this));
|
|
114
127
|
__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").subscribe('BackendWebSocketService:connectionStateChanged',
|
|
115
128
|
// Promise result intentionally not awaited
|
|
116
129
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
117
130
|
(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 relevant feature flags for chain prefixes
|
|
138
|
+
(state) => ({
|
|
139
|
+
remoteFeatureFlags: Object.fromEntries(Object.entries(CHAIN_PREFIX_FEATURE_FLAGS).map(([_prefix, flag]) => [
|
|
140
|
+
flag,
|
|
141
|
+
state.remoteFeatureFlags[flag],
|
|
142
|
+
])),
|
|
143
|
+
}));
|
|
118
144
|
__classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:addChannelCallback', {
|
|
119
145
|
channelName: `system-notifications.v1.${__classPrivateFieldGet(this, _AccountActivityService_options, "f").subscriptionNamespace}`,
|
|
120
146
|
callback: (notification) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_handleSystemNotification).call(this, notification),
|
|
@@ -227,20 +253,16 @@ _AccountActivityService_messenger = new WeakMap(), _AccountActivityService_optio
|
|
|
227
253
|
}, _AccountActivityService_handleSelectedAccountChange =
|
|
228
254
|
/**
|
|
229
255
|
* Handle selected account change event
|
|
230
|
-
*
|
|
231
|
-
* @param newAccount - The newly selected account
|
|
232
256
|
*/
|
|
233
|
-
async function _AccountActivityService_handleSelectedAccountChange(
|
|
234
|
-
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
257
|
+
async function _AccountActivityService_handleSelectedAccountChange() {
|
|
258
|
+
const selectedAccounts = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('AccountTreeController:getAccountsFromSelectedAccountGroup');
|
|
237
259
|
try {
|
|
238
|
-
// Convert new account to CAIP-10 format
|
|
239
|
-
const newAddress = __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_convertToCaip10Address).call(this, newAccount);
|
|
240
260
|
// First, unsubscribe from all current account activity subscriptions to avoid multiple subscriptions
|
|
241
261
|
await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_unsubscribeFromAllAccountActivity).call(this);
|
|
242
|
-
|
|
243
|
-
|
|
262
|
+
for (const address of __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_getSupportedMultichainCaip10Addresses).call(this, selectedAccounts)) {
|
|
263
|
+
// Subscribe to the new selected account in CAIP-10 format
|
|
264
|
+
await this.subscribe({ address });
|
|
265
|
+
}
|
|
244
266
|
}
|
|
245
267
|
catch (error) {
|
|
246
268
|
log('Account change failed', { error });
|
|
@@ -312,13 +334,10 @@ async function _AccountActivityService_handleWebSocketStateChange(connectionInfo
|
|
|
312
334
|
* Subscribe to the currently selected account only
|
|
313
335
|
*/
|
|
314
336
|
async function _AccountActivityService_subscribeToSelectedAccount() {
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
337
|
+
const selectedAccounts = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('AccountTreeController:getAccountsFromSelectedAccountGroup');
|
|
338
|
+
for (const address of __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_getSupportedMultichainCaip10Addresses).call(this, selectedAccounts)) {
|
|
339
|
+
await this.subscribe({ address });
|
|
318
340
|
}
|
|
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 });
|
|
322
341
|
}, _AccountActivityService_unsubscribeFromAllAccountActivity =
|
|
323
342
|
/**
|
|
324
343
|
* Unsubscribe from all account activity subscriptions for this service
|
|
@@ -331,18 +350,42 @@ async function _AccountActivityService_unsubscribeFromAllAccountActivity() {
|
|
|
331
350
|
await subscription.unsubscribe();
|
|
332
351
|
}
|
|
333
352
|
}, _AccountActivityService_convertToCaip10Address = function _AccountActivityService_convertToCaip10Address(account) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
353
|
+
const chainPrefix = __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_getSupportedChainPrefixes).call(this).find((prefix) => account.scopes.some((scope) => scope.startsWith(`${prefix}:`)));
|
|
354
|
+
return chainPrefix ? `${chainPrefix}:0:${account.address}` : undefined;
|
|
355
|
+
}, _AccountActivityService_getSupportedChainPrefixes = function _AccountActivityService_getSupportedChainPrefixes(remoteFeatureFlags = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('RemoteFeatureFlagController:getState').remoteFeatureFlags) {
|
|
356
|
+
const prefixes = [...ALWAYS_SUPPORTED_CHAIN_PREFIXES];
|
|
357
|
+
for (const [prefix, flagName] of Object.entries(CHAIN_PREFIX_FEATURE_FLAGS)) {
|
|
358
|
+
const flagValue = remoteFeatureFlags[flagName];
|
|
359
|
+
if (isObject(flagValue) &&
|
|
360
|
+
typeof flagValue.stage === 'number' &&
|
|
361
|
+
flagValue.stage >= 1) {
|
|
362
|
+
prefixes.push(prefix);
|
|
363
|
+
}
|
|
338
364
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
365
|
+
return prefixes;
|
|
366
|
+
}, _AccountActivityService_handleFeatureFlagsStateChange =
|
|
367
|
+
/**
|
|
368
|
+
* Handle remote feature flag changes: if the set of enabled chain prefixes
|
|
369
|
+
* changed while connected, resubscribe the selected account so new chains
|
|
370
|
+
* are picked up and disabled ones are dropped.
|
|
371
|
+
*/
|
|
372
|
+
async function _AccountActivityService_handleFeatureFlagsStateChange() {
|
|
373
|
+
try {
|
|
374
|
+
const { state } = __classPrivateFieldGet(this, _AccountActivityService_messenger, "f").call('BackendWebSocketService:getConnectionInfo');
|
|
375
|
+
if (state !== WebSocketState.CONNECTED) {
|
|
376
|
+
// Not connected: the next connection will subscribe with fresh flags
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_unsubscribeFromAllAccountActivity).call(this);
|
|
380
|
+
await __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_subscribeToSelectedAccount).call(this);
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
log('Feature flag change handling failed', { error });
|
|
343
384
|
}
|
|
344
|
-
|
|
345
|
-
return
|
|
385
|
+
}, _AccountActivityService_getSupportedMultichainCaip10Addresses = function _AccountActivityService_getSupportedMultichainCaip10Addresses(accounts) {
|
|
386
|
+
return accounts
|
|
387
|
+
.map((account) => __classPrivateFieldGet(this, _AccountActivityService_instances, "m", _AccountActivityService_convertToCaip10Address).call(this, account))
|
|
388
|
+
.filter((address) => address !== undefined);
|
|
346
389
|
}, _AccountActivityService_forceReconnection =
|
|
347
390
|
/**
|
|
348
391
|
* 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 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"]}
|
|
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 { 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// 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 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 '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 relevant feature flags for chain prefixes\n (state) => ({\n remoteFeatureFlags: Object.fromEntries(\n Object.entries(CHAIN_PREFIX_FEATURE_FLAGS).map(([_prefix, flag]) => [\n flag,\n state.remoteFeatureFlags[flag],\n ]),\n ),\n }),\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 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 for (const address of this.#getSupportedMultichainCaip10Addresses(\n selectedAccounts,\n )) {\n // Subscribe to the new selected account in CAIP-10 format\n await this.subscribe({ address });\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 for (const address of this.#getSupportedMultichainCaip10Addresses(\n selectedAccounts,\n )) {\n await this.subscribe({ address });\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 an InternalAccount address to CAIP-10 format, using the first\n * supported chain prefix matching the account's scopes\n *\n * @param account - The internal account to convert\n * @param account.address - The raw address of the account\n * @param account.scopes - The scopes of the account (used to determine chain type)\n * @returns The CAIP-10 formatted address (e.g. `eip155:0:address`, meaning\n * all chains of that namespace), or `undefined` if none of the account's\n * scopes are supported\n */\n #convertToCaip10Address(account: {\n address: InternalAccount['address'];\n scopes: InternalAccount['scopes'];\n }): string | undefined {\n const chainPrefix = this.#getSupportedChainPrefixes().find((prefix) =>\n account.scopes.some((scope) => scope.startsWith(`${prefix}:`)),\n );\n return chainPrefix ? `${chainPrefix}:0:${account.address}` : undefined;\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 * Get all supported multichain CAIP-10 addresses for a given account\n * This is used to determine which accounts to subscribe to for activity updates\n *\n * @param accounts - The internal accounts to get supported addresses for\n * @returns An array of CAIP-10 formatted addresses that are supported for subscription\n */\n #getSupportedMultichainCaip10Addresses(\n accounts: InternalAccount[],\n ): string[] {\n return accounts\n .map((account) => this.#convertToCaip10Address(account))\n .filter((address): address is string => address !== undefined);\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-
|
|
3
|
+
"version": "6.5.0-preview-0f07793cb",
|
|
4
4
|
"description": "Core backend services for MetaMask",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Ethereum",
|
|
@@ -55,11 +55,12 @@
|
|
|
55
55
|
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@metamask/
|
|
58
|
+
"@metamask/account-tree-controller": "^7.5.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",
|
|
63
64
|
"@metamask/utils": "^11.11.0",
|
|
64
65
|
"@tanstack/query-core": "^5.62.16",
|
|
65
66
|
"async-mutex": "^0.5.0",
|