Source Code

;; clarity-version 4
;; Simple Bitcoin Treasury DAO
;; Community manages pooled sBTC funds through voting

;; token definitions
(define-fungible-token sbtc-token)

;; constants
(define-constant err-not-member (err u101))
(define-constant err-proposal-not-found (err u102))
(define-constant err-voting-ended (err u103))
(define-constant err-insufficient-funds (err u104))
(define-constant err-already-voted (err u105))
(define-constant err-voting-not-ended (err u106))
(define-constant err-already-executed (err u107))
(define-constant err-proposal-rejected (err u108))
(define-constant err-asset-restriction-failed (err u109))
(define-constant err-invalid-signature (err u110))
(define-constant err-passkey-not-found (err u111))
(define-constant err-invalid-contract-hash (err u112))

;; Voting period: 24 hours (144 blocks * 10 minutes per block)
(define-constant voting-period-blocks u144)

;; data vars
(define-data-var proposal-nonce uint u0)
(define-data-var treasury-principal principal tx-sender)

;; data maps
(define-map dao-members
  principal
  bool
)
(define-map proposals
  uint
  {
    creator: principal,
    amount: uint,
    recipient: principal,
    yes-votes: uint,
    no-votes: uint,
    end-block: uint,
    end-timestamp: uint,
    executed: bool,
    created-at: uint,
  }
)
(define-map member-votes
  {
    proposal-id: uint,
    voter: principal,
  }
  bool
)

;; passkey storage for biometric authentication (Clarity 4)
(define-map member-passkeys
  principal
  (buff 33)
)

;; trusted contracts registry (Clarity 4)
(define-map trusted-contracts
  principal
  bool
)

;; read-only functions (defined before use)
(define-read-only (is-member (user principal))
  (default-to false (map-get? dao-members user))
)

;; public functions
(define-public (join-dao)
  (begin
    (map-set dao-members tx-sender true)
    (ok true)
  )
)

;; Join DAO with passkey (secp256r1-verify - Clarity 4)
(define-public (join-dao-with-passkey
    (public-key (buff 33))
    (message-hash (buff 32))
    (signature (buff 64))
  )
  (begin
    (asserts! (secp256r1-verify message-hash signature public-key)
      err-invalid-signature
    )
    (map-set dao-members tx-sender true)
    (map-set member-passkeys tx-sender public-key)
    (ok true)
  )
)

;; Vote with passkey authentication (secp256r1-verify - Clarity 4)
(define-public (vote-with-passkey
    (proposal-id uint)
    (vote-yes bool)
    (message-hash (buff 32))
    (signature (buff 64))
  )
  (let (
      (proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-found))
      (passkey (unwrap! (map-get? member-passkeys tx-sender) err-passkey-not-found))
    )
    (asserts! (secp256r1-verify message-hash signature passkey)
      err-invalid-signature
    )
    (asserts! (is-member tx-sender) err-not-member)
    (asserts! (< stacks-block-time (get end-timestamp proposal)) err-voting-ended)
    (asserts!
      (is-none (map-get? member-votes {
        proposal-id: proposal-id,
        voter: tx-sender,
      }))
      err-already-voted
    )

    (map-set member-votes {
      proposal-id: proposal-id,
      voter: tx-sender,
    }
      true
    )

    (if vote-yes
      (map-set proposals proposal-id
        (merge proposal { yes-votes: (+ (get yes-votes proposal) u1) })
      )
      (map-set proposals proposal-id
        (merge proposal { no-votes: (+ (get no-votes proposal) u1) })
      )
    )
    (ok true)
  )
)

;; Register trusted contract - simplified (Clarity 4)
(define-public (register-trusted-contract (contract principal))
  (begin
    (asserts! (is-member tx-sender) err-not-member)
    (map-set trusted-contracts contract true)
    (ok true)
  )
)

(define-public (deposit (amount uint))
  (begin
    (asserts! (is-member tx-sender) err-not-member)
    (ft-mint? sbtc-token amount tx-sender)
  )
)

(define-public (create-proposal
    (amount uint)
    (recipient principal)
  )
  (let (
      (proposal-id (+ (var-get proposal-nonce) u1))
      (current-time stacks-block-time)
      (voting-deadline (+ stacks-block-time u86400)) ;; 24 hours in seconds
    )
    (asserts! (is-member tx-sender) err-not-member)
    (map-set proposals proposal-id {
      creator: tx-sender,
      amount: amount,
      recipient: recipient,
      yes-votes: u0,
      no-votes: u0,
      end-block: (+ stacks-block-height voting-period-blocks),
      end-timestamp: voting-deadline,
      executed: false,
      created-at: current-time,
    })
    (var-set proposal-nonce proposal-id)
    (ok proposal-id)
  )
)

(define-public (vote
    (proposal-id uint)
    (vote-yes bool)
  )
  (let ((proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-found)))
    (asserts! (is-member tx-sender) err-not-member)
    (asserts! (< stacks-block-time (get end-timestamp proposal)) err-voting-ended)
    (asserts!
      (is-none (map-get? member-votes {
        proposal-id: proposal-id,
        voter: tx-sender,
      }))
      err-already-voted
    )

    (map-set member-votes {
      proposal-id: proposal-id,
      voter: tx-sender,
    }
      true
    )

    (if vote-yes
      (map-set proposals proposal-id
        (merge proposal { yes-votes: (+ (get yes-votes proposal) u1) })
      )
      (map-set proposals proposal-id
        (merge proposal { no-votes: (+ (get no-votes proposal) u1) })
      )
    )
    (ok true)
  )
)

(define-public (execute-proposal (proposal-id uint))
  (let (
      (proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-found))
      (transfer-amount (get amount proposal))
      (transfer-recipient (get recipient proposal))
    )
    (asserts! (>= stacks-block-time (get end-timestamp proposal))
      err-voting-not-ended
    )
    (asserts! (not (get executed proposal)) err-already-executed)
    (asserts! (> (get yes-votes proposal) (get no-votes proposal))
      err-proposal-rejected
    )
    (asserts! (>= (ft-get-balance sbtc-token tx-sender) transfer-amount)
      err-insufficient-funds
    )

    ;; Execute transfer
    (try! (ft-transfer? sbtc-token transfer-amount tx-sender transfer-recipient))
    (map-set proposals proposal-id (merge proposal { executed: true }))
    (ok true)
  )
)

;; Batch vote on multiple proposals (Clarity 4 - uses stacks-block-time)
(define-public (batch-vote
    (proposal-ids (list 10 uint))
    (votes (list 10 bool))
  )
  (begin
    (asserts! (is-member tx-sender) err-not-member)
    (asserts! (is-eq (len proposal-ids) (len votes)) (err u300))
    (ok (map batch-vote-helper proposal-ids votes))
  )
)

;; Helper for batch voting
(define-private (batch-vote-helper
    (proposal-id uint)
    (vote-yes bool)
  )
  (match (map-get? proposals proposal-id)
    proposal (if (and
        (< stacks-block-time (get end-timestamp proposal))
        (is-none (map-get? member-votes {
          proposal-id: proposal-id,
          voter: tx-sender,
        }))
      )
      (begin
        (map-set member-votes {
          proposal-id: proposal-id,
          voter: tx-sender,
        }
          true
        )
        (if vote-yes
          (map-set proposals proposal-id
            (merge proposal { yes-votes: (+ (get yes-votes proposal) u1) })
          )
          (map-set proposals proposal-id
            (merge proposal { no-votes: (+ (get no-votes proposal) u1) })
          )
        )
        true
      )
      false
    )
    false
  )
)

;; Update member passkey (secp256r1-verify - Clarity 4)
(define-public (update-passkey
    (new-public-key (buff 33))
    (message-hash (buff 32))
    (signature (buff 64))
  )
  (let ((old-passkey (unwrap! (map-get? member-passkeys tx-sender) err-passkey-not-found)))
    (asserts! (secp256r1-verify message-hash signature old-passkey)
      err-invalid-signature
    )
    (asserts! (is-member tx-sender) err-not-member)
    (map-set member-passkeys tx-sender new-public-key)
    (ok true)
  )
)

;; Cancel proposal before voting ends (Clarity 4 - uses stacks-block-time)
(define-public (cancel-proposal (proposal-id uint))
  (let ((proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-found)))
    (asserts! (is-eq tx-sender (get creator proposal)) (err u301))
    (asserts! (< stacks-block-time (get end-timestamp proposal)) err-voting-ended)
    (asserts! (not (get executed proposal)) err-already-executed)
    (map-set proposals proposal-id
      (merge proposal {
        executed: true,
        end-timestamp: stacks-block-time,
      })
    )
    (ok true)
  )
)

;; Delegate voting power with passkey (secp256r1-verify - Clarity 4)
(define-map delegations
  {
    delegator: principal,
    proposal-id: uint,
  }
  principal
)

(define-public (delegate-vote
    (proposal-id uint)
    (delegate principal)
    (message-hash (buff 32))
    (signature (buff 64))
  )
  (let (
      (proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-found))
      (passkey (unwrap! (map-get? member-passkeys tx-sender) err-passkey-not-found))
    )
    (asserts! (secp256r1-verify message-hash signature passkey)
      err-invalid-signature
    )
    (asserts! (is-member tx-sender) err-not-member)
    (asserts! (is-member delegate) err-not-member)
    (asserts! (< stacks-block-time (get end-timestamp proposal)) err-voting-ended)
    (asserts!
      (is-none (map-get? member-votes {
        proposal-id: proposal-id,
        voter: tx-sender,
      }))
      err-already-voted
    )
    (map-set delegations {
      delegator: tx-sender,
      proposal-id: proposal-id,
    }
      delegate
    )
    (ok true)
  )
)

;; Execute delegated vote
(define-public (execute-delegated-vote
    (proposal-id uint)
    (delegator principal)
    (vote-yes bool)
  )
  (let (
      (proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-found))
      (delegate (unwrap!
        (map-get? delegations {
          delegator: delegator,
          proposal-id: proposal-id,
        })
        (err u302)
      ))
    )
    (asserts! (is-eq tx-sender delegate) (err u303))
    (asserts! (< stacks-block-time (get end-timestamp proposal)) err-voting-ended)
    (asserts!
      (is-none (map-get? member-votes {
        proposal-id: proposal-id,
        voter: delegator,
      }))
      err-already-voted
    )

    (map-set member-votes {
      proposal-id: proposal-id,
      voter: delegator,
    }
      true
    )

    (if vote-yes
      (map-set proposals proposal-id
        (merge proposal { yes-votes: (+ (get yes-votes proposal) u1) })
      )
      (map-set proposals proposal-id
        (merge proposal { no-votes: (+ (get no-votes proposal) u1) })
      )
    )
    (ok true)
  )
)

;; Emergency withdraw for proposal creator if rejected (Clarity 4 - uses stacks-block-time)
(define-public (withdraw-rejected-proposal (proposal-id uint))
  (let ((proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-found)))
    (asserts! (is-eq tx-sender (get creator proposal)) (err u304))
    (asserts! (>= stacks-block-time (get end-timestamp proposal))
      err-voting-not-ended
    )
    (asserts! (not (get executed proposal)) err-already-executed)
    (asserts! (<= (get yes-votes proposal) (get no-votes proposal)) (err u305))
    (map-set proposals proposal-id (merge proposal { executed: true }))
    (ok true)
  )
)

;; Extend voting period (Clarity 4 - uses stacks-block-time)
(define-public (extend-voting-period
    (proposal-id uint)
    (additional-seconds uint)
  )
  (let ((proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-found)))
    (asserts! (is-eq tx-sender (get creator proposal)) (err u306))
    (asserts! (< stacks-block-time (get end-timestamp proposal)) err-voting-ended)
    (asserts! (not (get executed proposal)) err-already-executed)
    (asserts! (<= additional-seconds u172800) (err u307))
    ;; max 48 hours extension
    (map-set proposals proposal-id
      (merge proposal { end-timestamp: (+ (get end-timestamp proposal) additional-seconds) })
    )
    (ok true)
  )
)

;; Revoke vote delegation (Clarity 4 - uses stacks-block-time)
(define-public (revoke-delegation (proposal-id uint))
  (let ((proposal (unwrap! (map-get? proposals proposal-id) err-proposal-not-found)))
    (asserts! (is-member tx-sender) err-not-member)
    (asserts! (< stacks-block-time (get end-timestamp proposal)) err-voting-ended)
    (asserts!
      (is-some (map-get? delegations {
        delegator: tx-sender,
        proposal-id: proposal-id,
      }))
      (err u308)
    )
    (map-delete delegations {
      delegator: tx-sender,
      proposal-id: proposal-id,
    })
    (ok true)
  )
)

;; Bulk deposit for multiple members (Clarity 4)
(define-public (bulk-deposit
    (recipients (list 20 principal))
    (amounts (list 20 uint))
  )
  (begin
    (asserts! (is-member tx-sender) err-not-member)
    (asserts! (is-eq (len recipients) (len amounts)) (err u309))
    (ok (map bulk-deposit-helper recipients amounts))
  )
)

;; Helper for bulk deposit
(define-private (bulk-deposit-helper
    (recipient principal)
    (amount uint)
  )
  (match (ft-mint? sbtc-token amount recipient)
    success true
    error false
  )
)

;; additional read-only functions
(define-read-only (get-treasury-balance)
  (ft-get-balance sbtc-token tx-sender)
)

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

(define-read-only (get-proposal-status (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal (if (get executed proposal)
      "executed"
      (if (>= stacks-block-time (get end-timestamp proposal))
        (if (> (get yes-votes proposal) (get no-votes proposal))
          "passed-pending-execution"
          "rejected"
        )
        "voting-active"
      )
    )
    "not-found"
  )
)

(define-read-only (get-voting-deadline-info (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal (ok {
      end-timestamp: (get end-timestamp proposal),
      created-at: (get created-at proposal),
      time-remaining: (if (>= stacks-block-time (get end-timestamp proposal))
        u0
        (- (get end-timestamp proposal) stacks-block-time)
      ),
      is-active: (< stacks-block-time (get end-timestamp proposal)),
    })
    err-proposal-not-found
  )
)

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

(define-read-only (get-proposal-results (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal (ok {
      yes-votes: (get yes-votes proposal),
      no-votes: (get no-votes proposal),
      total-votes: (+ (get yes-votes proposal) (get no-votes proposal)),
      winning: (> (get yes-votes proposal) (get no-votes proposal)),
    })
    err-proposal-not-found
  )
)

;; Get proposal ID as ASCII string (to-ascii? - Clarity 4)
(define-read-only (get-proposal-id-as-string (proposal-id uint))
  (to-ascii? proposal-id)
)

;; Get formatted proposal info with ASCII labels (to-ascii? - Clarity 4)
(define-read-only (get-proposal-summary (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal (ok {
      id-string: (unwrap! (to-ascii? proposal-id) (err u200)),
      yes-string: (unwrap! (to-ascii? (get yes-votes proposal)) (err u201)),
      no-string: (unwrap! (to-ascii? (get no-votes proposal)) (err u202)),
      executed-string: (unwrap! (to-ascii? (get executed proposal)) (err u203)),
    })
    err-proposal-not-found
  )
)

;; Verify if contract is trusted (Clarity 4)
(define-read-only (is-trusted-contract (contract principal))
  (default-to false (map-get? trusted-contracts contract))
)

;; Check if member has passkey registered
(define-read-only (has-passkey (member principal))
  (is-some (map-get? member-passkeys member))
)

;; Get delegation info (Clarity 4)
(define-read-only (get-delegation
    (delegator principal)
    (proposal-id uint)
  )
  (map-get? delegations {
    delegator: delegator,
    proposal-id: proposal-id,
  })
)

;; Check if proposal can be extended (Clarity 4 - uses stacks-block-time)
(define-read-only (can-extend-proposal (proposal-id uint))
  (match (map-get? proposals proposal-id)
    proposal (ok {
      can-extend: (and
        (< stacks-block-time (get end-timestamp proposal))
        (not (get executed proposal))
      ),
      time-until-deadline: (if (>= stacks-block-time (get end-timestamp proposal))
        u0
        (- (get end-timestamp proposal) stacks-block-time)
      ),
    })
    err-proposal-not-found
  )
)

;; Get active proposals info (Clarity 4 - uses stacks-block-time)
(define-read-only (get-active-proposals-info)
  (ok {
    total-proposals: (var-get proposal-nonce),
    current-time: stacks-block-time,
  })
)

Functions (25)

FunctionAccessArgs
cancel-proposalpublicproposal-id: uint
is-memberread-onlyuser: principal
join-daopublic
join-dao-with-passkeypublicpublic-key: (buff 33
vote-with-passkeypublicproposal-id: uint, vote-yes: bool, message-hash: (buff 32
register-trusted-contractpubliccontract: principal
depositpublicamount: uint
execute-proposalpublicproposal-id: uint
batch-votepublicproposal-ids: (list 10 uint
update-passkeypublicnew-public-key: (buff 33
delegate-votepublicproposal-id: uint, delegate: principal, message-hash: (buff 32
withdraw-rejected-proposalpublicproposal-id: uint
revoke-delegationpublicproposal-id: uint
bulk-depositpublicrecipients: (list 20 principal
get-treasury-balanceread-only
get-proposalread-onlyproposal-id: uint
get-proposal-statusread-onlyproposal-id: uint
get-voting-deadline-inforead-onlyproposal-id: uint
get-proposal-resultsread-onlyproposal-id: uint
get-proposal-id-as-stringread-onlyproposal-id: uint
get-proposal-summaryread-onlyproposal-id: uint
is-trusted-contractread-onlycontract: principal
has-passkeyread-onlymember: principal
can-extend-proposalread-onlyproposal-id: uint
get-active-proposals-inforead-only