From Proof to Capital. Scientists publish 3-milestone research intents, patrons fund them in USDC, an AI gatekeeper screens publish-time and an AI verifier grades each milestone proof straight from Walrus storage — capital is released tranche-by-tranche from a Solidity escrow on Base, only after a verifier signature.
| App | https://aurasci-ethglobal.vercel.app |
| Backend API | https://aurasci-api-production.up.railway.app (Railway · always-on) |
| Escrow contract (Base Sepolia) | 0x69F15fafEF08a6Fb7fBF28e0F92467a5532F1812 |
| USDC (Base Sepolia) | 0x036CbD53842c5426634e7929541eC2318f3dCF7e |
| Chain | Base Sepolia (84532) |
| AI verifier | Anthropic Claude (claude-haiku-4-5) — real proof grading in llm mode |
| Release signer | Privy server wallet 0xA7084d5e27043F4126C161c8a31eF6D0efDca5Cd — signs EIP-712 releases, policy-restricted |
Sign in with a browser wallet (MetaMask / Rabby) on Base Sepolia; grab test ETH + USDC from the in-app faucet links. Frontend on Vercel, backend (api + chain indexer + AI worker) + Postgres on Railway — both always-on, no local host required. The Canton private rail is local-only (run
daml startin canton/); the Base + Walrus + Claude flow is live.
Grant funding takes 9–18 months and pays in lump sums with no accountability after the cheque clears. AuraSci replaces the cheque with a programmable USDC escrow on Base that releases capital milestone by milestone, gated by AI verification of the proof a scientist submits.
Three primitives:
| Primitive | What it is | Where it lives |
|---|---|---|
| Intent | A research proposal with funding goal + 3 milestones | DB row + on-chain escrow accounting |
| Patronage | A patron's USDC deposit toward an intent | Deposited event → DB row |
| Milestone release | A tranche payout to the scientist after AI verification | Released event signed by the AI verifier key |
No tokens, no governance noise — just verified milestone → released capital.
A parallel Aura social-points layer (backend/src/lib/aura.ts) runs alongside escrow: patrons spend season-budgeted Aura to "boost" intents they believe in, and earn yield Aura when those intents hit milestones. Pure off-chain reputation, no on-chain token.
┌──────────────────┐ ┌────────────────────────────────────┐
│ Next.js front │ HTTPS │ Hono backend (self-hosted) │
│ (Vercel) │◀──────▶│ │
│ - SIWE auth │ │ ┌──────────┐ ┌──────────┐ │
│ - wagmi/viem │ │ │ /api/* │ │ ai-worker│ │
│ - injected w. │ │ │ Hono │ │ (LLM │ │
└────────┬─────────┘ │ └────┬─────┘ │ jobs) │ │
│ writeContract │ │ └────┬─────┘ │
▼ │ ▼ │ │
┌──────────────────┐ │ ┌─────────────────────────────┐ │
│ AuraSciEscrow.sol│◀────────┼──│ Postgres (Prisma client) │ │
│ on Base (USDC) │ │ └─────────────────────────────┘ │
│ - deposit │ │ ▲ │
│ - release(sig) │ │ ┌─────────────┴────┐ │
│ - refund(sig) │ │ │ indexer │ ── viem ──┐ │
│ - adminWithdraw │ │ │ (event watcher) │ │ │
└────────┬─────────┘ │ └──────────────────┘ │ │
│ Deposited/ └──────────────────────────────────│──┘
│ Released/ │
│ Refunded │
└───────────────────────── Base RPC ───────────────────┘
The contract is a vault. The DB is the ledger. Walrus is the archive.
All intent metadata, AI scores, Aura points and activity logs live in
Postgres. Proof artifacts (papers, datasets, figures) are stored as
Walrus blobs — the DB keeps only their blobId +
SHA-256. The contract only knows: balances keyed by intentId, the AI
verifier's public key, and the admin's address.
| Layer | Path | Stack |
|---|---|---|
| Frontend | src/ |
Next.js 14 App Router + wagmi/viem + SIWE wallet login |
| Canton rail | canton/ |
Daml templates for ledger-private patronage (see canton/README.md) |
| Backend API | backend/src/server.ts |
Hono on Node 20 |
| Chain indexer | backend/src/indexer.ts |
viem watchContractEvent |
| AI worker | backend/src/ai-worker.ts |
Anthropic Claude grader + EIP-712 signer |
| Proof storage | backend/src/lib/walrus.ts |
Walrus HTTP publisher/aggregator (blobs certified on Sui) |
| Smart contract | contracts/src/AuraSciEscrow.sol |
Solidity 0.8.24 + OZ |
| DB schema | backend/prisma/schema.prisma |
Postgres via Prisma |
The contract supports four ways to move USDC. Three of them require an EIP-712 signature from the backend's verifier key:
| Function | Caller | What it does |
|---|---|---|
deposit(intentId, amount) |
Anyone (patrons) | Tags USDC against an intentId; emits Deposited. |
release(intentId, to, amount, nonce, reason, sig) |
Scientist | Pays a milestone tranche to the scientist after the AI verifier signs. Contract checks sig recovers to signer. |
refund(intentId, patron, amount, nonce, reason, sig) |
Patron | Claws back a deposit when intent or milestone is rejected. Same signature check. |
adminWithdraw(intentId, to, amount, reason) |
Admin only | Escape hatch / governance — bypasses milestone gating, capped at 100k USDC per tx. |
Admin power is rotatable via a two-step transferAdmin → acceptAdmin
flow. The signer is immutable (set once at deploy) — rotating the
verifier key means deploying a fresh escrow pointed at the new signer
address (which is how the Privy server wallet became the signer).
Why signatures and not direct admin calls? So the AI worker can sign release
authorizations without ever holding withdraw power. If the verifier key
leaks, the worst case is forged milestone releases — admin can rotate the
key and pause via setPaused. The admin key itself stays cold.
The reason field on every milestone release() carries the SHA-256 of
the proof artifact stored on Walrus — so each on-chain Released event is
a permanent commitment to exactly the bytes the AI verifier graded. Fetch
the blob from any Walrus aggregator, hash it, compare with the event: the
whole release trail is independently auditable.
Proof artifacts live on Walrus — decentralized blob storage certified on Sui, chain-agnostic by design (our money rail stays on Base). Three integration points:
| Path | Direction | What happens |
|---|---|---|
submit-proof |
write | Scientist's proof file → PUT {publisher}/v1/blobs → blobId + SHA-256 recorded on the Milestone row |
scoreProof |
read | In llm mode the verifier fetches the blob back from an aggregator, extracts content (PDF/text), and grades it — the release signature only exists because Walrus returned the artifact |
| Frontend | read | "Proof on Walrus ↗" links on milestone cards resolve via the public aggregator; the on-chain reason anchors the same bytes |
Client: backend/src/lib/walrus.ts (~100
lines, plain fetch, no SDK). Generic media uploads go through
POST /api/storage-upload (backend/src/routes/storage.ts).
The public testnet publisher/aggregator are the zero-config defaults — see
docs/WALRUS_INTEGRATION.md for the full
write-up, demo script and design notes.
Two distinct AI jobs, both run by the same worker process (backend/src/ai-worker.ts):
Gatekeeper (at publish time, backend/src/lib/ai.ts)
- 5-agent quorum, each scoring 0–100 from a different angle (feasibility, milestone clarity, scientific rigor, novelty, risk).
- Pass requires BOTH ≥3/5 agents approving AND mean score ≥ 70. Otherwise →
status = "rejected"and the intent is hidden from the market.
Both jobs call Anthropic Claude via the official @anthropic-ai/sdk
(backend/src/lib/ai.ts callClaude). Model is set by
ANTHROPIC_MODEL (default claude-haiku-4-5 — cheap enough to grade every
claim; bump to claude-sonnet-4-6 / claude-opus-4-8 for more rigor).
Verifier (per-milestone proof)
- In
llmmode (setAI_VERIFIER_MODE=llm+ANTHROPIC_API_KEY) the worker fetches the proof artifact back from Walrus, extracts readable content (PDF via unpdf, text formats directly), and grades it against the milestone's stated deliverable. A proof that can't be retrieved never produces a release signature.approvemode remains as a zero-key demo escape hatch. - Verified live: a real milestone proof scores 82/100 and a garbage
proof 5/100 —
npm run --prefix backend exec tsx scripts/verifier-claude-smoke.mts. - On pass, the worker signs an EIP-712 release payload and caches it on the milestone row. The scientist's "Claim" button broadcasts that cached signature — failed broadcasts (cancelled popups, gas issues) become a clean "Resume claim" without re-grading.
The verifier key never touches the frontend. The signed payload + nonce live
in Milestone.releaseSignature / releaseNonce.
The AI verifier is an agent, and its release-signing key is a
non-custodial Privy server wallet — not a raw private key in an env
var. After the verifier grades a proof, the backend signs the EIP-712
Release authorization with the server wallet via
@privy-io/server-auth (createViemAccount →
viem LocalAccount); the escrow's immutable signer is that wallet's
address, so its ECDSA check passes.
A policy engine rule governs the wallet: it may only call
eth_signTypedData_v4 on Base Sepolia (chain_id == 84532). It can
never send a transaction, sign another chain's data, or export its key —
so even a fully-compromised backend can't make the agent move funds, only
sign valid milestone releases the escrow already enforces.
- Switch signer via
RELEASE_SIGNER=local|privy; wallet ids inPRIVY_WALLET_ID/PRIVY_WALLET_ADDRESS. - Setup:
backend/scripts/privy-setup-wallet.mtscreates the wallet + policy;privy-sign-test.mtsproves a Privy-signed release recovers to the wallet. - Optional: setting
NEXT_PUBLIC_PRIVY_APP_ID(frontend) +PRIVY_APP_ID/PRIVY_APP_SECRET(backend) also lights up Privy email / Google / X login alongside the SIWE wallet flow (dual-token auth).
Built for the ETHGlobal Agents · Privy prize (server wallets + policy engine).
Login is Sign-In-With-Ethereum: the browser wallet signs a SIWE message carrying a one-time server nonce, and the backend (backend/src/routes/auth.ts) verifies it and issues a self-signed session JWT whose subject is the wallet address. Every API call carries that JWT; backend/src/lib/auth.ts verifies it and hydrates the local User row.
User.wallet is the user's permanent on-chain identity — the same
address that signed in is the only one every downstream check (intent
ownership, scientist payout, refund eligibility) accepts.
Frontend mirrors the same rule:
useEnsureWalletReady refuses to sign transactions
from any address other than /me.wallet.
- Node 20+
- Postgres 14+ (local or remote)
- Base Sepolia RPC URL (Alchemy, Infura, or public endpoint)
- An Anthropic API key (only needed for real AI scoring in
llmmode; the default verifier mode is"approve")
git clone https://github.com/evayou0915/Ethglobal.git
cd Ethglobal
npm install
cd backend && npm install && cd ..
cd contracts && npm install && cd ..cp .env.example .env.local # frontend (public NEXT_PUBLIC_* only)
cp backend/.env.example backend/.env # backend (DB url, signer key, JWT secret, Anthropic key)Critical vars:
DATABASE_URL— Postgres connection stringJWT_SECRET— session-JWT signing secret (openssl rand -hex 32)SIGNER_PRIVATE_KEY— EIP-712 release signer (a fresh dev key is fine for local)ESCROW_ADDRESS/USDC_ADDRESS— set to your deployed contract + USDC on Base SepoliaNEXT_PUBLIC_CHAIN_ID(84532 for Sepolia)WALRUS_PUBLISHER_URL/WALRUS_AGGREGATOR_URL— default to the public Walrus testnet endpoints; no key neededAI_VERIFIER_MODE—llmfor real Walrus-fetch + LLM grading (needsANTHROPIC_API_KEY),approveto demo the flow without one
cd contracts
npm run compile
npm test # 17 contract tests should pass
npm run deploy:sepolia # outputs the contract addressCopy the address into backend/.env (ESCROW_ADDRESS) and .env.local
(NEXT_PUBLIC_ESCROW_ADDRESS).
cd backend
npm run db:migrate:dev # apply migrations
npm run db:seed # optional demo data
npm run dev # api + indexer + ai-worker in parallel# in the repo root
npm run dev # next dev on :5173Open http://localhost:5173, sign in with your browser wallet (MetaMask / Rabby), grab
test USDC from the Circle Sepolia faucet, and
fund an intent.
Ethglobal/
├── src/ # Next.js frontend
│ ├── app/(app)/ # App Router pages (market, intent, create, scientist, portfolio, leaderboard, ...)
│ ├── client/ # api.ts, hooks.ts, wagmi config
│ ├── components/ # Nav, WalletPanel, SignInModal, Toast, ...
│ └── types/ # shared API DTOs
│
├── backend/ # self-hosted Hono backend
│ ├── src/
│ │ ├── server.ts # HTTP entry point
│ │ ├── indexer.ts # on-chain event → DB
│ │ ├── ai-worker.ts # AiJob queue drainer
│ │ ├── routes/ # /api/intents, /auth, /aura, /admin, /refunds, ...
│ │ └── lib/ # auth (SIWE JWT), ai (LLM quorum), eip712, db, ...
│ ├── prisma/
│ │ ├── schema.prisma
│ │ └── migrations/
│ └── scripts/ # one-off ops scripts (purge, simulate-release, ...)
│
├── contracts/ # Hardhat workspace, decoupled from the Next.js app
│ ├── src/
│ │ ├── AuraSciEscrow.sol
│ │ └── test/MockUSDC.sol
│ ├── test/ # 17 unit tests
│ └── scripts/deploy.ts
│
├── docs/
│ ├── ARCHITECTURE.md
│ ├── BASE_MIGRATION.md # design rationale + phase log
│ └── DEPLOYMENT.md
│
└── public/ # static demo data + landing assets
| Tier | Where | Notes |
|---|---|---|
| Frontend | Vercel | Static + SSR Next.js. NEXT_PUBLIC_* vars only. |
| Backend (API + indexer + ai-worker) | Self-hosted (Render / Fly / EC2 / your VPS) | Long-running Node processes. pm2 or docker-compose recommended — Vercel can't run them. |
| Database | Postgres on the backend host (or managed) | Single source of truth for off-chain state. |
| Contract | Base Sepolia (testnet) / Base mainnet | See docs/DEPLOYMENT.md. |
backend/ecosystem.config.cjs is a pm2 config that starts the three Node
processes (api / indexer / ai-worker) as one unit.
Done
- Full create → AI gatekeeper → publish → fund → submit-proof → AI verify → release → completed flow
- Walrus storage end-to-end: proofs stored as blobs at submit time, AI verifier reads them back through the aggregator before signing, on-chain
Released.reasonanchors the artifact's SHA-256, UI links resolve every proof publicly - Patron refund (per-rejected-milestone + full intent rejection)
- Admin escape hatches:
refundAll,adminWithdraw - Aura social-points: seasons, boosts, milestone-yield distribution
- Leaderboard + activity feed (live indexer events)
- Per-user portfolio with Boost from holdings rows
- Scientist dashboard with milestone trajectory, dueDate display + Overdue flag
- DB-pinned wallet enforcement: no more "active wallet drifts under the contract" bugs
Stubbed by design (v1 demo)
- Verifier default mode is
approve— real grading (AI_VERIFIER_MODE=llm) fetches the proof from Walrus and scores it with an LLM; the default stays permissive so the flow demos without an LLM key. Flip the env var for real verification. - Scientist "approved" is implicit — any user who completes
/onboardis treated as approved; no admin review queue. See src/app/(app)/scientist/page.tsx:44-48.
Not started
- Automated test coverage for backend + frontend (contract tests do exist)
- Verifier-key KMS / HSM storage (production-grade key custody)
- Phase 3 governance (patron-DAO challenges)
- docs/ARCHITECTURE.md — system-level diagram + data flow
- docs/WALRUS_INTEGRATION.md — Walrus storage design, demo script, verification trail
- docs/BASE_MIGRATION.md — design rationale + phase log
- docs/DEPLOYMENT.md — deploy + verify on Base
MIT © 2026 AuraSci
AuraSci — Where breakthroughs find believers, and capital follows proof.