Source Code

;; SprintFund Core Contract
;; This is the main contract for the SprintFund DAO
;; Enables lightning-fast micro-grants ($50-200 STX) with quadratic voting and reputation tracking

;; ============================================
;; Constants
;; ============================================

;; Error codes
(define-constant ERR-NOT-AUTHORIZED (err u100))
(define-constant ERR-PROPOSAL-NOT-FOUND (err u101))
(define-constant ERR-INSUFFICIENT-STAKE (err u102))
(define-constant ERR-ALREADY-EXECUTED (err u103))

;; ============================================
;; Data Variables
;; ============================================

;; Contract owner (DAO administrator)
(define-data-var contract-owner principal tx-sender)

;; Total number of proposals created
(define-data-var proposal-count uint u0)

;; Minimum stake required to submit a proposal (10 STX in microSTX)
(define-data-var min-stake-amount uint u10000000)

;; ============================================
;; Data Maps
;; ============================================

;; Proposals map: stores all proposal details
;; Key: proposal-id (unique identifier)
;; Value: proposal data tuple
(define-map proposals
  { proposal-id: uint }
  {
    proposer: principal,
    amount: uint,
    title: (string-utf8 100),
    description: (string-utf8 500),
    votes-for: uint,
    votes-against: uint,
    executed: bool,
    created-at: uint
  }
)

;; Stakes map: tracks staked amounts per user
;; Key: user principal
;; Value: staked amount in microSTX
(define-map stakes
  { staker: principal }
  { amount: uint }
)

;; Votes map: tracks votes per user per proposal
;; Key: proposal-id and voter principal
;; Value: vote weight (based on quadratic voting)
(define-map votes
  { proposal-id: uint, voter: principal }
  { weight: uint, support: bool }
)

;; ============================================
;; Read-Only Functions
;; ============================================

;; Get proposal details by ID
;; @param proposal-id: The unique identifier of the proposal
;; @returns: Optional tuple containing proposal details
(define-read-only (get-proposal (proposal-id uint))
  (map-get? proposals { proposal-id: proposal-id })
)

;; Get the current proposal count
;; @returns: Total number of proposals created
(define-read-only (get-proposal-count)
  (ok (var-get proposal-count))
)

;; Get stake amount for a user
;; @param staker: The principal to check
;; @returns: Optional tuple containing stake amount
(define-read-only (get-stake (staker principal))
  (map-get? stakes { staker: staker })
)

;; Get the minimum stake amount required
;; @returns: Minimum stake in microSTX
(define-read-only (get-min-stake-amount)
  (ok (var-get min-stake-amount))
)

;; Get the contract owner
;; @returns: Principal of the contract owner
(define-read-only (get-contract-owner)
  (ok (var-get contract-owner))
)

;; ============================================
;; Initialization
;; ============================================

;; Contract automatically initializes with tx-sender as owner
;; The contract-owner is set via the define-data-var above

;; ============================================
;; Public Functions
;; ============================================

;; Stake STX to gain proposal creation rights
;; @param amount: Amount to stake in microSTX
;; @returns: Success response or error
(define-public (stake (amount uint))
  (let
    (
      (current-stake (default-to { amount: u0 } (map-get? stakes { staker: tx-sender })))
      (new-stake-amount (+ (get amount current-stake) amount))
    )
    ;; Transfer STX from user to contract
    (try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
    
    ;; Update stake amount
    (map-set stakes
      { staker: tx-sender }
      { amount: new-stake-amount }
    )
    
    (ok new-stake-amount)
  )
)

;; Create a new proposal (requires minimum stake)
;; @param amount: Requested funding amount in microSTX
;; @param title: Proposal title (max 100 chars)
;; @param description: Proposal description (max 500 chars)
;; @returns: Proposal ID or error
(define-public (create-proposal (amount uint) (title (string-utf8 100)) (description (string-utf8 500)))
  (let
    (
      (proposer-stake (default-to { amount: u0 } (map-get? stakes { staker: tx-sender })))
      (current-count (var-get proposal-count))
      (new-proposal-id current-count)
    )
    ;; Check if proposer has minimum stake
    (asserts! (>= (get amount proposer-stake) (var-get min-stake-amount)) ERR-INSUFFICIENT-STAKE)
    
    ;; Create the proposal
    (map-set proposals
      { proposal-id: new-proposal-id }
      {
        proposer: tx-sender,
        amount: amount,
        title: title,
        description: description,
        votes-for: u0,
        votes-against: u0,
        executed: false,
        created-at: stacks-block-height
      }
    )
    
    ;; Increment proposal count
    (var-set proposal-count (+ current-count u1))
    
    (ok new-proposal-id)
  )
)

;; Withdraw staked STX (only if no active proposals)
;; @param amount: Amount to withdraw in microSTX
;; @returns: Success response or error
(define-public (withdraw-stake (amount uint))
  (let
    (
      (current-stake (default-to { amount: u0 } (map-get? stakes { staker: tx-sender })))
      (stake-amount (get amount current-stake))
    )
    ;; Check if user has enough stake
    (asserts! (>= stake-amount amount) ERR-INSUFFICIENT-STAKE)
    
    ;; Update stake amount
    (map-set stakes
      { staker: tx-sender }
      { amount: (- stake-amount amount) }
    )
    
    ;; Transfer STX back to user
    (try! (as-contract (stx-transfer? amount tx-sender tx-sender)))
    
    (ok (- stake-amount amount))
  )
)

;; Vote on a proposal with quadratic voting
;; @param proposal-id: The proposal to vote on
;; @param support: true for yes, false for no
;; @param vote-weight: Amount of voting power to use (will be squared)
;; @returns: Success response or error
(define-public (vote (proposal-id uint) (support bool) (vote-weight uint))
  (let
    (
      (proposal (unwrap! (map-get? proposals { proposal-id: proposal-id }) ERR-PROPOSAL-NOT-FOUND))
      (voter-stake (default-to { amount: u0 } (map-get? stakes { staker: tx-sender })))
      ;; Quadratic voting: cost = weight^2
      (vote-cost (* vote-weight vote-weight))
    )
    ;; Check if proposal exists and not executed
    (asserts! (not (get executed proposal)) ERR-ALREADY-EXECUTED)
    
    ;; Check if voter has enough stake for the vote cost
    (asserts! (>= (get amount voter-stake) vote-cost) ERR-INSUFFICIENT-STAKE)
    
    ;; Record the vote
    (map-set votes
      { proposal-id: proposal-id, voter: tx-sender }
      { weight: vote-weight, support: support }
    )
    
    ;; Update proposal vote counts
    (if support
      (map-set proposals
        { proposal-id: proposal-id }
        (merge proposal { votes-for: (+ (get votes-for proposal) vote-weight) })
      )
      (map-set proposals
        { proposal-id: proposal-id }
        (merge proposal { votes-against: (+ (get votes-against proposal) vote-weight) })
      )
    )
    
    (ok true)
  )
)

;; Execute a proposal if it has enough votes
;; @param proposal-id: The proposal to execute
;; @returns: Success response or error
(define-public (execute-proposal (proposal-id uint))
  (let
    (
      (proposal (unwrap! (map-get? proposals { proposal-id: proposal-id }) ERR-PROPOSAL-NOT-FOUND))
      (votes-for (get votes-for proposal))
      (votes-against (get votes-against proposal))
    )
    ;; Check if proposal exists and not already executed
    (asserts! (not (get executed proposal)) ERR-ALREADY-EXECUTED)
    
    ;; Check if proposal has more votes for than against
    (asserts! (> votes-for votes-against) ERR-NOT-AUTHORIZED)
    
    ;; Mark proposal as executed
    (map-set proposals
      { proposal-id: proposal-id }
      (merge proposal { executed: true })
    )
    
    ;; Transfer funds to proposer
    (try! (as-contract (stx-transfer? (get amount proposal) tx-sender (get proposer proposal))))
    
    (ok true)
  )
)

Functions (10)

FunctionAccessArgs
get-proposalread-onlyproposal-id: uint
get-proposal-countread-only
get-stakeread-onlystaker: principal
get-min-stake-amountread-only
get-contract-ownerread-only
stakepublicamount: uint
create-proposalpublicamount: uint, title: (string-utf8 100
withdraw-stakepublicamount: uint
votepublicproposal-id: uint, support: bool, vote-weight: uint
execute-proposalpublicproposal-id: uint