Source Code

;; Bitcoin-Anchored Prediction Market
;; A decentralized prediction market that settles based on Bitcoin block data
;; Uses Clarity 4 features: get-burn-block-info?, tenure-height, bitwise operations

;; Import SIP-010 trait for token interactions
(use-trait sip-010-trait-v4 .sip-010-trait-v5.sip-010-trait-v4)

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

;; Error codes
(define-constant ERR-NOT-AUTHORIZED (err u1000))
(define-constant ERR-MARKET-NOT-FOUND (err u1001))
(define-constant ERR-MARKET-CLOSED (err u1002))
(define-constant ERR-MARKET-NOT-SETTLED (err u1003))
(define-constant ERR-MARKET-ALREADY-SETTLED (err u1004))
(define-constant ERR-INVALID-OUTCOME (err u1005))
(define-constant ERR-INSUFFICIENT-FUNDS (err u1006))
(define-constant ERR-BET-TOO-SMALL (err u1007))
(define-constant ERR-ALREADY-CLAIMED (err u1008))
(define-constant ERR-NO-POSITION (err u1009))
(define-constant ERR-BURN-BLOCK-NOT-AVAILABLE (err u1010))
(define-constant ERR-INVALID-MARKET-PARAMS (err u1011))
(define-constant ERR-MARKET-NOT-READY-TO-SETTLE (err u1012))
(define-constant ERR-TRANSFER-FAILED (err u1013))
(define-constant ERR-CASHOUT-NOT-ALLOWED (err u1014))
(define-constant ERR-CHALLENGE-WINDOW (err u1015))
(define-constant ERR-ORACLE-NOT-AUTHORIZED (err u1016))
(define-constant ERR-ORACLE-NOT-SET (err u1017))
(define-constant ERR-ORACLE-ALREADY-SETTLED (err u1018))
(define-constant ERR-MARKET-EXPIRED (err u1019))
(define-constant ERR-MAX-POOL-EXCEEDED (err u1020))

;; Platform constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant MARKET-CREATION-FEE u5000000) ;; 5 STX in microSTX
(define-constant PLATFORM-FEE-PERCENT u200) ;; 2% (basis points)
(define-constant CREATOR-FEE-PERCENT u100) ;; 1% (basis points)
(define-constant LP-FEE-PERCENT u100) ;; 1% (basis points)
(define-constant LP-FEE-PRECISION u1000000)
(define-constant MIN-BET-AMOUNT u1000000) ;; 1 STX minimum bet
(define-constant BLOCKS-BEFORE-SETTLEMENT u6) ;; Wait 6 Bitcoin blocks for finality
(define-constant CHALLENGE-WINDOW-BLOCKS u6) ;; Dispute window after settlement
(define-constant MIN-MARKET-LIQUIDITY u5000000) ;; 5 STX minimum total pool
(define-constant MAX-MARKET-LIQUIDITY u100000000000) ;; 100k STX hard ceiling

;; Outcome bit flags (Clarity 4 bitwise operations)
(define-constant OUTCOME-A u1) ;; 0001
(define-constant OUTCOME-B u2) ;; 0010
(define-constant OUTCOME-C u4) ;; 0100
(define-constant OUTCOME-D u8) ;; 1000

;; =============================================
;; DATA VARIABLES
;; =============================================

(define-data-var market-nonce uint u0)
(define-data-var total-fees-collected uint u0)
(define-data-var platform-paused bool false)
(define-data-var oracle-admin principal tx-sender)

;; =============================================
;; DATA MAPS
;; =============================================

;; Main market storage
(define-map markets
  uint ;; market-id
  {
    creator: principal,
    title: (string-utf8 256),
    description: (string-utf8 1024),
    category: (string-ascii 32),
    settlement-burn-height: uint, ;; Bitcoin block height for settlement
    settlement-type: (string-ascii 32), ;; "hash-even-odd", "hash-range", "oracle"
    oracle: (optional principal),
    oracle-outcome: (optional uint),
    max-pool: uint,
    possible-outcomes: uint, ;; Bitwise packed outcomes (e.g., u3 = OUTCOME-A | OUTCOME-B)
    total-pool: uint,
    outcome-a-pool: uint,
    outcome-b-pool: uint,
    outcome-c-pool: uint,
    outcome-d-pool: uint,
    winning-outcome: (optional uint),
    settled: bool,
    expired: bool,
    expired-at-burn-height: (optional uint),
    settled-at-burn-height: (optional uint),
    settlement-block-hash: (optional (buff 32)),
    created-at-burn-height: uint,
    created-at-stacks-height: uint,
  }
)

;; User positions in markets
(define-map user-positions
  {
    market-id: uint,
    user: principal,
  }
  {
    outcome-a-amount: uint,
    outcome-b-amount: uint,
    outcome-c-amount: uint,
    outcome-d-amount: uint,
    total-invested: uint,
    claimed: bool,
  }
)

;; User stats (using bitwise flags for achievements)
(define-map user-stats
  principal
  {
    markets-created: uint,
    total-bets-placed: uint,
    total-winnings: uint,
    total-losses: uint,
    achievements: uint, ;; Bitwise packed achievement flags
  }
)

;; Market participants list
(define-map market-participants
  uint ;; market-id
  (list 500 principal)
)

;; Creator fee balances per market
(define-map creator-fees
  uint ;; market-id
  uint ;; accrued fees (microSTX)
)

;; Liquidity provider state per market
(define-map lp-state
  uint ;; market-id
  {
    total-liquidity: uint,
    acc-fee-per-liquidity: uint,
  }
)

;; Liquidity provider positions
(define-map lp-positions
  {
    market-id: uint,
    user: principal,
  }
  {
    liquidity: uint,
    reward-debt: uint,
  }
)

;; =============================================
;; CLARITY 4 HELPER FUNCTIONS
;; =============================================

;; Get current Bitcoin block height (Clarity 4: tenure-height)
(define-read-only (get-current-burn-height)
  tenure-height
)

;; Get Bitcoin block hash for verification (Clarity 4: get-burn-block-info?)
(define-read-only (get-burn-block-hash (burn-height uint))
  (get-burn-block-info? header-hash burn-height)
)

;; Get user's STX account info (Clarity 4: stx-account)
(define-read-only (get-user-stx-info (user principal))
  (stx-account user)
)

;; Check if user has locked STX (premium user feature)
(define-read-only (is-premium-user (user principal))
  (let ((account (stx-account user)))
    (>= (get locked account) u100000000)
    ;; 100+ STX locked = premium
  )
)

;; Expire a market if settlement height passed without enough liquidity
(define-public (expire-market (market-id uint))
  (let (
      (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
      (current-burn-height tenure-height)
    )
    (asserts! (not (get settled market)) ERR-MARKET-ALREADY-SETTLED)
    (asserts! (not (get expired market)) ERR-MARKET-EXPIRED)
    (asserts! (>= current-burn-height (get settlement-burn-height market))
      ERR-MARKET-NOT-READY-TO-SETTLE
    )
    (asserts! (< (get total-pool market) MIN-MARKET-LIQUIDITY) ERR-MARKET-EXPIRED)
    (map-set markets market-id
      (merge market {
        settled: true,
        expired: true,
        expired-at-burn-height: (some current-burn-height),
        settled-at-burn-height: none,
        winning-outcome: none,
      })
    )
    (ok true)
  )
)

;; Extract specific byte from hash using slice (Clarity 4: slice?)
(define-read-only (get-hash-byte
    (hash (buff 32))
    (index uint)
  )
  (slice? hash index (+ index u1))
)

;; Convert outcome to string for display (Clarity 4: int-to-ascii)
(define-read-only (outcome-to-string (outcome uint))
  (if (is-eq outcome OUTCOME-A)
    "A"
    (if (is-eq outcome OUTCOME-B)
      "B"
      (if (is-eq outcome OUTCOME-C)
        "C"
        (if (is-eq outcome OUTCOME-D)
          "D"
          "?"
        )
      )
    )
  )
)

;; =============================================
;; BITWISE OPERATION HELPERS (Clarity 4)
;; =============================================

;; Check if an outcome is enabled in the packed outcomes
(define-read-only (is-outcome-enabled
    (packed-outcomes uint)
    (outcome uint)
  )
  (> (bit-and packed-outcomes outcome) u0)
)

;; Pack multiple outcomes into single value
(define-read-only (pack-outcomes
    (a bool)
    (b bool)
    (c bool)
    (d bool)
  )
  (bit-or (bit-or (if a
    OUTCOME-A
    u0
  ) (if b
    OUTCOME-B
    u0
  ))
    (bit-or (if c
      OUTCOME-C
      u0
    )
      (if d
        OUTCOME-D
        u0
      ))
  )
)

;; Count enabled outcomes
(define-read-only (count-enabled-outcomes (packed uint))
  (+
    (+ (if (is-outcome-enabled packed OUTCOME-A)
      u1
      u0
    )
      (if (is-outcome-enabled packed OUTCOME-B)
        u1
        u0
      ))
    (+ (if (is-outcome-enabled packed OUTCOME-C)
      u1
      u0
    )
      (if (is-outcome-enabled packed OUTCOME-D)
        u1
        u0
      ))
  )
)

;; Determine winning outcome from Bitcoin block hash
;; Uses first byte of hash to determine outcome
(define-read-only (determine-outcome-from-hash
    (block-hash (buff 32))
    (num-outcomes uint)
  )
  (let (
      ;; Get first byte of hash using element-at and convert to uint
      (first-byte-opt (element-at? block-hash u0))
      (first-byte (match first-byte-opt
        byte (buff-to-uint-be byte)
        u0
      ))
      (outcome-index (mod first-byte num-outcomes))
    )
    ;; Map index to outcome flag
    (if (is-eq outcome-index u0)
      OUTCOME-A
      (if (is-eq outcome-index u1)
        OUTCOME-B
        (if (is-eq outcome-index u2)
          OUTCOME-C
          OUTCOME-D
        )
      )
    )
  )
)

;; Check if hash is "even" (last byte is even number)
(define-read-only (is-hash-even (block-hash (buff 32)))
  (let (
      (last-byte-opt (element-at? block-hash u31))
      (last-byte (match last-byte-opt
        byte (buff-to-uint-be byte)
        u0
      ))
    )
    (is-eq (mod last-byte u2) u0)
  )
)

;; =============================================
;; PUBLIC FUNCTIONS - MARKET CREATION
;; =============================================

;; Create a new binary prediction market (2 outcomes)
(define-public (create-binary-market
    (title (string-utf8 256))
    (description (string-utf8 1024))
    (category (string-ascii 32))
    (oracle (optional principal))
    (max-pool uint)
    (settlement-burn-height uint)
  )
  (let (
      (market-id (var-get market-nonce))
      (current-burn-height tenure-height)
    )
    ;; Validate parameters
    (asserts!
      (> settlement-burn-height (+ current-burn-height BLOCKS-BEFORE-SETTLEMENT))
      ERR-INVALID-MARKET-PARAMS
    )
    (asserts! (>= max-pool MIN-MARKET-LIQUIDITY) ERR-INVALID-MARKET-PARAMS)
    (asserts! (<= max-pool MAX-MARKET-LIQUIDITY) ERR-INVALID-MARKET-PARAMS)

    ;; Charge market creation fee
    (try! (stx-transfer? MARKET-CREATION-FEE tx-sender CONTRACT-OWNER))

    ;; Create market
    (map-set markets market-id {
      creator: tx-sender,
      title: title,
      description: description,
      category: category,
      settlement-burn-height: settlement-burn-height,
      settlement-type: (if (is-some oracle)
        "oracle"
        "hash-even-odd"
      ),
      oracle: oracle,
      oracle-outcome: none,
      max-pool: max-pool,
      possible-outcomes: (pack-outcomes true true false false), ;; Only A and B
      total-pool: u0,
      outcome-a-pool: u0,
      outcome-b-pool: u0,
      outcome-c-pool: u0,
      outcome-d-pool: u0,
      winning-outcome: none,
      settled: false,
      expired: false,
      expired-at-burn-height: none,
      settled-at-burn-height: none,
      settlement-block-hash: none,
      created-at-burn-height: current-burn-height,
      created-at-stacks-height: stacks-block-height,
    })

    ;; Initialize participants list
    (map-set market-participants market-id (list))
    (map-set creator-fees market-id u0)
    (map-set lp-state market-id {
      total-liquidity: u0,
      acc-fee-per-liquidity: u0,
    })

    ;; Update user stats
    (update-user-markets-created tx-sender)

    ;; Increment nonce
    (var-set market-nonce (+ market-id u1))
    (var-set total-fees-collected
      (+ (var-get total-fees-collected) MARKET-CREATION-FEE)
    )

    (ok market-id)
  )
)

;; Create a multi-outcome market (up to 4 outcomes)
(define-public (create-multi-market
    (title (string-utf8 256))
    (description (string-utf8 1024))
    (category (string-ascii 32))
    (oracle (optional principal))
    (max-pool uint)
    (settlement-burn-height uint)
    (enable-outcome-a bool)
    (enable-outcome-b bool)
    (enable-outcome-c bool)
    (enable-outcome-d bool)
  )
  (let (
      (market-id (var-get market-nonce))
      (current-burn-height tenure-height)
      (packed-outcomes (pack-outcomes enable-outcome-a enable-outcome-b enable-outcome-c
        enable-outcome-d
      ))
    )
    ;; Validate parameters
    (asserts!
      (> settlement-burn-height (+ current-burn-height BLOCKS-BEFORE-SETTLEMENT))
      ERR-INVALID-MARKET-PARAMS
    )
    (asserts! (>= (count-enabled-outcomes packed-outcomes) u2)
      ERR-INVALID-MARKET-PARAMS
    )
    (asserts! (>= max-pool MIN-MARKET-LIQUIDITY) ERR-INVALID-MARKET-PARAMS)
    (asserts! (<= max-pool MAX-MARKET-LIQUIDITY) ERR-INVALID-MARKET-PARAMS)

    ;; Charge market creation fee
    (try! (stx-transfer? MARKET-CREATION-FEE tx-sender CONTRACT-OWNER))

    ;; Create market
    (map-set markets market-id {
      creator: tx-sender,
      title: title,
      description: description,
      category: category,
      settlement-burn-height: settlement-burn-height,
      settlement-type: (if (is-some oracle)
        "oracle"
        "hash-range"
      ),
      oracle: oracle,
      oracle-outcome: none,
      max-pool: max-pool,
      possible-outcomes: packed-outcomes,
      total-pool: u0,
      outcome-a-pool: u0,
      outcome-b-pool: u0,
      outcome-c-pool: u0,
      outcome-d-pool: u0,
      winning-outcome: none,
      settled: false,
      expired: false,
      expired-at-burn-height: none,
      settled-at-burn-height: none,
      settlement-block-hash: none,
      created-at-burn-height: current-burn-height,
      created-at-stacks-height: stacks-block-height,
    })

    ;; Initialize participants list
    (map-set market-participants market-id (list))
    (map-set creator-fees market-id u0)
    (map-set lp-state market-id {
      total-liquidity: u0,
      acc-fee-per-liquidity: u0,
    })

    ;; Update user stats
    (update-user-markets-created tx-sender)

    ;; Increment nonce
    (var-set market-nonce (+ market-id u1))
    (var-set total-fees-collected
      (+ (var-get total-fees-collected) MARKET-CREATION-FEE)
    )

    (ok market-id)
  )
)

;; =============================================
;; PUBLIC FUNCTIONS - BETTING
;; =============================================

;; Place a bet on outcome A
(define-public (bet-outcome-a
    (market-id uint)
    (amount uint)
  )
  (place-bet-internal market-id amount OUTCOME-A)
)

;; Place a bet on outcome B
(define-public (bet-outcome-b
    (market-id uint)
    (amount uint)
  )
  (place-bet-internal market-id amount OUTCOME-B)
)

;; Place a bet on outcome C
(define-public (bet-outcome-c
    (market-id uint)
    (amount uint)
  )
  (place-bet-internal market-id amount OUTCOME-C)
)

;; Place a bet on outcome D
(define-public (bet-outcome-d
    (market-id uint)
    (amount uint)
  )
  (place-bet-internal market-id amount OUTCOME-D)
)

;; Provide liquidity to improve odds
(define-public (add-liquidity
    (market-id uint)
    (amount uint)
  )
  (let (
      (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
      (current-burn-height tenure-height)
      (lp (default-to {
        liquidity: u0,
        reward-debt: u0,
      }
        (map-get? lp-positions {
          market-id: market-id,
          user: tx-sender,
        })
      ))
      (state (default-to {
        total-liquidity: u0,
        acc-fee-per-liquidity: u0,
      }
        (map-get? lp-state market-id)
      ))
      (total-pool (get total-pool market))
      (num-outcomes (count-enabled-outcomes (get possible-outcomes market)))
    )
    (asserts! (not (get settled market)) ERR-MARKET-CLOSED)
    (asserts! (< current-burn-height (get settlement-burn-height market))
      ERR-MARKET-CLOSED
    )
    (asserts! (>= amount MIN-BET-AMOUNT) ERR-BET-TOO-SMALL)
    (asserts! (<= (+ (get total-pool market) amount) (get max-pool market))
      ERR-MAX-POOL-EXCEEDED
    )
    (asserts! (>= num-outcomes u2) ERR-INVALID-MARKET-PARAMS)

    ;; Harvest any pending LP fees
    (unwrap! (distribute-lp-fee market-id tx-sender lp state) ERR-TRANSFER-FAILED)

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

    ;; Allocate liquidity to pools
    (if (is-eq total-pool u0)
      (let (
          (base (/ amount num-outcomes))
          (remainder (- amount (* base num-outcomes)))
          (a-enabled (is-outcome-enabled (get possible-outcomes market) OUTCOME-A))
          (b-enabled (is-outcome-enabled (get possible-outcomes market) OUTCOME-B))
          (c-enabled (is-outcome-enabled (get possible-outcomes market) OUTCOME-C))
          (d-enabled (is-outcome-enabled (get possible-outcomes market) OUTCOME-D))
          (a-add (if a-enabled
            (+ base remainder)
            u0
          ))
          (b-add (if b-enabled
            base
            u0
          ))
          (c-add (if c-enabled
            base
            u0
          ))
          (d-add (if d-enabled
            base
            u0
          ))
        )
        (map-set markets market-id
          (merge market {
            total-pool: (+ total-pool amount),
            outcome-a-pool: (+ (get outcome-a-pool market) a-add),
            outcome-b-pool: (+ (get outcome-b-pool market) b-add),
            outcome-c-pool: (+ (get outcome-c-pool market) c-add),
            outcome-d-pool: (+ (get outcome-d-pool market) d-add),
          })
        )
      )
      (let (
          (pool-a (get outcome-a-pool market))
          (pool-b (get outcome-b-pool market))
          (pool-c (get outcome-c-pool market))
          (pool-d (get outcome-d-pool market))
          (a-add (/ (* pool-a amount) total-pool))
          (b-add (/ (* pool-b amount) total-pool))
          (c-add (/ (* pool-c amount) total-pool))
          (d-add (/ (* pool-d amount) total-pool))
          (sum-add (+ a-add b-add c-add d-add))
          (remainder (- amount sum-add))
          (a-final (+ a-add remainder))
        )
        (map-set markets market-id
          (merge market {
            total-pool: (+ total-pool amount),
            outcome-a-pool: (+ pool-a a-final),
            outcome-b-pool: (+ pool-b b-add),
            outcome-c-pool: (+ pool-c c-add),
            outcome-d-pool: (+ pool-d d-add),
          })
        )
      )
    )

    ;; Update LP position + state
    (map-set lp-positions {
      market-id: market-id,
      user: tx-sender,
    } {
      liquidity: (+ (get liquidity lp) amount),
      reward-debt: (calculate-reward-debt (+ (get liquidity lp) amount)
        (get acc-fee-per-liquidity state)
      ),
    })
    (map-set lp-state market-id
      (merge state { total-liquidity: (+ (get total-liquidity state) amount) })
    )

    (ok {
      market-id: market-id,
      amount: amount,
    })
  )
)

;; Withdraw liquidity before settlement
(define-public (remove-liquidity
    (market-id uint)
    (amount uint)
  )
  (let (
      (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
      (current-burn-height tenure-height)
      (lp (unwrap!
        (map-get? lp-positions {
          market-id: market-id,
          user: tx-sender,
        })
        ERR-NO-POSITION
      ))
      (state (default-to {
        total-liquidity: u0,
        acc-fee-per-liquidity: u0,
      }
        (map-get? lp-state market-id)
      ))
      (total-pool (get total-pool market))
    )
    (asserts! (not (get settled market)) ERR-MARKET-CLOSED)
    (asserts! (< current-burn-height (get settlement-burn-height market))
      ERR-MARKET-CLOSED
    )
    (asserts! (> amount u0) ERR-INVALID-MARKET-PARAMS)
    (asserts! (>= (get liquidity lp) amount) ERR-INSUFFICIENT-FUNDS)
    (asserts! (>= total-pool amount) ERR-INSUFFICIENT-FUNDS)

    ;; Harvest any pending LP fees
    (unwrap! (distribute-lp-fee market-id tx-sender lp state) ERR-TRANSFER-FAILED)

    (let (
        (pool-a (get outcome-a-pool market))
        (pool-b (get outcome-b-pool market))
        (pool-c (get outcome-c-pool market))
        (pool-d (get outcome-d-pool market))
        (a-rem (/ (* pool-a amount) total-pool))
        (b-rem (/ (* pool-b amount) total-pool))
        (c-rem (/ (* pool-c amount) total-pool))
        (d-rem (/ (* pool-d amount) total-pool))
        (sum-rem (+ a-rem b-rem c-rem d-rem))
        (remainder (- amount sum-rem))
        (a-final (+ a-rem remainder))
      )
      (map-set markets market-id
        (merge market {
          total-pool: (- total-pool amount),
          outcome-a-pool: (- pool-a a-final),
          outcome-b-pool: (- pool-b b-rem),
          outcome-c-pool: (- pool-c c-rem),
          outcome-d-pool: (- pool-d d-rem),
        })
      )
    )

    (map-set lp-positions {
      market-id: market-id,
      user: tx-sender,
    } {
      liquidity: (- (get liquidity lp) amount),
      reward-debt: (calculate-reward-debt (- (get liquidity lp) amount)
        (get acc-fee-per-liquidity state)
      ),
    })
    (map-set lp-state market-id
      (merge state { total-liquidity: (- (get total-liquidity state) amount) })
    )

    (as-contract (stx-transfer? amount tx-sender contract-caller))
  )
)

;; Cash out a position before settlement based on current pool odds
(define-public (cash-out
    (market-id uint)
    (outcome uint)
    (amount uint)
  )
  (let (
      (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
      (current-burn-height tenure-height)
      (position (unwrap!
        (map-get? user-positions {
          market-id: market-id,
          user: tx-sender,
        })
        ERR-NO-POSITION
      ))
      (outcome-pool (get-pool-for-outcome market outcome))
      (user-outcome-amount (get-position-for-outcome position outcome))
      (total-pool (get total-pool market))
    )
    (asserts! (not (get settled market)) ERR-MARKET-CLOSED)
    (asserts! (< current-burn-height (get settlement-burn-height market))
      ERR-MARKET-CLOSED
    )
    (asserts! (is-outcome-enabled (get possible-outcomes market) outcome)
      ERR-INVALID-OUTCOME
    )
    (asserts! (> amount u0) ERR-INVALID-MARKET-PARAMS)
    (asserts! (>= user-outcome-amount amount) ERR-INSUFFICIENT-FUNDS)
    (asserts! (>= outcome-pool amount) ERR-INSUFFICIENT-FUNDS)
    (asserts! (> outcome-pool u0) ERR-CASHOUT-NOT-ALLOWED)

    (let (
        (gross-payout (/ (* amount total-pool) outcome-pool))
        (platform-fee (/ (* gross-payout PLATFORM-FEE-PERCENT) u10000))
        (creator-fee (/ (* gross-payout CREATOR-FEE-PERCENT) u10000))
        (lp-fee (/ (* gross-payout LP-FEE-PERCENT) u10000))
        (net-payout (- gross-payout (+ platform-fee creator-fee lp-fee)))
        (other-total (- total-pool outcome-pool))
        (other-reduction (if (>= gross-payout amount)
          (- gross-payout amount)
          u0
        ))
        (pool-a (get outcome-a-pool market))
        (pool-b (get outcome-b-pool market))
        (pool-c (get outcome-c-pool market))
        (pool-d (get outcome-d-pool market))
        (r-a-base (if (or (is-eq outcome OUTCOME-A) (is-eq other-total u0))
          u0
          (/ (* pool-a other-reduction) other-total)
        ))
        (r-b-base (if (or (is-eq outcome OUTCOME-B) (is-eq other-total u0))
          u0
          (/ (* pool-b other-reduction) other-total)
        ))
        (r-c-base (if (or (is-eq outcome OUTCOME-C) (is-eq other-total u0))
          u0
          (/ (* pool-c other-reduction) other-total)
        ))
        (r-d-base (if (or (is-eq outcome OUTCOME-D) (is-eq other-total u0))
          u0
          (/ (* pool-d other-reduction) other-total)
        ))
        (sum-base (+ r-a-base r-b-base r-c-base r-d-base))
        (remainder (if (>= other-reduction sum-base)
          (- other-reduction sum-base)
          u0
        ))
        (remainder-rec (if (not (is-eq outcome OUTCOME-D))
          OUTCOME-D
          (if (not (is-eq outcome OUTCOME-C))
            OUTCOME-C
            (if (not (is-eq outcome OUTCOME-B))
              OUTCOME-B
              OUTCOME-A
            )
          )
        ))
        (r-a-other (if (and (not (is-eq outcome OUTCOME-A)) (is-eq remainder-rec OUTCOME-A))
          (+ r-a-base remainder)
          r-a-base
        ))
        (r-b-other (if (and (not (is-eq outcome OUTCOME-B)) (is-eq remainder-rec OUTCOME-B))
          (+ r-b-base remainder)
          r-b-base
        ))
        (r-c-other (if (and (not (is-eq outcome OUTCOME-C)) (is-eq remainder-rec OUTCOME-C))
          (+ r-c-base remainder)
          r-c-base
        ))
        (r-d-other (if (and (not (is-eq outcome OUTCOME-D)) (is-eq remainder-rec OUTCOME-D))
          (+ r-d-base remainder)
          r-d-base
        ))
        (r-a (if (is-eq outcome OUTCOME-A)
          amount
          r-a-other
        ))
        (r-b (if (is-eq outcome OUTCOME-B)
          amount
          r-b-other
        ))
        (r-c (if (is-eq outcome OUTCOME-C)
          amount
          r-c-other
        ))
        (r-d (if (is-eq outcome OUTCOME-D)
          amount
          r-d-other
        ))
      )
      (map-set markets market-id
        (merge market {
          total-pool: (- total-pool gross-payout),
          outcome-a-pool: (- pool-a r-a),
          outcome-b-pool: (- pool-b r-b),
          outcome-c-pool: (- pool-c r-c),
          outcome-d-pool: (- pool-d r-d),
        })
      )

      (map-set user-positions {
        market-id: market-id,
        user: tx-sender,
      } {
        outcome-a-amount: (if (is-eq outcome OUTCOME-A)
          (- (get outcome-a-amount position) amount)
          (get outcome-a-amount position)
        ),
        outcome-b-amount: (if (is-eq outcome OUTCOME-B)
          (- (get outcome-b-amount position) amount)
          (get outcome-b-amount position)
        ),
        outcome-c-amount: (if (is-eq outcome OUTCOME-C)
          (- (get outcome-c-amount position) amount)
          (get outcome-c-amount position)
        ),
        outcome-d-amount: (if (is-eq outcome OUTCOME-D)
          (- (get outcome-d-amount position) amount)
          (get outcome-d-amount position)
        ),
        total-invested: (- (get total-invested position) amount),
        claimed: false,
      })

      (var-set total-fees-collected
        (+ (var-get total-fees-collected) platform-fee)
      )
      (map-set creator-fees market-id
        (+ (default-to u0 (map-get? creator-fees market-id)) creator-fee)
      )
      (unwrap! (accrue-lp-fee market-id lp-fee) ERR-TRANSFER-FAILED)

      (if (> net-payout u0)
        (begin
          (try! (as-contract (stx-transfer? net-payout tx-sender contract-caller)))
          (ok {
            market-id: market-id,
            outcome: outcome,
            gross-payout: gross-payout,
            platform-fee: platform-fee,
            creator-fee: creator-fee,
            lp-fee: lp-fee,
            net-payout: net-payout,
          })
        )
        (ok {
          market-id: market-id,
          outcome: outcome,
          gross-payout: gross-payout,
          platform-fee: platform-fee,
          creator-fee: creator-fee,
          lp-fee: lp-fee,
          net-payout: net-payout,
        })
      )
    )
  )
)

;; Internal bet placement logic
(define-private (place-bet-internal
    (market-id uint)
    (amount uint)
    (outcome uint)
  )
  (let (
      (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
      (current-burn-height tenure-height)
      (current-position (default-to {
        outcome-a-amount: u0,
        outcome-b-amount: u0,
        outcome-c-amount: u0,
        outcome-d-amount: u0,
        total-invested: u0,
        claimed: false,
      }
        (map-get? user-positions {
          market-id: market-id,
          user: tx-sender,
        })
      ))
    )
    ;; Validate bet
    (asserts! (not (get settled market)) ERR-MARKET-CLOSED)
    (asserts! (< current-burn-height (get settlement-burn-height market))
      ERR-MARKET-CLOSED
    )
    (asserts! (is-outcome-enabled (get possible-outcomes market) outcome)
      ERR-INVALID-OUTCOME
    )
    (asserts! (>= amount MIN-BET-AMOUNT) ERR-BET-TOO-SMALL)
    (asserts! (<= (+ (get total-pool market) amount) (get max-pool market))
      ERR-MAX-POOL-EXCEEDED
    )

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

    ;; Update market pools
    (map-set markets market-id
      (merge market {
        total-pool: (+ (get total-pool market) amount),
        outcome-a-pool: (if (is-eq outcome OUTCOME-A)
          (+ (get outcome-a-pool market) amount)
          (get outcome-a-pool market)
        ),
        outcome-b-pool: (if (is-eq outcome OUTCOME-B)
          (+ (get outcome-b-pool market) amount)
          (get outcome-b-pool market)
        ),
        outcome-c-pool: (if (is-eq outcome OUTCOME-C)
          (+ (get outcome-c-pool market) amount)
          (get outcome-c-pool market)
        ),
        outcome-d-pool: (if (is-eq outcome OUTCOME-D)
          (+ (get outcome-d-pool market) amount)
          (get outcome-d-pool market)
        ),
      })
    )

    ;; Update user position
    (map-set user-positions {
      market-id: market-id,
      user: tx-sender,
    } {
      outcome-a-amount: (if (is-eq outcome OUTCOME-A)
        (+ (get outcome-a-amount current-position) amount)
        (get outcome-a-amount current-position)
      ),
      outcome-b-amount: (if (is-eq outcome OUTCOME-B)
        (+ (get outcome-b-amount current-position) amount)
        (get outcome-b-amount current-position)
      ),
      outcome-c-amount: (if (is-eq outcome OUTCOME-C)
        (+ (get outcome-c-amount current-position) amount)
        (get outcome-c-amount current-position)
      ),
      outcome-d-amount: (if (is-eq outcome OUTCOME-D)
        (+ (get outcome-d-amount current-position) amount)
        (get outcome-d-amount current-position)
      ),
      total-invested: (+ (get total-invested current-position) amount),
      claimed: false,
    })

    ;; Update user stats
    (update-user-bets-placed tx-sender amount)

    ;; Add to participants list
    (add-participant market-id tx-sender)

    (ok {
      market-id: market-id,
      outcome: outcome,
      amount: amount,
    })
  )
)

;; =============================================
;; PUBLIC FUNCTIONS - SETTLEMENT
;; =============================================

;; Settle market using Bitcoin block hash (anyone can call after settlement height)
(define-public (settle-market (market-id uint))
  (let (
      (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
      (settlement-height (get settlement-burn-height market))
      (current-burn-height tenure-height)
      ;; Clarity 4: Get Bitcoin block hash for settlement
      (block-hash (unwrap! (get-burn-block-info? header-hash settlement-height)
        ERR-BURN-BLOCK-NOT-AVAILABLE
      ))
      (num-outcomes (count-enabled-outcomes (get possible-outcomes market)))
      (oracle-outcome (get oracle-outcome market))
      (winning-outcome (if (is-eq (get settlement-type market) "oracle")
        (unwrap! oracle-outcome ERR-ORACLE-NOT-SET)
        (if (is-eq (get settlement-type market) "hash-even-odd")
          (if (is-hash-even block-hash)
            OUTCOME-A
            OUTCOME-B
          )
          (determine-outcome-from-hash block-hash num-outcomes)
        )
      ))
    )
    ;; Validate settlement
    (asserts! (not (get settled market)) ERR-MARKET-ALREADY-SETTLED)
    (asserts! (not (get expired market)) ERR-MARKET-EXPIRED)
    (asserts! (>= (get total-pool market) MIN-MARKET-LIQUIDITY)
      ERR-MARKET-EXPIRED
    )
    (asserts!
      (>= current-burn-height (+ settlement-height BLOCKS-BEFORE-SETTLEMENT))
      ERR-MARKET-NOT-READY-TO-SETTLE
    )

    ;; Update market with settlement info
    (map-set markets market-id
      (merge market {
        settled: true,
        winning-outcome: (some winning-outcome),
        settled-at-burn-height: (some current-burn-height),
        settlement-block-hash: (some block-hash),
      })
    )

    (ok {
      market-id: market-id,
      winning-outcome: winning-outcome,
      block-hash: block-hash,
      settlement-height: settlement-height,
    })
  )
)

;; =============================================
;; PUBLIC FUNCTIONS - CLAIMING WINNINGS
;; =============================================

;; Claim winnings from a settled market
(define-public (claim-winnings (market-id uint))
  (let (
      (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
      (position (unwrap!
        (map-get? user-positions {
          market-id: market-id,
          user: tx-sender,
        })
        ERR-NO-POSITION
      ))
      (winning-outcome (unwrap! (get winning-outcome market) ERR-MARKET-NOT-SETTLED))
      (settled-height (unwrap! (get settled-at-burn-height market) ERR-MARKET-NOT-SETTLED))
      (current-burn-height tenure-height)
    )
    ;; Validate claim
    (asserts! (get settled market) ERR-MARKET-NOT-SETTLED)
    (asserts! (not (get expired market)) ERR-MARKET-EXPIRED)
    (asserts! (>= current-burn-height (+ settled-height CHALLENGE-WINDOW-BLOCKS))
      ERR-CHALLENGE-WINDOW
    )
    (asserts! (not (get claimed position)) ERR-ALREADY-CLAIMED)

    (let (
        ;; Calculate user's winning bet amount
        (user-winning-amount (get-position-for-outcome position winning-outcome))
        ;; Get total pool for winning outcome
        (winning-pool (get-pool-for-outcome market winning-outcome))
        ;; Calculate payout
        (total-pool (get total-pool market))
        (gross-payout (if (is-eq winning-pool u0)
          u0
          (/ (* user-winning-amount total-pool) winning-pool)
        ))
        ;; Calculate platform fee (2%)
        (platform-fee (/ (* gross-payout PLATFORM-FEE-PERCENT) u10000))
        ;; Calculate creator fee (1%)
        (creator-fee (/ (* gross-payout CREATOR-FEE-PERCENT) u10000))
        ;; Calculate LP fee (1%)
        (lp-fee (/ (* gross-payout LP-FEE-PERCENT) u10000))
        (net-payout (- gross-payout (+ platform-fee creator-fee lp-fee)))
      )
      ;; Check if user has winning position
      (asserts! (> user-winning-amount u0) ERR-NO-POSITION)

      ;; Mark as claimed
      (map-set user-positions {
        market-id: market-id,
        user: tx-sender,
      }
        (merge position { claimed: true })
      )

      ;; Transfer winnings (minus fee)
      (if (> net-payout u0)
        (begin
          (try! (as-contract (stx-transfer? net-payout tx-sender
            (unwrap! (element-at? (list tx-sender) u0) ERR-TRANSFER-FAILED)
          )))
          (var-set total-fees-collected
            (+ (var-get total-fees-collected) platform-fee)
          )
          (map-set creator-fees market-id
            (+ (default-to u0 (map-get? creator-fees market-id)) creator-fee)
          )
          (unwrap! (accrue-lp-fee market-id lp-fee) ERR-TRANSFER-FAILED)
          ;; Update user stats
          (update-user-winnings tx-sender net-payout)
          (ok {
            market-id: market-id,
            gross-payout: gross-payout,
            platform-fee: platform-fee,
            creator-fee: creator-fee,
            lp-fee: lp-fee,
            net-payout: net-payout,
          })
        )
        (ok {
          market-id: market-id,
          gross-payout: u0,
          platform-fee: u0,
          creator-fee: u0,
          lp-fee: u0,
          net-payout: u0,
        })
      )
    )
  )
)

;; Claim refund for an expired market
(define-public (claim-refund (market-id uint))
  (let (
      (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
      (position (unwrap!
        (map-get? user-positions {
          market-id: market-id,
          user: tx-sender,
        })
        ERR-NO-POSITION
      ))
    )
    (asserts! (get expired market) ERR-MARKET-EXPIRED)
    (asserts! (not (get claimed position)) ERR-ALREADY-CLAIMED)
    (let (
        (refund (get total-invested position))
        (pool-a (get outcome-a-pool market))
        (pool-b (get outcome-b-pool market))
        (pool-c (get outcome-c-pool market))
        (pool-d (get outcome-d-pool market))
      )
      (asserts! (> refund u0) ERR-NO-POSITION)

      (map-set markets market-id
        (merge market {
          total-pool: (- (get total-pool market) refund),
          outcome-a-pool: (- pool-a (get outcome-a-amount position)),
          outcome-b-pool: (- pool-b (get outcome-b-amount position)),
          outcome-c-pool: (- pool-c (get outcome-c-amount position)),
          outcome-d-pool: (- pool-d (get outcome-d-amount position)),
        })
      )

      (map-set user-positions {
        market-id: market-id,
        user: tx-sender,
      } {
        outcome-a-amount: u0,
        outcome-b-amount: u0,
        outcome-c-amount: u0,
        outcome-d-amount: u0,
        total-invested: u0,
        claimed: true,
      })

      (as-contract (stx-transfer? refund tx-sender contract-caller))
    )
  )
)

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

(define-private (get-position-for-outcome
    (position {
      outcome-a-amount: uint,
      outcome-b-amount: uint,
      outcome-c-amount: uint,
      outcome-d-amount: uint,
      total-invested: uint,
      claimed: bool,
    })
    (outcome uint)
  )
  (if (is-eq outcome OUTCOME-A)
    (get outcome-a-amount position)
    (if (is-eq outcome OUTCOME-B)
      (get outcome-b-amount position)
      (if (is-eq outcome OUTCOME-C)
        (get outcome-c-amount position)
        (get outcome-d-amount position)
      )
    )
  )
)

(define-private (get-pool-for-outcome
    (market {
      creator: principal,
      title: (string-utf8 256),
      description: (string-utf8 1024),
      category: (string-ascii 32),
      settlement-burn-height: uint,
      settlement-type: (string-ascii 32),
      oracle: (optional principal),
      oracle-outcome: (optional uint),
      max-pool: uint,
      possible-outcomes: uint,
      total-pool: uint,
      outcome-a-pool: uint,
      outcome-b-pool: uint,
      outcome-c-pool: uint,
      outcome-d-pool: uint,
      winning-outcome: (optional uint),
      settled: bool,
      expired: bool,
      expired-at-burn-height: (optional uint),
      settled-at-burn-height: (optional uint),
      settlement-block-hash: (optional (buff 32)),
      created-at-burn-height: uint,
      created-at-stacks-height: uint,
    })
    (outcome uint)
  )
  (if (is-eq outcome OUTCOME-A)
    (get outcome-a-pool market)
    (if (is-eq outcome OUTCOME-B)
      (get outcome-b-pool market)
      (if (is-eq outcome OUTCOME-C)
        (get outcome-c-pool market)
        (get outcome-d-pool market)
      )
    )
  )
)

(define-private (calculate-reward-debt
    (liquidity uint)
    (acc-fee-per-liquidity uint)
  )
  (/ (* liquidity acc-fee-per-liquidity) LP-FEE-PRECISION)
)

(define-private (accrue-lp-fee
    (market-id uint)
    (fee uint)
  )
  (let ((state (default-to {
      total-liquidity: u0,
      acc-fee-per-liquidity: u0,
    }
      (map-get? lp-state market-id)
    )))
    (if (or (is-eq fee u0) (is-eq (get total-liquidity state) u0))
      (begin
        (var-set total-fees-collected (+ (var-get total-fees-collected) fee))
        (ok true)
      )
      (let ((increment (/ (* fee LP-FEE-PRECISION) (get total-liquidity state))))
        (map-set lp-state market-id
          (merge state { acc-fee-per-liquidity: (+ (get acc-fee-per-liquidity state) increment) })
        )
        (ok true)
      )
    )
  )
)

(define-private (distribute-lp-fee
    (market-id uint)
    (user principal)
    (position {
      liquidity: uint,
      reward-debt: uint,
    })
    (state {
      total-liquidity: uint,
      acc-fee-per-liquidity: uint,
    })
  )
  (let ((earned (calculate-reward-debt (get liquidity position)
      (get acc-fee-per-liquidity state)
    )))
    (if (<= earned (get reward-debt position))
      (ok true)
      (let ((pending (- earned (get reward-debt position))))
        (map-set lp-positions {
          market-id: market-id,
          user: user,
        } {
          liquidity: (get liquidity position),
          reward-debt: earned,
        })
        (as-contract (stx-transfer? pending tx-sender contract-caller))
      )
    )
  )
)

(define-private (update-user-markets-created (user principal))
  (let ((stats (default-to {
      markets-created: u0,
      total-bets-placed: u0,
      total-winnings: u0,
      total-losses: u0,
      achievements: u0,
    }
      (map-get? user-stats user)
    )))
    (map-set user-stats user
      (merge stats { markets-created: (+ (get markets-created stats) u1) })
    )
  )
)

(define-private (update-user-bets-placed
    (user principal)
    (amount uint)
  )
  (let ((stats (default-to {
      markets-created: u0,
      total-bets-placed: u0,
      total-winnings: u0,
      total-losses: u0,
      achievements: u0,
    }
      (map-get? user-stats user)
    )))
    (map-set user-stats user
      (merge stats { total-bets-placed: (+ (get total-bets-placed stats) amount) })
    )
  )
)

(define-private (update-user-winnings
    (user principal)
    (amount uint)
  )
  (let ((stats (default-to {
      markets-created: u0,
      total-bets-placed: u0,
      total-winnings: u0,
      total-losses: u0,
      achievements: u0,
    }
      (map-get? user-stats user)
    )))
    (map-set user-stats user
      (merge stats { total-winnings: (+ (get total-winnings stats) amount) })
    )
  )
)

(define-private (add-participant
    (market-id uint)
    (user principal)
  )
  (let ((current-participants (default-to (list) (map-get? market-participants market-id))))
    (if (is-none (index-of? current-participants user))
      (begin
        (map-set market-participants market-id
          (unwrap! (as-max-len? (append current-participants user) u500) false)
        )
        true
      )
      true
    )
  )
)

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

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

;; Oracle resolves an outcome for oracle markets
(define-public (oracle-settle
    (market-id uint)
    (outcome uint)
  )
  (let (
      (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
      (oracle (unwrap! (get oracle market) ERR-ORACLE-NOT-SET))
    )
    (asserts! (is-eq tx-sender oracle) ERR-ORACLE-NOT-AUTHORIZED)
    (asserts! (not (get settled market)) ERR-MARKET-ALREADY-SETTLED)
    (asserts! (is-outcome-enabled (get possible-outcomes market) outcome)
      ERR-INVALID-OUTCOME
    )
    (map-set markets market-id (merge market { oracle-outcome: (some outcome) }))
    (ok outcome)
  )
)

;; Admin can set or rotate oracle for a market before settlement
(define-public (set-market-oracle
    (market-id uint)
    (oracle (optional principal))
  )
  (let (
      (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
      (has-c (is-outcome-enabled (get possible-outcomes market) OUTCOME-C))
      (has-d (is-outcome-enabled (get possible-outcomes market) OUTCOME-D))
      (fallback-type (if (or has-c has-d)
        "hash-range"
        "hash-even-odd"
      ))
    )
    (asserts!
      (or (is-eq tx-sender (get creator market)) (is-eq tx-sender (var-get oracle-admin)))
      ERR-NOT-AUTHORIZED
    )
    (asserts! (not (get settled market)) ERR-MARKET-ALREADY-SETTLED)
    (map-set markets market-id
      (merge market {
        oracle: oracle,
        settlement-type: (if (is-some oracle)
          "oracle"
          fallback-type
        ),
      })
    )
    (ok oracle)
  )
)

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

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

(define-read-only (get-user-stats (user principal))
  (map-get? user-stats user)
)

(define-read-only (get-market-participants (market-id uint))
  (map-get? market-participants market-id)
)

(define-read-only (get-creator-fees (market-id uint))
  (map-get? creator-fees market-id)
)

(define-read-only (get-lp-state (market-id uint))
  (map-get? lp-state market-id)
)

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

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

(define-read-only (get-market-odds (market-id uint))
  (let ((market (unwrap! (map-get? markets market-id) none)))
    (some {
      outcome-a-odds: (calculate-odds (get outcome-a-pool market) (get total-pool market)),
      outcome-b-odds: (calculate-odds (get outcome-b-pool market) (get total-pool market)),
      outcome-c-odds: (calculate-odds (get outcome-c-pool market) (get total-pool market)),
      outcome-d-odds: (calculate-odds (get outcome-d-pool market) (get total-pool market)),
      total-pool: (get total-pool market),
    })
  )
)

(define-private (calculate-odds
    (outcome-pool uint)
    (total-pool uint)
  )
  (if (is-eq outcome-pool u0)
    u0
    (/ (* total-pool u10000) outcome-pool)
  )
  ;; Returns odds in basis points (100x multiplier)
)

;; Calculate potential payout for a bet
(define-read-only (calculate-potential-payout
    (market-id uint)
    (outcome uint)
    (bet-amount uint)
  )
  (let (
      (market (unwrap! (map-get? markets market-id) none))
      (outcome-pool (get-pool-for-outcome market outcome))
      (new-pool (+ outcome-pool bet-amount))
      (new-total (+ (get total-pool market) bet-amount))
      (gross-payout (/ (* bet-amount new-total) new-pool))
      (platform-fee (/ (* gross-payout PLATFORM-FEE-PERCENT) u10000))
    )
    (some {
      gross-payout: gross-payout,
      platform-fee: platform-fee,
      net-payout: (- gross-payout platform-fee),
    })
  )
)

;; Get time until settlement (in Bitcoin blocks)
(define-read-only (get-blocks-until-settlement (market-id uint))
  (let (
      (market (unwrap! (map-get? markets market-id) none))
      (current-burn-height tenure-height)
      (settlement-height (get settlement-burn-height market))
    )
    (if (>= current-burn-height settlement-height)
      (some u0)
      (some (- settlement-height current-burn-height))
    )
  )
)

;; Check if market is ready to be settled
(define-read-only (is-market-settleable (market-id uint))
  (let (
      (market (unwrap! (map-get? markets market-id) (some false)))
      (current-burn-height tenure-height)
      (settlement-height (get settlement-burn-height market))
    )
    (some (and
      (not (get settled market))
      (not (get expired market))
      (>= (get total-pool market) MIN-MARKET-LIQUIDITY)
      (>= current-burn-height (+ settlement-height BLOCKS-BEFORE-SETTLEMENT))
    ))
  )
)

;; Check if market winnings are claimable (after challenge window)
(define-read-only (is-claimable (market-id uint))
  (let (
      (market (unwrap! (map-get? markets market-id) (some false)))
      (current-burn-height tenure-height)
      (settled-height (default-to u0 (get settled-at-burn-height market)))
    )
    (some (and
      (get settled market)
      (not (get expired market))
      (>= current-burn-height (+ settled-height CHALLENGE-WINDOW-BLOCKS))
    ))
  )
)

;; Check if market is expired (low liquidity after settlement height)
(define-read-only (is-market-expired (market-id uint))
  (let (
      (market (unwrap! (map-get? markets market-id) (some false)))
      (current-burn-height tenure-height)
      (settlement-height (get settlement-burn-height market))
    )
    (some (or
      (get expired market)
      (and
        (not (get settled market))
        (>= current-burn-height settlement-height)
        (< (get total-pool market) MIN-MARKET-LIQUIDITY)
      )
    ))
  )
)

;; =============================================
;; ADMIN FUNCTIONS
;; =============================================

(define-public (withdraw-fees
    (amount uint)
    (recipient principal)
  )
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (as-contract (stx-transfer? amount tx-sender recipient))
  )
)

;; Creator withdraws accrued fees for a market
(define-public (withdraw-creator-fees
    (market-id uint)
    (amount uint)
    (recipient principal)
  )
  (let (
      (market (unwrap! (map-get? markets market-id) ERR-MARKET-NOT-FOUND))
      (available (default-to u0 (map-get? creator-fees market-id)))
    )
    (asserts! (is-eq tx-sender (get creator market)) ERR-NOT-AUTHORIZED)
    (asserts! (>= available amount) ERR-INSUFFICIENT-FUNDS)
    (map-set creator-fees market-id (- available amount))
    (as-contract (stx-transfer? amount tx-sender recipient))
  )
)

;; LP claims accrued fees for a market
(define-public (claim-lp-fees (market-id uint))
  (let (
      (position (unwrap!
        (map-get? lp-positions {
          market-id: market-id,
          user: tx-sender,
        })
        ERR-NO-POSITION
      ))
      (state (default-to {
        total-liquidity: u0,
        acc-fee-per-liquidity: u0,
      }
        (map-get? lp-state market-id)
      ))
    )
    (begin
      (unwrap! (distribute-lp-fee market-id tx-sender position state)
        ERR-TRANSFER-FAILED
      )
      (ok true)
    )
  )
)

(define-public (set-platform-paused (paused bool))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (var-set platform-paused paused)
    (ok paused)
  )
)

Functions (31)

FunctionAccessArgs
get-hash-byteread-onlyhash: (buff 32
get-current-burn-heightread-only
get-burn-block-hashread-onlyburn-height: uint
get-user-stx-inforead-onlyuser: principal
is-premium-userread-onlyuser: principal
expire-marketpublicmarket-id: uint
outcome-to-stringread-onlyoutcome: uint
count-enabled-outcomesread-onlypacked: uint
determine-outcome-from-hashread-onlyblock-hash: (buff 32
is-hash-evenread-onlyblock-hash: (buff 32
create-binary-marketpublictitle: (string-utf8 256
create-multi-marketpublictitle: (string-utf8 256
settle-marketpublicmarket-id: uint
claim-winningspublicmarket-id: uint
claim-refundpublicmarket-id: uint
update-user-markets-createdprivateuser: principal
get-marketread-onlymarket-id: uint
set-market-oraclepublicmarket-id: uint, oracle: (optional principal
get-market-countread-only
get-user-statsread-onlyuser: principal
get-market-participantsread-onlymarket-id: uint
get-creator-feesread-onlymarket-id: uint
get-lp-stateread-onlymarket-id: uint
get-total-fees-collectedread-only
get-market-oddsread-onlymarket-id: uint
get-blocks-until-settlementread-onlymarket-id: uint
is-market-settleableread-onlymarket-id: uint
is-claimableread-onlymarket-id: uint
is-market-expiredread-onlymarket-id: uint
claim-lp-feespublicmarket-id: uint
set-platform-pausedpublicpaused: bool