{"version":3,"file":"safety-service.mjs","names":[],"sources":["../../../src/services/safety-service.ts"],"sourcesContent":["/**\n * Safety Service — pre-flight checks before any on-chain write operation.\n *\n * Wired into defi-swap, clawnch-launch, and clawnch-fees before execution.\n * Uses herd-intelligence for token audits and swap validation.\n * Uses defi-balance logic for balance sufficiency checks.\n */\n\nimport { getWalletState, requirePublicClient, isBankrMode } from './walletconnect-service.js';\nimport { getPrice, getEthPrice } from './price-service.js';\nimport { getUserMode } from './mode-service.js';\nimport { getCredentialVault } from './credential-vault.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\nexport interface SafetyCheckResult {\n  safe: boolean;\n  warnings: string[];\n  blockers: string[];\n  details: Record<string, unknown>;\n}\n\n// ─── Balance Check ───────────────────────────────────────────────────────\n\n/**\n * Check if the connected wallet has enough ETH for a given amount + gas.\n */\nexport async function checkBalance(opts: {\n  requiredEth?: number;\n  requiredTokenAddress?: string;\n  requiredTokenAmount?: string;\n}): Promise<SafetyCheckResult> {\n  const warnings: string[] = [];\n  const blockers: string[] = [];\n  const details: Record<string, unknown> = {};\n\n  const state = getWalletState();\n  if (!state.connected || !state.address) {\n    return { safe: false, warnings, blockers: ['No wallet connected'], details };\n  }\n\n  try {\n    const publicClient = requirePublicClient();\n    const { formatEther } = await import('viem');\n\n    const balance = await publicClient.getBalance({ address: state.address });\n    const ethBalance = parseFloat(formatEther(balance));\n    details.ethBalance = ethBalance;\n\n    const gasBuffer = 0.005; // ~0.005 ETH for gas on Base\n    const requiredEth = (opts.requiredEth ?? 0) + gasBuffer;\n\n    if (ethBalance < requiredEth) {\n      blockers.push(\n        `Insufficient ETH. Have ${ethBalance.toFixed(4)} ETH, need ~${requiredEth.toFixed(4)} ETH ` +\n        `(${opts.requiredEth?.toFixed(4) ?? '0'} + ${gasBuffer} gas buffer).`\n      );\n    } else if (ethBalance < requiredEth * 1.2) {\n      warnings.push(\n        `Low ETH balance (${ethBalance.toFixed(4)}). Transaction may succeed but leaves little for future gas.`\n      );\n    }\n\n    // ERC-20 balance check when requiredTokenAddress is provided\n    if (opts.requiredTokenAddress && opts.requiredTokenAmount) {\n      try {\n        const tokenAddr = opts.requiredTokenAddress as `0x${string}`;\n        const erc20Abi = [\n          { name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'uint256' }] },\n          { name: 'decimals', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'uint8' }] },\n          { name: 'symbol', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'string' }] },\n        ] as const;\n\n        const [rawBalance, decimals, symbol] = await Promise.all([\n          publicClient.readContract({ address: tokenAddr, abi: erc20Abi, functionName: 'balanceOf', args: [state.address as `0x${string}`] }),\n          publicClient.readContract({ address: tokenAddr, abi: erc20Abi, functionName: 'decimals' }).catch(() => 18),\n          publicClient.readContract({ address: tokenAddr, abi: erc20Abi, functionName: 'symbol' }).catch(() => 'TOKEN'),\n        ]);\n\n        const { formatUnits } = await import('viem');\n        const tokenBalance = parseFloat(formatUnits(rawBalance as bigint, Number(decimals)));\n        const requiredAmount = parseFloat(opts.requiredTokenAmount);\n        details.tokenBalance = tokenBalance;\n        details.tokenSymbol = symbol;\n\n        if (tokenBalance < requiredAmount) {\n          blockers.push(\n            `Insufficient ${symbol}. Have ${tokenBalance.toFixed(4)}, need ${requiredAmount.toFixed(4)}.`\n          );\n        }\n      } catch (tokenErr) {\n        warnings.push(`Token balance check failed: ${tokenErr instanceof Error ? tokenErr.message : String(tokenErr)}`);\n      }\n    }\n  } catch (err) {\n    warnings.push(`Balance check failed: ${err instanceof Error ? err.message : String(err)}`);\n  }\n\n  return { safe: blockers.length === 0, warnings, blockers, details };\n}\n\n// ─── Token Audit ─────────────────────────────────────────────────────────\n\n/**\n * Audit a token for safety using herd-intelligence (if available).\n * Non-blocking — returns warnings but doesn't block if the service is unavailable.\n */\nexport async function auditToken(tokenAddress: string): Promise<SafetyCheckResult> {\n  const warnings: string[] = [];\n  const blockers: string[] = [];\n  const details: Record<string, unknown> = {};\n\n  const accessToken = getCredentialVault().getSecret('intel.herd.accessToken', 'safety-service');\n  if (!accessToken) {\n    warnings.push('Herd Intelligence not configured (no HERD_ACCESS_TOKEN). Token audit skipped.');\n    return { safe: true, warnings, blockers, details };\n  }\n\n  try {\n    const { HerdIntelligence } = await import('@clawnch/clawncher-sdk');\n    const herd = new HerdIntelligence({ accessToken });\n\n    const audit = await herd.auditTokenSafety(tokenAddress, { blockchain: 'base' });\n    details.audit = audit;\n\n    // Interpret audit results — structure depends on HerdIntelligence API response\n    const riskLevel = (audit as any)?.riskLevel ?? (audit as any)?.risk_level;\n    const reason = (audit as any)?.reason ?? (audit as any)?.summary ?? '';\n    const isHoneypot = (audit as any)?.isHoneypot ?? (audit as any)?.honeypot ?? false;\n\n    if (riskLevel === 'critical') {\n      blockers.push(\n        `CRITICAL RISK: Token ${tokenAddress} — ${reason || 'Do not interact.'}`\n      );\n    } else if (riskLevel === 'high') {\n      warnings.push(\n        `HIGH RISK: Token ${tokenAddress} — ${reason || 'Exercise extreme caution.'}`\n      );\n    } else if (riskLevel === 'medium') {\n      warnings.push(\n        `MEDIUM RISK: Token ${tokenAddress} — ${reason || 'Proceed with caution.'}`\n      );\n    }\n\n    if (isHoneypot) {\n      blockers.push(`HONEYPOT DETECTED: Token ${tokenAddress} cannot be sold after purchase.`);\n    }\n  } catch (err) {\n    warnings.push(`Token audit unavailable: ${err instanceof Error ? err.message : String(err)}`);\n  }\n\n  return { safe: blockers.length === 0, warnings, blockers, details };\n}\n\n// ─── Swap Validation ─────────────────────────────────────────────────────\n\n/**\n * Validate a swap before execution: balance + token audit + route check.\n * When routing through Bankr, skip local checks — Bankr's Sentinel handles security.\n */\nexport async function validateSwap(opts: {\n  tokenIn: string;\n  tokenOut: string;\n  amountEth: number;\n}): Promise<SafetyCheckResult> {\n  // C5 FIX: Only check authoritative wallet state, not caller-supplied param\n  if (isBankrMode()) {\n    return {\n      safe: true,\n      warnings: ['Bankr Sentinel active — security screening handled server-side'],\n      blockers: [],\n      details: { bankrMode: true },\n    };\n  }\n\n  const allWarnings: string[] = [];\n  const allBlockers: string[] = [];\n  const allDetails: Record<string, unknown> = {};\n\n  // M1: Value cap when both dangermode + autosign are active\n  // This prevents the agent from auto-executing large transactions without ANY human check\n  const AUTOSIGN_DANGER_CAP_ETH = 0.1;\n  try {\n    // Get user mode from connected wallet state (userId may be stored)\n    const walletState = getWalletState();\n    if (walletState.mode === 'private_key') {\n      // In private key mode, check if dangermode is active for any user\n      // Private key mode is inherently autosign — check if amountEth exceeds cap\n      if (opts.amountEth > AUTOSIGN_DANGER_CAP_ETH) {\n        allWarnings.push(\n          `Transaction value (${opts.amountEth} ETH) exceeds auto-sign safety cap of ${AUTOSIGN_DANGER_CAP_ETH} ETH. ` +\n          `In private key mode, consider using WalletConnect (/walletsign) for transactions above this threshold.`\n        );\n      }\n    }\n  } catch {\n    // Non-fatal — mode check shouldn't block swaps\n  }\n\n  // 1. Balance check\n  const isEthIn = opts.tokenIn.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'\n    || opts.tokenIn.toLowerCase() === '0x4200000000000000000000000000000000000006';\n\n  const balanceCheck = await checkBalance({\n    requiredEth: isEthIn ? opts.amountEth : undefined,\n    requiredTokenAddress: isEthIn ? undefined : opts.tokenIn,\n    requiredTokenAmount: isEthIn ? undefined : String(opts.amountEth),\n  });\n  allWarnings.push(...balanceCheck.warnings);\n  allBlockers.push(...balanceCheck.blockers);\n  allDetails.balance = balanceCheck.details;\n\n  // 2. Audit output token (skip well-known stables)\n  const SKIP_AUDIT = new Set([\n    '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',\n    '0x4200000000000000000000000000000000000006', // WETH\n    '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // USDC\n    '0xfde4c96c8593536e31f229ea8f37b2ada2699bb2', // USDT\n    '0x50c5725949a6f0c72e6c4a641f24049a917db0cb', // DAI\n  ]);\n\n  if (!SKIP_AUDIT.has(opts.tokenOut.toLowerCase())) {\n    const tokenAudit = await auditToken(opts.tokenOut);\n    allWarnings.push(...tokenAudit.warnings);\n    allBlockers.push(...tokenAudit.blockers);\n    allDetails.tokenAudit = tokenAudit.details;\n  }\n\n  // 3. Price context\n  try {\n    const [priceIn, priceOut] = await Promise.all([\n      getPrice(opts.tokenIn).catch(() => null),\n      getPrice(opts.tokenOut).catch(() => null),\n    ]);\n    allDetails.prices = {\n      tokenIn: priceIn ? { symbol: priceIn.symbol, priceUsd: priceIn.priceUsd } : null,\n      tokenOut: priceOut ? { symbol: priceOut.symbol, priceUsd: priceOut.priceUsd } : null,\n    };\n  } catch {\n    // Non-fatal\n  }\n\n  return {\n    safe: allBlockers.length === 0,\n    warnings: allWarnings,\n    blockers: allBlockers,\n    details: allDetails,\n  };\n}\n\n// ─── Launch Validation ───────────────────────────────────────────────────\n\n/**\n * Pre-flight check for token launch: balance for gas + dev buy.\n */\nexport async function validateLaunch(opts: {\n  devBuyEth?: number;\n}): Promise<SafetyCheckResult> {\n  const requiredEth = (opts.devBuyEth ?? 0) + 0.01; // gas for deploy tx\n  return checkBalance({ requiredEth });\n}\n\n// ─── Claim Validation ────────────────────────────────────────────────────\n\n/**\n * Pre-flight check for fee claims: just gas check.\n */\nexport async function validateClaim(): Promise<SafetyCheckResult> {\n  return checkBalance({ requiredEth: 0 }); // only gas buffer needed\n}\n"],"mappings":"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"}