Development of Blockchain Patient Consent Management System
Consent management in healthcare — more than just legal formality. GDPR requires explicit, informed, freely given consent for health data processing. HIPAA requires authorization for PHI (Protected Health Information) disclosure. Paper consent forms get lost, modified retroactively, and don't give patients real control. Blockchain turns consent into verifiable, auditable, and revocable on-chain record.
Consent Model
Consent is a structured document with specific parameters: who can see what data, for what purposes, for how long. It's not binary "yes/no", but granular permission.
contract ConsentRegistry {
enum ConsentStatus { ACTIVE, REVOKED, EXPIRED }
enum DataCategory {
DIAGNOSIS,
LAB_RESULTS,
PRESCRIPTIONS,
IMAGING,
MENTAL_HEALTH,
GENETIC,
HIV_STATUS,
SUBSTANCE_ABUSE
}
enum Purpose {
TREATMENT, // direct medical care
PAYMENT, // insurance and payment operations
HEALTHCARE_OPS, // operational activity
RESEARCH, // medical research
QUALITY_IMPROVEMENT, // service quality improvement
CARE_COORDINATION // treatment coordination
}
struct Consent {
bytes32 patientId;
address grantee; // who consent is granted to
DataCategory[] categories; // which data categories
Purpose[] purposes; // for what purposes
uint256 grantedAt;
uint256 expiresAt; // 0 = perpetual (until revoked)
ConsentStatus status;
bytes32 documentHash; // hash of consent PDF
string version; // privacy policy version
}
// consentId => Consent
mapping(bytes32 => Consent) public consents;
// patientId => grantee => consentIds
mapping(bytes32 => mapping(address => bytes32[])) public patientConsents;
event ConsentGranted(
bytes32 indexed consentId,
bytes32 indexed patientId,
address indexed grantee,
uint256 expiresAt
);
event ConsentRevoked(bytes32 indexed consentId, bytes32 indexed patientId);
function grantConsent(
bytes32 patientId,
address grantee,
DataCategory[] calldata categories,
Purpose[] calldata purposes,
uint256 duration,
bytes32 documentHash,
string calldata version
) external onlyPatient(patientId) returns (bytes32 consentId) {
consentId = keccak256(abi.encodePacked(
patientId, grantee, block.timestamp, block.number
));
consents[consentId] = Consent({
patientId: patientId,
grantee: grantee,
categories: categories,
purposes: purposes,
grantedAt: block.timestamp,
expiresAt: duration == 0 ? 0 : block.timestamp + duration,
status: ConsentStatus.ACTIVE,
documentHash: documentHash,
version: version
});
patientConsents[patientId][grantee].push(consentId);
emit ConsentGranted(consentId, patientId, grantee, block.timestamp + duration);
}
function revokeConsent(bytes32 consentId) external {
Consent storage consent = consents[consentId];
require(
isPatient(consent.patientId, msg.sender),
"Not patient"
);
require(consent.status == ConsentStatus.ACTIVE, "Not active");
consent.status = ConsentStatus.REVOKED;
emit ConsentRevoked(consentId, consent.patientId);
}
function isConsentValid(
bytes32 consentId,
DataCategory category,
Purpose purpose
) external view returns (bool) {
Consent storage consent = consents[consentId];
if (consent.status != ConsentStatus.ACTIVE) return false;
if (consent.expiresAt != 0 && block.timestamp > consent.expiresAt) return false;
bool hasCategory = false;
for (uint i = 0; i < consent.categories.length; i++) {
if (consent.categories[i] == category) { hasCategory = true; break; }
}
bool hasPurpose = false;
for (uint i = 0; i < consent.purposes.length; i++) {
if (consent.purposes[i] == purpose) { hasPurpose = true; break; }
}
return hasCategory && hasPurpose;
}
}
Electronic Signature of Consent Documents
Consent must be legally significant. This requires not just an on-chain record, but a signed document:
// Patient signs structured consent data via EIP-712
const consentTypedData = {
domain: {
name: "HealthConsent",
version: "1",
chainId: 1,
verifyingContract: CONSENT_REGISTRY_ADDRESS,
},
types: {
Consent: [
{ name: "patientId", type: "bytes32" },
{ name: "grantee", type: "address" },
{ name: "categories", type: "uint8[]" },
{ name: "purposes", type: "uint8[]" },
{ name: "expiresAt", type: "uint256" },
{ name: "documentHash", type: "bytes32" },
{ name: "version", type: "string" },
],
},
message: consentData,
};
const signature = await walletClient.signTypedData(consentTypedData);
// Signature is stored with consent and verified when needed
Consent PDF is generated automatically from structured data, its SHA-256 hash is stored on-chain in documentHash. Patient gets PDF copy, can verify hash in blockchain.
Emergency Access
Critical edge case: unconscious patient, consent not given, but medical help is necessary. Break-glass mechanism:
contract EmergencyAccess {
struct EmergencyAccessEvent {
bytes32 patientId;
address requester;
string justification; // reason for emergency access
uint256 timestamp;
bool approved; // approved retroactively
}
// Time to challenge emergency access: 72 hours
uint256 constant CHALLENGE_PERIOD = 72 hours;
mapping(bytes32 => EmergencyAccessEvent[]) public emergencyLog;
// Emergency access available to authorized medical providers
// Logged, patient notification after critical state resolved
function requestEmergencyAccess(
bytes32 patientId,
string calldata justification
) external onlyEmergencyProvider {
emergencyLog[patientId].push(EmergencyAccessEvent({
patientId: patientId,
requester: msg.sender,
justification: justification,
timestamp: block.timestamp,
approved: false // requires retroactive approval
}));
emit EmergencyAccessRequested(patientId, msg.sender, justification);
}
}
Integration with National Systems
Different countries have different consent management requirements:
EU (GDPR + eHealth): explicit consent, right to withdraw, purpose limitation. Consent must be granular (cannot require consent "for everything").
US (HIPAA): authorization form, minimum necessary standard, patient rights to amend. Consent for research separate from treatment.
Belarus / Russia / Ukraine: national health data laws, data localization requirements.
For multi-national systems: consent template system with jurisdiction-specific fields, automatic form loading based on geolocation.
Notifications and Patient Portal
After each data access — notification to patient:
// After each access to records
async function notifyPatientOfAccess(event: AccessEvent) {
const patient = await getPatient(event.patientId);
await sendNotification(patient.email, {
type: "data_access",
accessor: await getProviderName(event.accessor),
dataCategory: event.category,
purpose: event.purpose,
timestamp: event.timestamp,
txHash: event.transactionHash,
});
}
Patient portal shows: list of all active consents, access history (audit trail), ability to revoke consent in one click.
Technical Stack
| Component | Technology |
|---|---|
| Smart contracts | Solidity + OpenZeppelin |
| EIP-712 signatures | viem / ethers.js |
| PDF generation | PDFKit / WeasyPrint |
| Patient portal | React + wagmi |
| Notifications | Email (SendGrid) + Push (Web Push API) |
| Indexing | The Graph |
Development of consent management system: 2-3 months for full implementation with patient portal, PDF generation and emergency access.







