-
Notifications
You must be signed in to change notification settings - Fork 0
added jupiter api as op #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| [package] | ||
| name = "jup-price-feed" | ||
| version = "0.1.0" | ||
| edition = "2024" | ||
| # rust-version = "" | ||
|
|
||
| [features] | ||
| default = ["env-testnet"] | ||
| seda-hide = ["seda-sdk-rs/hide-panic-paths"] | ||
| env-testnet = ["testnet", "seda-hide"] | ||
| env-mainnet = ["mainnet", "seda-hide"] | ||
| test = ["testnet"] | ||
|
|
||
| testnet = [] | ||
| mainnet = [] | ||
|
|
||
| [dependencies] | ||
| serde = { version = "1.0" } | ||
| anyhow = "1.0" | ||
| serde_json = { version = "1.0"} | ||
| seda-sdk-rs = { version = "1.1", default-features = false } | ||
| ethabi = "18.0" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # Jupiter Price Feed | ||
|
|
||
| Deployments: | ||
| - [Testnet](https://testnet.explorer.seda.xyz/oracle-programs/6115dbd31500fda073eaf1f8d3a7abff5f2e1e9f434982e7a466e7789dfd3406) | ||
| - [Mainnet](https://explorer.seda.xyz/oracle-programs/f7076891e558ff8fc14bfaf4de16f015fd780d279bf2ed2e0f4e915c169c6850) | ||
|
|
||
| ## Overview | ||
|
|
||
| This Oracle Program gets the price of a specified Solana token in USD by leveraging the Jupiter Lite API and returns the raw price as a string. This program handles one token at a time. | ||
|
|
||
| You can test this Oracle Program on testnet with the following command: | ||
|
|
||
| ```sh | ||
| cargo post-dr jup-price-feed -n seda So11111111111111111111111111111111111111112 -i f7076891e558ff8fc14bfaf4de16f015fd780d279bf2ed2e0f4e915c169c6850 | ||
| ``` | ||
|
|
||
| ## Execution Phase: | ||
|
|
||
| ### Input format | ||
|
|
||
| The Execution Phase expects a single Solana token contract address i.e. `So11111111111111111111111111111111111111112` (SOL). | ||
|
|
||
| ### Process | ||
|
|
||
| 1. Validates the Data Request execution argument is not empty. | ||
| 1. Makes a HTTP call to the Jupiter Lite API. | ||
| 1. Extracts the `usdPrice` field from the response for the specified token. | ||
| 1. Returns the raw price as a string. | ||
|
|
||
| ### Example | ||
|
|
||
| Input: `"So11111111111111111111111111111111111111112"` | ||
|
|
||
| Output: `"245.67"` (raw USD price as string) | ||
|
|
||
| ## Tally Phase | ||
|
|
||
| ### Input | ||
|
|
||
| No additional input is required for this Oracle Program as the Tally Phase only uses the reveals from the Execution Phase. | ||
|
|
||
| ### Process | ||
|
|
||
| 1. Collects all price reveals from oracle nodes. | ||
| 1. Parses each reveal as a raw f64 price value. | ||
| 1. Calculates the median price from all the collected prices. | ||
| 1. Returns the final median price as a string. | ||
|
|
||
| ### Output Format | ||
|
|
||
| The result is returned as a string representation of the median price. | ||
|
|
||
| ### Example | ||
|
|
||
| If execution phase ran with a replication factor of 3 and the prices were: | ||
| - 245.50 | ||
| - 245.67 | ||
| - 245.80 | ||
|
|
||
| The tally phase would return `"245.67"` as the median price. | ||
|
|
||
| ## Supported Data | ||
|
|
||
| Any token supported by the Jupiter Lite API that has a valid Solana contract address. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| use anyhow::Result; | ||
| #[cfg(any(feature = "testnet", feature = "mainnet"))] | ||
| use seda_sdk_rs::{Process, elog, http_fetch, log}; | ||
|
|
||
| // Response: | ||
| // { | ||
| // "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v":{ | ||
| // "usdPrice":0.996, | ||
| // "blockId":365626874, | ||
| // "decimals":9, | ||
| // "priceChange24h":0.03667753493441 | ||
| // } | ||
| // } | ||
|
|
||
| pub fn execution_phase() -> Result<()> { | ||
| // Expected to be in the format "tokenContractAddressA,..." (e.g., "So11111111111111111111111111111111111111112"). | ||
| let dr_inputs_raw = String::from_utf8(Process::get_inputs())?; | ||
|
|
||
| // If no input is provided, log an error and return. | ||
| if dr_inputs_raw.is_empty() { | ||
| elog!("No input provided for the price feed request."); | ||
| Process::error("No input provided".as_bytes()); | ||
| return Ok(()); | ||
| } | ||
|
|
||
| // Log the asset pair being fetched as part of the Execution Standard Out. | ||
| log!("Fetching price for asset: {dr_inputs_raw}"); | ||
|
|
||
| let url: String = ["https://lite-api.jup.ag/price/v3?ids=", &dr_inputs_raw].concat(); | ||
| let response = http_fetch(url, None); | ||
|
|
||
| // Handle the case where the HTTP request failed or was rejected. | ||
| if !response.is_ok() { | ||
| elog!( | ||
| "HTTP Response was rejected: {} - {}", | ||
| response.status, | ||
| String::from_utf8(response.bytes)? | ||
| ); | ||
| Process::error("Error while fetching symbol prices".as_bytes()); | ||
| return Ok(()); | ||
| } | ||
|
|
||
| // Parse the API response as defined earlier. | ||
| let response_data = serde_json::from_slice::< | ||
| serde_json::value::Map<String, serde_json::value::Value>, | ||
| >(&response.bytes)?; | ||
|
|
||
| // Extract the prices for each symbol from the response data. | ||
| let price = if let Some(price_data) = response_data.get(&dr_inputs_raw) { | ||
| price_data["usdPrice"].as_f64().unwrap_or_default() | ||
| } else { | ||
| elog!("Price not found for token: {}", dr_inputs_raw); | ||
| Process::error("Token price not found".as_bytes()); | ||
| return Ok(()); | ||
| }; | ||
|
|
||
| log!("Fetched price: {price:?}"); | ||
|
|
||
| // Report the successful result back to the SEDA network. | ||
| Process::success(&price.to_le_bytes()); | ||
|
|
||
| Ok(()) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| use execution_phase::execution_phase; | ||
| use seda_sdk_rs::oracle_program; | ||
| use tally_phase::tally_phase; | ||
|
|
||
| mod execution_phase; | ||
| mod tally_phase; | ||
|
|
||
| #[oracle_program] | ||
| impl PriceFeed { | ||
| fn execute() { | ||
| execution_phase().unwrap(); | ||
| } | ||
|
|
||
| fn tally() { | ||
| tally_phase().unwrap(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| use anyhow::Result; | ||
| use seda_sdk_rs::{Process, elog, get_reveals, log}; | ||
|
|
||
| pub fn tally_phase() -> Result<()> { | ||
| // Retrieve consensus reveals from the tally phase. | ||
| let reveals = get_reveals()?; | ||
| let mut revealed_prices: Vec<f64> = Vec::with_capacity(reveals.len()); | ||
|
|
||
| for reveal in reveals { | ||
| let reveal_bytes = &reveal.body.reveal; | ||
| let price = match reveal_bytes.as_slice().try_into() { | ||
| Ok(bytes) => f64::from_le_bytes(bytes), | ||
| Err(_) => { | ||
| elog!("Failed to parse revealed price: expected 8 bytes for f64, got {} bytes", reveal_bytes.len()); | ||
| continue; | ||
| } | ||
| }; | ||
|
|
||
| revealed_prices.push(price); | ||
| } | ||
|
|
||
| if revealed_prices.is_empty() { | ||
| // If no valid prices were revealed, report an error indicating no consensus. | ||
| Process::error("No consensus among revealed results".as_bytes()); | ||
| return Ok(()); | ||
| } | ||
|
|
||
| // If there are valid prices revealed, calculate the median price from price reports. | ||
| let final_price = median(&revealed_prices); | ||
| log!("Final median prices: {final_price:?}"); | ||
|
|
||
| // Report the successful result in the tally phase. | ||
| Process::success(&final_price.to_string().as_bytes()); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| /// Finds the median of a list of prices per price report using f64 values. | ||
| fn median(data: &[f64]) -> f64 { | ||
|
gluax marked this conversation as resolved.
|
||
| let mut vals = data.to_vec(); | ||
| vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); | ||
| let mid = vals.len() / 2; | ||
|
|
||
| let median_price = if vals.len() % 2 == 0 { | ||
| (vals[mid - 1] + vals[mid]) / 2.0 | ||
| } else { | ||
| vals[mid] | ||
| }; | ||
|
|
||
| median_price | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neither of these urls match the OP ID in the example command: 739a91df635cca0bad6fe510ba46737ade9eed81f9b431198889cfd687abc03d
we should comment out the mainnet one if it's not deployed there :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.