The esports layer for Gigling Racing. GigaPrix turns isolated Gigaverse races (up to 8 pets each) into organized championships and leagues: a host creates a competition, players join with their wallet, the field auto-seeds into groups of 8, each group runs a real on-chain race, and the results are read back from the Abstract chain (chainId 2741) to auto-advance the stages until one champion remains.
It orchestrates races — it never creates them itself. Players race on Gigaverse; GigaPrix links the on-chain race to a heat, verifies it, and advances the bracket / standings.
- Two formats — Knockout Championship (staged groups → grand final) and League (round-robin matchdays, ranked by points). Coming soon: Time Trial, Swiss, Guild Cup.
- Race types — Dash (500m), Sprint (1200m), Marathon (2400m), Grand Prix (3000m).
- Wallet auth (SIWE) — connect an Abstract Global Wallet, then sign in once; the server verifies the signature (EOA + smart-account EIP-1271/6492) and never trusts a client-supplied address.
- Host & co-host tools — invite links, bulk-add wallets, whitelist, open/close registration, per-stage qualifier edits, timezone-aware scheduling, manual result overrides, reopen/simulate heats, test-mode auto-run, co-hosts.
- Live everything — link an on-chain race → spectate it embedded → fetch the result → qualifiers advance, with the bracket/standings updating in real time.
- Profiles — display name, Discord, Twitter, bio, owned giglings (rarity + ELO), competition history.
- Host dashboard with analytics (competitions, live, completed, total racers, champions crowned).
- Private "Creator Cups" — unlisted, invite-only competitions.
- Create a competition (format, race type, size, rules).
- Players connect + sign in, then join with their wallet (they pick a gigling at race time).
- The host starts it → the field auto-seeds into groups of 8.
- For each group: Create race on Gigaverse (same wallet) → Fetch race ID (auto-detects it) or paste it → Spectate → Fetch result.
- The top N qualify and advance automatically, stage by stage, to a final and a champion. Standings publish on completion (live for leagues).
- Host — creates and runs a competition (full control).
- Co-host — a wallet the host grants manager rights to (everything except cancel / managing co-hosts).
- Player — joins with a wallet, races their gigling.
- Spectator — anyone; watches live and browses results.
- Next.js 16 (App Router, RSC) · React 19 · TypeScript
- Tailwind v4 (CSS theme in
src/app/globals.css) - Prisma 6 · PostgreSQL (Neon in prod — IPv4, Vercel-friendly)
- viem (Abstract reads + signature verification) · wagmi + Abstract Global Wallet · pusher-js (GigaSocket lobby)
- TanStack Query · zod · Vitest
GigaPrix doesn't own any race data — it reads everything from Gigaverse over two channels and stores only the tournament structure (who's in which heat) itself.
Base URL: GIGAVERSE_API_BASE (default https://gigaverse.io/api/racing). All
calls live in src/lib/gigaverse/api.ts, go through one apiFetch helper
(6s timeout, 1 retry), and fall back to mock data when
NEXT_PUBLIC_USE_MOCK_GIGAVERSE="true".
| Function | Endpoint | Used by | Purpose |
|---|---|---|---|
fetchGlobalStats |
GET /stats |
landing (app/page.tsx) |
Hero stat cards (total races, entries, racers, resolved) |
fetchGiglingsByIds |
GET /pets?ids=… |
tournament + standings + match pages, api/giglings |
Batch-enrich entrants: name, image, rarity, faction, ELO, record |
fetchPlayerGiglings |
GET /races/{address} → GET /pets?ids=… |
profile page, api/giglings/[address], lib/users.ts (ELO sync) |
A wallet's racing pets, derived from races it entered |
fetchWalletRaces |
GET /races + GET /races/{address} |
actions/match.ts (detectLatestRaceAction) |
Auto-detect the race a host just created on Gigaverse so it can be linked to a heat |
fetchPetStats |
GET /pets/{id}/stats |
(helper) | Per-pet race count / wins / recent finishes |
fetchPlayerRaces |
GET /races/{address} |
(helper) | Recent races for a wallet |
fetchEloLeaderboard |
GET /leaderboard/elo |
(helper) | ELO leaderboard |
fetchRace |
GET /race/{id} |
(helper) | Single race lookup |
Result resolution is read straight from the contract on Abstract
(chainId 2741) via viem, not the REST API — it's the source of truth for
finishing order. Address = NEXT_PUBLIC_PET_RACING_ADDRESS, RPC =
NEXT_PUBLIC_ABSTRACT_RPC. Helpers in src/lib/gigaverse/contracts.ts:
| Read | Contract fn | Used by | Purpose |
|---|---|---|---|
getRacePhase |
getRacePhase |
lib/race/link-service.ts, api/cron/sync-races |
Is the linked race resolved yet? (phase 3 = done) |
getRacePets |
getRacePets |
lib/race/result-processor.ts |
Which pets ran |
getRaceFinalRanking |
getRaceFinalRanking |
lib/race/result-processor.ts |
Finishing order → who advances |
getRaceFinishTimes |
getRaceFinishTimes |
lib/race/result-processor.ts |
Finish times for telemetry/standings |
When a heat's linked race reaches phase 3, the cron (or the in-app Fetch result button) reads the ranking on-chain, advances qualifiers, and updates standings.
SIWE sign-in (api/auth/verify) verifies the wallet signature with viem's
publicClient.verifyMessage, which supports EIP-1271/6492 smart accounts (so
Abstract Global Wallet works, not just EOAs).
npm install1. Configure .env (single file at the repo root; .env* is gitignored):
| Var | Required | Notes |
|---|---|---|
DATABASE_URL / DIRECT_URL |
✅ | Supabase Postgres connection strings |
AUTH_SECRET |
✅ | Signs wallet session cookies (long random string) |
CRON_SECRET |
✅ in prod | Protects the race-sync cron |
NEXT_PUBLIC_ABSTRACT_RPC |
✅ | https://api.mainnet.abs.xyz |
NEXT_PUBLIC_PET_RACING_ADDRESS |
✅ | Gigaverse PetRacingSystem |
GIGAVERSE_API_BASE |
✅ | https://gigaverse.io/api/racing |
NEXT_PUBLIC_PUSHER_KEY / _CLUSTER |
optional | Live lobby |
NEXT_PUBLIC_SENTRY_DSN |
optional | Remote error reporting |
NEXT_PUBLIC_SITE_URL |
optional | Absolute URL for OG/social cards |
NEXT_PUBLIC_USE_MOCK_GIGAVERSE |
optional | "true" to run without the live API |
2. Database:
npm run db:push # sync schema to the DB
# Demo data is optional and DESTRUCTIVE (wipes the DB), so it's guarded:
SEED_RESET=true npm run db:seed3. Run:
npm run dev| Script | Description |
|---|---|
npm run dev |
Dev server |
npm run build |
prisma generate + production build |
npm run start |
Production server |
npm run typecheck |
tsc --noEmit |
npm run lint |
ESLint |
npm run test / test:watch |
Vitest (bracket/league/permissions logic) |
npm run db:push / db:migrate |
Apply schema |
npm run db:seed |
Seed demo data (needs SEED_RESET=true to wipe) |
npm run db:clear-demo |
Remove only the synthetic demo rows |
npm run db:studio |
Prisma Studio |
src/
app/ routes (landing, /tournaments, /dashboard, /profile, api/*)
api/auth/ SIWE: nonce / verify / session / logout
api/cron/ race-sync cron
actions/ server actions (tournament, match, profile) — session-gated
components/ UI (tournament/, profile/, shared/, ui/)
hooks/ useWallet, useAuth, useTournament, useGiglings
lib/
auth/ wallet session (HMAC cookie) + sign-in message
tournament/ seeding, advancement, standings (Knockout + League)
race/ on-chain race linking + result processing
gigaverse/ contract reads (viem) + REST API client
permissions.ts host / co-host checks
rate-limit.ts in-memory limiter
prisma/ schema, seed, clear-demo
Wallet auth is signature-based (SIWE) — every server action derives the caller from a verified session cookie, not from client arguments. Plus rate limiting, cron hardening, and HTTP security headers. See SECURITY.md.
Push to GitHub and import the repo in Vercel; set the env vars and the cron in
vercel.json runs automatically. Full checklist in DEPLOY.md.
- Formats: Time Trial, Swiss, Guild Cup (team scoring) — arrive with unlimited racing.
- Prizes / payouts, notifications, deeper analytics, and a full a11y pass.
Note on on-chain reads: the live
getRacestruct differs from the reference ABI, so status is read viagetRacePhaseand results via the typed array reads (getRaceFinalRanking,getRaceFinishTimes,getPetOwnerInRace,getRacePets) — all verified against mainnet.