Second-year CS & Maths at Durham. I mostly build maths-heavy or ML-adjacent things - a parimutuel prediction market for random instantenous events, a hackathon-winning Gemini-powered skill-exchange app, and multimodal classifiers over EEG/image/text embeddings.
Durham Events is a deliberate pivot. Got into Solana through Durham University's FinTech Society, spotted an obvious use case for the university scene, and built it; NFT-based event ticketing on Next.js 16 (Turbopack) + React 19, with Metaplex UMI and mpl-core handling minting, Supabase Postgres as the off-chain source of truth, and @solana/wallet-adapter-react for wallet auth. Every RSVP mints an mpl-core asset straight to your wallet with on-chain attributes (event ID, venue, date); no separate metadata accounts, cheaper than SPL Token + Metadata, self-contained and verifiable without any off-chain dependency.
Coming from Node/React Native backends into Anchor, UMI, and Solana's account model has been a proper learning curve. But blockchain and DeFi are where I'm heading, not just passing through; the plan is to go deeper into on-chain yield strategies and DeFi protocol mechanics, and eventually bring the quant background properly into that space.
Durham Events is a modern, decentralised event ticketing platform built for the North East UK university scene and beyond. Rather than relying on centralised ticket databases that can be faked, lost, or counterfeited, Durham Events issues every RSVP as a real NFT minted directly to your Solana wallet. Your ticket lives on-chain — verifiable, tamper-proof, and uniquely yours.
Connect your wallet, browse local events, RSVP in seconds, and receive a cryptographically unique NFT ticket. Event organisers can verify attendance with a QR code scanner, and attendees carry a digital collectible that proves they were there.
Built for the Campus to Colosseum Hackathon — a two-week Solana hackathon for North East UK university students.
- Features
- Architecture Overview
- Tech Stack
- Getting Started
- Project Structure
- How It Works
- NFT Ticketing Deep Dive
- Event Organiser Tools
- My Tickets & QR Verification
- Developer Notes
- Roadmap
- Contributing
- License
Connect any Solana-compatible wallet (Phantom, Backpack, Solflare, and more) in a single click. Wallet addresses serve as your identity — no sign-up, no password, no centralised account. Authentication is handled entirely on-chain, meaning Durham Events never stores your private keys or credentials.
Every RSVP triggers a real NFT mint using Metaplex UMI and mpl-core. Each ticket is:
- Unique — minted with event-specific metadata (name, date, venue, seat/RSVP ID)
- On-chain — verifiable at any time on Solana Explorer
- Owned by you — transferred directly to your wallet at mint time
- Non-duplicable — one wallet, one ticket, per event (enforced on-chain and in Supabase)
A Supabase Postgres database acts as the off-chain source of truth for event metadata, RSVP records, and NFT mint addresses. This hybrid approach gives you the verifiability of the blockchain with the query flexibility of a relational database — fast lookups, rich event data, and no RPC bottlenecks for UI rendering.
A personalised dashboard that scans your connected wallet for all Durham Events NFTs. For each ticket found, it displays:
- Event name, date, time, and venue
- Ticket mint address and on-chain status
- A generated QR code linking directly to the Solana Explorer page for that NFT
Each ticket is paired with a QR code encoding its on-chain Explorer URL. Event organisers scan the QR code at the door — if the NFT exists in the wallet shown, the ticket is valid. No proprietary scanner hardware. No app required. Just a phone and Solana Explorer.
A dedicated organiser interface for creating events, managing RSVPs, viewing attendee lists, and verifying ticket validity — all in one place.
Blazing-fast development builds and production SSR via Next.js 16's Turbopack integration. Pages that need SEO (event listings, landing pages) are server-rendered; wallet-dependent pages (RSVP, My Tickets) are client-hydrated cleanly using React 19's concurrent features.
┌─────────────────────────────────────────────────────────────────┐
│ Durham Events │
│ Next.js 16 (Turbopack) │
├──────────────────────────┬──────────────────────────────────────┤
│ Frontend │ Backend │
│ React 19 + Tailwind CSS │ Supabase (Postgres) │
│ Solana Wallet Adapter │ Events · RSVPs · Ticket Metadata │
│ react-qr-code │ │
├──────────────────────────┴──────────────────────────────────────┤
│ Solana Blockchain │
│ Metaplex UMI · mpl-core NFT Standard │
│ NFT Minting · Wallet Auth · On-chain Ticket Ownership │
└─────────────────────────────────────────────────────────────────┘
Data flow for an RSVP:
User clicks "RSVP"
│
▼
Wallet signs transaction
│
▼
Metaplex UMI mints NFT → NFT lands in user's wallet
│
▼
Mint address + RSVP record saved to Supabase
│
▼
My Tickets page fetches wallet NFTs + Supabase metadata
│
▼
QR code generated from on-chain Explorer URL
| Layer | Technology | Purpose |
|---|---|---|
| Framework | Next.js 16 (Turbopack) | SSR, routing, fast builds |
| UI | React 19, Tailwind CSS | Component model, responsive design |
| Blockchain | Solana | Decentralised ticketing layer |
| Wallet | @solana/wallet-adapter-react |
Wallet connection & signing |
| NFT Minting | Metaplex UMI, mpl-core |
NFT creation & metadata standard |
| Database | Supabase (Postgres) | Off-chain event & RSVP data |
| QR Codes | react-qr-code |
Ticket verification QR generation |
| Language | TypeScript | Full-stack type safety |
- Node.js ≥ 20
- pnpm (recommended) or npm/yarn
- A Supabase project (create one free)
- A Solana wallet (e.g. Phantom) connected to Devnet for local development
# Clone the repository
git clone https://github.com/your-username/durham-events.git
cd durham-events
# Install dependencies
pnpm installCreate a .env.local file in the project root:
# Supabase
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
# Solana
NEXT_PUBLIC_SOLANA_NETWORK=devnet
NEXT_PUBLIC_SOLANA_RPC_URL=https://api.devnet.solana.com
# Metaplex (optional: custom RPC for minting reliability)
NEXT_PUBLIC_METAPLEX_RPC_URL=https://api.devnet.solana.comRun the following SQL in your Supabase SQL editor to initialise the schema:
-- Events table
create table events (
id uuid primary key default gen_random_uuid(),
title text not null,
description text,
location text,
event_date timestamptz not null,
organiser_wallet text not null,
max_capacity int,
created_at timestamptz default now()
);
-- RSVPs table
create table rsvps (
id uuid primary key default gen_random_uuid(),
event_id uuid references events(id) on delete cascade,
wallet_address text not null,
nft_mint_address text unique,
rsvp_at timestamptz default now(),
unique(event_id, wallet_address)
);pnpm devOpen http://localhost:3000. The app runs on Devnet by default — airdrop yourself some SOL at faucet.solana.com to test minting.
durham-events/
├── app/ # Next.js 15+ App Router
│ ├── page.tsx # Landing / event listings
│ ├── events/
│ │ └── [id]/
│ │ └── page.tsx # Event detail + RSVP
│ ├── my-tickets/
│ │ └── page.tsx # User ticket dashboard
│ └── organiser/
│ └── page.tsx # Event creation & management
├── components/
│ ├── WalletButton.tsx # Wallet connect / disconnect UI
│ ├── EventCard.tsx # Event listing card
│ ├── RSVPButton.tsx # RSVP + mint trigger
│ ├── TicketCard.tsx # NFT ticket display + QR code
│ └── QRCode.tsx # react-qr-code wrapper
├── lib/
│ ├── supabase.ts # Supabase client initialisation
│ ├── metaplex.ts # Metaplex UMI setup & mint logic
│ └── solana.ts # RPC helpers & wallet utilities
├── hooks/
│ ├── useWalletNFTs.ts # Fetch NFTs from connected wallet
│ └── useRSVP.ts # RSVP state & mutation logic
└── types/
└── index.ts # Shared TypeScript interfaces
The app wraps your entire session in Solana's WalletProvider and ConnectionProvider. Clicking "Connect Wallet" opens the wallet adapter modal — select Phantom, Backpack, Solflare, or any injected wallet. Once connected, your public key is used as your identity across the app.
The homepage lists upcoming Durham events fetched server-side from Supabase. Each event page shows full details and an RSVP button. If you've already RSVPed (checked against Supabase + your wallet), the button shows your ticket status instead.
Clicking RSVP triggers the following sequence:
- The app checks Supabase to confirm your wallet hasn't already RSVPed for this event.
- Metaplex UMI constructs an
mpl-coreasset with event metadata (name, date, venue, RSVP ID) as on-chain attributes. - Your wallet signs the mint transaction.
- Once confirmed, the NFT mint address is written to Supabase alongside your RSVP record.
The My Tickets page fetches all NFTs in your wallet using the Solana RPC, filters for Durham Events assets (by collection or creator address), then cross-references Supabase for rich event metadata. Each ticket renders as a card with event info and a QR code.
Organisers scan the attendee's QR code. This opens the NFT's Solana Explorer page, instantly confirming:
- The NFT exists on-chain
- It was minted by the Durham Events program
- It lives in the wallet the attendee is showing
Durham Events uses the mpl-core standard from Metaplex — the next-generation NFT standard on Solana that supersedes the older Token Metadata program. mpl-core assets are leaner, cheaper to mint, and support on-chain attributes natively without needing separate metadata accounts.
Why mpl-core over SPL Token + Metadata?
| Feature | SPL Token + Metadata | mpl-core |
|---|---|---|
| Accounts per NFT | 3–4 | 1 |
| Attribute storage | Off-chain JSON | On-chain, native |
| Mint cost | ~0.012 SOL | ~0.005 SOL |
| Composability | External plugins | Built-in plugin system |
Ticket Metadata Structure:
{
"name": "Durham Events – Hack Night #4",
"uri": "https://your-metadata-uri.com/ticket/uuid",
"attributes": [
{ "key": "event_id", "value": "uuid-here" },
{ "key": "event_date", "value": "2025-03-20T19:00:00Z" },
{ "key": "venue", "value": "The Collingwood College Bar" },
{ "key": "rsvp_id", "value": "rsvp-uuid-here" }
]
}All attributes are written on-chain at mint time, making the ticket fully self-contained and verifiable without any off-chain dependency.
The organiser dashboard (gated by wallet address) provides:
- Create Event — Set title, description, date, venue, and capacity. Saved to Supabase instantly.
- RSVP List — View all wallet addresses that have RSVPed, with their NFT mint addresses and timestamps.
- Capacity Tracking — Real-time RSVP count vs. max capacity. The RSVP button disables automatically when full.
- Ticket Verification — Paste or scan a wallet address to confirm a valid NFT ticket exists for your event.
- Export — Download the RSVP list as CSV for offline check-in or reporting.
Organiser status is managed via a Supabase
organiserstable. In production, this can be extended to support multi-organiser roles and event co-hosting.
The My Tickets page is where on-chain and off-chain data merge:
// Simplified hook logic
const { nfts } = useWalletNFTs(); // Fetches from Solana RPC
const { tickets } = useSupabaseTickets(nfts); // Cross-references Supabase
// Each ticket renders with:
<TicketCard
eventName={ticket.event.title}
eventDate={ticket.event.event_date}
venue={ticket.event.location}
mintAddress={ticket.nft_mint_address}
/>
<QRCode value={`https://explorer.solana.com/address/${ticket.nft_mint_address}?cluster=devnet`} />The QR code value is the full Solana Explorer URL for the NFT mint address. Anyone scanning it can instantly verify the ticket on the public blockchain — no account, no app, no trust in Durham Events required.
The app defaults to Devnet for local development. Switch to Mainnet-Beta by updating NEXT_PUBLIC_SOLANA_NETWORK and NEXT_PUBLIC_SOLANA_RPC_URL. Note that Mainnet minting incurs real SOL costs — test thoroughly on Devnet first.
Public Solana RPC endpoints are rate-limited. For production, use a dedicated RPC provider such as Helius, Alchemy, or QuickNode. Set your endpoint via NEXT_PUBLIC_SOLANA_RPC_URL.
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { mplCore } from "@metaplex-foundation/mpl-core";
import { walletAdapterIdentity } from "@metaplex-foundation/umi-signer-wallet-adapters";
export const createUmiInstance = (connection: Connection, wallet: WalletAdapter) =>
createUmi(connection.rpcEndpoint)
.use(mplCore())
.use(walletAdapterIdentity(wallet));Enable RLS on both events and rsvps tables in production. The suggested policies:
events: Public read; insert/update only for organiser wallets (check againstorganiserstable)rsvps: Public read; insert only ifwallet_address = auth.jwt() ->> 'sub'(when using wallet-based JWT auth)
- Server components: Event listings page, event detail page (fast SSR, SEO-friendly)
- Client components: WalletButton, RSVPButton, My Tickets page (require wallet context)
The app follows Next.js App Router best practices — "use client" is only added where wallet interaction is genuinely needed, keeping the SSR surface area large and time-to-first-byte low.
- Mainnet deployment with production RPC
- Collection NFTs — group all Durham Events tickets under a single verified Metaplex collection
- Secondary market — list tickets for transfer via Tensor or Magic Eden
- Push notifications — Supabase Realtime alerts for event updates and capacity changes
- Mobile app — Expo Go / React Native port using the same Supabase + Solana stack
- Token-gated events — restrict RSVP to holders of specific NFTs or SPL tokens
- Organiser analytics — attendance graphs, demographic insights (wallet age, on-chain activity)
- POAP-style retrospective mints — post-event NFTs for confirmed attendees
Contributions are welcome and appreciated. To get started:
- Fork the repository
- Create your feature branch (
git checkout -b feature/token-gated-events) - Commit your changes (
git commit -m 'feat: add token gating for RSVPs') - Push to the branch (
git push origin feature/token-gated-events) - Open a Pull Request
Please ensure your PR includes:
- A clear description of what changed and why
- Tests for any new logic (especially minting and Supabase interactions)
- Updated documentation if you're adding a new feature
For major changes, open an issue first to discuss the approach.
Distributed under the MIT License. See LICENSE for details.
Built with ❤️ in Durham, UK · Powered by Solana · Campus to Colosseum Hackathon 2025
This is a Next.js project bootstrapped with create-next-app.
First, run the development server:
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun devOpen http://localhost:3000 with your browser to see the result.
You can start editing the page by modifying app/page.tsx. The page auto-updates as you edit the file.
This project uses next/font to automatically optimize and load Geist, a new font family for Vercel.
To learn more about Next.js, take a look at the following resources:
- Next.js Documentation - learn about Next.js features and API.
- Learn Next.js - an interactive Next.js tutorial.
You can check out the Next.js GitHub repository - your feedback and contributions are welcome!
The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.
Check out our Next.js deployment documentation for more details.
