All projects

Coursework · 2025 — 2026

NFT Event Ticketing

A ticketing experiment that taught me when blockchain earns its complexity and when a database is still the better tool.

My contribution

Owned the smart contracts, oracle, backend chain integration, and wallet purchase flow in a two-person team.

NFT Event Ticketing screenshot 1
1 / 5

Turning tickets into programmable assets

This project is how I learned that blockchain can solve real problems, but only when its benefits are worth the added cost. It began as an assignment for a blockchain course, and my teammate and I chose event ticketing because counterfeiting and scalping remain common problems in Indonesia.

We represented every ticket as an NFT, giving it a verifiable identity and allowing purchasing rules to live inside the smart contract. Even if someone bypassed the website, the contract could still reject a resale above the permitted price.

I had worked with the Internet Computer Protocol before, but I had never built with Solidity or the Ethereum ecosystem. I volunteered to own the smart-contract layer because I wanted to try something unfamiliar.

I am proudest of the anti-scalping rule and the signed tax oracle. The oracle showed me how carefully off-chain information must be verified before a contract can trust it.

Presale=PoriginalP_{\mathrm{resale}} = P_{\mathrm{original}}
The contract permits resale only at the original ticket price, so bypassing the website cannot create a higher listing.

The bug hiding outside the code

One of the hardest parts was simply running and testing the blockchain locally. At one point, the contracts stopped behaving as expected. I spent hours reading the code before discovering that local chain state or cached artifacts needed to be reset.

It taught me that a contract can be correct while its deployment state or surrounding environment is out of sync. Debugging requires understanding the environment, not only the code inside it.

Storing everything on-chain would have been unnecessarily expensive and slow. We kept ticket ownership, capacity, pricing rules, and financial state on the blockchain. Descriptions, images, categories, and analytics stayed in PostgreSQL.

The blockchain handled the state that needed to be trusted, while the database handled information that needed to be flexible and affordable.

From curiosity to skepticism

Our main goal was to make the system work, so we did not explore blockchain security as deeply as a production platform would require. A real event would demand threat modelling, careful key management, and a much deeper review of contract vulnerabilities.

Making the happy path work is only the beginning. A production contract must remain safe when someone deliberately tries to break it.

I started this project because Solidity was new and interesting. I finished it with a more critical view of blockchain.

Blockchain offers transparent and enforceable ownership rules, but it also introduces cost, slower operations, complicated infrastructure, and additional security risk. The project taught me that blockchain must justify its cost through a problem that genuinely requires trust, verifiability, or decentralized ownership.

What stayed with me

  • Blockchain has to earn its cost.
  • Rules enforced by a contract are stronger than rules enforced by a button.
  • Not everything valuable belongs on-chain.

Run it locally

Clone the source repository or download the project ZIP, then run these commands from the project folder.

Explore the complete project

Download the source, assets, and included documentation as a ZIP archive.

Download project ZIP
bash

Environment

$ cp backend/.env.example backend/.env
$ cp frontend/.env.example frontend/.env
$ cp smart-contract/.env.example smart-contract/.env

Services

$ make up
$ make migrate
$ make seed
$ cd backend && bun run seed:users-events && cd ..

Frontend

$ cd frontend
$ bun install
$ bun run dev

# open http://localhost:3000

Seeing the seeded events

# Seeded events start as DRAFT and the public list only shows ACTIVE ones,
# so browse them without a wallet by publishing them directly:
$ docker compose exec postgres psql -U postgres -d ticketing_db -c "UPDATE events SET status='ACTIVE';"