Source Code

;; title: NFTLEHDGER
;; version:
;; summary:
;; description:

;; Constants
(define-constant CONTRACT-OWNER tx-sender)

;; Voting strategy types
(define-constant STRATEGY-SIMPLE u1)
(define-constant STRATEGY-WEIGHTED u2)
(define-constant STRATEGY-QUADRATIC u3)
(define-constant STRATEGY-RANKED-CHOICE u4)

;; Error codes
(define-constant ERR-NOT-AUTHORIZED (err u601))
(define-constant ERR-INVALID-STRATEGY (err u602))
(define-constant ERR-PROPOSAL-NOT-FOUND (err u603))
(define-constant ERR-ALREADY-VOTED (err u604))
(define-constant ERR-INVALID-WEIGHT (err u605))
(define-constant ERR-INVALID-RANKING (err u606))

;; Data maps

;; Strategy configuration per proposal
(define-map proposal-strategies
    { proposal-id: uint }
    {
        strategy-type: uint,
        configured-at: uint,                 ;; Clarity 4: Unix timestamp
        min-weight: uint,
        max-weight: uint,
        requires-token: bool
    }
)

;; Weighted votes with Clarity 4 timestamps
(define-map weighted-votes
    { proposal-id: uint, voter: principal }
    {
        weight: uint,
        vote-for: bool,
        timestamp: uint                      ;; Clarity 4: Unix timestamp
    }
)

;; Quadratic votes with Clarity 4 timestamps
(define-map quadratic-votes
    { proposal-id: uint, voter: principal }
    {
        credits-spent: uint,
        vote-for: bool,
        effective-votes: uint,
        timestamp: uint                      ;; Clarity 4: Unix timestamp
    }
)

;; Ranked choice votes with Clarity 4 timestamps
(define-map ranked-votes
    { proposal-id: uint, voter: principal }
    {
        first-choice: uint,
        second-choice: (optional uint),
        third-choice: (optional uint),
        timestamp: uint                      ;; Clarity 4: Unix timestamp
    }
)

;; Proposal vote tallies
(define-map vote-tallies
    { proposal-id: uint }
    {
        simple-for: uint,
        simple-against: uint,
        weighted-for: uint,
        weighted-against: uint,
        quadratic-for: uint,
        quadratic-against: uint,
        total-voters: uint
    }
)

;; Read-only functions

;; Get proposal strategy
(define-read-only (get-proposal-strategy (proposal-id uint))
    (ok (map-get? proposal-strategies { proposal-id: proposal-id }))
)

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

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

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

;; Get vote tally
(define-read-only (get-vote-tally (proposal-id uint))
    (ok (map-get? vote-tallies { proposal-id: proposal-id }))
)

;; Calculate quadratic voting power from credits
(define-read-only (calculate-quadratic-power (credits uint))
    (ok (sqrti credits))
)

;; Public functions

;; Configure voting strategy for a proposal (only owner)
(define-public (configure-strategy (proposal-id uint) (strategy-type uint) (min-weight uint) (max-weight uint) (requires-token bool))
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
        (asserts! (or
            (is-eq strategy-type STRATEGY-SIMPLE)
            (or
                (is-eq strategy-type STRATEGY-WEIGHTED)
                (or
                    (is-eq strategy-type STRATEGY-QUADRATIC)
                    (is-eq strategy-type STRATEGY-RANKED-CHOICE)
                )
            )
        ) ERR-INVALID-STRATEGY)

        (map-set proposal-strategies
            { proposal-id: proposal-id }
            {
                strategy-type: strategy-type,
                configured-at: stacks-block-time,    ;; Clarity 4: Unix timestamp
                min-weight: min-weight,
                max-weight: max-weight,
                requires-token: requires-token
            }
        )

        ;; Initialize vote tally
        (map-set vote-tallies
            { proposal-id: proposal-id }
            {
                simple-for: u0,
                simple-against: u0,
                weighted-for: u0,
                weighted-against: u0,
                quadratic-for: u0,
                quadratic-against: u0,
                total-voters: u0
            }
        )

        (print {
            event: "strategy-configured",
            proposal-id: proposal-id,
            strategy-type: strategy-type,
            timestamp: stacks-block-time
        })
        (ok true)
    )
)

;; Cast weighted vote
(define-public (vote-weighted (proposal-id uint) (weight uint) (vote-for bool))
    (let
        (
            (strategy (unwrap! (map-get? proposal-strategies { proposal-id: proposal-id }) ERR-PROPOSAL-NOT-FOUND))
            (existing-vote (map-get? weighted-votes { proposal-id: proposal-id, voter: tx-sender }))
        )
        (asserts! (is-eq (get strategy-type strategy) STRATEGY-WEIGHTED) ERR-INVALID-STRATEGY)
        (asserts! (is-none existing-vote) ERR-ALREADY-VOTED)
        (asserts! (and (>= weight (get min-weight strategy)) (<= weight (get max-weight strategy))) ERR-INVALID-WEIGHT)

        ;; Record vote
        (map-set weighted-votes
            { proposal-id: proposal-id, voter: tx-sender }
            {
                weight: weight,
                vote-for: vote-for,
                timestamp: stacks-block-time         ;; Clarity 4: Unix timestamp
            }
        )

        ;; Update tally
        (let
            (
                (tally (unwrap! (map-get? vote-tallies { proposal-id: proposal-id }) ERR-PROPOSAL-NOT-FOUND))
            )
            (map-set vote-tallies
                { proposal-id: proposal-id }
                (merge tally {
                    weighted-for: (if vote-for (+ (get weighted-for tally) weight) (get weighted-for tally)),
                    weighted-against: (if (not vote-for) (+ (get weighted-against tally) weight) (get weighted-against tally)),
                    total-voters: (+ (get total-voters tally) u1)
                })
            )
        )

        (print {
            event: "weighted-vote-cast",
            proposal-id: proposal-id,
            voter: tx-sender,
            weight: weight,
            vote-for: vote-for,
            timestamp: stacks-block-time
        })
        (ok true)
    )
)

;; Cast quadratic vote
(define-public (vote-quadratic (proposal-id uint) (credits uint) (vote-for bool))
    (let
        (
            (strategy (unwrap! (map-get? proposal-strategies { proposal-id: proposal-id }) ERR-PROPOSAL-NOT-FOUND))
            (existing-vote (map-get? quadratic-votes { proposal-id: proposal-id, voter: tx-sender }))
            (effective-votes (sqrti credits))
        )
        (asserts! (is-eq (get strategy-type strategy) STRATEGY-QUADRATIC) ERR-INVALID-STRATEGY)
        (asserts! (is-none existing-vote) ERR-ALREADY-VOTED)
        (asserts! (> credits u0) ERR-INVALID-WEIGHT)

        ;; Record vote
        (map-set quadratic-votes
            { proposal-id: proposal-id, voter: tx-sender }
            {
                credits-spent: credits,
                vote-for: vote-for,
                effective-votes: effective-votes,
                timestamp: stacks-block-time         ;; Clarity 4: Unix timestamp
            }
        )

        ;; Update tally
        (let
            (
                (tally (unwrap! (map-get? vote-tallies { proposal-id: proposal-id }) ERR-PROPOSAL-NOT-FOUND))
            )
            (map-set vote-tallies
                { proposal-id: proposal-id }
                (merge tally {
                    quadratic-for: (if vote-for (+ (get quadratic-for tally) effective-votes) (get quadratic-for tally)),
                    quadratic-against: (if (not vote-for) (+ (get quadratic-against tally) effective-votes) (get quadratic-against tally)),
                    total-voters: (+ (get total-voters tally) u1)
                })
            )
        )

        (print {
            event: "quadratic-vote-cast",
            proposal-id: proposal-id,
            voter: tx-sender,
            credits-spent: credits,
            effective-votes: effective-votes,
            vote-for: vote-for,
            timestamp: stacks-block-time
        })
        (ok true)
    )
)

;; Cast ranked-choice vote
(define-public (vote-ranked (proposal-id uint) (first uint) (second (optional uint)) (third (optional uint)))
    (let
        (
            (strategy (unwrap! (map-get? proposal-strategies { proposal-id: proposal-id }) ERR-PROPOSAL-NOT-FOUND))
            (existing-vote (map-get? ranked-votes { proposal-id: proposal-id, voter: tx-sender }))
        )
        (asserts! (is-eq (get strategy-type strategy) STRATEGY-RANKED-CHOICE) ERR-INVALID-STRATEGY)
        (asserts! (is-none existing-vote) ERR-ALREADY-VOTED)

        ;; Validate rankings are different
        (asserts! (match second
            s2 (not (is-eq first s2))
            true
        ) ERR-INVALID-RANKING)

        (asserts! (match third
            s3 (and
                (not (is-eq first s3))
                (match second
                    s2 (not (is-eq s2 s3))
                    true
                )
            )
            true
        ) ERR-INVALID-RANKING)

        ;; Record vote
        (map-set ranked-votes
            { proposal-id: proposal-id, voter: tx-sender }
            {
                first-choice: first,
                second-choice: second,
                third-choice: third,
                timestamp: stacks-block-time         ;; Clarity 4: Unix timestamp
            }
        )

        ;; Update total voters
        (let
            (
                (tally (unwrap! (map-get? vote-tallies { proposal-id: proposal-id }) ERR-PROPOSAL-NOT-FOUND))
            )
            (map-set vote-tallies
                { proposal-id: proposal-id }
                (merge tally {
                    total-voters: (+ (get total-voters tally) u1)
                })
            )
        )

        (print {
            event: "ranked-vote-cast",
            proposal-id: proposal-id,
            voter: tx-sender,
            first-choice: first,
            second-choice: second,
            third-choice: third,
            timestamp: stacks-block-time
        })
        (ok true)
    )
)

Functions (10)

FunctionAccessArgs
get-proposal-strategyread-onlyproposal-id: uint
get-weighted-voteread-onlyproposal-id: uint, voter: principal
get-quadratic-voteread-onlyproposal-id: uint, voter: principal
get-ranked-voteread-onlyproposal-id: uint, voter: principal
get-vote-tallyread-onlyproposal-id: uint
calculate-quadratic-powerread-onlycredits: uint
configure-strategypublicproposal-id: uint, strategy-type: uint, min-weight: uint, max-weight: uint, requires-token: bool
vote-weightedpublicproposal-id: uint, weight: uint, vote-for: bool
vote-quadraticpublicproposal-id: uint, credits: uint, vote-for: bool
vote-rankedpublicproposal-id: uint, first: uint, second: (optional uint