Source Code

;; membership-nft.clar
;; NFT-based DAO membership with tiers and revocable memberships
;; Clarity 4 / Epoch 3.3

;; -----------------------------------------------
;; Constants
;; -----------------------------------------------
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-AUTHORIZED (err u500))
(define-constant ERR-ALREADY-MEMBER (err u501))
(define-constant ERR-NOT-MEMBER (err u502))
(define-constant ERR-INVALID-TIER (err u503))
(define-constant ERR-MEMBERSHIP-REVOKED (err u504))
(define-constant ERR-MINT-FAILED (err u505))

(define-constant TIER-CONTRIBUTOR u1)
(define-constant TIER-CORE u2)
(define-constant TIER-FOUNDING u3)

;; -----------------------------------------------
;; NFT Definition
;; -----------------------------------------------
(define-non-fungible-token dao-membership uint)

;; -----------------------------------------------
;; Data Variables
;; -----------------------------------------------
(define-data-var token-nonce uint u0)
(define-data-var total-members uint u0)
(define-data-var total-revoked uint u0)
(define-data-var minting-enabled bool true)

;; -----------------------------------------------
;; Data Maps
;; -----------------------------------------------
(define-map membership-data
  uint
  {
    owner: principal,
    tier: uint,
    joined-at: uint,
    revoked: bool
  }
)

(define-map member-tokens
  principal
  uint
)

(define-map tier-counts
  uint
  uint
)

(define-map membership-admins
  principal
  bool
)

;; -----------------------------------------------
;; Initialize
;; -----------------------------------------------
(map-set membership-admins CONTRACT-OWNER true)
(map-set tier-counts TIER-CONTRIBUTOR u0)
(map-set tier-counts TIER-CORE u0)
(map-set tier-counts TIER-FOUNDING u0)

;; -----------------------------------------------
;; Private Functions
;; -----------------------------------------------
(define-private (is-membership-admin (who principal))
  (default-to false (map-get? membership-admins who))
)

(define-private (is-valid-tier (tier uint))
  (or (is-eq tier TIER-CONTRIBUTOR)
      (is-eq tier TIER-CORE)
      (is-eq tier TIER-FOUNDING))
)

(define-private (increment-tier-count (tier uint))
  (let ((current (default-to u0 (map-get? tier-counts tier))))
    (map-set tier-counts tier (+ current u1))
  )
)

(define-private (decrement-tier-count (tier uint))
  (let ((current (default-to u0 (map-get? tier-counts tier))))
    (if (> current u0)
      (map-set tier-counts tier (- current u1))
      false
    )
  )
)

;; -----------------------------------------------
;; Public Functions
;; -----------------------------------------------

;; Add a membership admin
(define-public (add-admin (new-admin principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (map-set membership-admins new-admin true)
    (ok true)
  )
)

;; Mint a membership NFT for a new member
(define-public (mint-membership (recipient principal) (tier uint))
  (let ((token-id (var-get token-nonce)))
    (asserts! (is-membership-admin tx-sender) ERR-NOT-AUTHORIZED)
    (asserts! (var-get minting-enabled) ERR-NOT-AUTHORIZED)
    (asserts! (is-valid-tier tier) ERR-INVALID-TIER)
    (asserts! (is-none (map-get? member-tokens recipient))
              ERR-ALREADY-MEMBER)
    (unwrap! (nft-mint? dao-membership token-id recipient) ERR-MINT-FAILED)
    (map-set membership-data token-id
      {
        owner: recipient,
        tier: tier,
        joined-at: tenure-height,
        revoked: false
      })
    (map-set member-tokens recipient token-id)
    (increment-tier-count tier)
    (var-set token-nonce (+ token-id u1))
    (var-set total-members (+ (var-get total-members) u1))
    (ok token-id)
  )
)

;; Upgrade a member tier
(define-public (upgrade-tier (member principal) (new-tier uint))
  (let ((token-id (unwrap! (map-get? member-tokens member) ERR-NOT-MEMBER)))
    (let ((data (unwrap! (map-get? membership-data token-id) ERR-NOT-MEMBER)))
      (asserts! (is-membership-admin tx-sender) ERR-NOT-AUTHORIZED)
      (asserts! (is-valid-tier new-tier) ERR-INVALID-TIER)
      (asserts! (not (get revoked data)) ERR-MEMBERSHIP-REVOKED)
      (decrement-tier-count (get tier data))
      (increment-tier-count new-tier)
      (map-set membership-data token-id
        (merge data { tier: new-tier }))
      (ok true)
    )
  )
)

;; Revoke a membership
(define-public (revoke-membership (member principal))
  (let ((token-id (unwrap! (map-get? member-tokens member) ERR-NOT-MEMBER)))
    (let ((data (unwrap! (map-get? membership-data token-id) ERR-NOT-MEMBER)))
      (asserts! (is-membership-admin tx-sender) ERR-NOT-AUTHORIZED)
      (asserts! (not (get revoked data)) ERR-MEMBERSHIP-REVOKED)
      (map-set membership-data token-id
        (merge data { revoked: true }))
      (decrement-tier-count (get tier data))
      (var-set total-members (- (var-get total-members) u1))
      (var-set total-revoked (+ (var-get total-revoked) u1))
      (ok true)
    )
  )
)

;; Toggle minting on/off
(define-public (set-minting-enabled (enabled bool))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (var-set minting-enabled enabled)
    (ok true)
  )
)

;; -----------------------------------------------
;; Read-Only Functions
;; -----------------------------------------------
(define-read-only (get-membership (token-id uint))
  (map-get? membership-data token-id)
)

(define-read-only (get-member-token (member principal))
  (map-get? member-tokens member)
)

(define-read-only (is-active-member (member principal))
  (match (map-get? member-tokens member)
    token-id (match (map-get? membership-data token-id)
      data (not (get revoked data))
      false
    )
    false
  )
)

(define-read-only (get-member-tier (member principal))
  (match (map-get? member-tokens member)
    token-id (match (map-get? membership-data token-id)
      data (if (get revoked data) u0 (get tier data))
      u0
    )
    u0
  )
)

(define-read-only (get-membership-stats)
  {
    total-members: (var-get total-members),
    total-revoked: (var-get total-revoked),
    total-minted: (var-get token-nonce),
    minting-enabled: (var-get minting-enabled),
    contributors: (default-to u0 (map-get? tier-counts TIER-CONTRIBUTOR)),
    core-members: (default-to u0 (map-get? tier-counts TIER-CORE)),
    founding-members: (default-to u0 (map-get? tier-counts TIER-FOUNDING))
  }
)

(define-read-only (get-tier-name (tier uint))
  (if (is-eq tier TIER-CONTRIBUTOR) "contributor"
    (if (is-eq tier TIER-CORE) "core"
      (if (is-eq tier TIER-FOUNDING) "founding"
        "unknown")))
)

Functions (15)

FunctionAccessArgs
is-membership-adminprivatewho: principal
is-valid-tierprivatetier: uint
increment-tier-countprivatetier: uint
decrement-tier-countprivatetier: uint
add-adminpublicnew-admin: principal
mint-membershippublicrecipient: principal, tier: uint
upgrade-tierpublicmember: principal, new-tier: uint
revoke-membershippublicmember: principal
set-minting-enabledpublicenabled: bool
get-membershipread-onlytoken-id: uint
get-member-tokenread-onlymember: principal
is-active-memberread-onlymember: principal
get-member-tierread-onlymember: principal
get-membership-statsread-only
get-tier-nameread-onlytier: uint