# Example Oracle Programs

### Quick Links

* [Crypto Price Feed](#crypto-price-feed): Using Pyth Core to build and deploy a crypto feed on SEDA Fast.
* [Session-Aware Equity Feed](#session-aware-equity-feed): Using Pyth Core to build a trading session-aware equity feed.

***

## Example Crypto Price Feed

Using Pyth Core to build and deploy a crypto feed on SEDA Fast.

**Contents**

* [Requirements](#requirements)
* [Getting Started](#getting-started)
* [Build your Crypto Feed](#build-your-crypto-feed)

### **Requirements**

* **Bun**: Install [Bun](https://bun.sh/) for package management and building.
* **Rust**: Install [Rust](https://rustup.rs/) for development and building.
* **WASM**: Install the [`wasm32-wasip1`](https://doc.rust-lang.org/rustc/platform-support/wasm32-wasip1.html) target with `rustup target add wasm32-wasip1` for WASM compilation.
* Alternatively, use the [devcontainer](https://containers.dev/) for a pre-configured environment.
* A SEDA FAST developer key. Visit the [Developer Page](https://seda.xyz/dev) here and access a 7 day trial.

{% hint style="info" %}
If you need more than 7 days for your trial key, please reach out to our team on [Discord](https://discord.com/invite/uBrQJZ2nrB) for an extended free trial.
{% endhint %}

### **Getting Started**

Clone and checkout the seda-starter-kit repository:

```
git clone https://github.com/sedaprotocol/seda-starter-kit.git simple-price-feed; cd simple-price-feed
```

Bun install:

```
bun install
```

Copy and populate the `.env` file's `MNEMONIC` .

```
cp .env.example .env
```

> If you need a SEDA mnemonic this can most easily be achieved by downloading [Keplr wallet](https://keplr.app). You can then claim Testnet SEDA tokens at the [SEDA faucet](https://ping-api.testnet.seda.xyz/faucet)&#x20;

### **Build your Crypto Feed**

Start by navigating to the `src/execution_phase.rs` file. The execution phase contains the main computational component of your FAST feed. This phase dictates how data is fetched and aggregated. The `src/tally_phase.rs` is more important for feeds that require onchain delivery via SEDA Core or that need to be backwards compatible with a SEDA Core feed.

The first step of the program is to define which inputs will be called. In this example we will use Pyth Core as a data source for this feed. We need to know what Pyth Asset ID we want to fetch. Navigate to [https://insights.pyth.network/price-feeds](https://insights.pyth.network/price-feeds/Crypto.BTC%2FUSD) and select a feed. For this example I will take BTC/USD with asset ID `0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43` .&#x20;

```rust
#[derive(Serialize, Deserialize)]
struct PriceFeedInput {
    base_asset_id: String, // e.g. 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
}
```

We expect a stringified price as the output:

```rust
#[derive(Serialize, Deserialize)]
pub struct PriceFeedResponse {
    price: String, // e.g. "10000"
}
```

Next we parse the input and set a placeholder response for the time being:

```rust
use anyhow::Result;
use seda_sdk_rs::{log, Process};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct PriceFeedInput {
    base_asset_id: String, // e.g. 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
}

#[derive(Serialize, Deserialize)]
pub struct PriceFeedResponse {
    price: String,
    price_timestamp: i64
}

pub fn execution_phase() -> Result<()> {
    let price_feed_input = serde_json::from_slice::<PriceFeedInput>(&Process::get_inputs())?;
    let base_asset_id = price_feed_input.base_asset_id;

    log!("Fetching price for asset with id: {}", base_asset_id);

    // Fetch and process data

    let response_placeholder = PriceFeedResponse {
        price: "1000000".to_string(),
        price_timestamp: 123312
    };

    Process::success(serde_json::to_vec(&response_placeholder)?.as_slice());
}

```

Now we have to fetch the price from Pyth. For this we are going to use a [SEDA Data Proxy](https://docs.seda.xyz/home/for-data-providers/operating-a-data-proxy/introduction-to-data-proxy). SEDA hosts Pyth Hermes testnet proxy at: <http://pyth.proxy.testnet.seda.xyz/proxy>&#x20;

with public key: `033ed60cfdeb7e91f718bf28e514e5c2f2990400b4643e86856e530de9b46dfb47`

Lets create src/fetch\_pyth.rs to hold all our data fetching logic.

```rust
// fetch_pyth.rs
use seda_sdk_rs::{HttpFetchOptions, proxy_http_fetch};
use serde::Deserialize;

const TESTNET_PROXY_PUBLIC_KEY: &str =
    "033ed60cfdeb7e91f718bf28e514e5c2f2990400b4643e86856e530de9b46dfb47";
const TESTNET_PROXY_URL: &str = "http://pyth.proxy.testnet.seda.xyz/proxy/";

/* Example Proxy Response:
{
    "parsed": [
        {
            "price": {
               "price": "3206259",
               "conf": "4107",
               "expo": -5,
               "publish_time": 1761595219
            }
        }
*/

#[derive(Debug, Deserialize)]
struct PythPriceParsedResponse {
    price: PythPriceData,
}

#[derive(Debug, Deserialize)]
pub struct PythPriceResponse {
    parsed: Vec<PythPriceParsedResponse>,
}

// They both have the same structure for price data, so we can reuse the same structs
#[derive(Clone, Debug, Deserialize,)]
pub struct PythPriceData {
    pub price: String,
    // pub conf: String, // Confidence interval
    // pub expo: i32, // Exponent - can be used to convert the price to a more readable format
    pub publish_time: i64,
}


pub fn fetch_hermes_pyth_price(ids: Vec<String>) -> Result<PythPriceData, serde_json::Error> {
    // This proxy expects the ids to be in the format "ids[]=id1&ids[]=id2&ids[]=id3"
    let ids_str = ids.join("&ids[]=");

    // Fetch the price data from the Pyth proxy - verify the response is valid and signed by the proxy key
    let response = proxy_http_fetch(
        [TESTNET_PROXY_URL, "price/latest?ids[]=", ids_str.as_str()].concat(),
        Some(TESTNET_PROXY_PUBLIC_KEY.to_string()),
        Some(HttpFetchOptions {
            method: seda_sdk_rs::HttpFetchMethod::Get,
            headers: Default::default(),
            body: None,
            timeout_ms: Some(2_000),
        }),
    );

    // Parse the response into a PythPriceResponse struct
    let parsed_pyth_response = serde_json::from_slice::<PythPriceResponse>(&response.bytes)?;

    // Get the first (and only) price data from the response
    let price_data = 
        parsed_pyth_response
        .parsed
        .first()
        .expect("No price data found")
        .price.clone();

    // Return the price data
    Ok(price_data)
}

```

We then add fetch\_pyth to src/main.rs so it can be accessed from our execution phase

```rust
// src/main.rs
use execution_phase::execution_phase;
use seda_sdk_rs::oracle_program;
use tally_phase::tally_phase;

mod execution_phase;
mod tally_phase;
mod fetch_pyth; // new addition

#[oracle_program]
impl PriceFeed {
    fn execute() {
        execution_phase().unwrap();
    }

    fn tally() {
        tally_phase().unwrap();
    }
}

```

Now update `execution_phase.rs` to fetch the data using `fetch_pyth`  and return the result:

```rust
use anyhow::Result;
use seda_sdk_rs::{log, Process};
use serde::{Deserialize, Serialize};

use crate::fetch_pyth::fetch_hermes_pyth_price;

#[derive(Serialize, Deserialize)]
struct PriceFeedInput {
    base_asset_id: String, // e.g. 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43
}

#[derive(Serialize, Deserialize)]
pub struct PriceFeedResponse {
    price: String,
    price_timestamp: i64,
}

pub fn execution_phase() -> Result<()> {
    let price_feed_input = serde_json::from_slice::<PriceFeedInput>(&Process::get_inputs())?;
    let base_asset_id = price_feed_input.base_asset_id;

    log!("Fetching price for asset with id: {}", base_asset_id);

    let price_response = fetch_hermes_pyth_price(vec![base_asset_id])?;
    
    let response_placeholder = PriceFeedResponse {
        price: price_response.price.to_string(),
        price_timestamp: price_response.publish_time,
    };

    Process::success(serde_json::to_vec(&response_placeholder)?.as_slice());
}

```

Now we just alter `tally_phase.rs` to simply verify that our response is formatted correctly, and then forward it as is:

```rust
use anyhow::Result;
use crate::execution_phase::PriceFeedResponse;
use seda_sdk_rs::{log, Process, get_reveals};

pub fn tally_phase() -> Result<()> {
    // Retrieve consensus reveals from the tally phase.
    let reveals = get_reveals()?;

    if reveals.is_empty() {
        Process::error("No consensus among revealed results".as_bytes());
    }

    // Parse the first reveal as the response
    // SEDA Fast only has one executor, so we use the first (and only) reveal
    let latest_response: PriceFeedResponse = reveals
        .into_iter()
        .map(|reveal| serde_json::from_slice(&reveal.body.reveal))
        .next()
        .ok_or_else(|| anyhow::anyhow!("No reveals to process"))??;

    Process::success(&serde_json::to_vec(&latest_response)?)
}

```

We can now deploy the feed and start querying it:

```
bun run deploy
```

Which should result in something like:

<table><thead><tr><th width="153.859375"></th><th></th></tr></thead><tbody><tr><td>tx</td><td>06C4205396F6480F807A9A7B51D9C159C96E59A32C7D6DEA12F9F56C7051CC2A</td></tr><tr><td>oracleProgramId</td><td>e87808ce3f0809314e92a604164df5312cec84b4f0256c9af0a92000141f53c9</td></tr></tbody></table>

You can then query the fast oracle by calling:&#x20;

```bash
curl -L -X POST 'https://fast-api.testnet.seda.xyz/execute?encoding=json&includeDebugInfo=true'
-H 'Authorization: {{MY_BEARER_TOKEN}}'
-H 'Content-Type: application/json'
--data-raw '{ "execProgramId": "c0dca277052bf42c3a4af363cc73815c7c79119e2b1771c3b50648c902dc792d", "execInputs": { "base_asset_id": "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43" } } '
```

### What's Next

Add additional computation to your feed:

* Unit conversion on the Pyth price (take the expo + price and convert it)
* Handle confidence intervals
* Add a separate quote asset to the Input&#x20;
* Query the quote asset additionally to the base asset&#x20;
* Add a cross-rate for the base asset from e.g. USD -> USDC

***

## Session-Aware Equity Feed

Using Pyth Core to build a trading session-aware equity feed.

### Requirements

* **Bun**: Install [Bun](https://bun.sh/) for package management and building.
* **Rust**: Install [Rust](https://rustup.rs/) for development and building.
* **WASM**: Install the [`wasm32-wasip1`](https://doc.rust-lang.org/rustc/platform-support/wasm32-wasip1.html) target with `rustup target add wasm32-wasip1` for WASM compilation.
* Alternatively, use the [devcontainer](https://containers.dev/) for a pre-configured environment.
* A SEDA FAST developer key. Visit the [Developer Page](https://seda.xyz/dev) here and access a 7 day trial.

{% hint style="info" %}
If you need more than 7 days for your trial key, please reach out to our team on [Discord](https://discord.com/invite/uBrQJZ2nrB) for an extended free trial.
{% endhint %}

### Getting started

Clone and checkout the seda-starter-kit repository:

```
git clone https://github.com/sedaprotocol/seda-starter-kit.git sesssion-aware-feed; cd sesssion-aware-feed
```

Bun install:

```
bun install
```

Copy and populate the `.env` file's `MNEMONIC` .

```
cp .env.example .env
```

> If you need a SEDA mnemonic this can most easily be achieved by downloading [Keplr wallet](https://keplr.app). You can then claim Testnet SEDA tokens at the [SEDA faucet](https://ping-api.testnet.seda.xyz/faucet)&#x20;

### Building your session-aware equities price feed

#### Session awareness

Publicly listed equities typically follow three regular trading session, between 4:00am ET - 8:00pm ET. With SEDA developer can access the Blue Ocean ADS overnight trading session provided exclusively in partnership with Pyth. The full 24/7 trading sessions are as follows:

| Session name  | Session timeframe (US Eastern Time) |
| ------------- | ----------------------------------- |
| Pre-market    | 04:00am-09:30am                     |
| Regular hours | 9:30am-04:00pm                      |
| Post-market   | 04:00pm-08:00pm                     |
| Overnight     | 08:00pm-04:00am+1                   |

{% hint style="info" %}
**Key Points for Weekend Pricing:**

* Friday there is no overnight trading session
* Saturday there is no trading session
* Sunday overnight session provided by Blue Ocean ADS begins at 8pm
  {% endhint %}

Pyth aggregates pricing data across all institutional-grade regulated exchanges for equities. With SEDA you can combine all pricing sessions into one clean end-point for consumption.&#x20;

#### Getting started

Start by navigating to the `src/execution_phase.rs` file. The execution phase contains the main computational component of your FAST feed. This phase dictates how data is fetched and aggregated. The `src/tally_phase.rs` is more important for feeds that require onchain delivery via SEDA Core or that need to be backwards compatible with a SEDA Core feed.

For this feed we will take individual feeds, such as the Pre-market and Regular-market hours to create a unified endpoint to query the latest available price. The program automatically determines what session should be active and queries and returns the data correlated to this session.

We will start by defining our inputs. Since we will be using Pyth Core as a data source for this feed. We need to know what Pyth Asset ID we want to fetch. Navigate to <https://insights.pyth.network/price-feeds> and pick a feed. We are going with NVDA:USD which correlates to Asset IDs:

* Pre-market `0x61c4ca5b9731a79e285a01e24432d57d89f0ecdd4cd7828196ca8992d5eafef6`
* Regular hours `0xb1073854ed24cbc755dc527418f52b7d271f6cc967bbf8d8129112b18860a593`&#x20;

#### Defining your inputs & outputs

As inputs we require the two trading sessions, in chronological order.

```rust
#[derive(Deserialize)]
struct ExecutionInputs {
    pyth_assets: Vec<String>,
}
```

As output we expect the latest in-session price of the two selected sessions. We also want to know which session's data is currently being returned, and if the market is currently in-session, if not the price is stale.&#x20;

```rust
#[derive(Serialize, Deserialize)]
enum Session {
    PreMarket,
    RegularHours,
    Closed
}

#[derive(Serialize, Deserialize)]
pub struct PriceFeedResponse {
    price: String,
    session: Session
}
```

We then parse the expected inputs in the `execution_phase` function and replace the rest of the code with placeholders.

The code will look something like this:

```rust
use anyhow::Result;
use seda_sdk_rs::{elog, http_fetch, log, Process};
use serde::{Deserialize, Serialize};


#[derive(Deserialize)]
struct SessionAwareInputs {
    pyth_assets: Vec<String>,
}

#[derive(Serialize, Deserialize)]
enum Session {
    PreMarket,
    RegularHours,
    Closed
}

#[derive(Serialize, Deserialize)]
pub struct PriceFeedResponse {
    price: String,
    session: Session
}

pub fn execution_phase() -> Result<()> {
    // Get the input parameters for the data request (DR).
    let session_aware_inputs = serde_json::from_slice::<SessionAwareInputs>(&Process::get_inputs())?;

    // TODO: Determine the session based on the current time.

    // TODO: Get the price for the current session.
    
    // Temporary placeholder response.
    let placeholder_response = PriceFeedResponse {
        price: "100.0".to_string(),
        session: Session::RegularHours
    };
    Process::success(&serde_json::to_vec(&placeholder_response)?);
}

```

We continue by adding two dependencies to our Cargo.toml, `chrono` and `chrono-tz`:

`Cargo.toml` :

```toml
[package]
name = "oracle-program"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0"
serde = { version = "1.0", default-features = false }
serde_json = "1.0"
seda-sdk-rs = { version = "1.2" }
chrono = { version = "0.4", default-features = false }
chrono-tz = "0.10"

[profile.release-wasm]
inherits = "release"
lto = "fat"
opt-level = "z"
```

Now we create a new file in `/src` called `sessions.rs` . We will write our session determination logic in there.&#x20;

`sessions.rs`:

```rust
// sessions.rs
use anyhow::Result;
use chrono::{Datelike, Timelike, Weekday};
use chrono_tz::America::New_York;
use serde::{Deserialize, Serialize};

/// Moved from execution_phase.rs
#[derive(Serialize, Deserialize)]
pub enum Session {
    PreMarket,
    RegularHours,
    Closed,
}

impl Session {
    pub fn as_index(&self) -> usize {
        match self {
            Session::PreMarket => 0,
            Session::RegularHours => 1,
            Session::Closed => 2,
        }
    }
}

pub fn get_current_session() -> Result<Session> {
    let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?;
    let now_utc = chrono::DateTime::<chrono::Utc>::from_timestamp(now.as_secs() as i64, now.subsec_nanos())
        .expect("Failed to convert system time to UTC timestamp");

    // Convert to Eastern Time
    let now_et = now_utc.with_timezone(&New_York);
    let weekday = now_et.weekday();
    let hour = now_et.hour();
    let minute = now_et.minute();
    let time_mins = hour * 60 + minute; // minutes since midnight

    // Weekend check
    if weekday == Weekday::Sat || weekday == Weekday::Sun {
        return Ok(Session::Closed);
    }

    // US market hours (Eastern Time):
    // Pre-market: 4:00 AM - 9:30 AM ET
    // Regular:    9:30 AM - 4:00 PM ET
    const PREMARKET_START: u32 = 4 * 60;        // 4:00 AM = 240 mins
    const REGULAR_START: u32 = 9 * 60 + 30;     // 9:30 AM = 570 mins
    const REGULAR_END: u32 = 16 * 60;           // 4:00 PM = 960 mins

    let session = if time_mins >= PREMARKET_START && time_mins < REGULAR_START {
        Session::PreMarket
    } else if time_mins >= REGULAR_START && time_mins < REGULAR_END {
        Session::RegularHours
    } else {
        Session::Closed
    };

    Ok(session)
}
```

Add `mod sessions.rs;` to `main.rs` .

We then call the `get_current_session` function from `execution_phase.rs` :

```rust
pub fn execution_phase() -> Result<()> {
    // Get the input parameters for the data request (DR).
    let session_aware_inputs = serde_json::from_slice::<SessionAwareInputs>(&Process::get_inputs())?;

    // Determine the session based on the current time.
    let session = get_current_session()?;

```

We now want to fetch data for the correct, latest session. We can use the same `fetch_pyth` function we used in our previous example. Create the `src/fetch_pyth.rs` file:

```rust
// fetch_pyth.rs
use seda_sdk_rs::{HttpFetchOptions, proxy_http_fetch};
use serde::Deserialize;

const TESTNET_PROXY_PUBLIC_KEY: &str =
    "033ed60cfdeb7e91f718bf28e514e5c2f2990400b4643e86856e530de9b46dfb47";
const TESTNET_PROXY_URL: &str = "http://pyth.proxy.testnet.seda.xyz/proxy/";

/* Example Proxy Response:
{
    "parsed": [
        {
            "price": {
               "price": "3206259",
               "conf": "4107",
               "expo": -5,
               "publish_time": 1761595219
            }
        }
*/

#[derive(Debug, Deserialize)]
struct PythPriceParsedResponse {
    price: PythPriceData,
}

#[derive(Debug, Deserialize)]
pub struct PythPriceResponse {
    parsed: Vec<PythPriceParsedResponse>,
}

// They both have the same structure for price data, so we can reuse the same structs
#[derive(Clone, Debug, Deserialize,)]
pub struct PythPriceData {
    pub price: String,
    // pub conf: String, // Confidence interval
    // pub expo: i32, // Exponent - can be used to convert the price to a more readable format
    pub publish_time: i64,
}


pub fn fetch_hermes_pyth_price(ids: Vec<String>) -> Result<PythPriceData, serde_json::Error> {
    // This proxy expects the ids to be in the format "ids[]=id1&ids[]=id2&ids[]=id3"
    let ids_str = ids.join("&ids[]=");

    // Fetch the price data from the Pyth proxy - verify the response is valid and signed by the proxy key
    let response = proxy_http_fetch(
        [TESTNET_PROXY_URL, "price/latest?ids[]=", ids_str.as_str()].concat(),
        Some(TESTNET_PROXY_PUBLIC_KEY.to_string()),
        Some(HttpFetchOptions {
            method: seda_sdk_rs::HttpFetchMethod::Get,
            headers: Default::default(),
            body: None,
            timeout_ms: Some(2_000),
        }),
    );

    // Parse the response into a PythPriceResponse struct
    let parsed_pyth_response = serde_json::from_slice::<PythPriceResponse>(&response.bytes)?;

    // Get the first (and only) price data from the response
    let price_data = 
        parsed_pyth_response
        .parsed
        .first()
        .expect("No price data found")
        .price.clone();

    // Return the price data
    Ok(price_data)
}
```

Add `mod fetch_pyth.rs;` to `main.rs` .

Now fetch the price for the correct session and return it as `PriceFeedResponse`:

```rust
pub fn execution_phase() -> Result<()> {
    // Get the input parameters for the data request (DR).
    let session_aware_inputs = serde_json::from_slice::<SessionAwareInputs>(&Process::get_inputs())?;

    // Determine the session based on the current time.
    let session = get_current_session()?;

    let price = fetch_hermes_pyth_price(vec![session_aware_inputs.pyth_assets[session.as_index()].clone()])?;    

    // Temporary placeholder response.
    let response = PriceFeedResponse {
        price: price.price.to_string(),
        session: session,
    };
    Process::success(&serde_json::to_vec(&response)?);
}
```

Almost finish! Just implement the simple `tally_phase.rs` , similar as what we did with the crypto data feed example:

```rust
use anyhow::Result;
use crate::execution_phase::PriceFeedResponse;
use seda_sdk_rs::{Process, get_reveals};

pub fn tally_phase() -> Result<()> {
    // Retrieve consensus reveals from the tally phase.
    let reveals = get_reveals()?;

    if reveals.is_empty() {
        Process::error("No consensus among revealed results".as_bytes());
    }

    // Parse the first reveal as the response
    // SEDA Fast only has one executor, so we use the first (and only) reveal
    let latest_response: PriceFeedResponse = reveals
        .into_iter()
        .map(|reveal| serde_json::from_slice(&reveal.body.reveal))
        .next()
        .ok_or_else(|| anyhow::anyhow!("No reveals to process"))??;

    Process::success(&serde_json::to_vec(&latest_response)?)
}

```

We can now deploy the feed and start querying it:

```
bun run deploy
```

Which should result in something like:

<table><thead><tr><th width="153.859375"></th><th></th></tr></thead><tbody><tr><td>tx</td><td>65CBFC700883A870E24216F86434F1C0534AFBE1E67CCEB4952B1EE5255C81EC</td></tr><tr><td>oracleProgramId</td><td>8cf7808cdb5d16e9fb328007968b31727f8e67ac3c83b655aa61c4ccd4077125</td></tr></tbody></table>

You can then query the fast oracle by calling:&#x20;

```bash
curl -L -X POST 'https://fast-api.testnet.seda.xyz/execute?encoding=json&includeDebugInfo=true' \
-H 'Authorization: Bearer {{BEARER_TOKEN}}' \
-H 'Content-Type: application/json' \
--data-raw '{
    "execProgramId": "8cf7808cdb5d16e9fb328007968b31727f8e67ac3c83b655aa61c4ccd4077125",
    "execInputs": {
        "pyth_assets": ["0x61c4ca5b9731a79e285a01e24432d57d89f0ecdd4cd7828196ca8992d5eafef6", "0xb1073854ed24cbc755dc527418f52b7d271f6cc967bbf8d8129112b18860a593"]
    }
}'
```
