Source Code


(impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait)

;; ============================================
;; CONSTANTS
;; ============================================

(define-constant CONTRACT_OWNER tx-sender)

;; Error codes
(define-constant ERR_NOT_AUTHORIZED (err u100))
(define-constant ERR_NOT_TOKEN_OWNER (err u101))
(define-constant ERR_TOKEN_NOT_FOUND (err u102))
(define-constant ERR_INVALID_BADGE_TYPE (err u103))

;; Badge type IDs
(define-constant BADGE_WEEK_WARRIOR u1)      ;; 7-day streak
(define-constant BADGE_MONTHLY_MASTER u2)    ;; 30-day streak
(define-constant BADGE_CENTURY_CLUB u3)      ;; 100-day streak
(define-constant BADGE_CHATTERBOX u4)        ;; 100 comments
(define-constant BADGE_LOVE_MACHINE u5)      ;; 500 likes
(define-constant BADGE_OG_PRESENCE u6)       ;; 100 check-ins

;; Base URI for metadata (can be updated by owner)
;; Using 210 max length to allow room for badge-type suffix (int-to-ascii returns up to 40 chars)
(define-data-var base-uri (string-ascii 210) "https://presence-protocol.app/metadata/")

;; ============================================
;; NFT DEFINITION
;; ============================================

(define-non-fungible-token presence-badge uint)

;; ============================================
;; DATA STORAGE
;; ============================================

;; Track last token ID
(define-data-var last-token-id uint u0)

;; Map token ID to badge type
(define-map token-badge-type uint uint)

;; Map token ID to minted timestamp (block height)
(define-map token-mint-time uint uint)

;; Authorized minter (presence-tracker contract)
(define-data-var authorized-minter principal CONTRACT_OWNER)

;; ============================================
;; AUTHORIZATION
;; ============================================

;; Set the authorized minter (only presence-tracker contract should mint)
(define-public (set-authorized-minter (new-minter principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED)
    (var-set authorized-minter new-minter)
    (ok true)
  )
)

;; ============================================
;; SIP-009 REQUIRED FUNCTIONS
;; ============================================

;; Get last token ID
(define-read-only (get-last-token-id)
  (ok (var-get last-token-id))
)

;; Get token URI - returns metadata URL for the badge
(define-read-only (get-token-uri (token-id uint))
  (let
    (
      (badge-type (default-to u0 (map-get? token-badge-type token-id)))
    )
    (ok (some (concat (var-get base-uri) (int-to-ascii (to-int badge-type)))))
  )
)

;; Get owner of token
(define-read-only (get-owner (token-id uint))
  (ok (nft-get-owner? presence-badge token-id))
)

;; Transfer token
(define-public (transfer (token-id uint) (sender principal) (recipient principal))
  (begin
    (asserts! (is-eq tx-sender sender) ERR_NOT_TOKEN_OWNER)
    (asserts! (is-some (nft-get-owner? presence-badge token-id)) ERR_TOKEN_NOT_FOUND)
    (nft-transfer? presence-badge token-id sender recipient)
  )
)

;; ============================================
;; MINTING FUNCTIONS
;; ============================================

;; Mint a new badge (only callable by authorized minter or owner)
(define-public (mint (recipient principal) (badge-type uint))
  (let
    (
      (token-id (+ (var-get last-token-id) u1))
    )
    ;; Check authorization
    (asserts!
      (or
        (is-eq contract-caller (var-get authorized-minter))
        (is-eq contract-caller CONTRACT_OWNER)
      )
      ERR_NOT_AUTHORIZED
    )
    ;; Validate badge type (1-6)
    (asserts! (and (>= badge-type u1) (<= badge-type u6)) ERR_INVALID_BADGE_TYPE)
    ;; Mint the NFT
    (try! (nft-mint? presence-badge token-id recipient))
    ;; Store badge metadata
    (map-set token-badge-type token-id badge-type)
    (map-set token-mint-time token-id stacks-block-height)
    ;; Update last token ID
    (var-set last-token-id token-id)
    ;; Emit event
    (print {
      event: "badge-minted",
      token-id: token-id,
      badge-type: badge-type,
      recipient: recipient,
      block-height: stacks-block-height
    })
    (ok token-id)
  )
)

;; ============================================
;; READ-ONLY FUNCTIONS
;; ============================================

;; Get badge type for a token
(define-read-only (get-badge-type (token-id uint))
  (map-get? token-badge-type token-id)
)

;; Get mint time for a token
(define-read-only (get-mint-time (token-id uint))
  (map-get? token-mint-time token-id)
)

;; Get badge name by type
(define-read-only (get-badge-name (badge-type uint))
  (if (is-eq badge-type BADGE_WEEK_WARRIOR)
    (ok "Week Warrior")
    (if (is-eq badge-type BADGE_MONTHLY_MASTER)
      (ok "Monthly Master")
      (if (is-eq badge-type BADGE_CENTURY_CLUB)
        (ok "Century Club")
        (if (is-eq badge-type BADGE_CHATTERBOX)
          (ok "Chatterbox")
          (if (is-eq badge-type BADGE_LOVE_MACHINE)
            (ok "Love Machine")
            (if (is-eq badge-type BADGE_OG_PRESENCE)
              (ok "OG Presence")
              ERR_INVALID_BADGE_TYPE
            )
          )
        )
      )
    )
  )
)

;; Get authorized minter
(define-read-only (get-authorized-minter)
  (var-get authorized-minter)
)

;; ============================================
;; ADMIN FUNCTIONS
;; ============================================

;; Update base URI (only owner)
(define-public (set-base-uri (new-uri (string-ascii 210)))
  (begin
    (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_NOT_AUTHORIZED)
    (var-set base-uri new-uri)
    ;; Emit SIP-019 metadata update notification
    (print {
      notification: "token-metadata-update",
      payload: {
        contract-id: (as-contract tx-sender),
        token-class: "nft"
      }
    })
    (ok true)
  )
)

Functions (11)

FunctionAccessArgs
set-authorized-minterpublicnew-minter: principal
get-last-token-idread-only
get-token-uriread-onlytoken-id: uint
get-ownerread-onlytoken-id: uint
transferpublictoken-id: uint, sender: principal, recipient: principal
mintpublicrecipient: principal, badge-type: uint
get-badge-typeread-onlytoken-id: uint
get-mint-timeread-onlytoken-id: uint
get-badge-nameread-onlybadge-type: uint
get-authorized-minterread-only
set-base-uripublicnew-uri: (string-ascii 210