Skip to content

Repository files navigation

InternProof

Blockchain-verified OJT logbook system built on Ethereum Sepolia.

Live Demo → internproof.vercel.app
Contract → 0x9A8BD5059F3ec602c9b54D2C78d1f11eE0580bf4


What is InternProof?

InternProof replaces paper-based OJT (On-the-Job Training) logbooks with smart-contract-enforced records on Ethereum. Every clock-in, task description, supervisor signature, and coordinator confirmation is permanently recorded on-chain — independently verifiable by anyone, falsifiable by no one.

Built as a portfolio project demonstrating full-stack Web3 development: Solidity smart contracts, UUPS upgradeability, IPFS proof storage, and a production Next.js frontend.


Live Features

Student

  • Clock in/out on-chain with block timestamp enforcement
  • 4-hour minimum session before clock-out
  • Task description + proof image uploaded to IPFS on every entry
  • Submit past log entries (max 3 days back)
  • Real-time session timer in the dashboard
  • Download Supervisor Sign-off Certificate (PDF) at status 2
  • Download OJT Completion Certificate (PDF) at status 3

Supervisor

  • Approve or reject student log entries with reason
  • View proof images from IPFS inline
  • Expandable student cards with paginated log history
  • Search/filter students by name, ID, or course
  • Issue final sign-off when student reaches required hours

Coordinator

  • Confirm OJT completion after supervisor sign-off
  • View all students under their school
  • Expandable student detail cards with progress tracking

Admin

  • Register schools, courses, companies
  • Approve or revoke supervisor registrations
  • Register coordinators per school

Public

  • Verify any student's OJT record by student ID or wallet address
  • No wallet or account required
  • Displays full log history with IPFS proof images

Tech Stack

Smart Contract

LanguageSolidity 0.8.24
Upgrade patternUUPS (EIP-1822) via OpenZeppelin
Access controlOwnableUpgradeable + custom role modifiers
NetworkEthereum Sepolia testnet
DeploymentHardhat + @openzeppelin/hardhat-upgrades
Bytecode size23,531 / 24,576 bytes

Frontend

FrameworkNext.js 14 (App Router)
LanguageTypeScript
Web3ethers.js v6
WalletMetaMask (EIP-1193)
IPFSPinata — CID stored on-chain
StylingTailwind CSS + custom CSS variables
FontsSyne + IBM Plex Mono
PDFjsPDF (landscape certificates, client-side)
DeploymentVercel

Contract Architecture

InternProof (UUPS Proxy) ├── Proxy: 0x9A8BD5059F3ec602c9b54D2C78d1f11eE0580bf4 ├── Implementation: 0xCdFe7B03B4A2C26f5D1f3e010a539056d27684fE ├── Network: Ethereum Sepolia (Chain ID: 11155111) └── Pattern: UUPS upgradeable (EIP-1822)

Roles (on-chain)

ValueRoleDescription
1AdminSystem operator, one per deployment
2CoordinatorSchool representative, confirms completion
3Supervisor (pending)Awaiting admin approval
4Supervisor (approved)Can verify entries and issue sign-off
5Supervisor (revoked)Access removed by admin
6StudentIntern, clocks in/out daily

Key Functions

// StudentregisterStudent(fullName, studentId, schoolCode, courseId, supervisorCode)
clockIn()
clockOut(taskDescription) // taskDescription includes IPFS CID: [proof:CID]submitPastLogEntry(date, timeIn, timeOut, taskDescription)
// SupervisorverifyLogEntry(entryId)
rejectLogEntry(entryId, reason)
issueFinalSignOff(studentWallet)
// CoordinatorcoordinatorConfirm(studentWallet)
// AdminregisterSchool(name, code)
registerCourse(name, schoolCode, requiredHours)
registerCompany(name, code)
approveSupervisor(wallet)
registerCoordinator(wallet, schoolCode, name)

LogEntry Struct

struct LogEntry {
uint256 entryId;
address student;
uint256 date;
uint256 timeIn;
uint256 timeOut;
uint256 hoursWorked;
string taskDescription; // includes [proof:CID] for IPFSuint8 status; // 0=pending, 1=verified, 2=rejectedstring rejectionReason;
address resolvedBy;
uint256 submittedAt;
uint256 resolvedAt;
uint256 linkedToEntryId;
bool isClockedIn;
}

IPFS Proof System

Every clock-out and past log entry requires a proof image. The flow:

Roles (on-chain)

ValueRoleDescription
1AdminSystem operator, one per deployment
2CoordinatorSchool representative, confirms completion
3Supervisor (pending)Awaiting admin approval
4Supervisor (approved)Can verify entries and issue sign-off
5Supervisor (revoked)Access removed by admin
6StudentIntern, clocks in/out daily

Key Functions

// StudentregisterStudent(fullName, studentId, schoolCode, courseId, supervisorCode)
clockIn()
clockOut(taskDescription) // taskDescription includes IPFS CID: [proof:CID]submitPastLogEntry(date, timeIn, timeOut, taskDescription)
// SupervisorverifyLogEntry(entryId)
rejectLogEntry(entryId, reason)
issueFinalSignOff(studentWallet)
// CoordinatorcoordinatorConfirm(studentWallet)
// AdminregisterSchool(name, code)
registerCourse(name, schoolCode, requiredHours)
registerCompany(name, code)
approveSupervisor(wallet)
registerCoordinator(wallet, schoolCode, name)

LogEntry Struct

struct LogEntry {
uint256 entryId;
address student;
uint256 date;
uint256 timeIn;
uint256 timeOut;
uint256 hoursWorked;
string taskDescription; // includes [proof:CID] for IPFSuint8 status; // 0=pending, 1=verified, 2=rejectedstring rejectionReason;
address resolvedBy;
uint256 submittedAt;
uint256 resolvedAt;
uint256 linkedToEntryId;
bool isClockedIn;
}

IPFS Proof System

Every clock-out and past log entry requires a proof image. The flow:

Student uploads image → Pinata API (pinFileToIPFS) → Returns IPFS CID → CID appended to task description: "Task text\n[proof:Qm...]" → Stored on-chain via clockOut() or submitPastLogEntry() → Displayed in supervisor dashboard and public verify page

The CID is permanently stored on the Ethereum blockchain. The image itself lives on IPFS via Pinata. Anyone can retrieve it at:

https://gateway.pinata.cloud/ipfs/{CID}


Verification Flow

01 Student registers → wallet linked to school + supervisor on-chain 02 Clock in → block timestamp recorded as timeIn 03 Clock out → hours calculated, proof CID stored on-chain 04 Supervisor verifies → totalVerifiedHours updated atomically 05 Hours reach target → student status auto-advances 06 Supervisor sign-off → issueFinalSignOff() called 07 Coordinator confirms → coordinatorConfirm() — status = OJT Complete 08 Certificate issued → student downloads PDF generated client-side


Running Locally

Prerequisites

  • Node.js 18+
  • MetaMask browser extension
  • Sepolia testnet ETH (faucet)
  • Pinata account (free tier)

Frontend

git clone https://github.com/InternProof/internproof.git
cd internproof/frontend
npm install

Create .env.local:

NEXT_PUBLIC_CONTRACT_ADDRESS=0x9A8BD5059F3ec602c9b54D2C78d1f11eE0580bf4NEXT_PUBLIC_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEYNEXT_PUBLIC_PINATA_JWT=your_pinata_jwt_here
npm run dev
# → http://localhost:3000

Smart Contract (read-only — already deployed)

cd internproof
npm install
npx hardhat compile

To deploy a new instance:

npx hardhat run scripts/deploy.js --network sepolia

Project Structure

internproof/ ├── contracts/ │ └── InternProof.sol # UUPS upgradeable contract ├── scripts/ │ └── deploy.js # Hardhat deploy script ├── hardhat.config.js └── frontend/ ├── app/ │ ├── page.tsx # Landing page │ ├── verify/ # Public verification page │ ├── pending/ # Supervisor pending page │ ├── register/ │ │ ├── student/ # Student registration │ │ └── supervisor/ # Supervisor registration │ └── dashboard/ │ ├── admin/ # Admin dashboard │ ├── supervisor/ # Supervisor dashboard │ ├── student/ # Student dashboard │ └── coordinator/ # Coordinator dashboard ├── hooks/ │ └── useWallet.ts # MetaMask + role detection hook └── lib/ ├── contract.ts # Contract helpers + ABI ├── wallet.ts # Wallet connection └── pinata.ts # IPFS upload helpers


Screenshots

Landing PageStudent Dashboard
LandingStudent
Supervisor DashboardPublic Verify
SupervisorVerify

Screenshots in /docs/ — add your own after deployment.


Design System

Dark amber theme (#0f0e0a background, #f59e0b amber accent) with:

  • Syne — geometric display font for headings
  • IBM Plex Mono — monospace for numbers, wallets, and on-chain data
  • Role colors: Admin/Student = amber, Supervisor = purple (#7c3aed), Coordinator = cyan (#22d3ee)
  • Shimmer progress bars, fade-up animations, KPI card hover accents

What I Learned

  • UUPS proxy pattern — upgradeable contracts without changing the proxy address
  • Bytecode size constraints — stayed under Ethereum's 24,576-byte limit through optimizer tuning
  • ethers.js v6 — BrowserProvider, getSigner, and provider refresh to avoid stale wallet state
  • IPFS via Pinata — storing CIDs on-chain as part of string fields rather than separate mappings
  • jsPDF — generating landscape A4 certificates with geometric shapes, typography, and seal graphics entirely client-side
  • Next.js App Router — static export compatibility with Web3 wallet hooks

Author

Jimwell Calma
Built for academic portfolio — demonstrating blockchain development, smart contract architecture, and full-stack Web3.


License

MIT — free to use as reference for academic and personal projects.

About

Blockchain-verified OJT logbook system on Ethereum Sepolia

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages