Skip to main content

Record type registry

Every credential carries a recordType number. Ids 0 to 31 are the built-in types every issuer may use (Transcript, Degree, and so on). An institution that needs something else (a workshop certificate, a micro-credential) registers its own type in RecordTypeRegistry, and the credential registry refuses any id that is neither built in nor an active custom type of that issuer.

Id ranges​

uint32 public constant LAST_BUILT_IN_TYPE = 31;
uint32 public constant FIRST_CUSTOM_TYPE = 1000;
uint256 public constant MAX_NAME_BYTES = 64;
uint256 public constant MAX_DESCRIPTION_BYTES = 256;
RangeMeaningIssuance
0 to 31Built-in types, same meaning for every issuerAlways allowed
32 to 999Reserved for future built-in typesAlways refused (RecordTypeNotAllowed)
1000 and upCustom types, numbered per issuer from 1000 in registration orderAllowed only for the issuer that registered the id, and only while it is active

Because custom ids are per issuer, id 1000 of one university and id 1000 of another are different types. A verifier always reads a custom name with typeOf(issuer, id).

Code and early design disagree

An early design record (ADR-0038) described 0 to 999 as built in. The contract and the app use 0 to 31 as built in and keep 32 to 999 reserved. These pages follow the code.

Built-in names​

The app holds the 32 built-in names in src/types/records.ts (RECORD_TYPE_NAMES, index equals id); the contract stores no names for them.

IdNameIdNameIdNameIdName
0Transcript8National ID16Transfer Certificate24Circular
1Degree9Passport Copy17Bills25News
2Marksheet10Character Certificate18Fee Receipt26Recommendation Letter
3Diploma11Entrance Results19Scholarship Letter27Internship Certificate
4Certificate12Admit Card20Loan Document28Experience Letter
5Provisional Certificate13Counseling Letter21Hostel Clearance29Bonafide Certificate
6Birth Certificate14Seat Allotment Letter22Routine30No Objection Certificate
7Citizenship15Migration Certificate23Notice31Other

The list matches the 32 values of the v0.2 RecordType enum, so credentials of both generations use the same numbers for built-in types.

Storage in words​

MappingMeaning
_counts[issuer]How many custom types the issuer has registered. The next id is 1000 + count
_types[issuer][id]RecordTypeInfo {name, description, active}
_nameTaken[issuer][keccak256(name)]Names already used by that issuer, kept even after a type is switched off

The contract inherits only ERC165. It has no admin.

Functions​

FunctionWho may callChecksEffectsEventReverts
registerType(string name, string description) uint32An active issuerNot paused; isActiveIssuer(sender); name 1 to 64 bytes; description at most 256 bytes; name not used before by this issuerAssigns id 1000 + count, stores the type as activeRecordTypeRegistered(issuer, id, name, description)SystemPaused, NotIssuer, EmptyTypeName, TypeNameTooLong, TypeDescriptionTooLong, TypeNameInUse
setTypeActive(uint32 id, bool active)The issuer that owns the id; a retired key is refusedThe type exists in the caller's namespaceChanges the flag; does nothing (and emits nothing) if unchangedRecordTypeActiveChanged(issuer, id, active)NotIssuer, UnknownRecordType
typeOf(address issuer, uint32 id) RecordTypeInfoAnyone (view)The custom type existsUnknownRecordType
typeCount(address issuer) uint32Anyone (view)
isTypeAllowed(address issuer, uint32 id) boolAnyone (view); called by the credential registry at issuanceTrue for 0 to 31, or for an existing active custom type of that issuer
contractVersion() string, supportsInterface(bytes4) boolAnyone"0.5.0", ERC-165

Rules that follow from the code:

  • Ids are never reused, and a switched-off type keeps its name reserved for that issuer. Old credentials therefore keep a stable meaning.
  • Switching a type off stops new issuance under it but leaves existing credentials untouched; their name stays readable through typeOf.
  • setTypeActive keeps working while the set is paused. A removed issuer (no name) can still switch its types; a retired key cannot.
  • Built-in ids cannot be passed to setTypeActive or typeOf; both revert UnknownRecordType.
  • Custom types belong to the key that registered them. After a key retirement the successor starts with no custom types and registers its own.

How issuance uses it​

Reserved ids 32 to 999 fall through to the second question, and since custom ids start at 1000 they never exist, so they are always refused.

How the app uses it​

  • /records/types lists the issuer's types and offers registration and switching (src/services/domain/record-type-service.ts: registerType, setTypeActive, typeOf). The list is rebuilt from RecordTypeRegistered and RecordTypeActiveChanged events, because the contract returns no lists.
  • src/lib/record-types.ts holds the shared helpers: it classifies an id as built-in, reserved or custom and validates a new name and description before they are sent (it strips hidden characters).
  • The issue form, the holder inbox and the verify page show the type name, and mark custom ones as issuer-defined.

The full flow is in Record types.

Gas​

From blockchain/gas-snapshot.json: registerType 150,590; setTypeActive 32,070.

Source files​

  • blockchain/contracts/v03/RecordTypeRegistry.sol
  • blockchain/contracts/v03/interfaces/IRecordTypeRegistry.sol
  • src/services/domain/record-type-service.ts, src/lib/record-types.ts, src/types/records.ts

Tests​

  • blockchain/test/v03/RecordTypeRegistry.test.ts: reserved ranges, per-issuer numbering from 1000, names per issuer, ids never reused, removed issuers, bounds, owner-only switching, no event when unchanged, typeOf and isTypeAllowed
  • blockchain/test/v03/CredentialRecordTypes.test.ts: issuance with built-in, active custom, reserved, unregistered, foreign and inactive ids
  • blockchain/test/v03/KeyLineage.test.ts: a retired key cannot register or change a type