From a3421c6555a61998af0004a378e57a8ffaaca464 Mon Sep 17 00:00:00 2001 From: Daniel Chew Date: Wed, 31 Jul 2024 13:47:09 +0900 Subject: [PATCH 1/2] add fuel example --- .../price-feeds/use-real-time-data/_meta.json | 1 + pages/price-feeds/use-real-time-data/fuel.mdx | 188 ++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 pages/price-feeds/use-real-time-data/fuel.mdx diff --git a/pages/price-feeds/use-real-time-data/_meta.json b/pages/price-feeds/use-real-time-data/_meta.json index 7b8848f7..7cbb21f3 100644 --- a/pages/price-feeds/use-real-time-data/_meta.json +++ b/pages/price-feeds/use-real-time-data/_meta.json @@ -2,6 +2,7 @@ "evm": "in EVM Contracts", "solana": "in Solana and SVM Programs", "starknet": "in Starknet Contracts", + "fuel": "in Fuel Contracts", "aptos": "in Aptos Contracts", "sui": "in Sui Contracts", "cosmwasm": "in CosmWasm Contracts", diff --git a/pages/price-feeds/use-real-time-data/fuel.mdx b/pages/price-feeds/use-real-time-data/fuel.mdx new file mode 100644 index 00000000..ebe8b3b3 --- /dev/null +++ b/pages/price-feeds/use-real-time-data/fuel.mdx @@ -0,0 +1,188 @@ +--- +description: Consume Pyth Network prices in Fuel applications +--- + +import { Callout, Tabs } from "nextra/components"; + +# How to Use Real-Time Data in Fuel Contracts + +This guide explains how to use real-time Pyth data in Fuel contracts. + +## Install the Pyth SDK + +Use the following dependency in your `Forc.toml` file to use the latest Pyth Fuel package: + +```toml copy +[dependencies] +pyth_interface = { git = "https://github.com/pyth-network/pyth-crosschain", tag = "pyth-fuel-contract-v0.5.0" } +``` + +Pyth also provides a javascript SDK to interact with the Pyth contract on Fuel. You can install it using the following command: + + + ```npm install --save @pythnetwork/pyth-fuel-js ``` + ```yarn add @pythnetwork/pyth-fuel-js ``` + + +## Write Contract Code + +The code snippet below provides an example module fetching the ETH/USD price from Pyth price feeds: + +```sway copy +contract; + +use pyth_interface::{data_structures::price::{Price, PriceFeedId}, PythCore}; + +use std::bytes::Bytes; + +abi UpdatePrice { + fn valid_time_period() -> u64; + fn get_price(price_feed_id: PriceFeedId) -> Price; + fn get_price_unsafe(price_feed_id: PriceFeedId) -> Price; + fn update_fee(update_data: Vec) -> u64; + #[payable] + fn update_price_feeds(update_fee: u64, update_data: Vec); +} + +const PYTH_CONTRACT_ID = 0x1ab91bc1402a187055d3e827017ace566a103ce2a4126517da5d656d6a436aea; // Testnet Contract +const FUEL_ETH_BASE_ASSET_ID = 0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07; + +impl UpdatePrice for Contract { + fn valid_time_period() -> u64 { + let pyth_contract = abi(PythCore, PYTH_CONTRACT_ID); + let period = pyth_contract.valid_time_period(); + period + } + fn get_price(price_feed_id: PriceFeedId) -> Price { + let pyth_contract = abi(PythCore, PYTH_CONTRACT_ID); + let price = pyth_contract.price(price_feed_id); + price + } + fn get_price_unsafe(price_feed_id: PriceFeedId) -> Price { + let pyth_contract = abi(PythCore, PYTH_CONTRACT_ID); + let price = pyth_contract.price_unsafe(price_feed_id); + price + } + fn update_fee(update_data: Vec) -> u64 { + let pyth_contract = abi(PythCore, PYTH_CONTRACT_ID); + let fee = pyth_contract.update_fee(update_data); + fee + } + #[payable] + fn update_price_feeds(update_fee: u64, update_data: Vec) { + let pyth_contract = abi(PythCore, PYTH_CONTRACT_ID); + pyth_contract + .update_price_feeds { + asset_id: FUEL_ETH_BASE_ASSET_ID, + coins: update_fee, + }(update_data); + } +} +``` + +The `update_data` argument contains verified prices from Pyth. +Calling `pyth_contract.update_price_feeds` with this value updates the on-chain Pyth price and ensures your application has recent price data. +The `update_data` can be fetched from Hermes; Consult [Fetch Price Updates](https://docs.pyth.network/price-feeds/fetch-price-updates) for more information on how to fetch the `update_data`. + + + Regarding the Pyth contract on Fuel, the caller must pay the fee in the base + asset for functions like `update_price_feeds`. The fee is currently set to the + minimum possible value (1 wei). + + +The code snippet above does the following things: + +1. Defines an `UpdatePrice` ABI with functions to interact with the Pyth contract. +2. Implements the `UpdatePrice` ABI for the contract, providing the following functionality: + + - `valid_time_period()`: Retrieves the valid time period from the Pyth contract. + - `get_price(price_feed_id)`: Gets the price for a given price feed ID. + - `get_price_unsafe(price_feed_id)`: Gets the price for a given price feed ID without staleness checks. + - `update_fee(update_data)`: Calculates the fee required to update the price feeds. + - `update_price_feeds(update_fee, update_data)`: Updates the price feeds with the provided data and fee. + +3. Uses the `PYTH_CONTRACT_ID` constant to interact with the Pyth contract on testnet. +4. Uses the `FUEL_ETH_BASE_ASSET_ID` constant as the asset ID for paying update fees. + +To use this contract, you would typically: + +1. Call `update_fee()` to get the required fee for updating price feeds. +2. Call `update_price_feeds()` with the fee and update data to refresh the price feeds. +3. Use `get_price()` or `get_price_unsafe()` to read the updated prices. + +### Write Client Code + +The code snippet below provides an example of how to fetch price updates using NextJS, a full example can be found [here](https://github.com/pyth-network/pyth-examples/tree/main/price_feeds/fuel/fetch-and-update-btc-price). + +```ts copy +import { TestContractAbi__factory } from "@/sway-api"; +import PYTH_CONTRACT_ABI from "../abi/pyth-contract-abi.json"; +import { arrayify, Contract, hexlify } from "fuels"; + +const HERMES_V2_LATEST_PRICE_UPDATES_ENDPOINT = + "https://hermes.pyth.network/v2/updates/price/latest?ids[]="; +const FUEL_ETH_BASE_ASSET_ID = + "0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07"; +const PRICE_FEED_ID = + "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"; // ETH/USD + +const contractId = + CURRENT_ENVIRONMENT === "local" + ? contractIds.testContract + : (process.env.NEXT_PUBLIC_TESTNET_CONTRACT_ID as string); // Testnet Contract ID +const pythContractId = process.env + .NEXT_PUBLIC_PYTH_TESTNET_CONTRACT_ID as string; // Testnet Contract ID + +async function updateAndGetPrice() { + const fetchPriceUpdateData = async () => { + const response = await fetch( + HERMES_V2_LATEST_PRICE_UPDATES_ENDPOINT + PRICE_FEED_ID + ); + if (!response.ok) { + throw new Error("Failed to fetch price"); + } + const data = await response.json(); + const binaryData = data.binary.data[0]; + const buffer = Buffer.from(binaryData, "hex"); + return buffer; + }; + + const updateData = await fetchPriceUpdateData(); + + const { waitForResult: waitForResultFee } = await contract.functions + .update_fee([arrayify(updateData)]) + .addContracts([pythContract]) + .call(); + const { value: fee } = await waitForResultFee(); + + await contract.functions + .update_price_feeds(fee, [arrayify(updateData)]) + .addContracts([pythContract]) + .callParams({ + forward: [fee, hexlify(FUEL_ETH_BASE_ASSET_ID)], + }) + .call(); + + const { value: price } = await contract.functions + .get_price(hexlify(PRICE_FEED_ID)) + .addContracts([pythContract]) + .get(); + + console.log("Latest ETH/USD price after update:", price); + return price; +} + +updateAndGetPrice().catch(console.error); +``` + +## Additional Resources + +You may find these additional resources helpful for developing your Fuel application. + +### Interface + +The [Fuel Interface](https://github.com/pyth-network/pyth-crosschain/tree/main/target_chains/fuel/contracts/pyth-interface/src) directory contains multiple files that define the functions and structures for interacting with the Pyth contract deployed on Fuel. + +### Example Applications + +- [fetch-and-update-btc-price](https://github.com/pyth-network/pyth-examples/tree/main/price_feeds/fuel/fetch-and-update-btc-price), which fetches the latest price update from Hermes and updates the Pyth price feeds on Fuel. From 0d3eb84e8e952250774392c79b80f7d5063e8691 Mon Sep 17 00:00:00 2001 From: Daniel Chew Date: Wed, 31 Jul 2024 17:11:07 +0900 Subject: [PATCH 2/2] update client code to use HermesClient --- pages/price-feeds/use-real-time-data/fuel.mdx | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pages/price-feeds/use-real-time-data/fuel.mdx b/pages/price-feeds/use-real-time-data/fuel.mdx index ebe8b3b3..a2c6f9b2 100644 --- a/pages/price-feeds/use-real-time-data/fuel.mdx +++ b/pages/price-feeds/use-real-time-data/fuel.mdx @@ -118,12 +118,12 @@ The code snippet below provides an example of how to fetch price updates using N import { TestContractAbi__factory } from "@/sway-api"; import PYTH_CONTRACT_ABI from "../abi/pyth-contract-abi.json"; import { arrayify, Contract, hexlify } from "fuels"; +import { HermesClient } from "@pythnetwork/hermes-client"; -const HERMES_V2_LATEST_PRICE_UPDATES_ENDPOINT = - "https://hermes.pyth.network/v2/updates/price/latest?ids[]="; +const HERMES_ENDPOINT = "https://hermes.pyth.network/"; const FUEL_ETH_BASE_ASSET_ID = "0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07"; -const PRICE_FEED_ID = +const ETH_USD_PRICE_FEED_ID = "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"; // ETH/USD const contractId = @@ -135,15 +135,14 @@ const pythContractId = process.env async function updateAndGetPrice() { const fetchPriceUpdateData = async () => { - const response = await fetch( - HERMES_V2_LATEST_PRICE_UPDATES_ENDPOINT + PRICE_FEED_ID - ); - if (!response.ok) { - throw new Error("Failed to fetch price"); - } - const data = await response.json(); - const binaryData = data.binary.data[0]; - const buffer = Buffer.from(binaryData, "hex"); + const connection = new HermesClient(HERMES_ENDPOINT); + + // Latest price updates + const priceUpdates = await connection.getLatestPriceUpdates([ + ETH_USD_PRICE_FEED_ID, + ]); + + const buffer = Buffer.from(priceUpdates.binary.data[0], "hex"); return buffer; };