# Project export: icebreakers

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: Cal Hacks 12.0
- Tagline: Break the ice, make new friends, and earn SUI for doing it.
- Devpost: https://devpost.com/software/icebreakers-agzmov
- GitHub: https://github.com/Pravesh-mansharamani/icebreaker
- Demo: https://icebreaker-eta.vercel.app/
- Video: https://www.youtube.com/embed/DLgOAS4IV-4?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Pravesh Mansharamani (10 commits)

## Devpost submission (written by the team)

### Inspiration

Meeting people at hackathons can be awkward — we wanted to make it fun, rewarding, and on-chain.

### What it does

Gamifies networking: complete quests, share photos, and earn SUI rewards for meeting new people.

### How we built it

Built with Next.js, Sui SDK, Move contracts, and IPFS for storing photos. Integrated Slush Wallets for smooth sponsored transactions and future reward automation.

### Challenges we ran into

Originally planned to use Walrus, but it required too many WAL tokens and the faucet was unreliable — we couldn’t upload even one image. Switched to IPFS for speed and reliability. Accomplishments we're proud of Deployed a working on-chain reward pool, integrated Slush Wallets for sponsor-backed payouts, and made every user activity verifiable on-chain.

### What we learned

How to build with the Sui Move framework, use sponsored transactions, and design on-chain UX that feels like a normal social app.

### What's next

for Icebreaker Enable full reward verification, let sponsors create dynamic quests, move to mainnet, and power networking at real hackathons with on-chain social proofs.

## README (from the GitHub repository)

# Icebreaker 🧊

**Break the ice, make new friends, and earn SUI for doing it.**

Icebreaker is a social app where you can connect with others at events. Share photos, complete fun quests, and earn SUI rewards for every interaction!

## What is Icebreaker?

Icebreaker helps you meet new people at events through photo sharing, quests, and challenges. Every time you participate, you earn SUI rewards (0.003 SUI per activity). It's gamified networking that makes meeting new people fun and rewarding!

## 🚀 Features

- **Photo Sharing**: Take and share photos with the community
- **Quests & Challenges**: Complete tasks to earn points and SUI
- **Real Rewards**: Get 0.003 SUI for every upload (paid from our reward pool)
- **Live Escrow Pool Balance**: See exactly how much SUI is left in the reward pool
- **Leaderboards**: Track your points and level up

## 💰 Deployed Contracts & Pool Information

### Escrow Pool Contract
- **Package ID**: [`0xcc55d1b3e2b8f5ad42aeba278ac680fdcc7d0ccb602971346e60177ca7d9b761`](https://suiexplorer.com/object/0xcc55d1b3e2b8f5ad42aeba278ac680fdcc7d0ccb602971346e60177ca7d9b761?network=testnet)
- **Network**: Sui Testnet
- **Type**: Shared Object (Pool for reward distribution)

### Reward Pool Object
- **Object ID**: [`0x94bd6487e22a711507bd85144aca335e34996962065bbe9189c7aa7ac4c7447e`](https://suiexplorer.com/object/0x94bd6487e22a711507bd85144aca335e34996962065bbe9189c7aa7ac4c7447e?network=testnet)
- **Current Balance**: Check the live balance in the app navigation
- **Purpose**: Holds SUI for automatic rewards (0.003 SUI per upload)

### Sponsor Wallet
- **Address**: `0x29c9ea26349d4fcd9cc41620fb29d5c7bcc1da93577887fb1981681212cf66d1`
- **Purpose**: Funded wallet used to initialize and maintain the pool

## 🔄 How to Add More Money to the Pool

You can add more SUI to the reward pool using one of these methods:

### Option 1: Using Sui CLI

```bash
# Call the deposit_shared function directly
sui client call \
  --package 0xcc55d1b3e2b8f5ad42aeba278ac680fdcc7d0ccb602971346e60177ca7d9b761 \
  --module escrow_pool \
  --function deposit_shared \
  --args 0x94bd6487e22a711507bd85144aca335e34996962065bbe9189c7aa7ac4c7447e <YOUR_COIN_OBJECT_ID> \
  --gas-budget 10000000
```

### Option 2: Send SUI to the Pool Object

1. Send SUI coins to the pool object address using any Sui wallet
2. The pool will automatically accept and store them

## 🏗️ Architecture

- **Frontend**: Next.js 15 with TypeScript
- **Blockchain**: Sui Network (Testnet)
- **Smart Contracts**: Move language
- **Rewards**: Automated via escrow pool smart contract
- **Storage**: IPFS

## 📦 Project Structure

```
icebreaker/
├── contracts/              # Sui Move smart contracts
│   ├── sources/
│   │   └── escrow_pool.move
│   └── Move.toml
├── app/                   # Next.js app directory
│   ├── api/
│   │   ├── transfer-sui/  # Reward distribution endpoint
│   │   └── pool-balance/  # Pool balance checker
│   ├── upload/            # Photo upload page
│   └── ...
├── components/            # React components
└── scripts/               # Deployment scripts
```

## 🚀 Getting Started

### Prerequisites

- Node.js 18+
- pnpm
- Sui CLI (for deployment)

### Setup

1. **Clone and install**:
```bash
git clone <repo>
cd icebreaker
pnpm install
```

2. **Configure environment**:
```bash
cp .env.example .env
# Add your sponsor wallet and pool IDs
```

3. **Run the app**:
```bash
pnpm dev
```

Visit `http://localhost:3000` and start earning SUI!

## 🎯 How Rewards Work

1. User uploads a photo/participates in activity
2. App calls the escrow pool smart contract
3. Contract withdraws 0.003 SUI from the pool
4. SUI is transferred to the user's wallet
5. Pool balance updates in real-time
6. Everyone can see how much SUI is left

## 🔒 Security

- Pool is password-protected
- Only authorized withdrawals via sponsor wallet
- All transactions on-chain and transparent
- No private keys stored in the app

## 🤝 Built For

This project was built for a hackathon as a proof of concept for:
- Gamified social networking
- Automated micro-rewards via smart contracts
- Transparent reward distribution
- On-chain pool management



## Detected evidence (automated analysis)

Indexed codebase: 89 recognized source files, 480 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (101 of 101)

```
.env.example
.gitignore
app/api/auth/callback/route.ts
app/api/auth/logout/route.ts
app/api/auth/status/route.ts
app/api/pool-balance/route.ts
app/api/transfer-sui/route.ts
app/auth/page.tsx
app/feed/page.tsx
app/globals.css
app/layout.tsx
app/page.tsx
app/profile/page.tsx
app/providers.tsx
app/quests/page.tsx
app/share/page.tsx
app/sw.ts
app/upload/loading.tsx
app/upload/page.tsx
components.json
components/AutoConnectSlush.tsx
components/AutoConnectSlushWrapper.tsx
components/cyber-navigation.tsx
components/navigation.tsx
components/points-animation.tsx
components/theme-provider.tsx
components/ui/accordion.tsx
components/ui/alert-dialog.tsx
components/ui/alert.tsx
components/ui/aspect-ratio.tsx
components/ui/avatar.tsx
components/ui/badge.tsx
components/ui/breadcrumb.tsx
components/ui/button.tsx
components/ui/calendar.tsx
components/ui/card.tsx
components/ui/carousel.tsx
components/ui/chart.tsx
components/ui/checkbox.tsx
components/ui/collapsible.tsx
components/ui/command.tsx
components/ui/context-menu.tsx
components/ui/dialog.tsx
components/ui/drawer.tsx
components/ui/dropdown-menu.tsx
components/ui/form.tsx
components/ui/hover-card.tsx
components/ui/input-otp.tsx
components/ui/input.tsx
components/ui/label.tsx
components/ui/menubar.tsx
components/ui/navigation-menu.tsx
components/ui/pagination.tsx
components/ui/popover.tsx
components/ui/progress.tsx
components/ui/radio-group.tsx
components/ui/resizable.tsx
components/ui/scroll-area.tsx
components/ui/select.tsx
components/ui/separator.tsx
components/ui/sheet.tsx
components/ui/sidebar.tsx
components/ui/skeleton.tsx
components/ui/slider.tsx
components/ui/sonner.tsx
components/ui/switch.tsx
components/ui/table.tsx
components/ui/tabs.tsx
components/ui/textarea.tsx
components/ui/toast.tsx
components/ui/toaster.tsx
components/ui/toggle-group.tsx
components/ui/toggle.tsx
components/ui/tooltip.tsx
components/ui/use-mobile.tsx
components/ui/use-toast.ts
contracts/escrow_pool.move
contracts/Move.toml
contracts/sources/escrow_pool.move
hooks/use-mobile.tsx
hooks/use-toast.ts
lib/ipfs-helpers.ts
lib/utils.ts
middleware.ts
Move.lock
next-env.d.ts
next.config.mjs
package.json
postcss.config.mjs
public/manifest.json
public/sw.js
README.md
scripts/deploy-contract.ts
scripts/deposit-to-pool.ts
scripts/fix-secret-key.ts
scripts/get-wallet-key.ts
scripts/initialize-pool.ts
sources/escrow_pool.move
styles/globals.css
tailwind.config.ts
tsconfig.json
```

### Dependencies

- package.json: @hookform/resolvers@^3.9.1, @mysten/dapp-kit@^0.19.6, @mysten/slush-wallet@^0.2.7, @mysten/sui@^1.43.1, @mysten/sui.js@^0.54.1, @mysten/wallet-kit@^0.8.6, @mysten/walrus@^0.8.1, @radix-ui/react-accordion@1.2.2, @radix-ui/react-alert-dialog@1.1.4, @radix-ui/react-aspect-ratio@1.1.1, @radix-ui/react-avatar@1.1.2, @radix-ui/react-checkbox@1.1.3, @radix-ui/react-collapsible@1.1.2, @radix-ui/react-context-menu@2.2.4, @radix-ui/react-dialog@1.1.4, @radix-ui/react-dropdown-menu@2.1.4, @radix-ui/react-hover-card@1.1.4, @radix-ui/react-label@2.1.1, @radix-ui/react-menubar@1.1.4, @radix-ui/react-navigation-menu@1.2.3, @radix-ui/react-popover@1.1.4, @radix-ui/react-progress@latest, @radix-ui/react-radio-group@1.2.2, @radix-ui/react-scroll-area@1.2.2, @radix-ui/react-select@2.1.4, @radix-ui/react-separator@1.1.1, @radix-ui/react-slider@1.2.2, @radix-ui/react-slot@1.1.1, @radix-ui/react-switch@1.1.2, @radix-ui/react-tabs@1.1.2, @radix-ui/react-toast@1.2.4, @radix-ui/react-toggle@1.1.1, @radix-ui/react-toggle-group@1.1.1, @radix-ui/react-tooltip@1.1.6, @tanstack/react-query@^5.90.5, @types/node@^22, @types/react@^19, @types/react-dom@^19, @vercel/analytics@^1.5.0, autoprefixer@^10.4.20, canvas-confetti@latest, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@1.0.4, date-fns@latest, dotenv@^17.2.3, embla-carousel-react@8.5.1, input-otp@1.4.1, ipfs-http-client@^60.0.1, lucide-react@^0.454.0, multiformats@13, next@15.2.4, next-themes@^0.4.4, postcss@^8, react@^19, react-day-picker@9.5.1, react-dom@^19, react-hook-form@^7.54.1, react-resizable-panels@^2.1.7, recharts@2.15.0, sonner@^1.7.1, tailwind-merge@^2.5.5, tailwindcss@^3.4.17, tailwindcss-animate@^1.0.7, tsx@^4.7.0, typescript@^5, uint8arrays@^5.1.0, vaul@^1.1.2, zod@^3.24.1

### Recent commits (newest first)

- feat: ipfs
- fix: pool component
- contract
- feat: escrow rewards pool contract
- feat: analytics
- feat: micro txs
- fix: styling
- fix: pwa mobile
- fix: refactor
- feat: slush login auth
- feat: init version app

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

### package.json

```
{
  "name": "icebreaker",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "deploy:contract": "tsx scripts/deploy-contract.ts",
    "init:pool": "tsx scripts/initialize-pool.ts",
    "generate:wallet": "tsx scripts/get-wallet-key.ts",
    "fix:key": "tsx scripts/fix-secret-key.ts",
    "add:funds": "tsx scripts/deposit-to-pool.ts"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.9.1",
    "@mysten/dapp-kit": "^0.19.6",
    "@mysten/slush-wallet": "^0.2.7",
    "@mysten/sui": "^1.43.1",
    "@mysten/sui.js": "^0.54.1",
    "@mysten/wallet-kit": "^0.8.6",
    "@mysten/walrus": "^0.8.1",
    "@radix-ui/react-accordion": "1.2.2",
    "@radix-ui/react-alert-dialog": "1.1.4",
    "@radix-ui/react-aspect-ratio": "1.1.1",
    "@radix-ui/react-avatar": "1.1.2",
    "@radix-ui/react-checkbox": "1.1.3",
    "@radix-ui/react-collapsible": "1.1.2",
    "@radix-ui/react-context-menu": "2.2.4",
    "@radix-ui/react-dialog": "1.1.4",
    "@radix-ui/react-dropdown-menu": "2.1.4",
    "@radix-ui/react-hover-card": "1.1.4",
    "@radix-ui/react-label": "2.1.1",
    "@radix-ui/react-menubar": "1.1.4",
    "@radix-ui/react-navigation-menu": "1.2.3",
    "@radix-ui/react-popover": "1.1.4",
    "@radix-ui/react-progress": "latest",
    "@radix-ui/react-radio-group": "1.2.2",
    "@radix-ui/react-scroll-area": "1.2.2",
    "@radix-ui/react-select": "2.1.4",
    "@radix-ui/react-separator": "1.1.1",
    "@radix-ui/react-slider": "1.2.2",
    "@radix-ui/react-slot": "1.1.1",
    "@radix-ui/react-switch": "1.1.2",
    "@radix-ui/react-tabs": "1.1.2",
    "@radix-ui/react-toast": "1.2.4",
    "@radix-ui/react-toggle": "1.1.1",
    "@radix-ui/react-toggle-group": "1.1.1",
    "@radix-ui/react-tooltip": "1.1.6",
    "@tanstack/react-query": "^5.90.5",
    "@vercel/analytics": "^1.5.0",
    "autoprefixer": "^10.4.20",
    "canvas-confetti": "latest",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "1.0.4",
    "date-fns": "latest",
    "embla-carousel-react": "8.5.1",
    "input-otp": "1.4.1",
    "ipfs-http-client": "^60.0.1",
    "lucide-react": "^0.454.0",
    "multiformats": "13",
    "next": "15.2.4",
    "next-themes": "^0.4.4",
    "react": "^19",
    "react-day-picker": "9.5.1",
    "react-dom": "^19",
    "react-hook-form": "^7.54.1",
    "react-resizable-panels": "^2.1.7",
    "recharts": "2.15.0",
    "sonner": "^1.7.1",
    "tailwind-merge": "^2.5.5",
    "tailwindcss-animate": "^1.0.7",
    "uint8arrays": "^5.1.0",
    "vaul": "^1.1.2",
    "zod": "^3.24.1"
  },
  "devDependencies": {
    "@types/node": "^22",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "dotenv": "^17.2.3",
    "postcss": "^8",
    "tailwindcss": "^3.4.17",
    "tsx": "^4.7.0",
    "typescript": "^5"
  }
}
```

### app/page.tsx

```typescript
import { redirect } from "next/navigation"
import { cookies } from "next/headers"

export default async function RootPage() {
  const cookieStore = await cookies()
  const authToken = cookieStore.get("slush_auth_token")?.value

  if (authToken) {
    redirect("/feed")
  } else {
    redirect("/auth")
  }
}

```

### app/layout.tsx

```typescript
import type React from "react"
import type { Metadata, Viewport } from "next/dist/lib/metadata/types/metadata-interface"
import { Inter } from "next/font/google"
import "./globals.css"
import "@mysten/dapp-kit/dist/index.css"
import { Providers } from "./providers"
import { AutoConnectSlushWrapper } from "@/components/AutoConnectSlushWrapper"
import { Toaster } from "@/components/ui/sonner"

const inter = Inter({ subsets: ["latin"] })

export const metadata: Metadata = {
  title: "Icebreaker",
  description: "Gamify your events with Icebreaker",
  manifest: "/manifest.json",
  appleWebApp: {
    capable: true,
    statusBarStyle: "black-translucent",
    title: "Icebreaker",
  },
}

export const viewport: Viewport = {
  themeColor: "#000000",
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en">
      <head>
        <link rel="apple-touch-icon" href="/icon-512x512.png"></link>
        <link rel="icon" href="/icon-192x192.png" />
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
        <link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap" rel="stylesheet" />
      </head>
      <body className={`${inter.className} bg-black grid-pattern`}>
        <Providers>
          <AutoConnectSlushWrapper />
          {children}
          <Toaster />
        </Providers>
      </body>
    </html>
  )
}

```

### app/share/page.tsx

```typescript
"use client"

import { useIcebreaker } from "../providers"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Share2 } from "lucide-react"
import Link from "next/link"
import { useState } from "react"
import { PointsAnimation } from "@/components/points-animation"
import { useSearchParams } from "next/navigation"
import { CyberNavigation } from "@/components/cyber-navigation"

export default function SharePage() {
  const { userData, addPoints, completeQuest } = useIcebreaker()
  const searchParams = useSearchParams()
  const questId = searchParams.get("questId")
  const [showAnimation, setShowAnimation] = useState(false)
  const [shared, setShared] = useState(false)

  const handleShare = async () => {
    try {
      if (navigator.share) {
        await navigator.share({
          title: "Check out Icebreaker!",
          text: `I've earned ${userData.points} points at this event using Icebreaker!`,
          url: window.location.origin,
        })

        // Award points for sharing
        if (!shared) {
          addPoints(5)
          if (questId) {
            completeQuest(questId)
          }
          setShared(true)
          setShowAnimation(true)
          setTimeout(() => setShowAnimation(false), 1500)
        }
      } else {
        // Fallback for browsers that don't support the Web Share API
        alert(`Share this: I've earned ${userData.points} points at this event using Icebreaker!`)

        // Still award points
        if (!shared) {
          addPoints(5)
          if (questId) {
            completeQuest(questId)
          }
          setShared(true)
          setShowAnimation(true)
          setTimeout(() => setShowAnimation(false), 1500)
        }
      }
    } catch (error) {
      console.error("Error sharing:", error)
    }
  }

  return (
    <main className="flex min-h-screen flex-col">
      <CyberNavigation />

      <div className="container mx-auto max-w-4xl px-4 py-8 flex-1 flex items-center justify-center">
        <Card className="w-full max-w-md relative cyber-card">
          {showAnimation && <PointsAnimation points={5} />}

          <CardHeader>
            <CardTitle className="cyber-font text-primary">SHARE WITH FRIENDS</CardTitle>
            <CardDescription className="text-primary/70">Let others know about your achievements!</CardDescription>
          </CardHeader>

          <CardContent className="text-center">
            <div className="bg-muted p-6 rounded-lg mb-4 border border-primary/30">
              <p className="text-lg text-primary">
                I've earned <span className="font-bold cyber-font">{userData.points} POINTS</span> at this event using Icebreaker!
              </p>
            </div>

            <p className="text-sm text-primary/70">Share your progress with friends and earn 5 bonus points!</p>
          </CardContent>

          <CardFooter>
            <Button 
              className="w-full gap-2 bg-primary text-black cyber-font hover:bg-primary/80" 
              onClick={handleShare} 
              disabled={shared}
            >
              <Share2 className="h-4 w-4" />
              {shared ? "SHARED!" : "SHARE NOW"}
            </Button>
          </CardFooter>
        </Card>
      </div>

      <div className="wave-bg h-20 w-full"></div>
    </main>
  )
}

```

### app/auth/page.tsx

```typescript
"use client"
import { useEffect } from "react"
import Image from "next/image"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { ConnectModal, useCurrentWallet, useCurrentAccount } from "@mysten/dapp-kit"
import { toast } from "sonner"

export default function LoginPage() {
  const router = useRouter()
  const { currentWallet, connectionStatus } = useCurrentWallet()
  const currentAccount = useCurrentAccount()

  useEffect(() => {
    // Check if user is already connected
    if (currentAccount && connectionStatus === "connected") {
      // Call API to set auth cookie and redirect
      fetch("/api/auth/callback", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ address: currentAccount.address }),
      })
        .then(() => {
          router.push("/feed")
        })
        .catch((error) => {
          console.error("Failed to authenticate:", error)
          toast.error("Failed to authenticate")
        })
    }
  }, [currentAccount, connectionStatus, router])

  return (
    <main className="flex min-h-screen flex-col items-center justify-center bg-black text-white p-4">
      <div className="flex flex-col items-center gap-6 mb-12">
        <Image
          src="/icon-192x192.png"
          alt="Icebreaker Logo"
          width={100}
          height={100}
          className="rounded-full"
        />
        <h1 className="cyber-font text-5xl text-primary logo-animation">ICEBREAKER</h1>
      </div>

      <div className="cyber-card p-8 md:p-12 rounded-lg shadow-xl w-full max-w-md text-center">
        <h2 className="text-2xl font-semibold mb-2 text-primary/90">Welcome Back!</h2>
        <p className="text-primary/70 mb-8">Sign in to continue your adventure.</p>

        <ConnectModal
          trigger={
            <Button className="w-full bg-white hover:bg-gray-100 text-gray-700 cyber-font text-lg py-3 rounded-md transition-all duration-300 ease-in-out transform hover:scale-105 flex items-center justify-center gap-3 border border-gray-300 shadow-sm">
              <svg width="18" height="18" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
                <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
                <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
                <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
                <path d="M1 1h22v22H1z" fill="none"/>
              </svg>
              Connect with Slush
            </Button>
          }
        />

        <p className="text-xs text-primary/50 mt-8">
          Sign in with Slush to create or access your Sui wallet. First-time users will have a wallet created automatically.
        </p>
      </div>

      <div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 text-center">
          <p className="text-sm text-primary/60 cyber-font">Gamify Your Events</p>
      </div>
    </main>
  )
} 
```

### app/quests/page.tsx

```typescript
"use client"

import { useIcebreaker } from "../providers"
import { Button } from "@/components/ui/button"
import { Camera, MessageSquare, Share2, Smile } from "lucide-react"
import Link from "next/link"
import { useState } from "react"
import { PointsAnimation } from "@/components/points-animation"
import { CyberNavigation } from "@/components/cyber-navigation"

const quests = [
  {
    id: "selfie",
    title: "Take a Selfie",
    description: "Snap a selfie at the event",
    icon: <Camera className="h-6 w-6" />,
    points: 10,
    link: "/upload?type=photo&questId=selfie",
  },
  {
    id: "hot-take",
    title: "Share a Hot Take",
    description: "Post your spicy opinion about the event",
    icon: <Smile className="h-6 w-6" />,
    points: 15,
    link: "/upload?type=text&questId=hot-take",
  },
  {
    id: "meme",
    title: "Create a Meme",
    description: "Make a meme about the event",
    icon: <MessageSquare className="h-6 w-6" />,
    points: 15,
    link: "/upload?type=meme&questId=meme",
  },
  {
    id: "share",
    title: "Share with Friends",
    description: "Share your content with friends",
    icon: <Share2 className="h-6 w-6" />,
    points: 5,
    link: "/share?questId=share",
  },
]

export default function QuestsPage() {
  const { userData } = useIcebreaker()
  const [showAnimation, setShowAnimation] = useState<string | null>(null)

  return (
    <main className="flex min-h-screen flex-col">
      <CyberNavigation />

      <div className="container mx-auto max-w-4xl px-4 py-8 flex-1">
        <div className="flex justify-between items-center mb-6">
          <h2 className="cyber-font text-2xl text-primary glow-text">QUESTS</h2>
          <div className="flex items-center gap-2">
            <span className="cyber-font text-primary">{userData.points}</span>
            <span className="text-primary/70">POINTS</span>
          </div>
        </div>

        <p className="text-primary/70 mb-6">Complete these challenges to earn points!</p>

        <div className="grid gap-4">
          {quests.map((quest) => {
            const isCompleted = userData.completedQuests.includes(quest.id)

            return (
              <div key={quest.id} className="cyber-card relative overflow-hidden rounded-md">
                {showAnimation === quest.id && <PointsAnimation points={quest.points} />}
                <div className="flex items-center gap-4 p-4">
                  <div className={`p-2 rounded-full ${isCompleted ? "bg-primary/20" : "bg-muted"}`}>{quest.icon}</div>
                  <div>
                    <h3 className="cyber-font text-primary">{quest.title}</h3>
                    <p className="text-primary/70 text-sm">{quest.description}</p>
                  </div>
                </div>
                <div className="flex justify-between items-center px-4 pb-4 pt-2 border-t border-primary/20">
                  <div className="text-sm font-medium cyber-font text-secondary">{quest.points} POINTS</div>
                  {isCompleted ? (
                    <Button variant="outline" disabled className="border-primary/30 text-primary/50">
                      COMPLETED
                    </Button>
                  ) : (
                    <Link href={quest.link}>
                      <Button
                        className="bg-primary hover:bg-primary/80 text-black cyber-font"
                      >
                        START QUEST
                      </Button>
                    </Link>
                  )}
                </div>
              </div>
            )
          })}
        </div>
      </div>

      <div className="wave-bg h-20 w-full"></div>
    </main>
  )
}

```

### app/feed/page.tsx

```typescript
"use client"

import Link from "next/link"

import { useIcebreaker } from "../providers"
import { Button } from "@/components/ui/button"
import { Heart, MessageSquare, Share2 } from "lucide-react"
import { useState } from "react"
import { formatDistanceToNow } from "date-fns"
import { CyberNavigation } from "@/components/cyber-navigation"

export default function FeedPage() {
  const { userData } = useIcebreaker()
  const [likedPosts, setLikedPosts] = useState<Record<string, boolean>>({})

  const handleLike = (id: string) => {
    setLikedPosts((prev) => ({
      ...prev,
      [id]: !prev[id],
    }))
  }

  const renderMedia = (item: (typeof userData.media)[0]) => {
    if (item.type === "photo" || item.type === "meme") {
      return <img src={item.data || "/placeholder.svg"} alt="User content" className="w-full rounded-md" />
    }

    if (item.type === "video") {
      return <video src={item.data} controls className="w-full rounded-md" />
    }

    if (item.type === "text") {
      return (
        <div className="bg-muted p-4 rounded-md border border-primary/30">
          <p className="text-lg font-medium text-primary">{item.data}</p>
        </div>
      )
    }

    return null
  }

  return (
    <main className="flex min-h-screen flex-col">
      <CyberNavigation />

      <div className="container mx-auto max-w-4xl px-4 py-8 flex-1">
        <div className="flex justify-between items-center mb-6">
          <h2 className="cyber-font text-2xl text-primary glow-text">EVENT FEED</h2>
        </div>

        {userData.media.length === 0 ? (
          <div className="cyber-card p-8 text-center rounded-md">
            <p className="text-primary/70 mb-4">No content yet. Be the first to share something!</p>
            <Link href="/upload">
              <Button className="bg-primary hover:bg-primary/80 text-black cyber-font">CREATE POST</Button>
            </Link>
          </div>
        ) : (
          <div className="grid gap-6">
            {userData.media.map((item) => (
              <div key={item.id} className="cyber-card rounded-md p-4">
                <div className="pb-2 flex justify-between items-center">
                  <div className="text-sm text-primary/70">
                    {formatDistanceToNow(item.timestamp, { addSuffix: true })}
                  </div>
                  <div className="alien-symbol smile"></div>
                </div>

                <div className="space-y-4 my-4">
                  {renderMedia(item)}

                  {(item.caption || (item.hashtags && item.hashtags.length > 0)) && (
                    <div>
                      {item.caption && <p className="text-primary/90">{item.caption}</p>}
                      {item.hashtags && item.hashtags.length > 0 && (
                        <div className="flex flex-wrap gap-1 mt-1">
                          {item.hashtags.map((tag) => (
                            <span key={tag} className="text-secondary cyber-font text-sm">
                              #{tag}
                            </span>
                          ))}
                        </div>
                      )}
                    </div>
                  )}
                </div>

                <div className="flex justify-between border-t border-primary/20 pt-3">
                  <Button
                    variant="ghost"
                    size="sm"
                    className={likedPosts[item.id] ? "text-red-400" : "text-primary"}
                    onClick={() => handleLike(item.id)}
                  >
                    <Heart className="h-5 w-5 mr-1" />
                    {likedPosts[item.id] ? "Liked" : "Like"}
                  </Button>

                  <Button variant="ghost" size="sm" className="text-primary">
                    <MessageSquare className="h-5 w-5 mr-1" />
                    Comment
                  </Button>

                  <Button variant="ghost" size="sm" className="text-primary">
                    <Share2 className="h-5 w-5 mr-1" />
                    Share
                  </Button>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>

      <div className="wave-bg h-20 w-full"></div>
    </main>
  )
}

```

### app/upload/page.tsx

```typescript
"use client"

import type React from "react"

import { useIcebreaker } from "../providers"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { ImageIcon, X } from "lucide-react"
import { useRouter, useSearchParams } from "next/navigation"
import { useEffect, useRef, useState } from "react"
import { PointsAnimation } from "@/components/points-animation"
import { CyberNavigation } from "@/components/cyber-navigation"
import { toast } from "sonner"

export default function UploadPage() {
  const { addMedia, addPoints, completeQuest } = useIcebreaker()
  const router = useRouter()
  const searchParams = useSearchParams()
  const type = searchParams.get("type") || "photo"
  const questId = searchParams.get("questId")

  const [mediaData, setMediaData] = useState<string | null>(null)
  const [caption, setCaption] = useState("")
  const [hashtags, setHashtags] = useState("")
  const [showAnimation, setShowAnimation] = useState(false)

  const videoRef = useRef<HTMLVideoElement>(null)
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const fileInputRef = useRef<HTMLInputElement>(null)
  const streamRef = useRef<MediaStream | null>(null)

  useEffect(() => {
    if (type === "photo") {
      startCamera()
    }

    return () => {
      stopCamera()
    }
  }, [type])

  const startCamera = async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({
        video: true,
        audio: false,
      })

      streamRef.current = stream

      if (videoRef.current) {
        videoRef.current.srcObject = stream
      }
    } catch (err) {
      console.error("Error accessing camera:", err)
    }
  }

  const stopCamera = () => {
    if (streamRef.current) {
      streamRef.current.getTracks().forEach((track) => track.stop())
      streamRef.current = null
    }
  }

  const capturePhoto = () => {
    if (videoRef.current && canvasRef.current) {
      const video = videoRef.current
      const canvas = canvasRef.current

      canvas.width = video.videoWidth
      canvas.height = video.videoHeight

      const ctx = canvas.getContext("2d")
      ctx?.drawImage(video, 0, 0, canvas.width, canvas.height)

      const data = canvas.toDataURL("image/jpeg")
      setMediaData(data)
      stopCamera()
    }
  }

  const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0]
    if (!file) return

    const reader = new FileReader()
    reader.onload = (event) => {
      setMediaData(event.target?.result as string)
    }

    reader.readAsDataURL(file)
  }

  const handleSubmit = async () => {
    if (!mediaData && type !== "text") return

    const hashtagArray = hashtags
      .split(" ")
      .filter((tag) => tag.startsWith("#"))
      .map((tag) => tag.substring(1))

    let data = mediaData
    if (type === "text") {
      data = caption
    }

    if (data) {
      addMedia(type, data, caption, hashtagArray)

      // Award points based on type
      const pointsMap: Record<string, number> = {
        photo: 10,
        meme: 15,
        text: 5,
      }

      const pointsEarned = pointsMap[type] || 10
      addPoints(pointsEarned)

      // Complete the quest if this was part of a quest
      if (questId) {
        completeQuest(questId)
      }

      // Send 0.003 SUI reward
      try {
        const response = await fetch("/api/transfer-sui", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
        })

        if (response.ok) {
          const result = await response.json()
          const transactionUrl = `https://suiexplorer.com/txblock/${result.digest}?network=testnet`
          
          toast.success("🎉 Rewarded 0.003 SUI!", {
            description: "Your reward has been sent to your wallet",
            action: {
              label: "View Transaction",
              onClick: () => window.open(transactionUrl, '_blank')
            },
            duration: 8000,
          })
        } else {
          toast.error("Reward transfer failed, but your post was shared!")
        }
      } catch (error) {
        toast.error("Reward transfer failed, but your post was shared!")
      }


      // Show animation
      setShowAnimation(true)
      setTimeout(() => {
        setShowAnimation(false)
        router.push("/feed")
      }, 1500)
    }
  }

  const renderMediaCapture = () => {
    if (mediaData) {
      return (
        <div className="relative">
          <img
            src={mediaData || "/placeholder.svg"}
            alt="Captured"
            className="w-full rounded-md border border-primary/30"
          />

          <Button
            variant="destructive"
            size="icon"
            className="absolute top-2 right-2 rounded-full"
            onClick={() => {
              setMediaData(null)
              if (type === "photo") {
                startCamera()
              }
            }}
          >
            <X className="h-4 w-4" />
          </Button>
        </div>
      )
    }

    if (type === "photo") {
      return (
        <>
          <div className="relative aspect-video bg-black rounded-md overflow-hidden border border-primary/30">
            <video ref={videoRef} autoPlay playsInline muted className="w-full h-full object-cover" />
          </div>

          <div className="flex justify-center mt-4">
            <Button onClick={capturePhoto} className="bg-primary hover:bg-primary/80 text-black cyber-font">
              TAKE PHOTO
            </Button>
          </div>

          <canvas ref={canvasRef} className="hidden" />
        </>
      )
    }

    if (type === "meme") {
      return (
        <div className="flex flex-col items-center gap-4">
          <Button
            variant="outline"
            className="h-32 w-full border-dashed border-primary/50 bg-muted hover:bg-muted/80"
            onClic
[truncated — 2615 more characters]
```

### app/profile/page.tsx

```typescript
"use client"

import { useIcebreaker } from "../providers"
import { Button } from "@/components/ui/button"
import { Progress } from "@/components/ui/progress"
import { Trophy, Camera, MessageSquare, Share2, Wallet, ExternalLink, Coins } from "lucide-react"
import Link from "next/link"
import { CyberNavigation } from "@/components/cyber-navigation"
import { useCurrentAccount, useSuiClient } from "@mysten/dapp-kit"
import { useEffect, useState } from "react"

const LEVELS = [
  { name: "NEWBIE", threshold: 0 },
  { name: "SOCIALIZER", threshold: 50 },
  { name: "INFLUENCER", threshold: 100 },
  { name: "EVENT STAR", threshold: 200 },
  { name: "LEGEND", threshold: 500 },
]

export default function ProfilePage() {
  const { userData } = useIcebreaker()
  const account = useCurrentAccount()
  const client = useSuiClient()
  const [walletBalance, setWalletBalance] = useState<string>("0")
  const [balanceLoading, setBalanceLoading] = useState(false)
  const [poolBalance, setPoolBalance] = useState<number | null>(null)

  // Fetch wallet balance
  useEffect(() => {
    const fetchBalance = async () => {
      if (!account?.address) return
      
      setBalanceLoading(true)
      try {
        const balance = await client.getBalance({
          owner: account.address,
        })
        const suiBalance = (Number(balance.totalBalance) / 1_000_000_000).toFixed(4)
        setWalletBalance(suiBalance)
      } catch (error) {
        console.error('Failed to fetch balance:', error)
      } finally {
        setBalanceLoading(false)
      }
    }

    fetchBalance()
  }, [account?.address, client])

  // Fetch pool balance
  useEffect(() => {
    const fetchPoolBalance = async () => {
      try {
        const res = await fetch('/api/pool-balance')
        if (!res.ok) return
        const contentType = res.headers.get('content-type')
        if (contentType && contentType.includes('application/json')) {
          const data = await res.json()
          if (data.success) {
            setPoolBalance(data.balanceInSui)
          }
        }
      } catch (err) {
        console.error('Failed to fetch pool balance:', err)
      }
    }

    fetchPoolBalance()
    const interval = setInterval(fetchPoolBalance, 30000)
    return () => clearInterval(interval)
  }, [])

  // Calculate current level
  const currentLevel = LEVELS.reduce((prev, curr) => {
    return userData.points >= curr.threshold ? curr : prev
  }, LEVELS[0])

  // Find next level
  const nextLevelIndex = LEVELS.findIndex((level) => level.name === currentLevel.name) + 1
  const nextLevel = nextLevelIndex < LEVELS.length ? LEVELS[nextLevelIndex] : null

  // Calculate progress to next level
  const progressPercentage = nextLevel
    ? ((userData.points - currentLevel.threshold) / (nextLevel.threshold - currentLevel.threshold)) * 100
    : 100

  // Count completed quests by type
  const questCounts = {
    photo: userData.media.filter((item) => item.type === "photo").length,
    text: userData.media.filter((item) => item.type === "text").length,
    meme: userData.media.filter((item) => item.type === "meme").length,
  }

  return (
    <main className="flex min-h-screen flex-col">
      <CyberNavigation />

      <div className="container mx-auto max-w-4xl px-4 py-8 flex-1">
        {/* Wallet Info */}
        {account && (
          <div className="cyber-card mb-6 rounded-md relative overflow-hidden">
            <div className="p-4">
              <div className="flex items-center gap-2 mb-3">
                <Wallet className="h-5 w-5 text-primary" />
                <p className="text-sm text-primary/70">Wallet Details</p>
              </div>
              
              <div className="mb-3">
                <p className="text-sm text-primary/70 mb-1">Address</p>
                <div className="flex items-center gap-2">
                  <p className="font-mono text-primary text-xs break-all">{account.address}</p>
                  <Link 
                    href={`https://suiexplorer.com/address/${account.address}?network=testnet`}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="text-primary hover:text-primary/80"
                  >
                    <ExternalLink className="h-4 w-4" />
                  </Link>
                </div>
              </div>

              <div className="flex items-baseline gap-2">
                <p className="text-sm text-primary/70">Balance:</p>
                <p className="cyber-font text-2xl text-primary">
                  {balanceLoading ? "..." : walletBalance} SUI
                </p>
              </div>
            </div>
            
            <div className="px-4 pb-4">
              <Link 
                href={`https://suiexplorer.com/address/${account.address}?network=testnet`}
                target="_blank"
                rel="noopener noreferrer"
                className="flex items-center gap-2 text-sm text-primary hover:text-primary/80 transition-colors"
              >
                <ExternalLink className="h-4 w-4" />
                <span>View transactions on Sui Explorer</span>
              </Link>
            </div>
          </div>
        )}

        {/* Reward Pool Info */}
        {poolBalance !== null && (
          <div className="cyber-card mb-6 rounded-md relative overflow-hidden">
            <div className="p-4">
              <div className="flex items-center gap-2 mb-3">
                <Coins className="h-5 w-5 text-primary" />
                <p className="text-sm text-primary/70">Reward Pool Balance</p>
              </div>
              
              <div className="mb-3">
                <p className="text-sm text-primary/70 mb-1">Pool Object</p>
                <div className="flex items-center gap-2">
                  <p className="font-mono text-primary text-xs break-all">0x94bd6487e22a...c7447e</p>
                  <a 
                    href={`https://suiexplorer.com/object/
[truncated — 4670 more characters]
```

### app/api/pool-balance/route.ts

```typescript
import { NextResponse } from 'next/server'
import { SuiClient, getFullnodeUrl } from '@mysten/sui/client'

export async function GET() {
  try {
    const poolObjectId = process.env.ESCROW_POOL_OBJECT_ID

    if (!poolObjectId) {
      return NextResponse.json({ error: 'Pool not configured' }, { status: 500 })
    }

    // Connect to Sui network
    const client = new SuiClient({ url: getFullnodeUrl('testnet') })

    // Get pool object to check balance
    const poolObject = await client.getObject({
      id: poolObjectId,
      options: { showContent: true }
    })

    if (!poolObject.data) {
      return NextResponse.json({ error: 'Pool not found' }, { status: 500 })
    }

    // Extract balance from the pool object - it's stored in the 'coin' field
    const poolFields = poolObject.data?.content && 'fields' in poolObject.data.content
      ? poolObject.data.content.fields as any
      : null

    if (!poolFields || !poolFields.coin) {
      return NextResponse.json({ error: 'Balance not accessible' }, { status: 500 })
    }

    // Get coin balance - nested structure
    const coinData = poolFields.coin
    const poolBalanceValue = BigInt(coinData.fields?.balance || 0)
    const balanceInSui = Number(poolBalanceValue) / 1_000_000_000

    return NextResponse.json({
      success: true,
      balance: poolBalanceValue.toString(),
      balanceInSui: balanceInSui,
      poolObjectId,
    })
  } catch (error) {
    console.error('Failed to get pool balance:', error)
    return NextResponse.json(
      { error: 'Failed to get pool balance', details: error instanceof Error ? error.message : 'Unknown error' },
      { status: 500 }
    )
  }
}


```

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