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 IDs
export const PRICE_FEEDS = {
BTC_USD: '0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43',
STX_USD: '0xec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17',
ETH_USD: '0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace',
USDC_USD: '0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a'
};
// Initialize Pyth client
const 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 feed
const vaas = await pythClient.getLatestVaas([priceFeedId]);
if (!vaas || vaas.length === 0) {
throw new Error('No VAA data received');
}
// Convert base64 to hex for Clarity
const 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 data
const priceVAA = await fetchPriceUpdateVAA(PRICE_FEEDS.BTC_USD);
// Convert hex string to buffer Clarity value
const vaaBuffer = Cl.bufferFromHex(priceVAA.slice(2));
// Call contract with price data using request
const 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 (
<button
onClick={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-conditions
const 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>;
}

Next steps