Source Code

;; Reputation System Contract (Clarity 4)
;; Advanced reputation calculation and badge management
;; Uses stacks-block-time for time-weighted reputation scoring

;; Error Codes
(define-constant ERR_UNAUTHORIZED (err u4001))
(define-constant ERR_NOT_FOUND (err u4002))
(define-constant ERR_INVALID_PARAMS (err u4003))
(define-constant ERR_INSUFFICIENT_REPUTATION (err u4004))
(define-constant ERR_ALREADY_ENDORSED (err u4005))
(define-constant ERR_SELF_ENDORSE (err u4006))
(define-constant ERR_BADGE_EXISTS (err u4007))

;; Configuration
(define-constant CONTRACT_OWNER tx-sender)
(define-constant BASE_REPUTATION_SCORE u100)
(define-constant COMPLETION_BONUS u10)
(define-constant RATING_MULTIPLIER u5)
(define-constant ENDORSEMENT_VALUE u3)
(define-constant MAX_ENDORSEMENTS_PER_USER u50)
(define-constant DECAY_PERIOD u2592000) ;; 30 days in seconds

;; Badge Tiers
(define-constant BADGE_BRONZE "bronze")
(define-constant BADGE_SILVER "silver")
(define-constant BADGE_GOLD "gold")
(define-constant BADGE_PLATINUM "platinum")

;; Reputation Tiers
(define-constant TIER_NEWCOMER u0)
(define-constant TIER_CONTRIBUTOR u250)
(define-constant TIER_TRUSTED u500)
(define-constant TIER_EXPERT u1000)
(define-constant TIER_MASTER u2500)

;; Data Maps
(define-map reputation-scores
    principal
    {
        base-score: uint,
        completion-bonus: uint,
        rating-score: uint,
        endorsement-score: uint,
        total-score: uint,
        last-updated: uint,
        tier: uint
    })

(define-map user-endorsements
    {endorser: principal, endorsed: principal}
    {
        category: (string-ascii 20),
        comment: (string-ascii 100),
        endorsed-at: uint,
        value: uint
    })

(define-map endorsement-count principal uint)

(define-map skill-badges
    {user: principal, skill-category: (string-ascii 20)}
    {
        badge-tier: (string-ascii 20),
        earned-at: uint,
        services-completed: uint,
        average-rating: uint
    })

(define-map reputation-history
    {user: principal, timestamp: uint}
    {
        score: uint,
        action: (string-ascii 50),
        change: int
    })

;; Data vars
(define-data-var total-endorsements uint u0)
(define-data-var total-badges-awarded uint u0)
(define-data-var reputation-enabled bool true)

;; Traits
(define-trait time-bank-core-trait
    ((update-reputation (principal int) (response bool uint))))

;; Events using Clarity 4 stacks-block-time
(define-private (emit-reputation-updated (user principal) (new-score uint) (change int))
    (print {
        event: "reputation-updated",
        user: user,
        new-score: new-score,
        change: change,
        timestamp: stacks-block-time
    }))

(define-private (emit-endorsement-given (endorser principal) (endorsed principal) (category (string-ascii 20)))
    (print {
        event: "endorsement-given",
        endorser: endorser,
        endorsed: endorsed,
        category: category,
        timestamp: stacks-block-time
    }))

(define-private (emit-badge-awarded (user principal) (category (string-ascii 20)) (tier (string-ascii 20)))
    (print {
        event: "badge-awarded",
        user: user,
        category: category,
        tier: tier,
        timestamp: stacks-block-time
    }))

;; Helper Functions
(define-private (calculate-total-score (base uint) (completion uint) (rating uint) (endorsement uint))
    (+ base (+ completion (+ rating endorsement))))

(define-private (get-tier-from-score (score uint))
    (if (>= score TIER_MASTER)
        TIER_MASTER
        (if (>= score TIER_EXPERT)
            TIER_EXPERT
            (if (>= score TIER_TRUSTED)
                TIER_TRUSTED
                (if (>= score TIER_CONTRIBUTOR)
                    TIER_CONTRIBUTOR
                    TIER_NEWCOMER)))))

(define-private (apply-time-decay (score uint) (last-update uint))
    (let (
        (time-elapsed (- stacks-block-time last-update))
        (decay-periods (/ time-elapsed DECAY_PERIOD))
        (decay-amount (* decay-periods u5))
    )
        (if (> decay-amount score)
            u0
            (- score decay-amount))))

;; Public Functions
(define-public (initialize-reputation (user principal))
    (begin
        (asserts! (var-get reputation-enabled) ERR_UNAUTHORIZED)
        (asserts! (is-none (map-get? reputation-scores user)) ERR_BADGE_EXISTS)

        (map-set reputation-scores user {
            base-score: BASE_REPUTATION_SCORE,
            completion-bonus: u0,
            rating-score: u0,
            endorsement-score: u0,
            total-score: BASE_REPUTATION_SCORE,
            last-updated: stacks-block-time,
            tier: TIER_NEWCOMER
        })

        (emit-reputation-updated user BASE_REPUTATION_SCORE 0)
        (ok true)))

(define-public (record-exchange-completion
    (user principal)
    (rating uint)
    (core-contract <time-bank-core-trait>))
    (let (
        (rep-data (unwrap! (map-get? reputation-scores user) ERR_NOT_FOUND))
        (rating-bonus (* rating RATING_MULTIPLIER))
        (new-completion-bonus (+ (get completion-bonus rep-data) COMPLETION_BONUS))
        (new-rating-score (+ (get rating-score rep-data) rating-bonus))
        (new-total (calculate-total-score
            (get base-score rep-data)
            new-completion-bonus
            new-rating-score
            (get endorsement-score rep-data)))
        (new-tier (get-tier-from-score new-total))
        (score-change (to-int (- new-total (get total-score rep-data))))
    )
        (asserts! (var-get reputation-enabled) ERR_UNAUTHORIZED)
        (asserts! (<= rating u5) ERR_INVALID_PARAMS)

        (map-set reputation-scores user {
            base-score: (get base-score rep-data),
            completion-bonus: new-completion-bonus,
            rating-score: new-rating-score,
            endorsement-score: (get endorsement-score rep-data),
            total-score: new-total,
            last-updated: stacks-block-time,
            tier: new-tier
        })

        (try! (contract-call? core-contract update-reputation user score-change))

        (map-set reputation-history {user: user, timestamp: stacks-block-time} {
            score: new-total,
            action: "exchange-completion",
            change: score-change
        })

        (emit-reputation-updated user new-total score-change)
        (ok new-total)))

(define-public (endorse-user
    (endorsed principal)
    (category (string-ascii 20))
    (comment (string-ascii 100)))
    (let (
        (rep-data (unwrap! (map-get? reputation-scores endorsed) ERR_NOT_FOUND))
        (endorser-count (default-to u0 (map-get? endorsement-count endorsed)))
    )
        (asserts! (var-get reputation-enabled) ERR_UNAUTHORIZED)
        (asserts! (not (is-eq tx-sender endorsed)) ERR_SELF_ENDORSE)
        (asserts! (is-none (map-get? user-endorsements {endorser: tx-sender, endorsed: endorsed})) ERR_ALREADY_ENDORSED)
        (asserts! (< endorser-count MAX_ENDORSEMENTS_PER_USER) ERR_INVALID_PARAMS)

        (map-set user-endorsements {endorser: tx-sender, endorsed: endorsed} {
            category: category,
            comment: comment,
            endorsed-at: stacks-block-time,
            value: ENDORSEMENT_VALUE
        })

        (map-set endorsement-count endorsed (+ endorser-count u1))

        (let (
            (new-endorsement-score (+ (get endorsement-score rep-data) ENDORSEMENT_VALUE))
            (new-total (calculate-total-score
                (get base-score rep-data)
                (get completion-bonus rep-data)
                (get rating-score rep-data)
                new-endorsement-score))
            (new-tier (get-tier-from-score new-total))
        )
            (map-set reputation-scores endorsed (merge rep-data {
                endorsement-score: new-endorsement-score,
                total-score: new-total,
                tier: new-tier,
                last-updated: stacks-block-time
            }))

            (var-set total-endorsements (+ (var-get total-endorsements) u1))
            (emit-endorsement-given tx-sender endorsed category)
            (ok true))))

(define-public (award-skill-badge
    (user principal)
    (skill-category (string-ascii 20))
    (services-completed uint)
    (average-rating uint))
    (let (
        (badge-tier (if (>= services-completed u50)
            BADGE_PLATINUM
            (if (>= services-completed u25)
                BADGE_GOLD
                (if (>= services-completed u10)
                    BADGE_SILVER
                    BADGE_BRONZE))))
    )
        (asserts! (var-get reputation-enabled) ERR_UNAUTHORIZED)
        (asserts! (> services-completed u0) ERR_INVALID_PARAMS)
        (asserts! (<= average-rating u5) ERR_INVALID_PARAMS)

        (map-set skill-badges {user: user, skill-category: skill-category} {
            badge-tier: badge-tier,
            earned-at: stacks-block-time,
            services-completed: services-completed,
            average-rating: average-rating
        })

        (var-set total-badges-awarded (+ (var-get total-badges-awarded) u1))
        (emit-badge-awarded user skill-category badge-tier)
        (ok badge-tier)))

(define-public (apply-reputation-decay (user principal))
    (let (
        (rep-data (unwrap! (map-get? reputation-scores user) ERR_NOT_FOUND))
        (decayed-score (apply-time-decay (get total-score rep-data) (get last-updated rep-data)))
        (new-tier (get-tier-from-score decayed-score))
    )
        (asserts! (> (- stacks-block-time (get last-updated rep-data)) DECAY_PERIOD) ERR_INVALID_PARAMS)

        (map-set reputation-scores user (merge rep-data {
            total-score: decayed-score,
            tier: new-tier,
            last-updated: stacks-block-time
        }))

        (print {event: "reputation-decay-applied", user: user, new-score: decayed-score, timestamp: stacks-block-time})
        (ok decayed-score)))

(define-public (toggle-reputation-system)
    (begin
        (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
        (var-set reputation-enabled (not (var-get reputation-enabled)))
        (print {event: "reputation-system-toggled", enabled: (var-get reputation-enabled)})
        (ok true)))

;; Read-Only Functions
(define-read-only (get-reputation-score (user principal))
    (map-get? reputation-scores user))

(define-read-only (get-endorsement (endorser principal) (endorsed principal))
    (map-get? user-endorsements {endorser: endorser, endorsed: endorsed}))

(define-read-only (get-endorsement-count (user principal))
    (ok (default-to u0 (map-get? endorsement-count user))))

(define-read-only (get-skill-badge (user principal) (category (string-ascii 20)))
    (map-get? skill-badges {user: user, skill-category: category}))

(define-read-only (get-reputation-tier (user principal))
    (match (map-get? reputation-scores user)
        rep-data (ok (get tier rep-data))
        ERR_NOT_FOUND))

(define-read-only (get-total-reputation (user principal))
    (match (map-get? reputation-scores user)
        rep-data (ok (get total-score rep-data))
        (ok u0)))

(define-read-only (get-reputation-stats)
    (ok {
        total-endorsements: (var-get total-endorsements),
        total-badges-awarded: (var-get total-badges-awarded),
        reputation-enabled: (var-get reputation-enabled),
        base-reputation-score: BASE_REPUTATION_SCORE,
        completion-bonus: COMPLETION_BONUS,
        endorsement-value: ENDORSEMENT_VALUE
    }))

Functions (19)

FunctionAccessArgs
emit-reputation-updatedprivateuser: principal, new-score: uint, change: int
emit-endorsement-givenprivateendorser: principal, endorsed: principal, category: (string-ascii 20
emit-badge-awardedprivateuser: principal, category: (string-ascii 20
calculate-total-scoreprivatebase: uint, completion: uint, rating: uint, endorsement: uint
get-tier-from-scoreprivatescore: uint
apply-time-decayprivatescore: uint, last-update: uint
initialize-reputationpublicuser: principal
record-exchange-completionpublicuser: principal, rating: uint, core-contract: <time-bank-core-trait>
endorse-userpublicendorsed: principal, category: (string-ascii 20
award-skill-badgepublicuser: principal, skill-category: (string-ascii 20
apply-reputation-decaypublicuser: principal
toggle-reputation-systempublic
get-reputation-scoreread-onlyuser: principal
get-endorsementread-onlyendorser: principal, endorsed: principal
get-endorsement-countread-onlyuser: principal
get-skill-badgeread-onlyuser: principal, category: (string-ascii 20
get-reputation-tierread-onlyuser: principal
get-total-reputationread-onlyuser: principal
get-reputation-statsread-only