Source Code

;; ============================================
;; STAKE-PLEDGE: Accountability Contract
;; ============================================
;; Stake STX on your commitments. Your judge decides if you succeed.
;; Success = get your stake back. Failure = stake is burned or sent to judge.

;; ============================================
;; CONSTANTS
;; ============================================

(define-constant CONTRACT_OWNER tx-sender)

;; Error codes
(define-constant ERR_NOT_AUTHORIZED (err u100))
(define-constant ERR_COMMITMENT_NOT_FOUND (err u101))
(define-constant ERR_ALREADY_JUDGED (err u102))
(define-constant ERR_NOT_JUDGE (err u103))
(define-constant ERR_DEADLINE_NOT_REACHED (err u104))
(define-constant ERR_ALREADY_CLAIMED (err u105))
(define-constant ERR_NOT_CREATOR (err u106))
(define-constant ERR_NOT_SUCCESSFUL (err u107))
(define-constant ERR_INVALID_STAKE (err u108))
(define-constant ERR_INVALID_DEADLINE (err u109))
(define-constant ERR_SELF_JUDGE (err u110))

;; Commitment status
(define-constant STATUS_ACTIVE u1)
(define-constant STATUS_SUCCESS u2)
(define-constant STATUS_FAILED u3)
(define-constant STATUS_CLAIMED u4)

;; Minimum stake: 1 STX (1,000,000 microSTX)
(define-constant MIN_STAKE u1000000)

;; Blocks per day (~144 on Stacks)
(define-constant BLOCKS_PER_DAY u144)

;; ============================================
;; DATA STORAGE
;; ============================================

;; Commitment counter
(define-data-var commitment-counter uint u0)

;; Total STX staked (for stats)
(define-data-var total-staked uint u0)
(define-data-var total-burned uint u0)

;; Commitment data
(define-map commitments uint {
    creator: principal,
    judge: principal,
    description: (string-utf8 280),
    stake: uint,
    deadline: uint,
    status: uint,
    created-at: uint,
    judged-at: (optional uint)
})

;; User stats
(define-map user-stats principal {
    total-commitments: uint,
    successful: uint,
    failed: uint,
    total-staked: uint,
    total-lost: uint
})

;; ============================================
;; CORE FUNCTIONS
;; ============================================

;; Create a new commitment with STX stake
(define-public (create-commitment 
    (description (string-utf8 280))
    (judge principal)
    (deadline-blocks uint))
  (let
    (
      (creator tx-sender)
      (stake (stx-get-balance creator))
      (commitment-id (+ (var-get commitment-counter) u1))
      (deadline (+ stacks-block-height deadline-blocks))
    )
    ;; This function expects STX to be sent via stx-transfer in same tx
    ;; For simplicity, we'll use a separate stake function
    (ok commitment-id)
  )
)

;; Create and stake in one call
(define-public (pledge
    (description (string-utf8 280))
    (judge principal)
    (deadline-blocks uint)
    (stake-amount uint))
  (let
    (
      (creator tx-sender)
      (commitment-id (+ (var-get commitment-counter) u1))
      (deadline (+ stacks-block-height deadline-blocks))
    )
    ;; Validations
    (asserts! (>= stake-amount MIN_STAKE) ERR_INVALID_STAKE)
    (asserts! (> deadline-blocks u0) ERR_INVALID_DEADLINE)
    (asserts! (not (is-eq creator judge)) ERR_SELF_JUDGE)

    ;; Transfer stake to contract
    (try! (stx-transfer? stake-amount creator (as-contract tx-sender)))

    ;; Store commitment
    (map-set commitments commitment-id {
      creator: creator,
      judge: judge,
      description: description,
      stake: stake-amount,
      deadline: deadline,
      status: STATUS_ACTIVE,
      created-at: stacks-block-height,
      judged-at: none
    })

    ;; Update counter
    (var-set commitment-counter commitment-id)

    ;; Update stats
    (var-set total-staked (+ (var-get total-staked) stake-amount))
    (update-user-stats creator stake-amount)

    ;; Emit event
    (print {
      event: "commitment-created",
      id: commitment-id,
      creator: creator,
      judge: judge,
      stake: stake-amount,
      deadline: deadline,
      description: description
    })

    (ok commitment-id)
  )
)

;; Judge marks commitment as success or failure
(define-public (judge-commitment (commitment-id uint) (success bool))
  (let
    (
      (commitment (unwrap! (map-get? commitments commitment-id) ERR_COMMITMENT_NOT_FOUND))
      (caller tx-sender)
    )
    ;; Only judge can call
    (asserts! (is-eq caller (get judge commitment)) ERR_NOT_JUDGE)
    ;; Must still be active
    (asserts! (is-eq (get status commitment) STATUS_ACTIVE) ERR_ALREADY_JUDGED)
    ;; Deadline must be reached
    (asserts! (>= stacks-block-height (get deadline commitment)) ERR_DEADLINE_NOT_REACHED)

    ;; Update status
    (map-set commitments commitment-id 
      (merge commitment {
        status: (if success STATUS_SUCCESS STATUS_FAILED),
        judged-at: (some stacks-block-height)
      })
    )

    ;; If failed, handle the stake
    (if (not success)
      (begin
        ;; Burn the stake (send to a burn address)
        (try! (as-contract (stx-transfer? (get stake commitment) tx-sender (get judge commitment))))
        (var-set total-burned (+ (var-get total-burned) (get stake commitment)))
        (update-user-failure (get creator commitment) (get stake commitment))
      )
      (update-user-success (get creator commitment))
    )

    ;; Emit event
    (print {
      event: "commitment-judged",
      id: commitment-id,
      success: success,
      judge: caller,
      creator: (get creator commitment)
    })

    (ok success)
  )
)

;; Creator claims back stake after success
(define-public (claim-stake (commitment-id uint))
  (let
    (
      (commitment (unwrap! (map-get? commitments commitment-id) ERR_COMMITMENT_NOT_FOUND))
      (caller tx-sender)
    )
    ;; Only creator can claim
    (asserts! (is-eq caller (get creator commitment)) ERR_NOT_CREATOR)
    ;; Must be successful
    (asserts! (is-eq (get status commitment) STATUS_SUCCESS) ERR_NOT_SUCCESSFUL)

    ;; Update status to claimed
    (map-set commitments commitment-id 
      (merge commitment { status: STATUS_CLAIMED })
    )

    ;; Return stake to creator
    (try! (as-contract (stx-transfer? (get stake commitment) tx-sender caller)))

    ;; Emit event
    (print {
      event: "stake-claimed",
      id: commitment-id,
      creator: caller,
      amount: (get stake commitment)
    })

    (ok (get stake commitment))
  )
)

;; ============================================
;; HELPER FUNCTIONS
;; ============================================

(define-private (update-user-stats (user principal) (stake uint))
  (let
    (
      (stats (default-to 
        { total-commitments: u0, successful: u0, failed: u0, total-staked: u0, total-lost: u0 }
        (map-get? user-stats user)))
    )
    (map-set user-stats user {
      total-commitments: (+ (get total-commitments stats) u1),
      successful: (get successful stats),
      failed: (get failed stats),
      total-staked: (+ (get total-staked stats) stake),
      total-lost: (get total-lost stats)
    })
  )
)

(define-private (update-user-success (user principal))
  (let
    (
      (stats (unwrap-panic (map-get? user-stats user)))
    )
    (map-set user-stats user 
      (merge stats { successful: (+ (get successful stats) u1) })
    )
  )
)

(define-private (update-user-failure (user principal) (lost uint))
  (let
    (
      (stats (unwrap-panic (map-get? user-stats user)))
    )
    (map-set user-stats user {
      total-commitments: (get total-commitments stats),
      successful: (get successful stats),
      failed: (+ (get failed stats) u1),
      total-staked: (get total-staked stats),
      total-lost: (+ (get total-lost stats) lost)
    })
  )
)

;; ============================================
;; READ-ONLY FUNCTIONS
;; ============================================

(define-read-only (get-commitment (commitment-id uint))
  (map-get? commitments commitment-id)
)

(define-read-only (get-user-stats (user principal))
  (default-to 
    { total-commitments: u0, successful: u0, failed: u0, total-staked: u0, total-lost: u0 }
    (map-get? user-stats user))
)

(define-read-only (get-total-commitments)
  (var-get commitment-counter)
)

(define-read-only (get-global-stats)
  (ok {
    total-commitments: (var-get commitment-counter),
    total-staked: (var-get total-staked),
    total-burned: (var-get total-burned)
  })
)

(define-read-only (get-commitment-status (commitment-id uint))
  (match (map-get? commitments commitment-id)
    commitment (ok (get status commitment))
    ERR_COMMITMENT_NOT_FOUND
  )
)

(define-read-only (is-deadline-reached (commitment-id uint))
  (match (map-get? commitments commitment-id)
    commitment (ok (>= stacks-block-height (get deadline commitment)))
    ERR_COMMITMENT_NOT_FOUND
  )
)

(define-read-only (get-time-remaining (commitment-id uint))
  (match (map-get? commitments commitment-id)
    commitment 
      (let ((deadline (get deadline commitment)))
        (ok (if (>= stacks-block-height deadline)
          u0
          (- deadline stacks-block-height)))
      )
    ERR_COMMITMENT_NOT_FOUND
  )
)

(define-read-only (blocks-to-days (blocks uint))
  (/ blocks BLOCKS_PER_DAY)
)

(define-read-only (days-to-blocks (days uint))
  (* days BLOCKS_PER_DAY)
)

Functions (16)

FunctionAccessArgs
create-commitmentpublicdescription: (string-utf8 280
pledgepublicdescription: (string-utf8 280
judge-commitmentpubliccommitment-id: uint, success: bool
claim-stakepubliccommitment-id: uint
update-user-statsprivateuser: principal, stake: uint
update-user-successprivateuser: principal
update-user-failureprivateuser: principal, lost: uint
get-commitmentread-onlycommitment-id: uint
get-user-statsread-onlyuser: principal
get-total-commitmentsread-only
get-global-statsread-only
get-commitment-statusread-onlycommitment-id: uint
is-deadline-reachedread-onlycommitment-id: uint
get-time-remainingread-onlycommitment-id: uint
blocks-to-daysread-onlyblocks: uint
days-to-blocksread-onlydays: uint