Use Prices on Sui
Steps to verify prices on Sui
This guide covers steps to verify prices on Sui. Choose a Contract explains which to use.
Pyth Pro
Integrating with Pyth Pro in smart contracts is a three-step process:
- Subscribe to Pyth Pro websocket to receive price updates on backend or frontend.
- Verify price updates in a Programmable Transaction Block (PTB) using the Pyth Lazer Sui SDK.
- Consume the verified
Updatein your smart contract.
Subscribe to Pyth Pro to receive Price Updates
Pyth Pro provides a websocket endpoint to receive price updates. Pyth Pro also provides a TypeScript SDK to subscribe to the websocket endpoint.
Consult How to subscribe to prices for a complete step-by-step guide.
When subscribing, request the leEcdsa format which is compatible with Sui's secp256k1 signature verification:
client.subscribe({
type: "subscribe",
subscriptionId: 1,
priceFeedIds: [1, 2], // BTC/USD, ETH/USD
properties: ["price", "bestBidPrice", "bestAskPrice", "exponent"],
formats: ["leEcdsa"], // Required for Sui
channel: "fixed_rate@200ms",
jsonBinaryEncoding: "hex",
});Verify price updates in a PTB
Pyth Pro provides a Sui TypeScript SDK that handles the verification call for you.
Always call parse_and_verify_le_ecdsa_update_v2 in a Programmable Transaction Block (PTB), not inside your smart contract.
This allows your contract to work with any newer version of the Pyth Lazer contract, since the Update type remains stable.
Install the Sui SDK:
npm install @pythnetwork/pyth-lazer-sui-jsUse the SDK to build your transaction:
import { SuiGrpcClient } from "@mysten/sui/grpc";
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
import { Transaction } from "@mysten/sui/transactions";
import { PythLazerClient } from "@pythnetwork/pyth-lazer-sdk";
import { addParseAndVerifyLeEcdsaUpdateCall } from "@pythnetwork/pyth-lazer-sui-js";
// 1. Fetch the price update from Pyth Lazer in "leEcdsa" format:
const lazer = await PythLazerClient.create({ token: LAZER_TOKEN });
const latestPrice = await lazer.getLatestPrice({
channel: "fixed_rate@200ms",
formats: ["leEcdsa"],
jsonBinaryEncoding: "hex",
priceFeedIds: [1],
properties: ["price", "bestBidPrice", "bestAskPrice", "exponent"],
});
const update = Buffer.from(latestPrice.leEcdsa?.data ?? "", "hex");
// 2. Create a new Sui transaction:
const signer = Ed25519Keypair.fromSecretKey(SUI_KEY);
const client = new SuiGrpcClient({ baseUrl, network });
const tx = new Transaction();
// 3. Add the parse and verify call:
const verifiedUpdate = await addParseAndVerifyLeEcdsaUpdateCall({
client: client.core,
stateObjectId: STATE_ID,
tx,
update,
});
// 4. Consume `verifiedUpdate` in your own contract with additional calls...
tx.moveCall({
target: `${YOUR_PACKAGE_ID}::your_module::consume_price`,
arguments: [verifiedUpdate],
});
// 5. Sign and execute the transaction:
const result = await client.signAndExecuteTransaction({
signer,
transaction: tx,
});The stateObjectId is the Pyth Lazer State object on Sui. See the Contract Addresses page for the current mainnet and testnet state object IDs.
Consume the verified Update in your smart contract
Your contract receives a verified Update object and can extract the price data it needs. Add the Pyth Lazer package as a dependency in your Move.toml file:
[dependencies]
pyth_lazer = { git = "https://github.com/pyth-network/pyth-crosschain.git", subdir = "lazer/contracts/sui", rev = "main" }To use testnet version of Pyth Lazer contract, use rev = "sui-testnet" instead. We use custom deployment of the Wormhole contract on testnet for maintenance purposes.
Import the necessary modules and write a function that accepts the Update:
use pyth_lazer::update_v2::Update;
use pyth_lazer::feed::Feed;
public fun consume_price(update: Update) {
// Access update metadata
let timestamp = update.timestamp();
let channel = update.channel();
// Get the feeds vector
let feeds = update.feeds_ref();
// Access the first feed
let feed = &feeds[0];
// Extract price data
let feed_id = feed.feed_id();
let price = feed.price();
let exponent = feed.exponent();
// Process the price data according to your application logic
// ...
}See pyth_lazer::feed module for additional fields and their types.
Many feed properties return Option<Option<T>>. The outer Option indicates whether the property was requested in the subscription.
The inner Option indicates whether a value is available (e.g., price may be None if there aren't enough publishers).
Always check both levels when accessing values.
Working with Signed Integers
Pyth prices can be negative (e.g., for certain derivative products). The SDK provides I64 and I16 types for signed integers:
use pyth_lazer::i64::{Self, I64};
// Check if a price exists and extract it
let price_opt = feed.price();
if (price_opt.is_some()) {
let inner_opt = price_opt.borrow();
if (inner_opt.is_some()) {
let price_i64 = inner_opt.borrow();
// Check if negative
let is_negative = price_i64.get_is_negative();
// Get the magnitude
let magnitude = if (is_negative) {
price_i64.get_magnitude_if_negative()
} else {
price_i64.get_magnitude_if_positive()
};
}
}Pyth Core contracts
This section explains how Sui contracts read Pyth prices from the Pyth Core contracts.
The Pyth Core contracts on Sui could not be upgraded in place on August 26,
2026. Switch the Pyth package rev below to
sui-pro-compatible-contract-mainnet (or
sui-pro-compatible-contract-testnet) and the Pyth State and Wormhole State
IDs to the upgraded Sui addresses.
The Sui upgrade guide has the
steps.
Install Pyth SDK
Use the following dependency in your Move.toml file to use the latest Pyth Sui package and its dependencies:
[dependencies.Pyth]
git = "https://github.com/pyth-network/pyth-crosschain.git"
subdir = "target_chains/sui/contracts"
rev = "sui-contract-mainnet"
[dependencies.Wormhole]
git = "https://github.com/wormhole-foundation/wormhole.git"
subdir = "sui/wormhole"
rev = "sui/mainnet"
# Pyth is locked into this specific `rev` because the package depends on Wormhole and is pinned to this version.
[dependencies.Sui]
git = "https://github.com/MystenLabs/sui.git"
subdir = "crates/sui-framework/packages/sui-framework"
rev = "041c5f2bae2fe52079e44b70514333532d69f4e6"
Pyth also provides a javascript SDK to construct transaction blocks that update price feeds:
# NPM
npm install --save @pythnetwork/pyth-sui-js
# Yarn
yarn add @pythnetwork/pyth-sui-js
Write Contract Code
The code snippet below provides a general template for what your contract code should look like:
/// Module: oracle
module oracle::oracle;
use sui::clock::Clock;
use pyth::price_info;
use pyth::price_identifier;
use pyth::price;
use pyth::i64::I64;
use pyth::pyth;
use pyth::price_info::PriceInfoObject;
const E_INVALID_ID: u64 = 1;
public fun get_sui_price(
// Other arguments
clock: &Clock,
price_info_object: &PriceInfoObject,
): I64 {
let max_age = 60;
// Make sure the price is not older than max_age seconds
let price_struct = pyth::get_price_no_older_than(price_info_object, clock, max_age);
// Check the price feed ID
let price_info = price_info::get_price_info_from_price_info_object(price_info_object);
let price_id = price_identifier::get_bytes(&price_info::get_price_identifier(&price_info));
// SUI/USD price feed ID
// The complete list of feed IDs is available at https://docs.pyth.network/price-feeds/price-feeds
// Note: Sui uses the Pyth price feed ID without the `0x` prefix.
let testnet_sui_price_id = x"23d7315113f5b1d3ba7a83604c44b94d79f4fd69af77f804fc7f920a6dc65744";
assert!(price_id == testnet_sui_price_id, E_INVALID_ID);
// Extract the price, decimal, and timestamp from the price struct and use them.
let _decimal_i64 = price::get_expo(&price_struct);
let price_i64 = price::get_price(&price_struct);
let _timestamp_sec = price::get_timestamp(&price_struct);
price_i64
}Read the price with pyth::get_price_no_older_than, as shown above, or other utility functions on the PriceInfoObject.
The code snippet below provides an example of how to update the Pyth price feeds:
import {
SuiPythClient,
SuiPriceServiceConnection,
} from "@pythnetwork/pyth-sui-js";
import { SuiGrpcClient } from "@mysten/sui/grpc";
import { Transaction } from "@mysten/sui/transactions";
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
/// Step 1: Get the off-chain data.
const connection = new SuiPriceServiceConnection(
"https://pyth.dourolabs.app/hermes", // upgraded endpoint
// Or: "https://hermes.pyth.network" — auth required since August 26, 2026
{ accessToken: process.env.PYTH_API_KEY },
);
const priceIDs = [
// You can find the IDs of prices at https://docs.pyth.network/price-feeds/price-feeds
"0x23d7315113f5b1d3ba7a83604c44b94d79f4fd69af77f804fc7f920a6dc65744", // SUI/USD price ID
];
const priceUpdateData = await connection.getPriceFeedsUpdateData(priceIDs);
/// Step 2: Submit the new price on-chain and verify it using the contract.
const suiClient = new SuiGrpcClient({
network: "testnet",
baseUrl: "https://fullnode.testnet.sui.io:443",
});
// Upgraded Pyth Core testnet IDs, from
// https://docs.pyth.network/price-feeds/core/upgrade/contracts#sui
const pythTestnetStateId =
"0x3c48fe392912de6c18087a2b3f5fdbfbfdb4598e180947feff1f12f8e9ea073e"; // Testnet
const wormholeTestnetStateId =
"0x750da8e6d16b6a363a39fe2eaa8295ac224a1e6fce4e47b58845e2e8746164f0"; // Testnet
const pythClient = new SuiPythClient(
suiClient,
pythTestnetStateId,
wormholeTestnetStateId,
);
const transaction = new Transaction();
/// By calling the updatePriceFeeds function, the SuiPythClient adds the necessary
/// transactions to the transaction block to update the price feeds.
const priceInfoObjectIds = await pythClient.updatePriceFeeds(
transaction,
priceUpdateData,
priceIDs,
);
const suiPriceObjectId = priceInfoObjectIds[0];
if (!suiPriceObjectId) {
throw new Error("suiPriceObjectId is undefined");
}
/// This is the package id that we receive after publishing `oracle` contract from the previous step.
const testnetExampleContractPackageId =
"0x42d05111a160febe4144338647e0b7a80daea459c765c1e29a7a6198b235f67c";
const CLOCK =
"0x0000000000000000000000000000000000000000000000000000000000000006";
transaction.moveCall({
target: `${testnetExampleContractPackageId}::oracle::get_sui_price`,
arguments: [transaction.object(CLOCK), transaction.object(suiPriceObjectId)],
});
transaction.setGasBudget(1000000000);
const keypair = Ed25519Keypair.fromSecretKey(
process.env.ADMIN_SECRET_KEY!.toLowerCase(),
);
const result = await suiClient.core.signAndExecuteTransaction({
include: { effects: true, events: true },
signer: keypair,
transaction,
});By calling the updatePriceFeeds function, the SuiPythClient adds the necessary transactions to the transaction block to update the price feeds.
Your Sui Move module should NOT have a hard-coded call to pyth::update_single_price_feed. In other words, a contract should never call the Sui Pyth pyth::update_single_price_feed entry point. Instead, it should be called directly from client code (e.g., Typescript or Rust).
When Sui contracts are upgraded, the address changes, which makes the old address no longer valid. If your module has a hard-coded call to pyth::update_single_price_feed living at a fixed call-site, it may eventually get bricked due to how Pyth upgrades are implemented. (Pyth only allow users to interact with the most recent package version for security reasons).
Therefore, you should build a Sui programmable transaction that first updates the price by calling pyth::update_single_price_feed at the latest call-site from the client-side and then call a function in your contract that invokes pyth::get_price on the PriceInfoObject to get the recently updated price.
You can use SuiPythClient to build such transactions and handle all the complexity of updating the price feeds.
Consult Price Updates for the Pyth Core Contracts for more information on how to fetch the pyth_price_update.
Additional Resources
For the Pyth Pro contracts:
- Price Feed IDs: Pyth Terminal lists every supported price feed and its ID. To list the feeds from code, see Price Feed IDs.
- Examples: the Sui SDK's complete example builds a transaction that verifies a Pyth Pro price update. pyth-lazer-example-js subscribes to the Pyth Pro websocket.
For the Pyth Core contracts:
-
Price Feed IDs: Price Feed IDs lists every Pyth Core price feed and its ID.
-
Contract Addresses: the upgraded Sui addresses list the Pyth State, Pyth Package, Wormhole State and Wormhole Package IDs. Sui Contract Addresses lists the IDs from before the August 26, 2026 upgrade.
-
Command-line example: SuiRelay.ts fetches update data from Hermes for the given price feeds and calls the Pyth Sui contract with it. Run it from the root of the
pyth-crosschainrepository.SUI_KEYtakes the Sui private key in hex. This command updates SUI/USD on the Sui testnet:export SUI_KEY=YOUR_PRIV_KEY_HEX; export PYTH_API_KEY=YOUR_PYTH_API_KEY; pnpm turbo run example-relay --filter @pythnetwork/pyth-sui-js -- \ --feed-id "23d7315113f5b1d3ba7a83604c44b94d79f4fd69af77f804fc7f920a6dc65744" \ --hermes "https://pyth.dourolabs.app/hermes" \ --hermes-access-token "$PYTH_API_KEY" \ --transport grpc \ --full-node "https://fullnode.testnet.sui.io:443" \ --pyth-state-id "0x3c48fe392912de6c18087a2b3f5fdbfbfdb4598e180947feff1f12f8e9ea073e" \ --wormhole-state-id "0x750da8e6d16b6a363a39fe2eaa8295ac224a1e6fce4e47b58845e2e8746164f0"