Source Code

;; Certificate NFT Contract
;; Issue verifiable certificates/credentials on-chain
;; Halal - education and verification
;; Clarity 4 compatible

(define-non-fungible-token certificate uint)

(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-AUTHORIZED (err u401))
(define-constant ERR-NOT-ISSUER (err u402))
(define-constant ERR-CERT-NOT-FOUND (err u404))
(define-constant ERR-ALREADY-REVOKED (err u405))

(define-data-var cert-count uint u0)

(define-map issuers principal bool)
(define-map cert-data uint {
  recipient: principal,
  issuer: principal,
  title: (string-utf8 100),
  description: (string-utf8 200),
  issued-at: uint,
  revoked: bool
})

(map-set issuers CONTRACT-OWNER true)

(define-public (add-issuer (issuer principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (map-set issuers issuer true)
    (ok true)))

(define-public (issue-certificate (recipient principal) (title (string-utf8 100)) (description (string-utf8 200)))
  (let ((id (+ (var-get cert-count) u1)))
    (asserts! (default-to false (map-get? issuers tx-sender)) ERR-NOT-ISSUER)
    (try! (nft-mint? certificate id recipient))
    (map-set cert-data id {
      recipient: recipient, issuer: tx-sender, title: title,
      description: description, issued-at: stacks-block-height, revoked: false
    })
    (var-set cert-count id)
    (print { event: "cert-issued", id: id, recipient: recipient })
    (ok id)))

(define-public (revoke-certificate (cert-id uint))
  (let ((cert (unwrap! (map-get? cert-data cert-id) ERR-CERT-NOT-FOUND)))
    (asserts! (is-eq tx-sender (get issuer cert)) ERR-NOT-AUTHORIZED)
    (asserts! (not (get revoked cert)) ERR-ALREADY-REVOKED)
    (map-set cert-data cert-id (merge cert { revoked: true }))
    (ok true)))

(define-public (transfer-certificate (cert-id uint) (recipient principal))
  (let ((cert (unwrap! (map-get? cert-data cert-id) ERR-CERT-NOT-FOUND)))
    (asserts! (is-eq tx-sender (get recipient cert)) ERR-NOT-AUTHORIZED)
    (try! (nft-transfer? certificate cert-id tx-sender recipient))
    (map-set cert-data cert-id (merge cert { recipient: recipient }))
    (ok true)))

(define-read-only (get-certificate (cert-id uint)) (map-get? cert-data cert-id))
(define-read-only (get-cert-count) (ok (var-get cert-count)))
(define-read-only (is-valid (cert-id uint))
  (match (map-get? cert-data cert-id) c (ok (not (get revoked c))) (ok false)))
(define-read-only (get-cert-owner (cert-id uint)) (nft-get-owner? certificate cert-id))

Functions (8)

FunctionAccessArgs
add-issuerpublicissuer: principal
issue-certificatepublicrecipient: principal, title: (string-utf8 100
revoke-certificatepubliccert-id: uint
transfer-certificatepubliccert-id: uint, recipient: principal
get-certificateread-onlycert-id: uint
get-cert-countread-only
is-validread-onlycert-id: uint
get-cert-ownerread-onlycert-id: uint