// file: crates/ksp-offchain-transport-lib/src/market_price_observation.rs // version: 3 /// Maximum UTF-8 byte length accepted for safe provider provenance. pub const MARKET_PRICE_PROVENANCE_MAX_BYTES: usize = 256; /// Millisecond UTC timestamp used by provider-neutral public projections. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)] pub struct MarketPriceTimestamp { unix_millis: u64, } impl MarketPriceTimestamp { /// Creates a UTC timestamp from whole milliseconds since Unix epoch. #[must_use] pub const fn from_unix_millis(unix_millis: u64) -> Self { return Self { unix_millis }; } /// Returns whole milliseconds since Unix epoch. #[must_use] pub const fn unix_millis(&self) -> u64 { return self.unix_millis; } } /// Safe bounded provenance supplied by one provider adapter. #[derive(Clone, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)] #[serde(try_from = "std::string::String", into = "std::string::String")] pub struct MarketPriceProvenance(std::string::String); impl MarketPriceProvenance { /// Creates bounded non-empty provenance without accepting control characters. pub fn new(value: impl std::convert::Into) -> ksp_core_lib::Result { let value = value.into(); if !valid_provenance(value.as_str()) { return std::result::Result::Err(observation_error()); } return std::result::Result::Ok(Self(value)); } /// Returns the safe provider provenance. #[must_use] pub fn as_str(&self) -> &str { return self.0.as_str(); } } impl std::convert::TryFrom for MarketPriceProvenance { type Error = ksp_core_lib::Error; fn try_from(value: std::string::String) -> std::result::Result { return crate::MarketPriceProvenance::new(value); } } impl std::convert::From for std::string::String { fn from(value: MarketPriceProvenance) -> Self { return value.0; } } /// Public V1 SOL/USD observation normalized by Off-chain Transport. #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub struct MarketPriceObservation { pair: crate::MarketPricePair, price: crate::MarketPriceDecimal, provider_id: crate::MarketPriceProviderId, provider_timestamp: std::option::Option, provenance: crate::MarketPriceProvenance, received_at: crate::MarketPriceTimestamp, request_started_at: crate::MarketPriceTimestamp, semantics: crate::MarketPriceSemantics, } impl MarketPriceObservation { /// Creates one successful normalized SOL/USD observation. pub fn new( provider_id: crate::MarketPriceProviderId, price: crate::MarketPriceDecimal, semantics: crate::MarketPriceSemantics, request_started_at: crate::MarketPriceTimestamp, received_at: crate::MarketPriceTimestamp, provider_timestamp: std::option::Option, provenance: crate::MarketPriceProvenance, ) -> ksp_core_lib::Result { if received_at < request_started_at { return std::result::Result::Err(observation_error()); } return std::result::Result::Ok(Self { pair: crate::MarketPricePair::SolUsd, price, provider_id, provider_timestamp, provenance, received_at, request_started_at, semantics, }); } /// Returns the only V1 pair represented by this observation. #[must_use] pub const fn pair(&self) -> crate::MarketPricePair { return self.pair; } /// Returns the exact positive SOL/USD price. #[must_use] pub const fn price(&self) -> crate::MarketPriceDecimal { return self.price; } /// Returns the opaque provider identifier. #[must_use] pub const fn provider_id(&self) -> &crate::MarketPriceProviderId { return &self.provider_id; } /// Returns a provider timestamp only when the provider adapter has a real price-time field. #[must_use] pub const fn provider_timestamp(&self) -> std::option::Option { return self.provider_timestamp; } /// Returns safe provider provenance. #[must_use] pub const fn provenance(&self) -> &crate::MarketPriceProvenance { return &self.provenance; } /// Returns the KSP wall-clock receipt timestamp. #[must_use] pub const fn received_at(&self) -> crate::MarketPriceTimestamp { return self.received_at; } /// Returns the KSP wall-clock request-start timestamp. #[must_use] pub const fn request_started_at(&self) -> crate::MarketPriceTimestamp { return self.request_started_at; } /// Returns the provider-specific semantic class retained by the normalized observation. #[must_use] pub const fn semantics(&self) -> crate::MarketPriceSemantics { return self.semantics; } } fn observation_error() -> ksp_core_lib::Error { return ksp_core_lib::Error::new(crate::ERROR_CODE_MARKET_PRICE_OBSERVATION_INVALID, "invalid off-chain market-price observation"); } fn valid_provenance(value: &str) -> bool { if value.is_empty() || value.len() > crate::MARKET_PRICE_PROVENANCE_MAX_BYTES || value.trim() != value { return false; } for character in value.chars() { if character.is_control() { return false; } } return true; } #[cfg(test)] #[path = "../unit_tests/market_price_observation.rs"] mod tests;