;; ============================================================
;; 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 }))