{"version":3,"file":"heartbeat-monitor.mjs","names":[],"sources":["../../../src/services/heartbeat-monitor.ts"],"sourcesContent":["/**\n * Heartbeat Position Monitor — periodic checks on open positions.\n *\n * Inspired by ZeroClaw's heartbeat system.\n *\n * Periodically checks:\n * 1. Token positions for large price moves (configurable threshold)\n * 2. Portfolio value changes exceeding alert thresholds\n * 3. Stale/unknown tokens that appeared in the wallet\n *\n * Works with the tx-ledger to know which positions the agent opened,\n * and the portfolio-snapshot service to get current values.\n *\n * Fires alert callbacks when thresholds are breached. The caller\n * (index.ts) wires these to channel notifications.\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\nexport interface HeartbeatConfig {\n  /** How often to run checks in ms. Default: 300_000 (5 minutes). */\n  intervalMs?: number;\n  /** Price drop % that triggers an alert. Default: 10. */\n  priceDropAlertPercent?: number;\n  /** Price gain % that triggers an alert. Default: 20. */\n  priceGainAlertPercent?: number;\n  /** Portfolio value drop in USD that triggers an alert. Default: 100. */\n  portfolioDropAlertUsd?: number;\n  /** Whether the monitor is enabled. Default: true. */\n  enabled?: boolean;\n}\n\nexport interface PositionSnapshot {\n  symbol: string;\n  address: string;\n  chain: string;\n  priceUsd: number;\n  valueUsd: number;\n  balanceHuman: number;\n  timestamp: number;\n}\n\nexport interface HeartbeatAlert {\n  type: 'price_drop' | 'price_gain' | 'portfolio_drop' | 'new_token' | 'position_gone'\n    | 'health_factor_warning' | 'health_factor_critical';\n  severity: 'info' | 'warning' | 'critical';\n  symbol: string;\n  chain: string;\n  message: string;\n  data: Record<string, unknown>;\n  timestamp: number;\n}\n\nexport type AlertCallback = (alert: HeartbeatAlert) => void | Promise<void>;\n\n// ─── Heartbeat Monitor ──────────────────────────────────────────────────\n\nclass HeartbeatMonitor {\n  private config: Required<HeartbeatConfig>;\n  private timer: ReturnType<typeof setInterval> | null = null;\n  private running = false;\n  private lastPositions = new Map<string, PositionSnapshot>();\n  private lastPortfolioValueUsd = 0;\n  private lastCheckMs = 0;\n  private alerts: HeartbeatAlert[] = [];\n  private callbacks: AlertCallback[] = [];\n  private checkCount = 0;\n  private tickInProgress = false;\n\n  private static readonly MAX_ALERTS = 500;\n  private static readonly ALERT_KEY_SEP = ':';\n\n  constructor(config: HeartbeatConfig = {}) {\n    this.config = {\n      intervalMs: config.intervalMs ?? 300_000,\n      priceDropAlertPercent: config.priceDropAlertPercent ?? 10,\n      priceGainAlertPercent: config.priceGainAlertPercent ?? 20,\n      portfolioDropAlertUsd: config.portfolioDropAlertUsd ?? 100,\n      enabled: config.enabled ?? true,\n    };\n  }\n\n  /**\n   * Register a callback that fires when an alert is generated.\n   */\n  onAlert(cb: AlertCallback): void {\n    this.callbacks.push(cb);\n  }\n\n  /**\n   * Start the periodic heartbeat.\n   */\n  start(): void {\n    if (!this.config.enabled || this.running) return;\n\n    this.running = true;\n    this.timer = setInterval(() => {\n      this.tick().catch(() => {\n        // Best effort — don't let a failed tick stop the monitor\n      });\n    }, this.config.intervalMs);\n\n    // Don't prevent Node from exiting\n    if (this.timer && typeof this.timer === 'object' && 'unref' in this.timer) {\n      this.timer.unref();\n    }\n  }\n\n  /**\n   * Stop the periodic heartbeat.\n   */\n  stop(): void {\n    this.running = false;\n    if (this.timer) {\n      clearInterval(this.timer);\n      this.timer = null;\n    }\n  }\n\n  /**\n   * Run a single check cycle. Called automatically by the timer,\n   * but can also be called manually for testing.\n   */\n  async tick(): Promise<HeartbeatAlert[]> {\n    // Guard against overlapping ticks (slow RPC, slow portfolio fetch)\n    if (this.tickInProgress) return [];\n    this.tickInProgress = true;\n\n    this.checkCount++;\n    this.lastCheckMs = Date.now();\n    const newAlerts: HeartbeatAlert[] = [];\n\n    try {\n      try {\n        // Dynamically import to avoid circular deps and allow mocking\n        const { getPortfolioService } = await import('./portfolio-snapshot.js');\n        const { getWalletState } = await import('./walletconnect-service.js');\n\n        const wallet = getWalletState();\n        if (!wallet.connected || !wallet.address) return newAlerts;\n\n        const portfolioSvc = getPortfolioService();\n        const snapshot = await portfolioSvc.getSnapshot(wallet.address);\n\n        // ── Price change alerts ──────────────────────────────────────\n        for (const token of snapshot.tokens) {\n          const key = `${token.symbol}${HeartbeatMonitor.ALERT_KEY_SEP}${token.chain}`;\n          const prev = this.lastPositions.get(key);\n\n          if (prev && prev.priceUsd > 0 && token.priceUsd > 0) {\n            const changePct = ((token.priceUsd - prev.priceUsd) / prev.priceUsd) * 100;\n\n            if (changePct <= -this.config.priceDropAlertPercent) {\n              const alert: HeartbeatAlert = {\n                type: 'price_drop',\n                severity: changePct <= -25 ? 'critical' : 'warning',\n                symbol: token.symbol,\n                chain: token.chain,\n                message: `${token.symbol} dropped ${Math.abs(changePct).toFixed(1)}% ` +\n                  `($${prev.priceUsd.toFixed(4)} → $${token.priceUsd.toFixed(4)}) on ${token.chain}. ` +\n                  `Position value: $${token.valueUsd.toFixed(2)}`,\n                data: {\n                  previousPrice: prev.priceUsd,\n                  currentPrice: token.priceUsd,\n                  changePct: Math.round(changePct * 100) / 100,\n                  valueUsd: token.valueUsd,\n                  balance: token.balanceHuman,\n                },\n                timestamp: Date.now(),\n              };\n              newAlerts.push(alert);\n            } else if (changePct >= this.config.priceGainAlertPercent) {\n              const alert: HeartbeatAlert = {\n                type: 'price_gain',\n                severity: 'info',\n                symbol: token.symbol,\n                chain: token.chain,\n                message: `${token.symbol} gained ${changePct.toFixed(1)}% ` +\n                  `($${prev.priceUsd.toFixed(4)} → $${token.priceUsd.toFixed(4)}) on ${token.chain}. ` +\n                  `Position value: $${token.valueUsd.toFixed(2)}`,\n                data: {\n                  previousPrice: prev.priceUsd,\n                  currentPrice: token.priceUsd,\n                  changePct: Math.round(changePct * 100) / 100,\n                  valueUsd: token.valueUsd,\n                  balance: token.balanceHuman,\n                },\n                timestamp: Date.now(),\n              };\n              newAlerts.push(alert);\n            }\n          }\n\n          // Detect new tokens that weren't in the last snapshot\n          if (!prev && this.checkCount > 1) {\n            newAlerts.push({\n              type: 'new_token',\n              severity: 'info',\n              symbol: token.symbol,\n              chain: token.chain,\n              message: `New token detected: ${token.symbol} on ${token.chain} ` +\n                `(${token.balanceHuman.toFixed(4)} @ $${token.priceUsd.toFixed(4)}, ` +\n                `value: $${token.valueUsd.toFixed(2)})`,\n              data: {\n                address: token.address,\n                balance: token.balanceHuman,\n                priceUsd: token.priceUsd,\n                valueUsd: token.valueUsd,\n              },\n              timestamp: Date.now(),\n            });\n          }\n\n          // Update tracked position\n          this.lastPositions.set(key, {\n            symbol: token.symbol,\n            address: token.address,\n            chain: token.chain,\n            priceUsd: token.priceUsd,\n            valueUsd: token.valueUsd,\n            balanceHuman: token.balanceHuman,\n            timestamp: Date.now(),\n          });\n        }\n\n        // ── Detect positions that disappeared ──────────────────────────\n        if (this.checkCount > 1) {\n          const currentKeys = new Set(\n            snapshot.tokens.map(t => `${t.symbol}${HeartbeatMonitor.ALERT_KEY_SEP}${t.chain}`),\n          );\n          for (const [key, prev] of this.lastPositions) {\n            if (!currentKeys.has(key) && prev.valueUsd > 1) {\n              newAlerts.push({\n                type: 'position_gone',\n                severity: 'warning',\n                symbol: prev.symbol,\n                chain: prev.chain,\n                message: `Position disappeared: ${prev.symbol} on ${prev.chain} ` +\n                  `(was ${prev.balanceHuman.toFixed(4)} @ $${prev.priceUsd.toFixed(4)}, ` +\n                  `value was $${prev.valueUsd.toFixed(2)})`,\n                data: {\n                  lastBalance: prev.balanceHuman,\n                  lastPrice: prev.priceUsd,\n                  lastValue: prev.valueUsd,\n                },\n                timestamp: Date.now(),\n              });\n              this.lastPositions.delete(key);\n            }\n          }\n        }\n\n        // ── Portfolio value drop alert ────────────────────────────────\n        if (this.lastPortfolioValueUsd > 0) {\n          const dropUsd = this.lastPortfolioValueUsd - snapshot.totalValueUsd;\n          if (dropUsd >= this.config.portfolioDropAlertUsd) {\n            const dropPct = (dropUsd / this.lastPortfolioValueUsd) * 100;\n            newAlerts.push({\n              type: 'portfolio_drop',\n              severity: dropPct > 20 ? 'critical' : 'warning',\n              symbol: 'PORTFOLIO',\n              chain: 'all',\n              message: `Portfolio dropped $${dropUsd.toFixed(2)} (${dropPct.toFixed(1)}%) ` +\n                `since last check ($${this.lastPortfolioValueUsd.toFixed(2)} → $${snapshot.totalValueUsd.toFixed(2)})`,\n              data: {\n                previousValueUsd: this.lastPortfolioValueUsd,\n                currentValueUsd: snapshot.totalValueUsd,\n                dropUsd,\n                dropPct: Math.round(dropPct * 100) / 100,\n              },\n              timestamp: Date.now(),\n            });\n          }\n        }\n        this.lastPortfolioValueUsd = snapshot.totalValueUsd;\n\n        // ── Lending health factor check ───────────────────────────────\n        try {\n          const { getLendingService } = await import('./lending-service.js');\n          const { requirePublicClient } = await import('./walletconnect-service.js');\n\n          const lendingSvc = getLendingService();\n          const publicClient = requirePublicClient();\n          const accountData = await lendingSvc.getUserAccountData(\n            wallet.address as `0x${string}`, publicClient,\n          );\n\n          // Only alert if user has active debt (healthFactor !== Infinity)\n          if (accountData.healthFactor !== Infinity && accountData.totalDebtUsd > 0) {\n            if (accountData.healthFactor < 1.1) {\n              newAlerts.push({\n                type: 'health_factor_critical',\n                severity: 'critical',\n                symbol: 'AAVE_POSITION',\n                chain: 'base',\n                message: `DANGER: Aave health factor is ${accountData.healthFactor.toFixed(4)} — ` +\n                  `immediate liquidation risk! Collateral: $${accountData.totalCollateralUsd.toFixed(2)}, ` +\n                  `Debt: $${accountData.totalDebtUsd.toFixed(2)}. Add collateral or repay debt immediately.`,\n                data: {\n                  healthFactor: accountData.healthFactor,\n                  totalCollateralUsd: accountData.totalCollateralUsd,\n                  totalDebtUsd: accountData.totalDebtUsd,\n                  availableBorrowsUsd: accountData.availableBorrowsUsd,\n                },\n                timestamp: Date.now(),\n              });\n            } else if (accountData.healthFactor < 1.5) {\n              newAlerts.push({\n                type: 'health_factor_warning',\n                severity: 'warning',\n                symbol: 'AAVE_POSITION',\n                chain: 'base',\n                message: `WARNING: Aave health factor is ${accountData.healthFactor.toFixed(4)} — ` +\n                  `approaching liquidation zone. Collateral: $${accountData.totalCollateralUsd.toFixed(2)}, ` +\n                  `Debt: $${accountData.totalDebtUsd.toFixed(2)}. Consider adding collateral or repaying debt.`,\n                data: {\n                  healthFactor: accountData.healthFactor,\n                  totalCollateralUsd: accountData.totalCollateralUsd,\n                  totalDebtUsd: accountData.totalDebtUsd,\n                  availableBorrowsUsd: accountData.availableBorrowsUsd,\n                },\n                timestamp: Date.now(),\n              });\n            }\n          }\n        } catch {\n          // Lending check failed — skip silently (wallet may not have a public client)\n        }\n\n      } catch {\n        // Failed to get portfolio — skip this tick silently\n      }\n\n      // Store and fire alerts\n      for (const alert of newAlerts) {\n        this.alerts.push(alert);\n        for (const cb of this.callbacks) {\n          try {\n            await cb(alert);\n          } catch {\n            // Best effort\n          }\n        }\n      }\n\n      // Trim alert history\n      if (this.alerts.length > HeartbeatMonitor.MAX_ALERTS) {\n        this.alerts = this.alerts.slice(-HeartbeatMonitor.MAX_ALERTS);\n      }\n\n      return newAlerts;\n    } finally {\n      this.tickInProgress = false;\n    }\n  }\n\n  /**\n   * Get recent alerts.\n   */\n  getAlerts(limit = 50): HeartbeatAlert[] {\n    return this.alerts.slice(-limit);\n  }\n\n  /**\n   * Get current tracked positions.\n   */\n  getPositions(): PositionSnapshot[] {\n    return Array.from(this.lastPositions.values());\n  }\n\n  /**\n   * Get monitor status for diagnostics.\n   */\n  getStatus(): {\n    running: boolean;\n    enabled: boolean;\n    checkCount: number;\n    lastCheckMs: number;\n    trackedPositions: number;\n    totalAlerts: number;\n    intervalMs: number;\n    config: Required<HeartbeatConfig>;\n  } {\n    return {\n      running: this.running,\n      enabled: this.config.enabled,\n      checkCount: this.checkCount,\n      lastCheckMs: this.lastCheckMs,\n      trackedPositions: this.lastPositions.size,\n      totalAlerts: this.alerts.length,\n      intervalMs: this.config.intervalMs,\n      config: this.config,\n    };\n  }\n\n  /**\n   * Manually set position data (useful for testing or seeding from ledger).\n   */\n  seedPosition(pos: PositionSnapshot): void {\n    const key = `${pos.symbol}${HeartbeatMonitor.ALERT_KEY_SEP}${pos.chain}`;\n    this.lastPositions.set(key, pos);\n  }\n\n  /**\n   * Manually set the last portfolio value (useful for testing).\n   */\n  seedPortfolioValue(valueUsd: number): void {\n    this.lastPortfolioValueUsd = valueUsd;\n  }\n}\n\n// ─── Singleton ───────────────────────────────────────────────────────────\n\nlet _instance: HeartbeatMonitor | null = null;\n\nexport function getHeartbeatMonitor(config?: HeartbeatConfig): HeartbeatMonitor {\n  if (!_instance) {\n    _instance = new HeartbeatMonitor(config);\n  }\n  return _instance;\n}\n\nexport function resetHeartbeatMonitor(): void {\n  if (_instance) {\n    _instance.stop();\n  }\n  _instance = null;\n}\n"],"mappings":"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"}