Holder inbox
The student page (/student, header Holder, title My credentials) lists "Everything issued to this wallet, read from the chain." There is no server-side list and no database. The chain deliberately keeps no wallet-to-credential index, so the browser rebuilds the list from the wallet's enrolments and the CredentialIssued events for those enrolments.
Preconditions
- A connected wallet. Without one the page sends the visitor to
/login. Listing needs no signature and no transaction. - The wallet has accepted an invitation from at least one issuer (see Enrolment).
- The credentials were issued with Deliver to the student's linked wallet ticked, so their subject commitment equals the enrolment commitment. Credentials issued without delivery never appear, by design. Credentials from Bulk import are issued without delivery.
How the list is built
InboxService.myCredentials (src/services/domain/inbox-service.ts) does work proportional to the number of issuers and the holder's own credentials, never to all credentials:
- Issuers.
CredentialIndex.issuers()readsIssuerAddedandIssuerUpdatedevents from theIssuerRegistry, so removed issuers are included and names are current. - Enrolments. For each issuer,
HolderRegistry.enrolmentOf(issuer, wallet). A zero result means no enrolment there. - Credentials. For each enrolment, a log query on
CredentialIssuedfiltered by the indexedissuerandsubjectCommitmenttopics. - Description. Each entry gets its record type name (built-in names locally, custom names from the
RecordTypeRegistry) and its status. A status that cannot be read leaves the item in the list with Status unavailable. - Filter and sort. An event whose credential has status None is not a credential, whatever the event says, and is dropped. The rest are sorted by block, newest first.
AllSetsInbox (src/services/domain/all-sets-lists.ts) runs this for every listed contract set and merges the results, tagging each item with its set.
Log scanning
The log scans run in the browser through the CredentialIndex interface (LogQueryIndex in src/services/indexing/credential-index.ts), so a hosted indexer could replace them later without touching pages. Defaults (DEFAULT_SCAN_OPTIONS in log-scan.ts):
| Option | Value |
|---|---|
| Start block | The set's deploymentBlock from the manifest |
| Window | 20,000 blocks per request, shrinking to 500 when the provider refuses a range |
| Request cap | 1,000 requests, so a broken endpoint cannot keep the client scanning |
| Reorg margin | The last 12 blocks are scanned again on resume |
| Retries | 3, starting at 500 ms and doubling, with jitter |
| Cache | Results are kept in localStorage under keys starting certichain.scan.v1:, per chain, contract and topic filter |
The list
Section "Issued to this wallet". Each row shows:
| Element | Meaning |
|---|---|
| Seal | Drawn from the credential id (greyed when revoked) |
| Type name | Built-in name or the issuer's own, marked issuer-defined |
| Issuer name | From the IssuerRegistry events |
| Badge | Active, Suspended, Revoked, Superseded, or Status unavailable; an Active credential that is not valid shows a caution tone with "Recorded as active but not valid right now, usually because it expired. Open Verify for the reason." |
| Short id | The first and last characters of the id |
| Older set 0.3.0 | When the credential lives in a set that is not the active one |
| Verify | Opens /verify?id=... |
| Show QR | A QR code of the verification link |
| Manage access | Only where the access gate serves that set; see Private access proofs |
States of the panel:
| State | Shown |
|---|---|
| Loading | A placeholder, announced to screen readers as "Reading your credentials from the chain..." |
| Failed | Could not load your credentials. "This is not proof that you have none. The chain could not be read just now." with Try again |
| Empty | No credentials yet: "They appear here after a university you accepted an invitation from issues one to you." |
The package and the inbox
The inbox proves that a credential was issued to this wallet's enrolment, but it carries none of the private parts: the name, the student ID, the salts and the document key are only in the credential package, which the issuer hands over separately (download, email, archive). The tools below the list therefore take the package as input.
| Group | Panel | Uses |
|---|---|---|
| Open and share your documents | Open a document | The package opens the stored document; see Document storage |
| Open a shared document | A shared document file from someone else; see Sharing | |
| My share code | The holder's own share code, for receiving documents | |
| Show only what is needed | Disclosure panel | Makes a disclosure file |
| VC export panel | Exports a verifiable credential |
The page also shows Accept an invitation for linking with another institution.
Opening a document from the inbox
Opening needs no wallet signature: the key is in the package. The result card links to the verify page because the package says nothing about status.
Variants
- v0.2 (legacy). The student page shows My records: "The records issued to this wallet, and the records other people shared with you." Records are read from
AcademicRecords, and the "shared with you" list is rebuilt from sharing events, which needsNEXT_PUBLIC_START_BLOCK. See Legacy v0.2 contract. - Several contract sets. Items from an older set carry the Older set badge and are routed to that set for every action.
Limits
- The list can lag a fresh issuance by the time the RPC endpoint takes to index the block.
- A holder who lost the package can see and verify the credential but cannot open the document or prove their details. The issuer re-issues in that case.
- Every listed issuer costs one
enrolmentOfcall, so the cost grows with the number of registered issuers.
Code map
| Concern | File |
|---|---|
| Page | src/app/student/page.tsx, MyCredentialsPanel.tsx, CredentialRow.tsx, ToolGroup.tsx |
| Service | src/services/domain/inbox-service.ts, all-sets-lists.ts, set-fan-out.ts |
| Index | src/services/indexing/credential-index.ts, log-scan.ts, storage-scan-cache.ts, browser-cache.ts, provider-log-source.ts |
| Badges | src/components/credential-status.ts |
| Contracts | blockchain/contracts/v03/HolderRegistry.sol (enrolmentOf), CredentialRegistry.sol (CredentialIssued) |
Tests
src/services/domain/inbox-service.test.ts,all-sets-lists.test.ts,set-fan-out.test.tssrc/services/indexing/credential-index.test.ts,log-scan.test.ts,storage-scan-cache.test.ts,browser-cache.test.tssrc/app/student/MyCredentialsPanel.test.tsx,StudentPageV03.test.tsx- E2E:
e2e/journeys/invite-and-inbox.journey.ts,delivery.journey.ts,contract-sets.journey.ts