jupiter_swap_api_client_abrkn/
lib.rs1use 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("e_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("e_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}