-
Notifications
You must be signed in to change notification settings - Fork 36
Added Stacks guide #785
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Added Stacks guide #785
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
--- | ||
description: Consume Pyth Network prices in Stacks applications | ||
--- | ||
|
||
import { Callout } from "nextra/components"; | ||
|
||
# How to Use Real-Time Data in Stacks applications | ||
|
||
[Stacks](https://www.stacks.co/) is a layer 2 network on Bitcoin. It brings programmability and decentralized applications (dApps) to Bitcoin without modifying Bitcoin itself. Stacks lets developers build apps, smart contracts, NFTs, and DeFi on top of Bitcoin — while using Bitcoin as the settlement and security layer. | ||
|
||
This guide explains how to use real-time Pyth data in [Clarity](https://clarity-lang.org/) smart contracts on Stacks. | ||
|
||
## Write Contract Code | ||
|
||
The Pyth protocol integration for Stacks 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. | ||
Unlike other smart contract languages, there is no importing modules into Clarity smart contracts for certain security reasons. So the Pyth integration on Stacks is implemented as its own set of Clarity contracts where developers will invoke a `contract-call?` to the main Pyth Clarity [contract](https://explorer.hiro.so/txid/SP3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.pyth-oracle-v3?chain=mainnet). | ||
|
||
<Callout type="info" emoji="ℹ️"> | ||
Currently, the Pyth protocol integration is currently maintained by Trust Machines. It currently supports real-time price feeds for BTC, STX, ETH, and USDC. To request more price feeds, open an issue in Trust Machine's Pyth maintained repo [here](https://github.com/Trust-Machines/stacks-pyth-bridge). | ||
</Callout> | ||
|
||
### Read BTC prices from a Clarity smart contract | ||
|
||
For our example, we will imagine a contract that mints an NFT in exchange for $100 of sBTC. In order to determine the USD value of a user's sBTC amount, we'll need to use Pyth. Since market pricing for sBTC isn't supported currently, we'll use the price data from the BTC/USD price feed. | ||
|
||
<Callout type="info" emoji="ℹ️"> | ||
The maintained Pyth integration contract for Stacks is called [`.pyth-oracle-v3`](https://explorer.hiro.so/txid/0x745a0e07ef9487ebb2190da515bda60f1531299553420750b33b3ba4a97729e1?chain=mainnet). This contract serves as the main entry point for updating and getting price feed data. | ||
</Callout> | ||
|
||
You'll notice in the Clarity snippet below we open up `let` bindings of our function to: | ||
|
||
1. Verify & 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. | ||
2. Getting a fresh instance of the updated price data for BTC. | ||
|
||
```clarity | ||
;; --snip-- | ||
(define-public (join-the-benjamin-club (price-feed-bytes (buff 8192))) | ||
(let ( | ||
;; To verify & update price feeds is to participate in the pull price model of Pyth's decentralization. | ||
;; A VAA signed message is pulled from Wormhole via the Hermes API. This VAA signed message is what | ||
;; gets passed into this function to verify & update the price data of a particular price feed. | ||
(update-status (try! (contract-call? 'SP3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.pyth-oracle-v3 | ||
verify-and-update-price-feeds price-feed-bytes { | ||
pyth-storage-contract: 'SP3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.pyth-storage-v3, | ||
pyth-decoder-contract: 'SP3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.pyth-pnau-decoder-v2, | ||
wormhole-core-contract: 'SP3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.wormhole-core-v3, | ||
}))) | ||
;; The price data returned will be fresh from the VAA signed message data we passed in above. | ||
(price-data (try! (contract-call? 'SP3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.pyth-oracle-v3 | ||
get-price | ||
;; The official BTC price feed id. | ||
0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43 | ||
'SP3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.pyth-storage-v3 | ||
))) | ||
;; --snip-- | ||
``` | ||
|
||
After updating & 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-v3` contract looks like the below: | ||
|
||
```bash | ||
{ | ||
price-identifier: 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43, | ||
price: 10603557773590, | ||
conf: u3776653890, | ||
ema-price: 10602069900000, | ||
ema-conf: u4062895700, | ||
expo: -8, | ||
publish-time: u1750425711, | ||
prev-publish-time: u1750425710 | ||
} | ||
``` | ||
|
||
With the price feed data, we can adjust the price based on the `expo` property. Price feeds represent numbers in a fixed-point format. So in the above returned price feed data, the 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? | ||
|
||
```clarity | ||
;; --snip -- | ||
;; Price feeds represent numbers in a fixed-point format. The expo property tells us | ||
;; at what certain position is the decimal point implicity fixed. | ||
(price-denomination (pow 10 (* (get expo price-data) -1))) | ||
;; We'll adjust the price to its normal decimal representation. | ||
(adjusted-price (to-uint (/ (get price price-data) price-denomination))) | ||
;; Get the user's current sBTC balance. | ||
(user-sbtc-balance (unwrap! | ||
(contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token | ||
get-balance-available tx-sender | ||
) | ||
ERR_READING_SBTC_BALANCE | ||
)) | ||
) | ||
;; Determine if the user has at least $100 worth of sBTC to join the Benjamin Club. | ||
(if (> (/ (* user-sbtc-balance adjusted-price) (to-uint price-denomination)) | ||
COST-OF-BENJAMIN-NFT | ||
) | ||
(let ((hundred-dollars-in-sbtc (/ (* COST-OF-BENJAMIN-NFT (to-uint price-denomination)) adjusted-price))) | ||
(try! (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token | ||
transfer hundred-dollars-in-sbtc tx-sender (as-contract tx-sender) | ||
none | ||
)) | ||
(contract-call? .nft-contract mint tx-sender) | ||
) | ||
ERR_NOT_ENOUGH_SBTC | ||
) | ||
) | ||
) | ||
``` | ||
|
||
<Callout type="warning" emoji="⚠️"> | ||
The `verify-and-update-price-feeds` of the `.pyth-oracle-v3` contract applies a fee of 1 uSTX, or 1 micro-stx, which is 0.000001 STX. | ||
</Callout> | ||
|
||
Alternatively, developers can just invoke the `read-price-feed` public function of the `pyth-oracle-v3.clar` contract. This will simply return the price feed from the last updated feed. But it's always encouraged to determine if a staleness check is viable for your application. | ||
|
||
## Write Front-End Code | ||
|
||
In your front-end application code, you can install and use the methods brought by Pyth Network's Javascript SDK to fetch the latest price update, known as a VAA (Verified Action Approvals) message. | ||
|
||
```javascript copy | ||
import { PriceServiceConnection } from "@pythnetwork/price-service-client" | ||
import { Buffer } from "buffer" | ||
|
||
// --snip-- | ||
async function handleFetchLatestVaa() { | ||
const connection = new PriceServiceConnection("https://hermes.pyth.network", { | ||
priceFeedRequestConfig: { | ||
binary: true | ||
} | ||
}) | ||
|
||
const btcPriceId = ["0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"] | ||
|
||
const response = await connection.getLatestVaas(btcPriceId) | ||
let messageBuffer = Buffer.from(response[0], "base64") | ||
const hexString = messageBuffer.toString("hex") | ||
let latestVaaHex = `0x${hexString}` | ||
|
||
return latestVaaHex | ||
} | ||
// --snip-- | ||
``` | ||
|
||
The binary data returned from the Pyth SDK will be in base64 format which we'll need to convert to hexadecimal in order for the Pyth contract to properly ingest it. 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](https://github.com/hirosystems/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. | ||
|
||
```javascript copy | ||
let latestVaaHex = await handleFetchLatestVaa() | ||
|
||
let postCond1 = Pc.principal("SP1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRCBGD7R").willSendLte(1).ustx() | ||
|
||
const response = await request("stx_callContract", { | ||
contract: `SP1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRCBGD7R.benjamin-club`, | ||
functionName: "join-the-benjamin-club", | ||
functionArgs: [Cl.bufferFromHex(latestVaaHex)], | ||
network: "mainnet", | ||
postConditions: [postCond1], | ||
postConditionMode: "deny" | ||
}) | ||
``` | ||
|
||
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-v3` 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 | ||
|
||
You may find these additional resources helpful for developing your Stacks application with Pyth. | ||
|
||
- **[Hiro Docs](https://docs.hiro.so/resources/guides/using-pyth-price-feeds):** Check out the dedicated guide for using Pyth in Stacks applications in Hiro's documentation. In this guide you'll see a visual architecture overview of using Pyth in Stacks, how you can test your implementation, how to fetch VAAs on the front-end, learn best practices, and more. | ||
- **[Hiro How-To Tutorial](https://youtu.be/eybqQVRh_hw?si=KNfUp3RS3CnaST91):** Watch the dedicated video tutorial on using Pyth in Stacks and learn how a major Stacks DeFi app, Granite, is using Pyth. | ||
- **[Trust Machine's Pyth Github](https://github.com/Trust-Machines/stacks-pyth-bridge):** Check out the open-source repo for the Pyth integration Clarity contracts. | ||
- **[pyth-oracle-v3.clar](https://explorer.hiro.so/txid/SP3R4F6C1J3JQWWCVZ3S7FRRYPMYG6ZW6RZK31FXY.pyth-oracle-v3?chain=mainnet):** The latest Pyth integration contract on Stacks' mainnet. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please replace this with the hermes-client