Source Code

;; Sentinel Price Oracle
;; Decentralized price feeds for STX, sBTC, and SNTL
;; Supports multiple price reporters with median aggregation

;; ==========================================
;; CONSTANTS
;; ==========================================
(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u100))
(define-constant err-not-reporter (err u101))
(define-constant err-invalid-price (err u102))
(define-constant err-stale-price (err u103))
(define-constant err-reporter-exists (err u104))
(define-constant err-reporter-not-found (err u105))

;; Price staleness threshold (blocks) - ~1 hour
(define-constant staleness-threshold u360)

;; Asset IDs
(define-constant ASSET-STX u1)
(define-constant ASSET-SBTC u2)
(define-constant ASSET-SNTL u3)

;; ==========================================
;; DATA VARIABLES
;; ==========================================
(define-data-var reporter-count uint u0)
(define-data-var min-reporters uint u1) ;; Minimum reporters for valid price

;; ==========================================
;; PRICE DATA
;; ==========================================
;; Main price storage (asset-id -> price info)
(define-map prices uint 
  {
    price: uint,              ;; Price in USD micro-units (6 decimals)
    last-update: uint,        ;; Block height of last update
    reporter: principal,      ;; Who submitted this price
    confidence: uint          ;; Confidence score (0-10000)
  }
)

;; Individual reporter prices (for aggregation)
(define-map reporter-prices { asset: uint, reporter: principal }
  {
    price: uint,
    timestamp: uint
  }
)

;; Authorized reporters
(define-map reporters principal bool)

;; Price history (last 10 prices per asset)
(define-map price-history { asset: uint, index: uint }
  {
    price: uint,
    block: uint
  }
)

(define-map history-index uint uint) ;; Current index for each asset

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

;; Get current price for an asset
(define-read-only (get-price (asset-id uint))
  (match (map-get? prices asset-id)
    price-data
    (let (
      (blocks-since-update (- stacks-block-height (get last-update price-data)))
    )
      (if (> blocks-since-update staleness-threshold)
        { price: (get price price-data), fresh: false, stale-blocks: blocks-since-update }
        { price: (get price price-data), fresh: true, stale-blocks: u0 }
      )
    )
    { price: u0, fresh: false, stale-blocks: u999999 }
  )
)

;; Get STX price
(define-read-only (get-stx-price)
  (get price (get-price ASSET-STX))
)

;; Get sBTC price
(define-read-only (get-sbtc-price)
  (get price (get-price ASSET-SBTC))
)

;; Get SNTL price
(define-read-only (get-sntl-price)
  (get price (get-price ASSET-SNTL))
)

;; Get all prices
(define-read-only (get-all-prices)
  {
    stx: (get-price ASSET-STX),
    sbtc: (get-price ASSET-SBTC),
    sntl: (get-price ASSET-SNTL),
    block: stacks-block-height
  }
)

;; Check if price is fresh
(define-read-only (is-price-fresh (asset-id uint))
  (get fresh (get-price asset-id))
)

;; Check if address is reporter
(define-read-only (is-reporter (address principal))
  (default-to false (map-get? reporters address))
)

;; Get price in different denomination
;; Convert amount of asset to USD value
(define-read-only (get-usd-value (asset-id uint) (amount uint))
  (let (
    (price (get price (get-price asset-id)))
  )
    (/ (* amount price) u1000000)
  )
)

;; Get oracle stats
(define-read-only (get-oracle-stats)
  {
    reporter-count: (var-get reporter-count),
    min-reporters: (var-get min-reporters),
    stx-price: (get-stx-price),
    sbtc-price: (get-sbtc-price),
    sntl-price: (get-sntl-price),
    current-block: stacks-block-height
  }
)

;; Get price history for an asset
(define-read-only (get-price-at-index (asset-id uint) (index uint))
  (map-get? price-history { asset: asset-id, index: index })
)

;; ==========================================
;; REPORTER FUNCTIONS
;; ==========================================

;; Submit price update (reporters only)
(define-public (submit-price (asset-id uint) (price uint))
  (let (
    (reporter tx-sender)
  )
    ;; Validations
    (asserts! (is-reporter reporter) err-not-reporter)
    (asserts! (> price u0) err-invalid-price)
    (asserts! (or (is-eq asset-id ASSET-STX) (is-eq asset-id ASSET-SBTC) (is-eq asset-id ASSET-SNTL)) err-invalid-price)
    
    ;; Store reporter's individual price
    (map-set reporter-prices { asset: asset-id, reporter: reporter }
      { price: price, timestamp: stacks-block-height }
    )
    
    ;; Update main price (in production, would aggregate from multiple reporters)
    (map-set prices asset-id {
      price: price,
      last-update: stacks-block-height,
      reporter: reporter,
      confidence: u10000 ;; 100% for single reporter
    })
    
    ;; Update history
    (let (
      (current-index (default-to u0 (map-get? history-index asset-id)))
      (next-index (mod (+ current-index u1) u10))
    )
      (map-set price-history { asset: asset-id, index: current-index }
        { price: price, block: stacks-block-height }
      )
      (map-set history-index asset-id next-index)
    )
    
    (ok { asset: asset-id, price: price, block: stacks-block-height })
  )
)

;; Batch price update
(define-public (submit-prices (stx-price uint) (sbtc-price uint) (sntl-price uint))
  (begin
    (asserts! (is-reporter tx-sender) err-not-reporter)
    (try! (submit-price ASSET-STX stx-price))
    (try! (submit-price ASSET-SBTC sbtc-price))
    (try! (submit-price ASSET-SNTL sntl-price))
    (ok {
      stx: stx-price,
      sbtc: sbtc-price,
      sntl: sntl-price,
      block: stacks-block-height
    })
  )
)

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

;; Add price reporter
(define-public (add-reporter (reporter principal))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (asserts! (not (is-reporter reporter)) err-reporter-exists)
    (map-set reporters reporter true)
    (var-set reporter-count (+ (var-get reporter-count) u1))
    (ok reporter)
  )
)

;; Remove price reporter
(define-public (remove-reporter (reporter principal))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (asserts! (is-reporter reporter) err-reporter-not-found)
    (map-delete reporters reporter)
    (var-set reporter-count (- (var-get reporter-count) u1))
    (ok reporter)
  )
)

;; Set minimum reporters for valid price
(define-public (set-min-reporters (min uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (var-set min-reporters min)
    (ok min)
  )
)

;; Emergency price update (owner only)
(define-public (emergency-update (asset-id uint) (price uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (asserts! (> price u0) err-invalid-price)
    (map-set prices asset-id {
      price: price,
      last-update: stacks-block-height,
      reporter: tx-sender,
      confidence: u10000
    })
    (ok { asset: asset-id, price: price })
  )
)

;; Initialize oracle with default prices
(define-public (initialize-prices)
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    ;; Set initial prices (approximate market values)
    (map-set prices ASSET-STX { price: u1200000, last-update: stacks-block-height, reporter: tx-sender, confidence: u10000 }) ;; $1.20
    (map-set prices ASSET-SBTC { price: u100000000000, last-update: stacks-block-height, reporter: tx-sender, confidence: u10000 }) ;; $100,000
    (map-set prices ASSET-SNTL { price: u100, last-update: stacks-block-height, reporter: tx-sender, confidence: u10000 }) ;; $0.0001
    ;; Add owner as first reporter
    (map-set reporters tx-sender true)
    (var-set reporter-count u1)
    (ok true)
  )
)

Functions (17)

FunctionAccessArgs
get-priceread-onlyasset-id: uint
get-stx-priceread-only
get-sbtc-priceread-only
get-sntl-priceread-only
get-all-pricesread-only
is-price-freshread-onlyasset-id: uint
is-reporterread-onlyaddress: principal
get-usd-valueread-onlyasset-id: uint, amount: uint
get-oracle-statsread-only
get-price-at-indexread-onlyasset-id: uint, index: uint
submit-pricepublicasset-id: uint, price: uint
submit-pricespublicstx-price: uint, sbtc-price: uint, sntl-price: uint
add-reporterpublicreporter: principal
remove-reporterpublicreporter: principal
set-min-reporterspublicmin: uint
emergency-updatepublicasset-id: uint, price: uint
initialize-pricespublic