Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merge] Dev -> Main #1

Merged
merged 20 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,025 changes: 1,932 additions & 93 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "helius-rust-sdk"
name = "helius_sdk"
version = "0.1.0"
edition = "2021"
description ="An asynchronous Helius Rust SDK for building the future of Solana"
Expand All @@ -10,7 +10,12 @@ documentation = "https://docs.rs/helius-sdk"
readme = "README.md"

[dependencies]
reqwest = "0.12.3"
reqwest = { version = "0.12.3", features = ["json"] }
serde = "1.0.198"
serde_json = "1.0.116"
solana-sdk = "1.18.11"
thiserror = "1.0.58"
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "net"] }

[dev-dependencies]
mockito = "1.4.0"
2 changes: 1 addition & 1 deletion rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
max_width = 80
max_width = 120
edition = "2021"
34 changes: 34 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![allow(dead_code)]
use std::sync::Arc;

use crate::config::Config;
use crate::error::Result;
use crate::rpc_client::RpcClient;
use crate::types::Cluster;

use reqwest::Client;

pub struct Helius {
pub config: Arc<Config>,
pub client: Client,
pub rpc_client: Arc<RpcClient>,
}

impl Helius {
pub fn new(api_key: &str, cluster: Cluster) -> Result<Self> {
let config: Arc<Config> = Arc::new(Config::new(api_key, cluster)?);
let client: Client = Client::new();
let rpc_client: Arc<RpcClient> = Arc::new(RpcClient::new(Arc::new(client.clone()), config.clone())?);

Ok(Helius {
config,
client,
rpc_client,
})
}

/// Provides a thread-safe way to access RPC functionalities
pub fn rpc(&self) -> Arc<RpcClient> {
self.rpc_client.clone()
}
}
34 changes: 34 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use crate::error::{HeliusError, Result};
use crate::types::{Cluster, HeliusEndpoints};

#[derive(Clone)]
pub struct Config {
pub api_key: String,
pub cluster: Cluster,
pub endpoints: HeliusEndpoints,
}

impl Config {
pub fn new(api_key: &str, cluster: Cluster) -> Result<Self> {
if api_key.is_empty() {
return Err(HeliusError::InvalidInput("API key must not be empty".to_string()));
}

let endpoints: HeliusEndpoints = match cluster {
Cluster::Devnet => HeliusEndpoints {
api: "https://api-devnet.helius-rpc.com".to_string(),
rpc: "https://devnet.helius-rpc.com".to_string(),
},
Cluster::MainnetBeta => HeliusEndpoints {
api: "https://api-mainnet.helius-rpc.com".to_string(),
rpc: "https://mainnet.helius-rpc.com".to_string(),
},
};

Ok(Config {
api_key: api_key.to_string(),
cluster,
endpoints,
})
}
}
1 change: 1 addition & 0 deletions src/das_api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

48 changes: 48 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use reqwest::{Error as ReqwestError, StatusCode};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum HeliusError {
#[error("Bad request to {path}: {text}")]
BadRequest { path: String, text: String },

#[error("Internal server error: {code} - {text}")]
InternalError { code: StatusCode, text: String },

#[error("Invalid input: {0}")]
InvalidInput(String),

#[error("Network error: {0}")]
Network(ReqwestError),

#[error("Not found: {text}")]
NotFound { text: String },

#[error("Too many requests made to {path}")]
RateLimitExceeded { path: String },

#[error("Serialization / Deserialization error: {0}")]
SerdeJson(ReqwestError),

#[error("Unauthorized access to {path}: {text}")]
Unauthorized { path: String, text: String },

#[error("Unknown error has occurred: HTTP {code} - {text}")]
Unknown { code: StatusCode, text: String },
}

impl HeliusError {
pub fn from_response_status(status: StatusCode, path: String, text: String) -> Self {
match status {
StatusCode::BAD_REQUEST => HeliusError::BadRequest { path, text },
StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => HeliusError::Unauthorized { path, text },
StatusCode::NOT_FOUND => HeliusError::NotFound { text },
StatusCode::INTERNAL_SERVER_ERROR => HeliusError::InternalError { code: status, text },
StatusCode::TOO_MANY_REQUESTS => HeliusError::RateLimitExceeded { path },
_ => HeliusError::Unknown { code: status, text },
}
}
}

// Handy type alias
pub type Result<T> = std::result::Result<T, HeliusError>;
47 changes: 47 additions & 0 deletions src/factory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::sync::Arc;

use crate::client::Helius;
use crate::config::Config;
use crate::error::Result;
use crate::rpc_client::RpcClient;
use crate::types::Cluster;

use reqwest::Client;

pub struct HeliusFactory {
api_key: String,
}

impl HeliusFactory {
pub fn new(api_key: &str) -> Self {
HeliusFactory {
api_key: api_key.to_string(),
}
}

pub fn create(&self, cluster: Cluster) -> Result<Helius> {
let config: Arc<Config> = Arc::new(Config::new(&self.api_key, cluster)?);
let client: Client = Client::new();
let rpc_client: Arc<RpcClient> = Arc::new(RpcClient::new(Arc::new(client.clone()), config.clone())?);

Ok(Helius {
config,
client,
rpc_client,
})
}
}

// Example Usage

// #[tokio::main]
// async fn main() {
// let api_key = "your_api_key_here";
// let factory = HeliusFactory::new(api_key);

// let helius_dev = factory.create(Cluster::Devnet).unwrap();
// // helius_dev.request_name(...).await;

// let helius_main = factory.create(Cluster::MainnetBeta).unwrap();
// // helius_main.request_name(...).await;
// }
26 changes: 13 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
pub mod client;
pub mod config;
pub mod das_api;
pub mod error;
pub mod factory;
pub mod mint_api;
pub mod request_handler;
pub mod rpc_client;
pub mod types;
pub mod utils;
pub mod webhook;

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
pub use client::Helius;
pub use factory::HeliusFactory;
1 change: 1 addition & 0 deletions src/mint_api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

48 changes: 48 additions & 0 deletions src/request_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use crate::error::{HeliusError, Result};
use reqwest::{Client, Method, RequestBuilder, Response, StatusCode, Url};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::sync::Arc;

#[derive(Clone)]
pub struct RequestHandler {
pub http_client: Arc<Client>,
}

impl RequestHandler {
pub fn new(client: Arc<Client>) -> Result<Self> {
Ok(Self { http_client: client })
}

async fn send_request(&self, request_builder: RequestBuilder) -> Result<Response> {
let response: Response = request_builder.send().await.map_err(|e| HeliusError::Network(e))?;
Ok(response)
}

pub async fn send<R, T>(&self, method: Method, url: Url, body: Option<&R>) -> Result<T>
where
R: Serialize + ?Sized + Send + Sync + Debug,
T: for<'de> Deserialize<'de> + Default,
{
let mut request_builder: RequestBuilder = self.http_client.request(method, url.clone());

if let Some(body) = body {
request_builder = request_builder.json(body);
}

let response: Response = self.send_request(request_builder).await?;
let path: String = url.path().to_string();
self.handle_response(path, response).await
}

async fn handle_response<T: for<'de> Deserialize<'de>>(&self, path: String, response: Response) -> Result<T> {
let status: StatusCode = response.status();

if status == StatusCode::OK || status == StatusCode::CREATED {
response.json::<T>().await.map_err(HeliusError::SerdeJson)
} else {
let error_text: String = response.text().await.unwrap_or_default();
Err(HeliusError::from_response_status(status, path, error_text))
}
}
}
19 changes: 19 additions & 0 deletions src/rpc_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::sync::Arc;

use crate::config::Config;
use crate::error::Result;
use crate::request_handler::RequestHandler;

use reqwest::Client;

pub struct RpcClient {
pub handler: RequestHandler,
pub config: Arc<Config>,
}

impl RpcClient {
pub fn new(client: Arc<Client>, config: Arc<Config>) -> Result<Self> {
let handler: RequestHandler = RequestHandler::new(client)?;
Ok(RpcClient { handler, config })
}
}
13 changes: 13 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// Defines the available clusters supported by Helius
#[derive(Debug, Clone, PartialEq)]
pub enum Cluster {
Devnet,
MainnetBeta,
}

/// Stores the API and RPC endpoint URLs for a specific Helius cluster
#[derive(Debug, Clone)]
pub struct HeliusEndpoints {
pub api: String,
pub rpc: String,
}
6 changes: 6 additions & 0 deletions src/utils/is_valid_solana_address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;

pub fn is_valid_solana_address(address: &str) -> bool {
address.len() >= 32 && address.len() <= 44 && Pubkey::from_str(address).is_ok()
}
3 changes: 3 additions & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub use self::is_valid_solana_address::is_valid_solana_address;

mod is_valid_solana_address;
1 change: 1 addition & 0 deletions src/webhook.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

15 changes: 15 additions & 0 deletions tests/test_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use helius_sdk::client::Helius;
use helius_sdk::error::HeliusError;
use helius_sdk::types::Cluster;

#[test]
fn test_creating_new_client_success() {
let api_key: &str = "valid-api-key";
let cluster: Cluster = Cluster::Devnet;

let result: Result<Helius, HeliusError> = Helius::new(api_key, cluster);
assert!(result.is_ok());

let helius: Helius = result.unwrap();
assert_eq!(helius.config.api_key, api_key);
}
20 changes: 20 additions & 0 deletions tests/test_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use helius_sdk::config::Config;
use helius_sdk::error::HeliusError;
use helius_sdk::types::Cluster;

#[test]
fn test_config_new_with_empty_api_key() {
let result: Result<Config, HeliusError> = Config::new("", Cluster::Devnet);
assert!(matches!(result, Err(HeliusError::InvalidInput(_))));
}

#[test]
fn test_config_new_with_valid_api_key() {
let result: Result<Config, HeliusError> = Config::new("valid-api-key", Cluster::Devnet);
assert!(result.is_ok());

let config: Config = result.unwrap();
assert_eq!(config.api_key, "valid-api-key");
assert_eq!(config.endpoints.api, "https://api-devnet.helius-rpc.com");
assert_eq!(config.endpoints.rpc, "https://devnet.helius-rpc.com");
}
22 changes: 22 additions & 0 deletions tests/test_factory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use helius_sdk::types::Cluster;
use helius_sdk::{Helius, HeliusFactory};

#[test]
fn test_factory_create_devnet_instance() {
let factory: HeliusFactory = HeliusFactory::new("valid_api_key");
let helius: Helius = factory.create(Cluster::Devnet).unwrap();

assert_eq!(helius.config.api_key, "valid_api_key");
assert_eq!(helius.config.endpoints.api, "https://api-devnet.helius-rpc.com");
assert_eq!(helius.config.endpoints.rpc, "https://devnet.helius-rpc.com");
}

#[test]
fn test_factory_create_mainnet_instance() {
let factory: HeliusFactory = HeliusFactory::new("valid_api_key");
let helius: Helius = factory.create(Cluster::MainnetBeta).unwrap();

assert_eq!(helius.config.api_key, "valid_api_key");
assert_eq!(helius.config.endpoints.api, "https://api-mainnet.helius-rpc.com");
assert_eq!(helius.config.endpoints.rpc, "https://mainnet.helius-rpc.com");
}
3 changes: 3 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod utils {
mod test_is_valid_solana_address;
}
Loading
Loading