scan_contract
Analyze smart contracts for security vulnerabilities, exploit patterns, and rug-pull signals to assess risks before interacting with unfamiliar contracts.
Instructions
Analyze a smart contract's source code or bytecode for known exploit patterns, honeypot mechanics, rug-pull signals, and security vulnerabilities. Returns a risk score (0-100) and detailed findings. Use this BEFORE interacting with any unfamiliar contract.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| source | No | Solidity source code of the contract to analyze | |
| bytecode | No | Contract bytecode (hex) to analyze if source is unavailable | |
| contractAddress | No | Contract address - if provided, will attempt to fetch source from block explorer | |
| chainId | No | Chain ID (1=Ethereum, 8453=Base, 84532=Base Sepolia) |
Implementation Reference
- src/mcp-server/server.ts:30-81 (handler)Tool registration and handler definition for 'scan_contract'. It accepts source, bytecode, or a contract address, and calls the scanner engine.
server.tool( "scan_contract", "Analyze a smart contract's source code or bytecode for known exploit patterns, honeypot mechanics, rug-pull signals, and security vulnerabilities. Returns a risk score (0-100) and detailed findings. Use this BEFORE interacting with any unfamiliar contract.", { source: z.string().optional().describe("Solidity source code of the contract to analyze"), bytecode: z.string().optional().describe("Contract bytecode (hex) to analyze if source is unavailable"), contractAddress: z.string().optional().describe("Contract address - if provided, will attempt to fetch source from block explorer"), chainId: z.number().default(1).describe("Chain ID (1=Ethereum, 8453=Base, 84532=Base Sepolia)"), }, async ({ source, bytecode, contractAddress, chainId }) => { if (!source && !bytecode && !contractAddress) { return { content: [{ type: "text" as const, text: JSON.stringify({ error: "Provide at least one of: source, bytecode, or contractAddress", }), }], }; } // If we have a contract address but no source/bytecode, try to fetch it if (contractAddress && !source && !bytecode) { const fetched = await fetchContractSource(contractAddress, chainId); if (fetched.source) source = fetched.source; if (fetched.bytecode) bytecode = fetched.bytecode; } let result; if (source) { result = scanContractSource(source); } else if (bytecode) { result = scanBytecode(bytecode); } else { return { content: [{ type: "text" as const, text: JSON.stringify({ error: "Could not fetch contract source or bytecode. Provide them directly.", }), }], }; } return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2), }], }; }, ); - src/risk-engine/scanner.ts:35-61 (handler)The core logic for scanning contract source code against exploit patterns.
export function scanContractSource(source: string): ScanResult { const findings: ScanFinding[] = []; for (const pattern of EXPLOIT_PATTERNS) { if (!pattern.sourcePatterns) continue; for (const regex of pattern.sourcePatterns) { const match = source.match(regex); if (match) { // Avoid duplicate findings for the same pattern if (findings.some((f) => f.patternId === pattern.id)) continue; findings.push({ patternId: pattern.id, patternName: pattern.name, severity: pattern.severity, description: pattern.description, riskWeight: pattern.riskWeight, matchedSnippet: match[0].slice(0, 100), }); break; // One match per pattern is enough } } } return buildResult(findings); }