jupiter_swap_api_client_abrkn/
lib.rs

1use anyhow::{anyhow, Result};
2use quote::{QuoteRequest, QuoteResponse};
3use reqwest::{Client, Response};
4use serde::de::DeserializeOwned;
5use swap::{SwapInstructionsResponse, SwapInstructionsResponseInternal, SwapRequest, SwapResponse};
6
7pub mod quote;
8mod route_plan_with_metadata;
9mod serde_helpers;
10pub mod swap;
11pub mod transaction_config;
12
13pub const BASE_PATH: &str = "https://quote-api.jup.ag/v6";
14
15#[derive(Clone)]
16pub struct JupiterSwapApiClient {
17    pub client: Client,
18    pub base_path: String,
19}
20
21impl Default for JupiterSwapApiClient {
22    fn default() -> Self {
23        Self {
24            client: Client::new(),
25            base_path: BASE_PATH.to_string(),
26        }
27    }
28}
29
30async fn check_is_success(response: Response) -> Result<Response> {
31    if !response.status().is_success() {
32        return Err(anyhow!(
33            "request status not ok: {}, body: {:?}",
34            response.status(),
35            response.text().await
36        ));
37    }
38    Ok(response)
39}
40
41async fn check_status_code_and_deserialize<T: DeserializeOwned>(response: Response) -> Result<T> {
42    check_is_success(response)
43        .await?
44        .json::<T>()
45        .await
46        .map_err(Into::into)
47}
48
49impl JupiterSwapApiClient {
50    pub fn new(base_path: String, client: Client) -> Self {
51        Self { base_path, client }
52    }
53
54    pub async fn quote(&self, quote_request: &QuoteRequest) -> Result<QuoteResponse> {
55        let url = format!("{}/quote", self.base_path);
56        let response = self.client.get(url).query(&quote_request).send().await?;
57        check_status_code_and_deserialize(response).await
58    }
59
60    pub async fn swap(&self, swap_request: &SwapRequest) -> Result<SwapResponse> {
61        let response = self
62            .client
63            .post(format!("{}/swap", self.base_path))
64            .json(swap_request)
65            .send()
66            .await?;
67        check_status_code_and_deserialize(response).await
68    }
69
70    pub async fn swap_instructions(
71        &self,
72        swap_request: &SwapRequest,
73    ) -> Result<SwapInstructionsResponse> {
74        let response = self
75            .client
76            .post(format!("{}/swap-instructions", self.base_path))
77            .json(swap_request)
78            .send()
79            .await?;
80        check_status_code_and_deserialize::<SwapInstructionsResponseInternal>(response)
81            .await
82            .map(Into::into)
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use solana_sdk::{pubkey, pubkey::Pubkey};
89    use transaction_config::TransactionConfig;
90
91    use super::*;
92
93    const USDC_MINT: Pubkey = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
94    const NATIVE_MINT: Pubkey = pubkey!("So11111111111111111111111111111111111111112");
95
96    async fn get_quote_response(client: &JupiterSwapApiClient) -> Result<QuoteResponse> {
97        let quote_request = QuoteRequest {
98            input_mint: NATIVE_MINT,
99            output_mint: USDC_MINT,
100            amount: 10000000,
101            ..Default::default()
102        };
103
104        client.quote(&quote_request).await
105    }
106
107    #[tokio::test]
108    async fn test_quote() {
109        let client = JupiterSwapApiClient::default();
110
111        assert!(get_quote_response(&client).await.is_ok());
112    }
113
114    #[tokio::test]
115    async fn test_swap() {
116        let client = JupiterSwapApiClient::default();
117        let quote_response = get_quote_response(&client).await.unwrap();
118        let swap_request = SwapRequest {
119            user_public_key: Pubkey::default(),
120            quote_response,
121            config: TransactionConfig::default(),
122        };
123
124        assert!(client.swap(&swap_request).await.is_ok());
125    }
126
127    #[tokio::test]
128    async fn test_swap_instructions() {
129        let client = JupiterSwapApiClient::default();
130        let quote_response = get_quote_response(&client).await.unwrap();
131        let swap_request = SwapRequest {
132            user_public_key: Pubkey::default(),
133            quote_response,
134            config: TransactionConfig::default(),
135        };
136
137        assert!(client.swap_instructions(&swap_request).await.is_ok());
138    }
139}