1use std::fmt::Display;
2
3use alloy_network::TransactionBuilder;
4use alloy_primitives::{hex, Address, U256};
5use alloy_rpc_types::TransactionRequest;
6use serde::{Deserialize, Serialize};
7
8#[deprecated(
14 since = "0.17.0",
15 note = "Use ClientConfig::assemble_url instead for configurable endpoints"
16)]
17pub const ASSEMBLE_URL: &str = "https://api.odos.xyz/sor/assemble";
18
19#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
21#[serde(rename_all = "camelCase")]
22pub struct AssembleRequest {
23 pub user_addr: String,
24 pub path_id: String,
25 pub simulate: bool,
26 pub receiver: Option<Address>,
27}
28
29impl Display for AssembleRequest {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 write!(
32 f,
33 "AssembleRequest {{ user_addr: {}, path_id: {}, simulate: {}, receiver: {} }}",
34 self.user_addr,
35 self.path_id,
36 self.simulate,
37 self.receiver
38 .as_ref()
39 .map_or("None".to_string(), |s| s.to_string())
40 )
41 }
42}
43
44#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
46#[serde(rename_all = "camelCase")]
47pub struct AssemblyResponse {
48 pub transaction: TransactionData,
49 pub simulation: Option<Simulation>,
50}
51
52impl Display for AssemblyResponse {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 write!(
55 f,
56 "AssemblyResponse {{ transaction: {}, simulation: {} }}",
57 self.transaction,
58 self.simulation
59 .as_ref()
60 .map_or("None".to_string(), |s| s.to_string())
61 )
62 }
63}
64
65#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
67#[serde(rename_all = "camelCase")]
68pub struct TransactionData {
69 pub to: Address,
70 pub from: Address,
71 pub data: String,
72 pub value: String,
73 pub gas: i128,
74 pub gas_price: u128,
75 pub chain_id: u64,
76 pub nonce: u64,
77}
78
79impl TryFrom<TransactionData> for TransactionRequest {
81 type Error = crate::OdosError;
82
83 fn try_from(data: TransactionData) -> Result<Self, Self::Error> {
84 let input = hex::decode(&data.data)?;
85 let value = parse_value(&data.value)?;
86
87 Ok(TransactionRequest::default()
88 .with_input(input)
89 .with_value(value))
90 }
91}
92
93impl Display for TransactionData {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 write!(
96 f,
97 "TransactionData {{ to: {}, from: {}, data: {}, value: {}, gas: {}, gas_price: {}, chain_id: {}, nonce: {} }}",
98 self.to,
99 self.from,
100 self.data,
101 self.value,
102 self.gas,
103 self.gas_price,
104 self.chain_id,
105 self.nonce
106 )
107 }
108}
109
110#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
112#[serde(rename_all = "camelCase")]
113pub struct Simulation {
114 is_success: bool,
115 amounts_out: Vec<String>,
116 gas_estimate: i64,
117 simulation_error: SimulationError,
118}
119
120impl Simulation {
121 pub fn is_success(&self) -> bool {
122 self.is_success
123 }
124
125 pub fn error_message(&self) -> &str {
126 &self.simulation_error.error_message
127 }
128}
129
130impl Display for Simulation {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 write!(
133 f,
134 "Simulation {{ is_success: {}, amounts_out: {:?}, gas_estimate: {}, simulation_error: {} }}",
135 self.is_success,
136 self.amounts_out,
137 self.gas_estimate,
138 self.simulation_error.error_message
139 )
140 }
141}
142
143#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]
145#[serde(rename_all = "camelCase")]
146pub struct SimulationError {
147 r#type: String,
148 error_message: String,
149}
150
151impl SimulationError {
152 pub fn error_message(&self) -> &str {
153 &self.error_message
154 }
155}
156
157impl Display for SimulationError {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 write!(f, "Simulation error: {}", self.error_message)
160 }
161}
162
163pub fn parse_value(value: &str) -> crate::Result<U256> {
198 use crate::OdosError;
199
200 if value == "0" {
201 return Ok(U256::ZERO);
202 }
203
204 U256::from_str_radix(value, 10).or_else(|decimal_err| {
206 let hex_value = value.strip_prefix("0x").unwrap_or(value);
208 U256::from_str_radix(hex_value, 16).map_err(|hex_err| {
209 OdosError::invalid_input(format!(
210 "Failed to parse value '{}' as decimal ({}) or hexadecimal ({})",
211 value, decimal_err, hex_err
212 ))
213 })
214 })
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_parse_value_zero() {
223 let result = parse_value("0").unwrap();
224 assert_eq!(result, U256::ZERO);
225 }
226
227 #[test]
228 fn test_parse_value_decimal() {
229 assert_eq!(parse_value("1").unwrap(), U256::from(1));
231 assert_eq!(parse_value("123").unwrap(), U256::from(123));
232 assert_eq!(parse_value("1000").unwrap(), U256::from(1000));
233
234 assert_eq!(
236 parse_value("1000000000000000000").unwrap(),
237 U256::from(1000000000000000000u64)
238 );
239
240 let large_decimal = "123456789012345678901234567890";
242 let result = parse_value(large_decimal);
243 assert!(result.is_ok(), "Should parse large decimal values");
244 }
245
246 #[test]
247 fn test_parse_value_hex_with_prefix() {
248 assert_eq!(parse_value("0x0").unwrap(), U256::ZERO);
250 assert_eq!(parse_value("0xff").unwrap(), U256::from(255));
251 assert_eq!(parse_value("0xFF").unwrap(), U256::from(255));
252 assert_eq!(parse_value("0x1234").unwrap(), U256::from(0x1234));
253 assert_eq!(parse_value("0xabcdef").unwrap(), U256::from(0xabcdef));
254 }
255
256 #[test]
257 fn test_parse_value_hex_without_prefix() {
258 assert_eq!(parse_value("ff").unwrap(), U256::from(255));
260 assert_eq!(parse_value("FF").unwrap(), U256::from(255));
261 assert_eq!(parse_value("abcdef").unwrap(), U256::from(0xabcdef));
262 assert_eq!(parse_value("ABCDEF").unwrap(), U256::from(0xabcdef));
263
264 assert_eq!(parse_value("1234").unwrap(), U256::from(1234));
267 assert_ne!(parse_value("1234").unwrap(), U256::from(0x1234));
268 }
269
270 #[test]
271 fn test_parse_value_invalid() {
272 let result = parse_value("xyz");
274 assert!(result.is_err(), "Invalid characters should fail");
275
276 let result = parse_value("0xGHI");
278 assert!(result.is_err(), "Invalid hex characters should fail");
279
280 let result = parse_value("12@34");
282 assert!(result.is_err(), "Special characters should fail");
283
284 let result = parse_value("");
287 assert_eq!(
288 result.unwrap(),
289 U256::ZERO,
290 "Empty string parses to zero (standard Rust behavior)"
291 );
292 }
293
294 #[test]
295 fn test_parse_value_edge_cases() {
296 assert_eq!(parse_value("00123").unwrap(), U256::from(123));
298 assert_eq!(parse_value("0x00ff").unwrap(), U256::from(255));
299
300 let max_u64_str = u64::MAX.to_string();
302 let result = parse_value(&max_u64_str).unwrap();
303 assert_eq!(result, U256::from(u64::MAX));
304
305 let max_u128_str = u128::MAX.to_string();
307 let result = parse_value(&max_u128_str);
308 assert!(result.is_ok(), "Should handle u128::MAX");
309 }
310
311 #[test]
312 fn test_parse_value_realistic_transaction_values() {
313 let one_eth = "1000000000000000000";
315 assert_eq!(
316 parse_value(one_eth).unwrap(),
317 U256::from(1000000000000000000u64)
318 );
319
320 let hundred_eth = "100000000000000000000";
322 let result = parse_value(hundred_eth);
323 assert!(result.is_ok(), "Should parse 100 ETH");
324
325 let gas_price_hex = "0x2540be400"; let result = parse_value(gas_price_hex);
328 assert!(result.is_ok(), "Should parse hex gas price");
329 }
330
331 #[test]
332 fn test_parse_value_error_messages() {
333 let result = parse_value("invalid");
335 match result {
336 Err(e) => {
337 let error_msg = e.to_string();
338 assert!(
339 error_msg.contains("invalid"),
340 "Error should mention the invalid value"
341 );
342 assert!(
343 error_msg.contains("decimal") || error_msg.contains("hexadecimal"),
344 "Error should mention attempted parsing formats"
345 );
346 }
347 Ok(_) => panic!("Should have failed to parse 'invalid'"),
348 }
349 }
350}