# Crypto Price Feed

## Example Crypto Price Feed

Using Pyth Core or a public API to build and deploy a crypto feed on **SEDA Fast** or **SEDA Core**.

## Contents

* [Requirements](#requirements)
* [Getting Started](#getting-started)
* [Building the Oracle Program](#building-the-oracle-program)
* [Deploying & Querying](#deploying)

***

## 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&#x20;

The easiest way to start building a SEDA Oracle Program is by using the `seda-starter-kit`. This starter kit offers a streamlined setup process and comes with the essential tools and structure to help you create and deploy your first Oracle program on the SEDA network.

Clone and checkout the repository:

```
git clone https://github.com/sedaprotocol/seda-starter-kit.git simple-price-feed
cd simple-price-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 Oracle Program

The PriceFeed Oracle Program is divided into two phases:

Execution: Fetches the price of an asset pair (e.g., BTC-USDT or a Pyth asset ID) from an external source and processes the data.

Tally: Aggregates execution reports.

> 🔎 Difference:
>
> * **SEDA Fast**: Typically runs a single executor and forwards the result.
> * **SEDA Core**: Multiple Overlay Nodes execute the program, and the tally phase aggregates (e.g., median) results.

***

### Step 1: Entrypoint

The Oracle Program logic lives in the `src` folder, where `main.rs` serves as the entry point.

`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; // Required for Pyth-based Fast example

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

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

This setup initializes the PriceFeed Oracle program and links the execution and tally phases that make up the data request.

***

### Step 2: Execution Phase

The execution phase is where the Oracle Program fetches data and performs computation on the fetched data.

### Pyth Core via SEDA Data Proxy

Start by defining inputs and outputs:

```rust
#[derive(Serialize, Deserialize)]
struct PriceFeedInput {
    base_asset_id: String,
}

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

Parse input:

```rust
let price_feed_input = serde_json::from_slice::<PriceFeedInput>(&Process::get_inputs())?;
let base_asset_id = price_feed_input.base_asset_id;
```

#### Create `src/fetch_pyth.rs`

```rust
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/";
```

Define response structures and fetch logic:

```rust
pub fn fetch_hermes_pyth_price(ids: Vec<String>) -> Result<PythPriceData, serde_json::Error> {
    let ids_str = ids.join("&ids[]=");

    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),
        }),
    );

    let parsed_pyth_response = serde_json::from_slice::<PythPriceResponse>(&response.bytes)?;

    let price_data =
        parsed_pyth_response
        .parsed
        .first()
        .expect("No price data found")
        .price.clone();

    Ok(price_data)
}
```

Update execution phase:

```rust
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());
```

{% hint style="info" %}

* This example uses `proxy_http_fetch` and a signed SEDA Data Proxy.
* Suitable for both Fast and Core.
* In **Fast**, the result is typically forwarded directly.
* In **Core**, results are revealed and aggregated in tally phase.
  {% endhint %}

***

### Calling a Public API via http\_fetch

```rust
let response = http_fetch(
    format!(
        "https://api.binance.com/api/v3/ticker/price?symbol={}{}",
        symbol_a.to_uppercase(),
        symbol_b.to_uppercase()
    ),
    None,
);
```

Check for errors:

```rust
if !response.is_ok() {
    elog!(
        "HTTP Response was rejected: {} - {}",
        response.status,
        String::from_utf8(response.bytes)?
    );

    Process::error("Error while fetching price feed".as_bytes());
    return Ok(());
}
```

Parse and compute:

```rust
let data = serde_json::from_slice::<PriceFeedResponse>(&response.bytes)?;
let price: f32 = data.price.parse()?;
let result = (price * 1000000f32) as u128;

Process::success(&result.to_le_bytes());
```

***

### Step 3: Tally Phase

***

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

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

{% hint style="info" %}

* SEDA Fast only has one executor.
* The first reveal is used and forwarded.
  {% endhint %}

***

### SEDA Core Tally Phase (Median Aggregation)

```
let reveals = get_reveals()?;
let mut prices: Vec<u128> = Vec::new();

for reveal in reveals {
    let price_bytes_slice: [u8; 16] = match reveal.body.reveal.try_into() {
        Ok(value) => value,
        Err(_err) => {
            elog!("Reveal body could not be casted to u128");
            continue;
        }
    };

    let price = u128::from_le_bytes(price_bytes_slice);
    prices.push(price);
}

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

let final_price = median(prices);

Process::success(&final_price.to_be_bytes());
```

{% hint style="info" %}
SEDA Core Behavior:

* Multiple Overlay Nodes execute.
* Tally phase aggregates (median).
* Big-endian encoding for EVM compatibility.
  {% endhint %}

***

## Building the Oracle Program

To build:

```
bun run build
```

***

## Deploying

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