1use crate::auth::HmacKey;
4use reqwest::{
5 StatusCode,
6 header::{HeaderMap, HeaderValue},
7};
8
9use crate::{
10 ARBITRUM_ONE_RELAYER_BASE_URL, ARBITRUM_SEPOLIA_RELAYER_BASE_URL, AssembleQuoteOptionsV2,
11 BASE_MAINNET_RELAYER_BASE_URL, BASE_SEPOLIA_RELAYER_BASE_URL,
12 ETHEREUM_SEPOLIA_RELAYER_BASE_URL, ExternalMatchOptions, RequestQuoteOptions,
13 api_types::{
14 ASSEMBLE_MATCH_BUNDLE_ROUTE, AssemblyType, ExternalMatchResponseV2,
15 GET_MARKET_DEPTH_BY_MINT_ROUTE, GET_MARKETS_DEPTH_ROUTE, GET_MARKETS_ROUTE,
16 GetMarketDepthByMintResponse, GetMarketDepthsResponse, GetMarketsResponse,
17 exchange_metadata::ExchangeMetadataResponse,
18 },
19};
20
21#[allow(deprecated)]
22use crate::http::RelayerHttpClient;
23
24use super::{
25 api_types::{
26 ApiSignedQuoteV2, AssembleExternalMatchRequest, ExternalOrderV2, ExternalQuoteRequest,
27 ExternalQuoteResponse, GET_EXCHANGE_METADATA_ROUTE, SignedExternalQuoteV2,
28 },
29 error::ExternalMatchClientError,
30};
31
32pub const RENEGADE_API_KEY_HEADER: &str = "X-Renegade-Api-Key";
38
39const ARBITRUM_SEPOLIA_AUTH_BASE_URL: &str = "https://arbitrum-sepolia.v2.auth-server.renegade.fi";
41const ARBITRUM_ONE_AUTH_BASE_URL: &str = "https://arbitrum-one.v2.auth-server.renegade.fi";
43const BASE_SEPOLIA_AUTH_BASE_URL: &str = "https://base-sepolia.v2.auth-server.renegade.fi";
45const BASE_MAINNET_AUTH_BASE_URL: &str = "https://base-mainnet.v2.auth-server.renegade.fi";
47const ETHEREUM_SEPOLIA_AUTH_BASE_URL: &str = "https://ethereum-sepolia.v2.auth-server.renegade.fi";
49
50#[derive(Clone, Debug)]
56pub struct ExternalMatchClient {
57 pub(crate) api_key: String,
59 pub(crate) auth_http_client: RelayerHttpClient,
61 pub(crate) relayer_http_client: RelayerHttpClient,
65}
66
67impl ExternalMatchClient {
68 pub fn new(
70 api_key: &str,
71 api_secret: &str,
72 auth_base_url: &str,
73 relayer_base_url: &str,
74 ) -> Result<Self, ExternalMatchClientError> {
75 let api_secret = HmacKey::from_base64_string(api_secret)
76 .map_err(|_| ExternalMatchClientError::InvalidApiSecret)?;
77
78 Ok(Self {
79 api_key: api_key.to_string(),
80 auth_http_client: RelayerHttpClient::new(auth_base_url.to_string(), api_secret),
81 relayer_http_client: RelayerHttpClient::new(relayer_base_url.to_string(), api_secret),
82 })
83 }
84
85 pub fn new_with_client(
87 api_key: &str,
88 api_secret: &str,
89 auth_base_url: &str,
90 relayer_base_url: &str,
91 client: reqwest::Client,
92 ) -> Result<Self, ExternalMatchClientError> {
93 let api_secret = HmacKey::from_base64_string(api_secret)
94 .map_err(|_| ExternalMatchClientError::InvalidApiSecret)?;
95 let auth_http_client = RelayerHttpClient::new_with_client(
96 auth_base_url.to_string(),
97 api_secret,
98 client.clone(),
99 );
100 let relayer_http_client =
101 RelayerHttpClient::new_with_client(relayer_base_url.to_string(), api_secret, client);
102
103 Ok(Self { api_key: api_key.to_string(), auth_http_client, relayer_http_client })
104 }
105
106 pub fn new_ethereum_sepolia_client(
108 api_key: &str,
109 api_secret: &str,
110 ) -> Result<Self, ExternalMatchClientError> {
111 Self::new(
112 api_key,
113 api_secret,
114 ETHEREUM_SEPOLIA_AUTH_BASE_URL,
115 ETHEREUM_SEPOLIA_RELAYER_BASE_URL,
116 )
117 }
118
119 pub fn new_ethereum_sepolia_with_client(
122 api_key: &str,
123 api_secret: &str,
124 client: reqwest::Client,
125 ) -> Result<Self, ExternalMatchClientError> {
126 Self::new_with_client(
127 api_key,
128 api_secret,
129 ETHEREUM_SEPOLIA_AUTH_BASE_URL,
130 ETHEREUM_SEPOLIA_RELAYER_BASE_URL,
131 client,
132 )
133 }
134
135 pub fn new_arbitrum_sepolia_client(
137 api_key: &str,
138 api_secret: &str,
139 ) -> Result<Self, ExternalMatchClientError> {
140 Self::new(
141 api_key,
142 api_secret,
143 ARBITRUM_SEPOLIA_AUTH_BASE_URL,
144 ARBITRUM_SEPOLIA_RELAYER_BASE_URL,
145 )
146 }
147
148 pub fn new_base_sepolia_client(
150 api_key: &str,
151 api_secret: &str,
152 ) -> Result<Self, ExternalMatchClientError> {
153 Self::new(api_key, api_secret, BASE_SEPOLIA_AUTH_BASE_URL, BASE_SEPOLIA_RELAYER_BASE_URL)
154 }
155
156 pub fn new_arbitrum_one_client(
158 api_key: &str,
159 api_secret: &str,
160 ) -> Result<Self, ExternalMatchClientError> {
161 Self::new(api_key, api_secret, ARBITRUM_ONE_AUTH_BASE_URL, ARBITRUM_ONE_RELAYER_BASE_URL)
162 }
163
164 pub fn new_arbitrum_one_with_client(
166 api_key: &str,
167 api_secret: &str,
168 client: reqwest::Client,
169 ) -> Result<Self, ExternalMatchClientError> {
170 Self::new_with_client(
171 api_key,
172 api_secret,
173 ARBITRUM_ONE_AUTH_BASE_URL,
174 ARBITRUM_ONE_RELAYER_BASE_URL,
175 client,
176 )
177 }
178
179 pub fn new_base_mainnet_client(
181 api_key: &str,
182 api_secret: &str,
183 ) -> Result<Self, ExternalMatchClientError> {
184 Self::new(api_key, api_secret, BASE_MAINNET_AUTH_BASE_URL, BASE_MAINNET_RELAYER_BASE_URL)
185 }
186
187 pub fn new_base_mainnet_with_client(
189 api_key: &str,
190 api_secret: &str,
191 client: reqwest::Client,
192 ) -> Result<Self, ExternalMatchClientError> {
193 Self::new_with_client(
194 api_key,
195 api_secret,
196 BASE_MAINNET_AUTH_BASE_URL,
197 BASE_MAINNET_RELAYER_BASE_URL,
198 client,
199 )
200 }
201
202 pub async fn get_markets(&self) -> Result<GetMarketsResponse, ExternalMatchClientError> {
209 let path = GET_MARKETS_ROUTE;
210 let headers = self.get_headers()?;
211 let resp = self.auth_http_client.get_with_headers(path, headers).await?;
212
213 Ok(resp)
214 }
215
216 pub async fn get_market_depth(
220 &self,
221 address: &str,
222 ) -> Result<GetMarketDepthByMintResponse, ExternalMatchClientError> {
223 let path = GET_MARKET_DEPTH_BY_MINT_ROUTE.replace(":mint", address);
224 let headers = self.get_headers()?;
225 let resp = self.auth_http_client.get_with_headers(&path, headers).await?;
226
227 Ok(resp)
228 }
229
230 pub async fn get_market_depths_all_pairs(
232 &self,
233 ) -> Result<GetMarketDepthsResponse, ExternalMatchClientError> {
234 let path = GET_MARKETS_DEPTH_ROUTE;
235 let headers = self.get_headers()?;
236 let resp = self.auth_http_client.get_with_headers(path, headers).await?;
237
238 Ok(resp)
239 }
240
241 pub async fn request_quote_v2(
247 &self,
248 order: ExternalOrderV2,
249 ) -> Result<Option<SignedExternalQuoteV2>, ExternalMatchClientError> {
250 self.request_quote_with_options_v2(order, RequestQuoteOptions::default()).await
251 }
252
253 pub async fn request_quote_with_options_v2(
255 &self,
256 order: ExternalOrderV2,
257 options: RequestQuoteOptions,
258 ) -> Result<Option<SignedExternalQuoteV2>, ExternalMatchClientError> {
259 let request = ExternalQuoteRequest { external_order: order };
260 let path = options.build_request_path();
261 let headers = self.get_headers()?;
262
263 let resp = self.auth_http_client.post_with_headers_raw(&path, request, headers).await?;
264 let quote_resp = Self::handle_optional_response::<ExternalQuoteResponse>(resp).await?;
265 Ok(quote_resp
266 .map(|r| SignedExternalQuoteV2::from_api_quote(r.signed_quote, r.gas_sponsorship_info)))
267 }
268
269 pub async fn assemble_quote_v2(
271 &self,
272 quote: SignedExternalQuoteV2,
273 ) -> Result<Option<ExternalMatchResponseV2>, ExternalMatchClientError> {
274 self.assemble_quote_with_options_v2(quote, AssembleQuoteOptionsV2::default()).await
275 }
276
277 pub async fn assemble_quote_with_options_v2(
280 &self,
281 quote: SignedExternalQuoteV2,
282 options: AssembleQuoteOptionsV2,
283 ) -> Result<Option<ExternalMatchResponseV2>, ExternalMatchClientError> {
284 let path = ASSEMBLE_MATCH_BUNDLE_ROUTE;
285
286 let signed_quote = ApiSignedQuoteV2::from(quote);
287 let order =
288 AssemblyType::QuotedOrder { signed_quote, updated_order: options.updated_order };
289
290 let request = AssembleExternalMatchRequest {
291 receiver_address: options.receiver_address,
292 do_gas_estimation: options.do_gas_estimation,
293 order,
294 };
295
296 let headers = self.get_headers()?;
297 let resp = self.auth_http_client.post_with_headers_raw(path, request, headers).await?;
298
299 let match_resp = Self::handle_optional_response::<ExternalMatchResponseV2>(resp).await?;
300 Ok(match_resp)
301 }
302
303 pub async fn request_external_match_v2(
305 &self,
306 order: ExternalOrderV2,
307 ) -> Result<Option<ExternalMatchResponseV2>, ExternalMatchClientError> {
308 self.request_external_match_with_options_v2(order, Default::default()).await
309 }
310
311 pub async fn request_external_match_with_options_v2(
314 &self,
315 order: ExternalOrderV2,
316 options: ExternalMatchOptions,
317 ) -> Result<Option<ExternalMatchResponseV2>, ExternalMatchClientError> {
318 let path = options.build_request_path();
319
320 let order = AssemblyType::DirectOrder { external_order: order };
321 let request = AssembleExternalMatchRequest {
322 receiver_address: options.receiver_address,
323 do_gas_estimation: options.do_gas_estimation,
324 order,
325 };
326
327 let headers = self.get_headers()?;
328 let resp =
329 self.auth_http_client.post_with_headers_raw(path.as_str(), request, headers).await?;
330
331 let match_resp = Self::handle_optional_response::<ExternalMatchResponseV2>(resp).await?;
332 Ok(match_resp)
333 }
334
335 pub async fn get_exchange_metadata(
341 &self,
342 ) -> Result<ExchangeMetadataResponse, ExternalMatchClientError> {
343 let path = GET_EXCHANGE_METADATA_ROUTE;
344 let headers = self.get_headers()?;
345 let resp = self.auth_http_client.get_with_headers(path, headers).await?;
346
347 Ok(resp)
348 }
349
350 pub(crate) async fn handle_optional_response<T>(
357 response: reqwest::Response,
358 ) -> Result<Option<T>, ExternalMatchClientError>
359 where
360 T: serde::de::DeserializeOwned,
361 {
362 if response.status() == StatusCode::NO_CONTENT {
363 Ok(None)
364 } else if response.status() == StatusCode::OK {
365 let resp = response.json::<T>().await?;
366 Ok(Some(resp))
367 } else {
368 let status = response.status();
369 let msg = response.text().await?;
370 Err(ExternalMatchClientError::http(status, msg))
371 }
372 }
373
374 pub(crate) fn get_headers(&self) -> Result<HeaderMap, ExternalMatchClientError> {
376 let mut headers = HeaderMap::new();
377 let api_key = HeaderValue::from_str(&self.api_key)
378 .map_err(|_| ExternalMatchClientError::InvalidApiKey)?;
379 headers.insert(RENEGADE_API_KEY_HEADER, api_key);
380
381 Ok(headers)
382 }
383}