{"version":3,"file":"approvals.mjs","names":[],"sources":["../../../src/tools/approvals.ts"],"sourcesContent":["/**\n * Token Approvals Tool — scan, audit, and revoke ERC-20 approvals.\n *\n * Actions:\n *   scan      — Scan all token approvals for the connected wallet\n *   revoke    — Revoke a specific token+spender approval\n *   revoke_all — Revoke all non-zero approvals found by scan\n *\n * Uses Etherscan/Basescan event log API for comprehensive scanning,\n * falls back to AllowanceManager's known-tokens heuristic when no API key.\n */\n\nimport { Type } from '@sinclair/typebox';\nimport { formatUnits } from 'viem';\nimport { stringEnum, jsonResult, errorResult, readStringParam } from '../lib/tool-helpers.js';\nimport {\n  getWalletState,\n  requireWalletClient,\n  requirePublicClient,\n} from '../services/walletconnect-service.js';\nimport { getAllowanceManager, type AllowanceInfo } from '../services/allowance-manager.js';\nimport { getCredentialVault } from '../services/credential-vault.js';\nimport { guardedFetch } from '../services/endpoint-allowlist.js';\nimport { getRpcManager } from '../services/rpc-provider.js';\n\nconst ACTIONS = ['scan', 'revoke', 'revoke_all'] as const;\n\n/** Keccak-256 of Approval(address,address,uint256) */\nconst APPROVAL_EVENT_TOPIC =\n  '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925';\n\nconst ERC20_MINIMAL_ABI = [\n  {\n    inputs: [\n      { name: 'spender', type: 'address' },\n      { name: 'amount', type: 'uint256' },\n    ],\n    name: 'approve',\n    outputs: [{ name: '', type: 'bool' }],\n    stateMutability: 'nonpayable',\n    type: 'function',\n  },\n  {\n    inputs: [\n      { name: 'owner', type: 'address' },\n      { name: 'spender', type: 'address' },\n    ],\n    name: 'allowance',\n    outputs: [{ name: '', type: 'uint256' }],\n    stateMutability: 'view',\n    type: 'function',\n  },\n  {\n    inputs: [],\n    name: 'symbol',\n    outputs: [{ name: '', type: 'string' }],\n    stateMutability: 'view',\n    type: 'function',\n  },\n  {\n    inputs: [],\n    name: 'decimals',\n    outputs: [{ name: '', type: 'uint8' }],\n    stateMutability: 'view',\n    type: 'function',\n  },\n] as const;\n\nconst CHAIN_EXPLORER: Record<number, { apiUrl: string; keyPath: string; name: string }> = {\n  8453: { apiUrl: 'https://api.basescan.org/api', keyPath: 'explorer.basescan.apiKey', name: 'Basescan' },\n  1: { apiUrl: 'https://api.etherscan.io/api', keyPath: 'explorer.etherscan.apiKey', name: 'Etherscan' },\n};\n\nconst ApprovalsSchema = Type.Object({\n  action: stringEnum(ACTIONS, {\n    description:\n      'scan: audit all token approvals. revoke: revoke a specific approval. ' +\n      'revoke_all: revoke every non-zero approval found.',\n  }),\n  chain: Type.Optional(Type.String({\n    description: 'Chain: \"base\" (default) or \"ethereum\".',\n  })),\n  token: Type.Optional(Type.String({\n    description: 'Token contract address (0x...). Required for revoke.',\n  })),\n  spender: Type.Optional(Type.String({\n    description: 'Spender contract address (0x...). Required for revoke.',\n  })),\n});\n\nexport function createApprovalsTool() {\n  return {\n    name: 'approvals',\n    label: 'Token Approvals',\n    ownerOnly: true,\n    description:\n      'Scan, audit, and revoke ERC-20 token approvals. ' +\n      'Detects unlimited approvals and unknown spenders. ' +\n      'Uses Etherscan/Basescan for comprehensive event scanning, ' +\n      'with on-chain verification of current allowance state.',\n    parameters: ApprovalsSchema,\n    execute: async (_toolCallId: string, args: unknown) => {\n      const params = args as Record<string, unknown>;\n      const action = readStringParam(params, 'action', { required: true })!;\n\n      switch (action) {\n        case 'scan':\n          return handleScan(params);\n        case 'revoke':\n          return handleRevoke(params);\n        case 'revoke_all':\n          return handleRevokeAll(params);\n        default:\n          return errorResult(`Unknown action: ${action}. Use: scan, revoke, revoke_all`);\n      }\n    },\n  };\n}\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\ninterface ApprovalEntry {\n  token: string;\n  tokenAddress: string;\n  spender: string;\n  spenderAddress: string;\n  allowance: string;\n  allowanceHuman: string;\n  isUnlimited: boolean;\n  riskLevel: string;\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction resolveChainId(chain?: string): number {\n  if (!chain) return 8453;\n  switch (chain.toLowerCase()) {\n    case 'ethereum': case 'eth': case 'mainnet': return 1;\n    case 'base': default: return 8453;\n  }\n}\n\n/**\n * Scan via Etherscan/Basescan event logs API for all Approval events\n * where topic1 (owner) matches the wallet address.\n */\nasync function scanViaExplorer(\n  ownerAddress: string,\n  chainId: number,\n): Promise<Array<{ tokenAddress: string; spenderAddress: string }> | null> {\n  const explorer = CHAIN_EXPLORER[chainId];\n  if (!explorer) return null;\n\n  const apiKey = getCredentialVault().getSecret(explorer.keyPath, 'approvals');\n  if (!apiKey) return null;\n\n  // Pad owner address to 32 bytes for topic matching\n  const paddedOwner = '0x' + ownerAddress.slice(2).toLowerCase().padStart(64, '0');\n\n  const url = new URL(explorer.apiUrl);\n  url.searchParams.set('module', 'logs');\n  url.searchParams.set('action', 'getLogs');\n  url.searchParams.set('fromBlock', '0');\n  url.searchParams.set('toBlock', 'latest');\n  url.searchParams.set('topic0', APPROVAL_EVENT_TOPIC);\n  url.searchParams.set('topic1', paddedOwner);\n  url.searchParams.set('topic0_1_opr', 'and');\n  url.searchParams.set('apikey', apiKey);\n\n  try {\n    const response = await guardedFetch(url.toString(), {\n      headers: { Accept: 'application/json' },\n      signal: AbortSignal.timeout(20_000),\n    });\n\n    if (!response.ok) return null;\n\n    const data: any = await response.json();\n    if (data.status === '0') return null;\n\n    const logs: any[] = data.result ?? [];\n\n    // Deduplicate: unique (tokenAddress, spenderAddress) pairs\n    const seen = new Set<string>();\n    const pairs: Array<{ tokenAddress: string; spenderAddress: string }> = [];\n\n    for (const log of logs) {\n      const tokenAddress = log.address?.toLowerCase();\n      const spenderTopic = log.topics?.[2];\n      if (!tokenAddress || !spenderTopic) continue;\n\n      // Extract spender address from padded topic (last 40 hex chars)\n      const spenderAddress = '0x' + spenderTopic.slice(-40);\n      const key = `${tokenAddress}:${spenderAddress}`;\n      if (seen.has(key)) continue;\n      seen.add(key);\n      pairs.push({ tokenAddress, spenderAddress });\n    }\n\n    return pairs;\n  } catch {\n    return null;\n  }\n}\n\n/**\n * Verify current on-chain allowance for a token+spender pair.\n * Returns null if the token isn't a valid ERC-20 or allowance is zero.\n */\nasync function verifyAllowance(\n  ownerAddress: string,\n  tokenAddress: string,\n  spenderAddress: string,\n  chainId: number,\n): Promise<ApprovalEntry | null> {\n  const rpcManager = getRpcManager();\n  const client = await rpcManager.getClient(chainId);\n  const manager = getAllowanceManager();\n\n  try {\n    const [allowance, symbol, decimals] = await Promise.all([\n      client.readContract({\n        address: tokenAddress as `0x${string}`,\n        abi: ERC20_MINIMAL_ABI,\n        functionName: 'allowance',\n        args: [ownerAddress as `0x${string}`, spenderAddress as `0x${string}`],\n      }) as Promise<bigint>,\n      client.readContract({\n        address: tokenAddress as `0x${string}`,\n        abi: ERC20_MINIMAL_ABI,\n        functionName: 'symbol',\n      }).catch(() => 'UNKNOWN') as Promise<string>,\n      client.readContract({\n        address: tokenAddress as `0x${string}`,\n        abi: ERC20_MINIMAL_ABI,\n        functionName: 'decimals',\n      }).catch(() => 18) as Promise<number>,\n    ]);\n\n    if (allowance === 0n) return null;\n\n    const dec = Number(decimals);\n    const humanStr = formatUnits(allowance, dec);\n    const human = parseFloat(humanStr);\n    const isUnlimited = human > 1e12;\n    const spenderName = manager.resolveSpenderName(spenderAddress, chainId);\n\n    let riskLevel: string;\n    if (!isUnlimited && human < 1000) riskLevel = 'safe';\n    else if (spenderName === 'Unknown') riskLevel = isUnlimited ? 'critical' : 'high';\n    else if (isUnlimited) riskLevel = 'moderate';\n    else riskLevel = 'safe';\n\n    return {\n      token: String(symbol),\n      tokenAddress,\n      spender: spenderName,\n      spenderAddress,\n      allowance: allowance.toString(),\n      allowanceHuman: isUnlimited ? 'unlimited' : human.toLocaleString(),\n      isUnlimited,\n      riskLevel,\n    };\n  } catch {\n    return null;\n  }\n}\n\n// ── Cached scan results for revoke_all ─────────────────────────────────────\n\nlet _lastScanResult: ApprovalEntry[] = [];\nlet _lastScanChainId = 0;\nlet _lastScanTimestamp = 0;\n\n// ── Action Handlers ────────────────────────────────────────────────────────\n\nasync function handleScan(params: Record<string, unknown>) {\n  const state = getWalletState();\n  if (!state.connected || !state.address) {\n    return errorResult('No wallet connected. Connect a wallet first.');\n  }\n\n  const chainId = resolveChainId(readStringParam(params, 'chain'));\n  const chainName = chainId === 1 ? 'ethereum' : 'base';\n\n  // Try comprehensive scan via Etherscan/Basescan event logs\n  const explorerPairs = await scanViaExplorer(state.address, chainId);\n\n  let approvals: ApprovalEntry[];\n\n  if (explorerPairs && explorerPairs.length > 0) {\n    // Verify each pair on-chain (filter zero allowances, get current state)\n    // Process in batches of 10 to avoid rate limits\n    const batchSize = 10;\n    const verified: ApprovalEntry[] = [];\n\n    for (let i = 0; i < explorerPairs.length; i += batchSize) {\n      const batch = explorerPairs.slice(i, i + batchSize);\n      const results = await Promise.all(\n        batch.map(({ tokenAddress, spenderAddress }) =>\n          verifyAllowance(state.address!, tokenAddress, spenderAddress, chainId),\n        ),\n      );\n      for (const r of results) {\n        if (r) verified.push(r);\n      }\n    }\n\n    approvals = verified;\n  } else {\n    // Fallback: use AllowanceManager's known tokens × known spenders\n    const manager = getAllowanceManager();\n    const report = await manager.auditAllowances(state.address, chainId);\n    approvals = report.allowances.map((a: AllowanceInfo) => ({\n      token: a.token,\n      tokenAddress: a.tokenAddress,\n      spender: a.spenderName,\n      spenderAddress: a.spenderAddress,\n      allowance: a.allowance,\n      allowanceHuman: a.allowanceHuman,\n      isUnlimited: a.isUnlimited,\n      riskLevel: a.riskLevel,\n    }));\n  }\n\n  // Sort: critical > high > moderate > safe\n  const riskOrder: Record<string, number> = { critical: 0, high: 1, moderate: 2, safe: 3 };\n  approvals.sort((a, b) => (riskOrder[a.riskLevel] ?? 4) - (riskOrder[b.riskLevel] ?? 4));\n\n  // Cache for revoke_all\n  _lastScanResult = approvals;\n  _lastScanChainId = chainId;\n  _lastScanTimestamp = Date.now();\n\n  const unlimited = approvals.filter(a => a.isUnlimited).length;\n  const critical = approvals.filter(a => a.riskLevel === 'critical').length;\n  const high = approvals.filter(a => a.riskLevel === 'high').length;\n\n  const recommendations: string[] = [];\n  if (critical > 0) {\n    recommendations.push(\n      `${critical} CRITICAL approval${critical > 1 ? 's' : ''} to unknown contracts — revoke immediately.`,\n    );\n  }\n  if (unlimited > 0) {\n    recommendations.push(\n      `${unlimited} unlimited approval${unlimited > 1 ? 's' : ''} found. Revoke any you no longer use.`,\n    );\n  }\n  if (high > 0) {\n    recommendations.push(\n      `${high} high-risk approval${high > 1 ? 's' : ''} detected. Review spender contracts.`,\n    );\n  }\n  if (approvals.length === 0) {\n    recommendations.push('No active approvals found — minimal token approval exposure.');\n  }\n\n  return jsonResult({\n    chain: chainName,\n    address: state.address,\n    scanMethod: explorerPairs ? 'event_logs' : 'known_spenders',\n    totalApprovals: approvals.length,\n    unlimited,\n    critical,\n    highRisk: high,\n    approvals: approvals.map(a => ({\n      token: a.token,\n      tokenAddress: a.tokenAddress,\n      spender: a.spender,\n      spenderAddress: a.spenderAddress,\n      allowance: a.allowanceHuman,\n      risk: a.riskLevel,\n    })),\n    recommendations,\n    tip: approvals.length > 0\n      ? 'Use approvals action=revoke token=<address> spender=<address> to revoke specific approvals, or action=revoke_all to revoke everything.'\n      : undefined,\n  });\n}\n\nasync function handleRevoke(params: Record<string, unknown>) {\n  const tokenAddress = readStringParam(params, 'token');\n  const spenderAddress = readStringParam(params, 'spender');\n  if (!tokenAddress || !spenderAddress) {\n    return errorResult('Both token and spender addresses are required for revoke.');\n  }\n\n  const state = getWalletState();\n  if (!state.connected || !state.address) {\n    return errorResult('No wallet connected. Connect a wallet first.');\n  }\n\n  try {\n    const wallet = requireWalletClient();\n    const publicClient = requirePublicClient();\n    const chainId = resolveChainId(readStringParam(params, 'chain'));\n\n    // Verify current allowance first\n    const currentAllowance = await publicClient.readContract({\n      address: tokenAddress as `0x${string}`,\n      abi: ERC20_MINIMAL_ABI,\n      functionName: 'allowance',\n      args: [state.address as `0x${string}`, spenderAddress as `0x${string}`],\n    }) as bigint;\n\n    if (currentAllowance === 0n) {\n      return jsonResult({\n        status: 'already_zero',\n        token: tokenAddress,\n        spender: spenderAddress,\n        message: 'Approval is already zero — nothing to revoke.',\n      });\n    }\n\n    // Get token symbol for reporting\n    let symbol = 'UNKNOWN';\n    try {\n      symbol = await publicClient.readContract({\n        address: tokenAddress as `0x${string}`,\n        abi: ERC20_MINIMAL_ABI,\n        functionName: 'symbol',\n      }) as string;\n    } catch { /* use UNKNOWN */ }\n\n    // Send approve(spender, 0) transaction\n    const hash = await wallet.writeContract({\n      address: tokenAddress as `0x${string}`,\n      abi: ERC20_MINIMAL_ABI,\n      functionName: 'approve',\n      args: [spenderAddress as `0x${string}`, 0n],\n    });\n\n    // Wait for revoke tx to confirm\n    await publicClient.waitForTransactionReceipt({ hash });\n\n    // Resolve spender name\n    const manager = getAllowanceManager();\n    const spenderName = manager.resolveSpenderName(spenderAddress, chainId);\n\n    return jsonResult({\n      status: 'revoked',\n      token: symbol,\n      tokenAddress,\n      spender: spenderName,\n      spenderAddress,\n      txHash: hash,\n      previousAllowance: currentAllowance.toString(),\n    });\n  } catch (err) {\n    return errorResult(`Revoke failed: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n\nasync function handleRevokeAll(params: Record<string, unknown>) {\n  const state = getWalletState();\n  if (!state.connected || !state.address) {\n    return errorResult('No wallet connected. Connect a wallet first.');\n  }\n\n  const chainId = resolveChainId(readStringParam(params, 'chain'));\n\n  // Use cached scan if fresh (< 5 minutes), otherwise re-scan\n  let approvals = _lastScanResult;\n  if (_lastScanChainId !== chainId || Date.now() - _lastScanTimestamp > 300_000 || approvals.length === 0) {\n    // Re-scan\n    const scanResult = await handleScan(params);\n    const scanData = scanResult.details as any;\n    if (scanData?.error) return scanResult;\n    approvals = _lastScanResult;\n  }\n\n  if (approvals.length === 0) {\n    return jsonResult({\n      status: 'nothing_to_revoke',\n      message: 'No active approvals found.',\n    });\n  }\n\n  const wallet = requireWalletClient();\n  const publicClient = requirePublicClient();\n  const results: Array<{ token: string; spender: string; txHash?: string; error?: string }> = [];\n\n  for (const approval of approvals) {\n    try {\n      const hash = await wallet.writeContract({\n        address: approval.tokenAddress as `0x${string}`,\n        abi: ERC20_MINIMAL_ABI,\n        functionName: 'approve',\n        args: [approval.spenderAddress as `0x${string}`, 0n],\n      });\n\n      // Wait for each revoke to confirm before sending the next\n      await publicClient.waitForTransactionReceipt({ hash });\n\n      results.push({\n        token: `${approval.token} (${approval.tokenAddress})`,\n        spender: `${approval.spender} (${approval.spenderAddress})`,\n        txHash: hash,\n      });\n    } catch (err) {\n      results.push({\n        token: `${approval.token} (${approval.tokenAddress})`,\n        spender: `${approval.spender} (${approval.spenderAddress})`,\n        error: err instanceof Error ? err.message : String(err),\n      });\n    }\n  }\n\n  const succeeded = results.filter(r => r.txHash).length;\n  const failed = results.filter(r => r.error).length;\n\n  // Clear cache\n  _lastScanResult = [];\n  _lastScanTimestamp = 0;\n\n  return jsonResult({\n    status: 'batch_revoke_complete',\n    total: results.length,\n    succeeded,\n    failed,\n    results,\n  });\n}\n"],"mappings":"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"}