Using Pyth with Stacks
For the latest releases and versions of the Stacks-Pyth contracts, check out the open-source repo here.
The contract logic, that we’ll use for this example, will mint an NFT in exchange for $100 of sBTC. Our Clarity contract will read the price of BTC/USD from the Pyth integration contract to calculate the amount of sBTC required to mint the NFT.
Each Pyth Network price feed is referred to via a unique ID. The full list of price feeds is listed on the pyth.network website. To use a price feed on-chain, look up its ID, then store the feed ID in your program for price feed queries. Each price feed has its own unique id:
Available Pyth price feeds for Stacks:
Pyth Network uses a pull price update model that is slightly different from other oracles you may be more familiar with. Most oracles today use a push model, where the oracle runs an off-chain process that continuously sends transactions to update an on-chain price. In contrast, Pyth Network does not operate an off-chain process that pushes prices on-chain. Instead, it delegates this work to Pyth Network users.

The maintained Pyth integration contract for Stacks is called .pyth-oracle-v4. This contract serves as the main entry point for updating and getting price feed data. The Pyth protocol integration is available as a Beta on both testnet and mainnet networks, to help developers test, give feedback, and ensure the reliability and stability of the integration.
File setup
Below are how the contracts and mainfest files are setup in this example Clarinet project. We'll be using Clarinet's mainnet simulation for this example, hence why we are adding mainnet contracts for Pyth in our files.
Contract Walkthrough
The walkthrough below will use a example contract that will mint an NFT in exchange for $100 of sBTC. Our Clarity contract will read the price of BTC/USD from the Pyth integration contract to calculate the amount of sBTC required to mint the NFT. The action of minting an NFT if one has at least $100 worth of sBTC will be deemed as "joining the benjamin club". Benjamin is in reference to Benjamin Franklin being the face of a one hundred dollar bill, get it?
Verify and update the BTC price feed
We'll open up our function by accepting a (price-feed-bytes (buff 8192)) parameter. This price-feed-bytes is in reference to a VAA message payload. Later in this guide we'll show you how to fetch this payload on the front end.
You'll notice in the Clarity snippet below we open up let bindings of our function to:
Verify and update the BTC price feed with its latest VAA message (more on how to pull the VAA later in this guide). This is a means of participating in the pull price update model.
Getting a fresh instance of the updated price data for BTC.
Handling `get-price` data
After updating and verifying the price feed in question, and then getting the updated price feed data, we'll need to handle the price feed data and its properties.
The price feed data returned from invoking the get-price function of the .pyth-oracle-v4 contract looks like the below:
Adjust returned price value and determine mint eligibility
With the latest price feed data returned, we can adjust the price based on the expo property. Price feeds represent numbers in a fixed-point format. Fixed-point numeric representation is a way of storing numbers with fractional parts using integers, where the decimal point is implicitly fixed at a certain position. So in the above returned price feed data, the returned price of 10603557773590 and given expo of -8 should be formatted as 106035. The same exponent is used for both the price and confidence interval.
We can then determine the USD amount of sBTC the user owns and decide if it is enough to mint a benjamin-nft for $100 worth of sBTC. Benjamin is in reference to Benjamin Franklin being the face of a one hundred dollar bill, get it?
Interact with contract in console using mxs
Since we are using Pyth and sBTC's mainnet contract addresses in our contract, we'll use Clarinet's mainnet simulation feature to interact with the contract in simnet.
Be sure you've enabled mainnet simulation in your manifest file. See the Clarinet.toml file above.
Set `tx_sender` to a mainnet address
In the Clarinet console run: ::set_tx_sender <mainnet-address>
You'll want to grab a <mainnet-address> that most likely has over $100 of sBTC. This is so we can have a simulation of being able to mint the NFT. Then switch the tx_sender context so that we can have that mainnet address simulate calling our contract.
Call `join-the-benjamin-club` with a VAA payload
In the Clarinet console run: (contract-call? 'SP1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRCBGD7R.main join-the-benjamin-club <vaa-payload-hex>)
Pass in a valid VAA payload into the join-the-benjamin-club function. If everything passes, you should see the events emitted from the contract along with an (ok u1) response.
Frontend Walkthrough
Wormhole is a decentralized attestation engine that leverages its network of guardians to trustlessly bridge information between the chains it supports. Wormhole has a simple, elegant, and pragmatic design that has enabled it to be the first real solution to ship to market and has received wide recognition and support from its member chains.
Hermes is a web service that listens to the Wormhole Network for signed and attested price updates, and serves them via a convenient web API. It provides Pyth's latest price update data format that is more cost-effective to verify and use on-chain. Hermes allows users to easily query for recent price updates via a REST API, or subscribe to a websocket for streaming updates. The Pyth Network also provides a Javascript SDK to connect to an instance of Hermes for fetching price updates.
In your front-end application code, you can install and use the methods brought by Pyth Network's hermes-client Javascript SDK to fetch the latest price update, known as a VAA (Verified Action Approvals) message.
The binary data returned from the Pyth SDK will already be in hexadecimal format. We'll then take this hexadecimal VAA message and pass it into our Clarity function as an argument.
Using Stacks Connect of the stacks.js monorepo, we'll open up a stx_callContract request and invoke our public function while passing in the latestVaaHex as the function argument.
If you noticed, we set a post-condition statement of our user transferring less than or equal to 1 uSTX, which is 0.000001 STX. This is because the verify-and-update-price-feeds of the .pyth-oracle-v4 contract applies a fee for this.
Setting a separate post-condition statement on the actual sbtc token transfer in our example will also be needed. Beforehand, you could invoke the decode-price-feeds function with the latestVaaHex to simply have the contained price data decoded and returned. From there you could pre-determine the estimated amount of sbtc tokens to be transferred and set in a separate post-condition.
Additional Resources
Last updated
Was this helpful?
