Skip to content
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
13 changes: 13 additions & 0 deletions examples/src/ultra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,16 @@ pub async fn ultra() {

println!("✅ Transaction submitted: {}", tx_signature);
}

pub async fn ultra_token_search() {
let client = JupiterClient::new("https:://lite-api.jup.ag");

let mints = vec![String::from("JUP")];

let data = client
.ultra_token_search(&mints)
.await
.expect("failed to get token info");

println!("data: {:?}", data);
}
55 changes: 53 additions & 2 deletions jup-ag-sdk/src/client/ultra_api.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{
error::{JupiterClientError, handle_response},
types::{
Router, Shield, TokenBalancesResponse, UltraExecuteOrderRequest, UltraExecuteOrderResponse,
UltraOrderRequest, UltraOrderResponse,
Router, Shield, TokenBalancesResponse, TokenInfo, UltraExecuteOrderRequest,
UltraExecuteOrderResponse, UltraOrderRequest, UltraOrderResponse,
},
};

Expand Down Expand Up @@ -190,6 +190,57 @@ impl JupiterClient {
}
}

/// search for a token and its information by its symbol, name or mint address
///
/// Limit to 100 mint addresses in query
///
/// # Arguments
///
/// * `mints` - A slice of mint addresses (`&[String]`) to inspect.
///
/// # Returns
///
/// * `Ok(Vec<TokenInfo>)` containing token safety metadata.
/// * `Err` if the request or deserialization fails.
///
/// # Jupiter API Reference
///
/// - [Search Endpoint](https://dev.jup.ag/docs/api/ultra-api/search)
///
/// # Example
///
/// ```
/// let mints = vec![
/// String::from("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"),
/// String::from("JUP")
/// ];
/// let token_info = client.ultra_token_search(&mints).await?;
/// ```
pub async fn ultra_token_search(
&self,
mints: &[String],
) -> Result<Vec<TokenInfo>, JupiterClientError> {
let query_params = vec![("query", mints.join(","))];

let response = match self
.client
.get(format!("{}/ultra/v1/search", self.base_url))
.query(&query_params)
.send()
.await
{
Ok(resp) => resp,
Err(e) => return Err(JupiterClientError::RequestError(e)),
};

let response = handle_response(response).await?;

match response.json::<Vec<TokenInfo>>().await {
Ok(data) => Ok(data),
Err(e) => Err(JupiterClientError::DeserializationError(e.to_string())),
}
}

/// Request for the list of routers available in the routing engine of Ultra, which is Juno
pub async fn routers(&self) -> Result<Vec<Router>, JupiterClientError> {
let response = match self
Expand Down
109 changes: 109 additions & 0 deletions jup-ag-sdk/src/types/ultra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,112 @@ pub struct Router {
pub name: String,
pub icon: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenStats {
pub price_change: Option<f64>,
pub holder_change: Option<f64>,
pub liquidity_change: Option<f64>,
pub volume_change: Option<f64>,
pub buy_volume: Option<f64>,
pub sell_volume: Option<f64>,
pub buy_organic_volume: Option<f64>,
pub sell_organic_volume: Option<f64>,
pub num_buys: Option<u64>,
pub num_sells: Option<u64>,
pub num_traders: Option<u64>,
pub num_organic_buyers: Option<u64>,
pub num_net_buyers: Option<u64>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FirstPool {
pub id: String,
pub created_at: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Audit {
#[serde(default)]
pub is_sus: Option<bool>,
#[serde(default)]
pub mint_authority_disabled: Option<bool>,
#[serde(default)]
pub freeze_authority_disabled: Option<bool>,
#[serde(default)]
pub top_holders_percentage: Option<f64>,
#[serde(default)]
pub dev_balance_percentage: Option<f64>,
#[serde(default)]
pub dev_migrations: Option<u64>,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenInfo {
pub id: String,
pub name: String,
pub symbol: String,
#[serde(default)]
pub icon: Option<String>,
pub decimals: u8,
#[serde(default)]
pub twitter: Option<String>,
#[serde(default)]
pub telegram: Option<String>,
#[serde(default)]
pub website: Option<String>,
#[serde(default)]
pub dev: Option<String>,
pub circ_supply: f64,
pub total_supply: f64,
pub token_program: String,

#[serde(default)]
pub launchpad: Option<String>,
#[serde(default)]
pub partner_config: Option<String>,
#[serde(default)]
pub graduated_pool: Option<String>,
#[serde(default)]
pub graduated_at: Option<String>,
#[serde(default)]
pub mint_authority: Option<String>,
#[serde(default)]
pub freeze_authority: Option<String>,

pub first_pool: FirstPool,
pub holder_count: u64,
#[serde(default)]
pub audit: Option<Audit>,
pub organic_score: f64,
pub organic_score_label: String,
#[serde(default)]
pub is_verified: Option<bool>,
#[serde(default)]
pub cexes: Vec<String>,
#[serde(default)]
pub tags: Vec<String>,
pub fdv: f64,
pub mcap: f64,
pub usd_price: f64,
pub price_block_id: u64,
pub liquidity: f64,

#[serde(default)]
pub stats5m: Option<TokenStats>,
#[serde(default)]
pub stats1h: Option<TokenStats>,
#[serde(default)]
pub stats6h: Option<TokenStats>,
#[serde(default)]
pub stats24h: Option<TokenStats>,
#[serde(default)]
pub ct_likes: Option<u64>,
#[serde(default)]
pub smart_ct_likes: Option<u64>,
pub updated_at: String,
}
2 changes: 1 addition & 1 deletion tests/src/trigger.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#[cfg(test)]
mod ultra_tests {
mod trigger_tests {
use jup_ag_sdk::types::{
CreateTriggerOrder, ExecuteTriggerOrder, GetTriggerOrders, OrderStatus,
};
Expand Down
26 changes: 26 additions & 0 deletions tests/src/ultra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,32 @@ mod ultra_tests {
);
}

#[tokio::test]
async fn test_ultra_token_search() {
let client = create_test_client();

let mints = vec!["JUP".to_string()];

let tokens = client
.ultra_token_search(&mints)
.await
.expect("Failed to search tokens");

let token = &tokens[0];
assert_eq!(token.id, JUP_MINT, "Token ID should match JUP");
assert!(!token.id.is_empty());
assert!(!token.name.is_empty());
assert!(!token.symbol.is_empty());
assert!(token.decimals == 6);
assert!(token.circ_supply > 0.0);
assert!(token.total_supply > 0.0);
assert!(token.holder_count > 0);
assert!(token.fdv > 0.0);
assert!(token.mcap > 0.0);
assert!(token.usd_price > 0.0);
assert!(token.liquidity > 0.0);
}

#[tokio::test]
async fn test_routers() {
let client = create_test_client();
Expand Down