;; 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")))
)