Source Code

;; DAO Voting Contract
;; Decentralized governance for proposals and voting
;; Built by rajuice for Stacks Builder Rewards

;; Constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-AUTHORIZED (err u401))
(define-constant ERR-PROPOSAL-NOT-FOUND (err u404))
(define-constant ERR-ALREADY-VOTED (err u405))
(define-constant ERR-VOTING-CLOSED (err u406))
(define-constant ERR-PROPOSAL-ACTIVE (err u407))
(define-constant ERR-INSUFFICIENT-VOTES (err u408))
(define-constant ERR-NOT-MEMBER (err u409))
(define-constant ERR-PROPOSAL-EXECUTED (err u410))
(define-constant ERR-ALREADY-MEMBER (err u411))

;; Voting parameters
(define-constant VOTING-PERIOD u1008) ;; ~7 days in blocks
(define-constant MIN-VOTES-TO-PASS u3) ;; Minimum votes to pass

;; Data vars
(define-data-var proposal-count uint u0)
(define-data-var member-count uint u0)
(define-data-var treasury-balance uint u0)

;; Proposal structure
(define-map proposals uint {
  proposer: principal,
  title: (string-utf8 100),
  description: (string-utf8 500),
  start-block: uint,
  end-block: uint,
  votes-for: uint,
  votes-against: uint,
  executed: bool
})

;; Vote tracking
(define-map votes { proposal-id: uint, voter: principal } {
  vote: bool,
  voting-power: uint
})

;; Member tracking
(define-map members principal {
  voting-power: uint,
  proposals-created: uint,
  total-votes: uint,
  joined-at: uint
})

;; Private function to get contract principal
(define-private (get-contract-principal)
  (as-contract tx-sender))

;; Private function for withdrawing funds
(define-private (withdraw-stx (amount uint) (recipient principal))
  (as-contract (stx-transfer? amount tx-sender recipient)))

;; Proposal Functions

;; Create a new proposal
(define-public (create-proposal (title (string-utf8 100)) (description (string-utf8 500)))
  (let (
    (member-info (unwrap! (map-get? members tx-sender) ERR-NOT-MEMBER))
    (proposal-id (+ (var-get proposal-count) u1))
  )
    (map-set proposals proposal-id {
      proposer: tx-sender,
      title: title,
      description: description,
      start-block: stacks-block-height,
      end-block: (+ stacks-block-height VOTING-PERIOD),
      votes-for: u0,
      votes-against: u0,
      executed: false
    })
    ;; Update member stats
    (map-set members tx-sender {
      voting-power: (get voting-power member-info),
      proposals-created: (+ (get proposals-created member-info) u1),
      total-votes: (get total-votes member-info),
      joined-at: (get joined-at member-info)
    })
    (var-set proposal-count proposal-id)
    (ok proposal-id)))

;; Vote on a proposal
(define-public (vote (proposal-id uint) (vote-for bool))
  (let (
    (proposal (unwrap! (map-get? proposals proposal-id) ERR-PROPOSAL-NOT-FOUND))
    (member-info (unwrap! (map-get? members tx-sender) ERR-NOT-MEMBER))
    (voting-power (get voting-power member-info))
  )
    ;; Check if voting is still open
    (asserts! (<= stacks-block-height (get end-block proposal)) ERR-VOTING-CLOSED)
    ;; Check if already voted
    (asserts! (is-none (map-get? votes { proposal-id: proposal-id, voter: tx-sender })) ERR-ALREADY-VOTED)
    ;; Record the vote
    (map-set votes { proposal-id: proposal-id, voter: tx-sender } {
      vote: vote-for,
      voting-power: voting-power
    })
    ;; Update proposal vote count
    (map-set proposals proposal-id {
      proposer: (get proposer proposal),
      title: (get title proposal),
      description: (get description proposal),
      start-block: (get start-block proposal),
      end-block: (get end-block proposal),
      votes-for: (if vote-for 
        (+ (get votes-for proposal) voting-power) 
        (get votes-for proposal)),
      votes-against: (if (not vote-for) 
        (+ (get votes-against proposal) voting-power) 
        (get votes-against proposal)),
      executed: (get executed proposal)
    })
    ;; Update member stats
    (map-set members tx-sender {
      voting-power: voting-power,
      proposals-created: (get proposals-created member-info),
      total-votes: (+ (get total-votes member-info) u1),
      joined-at: (get joined-at member-info)
    })
    (ok true)))

;; Execute a passed proposal
(define-public (execute-proposal (proposal-id uint))
  (let ((proposal (unwrap! (map-get? proposals proposal-id) ERR-PROPOSAL-NOT-FOUND)))
    ;; Check voting has ended
    (asserts! (> stacks-block-height (get end-block proposal)) ERR-PROPOSAL-ACTIVE)
    ;; Check not already executed
    (asserts! (not (get executed proposal)) ERR-PROPOSAL-EXECUTED)
    ;; Check it passed
    (asserts! (> (get votes-for proposal) (get votes-against proposal)) ERR-INSUFFICIENT-VOTES)
    (asserts! (>= (+ (get votes-for proposal) (get votes-against proposal)) MIN-VOTES-TO-PASS) ERR-INSUFFICIENT-VOTES)
    ;; Mark as executed
    (map-set proposals proposal-id {
      proposer: (get proposer proposal),
      title: (get title proposal),
      description: (get description proposal),
      start-block: (get start-block proposal),
      end-block: (get end-block proposal),
      votes-for: (get votes-for proposal),
      votes-against: (get votes-against proposal),
      executed: true
    })
    (print { event: "proposal-executed", proposal-id: proposal-id })
    (ok true)))

;; Member Functions

;; Join the DAO (stake STX to become a member)
(define-public (join-dao (stake-amount uint))
  (let ((contract-address (get-contract-principal)))
    (asserts! (>= stake-amount u1000000) ERR-INSUFFICIENT-VOTES) ;; Min 1 STX
    (asserts! (is-none (map-get? members tx-sender)) ERR-ALREADY-MEMBER)
    ;; Transfer stake to DAO treasury
    (try! (stx-transfer? stake-amount tx-sender contract-address))
    ;; Add as member
    (map-set members tx-sender {
      voting-power: (/ stake-amount u1000000), ;; 1 voting power per STX
      proposals-created: u0,
      total-votes: u0,
      joined-at: stacks-block-height
    })
    (var-set member-count (+ (var-get member-count) u1))
    (var-set treasury-balance (+ (var-get treasury-balance) stake-amount))
    (ok (/ stake-amount u1000000))))

;; Leave the DAO
(define-public (leave-dao)
  (let (
    (member-info (unwrap! (map-get? members tx-sender) ERR-NOT-MEMBER))
    (stake-amount (* (get voting-power member-info) u1000000))
    (caller tx-sender)
  )
    ;; Return stake
    (try! (withdraw-stx stake-amount caller))
    ;; Remove member
    (map-delete members caller)
    (var-set member-count (- (var-get member-count) u1))
    (var-set treasury-balance (- (var-get treasury-balance) stake-amount))
    (ok stake-amount)))

;; Increase stake
(define-public (increase-stake (additional-amount uint))
  (let (
    (member-info (unwrap! (map-get? members tx-sender) ERR-NOT-MEMBER))
    (contract-address (get-contract-principal))
  )
    (try! (stx-transfer? additional-amount tx-sender contract-address))
    (map-set members tx-sender {
      voting-power: (+ (get voting-power member-info) (/ additional-amount u1000000)),
      proposals-created: (get proposals-created member-info),
      total-votes: (get total-votes member-info),
      joined-at: (get joined-at member-info)
    })
    (var-set treasury-balance (+ (var-get treasury-balance) additional-amount))
    (ok (+ (get voting-power member-info) (/ additional-amount u1000000)))))

;; Admin Functions

;; Add member directly (owner only - for initial setup)
(define-public (add-member (member principal) (voting-power uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (map-set members member {
      voting-power: voting-power,
      proposals-created: u0,
      total-votes: u0,
      joined-at: stacks-block-height
    })
    (var-set member-count (+ (var-get member-count) u1))
    (ok true)))

;; Read-only Functions

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

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

(define-read-only (get-member (member principal))
  (map-get? members member))

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

(define-read-only (get-member-count)
  (ok (var-get member-count)))

(define-read-only (get-treasury-balance)
  (ok (var-get treasury-balance)))

(define-read-only (get-voting-period)
  (ok VOTING-PERIOD))

(define-read-only (get-min-votes)
  (ok MIN-VOTES-TO-PASS))

(define-read-only (is-proposal-active (proposal-id uint))
  (let ((proposal (map-get? proposals proposal-id)))
    (match proposal
      p (ok (and (<= stacks-block-height (get end-block p)) (not (get executed p))))
      (err ERR-PROPOSAL-NOT-FOUND))))

(define-read-only (is-member (account principal))
  (is-some (map-get? members account)))

Functions (19)

FunctionAccessArgs
get-contract-principalprivate
withdraw-stxprivateamount: uint, recipient: principal
create-proposalpublictitle: (string-utf8 100
votepublicproposal-id: uint, vote-for: bool
execute-proposalpublicproposal-id: uint
join-daopublicstake-amount: uint
leave-daopublic
increase-stakepublicadditional-amount: uint
add-memberpublicmember: principal, voting-power: uint
get-proposalread-onlyproposal-id: uint
get-voteread-onlyproposal-id: uint, voter: principal
get-memberread-onlymember: principal
get-proposal-countread-only
get-member-countread-only
get-treasury-balanceread-only
get-voting-periodread-only
get-min-votesread-only
is-proposal-activeread-onlyproposal-id: uint
is-memberread-onlyaccount: principal