Source Code

;; Bounty System Contract v5.1
;; Create, fund, and claim bounties for DAO tasks
;; Clarity 4
;;
;; FEATURES:
;; - Create bounties with token rewards
;; - Multiple submissions per bounty
;; - Reviewer/judge system
;; - Milestone-based bounties
;; - Skill tags and categories
;; - Reputation tracking for hunters
;; - Dispute resolution mechanism

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

(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-AUTHORIZED (err u500))
(define-constant ERR-BOUNTY-NOT-FOUND (err u501))
(define-constant ERR-BOUNTY-CLOSED (err u502))
(define-constant ERR-ALREADY-SUBMITTED (err u503))
(define-constant ERR-SUBMISSION-NOT-FOUND (err u504))
(define-constant ERR-INVALID-AMOUNT (err u505))
(define-constant ERR-NOT-REVIEWER (err u506))
(define-constant ERR-ALREADY-CLAIMED (err u507))
(define-constant ERR-DISPUTE-ACTIVE (err u508))
(define-constant ERR-NO-DISPUTE (err u509))
(define-constant ERR-BOUNTY-EXPIRED (err u510))
(define-constant ERR-INSUFFICIENT-FUNDS (err u511))
(define-constant ERR-INVALID-STATUS (err u512))
(define-constant ERR-COOLDOWN (err u513))

;; Bounty statuses
(define-constant STATUS-OPEN u1)
(define-constant STATUS-IN-REVIEW u2)
(define-constant STATUS-COMPLETED u3)
(define-constant STATUS-CANCELLED u4)
(define-constant STATUS-DISPUTED u5)

;; Submission statuses
(define-constant SUBMISSION-PENDING u1)
(define-constant SUBMISSION-APPROVED u2)
(define-constant SUBMISSION-REJECTED u3)
(define-constant SUBMISSION-DISPUTED u4)

;; Categories
(define-constant CAT-DEVELOPMENT u1)
(define-constant CAT-DESIGN u2)
(define-constant CAT-CONTENT u3)
(define-constant CAT-MARKETING u4)
(define-constant CAT-RESEARCH u5)
(define-constant CAT-COMMUNITY u6)
(define-constant CAT-SECURITY u7)
(define-constant CAT-OTHER u8)

;; Difficulty levels
(define-constant DIFFICULTY-EASY u1)
(define-constant DIFFICULTY-MEDIUM u2)
(define-constant DIFFICULTY-HARD u3)
(define-constant DIFFICULTY-EXPERT u4)

;; Dispute resolution period
(define-constant DISPUTE-PERIOD u1008)  ;; ~7 days

;; Creator fee
(define-constant PLATFORM-FEE u250)  ;; 2.5%

;; =====================
;; DATA VARIABLES
;; =====================

(define-data-var admin principal tx-sender)
(define-data-var treasury-address principal tx-sender)
(define-data-var bounty-counter uint u0)
(define-data-var submission-counter uint u0)
(define-data-var total-bounties-paid uint u0)
(define-data-var paused bool false)

;; =====================
;; DATA MAPS
;; =====================

;; Bounty listings
(define-map bounties
  uint  ;; bounty-id
  {
    creator: principal,
    title: (string-ascii 100),
    description: (string-utf8 1000),
    category: uint,
    difficulty: uint,
    reward-amount: uint,
    deadline: uint,
    status: uint,
    submissions-count: uint,
    winner: (optional principal),
    created-at: uint,
    max-submissions: uint,
    requires-review: bool
  }
)

;; Bounty funding (escrowed tokens)
(define-map bounty-escrow uint uint)

;; Submissions
(define-map submissions
  uint  ;; submission-id
  {
    bounty-id: uint,
    hunter: principal,
    submission-url: (string-utf8 500),
    description: (string-utf8 500),
    submitted-at: uint,
    status: uint,
    reviewer: (optional principal),
    review-notes: (optional (string-utf8 500)),
    reviewed-at: uint
  }
)

;; Track submissions per bounty
(define-map bounty-submissions uint (list 50 uint))

;; Track submissions per hunter
(define-map hunter-submissions principal (list 100 uint))

;; Authorized reviewers
(define-map reviewers principal bool)

;; Hunter reputation
(define-map hunter-reputation
  principal
  {
    bounties-completed: uint,
    bounties-submitted: uint,
    total-earned: uint,
    reputation-score: uint,
    disputes-won: uint,
    disputes-lost: uint
  }
)

;; Disputes
(define-map disputes
  uint  ;; submission-id
  {
    disputer: principal,
    reason: (string-utf8 500),
    created-at: uint,
    resolved: bool,
    resolution: (optional bool),
    resolver: (optional principal)
  }
)

;; Hunter skill tags
(define-map hunter-skills
  principal
  (list 10 uint)  ;; category IDs they've completed bounties in
)

;; =====================
;; PRIVATE FUNCTIONS
;; =====================

;; Update hunter reputation
(define-private (update-hunter-rep (hunter principal) (completed bool) (amount uint))
  (let (
    (current (default-to 
      { bounties-completed: u0, bounties-submitted: u0, total-earned: u0, reputation-score: u100, disputes-won: u0, disputes-lost: u0 }
      (map-get? hunter-reputation hunter)))
  )
    (if completed
      (map-set hunter-reputation hunter {
        bounties-completed: (+ (get bounties-completed current) u1),
        bounties-submitted: (get bounties-submitted current),
        total-earned: (+ (get total-earned current) amount),
        reputation-score: (+ (get reputation-score current) u10),
        disputes-won: (get disputes-won current),
        disputes-lost: (get disputes-lost current)
      })
      (map-set hunter-reputation hunter {
        bounties-completed: (get bounties-completed current),
        bounties-submitted: (+ (get bounties-submitted current) u1),
        total-earned: (get total-earned current),
        reputation-score: (get reputation-score current),
        disputes-won: (get disputes-won current),
        disputes-lost: (get disputes-lost current)
      })
    )
  )
)

;; =====================
;; PUBLIC FUNCTIONS
;; =====================

;; Create a new bounty
(define-public (create-bounty
  (title (string-ascii 100))
  (description (string-utf8 1000))
  (category uint)
  (difficulty uint)
  (reward-amount uint)
  (deadline uint)
  (max-submissions uint)
  (requires-review bool))
  (let (
    (bounty-id (+ (var-get bounty-counter) u1))
    (creator tx-sender)
  )
    (asserts! (not (var-get paused)) ERR-NOT-AUTHORIZED)
    (asserts! (> reward-amount u0) ERR-INVALID-AMOUNT)
    (asserts! (> deadline stacks-block-height) ERR-BOUNTY-EXPIRED)
    (asserts! (and (>= category CAT-DEVELOPMENT) (<= category CAT-OTHER)) ERR-INVALID-STATUS)
    
    ;; Transfer tokens to escrow
    (try! (contract-call? .dao-token-v5-1 transfer reward-amount creator (as-contract tx-sender) none))
    
    ;; Create bounty
    (map-set bounties bounty-id {
      creator: creator,
      title: title,
      description: description,
      category: category,
      difficulty: difficulty,
      reward-amount: reward-amount,
      deadline: deadline,
      status: STATUS-OPEN,
      submissions-count: u0,
      winner: none,
      created-at: stacks-block-height,
      max-submissions: max-submissions,
      requires-review: requires-review
    })
    
    ;; Track escrow
    (map-set bounty-escrow bounty-id reward-amount)
    
    (var-set bounty-counter bounty-id)
    
    (print { 
      event: "bounty-created", 
      version: "5.1",
      bounty-id: bounty-id, 
      creator: creator, 
      title: title,
      reward: reward-amount,
      category: category,
      deadline: deadline
    })
    (ok bounty-id)
  )
)

;; Submit work for a bounty
(define-public (submit-work
  (bounty-id uint)
  (submission-url (string-utf8 500))
  (description (string-utf8 500)))
  (let (
    (bounty (unwrap! (map-get? bounties bounty-id) ERR-BOUNTY-NOT-FOUND))
    (submission-id (+ (var-get submission-counter) u1))
    (hunter tx-sender)
    (current-submissions (default-to (list) (map-get? bounty-submissions bounty-id)))
  )
    (asserts! (is-eq (get status bounty) STATUS-OPEN) ERR-BOUNTY-CLOSED)
    (asserts! (<= stacks-block-height (get deadline bounty)) ERR-BOUNTY-EXPIRED)
    (asserts! (< (get submissions-count bounty) (get max-submissions bounty)) ERR-BOUNTY-CLOSED)
    
    ;; Create submission
    (map-set submissions submission-id {
      bounty-id: bounty-id,
      hunter: hunter,
      submission-url: submission-url,
      description: description,
      submitted-at: stacks-block-height,
      status: SUBMISSION-PENDING,
      reviewer: none,
      review-notes: none,
      reviewed-at: u0
    })
    
    ;; Update bounty submissions count
    (map-set bounties bounty-id 
      (merge bounty { submissions-count: (+ (get submissions-count bounty) u1) }))
    
    ;; Track submission
    (map-set bounty-submissions bounty-id (unwrap-panic (as-max-len? (append current-submissions submission-id) u50)))
    
    ;; Update hunter stats
    (update-hunter-rep hunter false u0)
    
    (var-set submission-counter submission-id)
    
    (print { 
      event: "submission-created", 
      version: "5.1",
      submission-id: submission-id, 
      bounty-id: bounty-id, 
      hunter: hunter 
    })
    (ok submission-id)
  )
)

;; Approve submission (by creator or reviewer)
(define-public (approve-submission (submission-id uint))
  (let (
    (submission (unwrap! (map-get? submissions submission-id) ERR-SUBMISSION-NOT-FOUND))
    (bounty (unwrap! (map-get? bounties (get bounty-id submission)) ERR-BOUNTY-NOT-FOUND))
    (reward (get reward-amount bounty))
    (platform-cut (/ (* reward PLATFORM-FEE) u10000))
    (hunter-reward (- reward platform-cut))
    (hunter (get hunter submission))
  )
    ;; Check authorization
    (asserts! (or 
      (is-eq tx-sender (get creator bounty))
      (default-to false (map-get? reviewers tx-sender))
    ) ERR-NOT-AUTHORIZED)
    (asserts! (is-eq (get status submission) SUBMISSION-PENDING) ERR-INVALID-STATUS)
    (asserts! (is-eq (get status bounty) STATUS-OPEN) ERR-BOUNTY-CLOSED)
    
    ;; Update submission
    (map-set submissions submission-id 
      (merge submission { 
        status: SUBMISSION-APPROVED,
        reviewer: (some tx-sender),
        reviewed-at: stacks-block-height
      }))
    
    ;; Update bounty
    (map-set bounties (get bounty-id submission) 
      (merge bounty { 
        status: STATUS-COMPLETED,
        winner: (some hunter)
      }))
    
    ;; Pay hunter
    (try! (as-contract (contract-call? .dao-token-v5-1 transfer hunter-reward tx-sender hunter none)))
    
    ;; Pay platform fee
    (try! (as-contract (contract-call? .dao-token-v5-1 transfer platform-cut tx-sender (var-get treasury-address) none)))
    
    ;; Update escrow
    (map-set bounty-escrow (get bounty-id submission) u0)
    
    ;; Update hunter reputation
    (update-hunter-rep hunter true hunter-reward)
    
    (var-set total-bounties-paid (+ (var-get total-bounties-paid) hunter-reward))
    
    (print { 
      event: "submission-approved", 
      version: "5.1",
      submission-id: submission-id, 
      bounty-id: (get bounty-id submission),
      hunter: hunter,
      reward: hunter-reward
    })
    (ok hunter-reward)
  )
)

;; Reject submission
(define-public (reject-submission (submission-id uint) (reason (string-utf8 500)))
  (let (
    (submission (unwrap! (map-get? submissions submission-id) ERR-SUBMISSION-NOT-FOUND))
    (bounty (unwrap! (map-get? bounties (get bounty-id submission)) ERR-BOUNTY-NOT-FOUND))
  )
    (asserts! (or 
      (is-eq tx-sender (get creator bounty))
      (default-to false (map-get? reviewers tx-sender))
    ) ERR-NOT-AUTHORIZED)
    (asserts! (is-eq (get status submission) SUBMISSION-PENDING) ERR-INVALID-STATUS)
    
    (map-set submissions submission-id 
      (merge submission { 
        status: SUBMISSION-REJECTED,
        reviewer: (some tx-sender),
        review-notes: (some reason),
        reviewed-at: stacks-block-height
      }))
    
    (print { event: "submission-rejected", version: "5.1", submission-id: submission-id })
    (ok true)
  )
)

;; Dispute a rejection
(define-public (dispute-submission (submission-id uint) (reason (string-utf8 500)))
  (let (
    (submission (unwrap! (map-get? submissions submission-id) ERR-SUBMISSION-NOT-FOUND))
  )
    (asserts! (is-eq tx-sender (get hunter submission)) ERR-NOT-AUTHORIZED)
    (asserts! (is-eq (get status submission) SUBMISSION-REJECTED) ERR-INVALID-STATUS)
    (asserts! (is-none (map-get? disputes submission-id)) ERR-DISPUTE-ACTIVE)
    
    ;; Create dispute
    (map-set disputes submission-id {
      disputer: tx-sender,
      reason: reason,
      created-at: stacks-block-height,
      resolved: false,
      resolution: none,
      resolver: none
    })
    
    ;; Update submission status
    (map-set submissions submission-id (merge submission { status: SUBMISSION-DISPUTED }))
    
    (print { event: "dispute-created", version: "5.1", submission-id: submission-id })
    (ok true)
  )
)

;; Resolve dispute (admin only)
(define-public (resolve-dispute (submission-id uint) (in-favor-of-hunter bool))
  (let (
    (dispute (unwrap! (map-get? disputes submission-id) ERR-NO-DISPUTE))
    (submission (unwrap! (map-get? submissions submission-id) ERR-SUBMISSION-NOT-FOUND))
    (bounty (unwrap! (map-get? bounties (get bounty-id submission)) ERR-BOUNTY-NOT-FOUND))
    (hunter (get hunter submission))
  )
    (asserts! (is-eq tx-sender (var-get admin)) ERR-NOT-AUTHORIZED)
    (asserts! (not (get resolved dispute)) ERR-DISPUTE-ACTIVE)
    
    ;; Update dispute
    (map-set disputes submission-id 
      (merge dispute { 
        resolved: true, 
        resolution: (some in-favor-of-hunter),
        resolver: (some tx-sender)
      }))
    
    (if in-favor-of-hunter
      (begin
        ;; Pay the hunter
        (let (
          (reward (get reward-amount bounty))
          (platform-cut (/ (* reward PLATFORM-FEE) u10000))
          (hunter-reward (- reward platform-cut))
        )
          (try! (as-contract (contract-call? .dao-token-v5-1 transfer hunter-reward tx-sender hunter none)))
          (map-set bounties (get bounty-id submission) (merge bounty { status: STATUS-COMPLETED, winner: (some hunter) }))
          (update-hunter-rep hunter true hunter-reward)
          
          ;; Update hunter disputes won
          (let ((rep (unwrap-panic (map-get? hunter-reputation hunter))))
            (map-set hunter-reputation hunter (merge rep { disputes-won: (+ (get disputes-won rep) u1) })))
        )
        (print { event: "dispute-resolved", version: "5.1", submission-id: submission-id, favor: "hunter" })
      )
      (begin
        ;; Return funds to creator
        (let ((escrowed (default-to u0 (map-get? bounty-escrow (get bounty-id submission)))))
          (if (> escrowed u0)
            (try! (as-contract (contract-call? .dao-token-v5-1 transfer escrowed tx-sender (get creator bounty) none)))
            true
          )
        )
        
        ;; Update hunter disputes lost
        (let ((rep (unwrap-panic (map-get? hunter-reputation hunter))))
          (map-set hunter-reputation hunter 
            (merge rep { 
              disputes-lost: (+ (get disputes-lost rep) u1),
              reputation-score: (if (> (get reputation-score rep) u10) 
                                  (- (get reputation-score rep) u10) 
                                  u0)
            })))
        
        (print { event: "dispute-resolved", version: "5.1", submission-id: submission-id, favor: "creator" })
      )
    )
    (ok in-favor-of-hunter)
  )
)

;; Cancel bounty (creator only, if no approved submissions)
(define-public (cancel-bounty (bounty-id uint))
  (let (
    (bounty (unwrap! (map-get? bounties bounty-id) ERR-BOUNTY-NOT-FOUND))
    (escrowed (default-to u0 (map-get? bounty-escrow bounty-id)))
  )
    (asserts! (is-eq tx-sender (get creator bounty)) ERR-NOT-AUTHORIZED)
    (asserts! (is-eq (get status bounty) STATUS-OPEN) ERR-BOUNTY-CLOSED)
    (asserts! (is-none (get winner bounty)) ERR-ALREADY-CLAIMED)
    
    ;; Return escrowed funds
    (if (> escrowed u0)
      (try! (as-contract (contract-call? .dao-token-v5-1 transfer escrowed tx-sender (get creator bounty) none)))
      true
    )
    
    ;; Update bounty
    (map-set bounties bounty-id (merge bounty { status: STATUS-CANCELLED }))
    (map-set bounty-escrow bounty-id u0)
    
    (print { event: "bounty-cancelled", version: "5.1", bounty-id: bounty-id })
    (ok true)
  )
)

;; Increase bounty reward
(define-public (increase-bounty (bounty-id uint) (additional-amount uint))
  (let (
    (bounty (unwrap! (map-get? bounties bounty-id) ERR-BOUNTY-NOT-FOUND))
    (current-escrow (default-to u0 (map-get? bounty-escrow bounty-id)))
  )
    (asserts! (is-eq (get status bounty) STATUS-OPEN) ERR-BOUNTY-CLOSED)
    (asserts! (> additional-amount u0) ERR-INVALID-AMOUNT)
    
    ;; Transfer additional tokens
    (try! (contract-call? .dao-token-v5-1 transfer additional-amount tx-sender (as-contract tx-sender) none))
    
    ;; Update bounty and escrow
    (map-set bounties bounty-id 
      (merge bounty { reward-amount: (+ (get reward-amount bounty) additional-amount) }))
    (map-set bounty-escrow bounty-id (+ current-escrow additional-amount))
    
    (print { event: "bounty-increased", version: "5.1", bounty-id: bounty-id, additional: additional-amount })
    (ok (+ (get reward-amount bounty) additional-amount))
  )
)

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

(define-public (add-reviewer (reviewer principal))
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) ERR-NOT-AUTHORIZED)
    (map-set reviewers reviewer true)
    (print { event: "reviewer-added", version: "5.1", reviewer: reviewer })
    (ok true)
  )
)

(define-public (remove-reviewer (reviewer principal))
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) ERR-NOT-AUTHORIZED)
    (map-delete reviewers reviewer)
    (ok true)
  )
)

(define-public (set-treasury (new-treasury principal))
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) ERR-NOT-AUTHORIZED)
    (var-set treasury-address new-treasury)
    (ok true)
  )
)

(define-public (toggle-pause)
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) ERR-NOT-AUTHORIZED)
    (var-set paused (not (var-get paused)))
    (ok (var-get paused))
  )
)

(define-public (transfer-admin (new-admin principal))
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) ERR-NOT-AUTHORIZED)
    (var-set admin new-admin)
    (ok true)
  )
)

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

(define-read-only (get-bounty (bounty-id uint))
  (map-get? bounties bounty-id)
)

(define-read-only (get-submission (submission-id uint))
  (map-get? submissions submission-id)
)

(define-read-only (get-bounty-submissions (bounty-id uint))
  (default-to (list) (map-get? bounty-submissions bounty-id))
)

(define-read-only (get-hunter-reputation (hunter principal))
  (default-to 
    { bounties-completed: u0, bounties-submitted: u0, total-earned: u0, reputation-score: u100, disputes-won: u0, disputes-lost: u0 }
    (map-get? hunter-reputation hunter))
)

(define-read-only (get-dispute (submission-id uint))
  (map-get? disputes submission-id)
)

(define-read-only (is-reviewer (account principal))
  (default-to false (map-get? reviewers account))
)

(define-read-only (get-bounty-escrow (bounty-id uint))
  (default-to u0 (map-get? bounty-escrow bounty-id))
)

(define-read-only (get-total-bounties-paid)
  (var-get total-bounties-paid)
)

(define-read-only (get-bounty-count)
  (var-get bounty-counter)
)

;; =====================
;; INITIALIZATION
;; =====================

(begin
  (print { event: "bounty-system-deployed", version: "5.1" })
)

Functions (23)

FunctionAccessArgs
update-hunter-repprivatehunter: principal, completed: bool, amount: uint
create-bountypublictitle: (string-ascii 100
submit-workpublicbounty-id: uint, submission-url: (string-utf8 500
approve-submissionpublicsubmission-id: uint
reject-submissionpublicsubmission-id: uint, reason: (string-utf8 500
dispute-submissionpublicsubmission-id: uint, reason: (string-utf8 500
resolve-disputepublicsubmission-id: uint, in-favor-of-hunter: bool
cancel-bountypublicbounty-id: uint
increase-bountypublicbounty-id: uint, additional-amount: uint
add-reviewerpublicreviewer: principal
remove-reviewerpublicreviewer: principal
set-treasurypublicnew-treasury: principal
toggle-pausepublic
transfer-adminpublicnew-admin: principal
get-bountyread-onlybounty-id: uint
get-submissionread-onlysubmission-id: uint
get-bounty-submissionsread-onlybounty-id: uint
get-hunter-reputationread-onlyhunter: principal
get-disputeread-onlysubmission-id: uint
is-reviewerread-onlyaccount: principal
get-bounty-escrowread-onlybounty-id: uint
get-total-bounties-paidread-only
get-bounty-countread-only