# Project export: mic: Micro prediction markets for what matters

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: What if prediction markets existed for your life? mic is a micro prediction market for localized communities—letting users trade on meaningful events and aggregate shared expectations in real time.
- Devpost: https://devpost.com/software/mic-micro-prediction-markets-for-what-matters
- GitHub: https://github.com/sohinibanerjee247/treehacks-project-2026
- Video: https://www.youtube.com/embed/vFkorp-Y6dA?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Tahira Grewal (6 commits)

## Devpost submission (written by the team)

### Inspiration

Prediction markets are one of the best ways to aggregate information, but most platforms are built for finance professionals. We wanted to make that same “collective intelligence” experience accessible to students in a campus-friendly format. The goal was to let communities ask meaningful yes/no questions, express conviction with bets, and learn from market sentiment over time.

### What it does

mic is a micro prediction market platform for student communities. Users sign in, join channels, and participate in yes/no markets. Markets include clear metadata: title, rules, resolution source, close time, and expected resolution time. Users can place and adjust positions (buy/sell behavior), track sentiment, and discuss markets. Admins can create/manage channels and markets, then resolve outcomes. The app separates active vs resolved markets and keeps role-specific experiences for admins vs non-admins.

### How we built it

We built the product as a full-stack web app with: Next.js (App Router) for frontend + API routes TypeScript for safer, maintainable code Tailwind CSS for a clean, minimal UI Supabase Auth + Postgres for authentication and persistence Architecture highlights: Role-aware access control in API routes (admin vs non-admin actions) Structured schema around profiles, channels, channel_members, markets, bets, and positions Real-time-ish market updates and sentiment visuals on market pages Admin tooling for market lifecycle (creation → monitoring → resolution) UI flows tuned for clarity: channel browse/join, market detail, bet entry, resolve confirmation

### Challenges we ran into

Keeping role-based behavior consistent across UI and API as features expanded. Avoiding confusing market language and simplifying user-facing terminology. Handling schema/implementation drift while iterating (field consistency, endpoint behavior). Balancing live sentiment with stable resolved-market display semantics. Supabase setup issues (env/auth config and permission alignment) during integration.

### Accomplishments we're proud of

Built a coherent end-to-end prediction market experience with strong admin/non-admin separation. Designed a schema-backed workflow that supports market creation, participation, and resolution. Added richer market context (rules, resolution source, timeline fields) for trust and clarity. Delivered a polished UI with consistent yes/no visual semantics and cleaner user flows. Iterated fast on feedback and kept the app buildable and deployable throughout.

### What we learned

Product clarity matters as much as technical correctness in market apps. Rapid prototyping works best when data model and API contracts stay tightly aligned.

### What's next

Add stronger market integrity features (audit logs, resolution notes/history, dispute windows). Add notifications, watchlists, and better discovery across channels and topics. Expand social features (comments ranking, creator profiles, credibility tracking). Add analytics dashboards for market quality, participation, and forecasting performance. Pilot with real campus orgs/classes and iterate based on real user behavior.

## README (from the GitHub repository)

# mic

Minimal frontend (Next.js, React, TypeScript, Tailwind). Backend and database to be added later.

## Run

```bash
npm install
npm run dev
```

Open [http://localhost:3000](http://localhost:3000).

## Structure

- **app/** – Routes (layout, home, login, channels, markets). Add API routes under `app/api/` when you add a backend.
- **components/** – `ui/` (Button, Card, Input), `layout/` (Header, Nav).
- **lib/** – `constants.ts` (ROUTES), `utils.ts` (cn, formatters), `api.ts` (fetch helper for future API calls).
- **types/** – Shared types (User, Channel, Market, Bet).

## Next steps

1. Add API routes in `app/api/` and connect to a database (e.g. Prisma + Postgres on Google Cloud).
2. Wire the frontend to the API via `lib/api.ts` and replace mock data.


## Detected evidence (automated analysis)

Indexed codebase: 61 recognized source files, 136 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (64 of 64)

```
.gitignore
app/admin/create-channel/CreateChannelForm.tsx
app/admin/create-channel/page.tsx
app/admin/create-market/CreateMarketForm.tsx
app/admin/create-market/page.tsx
app/api/ai/generate-market/route.ts
app/api/channels/[id]/join/route.ts
app/api/channels/[id]/leave/route.ts
app/api/channels/[id]/route.ts
app/api/channels/route.ts
app/api/markets/[id]/bet/route.ts
app/api/markets/[id]/comments/route.ts
app/api/markets/[id]/resolve/route.ts
app/api/markets/route.ts
app/api/orders/[id]/route.ts
app/auth/callback/route.ts
app/channels/[id]/page.tsx
app/channels/[id]/RenameChannelButton.tsx
app/channels/ChannelSearch.tsx
app/channels/JoinButton.tsx
app/channels/page.tsx
app/dashboard/BetHistory.tsx
app/dashboard/page.tsx
app/dashboard/PendingOrders.tsx
app/dashboard/SuccessBanner.tsx
app/globals.css
app/layout.tsx
app/login/LoginForm.tsx
app/login/page.tsx
app/markets/[id]/BetForm.tsx
app/markets/[id]/DiscussionThread.tsx
app/markets/[id]/MarketLive.tsx
app/markets/[id]/page.tsx
app/markets/[id]/ResolveButtons.tsx
app/markets/[id]/SentimentChart.tsx
app/page.tsx
components/home/MarketTicker.tsx
components/layout/Header.tsx
components/layout/index.ts
components/layout/UserMenu.tsx
components/ui/Button.tsx
components/ui/Card.tsx
components/ui/index.ts
components/ui/Input.tsx
FIX_PERMISSIONS.md
lib/admin.ts
lib/api.ts
lib/constants.ts
lib/supabase/admin.ts
lib/supabase/client.ts
lib/supabase/middleware.ts
lib/supabase/server.ts
lib/utils.ts
middleware.ts
next-env.d.ts
next.config.js
package.json
postcss.config.js
README_ADMIN.md
README.md
supabase/migrations/add_market_comments_and_realtime.sql
tailwind.config.ts
tsconfig.json
types/index.ts
```

### Dependencies

- package.json: @supabase/ssr@^0.8.0, @supabase/supabase-js@^2.78.0, @types/node@^20, @types/react@^18, @types/react-dom@^18, autoprefixer@^10.4.20, next@14.2.15, openai@^6.22.0, postcss@^8.4.49, react@^18.3.1, react-dom@^18.3.1, recharts@^3.7.0, tailwindcss@^3.4.15, typescript@^5

### Recent commits (newest first)

- Logo fixed, market resolution closure addressed
- fixed resolve percentage
- Percentage update, discussion thread, etc.
- Order book matching logic added
- removed pool amt from market pg
- fixed ui to display correct info
- User view updates
- change admin pg layout
- Login updated
- basic admin pg to create/resolve
- Frontend revamp, supabase auth integration
- basic frontend with dummy vars
- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### README_ADMIN.md

```markdown
# Admin Access

Your email **sohinibanerjee247@gmail.com** is automatically recognized as admin in the code. No database setup needed.

## Admin features

When logged in as admin, you can:

1. **Create channels** - Click your profile menu → "Create channel"
2. **Create markets** - Click your profile menu → "Create market"
3. **Resolve markets** - Go to any open market → "Resolve Yes/No" buttons appear at the bottom

All admin features are available through the UI.

```

### FIX_PERMISSIONS.md

```markdown
# Fix Admin & User Permissions

Run this SQL in your Supabase SQL Editor to fix the permissions:

```sql
-- =========================
-- DROP OLD POLICIES
-- =========================
DROP POLICY IF EXISTS "admins manage channels" ON channels;
DROP POLICY IF EXISTS "admins manage markets" ON markets;
DROP POLICY IF EXISTS "read own memberships" ON channel_members;
DROP POLICY IF EXISTS "read markets in joined channel" ON markets;


-- =========================
-- NEW POLICIES: CHANNELS
-- =========================

-- Anyone can read channels
CREATE POLICY "read channels"
ON channels FOR SELECT
USING (true);

-- Only check admin in API (policies allow all authenticated writes)
CREATE POLICY "authenticated users manage channels"
ON channels FOR ALL
USING (auth.uid() IS NOT NULL)
WITH CHECK (auth.uid() IS NOT NULL);


-- =========================
-- NEW POLICIES: CHANNEL MEMBERS
-- =========================

-- Users can see all memberships (to check who's in a channel)
CREATE POLICY "read all memberships"
ON channel_members FOR SELECT
USING (true);

-- Users can join channels
CREATE POLICY "users can join channels"
ON channel_members FOR INSERT
WITH CHECK (auth.uid() = user_id);

-- Users can leave channels
CREATE POLICY "users can leave channels"
ON channel_members FOR DELETE
USING (auth.uid() = user_id);


-- =========================
-- NEW POLICIES: MARKETS
-- =========================

-- Anyone can read all markets
CREATE POLICY "read all markets"
ON markets FOR SELECT
USING (true);

-- Only check admin in API (policies allow all authenticated writes)
CREATE POLICY "authenticated users manage markets"
ON markets FOR ALL
USING (auth.uid() IS NOT NULL)
WITH CHECK (auth.uid() IS NOT NULL);


-- =========================
-- POLICIES: BETS (unchanged)
-- =========================
-- These are already correct - users insert/read their own bets
```

**What this does:**

1. **Channels & Markets:**
   - Everyone can read
   - API checks if you're admin (via email) before allowing create/update/delete
   - Database just checks you're logged in

2. **Channel Members (joining):**
   - Users can join any channel
   - Users can leave channels they're in

3. **Bets:**
   - Already correct - users can only bet and see their own bets

**After running this:**
- You can create channels and markets (admin email check is in the API)
- Normal users can join channels and place bets
- No need to set `role = 'admin'` in the database anymore

```

### package.json

```
{
  "name": "micro-prediction-market",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@supabase/ssr": "^0.8.0",
    "@supabase/supabase-js": "^2.78.0",
    "next": "14.2.15",
    "openai": "^6.22.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "recharts": "^3.7.0"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.4.20",
    "postcss": "^8.4.49",
    "tailwindcss": "^3.4.15",
    "typescript": "^5"
  }
}

```

### types/index.ts

```typescript
/** Shared types. Extend when you add API/DB. */

export type User = {
  id: string;
  name: string;
  balanceCents: number;
};

export type Channel = {
  id: string;
  name: string;
  memberCount?: number;
  joined?: boolean;
};

export type MarketStatus = "open" | "resolved_yes" | "resolved_no";

export type Market = {
  id: string;
  channelId: string;
  title: string;
  description?: string | null;
  status: MarketStatus;
  yesOdds?: number;
  noOdds?: number;
};

export type Bet = {
  id: string;
  marketId: string;
  userId: string;
  side: "yes" | "no";
  amountCents: number;
};

```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Header from "@/components/layout/Header";

const inter = Inter({
  subsets: ["latin"],
  variable: "--font-sans",
  display: "swap",
});

export const metadata: Metadata = {
  title: "mic",
  description: "Bet on stuff with play money",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" className={inter.variable}>
      <body className="min-h-screen font-sans bg-[#0a0a0a] text-zinc-100 antialiased">
        <div className="mx-auto max-w-6xl px-6 py-8 sm:px-8 lg:py-12">
          <Header />
          <main className="mt-12 lg:mt-16">{children}</main>
        </div>
      </body>
    </html>
  );
}

```

### app/page.tsx

```typescript
import Link from "next/link";
import { ROUTES } from "@/lib/constants";
import MarketTicker from "@/components/home/MarketTicker";

export default function HomePage() {
  return (
    <div className="grid gap-12 lg:grid-cols-2 lg:gap-16">
      {/* Left: Animated ticker */}
      <div className="order-2 lg:order-1">
        <MarketTicker />
      </div>

      {/* Right: Hero content */}
      <div className="order-1 flex flex-col justify-center lg:order-2">
        <h1 className="text-5xl font-bold tracking-tight text-zinc-100 sm:text-6xl lg:text-7xl">
          Bet anytime.
          <br />
          anywhere.
        </h1>
        <p className="mt-6 text-lg text-zinc-400 leading-relaxed">
          Predict Northwestern outcomes with play money. Start with $1,000.
        </p>
        <div className="mt-10 flex flex-col sm:flex-row gap-4">
          <Link
            href={ROUTES.LOGIN}
            className="inline-flex items-center justify-center rounded-xl bg-accent px-8 py-4 text-base font-semibold text-[#0a0a0a] shadow-lg shadow-accent/20 hover:bg-accent-hover hover:shadow-xl hover:shadow-accent/30 transition-all duration-200"
          >
            Get Started
          </Link>
          <Link
            href={ROUTES.CHANNELS}
            className="inline-flex items-center justify-center rounded-xl border border-zinc-700 px-8 py-4 text-base font-medium text-zinc-300 hover:border-zinc-600 hover:bg-zinc-900/50 transition-all duration-200"
          >
            Browse Markets
          </Link>
        </div>
      </div>
    </div>
  );
}

```

### components/layout/index.ts

```typescript
export { default as Header } from "./Header";

```

### components/ui/index.ts

```typescript
export { default as Button } from "./Button";
export { default as Card } from "./Card";
export { default as Input } from "./Input";

```

### lib/supabase/server.ts

```typescript
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";

export async function createClient() {
  const cookieStore = await cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            );
          } catch {
            // Called from Server Component; middleware will refresh sessions
          }
        },
      },
    }
  );
}

```

### app/login/page.tsx

```typescript
import Link from "next/link";
import LoginForm from "./LoginForm";

export default function LoginPage() {
  return (
    <div className="mx-auto max-w-md">
      <div className="rounded-2xl border border-zinc-800/80 bg-zinc-900/30 p-8 shadow-xl shadow-black/20">
        <h2 className="text-2xl font-semibold tracking-tight text-zinc-100">
          Welcome back
        </h2>
        <p className="mt-2 text-[15px] text-zinc-500 leading-relaxed">
          Sign in or create an account. You&apos;ll get $1000 in play money to
          start.
        </p>
        <div className="mt-8">
          <LoginForm />
        </div>
      </div>
      <p className="mt-8 text-center">
        <Link
          href="/"
          className="text-sm text-zinc-500 hover:text-zinc-300 transition-colors"
        >
          ← Back to home
        </Link>
      </p>
    </div>
  );
}

```

[51 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]