Inspiration
Every year, hundreds of millions of dollars get drained from smart contracts that passed every static scanner and every manual review. The DAO hack. Cream Finance. Beanstalk. The pattern is always the same: the code looked fine on a static read-through, right up until someone found the one execution order nobody tested.
That's the gap we kept staring at. Static analyzers like Slither and Mythril generate mountains of false positives that developers learn to tune out. Manual audits from firms like OpenZeppelin cost $10K–$100K and take weeks — completely out of reach for the solo/small-team DeFi developers who need it most.
So we asked a simpler question: what if, instead of reading a contract for bugs, something actually tried to break it — live, on a real blockchain, before an attacker got the chance?
That's LedgerGuard. "We don't scan your smart contract — we attack it, live, before someone else does."
What it does
LedgerGuard parses a Solidity contract, maps its function-call and state-access logic into a Neo4j dependency graph, spins up a real local Hardhat blockchain node, and deploys the contract onto it. Then three specialized AI agents take over:
- The Attacker drafts and executes real exploit scripts against reentrancy and flash-loan patterns
- The User runs a deterministic baseline of normal transactions to prove the "attack" isn't just a false positive
- The Auditor correlates both results against the graph and produces a risk report
Every verdict — exploited, blocked, or inconclusive — comes from real Mocha test execution on a real EVM, never from an LLM's opinion of what should have happened. The LLM only explains findings in plain English after the blockchain has already decided the outcome. We built that separation deliberately, because we didn't want to build "AI vibes with a security label on it."
How we built it
Six weeks, one deliberate gate at the end of every phase: parser → graph → blockchain provisioning → agents → scoring → polish. We didn't move to the next phase until the previous one had a real, adversarially-verified pass/fail test behind it — not a claim, an actual red-to-green flip we watched happen.
- Parser: tree-sitter walks the Solidity AST, detecting external calls that occur before a related state write — the classic reentrancy ordering signal — not just "does this function make a call."
- Graph: Neo4j stores that logic as
(:Function)-[:MAKES_EXTERNAL_CALL_BEFORE_STATE_UPDATE]->(:StateVariable)edges, keyed bycontract_id = sha256(normalized_source)so re-uploading the same contract never creates duplicates, with a separateaudit_run_idtracking each individual run. - Blockchain: a Python-orchestrated Hardhat node deploys arbitrary contract source dynamically, with a session-scoped fixture that polls for real readiness instead of blindly sleeping.
- Agents: NVIDIA's
nemotron-3-super-120b-a12bdrafts exploit scripts; a globalpipeline_lockserializes every Hardhat/filesystem operation so concurrent audits can't corrupt each other. - UI: a live-streaming agent log and an annotated risk graph, restyled through a shared Stitch-generated design system for visual consistency across screens.
That discipline mattered more than we expected, because almost nothing about this project went smoothly on the first try.
Challenges we ran into
We lost an entire day's work and had to rebuild it from memory. Midway through Week 2, a fully-built and verified Hardhat provisioning layer — the piece that lets our backend actually deploy and control a live blockchain node — turned out to not exist in the real project state, despite having been shown working earlier that same session. We had to rebuild it from scratch. The silver lining: the rebuild came out cleaner than the original, with proper node-lifecycle management and a clean-slate wipe before every deployment. But it taught us a rule we never broke again: commit the moment a gate test passes, never at the end of a session.
We leaked a live API key and had to do emergency surgery on our own git history. A .env file with a real NVIDIA NIM key got committed and pushed before we caught it. We rotated the key immediately, rewrote git history to scrub it, hardened .gitignore, and force-pushed — then had to walk a teammate through re-cloning the entire repo so they wouldn't resurrect the leaked commit.
Six real bugs almost invalidated our single most important piece of evidence. Building the Attacker Agent, we found — one by one, through relentless adversarial testing rather than trusting green checkmarks — a token-truncation bug that silently corrupted LLM output, an Ethers.js version mismatch that made generated exploit scripts fail to compile, a sequencing bug that left the attacker's own setup unset, a missing network flag that caused Hardhat to silently test against the wrong blockchain, and a test-isolation bug where our own baseline agent drained the very funds the attack needed. The worst one: our original exploit assertion (balanceAfter > balanceBefore) would have been satisfied by a single legitimate withdrawal — meaning our safe contract could have falsely reported itself as hacked. We caught it before it ever reached the gate test. If we hadn't, our whole "before/after, vulnerable vs. patched" proof — the single moment meant to convince a judge this is real — would have quietly lied to everyone watching it.
Windows fought us the entire way. A Hardhat compile race condition unique to Windows filesystem timing forced us to redesign our deployment path isolation entirely, separating deploy artifacts from source so two audits running back-to-back couldn't corrupt each other.
Accomplishments that we're proud of
- A genuine red-to-green proof chain at every phase gate, not agent-reported summaries: we deliberately broke working tests, watched them fail for the right reason, then fixed them and watched them pass again — for the parser, the Neo4j writes, the Hardhat deployment, and the exploit agent alike.
- A three-way outcome classification (
EXPLOIT_SUCCEEDED/EXPLOIT_BLOCKED/SCRIPT_ERROR) that makes it structurally impossible for a broken exploit script to masquerade as "the contract is safe" — a distinction most AI security demos don't bother making. - An architecture where the LLM never decides risk — every risk score is computed deterministically in Python from real graph structure and real test outcomes before a single token of NIM explanation is generated, and we verified this holds even when we deliberately broke the NIM call.
- Two full attack classes, six contracts, fully differentiated — reentrancy and flash-loan exploits, each proven against both a vulnerable and a patched version, with the tool correctly identifying every single one.
- Catching every one of the above failures ourselves, before a judge or user ever could, through the same adversarial mindset we built the product to enforce on smart contracts.
What we learned
That the gap between "looks done" and "is actually true" is where every real bug hides — and the only way to close it is to distrust your own green checkmarks until you've tried to break them yourself. That a security tool which can't survive its own team trying to fool it has no business telling anyone else their contract is safe. And that the discipline of gating every phase behind real, adversarial proof — rather than agent-reported summaries — is exactly what turns a weekend AI wrapper into something a judge can actually trust.
What's next for LedgerGuard
Expanding the attack-pattern library beyond reentrancy and flash-loans — oracle manipulation, governance attacks, integer-overflow edge cases. Testnet/mainnet-fork-based auditing using real on-chain state instead of a fresh local node. And a CI/CD integration that runs LedgerGuard as a pre-merge gate, so the next DAO hack gets caught by an agent instead of an attacker.
Built With
- agentic-ai
- blockchain
- chai
- docker
- ethers.js
- fastapi
- graph-database
- hardhat
- langchain
- llama
- llm
- mocha
- multi-agent
- neo4j
- nextjs
- nvidia-nim
- postgresql
- python
- react
- react-force-graph
- solidity
- tailwindcss
- tree-sitter
- typescript
- web3py
Log in or sign up for Devpost to join the conversation.