v0.2.8-pre.006
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-onchain-transport-lib/src/ws_helius_transactions.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
const MAX_HELIUS_TRANSACTION_FILTER_ACCOUNTS: usize = 50_000;
|
||||
|
||||
@@ -150,7 +150,6 @@ impl HeliusTransactionSubscribeFilter {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn to_json_value(&self) -> serde_json::Value {
|
||||
let mut object = serde_json::Map::new();
|
||||
if let std::option::Option::Some(vote) = self.vote {
|
||||
@@ -261,7 +260,6 @@ impl HeliusTransactionSubscribeOptions {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn to_json_value(self) -> serde_json::Value {
|
||||
let mut object = serde_json::Map::new();
|
||||
if let std::option::Option::Some(commitment) = self.commitment {
|
||||
@@ -283,11 +281,10 @@ impl HeliusTransactionSubscribeOptions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete typed request contract for Helius `transactionSubscribe` before actor registration.
|
||||
/// Complete typed request contract for Helius `transactionSubscribe`.
|
||||
///
|
||||
/// The request owns the exact provider filter and optional result-shaping object. `pre.005` deliberately does not expose a public live subscription method:
|
||||
/// actor-owned registration, notification delivery, reconnect and unsubscribe races are added atomically in `pre.006` so callers never receive an incomplete
|
||||
/// provider subscription handle.
|
||||
/// The request owns the exact provider filter and optional result-shaping object. Validation and serialization occur before actor registration so deterministic
|
||||
/// provider constraints fail without WebSocket I/O.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct HeliusTransactionSubscribeRequest {
|
||||
filter: crate::HeliusTransactionSubscribeFilter,
|
||||
@@ -335,7 +332,6 @@ impl std::fmt::Debug for HeliusTransactionSubscribeRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn helius_transaction_subscribe_params(request: &crate::HeliusTransactionSubscribeRequest) -> ksp_core_lib::Result<std::vec::Vec<serde_json::Value>> {
|
||||
let validation = request.validate();
|
||||
if let std::result::Result::Err(error) = validation {
|
||||
@@ -348,41 +344,189 @@ fn helius_transaction_subscribe_params(request: &crate::HeliusTransactionSubscri
|
||||
return std::result::Result::Ok(params);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
const fn helius_transaction_subscribe_method() -> &'static str {
|
||||
return "transactionSubscribe";
|
||||
/// Full/accounts-mode notification delivered by Helius `transactionSubscribe`.
|
||||
///
|
||||
/// The nested transaction payload is deliberately retained as JSON because its exact Solana wire representation depends on the requested encoding and detail
|
||||
/// mode. KSP types the stable provider envelope while preserving the full nested payload without Program-specific decoding.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct HeliusFullTransactionNotification {
|
||||
transaction: serde_json::Value,
|
||||
signature: std::string::String,
|
||||
slot: u64,
|
||||
transaction_index: u64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
const fn helius_transaction_unsubscribe_method() -> &'static str {
|
||||
return "transactionUnsubscribe";
|
||||
impl HeliusFullTransactionNotification {
|
||||
/// Returns the provider transaction/status payload without interpreting Program-specific contents.
|
||||
#[must_use]
|
||||
pub const fn transaction(&self) -> &serde_json::Value {
|
||||
return &self.transaction;
|
||||
}
|
||||
|
||||
/// Returns the base58 transaction signature reported by Helius.
|
||||
#[must_use]
|
||||
pub fn signature(&self) -> &str {
|
||||
return self.signature.as_str();
|
||||
}
|
||||
|
||||
/// Returns the slot in which the transaction was processed.
|
||||
#[must_use]
|
||||
pub const fn slot(&self) -> u64 {
|
||||
return self.slot;
|
||||
}
|
||||
|
||||
/// Returns the zero-based transaction position within the block.
|
||||
#[must_use]
|
||||
pub const fn transaction_index(&self) -> u64 {
|
||||
return self.transaction_index;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_helius_transaction_subscribe_result(value: serde_json::Value) -> ksp_core_lib::Result<u64> {
|
||||
return match value.as_u64() {
|
||||
std::option::Option::Some(remote_id) => std::result::Result::Ok(remote_id),
|
||||
std::option::Option::None => std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "Helius transactionSubscribe acknowledgement must contain a numeric subscription id")
|
||||
.with_context("rpc_method", "transactionSubscribe"),
|
||||
),
|
||||
};
|
||||
/// Signatures-mode notification delivered by Helius `transactionSubscribe`.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct HeliusTransactionSignatureNotification {
|
||||
signature: std::string::String,
|
||||
slot: u64,
|
||||
transaction_index: u64,
|
||||
err: crate::SolanaWireField<serde_json::Value>,
|
||||
memo: crate::SolanaWireField<std::string::String>,
|
||||
block_time: crate::SolanaWireField<i64>,
|
||||
confirmation_status: crate::SolanaWireField<std::string::String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn helius_transaction_unsubscribe_params(remote_id: u64) -> std::vec::Vec<serde_json::Value> {
|
||||
return std::vec![serde_json::Value::Number(remote_id.into())];
|
||||
impl HeliusTransactionSignatureNotification {
|
||||
/// Returns the base58 transaction signature reported by Helius.
|
||||
#[must_use]
|
||||
pub fn signature(&self) -> &str {
|
||||
return self.signature.as_str();
|
||||
}
|
||||
|
||||
/// Returns the slot in which the transaction was processed.
|
||||
#[must_use]
|
||||
pub const fn slot(&self) -> u64 {
|
||||
return self.slot;
|
||||
}
|
||||
|
||||
/// Returns the zero-based transaction position within the block.
|
||||
#[must_use]
|
||||
pub const fn transaction_index(&self) -> u64 {
|
||||
return self.transaction_index;
|
||||
}
|
||||
|
||||
/// Returns the optional transaction error while preserving omitted/null/value wire states.
|
||||
#[must_use]
|
||||
pub const fn err(&self) -> &crate::SolanaWireField<serde_json::Value> {
|
||||
return &self.err;
|
||||
}
|
||||
|
||||
/// Returns the optional memo while preserving omitted/null/value wire states.
|
||||
#[must_use]
|
||||
pub const fn memo(&self) -> &crate::SolanaWireField<std::string::String> {
|
||||
return &self.memo;
|
||||
}
|
||||
|
||||
/// Returns the optional block time while preserving omitted/null/value wire states.
|
||||
#[must_use]
|
||||
pub const fn block_time(&self) -> &crate::SolanaWireField<i64> {
|
||||
return &self.block_time;
|
||||
}
|
||||
|
||||
/// Returns the optional confirmation-status label while preserving omitted/null/value wire states.
|
||||
#[must_use]
|
||||
pub const fn confirmation_status(&self) -> &crate::SolanaWireField<std::string::String> {
|
||||
return &self.confirmation_status;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn decode_helius_transaction_unsubscribe_result(value: serde_json::Value) -> ksp_core_lib::Result<bool> {
|
||||
return match value.as_bool() {
|
||||
std::option::Option::Some(unsubscribed) => std::result::Result::Ok(unsubscribed),
|
||||
std::option::Option::None => std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "Helius transactionUnsubscribe acknowledgement must contain a boolean result")
|
||||
.with_context("rpc_method", "transactionUnsubscribe"),
|
||||
),
|
||||
};
|
||||
/// Typed Helius `transactionNotification` payload union.
|
||||
///
|
||||
/// `Full` also covers the provider `accounts` detail mode because both contain the nested `transaction` member. `Signature` covers the lightweight
|
||||
/// signatures mode. `Unknown` preserves `none` mode and forward-compatible provider shapes instead of failing the logical subscription.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum HeliusTransactionNotification {
|
||||
/// Full/accounts notification carrying the nested transaction payload.
|
||||
Full(crate::HeliusFullTransactionNotification),
|
||||
/// Lightweight signatures notification.
|
||||
Signature(crate::HeliusTransactionSignatureNotification),
|
||||
/// Provider shape not currently typed by KSP, preserved losslessly.
|
||||
Unknown(serde_json::Value),
|
||||
}
|
||||
|
||||
impl crate::HeliusLaserStreamWsSession {
|
||||
/// Opens one Helius `transactionSubscribe` logical subscription through the shared physical actor.
|
||||
///
|
||||
/// The returned handle keeps a stable local identity across physical reconnects. Helius remote subscription IDs stay actor-private and are remapped after
|
||||
/// resubscribe. Calling [`crate::WsSubscription::unsubscribe`] removes the remote mapping before sending `transactionUnsubscribe`, so provider messages
|
||||
/// already in flight after cancellation are ignored without reactivating the logical subscription.
|
||||
pub async fn transaction_subscribe(
|
||||
&self,
|
||||
request: &crate::HeliusTransactionSubscribeRequest,
|
||||
) -> ksp_core_lib::Result<crate::WsSubscription<crate::HeliusTransactionNotification>> {
|
||||
let params = helius_transaction_subscribe_params(request);
|
||||
let params = match params {
|
||||
std::result::Result::Ok(params) => params,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return self
|
||||
.physical_session()
|
||||
.subscribe_typed(crate::WsSubscriptionKind::HeliusTransaction, params, |value| return decode_helius_transaction_notification(value))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireHeliusFullTransactionNotification {
|
||||
transaction: serde_json::Value,
|
||||
signature: std::string::String,
|
||||
slot: u64,
|
||||
transaction_index: u64,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WireHeliusTransactionSignatureNotification {
|
||||
signature: std::string::String,
|
||||
slot: u64,
|
||||
transaction_index: u64,
|
||||
#[serde(default)]
|
||||
err: crate::SolanaWireField<serde_json::Value>,
|
||||
#[serde(default)]
|
||||
memo: crate::SolanaWireField<std::string::String>,
|
||||
#[serde(default)]
|
||||
block_time: crate::SolanaWireField<i64>,
|
||||
#[serde(default)]
|
||||
confirmation_status: crate::SolanaWireField<std::string::String>,
|
||||
}
|
||||
|
||||
fn decode_helius_transaction_notification(value: serde_json::Value) -> ksp_core_lib::Result<crate::HeliusTransactionNotification> {
|
||||
if value.get("transaction").is_some() {
|
||||
let decoded = crate::decode_wire_json::<WireHeliusFullTransactionNotification>("transactionNotification", value.clone());
|
||||
if let std::result::Result::Ok(decoded) = decoded {
|
||||
return std::result::Result::Ok(crate::HeliusTransactionNotification::Full(crate::HeliusFullTransactionNotification {
|
||||
transaction: decoded.transaction,
|
||||
signature: decoded.signature,
|
||||
slot: decoded.slot,
|
||||
transaction_index: decoded.transaction_index,
|
||||
}));
|
||||
}
|
||||
}
|
||||
if value.get("signature").is_some() && value.get("slot").is_some() && value.get("transactionIndex").is_some() {
|
||||
let decoded = crate::decode_wire_json::<WireHeliusTransactionSignatureNotification>("transactionNotification", value.clone());
|
||||
if let std::result::Result::Ok(decoded) = decoded {
|
||||
return std::result::Result::Ok(crate::HeliusTransactionNotification::Signature(crate::HeliusTransactionSignatureNotification {
|
||||
signature: decoded.signature,
|
||||
slot: decoded.slot,
|
||||
transaction_index: decoded.transaction_index,
|
||||
err: decoded.err,
|
||||
memo: decoded.memo,
|
||||
block_time: decoded.block_time,
|
||||
confirmation_status: decoded.confirmation_status,
|
||||
}));
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(crate::HeliusTransactionNotification::Unknown(value));
|
||||
}
|
||||
|
||||
fn validate_account_list(field: &'static str, accounts: std::option::Option<&[ksp_core_lib::Pubkey]>) -> ksp_core_lib::Result<()> {
|
||||
@@ -400,7 +544,6 @@ fn validate_account_list(field: &'static str, accounts: std::option::Option<&[ks
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn insert_account_list(
|
||||
object: &mut serde_json::Map<std::string::String, serde_json::Value>,
|
||||
field: &'static str,
|
||||
|
||||
Reference in New Issue
Block a user