Source Code

;; B2S Prediction Market
;; Phase 1: Owner resolve -> Phase 2: DAO vote

(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-OWNER (err u100))
(define-constant ERR-MARKET-NOT-FOUND (err u101))
(define-constant ERR-MARKET-CLOSED (err u102))
(define-constant ERR-MARKET-NOT-RESOLVED (err u103))
(define-constant ERR-MARKET-ALREADY-RESOLVED (err u104))
(define-constant ERR-ALREADY-CLAIMED (err u105))
(define-constant ERR-NO-BET (err u106))
(define-constant ERR-INVALID-AMOUNT (err u107))
(define-constant ERR-DEADLINE-NOT-REACHED (err u109))
(define-constant ERR-MARKET-OPEN (err u110))
(define-constant PLATFORM-FEE-PCT u200)
(define-constant MIN-BET u1000000)

(define-data-var market-counter uint u0)
(define-data-var total-platform-fees uint u0)

(define-map markets
  { market-id: uint }
  {
    creator: principal,
    question: (string-utf8 256),
    category: (string-ascii 32),
    deadline: uint,
    resolved: bool,
    outcome: bool,
    total-yes: uint,
    total-no: uint,
    resolved-by: (optional principal),
  }
)

(define-map bets
  { market-id: uint, bettor: principal }
  {
    yes-amount: uint,
    no-amount: uint,
    claimed: bool,
  }
)

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

(define-read-only (get-bet (market-id uint) (bettor principal))
  (map-get? bets { market-id: market-id, bettor: bettor })
)

(define-read-only (get-market-count)
  (var-get market-counter)
)

(define-read-only (get-platform-fees)
  (var-get total-platform-fees)
)

(define-public (create-market
  (question (string-utf8 256))
  (category (string-ascii 32))
  (deadline-blocks uint))
  (let (
    (market-id (+ (var-get market-counter) u1))
    (deadline (+ block-height deadline-blocks))
  )
    (map-set markets
      { market-id: market-id }
      {
        creator: tx-sender,
        question: question,
        category: category,
        deadline: deadline,
        resolved: false,
        outcome: false,
        total-yes: u0,
        total-no: u0,
        resolved-by: none,
      }
    )
    (var-set market-counter market-id)
    (ok market-id)
  )
)

(define-public (place-bet (market-id uint) (vote bool) (amount uint))
  (let (
    (market (unwrap! (map-get? markets { market-id: market-id }) ERR-MARKET-NOT-FOUND))
    (existing-bet (default-to
      { yes-amount: u0, no-amount: u0, claimed: false }
      (map-get? bets { market-id: market-id, bettor: tx-sender })))
  )
    (asserts! (not (get resolved market)) ERR-MARKET-ALREADY-RESOLVED)
    (asserts! (< block-height (get deadline market)) ERR-MARKET-CLOSED)
    (asserts! (>= amount MIN-BET) ERR-INVALID-AMOUNT)
    (try! (contract-call? .b2s-token transfer amount tx-sender (as-contract tx-sender) none))
    (map-set bets
      { market-id: market-id, bettor: tx-sender }
      {
        yes-amount: (if vote (+ (get yes-amount existing-bet) amount) (get yes-amount existing-bet)),
        no-amount: (if vote (get no-amount existing-bet) (+ (get no-amount existing-bet) amount)),
        claimed: false,
      }
    )
    (map-set markets
      { market-id: market-id }
      (merge market {
        total-yes: (if vote (+ (get total-yes market) amount) (get total-yes market)),
        total-no: (if vote (get total-no market) (+ (get total-no market) amount)),
      })
    )
    (ok true)
  )
)

(define-public (resolve-market (market-id uint) (outcome bool))
  (let (
    (market (unwrap! (map-get? markets { market-id: market-id }) ERR-MARKET-NOT-FOUND))
  )
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-OWNER)
    (asserts! (not (get resolved market)) ERR-MARKET-ALREADY-RESOLVED)
    (asserts! (>= block-height (get deadline market)) ERR-DEADLINE-NOT-REACHED)
    (map-set markets
      { market-id: market-id }
      (merge market {
        resolved: true,
        outcome: outcome,
        resolved-by: (some tx-sender),
      })
    )
    (ok true)
  )
)

(define-public (claim-winnings (market-id uint))
  (let (
    (market (unwrap! (map-get? markets { market-id: market-id }) ERR-MARKET-NOT-FOUND))
    (bet (unwrap! (map-get? bets { market-id: market-id, bettor: tx-sender }) ERR-NO-BET))
    (total-pool (+ (get total-yes market) (get total-no market)))
    (user-side (if (get outcome market) (get yes-amount bet) (get no-amount bet)))
    (winning-pool (if (get outcome market) (get total-yes market) (get total-no market)))
  )
    (asserts! (get resolved market) ERR-MARKET-NOT-RESOLVED)
    (asserts! (not (get claimed bet)) ERR-ALREADY-CLAIMED)
    (asserts! (> user-side u0) ERR-NO-BET)
    (asserts! (> winning-pool u0) ERR-INVALID-AMOUNT)
    (let (
      (gross-payout (/ (* user-side total-pool) winning-pool))
      (fee (/ (* gross-payout PLATFORM-FEE-PCT) u10000))
      (net-payout (- gross-payout fee))
    )
      (map-set bets
        { market-id: market-id, bettor: tx-sender }
        (merge bet { claimed: true })
      )
      (var-set total-platform-fees (+ (var-get total-platform-fees) fee))
      (try! (as-contract (contract-call? .b2s-token transfer net-payout tx-sender tx-sender none)))
      (ok net-payout)
    )
  )
)

(define-public (withdraw-fees (recipient principal))
  (let ((fees (var-get total-platform-fees)))
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-OWNER)
    (asserts! (> fees u0) ERR-INVALID-AMOUNT)
    (var-set total-platform-fees u0)
    (as-contract (contract-call? .b2s-token transfer fees tx-sender recipient none))
  )
)

(define-public (emergency-refund (market-id uint))
  (let (
    (market (unwrap! (map-get? markets { market-id: market-id }) ERR-MARKET-NOT-FOUND))
    (bet (unwrap! (map-get? bets { market-id: market-id, bettor: tx-sender }) ERR-NO-BET))
    (user-total (+ (get yes-amount bet) (get no-amount bet)))
  )
    (asserts! (not (get resolved market)) ERR-MARKET-ALREADY-RESOLVED)
    (asserts! (>= block-height (+ (get deadline market) u1000)) ERR-MARKET-OPEN)
    (asserts! (not (get claimed bet)) ERR-ALREADY-CLAIMED)
    (asserts! (> user-total u0) ERR-NO-BET)
    (map-set bets
      { market-id: market-id, bettor: tx-sender }
      (merge bet { claimed: true })
    )
    (as-contract (contract-call? .b2s-token transfer user-total tx-sender tx-sender none))
  )
)

Functions (10)

FunctionAccessArgs
get-marketread-onlymarket-id: uint
get-betread-onlymarket-id: uint, bettor: principal
get-market-countread-only
get-platform-feesread-only
create-marketpublicquestion: (string-utf8 256
place-betpublicmarket-id: uint, vote: bool, amount: uint
resolve-marketpublicmarket-id: uint, outcome: bool
claim-winningspublicmarket-id: uint
withdraw-feespublicrecipient: principal
emergency-refundpublicmarket-id: uint