Legacy v0.2 roles
The v0.2 generation (release v2.0.0) is one contract, AcademicRecords, built on OpenZeppelin 4.9 AccessControl and Pausable through the abstract RoleManager, with a linked RecordStorage library and a StudentManagement module it deploys and owns. It stays deployed on Sepolia, and the app still runs against it whenever NEXT_PUBLIC_CONTRACT_VERSION is not v03. New work targets v0.3; this page exists so the older pages and roles are documented. See Legacy v0.2 contract and Contract versions and sets.
v0.2 stores the student's name, student ID and wallet address and the document's IPFS hash in contract storage, which anyone can read from the chain. The app reads records only through the permission-aware call getRecordWithPermission, and every page shows the banner "Test network demo. Do not upload real personal documents. Data written to the blockchain is public." The v0.3 registries store none of this. See Privacy design.
The four roles
| Role | On-chain marker | Granted by | Powers |
|---|---|---|---|
| Super admin | The immutable SUPER_ADMIN address (the deployer), which also holds SUPER_ADMIN_ROLE, ADMIN_ROLE and DEFAULT_ADMIN_ROLE | The constructor | Everything an admin can do, plus addAdmin and removeAdmin |
| Admin | ADMIN_ROLE | The super admin (addAdmin) | addUniversity, removeUniversity, registerStudentFor, migrateStudentWallet, pause, unpause; read any record |
| University | UNIVERSITY_ROLE plus a registered name | An admin (addUniversity(address, name)) | addRecord, deleteStudent, addCustomRecordType, updateCustomRecordType, read its own records |
| Student | A student ID bound to a wallet in StudentManagement | An admin (registerStudentFor), or automatically by a university's first addRecord for that ID and wallet | Read own records, shareRecord and unshareRecord |
The registry roles cannot be moved with the generic grantRole, revokeRole or renounceRole: RoleManager refuses those for its roles, so universities and admins change only through the named functions.
How the app recognises them
IssuerAccessService.accessOf reads hasRole on AcademicRecords:
| Result | Workspace | Sign-in goes to |
|---|---|---|
ADMIN_ROLE or SUPER_ADMIN_ROLE | Admin | /admin |
UNIVERSITY_ROLE | Issue (links Overview, Records, Add a record) | /dashboard |
| Neither | My credentials (link My records) | /student |
The /admin page loads LegacyAdminPage when the contract version is not v03; it checks ADMIN_ROLE, SUPER_ADMIN_ROLE and UNIVERSITY_ROLE and sends any wallet without an admin role to /login.
Super admin and admin pages
/admin (System management, eyebrow Super admin or Admin, lead "Universities, admins, students and record types on the v0.2 contract.") has a pause control and these tabs:
| Tab | Who sees it | Actions |
|---|---|---|
| Overview | Both | "Where to start" links into the other tabs |
| Universities | Both | Add a university form, submit Add university; Remove per row (shown to the super admin only), confirmed in a dialog |
| Admins | Super admin only | Add an admin form, submit Add admin; Remove per row, confirmed with Remove admin |
| Students | Both | Register a student, submit Register student; Move a student to a new wallet, submit Migrate wallet (dialog: "Move this student to a new wallet?") |
| Record types | Both | Lists standard and custom types; the add form is shown only when the wallet also holds UNIVERSITY_ROLE. The tab notes that custom types cannot be attached to records on this contract version |
The pause control (Pause contract with the dialog "Pause the contract?", or Unpause contract) is shown to the super admin only.
The contract lets any admin call removeUniversity, pause and unpause (onlyAdminOrSuper). The admin page offers Remove for universities and the pause control only to the super admin, and its paused notice says a super admin unpauses.
While paused, addRecord, deleteStudent, shareRecord, unshareRecord, custom type changes, recordAccess, registerStudentFor, migrateStudentWallet, addUniversity and setUniversityName revert. pause, unpause, addAdmin, removeAdmin, removeUniversity and the role functions stay available, and reads are never paused.
University pages
| Route | Page | Actions |
|---|---|---|
/dashboard | University dashboard ("Every record your university issued on the v0.2 contract.") | Issue record; the Issued records list with View per row |
/records/add | Add Academic Record | Student Name, Student ID, Student Ethereum Address, Record Type, the Document file with Upload to IPFS (plain upload, not encrypted), then Issue Record |
/records/[id] | Academic record | The record sheet with Verified or Unverified and View on IPFS |
addRecord binds the student ID to the wallet on first use and refuses a later record whose ID or wallet does not match the existing binding ("Student ID mismatch", "Student wallet mismatch"). The institution name is taken from the registry, not from the form. deleteStudent exists in the contract but no page calls it. The Records link in the university's top bar opens /records, which sends a university back to /dashboard.
Flow: Issuing (v0.2 variant).
Student pages
| Route | Page | Actions |
|---|---|---|
/student | My records | If the wallet is not yet registered, "No student record yet" with Check Registration. Otherwise the tables My Records and Records Shared With Me, each with View |
/records | Your academic records | View and Verify per record |
/records/[id] | Academic record | For the record's own student: Share this record with a Wallet address and Share, and Revoke per existing grant |
Records Shared With Me is rebuilt from RecordShared events, so it needs NEXT_PUBLIC_START_BLOCK set to the block the contract was deployed in; without it the page says the share history is not configured. Flow: Sharing (v0.2 variant).
Verifier page
On v0.2, /verify shows Verify an academic record with a Record ID field (a number). The result shows Record found on-chain or Issuer no longer registered, the holder name, university, issue date and issuer, without the IPFS hash.
What each role cannot do
- A university cannot issue for another university, change a record after issuing, or register students directly (only through
addRecord). - An admin cannot add or remove admins; the super admin cannot be removed.
- A student cannot share a record that is not theirs ("Not your record") or share one record with more than 32 wallets.
- No v0.2 role can revoke a record in the v0.3 sense; v0.2 has no status lifecycle, and
isVerifiedis set at issuance.
Source files
blockchain/contracts/AcademicRecords.sol,blockchain/contracts/abstract/RoleManager.sol,blockchain/contracts/modules/StudentManagement.sol,blockchain/contracts/libraries/src/app/admin/LegacyAdminPage.tsx,src/app/admin/legacy/,src/app/admin/CustomTypesTab.tsxsrc/app/dashboard/LegacyDashboardPage.tsx,src/app/records/LegacyRecordsPage.tsx,src/app/records/[id]/LegacyRecordDetailPage.tsx,src/app/records/[id]/SharePanel.tsxsrc/app/student/LegacyStudentView.tsx,src/app/verify/VerifyPageContent.tsxsrc/services/domain/role-service.ts,src/services/domain/record-service.ts,src/services/domain/sharing-service.tssrc/components/layout/DemoBanner.tsx,src/components/layout/navigation.ts
Tests
- Contracts:
blockchain/test/Registry.test.ts,StudentRegistration.test.ts,RecordPermission.test.ts,Sharing.test.ts,ShareLimit.test.ts,Pause.test.ts,UniversityName.test.ts,UniversityNameIntegrity.test.ts,Smoke.test.ts,Coverage.test.ts - Rendered:
src/app/admin/legacy/AdminForm.test.tsx,src/app/admin/legacy/SectionTabs.test.tsx,src/app/admin/CustomTypesTab.test.tsx,src/app/verify/VerifyPageContent.test.tsx,src/components/layout/DemoBanner.test.tsx - Source scan:
src/app/data-exposure.test.ts(pages read records only through the permission-aware call) - E2E: none. The E2E harness deploys only v0.3.