AI-powered security auditor for ERC-7730 clear signing descriptor files. Validates that transaction descriptors accurately represent what smart contracts actually do — and optionally captures Ledger device screenshots to verify on-device rendering.
ERC-7730 defines how wallets display human-readable transaction information. A malicious or incorrect descriptor could mislead users into signing harmful transactions. This tool:
- Parses the ERC-7730 descriptor and resolves its ABI / format definitions
- Fetches real transactions from the blockchain (Snowflake, Etherscan, Blockscout, RPC)
- Decodes calldata against the ABI and extracts contract source code
- Optionally captures Ledger device screenshots via
clear-signing-tester+ Speculos - Sends everything to an LLM for per-selector security audit
- Generates markdown reports with critical issues, recommendations, and screenshots
┌─────────────────┐ ┌──────────────────┐ ┌───────────────────────┐
│ ERC-7730 File │ │ Data Sources │ │ Ledger Screenshots │
│ (descriptor) │ │ Snowflake │ │ cs-tester + Speculos │
│ │ │ Etherscan │ └───────────┬───────────┘
│ │ │ Blockscout │ │
│ │ │ JSON-RPC │ │
└────────┬────────┘ └────────┬─────────┘ │
│ │ │
└───────────────────────┼───────────────────────────┘
▼
┌──────────────────────────┐
│ Pipeline │
│ • Decode transactions │
│ • Extract source code │
│ • Capture screenshots │
└──────────────┬───────────┘
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌──────────────────┐ ┌────────────────┐ ┌────────────────┐
│ Primary Auditor │ │ Validator │ │ Reducer │
│ Drafts findings │ │ Challenges the │ │ Merges into │
│ per selector │ │ draft, requests│ │ final verdict │
│ │ │ extra evidence │ │ │
└────────┬─────────┘ └───────┬────────┘ └───────┬────────┘
└────────────────────┼──────────────────┘
▼
┌──────────────────────────┐
│ Audit Reports │
│ • CRITICALS_*.md │
│ • FULL_REPORT_*.md │
│ • results_*.json │
└──────────────────────────┘
Each selector is audited by a three-agent pipeline:
- Primary Auditor — receives the selector packet (descriptor, decoded transactions, source code, screenshots) and produces a draft audit report. Can request tools (extra source code, ABI lookups) if evidence is missing.
- Validator — skeptically challenges the primary draft. Looks for unsupported assumptions, overconfident severity, and missed issues. Can gather its own tool evidence.
- Reducer — receives both reports plus all tool evidence and produces the final conservative verdict. If the validator fully agrees with the primary, the reducer is skipped.
In single mode, a single LLM call per selector replaces the three-agent flow.
| Feature | Description |
|---|---|
| Critical Issue Detection | Identifies hidden or misleading parameters that could enable attacks |
| Source Code Analysis | Extracts and analyzes relevant contract functions |
| Real Transaction Validation | Decodes actual on-chain transactions, not just static analysis |
| Ledger Screenshot Capture | Renders transactions on emulated Ledger devices (Stax/Flex) |
| Multi-Source Fetching | Snowflake → Etherscan → Blockscout → RPC fallback chain |
| Spec Limitation Warnings | Flags parameters that ERC-7730 cannot properly display |
| Actionable Fixes | Provides JSON snippets to fix identified issues |
- Python 3.12+
- uv
- API keys: OpenAI, Etherscan
Optional (for screenshots):
- Docker (Speculos runs as a sibling container)
device-sdk-tswithclear-signing-testerbuilt- Ethereum app ELF files for the target Ledger device
git clone https://github.com/LedgerHQ/erc7730-analyzer.git
cd erc7730-analyzer
uv sync
cp .env.example .env
# Edit .env with your API keys# Basic analysis
uv run analyze_7730 --erc7730_file path/to/calldata.json
# With screenshots on Ledger Stax
uv run analyze_7730 --erc7730_file path/to/calldata.json --enable-screenshots
# Multi-agent mode (deeper analysis, more LLM rounds)
uv run analyze_7730 --erc7730_file path/to/calldata.json --analysis-mode multi
# Custom model and reasoning effort
uv run analyze_7730 --erc7730_file path/to/calldata.json --model gpt-5.4 --reasoning-effort high
# Debug logging
uv run analyze_7730 --erc7730_file path/to/calldata.json --debugReports are saved to ./output/:
CRITICALS_<protocol>.md— critical issues requiring immediate attentionFULL_REPORT_<protocol>.md— comprehensive analysis with all findingsresults_<protocol>.json— raw structured results
Set in .env or pass directly:
| Variable | Required | Description |
|---|---|---|
OPENAI_API_KEY |
Yes | LLM API key for analysis |
ETHERSCAN_API_KEY |
Yes | For fetching ABI, source code, and transactions |
COREDAO_API_KEY |
No | For Core DAO chain (1116) |
INFURA_RPC_KEY |
No | Infura project key for RPC fallback |
SNOWFLAKE_ENABLED |
No | Enable Snowflake as primary tx source (true/false) |
SNOWFLAKE_USER |
No | Snowflake authentication user |
SNOWFLAKE_INSTANCE |
No | Snowflake account identifier |
SNOWFLAKE_WAREHOUSE |
No | Snowflake warehouse name |
SNOWFLAKE_ROLE |
No | Snowflake role |
SNOWFLAKE_PRIVATE_KEY |
No | Snowflake RSA private key (PEM) |
ENABLE_SCREENSHOTS |
No | Capture Ledger device screenshots (true/false) |
CS_TESTER_ROOT |
No | Path to device-sdk-ts repo root |
ETH_APP_ELF_ROOT |
No | Path to directory containing Ethereum app ELF files |
LLM_MODEL |
No | Model name (default: gpt-5.4-nano) |
LLM_REASONING_EFFORT |
No | low, medium, or high (default: low) |
ANALYSIS_MODE |
No | single or multi (default: single) |
LOOKBACK_DAYS |
No | Transaction lookback period in days (default: 7) |
MAX_CONCURRENT_API_CALLS |
No | Max concurrent internal API/model calls (default: 2) |
MAX_SELECTOR_TOOL_ROUNDS |
No | Max multi-agent evidence-gathering rounds (default: 1) |
MAX_TOOL_REQUESTS_PER_ROUND |
No | Max tool requests per multi-agent round (default: 1) |
All environment variables can be overridden via CLI flags. Run uv run analyze_7730 --help for the full list.
For the local analyzer CLI, descriptor includes are resolved only within the descriptor file's directory by default. Use a descriptor path whose sibling tree contains the full include graph.
The analyzer runs as a FastAPI service behind AWS App Runner. It exposes an async polling API so analyses can run far longer than App Runner's 120-second request timeout.
The polling client is quiet by default and prints coarse status transitions.
Pass --verbose to erc7730-client to opt into detailed live analysis logs.
Descriptors with a top-level includes field are sent as a zip bundle (standard base64 in the JSON body). By default the client bundles files from the descriptor file's directory; use --bundle-root <dir> only when the descriptor/include tree lives elsewhere.
Start (or resume) an analysis. Returns immediately with a job handle.
- Auth: Bearer token (GitHub OIDC JWT). Skipped when
DISABLE_OIDC_AUTH=true. - Body: JSON matching
AnalyzeRequest. Use one of:- Single descriptor:
descriptor(object) plus optionaldescriptor_filename. Suitable when the spec has noincludesor the client inlines everything. - Descriptor bundle (for specs with
includes):descriptor_bundle_base64(standard base64 of a zip containing all JSON files in the include graph) andbundle_entrypoint(relative POSIX path inside the zip to the entry descriptor). Do not senddescriptorin the same request. The server extracts the zip into a private temp directory and only resolvesincludeswithin that tree (path traversal is rejected).
- Single descriptor:
- Verbose live logs: set
verbose: truein the request body to capture detailed analysis logs for polling clients. - Idempotency: when OIDC is enabled the job is keyed by
(repository, run_id, run_attempt)from the JWT. Repeating the same POST returns the existing job instead of starting a duplicate. - Responses:
202 Accepted— job created or still running.200 OK— job already completed (result included).503 Service Unavailable— another analysis is in progress.
Poll the status of a running analysis or retrieve the final result.
- Auth: same Bearer token. When OIDC is disabled, pass
?run_key=<key>(the key returned by POST). - Live logs: pass
?include_logs=trueto include the latest live log lines for a running job. When omitted, the server defaults to the job's requested verbosity. - Responses:
202 Accepted— job queued or running (includespoll_after_secondsand a coarsestatus_message;recent_logsis only included when live logs are enabled).200 OK— job succeeded (includesprotocol,has_criticals,summary_report,criticals_report,results_json).404 Not Found— no job for this run.
| Aspect | Detail |
|---|---|
| Job storage | In-memory (transitional). Jobs are lost on restart, deploy, or crash. |
| Concurrency | One analysis at a time (asyncio.Semaphore(1)). |
| Scaling | App Runner pinned to max_instances=1 while storage is in-memory. |
| TTL | Completed jobs are evicted after JOB_RETENTION_TTL seconds (default 3600). |
| Timeout | Background analysis times out after 45 minutes. |
In addition to the standard config variables listed above, the service accepts:
| Variable | Default | Description |
|---|---|---|
JOB_RETENTION_TTL |
3600 |
Seconds to retain completed jobs before eviction |
MAX_RETAINED_LOG_LINES |
500 |
Max live log lines kept per job for verbose polling |
POLL_INTERVAL_HINT |
5 |
Suggested poll interval returned to clients (seconds) |
erc7730-analyzer/
├── src/
│ ├── main.py # CLI entry point
│ ├── service/ # FastAPI service (API mode)
│ │ ├── app.py # POST/GET /analyze (async polling)
│ │ ├── auth.py # GitHub OIDC auth + run-key derivation
│ │ ├── client.py # Python client (POST + poll loop)
│ │ ├── config.py # Service configuration
│ │ └── jobs.py # In-memory job model and registry
│ └── utils/
│ ├── core/ # Analysis pipeline orchestration
│ ├── abi/ # ABI fetching and parsing
│ ├── extraction/ # Contract source code extraction
│ ├── clients/ # Transaction fetching (Snowflake, Etherscan, etc.)
│ ├── auditing/ # LLM audit logic (single + multi-agent)
│ ├── reporting/ # Markdown and JSON report generation
│ ├── screenshots/ # Ledger screenshot capture via cs-tester
│ ├── rpc_helpers.py # JSON-RPC fallback utilities
│ └── audit_rules/ # Static analysis rule definitions (JSON)
├── tests/ # pytest suite (auth, jobs, API contract)
├── docs/
│ └── DEPLOYMENT.md # Deployment and local testing guide
├── testing/registry/ # Test descriptor files
├── Dockerfile
├── docker-compose.yml
└── pyproject.toml
CC0 1.0 Universal — see LICENSE file for details.