v0.2.7-pre.010

This commit is contained in:
2026-08-23 00:12:39 +02:00
parent 98bf88e431
commit 1391858972
14 changed files with 838 additions and 29 deletions

View File

@@ -1,5 +1,79 @@
// file: crates/ksp-onchain-transport-lib/src/ws_transactions.rs
// version: 2
// version: 3
/// Optional configuration accepted by standard Solana `signatureSubscribe`.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct SolanaSignatureSubscribeConfig {
commitment: std::option::Option<crate::SolanaCommitment>,
enable_received_notification: std::option::Option<bool>,
}
impl SolanaSignatureSubscribeConfig {
/// Creates an explicit signature-subscription configuration.
#[must_use]
pub const fn new(commitment: std::option::Option<crate::SolanaCommitment>, enable_received_notification: std::option::Option<bool>) -> Self {
return Self { commitment, enable_received_notification };
}
/// Returns the optional commitment level.
#[must_use]
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
return self.commitment;
}
/// Returns whether the server should emit the early `receivedSignature` notification when explicitly configured.
#[must_use]
pub const fn enable_received_notification(&self) -> std::option::Option<bool> {
return self.enable_received_notification;
}
fn is_empty(&self) -> bool {
return self.commitment.is_none() && self.enable_received_notification.is_none();
}
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(enable_received_notification) = self.enable_received_notification {
object.insert("enableReceivedNotification".to_owned(), serde_json::Value::Bool(enable_received_notification));
}
return serde_json::Value::Object(object);
}
}
/// Typed value carried by standard Solana `signatureNotification` messages.
#[derive(Clone, Debug, PartialEq)]
pub enum SolanaSignatureNotification {
/// Early notification emitted when the RPC node first receives the signature and `enableReceivedNotification` is enabled.
ReceivedSignature,
/// Terminal processing notification emitted when the configured commitment is reached.
Processed {
/// Nullable transaction-error wire value; `None` means the transaction succeeded at the requested commitment.
err: std::option::Option<serde_json::Value>,
},
}
impl SolanaSignatureNotification {
/// Returns whether this notification terminates the server-side one-shot subscription.
#[must_use]
pub const fn is_terminal(&self) -> bool {
return match self {
Self::ReceivedSignature => false,
Self::Processed { .. } => true,
};
}
/// Returns the transaction-error wire value for a terminal processing notification when present.
#[must_use]
pub const fn err(&self) -> std::option::Option<&serde_json::Value> {
return match self {
Self::ReceivedSignature | Self::Processed { err: std::option::Option::None } => std::option::Option::None,
Self::Processed { err: std::option::Option::Some(err) } => std::option::Option::Some(err),
};
}
}
/// Filter accepted by the standard Solana `logsSubscribe` WebSocket method.
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -51,6 +125,31 @@ impl SolanaLogsNotification {
}
impl crate::WsSession {
/// Subscribes to one Solana transaction signature through standard `signatureSubscribe`.
///
/// The server automatically terminates this subscription after the terminal processed notification. When
/// `enableReceivedNotification` is enabled, an earlier `ReceivedSignature` value may be delivered first without closing the logical handle.
pub async fn signature_subscribe(
&self,
signature: &str,
config: std::option::Option<&crate::SolanaSignatureSubscribeConfig>,
) -> ksp_core_lib::Result<crate::WsSubscription<crate::SolanaRpcResponse<crate::SolanaSignatureNotification>>> {
let mut params = std::vec![serde_json::Value::String(signature.to_owned())];
if let std::option::Option::Some(config) = config
&& !config.is_empty()
{
params.push((*config).to_json_value());
}
return self
.subscribe_typed_with_completion(
crate::WsSubscriptionKind::Signature,
params,
|value| return decode_signature_notification("signatureSubscribe", value),
|notification| return notification.value().is_terminal(),
)
.await;
}
/// Subscribes to Solana transaction logs through standard `logsSubscribe`.
pub async fn logs_subscribe(
&self,
@@ -67,6 +166,24 @@ impl crate::WsSession {
}
}
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum WireSignatureNotification {
Received(std::string::String),
Processed(WireSignatureProcessed),
}
#[derive(serde::Deserialize)]
struct WireSignatureProcessed {
err: serde_json::Value,
}
#[derive(serde::Deserialize)]
struct WireRpcResponseSignature {
context: serde_json::Value,
value: WireSignatureNotification,
}
#[derive(serde::Deserialize)]
struct WireRpcResponse {
context: serde_json::Value,
@@ -80,6 +197,36 @@ struct WireLogsNotification {
logs: std::vec::Vec<std::string::String>,
}
fn decode_signature_notification(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<crate::SolanaRpcResponse<crate::SolanaSignatureNotification>> {
let decoded = crate::decode_wire_json::<WireRpcResponseSignature>(method, value);
let wire = match decoded {
std::result::Result::Ok(wire) => wire,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let context = crate::SolanaRpcContext::decode_wire(method, wire.context);
let context = match context {
std::result::Result::Ok(context) => context,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let notification = match wire.value {
WireSignatureNotification::Received(value) if value == "receivedSignature" => crate::SolanaSignatureNotification::ReceivedSignature,
WireSignatureNotification::Received(_) => {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "signatureSubscribe notification contains an unknown string variant")
.with_context("rpc_method", method),
);
},
WireSignatureNotification::Processed(processed) => {
let err = match processed.err {
serde_json::Value::Null => std::option::Option::None,
value => std::option::Option::Some(value),
};
crate::SolanaSignatureNotification::Processed { err }
},
};
return std::result::Result::Ok(crate::SolanaRpcResponse::new(context, notification));
}
fn decode_logs_notification(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<crate::SolanaRpcResponse<crate::SolanaLogsNotification>> {
let decoded = crate::decode_wire_json::<WireRpcResponse>(method, value);
let wire = match decoded {