Skip to main content

wp_solana_jupiter_swap_client/
lib.rs

1//! Jupiter swap API client.
2//!
3//! Provides async methods for quoting and executing token swaps via the
4//! Jupiter aggregator REST API.
5
6use std::collections::HashMap;
7
8use quote::{InternalQuoteRequest, QuoteRequest, QuoteResponse};
9use reqwest::{Client, Response};
10use serde::de::DeserializeOwned;
11use swap::{SwapInstructionsResponse, SwapInstructionsResponseInternal, SwapRequest, SwapResponse};
12use thiserror::Error;
13
14pub mod quote;
15pub mod route_plan_with_metadata;
16pub mod serde_helpers;
17pub mod swap;
18pub mod transaction_config;
19
20/// Default Jupiter API base URL
21pub const DEFAULT_API_BASE_URL: &str = "https://api.jup.ag/swap/v1";
22
23/// HTTP client for the Jupiter swap API.
24#[derive(Clone)]
25pub struct JupiterSwapApiClient {
26    /// API base URL (e.g. `https://api.jup.ag/swap/v1`).
27    pub base_path: String,
28    /// API key sent via the `x-api-key` header.
29    pub api_key: String,
30}
31
32/// Errors produced by the Jupiter swap client.
33#[derive(Debug, Error)]
34pub enum ClientError {
35    /// The API returned a non-2xx status code.
36    #[error("Request failed with status {status}: {body}")]
37    RequestFailed {
38        /// HTTP status code.
39        status: reqwest::StatusCode,
40        /// Response body text.
41        body: String,
42    },
43    /// The response body could not be deserialized.
44    #[error("Failed to deserialize response: {0}")]
45    DeserializationError(#[from] reqwest::Error),
46}
47
48async fn check_is_success(response: Response) -> Result<Response, ClientError> {
49    if !response.status().is_success() {
50        let status = response.status();
51        let body = response.text().await.unwrap_or_default();
52        return Err(ClientError::RequestFailed { status, body });
53    }
54    Ok(response)
55}
56
57async fn check_status_code_and_deserialize<T: DeserializeOwned>(
58    response: Response,
59) -> Result<T, ClientError> {
60    let response = check_is_success(response).await?;
61    response.json::<T>().await.map_err(ClientError::DeserializationError)
62}
63
64impl JupiterSwapApiClient {
65    /// Create a new client with a custom base URL.
66    pub fn new(base_path: String, api_key: String) -> Self {
67        Self { base_path, api_key }
68    }
69
70    /// Create a new client targeting the mainnet Jupiter API.
71    pub fn mainnet(api_key: String) -> Self {
72        Self::new(DEFAULT_API_BASE_URL.to_string(), api_key)
73    }
74
75    /// Get a swap quote for the given request parameters.
76    pub async fn quote(&self, quote_request: &QuoteRequest) -> Result<QuoteResponse, ClientError> {
77        let url = format!("{}/quote", self.base_path);
78        let extra_args = quote_request.quote_args.clone();
79        let internal_quote_request = InternalQuoteRequest::from(quote_request.clone());
80        let response = Client::new()
81            .get(url)
82            .query(&internal_quote_request)
83            .query(&extra_args)
84            .header("x-api-key", self.api_key.clone())
85            .send()
86            .await?;
87        // println!("Response: {:?}", response.text().await.unwrap());
88        // unimplemented!()
89        check_status_code_and_deserialize(response).await
90    }
91
92    /// Execute a swap and return the signed transaction.
93    pub async fn swap(
94        &self,
95        swap_request: &SwapRequest,
96        extra_args: Option<HashMap<String, String>>,
97    ) -> Result<SwapResponse, ClientError> {
98        let response = Client::new()
99            .post(format!("{}/swap", self.base_path))
100            .query(&extra_args)
101            .json(swap_request)
102            .header("x-api-key", self.api_key.clone())
103            .send()
104            .await?;
105        check_status_code_and_deserialize(response).await
106    }
107
108    /// Get individual swap instructions (useful for composing transactions).
109    pub async fn swap_instructions(
110        &self,
111        swap_request: &SwapRequest,
112    ) -> Result<SwapInstructionsResponse, ClientError> {
113        let response = Client::new()
114            .post(format!("{}/swap-instructions", self.base_path))
115            .json(swap_request)
116            .header("x-api-key", self.api_key.clone())
117            .send()
118            .await?;
119        check_status_code_and_deserialize::<SwapInstructionsResponseInternal>(response)
120            .await
121            .map(Into::into)
122    }
123}