Source Code

;; Crowdfund NFT Reward Contract
;; Backers receive NFT rewards based on contribution tiers
;; SIP-009 style NFT for crowdfund participants

(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u300))
(define-constant err-not-authorized (err u301))
(define-constant err-not-found (err u302))
(define-constant err-already-minted (err u303))
(define-constant err-invalid-tier (err u304))

(define-constant TIER-BRONZE-MIN u1000000)     ;; 1 STX
(define-constant TIER-SILVER-MIN u10000000)    ;; 10 STX
(define-constant TIER-GOLD-MIN u100000000)     ;; 100 STX
(define-constant TIER-DIAMOND-MIN u1000000000) ;; 1000 STX

(define-data-var token-id-nonce uint u0)
(define-data-var total-minted uint u0)

(define-map authorized-minters principal bool)

;; NFT ownership
(define-map nft-owners uint principal)
(define-map owner-tokens principal (list 20 uint))

;; NFT metadata
(define-map nft-metadata uint
  {
    campaign-id: uint,
    backer: principal,
    tier: uint,         ;; 1=bronze, 2=silver, 3=gold, 4=diamond
    contribution: uint,
    minted-at: uint,
    transferable: bool
  }
)

;; Prevent double-mint per campaign
(define-map campaign-minted
  { campaign-id: uint, backer: principal }
  uint  ;; token-id
)

;; Read-only
(define-read-only (get-owner (token-id uint))
  (map-get? nft-owners token-id)
)

(define-read-only (get-metadata (token-id uint))
  (map-get? nft-metadata token-id)
)

(define-read-only (get-tier-for-contribution (amount uint))
  (if (>= amount TIER-DIAMOND-MIN) u4
    (if (>= amount TIER-GOLD-MIN) u3
      (if (>= amount TIER-SILVER-MIN) u2 u1)
    )
  )
)

(define-read-only (get-tier-label (tier uint))
  (if (is-eq tier u4) "DIAMOND"
    (if (is-eq tier u3) "GOLD"
      (if (is-eq tier u2) "SILVER" "BRONZE")))
)

(define-read-only (has-nft (campaign-id uint) (backer principal))
  (is-some (map-get? campaign-minted { campaign-id: campaign-id, backer: backer }))
)

(define-read-only (get-tokens-for-owner (owner principal))
  (default-to (list) (map-get? owner-tokens owner))
)

(define-read-only (get-token-count)
  (var-get total-minted)
)

;; Public functions
(define-public (add-minter (minter principal))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (map-set authorized-minters minter true)
    (ok minter)
  )
)

(define-public (mint-reward
    (campaign-id uint)
    (backer principal)
    (contribution uint))
  (let (
    (token-id (var-get token-id-nonce))
    (tier (get-tier-for-contribution contribution))
  )
    (asserts! (or (is-eq tx-sender contract-owner)
                  (default-to false (map-get? authorized-minters tx-sender))) err-not-authorized)
    (asserts! (not (has-nft campaign-id backer)) err-already-minted)
    (asserts! (>= contribution TIER-BRONZE-MIN) err-invalid-tier)

    (map-set nft-owners token-id backer)
    (map-set nft-metadata token-id {
      campaign-id: campaign-id,
      backer: backer,
      tier: tier,
      contribution: contribution,
      minted-at: stacks-block-height,
      transferable: true
    })

    (map-set campaign-minted { campaign-id: campaign-id, backer: backer } token-id)

    (let ((current-tokens (get-tokens-for-owner backer)))
      (map-set owner-tokens backer (unwrap-panic (as-max-len? (append current-tokens token-id) u20)))
    )

    (var-set token-id-nonce (+ token-id u1))
    (var-set total-minted (+ (var-get total-minted) u1))

    (ok { token-id: token-id, tier: (get-tier-label tier), backer: backer })
  )
)

(define-public (transfer-nft (token-id uint) (recipient principal))
  (match (map-get? nft-metadata token-id)
    metadata
    (begin
      (asserts! (is-eq (some tx-sender) (map-get? nft-owners token-id)) err-not-authorized)
      (asserts! (get transferable metadata) err-not-authorized)
      (map-set nft-owners token-id recipient)
      (ok { token-id: token-id, new-owner: recipient })
    )
    err-not-found
  )
)

(define-public (lock-nft (token-id uint))
  (match (map-get? nft-metadata token-id)
    metadata
    (begin
      (asserts! (is-eq (some tx-sender) (map-get? nft-owners token-id)) err-not-authorized)
      (map-set nft-metadata token-id (merge metadata { transferable: false }))
      (ok token-id)
    )
    err-not-found
  )
)

Functions (11)

FunctionAccessArgs
get-ownerread-onlytoken-id: uint
get-metadataread-onlytoken-id: uint
get-tier-for-contributionread-onlyamount: uint
get-tier-labelread-onlytier: uint
has-nftread-onlycampaign-id: uint, backer: principal
get-tokens-for-ownerread-onlyowner: principal
get-token-countread-only
add-minterpublicminter: principal
mint-rewardpubliccampaign-id: uint, backer: principal, contribution: uint
transfer-nftpublictoken-id: uint, recipient: principal
lock-nftpublictoken-id: uint