1use std::fmt::Display;
2
3use alloy_primitives::{Address, Bytes, U256};
4use bon::Builder;
5use serde::{Deserialize, Serialize};
6use tracing::debug;
7use url::Url;
8
9use crate::{
10 error_code::TraceId,
11 OdosError,
12 OdosRouterV2::{inputTokenInfo, outputTokenInfo, swapTokenInfo},
13 OdosV2Router::{swapCall, OdosV2RouterCalls},
14 Result,
15};
16
17#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
19pub enum Endpoint {
20 Public,
22 Enterprise,
24}
25
26impl Endpoint {
27 pub fn base_url(&self) -> Url {
29 match self {
30 Endpoint::Public => Url::parse("https://api.odos.xyz/").unwrap(),
31 Endpoint::Enterprise => Url::parse("https://enterprise-api.odos.xyz/").unwrap(),
32 }
33 }
34
35 pub fn quote_url_v2(&self) -> Url {
37 self.base_url().join("sor/quote/v2").unwrap()
38 }
39
40 pub fn quote_url_v3(&self) -> Url {
42 self.base_url().join("sor/quote/v3").unwrap()
43 }
44
45 pub fn assemble_url(&self) -> Url {
47 self.base_url().join("sor/assemble").unwrap()
48 }
49}
50
51#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
53#[serde(rename_all = "camelCase")]
54pub struct InputToken {
55 token_address: String,
57 amount: String,
59}
60
61impl InputToken {
62 pub fn new(token_address: Address, amount: U256) -> Self {
63 Self {
64 token_address: token_address.to_string(),
65 amount: amount.to_string(),
66 }
67 }
68}
69
70impl From<(Address, U256)> for InputToken {
71 fn from((token_address, amount): (Address, U256)) -> Self {
72 Self::new(token_address, amount)
73 }
74}
75
76impl Display for InputToken {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 write!(
79 f,
80 "InputToken {{ token_address: {}, amount: {} }}",
81 self.token_address, self.amount
82 )
83 }
84}
85
86#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
88#[serde(rename_all = "camelCase")]
89pub struct OutputToken {
90 token_address: String,
92 proportion: u32,
93}
94
95impl OutputToken {
96 pub fn new(token_address: Address, proportion: u32) -> Self {
97 Self {
98 token_address: token_address.to_string(),
99 proportion,
100 }
101 }
102}
103
104impl From<(Address, u32)> for OutputToken {
105 fn from((token_address, proportion): (Address, u32)) -> Self {
106 Self::new(token_address, proportion)
107 }
108}
109
110impl Display for OutputToken {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 write!(
113 f,
114 "OutputToken {{ token_address: {}, proportion: {} }}",
115 self.token_address, self.proportion
116 )
117 }
118}
119
120#[derive(Builder, Clone, Debug, Default, PartialEq, PartialOrd, Deserialize, Serialize)]
122#[serde(rename_all = "camelCase")]
123pub struct QuoteRequest {
124 chain_id: u64,
125 input_tokens: Vec<InputToken>,
126 output_tokens: Vec<OutputToken>,
127 slippage_limit_percent: f64,
128 user_addr: String,
130 compact: bool,
131 simple: bool,
132 referral_code: u32,
133 disable_rfqs: bool,
134 #[builder(default)]
135 source_blacklist: Vec<String>,
136}
137
138#[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)]
140#[serde(rename_all = "camelCase")]
141pub struct SingleQuoteResponse {
142 block_number: u64,
143 data_gas_estimate: u64,
144 gas_estimate: f64,
145 gas_estimate_value: f64,
146 gwei_per_gas: f64,
147 in_amounts: Vec<String>,
148 in_tokens: Vec<Address>,
149 in_values: Vec<f64>,
150 net_out_value: f64,
151 out_amounts: Vec<String>,
152 out_tokens: Vec<Address>,
153 out_values: Vec<f64>,
154 partner_fee_percent: f64,
155 path_id: String,
156 path_viz: Option<String>,
157 percent_diff: f64,
158 price_impact: f64,
159}
160
161impl SingleQuoteResponse {
162 pub fn data_gas_estimate(&self) -> u64 {
164 self.data_gas_estimate
165 }
166
167 pub fn get_block_number(&self) -> u64 {
169 self.block_number
170 }
171
172 pub fn gas_estimate(&self) -> f64 {
174 self.gas_estimate
175 }
176
177 pub fn in_amounts_iter(&self) -> impl Iterator<Item = &String> {
179 self.in_amounts.iter()
180 }
181
182 pub fn in_amount_u256(&self) -> Result<U256> {
184 let amount_str = self
185 .in_amounts_iter()
186 .next()
187 .ok_or_else(|| OdosError::missing_data("Missing input amount"))?;
188 let amount: u128 = amount_str
189 .parse()
190 .map_err(|_| OdosError::invalid_input("Invalid input amount format"))?;
191 Ok(U256::from(amount))
192 }
193
194 pub fn out_amount(&self) -> Option<&String> {
196 self.out_amounts.first()
197 }
198
199 pub fn out_amounts_iter(&self) -> impl Iterator<Item = &String> {
201 self.out_amounts.iter()
202 }
203
204 pub fn in_tokens_iter(&self) -> impl Iterator<Item = &Address> {
206 self.in_tokens.iter()
207 }
208
209 pub fn first_in_token(&self) -> Option<&Address> {
211 self.in_tokens.first()
212 }
213
214 pub fn out_tokens_iter(&self) -> impl Iterator<Item = &Address> {
215 self.out_tokens.iter()
216 }
217
218 pub fn first_out_token(&self) -> Option<&Address> {
220 self.out_tokens.first()
221 }
222
223 pub fn out_values_iter(&self) -> impl Iterator<Item = &f64> {
225 self.out_values.iter()
226 }
227
228 pub fn path_id(&self) -> &str {
230 &self.path_id
231 }
232
233 pub fn path_definition_as_vec_u8(&self) -> Vec<u8> {
235 self.path_id().as_bytes().to_vec()
236 }
237
238 pub fn swap_input_token_and_amount(&self) -> Result<(Address, U256)> {
240 let input_token = *self
241 .in_tokens_iter()
242 .next()
243 .ok_or_else(|| OdosError::missing_data("Missing input token"))?;
244 let input_amount_in_u256 = self.in_amount_u256()?;
245
246 Ok((input_token, input_amount_in_u256))
247 }
248
249 pub fn price_impact(&self) -> f64 {
251 self.price_impact
252 }
253}
254
255#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
271#[serde(rename_all = "camelCase")]
272pub struct OdosApiErrorResponse {
273 pub detail: String,
275 pub trace_id: TraceId,
277 pub error_code: u16,
279}
280
281#[derive(Clone, Debug)]
283pub struct SwapInputs {
284 executor: Address,
285 path_definition: Bytes,
286 input_token_info: inputTokenInfo,
287 output_token_info: outputTokenInfo,
288 value_out_min: U256,
289}
290
291impl TryFrom<OdosV2RouterCalls> for SwapInputs {
292 type Error = OdosError;
293
294 fn try_from(swap: OdosV2RouterCalls) -> std::result::Result<Self, Self::Error> {
295 match swap {
296 OdosV2RouterCalls::swap(call) => {
297 debug!(
298 swap_type = "V2Router",
299 input.token = %call.tokenInfo.inputToken,
300 input.amount_wei = %call.tokenInfo.inputAmount,
301 output.token = %call.tokenInfo.outputToken,
302 output.min_wei = %call.tokenInfo.outputMin,
303 executor = %call.executor,
304 "Extracting swap inputs from V2 router call"
305 );
306
307 let swapCall {
308 executor,
309 pathDefinition,
310 referralCode,
311 tokenInfo,
312 } = call;
313
314 let _referral_code = referralCode;
315
316 let swapTokenInfo {
317 inputToken,
318 inputAmount,
319 inputReceiver,
320 outputMin,
321 outputQuote,
322 outputReceiver,
323 outputToken,
324 } = tokenInfo;
325
326 let _output_quote = outputQuote;
327
328 Ok(Self {
329 executor,
330 path_definition: pathDefinition,
331 input_token_info: inputTokenInfo {
332 tokenAddress: inputToken,
333 amountIn: inputAmount,
334 receiver: inputReceiver,
335 },
336 output_token_info: outputTokenInfo {
337 tokenAddress: outputToken,
338 relativeValue: U256::from(1),
339 receiver: outputReceiver,
340 },
341 value_out_min: outputMin,
342 })
343 }
344 _ => Err(OdosError::invalid_input("Unexpected OdosV2RouterCalls")),
345 }
346 }
347}
348
349impl SwapInputs {
350 pub fn executor(&self) -> Address {
352 self.executor
353 }
354
355 pub fn path_definition(&self) -> &Bytes {
357 &self.path_definition
358 }
359
360 pub fn token_address(&self) -> Address {
362 self.input_token_info.tokenAddress
363 }
364
365 pub fn amount_in(&self) -> U256 {
367 self.input_token_info.amountIn
368 }
369
370 pub fn receiver(&self) -> Address {
372 self.input_token_info.receiver
373 }
374
375 pub fn relative_value(&self) -> U256 {
377 self.output_token_info.relativeValue
378 }
379
380 pub fn output_token_address(&self) -> Address {
382 self.output_token_info.tokenAddress
383 }
384
385 pub fn value_out_min(&self) -> U256 {
387 self.value_out_min
388 }
389}