Source Code

;; title: heropick
;; version: 1.0.0
;; summary: Decentralized prediction market for future events
;; description: A prediction market where users can create markets, place bets on outcomes, and resolve markets with payouts

;; constants
(define-constant ERR-MARKET-NOT-FOUND (err u1001))
(define-constant ERR-MARKET-CLOSED (err u1002))
(define-constant ERR-MARKET-ALREADY-RESOLVED (err u1003))
(define-constant ERR-INVALID-OUTCOME (err u1004))
(define-constant ERR-INSUFFICIENT-BALANCE (err u1005))
(define-constant ERR-UNAUTHORIZED (err u1006))
(define-constant ERR-INVALID-AMOUNT (err u1007))
(define-constant ERR-MARKET-ALREADY-EXISTS (err u1008))

;; data vars
(define-data-var market-counter uint u0)
(define-data-var bet-counter uint u0)

;; data maps
;; Market data: market-id -> (creator, description, category, end-block, resolved, winning-outcome, total-bets-yes, total-bets-no)
(define-map markets uint {
  creator: principal,
  description: (string-ascii 500),
  category: (string-ascii 100),
  end-block: uint,
  resolved: bool,
  winning-outcome: (optional bool),
  total-bets-yes: uint,
  total-bets-no: uint
})

;; Bet data: bet-id -> (market-id, bettor, outcome, amount, timestamp)
(define-map bets uint {
  market-id: uint,
  bettor: principal,
  outcome: bool, ;; true = yes, false = no
  amount: uint,
  timestamp: uint
})

;; Market bets: (market-id, bettor) -> bet-id
(define-map market-bettors (tuple (market-id uint) (bettor principal)) uint)

;; read only functions

;; Get market information
(define-read-only (get-market (market-id uint))
  (map-get? markets market-id)
)

;; Get bet information
(define-read-only (get-bet (bet-id uint))
  (map-get? bets bet-id)
)

;; Get market counter
(define-read-only (get-market-counter)
  (var-get market-counter)
)

;; Get bet counter
(define-read-only (get-bet-counter)
  (var-get bet-counter)
)

;; Get user's bet for a market
(define-read-only (get-user-bet (market-id uint) (bettor principal))
  (let ((key (tuple (market-id market-id) (bettor bettor))))
    (map-get? market-bettors key)
  )
)

;; Get market statistics
(define-read-only (get-market-stats (market-id uint))
  (match (map-get? markets market-id)
    market (ok (tuple
      (total-bets-yes (get total-bets-yes market))
      (total-bets-no (get total-bets-no market))
      (resolved (get resolved market))
    ))
    (err ERR-MARKET-NOT-FOUND)
  )
)

;; public functions

;; Create a new prediction market
(define-public (create-market
    (description (string-ascii 500))
    (category (string-ascii 100))
    (end-block uint)
)
  (let (
    (caller tx-sender)
    (market-id (var-get market-counter))
  )
    ;; Note: End block validation should be done off-chain
    ;; The end-block is stored for reference and used in resolve-market
    
    ;; Create market entry
    (map-set markets market-id {
      creator: caller,
      description: description,
      category: category,
      end-block: end-block,
      resolved: false,
      winning-outcome: none,
      total-bets-yes: u0,
      total-bets-no: u0
    })
    
    ;; Increment market counter
    (var-set market-counter (+ market-id u1))
    
    (ok market-id)
  )
)

;; Place a bet on a market outcome
(define-public (place-bet
    (market-id uint)
    (outcome bool) ;; true = yes, false = no
    (amount uint)
)
  (let (
    (caller tx-sender)
    (bet-id (var-get bet-counter))
    (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
  )
    ;; Validate market is not resolved
    (asserts! (not (get resolved market)) ERR-MARKET-ALREADY-RESOLVED)
    
    ;; Note: Market closure validation should be done off-chain
    ;; The end-block is stored for reference
    
    ;; Validate amount is greater than zero
    (asserts! (> amount u0) ERR-INVALID-AMOUNT)
    
    ;; Check if user already has a bet on this market
    (let ((key (tuple (market-id market-id) (bettor caller))))
      (asserts! (is-none (map-get? market-bettors key)) ERR-MARKET-ALREADY-EXISTS)
    )
    
    ;; Note: Users must send STX with the transaction when calling this function
    ;; The contract will receive the STX automatically
    ;; In a production environment, you would validate the received amount
    
    ;; Create bet entry
    (map-set bets bet-id {
      market-id: market-id,
      bettor: caller,
      outcome: outcome,
      amount: amount,
      timestamp: u0  ;; Note: Timestamp tracking can be added via off-chain indexing
    })
    
    ;; Record bettor for this market
    (map-set market-bettors (tuple (market-id market-id) (bettor caller)) bet-id)
    
    ;; Update market totals
    (if outcome
      (map-set markets market-id {
        creator: (get creator market),
        description: (get description market),
        category: (get category market),
        end-block: (get end-block market),
        resolved: (get resolved market),
        winning-outcome: (get winning-outcome market),
        total-bets-yes: (+ (get total-bets-yes market) amount),
        total-bets-no: (get total-bets-no market)
      })
      (map-set markets market-id {
        creator: (get creator market),
        description: (get description market),
        category: (get category market),
        end-block: (get end-block market),
        resolved: (get resolved market),
        winning-outcome: (get winning-outcome market),
        total-bets-yes: (get total-bets-yes market),
        total-bets-no: (+ (get total-bets-no market) amount)
      })
    )
    
    ;; Increment bet counter
    (var-set bet-counter (+ bet-id u1))
    
    (ok bet-id)
  )
)

;; Resolve a market and distribute winnings
(define-public (resolve-market
    (market-id uint)
    (winning-outcome bool)
)
  (let (
    (caller tx-sender)
    (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
  )
    ;; Only market creator can resolve
    (asserts! (is-eq caller (get creator market)) ERR-UNAUTHORIZED)
    
    ;; Note: End block validation should be done off-chain
    ;; Market creator is responsible for resolving only after end block
    
    ;; Market must not already be resolved
    (asserts! (not (get resolved market)) ERR-MARKET-ALREADY-RESOLVED)
    
    ;; Mark market as resolved
    (map-set markets market-id {
      creator: (get creator market),
      description: (get description market),
      category: (get category market),
      end-block: (get end-block market),
      resolved: true,
      winning-outcome: (some winning-outcome),
      total-bets-yes: (get total-bets-yes market),
      total-bets-no: (get total-bets-no market)
    })
    
    (ok true)
  )
)

;; Claim winnings for a resolved market
;; Returns the payout amount (users need to handle actual STX transfer off-chain or via separate mechanism)
(define-public (claim-winnings (market-id uint))
  (let (
    (caller tx-sender)
    (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
    (key (tuple (market-id market-id) (bettor caller)))
    (bet-id-opt (map-get? market-bettors key))
  )
    ;; Market must be resolved
    (asserts! (get resolved market) ERR-MARKET-ALREADY-RESOLVED)
    
    ;; User must have placed a bet
    (let ((bet-id (unwrap! bet-id-opt ERR-MARKET-NOT-FOUND)))
      (let ((bet (unwrap! (map-get? bets bet-id) ERR-MARKET-NOT-FOUND)))
        ;; Check if bettor bet on the winning outcome
        (let ((winning-outcome (unwrap! (get winning-outcome market) ERR-MARKET-NOT-FOUND)))
          (if (is-eq (get outcome bet) winning-outcome)
            ;; Calculate winnings proportionally
            (let (
              (bet-amount (get amount bet))
              (winning-pool (if winning-outcome (get total-bets-yes market) (get total-bets-no market)))
              (total-pool (+ (get total-bets-yes market) (get total-bets-no market)))
              (payout (/ (* bet-amount total-pool) winning-pool))
            )
              ;; Return payout amount
              ;; Note: Actual STX transfer should be handled via:
              ;; 1. Off-chain service that monitors claims and transfers STX
              ;; 2. Separate contract function with proper STX handling
              ;; 3. Manual payout by market creator
              (ok payout)
            )
            ;; Bet on losing outcome, no payout
            (ok u0)
          )
        )
      )
    )
  )
)

Functions (8)

FunctionAccessArgs
get-marketread-onlymarket-id: uint
get-betread-onlybet-id: uint
get-market-counterread-only
get-bet-counterread-only
get-user-betread-onlymarket-id: uint, bettor: principal
get-market-statsread-onlymarket-id: uint
create-marketpublicdescription: (string-ascii 500
claim-winningspublicmarket-id: uint