See what your smart contract actually does. Your Foundry tests, decoded line by line, mapped as a graph, and checked by a human. AI explains. You judge.
Luora is a visual workbench for smart contract auditors and bug bounty hunters. You run your usual Foundry tests, and Luora turns the output, normally a wall of text, into something a person can read: every call next to the line of code it ran, the contract as a map, test scenarios you build by connecting cards, the biggest DeFi protocols explained flow by flow, and real hacks taken apart as post-mortems. Tests can also run on a copy of six EVM testnets.
Luora never decides for you. AI is there to explain one piece at a time. The verdict is always a human's.
- The AI is your own: your Claude Code, a local model, your own API key, or none at all
- It runs on your machine next to your Foundry project. Nothing you export or drop in is uploaded
- It is defensive only: scenarios test intended behaviour, and hack pages carry no exploit code
The docs live at luorasec.xyz/docs, with short recordings of the app on the pages that show it:
| Section | Pages |
|---|---|
| Start here | What is Luora? · Quick start · Your first audit session |
| Using Luora | Trace · Atlas · Scenarios · Findings & triage · Explore · Exploits |
| Running tests | The exporter · Testnet forks · Run tests from the browser |
| AI | Ask AI providers · MCP server |
| More | Run Luora locally · Privacy & security · Known limits |
The pages are written as data in features/docs/pages.ts.
Meet Rina. She hunts bugs in DeFi protocols in the evenings. Tonight she is reading a lending vault and wants to know one thing: can withdraw pay out before the shares are burned?
She runs forge test -vvvv. The terminal prints four hundred lines of nested calls, raw hex amounts and addresses. She scrolls, loses her place, opens the source in another window, and tries to match each call to a line by hand. Then she asks an AI tool, which answers with total confidence that the vault is safe, without having run anything. She does not know which part to trust, so she starts over.
Rina's problem is not a missing scanner. It is that there is no place where her own tests become readable, where the code, the calls and the findings sit side by side, and where AI helps her understand without pretending to judge.
Checking a smart contract today means doing the hard part by hand, while the noise keeps growing.
- The stakes are high: $3.4 billion was stolen from crypto in 2025 (Chainalysis, 2026 Crypto Crime Report)
- Bug bounties are drowning in AI slop: curl shut down its bug bounty in January 2026 after fewer than 1 in 20 reports turned out to be real (BleepingComputer, The Register)
- Platforms are pushing back: Immunefi bans hunters for AI-generated reports, because "the outputs are never valid bug reports" (Immunefi on X, January 2023, via CoinDesk)
- The tool everyone uses is hard to read: Foundry is the main framework for 57% of Solidity developers (Solidity Developer Survey 2025), yet its traces print as walls of text
- Findings live in separate places: Slither, Aderyn, coverage and traces each print their own report, and nothing ties them to the same line of code
AI auditors try to solve the reading by handing out a verdict. That is how the slop is made: a confident answer nobody checked.
How might we make a contract's real behaviour readable from the tests people already write, and use AI to explain it, while the decision stays with a human?
Luora answers with five pieces, each doing a job the others cannot.
1. Traces you can read: the exporter runs forge test --json -vvvv, decodes every call with the project's own ABIs, and ties each frame to the exact source lines it executed. Click a call, see the line. Click a test, see why it passed: the checks that held.
2. The contract as a map: the Atlas is built from the compiler's AST and storage layout. Every function, who can call it, the state it reads and writes, the external calls it makes, with Slither, Aderyn and forge coverage pinned to the piece they belong to. Paths replays each entry point step by step and flags a write that lands after an external call.
3. Tests you build by wiring cards: in Scenarios you connect an actor, a call, an expected revert and a rule that must hold. Press Run to watch each step pass or revert in the browser, then generate a real Foundry test file and run it with forge.
4. Learn from protocols and from hacks: Explore maps Uniswap v3, Aave v3.5, Morpho Blue and Compound III from their pinned source, every business flow as a sequence diagram tied to the code. Exploits takes four of the largest smart contract incidents on rekt.news apart: the attack path, the check that was missing, and the invariant that would have caught it. No exploit code.
5. AI that explains, on your terms: Ask AI explains one piece or one call at a time, through your own Claude Code, a local model, or your own key. An MCP server lets your AI client read a run and propose scenarios, and only scenarios that test intended behaviour are accepted.
A run is a choice of which tests and which chain. Luora ships every shape below through the same exporter and the same Start test button.
| All tests, local | One test | A few tests | One testnet fork | All six forks | |
|---|---|---|---|---|---|
| Command | node luora-export.mjs |
--mt test_deposit |
Choose in the app | --chain base-sepolia |
--forks all |
| In the app | Test all | This test | Choose | Fork picker (the local run plus that chain) | command line only |
| Chain state | fresh local EVM | fresh local EVM | fresh local EVM | that testnet's latest block | each testnet's latest block |
| Result in the app | a fresh run | merged into the open run | merged into the open run | merged in, one group per chain | one group per chain |
| Cost | none | none | none | none: read-only public RPC, no key, no gas | none |
Test all gives a first full picture. One test is the inner loop while reading a single function. A few tests checks a hypothesis across related cases. A fork runs the same tests against contracts that are really deployed on that chain, so the trace shows calls into live code without sending a single transaction.
node luora-export.mjs [--mt …] [--mc …] [--chain <key> | --forks all]
├─ forge build
├─ forge test --json -vvvv (on a fork: --fork-url <rpc> at the chain's latest block)
│ └─ no JSON back → "forge ran no test (does a --mt / --mc filter match nothing?)"
├─ forge test --debug --dump one per unit test, for source-line steps
├─ decode frames with the project ABIs, map pc → source lines (solc source maps)
├─ atlas: forge build --ast + forge inspect storageLayout (skipped with a note if it fails)
├─ findings: merge reports/slither.json and reports/aderyn.json into the atlas
├─ coverage: forge coverage --report lcov (skipped with a note if it fails)
└─ write luora-data.json (LUORA_MERGE=1: merge into the existing run instead of replacing it)
- Decoded Call Trace: every call, event and cheatcode with decoded arguments, return values, gas and the source line it ran
- Why It Passed: click a test to see the exact assertions that held and the lines they checked
- Call Graph: the same run drawn as a map of who called whom
- Start Test From The App: run all tests, one test, or a chosen few, with a live pass or fail log per test
- Testnet Forks: run the same tests on Ethereum Sepolia, Arbitrum Sepolia, Base Sepolia, Robinhood Chain Testnet, HyperEVM Testnet or Tempo Moderato
- Contract Atlas: functions, storage slots, external calls and events as a node graph built from the AST
- Paths: replay each entry point step by step, with writes after an external call flagged in red
- Findings Where They Belong: Slither and Aderyn results deduplicated and pinned to the function they concern
- Coverage On The Map:
forge coverageper function, untested pieces counted in the toolbar - Visual Scenarios: wire actor, call, expected revert, snapshot and assert cards, run them in the browser
- Generate A Foundry Test: every scenario exports a
.t.solfile you run withforge - Explore: Uniswap v3, Aave v3.5, Morpho Blue and Compound III, every flow as a sequence diagram linked to the pinned source on GitHub
- Exploits: Poly Network, Euler Finance, Nomad Bridge and Beanstalk as post-mortems with funds, root cause and the invariant that catches it
- Ask AI: explain a call, a function or a range of calls, with every
#nin the answer selecting that call - Bring Your Own AI: your Claude Code and Claude plan, Ollama or LM Studio, or your own Anthropic, OpenAI or OpenRouter key
- MCP Server: your AI client reads a run and proposes scenarios, checked against the Lab's model
- Sample Vault: try everything without a project, on the bundled LuoraVault
- Keyboard First: arrows step through calls,
123switch views,Aasks AI,Fgoes full screen
| Layer | Technology |
|---|---|
| Frontend | Next.js 16 (App Router, React Compiler), React 19, TypeScript, Tailwind CSS v4 |
| Motion | Motion, GSAP, Lenis (landing page) |
| Exporter | Plain Node.js 20+ scripts, no dependencies |
| Smart contracts | Foundry (forge build, forge test, forge coverage, forge inspect), Solidity 0.8.20 |
| Static analysis | Slither, Aderyn (JSON reports merged into the atlas) |
| Chains | Six EVM testnets through public read-only RPCs, overridable per chain |
| AI | Claude through the user's own Claude Code, Ollama / LM Studio, Anthropic, OpenAI, OpenRouter |
| AI tooling | MCP server over stdio (JSON-RPC 2.0) |
| Tests | Node built-in test runner (8 exporter test files), 17 *.check.ts self-checks in the app, Foundry unit, fork and invariant tests |
| Lint / Format | Biome |
| CI / Deploy | GitHub Actions (lint, typecheck, build, forge test, export smoke test), Vercel |
| Built with | Claude Code, Orca worktrees; videos with Remotion and ElevenLabs |
Foundry is where the data comes from. Luora adds nothing to your project except seven plain scripts, and reads only what forge already produces.
| Component | File | Description |
|---|---|---|
| Exporter | luora-export.mjs |
Runs forge build, forge test --json -vvvv and one --debug --dump per unit test, decodes frames, writes luora-data.json. Prints machine-readable luora:: progress lines when LUORA_PROGRESS is set |
| Decoding | luora-lib.mjs |
keccak256, ABI parameter decoding, revert reasons, solc source maps to line indexes, and mergeRun for partial runs |
| Atlas | luora-atlas.mjs |
The contract map from forge build --ast and forge inspect … storageLayout |
| Findings | luora-findings.mjs |
Parses Slither and Aderyn JSON and merges them into the atlas, deduplicated |
| Coverage | luora-coverage.mjs |
forge coverage --report lcov mapped onto atlas pieces |
| Chains | luora-chains.mjs |
The six testnets, --chain / --forks, and a check that each RPC really serves the chain it claims |
| Scenario runner | luora-scenario.mjs |
Copies a test exported from Scenarios into test/scenarios/ and runs just that file with forge test --match-path |
| Protocol maps | luora-protocol.mjs |
Joins the maps of a protocol's contracts into one graph for Explore and checks the written flows against the AST |
| Start test route | route.ts |
Runs the exporter from the app, streams its output as NDJSON, one run at a time, 15 minute timeout |
| Bridge | luora-bridge.mjs |
Lets the hosted app run the exporter on your machine: listens on 127.0.0.1 only, answers only the Luora origins and the token in the link it prints, runs nothing but node luora-export.mjs with validated test and fork names, same NDJSON stream as the route |
| Command | Purpose |
|---|---|
forge build |
Compile and produce the ABIs the decoder needs |
forge test --json -vvvv |
The call traces, results and gas |
forge test --debug --dump |
Step data that ties every frame to its source lines |
forge test --mt / --mc |
Run one test or a chosen set (^(name)\( for functions, ^(Contract)$ for suites) |
forge test --fork-url |
The same tests on a testnet's latest block |
forge build --ast |
The contract map |
forge inspect … storageLayout |
Storage slots on the map |
forge coverage --report lcov |
Coverage per function |
Luora hosts no AI and calls no model by default. The user picks one in Connect AI.
| Option | What answers | What leaves your machine |
|---|---|---|
| None (default) | Luora's built-in text | Nothing. No network call is made |
| Your Claude | Your own Claude Code, signed in with your own Claude plan | The prompt goes through the app's local route to the claude program on your laptop |
| Local model | Ollama, LM Studio or any OpenAI-compatible server you run | The prompt goes to the endpoint you typed, normally localhost |
| Own API key | Anthropic, OpenAI or OpenRouter | The prompt and your key go from your browser straight to that provider |
Only the selected piece is sent: its name, its own source lines (at most 120), the pieces connected to it, open findings on it, a built-in checklist and the question. A fixed system prompt keeps the model defensive: explain behaviour, list what an auditor should check, write a Foundry test that pins intended behaviour, and refuse to write exploits. Details in docs/ai-provider.md.
luora-mcp.mjs lets your own AI client (Claude Code, Claude Desktop, Cursor) read a run. Plain Node, stdio, no network. Details in docs/mcp.md.
| Tool | Access | Returns |
|---|---|---|
overview |
read | Contract, tests, finding counts, coverage totals and the scenario model |
get_trace(test) |
read | One test's decoded frames |
get_atlas |
read | Every mapped piece with its line, plus each entry point's reads, writes, calls and emits |
get_piece(id) |
read | One piece: source lines, links, findings, reverts and coverage |
list_findings(severity?, tool?) |
read | Slither, Aderyn and Luora's own findings |
propose_scenario(scenario) |
append | Checks a scenario against the Lab's cards and saves it to luora-scenarios.json |
A proposal shows up in Atlas → Scenarios (and Trace's Scenario view) under Proposed by your AI client: in a local Luora, or on luorasec.xyz while node luora-bridge.mjs runs in the project (switch back to the tab to pick up a new one). Pick it, check it, press Run, then export the .t.sol.
claude mcp add luora -- node /abs/path/luora-mcp.mjs --data=/abs/path/luora-data.jsonLuora forks six EVM testnets. A fork run copies the chain's state at its latest block and runs your tests on that copy: nothing is sent, no wallet, no key, no gas.
| Key | Chain | Chain id | Default RPC |
|---|---|---|---|
sepolia |
Ethereum Sepolia | 11155111 | ethereum-sepolia-rpc.publicnode.com |
arbitrum-sepolia |
Arbitrum Sepolia | 421614 | sepolia-rollup.arbitrum.io/rpc |
base-sepolia |
Base Sepolia | 84532 | sepolia.base.org |
robinhood-testnet |
Robinhood Chain Testnet | 46630 | rpc.testnet.chain.robinhood.com |
hyperevm-testnet |
Hyperliquid HyperEVM Testnet | 998 | rpc.hyperliquid-testnet.xyz/evm |
tempo-testnet |
Tempo Testnet (Moderato) | 42431 | rpc.moderato.tempo.xyz |
Set LUORA_RPC_<KEY> (for example LUORA_RPC_BASE_SEPOLIA=https://…) to use your own provider, since public RPCs rate-limit. Before every fork run, latestBlock asks the RPC for eth_chainId and refuses an endpoint that serves a different chain. The sample's Fork.t.sol reads Multicall3, deployed at the same address on all six, to prove the run really touched the live chain.
sequenceDiagram
participant Auditor
participant App as Luora app (localhost)
participant Exporter as luora-export.mjs
participant Forge as forge
participant Chain as Testnet RPC (fork only)
participant File as luora-data.json
participant AI as Your AI (optional)
Auditor->>App: Start test (all / one / chosen, local or fork)
App->>Exporter: spawn with LUORA_PROGRESS=1
Exporter->>Forge: forge build
Exporter->>Forge: forge test --json -vvvv
Forge->>Chain: read state at latest block (fork only)
Forge-->>Exporter: results + call traces
Exporter->>Forge: --debug --dump, --ast, storageLayout, coverage
Exporter-->>App: luora:: progress lines (streamed)
Exporter->>File: write or merge the run
App->>File: read the fresh run
App-->>Auditor: Trace, Call graph, Atlas, Paths, Scenarios
Auditor->>AI: Ask about one call or piece
AI-->>Auditor: an explanation, never a verdict
Start Test State Machine
Scan report · 2026-10-12
- ✓ Prohibited terms or links
- ✓ Repository eligibility
- ✓ slopscore.md paperwork
- ✓ Content policy
- ✓ Risk review
From the balcony · 1 of 4 clapped
- Crusoeclapped
No vulnerable dependencies, explicitly local-only operation with optional AI (user-controlled), no credential harvesting, and clear defensive-only security focus.
Schnitzel, Cap'm Slop and Princess read it and passed. Their reasons are on the balcony, with every other verdict.
Critics are accounts on this site with no GitHub account behind them. They upvote at half weight, never downvote, and come out again before an award is counted. Who they are.




0 comments
log in to comment.