// file: crates/ksp-onchain-transport-lib/src/rpc_common.rs // version: 7 /// Commitment level accepted by typed Solana HTTP, WebSocket and Yellowstone gRPC adapters. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum SolanaCommitment { /// Query the most recent processed bank. Processed, /// Query a bank confirmed by cluster vote. Confirmed, /// Query a finalized bank. Finalized, } impl SolanaCommitment { /// Returns the Solana JSON-RPC commitment string. #[must_use] pub const fn as_str(self) -> &'static str { return match self { Self::Processed => "processed", Self::Confirmed => "confirmed", Self::Finalized => "finalized", }; } } /// Optional commitment-only configuration shared by typed Solana RPC methods. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct SolanaCommitmentConfig { commitment: std::option::Option, } impl SolanaCommitmentConfig { /// Creates an explicit commitment-only configuration. #[must_use] pub const fn new(commitment: std::option::Option) -> Self { return Self { commitment }; } /// Returns the optional commitment level. #[must_use] pub const fn commitment(&self) -> std::option::Option { return self.commitment; } /// Serializes this config to the Solana JSON-RPC wire object. #[must_use] pub(crate) fn to_json_value(self) -> serde_json::Value { let mut object = serde_json::Map::new(); if let std::option::Option::Some(commitment) = self.commitment { object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned())); } return serde_json::Value::Object(object); } } /// Optional commitment and minimum-context configuration shared by typed Solana HTTP RPC methods. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct SolanaContextConfig { commitment: std::option::Option, min_context_slot: std::option::Option, } impl SolanaContextConfig { /// Creates an explicit context-aware RPC configuration. #[must_use] pub const fn new(commitment: std::option::Option, min_context_slot: std::option::Option) -> Self { return Self { commitment, min_context_slot }; } /// Returns the optional commitment level. #[must_use] pub const fn commitment(&self) -> std::option::Option { return self.commitment; } /// Returns the optional minimum context slot. #[must_use] pub const fn min_context_slot(&self) -> std::option::Option { return self.min_context_slot; } /// Serializes this config to the Solana JSON-RPC wire object. #[must_use] pub(crate) fn to_json_value(self) -> serde_json::Value { let mut object = serde_json::Map::new(); if let std::option::Option::Some(commitment) = self.commitment { object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned())); } if let std::option::Option::Some(min_context_slot) = self.min_context_slot { object.insert("minContextSlot".to_owned(), serde_json::Value::Number(min_context_slot.into())); } return serde_json::Value::Object(object); } } /// Typed Solana RPC context shared by contextual HTTP and WebSocket responses. #[derive(Clone, Debug, Eq, PartialEq)] pub struct SolanaRpcContext { slot: u64, api_version: std::option::Option, } impl SolanaRpcContext { /// Returns the context slot reported by the RPC node. #[must_use] pub const fn slot(&self) -> u64 { return self.slot; } /// Returns the optional RPC API version reported by the node. #[must_use] pub fn api_version(&self) -> std::option::Option<&str> { return match self.api_version.as_ref() { std::option::Option::Some(value) => std::option::Option::Some(value.as_str()), std::option::Option::None => std::option::Option::None, }; } /// Decodes one RPC context from a parsed JSON value for typed RPC adapters. pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result { let decoded = decode_wire_json::(method, value); let context = match decoded { std::result::Result::Ok(context) => context, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return std::result::Result::Ok(Self { slot: context.slot, api_version: context.api_version }); } } /// Generic contextual result returned by typed Solana HTTP and WebSocket adapters. #[derive(Clone, Debug, PartialEq)] pub struct SolanaRpcResponse { context: crate::SolanaRpcContext, value: T, } impl SolanaRpcResponse { /// Returns the Solana response context. #[must_use] pub const fn context(&self) -> &crate::SolanaRpcContext { return &self.context; } /// Returns the typed response value. #[must_use] pub const fn value(&self) -> &T { return &self.value; } /// Creates a contextual response after wire decoding and validation. #[must_use] pub(crate) const fn new(context: crate::SolanaRpcContext, value: T) -> Self { return Self { context, value }; } } #[derive(serde::Deserialize)] struct WireRpcContext { slot: u64, #[serde(rename = "apiVersion", default)] api_version: std::option::Option, } /// Decodes one private serde wire type and maps shape failures to the shared Transport error domain. pub(crate) fn decode_wire_json(method: &str, value: serde_json::Value) -> ksp_core_lib::Result { let decoded = serde_json::from_value::(value); return match decoded { std::result::Result::Ok(decoded) => std::result::Result::Ok(decoded), std::result::Result::Err(error) => std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "typed Solana RPC response has an invalid wire shape") .with_context("rpc_method", method) .with_source(error), ), }; } /// Parses a base58 public key from one wire field without echoing its value into diagnostics. pub(crate) fn parse_wire_pubkey(method: &str, field: &str, value: &str) -> ksp_core_lib::Result { let parsed = value.parse::(); return match parsed { std::result::Result::Ok(pubkey) => std::result::Result::Ok(pubkey), std::result::Result::Err(_) => std::result::Result::Err( ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "typed Solana RPC response contains an invalid public key") .with_context("rpc_method", method) .with_context("field", field), ), }; } #[cfg(test)] #[path = "../unit_tests/rpc_common.rs"] mod tests;