Source Code

;; Badge Issuer Contract
;; Implements badge creation and minting functionality
;;
;; Error Codes Used:
;; - u100: ERR-OWNER-ONLY - Action restricted to contract owner
;; - u104: ERR-UNAUTHORIZED - Caller lacks required permissions
;; - u204: ERR-INVALID-RECIPIENT - Invalid recipient address
;; - u600: ERR-TEMPLATE-NOT-FOUND - Template does not exist
;; - u601: ERR-INVALID-TEMPLATE - Template data is invalid
;; - u700: ERR-BATCH-TOO-LARGE - Batch exceeds size limit
;; - u701: ERR-BATCH-EMPTY - Batch array is empty
;; - u702: ERR-BATCH-MISMATCHED-LENGTHS - Array length mismatch
;; - u110: ERR-PAUSED - Contract is paused

(use-trait badge-issuer-trait .badge-issuer-trait.badge-issuer)

;; Import error codes from centralized error-codes contract
(define-constant ERR-OWNER-ONLY (err u100))
(define-constant ERR-UNAUTHORIZED (err u104))
(define-constant ERR-INVALID-RECIPIENT (err u204))
(define-constant ERR-TEMPLATE-NOT-FOUND (err u600))
(define-constant ERR-INVALID-TEMPLATE (err u601))
(define-constant ERR-BATCH-TOO-LARGE (err u700))
(define-constant ERR-BATCH-EMPTY (err u701))
(define-constant ERR-BATCH-MISMATCHED-LENGTHS (err u702))
(define-constant ERR-PAUSED (err u110))

;; Contract constants
(define-constant contract-owner tx-sender)

;; Data variables
(define-data-var next-badge-id uint u1)
(define-data-var batch-mint-counter uint u0)

;; Access control map
(define-map authorized-issuers principal bool)

;; Initialize contract owner as authorized issuer
(map-set authorized-issuers contract-owner true)

;; Access control functions
(define-public (authorize-issuer (issuer principal))
  (begin
    (asserts! (is-eq tx-sender contract-owner) ERR-OWNER-ONLY)

    ;; Emit issuer authorized event
    (print {
      event: "issuer-authorized",
      issuer: issuer,
      authorized-by: tx-sender,
      block-height: block-height
    })

    (ok (map-set authorized-issuers issuer true))
  )
)

(define-public (revoke-issuer (issuer principal))
  (begin
    (asserts! (is-eq tx-sender contract-owner) ERR-OWNER-ONLY)

    ;; Emit issuer revoked event
    (print {
      event: "issuer-revoked",
      issuer: issuer,
      revoked-by: tx-sender,
      block-height: block-height
    })

    (ok (map-set authorized-issuers issuer false))
  )
)

(define-read-only (is-authorized-issuer (issuer principal))
  (default-to false (map-get? authorized-issuers issuer))
)

;; Badge template creation
(define-public (create-badge-template (name (string-ascii 64)) (description (string-ascii 256)) (category uint) (default-level uint) (community-id uint) (expiration-duration uint))
  (begin
    (asserts! (not (unwrap-panic (contract-call? .access-control is-paused))) ERR-PAUSED)
    (asserts! (or 
      (is-authorized-issuer tx-sender)
      (contract-call? .access-control can-issue-badges-in-community community-id tx-sender)
    ) ERR-UNAUTHORIZED)

    (let
      (
        (result (try! (contract-call? .badge-metadata create-badge-template name description category default-level expiration-duration)))
      )
      ;; Emit template created event
      (print {
        event: "template-created",
        template-id: result,
        name: name,
        description: description,
        category: category,
        default-level: default-level,
        expiration-duration: expiration-duration,
        creator: tx-sender,
        block-height: block-height
      })

      (ok result)
    )
  )
)

;; Badge minting function
(define-public (mint-badge (recipient principal) (template-id uint) (community-id uint))
  (begin
    (asserts! (not (unwrap-panic (contract-call? .access-control is-paused))) ERR-PAUSED)
    (asserts! (or 
      (is-authorized-issuer tx-sender)
      (contract-call? .access-control can-issue-badges-in-community community-id tx-sender)
    ) ERR-UNAUTHORIZED)

    (let
      (
        (badge-id (var-get next-badge-id))
        (template (unwrap! (contract-call? .badge-metadata get-badge-template template-id) ERR-TEMPLATE-NOT-FOUND))
        (expiration-height (if (> (get expiration-duration template) u0)
                             (+ block-height (get expiration-duration template))
                             u0))
      )
      ;; Mint NFT
      (try! (contract-call? .passport-nft mint recipient))

      ;; Set badge metadata
      (try! (contract-call? .badge-metadata set-badge-metadata
        badge-id
        {
          template-id: template-id,
          level: (get default-level template),
          category: (get category template),
          timestamp: block-height,
          expiration-height: expiration-height,
          issuer: tx-sender,
          active: true
        }
      ))

      ;; Emit badge minted event
      (print {
        event: "badge-minted",
        badge-id: badge-id,
        recipient: recipient,
        template-id: template-id,
        issuer: tx-sender,
        level: (get default-level template),
        category: (get category template),
        expiration-height: expiration-height,
        block-height: block-height
      })

      (var-set next-badge-id (+ badge-id u1))
      (ok badge-id)
    )
  )
)

;; Private helper for fold-based batch minting
(define-private (batch-mint-step
    (recipient principal)
    (acc {next-badge-id: uint, template-ids: (list 50 uint), index: uint}))
  (let (
      (badge-id (get next-badge-id acc))
      (index (get index acc))
      (template-id (unwrap-panic (element-at (get template-ids acc) index)))
      (template (unwrap-panic (contract-call? .badge-metadata get-badge-template template-id)))
      (expiration-height (if (> (get expiration-duration template) u0)
                           (+ block-height (get expiration-duration template))
                           u0))
    )
    (unwrap-panic (contract-call? .passport-nft mint recipient))
    (unwrap-panic (contract-call? .badge-metadata set-badge-metadata badge-id {
      template-id: template-id,
      level: (get default-level template),
      category: (get category template),
      timestamp: block-height,
      expiration-height: expiration-height,
      issuer: tx-sender,
      active: true
    }))
    {next-badge-id: (+ badge-id u1), template-ids: (get template-ids acc), index: (+ index u1)}
  )
)

;; Batch mint badges to multiple recipients with corresponding template IDs
(define-public (batch-mint-badges (recipients (list 50 principal)) (template-ids (list 50 uint)) (community-id uint))
  (begin
    (asserts! (not (unwrap-panic (contract-call? .access-control is-paused))) ERR-PAUSED)
    (asserts! (or
      (is-authorized-issuer tx-sender)
      (contract-call? .access-control can-issue-badges-in-community community-id tx-sender)
    ) ERR-UNAUTHORIZED)

    (let (
        (recipients-len (len recipients))
        (template-ids-len (len template-ids))
        (current-badge-id (var-get next-badge-id))
        (batch-id (var-get batch-mint-counter))
      )
      ;; Input validation
      (asserts! (is-eq recipients-len template-ids-len) ERR-BATCH-MISMATCHED-LENGTHS)
      (asserts! (<= recipients-len u50) ERR-BATCH-TOO-LARGE)
      (asserts! (> recipients-len u0) ERR-BATCH-EMPTY)

      (let ((result (fold batch-mint-step recipients
                      {next-badge-id: current-badge-id, template-ids: template-ids, index: u0})))

        ;; Update the next badge ID
        (var-set next-badge-id (get next-badge-id result))

        ;; Emit batch mint event
        (print {
          event: "batch-badges-minted",
          batch-id: batch-id,
          issuer: tx-sender,
          recipients: recipients,
          template-ids: template-ids,
          count: recipients-len,
          block-height: block-height
        })

        (var-set batch-mint-counter (+ batch-id u1))
        (ok true)
      )
    )
  )
)

;; Revoke badge
(define-public (revoke-badge (badge-id uint) (community-id uint))
  (begin
    (asserts! (not (unwrap-panic (contract-call? .access-control is-paused))) ERR-PAUSED)
    (let
      (
        (metadata (unwrap! (contract-call? .badge-metadata get-badge-metadata badge-id) ERR-INVALID-TEMPLATE))
      )
      (asserts! (or 
        (is-eq tx-sender (get issuer metadata)) 
        (is-eq tx-sender contract-owner)
        (contract-call? .access-control can-revoke-badges-in-community community-id tx-sender)
      ) ERR-UNAUTHORIZED)

      ;; Emit badge revoked event
      (print {
        event: "badge-revoked",
        badge-id: badge-id,
        issuer: (get issuer metadata),
        revoked-by: tx-sender,
        block-height: block-height
      })

      (contract-call? .badge-metadata set-badge-metadata
        badge-id
        (merge metadata { active: false })
      )
    )
  )
)

;; Update badge metadata
(define-public (update-badge-metadata (badge-id uint) (new-metadata {level: uint, category: uint, timestamp: uint, expiration-height: uint}) (community-id uint))
  (begin
    (asserts! (not (unwrap-panic (contract-call? .access-control is-paused))) ERR-PAUSED)
    (let
      (
        (current-metadata (unwrap! (contract-call? .badge-metadata get-badge-metadata badge-id) ERR-INVALID-TEMPLATE))
      )
      (asserts! (or 
        (is-eq tx-sender (get issuer current-metadata)) 
        (is-eq tx-sender contract-owner)
        (contract-call? .access-control can-issue-badges-in-community community-id tx-sender)
      ) ERR-UNAUTHORIZED)

      ;; Emit metadata updated event
      (print {
        event: "badge-metadata-updated",
        badge-id: badge-id,
        old-level: (get level current-metadata),
        new-level: (get level new-metadata),
        old-category: (get category current-metadata),
        new-category: (get category new-metadata),
        updated-by: tx-sender,
        block-height: block-height
      })

      (contract-call? .badge-metadata set-badge-metadata
        badge-id
        (merge current-metadata (merge new-metadata { issuer: (get issuer current-metadata), active: (get active current-metadata) }))
      )
    )
  )
)

;; Renew badge
(define-public (renew-badge (badge-id uint) (community-id uint))
  (begin
    (asserts! (not (unwrap-panic (contract-call? .access-control is-paused))) ERR-PAUSED)
    (let
      (
        (metadata (unwrap! (contract-call? .badge-metadata get-badge-metadata badge-id) ERR-INVALID-TEMPLATE))
        (template (unwrap! (contract-call? .badge-metadata get-badge-template (get template-id metadata)) ERR-TEMPLATE-NOT-FOUND))
        (expiration-duration (get expiration-duration template))
      )
      (asserts! (or 
        (is-eq tx-sender (get issuer metadata)) 
        (is-eq tx-sender contract-owner)
        (contract-call? .access-control can-issue-badges-in-community community-id tx-sender)
      ) ERR-UNAUTHORIZED)

      ;; Can only renew badges that have an expiration duration
      (asserts! (> expiration-duration u0) ERR-INVALID-TEMPLATE)

      (let
        (
          (new-expiration-height (+ block-height expiration-duration))
          (new-metadata (merge metadata { expiration-height: new-expiration-height, timestamp: block-height, active: true }))
        )
        
        ;; Update metadata
        (try! (contract-call? .badge-metadata set-badge-metadata badge-id new-metadata))

        ;; Emit badge renewed event
        (print {
          event: "badge-renewed",
          badge-id: badge-id,
          new-expiration-height: new-expiration-height,
          renewed-by: tx-sender,
          block-height: block-height
        })

        (ok true)
      )
    )
  )
)

Functions (9)

FunctionAccessArgs
authorize-issuerpublicissuer: principal
revoke-issuerpublicissuer: principal
is-authorized-issuerread-onlyissuer: principal
create-badge-templatepublicname: (string-ascii 64
mint-badgepublicrecipient: principal, template-id: uint, community-id: uint
batch-mint-badgespublicrecipients: (list 50 principal
revoke-badgepublicbadge-id: uint, community-id: uint
update-badge-metadatapublicbadge-id: uint, new-metadata: {level: uint, category: uint, timestamp: uint, expiration-height: uint}, community-id: uint
renew-badgepublicbadge-id: uint, community-id: uint