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