{"version":3,"file":"AccountActivityService.cjs","sourceRoot":"","sources":["../src/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 { RestrictedMessenger } from '@metamask/base-controller';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\n\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';\nimport { projectLogger, createModuleLogger } from './logger';\nimport type {\n  Transaction,\n  AccountActivityMessage,\n  BalanceUpdate,\n} from './types';\n\n// =============================================================================\n// Utility Functions\n// =============================================================================\n\n/**\n * Fetches supported networks from the v2 API endpoint.\n * Returns chain IDs already in CAIP-2 format.\n *\n * Note: This directly calls the Account API v2 endpoint. In the future, this should\n * be moved to a dedicated data layer service for better separation of concerns.\n *\n * @returns Array of supported chain IDs in CAIP-2 format (e.g., \"eip155:1\")\n */\nasync function fetchSupportedChainsInCaipFormat(): Promise<string[]> {\n  const url = 'https://accounts.api.cx.metamask.io/v2/supportedNetworks';\n  const response = await fetch(url);\n\n  if (!response.ok) {\n    throw new Error(\n      `Failed to fetch supported networks: ${response.status} ${response.statusText}`,\n    );\n  }\n\n  const data: {\n    fullSupport: string[];\n    partialSupport: { balances: string[] };\n  } = await response.json();\n\n  // v2 endpoint already returns data in CAIP-2 format\n  return data.fullSupport;\n}\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};\n\nconst SERVICE_NAME = 'AccountActivityService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'] as const;\n\n// Default supported chains used as fallback when API is unavailable\n// This list should match the expected chains from the accounts API v2/supportedNetworks endpoint\nconst DEFAULT_SUPPORTED_CHAINS = [\n  'eip155:1', // Ethereum Mainnet\n  'eip155:137', // Polygon\n  'eip155:56', // BSC\n  'eip155:59144', // Linea\n  'eip155:8453', // Base\n  'eip155:10', // Optimism\n  'eip155:42161', // Arbitrum One\n  'eip155:534352', // Scroll\n  'eip155:1329', // Sei\n];\nconst SUBSCRIPTION_NAMESPACE = 'account-activity.v1';\n\n// Cache TTL for supported chains (5 hours in milliseconds)\nconst SUPPORTED_CHAINS_CACHE_TTL = 5 * 60 * 60 * 1000;\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};\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:disconnect',\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 AccountActivityServiceAllowedActions =\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    },\n  ];\n};\n\nexport type AccountActivityServiceEvents =\n  | AccountActivityServiceTransactionUpdatedEvent\n  | AccountActivityServiceBalanceUpdatedEvent\n  | AccountActivityServiceSubscriptionErrorEvent\n  | AccountActivityServiceStatusChangedEvent;\n\nexport type AccountActivityServiceAllowedEvents =\n  | AccountsControllerSelectedAccountChangeEvent\n  | BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type AccountActivityServiceMessenger = RestrictedMessenger<\n  typeof SERVICE_NAME,\n  AccountActivityServiceActions | AccountActivityServiceAllowedActions,\n  AccountActivityServiceEvents | AccountActivityServiceAllowedEvents,\n  AccountActivityServiceAllowedActions['type'],\n  AccountActivityServiceAllowedEvents['type']\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<AccountActivityServiceOptions>;\n\n  #supportedChains: string[] | null = null;\n\n  #supportedChainsExpiresAt: number = 0;\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    this.#messenger.registerMethodActionHandlers(\n      this,\n      MESSENGER_EXPOSED_METHODS,\n    );\n    this.#messenger.subscribe(\n      'AccountsController:selectedAccountChange',\n      async (account: InternalAccount) =>\n        await this.#handleSelectedAccountChange(account),\n    );\n    this.#messenger.subscribe(\n      'BackendWebSocketService:connectionStateChanged',\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(\n          notification.data as SystemNotificationData,\n        ),\n    });\n  }\n\n  // =============================================================================\n  // Public Methods - Chain Management\n  // =============================================================================\n\n  /**\n   * Fetch supported chains from API with fallback to hardcoded list.\n   * Uses expiry-based caching with TTL to prevent stale data.\n   *\n   * @returns Array of supported chain IDs in CAIP-2 format\n   */\n  async getSupportedChains(): Promise<string[]> {\n    // Return cached result if available and not expired\n    if (\n      this.#supportedChains !== null &&\n      Date.now() < this.#supportedChainsExpiresAt\n    ) {\n      return this.#supportedChains;\n    }\n\n    try {\n      // Try to fetch from API\n      this.#supportedChains = await fetchSupportedChainsInCaipFormat();\n    } catch {\n      // Fallback to hardcoded list and cache it with timestamp\n      this.#supportedChains = Array.from(DEFAULT_SUPPORTED_CHAINS);\n    }\n\n    this.#supportedChainsExpiresAt = Date.now() + SUPPORTED_CHAINS_CACHE_TTL;\n\n    return this.#supportedChains;\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        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: \"0x123\",\n   *   tx: { hash: \"0x...\", chain: \"eip155:1\", status: \"completed\", ... },\n   *   updates: [{\n   *     asset: { fungible: true, type: \"eip155:1/erc20:0x...\", unit: \"USDT\" },\n   *     postBalance: { amount: \"1254.75\" },\n   *     transfers: [{ from: \"0x...\", to: \"0x...\", amount: \"500.00\" }]\n   *   }]\n   * }\n   * Output: Transaction and balance updates published separately\n   */\n  #handleAccountActivityUpdate(payload: AccountActivityMessage): void {\n    const { address, tx, updates } = payload;\n\n    log('Handling account activity update', {\n      address,\n      updateCount: updates.length,\n    });\n\n    // Process transaction update\n    this.#messenger.publish(`AccountActivityService:transactionUpdated`, tx);\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   * 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 data - System notification data containing chain status updates\n   */\n  #handleSystemNotification(data: SystemNotificationData): void {\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    // Publish status change directly (delta update)\n    this.#messenger.publish(`AccountActivityService:statusChanged`, {\n      chainIds: data.chainIds,\n      status: data.status,\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    const supportedChains = await this.getSupportedChains();\n\n    if (state === WebSocketState.CONNECTED) {\n      // WebSocket connected - resubscribe and set all chains as up\n      await this.#subscribeToSelectedAccount();\n\n      // Publish initial status - all supported chains are up when WebSocket connects\n      this.#messenger.publish(`AccountActivityService:statusChanged`, {\n        chainIds: supportedChains,\n        status: 'up',\n      });\n\n      log('WebSocket connected - Published all chains as up', {\n        count: supportedChains.length,\n        chains: supportedChains,\n      });\n    } else if (\n      state === WebSocketState.DISCONNECTED ||\n      state === WebSocketState.ERROR\n    ) {\n      this.#messenger.publish(`AccountActivityService:statusChanged`, {\n        chainIds: supportedChains,\n        status: 'down',\n      });\n\n      log('WebSocket error/disconnection - Published all chains as down', {\n        count: supportedChains.length,\n        chains: supportedChains,\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 || !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    try {\n      log('Forcing WebSocket reconnection to clean up subscription state');\n\n      // All subscriptions will be cleaned up automatically on WebSocket disconnect\n\n      await this.#messenger.call('BackendWebSocketService:disconnect');\n      await this.#messenger.call('BackendWebSocketService:connect');\n    } catch (error) {\n      log('Failed to force WebSocket reconnection', { error });\n    }\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"]}