A blockchain-based ticket system built on Sui for CalHacks 12.0. This dApp allows users to mint NFT tickets, view them with QR codes, and scan them for event check-in - all without a separate backend.
- Mint NFT Tickets: Free minting with a 50-ticket supply cap
- My Tickets: View your owned tickets with auto-generated QR codes
- QR Scanner: Scan tickets to verify validity and prevent double-use
- On-Chain Status: All ticket status is stored on the blockchain (fraud-proof)
- No Backend: 100% decentralized - all logic runs on Sui Move smart contracts
- Sui Move - Smart contracts
- React + TypeScript - Frontend
- Vite - Build tooling
- Radix UI - UI components
- @mysten/dapp-kit - Sui wallet integration
- qrcode.react - QR code generation
- html5-qrcode - QR code scanning
- pnpm - Package management
- Clone the repository and install dependencies:
pnpm install- Set up Sui CLI for devnet (or testnet):
# Create devnet environment
sui client new-env --alias devnet --rpc https://fullnode.devnet.sui.io:443
sui client switch --env devnet
# Create a new wallet address if needed
sui client new-address secp256k1
sui client switch --address 0xYOUR_ADDRESS
# Get devnet SUI tokens
# Visit: https://faucet.sui.ioNavigate to the Move package directory and publish:
cd move/suicket
sui client publish --gas-budget 100000000Important: Save the following from the output:
- Package ID - Look for
"packageId"in the published objects - EventCounter Object ID - Look for the shared object with type
EventCounter
Example output:
Published Objects:
PackageID: 0xabcd1234...
SharedObject EventCounter: 0xef567890...
- Copy the environment template:
cp .env.example .env- Edit
.envand fill in your deployment values:
VITE_PACKAGE_ID=0xYOUR_PACKAGE_ID_HERE
VITE_COUNTER_ID=0xYOUR_COUNTER_ID_HEREStart the development server:
pnpm devThe app will be available at http://localhost:5173
- Connect Wallet: Click "Connect" in the top right
- Mint Ticket: Go to the "Mint" page and click "Mint Ticket"
- View Ticket: Navigate to "My Tickets" to see your NFT with QR code
- Check-In: Show your QR code at the event entrance
- Open the "Scanner" page
- Click "Start Scanner" to activate camera
- Point camera at attendee's QR code
- System automatically verifies on-chain:
- ✅ Green = Valid ticket (first use)
- ❌ Red = Already used or invalid
suicket/
├── move/suicket/ # Sui Move smart contracts
│ ├── sources/
│ │ └── suicket.move # Ticket NFT contract
│ └── Move.toml # Package config
├── src/
│ ├── App.tsx # Main app with routing
│ ├── MintTicket.tsx # Minting page
│ ├── MyTickets.tsx # Ticket display with QR codes
│ ├── Scanner.tsx # QR scanner for check-in
│ ├── types.ts # TypeScript types
│ ├── constants.ts # Package/Counter IDs
│ └── networkConfig.ts # Network configuration
├── .env.example # Environment template
└── README.md # This file
EventCounter (Shared Object)
- Tracks total minted tickets
- Max supply: 50
Ticket (NFT)
ticket_number: Sequential number (1-50)owner: Current owner addressstatus: 0 = Valid, 1 = Used
mint_ticket(counter, ctx)
- Mints a new ticket NFT
- Aborts if supply cap reached
use_ticket(ticket, ctx)
- Marks ticket as "used"
- Only owner can call
- Prevents double-use
transfer_ticket(ticket, recipient, ctx)
- Transfers ticket to new owner
- Updates owner field
Run the built-in unit tests:
cd move/suicket
sui move testTests cover:
- ✅ Minting within supply cap
- ✅ Supply cap enforcement
- ✅ Ticket usage (single use)
- ✅ Double-use prevention
- ✅ Owner-only usage restriction
- ✅ Ticket transfers
- Mint a ticket
- Check it appears in "My Tickets"
- Verify QR code displays
- Scan QR code with scanner
- Mark ticket as used
- Try scanning again (should show "Already Used")
"Counter ID not configured"
- Make sure you've created
.envand filled inVITE_COUNTER_ID
"Transaction failed: Insufficient gas"
- Request more SUI from the faucet
"Camera permission denied"
- Grant camera access in browser settings for QR scanning
TypeScript errors after install
- Run
pnpm installagain - Make sure you're using Node.js v18+
For mainnet deployment:
- Switch to mainnet:
sui client switch --env mainnet- Publish contract (costs real SUI):
cd move/suicket
sui client publish --gas-budget 100000000- Update src/constants.ts:
export const MAINNET_COUNTER_PACKAGE_ID = "0xYOUR_MAINNET_PACKAGE";
export const MAINNET_COUNTER_ID = "0xYOUR_MAINNET_COUNTER";- Build for production:
pnpm build- Deploy the
dist/folder to your hosting service (Vercel, Netlify, etc.)
See DEMO_SCRIPT.md for a 2-minute presentation walkthrough for judges.
MIT