Blockchain-based university credential issuance & verification. Degrees you can verify in seconds, not days.
Universities issue academic degrees as entries in a Solidity smart contract instead of paper certificates alone. Each credential gets a unique ID and a QR code. Anyone — an employer, another institution, an immigration office — can scan that code and get a cryptographically verified answer straight from the blockchain, in seconds, with no phone calls and no waiting on the registrar's office.
TRADITIONAL WAY: Employer calls the university → waits 3–7 days → maybe gets confirmation
CREDCHAIN WAY: Employer scans a QR code → blockchain answers in seconds → done
A credential recorded on-chain cannot be silently edited or forged — only wallets the issuing university has explicitly authorized can create one, and any tampering after the fact is immediately visible.
- How It Works
- Architecture
- Tech Stack
- Features
- Access Model
- Beginner's Guide — What You Need & Where to Get It
- Quick Start (Local, No Real Crypto Needed)
- Public Testnet Deployment (Polygon Amoy)
- Project Structure
- Testing
- Security Notes
- Troubleshooting
- Corrections From the Original Guide
flowchart TD
A["🏛️ University Admin<br/>fills in student, degree, CGPA"] -->|"signs transaction<br/>(must be an authorized issuer)"| B["📜 Smart Contract<br/>CredentialRegistry.sol"]
B --> C["✅ Credential stored on-chain<br/>permanent, tamper-proof"]
C --> D["🔳 QR Code generated<br/>encodes the credential ID"]
D --> E["🎓 Student<br/>receives & shares the QR code"]
E --> F["🏢 Employer / Verifier<br/>scans QR or pastes the ID"]
F -->|"read-only, free,<br/>no wallet required"| B
B --> G{"Status?"}
G -->|"valid"| H["✅ Verified<br/>full details shown"]
G -->|"revoked"| I["⚠️ Revoked<br/>flagged, not trusted"]
G -->|"unknown ID"| J["❌ Not Found"]
style B fill:#1b2444,stroke:#a9772c,color:#fff
style C fill:#e5f4ec,stroke:#137a56,color:#12162a
style H fill:#e5f4ec,stroke:#137a56,color:#12162a
style I fill:#fbf2e1,stroke:#a9772c,color:#12162a
style J fill:#fbeaec,stroke:#b23a45,color:#12162a
flowchart LR
subgraph Frontend ["Frontend — React + Vite"]
UI["Issue / Verify UI"]
Wallet["useWallet hook"]
end
subgraph Chain ["Blockchain"]
SC["CredentialRegistry.sol<br/>(Hardhat local or Polygon Amoy)"]
end
MM["🦊 MetaMask<br/>(issuing only)"]
UI -- "ethers.js v6" --> SC
UI --> Wallet
Wallet -- "sign & send tx" --> MM
MM -- "broadcasts" --> SC
UI -. "read-only query<br/>(verification, no wallet)" .-> SC
style SC fill:#1b2444,stroke:#a9772c,color:#fff
style MM fill:#fbf2e1,stroke:#a9772c,color:#12162a
| Layer | Technology | Why |
|---|---|---|
| Smart contract | Solidity 0.8.24 | Access-controlled issuance, revocation, on-chain storage |
| Contract tooling | Hardhat | Compile, test, deploy, local blockchain node |
| Blockchain calls | ethers.js v6 | Talks to the contract from the browser |
| Frontend | React 18 + Vite | Fast dev server, modern build tooling |
| Routing | react-router-dom | Home / Issue / Verify pages |
| QR generation | qrcode.react, qrcode | On-screen QR + downloadable credential card |
| QR scanning | html5-qrcode | Camera-based scanning on the Verify page |
| Wallet | MetaMask | Only needed by university admins issuing credentials |
| Target network | Polygon Amoy (public) / Hardhat Local (dev) | Free testnet — no real funds involved anywhere in this project |
- 🔐 Institution-bound issuing — each authorized wallet is bound on-chain to exactly one institution, and the contract stamps that institution onto every credential it issues. The university name is never caller-supplied, so an authorized wallet cannot mint a degree in another university's name
- 🏛️ Layered authorization — issuing requires a verified institution and an explicitly authorized account and a registered wallet. An institutional email alone grants nothing
- 🆔 Roll number field — stored on-chain alongside the student's name, so two students who happen to share a name are never ambiguous when verifying
- 🚫 Revocation — a mistaken or fraudulent credential can be revoked by its issuer or the contract owner, and the Verify page clearly distinguishes revoked from never existed
- 📷 Two ways to verify — paste a credential ID, or scan the student's QR code with a camera
- 🖼️ Shareable credential card — a downloadable/shareable image (QR + full details, styled like a certificate) generated entirely client-side, with no app branding overpowering the university's own
⚠️ Demo mode safety — in demo mode every issued record is clearly marked as a test credential, on screen and on the downloadable card, so it can't be passed off as an official document- 🌗 Light & dark mode
- ✅ 13 passing unit tests covering issuer binding, institution-name forgery, revoked issuers, duplicate IDs, verification, and revocation rules
Verification is public. Issuing is not.
| Area | Access |
|---|---|
| Home | No login |
| Verify a credential (ID or QR) | No login, no wallet, no gas |
| Institution Portal | Login required |
| Institution onboarding | Submitted for review — approval is a separate step |
| Issue a credential | Verified institution + authorized account + on-chain registered wallet |
The trust chain enforced end to end:
Account created → Email verified → Institution verified
→ Account explicitly authorized → Wallet bound on-chain → Credential issued
Each link proves something different, and the design deliberately keeps them separate: an institutional email proves affiliation, institution verification proves legitimacy, an explicit invitation proves permission, and MetaMask proves transaction ownership. None of them substitutes for another.
What is simulated here: accounts, email verification, and institution review are mocked with demo data (
src/config/demo.js) — a hackathon prototype has no access to official university registries. What is real is the blockchain half: the contract independently enforces which wallet may issue and which institution it issues for, so bypassing the UI gains nothing. Swapping the mock layer for a real backend does not change the flow around it.
If you are new to this: you need five things installed before touching any code. Click through, install, then come back.
| # | Tool | What it's for | Direct link |
|---|---|---|---|
| 1 | Node.js (LTS 18.x or 20.x) | Runs everything JavaScript-related (npm, Hardhat, Vite) |
nodejs.org |
| 2 | Git | Version control | git-scm.com |
| 3 | A code editor (VS Code recommended) | Where you'll read/edit the code | code.visualstudio.com |
| 4 | MetaMask (browser extension) | Wallet — only needed to issue credentials; verifying needs no wallet at all | metamask.io |
| 5 | (optional, public deploy only) Test POL tokens | Free fake currency to pay gas on Polygon Amoy | faucet.polygon.technology |
No real money is ever involved anywhere in this project. Everything runs on a free local blockchain by default, and the public option (Polygon Amoy) is a free testnet — the faucet gives away test tokens that have no real-world value.
Verify Node and Git installed correctly:
node --version
npm --version
git --versionThis is the fastest path — a full working app on your own machine in under five minutes.
# 1. Install backend (Hardhat) dependencies
npm install
# 2. Start a local blockchain — keep this terminal running
npx hardhat node
# 3. In a NEW terminal — deploy the contract to that local chain
npx hardhat run scripts/deploy.js --network localhost
# 4. Install frontend dependencies
cd frontend
npm install
# 5. Run the frontend
npm run devOpen the printed local URL (typically http://localhost:5173).
- The Home and Verify pages work immediately — no wallet needed.
- To use Issue Credential, connect MetaMask to the
Hardhat Localnetwork:- Network name:
Hardhat Local - RPC URL:
http://127.0.0.1:8545 - Chain ID:
31337 - Import one of the private keys
npx hardhat nodeprinted in your terminal (these are well-known public test keys — perfectly fine for localhost, never use them anywhere real).
- Network name:
Use this if you want a link you can share, or you're demoing at a hackathon.
-
Copy
.env.exampleto.envand fill inPRIVATE_KEY— use a throwaway MetaMask wallet's key, never one holding real funds. -
Add the Amoy network to MetaMask (it can prompt you automatically, or add manually):
Field Value Network name Polygon Amoy Testnet RPC URL https://rpc-amoy.polygon.technologyChain ID 80002Currency symbol POLExplorer amoy.polygonscan.com -
Get free test POL from the official faucet (select Amoy).
-
Deploy:
npx hardhat run scripts/deploy.js --network amoy
-
The script writes the new contract address + ABI straight into
frontend/src/config/contract.json— restartnpm run devto pick it up.
Cred_Chain/
├── contracts/
│ └── CredentialRegistry.sol # the smart contract
├── scripts/
│ └── deploy.js # deploy + auto-writes frontend config
├── test/
│ └── CredentialRegistry.test.js # 13 unit tests
├── hardhat.config.js
├── package.json # Hardhat / backend deps
├── .env.example
├── frontend/
│ ├── public/favicon.svg
│ ├── src/
│ │ ├── components/ # Header, Home, IssueCredential, VerifyCredential, ...
│ │ │ └── portal/ # login, email verification, onboarding, dashboard, guard
│ │ ├── hooks/ # useWallet, useSession, useIssuerStatus, useTheme, useToast
│ │ ├── utils/ # network switching, password hashing, credential-card image
│ │ ├── config/
│ │ │ ├── contract.json # address + ABI (written by deploy script)
│ │ │ └── demo.js # demo/mock institution + account data
│ │ ├── App.jsx / App.css
│ │ ├── index.css
│ │ └── main.jsx
│ ├── vite.config.js
│ └── package.json
└── README.md
npx hardhat testCovers: issuer authorization and institution binding, rejection of unauthorized issuers, institution-name forgery (a wallet bound to one university cannot issue under another's name), revoked issuers losing the ability to issue, duplicate credential IDs, verification of valid/unknown IDs, revocation by the issuer and by the owner, and rejection of revocation attempts by unrelated wallets — 13/13 passing.
issueCredential()is restricted to wallets the contract owner has authorized viaauthorizeIssuer(wallet, institution)— an unauthorized wallet cannot mint a "verified" degree.- The institution name is not a parameter. It is read from the caller's on-chain binding, so a wallet authorized for one university cannot issue a credential in another university's name. This is enforced in the contract, not the UI, so bypassing the frontend gains nothing.
- Issuer authorization can be withdrawn (
revokeIssuer); credentials already issued stay valid, which is the correct behaviour — losing issuing rights should not retroactively void real degrees. .env(holding a private key) is git-ignored. Never paste a private key into any source file.- Verification is entirely read-only — no wallet, no gas, no signature required. Safe for anyone, including non-technical employers, to use directly.
- For a production system beyond a demo, having credential data (name, degree, CGPA) fully public on-chain is a privacy tradeoff worth revisiting — e.g. hashing personal fields on-chain and releasing full details off-chain with consent.
| Problem | Fix |
|---|---|
| MetaMask doesn't pop up | Confirm the extension is installed/unlocked and has permission for the site (MetaMask icon → Connected sites) |
| "Not authorized" when issuing | Your connected wallet hasn't been granted issuer access — the contract owner must call authorizeIssuer(wallet, institution) for it first |
| Verify page says "not found" for a real ID | Wrong network selected, or the transaction hasn't confirmed yet — wait a few seconds and retry |
--network amoy deploy fails |
Check .env has a valid PRIVATE_KEY and that wallet holds test POL from the faucet |
| Frontend shows a stale contract address | Restart npm run dev after redeploying — contract.json is only re-read on server (re)start |
This project started from a beginner hackathon guide. A few things in that original guide were outdated, insecure, or incomplete — corrected here and documented so nothing is silently different:
| Guide said | This project uses | Why |
|---|---|---|
| Polygon Mumbai testnet | Polygon Amoy testnet | Mumbai was fully shut down by Polygon in 2024 |
| Create React App | Vite + React | CRA is deprecated/unmaintained; Vite is the current standard |
ethers v5 (Web3Provider) |
ethers v6 (BrowserProvider) |
v5 is legacy |
Anyone can issueCredential() |
Only owner-authorized university wallets can | The open version lets anyone forge a "verified" degree |
| No revoke function | revokeCredential() added |
Mistakes and fraud need to be correctable |
Private key pasted into hardhat.config.js |
Loaded from a git-ignored .env |
Never commit secrets |
| QR generation only | Camera-based QR scanning added | The guide described scanning but never implemented it |