use crate::config::Config;
use crate::error::{HeliusError, Result};
use crate::types::{validate_rpc_url, validate_ws_url, ApiKey, Cluster, HeliusEndpoints};
use crate::websocket::EnhancedWebsocket;
use crate::Helius;
use reqwest::Client;
use solana_client::nonblocking::rpc_client::RpcClient as AsyncSolanaRpcClient;
use solana_commitment_config::CommitmentConfig;
use std::sync::Arc;
use url::Url;
#[derive(Default)]
pub struct HeliusBuilder {
api_key: Option<ApiKey>,
cluster: Option<Cluster>,
custom_rpc_url: Option<Url>,
custom_api_url: Option<Url>,
custom_ws_url: Option<Url>,
commitment: Option<CommitmentConfig>,
http_client: Option<Client>,
enable_async: bool,
ws_config: Option<(Option<u64>, Option<u64>)>, }
impl HeliusBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_api_key(mut self, key: impl Into<String>) -> Result<Self> {
self.api_key = Some(ApiKey::new(key)?);
Ok(self)
}
pub fn with_cluster(mut self, cluster: Cluster) -> Self {
self.cluster = Some(cluster);
self
}
pub fn with_custom_url(mut self, url: impl AsRef<str>) -> Result<Self> {
let validated = validate_rpc_url(url.as_ref())?;
self.custom_rpc_url = Some(validated.clone());
if self.custom_api_url.is_none() {
self.custom_api_url = Some(validated);
}
Ok(self)
}
pub fn with_custom_api_url(mut self, url: impl AsRef<str>) -> Result<Self> {
self.custom_api_url = Some(validate_rpc_url(url.as_ref())?);
Ok(self)
}
pub fn with_custom_ws_url(mut self, url: impl AsRef<str>) -> Result<Self> {
self.custom_ws_url = Some(validate_ws_url(url.as_ref())?);
Ok(self)
}
pub fn with_commitment(mut self, commitment: CommitmentConfig) -> Self {
self.commitment = Some(commitment);
self
}
pub fn with_async_solana(mut self) -> Self {
self.enable_async = true;
self
}
pub fn with_websocket(mut self, ping_interval_secs: Option<u64>, pong_timeout_secs: Option<u64>) -> Self {
self.ws_config = Some((ping_interval_secs, pong_timeout_secs));
self
}
pub fn with_http_client(mut self, client: Client) -> Self {
self.http_client = Some(client);
self
}
pub async fn build(self) -> Result<Helius> {
let config = self.build_config()?;
let custom_ws_url = self.custom_ws_url;
let ws_config = self.ws_config;
let commitment = self.commitment;
let enable_async = self.enable_async;
let http_client = self.http_client;
let client = http_client.unwrap_or_else(|| Client::builder().build().expect("Failed to build HTTP client"));
let rpc_client = if let Some(commitment) = commitment {
Arc::new(crate::rpc_client::RpcClient::new_with_commitment(
Arc::new(client.clone()),
config.clone(),
commitment,
)?)
} else {
Arc::new(crate::rpc_client::RpcClient::new(
Arc::new(client.clone()),
config.clone(),
)?)
};
let async_rpc_client = if enable_async {
let url = config.build_rpc_url();
let async_client = if let Some(commitment) = commitment {
AsyncSolanaRpcClient::new_with_commitment(url, commitment)
} else {
AsyncSolanaRpcClient::new(url)
};
Some(Arc::new(async_client))
} else {
None
};
let ws_client = if let Some((ping_interval, pong_timeout)) = ws_config {
let ws_url = if let Some(custom_ws) = custom_ws_url {
custom_ws.to_string()
} else {
let api_key = config.require_api_key("WebSocket connections")?;
EnhancedWebsocket::get_url(&config.cluster, api_key.as_str())?
};
Some(Arc::new(
EnhancedWebsocket::new(&ws_url, ping_interval, pong_timeout).await?,
))
} else {
None
};
Ok(Helius {
config,
client,
rpc_client,
async_rpc_client,
ws_client,
})
}
fn build_config(&self) -> Result<Arc<Config>> {
if let Some(ref rpc_url) = self.custom_rpc_url {
return Ok(Arc::new(Config {
api_key: self.api_key.clone(),
cluster: self.cluster.clone().unwrap_or(Cluster::Devnet), endpoints: HeliusEndpoints {
api: self
.custom_api_url
.as_ref()
.map(|u| u.to_string())
.unwrap_or_else(|| rpc_url.to_string()),
rpc: rpc_url.to_string(),
},
custom_url: Some(rpc_url.to_string()),
}));
}
let cluster = self.cluster.clone().ok_or_else(|| {
HeliusError::InvalidInput(
"Either cluster or custom URL must be specified. \
Use .with_cluster(Cluster::MainnetBeta) or .with_custom_url(\"...\")"
.to_string(),
)
})?;
if self.api_key.is_none() {
log::warn!(
"No API key provided for Helius endpoint. \
Most features require authentication. Use .with_api_key(\"your-key\")"
);
}
let endpoints = HeliusEndpoints::for_cluster(&cluster);
Ok(Arc::new(Config {
api_key: self.api_key.clone(),
cluster,
endpoints,
custom_url: None,
}))
}
}