From 000d7a05840943a955160ce36efebda42df05edc Mon Sep 17 00:00:00 2001 From: Alexandros Tzimas Date: Thu, 15 May 2025 17:30:16 +0300 Subject: [PATCH 1/4] Update Sui how-to guide with corrected code and IDs 1. The assertion in the Move contract was incorrect. 2. Update the testnet pyth state id in the CLI example. 3. The TS code was deprecated. Used the latest @mysten/sui instead. 4. Added explanatory comments. --- pages/price-feeds/use-real-time-data/sui.mdx | 166 +++++++++++-------- 1 file changed, 98 insertions(+), 68 deletions(-) diff --git a/pages/price-feeds/use-real-time-data/sui.mdx b/pages/price-feeds/use-real-time-data/sui.mdx index 07a94ae4..458e82a3 100644 --- a/pages/price-feeds/use-real-time-data/sui.mdx +++ b/pages/price-feeds/use-real-time-data/sui.mdx @@ -72,39 +72,43 @@ Pyth also provides a javascript SDK to construct transaction blocks that update The code snippet below provides a general template for what your contract code should look like: ```rust {18} copy -module pyth_example::main { - use sui::clock::Clock; - use pyth::price_info; - use pyth::price_identifier; - use pyth::price; - use pyth::pyth; - use pyth::price_info::PriceInfoObject; - - const E_INVALID_ID: u64 = 1; - - public fun use_pyth_price( - // Other arguments - clock: &Clock, - price_info_object: &PriceInfoObject, - ){ - 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)); - - // ETH/USD price feed ID - // The complete list of feed IDs is available at https://pyth.network/developers/price-feed-ids - // Note: Sui uses the Pyth price feed ID without the `0x` prefix. - assert!(price_id!=x"ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", 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); - } +/// 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://pyth.network/developers/price-feed-ids + // Note: Sui uses the Pyth price feed ID without the `0x` prefix. + let testnet_sui_price_id = x"50c67b3fd225db8912a424dd4baed60ffdde625ed2feaaf283724f9608fea266"; + 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); } ``` @@ -113,48 +117,74 @@ One can consume the price by calling `pyth::get_price` abovementioned or other u The code snippet below provides an example of how to update the Pyth price feeds: ```ts copy -import { SuiPriceServiceConnection, SuiPythClient } from "@pythnetwork/pyth-sui-js"; -import { TransactionBlock } from "@mysten/sui.js"; +import { SuiPythClient, SuiPriceServiceConnection } from "@pythnetwork/pyth-sui-js" +import { SuiClient } from "@mysten/sui/client" +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://hermes-beta.pyth.network", // [!] Only for Sui Testnet + // "https://hermes.pyth.network/", // Use this for Mainnet + { + // Provide this option to retrieve signed price updates for on-chain contracts! + priceFeedRequestConfig: { + 'binary': true + } + } +); +const priceIDs = [ + // You can find the IDs of prices at: + // - https://pyth.network/developers/price-feed-ids for Mainnet + // - https://www.pyth.network/developers/price-feed-ids#beta for Testnet + "0x50c67b3fd225db8912a424dd4baed60ffdde625ed2feaaf283724f9608fea266", // SUI/USD price ID +]; +const priceUpdateData = await connection.getPriceFeedsUpdateData(priceIDs); -// Get the Stable Hermes service URL from https://docs.pyth.network/price-feeds/api-instances-and-providers/hermes -const connection = new SuiPriceServiceConnection("https://hermes-beta.pyth.network"); +/// Step 2: Submit the new price on-chain and verify it using the contract. +const suiClient = new SuiClient({ url: 'https://fullnode.testnet.sui.io:443' }); -const priceIDs = [ - // You can find the IDs of prices at https://pyth.network/developers/price-feed-ids - "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43", // BTC/USD price ID - "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", // ETH/USD price ID -]; - -const priceUpdateData = await connection.getPriceFeedsUpdateData(priceIDs); - -// It is either injected from the browser or instantiated in the backend via some private key -const wallet: SignerWithProvider = getWallet(); -// Get the state IDs of the Pyth and Wormhole contracts from -// https://docs.pyth.network/price-feeds/contract-addresses/sui -const wormholeStateId = "0x5306f64e312b581766351c07af79c72fcb1cd25147157fdc2f8ad76de9a3fb6a"; -const pythStateId = "0x1f9310238ee9298fb703c3419030b35b22bb1cc37113e3bb5007c99aec79e5b8"; - -const client = new SuiPythClient(wallet.provider, pythStateId, wormholeStateId); -const tx = new TransactionBlock(); -const priceInfoObjectIds = await client.updatePriceFeeds(tx, priceFeedUpdateData, priceIDs); - -tx.moveCall({ - target: `pyth_example::main::use_pyth_price`, +// Fixed the StateIds using the CLI example extracting them from +// here: https://docs.pyth.network/price-feeds/contract-addresses/sui +const pythTestnetStateId = "0x243759059f4c3111179da5878c12f68d612c21a8d54d85edc86164bb18be1c7c"; // Testnet +const wormholeTestnetStateId = "0x31358d198147da50db32eda2562951d53973a0c0ad5ed738e9b17d88b213d790"; // 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); + +let 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. +let testnetExampleContractPackageId = "0x42d05111a160febe4144338647e0b7a80daea459c765c1e29a7a6198b235f67c"; +const CLOCK = "0x0000000000000000000000000000000000000000000000000000000000000006"; +transaction.moveCall({ + target: `${testnetExampleContractPackageId}::oracle::get_sui_price`, arguments: [ - ..., // other arguments needed for your contract - tx.object(priceInfoObjectIds[0]), + transaction.object(CLOCK), + transaction.object(suiPriceObjectId), ], }); - -const txBlock = { - transactionBlock: tx, - options: { +transaction.setGasBudget(1000000000); + +const keypair = Ed25519Keypair.fromSecretKey( + process.env.ADMIN_SECRET_KEY!.toLowerCase() +); +const result = await suiClient.signAndExecuteTransaction({ + transaction, + signer: keypair, + options: { showEffects: true, showEvents: true, }, -}; - -const result = await wallet.signAndExecuteTransactionBlock(txBlock); +}); ``` By calling the `updatePriceFeeds` function, the `SuiPythClient` adds the necessary transactions to the transaction block to update the price feeds. @@ -190,7 +220,7 @@ export SUI_KEY=YOUR_PRIV_KEY; npm run example-relay -- --feed-id "5a035d5440f5c163069af66062bac6c79377bf88396fa27e6067bfca8096d280" \ --hermes "https://hermes-beta.pyth.network" \ --full-node "https://fullnode.testnet.sui.io:443" \ ---pyth-state-id "0xd3e79c2c083b934e78b3bd58a490ec6b092561954da6e7322e1e2b3c8abfddc0" \ +--pyth-state-id "0x243759059f4c3111179da5878c12f68d612c21a8d54d85edc86164bb18be1c7c" \ --wormhole-state-id "0x31358d198147da50db32eda2562951d53973a0c0ad5ed738e9b17d88b213d790" ``` From 5799959253e33531db38c10166adf0c00e4f3ace Mon Sep 17 00:00:00 2001 From: Alexandros Tzimas Date: Thu, 15 May 2025 17:35:21 +0300 Subject: [PATCH 2/4] Fix lint errors --- pages/price-feeds/use-real-time-data/sui.mdx | 96 +++++++++++--------- 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/pages/price-feeds/use-real-time-data/sui.mdx b/pages/price-feeds/use-real-time-data/sui.mdx index 458e82a3..1b24dbf8 100644 --- a/pages/price-feeds/use-real-time-data/sui.mdx +++ b/pages/price-feeds/use-real-time-data/sui.mdx @@ -91,7 +91,7 @@ public fun get_sui_price( 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); @@ -104,7 +104,7 @@ public fun get_sui_price( // Note: Sui uses the Pyth price feed ID without the `0x` prefix. let testnet_sui_price_id = x"50c67b3fd225db8912a424dd4baed60ffdde625ed2feaaf283724f9608fea266"; 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); @@ -117,73 +117,85 @@ One can consume the price by calling `pyth::get_price` abovementioned or other u The code snippet below provides an example of how to update the Pyth price feeds: ```ts copy -import { SuiPythClient, SuiPriceServiceConnection } from "@pythnetwork/pyth-sui-js" -import { SuiClient } from "@mysten/sui/client" -import { Transaction } from "@mysten/sui/transactions" +import { + SuiPythClient, + SuiPriceServiceConnection, +} from "@pythnetwork/pyth-sui-js"; +import { SuiClient } from "@mysten/sui/client"; +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://hermes-beta.pyth.network", // [!] Only for Sui Testnet - // "https://hermes.pyth.network/", // Use this for Mainnet - { - // Provide this option to retrieve signed price updates for on-chain contracts! - priceFeedRequestConfig: { - 'binary': true - } - } + "https://hermes-beta.pyth.network", // [!] Only for Sui Testnet + // "https://hermes.pyth.network/", // Use this for Mainnet + { + // Provide this option to retrieve signed price updates for on-chain contracts! + priceFeedRequestConfig: { + binary: true, + }, + } ); const priceIDs = [ // You can find the IDs of prices at: // - https://pyth.network/developers/price-feed-ids for Mainnet // - https://www.pyth.network/developers/price-feed-ids#beta for Testnet "0x50c67b3fd225db8912a424dd4baed60ffdde625ed2feaaf283724f9608fea266", // SUI/USD price ID -]; -const priceUpdateData = await connection.getPriceFeedsUpdateData(priceIDs); +]; +const priceUpdateData = await connection.getPriceFeedsUpdateData(priceIDs); -/// Step 2: Submit the new price on-chain and verify it using the contract. -const suiClient = new SuiClient({ url: 'https://fullnode.testnet.sui.io:443' }); +/// Step 2: Submit the new price on-chain and verify it using the contract. +const suiClient = new SuiClient({ url: "https://fullnode.testnet.sui.io:443" }); -// Fixed the StateIds using the CLI example extracting them from +// Fixed the StateIds using the CLI example extracting them from // here: https://docs.pyth.network/price-feeds/contract-addresses/sui -const pythTestnetStateId = "0x243759059f4c3111179da5878c12f68d612c21a8d54d85edc86164bb18be1c7c"; // Testnet -const wormholeTestnetStateId = "0x31358d198147da50db32eda2562951d53973a0c0ad5ed738e9b17d88b213d790"; // Testnet - -const pythClient = new SuiPythClient(suiClient, pythTestnetStateId, wormholeTestnetStateId); +const pythTestnetStateId = + "0x243759059f4c3111179da5878c12f68d612c21a8d54d85edc86164bb18be1c7c"; // Testnet +const wormholeTestnetStateId = + "0x31358d198147da50db32eda2562951d53973a0c0ad5ed738e9b17d88b213d790"; // Testnet + +const pythClient = new SuiPythClient( + suiClient, + pythTestnetStateId, + wormholeTestnetStateId +); const transaction = new Transaction(); -/// By calling the updatePriceFeeds function, the SuiPythClient adds the necessary +/// 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 priceInfoObjectIds = await pythClient.updatePriceFeeds( + transaction, + priceUpdateData, + priceIDs +); -let suiPriceObjectId = priceInfoObjectIds[0] +let suiPriceObjectId = priceInfoObjectIds[0]; if (!suiPriceObjectId) { - throw new Error("suiPriceObjectId is undefined"); + throw new Error("suiPriceObjectId is undefined"); } -/// This is the package id that we receive after publishing `oracle` contract from the previous step. -let testnetExampleContractPackageId = "0x42d05111a160febe4144338647e0b7a80daea459c765c1e29a7a6198b235f67c"; -const CLOCK = "0x0000000000000000000000000000000000000000000000000000000000000006"; +/// This is the package id that we receive after publishing `oracle` contract from the previous step. +let testnetExampleContractPackageId = + "0x42d05111a160febe4144338647e0b7a80daea459c765c1e29a7a6198b235f67c"; +const CLOCK = + "0x0000000000000000000000000000000000000000000000000000000000000006"; transaction.moveCall({ - target: `${testnetExampleContractPackageId}::oracle::get_sui_price`, - arguments: [ - transaction.object(CLOCK), - transaction.object(suiPriceObjectId), - ], + 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() -); + process.env.ADMIN_SECRET_KEY!.toLowerCase() +); const result = await suiClient.signAndExecuteTransaction({ - transaction, - signer: keypair, - options: { - showEffects: true, - showEvents: true, - }, + transaction, + signer: keypair, + options: { + showEffects: true, + showEvents: true, + }, }); ``` From d174e7b3138db80163fade11b90013722c07ece5 Mon Sep 17 00:00:00 2001 From: Alexandros Tzimas Date: Thu, 19 Jun 2025 16:30:48 +0300 Subject: [PATCH 3/4] Resolve PR comment: return price_i64 --- pages/price-feeds/use-real-time-data/sui.mdx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pages/price-feeds/use-real-time-data/sui.mdx b/pages/price-feeds/use-real-time-data/sui.mdx index 1b24dbf8..22b39339 100644 --- a/pages/price-feeds/use-real-time-data/sui.mdx +++ b/pages/price-feeds/use-real-time-data/sui.mdx @@ -106,9 +106,11 @@ public fun get_sui_price( 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 _decimal_i64 = price::get_expo(&price_struct); let price_i64 = price::get_price(&price_struct); - let timestamp_sec = price::get_timestamp(&price_struct); + let _timestamp_sec = price::get_timestamp(&price_struct); + + price_i64 } ``` From 92541e185e175fcba310e67bbe3f3caec4d488d9 Mon Sep 17 00:00:00 2001 From: Alexandros Tzimas Date: Fri, 20 Jun 2025 13:44:54 +0300 Subject: [PATCH 4/4] Run pre-commit --- pages/price-feeds/use-real-time-data/sui.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/price-feeds/use-real-time-data/sui.mdx b/pages/price-feeds/use-real-time-data/sui.mdx index 22b39339..2a51196e 100644 --- a/pages/price-feeds/use-real-time-data/sui.mdx +++ b/pages/price-feeds/use-real-time-data/sui.mdx @@ -109,7 +109,7 @@ public fun get_sui_price( 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 } ```