Use Case Guides/Template Libraries/EVM Templates

Health Records

A healthcare smart contract is a blockchain-based program designed to automate and securely manage various processes within the healthcare ecosystem, such as patient data access, provider accreditation, insurance claim processing, and treatment record keeping. These contracts enforce logic through code, ensuring that only authorized entities can perform sensitive actions like registering patients, submitting medical claims, or accessing health records. By using cryptographic consent and role-based access control, healthcare smart contracts give patients greater control over their data while reducing the administrative overhead for providers and insurers. All interactions are logged immutably, providing transparency and accountability for regulators and auditors.


The use of healthcare smart contracts supports a wide range of applications, including the creation of national electronic health record systems, streamlined insurance claim workflows, public health campaign tracking, and secure sharing of health credentials. These contracts help address major challenges in the healthcare sector such as data fragmentation, fraud, manual processing delays, and lack of traceability. They ensure compliance with legal frameworks by encoding privacy rules directly into the system and by enabling real-time auditability of care delivery and financial transactions.

Disclaimer

This smart contract implementation is provided for educational and illustrative purposes only. It represents a conceptual framework for blockchain-based healthcare systems.

Key considerations before any production use:

  • Legal Compliance: healthcare systems are highly regulated and vary significantly by jurisdiction. This implementation would require substantial modification to meet specific legal requirements in any given location.

  • Security Review: The contract should undergo comprehensive security auditing by qualified blockchain security professionals before handling real user or financial transactions.

  • No Warranty: The authors and contributors disclaim all liability for any use of this code. Users assume all risks associated with implementation and operation.

  • Consultation Required: Any organization considering using this template should obtain advice from qualified legal counsel, and blockchain security experts before deployment.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
 
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
 
contract NationalHealthcareSystem is
    Initializable,
    UUPSUpgradeable,
    AccessControlUpgradeable,
    PausableUpgradeable,
    ReentrancyGuardUpgradeable
{
    using CountersUpgradeable for CountersUpgradeable.Counter;
 
    // ========== CONSTANTS ==========
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 public constant PROVIDER_ROLE = keccak256("PROVIDER_ROLE");
    bytes32 public constant AUDITOR_ROLE = keccak256("AUDITOR_ROLE");
 
    // ICD-10 code length validation
    uint256 public constant MIN_DIAGNOSIS_CODE_LENGTH = 3;
    uint256 public constant MAX_DIAGNOSIS_CODE_LENGTH = 7;
 
    // ========== STRUCTS ==========
    struct Patient {
        address walletAddress;
        bytes32 nationalIdHash; // SHA-3 hashed national ID
        bool isActive;
        uint256 registrationDate;
        uint256 lastUpdated;
    }
 
    struct Provider {
        string name;
        string licenseNumber;
        string providerType; // "HOSPITAL"|"CLINIC"|"LAB"
        bool isActive;
        bool isSuspended;
        uint256 registrationDate;
        uint256 lastUpdated;
    }
 
    struct Consent {
        address providerAddress;
        bool isGranted;
        uint256 grantDate;
        uint256 revokeDate;
        string purpose; // "TREATMENT"|"CLAIMS"|"RESEARCH"
    }
 
    struct EHR {
        string ipfsHash;
        address providerAddress;
        string documentType; // "PRESCRIPTION"|"LAB_RESULT"|"IMAGE"
        uint256 timestamp;
        bytes32 dataHash; // Hash of original data for integrity
    }
 
    struct InsuranceClaim {
        address patientAddress;
        address providerAddress;
        string diagnosisCode; // ICD-10
        uint256 amountRequested;
        uint256 amountApproved;
        ClaimStatus status;
        uint256 submissionDate;
        uint256 approvalDate;
        uint256 settlementDate;
        uint256 denialDate;
        string denialReason;
        string[] supportingEHRs; // Supporting EHR documents (e.g., IPFS hashes)
    }
 
    enum ClaimStatus {
        PENDING,
        APPROVED,
        DENIED,
        SETTLED
    }
 
    // ========== STATE VARIABLES ==========
    CountersUpgradeable.Counter private _patientIds;
    CountersUpgradeable.Counter private _claimIds;
 
    mapping(uint256 => Patient) private _patients;
    mapping(address => Provider) private _providers;
    mapping(address => mapping(address => Consent)) private _consents;
    mapping(address => EHR[]) private _patientEHRs;
    mapping(uint256 => InsuranceClaim) private _claims;
    mapping(bytes32 => bool) private _registeredNationalIds;
    mapping(address => uint256) private _addressToPatientId;
    mapping(string => uint256) private _licenseToProviderCount;
 
    // New: Array to track provider addresses
    address[] private _providerAddresses;
 
    // ========== EVENTS ==========
    event PatientRegistered(uint256 indexed patientId, address indexed walletAddress);
    event PatientUpdated(uint256 indexed patientId, bool isActive);
    event ProviderRegistered(address indexed providerAddress, string providerType);
    event ProviderUpdated(address indexed providerAddress, bool isActive, bool isSuspended);
    event ConsentGranted(address indexed patientAddress, address indexed providerAddress, string purpose);
    event ConsentRevoked(address indexed patientAddress, address indexed providerAddress);
    event EHRAdded(address indexed patientAddress, address indexed providerAddress, string documentType, string ipfsHash);
    event ClaimSubmitted(uint256 indexed claimId, address indexed patientAddress, string diagnosisCode);
    event ClaimApproved(uint256 indexed claimId, uint256 amountApproved);
    event ClaimDenied(uint256 indexed claimId, string reason);
    event ClaimSettled(uint256 indexed claimId);
    event EmergencyPaused(address indexed admin);
    event EmergencyUnpaused(address indexed admin);
 
    // ========== MODIFIERS ==========
    modifier onlyActiveProvider() {
        require(
            _providers[msg.sender].isActive && !_providers[msg.sender].isSuspended,
            "Provider not active"
        );
        _;
    }
 
    modifier onlyValidPatient(address patientAddress) {
        require(_addressToPatientId[patientAddress] != 0, "Patient not registered");
        _;
    }
 
    modifier onlyWithConsent(address patientAddress) {
        require(
            _consents[patientAddress][msg.sender].isGranted,
            "Consent not granted"
        );
        _;
    }
 
    modifier validDiagnosisCode(string memory code) {
        bytes memory codeBytes = bytes(code);
        require(
            codeBytes.length >= MIN_DIAGNOSIS_CODE_LENGTH &&
            codeBytes.length <= MAX_DIAGNOSIS_CODE_LENGTH,
            "Invalid diagnosis code"
        );
        _;
    }
 
    // ========== INITIALIZATION ==========
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }
 
    function initialize(address superAdmin) public initializer {
        __AccessControl_init();
        __UUPSUpgradeable_init();
        __Pausable_init();
        __ReentrancyGuard_init();
 
        _setupRole(DEFAULT_ADMIN_ROLE, superAdmin);
        _setupRole(ADMIN_ROLE, superAdmin);
        _setupRole(AUDITOR_ROLE, superAdmin);
 
        // Increment patient counter so that the first valid patientId is 1
        _patientIds.increment();
    }
 
    function _authorizeUpgrade(address newImplementation)
        internal
        override
        onlyRole(DEFAULT_ADMIN_ROLE)
    {
        require(newImplementation != address(0), "Invalid new implementation");
    }
 
    // ========== PATIENT REGISTRY (ENHANCED) ==========
    function registerPatient(
        address walletAddress,
        bytes32 nationalIdHash,
        bytes calldata governmentSignature
    ) external onlyRole(ADMIN_ROLE) whenNotPaused nonReentrant {
        require(!_registeredNationalIds[nationalIdHash], "Patient already registered");
        require(_addressToPatientId[walletAddress] == 0, "Wallet already registered");
        require(_verifyGovernmentSignature(walletAddress, nationalIdHash, governmentSignature), "Invalid signature");
 
        _patientIds.increment();
        uint256 patientId = _patientIds.current();
 
        _patients[patientId] = Patient({
            walletAddress: walletAddress,
            nationalIdHash: nationalIdHash,
            isActive: true,
            registrationDate: block.timestamp,
            lastUpdated: block.timestamp
        });
 
        _registeredNationalIds[nationalIdHash] = true;
        _addressToPatientId[walletAddress] = patientId;
 
        emit PatientRegistered(patientId, walletAddress);
    }
 
    function updatePatientStatus(uint256 patientId, bool isActive) external onlyRole(ADMIN_ROLE) {
        require(_patients[patientId].walletAddress != address(0), "Patient not found");
        _patients[patientId].isActive = isActive;
        _patients[patientId].lastUpdated = block.timestamp;
 
        emit PatientUpdated(patientId, isActive);
    }
 
    // ========== PROVIDER REGISTRY (ENHANCED) ==========
    function registerProvider(
        address providerAddress,
        string memory name,
        string memory licenseNumber,
        string memory providerType,
        bytes calldata accreditationProof
    ) external onlyRole(ADMIN_ROLE) whenNotPaused {
        require(!_providerExists(providerAddress), "Provider already registered");
        require(_verifyAccreditation(providerAddress, licenseNumber, providerType, accreditationProof), "Invalid accreditation");
 
        _providers[providerAddress] = Provider({
            name: name,
            licenseNumber: licenseNumber,
            providerType: providerType,
            isActive: true,
            isSuspended: false,
            registrationDate: block.timestamp,
            lastUpdated: block.timestamp
        });
 
        _licenseToProviderCount[licenseNumber]++;
        _grantRole(PROVIDER_ROLE, providerAddress);
 
        // Track the provider address for consent lookups
        _providerAddresses.push(providerAddress);
 
        emit ProviderRegistered(providerAddress, providerType);
    }
 
    function suspendProvider(address providerAddress, bool suspend) external onlyRole(ADMIN_ROLE) {
        require(_providerExists(providerAddress), "Provider not found");
        _providers[providerAddress].isSuspended = suspend;
        _providers[providerAddress].lastUpdated = block.timestamp;
 
        emit ProviderUpdated(providerAddress, _providers[providerAddress].isActive, suspend);
    }
 
    // ========== CONSENT MANAGEMENT (ENHANCED) ==========
    function grantConsent(
        address providerAddress,
        string memory purpose
    ) external onlyValidPatient(msg.sender) whenNotPaused {
        require(_providerExists(providerAddress), "Provider not found");
        require(!_consents[msg.sender][providerAddress].isGranted, "Consent already granted");
 
        _consents[msg.sender][providerAddress] = Consent({
            providerAddress: providerAddress,
            isGranted: true,
            grantDate: block.timestamp,
            revokeDate: 0,
            purpose: purpose
        });
 
        emit ConsentGranted(msg.sender, providerAddress, purpose);
    }
 
    function revokeConsent(address providerAddress) external onlyValidPatient(msg.sender) {
        require(_consents[msg.sender][providerAddress].isGranted, "No active consent");
 
        _consents[msg.sender][providerAddress].isGranted = false;
        _consents[msg.sender][providerAddress].revokeDate = block.timestamp;
 
        emit ConsentRevoked(msg.sender, providerAddress);
    }
 
    // ========== EHR MANAGEMENT (ENHANCED) ==========
    function addEHR(
        address patientAddress,
        string memory ipfsHash,
        string memory documentType,
        bytes32 dataHash
    ) external onlyActiveProvider onlyWithConsent(patientAddress) whenNotPaused nonReentrant {
        _patientEHRs[patientAddress].push(EHR({
            ipfsHash: ipfsHash,
            providerAddress: msg.sender,
            documentType: documentType,
            timestamp: block.timestamp,
            dataHash: dataHash
        }));
 
        emit EHRAdded(patientAddress, msg.sender, documentType, ipfsHash);
    }
 
    // ========== CLAIMS MANAGEMENT (ENHANCED) ==========
    function submitClaim(
        address patientAddress,
        string memory diagnosisCode,
        uint256 amountRequested,
        string[] calldata supportingEHRs
    ) external onlyActiveProvider onlyWithConsent(patientAddress) validDiagnosisCode(diagnosisCode)
      whenNotPaused nonReentrant returns (uint256) {
 
        _claimIds.increment();
        uint256 claimId = _claimIds.current();
 
        _claims[claimId] = InsuranceClaim({
            patientAddress: patientAddress,
            providerAddress: msg.sender,
            diagnosisCode: diagnosisCode,
            amountRequested: amountRequested,
            amountApproved: 0,
            status: ClaimStatus.PENDING,
            submissionDate: block.timestamp,
            approvalDate: 0,
            settlementDate: 0,
            denialDate: 0,
            denialReason: "",
            supportingEHRs: supportingEHRs
        });
 
        emit ClaimSubmitted(claimId, patientAddress, diagnosisCode);
        return claimId;
    }
 
    function approveClaim(
        uint256 claimId,
        uint256 approvedAmount,
        string memory approvalNotes
    ) external onlyRole(ADMIN_ROLE) whenNotPaused {
        require(_claims[claimId].status == ClaimStatus.PENDING, "Claim not pending");
        require(approvedAmount <= _claims[claimId].amountRequested, "Amount exceeds request");
 
        _claims[claimId].status = ClaimStatus.APPROVED;
        _claims[claimId].amountApproved = approvedAmount;
        _claims[claimId].approvalDate = block.timestamp;
 
        emit ClaimApproved(claimId, approvedAmount);
    }
 
    function denyClaim(
        uint256 claimId,
        string memory reason
    ) external onlyRole(ADMIN_ROLE) whenNotPaused {
        require(_claims[claimId].status == ClaimStatus.PENDING, "Claim not pending");
 
        _claims[claimId].status = ClaimStatus.DENIED;
        _claims[claimId].denialReason = reason;
        _claims[claimId].denialDate = block.timestamp;
 
        emit ClaimDenied(claimId, reason);
    }
 
    // New: Settle an approved claim
    function settleClaim(uint256 claimId) external onlyRole(ADMIN_ROLE) whenNotPaused {
        require(_claims[claimId].status == ClaimStatus.APPROVED, "Claim must be approved to settle");
        _claims[claimId].status = ClaimStatus.SETTLED;
        _claims[claimId].settlementDate = block.timestamp;
 
        emit ClaimSettled(claimId);
    }
 
    // ========== EMERGENCY FUNCTIONS ==========
    function emergencyPause() external onlyRole(DEFAULT_ADMIN_ROLE) {
        _pause();
        emit EmergencyPaused(msg.sender);
    }
 
    function emergencyUnpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
        _unpause();
        emit EmergencyUnpaused(msg.sender);
    }
 
    // ========== VIEW FUNCTIONS ==========
    function getPatientConsents(address patientAddress) external view returns (Consent[] memory) {
        uint256 count;
        address[] memory providers = _getAllProviders();
 
        // First pass: count valid consents
        for (uint i = 0; i < providers.length; i++) {
            if (_consents[patientAddress][providers[i]].isGranted) {
                count++;
            }
        }
 
        // Second pass: populate result array
        Consent[] memory result = new Consent[](count);
        uint256 index;
        for (uint i = 0; i < providers.length; i++) {
            if (_consents[patientAddress][providers[i]].isGranted) {
                result[index] = _consents[patientAddress][providers[i]];
                index++;
            }
        }
 
        return result;
    }
 
    // ========== PRIVATE HELPERS ==========
    function _providerExists(address providerAddress) private view returns (bool) {
        return bytes(_providers[providerAddress].licenseNumber).length > 0;
    }
 
    function _getAllProviders() private view returns (address[] memory) {
        return _providerAddresses;
    }
 
    function _verifyGovernmentSignature(
        address walletAddress,
        bytes32 nationalIdHash,
        bytes calldata signature
    ) private pure returns (bool) {
        // Robust verification logic to be implemented.
        return true;
    }
 
    function _verifyAccreditation(
        address providerAddress,
        string memory licenseNumber,
        string memory providerType,
        bytes calldata proof
    ) private pure returns (bool) {
        // Robust accreditation verification to be implemented.
        return true;
    }
}

Detailed explanation:


1. Upgradeability and initialization

  • Upgradeable architecture:
    The contract uses the UUPS (Universal Upgradeable Proxy Standard) pattern to allow its implementation to be updated without losing state. The _authorizeUpgrade function restricts upgrades to addresses that hold the DEFAULT_ADMIN_ROLE and ensures that the new implementation address is valid (non-zero).

  • Initialization process:
    Instead of a traditional constructor, the contract has an initialize function that:

    • Initializes inherited modules such as access control, upgradeability, pausing, and reentrancy guards.
    • Sets up key roles (DEFAULT_ADMIN_ROLE, ADMIN_ROLE, and AUDITOR_ROLE) with a provided super administrator.
    • Increments the patient ID counter during initialization so that the first patient receives an ID of 1, thereby avoiding potential ambiguity with an unregistered state.

2. Access control and role management

  • Defined Roles:
    The contract establishes three main roles:

    • ADMIN_ROLE: Grants permissions for administrative actions including patient and provider registration, status updates, and claim management.
    • PROVIDER_ROLE: Assigned to healthcare providers after successful registration and accreditation.
    • AUDITOR_ROLE: Intended for users responsible for oversight and auditing without direct control over system operations.
  • Modifiers for Security:
    Several modifiers enforce strict access control and state validations:

    • onlyActiveProvider: Ensures that a provider is active and not suspended before they can execute certain functions.
    • onlyValidPatient: Confirms that a patient is registered by checking for a non-zero patient ID.
    • onlyWithConsent: Validates that a provider has been granted consent by the patient.
    • validDiagnosisCode: Checks that a diagnosis code is within the acceptable ICD-10 length range (between 3 and 7 characters).

3. Patient registry

  • Patient Data Structure:
    The Patient struct holds critical information including:

    • The wallet address.
    • A SHA-3 hashed version of the national ID.
    • An active status flag.
    • Timestamps for registration and the most recent update.
  • Patient registration process:
    The registerPatient function (accessible only by an admin) performs several validations:

    • It confirms that the hashed national ID has not been registered before.
    • It ensures that the wallet address is not already associated with another patient.
    • It verifies a government signature (currently a placeholder) to authenticate the registration.

    On successful validation, a new patient ID is generated (starting from 1), and the patient’s data is stored. The mapping from wallet address to patient ID is also updated for quick lookups.

  • Updating patient status:
    Administrators can change the active status of a patient using the updatePatientStatus function, which also updates the patient’s last modified timestamp.


4. Provider registry

  • Provider data structure:
    The Provider struct contains:

    • The provider's name, license number, and type (e.g., "HOSPITAL", "CLINIC", "LAB").
    • Flags indicating if the provider is active or suspended.
    • Timestamps for when the provider was registered and last updated.
  • Provider registration and tracking:
    The registerProvider function allows an admin to add a new provider after:

    • Checking that the provider is not already registered.
    • Verifying the provider’s accreditation using a placeholder function.

    Once validated, the provider’s data is stored, the PROVIDER_ROLE is granted, and the provider’s address is added to a dedicated array (_providerAddresses). This array is later used to retrieve all provider addresses for operations like consent management.

  • Provider suspension:
    Administrators can suspend or reinstate a provider using the suspendProvider function, which updates the provider’s suspension status and last updated timestamp.


  • Consent data structure:
    The Consent struct records:

    • The provider’s address for which consent is granted.
    • A flag indicating whether the consent is active.
    • Timestamps for when the consent was granted and, if applicable, when it was revoked.
    • The purpose for which consent is provided (e.g., "TREATMENT", "CLAIMS", "RESEARCH").
  • Granting and revoking consent:

    • Granting consent:
      Patients can call grantConsent to allow a provider to access their data for a specific purpose. This function checks that the provider exists and that consent has not been previously granted.
    • Revoking consent:
      The revokeConsent function allows patients to withdraw consent, updating the consent status and recording the time of revocation.

6. Electronic health records (EHR) Management

  • EHR data structure:
    The EHR struct is used to store:

    • An IPFS hash that points to the off-chain location of the health record.
    • The provider’s address that is adding the record.
    • The document type (such as "PRESCRIPTION", "LAB_RESULT", or "IMAGE").
    • A timestamp of when the record was added.
    • A data hash for ensuring the integrity of the record.
  • Recording EHRs:
    The addEHR function enables an active provider (with valid consent from the patient) to add an EHR. The record is stored in an array associated with the patient’s address and an event is emitted to log this addition.


7. Insurance claims management

  • Insurance claim data structure:
    The InsuranceClaim struct encapsulates:

    • Addresses for both the patient and the provider.
    • The diagnosis code (which must adhere to ICD-10 length requirements).
    • The requested amount and the amount that gets approved.
    • A status field using an enum (PENDING, APPROVED, DENIED, SETTLED).
    • Timestamps for submission, approval, settlement, and denial.
    • A string to record the reason for denial.
    • An array to hold supporting EHR document references (e.g., IPFS hashes).
  • Claims workflow:

    • Submission:
      The submitClaim function lets a provider submit a claim on behalf of a patient (with proper consent). It validates the diagnosis code, accepts supporting EHRs, and logs the claim submission.
    • Approval:
      Through the approveClaim function, an admin can approve a claim, ensuring the approved amount does not exceed the requested amount. The approval timestamp is recorded.
    • Denial:
      The denyClaim function allows an admin to deny a claim, documenting the reason and timestamp for the denial.
    • Settlement:
      The newly added settleClaim function permits an admin to mark an approved claim as settled, updating its status and recording the settlement timestamp.

8. Emergency controls

  • Pausing operations:
    The contract integrates a pausing mechanism using OpenZeppelin’s pausable module.

    • The emergencyPause function allows an admin to pause all contract operations, which is useful in a crisis or security incident.
    • The emergencyUnpause function restores normal operations.

    Both functions emit events to provide an audit trail of emergency actions.


9. View functions and private helpers

  • Retrieving patient consents:
    The getPatientConsents function gathers all active consents for a given patient by iterating through the tracked provider addresses. It compiles and returns an array of active consent records.

  • Private helper functions:

    • _providerExists: Checks if a provider is registered by verifying the existence of a license number.
    • _getAllProviders: Returns the list of all registered provider addresses stored in _providerAddresses.
    • _verifyGovernmentSignature and _verifyAccreditation: These are stub functions that currently always return true; they serve as placeholders where robust external verification logic will be integrated.