Manual checks
The automated suites stop at what a machine can drive: the E2E harness uses an injected test wallet, an in-memory upload stand-in and a local chain. These scripts cover the rest: a real wallet extension (MetaMask), the real upload route with Pinata, a phone camera, and the public test network. Run them after a change to a flow, before a release, and when the handbook lists a manual check as open.
Each script names the role, the starting route, the labels to press (in bold, as in the app) and the expected result. "Wallet: signature" means a signing prompt that sends nothing; "Wallet: transaction" means a transaction that costs gas.
Use synthetic names and documents ("Asha Rai", student ID "S-1001"). Every page shows "Test network demo. Do not upload real personal documents. Data written to the blockchain is public." Never import a Hardhat development key into a wallet you use on a public network.
Setting up
Local chain
-
Start a node and keep it running:
cd blockchain && npx hardhat node(JSON-RPC athttp://127.0.0.1:8545, chain 31337). It prints the development accounts and their keys. -
In a second terminal deploy the v0.3 set with a guardian and register an issuer:
cd blockchainGUARDIAN_ADDRESS=<account 4> npm run deploy:v03:localISSUERS="<account 1>=Test University" npm run issuers:v03:localnpm run deploy:access:local # optional, for the access checks -
In the repository root, give the app a local environment (names only shown; see Configuration):
NEXT_PUBLIC_CONTRACT_VERSION=v03,NEXT_PUBLIC_NETWORK_ID=31337,NEXT_PUBLIC_RPC_URL=http://127.0.0.1:8545,NEXT_PUBLIC_START_BLOCK=0, aSESSION_SECRETof at least 32 characters, and a test Pinata credential (PINATA_JWTwith file read and write permission) withNEXT_PUBLIC_PINATA_GATEWAY_URL. Redis is not needed outside production; the server uses an in-memory store. -
npm run devand openhttp://localhost:3000in Chrome or Firefox (Safari may refuse theSecuresession cookie onhttp://localhost). -
In MetaMask add the network (RPC
http://127.0.0.1:8545, chain id 31337) and import development accounts 0 (admin), 1 (issuer), 2 (student) and 4 (guardian), the same roles the E2E harness gives them.
After restarting the node, redeploy and reset MetaMask's activity for the local network, or nonces will not match. See Local development.
Sepolia
Use the deployed site, a test issuer wallet registered in the active set, and a student wallet with a little Sepolia ETH. As of 2026-10-24 the active set is 0.3.0. Admin actions on Sepolia go through the Safe and, for a set under the timelock, through a scheduled operation; they are decisions of the Safe owners, not steps to try from the site.
Verifier (no wallet)
Use a private window with no wallet extension, or the visitor role on a phone.
| Step | Action | Expected |
|---|---|---|
| V1 | Open /verify, paste a known credential id into Credential id, press Verify | Credential found on-chain, Issuer signature valid, status Active, the issuer address and the document hash. The note says the page does not yet check whether the issuer is still registered |
| V2 | Paste 0x followed by 64 zeros, press Verify | Credential not found: "No credential with this id exists on this network. Check the id and try again." |
| V3 | Paste abc, press Verify | "This is not a valid credential id. It must be a 0x-prefixed 32-byte hex value." |
| V4 | Local only: stop the Hardhat node, look up a valid id | "Could not reach the network, so the credential was not checked. This is not a verification result. Please try again." (never "not found") |
| V5 | Under Check a document, choose the issued document, then a different file | Document matches the issued credential, then Document does NOT match the issued credential |
| V6 | Under Check holder details, choose the credential package | Holder details match the on-chain commitments with the name and student ID |
| V7 | Under Check a verifiable credential file, choose an exported VC | Credential verified (or Credential verified, details not proven when the file shows no details) |
| V8 | Open the verification link from a QR code with the phone camera | The same result as V1, with no wallet |
Holder (account 2, or the Sepolia student wallet)
| Step | Action | Expected |
|---|---|---|
| H1 | Connect and sign in | Sent to /student, page My credentials, top bar shows My credentials |
| H2 | Under Accept an invitation, choose the Invitation file from I5, press Accept invitation | Wallet: transaction. Success notice; credentials issued with delivery appear under Issued to this wallet |
| H3 | On a row, press Show QR, then Verify | A QR of the verification link; /verify shows the credential |
| H4 | Under Open a document, choose the package, press Open document | The document opens; a link to check the credential is still active |
| H5 | With a second wallet open /student, press Show my share code, Copy code | Wallet: signature (may be asked). A ccshare1_... code |
| H6 | Back as the holder, paste it into Recipient's share code, press Create shared file | "Shared file saved..." The second wallet opens it under Open a shared document; a third wallet cannot |
| H7 | Press Create a link, confirm Create link, Copy link, open it in a private window | /open decrypts and checks the document, then offers it |
| H8 | Verifier copies the Challenge under Check a disclosure; holder uses Share some details only, picks one field, pastes the challenge, presses Create disclosure file | Wallet: signature. The verifier gets Disclosure verified showing only that field |
| H9 | On a phone, open /wallet, add the credential (file, code or scan), Add to this phone, Show the code; scan it on /present with another device | The verdict appears offline first, then online |
Issuer (account 1, or the Sepolia test issuer)
| Step | Action | Expected |
|---|---|---|
| I1 | Connect and sign in | Sent to /dashboard with the registered name and the Registered issuer badge |
| I2 | Issue a credential: fill Student Name, Student ID, choose a Record Type, choose the Document file, press Encrypt and upload | Wallet: signature (Sign-In with Ethereum, first upload of the session). IPFS Hash and Document SHA-256 filled |
| I3 | Leave delivery unticked and press Issue Record | The dialog "Issue without delivering to a wallet?"; press Go back and tick Deliver to the student's linked wallet |
| I4 | Press Issue Record | Wallet: two signatures (delivery and the credential), then a transaction. The card says Credential issued; press Download credential package |
| I5 | Students: enter the Student ID and Student wallet, choose Valid for, press Create invitation | Wallet: signature. The invitation file is saved; hand it to the holder (H2) |
| I6 | On the dashboard row, press Revoke, pick a Reason, press Revoke credential | Wallet: transaction. "Credential revoked. Anyone who checks it now sees it as revoked." V1 now shows revoked |
| I7 | Issue another credential, press Re-issue on its row and issue the replacement | Wallet: signatures, then one transaction (issueReplacing). The old credential shows superseded and names the new one |
| I8 | On /verify for an active credential, press Manage as issuer, then Suspend and Confirm suspend; then Reinstate | Wallet: transaction each. The status banner follows |
| I9 | Record types: add a Name, press Add type; then Turn off | Wallet: transaction each. The type appears, then shows Inactive and is no longer offered when issuing |
| I10 | Students: Look up by student ID, Unlink wallet, confirm Unlink | Wallet: transaction. The holder's inbox no longer lists the credential |
Guardian (account 4, or the Sepolia guardian)
| Step | Action | Expected |
|---|---|---|
| G1 | Connect and sign in | Sent to /admin, page Governance, state Running, the admin address and no pending transfer |
| G2 | Press Pause the system | Wallet: transaction. State Paused |
| G3 | As the issuer, open Issue a credential | Issue Record is disabled and the paused notice is shown |
| G4 | As the issuer, revoke a credential (I6); as a visitor, verify one (V1) | Both still work |
| G5 | Restart (admin, see A2) | State Running again; issuing works |
On Sepolia, pause only with the Safe owners' agreement, because only they can restart a 0.3.0 set.
Admin
| Step | Action | Expected |
|---|---|---|
| A1 | cd blockchain && npm run governance:v03:sepolia (read only) | Each registry's admin is the Safe or the timelock as recorded, and no unexpected pending transfer |
| A2 | Local only: npx hardhat console --network localhost, then const r = await ethers.getContractAt("IssuerRegistry", "<IssuerRegistry from the manifest>") and await r.unpause() | The guardian's /admin page shows Running after a reload |
| A3 | Local only: register a second issuer with ISSUERS="<account 5>=Second College" npm run issuers:v03:local | That account's sign-in lands on /dashboard |
| A4 | Sepolia, with the Safe owners: build a proposal with npm run timelock:propose:sepolia, schedule it from the Safe | /admin lists it under Queued admin actions as Waiting, later Ready; after execution the change is visible on chain |
Recording the result
Write down the date, the network, the build or commit, each step's result and any message that differed from this page, in the step's session log. A failing step is reported with the exact message and the wallet prompt that preceded it.
Source files
- The labels on this page come from
src/app/verify/,src/app/student/,src/app/wallet/,src/app/present/,src/app/dashboard/,src/app/records/add/,src/app/students/invite/,src/app/records/types/,src/app/admin/ blockchain/scripts/deploy-v03.ts,add-issuers-v03.ts,deploy-access-gate.ts,governance-status.ts,timelock-propose.tssrc/lib/server/config.ts,src/lib/server/rate-limit.ts,src/lib/server/session.tsdocs/process/verification-checklist.md(the handbook's release checklist)