From 41804c8fd2fdcc7ad655bbc459ec4fc07f2f69e1 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 24 Jul 2026 01:30:18 +0200 Subject: [PATCH] Separated fetch --- src/engine/engine.rs | 87 +------------------------------------------- src/engine/fetch.rs | 80 ++++++++++++++++++++++++++++++++++++++++ src/engine/main.rs | 1 + 3 files changed, 82 insertions(+), 86 deletions(-) create mode 100644 src/engine/fetch.rs diff --git a/src/engine/engine.rs b/src/engine/engine.rs index a4f3589..ed734b6 100644 --- a/src/engine/engine.rs +++ b/src/engine/engine.rs @@ -1,8 +1,3 @@ -use std::collections::HashMap; - -use pulse_wire::terminal::WatchListItem; -use serde_json::Value; - use crate::terminal::TerminalServer; const WATCH_LIST_SYMBOLS: &[&str] = &["BTC", "ETH", "SOL", "XRP"]; @@ -38,7 +33,7 @@ impl Engine { loop { refresh.tick().await; - match fetch_watch_list(WATCH_LIST_SYMBOLS).await { + match crate::fetch::fetch_watch_list(WATCH_LIST_SYMBOLS).await { Ok(watch_list) => { if let Err(error) = terminal_server .broadcast( @@ -63,83 +58,3 @@ impl Engine { } } } - -async fn fetch_watch_list(symbols: &[&str]) -> Result, String> { - let response = hypersdk::hypercore::mainnet() - .meta_and_asset_ctxs(None) - .await - .map_err(|error| format!("Hyperliquid metaAndAssetCtxs request failed: {error}"))?; - - normalize_watch_list(&response, symbols) -} - -fn normalize_watch_list(response: &Value, symbols: &[&str]) -> Result, String> { - let response = response - .as_array() - .ok_or("metaAndAssetCtxs response must be an array")?; - - if response.len() != 2 { - return Err(format!( - "metaAndAssetCtxs response must contain meta and contexts, got {} entries", - response.len() - )); - } - - let universe = response[0]["universe"] - .as_array() - .ok_or("metaAndAssetCtxs response is missing meta.universe")?; - let contexts = response[1] - .as_array() - .ok_or("metaAndAssetCtxs response contexts must be an array")?; - - if universe.len() != contexts.len() { - return Err(format!( - "Hyperliquid returned {} instruments but {} contexts", - universe.len(), - contexts.len() - )); - } - - let mut by_symbol = HashMap::with_capacity(universe.len()); - - for (meta, context) in universe.iter().zip(contexts) { - let symbol = meta["name"] - .as_str() - .ok_or("instrument metadata is missing a name")?; - let price = number(context, "markPx")?; - let previous_day_price = number(context, "prevDayPx")?; - let volume_24h = number(context, "dayNtlVlm")?; - - if previous_day_price <= 0.0 { - return Err(format!("{symbol} has an invalid previous-day price")); - } - - by_symbol.insert( - symbol, - WatchListItem { - symbol: symbol.to_owned(), - price, - trend: ((price / previous_day_price) - 1.0) * 100.0, - volume_24h, - }, - ); - } - - symbols - .iter() - .map(|symbol| { - by_symbol - .remove(*symbol) - .ok_or_else(|| format!("{symbol} is not in the Hyperliquid perpetual universe")) - }) - .collect() -} - -fn number(value: &Value, field: &str) -> Result { - let raw = value[field] - .as_str() - .ok_or_else(|| format!("asset context field {field} must be a string"))?; - - raw.parse::() - .map_err(|error| format!("could not parse asset context field {field} ({raw}): {error}")) -} diff --git a/src/engine/fetch.rs b/src/engine/fetch.rs new file mode 100644 index 0000000..1c6ae18 --- /dev/null +++ b/src/engine/fetch.rs @@ -0,0 +1,80 @@ +use std::collections::HashMap; + +use pulse_wire::terminal::WatchListItem; +use serde_json::Value; + +fn number(value: &Value, field: &str) -> Result { + let raw = value[field] + .as_str() + .ok_or_else(|| format!("asset context field {field} must be a string"))?; + + raw.parse::() + .map_err(|error| format!("could not parse asset context field {field} ({raw}): {error}")) +} + +pub async fn fetch_watch_list(symbols: &[&str]) -> Result, String> { + let response = hypersdk::hypercore::mainnet() + .meta_and_asset_ctxs(None) + .await + .map_err(|error| format!("Hyperliquid metaAndAssetCtxs request failed: {error}"))?; + + let response = response + .as_array() + .ok_or("metaAndAssetCtxs response must be an array")?; + + if response.len() != 2 { + return Err(format!( + "metaAndAssetCtxs response must contain meta and contexts, got {} entries", + response.len() + )); + } + + let universe = response[0]["universe"] + .as_array() + .ok_or("metaAndAssetCtxs response is missing meta.universe")?; + let contexts = response[1] + .as_array() + .ok_or("metaAndAssetCtxs response contexts must be an array")?; + + if universe.len() != contexts.len() { + return Err(format!( + "Hyperliquid returned {} instruments but {} contexts", + universe.len(), + contexts.len() + )); + } + + let mut by_symbol = HashMap::with_capacity(universe.len()); + + for (meta, context) in universe.iter().zip(contexts) { + let symbol = meta["name"] + .as_str() + .ok_or("instrument metadata is missing a name")?; + let price = number(context, "markPx")?; + let previous_day_price = number(context, "prevDayPx")?; + let volume_24h = number(context, "dayNtlVlm")?; + + if previous_day_price <= 0.0 { + return Err(format!("{symbol} has an invalid previous-day price")); + } + + by_symbol.insert( + symbol, + WatchListItem { + symbol: symbol.to_owned(), + price, + trend: ((price / previous_day_price) - 1.0) * 100.0, + volume_24h, + }, + ); + } + + symbols + .iter() + .map(|symbol| { + by_symbol + .remove(*symbol) + .ok_or_else(|| format!("{symbol} is not in the Hyperliquid perpetual universe")) + }) + .collect() +} diff --git a/src/engine/main.rs b/src/engine/main.rs index 55247d7..bb7f06f 100644 --- a/src/engine/main.rs +++ b/src/engine/main.rs @@ -1,4 +1,5 @@ pub mod engine; +pub mod fetch; pub mod terminal; #[tokio::main]