Record types
Every credential carries a recordType, a 32-bit number in the signed fields and the anchor. It says what kind of credential it is (a degree, a transcript, a certificate). The number space has three ranges:
| Range | Kind | Who defines the name |
|---|---|---|
| 0 to 31 | Built in | The app, the same list for every issuer |
| 32 to 999 | Reserved | Nobody yet. Refused at issuance. |
| 1000 and up | Issuer-defined | Each issuer for itself, numbered per issuer |
Issuer-defined ids are per issuer: issuer A's type 1000 and issuer B's type 1000 are different types with different names. A verifier therefore resolves a custom type with the credential's issuer, and the UI marks every custom name as issuer-defined ("This type name was chosen by the issuer"), because it is the issuer's own choice and not a shared vocabulary.
Built-in types
| Id | Name | Id | Name |
|---|---|---|---|
| 0 | Transcript | 16 | Transfer Certificate |
| 1 | Degree | 17 | Bills |
| 2 | Marksheet | 18 | Fee Receipt |
| 3 | Diploma | 19 | Scholarship Letter |
| 4 | Certificate | 20 | Loan Document |
| 5 | Provisional Certificate | 21 | Hostel Clearance |
| 6 | Birth Certificate | 22 | Routine |
| 7 | Citizenship | 23 | Notice |
| 8 | National ID | 24 | Circular |
| 9 | Passport Copy | 25 | News |
| 10 | Character Certificate | 26 | Recommendation Letter |
| 11 | Entrance Results | 27 | Internship Certificate |
| 12 | Admit Card | 28 | Experience Letter |
| 13 | Counseling Letter | 29 | Bonafide Certificate |
| 14 | Seat Allotment Letter | 30 | No Objection Certificate |
| 15 | Migration Certificate | 31 | Other |
The names live in RECORD_TYPE_NAMES (src/types/records.ts) and are the same list v0.2 used. The contract only knows the numbers: isTypeAllowed(issuer, id) is true for any id up to 31.
Registering a type
Preconditions: v0.3, an active issuer, the system not paused. The page is Record types (/records/types), "Add the kinds of credential your institution issues beyond the built-in ones, and switch a type off when you stop issuing it." It is linked from the issue form (Manage your record types) and from the Issue menu (Record types). A visitor without a wallet goes to /login, a v0.2 deployment to /dashboard, and a non-issuer to /records.
Section Add a record type ("Name it the way it should read on a credential."): Name and optional Description ("A line verifiers see under the name."), button Add type.
| Rule | Where | Limit or check | Error |
|---|---|---|---|
| Hidden characters removed | Browser | Controls, bidirectional marks and isolates, zero-width characters, line separators | |
| Name required | Browser and contract | Not empty | "The type name is required." (EmptyTypeName) |
| Name length | Browser and contract | At most 64 bytes (UTF-8) | "The type name is too long." (TypeNameTooLong) |
| Description length | Browser and contract | At most 256 bytes | "The type description is too long." (TypeDescriptionTooLong) |
| Unique per issuer | Contract | No other type of this issuer with the same exact name | "You already have a record type with this name. Pick another name." (TypeNameInUse) |
| Active issuer | Contract | Registered and not retired | "Your account is not registered as an issuer on this network, so it cannot manage record types." (NotIssuer) |
| Not paused | Contract | IssuerRegistry.paused() is false | "The system is paused, so new record types cannot be added right now. Try again when it restarts." (SystemPaused) |
The new id is 1000 + the number of types this issuer has registered, so ids are dense per issuer and never reused. A type cannot be renamed or deleted.
Switching a type off and on
Section Your record types lists each type with its description and a badge Active or Inactive, and a button Turn off or Turn on. The call is setTypeActive(id, active); it emits RecordTypeActiveChanged only when the state changes. A retired issuer key cannot change its types (NotIssuer), and an id the issuer never registered gives "This record type does not exist for your account." (UnknownRecordType). Switching is not stopped by the pause.
An inactive type is no longer offered on the issue form and is refused at issuance (RecordTypeNotAllowed). Credentials already issued with it are untouched and still show its name.
Where types appear
| Place | Behaviour |
|---|---|
| Issue form, Record Type | The 32 built-in types, then the issuer's active custom types labelled "(issuer-defined)". If the custom types cannot be read: "Your own record types could not be loaded, so only the standard types are listed." |
| Issuance | The CredentialRegistry calls isTypeAllowed(issuer, recordType). A reserved id, another issuer's id, an unregistered id or an inactive type reverts with RecordTypeNotAllowed: "This record type is not available to you. It may be switched off, or it may belong to another institution." |
| Verify page heading | The resolved name, with the issuer-defined badge for custom types. "Unknown type" for a reserved id or an id the registry does not know, "Type unavailable" when the lookup failed. |
| Holder inbox and dashboard | The same resolved name and badge |
| Bulk import | recordType is given by name, case-insensitive, matched against the built-in names and the issuer's types; a name two of the issuer's types share is refused. See Bulk import files. |
| Open Badge export | Built-in types map to an achievement type where one fits; others may not be exportable as a badge (the issued card then explains why). |
Custom names are resolved with typeOf(issuer, id) and cached once found. A name not found yet is not cached, because a node can lag behind the issuer's transaction. All names pass through the same hidden-character filter before display.
Variants
- v0.2 (legacy) has only the built-in list; the issue form offers those 32 names.
- Several contract sets. Each set has its own
RecordTypeRegistry. An issuer's custom types in one set are not visible in another, so an issuer who moves to a new set registers its types there again.
Code map
| Concern | File |
|---|---|
| Contract | blockchain/contracts/v03/RecordTypeRegistry.sol |
| Names and rules | src/types/records.ts, src/lib/record-types.ts (typeKind, validateTypeInput, cleanTypeText) |
| Service | src/services/domain/record-type-service.ts (register, setActive, typesOf, issuable, resolve) |
| Events | src/services/indexing/credential-index.ts (recordTypesOf) |
| Messages | src/lib/record-type-errors.ts, src/lib/credential-errors.ts |
| Pages | src/app/records/types/page.tsx, RecordTypesPanel.tsx, src/app/records/add/issue-types.ts, src/app/verify/ResolvedRecordType.tsx, src/components/RecordTypeLabel.tsx |
Tests
blockchain/test/v03/RecordTypeRegistry.test.ts,CredentialRecordTypes.test.tssrc/lib/record-types.test.ts,src/lib/record-type-errors.test.ts,src/services/domain/record-type-service.test.tssrc/app/records/types/RecordTypesPanel.test.tsx,src/app/records/add/issue-types.test.ts,src/app/verify/ResolvedRecordType.test.tsx,src/components/RecordTypeLabel.test.tsx