"The AI gave your project a 72. But who scored the AI?"
More and more competitions use AI to judge submissions. But participants are left wondering: Were the judging criteria really what they claimed? Could the organizer have tweaked the prompt after seeing the results?
ClearBox makes AI judging verifiable. Every score is cryptographically signed and recorded on the Sui blockchain. Anyone can independently prove that the judging followed the published criteria — no trust required.
Organizer (Sui Wallet) ClearBox Sui Testnet
──────────────────── ──────── ───────────
│ │ │
│ Connect wallet │ │
│ Define criteria + model │ │
│ Sign create_competition │ │
│ ───────────────────────────────────────────────────────────► │
│ │ │
│ Add repos + Gemini key │ │
│ ─────────────────────────► │ │
│ │ ┌─────────────────────────┐ │
│ Sign update_status(1) │ │ Isolated Judging TEE │ │
│ ───────────────────────────│──│──────────────────────► │ │
│ │ │ 1. Fetch README │ │
│ │ │ 2. Score with Gemini │ │
│ │ │ 3. Sign with Ed25519 │ │
│ │ └─────────────────────────┘ │
│ │ │
│ │ Submit signed scores on-chain │
│ │ ──────────────────────────────► │
│ │ (signature verified │
│ Sign update_status(2) │ by Move contract) │
│ ───────────────────────────────────────────────────────────► │
│ │ │
Participant (no wallet) │ │
─────────────────────── │ │
│ Browse competitions │ │
│ View scores + verify │ │
│ ─────────────────────────► │ Read on-chain data │
│ │ ──────────────────────────────► │
What gets verified on-chain:
| Check | How |
|---|---|
| Prompt integrity | SHA-256 hash of the judging prompt is stored on-chain. Hash the published prompt yourself — it must match. |
| Score authenticity | Every score is Ed25519 signed by the judging service's ephemeral keypair. |
| Tamper-proof | The Move contract verifies the signature before accepting any result. Invalid signatures are rejected. |
In production, the judging service runs inside a Nautilus Nitro Enclave (Trusted Execution Environment) on Sui. The enclave generates its keypair in hardware-isolated memory, and AWS provides a cryptographic attestation proving exactly what code is running — down to the byte.
For this demo, we implement the same verification pattern with a simulated TEE. The architecture is identical; swapping to a real Nautilus enclave requires zero code changes.
┌─────────────────────┐ ┌──────────────────────┐ ┌─────────────────┐
│ React SPA │◄────►│ Web Backend (API) │─────►│ Sui Testnet │
│ Tailwind CSS │ │ Express/TypeScript │ │ Move Contracts │
│ @mysten/dapp-kit │──────│──────────────────────────────►│ │
│ (wallet signing) │ └──────────┬─────────────┘ └─────────────────┘
└─────────────────────┘ │
▼
┌──────────────────────┐
│ Judging Service │
│ (Simulated TEE) │
│ │
│ Ephemeral Ed25519 │
│ keypair (in-memory) │
│ ──────────────── │
│ Fetches README │
│ Calls Gemini API │
│ Signs every result │
└──────────────────────┘
Two views:
- Organizer — connects Sui wallet, creates competitions on-chain, adds repos, provides their own Gemini API key, triggers judging
- Participant — browses competitions and verifies scores publicly (no wallet needed)
| Component | Tech |
|---|---|
| Smart Contracts | Sui Move 2024 |
| Judging Service | TypeScript, Express, @noble/ed25519, Gemini API |
| Web Backend | TypeScript, Express, @mysten/sui SDK |
| Frontend | React, Vite, Tailwind CSS, @mysten/dapp-kit |
| Monorepo | pnpm workspaces |
- Node.js 18+
- pnpm 8+
- Sui CLI
- A Sui Testnet wallet with SUI (use
sui client faucet) - A Sui wallet browser extension (e.g., Sui Wallet)
- A Gemini API key (entered per session, not stored)
pnpm install./scripts/deploy.shThis publishes the contract to Sui Testnet and prints a PACKAGE_ID.
cp .env.example .envFill in:
SUI_PRIVATE_KEY=your-base64-encoded-sui-private-key
PACKAGE_ID=0x-from-deploy-script
SUI_PRIVATE_KEY — This is the backend server's Sui private key, used only for submitting judging results on-chain (submit_result). The Move contract verifies the judging service's Ed25519 signature before accepting results, and the backend is the transaction sender. This key is not used for creating competitions or updating status — those are signed by the organizer's own wallet in the browser.
To export your private key:
# List your addresses
sui client addresses
# Export the private key (base64-encoded)
sui keytool export --key-identity <your-address> --jsonCopy the key field (the base64 string starting with suiprivkey...) into SUI_PRIVATE_KEY.
Make sure this address has testnet SUI for gas fees (sui client faucet).
Note: GEMINI_API_KEY is no longer needed in the server .env — organizers provide their own API key through the UI when they trigger judging.
pnpm devThis starts all three services in parallel:
- Frontend — http://localhost:5173
- Backend — http://localhost:3000
- Judging Service — http://localhost:3001
As an organizer:
- Open http://localhost:5173
- Click I'm an Organizer
- Connect your Sui wallet
- Click + New Competition, enter a name, judging criteria, and select a model
- Click Create Competition (signs a transaction with your wallet)
- Add GitHub repository URLs on the manage page
- Enter your Gemini API key and click Start Judging (signs status update transactions)
- View the scores once judging completes
As a participant:
- Open http://localhost:5173
- Click I'm a Participant
- Browse all competitions with organizer addresses and status
- Click a completed competition to view scores
- Click Verify on Chain to inspect the on-chain attestation
clearbox/
├── contracts/ # Sui Move smart contracts
│ └── sources/
│ ├── clearbox.move # Competition, Submission, signature verification
│ └── clearbox_tests.move # Move unit tests
├── judging-service/ # Simulated TEE — isolated scoring + signing
│ └── src/
│ ├── crypto.ts # Ed25519 keypair, signing, payload hashing
│ ├── github.ts # GitHub API README fetcher
│ ├── gemini.ts # Gemini API scoring (accepts API key per request)
│ └── index.ts # Express server
├── backend/ # API server — orchestration + Sui interaction
│ └── src/
│ ├── store.ts # In-memory competition store
│ ├── sui.ts # Sui transaction building (submit_result only)
│ └── index.ts # Express server (registration, repos, judging)
├── frontend/ # React SPA with wallet integration
│ └── src/
│ ├── layouts/ # OrganizerLayout, ParticipantLayout
│ ├── pages/ # LandingPage, OrganizerDashboard, CreateCompetition,
│ │ # ManageCompetition, CompetitionDirectory, CompetitionResults
│ ├── components/ # ScoreCard, VerifyPanel, WalletGuard, CompetitionCard
│ ├── providers.tsx # dapp-kit + React Query provider stack
│ ├── config.ts # Shared constants (PACKAGE_ID)
│ └── api.ts # API client + TypeScript interfaces
└── scripts/
└── deploy.sh # One-click contract deployment
Every result stored on Sui contains:
prompt_hash— SHA-256 of the exact judging promptenclave_pubkey— the judging service's Ed25519 public keysigned_payload— the canonical JSON that was signed (includes scores, prompt hash, timestamp)signature— Ed25519 signature, verified bysui::ed25519::ed25519_verifybefore storage
The Move contract rejects any submission with an invalid signature. If the judging service is tampered with, the signature won't match and the results cannot be recorded.
- Sui — Layer 1 blockchain
- Sui Move — Smart contract language
- Nautilus — TEE framework for Sui (production path)
- Gemini — AI model for scoring
- React + Vite + Tailwind CSS — Frontend
- Express — Backend API
- @noble/ed25519 — Cryptographic signing