1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[non_exhaustive]
11pub enum Chain {
12 Ethereum,
14 Polygon,
16 Bsc,
18 Avalanche,
20 Fantom,
22 Arbitrum,
24 Optimism,
26 Base,
28 ZkSync,
30 PolygonZkEvm,
32}
33
34impl Chain {
35 #[must_use]
37 pub const fn chain_id(&self) -> u64 {
38 match self {
39 Self::Ethereum => 1,
40 Self::Polygon => 137,
41 Self::Bsc => 56,
42 Self::Avalanche => 43114,
43 Self::Fantom => 250,
44 Self::Arbitrum => 42161,
45 Self::Optimism => 10,
46 Self::Base => 8453,
47 Self::ZkSync => 324,
48 Self::PolygonZkEvm => 1101,
49 }
50 }
51
52 #[must_use]
54 pub const fn as_str(&self) -> &'static str {
55 match self {
56 Self::Ethereum => "ethereum",
57 Self::Polygon => "polygon",
58 Self::Bsc => "bsc",
59 Self::Avalanche => "avalanche",
60 Self::Fantom => "fantom",
61 Self::Arbitrum => "arbitrum",
62 Self::Optimism => "optimism",
63 Self::Base => "base",
64 Self::ZkSync => "zksync",
65 Self::PolygonZkEvm => "polygon-zkevm",
66 }
67 }
68
69 #[must_use]
71 pub const fn from_chain_id(id: u64) -> Option<Self> {
72 match id {
73 1 => Some(Self::Ethereum),
74 137 => Some(Self::Polygon),
75 56 => Some(Self::Bsc),
76 43114 => Some(Self::Avalanche),
77 250 => Some(Self::Fantom),
78 42161 => Some(Self::Arbitrum),
79 10 => Some(Self::Optimism),
80 8453 => Some(Self::Base),
81 324 => Some(Self::ZkSync),
82 1101 => Some(Self::PolygonZkEvm),
83 _ => None,
84 }
85 }
86}
87
88impl std::fmt::Display for Chain {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 write!(f, "{}", self.as_str())
91 }
92}
93
94impl TryFrom<yldfi_common::Chain> for Chain {
95 type Error = &'static str;
96
97 fn try_from(chain: yldfi_common::Chain) -> Result<Self, Self::Error> {
98 match chain {
99 yldfi_common::Chain::Ethereum => Ok(Self::Ethereum),
100 yldfi_common::Chain::Polygon => Ok(Self::Polygon),
101 yldfi_common::Chain::Bsc => Ok(Self::Bsc),
102 yldfi_common::Chain::Avalanche => Ok(Self::Avalanche),
103 yldfi_common::Chain::Fantom => Ok(Self::Fantom),
104 yldfi_common::Chain::Arbitrum => Ok(Self::Arbitrum),
105 yldfi_common::Chain::Optimism => Ok(Self::Optimism),
106 yldfi_common::Chain::Base => Ok(Self::Base),
107 yldfi_common::Chain::ZkSync => Ok(Self::ZkSync),
108 yldfi_common::Chain::PolygonZkEvm => Ok(Self::PolygonZkEvm),
109 _ => Err("Chain not supported by Velora/ParaSwap API"),
110 }
111 }
112}
113
114impl From<Chain> for yldfi_common::Chain {
115 fn from(chain: Chain) -> Self {
116 match chain {
117 Chain::Ethereum => Self::Ethereum,
118 Chain::Polygon => Self::Polygon,
119 Chain::Bsc => Self::Bsc,
120 Chain::Avalanche => Self::Avalanche,
121 Chain::Fantom => Self::Fantom,
122 Chain::Arbitrum => Self::Arbitrum,
123 Chain::Optimism => Self::Optimism,
124 Chain::Base => Self::Base,
125 Chain::ZkSync => Self::ZkSync,
126 Chain::PolygonZkEvm => Self::PolygonZkEvm,
127 }
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
133#[serde(rename_all = "UPPERCASE")]
134pub enum Side {
135 #[default]
137 Sell,
138 Buy,
140}
141
142#[derive(Debug, Clone, Default)]
144pub struct PriceRequest {
145 pub src_token: String,
147 pub dest_token: String,
149 pub amount: String,
151 pub side: Side,
153 pub src_decimals: Option<u8>,
155 pub dest_decimals: Option<u8>,
157 pub user_address: Option<String>,
159 pub partner: Option<String>,
161 pub exclude_dexs: Option<String>,
163 pub include_dexs: Option<String>,
165 pub exclude_pools_with_low_tvl: Option<bool>,
167}
168
169impl PriceRequest {
170 #[must_use]
172 pub fn sell(
173 src_token: impl Into<String>,
174 dest_token: impl Into<String>,
175 amount: impl Into<String>,
176 ) -> Self {
177 Self {
178 src_token: src_token.into(),
179 dest_token: dest_token.into(),
180 amount: amount.into(),
181 side: Side::Sell,
182 ..Default::default()
183 }
184 }
185
186 #[must_use]
188 pub fn buy(
189 src_token: impl Into<String>,
190 dest_token: impl Into<String>,
191 amount: impl Into<String>,
192 ) -> Self {
193 Self {
194 src_token: src_token.into(),
195 dest_token: dest_token.into(),
196 amount: amount.into(),
197 side: Side::Buy,
198 ..Default::default()
199 }
200 }
201
202 #[must_use]
204 pub fn with_src_decimals(mut self, decimals: u8) -> Self {
205 self.src_decimals = Some(decimals);
206 self
207 }
208
209 #[must_use]
211 pub fn with_dest_decimals(mut self, decimals: u8) -> Self {
212 self.dest_decimals = Some(decimals);
213 self
214 }
215
216 #[must_use]
218 pub fn with_user_address(mut self, address: impl Into<String>) -> Self {
219 self.user_address = Some(address.into());
220 self
221 }
222
223 #[must_use]
225 pub fn with_partner(mut self, partner: impl Into<String>) -> Self {
226 self.partner = Some(partner.into());
227 self
228 }
229
230 #[must_use]
232 pub fn with_exclude_dexs(mut self, dexs: impl Into<String>) -> Self {
233 self.exclude_dexs = Some(dexs.into());
234 self
235 }
236
237 #[must_use]
239 pub fn to_query_params(&self, network: u64) -> Vec<(String, String)> {
240 let mut params = vec![
241 ("srcToken".to_string(), self.src_token.clone()),
242 ("destToken".to_string(), self.dest_token.clone()),
243 ("amount".to_string(), self.amount.clone()),
244 (
245 "side".to_string(),
246 match self.side {
247 Side::Sell => "SELL".to_string(),
248 Side::Buy => "BUY".to_string(),
249 },
250 ),
251 ("network".to_string(), network.to_string()),
252 ];
253
254 if let Some(decimals) = self.src_decimals {
255 params.push(("srcDecimals".to_string(), decimals.to_string()));
256 }
257 if let Some(decimals) = self.dest_decimals {
258 params.push(("destDecimals".to_string(), decimals.to_string()));
259 }
260 if let Some(ref addr) = self.user_address {
261 params.push(("userAddress".to_string(), addr.clone()));
262 }
263 if let Some(ref partner) = self.partner {
264 params.push(("partner".to_string(), partner.clone()));
265 }
266 if let Some(ref dexs) = self.exclude_dexs {
267 params.push(("excludeDEXS".to_string(), dexs.clone()));
268 }
269 if let Some(ref dexs) = self.include_dexs {
270 params.push(("includeDEXS".to_string(), dexs.clone()));
271 }
272 if let Some(exclude) = self.exclude_pools_with_low_tvl {
273 params.push(("excludePoolsWithLowTVL".to_string(), exclude.to_string()));
274 }
275
276 params
277 }
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
282#[serde(rename_all = "camelCase")]
283pub struct PriceResponse {
284 pub price_route: PriceRoute,
286}
287
288#[derive(Debug, Clone, Deserialize, Serialize)]
290#[serde(rename_all = "camelCase")]
291pub struct PriceRoute {
292 pub block_number: u64,
294 pub network: u64,
296 pub src_token: String,
298 pub src_decimals: u8,
300 pub src_amount: String,
302 pub dest_token: String,
304 pub dest_decimals: u8,
306 pub dest_amount: String,
308 pub best_route: Vec<Route>,
310 pub token_transfer_proxy: String,
312 pub contract_address: String,
314 pub contract_method: String,
316 #[serde(default)]
318 pub partner_fee: f64,
319 pub gas_cost: Option<String>,
321 pub gas_cost_usd: Option<String>,
323 pub side: String,
325 pub src_usd: Option<String>,
327 pub dest_usd: Option<String>,
329 pub max_impact_reached: Option<bool>,
331 #[serde(default)]
333 pub price_impact: Option<String>,
334}
335
336#[derive(Debug, Clone, Deserialize, Serialize)]
338#[serde(rename_all = "camelCase")]
339pub struct Route {
340 pub percent: f64,
342 pub swaps: Vec<Swap>,
344}
345
346#[derive(Debug, Clone, Deserialize, Serialize)]
348#[serde(rename_all = "camelCase")]
349pub struct Swap {
350 pub src_token: String,
352 pub src_decimals: u8,
354 pub dest_token: String,
356 pub dest_decimals: u8,
358 pub swap_exchanges: Vec<SwapExchange>,
360}
361
362#[derive(Debug, Clone, Deserialize, Serialize)]
364#[serde(rename_all = "camelCase")]
365pub struct SwapExchange {
366 pub exchange: String,
368 pub src_amount: String,
370 pub dest_amount: String,
372 pub percent: f64,
374 #[serde(default)]
376 pub pool_addresses: Vec<String>,
377 #[serde(default)]
379 pub data: Option<serde_json::Value>,
380}
381
382#[derive(Debug, Clone, Serialize)]
384#[serde(rename_all = "camelCase")]
385pub struct TransactionRequest {
386 pub src_token: String,
388 pub dest_token: String,
390 pub src_amount: String,
392 pub dest_amount: String,
394 pub price_route: serde_json::Value,
396 pub slippage: u32,
398 pub user_address: String,
400 #[serde(skip_serializing_if = "Option::is_none")]
402 pub partner: Option<String>,
403 #[serde(skip_serializing_if = "Option::is_none")]
405 pub receiver: Option<String>,
406 #[serde(skip_serializing_if = "Option::is_none")]
408 pub deadline: Option<String>,
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub permit: Option<String>,
412 #[serde(skip_serializing_if = "Option::is_none")]
414 pub ignore_gas: Option<bool>,
415 #[serde(skip_serializing_if = "Option::is_none")]
417 pub ignore_checks: Option<bool>,
418}
419
420impl TransactionRequest {
421 #[must_use]
423 pub fn new(price_route: &PriceRoute, user_address: impl Into<String>, slippage: u32) -> Self {
424 Self {
425 src_token: price_route.src_token.clone(),
426 dest_token: price_route.dest_token.clone(),
427 src_amount: price_route.src_amount.clone(),
428 dest_amount: price_route.dest_amount.clone(),
429 price_route: serde_json::to_value(price_route).unwrap_or_default(),
430 slippage,
431 user_address: user_address.into(),
432 partner: None,
433 receiver: None,
434 deadline: None,
435 permit: None,
436 ignore_gas: None,
437 ignore_checks: None,
438 }
439 }
440
441 #[must_use]
443 pub fn with_receiver(mut self, receiver: impl Into<String>) -> Self {
444 self.receiver = Some(receiver.into());
445 self
446 }
447
448 #[must_use]
450 pub fn with_partner(mut self, partner: impl Into<String>) -> Self {
451 self.partner = Some(partner.into());
452 self
453 }
454
455 #[must_use]
457 pub fn with_deadline(mut self, deadline: impl Into<String>) -> Self {
458 self.deadline = Some(deadline.into());
459 self
460 }
461
462 #[must_use]
464 pub fn with_ignore_gas(mut self, ignore: bool) -> Self {
465 self.ignore_gas = Some(ignore);
466 self
467 }
468
469 #[must_use]
471 pub fn with_ignore_checks(mut self, ignore: bool) -> Self {
472 self.ignore_checks = Some(ignore);
473 self
474 }
475}
476
477#[derive(Debug, Clone, Serialize, Deserialize)]
479#[serde(rename_all = "camelCase")]
480pub struct TransactionResponse {
481 pub from: String,
483 pub to: String,
485 pub chain_id: u64,
487 pub value: String,
489 pub data: String,
491 #[serde(default)]
493 pub gas_price: Option<String>,
494 #[serde(default)]
496 pub gas: Option<String>,
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize)]
501#[serde(rename_all = "camelCase")]
502pub struct Token {
503 pub address: String,
505 pub symbol: String,
507 #[serde(default)]
509 pub name: Option<String>,
510 pub decimals: u8,
512 #[serde(default)]
514 pub img: Option<String>,
515 #[serde(default)]
517 pub is_native: Option<bool>,
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize)]
522pub struct TokenListResponse {
523 pub tokens: Vec<Token>,
525}
526
527#[derive(Debug, Clone, Deserialize)]
529pub struct ApiErrorResponse {
530 pub error: String,
532}
533
534#[cfg(test)]
535mod tests {
536 use super::*;
537
538 #[test]
539 fn test_chain_id() {
540 assert_eq!(Chain::Ethereum.chain_id(), 1);
541 assert_eq!(Chain::Polygon.chain_id(), 137);
542 assert_eq!(Chain::Arbitrum.chain_id(), 42161);
543 }
544
545 #[test]
546 fn test_chain_from_id() {
547 assert_eq!(Chain::from_chain_id(1), Some(Chain::Ethereum));
548 assert_eq!(Chain::from_chain_id(137), Some(Chain::Polygon));
549 assert_eq!(Chain::from_chain_id(999999), None);
550 }
551
552 #[test]
553 fn test_price_request() {
554 let request = PriceRequest::sell(
555 "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
556 "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
557 "1000000000000000000",
558 )
559 .with_src_decimals(18)
560 .with_dest_decimals(6);
561
562 assert_eq!(request.side, Side::Sell);
563 assert_eq!(request.src_decimals, Some(18));
564 assert_eq!(request.dest_decimals, Some(6));
565 }
566}