A Solana MEV transparency platform — sandwich detection, validator scoring, per-victim receipts.
Real-time live feed, counterfactual replay, and validator risk leaderboards across the Solana DEX surface.
Quick Start · What Vigil Shows · Architecture · Detector Integration · Development
Solana MEV — sandwich attacks, frontruns, backruns — extracts hundreds of millions per year from victim swaps. Vigil turns that detection stream into something a user can read: a live feed of attacks pushed in real time, per-victim receipts that show the counterfactual ("what would your swap have returned if no attacker had been present?"), validator risk leaderboards, and protocol-level breakdowns.
Powered by solana-sandwich-detector. The detector is the upstream stream-in / stream-out primitive (Rust). Vigil is the persistence, scoring, and presentation layer downstream — Nest.js BE, Next.js FE, PostgreSQL.
| Surface | What you see |
|---|---|
Dashboard (/dashboard) |
Live attack feed pushed via WebSocket, 24h MEV extraction stats, validator + pool leaderboards, 24h timeseries |
MEV Receipt (/receipt) |
Per-wallet history of MEV damage. Each attack: confidence badge (Verified / Likely / Unverified), evidence chain (detection method, bundle provenance, loss source), and counterfactual replay of the pool state across AMM / Whirlpool / DLMM |
Validator (/validator/[identity]) |
Normalised + trend-adjusted risk score, attack heatmap, attack distribution, telemetry table |
Analytics (/analytics) |
Protocol-level leaderboards, attack-type breakdown, epoch-by-epoch summaries |
# Install workspace deps
pnpm install
# Backend (needs DATABASE_URL + Solana RPC + a built sandwich-detect binary)
pnpm --filter backend dev # listens on :3001
# Frontend
pnpm --filter vigil-frontend dev # opens on :3000The frontend requires NEXT_PUBLIC_API_URL to point at the backend. Without it, every page surfaces an explicit error state — there is no runtime mock fallback.
# Type-check + bundle
pnpm --filter backend build
pnpm --filter vigil-frontend build
# Lint + tests
pnpm --filter vigil-frontend lint
pnpm --filter vigil-frontend test
pnpm --filter backend test
# Prisma
cd backend && npx prisma generate
cd backend && npx prisma migrate dev[Solana RPC / Helius]
↓
[solana-sandwich-detector] ← spawned as a child process; emits JSONL on stdout
↓
[Nest.js Backend] ← parse → DB write (Prisma) → broadcast
↓ REST + WebSocket
[Next.js Frontend] ← dashboard, receipt, validator, analytics
| Layer | Path | Stack |
|---|---|---|
| Frontend | frontend/ |
Next.js 16, React 19, TypeScript strict, Tailwind 4, Chart.js, socket.io-client |
| Backend | backend/ |
NestJS 11, TypeScript strict, Prisma + PostgreSQL, socket.io |
| Detector | external | Rust — see solana-sandwich-detector |
The backend's EventsGateway exposes the /events namespace and emits new_attack to all subscribed clients on every detection. The frontend's useLiveAttacks hook does a REST seed + WS subscription, so the first paint isn't blocked on the WS handshake; subsequent attacks arrive instantly. CORS for both REST and WS is gated by ALLOWED_ORIGINS.
Each receipt carries the detector's reasoning end-to-end:
confidenceLevel(high/medium/low) — composite score from the detector's signal ensembledetectionMethod—header(same-block) /cross_slot_window/jito_bundlebundleProvenance—atomic/spanning/tip_race/organiclossSource— which replay branch produced the loss number (amm_replay/whirlpool_replay/dlmm_replay/pool_amount_out/unenriched)replayTrace— for AMM / Whirlpool / DLMM pools: pre / post-frontrun / post-victim / post-backrun pool snapshots so the receipt UI can render the counterfactual ("if no attack" vs actual)
CLOB venues (Phoenix) emit detections without loss enrichment; the UI surfaces these as Loss not estimated instead of synthetic zeros.
backend/src/detector/detector.service.ts consumes the detector's JSONL stream:
- Spawn
sandwich-detect --followas a child process — path viaDETECTOR_BIN - Read newline-delimited JSON from stdout:
_header,_heartbeat, or attack payloads transform.service.tsnormalises detector snake_case → frontend camelCase, deriveslossSourcefrom which replay branch fired, and builds the FEreplayTrace- Persist via Prisma (
MevAttack,MevReceipt,SandwichDetail,ValidatorStats) EventsGateway.broadcastAttackpushes the FE-shaped payload to live clients
The detector schema is pinned at vigil-v1. Schema breakage requires an explicit version bump on the detector side; transform.service.ts's normalizers absorb minor key drift defensively.
| Var | Purpose |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
HELIUS_API_KEY |
Helius RPC API key |
HELIUS_WS_URL |
Helius WebSocket URL |
PORT |
Backend port (default 3001) |
ALLOWED_ORIGINS |
Comma-separated CORS allow-list (REST + WS). Unset → '*' in dev with a prod warning. |
DETECTOR_BIN |
Path to sandwich-detect binary (built from the detector repo) |
| Var | Purpose |
|---|---|
NEXT_PUBLIC_API_URL |
Backend API base, e.g. http://localhost:3001/api/v1. Required at runtime — there is no mock fallback. |
Internal architecture / convention notes live in:
CLAUDE.md— root: architecture overview, FE↔BE protocolbackend/CLAUDE.md— module map, env vars, conventionsfrontend/CLAUDE.fe.md— folder structure, page-state guidance
MIT