CT124-3-3-BCD Blockchain Development · Asia Pacific University
A full-stack, role-based supply chain tracking application built on Ethereum. Every product journey — from manufacturing to final sale — is written permanently to a blockchain. Anyone can verify product authenticity by scanning a QR code, with no crypto wallet required.
| Name | TP Number |
|---|---|
| NOOR KHALIL ABDULLAH KHALED | TP078880 |
| TAHA FAHD AHMED MOHAMMED THABIT | TP078281 |
| ABUBAKER ELSIDDIG TAGELDEEN SIDDIG | TP078003 |
| MUHMMAD AHMED KHAN | TP069769 |
- Tech Stack
- Architecture
- Roles
- Full Setup Guide
- Starting the App (Every Session)
- Feature Walkthrough
- Hardhat Test Accounts
- Smart Contract Functions
- API Reference
- Known Limitations
| Layer | Technology |
|---|---|
| Smart Contract | Solidity 0.8.20, Hardhat, ethers v6 |
| Frontend | Next.js 14 (App Router), TypeScript, Tailwind CSS |
| Animations | Framer Motion, Lucide React |
| Off-chain Storage | MySQL (metadata + events), IPFS / Kubo (certificates) |
| Wallet | MetaMask |
User Layer Manufacturer · Distributor · Retailer · Regulator · Public · IoT
│
Application Layer Next.js Frontend ──── /api/* Routes ──── MySQL
│
Blockchain Layer SupplyChain.sol on Hardhat Node (Chain ID 31337)
│
Storage Layer IPFS (Kubo local daemon) — certificates anchored on-chain by CID
| Role | What they can do |
|---|---|
| MANUFACTURER | Add products, upload certificates to IPFS, issue/lift recalls, transfer to Distributor |
| DISTRIBUTOR | Accept ownership, update status to IN_TRANSIT, log IoT sensor readings, transfer to Retailer |
| RETAILER | Accept ownership, update status to DELIVERED or SOLD, log IoT sensor readings |
| REGULATOR | Read-only access to the full audit log and all product histories |
| Public | Verify any product by ID or QR code — no wallet needed |
- Node.js 18+
- MetaMask browser extension
- XAMPP (for MySQL) or any MySQL 8 instance
- IPFS Kubo (for certificate uploads)
# Install Hardhat dependencies
cd hardhat-project
npm install
# Install frontend dependencies
cd ../frontend
npm installStart MySQL (via XAMPP or any MySQL 8 service), then run the following SQL:
CREATE DATABASE IF NOT EXISTS supplychain;
USE supplychain;
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
product_id INT NOT NULL UNIQUE,
name VARCHAR(255),
batch_number VARCHAR(100),
description TEXT,
origin VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS events_log (
id INT AUTO_INCREMENT PRIMARY KEY,
product_id INT NOT NULL,
actor_address VARCHAR(42),
action VARCHAR(100),
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS contacts (
id INT AUTO_INCREMENT PRIMARY KEY,
owner_wallet VARCHAR(42) NOT NULL,
contact_address VARCHAR(42) NOT NULL,
name VARCHAR(120),
role ENUM('MANUFACTURER','DISTRIBUTOR','RETAILER','REGULATOR') DEFAULT NULL,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uq_owner_contact (owner_wallet, contact_address)
);
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
wallet_address VARCHAR(42) NOT NULL UNIQUE,
role VARCHAR(20) NOT NULL,
company_name VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);Create frontend/.env.local (the deploy script will auto-update NEXT_PUBLIC_CONTRACT_ADDRESS):
NEXT_PUBLIC_CONTRACT_ADDRESS=
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_NAME=supplychainipfs init # only needed once
ipfs daemon # keep this runningcd hardhat-project
npx hardhat nodeIn a second terminal:
cd hardhat-project
npx hardhat run scripts/deploy.ts --network localhostThe deploy script automatically updates frontend/.env.local with the new contract address and writes the ABI to frontend/public/abi/SupplyChain.json.
cd hardhat-project
npx hardhat run scripts/assignGroupRoles.ts --network localhostcd frontend
npm run devOpen http://localhost:3000.
The Hardhat node stores everything in memory — restarting it wipes all deployed contracts. Every session requires:
- Start MySQL (XAMPP or service)
- Start IPFS:
ipfs daemon - Double-click
hardhat-project/start-local.bat— this opens the node and deploys in one step - Assign roles:
npx hardhat run scripts/assignGroupRoles.ts --network localhost - Start frontend:
cd frontend && npm run dev
After each deploy the contract address changes. The
.env.localis updated automatically but the Next.js dev server must be restarted to pick it up.
- Open MetaMask and switch to the Localhost 8545 network (Chain ID: 31337).
- Import a test account using a private key from the Hardhat node output (see Hardhat Test Accounts).
- Click Connect Wallet in the navbar.
- If this wallet has never connected before, a Register User modal appears.
- Enter your Company Name and select your Role (must match the role assigned by
assignGroupRoles.ts). - Click Register. Your company name and role badge will appear immediately in the navbar and on the dashboard.
The role you select in the registration form is for display purposes. The actual on-chain permissions come from the
assignGroupRoles.tsscript. Make sure they match.
After connecting, the dashboard shows:
- Company name as the page heading
- Role badge (colour-coded: violet = Manufacturer, amber = Distributor, green = Retailer)
- Stat tiles — Total products owned, In Transit, Delivered, Sold
- Live Event Feed — real-time blockchain events as they happen
- Your Products — all products currently owned by this wallet, with search
Only available to MANUFACTURER role.
- Click Add Product in the navbar.
- Step 1 — Product Details: Fill in Product Name, Batch Number, Origin, and Description.
- Step 2 — Upload Certificate: Drop a PDF or image certificate file. It is uploaded to IPFS and the returned CID is anchored on-chain via
addCertificationHash. - Step 3 — Review: Confirm all details.
- Click Submit. MetaMask opens — approve the transaction.
- On success: a confetti animation plays and a QR code is generated linking to the product's public verification page (
/verify?id=<n>). - Right-click the QR code to download it as a PNG for printing on packaging.
Any connected wallet can visit /track/<productId> to see the full product page:
- Recall Banner — red alert at the top if the product is currently recalled
- Product header — name, batch, origin, current owner, status progress bar
- History Timeline — every on-chain event (created, transferred, status change, recall issued/lifted)
- Sensor Chart — line chart of all IoT temperature and humidity readings
- QR Code — scannable link to the public verify page
- IPFS certificate link — opens the certificate from the local IPFS gateway
- Transfer button (owner only) — opens the Transfer Ownership modal
- Update Status button (owner only) — advances the product one step in the lifecycle
- Issue Recall / Lift Recall button (MANUFACTURER only)
The current owner can transfer to any wallet that has a valid on-chain role.
- On the dashboard or track page, click Transfer.
- The Transfer Ownership Modal opens with two tabs:
- Pick from contacts — choose a saved contact from the dropdown
- Enter new address — type a 42-character
0xaddress manually, with an option to save it to your contacts
- Select or enter the recipient address.
- Click Transfer. MetaMask opens — approve the transaction.
- The product disappears from your dashboard and appears on the recipient's dashboard.
The recipient must already have a role assigned on-chain. Transfers to wallets with no role will be rejected by the smart contract.
Only the current owner can update status. Status advances one step at a time.
| From | To | Who |
|---|---|---|
| CREATED | IN_TRANSIT | Distributor |
| IN_TRANSIT | DELIVERED | Retailer |
| DELIVERED | SOLD | Retailer |
| SOLD | — | Terminal state, no further updates |
- On the track page or dashboard, click Update Status.
- The modal shows the next available status.
- Approve in MetaMask. The timeline and progress bar update instantly.
No wallet required. Anyone can use this page.
- Go to
/verify. - Enter a product ID, or click Scan QR and upload a QR code PNG from the Upload tab.
- The product's name, owner, status, and authenticity seal appear.
- If the product is currently recalled, a red recall banner appears above the seal.
- Click View Full History to go to the track page.
Accessible to all connected wallets. Best used by the REGULATOR role.
- Click Audit in the navbar.
- The full event log shows every supply-chain action across all products, ordered newest first.
- Filters: narrow by Product ID, Batch Number, Action type, or date range.
- Donut chart shows the distribution of event types visually.
- Click Export CSV to download the filtered results.
The audit log reads from the MySQL
events_logtable. It is populated automatically when products are added, transferred, or have their status updated.
Available to DISTRIBUTOR and RETAILER roles.
- Click IoT Simulator in the navbar.
- Enter the Product ID you currently own.
- Set Temperature (°C), Humidity (%), and Location string.
- Click Submit Reading. MetaMask opens — approve the transaction.
- Navigate to the track page for that product — the sensor chart updates with the new reading.
All readings are stored entirely on-chain in a SensorEntry struct array. No database is involved.
Issue and lift recalls: MANUFACTURER only. Recall banners are visible to everyone.
Issue a recall:
- On the dashboard (product card) or the track page, click Issue Recall.
- Enter a reason (e.g., "Contamination detected in batch B-204").
- Approve in MetaMask.
- A red PRODUCT RECALLED banner immediately appears on the track page and the verify page.
- The product card on the dashboard shows a red RECALLED badge.
Lift a recall:
- On the same product, click Lift Recall.
- Confirm in the modal and approve in MetaMask.
- The banner disappears. The full issue-and-lift history remains permanently on the product timeline.
- Click Contacts in the navbar.
- Click Add Contact — enter a wallet address, name, and role.
- Saved contacts appear in the Transfer Ownership modal under "Pick from contacts".
- Contacts can be edited or deleted from the contacts page.
- Contacts are stored in MySQL per wallet — they are display metadata only; the blockchain record of ownership is authoritative.
Click the palette icon in the navbar to switch between three themes:
| Theme | Colours |
|---|---|
| Nebula (default) | Violet / Indigo / Cyan with animated aurora background |
| Aurora | Mint / Sky / Violet |
| Obsidian | Monochrome / Bronze |
All colours are CSS custom properties (var(--sig-1), var(--role-mfr), etc.). Switching a theme changes one class on <html> — no component has a hardcoded colour.
The Hardhat node generates 20 deterministic accounts from a fixed mnemonic. The assignGroupRoles.ts script assigns:
| Index | Address | Role |
|---|---|---|
| 0 | 0xf39F...2266 |
Contract Owner (admin) |
| 1 | 0x70997...79C8 |
MANUFACTURER |
| 2 | 0x3C44...93BC |
DISTRIBUTOR |
| 3 | 0x90F7...b906 |
RETAILER |
| 4 | 0x15d3...522F |
REGULATOR |
Private keys are printed in the terminal when npx hardhat node starts. Import them into MetaMask using Import Account → Private Key.
| Function | Access | Description |
|---|---|---|
addProduct(name, batch, origin, desc) |
MANUFACTURER | Register a new product on-chain |
getProduct(id) |
Public | Read product details |
getTotalProducts() |
Public | Total number of products |
transferOwnership(id, newOwner) |
Current owner | Transfer product to a new wallet |
updateStatus(id, newStatus) |
Current owner | Advance status one step |
addCertificationHash(id, cid) |
MANUFACTURER | Anchor an IPFS CID on-chain |
getHistory(id) |
Public | Full on-chain history timeline |
verifyProduct(id) |
Public | Read-only product verification |
logSensorReading(id, temp, humidity, location) |
DISTRIBUTOR / RETAILER | Store an IoT reading on-chain |
getSensorReadings(id) |
Public | All sensor readings for a product |
issueRecall(id, reason) |
MANUFACTURER | Issue a product recall |
liftRecall(id) |
MANUFACTURER | Lift an active recall |
getRecall(id) |
Public | Get the current recall status |
assignRole(wallet, role) |
Contract Owner | Grant a role to a wallet |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/products?wallet=<addr> |
Products owned by a wallet |
| POST | /api/products |
Save product metadata to MySQL |
| GET | /api/events |
All events (audit log) |
| GET | /api/events?product_id=<n> |
Events for a single product |
| POST | /api/events |
Log an event |
| GET | /api/contacts?wallet=<addr> |
Saved contacts for a wallet |
| POST | /api/contacts |
Create or update a contact |
| PATCH | /api/contacts/[id] |
Update contact name / role / notes |
| DELETE | /api/contacts/[id] |
Delete a contact |
| GET | /api/users?wallet=<addr> |
Look up a registered user |
| POST | /api/users |
Register a new user (company name + role) |
- Local Hardhat node only. Every restart wipes all contracts — redeploy and reassign roles each session. Use
start-local.batto automate this. - IPFS local daemon. Certificates are pinned to a local Kubo node. In production, use a pinning service (Pinata, Filebase) for redundancy.
- MySQL required. The audit log, contacts, and user registration all depend on MySQL. If MySQL is unavailable, blockchain features still work but the audit log and contacts will be empty.
- IoT readings are manual. The simulator page submits readings via MetaMask. In production, a gateway process would sign transactions on behalf of hardware devices.