> For the complete documentation index, see [llms.txt](https://docs.seda.xyz/home/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.seda.xyz/home/for-developers/build-or-access-oracle-programs/session-aware-equity-price-feed.md).

# Session-Aware Equity Price Feed

### Contents

* [Requirements](#requirements)
* [Getting Started](#getting-started)
* [Building the Session-Aware Equities Price Feed](#building-your-session-aware-equities-price-feed)
* [Oracle Program Structure](#oracle-program-structure)
* [Defining Inputs & Outputs](#defining-your-inputs-and-outputs)
* [Defining the Inputs & Outputs](#defining-your-inputs-and-outputs)
* [Build & Deploy](#build-and-deploy)
* [SEDA Fast Deployment](#seda-fast-deployment)
* [SEDA Core Deployment](#seda-core-deployment)

## 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.

```rust
rustup target add wasm32-wasip1
```

Alternatively, use the [devcontainer](https://containers.dev/) for a pre-configured environment.

#### Additional Requirement for SEDA Fast

{% hint style="info" icon="magnifying-glass" %}

* **SEDA Fast** requires a FAST API key for querying.

* **SEDA Core** does not require a FAST API key, but instead requires submitting a Data Request onchain from a supported network or on SEDA Chain directly.
  {% endhint %}

* A **SEDA FAST developer key**. Visit the [Developer Page](https://seda.xyz/dev) 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 session-aware-feed; cd session-aware-feed
```

Install dependencies:

```
bun install
```

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

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

{% hint style="info" %}
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;
{% endhint %}

***

## Building the Session-Aware Equities Price Feed

### Session Awareness

Publicly listed equities typically follow three regular trading sessions, between 4:00am ET - 8:00pm ET. With SEDA, developers can access the Blue Ocean ADS overnight trading session provided exclusively in partnership with Pyth. The full 24/5 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.

***

## Oracle Program Structure

Start by navigating to the `src/execution_phase.rs` file. The execution phase contains the main computational component of your 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.

{% hint style="info" %}

* **SEDA Fast**: Typically runs a single executor and forwards the result.
* **SEDA Core**: Multiple Overlay Nodes can execute the program, and the tally phase aggregates results.
  {% endhint %}

***

## Defining the Inputs & Outputs

We will be using Pyth Core as a data source. 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`

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

```rust
#[derive(Deserialize)]
struct SessionAwareInputs {
    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.

```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 finished! 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)?)
}
```

### SEDA Fast Tally Phase (Single Executor Forwarding)

```rust
pub fn tally_phase() -> Result<()> {
    let reveals = get_reveals()?;

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

    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)?)
}
```

***

### SEDA Core Tally Phase (Consensus-Based Aggregation)

For SEDA Core compatibility, you can extend tally logic to:

* Parse all reveals
* Verify matching session
* Select median price or most recent publish\_time
* Encode result in big-endian for EVM compatibility

Core uses multiple Overlay Nodes and aggregates execution results before returning the final output onchain.

***

## Build & Deploy

Build:

```
bun run build
```

***

### SEDA Fast Deployment

```
bun run deploy
```

Query:

```
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"
        ]
    }
}'
```

***

### SEDA Core Deployment

After building, you'll have the `.wasm` artifacts in the `build` directory.

These artifacts can be deployed to the SEDA network and referenced in onchain Data Requests from supported networks.

Differences between SEDA Core and Fast:

| Component             | SEDA Fast              | SEDA Core                          |
| --------------------- | ---------------------- | ---------------------------------- |
| Execution Replication | Single executor        | Multiple Overlay Nodes             |
| Tally Behavior        | Forward first reveal   | Aggregated consensus (e.g. median) |
| Access Method         | Authenticated REST API | Onchain Data Request               |
| Encoding              | JSON common            | Big-endian for EVM                 |

The Oracle Program logic remains portable between both delivery methods.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.seda.xyz/home/for-developers/build-or-access-oracle-programs/session-aware-equity-price-feed.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
