class SolidityAuditor:
def __init__(self, anthropic_api_key: str, temp_dir: Optional[Path] = None):
self.client = anthropic.Anthropic(api_key=anthropic_api_key)
self.temp_dir = temp_dir or Path(tempfile.gettempdir()) / "audit_repos"
self.max_file_size = 100000 # 100KB max per file
self.max_retries = 2
```python
## Audit Prompt Engineering
### Security-Focused System Prompt
The bot uses a carefully crafted prompt that focuses Claude on smart contract security:
```python
AUDIT_PROMPT = """You are an expert smart contract security auditor. Analyze the following Solidity code for security vulnerabilities.
Focus on these vulnerability categories:
1. **Reentrancy attacks** - State changes after external calls, cross-function reentrancy
2. **Access control issues** - Missing modifiers, improper role management, unprotected functions
3. **Integer overflow/underflow** - Unchecked arithmetic (especially in older Solidity versions)
4. **Unchecked external calls** - Missing return value checks, call/delegatecall issues
5. **Centralization risks** - Single owner control, upgrade mechanisms, emergency functions
6. **Gas optimization issues** - Unbounded loops, expensive storage operations
7. **Logic errors** - Incorrect state transitions, race conditions
8. **Front-running vulnerabilities** - Price manipulation, sandwich attacks
9. **Denial of Service** - Block gas limit, failed transfers blocking execution
10. **Oracle manipulation** - Price oracle issues, flash loan attacks
For each vulnerability found, provide:
- Severity (Critical/High/Medium/Low)
- Title (brief description)
- Description (detailed explanation)
- Location (function or line reference if possible)
- Recommendation (how to fix)
Respond in JSON format:
{
"findings": [
{
"severity": "Critical|High|Medium|Low",
"title": "Brief title",
"description": "Detailed description",
"location": "function name or line reference",
"recommendation": "How to fix"
}
],
"summary": "Brief overall assessment of the contract's security"
}
If no vulnerabilities are found, return an empty findings array with a positive summary.
Here is the Solidity code to audit:
```solidity
{code}
```"""
```python
<Info>
The structured JSON output ensures consistent parsing and enables programmatic processing of audit results.
</Info>
## Audit Workflow
### Repository-Based Audits
For contracts with associated GitHub repositories:
```python
def audit_repo(self, repo_url: str) -> FullAuditReport:
"""Perform a full audit of a GitHub repository."""
report = FullAuditReport(
repo_url=repo_url,
audit_date=datetime.utcnow(),
files_audited=0,
total_findings=0,
critical_count=0,
high_count=0,
medium_count=0,
low_count=0
)
clone_path = None
try:
# Clone repository (shallow clone)
clone_path = self.clone_repo(repo_url)
# Find Solidity files
sol_files = self.find_solidity_files(clone_path)
if not sol_files:
report.error = "No Solidity files found"
return report
# Audit each file
for sol_file in sol_files:
result = self.audit_file(sol_file)
if result.findings:
report.files_audited += 1
for finding in result.findings:
# Add file context to location
finding.location = f"{sol_file.name}: {finding.location}"
report.findings.append(finding)
# Count findings by severity
for finding in report.findings:
severity = finding.severity.lower()
if severity == "critical":
report.critical_count += 1
elif severity == "high":
report.high_count += 1
elif severity == "medium":
report.medium_count += 1
elif severity == "low":
report.low_count += 1
report.total_findings = len(report.findings)
report.summary = self._generate_summary(report)
finally:
# Cleanup cloned repo
if clone_path and clone_path.exists():
self.cleanup(clone_path)
return report
```python
### Finding Solidity Files
The auditor searches common directories and filters out test files:
```python
def find_solidity_files(self, repo_path: Path) -> list[Path]:
"""Find all Solidity files in a repository."""
# Common directories to search
search_dirs = [
repo_path / "contracts",
repo_path / "src",
repo_path / "lib",
repo_path,
]
sol_files = []
for search_dir in search_dirs:
if search_dir.exists():
sol_files.extend(search_dir.rglob("*.sol"))
# Remove duplicates and sort
sol_files = sorted(set(sol_files))
# Filter out test files and mocks
filtered_files = [
f for f in sol_files
if not any(x in str(f).lower() for x in ["test", "mock", "script", "node_modules"])
]
return filtered_files
```python
<Warning>
The bot skips:
- Test files (containing "test" in path)
- Mock contracts (containing "mock" in path)
- Scripts (containing "script" in path)
- Dependencies (node_modules, lib)
This focuses audits on production contract code.
</Warning>
### Single File Audits
The core auditing logic analyzes individual Solidity files:
```python
def audit_file(self, file_path: Path) -> AuditResult:
"""Audit a single Solidity file using Claude."""
result = AuditResult(file_path=str(file_path))
try:
# Read file content
content = file_path.read_text(encoding="utf-8", errors="ignore")
# Skip if file is too large
if len(content) > self.max_file_size:
result.error = "File too large to audit"
return result
# Skip if file is mostly imports or interfaces
if self._is_minimal_contract(content):
return result
# Call Claude API
response = self._call_claude(content)
if response:
result.findings = [
Finding(
severity=f.get("severity", "Medium"),
title=f.get("title", "Unknown"),
description=f.get("description", ""),
location=f.get("location"),
recommendation=f.get("recommendation")
)
for f in response.get("findings", []) if isinstance(f, dict)
]
except Exception as e:
result.error = str(e)
return result
```python
### Filtering Minimal Contracts
To avoid wasting API calls, the auditor skips interface-only files:
```python
def _is_minimal_contract(self, content: str) -> bool:
"""Check if a file is mostly imports or interfaces."""
lines = content.split("\n")
code_lines = [
l for l in lines
if l.strip() and not l.strip().startswith("//") and not l.strip().startswith("import")
]
# If less than 10 lines of actual code, skip
if len(code_lines) < 10:
return True
# If file is an interface (all functions are external/public with no body)
if "interface " in content and "function " in content:
func_count = content.count("function ")
empty_funcs = content.count(");")
if empty_funcs >= func_count * 0.8: # 80% or more are interface functions
return True
return False
```python
## Claude API Integration
### Making API Calls
```python
def _call_claude(self, code: str) -> Optional[dict]:
"""Call Claude API for code analysis."""
for attempt in range(self.max_retries + 1):
try:
message = self.client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=4096,
messages=[
{
"role": "user",
"content": self.AUDIT_PROMPT.format(code=code)
}
]
)
# Parse response
response_text = message.content[0].text
# Extract JSON from response
json_start = response_text.find("{")
json_end = response_text.rfind("}") + 1
if json_start >= 0 and json_end > json_start:
json_str = response_text[json_start:json_end]
return json.loads(json_str)
return None
except anthropic.RateLimitError:
if attempt < self.max_retries:
logger.warning("Rate limited, waiting before retry...")
time.sleep(60) # Wait 60 seconds
continue
raise
except Exception as e:
logger.error(f"Claude API error: {e}")
if attempt < self.max_retries:
continue
return None
return None
```python
### Rate Limit Handling
<Tip>
**Rate limit strategy:**
- Automatic retry with 60-second delay on rate limit errors
- Max 2 retries per file to avoid excessive delays
- Failed audits are logged but don't block other files
</Tip>
## Data Structures
### Finding Object
```python
@dataclass
class Finding:
"""Represents a single security finding."""
severity: str # Critical, High, Medium, Low
title: str
description: str
location: Optional[str] = None
recommendation: Optional[str] = None
```python
### FullAuditReport Object
```python
@dataclass
class FullAuditReport:
"""Complete audit report for a repository."""
repo_url: str
audit_date: datetime
files_audited: int
total_findings: int
critical_count: int
high_count: int
medium_count: int
low_count: int
findings: list[Finding] = field(default_factory=list)
summary: str = ""
error: Optional[str] = None
```python
## Direct Source Code Audits
For verified contracts without GitHub repositories:
```python
def audit_source_code(self, source_code: str, contract_name: str = "Unknown") -> FullAuditReport:
"""Audit source code directly without cloning a repo."""
report = FullAuditReport(
repo_url="direct_source",
audit_date=datetime.utcnow(),
files_audited=1,
total_findings=0,
critical_count=0,
high_count=0,
medium_count=0,
low_count=0
)
try:
# Handle multi-file JSON format from Basescan
if source_code.startswith("{{"):
source_code = source_code[1:-1]
sources = json.loads(source_code)
if "sources" in sources:
all_code = []
for file_info in sources["sources"].values():
all_code.append(file_info.get("content", ""))
source_code = "\n\n".join(all_code)
# Audit the source code
response = self._call_claude(source_code)
if response:
findings = response.get("findings", [])
for finding_data in findings:
if isinstance(finding_data, dict):
finding = Finding(
severity=finding_data.get("severity", "Medium"),
title=finding_data.get("title", "Unknown"),
description=finding_data.get("description", ""),
location=finding_data.get("location", contract_name),
recommendation=finding_data.get("recommendation")
)
report.findings.append(finding)
# Count by severity
for finding in report.findings:
severity = finding.severity.lower()
if severity == "critical":
report.critical_count += 1
elif severity == "high":
report.high_count += 1
elif severity == "medium":
report.medium_count += 1
elif severity == "low":
report.low_count += 1
report.total_findings = len(report.findings)
report.summary = response.get("summary", self._generate_summary(report))
except Exception as e:
report.error = str(e)
report.summary = f"Audit failed: {str(e)}"
return report
```python
## Summary Generation
Human-readable summaries are generated from audit results:
```python
def _generate_summary(self, report: FullAuditReport) -> str:
"""Generate a human-readable summary of the audit."""
if report.total_findings == 0:
return "No security issues detected. The contracts appear to follow security best practices."
severity_parts = []
if report.critical_count > 0:
severity_parts.append(f"{report.critical_count} critical")
if report.high_count > 0:
severity_parts.append(f"{report.high_count} high")
if report.medium_count > 0:
severity_parts.append(f"{report.medium_count} medium")
if report.low_count > 0:
severity_parts.append(f"{report.low_count} low")
summary = f"Found {report.total_findings} issue(s): {', '.join(severity_parts)}."
if report.critical_count > 0:
summary += " Immediate attention required for critical issues."
elif report.high_count > 0:
summary += " High severity issues should be addressed before deployment."
return summary
```python
## Integration with Main Bot
The auditor is called after contract discovery:
```python
# Initialize auditor
self.auditor = SolidityAuditor(
anthropic_api_key=config.anthropic_api_key,
temp_dir=config.temp_dir
)
# Perform audit
if repo_url:
report = self.auditor.audit_repo(repo_url)
else:
report = self.auditor.audit_source_code(source_code, contract_name)
# Save and post results
if not report.error:
audit = Audit(
contract_id=contract.id,
audit_date=datetime.utcnow(),
critical_count=report.critical_count,
high_count=report.high_count,
medium_count=report.medium_count,
low_count=report.low_count,
summary=report.summary
)
self.db.add_audit(audit)
self.twitter_bot.post_audit(contract, audit)
```python
## Performance Characteristics
### Latency
- **Single file**: 2-5 seconds (Claude API latency)
- **Small repo (5-10 files)**: 10-50 seconds
- **Large repo (20+ files)**: 1-2 minutes
### Cost Optimization
<Info>
**Cost reduction strategies:**
1. Skip interface-only files
2. Filter test and mock contracts
3. Limit file size to 100KB
4. Use shallow git clones
5. Clean up temp directories after audits
</Info>
## Next Steps
<CardGroup cols={2}>
<Card title="Twitter Integration" icon="twitter" href="/concepts/twitter-integration">
Learn how audit results are posted to Twitter
</Card>
<Card title="System Architecture" icon="diagram-project" href="/concepts/architecture">
Understand how all components work together
</Card>
</CardGroup>