Using Pyth oracle data
Learn how to integrate real-time market price data from Pyth Network into your Clarity smart contracts.
What you'll learn
In this guide, you'll learn how to read real-time price data from Pyth Network's decentralized oracle in your Clarity contracts.
Prerequisites
To follow this guide, you'll need:
Understanding Pyth oracles
Pyth Network uses a pull-based oracle model, different from traditional push-based oracles:
- Push model: Oracle continuously sends price updates on-chain
- Pull model: Users fetch and submit price updates when needed
This approach is more gas-efficient and allows for higher-frequency price updates.
Supported price feeds
The Pyth integration on Stacks currently supports these price feeds:
Asset | Price Feed ID | Explorer Link |
---|---|---|
BTC/USD | 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43 | View |
STX/USD | 0xec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17 | View |
ETH/USD | 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace | View |
USDC/USD | 0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a | View |
Quickstart
Set up contract references
The Pyth oracle on Stacks consists of several contracts. Add these references to your contract:
;; Pyth oracle contract addresses (mainnet)(define-constant PYTH-ORACLE-CONTRACT 'SP3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.pyth-oracle-v3)(define-constant PYTH-STORAGE-CONTRACT 'SP3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.pyth-storage-v3)(define-constant PYTH-DECODER-CONTRACT 'SP3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.pyth-pnau-decoder-v2)(define-constant WORMHOLE-CORE-CONTRACT 'SP3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.wormhole-core-v3);; Price feed IDs(define-constant BTC-USD-FEED-ID 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
Update and read price data
Create a function that updates the price feed and reads the current price:
(define-public (get-btc-price (price-feed-bytes (buff 8192)))(let (;; Update the price feed with fresh data(update-result (try! (contract-call? PYTH-ORACLE-CONTRACTverify-and-update-price-feeds price-feed-bytes {pyth-storage-contract: PYTH-STORAGE-CONTRACT,pyth-decoder-contract: PYTH-DECODER-CONTRACT,wormhole-core-contract: WORMHOLE-CORE-CONTRACT})));; Read the updated price(price-data (try! (contract-call? PYTH-ORACLE-CONTRACTget-priceBTC-USD-FEED-IDPYTH-STORAGE-CONTRACT))))(ok price-data)))
The update operation requires a small fee (typically 1 uSTX) to prevent spam.
Process price data
Price data uses fixed-point representation. Convert it to a usable format:
(define-read-only (process-price-data (price-data {price-identifier: (buff 32),price: int,conf: uint,expo: int,ema-price: int,ema-conf: uint,publish-time: uint,prev-publish-time: uint}))(let (;; Calculate the denominator based on the exponent;; For expo = -8, denominator = 10^8 = 100,000,000(exponent (get expo price-data))(denominator (pow u10 (to-uint (* exponent -1))));; Convert to standard decimal representation;; If price = 10603557773590 and expo = -8;; Actual price = 10603557773590 / 100,000,000 = 106,035.58(adjusted-price (/ (to-uint (get price price-data)) denominator)))adjusted-price))
Common patterns
USD-denominated NFT minting
Create an NFT that costs exactly $100 worth of sBTC:
;; Benjamin Club - $100 NFT minting contract(define-constant BENJAMIN-COST u100) ;; $100 USD(define-constant ERR-INSUFFICIENT-FUNDS (err u100))(define-constant ERR-PRICE-UPDATE-FAILED (err u101))(define-non-fungible-token benjamin-nft uint)(define-data-var last-token-id uint u0)(define-public (mint-for-hundred-dollars (price-feed-bytes (buff 8192)))(let (;; Update and get BTC price(price-data (try! (get-btc-price price-feed-bytes)))(btc-price-cents (process-price-data price-data));; Calculate required sBTC amount;; $100 = 10,000 cents(required-sbtc (/ (* u10000 u100000000) btc-price-cents));; Get user's sBTC balance(user-balance (unwrap!(contract-call? .sbtc-token get-balance tx-sender)ERR-INSUFFICIENT-FUNDS)));; Verify user has enough sBTC(asserts! (>= user-balance required-sbtc) ERR-INSUFFICIENT-FUNDS);; Transfer sBTC and mint NFT(try! (contract-call? .sbtc-token transferrequired-sbtc tx-sender (as-contract tx-sender) none));; Mint the NFT(let ((token-id (+ (var-get last-token-id) u1)))(try! (nft-mint? benjamin-nft token-id tx-sender))(var-set last-token-id token-id)(ok token-id))))
Price staleness protection
Ensure price data is recent enough for your use case:
(define-constant MAX-PRICE-AGE u300) ;; 5 minutes in seconds(define-read-only (is-price-fresh (price-data (tuple)))(let ((current-time (unwrap-panic (get-block-info? time block-height)))(publish-time (get publish-time price-data)))(<= (- current-time publish-time) MAX-PRICE-AGE)))
Multi-asset price aggregation
Get multiple price feeds in one transaction:
(define-public (get-multiple-prices(btc-vaa (buff 8192))(eth-vaa (buff 8192))(stx-vaa (buff 8192)))(let (;; Update all price feeds(updates (try! (contract-call? PYTH-ORACLE-CONTRACTverify-and-update-price-feeds(concat btc-vaa (concat eth-vaa stx-vaa)){ pyth-storage-contract: PYTH-STORAGE-CONTRACT,pyth-decoder-contract: PYTH-DECODER-CONTRACT,wormhole-core-contract: WORMHOLE-CORE-CONTRACT })));; Read all prices(btc-price (try! (get-price-by-id BTC-USD-FEED-ID)))(eth-price (try! (get-price-by-id ETH-USD-FEED-ID)))(stx-price (try! (get-price-by-id STX-USD-FEED-ID))))(ok { btc: btc-price, eth: eth-price, stx: stx-price })))
Testnet deployment
For testnet development, use these contract addresses:
;; Testnet addresses(define-constant PYTH-ORACLE-CONTRACT-TESTNET 'ST3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.pyth-oracle-v3)(define-constant PYTH-STORAGE-CONTRACT-TESTNET 'ST3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.pyth-storage-v3)