Pyth oracle integration
Learn how to fetch price data and interact with Pyth oracle contracts using Stacks.js.
What you'll learn
In this guide, you'll learn how to integrate Pyth Network price feeds into your frontend application using Stacks.js.
Installing and configuring the Pyth SDK
Fetching VAA messages from Hermes API
Building transactions with oracle data
Handling post-conditions for oracle fees
Prerequisites
To follow this guide, you'll need:
A React or Node.js application with Stacks.js installed
Understanding of Pyth oracle contracts
Quickstart
Install dependencies
Install the Pyth SDK alongside your existing Stacks.js packages:
Terminal
$npm install @pythnetwork/price-service-client buffer
The buffer package is needed for data format conversion in browser environments.
Set up the Pyth client
Create a service to handle Pyth price feed interactions:
services/pyth.ts
import { PriceServiceConnection } from '@pythnetwork/price-service-client';import { Buffer } from 'buffer';// Price feed IDsexport const PRICE_FEEDS = {BTC_USD: '0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43',STX_USD: '0xec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17',ETH_USD: '0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace',USDC_USD: '0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a'};// Initialize Pyth clientconst pythClient = new PriceServiceConnection('https://hermes.pyth.network',{priceFeedRequestConfig: {binary: true // Request binary format for on-chain use}});export async function fetchPriceUpdateVAA(priceFeedId: string): Promise<string> {try {// Fetch the latest VAA for the price feedconst vaas = await pythClient.getLatestVaas([priceFeedId]);if (!vaas || vaas.length === 0) {throw new Error('No VAA data received');}// Convert base64 to hex for Clarityconst messageBuffer = Buffer.from(vaas[0], 'base64');const hexString = messageBuffer.toString('hex');return `0x${hexString}`;} catch (error) {console.error('Failed to fetch price VAA:', error);throw error;}}
Build oracle-enabled transactions
Create a transaction that includes fresh price data:
components/PythTransaction.tsx
import { request } from '@stacks/connect';import {Cl,PostConditionMode} from '@stacks/transactions';import { fetchPriceUpdateVAA, PRICE_FEEDS } from '../services/pyth';import { useState } from 'react';export function MintWithOraclePrice() {const [loading, setLoading] = useState(false);const handleMint = async () => {setLoading(true);try {// Fetch fresh price dataconst priceVAA = await fetchPriceUpdateVAA(PRICE_FEEDS.BTC_USD);// Convert hex string to buffer Clarity valueconst vaaBuffer = Cl.bufferFromHex(priceVAA.slice(2));// Call contract with price data using requestconst response = await request('stx_callContract', {contract: 'SP1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRCBGD7R.benjamin-club',functionName: 'mint-for-hundred-dollars',functionArgs: [vaaBuffer],postConditionMode: 'deny',network: 'mainnet'});console.log('Transaction submitted:', response.txid);alert(`NFT minted! Transaction ID: ${response.txid}`);} catch (error) {console.error('Minting failed:', error);alert('Failed to mint NFT');} finally {setLoading(false);}};return (<buttononClick={handleMint}disabled={loading}>{loading ? 'Processing...' : 'Mint NFT for $100'}</button>);}
Add post-conditions for oracle fees
Pyth oracle updates require a small fee. Include post-conditions when calling contracts:
components/MintWithPostConditions.tsx
import { request } from '@stacks/connect';import { Cl, Pc } from '@stacks/transactions';import { fetchPriceUpdateVAA, PRICE_FEEDS } from '../services/pyth';export function MintWithPostConditions() {const handleMint = async () => {const userAddress = 'SP1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRCBGD7R';// Create post-conditionsconst postConditions = [// Oracle fee (1 uSTX)Pc.principal(userAddress).willSendLte(1).ustx(),// sBTC transfer for $100 worth (example: 100,000 sats)Pc.principal(userAddress).willSendEq(100000).ft('SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sbtc-token', 'sbtc')];try {const priceVAA = await fetchPriceUpdateVAA(PRICE_FEEDS.BTC_USD);const response = await request('stx_callContract', {contract: `${userAddress}.benjamin-club`,functionName: 'mint-for-hundred-dollars',functionArgs: [Cl.bufferFromHex(priceVAA.slice(2))],postConditions,postConditionMode: 'deny',network: 'mainnet'});console.log('Transaction successful:', response.txid);} catch (error) {console.error('Transaction failed:', error);}};return <button onClick={handleMint}>Mint with Post-Conditions</button>;}