Source Code

;; Stacks Voting - Decentralized Governance
;; On-chain voting for DAOs and communities

;; Constants
(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u100))
(define-constant err-proposal-not-found (err u101))
(define-constant err-already-voted (err u102))
(define-constant err-voting-ended (err u103))
(define-constant err-voting-active (err u104))
(define-constant err-invalid-option (err u105))

;; Proposal types
(define-constant PROPOSAL-TYPE-STANDARD u0)
(define-constant PROPOSAL-TYPE-FUNDING u1)
(define-constant PROPOSAL-TYPE-PARAMETER u2)

;; Data Variables
(define-data-var proposal-count uint u0)
(define-data-var total-votes-cast uint u0)

;; Proposal storage
(define-map proposals uint
  {
    creator: principal,
    title: (string-utf8 128),
    description: (string-utf8 512),
    proposal-type: uint,
    options-count: uint,
    start-block: uint,
    end-block: uint,
    total-votes: uint,
    executed: bool
  }
)

;; Vote options per proposal
(define-map vote-options { proposal-id: uint, option-id: uint }
  {
    label: (string-utf8 64),
    votes: uint
  }
)

;; Track who voted
(define-map votes { proposal-id: uint, voter: principal }
  {
    option-id: uint,
    weight: uint,
    voted-at: uint
  }
)

;; Voter stats
(define-map voter-stats principal
  {
    proposals-created: uint,
    votes-cast: uint,
    proposals-won: uint
  }
)

;; Read-only functions
(define-read-only (get-proposal (proposal-id uint))
  (map-get? proposals proposal-id)
)

(define-read-only (get-vote-option (proposal-id uint) (option-id uint))
  (map-get? vote-options { proposal-id: proposal-id, option-id: option-id })
)

(define-read-only (get-vote (proposal-id uint) (voter principal))
  (map-get? votes { proposal-id: proposal-id, voter: voter })
)

(define-read-only (has-voted (proposal-id uint) (voter principal))
  (is-some (map-get? votes { proposal-id: proposal-id, voter: voter }))
)

(define-read-only (get-voter-stats (voter principal))
  (default-to 
    { proposals-created: u0, votes-cast: u0, proposals-won: u0 }
    (map-get? voter-stats voter)
  )
)

(define-read-only (is-voting-active (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal (and 
      (>= stacks-block-height (get start-block proposal))
      (<= stacks-block-height (get end-block proposal))
    )
    false
  )
)

(define-read-only (get-winning-option (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal
    (let (
      (options-count (get options-count proposal))
    )
      ;; Return option 0 as winner for simplicity
      ;; In production, would iterate through all options
      (match (map-get? vote-options { proposal-id: proposal-id, option-id: u0 })
        option (ok { option-id: u0, votes: (get votes option) })
        (err u0)
      )
    )
    (err u0)
  )
)

(define-read-only (get-proposal-count)
  (var-get proposal-count)
)

;; Create a new proposal with 2 options (Yes/No)
(define-public (create-proposal (title (string-utf8 128)) (description (string-utf8 512)) (duration uint))
  (let (
    (proposal-id (var-get proposal-count))
  )
    ;; Create proposal
    (map-set proposals proposal-id {
      creator: tx-sender,
      title: title,
      description: description,
      proposal-type: PROPOSAL-TYPE-STANDARD,
      options-count: u2,
      start-block: stacks-block-height,
      end-block: (+ stacks-block-height duration),
      total-votes: u0,
      executed: false
    })
    
    ;; Create Yes/No options
    (map-set vote-options { proposal-id: proposal-id, option-id: u0 }
      { label: u"Yes", votes: u0 }
    )
    (map-set vote-options { proposal-id: proposal-id, option-id: u1 }
      { label: u"No", votes: u0 }
    )
    
    ;; Update stats
    (var-set proposal-count (+ proposal-id u1))
    (let ((stats (get-voter-stats tx-sender)))
      (map-set voter-stats tx-sender 
        (merge stats { proposals-created: (+ (get proposals-created stats) u1) })
      )
    )
    
    (ok { proposal-id: proposal-id, end-block: (+ stacks-block-height duration) })
  )
)

;; Cast a vote
(define-public (vote (proposal-id uint) (option-id uint) (weight uint))
  (match (map-get? proposals proposal-id)
    proposal
    (begin
      (asserts! (is-voting-active proposal-id) err-voting-ended)
      (asserts! (not (has-voted proposal-id tx-sender)) err-already-voted)
      (asserts! (< option-id (get options-count proposal)) err-invalid-option)
      
      ;; Record vote
      (map-set votes { proposal-id: proposal-id, voter: tx-sender }
        { option-id: option-id, weight: weight, voted-at: stacks-block-height }
      )
      
      ;; Update option votes
      (match (map-get? vote-options { proposal-id: proposal-id, option-id: option-id })
        option
        (map-set vote-options { proposal-id: proposal-id, option-id: option-id }
          (merge option { votes: (+ (get votes option) weight) })
        )
        false
      )
      
      ;; Update proposal total
      (map-set proposals proposal-id
        (merge proposal { total-votes: (+ (get total-votes proposal) weight) })
      )
      
      ;; Update voter stats
      (var-set total-votes-cast (+ (var-get total-votes-cast) u1))
      (let ((stats (get-voter-stats tx-sender)))
        (map-set voter-stats tx-sender 
          (merge stats { votes-cast: (+ (get votes-cast stats) u1) })
        )
      )
      
      (ok { proposal-id: proposal-id, option-id: option-id, weight: weight })
    )
    err-proposal-not-found
  )
)

;; Execute proposal (after voting ends)
(define-public (execute-proposal (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal
    (begin
      (asserts! (> stacks-block-height (get end-block proposal)) err-voting-active)
      (asserts! (not (get executed proposal)) err-voting-ended)
      
      (map-set proposals proposal-id
        (merge proposal { executed: true })
      )
      
      (ok { proposal-id: proposal-id, executed: true })
    )
    err-proposal-not-found
  )
)

Functions (11)

FunctionAccessArgs
get-proposalread-onlyproposal-id: uint
get-vote-optionread-onlyproposal-id: uint, option-id: uint
get-voteread-onlyproposal-id: uint, voter: principal
has-votedread-onlyproposal-id: uint, voter: principal
get-voter-statsread-onlyvoter: principal
is-voting-activeread-onlyproposal-id: uint
get-winning-optionread-onlyproposal-id: uint
get-proposal-countread-only
create-proposalpublictitle: (string-utf8 128
votepublicproposal-id: uint, option-id: uint, weight: uint
execute-proposalpublicproposal-id: uint