Skip to content

Commit

Permalink
Merge pull request #99 from helius-labs/dev
Browse files Browse the repository at this point in the history
[Merge] Dev -> Main
  • Loading branch information
0xIchigo authored Dec 6, 2024
2 parents 362fa32 + d3efdda commit 2056853
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 24 deletions.
22 changes: 8 additions & 14 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"]
Expand All @@ -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"
Expand All @@ -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" }
23 changes: 23 additions & 0 deletions examples/async_config_based_creation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use helius::config::Config;
use helius::error::Result;
use helius::types::Cluster;
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(())
}
3 changes: 2 additions & 1 deletion examples/enhanced_websocket_transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_with_timeouts(api_key, cluster, Some(15), Some(45)).await?;

let key: pubkey::Pubkey = pubkey!("BtsmiEEvnSuUnKxqXj2PZRYpPJAc7C34mGz8gtJ1DAaH");

Expand Down
1 change: 1 addition & 0 deletions examples/send_smart_transaction_with_tip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
27 changes: 25 additions & 2 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,23 @@ 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<Self> {
pub async fn new_with_ws_with_timeouts(
api_key: &str,
cluster: Cluster,
ping_interval_secs: Option<u64>,
pong_timeout_secs: Option<u64>,
) -> Result<Self> {
let config: Arc<Config> = Arc::new(Config::new(api_key, cluster)?);
let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?;
let rpc_client: Arc<RpcClient> = Arc::new(RpcClient::new(Arc::new(client.clone()), config.clone())?);
let wss: String = format!("{}{}", ENHANCED_WEBSOCKET_URL, api_key);
let ws_client: Arc<EnhancedWebsocket> = Arc::new(EnhancedWebsocket::new(&wss).await?);
let ws_client: Arc<EnhancedWebsocket> =
Arc::new(EnhancedWebsocket::new(&wss, ping_interval_secs, pong_timeout_secs).await?);

Ok(Helius {
config,
Expand All @@ -113,6 +122,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(api_key: &str, cluster: Cluster) -> Result<Self> {
Self::new_with_ws_with_timeouts(api_key, cluster, None, None).await
}

/// Provides a thread-safe way to access RPC functionalities
///
/// # Returns
Expand Down
116 changes: 116 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +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::Helius;
use reqwest::Client;
use solana_client::nonblocking::rpc_client::RpcClient as AsyncSolanaRpcClient;
use std::sync::Arc;
use url::{ParseError, Url};

/// Configuration settings for the Helius client
///
Expand Down Expand Up @@ -41,6 +48,115 @@ impl Config {
})
}

pub fn rpc_client_with_reqwest_client(&self, client: Client) -> Result<RpcClient> {
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<Helius> {
let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?;
let rpc_client: Arc<RpcClient> = 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<Helius> {
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<AsyncSolanaRpcClient> = Arc::new(AsyncSolanaRpcClient::new(rpc_url.to_string()));
let rpc_client: Arc<RpcClient> = 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<u64>,
pong_timeout_secs: Option<u64>,
) -> Result<Helius> {
let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?;
let rpc_client: Arc<RpcClient> = Arc::new(self.rpc_client_with_reqwest_client(client.clone())?);

let wss: String = format!("{}{}", ENHANCED_WEBSOCKET_URL, self.api_key);
let ws_client: Arc<EnhancedWebsocket> =
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<u64>,
pong_timeout_secs: Option<u64>,
) -> Result<Helius> {
let client: Client = Client::builder().build().map_err(HeliusError::ReqwestError)?;
let rpc_client: Arc<RpcClient> = 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<EnhancedWebsocket> =
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)
}
Expand Down
11 changes: 9 additions & 2 deletions src/optimized_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,15 @@ impl Helius {
"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);
let compute_budget_ix: Instruction = ComputeBudgetInstruction::set_compute_unit_price(priority_fee);
let mut updated_instructions: Vec<Instruction> = config.instructions.clone();
updated_instructions.push(compute_budget_ix.clone());
final_instructions.push(compute_budget_ix);
Expand Down Expand Up @@ -474,6 +480,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 {
Expand Down
4 changes: 4 additions & 0 deletions src/types/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,7 @@ pub struct CreateSmartTransactionConfig {
pub signers: Vec<Arc<dyn Signer>>,
pub lookup_tables: Option<Vec<AddressLookupTableAccount>>,
pub fee_payer: Option<Arc<dyn Signer>>,
pub priority_fee_cap: Option<u64>,
}

impl CreateSmartTransactionConfig {
Expand All @@ -965,6 +966,7 @@ impl CreateSmartTransactionConfig {
signers,
lookup_tables: None,
fee_payer: None,
priority_fee_cap: None,
}
}
}
Expand Down Expand Up @@ -1016,6 +1018,7 @@ pub struct CreateSmartTransactionSeedConfig {
pub signer_seeds: Vec<[u8; 32]>,
pub fee_payer_seed: Option<[u8; 32]>,
pub lookup_tables: Option<Vec<AddressLookupTableAccount>>,
pub priority_fee_cap: Option<u64>,
}

impl CreateSmartTransactionSeedConfig {
Expand All @@ -1025,6 +1028,7 @@ impl CreateSmartTransactionSeedConfig {
signer_seeds,
fee_payer_seed: None,
lookup_tables: None,
priority_fee_cap: None,
}
}

Expand Down
Loading

0 comments on commit 2056853

Please sign in to comment.