Skip to main content

Routes

This page lists every route of the web application in src/app/. A route is a folder with a page.tsx. Server routes under src/app/api/ are listed in Server routes.

Three ideas explain most of the table:

  • Wallet-free pages. Verification pages (/verify, /present, /open) read the chain over the public RPC endpoints and never need a wallet. See Reading the chain.
  • Role gates are client-side redirects. A gated page waits for the wallet session, asks the chain for the wallet's role through services.issuerAccess, and redirects when the role does not fit. The redirect is a convenience, not a security boundary: every write is still checked by the contracts, and every server route checks its own session.
  • Version switch. readContractVersion() picks the v0.3 or v0.2 behaviour. Some v0.2 routes redirect on v0.3, and some v0.3 routes redirect on v0.2.

How the app recognises a role​

IssuerAccessService (src/services/domain/issuer-access-service.ts) answers accessOf(address) from the chain. A failed read throws; it is never treated as "no role".

Checkv0.3 (IssuerRegistry of the active set)v0.2 (AcademicRecords)
isIssuerisIssuer(address) is true and the key is not retired (lineage read)hasRole(UNIVERSITY_ROLE, address)
isAdminisGuardian(address)hasRole(ADMIN_ROLE, address)
isSuperAdminalways falsehasRole(SUPER_ADMIN_ROLE, address)

On v0.3 the Admin workspace belongs to the guardian. Registering issuers is done by the admin Safe (and, after the handover, through the timelock), not through a page of this app. See Guardian and Admin.

Signing in​

/login connects the wallet and sends the user to the workspace that fits the role.

This is not a server sign-in. The server session used by upload and document routes is created separately, on demand, when an issuer first uploads; see Sign-in and sessions.

Route table​

"Wallet" means a connected wallet is required. "Redirect" lists where a gated page sends a visitor who does not fit.

Public pages​

RouteWho sees itv0.3v0.2PurposeMain components
/AnyoneSame pageSame page, v0.2 copyHome pageHero, TrustSteps, Audiences, TrustBoundaries, ClosingCall with copy from homeCopy(version) in src/components/home/home-copy.ts
/aboutAnyoneSame pageSame page, v0.2 chaptersWhat Certichain is and where data livesAbout with chaptersFor(version) from src/app/about/chapters.ts
/loginAnyoneSameSameConnect a wallet and go to the right workspacesrc/app/login/page.tsx
/verifyAnyone, no walletVerify a credential: look up by id (?id= is read on load), check a document, a package, a VC or Open Badge file, a disclosureLegacy lookupVerificationv0.3 CredentialVerifyContent (with DocumentCheckPanel, VcCheckPanel, DisclosurePanel, HolderDetailsPanel, IssuerSignatureStatus, CredentialStatusBanner, ManageCredential); v0.2 VerifyPageContent
/presentAnyone, no walletCheck a credential: scan a holder's phone code with the camera or a picture, or issue a challengeSame pageIn-person verificationPresentContent, ChallengePanel, VerdictView, QrScanner
/walletAnyone (the holder's phone)Credentials on this phone: add, list and present credentials stored on the deviceSame pagePhone walletWalletContent, AddCredentialPanel, CredentialCard, SignForVerifierPanel
/openAnyone with a capability linkOpen a shared documentSame pageOpens a document from a link whose key is in the URL fragmentOpenLinkContent
/accessAnyone; a wallet is needed to derive an identity from signatures and to send a proofProve access to a credential; shows Not on this network yet when no access gate is recorded for the chainSame pageZero-knowledge access requestsAccessRequestContent, AccessRequestView, IdentitySection, ProveSection
/privacy-policyAnyoneSameSameLegal textLegalDocument
/terms-conditionsAnyoneSameSameLegal textLegalDocument

Notes:

  • /open copies window.location.hash into memory and immediately removes it from the address bar with history.replaceState, so the key is not left in the visible address or history entry. The route is served with Referrer-Policy: no-referrer and Cache-Control: no-store (see Security controls).
  • /present and /wallet are served with Referrer-Policy: no-referrer and Permissions-Policy: camera=(self). On /wallet, SignForVerifierPanel is the only part that needs a wallet; without one it says "Signing needs a wallet. Open this page inside your wallet app's browser, then try again."
  • The legal pages show a fixed "last updated" date.

Holder pages​

RouteWho sees itv0.3v0.2RedirectMain components
/studentWallet, not an issuerMy credentials (eyebrow Holder): accept an invitation, list credentials from every contract set, open and share documents, make a disclosure, export a VC or Open Badge, manage accessMy records (eyebrow Student): records issued to the wallet and records shared with itNo wallet: /login. An issuer: /dashboardv0.3 AcceptInvitationPanel, MyCredentialsPanel, OpenDocumentPanel, SharedDocumentPanel, MyShareCodePanel, MakeDisclosurePanel, VcExportPanel; v0.2 LegacyStudentView

Issuer pages​

RouteWho sees itv0.3v0.2RedirectMain components
/dashboardIssuerIssuer dashboard: issued credentials from every set, revoke, re-issue, open document, replace a document key, add a disclosure tree, export, access management. A retired key sees Retired key and the successor's addressUniversity dashboard: issued recordsNo wallet: /login. v0.3 non-issuer: /student (unless retired). v0.2 non-university: /records. A failed role read shows Could not check your issuer registration.v0.3 IssuerDashboardPage, IssuedCredentialsPanel, IssuerActions, RevokeDialog, ReplaceKeyFlow, AddDisclosurePanel, IssuerAccessSection; v0.2 LegacyDashboardPage
/records/addIssuerAdd Academic Record (eyebrow Issue New Record, or Re-issue Record with ?replaces=): encrypt and upload the document, sign, anchorAdd a v0.2 recordNo wallet: /login. Non-issuer: /recordsIssueSession (in Suspense with IssueSkeleton), AddRecordPage, DocumentUploadField, StudentLinkCheck, DeliveryOption, CredentialIssuedCard, SendToPhone
/records/importv0.3 issuerImport credentials from CSV or JSONRedirects to /dashboardNo wallet: /login. Non-issuer: /recordsImportPage, ImportWorkspace, ImportReview, ImportProgress, ImportResults
/records/typesv0.3 issuerRecord types: register and switch off the issuer's own typesRedirects to /dashboardNo wallet: /login. Non-issuer: /recordsRecordTypesPanel
/students/invitev0.3 issuerStudents: create invitations and manage wallet bindingsRedirects to /dashboardNo wallet: /login. Non-issuer: /recordsInviteStudentPanel, ManageStudentPanel
/recordsv0.2Redirects to /dashboard (RedirectTo)Records listv0.2 university: /dashboardLegacyRecordsPage
/records/[id]v0.2Redirects to /verify?id=<id> (v03RecordTarget)Record detailNo wallet: /loginLegacyRecordDetailPage

On v0.3 a holder who lands on /records/add is sent to /records, which redirects to /dashboard, which sends a non-issuer to /student. The chain of redirects ends on the right workspace without a dedicated rule.

Admin pages​

RouteWho sees itv0.3v0.2RedirectMain components
/adminv0.3 guardian; v0.2 admin or super adminGovernance (section Platform governance, read from the chain when the page opened): Running or Paused, registry admins and pending admin transfers, the timelock queue (read-only), retired keys, and Pause the system while the system runs. A note points admin actions (adding or removing an issuer, restarting after a pause, lifting an issuer suspension, accepting an admin transfer) to the Safe. A failed read shows Could not read the governance state.Legacy admin: universities, admins (super admin only), students, custom record types, pauseNo wallet or no role: /loginv0.3 GovernancePage, GovernancePanel, TimelockSection, RetiredKeysSection; v0.2 LegacyAdminPage and src/app/admin/legacy/*

Pause notice​

Issuer pages on v0.3 read platform.isPaused() through usePlatformPaused(version) (src/services/react/use-platform-paused.ts). While the issuer registry is paused they show PausedNotice: "The system is paused. New credentials, invitations and record types cannot be created right now. Revoking, suspending and other corrections still work." A failed read leaves the notice hidden and logs the error; the contracts refuse paused writes anyway.

Files served with the pages​

PathWhat it is
/manifest.webmanifestWeb app manifest for installing the phone pages
/sw.jsOffline shell service worker, served with Cache-Control: no-cache so a fixed worker reaches phones on the next visit
/zk/*Circuit files for the browser prover, copied by npm run zk:public and checked against src/lib/zk/circuit-hashes.json
/api/*Server routes, see Server routes

Source files​

  • src/app/*/page.tsx and the components next to each page
  • src/app/login/page.tsx
  • src/app/records/records-routing.ts
  • src/app/records/add/use-issuer-setup.ts, src/app/admin/legacy/use-legacy-admin.ts
  • src/services/domain/issuer-access-service.ts
  • src/services/react/use-wallet-session.ts, src/services/react/use-platform-paused.ts
  • src/components/RedirectTo.tsx, src/components/PausedNotice.tsx
  • src/lib/security-headers.ts, next.config.ts

Tests​

  • src/services/domain/issuer-access-service.test.ts
  • src/components/RedirectTo.test.tsx, src/components/PausedNotice.test.tsx
  • Page tests next to their pages, for example src/app/student/StudentPageV03.test.tsx, src/app/verify/CredentialVerifyContent.test.tsx, src/app/open/OpenLinkContent.test.tsx, src/app/present/PresentContent.test.tsx, src/app/wallet/WalletContent.test.tsx
  • Browser journeys in e2e/journeys/, for example issue-and-verify.journey.ts, mobile-wallet.journey.ts, bulk-import.journey.ts, pause-and-restart.journey.ts