Source Code

;; title: stx-proCredentials
;; version:
;; summary:
;; description:

;; title: resume-registry
;; version:
;; summary:
;; description:

;; Professional Credentials Verification Contract
;; A decentralized system for verifying and managing professional credentials
;; including education degrees, employment history, and skill certifications.
;; Organizations can issue verifications, and individuals maintain verified profiles
;; that employers and recruiters can trust for hiring decisions.

;; Contract owner principal stored at deployment
(define-constant contract-owner tx-sender)

;; Error codes for various failure scenarios
(define-constant ERR-OWNER-ONLY (err u100))
(define-constant ERR-NOT-FOUND (err u101))
(define-constant ERR-UNAUTHORIZED-ACCESS (err u102))
(define-constant ERR-ALREADY-EXISTS (err u103))
(define-constant ERR-INVALID-DATA (err u104))
(define-constant ERR-CREDENTIAL-EXPIRED (err u105))
(define-constant ERR-INVALID-INPUT (err u106))

;; Sequence counters for generating unique credential identifiers
(define-data-var education-credential-counter uint u0)
(define-data-var employment-credential-counter uint u0)
(define-data-var skill-credential-counter uint u0)

;; Storage for registered organizations that can issue verifications
;; Maps organization identifier to organization details
(define-map registered-organizations
  { organization-identifier: (string-ascii 64) }
  {
    organization-name: (string-ascii 100),
    organization-domain: (string-ascii 64),
    is-verified: bool,
    authorized-principal: principal
  }
)

;; Storage for individual user profiles
;; Maps user principal to their profile information
(define-map user-profiles
  { user-address: principal }
  {
    full-name: (string-ascii 100),
    email-address: (string-ascii 100),
    profile-metadata-uri: (optional (string-utf8 256)),
    profile-created-at: uint,
    profile-updated-at: uint
  }
)

;; Storage for education credentials
;; Maps combination of user address and credential ID to education details
(define-map education-records
  { 
    holder-address: principal,
    record-identifier: (string-ascii 64)
  }
  {
    issuing-institution-id: (string-ascii 64),
    degree-title: (string-ascii 100),
    study-field: (string-ascii 100),
    education-start-date: uint,
    education-end-date: uint,
    is-verified: bool,
    verified-by: (optional principal),
    verified-at: (optional uint),
    additional-metadata-uri: (optional (string-utf8 256))
  }
)

;; Storage for employment credentials
;; Maps combination of user address and credential ID to employment details
(define-map employment-records
  { 
    holder-address: principal,
    record-identifier: (string-ascii 64)
  }
  {
    employer-organization-id: (string-ascii 64),
    job-title: (string-ascii 100),
    job-description: (string-utf8 500),
    employment-start-date: uint,
    employment-end-date: (optional uint),
    is-verified: bool,
    verified-by: (optional principal),
    verified-at: (optional uint),
    additional-metadata-uri: (optional (string-utf8 256))
  }
)

;; Storage for skill and certification credentials
;; Maps combination of user address and credential ID to skill details
(define-map skill-records
  { 
    holder-address: principal,
    record-identifier: (string-ascii 64)
  }
  {
    skill-title: (string-ascii 100),
    certification-issuer: (optional (string-ascii 64)),
    certification-issue-date: uint,
    certification-expiry-date: (optional uint),
    is-verified: bool,
    verified-by: (optional principal),
    verified-at: (optional uint),
    additional-metadata-uri: (optional (string-utf8 256))
  }
)

;; Validates that organization identifier meets length requirements
(define-private (validate-organization-id (org-id (string-ascii 64)))
  (and 
    (>= (len org-id) u1)
    (<= (len org-id) u64)
  )
)

;; Validates that credential identifier meets length requirements
(define-private (validate-record-id (record-id (string-ascii 64)))
  (and 
    (>= (len record-id) u1)
    (<= (len record-id) u64)
  )
)

;; Checks if the transaction sender is the contract owner
(define-private (check-is-contract-owner)
  (is-eq tx-sender contract-owner)
)

;; Checks if the transaction sender is authorized for a specific organization
(define-private (check-is-organization-authorized (org-id (string-ascii 64)))
  (if (validate-organization-id org-id)
    (match (map-get? registered-organizations { organization-identifier: org-id })
      organization-data (is-eq tx-sender (get authorized-principal organization-data))
      false
    )
    false
  )
)

;; Checks if transaction sender has verification rights for an organization
;; Either contract owner or organization's authorized principal
(define-private (check-verification-authorization (org-id (string-ascii 64)))
  (if (validate-organization-id org-id)
    (or (check-is-contract-owner) (check-is-organization-authorized org-id))
    false
  )
)

;; Checks if a user profile exists for given address
(define-private (check-profile-exists (user-addr principal))
  (is-some (map-get? user-profiles { user-address: user-addr }))
)

;; Checks if an organization is registered
(define-private (check-organization-exists (org-id (string-ascii 64)))
  (if (validate-organization-id org-id)
    (is-some (map-get? registered-organizations { organization-identifier: org-id }))
    false
  )
)

;; Checks if an education record exists
(define-private (check-education-record-exists (holder-addr principal) (record-id (string-ascii 64)))
  (if (validate-record-id record-id)
    (is-some (map-get? education-records { holder-address: holder-addr, record-identifier: record-id }))
    false
  )
)

;; Checks if an employment record exists
(define-private (check-employment-record-exists (holder-addr principal) (record-id (string-ascii 64)))
  (if (validate-record-id record-id)
    (is-some (map-get? employment-records { holder-address: holder-addr, record-identifier: record-id }))
    false
  )
)

;; Checks if a skill record exists
(define-private (check-skill-record-exists (holder-addr principal) (record-id (string-ascii 64)))
  (if (validate-record-id record-id)
    (is-some (map-get? skill-records { holder-address: holder-addr, record-identifier: record-id }))
    false
  )
)

;; Registers a new organization that can issue credential verifications
;; Organization starts as unverified and must be verified by contract owner
(define-public (register-organization 
    (org-id (string-ascii 64)) 
    (name (string-ascii 100)) 
    (domain (string-ascii 64)))
  (begin
    (asserts! (validate-organization-id org-id) ERR-INVALID-INPUT)
    (asserts! (>= (len name) u1) ERR-INVALID-INPUT)
    (asserts! (>= (len domain) u1) ERR-INVALID-INPUT)

    (let ((organization-data {
          organization-name: name,
          organization-domain: domain,
          is-verified: false,
          authorized-principal: tx-sender
        }))
      (if (check-organization-exists org-id)
        ERR-ALREADY-EXISTS
        (ok (map-set registered-organizations { organization-identifier: org-id } organization-data))
      )
    )
  )
)

;; Verifies an organization, allowing them to issue credential verifications
;; Only contract owner can verify organizations
(define-public (verify-organization (org-id (string-ascii 64)))
  (begin
    (asserts! (validate-organization-id org-id) ERR-INVALID-INPUT)
    (asserts! (check-is-contract-owner) ERR-OWNER-ONLY)

    (match (map-get? registered-organizations { organization-identifier: org-id })
      organization-data (ok (map-set registered-organizations 
                { organization-identifier: org-id } 
                (merge organization-data { is-verified: true })))
      ERR-NOT-FOUND
    )
  )
)

;; Updates organization information
;; Only the organization's authorized principal can update their info
(define-public (update-organization 
    (org-id (string-ascii 64)) 
    (name (string-ascii 100)) 
    (domain (string-ascii 64)))
  (begin
    (asserts! (validate-organization-id org-id) ERR-INVALID-INPUT)
    (asserts! (>= (len name) u1) ERR-INVALID-INPUT)
    (asserts! (>= (len domain) u1) ERR-INVALID-INPUT)
    (asserts! (check-is-organization-authorized org-id) ERR-UNAUTHORIZED-ACCESS)

    (match (map-get? registered-organizations { organization-identifier: org-id })
      organization-data (ok (map-set registered-organizations 
                { organization-identifier: org-id } 
                (merge organization-data { 
                  organization-name: name, 
                  organization-domain: domain 
                })))
      ERR-NOT-FOUND
    )
  )
)



;; Adds an education credential to user's profile
;; User must have a profile before adding credentials
(define-public (add-education-credential
    (institution-id (string-ascii 64))
    (degree (string-ascii 100))
    (field-of-study (string-ascii 100))
    (start-date uint)
    (end-date uint)
    (metadata-uri (optional (string-utf8 256))))
  (begin
    (asserts! (validate-organization-id institution-id) ERR-INVALID-INPUT)
    (asserts! (>= (len degree) u1) ERR-INVALID-INPUT)
    (asserts! (>= (len field-of-study) u1) ERR-INVALID-INPUT)
    (asserts! (check-profile-exists tx-sender) ERR-NOT-FOUND)
    (asserts! (<= start-date end-date) ERR-INVALID-DATA)

    (let ((current-counter (var-get education-credential-counter))
          (generated-record-id (unwrap-panic 
                          (as-max-len? 
                            (concat "EDU-" 
                                   (concat (unwrap-panic (as-max-len? institution-id u20)) 
                                           (concat "-" (unwrap-panic (as-max-len? degree u8)))))
                            u64)))
          (education-data {
            issuing-institution-id: institution-id,
            degree-title: degree,
            study-field: field-of-study,
            education-start-date: start-date,
            education-end-date: end-date,
            is-verified: false,
            verified-by: none,
            verified-at: none,
            additional-metadata-uri: metadata-uri
          }))
      (var-set education-credential-counter (+ current-counter u1))
      (asserts! (validate-record-id generated-record-id) ERR-INVALID-INPUT)

      (ok (map-set education-records 
            { holder-address: tx-sender, record-identifier: generated-record-id } 
            education-data))
    )
  )
)

;; Adds an employment credential to user's profile
;; End date is optional for current employment
(define-public (add-employment-credential
    (organization-id (string-ascii 64))
    (title (string-ascii 100))
    (description (string-utf8 500))
    (start-date uint)
    (end-date (optional uint))
    (metadata-uri (optional (string-utf8 256))))
  (begin
    (asserts! (validate-organization-id organization-id) ERR-INVALID-INPUT)
    (asserts! (>= (len title) u1) ERR-INVALID-INPUT)
    (asserts! (>= (len description) u1) ERR-INVALID-INPUT)
    (asserts! (check-profile-exists tx-sender) ERR-NOT-FOUND)

    (match end-date
      end-timestamp (asserts! (<= start-date end-timestamp) ERR-INVALID-DATA)
      true
    )

    (let ((current-counter (var-get employment-credential-counter))
          (generated-record-id (unwrap-panic 
                          (as-max-len? 
                            (concat "EMP-" 
                                   (concat (unwrap-panic (as-max-len? organization-id u20)) 
                                           (concat "-" (unwrap-panic (as-max-len? title u8)))))
                            u64)))
          (employment-data {
            employer-organization-id: organization-id,
            job-title: title,
            job-description: description,
            employment-start-date: start-date,
            employment-end-date: end-date,
            is-verified: false,
            verified-by: none,
            verified-at: none,
            additional-metadata-uri: metadata-uri
          }))
      (var-set employment-credential-counter (+ current-counter u1))
      (asserts! (validate-record-id generated-record-id) ERR-INVALID-INPUT)

      (ok (map-set employment-records 
            { holder-address: tx-sender, record-identifier: generated-record-id } 
            employment-data))
    )
  )
)

;; Adds a skill or certification credential to user's profile
;; Expiry date is optional for skills that don't expire
(define-public (add-skill-credential
    (skill-name (string-ascii 100))
    (issuer (optional (string-ascii 64)))
    (issue-date uint)
    (expiry-date (optional uint))
    (metadata-uri (optional (string-utf8 256))))
  (begin
    (asserts! (>= (len skill-name) u1) ERR-INVALID-INPUT)

    (match issuer
      issuer-organization-id (begin
                  (asserts! (validate-organization-id issuer-organization-id) ERR-INVALID-INPUT)
                  (asserts! (check-organization-exists issuer-organization-id) ERR-NOT-FOUND))
      true
    )

    (asserts! (check-profile-exists tx-sender) ERR-NOT-FOUND)

    (match expiry-date
      expiry-timestamp (asserts! (<= issue-date expiry-timestamp) ERR-INVALID-DATA)
      true
    )

    (let ((current-counter (var-get skill-credential-counter))
          (issuer-substring (match issuer
                          some-issuer-id (unwrap-panic (as-max-len? some-issuer-id u15))
                          "none"))
          (generated-record-id (unwrap-panic 
                          (as-max-len? 
                            (concat "SKILL-" 
                                   (concat issuer-substring 
                                           (concat "-" (unwrap-panic (as-max-len? skill-name u15)))))
                            u64)))
          (skill-data {
            skill-title: skill-name,
            certification-issuer: issuer,
            certification-issue-date: issue-date,
            certification-expiry-date: expiry-date,
            is-verified: false,
            verified-by: none,
            verified-at: none,
            additional-metadata-uri: metadata-uri
          }))
      (var-set skill-credential-counter (+ current-counter u1))
      (asserts! (validate-record-id generated-record-id) ERR-INVALID-INPUT)

      (ok (map-set skill-records 
            { holder-address: tx-sender, record-identifier: generated-record-id } 
            skill-data))
    )
  )
)



;; Revokes an education credential verification
;; Only issuing organization or contract owner can revoke
(define-public (revoke-education-verification
    (profile-address principal)
    (credential-id (string-ascii 64))
    (institution-id (string-ascii 64)))
  (begin
    (asserts! (validate-organization-id institution-id) ERR-INVALID-INPUT)
    (asserts! (validate-record-id credential-id) ERR-INVALID-INPUT)
    (asserts! (or (check-is-contract-owner) (check-is-organization-authorized institution-id)) ERR-UNAUTHORIZED-ACCESS)
    (asserts! (check-education-record-exists profile-address credential-id) ERR-NOT-FOUND)

    (match (map-get? education-records 
            { holder-address: profile-address, record-identifier: credential-id })
      education-record 
      (begin
        (asserts! (is-eq (get issuing-institution-id education-record) institution-id) ERR-UNAUTHORIZED-ACCESS)
        (asserts! (get is-verified education-record) ERR-INVALID-DATA)
        (ok (map-set education-records 
              { holder-address: profile-address, record-identifier: credential-id } 
              (merge education-record { 
                is-verified: false, 
                verified-by: none,
                verified-at: none 
              })))
      )
      ERR-NOT-FOUND
    )
  )
)

;; Revokes an employment credential verification
;; Only issuing organization or contract owner can revoke
(define-public (revoke-employment-verification
    (profile-address principal)
    (credential-id (string-ascii 64))
    (organization-id (string-ascii 64)))
  (begin
    (asserts! (validate-organization-id organization-id) ERR-INVALID-INPUT)
    (asserts! (validate-record-id credential-id) ERR-INVALID-INPUT)
    (asserts! (or (check-is-contract-owner) (check-is-organization-authorized organization-id)) ERR-UNAUTHORIZED-ACCESS)
    (asserts! (check-employment-record-exists profile-address credential-id) ERR-NOT-FOUND)

    (match (map-get? employment-records 
            { holder-address: profile-address, record-identifier: credential-id })
      employment-record 
      (begin
        (asserts! (is-eq (get employer-organization-id employment-record) organization-id) ERR-UNAUTHORIZED-ACCESS)
        (asserts! (get is-verified employment-record) ERR-INVALID-DATA)
        (ok (map-set employment-records 
              { holder-address: profile-address, record-identifier: credential-id } 
              (merge employment-record { 
                is-verified: false, 
                verified-by: none,
                verified-at: none 
              })))
      )
      ERR-NOT-FOUND
    )
  )
)

;; Revokes a skill credential verification
;; Only issuing organization or contract owner can revoke
(define-public (revoke-skill-verification
    (profile-address principal)
    (credential-id (string-ascii 64))
    (org-id (string-ascii 64)))
  (begin
    (asserts! (validate-organization-id org-id) ERR-INVALID-INPUT)
    (asserts! (validate-record-id credential-id) ERR-INVALID-INPUT)
    (asserts! (or (check-is-contract-owner) (check-is-organization-authorized org-id)) ERR-UNAUTHORIZED-ACCESS)
    (asserts! (check-skill-record-exists profile-address credential-id) ERR-NOT-FOUND)

    (match (map-get? skill-records 
            { holder-address: profile-address, record-identifier: credential-id })
      skill-record 
      (begin
        (match (get certification-issuer skill-record)
          issuer-organization-id (asserts! (is-eq issuer-organization-id org-id) ERR-UNAUTHORIZED-ACCESS)
          true
        )
        (asserts! (get is-verified skill-record) ERR-INVALID-DATA)
        (ok (map-set skill-records 
              { holder-address: profile-address, record-identifier: credential-id } 
              (merge skill-record { 
                is-verified: false, 
                verified-by: none,
                verified-at: none 
              })))
      )
      ERR-NOT-FOUND
    )
  )
)

;; Retrieves profile information for a given address
;; Returns none if profile doesn't exist
(define-read-only (get-profile (address principal))
  (map-get? user-profiles { user-address: address })
)

;; Retrieves organization information by organization ID
;; Returns none if organization doesn't exist
(define-read-only (get-organization (org-id (string-ascii 64)))
  (if (validate-organization-id org-id)
    (map-get? registered-organizations { organization-identifier: org-id })
    none
  )
)

;; Retrieves education credential details
;; Returns none if credential doesn't exist
(define-read-only (get-education-credential (profile-address principal) (credential-id (string-ascii 64)))
  (if (validate-record-id credential-id)
    (map-get? education-records { holder-address: profile-address, record-identifier: credential-id })
    none
  )
)

;; Retrieves employment credential details
;; Returns none if credential doesn't exist
(define-read-only (get-employment-credential (profile-address principal) (credential-id (string-ascii 64)))
  (if (validate-record-id credential-id)
    (map-get? employment-records { holder-address: profile-address, record-identifier: credential-id })
    none
  )
)

;; Retrieves skill credential details
;; Returns none if credential doesn't exist
(define-read-only (get-skill-credential (profile-address principal) (credential-id (string-ascii 64)))
  (if (validate-record-id credential-id)
    (map-get? skill-records { holder-address: profile-address, record-identifier: credential-id })
    none
  )
)

;; Checks if an education credential is currently valid
;; Returns true only if verified
(define-read-only (is-education-credential-valid (profile-address principal) (credential-id (string-ascii 64)))
  (if (validate-record-id credential-id)
    (match (map-get? education-records { holder-address: profile-address, record-identifier: credential-id })
      education-record (get is-verified education-record)
      false
    )
    false
  )
)

;; Checks if an employment credential is currently valid
;; Returns true only if verified
(define-read-only (is-employment-credential-valid (profile-address principal) (credential-id (string-ascii 64)))
  (if (validate-record-id credential-id)
    (match (map-get? employment-records { holder-address: profile-address, record-identifier: credential-id })
      employment-record (get is-verified employment-record)
      false
    )
    false
  )
)

Functions (26)

FunctionAccessArgs
validate-organization-idprivateorg-id: (string-ascii 64
validate-record-idprivaterecord-id: (string-ascii 64
check-is-contract-ownerprivate
check-is-organization-authorizedprivateorg-id: (string-ascii 64
check-verification-authorizationprivateorg-id: (string-ascii 64
check-profile-existsprivateuser-addr: principal
check-organization-existsprivateorg-id: (string-ascii 64
check-education-record-existsprivateholder-addr: principal, record-id: (string-ascii 64
check-employment-record-existsprivateholder-addr: principal, record-id: (string-ascii 64
check-skill-record-existsprivateholder-addr: principal, record-id: (string-ascii 64
register-organizationpublicorg-id: (string-ascii 64
verify-organizationpublicorg-id: (string-ascii 64
update-organizationpublicorg-id: (string-ascii 64
add-education-credentialpublicinstitution-id: (string-ascii 64
add-employment-credentialpublicorganization-id: (string-ascii 64
add-skill-credentialpublicskill-name: (string-ascii 100
revoke-education-verificationpublicprofile-address: principal, credential-id: (string-ascii 64
revoke-employment-verificationpublicprofile-address: principal, credential-id: (string-ascii 64
revoke-skill-verificationpublicprofile-address: principal, credential-id: (string-ascii 64
get-profileread-onlyaddress: principal
get-organizationread-onlyorg-id: (string-ascii 64
get-education-credentialread-onlyprofile-address: principal, credential-id: (string-ascii 64
get-employment-credentialread-onlyprofile-address: principal, credential-id: (string-ascii 64
get-skill-credentialread-onlyprofile-address: principal, credential-id: (string-ascii 64
is-education-credential-validread-onlyprofile-address: principal, credential-id: (string-ascii 64
is-employment-credential-validread-onlyprofile-address: principal, credential-id: (string-ascii 64