Blockchain-verified OJT logbook system built on Ethereum Sepolia.
Live Demo → internproof.vercel.app
Contract → 0x9A8BD5059F3ec602c9b54D2C78d1f11eE0580bf4
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.
- 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
- 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
- Confirm OJT completion after supervisor sign-off
- View all students under their school
- Expandable student detail cards with progress tracking
- Register schools, courses, companies
- Approve or revoke supervisor registrations
- Register coordinators per school
- 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
| Language | Solidity 0.8.24 |
| Upgrade pattern | UUPS (EIP-1822) via OpenZeppelin |
| Access control | OwnableUpgradeable + custom role modifiers |
| Network | Ethereum Sepolia testnet |
| Deployment | Hardhat + @openzeppelin/hardhat-upgrades |
| Bytecode size | 23,531 / 24,576 bytes |
| Framework | Next.js 14 (App Router) |
| Language | TypeScript |
| Web3 | ethers.js v6 |
| Wallet | MetaMask (EIP-1193) |
| IPFS | Pinata — CID stored on-chain |
| Styling | Tailwind CSS + custom CSS variables |
| Fonts | Syne + IBM Plex Mono |
| jsPDF (landscape certificates, client-side) | |
| Deployment | Vercel |
InternProof (UUPS Proxy) ├── Proxy: 0x9A8BD5059F3ec602c9b54D2C78d1f11eE0580bf4 ├── Implementation: 0xCdFe7B03B4A2C26f5D1f3e010a539056d27684fE ├── Network: Ethereum Sepolia (Chain ID: 11155111) └── Pattern: UUPS upgradeable (EIP-1822)
| Value | Role | Description |
|---|---|---|
| 1 | Admin | System operator, one per deployment |
| 2 | Coordinator | School representative, confirms completion |
| 3 | Supervisor (pending) | Awaiting admin approval |
| 4 | Supervisor (approved) | Can verify entries and issue sign-off |
| 5 | Supervisor (revoked) | Access removed by admin |
| 6 | Student | Intern, clocks in/out daily |
// Student
registerStudent(fullName, studentId, schoolCode, courseId, supervisorCode)
clockIn()
clockOut(taskDescription) // taskDescription includes IPFS CID: [proof:CID]
submitPastLogEntry(date, timeIn, timeOut, taskDescription)
// Supervisor
verifyLogEntry(entryId)
rejectLogEntry(entryId, reason)
issueFinalSignOff(studentWallet)
// Coordinator
coordinatorConfirm(studentWallet)
// Admin
registerSchool(name, code)
registerCourse(name, schoolCode, requiredHours)
registerCompany(name, code)
approveSupervisor(wallet)
registerCoordinator(wallet, schoolCode, name)struct LogEntry {
uint256 entryId;
address student;
uint256 date;
uint256 timeIn;
uint256 timeOut;
uint256 hoursWorked;
string taskDescription; // includes [proof:CID] for IPFS
uint8 status; // 0=pending, 1=verified, 2=rejected
string rejectionReason;
address resolvedBy;
uint256 submittedAt;
uint256 resolvedAt;
uint256 linkedToEntryId;
bool isClockedIn;
}Every clock-out and past log entry requires a proof image. The flow:
| Value | Role | Description |
|---|---|---|
| 1 | Admin | System operator, one per deployment |
| 2 | Coordinator | School representative, confirms completion |
| 3 | Supervisor (pending) | Awaiting admin approval |
| 4 | Supervisor (approved) | Can verify entries and issue sign-off |
| 5 | Supervisor (revoked) | Access removed by admin |
| 6 | Student | Intern, clocks in/out daily |
// Student
registerStudent(fullName, studentId, schoolCode, courseId, supervisorCode)
clockIn()
clockOut(taskDescription) // taskDescription includes IPFS CID: [proof:CID]
submitPastLogEntry(date, timeIn, timeOut, taskDescription)
// Supervisor
verifyLogEntry(entryId)
rejectLogEntry(entryId, reason)
issueFinalSignOff(studentWallet)
// Coordinator
coordinatorConfirm(studentWallet)
// Admin
registerSchool(name, code)
registerCourse(name, schoolCode, requiredHours)
registerCompany(name, code)
approveSupervisor(wallet)
registerCoordinator(wallet, schoolCode, name)struct LogEntry {
uint256 entryId;
address student;
uint256 date;
uint256 timeIn;
uint256 timeOut;
uint256 hoursWorked;
string taskDescription; // includes [proof:CID] for IPFS
uint8 status; // 0=pending, 1=verified, 2=rejected
string rejectionReason;
address resolvedBy;
uint256 submittedAt;
uint256 resolvedAt;
uint256 linkedToEntryId;
bool isClockedIn;
}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}
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
- Node.js 18+
- MetaMask browser extension
- Sepolia testnet ETH (faucet)
- Pinata account (free tier)
git clone https://github.com/InternProof/internproof.git
cd internproof/frontend
npm installCreate .env.local:
NEXT_PUBLIC_CONTRACT_ADDRESS=0x9A8BD5059F3ec602c9b54D2C78d1f11eE0580bf4
NEXT_PUBLIC_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
NEXT_PUBLIC_PINATA_JWT=your_pinata_jwt_herenpm run dev
# → http://localhost:3000cd internproof
npm install
npx hardhat compileTo deploy a new instance:
npx hardhat run scripts/deploy.js --network sepoliainternproof/ ├── 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
| Landing Page | Student Dashboard |
|---|---|
![]() |
![]() |
| Supervisor Dashboard | Public Verify |
|---|---|
![]() |
![]() |
Screenshots in
/docs/— add your own after deployment.
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
- 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
Jimwell Calma
Built for academic portfolio — demonstrating blockchain development, smart contract architecture, and full-stack Web3.
- GitHub: @JimwellC
- Contract: Etherscan
- Live: internproof.vercel.app
MIT — free to use as reference for academic and personal projects.



