Pyth
Pyth Pro on Cardano is currently in Beta. The API may change before release.
Pyth is the production price oracle this curriculum recommends. If oracles are new to you, Oracles covers the general problem of getting off-chain data on-chain and the pull-based model Cardano contracts use to read it.
What is Pyth?
Pyth is a high-frequency oracle network that delivers real-time price data across multiple blockchains. Pyth Pro (Lazer) provides sub-second price feeds using a pull-based model: consumers fetch signed updates off-chain and verify them on-chain.
On Cardano, price updates are verified through a zero-withdrawal from the Pyth withdraw script. Your validator calls pyth.get_updates to read verified updates directly from the transaction being validated. The Pyth script handles signature verification so your contract doesn't have to.
What Pyth provides
Pyth Pro (Lazer): Sub-second, high-frequency price feeds via a pull-based model. You subscribe to a websocket or fetch the latest price and include the signed update in your transaction.
On-chain Aiken Library: The pyth-lazer-cardano library handles signature verification and exposes parsed price data including price, confidence, EMA price, bid/ask, and exponent.
Off-chain TypeScript SDK: The @pythnetwork/pyth-lazer-sdk provides websocket streaming and one-shot fetching of signed price updates.
Integration guide
Integrating Pyth Pro into a Cardano smart contract is a three-step process:
Step 1: Use the Aiken library on-chain
Add the Pyth Lazer Cardano library to your aiken.toml:
[[dependencies]]
name = "pyth-network/pyth-lazer-cardano"
version = "main"
source = "github"
Your contract reads verified updates from the transaction via pyth.get_updates. This function reads the Pyth state from reference_inputs and the verified update bytes from the Pyth withdraw script's redeemer.
The following example reads the ADA/USD feed (Pyth Pro feed ID 16) and converts the result into Aiken's Rational type:
use aiken/collection/list
use aiken/math/rational.{Rational}
use cardano/assets.{PolicyId}
use cardano/transaction.{Transaction}
use pyth
use types/u32
fn read_ada_usd_price(pyth_id: PolicyId, self: Transaction) -> Rational {
expect [update] = pyth.get_updates(pyth_id, self)
expect Some(feed) = list.find(update.feeds, fn(feed) {
u32.as_int(feed.feed_id) == 16
})
expect Some(Some(price)) = feed.price
expect Some(exponent) = feed.exponent
expect Some(multiplier) = rational.from_int(10) |> rational.pow(exponent)
rational.from_int(price) |> rational.mul(multiplier)
}
Each PriceUpdate includes timestamp_us, channel_id, and a list of feeds. Each Feed includes fields such as feed_id, price, best_bid_price, best_ask_price, exponent, confidence, ema_price, and feed_update_timestamp.
The Pyth withdraw script verifies signature validity but does not enforce freshness. A valid signature proves the price is genuinely Pyth's (integrity); it does not prove the update is recent, or that one was posted at all (liveness). If your contract requires a validity window, enforce it directly by checking the timestamp_us field. See who publishes, and what that guarantees for the trust model behind this.
pyth.get_updates requires the Pyth state UTxO to be present as a reference input. If you omit it, your validator will fail when it tries to locate the Pyth State NFT and withdraw-script hash.
Step 2: Fetch signed price updates off-chain
Use the TypeScript SDK to fetch a signed update. You need to request the solana format, which is the little-endian Ed25519-signed binary format used for both Cardano and Solana integrations.
import { PythLazerClient } from "@pythnetwork/pyth-lazer-sdk";
const lazer = await PythLazerClient.create({ token: LAZER_TOKEN });
const latestPrice = await lazer.getLatestPrice({
channel: "fixed_rate@200ms",
formats: ["solana"],
jsonBinaryEncoding: "hex",
priceFeedIds: [16],
properties: ["price", "exponent"],
});
if (!latestPrice.solana?.data) {
throw new Error("Missing update payload");
}
const update = Buffer.from(latestPrice.solana.data, "hex");
If you need streaming integration instead of a one-shot fetch, see the Pyth Pro subscription guide.
Step 3: Include the update in a Cardano transaction
Build a transaction that performs a zero-withdrawal from the Pyth withdraw script, passing the signed update as the redeemer. The pyth_id is the Pyth deployment policy ID for your network.
import { ScriptHash } from "@evolution-sdk/evolution";
import {
getPythScriptHash,
getPythState,
} from "@pythnetwork/pyth-lazer-cardano-js";
const client = createClient({ network, provider });
const pythState = await getPythState(POLICY_ID, client);
const pythScript = getPythScriptHash(pythState);
const wallet = client.attachWallet({
mnemonic: CARDANO_MNEMONIC,
type: "seed",
});
const now = BigInt(Date.now());
const tx = wallet
.newTx()
.setValidity({ from: now - 60_000n, to: now + 60_000n })
.readFrom({ referenceInputs: [pythState] })
.withdraw({
amount: 0n,
redeemer: [update],
stakeCredential: ScriptHash.fromHex(pythScript),
});
// Add your own scripts and transaction data, then sign and submit:
const builtTx = await tx.build();
const digest = await builtTx.signAndSubmit();
The zero-withdrawal and your consuming validator must be in the same transaction. pyth.get_updates reads the withdrawal redeemer directly from the transaction being validated.
Network support
Pyth Pro on Cardano is currently in Beta.
Additional resources
- Pyth Pro Price Feed IDs: complete list of supported feeds
- Contract sources: Aiken contracts and off-chain SDK
- fetch-and-verify.ts: full off-chain example