Source Code

;; ============================================================
;;  Prediction Market (Stacks - Clarity)
;;  Binary YES/NO Market using STX
;; ============================================================

(define-constant ERR-NOT-AUTHORIZED (err u100))
(define-constant ERR-MARKET-NOT-FOUND (err u101))
(define-constant ERR-INVALID-STATE (err u103))
(define-constant ERR-INVALID-AMOUNT (err u104))
(define-constant ERR-ALREADY-RESOLVED (err u105))
(define-constant ERR-NOT-RESOLVED (err u106))
(define-constant ERR-NO-SHARES (err u107))
(define-constant ERR-MARKET-EXISTS (err u102))
(define-constant ERR-MARKET-EXPIRED    (err u108))

;; ------------------------------------------------------------
;; Data Types
;; ------------------------------------------------------------

(define-constant MARKET-STATE-OPEN u0)
(define-constant MARKET-STATE-CLOSED u1)
(define-constant MARKET-STATE-RESOLVED u2)

(define-constant OUTCOME-YES u1)
(define-constant OUTCOME-NO u2)

;; ------------------------------------------------------------
;; Contract Owner (Oracle / Admin)
;; ------------------------------------------------------------

(define-data-var contract-owner principal tx-sender)

;; ------------------------------------------------------------
;; Market Storage
;; ------------------------------------------------------------

(define-map markets
  { market-id: uint }
  {
    creator: principal,
    question: (string-utf8 256),
    end-block: uint,
    state: uint,
    total-yes: uint,
    total-no: uint,
    outcome: uint
  }
)

(define-map positions
  {
    market-id: uint,
    user: principal
  }
  {
    yes-shares: uint,
    no-shares: uint,
    claimed: bool
  }
)

(define-data-var market-counter uint u0)

;; ------------------------------------------------------------
;; Internal Helpers
;; ------------------------------------------------------------

(define-private (is-owner)
  (is-eq tx-sender (var-get contract-owner))
)

(define-private (get-market (id uint))
  (match (map-get? markets { market-id: id })
    market (ok market)
    ERR-MARKET-NOT-FOUND
  )
)

;; ------------------------------------------------------------
;; Create Market
;; ------------------------------------------------------------

(define-public (create-market (question (string-utf8 256)) (duration uint))
  (let
    (
      (new-id (+ (var-get market-counter) u1))
      (end-block (+ stacks-block-height duration))
    )
    (begin
      (asserts! (> duration u0) ERR-INVALID-AMOUNT)
      (map-set markets
        { market-id: new-id }
        {
          creator: tx-sender,
          question: question,
          end-block: end-block,
          state: MARKET-STATE-OPEN,
          total-yes: u0,
          total-no: u0,
          outcome: u0
        }
      )
      (var-set market-counter new-id)
      (ok new-id)
    )
  )
)

;; ------------------------------------------------------------
;; Buy Shares (checks BEFORE transfer)
;; ------------------------------------------------------------

(define-public (buy (market-id uint) (outcome uint) (amount uint))
  (let
    (
      (market-data (unwrap! (get-market market-id) ERR-MARKET-NOT-FOUND))
    )
    (begin
      ;; === all validation first ===
      (asserts! (is-eq (get state market-data) MARKET-STATE-OPEN) ERR-INVALID-STATE)
      (asserts! (< stacks-block-height (get end-block market-data)) ERR-MARKET-EXPIRED)
      (asserts! (> amount u0) ERR-INVALID-AMOUNT)
      (asserts! (or (is-eq outcome OUTCOME-YES) (is-eq outcome OUTCOME-NO)) ERR-INVALID-STATE)

      ;; === now safe to transfer ===
      (try! (stx-transfer? amount tx-sender (as-contract tx-sender)))

      ;; update market totals
      (let
        (
          (updated-market (if (is-eq outcome OUTCOME-YES)
                              (merge market-data { total-yes: (+ (get total-yes market-data) amount) })
                              (merge market-data { total-no: (+ (get total-no market-data) amount) })))
        )
        (map-set markets { market-id: market-id } updated-market)
      )

      ;; update user position
      (let
        (
          (pos (default-to
                 { yes-shares: u0, no-shares: u0, claimed: false }
                 (map-get? positions { market-id: market-id, user: tx-sender })))
        )
        (map-set positions
          { market-id: market-id, user: tx-sender }
          (if (is-eq outcome OUTCOME-YES)
              (merge pos { yes-shares: (+ (get yes-shares pos) amount) })
              (merge pos { no-shares: (+ (get no-shares pos) amount) })))
      )

      (ok true)
    )
  )
)

;; ------------------------------------------------------------
;; Close / Resolve / Claim (unchanged - they were already solid)
;; ------------------------------------------------------------

(define-public (close-market (market-id uint))
  (let ((market-data (unwrap! (get-market market-id) ERR-MARKET-NOT-FOUND)))
    (begin
      (asserts! (is-owner) ERR-NOT-AUTHORIZED)
      (asserts! (is-eq (get state market-data) MARKET-STATE-OPEN) ERR-INVALID-STATE)
      (map-set markets { market-id: market-id }
        (merge market-data { state: MARKET-STATE-CLOSED }))
      (ok true)
    )
  )
)

(define-public (resolve-market (market-id uint) (winning-outcome uint))
  (let ((market-data (unwrap! (get-market market-id) ERR-MARKET-NOT-FOUND)))
    (begin
      (asserts! (is-owner) ERR-NOT-AUTHORIZED)
      (asserts! (not (is-eq (get state market-data) MARKET-STATE-RESOLVED)) ERR-ALREADY-RESOLVED)
      (asserts! (or (is-eq winning-outcome OUTCOME-YES)
                    (is-eq winning-outcome OUTCOME-NO))
                ERR-INVALID-STATE)
      (map-set markets { market-id: market-id }
        (merge market-data {
          state: MARKET-STATE-RESOLVED,
          outcome: winning-outcome
        }))
      (ok true)
    )
  )
)

(define-public (claim (market-id uint))
  (let
    (
      (market-data (unwrap! (get-market market-id) ERR-MARKET-NOT-FOUND))
      (pos (unwrap! (map-get? positions { market-id: market-id, user: tx-sender })
                    ERR-NO-SHARES))
    )
    (begin
      (asserts! (is-eq (get state market-data) MARKET-STATE-RESOLVED) ERR-NOT-RESOLVED)
      (asserts! (not (get claimed pos)) ERR-INVALID-STATE)

      (let
        (
          (winner (get outcome market-data))
          (total-pool (+ (get total-yes market-data) (get total-no market-data)))
          (winning-pool (if (is-eq winner OUTCOME-YES)
                            (get total-yes market-data)
                            (get total-no market-data)))
          (user-shares (if (is-eq winner OUTCOME-YES)
                           (get yes-shares pos)
                           (get no-shares pos)))
        )
        (begin
          (asserts! (> user-shares u0) ERR-NO-SHARES)

          (let ((payout (/ (* user-shares total-pool) winning-pool)))
            (map-set positions
              { market-id: market-id, user: tx-sender }
              (merge pos { claimed: true }))
            (try! (stx-transfer? payout (as-contract tx-sender) tx-sender))
            (ok payout)
          )
        )
      )
    )
  )
)

;; ------------------------------------------------------------
;; Read Only
;; ------------------------------------------------------------

(define-read-only (get-market-data (market-id uint))
  (map-get? markets { market-id: market-id }))

(define-read-only (get-position (market-id uint) (user principal))
  (map-get? positions { market-id: market-id, user: user }))

Functions (9)

FunctionAccessArgs
is-ownerprivate
get-marketprivateid: uint
create-marketpublicquestion: (string-utf8 256
buypublicmarket-id: uint, outcome: uint, amount: uint
close-marketpublicmarket-id: uint
resolve-marketpublicmarket-id: uint, winning-outcome: uint
claimpublicmarket-id: uint
get-market-dataread-onlymarket-id: uint
get-positionread-onlymarket-id: uint, user: principal