odos_sdk/api.rs
1use std::fmt::Display;
2
3use alloy_primitives::{Address, U256};
4use bon::Builder;
5use serde::{Deserialize, Serialize};
6use url::Url;
7
8use crate::{error_code::TraceId, OdosError, Result};
9
10#[cfg(feature = "v2")]
11use {
12 crate::OdosRouterV2::{inputTokenInfo, outputTokenInfo, swapTokenInfo},
13 crate::OdosV2Router::{swapCall, OdosV2RouterCalls},
14 alloy_primitives::Bytes,
15 tracing::debug,
16};
17
18#[cfg(feature = "v3")]
19use {
20 crate::IOdosRouterV3::swapTokenInfo as v3SwapTokenInfo, crate::OdosV3Router::OdosV3RouterCalls,
21};
22
23/// API host tier for the Odos API
24///
25/// Odos provides two API host tiers:
26/// - **Public**: Standard API available to all users at <https://api.odos.xyz>
27/// - **Enterprise**: Premium API with enhanced features at <https://enterprise-api.odos.xyz>
28///
29/// Use in combination with [`ApiVersion`] via the [`Endpoint`] type for complete
30/// endpoint configuration.
31///
32/// # Examples
33///
34/// ```rust
35/// use odos_sdk::{ApiHost, ApiVersion, Endpoint};
36///
37/// // Use directly with Endpoint
38/// let endpoint = Endpoint::new(ApiHost::Public, ApiVersion::V2);
39///
40/// // Or use convenience methods
41/// let endpoint = Endpoint::public_v2();
42/// ```
43#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
44#[serde(rename_all = "lowercase")]
45pub enum ApiHost {
46 /// Public API endpoint <https://docs.odos.xyz/build/api-docs>
47 ///
48 /// Standard API available to all users. Suitable for most use cases.
49 Public,
50 /// Enterprise API endpoint <https://docs.odos.xyz/build/enterprise-api>
51 ///
52 /// Premium API with enhanced features, higher rate limits, and dedicated support.
53 /// Requires an API key obtained through the Odos Enterprise program.
54 Enterprise,
55}
56
57impl ApiHost {
58 /// Get the base URL for the API host
59 ///
60 /// Returns the root URL for the selected host tier without any path segments.
61 ///
62 /// # Examples
63 ///
64 /// ```rust
65 /// use odos_sdk::ApiHost;
66 ///
67 /// let public = ApiHost::Public;
68 /// assert_eq!(public.base_url().as_str(), "https://api.odos.xyz/");
69 ///
70 /// let enterprise = ApiHost::Enterprise;
71 /// assert_eq!(enterprise.base_url().as_str(), "https://enterprise-api.odos.xyz/");
72 /// ```
73 pub fn base_url(&self) -> Url {
74 match self {
75 ApiHost::Public => Url::parse("https://api.odos.xyz/").unwrap(),
76 ApiHost::Enterprise => Url::parse("https://enterprise-api.odos.xyz/").unwrap(),
77 }
78 }
79}
80
81/// Version of the Odos API
82///
83/// Odos provides multiple API versions with different features and response formats:
84/// - **V2**: Stable production version with comprehensive swap routing
85/// - **V3**: Latest version with enhanced features and optimizations
86///
87/// Use in combination with [`ApiHost`] via the [`Endpoint`] type for complete
88/// endpoint configuration.
89///
90/// # Examples
91///
92/// ```rust
93/// use odos_sdk::{ApiHost, ApiVersion, Endpoint};
94///
95/// // Recommended: Use V2 for production
96/// let endpoint = Endpoint::new(ApiHost::Public, ApiVersion::V2);
97///
98/// // Or use V3 for latest features
99/// let endpoint = Endpoint::new(ApiHost::Public, ApiVersion::V3);
100/// ```
101#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
102#[serde(rename_all = "lowercase")]
103pub enum ApiVersion {
104 /// API version 2 - Stable production version
105 ///
106 /// Recommended for most production use cases. Provides comprehensive
107 /// swap routing with extensive DEX coverage.
108 V2,
109 /// API version 3 - Latest version with enhanced features
110 ///
111 /// Includes optimizations and new features. Check the Odos documentation
112 /// for specific enhancements over V2.
113 V3,
114}
115
116impl ApiVersion {
117 /// Get the path segment for this version
118 ///
119 /// Returns the path component to use in API URLs (e.g., "v2", "v3").
120 fn path(&self) -> &'static str {
121 match self {
122 ApiVersion::V2 => "v2",
123 ApiVersion::V3 => "v3",
124 }
125 }
126}
127
128/// Complete API endpoint configuration combining host tier and API version
129///
130/// The `Endpoint` type provides an ergonomic way to configure both the API host
131/// tier (Public/Enterprise) and version (V2/V3) together.
132///
133/// # Examples
134///
135/// ## Using convenience constructors (recommended)
136///
137/// ```rust
138/// use odos_sdk::{ClientConfig, Endpoint};
139///
140/// // Public API V2 (default, recommended for production)
141/// let config = ClientConfig {
142/// endpoint: Endpoint::public_v2(),
143/// ..Default::default()
144/// };
145///
146/// // Enterprise API V3 (latest features)
147/// let config = ClientConfig {
148/// endpoint: Endpoint::enterprise_v3(),
149/// ..Default::default()
150/// };
151/// ```
152///
153/// ## Using explicit construction
154///
155/// ```rust
156/// use odos_sdk::{Endpoint, ApiHost, ApiVersion};
157///
158/// let endpoint = Endpoint::new(ApiHost::Enterprise, ApiVersion::V2);
159/// assert_eq!(endpoint.quote_url().as_str(), "https://enterprise-api.odos.xyz/sor/quote/v2");
160/// ```
161///
162/// ## Migration from old API
163///
164/// ```rust
165/// use odos_sdk::{ClientConfig, Endpoint};
166///
167/// // Old way (still works but deprecated)
168/// // let config = ClientConfig {
169/// // endpoint: EndpointBase::Public,
170/// // endpoint_version: EndpointVersion::V2,
171/// // ..Default::default()
172/// // };
173///
174/// // New way
175/// let config = ClientConfig {
176/// endpoint: Endpoint::public_v2(),
177/// ..Default::default()
178/// };
179/// ```
180#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Deserialize, Serialize)]
181pub struct Endpoint {
182 host: ApiHost,
183 version: ApiVersion,
184}
185
186impl Endpoint {
187 /// Create a new endpoint with specific host and version
188 ///
189 /// # Examples
190 ///
191 /// ```rust
192 /// use odos_sdk::{Endpoint, ApiHost, ApiVersion};
193 ///
194 /// let endpoint = Endpoint::new(ApiHost::Public, ApiVersion::V2);
195 /// ```
196 pub const fn new(host: ApiHost, version: ApiVersion) -> Self {
197 Self { host, version }
198 }
199
200 /// Public API V2 endpoint (default, recommended for production)
201 ///
202 /// This is the recommended configuration for most production use cases.
203 ///
204 /// # Examples
205 ///
206 /// ```rust
207 /// use odos_sdk::Endpoint;
208 ///
209 /// let endpoint = Endpoint::public_v2();
210 /// assert_eq!(endpoint.quote_url().as_str(), "https://api.odos.xyz/sor/quote/v2");
211 /// ```
212 pub const fn public_v2() -> Self {
213 Self::new(ApiHost::Public, ApiVersion::V2)
214 }
215
216 /// Public API V3 endpoint
217 ///
218 /// Use for latest features and optimizations on the public API.
219 ///
220 /// # Examples
221 ///
222 /// ```rust
223 /// use odos_sdk::Endpoint;
224 ///
225 /// let endpoint = Endpoint::public_v3();
226 /// assert_eq!(endpoint.quote_url().as_str(), "https://api.odos.xyz/sor/quote/v3");
227 /// ```
228 pub const fn public_v3() -> Self {
229 Self::new(ApiHost::Public, ApiVersion::V3)
230 }
231
232 /// Enterprise API V2 endpoint
233 ///
234 /// Use for enterprise tier with V2 API. Requires an API key.
235 ///
236 /// # Examples
237 ///
238 /// ```rust
239 /// use odos_sdk::Endpoint;
240 ///
241 /// let endpoint = Endpoint::enterprise_v2();
242 /// assert_eq!(endpoint.quote_url().as_str(), "https://enterprise-api.odos.xyz/sor/quote/v2");
243 /// ```
244 pub const fn enterprise_v2() -> Self {
245 Self::new(ApiHost::Enterprise, ApiVersion::V2)
246 }
247
248 /// Enterprise API V3 endpoint
249 ///
250 /// Use for enterprise tier with latest V3 features. Requires an API key.
251 ///
252 /// # Examples
253 ///
254 /// ```rust
255 /// use odos_sdk::Endpoint;
256 ///
257 /// let endpoint = Endpoint::enterprise_v3();
258 /// assert_eq!(endpoint.quote_url().as_str(), "https://enterprise-api.odos.xyz/sor/quote/v3");
259 /// ```
260 pub const fn enterprise_v3() -> Self {
261 Self::new(ApiHost::Enterprise, ApiVersion::V3)
262 }
263
264 /// Get the quote URL for this endpoint
265 ///
266 /// Constructs the full URL for the quote endpoint by combining the base URL
267 /// with the appropriate version path.
268 ///
269 /// # Examples
270 ///
271 /// ```rust
272 /// use odos_sdk::Endpoint;
273 ///
274 /// let endpoint = Endpoint::public_v2();
275 /// assert_eq!(endpoint.quote_url().as_str(), "https://api.odos.xyz/sor/quote/v2");
276 ///
277 /// let endpoint = Endpoint::enterprise_v3();
278 /// assert_eq!(endpoint.quote_url().as_str(), "https://enterprise-api.odos.xyz/sor/quote/v3");
279 /// ```
280 pub fn quote_url(&self) -> Url {
281 self.host
282 .base_url()
283 .join(&format!("sor/quote/{}", self.version.path()))
284 .unwrap()
285 }
286
287 /// Get the assemble URL for this endpoint
288 ///
289 /// The assemble endpoint is version-independent and constructs transaction data
290 /// from a previously obtained quote path ID.
291 ///
292 /// # Examples
293 ///
294 /// ```rust
295 /// use odos_sdk::Endpoint;
296 ///
297 /// let endpoint = Endpoint::public_v2();
298 /// assert_eq!(endpoint.assemble_url().as_str(), "https://api.odos.xyz/sor/assemble");
299 /// ```
300 pub fn assemble_url(&self) -> Url {
301 self.host.base_url().join("sor/assemble").unwrap()
302 }
303
304 /// Get the API host tier
305 ///
306 /// # Examples
307 ///
308 /// ```rust
309 /// use odos_sdk::{Endpoint, ApiHost};
310 ///
311 /// let endpoint = Endpoint::public_v2();
312 /// assert_eq!(endpoint.host(), ApiHost::Public);
313 /// ```
314 pub const fn host(&self) -> ApiHost {
315 self.host
316 }
317
318 /// Get the API version
319 ///
320 /// # Examples
321 ///
322 /// ```rust
323 /// use odos_sdk::{Endpoint, ApiVersion};
324 ///
325 /// let endpoint = Endpoint::public_v2();
326 /// assert_eq!(endpoint.version(), ApiVersion::V2);
327 /// ```
328 pub const fn version(&self) -> ApiVersion {
329 self.version
330 }
331}
332
333impl Default for Endpoint {
334 /// Returns the default endpoint: Public API V2
335 ///
336 /// This is the recommended configuration for most production use cases.
337 fn default() -> Self {
338 Self::public_v2()
339 }
340}
341
342/// Input token for the Odos quote API
343#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
344#[serde(rename_all = "camelCase")]
345pub struct InputToken {
346 // Haven't looked much into it, but there's trouble if you try to make this a `Address`
347 token_address: String,
348 // Odos API error message: "Input Amount should be positive integer in string form with < 64 digits[0x6]"
349 amount: String,
350}
351
352impl InputToken {
353 pub fn new(token_address: Address, amount: U256) -> Self {
354 Self {
355 token_address: token_address.to_string(),
356 amount: amount.to_string(),
357 }
358 }
359}
360
361impl From<(Address, U256)> for InputToken {
362 fn from((token_address, amount): (Address, U256)) -> Self {
363 Self::new(token_address, amount)
364 }
365}
366
367impl Display for InputToken {
368 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369 write!(
370 f,
371 "InputToken {{ token_address: {}, amount: {} }}",
372 self.token_address, self.amount
373 )
374 }
375}
376
377/// Output token for the Odos quote API
378#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
379#[serde(rename_all = "camelCase")]
380pub struct OutputToken {
381 // Haven't looked much into it, but there's trouble if you try to make this a `Address`
382 token_address: String,
383 proportion: u32,
384}
385
386impl OutputToken {
387 pub fn new(token_address: Address, proportion: u32) -> Self {
388 Self {
389 token_address: token_address.to_string(),
390 proportion,
391 }
392 }
393}
394
395impl From<(Address, u32)> for OutputToken {
396 fn from((token_address, proportion): (Address, u32)) -> Self {
397 Self::new(token_address, proportion)
398 }
399}
400
401impl Display for OutputToken {
402 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403 write!(
404 f,
405 "OutputToken {{ token_address: {}, proportion: {} }}",
406 self.token_address, self.proportion
407 )
408 }
409}
410
411/// Request to the Odos quote API: <https://docs.odos.xyz/build/api-docs>
412///
413/// # Using Type-Safe Newtypes
414///
415/// You can use the type-safe [`Slippage`](crate::Slippage), [`Chain`](crate::Chain),
416/// and [`ReferralCode`](crate::ReferralCode) types with their conversion methods:
417///
418/// ```rust
419/// use odos_sdk::{QuoteRequest, Slippage, Chain, ReferralCode};
420///
421/// let request = QuoteRequest::builder()
422/// .chain_id(Chain::ethereum().id())
423/// .slippage_limit_percent(Slippage::percent(0.5).unwrap().as_percent())
424/// .referral_code(ReferralCode::NONE.code())
425/// // ... other fields
426/// # .input_tokens(vec![])
427/// # .output_tokens(vec![])
428/// # .user_addr("0x0".to_string())
429/// # .compact(false)
430/// # .simple(false)
431/// # .disable_rfqs(false)
432/// .build();
433/// ```
434#[derive(Builder, Clone, Debug, Default, PartialEq, PartialOrd, Deserialize, Serialize)]
435#[serde(rename_all = "camelCase")]
436pub struct QuoteRequest {
437 chain_id: u64,
438 input_tokens: Vec<InputToken>,
439 output_tokens: Vec<OutputToken>,
440 slippage_limit_percent: f64,
441 // Haven't looked much into it, but there's trouble if you try to make this a `Address`
442 user_addr: String,
443 compact: bool,
444 simple: bool,
445 referral_code: u32,
446 disable_rfqs: bool,
447 #[builder(default)]
448 source_blacklist: Vec<String>,
449}
450
451/// Single quote response from the Odos quote API: <https://docs.odos.xyz/build/api-docs>
452#[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)]
453#[serde(rename_all = "camelCase")]
454pub struct SingleQuoteResponse {
455 block_number: u64,
456 data_gas_estimate: u64,
457 gas_estimate: f64,
458 gas_estimate_value: f64,
459 gwei_per_gas: f64,
460 in_amounts: Vec<String>,
461 in_tokens: Vec<Address>,
462 in_values: Vec<f64>,
463 net_out_value: f64,
464 out_amounts: Vec<String>,
465 out_tokens: Vec<Address>,
466 out_values: Vec<f64>,
467 /// Partner fee percentage. Defaults to 0.0 if not present (V3 API compatibility).
468 #[serde(default)]
469 partner_fee_percent: f64,
470 path_id: String,
471 path_viz: Option<String>,
472 percent_diff: f64,
473 price_impact: f64,
474}
475
476impl SingleQuoteResponse {
477 /// Get the data gas estimate of the quote
478 pub fn data_gas_estimate(&self) -> u64 {
479 self.data_gas_estimate
480 }
481
482 /// Get the block number of the quote
483 pub fn get_block_number(&self) -> u64 {
484 self.block_number
485 }
486
487 /// Get the gas estimate of the quote
488 pub fn gas_estimate(&self) -> f64 {
489 self.gas_estimate
490 }
491
492 /// Get the in amounts of the quote
493 pub fn in_amounts_iter(&self) -> impl Iterator<Item = &String> {
494 self.in_amounts.iter()
495 }
496
497 /// Get the in amount of the quote
498 pub fn in_amount_u256(&self) -> Result<U256> {
499 let amount_str = self
500 .in_amounts_iter()
501 .next()
502 .ok_or_else(|| OdosError::missing_data("Missing input amount"))?;
503 let amount: u128 = amount_str
504 .parse()
505 .map_err(|_| OdosError::invalid_input("Invalid input amount format"))?;
506 Ok(U256::from(amount))
507 }
508
509 /// Get the out amount of the quote
510 pub fn out_amount(&self) -> Option<&String> {
511 self.out_amounts.first()
512 }
513
514 /// Get the out amounts of the quote
515 pub fn out_amounts_iter(&self) -> impl Iterator<Item = &String> {
516 self.out_amounts.iter()
517 }
518
519 /// Get the in tokens of the quote
520 pub fn in_tokens_iter(&self) -> impl Iterator<Item = &Address> {
521 self.in_tokens.iter()
522 }
523
524 /// Get the in token of the quote
525 pub fn first_in_token(&self) -> Option<&Address> {
526 self.in_tokens.first()
527 }
528
529 pub fn out_tokens_iter(&self) -> impl Iterator<Item = &Address> {
530 self.out_tokens.iter()
531 }
532
533 /// Get the out token of the quote
534 pub fn first_out_token(&self) -> Option<&Address> {
535 self.out_tokens.first()
536 }
537
538 /// Get the out values of the quote
539 pub fn out_values_iter(&self) -> impl Iterator<Item = &f64> {
540 self.out_values.iter()
541 }
542
543 /// Get the path id of the quote
544 pub fn path_id(&self) -> &str {
545 &self.path_id
546 }
547
548 /// Get the path id as a vector of bytes
549 pub fn path_definition_as_vec_u8(&self) -> Vec<u8> {
550 self.path_id().as_bytes().to_vec()
551 }
552
553 /// Get the swap input token and amount
554 pub fn swap_input_token_and_amount(&self) -> Result<(Address, U256)> {
555 let input_token = *self
556 .in_tokens_iter()
557 .next()
558 .ok_or_else(|| OdosError::missing_data("Missing input token"))?;
559 let input_amount_in_u256 = self.in_amount_u256()?;
560
561 Ok((input_token, input_amount_in_u256))
562 }
563
564 /// Get the price impact of the quote
565 pub fn price_impact(&self) -> f64 {
566 self.price_impact
567 }
568}
569
570/// Error response from the Odos API
571///
572/// When the Odos API returns an error, it includes:
573/// - `detail`: Human-readable error message
574/// - `traceId`: UUID for tracking the error in Odos logs
575/// - `errorCode`: Numeric error code indicating the specific error type
576///
577/// Example error response:
578/// ```json
579/// {
580/// "detail": "Error getting quote, please try again",
581/// "traceId": "10becdc8-a021-4491-8201-a17b657204e0",
582/// "errorCode": 2999
583/// }
584/// ```
585#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
586#[serde(rename_all = "camelCase")]
587pub struct OdosApiErrorResponse {
588 /// Human-readable error message
589 pub detail: String,
590 /// Trace ID for debugging (UUID)
591 pub trace_id: TraceId,
592 /// Numeric error code
593 pub error_code: u16,
594}
595
596/// Swap inputs for the Odos assemble API
597///
598/// Available only when the `v2` feature is enabled.
599#[cfg(feature = "v2")]
600#[derive(Clone, Debug)]
601pub struct SwapInputs {
602 executor: Address,
603 path_definition: Bytes,
604 input_token_info: inputTokenInfo,
605 output_token_info: outputTokenInfo,
606 value_out_min: U256,
607}
608
609#[cfg(feature = "v2")]
610impl TryFrom<OdosV2RouterCalls> for SwapInputs {
611 type Error = OdosError;
612
613 fn try_from(swap: OdosV2RouterCalls) -> std::result::Result<Self, Self::Error> {
614 match swap {
615 OdosV2RouterCalls::swap(call) => {
616 debug!(
617 swap_type = "V2Router",
618 input.token = %call.tokenInfo.inputToken,
619 input.amount_wei = %call.tokenInfo.inputAmount,
620 output.token = %call.tokenInfo.outputToken,
621 output.min_wei = %call.tokenInfo.outputMin,
622 executor = %call.executor,
623 "Extracting swap inputs from V2 router call"
624 );
625
626 let swapCall {
627 executor,
628 pathDefinition,
629 referralCode,
630 tokenInfo,
631 } = call;
632
633 let _referral_code = referralCode;
634
635 let swapTokenInfo {
636 inputToken,
637 inputAmount,
638 inputReceiver,
639 outputMin,
640 outputQuote,
641 outputReceiver,
642 outputToken,
643 } = tokenInfo;
644
645 let _output_quote = outputQuote;
646
647 Ok(Self {
648 executor,
649 path_definition: pathDefinition,
650 input_token_info: inputTokenInfo {
651 tokenAddress: inputToken,
652 amountIn: inputAmount,
653 receiver: inputReceiver,
654 },
655 output_token_info: outputTokenInfo {
656 tokenAddress: outputToken,
657 relativeValue: U256::from(1),
658 receiver: outputReceiver,
659 },
660 value_out_min: outputMin,
661 })
662 }
663 _ => Err(OdosError::invalid_input("Unexpected OdosV2RouterCalls")),
664 }
665 }
666}
667
668#[cfg(feature = "v3")]
669impl TryFrom<OdosV3RouterCalls> for SwapInputs {
670 type Error = OdosError;
671
672 fn try_from(swap: OdosV3RouterCalls) -> std::result::Result<Self, Self::Error> {
673 match swap {
674 OdosV3RouterCalls::swap(call) => {
675 debug!(
676 swap_type = "V3Router",
677 input.token = %call.tokenInfo.inputToken,
678 input.amount_wei = %call.tokenInfo.inputAmount,
679 output.token = %call.tokenInfo.outputToken,
680 output.min_wei = %call.tokenInfo.outputMin,
681 executor = %call.executor,
682 "Extracting swap inputs from V3 router call"
683 );
684
685 let v3SwapTokenInfo {
686 inputToken,
687 inputAmount,
688 inputReceiver,
689 outputMin,
690 outputQuote,
691 outputReceiver,
692 outputToken,
693 } = call.tokenInfo;
694
695 let _output_quote = outputQuote;
696 let _referral_info = call.referralInfo;
697
698 Ok(Self {
699 executor: call.executor,
700 path_definition: call.pathDefinition,
701 input_token_info: inputTokenInfo {
702 tokenAddress: inputToken,
703 amountIn: inputAmount,
704 receiver: inputReceiver,
705 },
706 output_token_info: outputTokenInfo {
707 tokenAddress: outputToken,
708 relativeValue: U256::from(1),
709 receiver: outputReceiver,
710 },
711 value_out_min: outputMin,
712 })
713 }
714 _ => Err(OdosError::invalid_input("Unexpected OdosV3RouterCalls")),
715 }
716 }
717}
718
719#[cfg(feature = "v2")]
720impl SwapInputs {
721 /// Get the executor of the swap
722 pub fn executor(&self) -> Address {
723 self.executor
724 }
725
726 /// Get the path definition of the swap
727 pub fn path_definition(&self) -> &Bytes {
728 &self.path_definition
729 }
730
731 /// Get the token address of the swap
732 pub fn token_address(&self) -> Address {
733 self.input_token_info.tokenAddress
734 }
735
736 /// Get the amount in of the swap
737 pub fn amount_in(&self) -> U256 {
738 self.input_token_info.amountIn
739 }
740
741 /// Get the receiver of the swap
742 pub fn receiver(&self) -> Address {
743 self.input_token_info.receiver
744 }
745
746 /// Get the relative value of the swap
747 pub fn relative_value(&self) -> U256 {
748 self.output_token_info.relativeValue
749 }
750
751 /// Get the output token address of the swap
752 pub fn output_token_address(&self) -> Address {
753 self.output_token_info.tokenAddress
754 }
755
756 /// Get the value out min of the swap
757 pub fn value_out_min(&self) -> U256 {
758 self.value_out_min
759 }
760}
761
762#[cfg(test)]
763mod tests {
764 use super::*;
765
766 #[test]
767 fn test_api_host_base_url() {
768 assert_eq!(ApiHost::Public.base_url().as_str(), "https://api.odos.xyz/");
769 assert_eq!(
770 ApiHost::Enterprise.base_url().as_str(),
771 "https://enterprise-api.odos.xyz/"
772 );
773 }
774
775 #[test]
776 fn test_api_version_path() {
777 assert_eq!(ApiVersion::V2.path(), "v2");
778 assert_eq!(ApiVersion::V3.path(), "v3");
779 }
780
781 #[test]
782 fn test_endpoint_constructors() {
783 let endpoint = Endpoint::public_v2();
784 assert_eq!(endpoint.host(), ApiHost::Public);
785 assert_eq!(endpoint.version(), ApiVersion::V2);
786
787 let endpoint = Endpoint::public_v3();
788 assert_eq!(endpoint.host(), ApiHost::Public);
789 assert_eq!(endpoint.version(), ApiVersion::V3);
790
791 let endpoint = Endpoint::enterprise_v2();
792 assert_eq!(endpoint.host(), ApiHost::Enterprise);
793 assert_eq!(endpoint.version(), ApiVersion::V2);
794
795 let endpoint = Endpoint::enterprise_v3();
796 assert_eq!(endpoint.host(), ApiHost::Enterprise);
797 assert_eq!(endpoint.version(), ApiVersion::V3);
798
799 let endpoint = Endpoint::new(ApiHost::Public, ApiVersion::V2);
800 assert_eq!(endpoint.host(), ApiHost::Public);
801 assert_eq!(endpoint.version(), ApiVersion::V2);
802 }
803
804 #[test]
805 fn test_endpoint_quote_urls() {
806 assert_eq!(
807 Endpoint::public_v2().quote_url().as_str(),
808 "https://api.odos.xyz/sor/quote/v2"
809 );
810 assert_eq!(
811 Endpoint::public_v3().quote_url().as_str(),
812 "https://api.odos.xyz/sor/quote/v3"
813 );
814 assert_eq!(
815 Endpoint::enterprise_v2().quote_url().as_str(),
816 "https://enterprise-api.odos.xyz/sor/quote/v2"
817 );
818 assert_eq!(
819 Endpoint::enterprise_v3().quote_url().as_str(),
820 "https://enterprise-api.odos.xyz/sor/quote/v3"
821 );
822 }
823
824 #[test]
825 fn test_endpoint_assemble_urls() {
826 assert_eq!(
827 Endpoint::public_v2().assemble_url().as_str(),
828 "https://api.odos.xyz/sor/assemble"
829 );
830 assert_eq!(
831 Endpoint::public_v3().assemble_url().as_str(),
832 "https://api.odos.xyz/sor/assemble"
833 );
834 assert_eq!(
835 Endpoint::enterprise_v2().assemble_url().as_str(),
836 "https://enterprise-api.odos.xyz/sor/assemble"
837 );
838 assert_eq!(
839 Endpoint::enterprise_v3().assemble_url().as_str(),
840 "https://enterprise-api.odos.xyz/sor/assemble"
841 );
842 }
843
844 #[test]
845 fn test_endpoint_default() {
846 let endpoint = Endpoint::default();
847 assert_eq!(endpoint.host(), ApiHost::Public);
848 assert_eq!(endpoint.version(), ApiVersion::V2);
849 assert_eq!(
850 endpoint.quote_url().as_str(),
851 "https://api.odos.xyz/sor/quote/v2"
852 );
853 }
854
855 #[test]
856 fn test_endpoint_equality() {
857 assert_eq!(
858 Endpoint::public_v2(),
859 Endpoint::new(ApiHost::Public, ApiVersion::V2)
860 );
861 assert_eq!(
862 Endpoint::enterprise_v3(),
863 Endpoint::new(ApiHost::Enterprise, ApiVersion::V3)
864 );
865 assert_ne!(Endpoint::public_v2(), Endpoint::public_v3());
866 assert_ne!(Endpoint::public_v2(), Endpoint::enterprise_v2());
867 }
868}