# Project export: SuiTix

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: Sui-powered blockchain-based ticketing and loyalty platform with policy-enforced, fraud-proof resale via Kiosk; buy in one click, resell fairly, and earn perks from purchase to check-in.
- Devpost: https://devpost.com/software/suitix
- GitHub: https://github.com/RohanChintakindi/sui-tix
- Demo: https://drive.google.com/file/d/1bEc5Nz9eCxpKmzFR9lEv-mVdrdfdCW9_/view?usp=sharing
- Video: https://www.youtube.com/embed/Whz3DoW8TeI?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Sui: Best Use of Sui)
- Team: 5 GitHub contributor(s) — Vitthal Agarwal (47 commits), Rohan Chintakindi (26 commits), Claude (9 commits), diyan-chokshi (8 commits), anishnarang9 (7 commits)

## Devpost submission (written by the team)

### Overview

Video Demo - https://drive.google.com/file/d/1bEc5Nz9eCxpKmzFR9lEv-mVdrdfdCW9_/view?usp=sharing SuiTix is a fair, transparent ticketing and loyalty platform on the Sui blockchain. We built SuiTix because the status quo leaves too many real fans outside the venue and too much value off the stage. Scalping, opaque fees, QR screenshots that get reused, and resale experiences that reward bots over communities—none of that felt inevitable. Sui’s object-centric design, dynamic NFTs, and the Kiosk + Transfer Policy combo let us design a system where rules are enforced by code, ownership is crystal clear, and loyalty actually compounds over time. What SuiTix delivers Authentic tickets as first-class Sui objects with rich Display metadata and a clean lifecycle (mint → own → list/resell → check-in). Secure secondary market via Kiosk, where every sale is mediated by a Transfer Policy that enforces fair-price ranges, time windows, and “not checked-in” requirements before a trade can finalize. Dynamic primary pricing that reacts to real demand (with guardrails so it’s responsive, not volatile). Loyalty that matters: Bronze/Silver/Gold/Platinum tiers, points for purchases and attendance, milestone badges, and a claim-based referral system that fits Sui’s ownership rules. Anti-scalping controls: per-wallet purchase caps, cool-downs, and resale restrictions that make drops calmer and markets fairer. How we put it together (textual tour of the smart contracts) Ticketing layer (the ticket as a living object) A Ticket is an owned Sui object that carries its class reference, seat info (or “general admission”), and a simple “consumed” flag for check-in. Display templates make it render like an NFT card everywhere (wallets, marketplace, our UI). Check-in is just a state transition: once marked, it’s visibly “used” and can’t be resold. Tickets are minted against a Ticket Class that defines supply, base price, metadata, and organizer economics. Marketplace layer (Kiosk integration without surprises) We don’t reinvent a marketplace. Sellers keep a personal, on-chain Kiosk that can hold items, list them at a fixed price, and accrue their sale proceeds. While listed, a ticket is immutable and not withdrawable—no tampering. We support two custody modes: Soft: owners hold tickets in their wallet and only place them in a Kiosk when they choose to sell. Strong: after primary purchase, the ticket is automatically placed (and locked) in the buyer’s Kiosk so every future transfer is a Kiosk trade—no off-market side doors. Transfer Policy (the rules engine for resale) There’s a single policy dedicated to the Ticket type. Every Kiosk purchase produces a transfer request that the policy must confirm. Confirmation verifies that the price is within the allowed band for the class, the event hasn’t started (or resale hasn’t been explicitly paused), and the ticket isn’t checked-in. It also captures creator royalties into an on-chain balance organizers can withdraw. If any rule fails, the whole trade cleanly aborts. Loyalty (points, badges, and referrals designed for Sui) Fans have a personal Loyalty Card object that tracks balances and tier. Badges are non-transferable collectibles for milestones. Referrals use a shared “pending claims” registry: the new user proves the invite, the registry validates, and both parties get credited—no cross-owner mutations needed. We hook loyalty updates into primary buys, successful resales, and check-ins. Dynamic pricing (responsive, not noisy) Ticket Classes can opt into demand-aware pricing. The system looks at recent sales velocity and nudges price up/down within safe bounds set by the organizer. Guardrails prevent extreme swings, and toggles let organizers opt out for fixed-price shows. Anti-scalping (friction where it counts) We track per-wallet counters at the event level, apply cool-downs during hot drops, and disallow post-check-in resales. Optional “presale lists” and purchase windows make access fair for communities. State design (shared vs. owned) Global catalogs (events, classes, referral registry) live as shared objects; tickets, badges, and loyalty cards stay owned by users. This separation keeps hot paths fast and permissions crystal clear. Frontend & UX (how it feels to use) Next.js + TypeScript with Sui dApp Kit for wallet and signing. One-click flows using Programmable Transaction Blocks: Primary buy: pay once, receive the ticket, and—if strong custody is on—have it placed and locked in your Kiosk automatically. Resale: press Buy on a listing; the marketplace executes a Kiosk purchase, the policy confirms the rules, and ownership flips atomically. Attendee check-in: scan once; the ticket flips to “used” and can’t re-enter the resale pool. Primary buy: pay once, receive the ticket, and—if strong custody is on—have it placed and locked in your Kiosk automatically. Resale: press Buy on a listing; the marketplace executes a Kiosk purchase, the policy confirms the rules, and ownership flips atomically. Attendee check-in: scan once; the ticket flips to “used” and can’t re-enter the resale pool. Marketplace grid merges Kiosk listing events with Ticket Display metadata for crisp cards. Organizer controls for pausing resale, withdrawing royalties, and tuning price bounds—no redeploy required.

### Challenges we ran into

(and how we solved them) Sui’s ownership boundaries meant our first referral design couldn’t modify a referrer’s object during a newcomer’s transaction. The claim-based registry pattern fixed that cleanly. Entry-point ergonomics forced us to split certain combined flows and pass addresses instead of trying to smuggle optional mutable references. Cleaner in the end. Learning Kiosk + policy interplay took iteration: listing states, lock semantics, and when the transfer request is produced. Once we aligned on strong custody, everything snapped into place. Demand-based pricing without exploits required guardrails, sanity caps, and smoothing—users see “responsive,” not “random.” PTB complexity: bundling ticketing, loyalty, and marketplace actions in the right order with the right coin handling was a puzzle we’re glad we solved. Accomplishments we’re proud of A complete loop—primary sale, policy-enforced resale, check-in, royalties, and loyalty—that’s atomic and auditable end-to-end. Real anti-scalping and fair-pricing mechanics, not just rhetoric. A badge-driven loyalty system that actually feels fun. A modular contract suite that teams can extend without touching core logic.

### What we learned

along the way Sui’s object model makes stateful tickets first-class citizens instead of workarounds. Programmable Transaction Blocks unlock surprisingly elegant UX when you think in “atomic steps.” Kiosk + Transfer Policy is a safer, simpler foundation than bespoke marketplaces with scattered rules. When ownership rules push back, separate concerns across transactions or pivot to shared registries—don’t fight the model.

### What's next

Social groups, ticket splitting, and shared itineraries. Cross-event perks and partner redemptions. Smarter pricing (better signals now; ML when data warrants). Native apps and a measured mainnet rollout with real venues. Bridges to incumbents to onboard the mainstream, and multi-chain reads with Sui as source of truth. Google Drive for demo videos - https://drive.google.com/drive/folders/1Klj85AInY0aYRiFudbvxd-EuXqOxsS-D?usp=sharing

## README (from the GitHub repository)

# 🎫 SuiTix - Next-Generation Blockchain Ticketing Platform

> **Production-ready ticketing ecosystem leveraging advanced Sui blockchain primitives including Kiosk standard, TransferPolicy, dynamic fields, and programmable transaction blocks**

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Sui](https://img.shields.io/badge/Sui-Testnet-blue)](https://sui.io)
[![Move](https://img.shields.io/badge/Move-2024.beta-red)](https://docs.sui.io/concepts/sui-move-concepts)
[![Next.js](https://img.shields.io/badge/Next.js-14-black)](https://nextjs.org)

## 🌟 Overview

SuiTix is an enterprise-grade event ticketing platform that showcases the full power of the Sui blockchain. Built with production-ready Move smart contracts and a sophisticated Next.js frontend, it demonstrates advanced blockchain patterns including the Sui Kiosk standard, custom TransferPolicy rules, dynamic NFT fields, and complex royalty distribution.

**Live Demo:** Coming Soon | **Transaction Explorer:** [View on Sui Testnet](https://suiscan.xyz/testnet)

---

## 🏆 Advanced Sui Features Implemented

### 🎯 Sui Kiosk Standard Integration

We've implemented the **full Sui Kiosk specification** for a decentralized NFT marketplace:

```move
// Kiosk adapter for ticket NFTs with enforce/soft modes
module suitix::kiosk_adapter {
    public fun place_ticket(ticket: Ticket, kiosk: &mut Kiosk, cap: &KioskOwnerCap)
    public fun lock_ticket(kiosk: &mut Kiosk, cap: &KioskOwnerCap, policy: &TransferPolicy<Ticket>, ticket: &Ticket)
    public fun list_ticket(kiosk: &mut Kiosk, cap: &KioskOwnerCap, ticket_id: ID, price: u64)
}
```

**Why This Matters:**
- ✅ Native compatibility with Sui ecosystem marketplaces
- ✅ Standard purchase/transfer flows without custom escrow
- ✅ Built-in royalty enforcement via TransferPolicy
- ✅ Each seller maintains their own kiosk (decentralized architecture)

### 🔐 TransferPolicy with Custom Rules

Advanced royalty enforcement that validates transfers with **multi-party fee distribution**:

```move
module suitix::policy {
    // Validates ticket transfers with comprehensive rule checking
    public fun confirm_with_rules(
        policy: &mut TransferPolicy<Ticket>,
        request: TransferRequest<Ticket>,
        event: &mut Event,
        config: &ResaleConfig,
        ticket: &Ticket,
        clock: &Clock,
        marketplace_treasury: &mut MarketplaceTreasury,
        platform_fee_payment: Coin<SUI>,
        organizer_royalty_payment: Coin<SUI>,
    ) {
        // 1. Floor/ceiling price validation
        // 2. Event timing checks
        // 3. Ticket status verification
        // 4. Three-way royalty split: Platform (2%) | Organizer (remaining) | Seller (price)
    }
}
```

**Rule Enforcement:**
- ✅ Price floor/ceiling validation
- ✅ Event timing restrictions (no resale after event starts)
- ✅ Ticket status checks (used tickets can't be transferred)
- ✅ Automated royalty distribution to multiple parties

### 🧩 Dynamic Fields for Validation

Innovative **non-destructive ticket validation** using Sui's dynamic fields:

```move
// Validation is stored as a dynamic field on Event object
// Ticket NFT remains unmodified in user's wallet!
public fun validate_ticket(
    ticket_id: ID,  // Just the ID, not the object itself
    event: &mut Event,
    cap: &OrganizerCap,
    clock: &Clock,
) {
    // Add validation record as dynamic field
    dynamic_field::add(&mut event.id, ticket_id, ValidationRecord {
        validated_at: clock::timestamp_ms(clock),
        validator: tx_context::sender(ctx),
    });
}
```

**Why This Is Revolutionary:**
- ✅ Organizer can validate tickets they don't own
- ✅ Ticket NFT stays in user's wallet (no transfer needed)
- ✅ On-chain proof of attendance without modifying the NFT
- ✅ Enables future airdrops to validated attendees

### 💰 Treasury Architecture & Fee Distribution

Sophisticated **three-way payment split** with platform fee treasury:

```move
module suitix::marketplace {
    public struct MarketplaceTreasury has key {
        id: UID,
        balance: Balance<SUI>,  // Accumulated platform fees
        admin: address,
    }

    // Platform collects 2% of royalties, organizer gets the rest
    public fun deposit_platform_fee(treasury: &mut MarketplaceTreasury, fee: Coin<SUI>)
    public entry fun withdraw_fees(treasury: &mut MarketplaceTreasury, cap: &MarketplaceCap, amount: u64)
}
```

**Payment Flow on Secondary Sales:**
```
Sale Price: 10 SUI
├─ Seller receives: 10 SUI (full price)
├─ Organizer royalty (5% = 0.5 SUI)
│  ├─ Platform fee (2% of price = 0.2 SUI) → MarketplaceTreasury
│  └─ Organizer share (0.3 SUI) → Event Treasury
└─ Buyer pays: 10.5 SUI total
```

### 🎭 Capability-Based Access Control

**Zero-trust security** using Move's capability pattern:

```move
// OrganizerCap: Manage events, validate tickets, withdraw revenue
public struct OrganizerCap has key, store {
    id: UID,
    event_id: ID,
    organizer: address,
}

// MarketplaceCap: Withdraw platform fees, manage marketplace
public struct MarketplaceCap has key, store {
    id: UID,
    admin: address,
}

// PlatformCap: Award loyalty points, manage rewards catalog
public struct PlatformCap has key, store {
    id: UID,
    admin: address,
}

// TransferPolicyCap: Manage transfer rules, withdraw royalties from policy
public struct TransferPolicyCap<phantom T> has key, store {
    id: UID,
    policy_id: ID,
}
```

**Security Benefits:**
- ✅ Only capability holder can perform privileged operations
- ✅ Capabilities are transferable (can sell event to new organizer)
- ✅ No address-based access control (compromised key = no access)
- ✅ Type-safe: Can't use wrong cap for wrong operation

---

## 🏗️ Smart Contract Architecture

### 📦 Module Overview

```
contracts/sources/
├── ticket_nft.move          # Core ticket lifecycle & event management
├── loyalty_system.move      # Tiered loyalty program with cross-event rewards
├── marketplace.move         # Fee collection treasury for platform
├── policy.move              # TransferPolicy rules & royalty enforcement
├── kiosk_adapter.move       # Sui Kiosk integration for NFT marketplace
├── dynamic_pricing.move     # Demand-based pricing algorithm
├── anti_scalping.move       # Purchase limits & bot prevention
└── ticket_upgrades.move     # Premium ticket tier upgrades
```

### 🎫 Ticket NFT Module (`ticket_nft.move`)

**Advanced Features:**
- ✅ **Shared Event Objects**: Multiple users can purchase simultaneously (parallel execution)
- ✅ **Dynamic Metadata**: Ticket properties update without NFT recreation
- ✅ **Treasury Pattern**: Revenue accumulates in event treasury, withdrawable by organizer
- ✅ **Lifecycle States**: PURCHASED → VALIDATED → USED (represented as integers 0, 1, 2)
- ✅ **Event Registry**: Global shared object for event discovery

**Core Functions:**
```move
// Create event with organizer capability
public entry fun create_event(
    registry: &mut EventRegistry,
    name: vector<u8>,
    description: vector<u8>,
    venue: vector<u8>,
    event_date: u64,
    capacity: u64,
    base_price: u64,
    royalty_percentage: u64,  // For secondary sales
    category: vector<u8>,
    image_url: vector<u8>,
    clock: &Clock,
    ctx: &mut TxContext
) -> OrganizerCap

// Purchase with loyalty integration
public entry fun purchase_ticket_with_loyalty(
    event: &mut Event,
    loyalty_card: &mut LoyaltyCard,
    history: &mut TransactionHistory,
    seat_number: vector<u8>,
    seat_section: vector<u8>,
    ticket_type: vector<u8>,
    metadata_uri: vector<u8>,
    payment: Coin<SUI>,
    clock: &Clock,
    ctx: &mut TxContext
) // Awards points automatically!

// Non-destructive validation using dynamic fields
public fun validate_ticket(
    ticket_id: ID,
    event: &mut Event,
    cap: &OrganizerCap,
    clock: &Clock,
)
```

### 🎁 Loyalty System Module (`loyalty_system.move`)

**Tiered Progression:**
```move
const BRONZE: u8 = 0;    // 0-9

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 92 recognized source files, 782 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
- Vercel (technology) — detected in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (108 of 108)

```
.gitignore
contracts/DEPLOYMENT_INFO.md
contracts/DEPLOYMENT_INSTRUCTIONS.md
contracts/Move.lock
contracts/Move.toml
contracts/sources/anti_scalping.move
contracts/sources/dynamic_pricing.move
contracts/sources/kiosk_adapter.move
contracts/sources/loyalty_system.move
contracts/sources/marketplace.move
contracts/sources/policy.move
contracts/sources/ticket_nft.move
contracts/sources/ticket_upgrades.move
create-resale-config.js
deploy.sh
documentation/ADMIN_WALLET_GUIDE.md
documentation/API_DOCUMENTATION.md
documentation/ARCHITECTURE.md
documentation/CLAUDE.md
documentation/CONTRIBUTING.md
documentation/CREATE_RESALE_CONFIG_INSTRUCTIONS.md
documentation/CRITICAL_FIXES_NEEDED.md
documentation/DEPLOYMENT_GUIDE.md
documentation/DEPLOYMENT_SUMMARY.md
documentation/FIXES_APPLIED.md
documentation/KIOSK_INTEGRATION_STATUS.md
documentation/KIOSK_MARKETPLACE.md
documentation/LOYALTY_DEBUG.md
documentation/LOYALTY_INTEGRATION_COMPLETE.md
documentation/LOYALTY_MILESTONES_IMPLEMENTED.md
documentation/LOYALTY_POINTS_ECONOMY.md
documentation/LOYALTY_SYSTEM_README.md
documentation/ORGANIZER_DASHBOARD_COMPLETE.md
documentation/PREFLIGHT_CHECKLIST.md
documentation/PROJECT_PLAN.md
documentation/PROJECT_RENAMED_TO_SUITIX.md
documentation/PROJECT_SUMMARY.md
documentation/QUICK_START.md
documentation/README_DEPLOYMENT.md
documentation/REDEPLOYMENT_GUIDE.md
documentation/ROOT_CAUSE_ANALYSIS.md
documentation/ROYALTY_SPLIT_IMPLEMENTATION.md
documentation/SEAT_MAP_IMPLEMENTATION.md
documentation/SESSION_SUMMARY.md
documentation/SETUP_GUIDE.md
documentation/SMART_CONTRACT_AUDIT.md
frontend/.env.example
frontend/.gitignore
frontend/next.config.js
frontend/package.json
frontend/postcss.config.js
frontend/src/app/api/ticket-image/route.tsx
frontend/src/app/debug/page.tsx
frontend/src/app/events/[id]/page.tsx
frontend/src/app/events/page.tsx
frontend/src/app/globals.css
frontend/src/app/layout.tsx
frontend/src/app/loyalty/page.tsx
frontend/src/app/marketplace/page.tsx
frontend/src/app/my-tickets/page.tsx
frontend/src/app/not-found.tsx
frontend/src/app/organizer/checkin/CheckInClient.tsx
frontend/src/app/organizer/checkin/page.tsx
frontend/src/app/organizer/page.tsx
frontend/src/app/page.tsx
frontend/src/components/auth/LoginButton.tsx
frontend/src/components/blockchain/AntiScalpingBadge.tsx
frontend/src/components/blockchain/BlockchainNotification.tsx
frontend/src/components/blockchain/BlockchainStats.tsx
frontend/src/components/blockchain/LiveActivityFeed.tsx
frontend/src/components/blockchain/TransactionToast.tsx
frontend/src/components/EmptyState.tsx
frontend/src/components/events/SeatSelection.tsx
frontend/src/components/home/FeaturedEvents.tsx
frontend/src/components/home/Features.tsx
frontend/src/components/home/Hero.tsx
frontend/src/components/home/Stats.tsx
frontend/src/components/marketplace/MarketplaceListing.tsx
frontend/src/components/Navigation.tsx
frontend/src/components/organizer/CreateEventForm.tsx
frontend/src/components/organizer/Dashboard.tsx
frontend/src/components/organizer/EventsList.tsx
frontend/src/components/organizer/TicketScanner.tsx
frontend/src/components/pricing/PriceDisplay.tsx
frontend/src/components/Providers.tsx
frontend/src/components/RegisterEnokiWallets.tsx
frontend/src/components/tickets/KioskProfitsBanner.tsx
frontend/src/components/tickets/ListTicketModal.tsx
frontend/src/components/tickets/SeatMap.tsx
frontend/src/components/tickets/TicketCard.tsx
frontend/src/components/ui/toaster.tsx
frontend/src/hooks/useDynamicPricing.ts
frontend/src/lib/kiosk.ts
frontend/src/lib/ptb/delist.ts
frontend/src/lib/ptb/ensureKiosk.ts
frontend/src/lib/ptb/index.ts
frontend/src/lib/ptb/placeAndList.ts
frontend/src/lib/ptb/purchaseFromKiosk.ts
frontend/src/lib/ptb/withdrawRoyalty.ts
frontend/src/lib/ptb/withdrawSeller.ts
frontend/src/lib/qr-generator.ts
frontend/src/lib/sui-client.ts
frontend/src/lib/types/protocol.ts
frontend/src/lib/utils.ts
frontend/tailwind.config.ts
frontend/tsconfig.json
LICENSE
README.md
```

### Dependencies

- frontend/package.json: @mysten/dapp-kit@^0.19.6, @mysten/enoki@^0.12.8, @mysten/sui@^1.43.1, @openai/codex@^0.50.0, @radix-ui/react-dialog@^1.0.5, @radix-ui/react-dropdown-menu@^2.0.6, @radix-ui/react-select@^2.0.0, @radix-ui/react-tabs@^1.0.4, @radix-ui/react-toast@^1.1.5, @tanstack/react-query@^5.0.0, @types/node@^20, @types/qrcode@^1.5.5, @types/react@^18, @types/react-dom@^18, @yudiel/react-qr-scanner@^2.4.1, autoprefixer@^10.0.1, chart.js@^4.4.0, class-variance-authority@^0.7.0, clsx@^2.0.0, eslint@^8, eslint-config-next@14.0.4, html5-qrcode@^2.3.8, lucide-react@^0.294.0, next@14.0.4, postcss@^8, qrcode@^1.5.3, qrcode.react@^4.2.0, react@^18, react-chartjs-2@^5.2.0, react-dom@^18, tailwind-merge@^2.1.0, tailwindcss@^3.3.0, tailwindcss-animate@^1.0.7, typescript@^5, vercel@^48.6.0

### Recent commits (newest first)

- last fix
- folder structure fix
- folder cleanup
- updated marketplace
- cooked with my tickets
- Merge pull request #5 from RohanChintakindi/newticket
- Merge branch 'main' into newticket
- added black color to tickets
- marketplace tickets and updates
- frontend UI changes and blockchain notifs
- Merge branch 'main' of https://github.com/RohanChintakindi/sui-hackathon-project
- qr enlarge
- qr code fixed new lib
- Update copyright year in LICENSE file
- added readme
- Merge newticket branch with updated ticket design
- fixed chintu's frontend
- ticket ID copy
- new ticket frontend
- fixed scanner

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

### documentation/FIXES_APPLIED.md

```markdown
# Critical Fixes Applied - Summary

**Date:** October 25, 2025
**Status:** ✅ ALL CRITICAL FIXES COMPLETED

## ✅ Summary of Changes

### Files Modified:
1. **contracts/sources/ticket_nft.move**
   - Added 9 new error constants (lines 22-30)
   - Added comprehensive input validations to `create_event()` (lines 144-164)
   - Added Clock parameter to function signature (line 141)

2. **frontend/src/components/organizer/CreateEventForm.tsx**
   - Added Clock object parameter to create_event transaction (line 128)

### Build Status: ✅ SUCCESS
```
BUILDING sui_ticketing_loyalty
✅ All modules compiled successfully
⚠️  31 warnings (style only, no errors)
```

## 🔒 Security Improvements

### Validations Added:

1. **Royalty Percentage:** Max 50% (prevents overflow)
2. **Event Capacity:** 1 to 1,000,000 (prevents DOS)
3. **Base Price:** > 0 and <= 1000 SUI (prevents free tickets)
4. **Event Name:** 1-200 characters (prevents empty/bloat)
5. **Description:** <= 2000 characters (prevents storage attacks)
6. **Venue:** 1-300 characters (prevents empty venues)
7. **Event Date:** Must be in future (prevents past events)

## 🎯 Attack Vectors Eliminated

- ❌ Royalty overflow (10000% royalty)
- ❌ Storage bloat (GB descriptions)
- ❌ Zero-capacity DOS
- ❌ Past event creation
- ❌ Free ticket exploits

## 📊 Next Steps

✅ **COMPLETED:** All critical fixes applied
🔜 **NEXT:** Deploy to testnet and test validations
🔜 **AFTER:** Add unit tests for each validation

---

**Status:** Ready for testnet deployment

```

### documentation/CREATE_RESALE_CONFIG_INSTRUCTIONS.md

```markdown
# How to Fix Marketplace Purchase Error

## Problem
You're getting a `TypeMismatch` error when trying to buy tickets from the marketplace because the event doesn't have a `ResaleConfig` object.

## Solution
Create a ResaleConfig for the event that has the listed ticket.

---

## Quick Fix (Recommended)

### Step 1: Find the Event ID
The marketplace should show you the event ID in the console when you try to buy. Look for:
```
Event ID: 0x...
```

### Step 2: Create ResaleConfig using Sui CLI

Run this command (replace `YOUR_EVENT_ID` with the actual event ID):

```bash
sui client call --package $NEXT_PUBLIC_PACKAGE_ID \
  --module policy \
  --function create_resale_config \
  --args YOUR_EVENT_ID 0 0 \
  --gas-budget 10000000
```

**Parameters:**
- `YOUR_EVENT_ID`: The event ID from step 1
- `0`: Floor price (0 = no minimum)
- `0`: Ceiling price (0 = no maximum)

### Step 3: Try purchasing again
Go back to the marketplace and try buying the ticket again. It should work now!

---

## Alternative: Create ResaleConfig from the Event Details Page

If you know the event ID, you can also create it via a transaction in the browser console:

```javascript
// In browser console on any page
const { Transaction } = await import('@mysten/sui/transactions');
const tx = new Transaction();

const PACKAGE_ID = "0x90b00f81442d186d69f508a9ab86c071a8cf2e6fac7c7d2b169e16ea1d79d22d";
const EVENT_ID = "YOUR_EVENT_ID"; // Replace this

tx.moveCall({
  target: `${PACKAGE_ID}::policy::create_resale_config`,
  arguments: [
    tx.object(EVENT_ID),
    tx.pure.u64(0), // floor price (0 = no min)
    tx.pure.u64(0), // ceiling price (0 = no max)
  ],
});

// Then sign and execute with your wallet
```

---

## Going Forward

**New events will automatically have ResaleConfig created**, so this issue won't happen for events you create from now on.

For existing events without ResaleConfig, you need to manually create one using the methods above.

---

## What is ResaleConfig?

ResaleConfig defines the rules for secondary market sales:
- **Floor Price**: Minimum price tickets can be resold for
- **Ceiling Price**: Maximum price tickets can be resold for (0 = unlimited)
- **Resale Enabled**: Whether tickets can be resold at all

It's required by the `confirm_with_rules` function to validate marketplace purchases and enforce royalty splits.

```

### frontend/package.json

```
{
  "name": "suitix-frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@mysten/dapp-kit": "^0.19.6",
    "@mysten/enoki": "^0.12.8",
    "@mysten/sui": "^1.43.1",
    "@openai/codex": "^0.50.0",
    "@radix-ui/react-dialog": "^1.0.5",
    "@radix-ui/react-dropdown-menu": "^2.0.6",
    "@radix-ui/react-select": "^2.0.0",
    "@radix-ui/react-tabs": "^1.0.4",
    "@radix-ui/react-toast": "^1.1.5",
    "@tanstack/react-query": "^5.0.0",
    "@yudiel/react-qr-scanner": "^2.4.1",
    "chart.js": "^4.4.0",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.0.0",
    "html5-qrcode": "^2.3.8",
    "lucide-react": "^0.294.0",
    "next": "14.0.4",
    "qrcode": "^1.5.3",
    "qrcode.react": "^4.2.0",
    "react": "^18",
    "react-chartjs-2": "^5.2.0",
    "react-dom": "^18",
    "tailwind-merge": "^2.1.0",
    "tailwindcss-animate": "^1.0.7",
    "vercel": "^48.6.0"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/qrcode": "^1.5.5",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "eslint": "^8",
    "eslint-config-next": "14.0.4",
    "postcss": "^8",
    "tailwindcss": "^3.3.0",
    "typescript": "^5"
  }
}

```

### frontend/src/app/page.tsx

```typescript
"use client";

import { Hero } from "@/components/home/Hero";
import { FeaturedEvents } from "@/components/home/FeaturedEvents";
import { Features } from "@/components/home/Features";
import { LiveActivityFeed } from "@/components/blockchain/LiveActivityFeed";

export default function Home() {
  return (
    <div className="space-y-16">
      <Hero />

      <FeaturedEvents />

      {/* Live Blockchain Activity */}
      <section className="container mx-auto px-4">
        <LiveActivityFeed />
      </section>

      <Features />
    </div>
  );
}



```

### frontend/src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "@mysten/dapp-kit/dist/index.css";
import "./globals.css";
import { Providers } from "@/components/Providers";
import { Navigation } from "@/components/Navigation";
import { ToasterProvider } from "@/components/ui/toaster";

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

export const metadata: Metadata = {
  title: "SuiTix - Blockchain Ticketing Platform",
  description: "Next-generation blockchain-based ticketing and loyalty rewards system on Sui",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Providers>
          <ToasterProvider>
            <div className="min-h-screen bg-black">
              <Navigation />
              <main className="container mx-auto px-6 py-12">
                {children}
              </main>
            </div>
          </ToasterProvider>
        </Providers>
      </body>
    </html>
  );
}



```

### frontend/src/lib/ptb/index.ts

```typescript
/**
 * PTB (Programmable Transaction Block) Helpers
 * Re-exports all transaction builder functions for kiosk operations
 */

export { buildEnsureKioskTx } from './ensureKiosk';
export { buildPlaceAndListTx } from './placeAndList';
export { buildPurchaseFromKioskTx } from './purchaseFromKiosk';
export { buildDelistTx, buildUpdatePriceTx } from './delist';
export { buildWithdrawAllProfitsTx, buildWithdrawProfitsTx } from './withdrawSeller';
export { buildWithdrawAllRoyaltiesTx, buildWithdrawRoyaltyAmountTx } from './withdrawRoyalty';

```

### frontend/src/app/organizer/page.tsx

```typescript
"use client";

import { useState } from "react";
import { useCurrentAccount } from "@mysten/dapp-kit";
import { useRouter } from "next/navigation";
import { Calendar, List, BarChart3, ScanLine } from "lucide-react";
import { CreateEventForm } from "@/components/organizer/CreateEventForm";
import { EventsList } from "@/components/organizer/EventsList";
import { Dashboard } from "@/components/organizer/Dashboard";

export default function OrganizerPage() {
  const account = useCurrentAccount();
  const router = useRouter();
  const [activeTab, setActiveTab] = useState<"dashboard" | "create" | "manage">("dashboard");

  if (!account) {
    return (
      <div className="flex items-center justify-center min-h-[60vh]">
        <div className="text-center glass rounded-2xl p-12">
          <div className="w-20 h-20 rounded-xl bg-gradient-to-br from-blue-500 to-purple-500 flex items-center justify-center mx-auto mb-6">
            <Calendar className="w-10 h-10 text-white" />
          </div>
          <h2 className="text-3xl font-bold text-white mb-3">
            Connect Your Wallet
          </h2>
          <p className="text-gray-400">
            Please connect your wallet to create events
          </p>
        </div>
      </div>
    );
  }

  return (
    <div className="max-w-7xl mx-auto py-8">
      {/* Clean Tab Navigation */}
      <div className="flex items-center space-x-1 mb-8 bg-white/5 rounded-xl p-1 w-fit">
        <button
          onClick={() => setActiveTab("dashboard")}
          className={`
            px-6 py-2.5 rounded-lg font-medium transition-all text-sm flex items-center space-x-2
            ${
              activeTab === "dashboard"
                ? "bg-white text-gray-900 shadow-lg"
                : "text-gray-400 hover:text-gray-300"
            }
          `}
        >
          <BarChart3 className="w-4 h-4" />
          <span>Dashboard</span>
        </button>
        <button
          onClick={() => setActiveTab("create")}
          className={`
            px-6 py-2.5 rounded-lg font-medium transition-all text-sm flex items-center space-x-2
            ${
              activeTab === "create"
                ? "bg-white text-gray-900 shadow-lg"
                : "text-gray-400 hover:text-gray-300"
            }
          `}
        >
          <Calendar className="w-4 h-4" />
          <span>Create Event</span>
        </button>
        <button
          onClick={() => setActiveTab("manage")}
          className={`
            px-6 py-2.5 rounded-lg font-medium transition-all text-sm flex items-center space-x-2
            ${
              activeTab === "manage"
                ? "bg-white text-gray-900 shadow-lg"
                : "text-gray-400 hover:text-gray-300"
            }
          `}
        >
          <List className="w-4 h-4" />
          <span>My Events</span>
        </button>
        <button
          onClick={() => router.push("/organizer/checkin")}
          className="px-6 py-2.5 rounded-lg font-medium transition-all text-sm flex items-center space-x-2 text-gray-400 hover:text-gray-300 hover:bg-white/5"
        >
          <ScanLine className="w-4 h-4" />
          <span>Scanner</span>
        </button>
      </div>

      {/* Content */}
      <div>
        {activeTab === "dashboard" && <Dashboard />}
        {activeTab === "create" && <CreateEventForm />}
        {activeTab === "manage" && <EventsList />}
      </div>
    </div>
  );
}

```

### frontend/src/app/debug/page.tsx

```typescript
"use client";

import { useState } from "react";
import { useCurrentAccount, useSuiClient } from "@mysten/dapp-kit";
import { Loader2 } from "lucide-react";

const PACKAGE_ID = process.env.NEXT_PUBLIC_PACKAGE_ID || "0x0";

export default function DebugPage() {
  const account = useCurrentAccount();
  const suiClient = useSuiClient();
  const [debugInfo, setDebugInfo] = useState<any>(null);
  const [isLoading, setIsLoading] = useState(false);

  const runDiagnostics = async () => {
    if (!account?.address) {
      alert("Please connect your wallet first");
      return;
    }

    setIsLoading(true);
    const info: any = {
      packageId: PACKAGE_ID,
      userAddress: account.address,
      userTickets: [],
      userKiosks: [],
      itemListedEvents: [],
      itemPurchasedEvents: [],
      errors: [],
    };

    try {
      // 1. Check user's tickets
      console.log("Checking user tickets...");
      try {
        const tickets = await suiClient.getOwnedObjects({
          owner: account.address,
          filter: {
            StructType: `${PACKAGE_ID}::ticket_nft::Ticket`,
          },
          options: {
            showContent: true,
            showType: true,
            showOwner: true,
          },
        });
        info.userTickets = tickets.data;
        console.log("User tickets:", tickets.data);
      } catch (e: any) {
        info.errors.push(`Error fetching tickets: ${e.message}`);
      }

      // 2. Check user's kiosks
      console.log("Checking user kiosks...");
      try {
        const kiosks = await suiClient.getOwnedObjects({
          owner: account.address,
          filter: {
            StructType: `0x2::kiosk::KioskOwnerCap`,
          },
          options: {
            showContent: true,
            showType: true,
          },
        });
        info.userKiosks = kiosks.data;
        console.log("User kiosks:", kiosks.data);

        // For each kiosk, get its details
        for (const kioskCap of kiosks.data) {
          if (kioskCap.data?.content?.dataType === "moveObject") {
            const fields = (kioskCap.data.content as any).fields;
            const kioskId = fields.for || fields.kiosk;

            if (kioskId) {
              try {
                const kioskObj = await suiClient.getObject({
                  id: kioskId,
                  options: {
                    showContent: true,
                    showOwner: true,
                  },
                });
                console.log("Kiosk object:", kioskObj);
              } catch (e: any) {
                info.errors.push(`Error fetching kiosk ${kioskId}: ${e.message}`);
              }
            }
          }
        }
      } catch (e: any) {
        info.errors.push(`Error fetching kiosks: ${e.message}`);
      }

      // 3. Check ItemListed events
      console.log("Checking ItemListed events...");
      try {
        const listedEvents = await suiClient.queryEvents({
          query: {
            MoveEventType: `0x2::kiosk::ItemListed<${PACKAGE_ID}::ticket_nft::Ticket>`
          },
          limit: 50,
        });
        info.itemListedEvents = listedEvents.data;
        console.log("ItemListed events:", listedEvents.data);
      } catch (e: any) {
        info.errors.push(`Error querying ItemListed events: ${e.message}`);
      }

      // 4. Check ItemPurchased events
      console.log("Checking ItemPurchased events...");
      try {
        const purchasedEvents = await suiClient.queryEvents({
          query: {
            MoveEventType: `0x2::kiosk::ItemPurchased<${PACKAGE_ID}::ticket_nft::Ticket>`
          },
          limit: 50,
        });
        info.itemPurchasedEvents = purchasedEvents.data;
        console.log("ItemPurchased events:", purchasedEvents.data);
      } catch (e: any) {
        info.errors.push(`Error querying ItemPurchased events: ${e.message}`);
      }

      // 5. Check custom TicketListed events from kiosk_adapter
      console.log("Checking custom TicketListed events...");
      try {
        const customEvents = await suiClient.queryEvents({
          query: {
            MoveEventType: `${PACKAGE_ID}::kiosk_adapter::TicketListed`
          },
          limit: 50,
        });
        info.customTicketListedEvents = customEvents.data;
        console.log("Custom TicketListed events:", customEvents.data);
      } catch (e: any) {
        info.errors.push(`Error querying custom TicketListed events: ${e.message}`);
      }

    } catch (e: any) {
      info.errors.push(`General error: ${e.message}`);
    }

    setDebugInfo(info);
    setIsLoading(false);
  };

  return (
    <div className="container mx-auto px-4 py-8">
      <div className="max-w-4xl mx-auto">
        <h1 className="text-4xl font-bold text-white mb-8">Debug Marketplace</h1>

        <div className="glass rounded-xl p-6 mb-6">
          <h2 className="text-xl font-bold text-white mb-4">Diagnostics</h2>
          <p className="text-gray-400 mb-4">
            This page will help diagnose why kiosk listings aren't showing up in the marketplace.
          </p>

          <button
            onClick={runDiagnostics}
            disabled={isLoading || !account}
            className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white px-6 py-3 rounded-lg font-semibold transition-all disabled:opacity-50 disabled:cursor-not-allowed"
          >
            {isLoading ? (
              <span className="flex items-center">
                <Loader2 className="w-5 h-5 animate-spin mr-2" />
                Running Diagnostics...
              </span>
            ) : (
              "Run Diagnostics"
            )}
          </button>

          {!account && (
            <p className="text-orange-400 mt-4">⚠️ Please connect your wallet first</p>
          )}
        </div>

        {debugInfo && (
          <div className="space-y-4">
            {/* Package Info */}
            <div className="glass rounded-xl p-6">
        
[truncated — 5793 more characters]
```

### frontend/src/app/events/page.tsx

```typescript
﻿"use client";

import { useState, useEffect, useRef } from "react";
import { Search, Filter, Calendar, MapPin, DollarSign, Users, TrendingUp, Loader2 } from "lucide-react";
import Link from "next/link";
import { useSuiClient } from "@mysten/dapp-kit";
import { calculateDynamicPrice } from "@/lib/sui-client";
import { PriceDisplay, PriceProgressBar } from "@/components/pricing/PriceDisplay";

const PACKAGE_ID = process.env.NEXT_PUBLIC_PACKAGE_ID!;
const EVENT_REGISTRY = process.env.NEXT_PUBLIC_EVENT_REGISTRY!;

interface BlockchainEvent {
  id: string;
  name: string;
  description: string;
  venue: string;
  category: string;
  eventDate: number;
  capacity: number;
  basePrice: number;
  ticketsSold: number;
  isActive: boolean;
  organizer: string;
  imageUrl: string;
}

const CATEGORIES = [
  { id: "all", name: "All Events", icon: "🎫" },
  { id: "music", name: "Music", icon: "🎵" },
  { id: "sports", name: "Sports", icon: "⚽" },
  { id: "tech", name: "Tech", icon: "💻" },
  { id: "comedy", name: "Comedy", icon: "😄" },
  { id: "art", name: "Art", icon: "🎨" },
  { id: "other", name: "Other", icon: "📌" },
];

export default function EventsPage() {
  const [events, setEvents] = useState<BlockchainEvent[]>([]);
  const [filteredEvents, setFilteredEvents] = useState<BlockchainEvent[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [searchQuery, setSearchQuery] = useState("");
  const [selectedCategory, setSelectedCategory] = useState("all");
  const [showPriceDropdown, setShowPriceDropdown] = useState(false);
  const priceButtonRef = useRef<HTMLButtonElement | null>(null);
  const priceDropdownRef = useRef<HTMLDivElement | null>(null);
  const [sortBy, setSortBy] = useState<"date" | "price" | "popular">("date");
  const [priceRange, setPriceRange] = useState({ min: 0, max: 1000 });
  const [dateFilter, setDateFilter] = useState<"all" | "today" | "week" | "month">("all");

  const suiClient = useSuiClient();

  // Close price dropdown when clicking outside
  useEffect(() => {
    function handleClickOutside(e: MouseEvent) {
      const target = e.target as Node;
      if (
        showPriceDropdown &&
        priceDropdownRef.current &&
        !priceDropdownRef.current.contains(target) &&
        priceButtonRef.current &&
        !priceButtonRef.current.contains(target)
      ) {
        setShowPriceDropdown(false);
      }
    }
    document.addEventListener("mousedown", handleClickOutside);
    return () => document.removeEventListener("mousedown", handleClickOutside);
  }, [showPriceDropdown]);

  // Fetch all events
  useEffect(() => {
    async function fetchEvents() {
      try {
        setIsLoading(true);
        const registryObj = await suiClient.getObject({
          id: EVENT_REGISTRY,
          options: { showContent: true },
        });

        if (registryObj.data?.content?.dataType !== "moveObject") {
          setIsLoading(false);
          return;
        }

        const registryFields = (registryObj.data.content as any).fields;
        const eventIds: string[] = registryFields.events || [];

        if (eventIds.length === 0) {
          setEvents([]);
          setFilteredEvents([]);
          setIsLoading(false);
          return;
        }

        const eventsData = await Promise.all(
          eventIds.map(async (eventId) => {
            try {
              const eventObj = await suiClient.getObject({
                id: eventId,
                options: { showContent: true },
              });

              if (eventObj.data?.content?.dataType === "moveObject") {
                const fields = (eventObj.data.content as any).fields;
                return {
                  id: eventObj.data.objectId,
                  name: fields.name || "Untitled Event",
                  description: fields.description || "",
                  venue: fields.venue || "TBA",
                  category: fields.category || "music",
                  eventDate: parseInt(fields.event_date || "0"),
                  capacity: parseInt(fields.capacity || "0"),
                  basePrice: parseInt(fields.base_price || "0"),
                  ticketsSold: parseInt(fields.tickets_sold || "0"),
                  isActive: fields.is_active !== false,
                  organizer: fields.organizer || "",
                  imageUrl: fields.image_url || "",
                };
              }
              return null;
            } catch {
              return null;
            }
          })
        );

        const validEvents = eventsData.filter((e): e is BlockchainEvent =>
          e !== null && e.isActive
        );

        setEvents(validEvents);
        setFilteredEvents(validEvents);
        setIsLoading(false);
      } catch {
        setIsLoading(false);
      }
    }

    if (PACKAGE_ID && EVENT_REGISTRY) {
      fetchEvents();
    }
  }, [suiClient]);

  // Apply filters whenever dependencies change
  useEffect(() => {
    let filtered = [...events];

    // Search filter
    if (searchQuery) {
      filtered = filtered.filter(event =>
        event.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
        event.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
        event.venue.toLowerCase().includes(searchQuery.toLowerCase())
      );
    }

    // Category filter (use actual category field)
    if (selectedCategory !== "all") {
      filtered = filtered.filter(event =>
        event.category.toLowerCase() === selectedCategory.toLowerCase()
      );
    }

    // Price filter (use current dynamic price, not base)
    filtered = filtered.filter(event => {
      const dynamic = calculateDynamicPrice(
        event.basePrice,
        event.ticketsSold,
        event.capacity,
        event.eventDate,
        undefined,
        undefined
      );
      const priceInSui = dynamic.currentPrice / 1_000_000_000;
      return priceInSui >= priceRange.min && priceInSui <= priceRange.max;
    });

    // Date filter
    const now = Date.
[truncated — 11676 more characters]
```

### frontend/src/app/my-tickets/page.tsx

```typescript
"use client";

import { useCurrentAccount, useSuiClient, useSignAndExecuteTransaction } from "@mysten/dapp-kit";
import { TicketCard } from "@/components/tickets/TicketCard";
import { ListTicketModal } from "@/components/tickets/ListTicketModal";
import { EmptyState } from "@/components/EmptyState";
import { Ticket, Loader2, DollarSign, TrendingUp, X } from "lucide-react";
import { useEffect, useState } from "react";
import { fetchUserTickets, fetchUserKiosk, PACKAGE_ID, MODULES } from "@/lib/sui-client";
import { Transaction } from "@mysten/sui/transactions";
import { BlockchainNotification } from "@/components/blockchain/BlockchainNotification";

interface TicketData {
  id: string;
  eventName: string;
  eventDate: string;
  eventTime?: string;
  eventImageUrl?: string;
  venue: string;
  seatNumber: string;
  seatSection: string;
  ticketType: string;
  status: number;
  purchasePrice: string;
  qrCode?: string;
}

export default function MyTicketsPage() {
  const account = useCurrentAccount();
  const suiClient = useSuiClient();
  const { mutate: signAndExecute } = useSignAndExecuteTransaction();
  const [tickets, setTickets] = useState<TicketData[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [kioskId, setKioskId] = useState<string | null>(null);
  const [kioskCapId, setKioskCapId] = useState<string | null>(null);
  const [kioskProfits, setKioskProfits] = useState<string>("0");
  const [isWithdrawing, setIsWithdrawing] = useState(false);
  const [showBanner, setShowBanner] = useState(true);
  const [listTicketId, setListTicketId] = useState<string | null>(null);
  const [listTicketInfo, setListTicketInfo] = useState<{ eventName: string; seatNumber: string; seatSection: string; purchasePrice: string } | null>(null);

  // Notification state
  const [txStatus, setTxStatus] = useState<"pending" | "success" | "error" | null>(null);
  const [txDigest, setTxDigest] = useState<string | undefined>();
  const [txMessage, setTxMessage] = useState("");
  const [txTechnicalDetails, setTxTechnicalDetails] = useState<string[]>([]);

  useEffect(() => {
    async function loadTickets() {
      if (!account?.address) {
        setIsLoading(false);
        return;
      }

      try {
        setIsLoading(true);
        setError(null);

        console.log("Fetching tickets for address:", account.address);

        // Fetch user's kiosk to check for profits
        try {
          const kiosks = await fetchUserKiosk(account.address);
          if (kiosks.length > 0 && kiosks[0].data?.content) {
            const content = kiosks[0].data.content as any;
            if (content.dataType === "moveObject" && content.fields) {
              const capId = kiosks[0].data.objectId;
              const kId = content.fields.for;

              setKioskCapId(capId);
              setKioskId(kId);

              // Fetch kiosk object to get profits
              const kioskObj = await suiClient.getObject({
                id: kId,
                options: { showContent: true },
              });

              if (kioskObj.data?.content && 'fields' in kioskObj.data.content) {
                const kioskFields = kioskObj.data.content.fields as any;
                const profits = kioskFields.profits?.toString() || "0";
                setKioskProfits(profits);
                console.log("Kiosk profits:", profits);
              }
            }
          }
        } catch (err) {
          console.error("Error fetching kiosk:", err);
        }

        // Fetch user's tickets from blockchain
        const ticketObjects = await fetchUserTickets(account.address);

        console.log("Fetched ticket objects:", ticketObjects);

        // Transform blockchain data to match UI format
        const ticketsData = await Promise.all(
          ticketObjects.map(async (ticketObj) => {
            try {
              if (ticketObj.data?.content?.dataType === "moveObject") {
                const fields = (ticketObj.data.content as any).fields;

                // Fetch event details using event_id
                let eventName = "Unknown Event";
                let eventDate = "TBA";
                let eventTime: string | undefined = undefined;
                let venue = "TBA";
                let eventImageUrl: string | undefined = undefined;

                try {
                  const eventObj = await suiClient.getObject({
                    id: fields.event_id,
                    options: {
                      showContent: true,
                    },
                  });

                  if (eventObj.data?.content?.dataType === "moveObject") {
                    const eventFields = (eventObj.data.content as any).fields;
                    eventName = eventFields.name || "Unknown Event";
                    venue = eventFields.venue || "TBA";
                    eventImageUrl = eventFields.image_url || undefined;
                    const eventTimestamp = parseInt(
                      eventFields.event_date || "0"
                    );
                    if (eventTimestamp > 0) {
                      const d = new Date(eventTimestamp);
                      eventDate = d.toLocaleDateString("en-US", {
                        year: "numeric",
                        month: "long",
                        day: "numeric",
                      });
                      eventTime = d.toLocaleTimeString("en-US", {
                        hour: "2-digit",
                        minute: "2-digit",
                      });
                    }
                  }
                } catch (err) {
                  console.error("Error fetching event details:", err);
                }

                // DYNAMIC NFT - Check validation status from Event's dynamic fields!
                let ticketStatus = parseInt(fields.status || "0");

                try {
                  // Query if this ticket has been validated (stored as 
[truncated — 12283 more characters]
```

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