Source Code

;; SPDX-License-Identifier: BUSL-1.1

(define-constant SUCCESS (ok true))
(define-constant ERR-NOT-AUTHORIZED (err u80000))
(define-constant ERR-UNSUPPORTED-ASSET (err u80001))
(define-constant ERR-PYTH-PRICE-STALE (err u80002))
(define-constant ERR-INVALID-MAX-CONFIDENCE-RATIO (err u80003))
(define-constant ERR-PRICE-CONFIDENCE-LOW (err u80004))

(define-constant STACKS_BLOCK_TIME (contract-call? .constants-v1 get-stacks-block-time ))
;; Minimum time delta of 1 minute.
(define-constant MINIMUM_TIME_DELTA u60)
;; Confidence ratio scaling factor  = 100% confidence
(define-constant CONFIDENCE_SCALING_FACTOR u10000)
;; Price scaling factor decimals
(define-constant PRICE_DECIMALS (to-int (contract-call? .constants-v2 get-price-decimals)))

;; price feeds can be found in https://pyth.network/developers/price-feed-ids
(define-map price-feeds principal {
  feed-id: (buff 32),
  max-confidence-ratio: uint
})

(define-data-var time-delta uint u300)

;; admin-level maintenance functions
(define-public (update-price-feed-id (token principal) (new-feed-id (buff 32)) (max-confidence-ratio uint))
  (begin 
    (asserts! (is-eq (contract-call? .state-v1 get-governance) contract-caller) ERR-NOT-AUTHORIZED)
    (asserts! (<= max-confidence-ratio CONFIDENCE_SCALING_FACTOR) ERR-INVALID-MAX-CONFIDENCE-RATIO)
    (map-set price-feeds token {
      feed-id: new-feed-id,
      max-confidence-ratio: max-confidence-ratio
    })
    (print  {
      event-type: "update-price-feed-id",
      asset: token,
      user: contract-caller,
      feed-id: new-feed-id,
      max-confidence-ratio: max-confidence-ratio,
    })
    SUCCESS
))

(define-public (update-time-delta (delta uint))
  (begin 
    (asserts! (is-eq (contract-call? .state-v1 get-governance) contract-caller) ERR-NOT-AUTHORIZED)
    (print  {
      event-type: "update-time-delta",
      old-val: (var-get time-delta),
      new-val: delta,
      user: contract-caller,
    })
    (var-set time-delta delta)
    SUCCESS
  )
)

(define-read-only (get-pyth-time-delta)
  (var-get time-delta)
)

(define-read-only (get-pyth-minimum-time-delta) MINIMUM_TIME_DELTA)

(define-read-only (read-price (token principal))
  (let 
    (
      (pyth-feed-data (unwrap! (map-get? price-feeds token) ERR-UNSUPPORTED-ASSET))
      (pyth-record 
          (try! (contract-call? 
            'SP1CGXWEAMG6P6FT04W66NVGJ7PQWMDAC19R7PJ0Y.pyth-storage-v4
            get-price
            (get feed-id pyth-feed-data)
          ))
        )
    )
    (decode-pyth pyth-record (get max-confidence-ratio pyth-feed-data))
  )
)

(define-public (update-pyth (maybe-vaa-buffer (optional (buff 8192))))
  (match maybe-vaa-buffer vaa-buffer
    (begin
      (try! 
        (contract-call? 'SP1CGXWEAMG6P6FT04W66NVGJ7PQWMDAC19R7PJ0Y.pyth-oracle-v4 verify-and-update-price-feeds 
          vaa-buffer
          {
            pyth-storage-contract: 'SP1CGXWEAMG6P6FT04W66NVGJ7PQWMDAC19R7PJ0Y.pyth-storage-v4,
            pyth-decoder-contract: 'SP1CGXWEAMG6P6FT04W66NVGJ7PQWMDAC19R7PJ0Y.pyth-pnau-decoder-v3,
            wormhole-core-contract: 'SP1CGXWEAMG6P6FT04W66NVGJ7PQWMDAC19R7PJ0Y.wormhole-core-v4
          }) 
      )
      SUCCESS
    )
    SUCCESS
  )
)

(define-read-only (bulk-read-collateral-prices (collaterals (list 10 principal)))
  (fold check-collateral-price-exists collaterals (ok (list)))
)

(define-private (check-collateral-price-exists (collateral principal) (res (response (list 10 uint) uint)))
  (let (
    (price-list (try! res))
    (price (try! (read-price collateral)))
    (updated-price-list (unwrap-panic (as-max-len? (append price-list price) u10)))
  )
    (ok updated-price-list)
  )
)

(define-private (decode-pyth (pyth-record 
  {conf: uint, ema-conf: uint, ema-price: int, expo: int, prev-publish-time: uint, price: int, publish-time: uint}
) (max-confidence-ratio uint)) 
  (let 
    (
      (timestamp (get publish-time pyth-record))
      (expo (get expo pyth-record))
      (price (get price pyth-record))
      (price-conf (get conf pyth-record))
      
    )
    (asserts! (is-valid timestamp) ERR-PYTH-PRICE-STALE)
    (try! (check-confidence (to-uint price) price-conf max-confidence-ratio))
    (ok (to-uint (convert-res price expo PRICE_DECIMALS)))
))

(define-private (check-confidence (price uint) (confidence uint) (max-confidence-ratio uint))
  (if (or (is-eq u0 price) (<= confidence (/ (* price max-confidence-ratio) CONFIDENCE_SCALING_FACTOR)))
    (ok true)
    ERR-PRICE-CONFIDENCE-LOW
  )
)

(define-private (is-valid (timestamp uint))
  (let ((block-timestamp (+ (unwrap-panic (get-stacks-block-info? time (- stacks-block-height u1))) STACKS_BLOCK_TIME)))
    (if (>= timestamp block-timestamp) 
      true
      (> timestamp (- block-timestamp (var-get time-delta))))
  )
)

(define-private (abs (val int))
  (if (> val 0) val (- 0 val))
)

(define-private (convert-res (price int) (expo int) (resolution-digits int))
  (if (>= expo 0)
    (* price (pow 10 (+ expo resolution-digits)))
    (let ((diff (- resolution-digits (abs expo))))
      (if (is-eq diff 0) 
        price 
        (if (> diff 0) (* price (pow 10 diff)) (/ price (pow 10 (abs diff))))
    ))
))

(map-set price-feeds 'SP120SBRBQJ00MCWS7TM5R8WJNTTKD5K0HFRC2CNE.usdcx {
  feed-id: 0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a,
  max-confidence-ratio: u100
})

(map-set price-feeds 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token {
  feed-id: 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43,
  max-confidence-ratio: u500
})

Functions (12)

FunctionAccessArgs
update-price-feed-idpublictoken: principal, new-feed-id: (buff 32
update-time-deltapublicdelta: uint
get-pyth-time-deltaread-only
get-pyth-minimum-time-deltaread-only
read-priceread-onlytoken: principal
update-pythpublicmaybe-vaa-buffer: (optional (buff 8192
bulk-read-collateral-pricesread-onlycollaterals: (list 10 principal
decode-pythprivatepyth-record: {conf: uint, ema-conf: uint, ema-price: int, expo: int, prev-publish-time: uint, price: int, publish-time: uint}, max-confidence-ratio: uint
check-confidenceprivateprice: uint, confidence: uint, max-confidence-ratio: uint
is-validprivatetimestamp: uint
absprivateval: int
convert-resprivateprice: int, expo: int, resolution-digits: int