From 92d2f74662ba4c9ab7158f34b32b4dc64b943961 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Wed, 27 Nov 2024 11:48:54 -0500 Subject: [PATCH 01/21] Add priority_fee_cap Type --- src/types/types.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/types/types.rs b/src/types/types.rs index 3b0f97c..f627021 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -956,6 +956,7 @@ pub struct CreateSmartTransactionConfig { pub signers: Vec>, pub lookup_tables: Option>, pub fee_payer: Option>, + pub priority_fee_cap: Option, } impl CreateSmartTransactionConfig { @@ -965,6 +966,7 @@ impl CreateSmartTransactionConfig { signers, lookup_tables: None, fee_payer: None, + priority_fee_cap: None, } } } @@ -1016,6 +1018,7 @@ pub struct CreateSmartTransactionSeedConfig { pub signer_seeds: Vec<[u8; 32]>, pub fee_payer_seed: Option<[u8; 32]>, pub lookup_tables: Option>, + pub priority_fee_cap: Option, } impl CreateSmartTransactionSeedConfig { @@ -1025,6 +1028,7 @@ impl CreateSmartTransactionSeedConfig { signer_seeds, fee_payer_seed: None, lookup_tables: None, + priority_fee_cap: None, } } From 333cea7e677d176f61c0769d32f4f5371cc21fe5 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Wed, 27 Nov 2024 11:49:15 -0500 Subject: [PATCH 02/21] Implement Priority Fee Cap For Smart Txns --- src/optimized_transaction.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 108885a..5d680fc 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -243,10 +243,17 @@ impl Helius { .ok_or(HeliusError::InvalidInput( "Priority fee estimate not available".to_string(), ))? as u64; + + let priority_fee: u64 = if let Some(provided_fee) = config.priority_fee_cap { + // Take the minimum between the estimate and the user-provided cap + std::cmp::min(priority_fee_recommendation, provided_fee) + } else { + priority_fee_recommendation + }; // Add the compute unit price instruction with the estimated fee let compute_budget_ix: Instruction = - ComputeBudgetInstruction::set_compute_unit_price(priority_fee_recommendation); + ComputeBudgetInstruction::set_compute_unit_price(priority_fee); let mut updated_instructions: Vec = config.instructions.clone(); updated_instructions.push(compute_budget_ix.clone()); final_instructions.push(compute_budget_ix); @@ -474,6 +481,7 @@ impl Helius { signers, lookup_tables: create_config.lookup_tables, fee_payer: Some(fee_payer), + priority_fee_cap: create_config.priority_fee_cap, }; let smart_transaction_config: SmartTransactionConfig = SmartTransactionConfig { From daec7a7f80dd1ae4de692afa7637fc2cdbb88c65 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Wed, 27 Nov 2024 11:49:34 -0500 Subject: [PATCH 03/21] Update Smart Txn With Tip Example With Priority Fee Cap --- examples/send_smart_transaction_with_tip.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/send_smart_transaction_with_tip.rs b/examples/send_smart_transaction_with_tip.rs index 9e3dc74..a0f5f7f 100644 --- a/examples/send_smart_transaction_with_tip.rs +++ b/examples/send_smart_transaction_with_tip.rs @@ -32,6 +32,7 @@ async fn main() { signers: vec![Arc::new(from_keypair)], lookup_tables: None, fee_payer: None, + priority_fee_cap: None, }; let config: SmartTransactionConfig = SmartTransactionConfig { From 495c8c64fa01385d69d0b2bd35bc27f882c23229 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Wed, 27 Nov 2024 11:50:01 -0500 Subject: [PATCH 04/21] Formattooorrr --- src/optimized_transaction.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/optimized_transaction.rs b/src/optimized_transaction.rs index 5d680fc..7cad1f4 100644 --- a/src/optimized_transaction.rs +++ b/src/optimized_transaction.rs @@ -243,7 +243,7 @@ impl Helius { .ok_or(HeliusError::InvalidInput( "Priority fee estimate not available".to_string(), ))? as u64; - + let priority_fee: u64 = if let Some(provided_fee) = config.priority_fee_cap { // Take the minimum between the estimate and the user-provided cap std::cmp::min(priority_fee_recommendation, provided_fee) @@ -252,8 +252,7 @@ impl Helius { }; // Add the compute unit price instruction with the estimated fee - let compute_budget_ix: Instruction = - ComputeBudgetInstruction::set_compute_unit_price(priority_fee); + let compute_budget_ix: Instruction = ComputeBudgetInstruction::set_compute_unit_price(priority_fee); let mut updated_instructions: Vec = config.instructions.clone(); updated_instructions.push(compute_budget_ix.clone()); final_instructions.push(compute_budget_ix); From c17bf570b4c9b01c9d37f2154fa9a431689c4ece Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Wed, 27 Nov 2024 16:01:59 -0500 Subject: [PATCH 05/21] Add Better Ping Pong Health Check --- src/websocket.rs | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/websocket.rs b/src/websocket.rs index fc6c43c..960d2b4 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -31,6 +31,7 @@ use tokio_tungstenite::{ pub const ENHANCED_WEBSOCKET_URL: &str = "wss://atlas-mainnet.helius-rpc.com/?api-key="; const DEFAULT_PING_DURATION_SECONDS: u64 = 10; +const DEFAULT_MAX_FAILED_PINGS: usize = 3; // pub type Result = Result; @@ -52,13 +53,20 @@ pub struct EnhancedWebsocket { impl EnhancedWebsocket { /// Expects enhanced websocket endpoint: wss://atlas-mainnet.helius-rpc.com?api-key= - pub async fn new(url: &str) -> Result { + pub async fn new(url: &str, ping_interval_secs: Option, pong_timeout_secs: Option) -> Result { let (ws, _response) = connect_async(url).await.map_err(HeliusError::Tungstenite)?; let (subscribe_sender, subscribe_receiver) = mpsc::unbounded_channel(); let (_request_sender, request_receiver) = mpsc::unbounded_channel(); let (shutdown_sender, shutdown_receiver) = oneshot::channel(); + let ping_interval = ping_interval_secs.unwrap_or(DEFAULT_PING_DURATION_SECONDS); + let max_failed_pings = if let Some(timeout) = pong_timeout_secs { + (timeout as f64 / ping_interval as f64).ceil() as usize + } else { + DEFAULT_MAX_FAILED_PINGS + }; + Ok(Self { subscribe_sender, shutdown_sender, @@ -68,7 +76,8 @@ impl EnhancedWebsocket { subscribe_receiver, request_receiver, shutdown_receiver, - DEFAULT_PING_DURATION_SECONDS, + ping_interval, + max_failed_pings, )), }) } @@ -189,8 +198,10 @@ impl EnhancedWebsocket { mut request_receiver: mpsc::UnboundedReceiver, mut shutdown_receiver: oneshot::Receiver<()>, ping_duration_seconds: u64, + max_failed_pings: usize, ) -> Result<()> { let mut request_id: u64 = 0; + let mut unmatched_pings: usize = 0; let mut requests_subscribe = BTreeMap::new(); let mut requests_unsubscribe = BTreeMap::>::new(); @@ -209,7 +220,23 @@ impl EnhancedWebsocket { }, // Send `Message::Ping` each 10s if no any other communication () = sleep(Duration::from_secs(ping_duration_seconds)) => { + // Check if we've exceeded our failed ping threshold + if unmatched_pings >= max_failed_pings { + let frame = CloseFrame { + code: CloseCode::Abnormal, + reason: format!("No pong received after {} pings", max_failed_pings).into() + }; + + ws.send(Message::Close(Some(frame))).await?; + ws.flush().await?; + + return Err(HeliusError::WebsocketClosed( + format!("Connection timeout: no pong received after {} pings", max_failed_pings) + )); + } + ws.send(Message::Ping(Vec::new())).await?; + unmatched_pings += 1; }, // Read message for subscribe Some((operation, params, response_sender)) = subscribe_receiver.recv() => { @@ -250,7 +277,10 @@ impl EnhancedWebsocket { ws.send(Message::Pong(data)).await?; continue }, - Message::Pong(_data) => continue, + Message::Pong(_data) => { + unmatched_pings = 0; + continue; + }, Message::Close(_frame) => break, Message::Frame(_frame) => continue, }; From f02987b172bc6d521fd4538b692f9874a1e6c122 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Wed, 27 Nov 2024 16:02:30 -0500 Subject: [PATCH 06/21] Handle New Params and Create new_with_ws_default --- src/client.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/client.rs b/src/client.rs index 8d4ce9b..16b670b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -95,14 +95,19 @@ impl Helius { /// # Arguments /// * `api_key` - The API key required for authenticating requests made /// * `cluster` - The Solana cluster (Devnet or MainnetBeta) that defines the given network environment + /// * `ping_interval_secs` - Optional duration in seconds between ping messages (defaults to 10 seconds if None) + /// * `pong_timeout_secs` - Optional duration in seconds to wait for a pong response before considering the connection dead + /// /// # Returns /// An instance of `Helius` if successful. A `HeliusError` is returned if an error occurs during configuration or initialization of the HTTP, RPC, or WS client - pub async fn new_with_ws(api_key: &str, cluster: Cluster) -> Result { + pub async fn new_with_ws(api_key: &str, cluster: Cluster, ping_interval_secs: Option, pong_timeout_secs: Option) -> Result { let config: Arc = Arc::new(Config::new(api_key, cluster)?); let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; let rpc_client: Arc = Arc::new(RpcClient::new(Arc::new(client.clone()), config.clone())?); let wss: String = format!("{}{}", ENHANCED_WEBSOCKET_URL, api_key); - let ws_client: Arc = Arc::new(EnhancedWebsocket::new(&wss).await?); + let ws_client: Arc = Arc::new( + EnhancedWebsocket::new(&wss, ping_interval_secs, pong_timeout_secs).await? + ); Ok(Helius { config, @@ -113,6 +118,20 @@ impl Helius { }) } + /// Creates a new instance of `Helius` with an enhanced websocket client using default timeout settings. + /// This is a convenience method that uses default values of 10 seconds for ping interval and 3 failed pings + /// before considering the connection dead. + /// + /// # Arguments + /// * `api_key` - The API key required for authenticating requests made + /// * `cluster` - The Solana cluster (Devnet or MainnetBeta) that defines the given network environment + /// + /// # Returns + /// An instance of `Helius` if successful. A `HeliusError` is returned if an error occurs during configuration or initialization of the HTTP, RPC, or WS client + pub async fn new_with_ws_default(api_key: &str, cluster: Cluster) -> Result { + Self::new_with_ws(api_key, cluster, None, None).await + } + /// Provides a thread-safe way to access RPC functionalities /// /// # Returns From 05d8e3b4e5450e2db19da1a7b451abe2907f58ca Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Wed, 27 Nov 2024 16:02:41 -0500 Subject: [PATCH 07/21] Update Examples --- examples/enhanced_websocket_accounts.rs | 2 +- examples/enhanced_websocket_transactions.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/enhanced_websocket_accounts.rs b/examples/enhanced_websocket_accounts.rs index 9a4af96..746df05 100644 --- a/examples/enhanced_websocket_accounts.rs +++ b/examples/enhanced_websocket_accounts.rs @@ -9,7 +9,7 @@ async fn main() -> Result<()> { let api_key: &str = "your_api_key"; let cluster: Cluster = Cluster::MainnetBeta; - let helius: Helius = Helius::new_with_ws(api_key, cluster).await?; + let helius: Helius = Helius::new_with_ws_default(api_key, cluster).await?; let key: pubkey::Pubkey = pubkey!("BtsmiEEvnSuUnKxqXj2PZRYpPJAc7C34mGz8gtJ1DAaH"); diff --git a/examples/enhanced_websocket_transactions.rs b/examples/enhanced_websocket_transactions.rs index 5115c62..ebbcfcc 100644 --- a/examples/enhanced_websocket_transactions.rs +++ b/examples/enhanced_websocket_transactions.rs @@ -9,7 +9,8 @@ async fn main() -> Result<()> { let api_key: &str = "your_api_key"; let cluster: Cluster = Cluster::MainnetBeta; - let helius: Helius = Helius::new_with_ws(api_key, cluster).await.unwrap(); + // Uses custom ping-pong timeouts to ping every 15s and timeout after 45s of no pong + let helius: Helius = Helius::new_with_ws(api_key, cluster, Some(15), Some(45)).await.unwrap(); let key: pubkey::Pubkey = pubkey!("BtsmiEEvnSuUnKxqXj2PZRYpPJAc7C34mGz8gtJ1DAaH"); From c3ad8299f82bb6c8e934e5dbed5578583a78249d Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Wed, 27 Nov 2024 16:02:57 -0500 Subject: [PATCH 08/21] Formattooorrr --- src/client.rs | 14 +++++++++----- src/websocket.rs | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/client.rs b/src/client.rs index 16b670b..257c402 100644 --- a/src/client.rs +++ b/src/client.rs @@ -97,17 +97,21 @@ impl Helius { /// * `cluster` - The Solana cluster (Devnet or MainnetBeta) that defines the given network environment /// * `ping_interval_secs` - Optional duration in seconds between ping messages (defaults to 10 seconds if None) /// * `pong_timeout_secs` - Optional duration in seconds to wait for a pong response before considering the connection dead - /// + /// /// # Returns /// An instance of `Helius` if successful. A `HeliusError` is returned if an error occurs during configuration or initialization of the HTTP, RPC, or WS client - pub async fn new_with_ws(api_key: &str, cluster: Cluster, ping_interval_secs: Option, pong_timeout_secs: Option) -> Result { + pub async fn new_with_ws( + api_key: &str, + cluster: Cluster, + ping_interval_secs: Option, + pong_timeout_secs: Option, + ) -> Result { let config: Arc = Arc::new(Config::new(api_key, cluster)?); let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; let rpc_client: Arc = Arc::new(RpcClient::new(Arc::new(client.clone()), config.clone())?); let wss: String = format!("{}{}", ENHANCED_WEBSOCKET_URL, api_key); - let ws_client: Arc = Arc::new( - EnhancedWebsocket::new(&wss, ping_interval_secs, pong_timeout_secs).await? - ); + let ws_client: Arc = + Arc::new(EnhancedWebsocket::new(&wss, ping_interval_secs, pong_timeout_secs).await?); Ok(Helius { config, diff --git a/src/websocket.rs b/src/websocket.rs index 960d2b4..1327843 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -62,9 +62,9 @@ impl EnhancedWebsocket { let ping_interval = ping_interval_secs.unwrap_or(DEFAULT_PING_DURATION_SECONDS); let max_failed_pings = if let Some(timeout) = pong_timeout_secs { - (timeout as f64 / ping_interval as f64).ceil() as usize + (timeout as f64 / ping_interval as f64).ceil() as usize } else { - DEFAULT_MAX_FAILED_PINGS + DEFAULT_MAX_FAILED_PINGS }; Ok(Self { From d358d16ab27b641169d5388e682d70609320cb0d Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Wed, 27 Nov 2024 16:13:07 -0500 Subject: [PATCH 09/21] Refactor to Make Backwards Compatible --- examples/enhanced_websocket_accounts.rs | 2 +- examples/enhanced_websocket_transactions.rs | 2 +- src/client.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/enhanced_websocket_accounts.rs b/examples/enhanced_websocket_accounts.rs index 746df05..9a4af96 100644 --- a/examples/enhanced_websocket_accounts.rs +++ b/examples/enhanced_websocket_accounts.rs @@ -9,7 +9,7 @@ async fn main() -> Result<()> { let api_key: &str = "your_api_key"; let cluster: Cluster = Cluster::MainnetBeta; - let helius: Helius = Helius::new_with_ws_default(api_key, cluster).await?; + let helius: Helius = Helius::new_with_ws(api_key, cluster).await?; let key: pubkey::Pubkey = pubkey!("BtsmiEEvnSuUnKxqXj2PZRYpPJAc7C34mGz8gtJ1DAaH"); diff --git a/examples/enhanced_websocket_transactions.rs b/examples/enhanced_websocket_transactions.rs index ebbcfcc..da287ab 100644 --- a/examples/enhanced_websocket_transactions.rs +++ b/examples/enhanced_websocket_transactions.rs @@ -10,7 +10,7 @@ async fn main() -> Result<()> { let cluster: Cluster = Cluster::MainnetBeta; // Uses custom ping-pong timeouts to ping every 15s and timeout after 45s of no pong - let helius: Helius = Helius::new_with_ws(api_key, cluster, Some(15), Some(45)).await.unwrap(); + let helius: Helius = Helius::new_with_ws_with_timeouts(api_key, cluster, Some(15), Some(45)).await?; let key: pubkey::Pubkey = pubkey!("BtsmiEEvnSuUnKxqXj2PZRYpPJAc7C34mGz8gtJ1DAaH"); diff --git a/src/client.rs b/src/client.rs index 257c402..9aa567e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -100,7 +100,7 @@ impl Helius { /// /// # Returns /// An instance of `Helius` if successful. A `HeliusError` is returned if an error occurs during configuration or initialization of the HTTP, RPC, or WS client - pub async fn new_with_ws( + pub async fn new_with_ws_with_timeouts( api_key: &str, cluster: Cluster, ping_interval_secs: Option, @@ -132,8 +132,8 @@ impl Helius { /// /// # Returns /// An instance of `Helius` if successful. A `HeliusError` is returned if an error occurs during configuration or initialization of the HTTP, RPC, or WS client - pub async fn new_with_ws_default(api_key: &str, cluster: Cluster) -> Result { - Self::new_with_ws(api_key, cluster, None, None).await + pub async fn new_with_ws(api_key: &str, cluster: Cluster) -> Result { + Self::new_with_ws_with_timeouts(api_key, cluster, None, None).await } /// Provides a thread-safe way to access RPC functionalities From afe0787857ba21c56ac51c99a551fa7ea9b25fce Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Thu, 28 Nov 2024 13:43:52 -0500 Subject: [PATCH 10/21] Update unmatched_pings Reset --- src/websocket.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/websocket.rs b/src/websocket.rs index 1327843..fd6b56c 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -269,6 +269,9 @@ impl EnhancedWebsocket { None => break, }; + // Reset unmatched_pings on any received frame + unmatched_pings = 0; + // Get text from the message let text = match msg { Message::Text(text) => text, @@ -278,7 +281,6 @@ impl EnhancedWebsocket { continue }, Message::Pong(_data) => { - unmatched_pings = 0; continue; }, Message::Close(_frame) => break, From 6c91455ad3c37580bc0a278bbfcb354d7bf43b23 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Thu, 28 Nov 2024 13:47:47 -0500 Subject: [PATCH 11/21] Handle Ping Pong Params Better --- src/websocket.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/websocket.rs b/src/websocket.rs index fd6b56c..ff40a11 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -60,12 +60,16 @@ impl EnhancedWebsocket { let (_request_sender, request_receiver) = mpsc::unbounded_channel(); let (shutdown_sender, shutdown_receiver) = oneshot::channel(); - let ping_interval = ping_interval_secs.unwrap_or(DEFAULT_PING_DURATION_SECONDS); - let max_failed_pings = if let Some(timeout) = pong_timeout_secs { - (timeout as f64 / ping_interval as f64).ceil() as usize - } else { - DEFAULT_MAX_FAILED_PINGS - }; + let ping_interval = ping_interval_secs.filter(|interval: &u64| *interval !=0).unwrap_or(DEFAULT_PING_DURATION_SECONDS); + let max_failed_pings = pong_timeout_secs + .map(|timeout| (timeout as f64 / ping_interval as f64).ceil() as usize) + .map_or(DEFAULT_MAX_FAILED_PINGS, |max_failed_pings| { + if max_failed_pings != 0 { + max_failed_pings + } else { + usize::MAX + } + }); Ok(Self { subscribe_sender, From bdcb553838051e2816a70654badb56f06f92b436 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Thu, 28 Nov 2024 13:48:07 -0500 Subject: [PATCH 12/21] Make Constants Public --- src/websocket.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/websocket.rs b/src/websocket.rs index ff40a11..e0cba03 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -30,8 +30,8 @@ use tokio_tungstenite::{ }; pub const ENHANCED_WEBSOCKET_URL: &str = "wss://atlas-mainnet.helius-rpc.com/?api-key="; -const DEFAULT_PING_DURATION_SECONDS: u64 = 10; -const DEFAULT_MAX_FAILED_PINGS: usize = 3; +pub const DEFAULT_PING_DURATION_SECONDS: u64 = 10; +pub const DEFAULT_MAX_FAILED_PINGS: usize = 3; // pub type Result = Result; From b844a88a2219c05aa9ece6d8f1594dd16838d4a7 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Thu, 28 Nov 2024 13:48:29 -0500 Subject: [PATCH 13/21] Formattooorrr --- src/websocket.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/websocket.rs b/src/websocket.rs index e0cba03..9370de1 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -60,16 +60,18 @@ impl EnhancedWebsocket { let (_request_sender, request_receiver) = mpsc::unbounded_channel(); let (shutdown_sender, shutdown_receiver) = oneshot::channel(); - let ping_interval = ping_interval_secs.filter(|interval: &u64| *interval !=0).unwrap_or(DEFAULT_PING_DURATION_SECONDS); + let ping_interval = ping_interval_secs + .filter(|interval: &u64| *interval != 0) + .unwrap_or(DEFAULT_PING_DURATION_SECONDS); let max_failed_pings = pong_timeout_secs - .map(|timeout| (timeout as f64 / ping_interval as f64).ceil() as usize) - .map_or(DEFAULT_MAX_FAILED_PINGS, |max_failed_pings| { - if max_failed_pings != 0 { - max_failed_pings - } else { - usize::MAX - } - }); + .map(|timeout| (timeout as f64 / ping_interval as f64).ceil() as usize) + .map_or(DEFAULT_MAX_FAILED_PINGS, |max_failed_pings| { + if max_failed_pings != 0 { + max_failed_pings + } else { + usize::MAX + } + }); Ok(Self { subscribe_sender, From 66737634d943c1c26744f39eeef2967bdba829fd Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Thu, 28 Nov 2024 14:06:55 -0500 Subject: [PATCH 14/21] Update Comment --- src/websocket.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/websocket.rs b/src/websocket.rs index 9370de1..13d6284 100644 --- a/src/websocket.rs +++ b/src/websocket.rs @@ -41,7 +41,7 @@ type SubscribeRequestMsg = (String, Value, oneshot::Sender type SubscribeResult<'a, T> = Result<(BoxStream<'a, T>, UnsubscribeFn)>; type RequestMsg = (String, Value, oneshot::Sender>); -/// A client for subscribing to transaction or account updates from a Helius (geyser) enhanced websocket server. +/// A client for subscribing to transaction or account updates from a Helius (Geyser) enhanced websocket server. /// /// Forked from Solana's [`PubsubClient`]. pub struct EnhancedWebsocket { From e3191038c66f34160008d7f4a40b7d3958ce5b03 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Thu, 28 Nov 2024 15:54:50 -0500 Subject: [PATCH 15/21] Allow Config-based Client Creation --- src/config.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/src/config.rs b/src/config.rs index 3630d66..575d93c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,12 @@ use crate::error::{HeliusError, Result}; use crate::types::{Cluster, HeliusEndpoints, MintApiAuthority}; +use crate::websocket::{ EnhancedWebsocket, ENHANCED_WEBSOCKET_URL }; +use crate::Helius; +use crate::rpc_client::RpcClient; +use std::sync::Arc; +use reqwest::Client; +use url::{ParseError, Url}; +use solana_client::nonblocking::rpc_client::RpcClient as AsyncSolanaRpcClient; /// Configuration settings for the Helius client /// @@ -41,6 +48,119 @@ impl Config { }) } + pub fn rpc_client_with_reqwest_client(&self, client: Client) -> Result { + RpcClient::new(Arc::new(client), Arc::new(self.clone())) + } + + /// Creates a basic Helius client from this configuration + /// + /// # Returns + /// A `Result` containing a Helius client with basic RPC capabilities + pub fn create_client(self) -> Result { + let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; + let rpc_client: Arc = Arc::new(self.rpc_client_with_reqwest_client(client.clone())?); + + Ok(Helius { + config: Arc::new(self), + client, + rpc_client, + async_rpc_client: None, + ws_client: None, + }) + } + + /// Creates a Helius client with async Solana capabilities + /// + /// # Returns + /// A `Result` containing a Helius client with both RPC and async Solana capabilities + pub fn create_client_with_async(self) -> Result { + let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; + let mut rpc_url: Url = Url::parse(&self.endpoints.rpc) + .map_err(|e: ParseError| HeliusError::InvalidInput(format!("Invalid RPC URL: {}", e)))?; + + rpc_url.query_pairs_mut() + .append_pair("api-key", &self.api_key); + + let async_solana_client: Arc = Arc::new(AsyncSolanaRpcClient::new(rpc_url.to_string())); + let rpc_client: Arc = Arc::new(self.rpc_client_with_reqwest_client(client.clone())?); + + Ok(Helius { + config: Arc::new(self), + client, + rpc_client, + async_rpc_client: Some(async_solana_client), + ws_client: None, + }) + } + + /// Creates a Helius client with websocket support + /// + /// # Arguments + /// * `ping_interval_secs` - Optional duration in seconds between ping messages + /// * `pong_timeout_secs` - Optional duration in seconds to wait for pong response + /// + /// # Returns + /// A `Result` containing a Helius client with websocket support + pub async fn create_client_with_ws( + self, + ping_interval_secs: Option, + pong_timeout_secs: Option, + ) -> Result { + let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; + let rpc_client: Arc = Arc::new(self.rpc_client_with_reqwest_client(client.clone())?); + + let wss: String = format!("{}{}", ENHANCED_WEBSOCKET_URL, self.api_key); + let ws_client: Arc = Arc::new( + EnhancedWebsocket::new(&wss, ping_interval_secs, pong_timeout_secs).await? + ); + + Ok(Helius { + config: Arc::new(self), + client, + rpc_client, + async_rpc_client: None, + ws_client: Some(ws_client), + }) + } + + /// Creates a full-featured Helius client with both async and websocket support + /// + /// # Arguments + /// * `ping_interval_secs` - Optional duration in seconds between ping messages + /// * `pong_timeout_secs` - Optional duration in seconds to wait for pong response + /// + /// # Returns + /// A `Result` containing a fully-featured Helius client + pub async fn create_full_client( + self, + ping_interval_secs: Option, + pong_timeout_secs: Option, + ) -> Result { + let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; + let rpc_client: Arc = Arc::new(self.rpc_client_with_reqwest_client(client.clone())?); + + // Setup async client + let mut rpc_url: Url = Url::parse(&self.endpoints.rpc) + .map_err(|e: ParseError| HeliusError::InvalidInput(format!("Invalid RPC URL: {}", e)))?; + rpc_url.query_pairs_mut() + .append_pair("api-key", &self.api_key); + let async_solana_client = Arc::new(AsyncSolanaRpcClient::new(rpc_url.to_string())); + + // Setup websocket + let wss: String = format!("{}{}", ENHANCED_WEBSOCKET_URL, self.api_key); + let ws_client: Arc = Arc::new( + EnhancedWebsocket::new(&wss, ping_interval_secs, pong_timeout_secs).await? + ); + + Ok(Helius { + config: Arc::new(self), + client, + rpc_client, + async_rpc_client: Some(async_solana_client), + ws_client: Some(ws_client), + }) + } + pub fn mint_api_authority(&self) -> MintApiAuthority { MintApiAuthority::from_cluster(&self.cluster) } From 2f60d31e32ec6be52dd41a5befefa3dd2fea6ded Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Thu, 28 Nov 2024 15:58:44 -0500 Subject: [PATCH 16/21] Add Example of Config-based Creation --- examples/async_config_based_creation.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 examples/async_config_based_creation.rs diff --git a/examples/async_config_based_creation.rs b/examples/async_config_based_creation.rs new file mode 100644 index 0000000..c32ae8d --- /dev/null +++ b/examples/async_config_based_creation.rs @@ -0,0 +1,20 @@ +use helius::error::Result; +use helius::types::Cluster; +use helius::config::Config; +use helius::Helius; + +/// Demonstrates creating a Helius client with async Solana capabilities using the config-based approach +#[tokio::main] +async fn main() -> Result<()> { + let api_key: &str = "your_api_key"; + let cluster: Cluster = Cluster::MainnetBeta; + + let config: Config = Config::new(api_key, cluster)?; + let async_client: Helius = config.create_client_with_async()?; + + if let Ok(async_conn) = async_client.async_connection() { + println!("Async client - Get Block Height: {:?}", async_conn.get_block_height().await); + } + + Ok(()) +} From e35af6d399cefeb2e2ed619364af0cf2666927f1 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Thu, 28 Nov 2024 15:59:08 -0500 Subject: [PATCH 17/21] Formattooorrr --- examples/async_config_based_creation.rs | 9 +++++--- src/config.rs | 30 +++++++++++-------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/examples/async_config_based_creation.rs b/examples/async_config_based_creation.rs index c32ae8d..e23b543 100644 --- a/examples/async_config_based_creation.rs +++ b/examples/async_config_based_creation.rs @@ -1,6 +1,6 @@ +use helius::config::Config; use helius::error::Result; use helius::types::Cluster; -use helius::config::Config; use helius::Helius; /// Demonstrates creating a Helius client with async Solana capabilities using the config-based approach @@ -11,9 +11,12 @@ async fn main() -> Result<()> { let config: Config = Config::new(api_key, cluster)?; let async_client: Helius = config.create_client_with_async()?; - + if let Ok(async_conn) = async_client.async_connection() { - println!("Async client - Get Block Height: {:?}", async_conn.get_block_height().await); + println!( + "Async client - Get Block Height: {:?}", + async_conn.get_block_height().await + ); } Ok(()) diff --git a/src/config.rs b/src/config.rs index 575d93c..1700980 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,12 @@ use crate::error::{HeliusError, Result}; +use crate::rpc_client::RpcClient; use crate::types::{Cluster, HeliusEndpoints, MintApiAuthority}; -use crate::websocket::{ EnhancedWebsocket, ENHANCED_WEBSOCKET_URL }; +use crate::websocket::{EnhancedWebsocket, ENHANCED_WEBSOCKET_URL}; use crate::Helius; -use crate::rpc_client::RpcClient; -use std::sync::Arc; use reqwest::Client; -use url::{ParseError, Url}; use solana_client::nonblocking::rpc_client::RpcClient as AsyncSolanaRpcClient; +use std::sync::Arc; +use url::{ParseError, Url}; /// Configuration settings for the Helius client /// @@ -77,9 +77,8 @@ impl Config { let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; let mut rpc_url: Url = Url::parse(&self.endpoints.rpc) .map_err(|e: ParseError| HeliusError::InvalidInput(format!("Invalid RPC URL: {}", e)))?; - - rpc_url.query_pairs_mut() - .append_pair("api-key", &self.api_key); + + rpc_url.query_pairs_mut().append_pair("api-key", &self.api_key); let async_solana_client: Arc = Arc::new(AsyncSolanaRpcClient::new(rpc_url.to_string())); let rpc_client: Arc = Arc::new(self.rpc_client_with_reqwest_client(client.clone())?); @@ -108,11 +107,10 @@ impl Config { ) -> Result { let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; let rpc_client: Arc = Arc::new(self.rpc_client_with_reqwest_client(client.clone())?); - + let wss: String = format!("{}{}", ENHANCED_WEBSOCKET_URL, self.api_key); - let ws_client: Arc = Arc::new( - EnhancedWebsocket::new(&wss, ping_interval_secs, pong_timeout_secs).await? - ); + let ws_client: Arc = + Arc::new(EnhancedWebsocket::new(&wss, ping_interval_secs, pong_timeout_secs).await?); Ok(Helius { config: Arc::new(self), @@ -138,19 +136,17 @@ impl Config { ) -> Result { let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?; let rpc_client: Arc = Arc::new(self.rpc_client_with_reqwest_client(client.clone())?); - + // Setup async client let mut rpc_url: Url = Url::parse(&self.endpoints.rpc) .map_err(|e: ParseError| HeliusError::InvalidInput(format!("Invalid RPC URL: {}", e)))?; - rpc_url.query_pairs_mut() - .append_pair("api-key", &self.api_key); + rpc_url.query_pairs_mut().append_pair("api-key", &self.api_key); let async_solana_client = Arc::new(AsyncSolanaRpcClient::new(rpc_url.to_string())); // Setup websocket let wss: String = format!("{}{}", ENHANCED_WEBSOCKET_URL, self.api_key); - let ws_client: Arc = Arc::new( - EnhancedWebsocket::new(&wss, ping_interval_secs, pong_timeout_secs).await? - ); + let ws_client: Arc = + Arc::new(EnhancedWebsocket::new(&wss, ping_interval_secs, pong_timeout_secs).await?); Ok(Helius { config: Arc::new(self), From 80a5c9dcbfa9a4616f2f5897d4a7b5be9a5528b1 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Thu, 28 Nov 2024 16:53:45 -0500 Subject: [PATCH 18/21] Add More Config Tests --- tests/test_config.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_config.rs b/tests/test_config.rs index 75568aa..83f99e3 100644 --- a/tests/test_config.rs +++ b/tests/test_config.rs @@ -1,6 +1,7 @@ use helius::config::Config; use helius::error::{HeliusError, Result}; use helius::types::Cluster; +use helius::Helius; #[test] fn test_config_new_with_empty_api_key() { @@ -18,3 +19,25 @@ fn test_config_new_with_valid_api_key() { assert_eq!(config.endpoints.api, "https://api-devnet.helius-rpc.com/"); assert_eq!(config.endpoints.rpc, "https://devnet.helius-rpc.com/"); } + +#[test] +fn test_create_basic_client() { + let config: Config = Config::new("valid-api-key", Cluster::Devnet).unwrap(); + let result: Result = config.create_client(); + assert!(result.is_ok()); + + let client: Helius = result.unwrap(); + assert!(client.async_rpc_client.is_none()); + assert!(client.ws_client.is_none()); +} + +#[test] +fn test_create_async_client() { + let config: Config = Config::new("valid-api-key", Cluster::Devnet).unwrap(); + let result: Result = config.create_client_with_async(); + assert!(result.is_ok()); + + let client: Helius = result.unwrap(); + assert!(client.async_rpc_client.is_some()); + assert!(client.ws_client.is_none()); +} From 861e9f46389394e9a1e481011aa5c2140d1c1708 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Thu, 5 Dec 2024 19:01:13 -0500 Subject: [PATCH 19/21] Remove Patch For Curve25519 Dalek --- Cargo.toml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 42037cc..af162ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,9 +51,3 @@ rustls = [ "reqwest/rustls-tls", "tokio-tungstenite/rustls-tls-webpki-roots" ] - -[patch.crates-io] -# https://github.com/solana-labs/solana/issues/26688#issuecomment-2136066879 -# For curve25519-dalek use the same revision Solana uses -# https://github.com/solana-labs/solana/blob/27eff8408b7223bb3c4ab70523f8a8dca3ca6645/Cargo.toml#L475-L563 -curve25519-dalek = { git = "https://github.com/solana-labs/curve25519-dalek.git", rev = "b500cdc2a920cd5bff9e2dd974d7b97349d61464" } From fc5299178f8a524bd31bddfb570ff188892fc9e1 Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Thu, 5 Dec 2024 19:42:10 -0500 Subject: [PATCH 20/21] Update Solana Deps and Downgrade Reqwest --- Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index af162ef..67cb84f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,17 +20,17 @@ futures-util = "0.3.30" mpl-token-metadata = { version = "5.0.0-beta.0" } phf = { version = "0.11.2", features = ["macros"] } rand = "0.8.5" -reqwest = { version = "0.12.8", features = ["json", "native-tls"] } +reqwest = { version = "0.11.22", features = ["json", "native-tls"] } semver = "1.0.23" serde = "1.0.198" serde-enum-str = "0.4.0" serde_json = "1.0.116" -solana-account-decoder = "2.0" -solana-client = "2.0" -solana-program = "2.0" -solana-rpc-client-api = "2.0" -solana-sdk = "2.0" -solana-transaction-status = "2.0" +solana-account-decoder = "2.1.4" +solana-client = "2.1.4" +solana-program = "2.1.4" +solana-rpc-client-api = "2.1.4" +solana-sdk = "2.1.4" +solana-transaction-status = "2.1.4" thiserror = "1.0.58" tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "net", "time"] } tokio-stream = "0.1.15" From d630006db8d0d4fe65af7d9c5f3d97c676195d3d Mon Sep 17 00:00:00 2001 From: Evan <0xIchigo@protonmail.com> Date: Thu, 5 Dec 2024 20:13:40 -0500 Subject: [PATCH 21/21] Bump Version to 0.2.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 67cb84f..cdbd15c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helius" -version = "0.2.2" +version = "0.2.3" edition = "2021" description = "An asynchronous Helius Rust SDK for building the future of Solana" keywords = ["helius", "solana", "asynchronous-sdk", "das", "cryptocurrency"]