1use num_bigint::BigUint;
11use serde::{Deserialize, Serialize};
12use serde_with::{serde_as, DisplayFromStr};
13use uuid::Uuid;
14
15mod hex_bytes_serde {
23 use serde::{Deserialize, Deserializer, Serializer};
24
25 pub fn serialize<S>(x: &bytes::Bytes, s: S) -> Result<S::Ok, S::Error>
26 where
27 S: Serializer,
28 {
29 s.serialize_str(&format!("0x{}", hex::encode(x.as_ref())))
30 }
31
32 pub fn deserialize<'de, D>(d: D) -> Result<bytes::Bytes, D::Error>
33 where
34 D: Deserializer<'de>,
35 {
36 let s = String::deserialize(d)?;
37 let stripped = s.strip_prefix("0x").unwrap_or(&s);
38 hex::decode(stripped)
39 .map(bytes::Bytes::from)
40 .map_err(serde::de::Error::custom)
41 }
42}
43
44#[derive(Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
51pub struct Bytes(#[serde(with = "hex_bytes_serde")] pub bytes::Bytes);
52
53impl Bytes {
54 pub fn len(&self) -> usize {
55 self.0.len()
56 }
57
58 pub fn is_empty(&self) -> bool {
59 self.0.is_empty()
60 }
61}
62
63impl std::fmt::Debug for Bytes {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 write!(f, "Bytes(0x{})", hex::encode(self.0.as_ref()))
66 }
67}
68
69impl AsRef<[u8]> for Bytes {
70 fn as_ref(&self) -> &[u8] {
71 self.0.as_ref()
72 }
73}
74
75impl From<&[u8]> for Bytes {
76 fn from(src: &[u8]) -> Self {
77 Self(bytes::Bytes::copy_from_slice(src))
78 }
79}
80
81impl From<Vec<u8>> for Bytes {
82 fn from(src: Vec<u8>) -> Self {
83 Self(src.into())
84 }
85}
86
87impl From<bytes::Bytes> for Bytes {
88 fn from(src: bytes::Bytes) -> Self {
89 Self(src)
90 }
91}
92
93impl<const N: usize> From<[u8; N]> for Bytes {
94 fn from(src: [u8; N]) -> Self {
95 Self(bytes::Bytes::copy_from_slice(&src))
96 }
97}
98
99pub type Address = Bytes;
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
108#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
109pub struct QuoteRequest {
110 orders: Vec<Order>,
112 #[serde(default)]
114 options: QuoteOptions,
115}
116
117impl QuoteRequest {
118 pub fn new(orders: Vec<Order>) -> Self {
120 Self { orders, options: QuoteOptions::default() }
121 }
122
123 pub fn with_options(mut self, options: QuoteOptions) -> Self {
125 self.options = options;
126 self
127 }
128
129 pub fn orders(&self) -> &[Order] {
131 &self.orders
132 }
133
134 pub fn options(&self) -> &QuoteOptions {
136 &self.options
137 }
138}
139
140#[serde_as]
142#[derive(Debug, Clone, Default, Serialize, Deserialize)]
143#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
144pub struct QuoteOptions {
145 #[cfg_attr(feature = "openapi", schema(example = 2000))]
147 timeout_ms: Option<u64>,
148 #[serde(default, skip_serializing_if = "Option::is_none")]
154 min_responses: Option<usize>,
155 #[serde_as(as = "Option<DisplayFromStr>")]
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, example = "500000"))]
159 max_gas: Option<BigUint>,
160 encoding_options: Option<EncodingOptions>,
162}
163
164impl QuoteOptions {
165 pub fn with_timeout_ms(mut self, ms: u64) -> Self {
167 self.timeout_ms = Some(ms);
168 self
169 }
170
171 pub fn with_min_responses(mut self, n: usize) -> Self {
173 self.min_responses = Some(n);
174 self
175 }
176
177 pub fn with_max_gas(mut self, gas: BigUint) -> Self {
179 self.max_gas = Some(gas);
180 self
181 }
182
183 pub fn with_encoding_options(mut self, opts: EncodingOptions) -> Self {
185 self.encoding_options = Some(opts);
186 self
187 }
188
189 pub fn timeout_ms(&self) -> Option<u64> {
191 self.timeout_ms
192 }
193
194 pub fn min_responses(&self) -> Option<usize> {
196 self.min_responses
197 }
198
199 pub fn max_gas(&self) -> Option<&BigUint> {
201 self.max_gas.as_ref()
202 }
203
204 pub fn encoding_options(&self) -> Option<&EncodingOptions> {
206 self.encoding_options.as_ref()
207 }
208}
209
210#[non_exhaustive]
212#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
213#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
214#[serde(rename_all = "snake_case")]
215pub enum UserTransferType {
216 TransferFromPermit2,
218 #[default]
220 TransferFrom,
221 UseVaultsFunds,
223}
224
225#[serde_as]
230#[derive(Debug, Clone, Serialize, Deserialize)]
231#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
232pub struct ClientFeeParams {
233 #[cfg_attr(feature = "openapi", schema(example = 100))]
235 bps: u16,
236 #[cfg_attr(
238 feature = "openapi",
239 schema(value_type = String, example = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
240 )]
241 receiver: Bytes,
242 #[serde_as(as = "DisplayFromStr")]
244 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "0"))]
245 max_contribution: BigUint,
246 #[cfg_attr(feature = "openapi", schema(example = 1893456000))]
248 deadline: u64,
249 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "0xabcd..."))]
251 signature: Bytes,
252}
253
254impl ClientFeeParams {
255 pub fn new(
257 bps: u16,
258 receiver: Bytes,
259 max_contribution: BigUint,
260 deadline: u64,
261 signature: Bytes,
262 ) -> Self {
263 Self { bps, receiver, max_contribution, deadline, signature }
264 }
265
266 pub fn bps(&self) -> u16 {
268 self.bps
269 }
270
271 pub fn receiver(&self) -> &Bytes {
273 &self.receiver
274 }
275
276 pub fn max_contribution(&self) -> &BigUint {
278 &self.max_contribution
279 }
280
281 pub fn deadline(&self) -> u64 {
283 self.deadline
284 }
285
286 pub fn signature(&self) -> &Bytes {
288 &self.signature
289 }
290}
291
292#[serde_as]
296#[derive(Debug, Clone, Serialize, Deserialize)]
297#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
298pub struct FeeBreakdown {
299 #[serde_as(as = "DisplayFromStr")]
301 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "350000"))]
302 router_fee: BigUint,
303 #[serde_as(as = "DisplayFromStr")]
305 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "2800000"))]
306 client_fee: BigUint,
307 #[serde_as(as = "DisplayFromStr")]
309 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "3496850"))]
310 max_slippage: BigUint,
311 #[serde_as(as = "DisplayFromStr")]
314 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "3493353150"))]
315 min_amount_received: BigUint,
316}
317
318impl FeeBreakdown {
319 pub fn router_fee(&self) -> &BigUint {
321 &self.router_fee
322 }
323
324 pub fn client_fee(&self) -> &BigUint {
326 &self.client_fee
327 }
328
329 pub fn max_slippage(&self) -> &BigUint {
331 &self.max_slippage
332 }
333
334 pub fn min_amount_received(&self) -> &BigUint {
336 &self.min_amount_received
337 }
338}
339
340#[serde_as]
342#[derive(Debug, Clone, Serialize, Deserialize)]
343#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
344pub struct EncodingOptions {
345 #[serde_as(as = "DisplayFromStr")]
346 #[cfg_attr(feature = "openapi", schema(example = "0.001"))]
347 slippage: f64,
348 #[serde(default)]
350 transfer_type: UserTransferType,
351 #[serde(default, skip_serializing_if = "Option::is_none")]
353 permit: Option<PermitSingle>,
354 #[serde(default, skip_serializing_if = "Option::is_none")]
356 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, example = "0xabcd..."))]
357 permit2_signature: Option<Bytes>,
358 #[serde(default, skip_serializing_if = "Option::is_none")]
360 client_fee_params: Option<ClientFeeParams>,
361}
362
363impl EncodingOptions {
364 pub fn new(slippage: f64) -> Self {
366 Self {
367 slippage,
368 transfer_type: UserTransferType::default(),
369 permit: None,
370 permit2_signature: None,
371 client_fee_params: None,
372 }
373 }
374
375 pub fn with_transfer_type(mut self, t: UserTransferType) -> Self {
377 self.transfer_type = t;
378 self
379 }
380
381 pub fn with_permit2(mut self, permit: PermitSingle, sig: Bytes) -> Self {
383 self.permit = Some(permit);
384 self.permit2_signature = Some(sig);
385 self
386 }
387
388 pub fn slippage(&self) -> f64 {
390 self.slippage
391 }
392
393 pub fn transfer_type(&self) -> &UserTransferType {
395 &self.transfer_type
396 }
397
398 pub fn permit(&self) -> Option<&PermitSingle> {
400 self.permit.as_ref()
401 }
402
403 pub fn permit2_signature(&self) -> Option<&Bytes> {
405 self.permit2_signature.as_ref()
406 }
407
408 pub fn with_client_fee_params(mut self, params: ClientFeeParams) -> Self {
410 self.client_fee_params = Some(params);
411 self
412 }
413
414 pub fn client_fee_params(&self) -> Option<&ClientFeeParams> {
416 self.client_fee_params.as_ref()
417 }
418}
419
420#[serde_as]
422#[derive(Debug, Clone, Serialize, Deserialize)]
423#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
424pub struct PermitSingle {
425 details: PermitDetails,
427 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"))]
429 spender: Bytes,
430 #[serde_as(as = "DisplayFromStr")]
432 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "1893456000"))]
433 sig_deadline: BigUint,
434}
435
436impl PermitSingle {
437 pub fn new(details: PermitDetails, spender: Bytes, sig_deadline: BigUint) -> Self {
439 Self { details, spender, sig_deadline }
440 }
441
442 pub fn details(&self) -> &PermitDetails {
444 &self.details
445 }
446
447 pub fn spender(&self) -> &Bytes {
449 &self.spender
450 }
451
452 pub fn sig_deadline(&self) -> &BigUint {
454 &self.sig_deadline
455 }
456}
457
458#[serde_as]
460#[derive(Debug, Clone, Serialize, Deserialize)]
461#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
462pub struct PermitDetails {
463 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"))]
465 token: Bytes,
466 #[serde_as(as = "DisplayFromStr")]
468 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "1000000000000000000"))]
469 amount: BigUint,
470 #[serde_as(as = "DisplayFromStr")]
472 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "1893456000"))]
473 expiration: BigUint,
474 #[serde_as(as = "DisplayFromStr")]
476 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "0"))]
477 nonce: BigUint,
478}
479
480impl PermitDetails {
481 pub fn new(token: Bytes, amount: BigUint, expiration: BigUint, nonce: BigUint) -> Self {
483 Self { token, amount, expiration, nonce }
484 }
485
486 pub fn token(&self) -> &Bytes {
488 &self.token
489 }
490
491 pub fn amount(&self) -> &BigUint {
493 &self.amount
494 }
495
496 pub fn expiration(&self) -> &BigUint {
498 &self.expiration
499 }
500
501 pub fn nonce(&self) -> &BigUint {
503 &self.nonce
504 }
505}
506
507#[must_use]
516#[serde_as]
517#[derive(Debug, Clone, Serialize, Deserialize)]
518#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
519pub struct Quote {
520 orders: Vec<OrderQuote>,
522 #[serde_as(as = "DisplayFromStr")]
524 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "150000"))]
525 total_gas_estimate: BigUint,
526 #[cfg_attr(feature = "openapi", schema(example = 12))]
528 solve_time_ms: u64,
529}
530
531impl Quote {
532 pub fn new(orders: Vec<OrderQuote>, total_gas_estimate: BigUint, solve_time_ms: u64) -> Self {
534 Self { orders, total_gas_estimate, solve_time_ms }
535 }
536
537 pub fn orders(&self) -> &[OrderQuote] {
539 &self.orders
540 }
541
542 pub fn into_orders(self) -> Vec<OrderQuote> {
544 self.orders
545 }
546
547 pub fn total_gas_estimate(&self) -> &BigUint {
549 &self.total_gas_estimate
550 }
551
552 pub fn solve_time_ms(&self) -> u64 {
554 self.solve_time_ms
555 }
556}
557
558#[serde_as]
562#[derive(Debug, Clone, Serialize, Deserialize)]
563#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
564pub struct Order {
565 #[serde(default = "generate_order_id", skip_deserializing)]
569 id: String,
570 #[cfg_attr(
572 feature = "openapi",
573 schema(value_type = String, example = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
574 )]
575 token_in: Address,
576 #[cfg_attr(
578 feature = "openapi",
579 schema(value_type = String, example = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
580 )]
581 token_out: Address,
582 #[serde_as(as = "DisplayFromStr")]
584 #[cfg_attr(
585 feature = "openapi",
586 schema(value_type = String, example = "1000000000000000000")
587 )]
588 amount: BigUint,
589 side: OrderSide,
591 #[cfg_attr(
593 feature = "openapi",
594 schema(value_type = String, example = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
595 )]
596 sender: Address,
597 #[serde(default, skip_serializing_if = "Option::is_none")]
601 #[cfg_attr(
602 feature = "openapi",
603 schema(value_type = Option<String>, example = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
604 )]
605 receiver: Option<Address>,
606}
607
608impl Order {
609 pub fn new(
611 token_in: Address,
612 token_out: Address,
613 amount: BigUint,
614 side: OrderSide,
615 sender: Address,
616 ) -> Self {
617 Self { id: String::new(), token_in, token_out, amount, side, sender, receiver: None }
618 }
619
620 pub fn with_id(mut self, id: impl Into<String>) -> Self {
622 self.id = id.into();
623 self
624 }
625
626 pub fn with_receiver(mut self, receiver: Address) -> Self {
628 self.receiver = Some(receiver);
629 self
630 }
631
632 pub fn id(&self) -> &str {
634 &self.id
635 }
636
637 pub fn token_in(&self) -> &Address {
639 &self.token_in
640 }
641
642 pub fn token_out(&self) -> &Address {
644 &self.token_out
645 }
646
647 pub fn amount(&self) -> &BigUint {
649 &self.amount
650 }
651
652 pub fn side(&self) -> OrderSide {
654 self.side
655 }
656
657 pub fn sender(&self) -> &Address {
659 &self.sender
660 }
661
662 pub fn receiver(&self) -> Option<&Address> {
664 self.receiver.as_ref()
665 }
666}
667
668#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
672#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
673#[serde(rename_all = "snake_case")]
674pub enum OrderSide {
675 Sell,
677}
678
679#[must_use]
684#[serde_as]
685#[derive(Debug, Clone, Serialize, Deserialize)]
686#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
687pub struct OrderQuote {
688 #[cfg_attr(feature = "openapi", schema(example = "f47ac10b-58cc-4372-a567-0e02b2c3d479"))]
690 order_id: String,
691 status: QuoteStatus,
693 #[serde(skip_serializing_if = "Option::is_none")]
695 route: Option<Route>,
696 #[serde_as(as = "DisplayFromStr")]
698 #[cfg_attr(
699 feature = "openapi",
700 schema(value_type = String, example = "1000000000000000000")
701 )]
702 amount_in: BigUint,
703 #[serde_as(as = "DisplayFromStr")]
705 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "3500000000"))]
706 amount_out: BigUint,
707 #[serde_as(as = "DisplayFromStr")]
709 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "150000"))]
710 gas_estimate: BigUint,
711 #[serde(skip_serializing_if = "Option::is_none")]
713 price_impact_bps: Option<i32>,
714 #[serde_as(as = "DisplayFromStr")]
717 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "3498000000"))]
718 amount_out_net_gas: BigUint,
719 block: BlockInfo,
721 #[serde_as(as = "Option<DisplayFromStr>")]
723 #[serde(skip_serializing_if = "Option::is_none")]
724 #[cfg_attr(feature = "openapi", schema(value_type = Option<String>, example = "20000000000"))]
725 gas_price: Option<BigUint>,
726 transaction: Option<Transaction>,
728 #[serde(skip_serializing_if = "Option::is_none")]
730 fee_breakdown: Option<FeeBreakdown>,
731}
732
733impl OrderQuote {
734 pub fn order_id(&self) -> &str {
736 &self.order_id
737 }
738
739 pub fn status(&self) -> QuoteStatus {
741 self.status
742 }
743
744 pub fn route(&self) -> Option<&Route> {
746 self.route.as_ref()
747 }
748
749 pub fn amount_in(&self) -> &BigUint {
751 &self.amount_in
752 }
753
754 pub fn amount_out(&self) -> &BigUint {
756 &self.amount_out
757 }
758
759 pub fn gas_estimate(&self) -> &BigUint {
761 &self.gas_estimate
762 }
763
764 pub fn price_impact_bps(&self) -> Option<i32> {
766 self.price_impact_bps
767 }
768
769 pub fn amount_out_net_gas(&self) -> &BigUint {
771 &self.amount_out_net_gas
772 }
773
774 pub fn block(&self) -> &BlockInfo {
776 &self.block
777 }
778
779 pub fn gas_price(&self) -> Option<&BigUint> {
781 self.gas_price.as_ref()
782 }
783
784 pub fn transaction(&self) -> Option<&Transaction> {
786 self.transaction.as_ref()
787 }
788
789 pub fn fee_breakdown(&self) -> Option<&FeeBreakdown> {
791 self.fee_breakdown.as_ref()
792 }
793}
794
795#[non_exhaustive]
797#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
798#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
799#[serde(rename_all = "snake_case")]
800pub enum QuoteStatus {
801 Success,
803 NoRouteFound,
805 InsufficientLiquidity,
807 Timeout,
809 NotReady,
811}
812
813#[derive(Debug, Clone, Serialize, Deserialize)]
818#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
819pub struct BlockInfo {
820 #[cfg_attr(feature = "openapi", schema(example = 21000000))]
822 number: u64,
823 #[cfg_attr(
825 feature = "openapi",
826 schema(example = "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd")
827 )]
828 hash: String,
829 #[cfg_attr(feature = "openapi", schema(example = 1730000000))]
831 timestamp: u64,
832}
833
834impl BlockInfo {
835 pub fn new(number: u64, hash: String, timestamp: u64) -> Self {
837 Self { number, hash, timestamp }
838 }
839
840 pub fn number(&self) -> u64 {
842 self.number
843 }
844
845 pub fn hash(&self) -> &str {
847 &self.hash
848 }
849
850 pub fn timestamp(&self) -> u64 {
852 self.timestamp
853 }
854}
855
856#[must_use]
865#[derive(Debug, Clone, Serialize, Deserialize)]
866#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
867pub struct Route {
868 swaps: Vec<Swap>,
870}
871
872impl Route {
873 pub fn new(swaps: Vec<Swap>) -> Self {
875 Self { swaps }
876 }
877
878 pub fn swaps(&self) -> &[Swap] {
880 &self.swaps
881 }
882
883 pub fn into_swaps(self) -> Vec<Swap> {
885 self.swaps
886 }
887}
888
889#[serde_as]
893#[derive(Debug, Clone, Serialize, Deserialize)]
894#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
895pub struct Swap {
896 #[cfg_attr(
898 feature = "openapi",
899 schema(example = "0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc")
900 )]
901 component_id: String,
902 #[cfg_attr(feature = "openapi", schema(example = "uniswap_v2"))]
904 protocol: String,
905 #[cfg_attr(
907 feature = "openapi",
908 schema(value_type = String, example = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
909 )]
910 token_in: Address,
911 #[cfg_attr(
913 feature = "openapi",
914 schema(value_type = String, example = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
915 )]
916 token_out: Address,
917 #[serde_as(as = "DisplayFromStr")]
919 #[cfg_attr(
920 feature = "openapi",
921 schema(value_type = String, example = "1000000000000000000")
922 )]
923 amount_in: BigUint,
924 #[serde_as(as = "DisplayFromStr")]
926 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "3500000000"))]
927 amount_out: BigUint,
928 #[serde_as(as = "DisplayFromStr")]
930 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "150000"))]
931 gas_estimate: BigUint,
932 #[serde_as(as = "DisplayFromStr")]
934 #[cfg_attr(feature = "openapi", schema(example = "0.0"))]
935 split: f64,
936}
937
938impl Swap {
939 #[allow(clippy::too_many_arguments)]
941 pub fn new(
942 component_id: String,
943 protocol: String,
944 token_in: Address,
945 token_out: Address,
946 amount_in: BigUint,
947 amount_out: BigUint,
948 gas_estimate: BigUint,
949 split: f64,
950 ) -> Self {
951 Self {
952 component_id,
953 protocol,
954 token_in,
955 token_out,
956 amount_in,
957 amount_out,
958 gas_estimate,
959 split,
960 }
961 }
962
963 pub fn component_id(&self) -> &str {
965 &self.component_id
966 }
967
968 pub fn protocol(&self) -> &str {
970 &self.protocol
971 }
972
973 pub fn token_in(&self) -> &Address {
975 &self.token_in
976 }
977
978 pub fn token_out(&self) -> &Address {
980 &self.token_out
981 }
982
983 pub fn amount_in(&self) -> &BigUint {
985 &self.amount_in
986 }
987
988 pub fn amount_out(&self) -> &BigUint {
990 &self.amount_out
991 }
992
993 pub fn gas_estimate(&self) -> &BigUint {
995 &self.gas_estimate
996 }
997
998 pub fn split(&self) -> f64 {
1000 self.split
1001 }
1002}
1003
1004#[derive(Debug, Clone, Serialize, Deserialize)]
1010#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1011pub struct HealthStatus {
1012 #[cfg_attr(feature = "openapi", schema(example = true))]
1014 healthy: bool,
1015 #[cfg_attr(feature = "openapi", schema(example = 1250))]
1017 last_update_ms: u64,
1018 #[cfg_attr(feature = "openapi", schema(example = 2))]
1020 num_solver_pools: usize,
1021 #[serde(default)]
1027 #[cfg_attr(feature = "openapi", schema(example = true))]
1028 derived_data_ready: bool,
1029 #[serde(default, skip_serializing_if = "Option::is_none")]
1031 #[cfg_attr(feature = "openapi", schema(example = 12000))]
1032 gas_price_age_ms: Option<u64>,
1033}
1034
1035impl HealthStatus {
1036 pub fn new(
1038 healthy: bool,
1039 last_update_ms: u64,
1040 num_solver_pools: usize,
1041 derived_data_ready: bool,
1042 gas_price_age_ms: Option<u64>,
1043 ) -> Self {
1044 Self { healthy, last_update_ms, num_solver_pools, derived_data_ready, gas_price_age_ms }
1045 }
1046
1047 pub fn healthy(&self) -> bool {
1049 self.healthy
1050 }
1051
1052 pub fn last_update_ms(&self) -> u64 {
1054 self.last_update_ms
1055 }
1056
1057 pub fn num_solver_pools(&self) -> usize {
1059 self.num_solver_pools
1060 }
1061
1062 pub fn derived_data_ready(&self) -> bool {
1064 self.derived_data_ready
1065 }
1066
1067 pub fn gas_price_age_ms(&self) -> Option<u64> {
1069 self.gas_price_age_ms
1070 }
1071}
1072
1073#[derive(Debug, Clone, Serialize, Deserialize)]
1075#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1076pub struct InstanceInfo {
1077 #[cfg_attr(feature = "openapi", schema(example = 1))]
1079 chain_id: u64,
1080 #[cfg_attr(
1082 feature = "openapi",
1083 schema(value_type = String, example = "0xfD0b31d2E955fA55e3fa641Fe90e08b677188d35")
1084 )]
1085 router_address: Bytes,
1086 #[cfg_attr(
1088 feature = "openapi",
1089 schema(value_type = String, example = "0x000000000022D473030F116dDEE9F6B43aC78BA3")
1090 )]
1091 permit2_address: Bytes,
1092}
1093
1094impl InstanceInfo {
1095 pub fn new(chain_id: u64, router_address: Bytes, permit2_address: Bytes) -> Self {
1097 Self { chain_id, router_address, permit2_address }
1098 }
1099
1100 pub fn chain_id(&self) -> u64 {
1102 self.chain_id
1103 }
1104
1105 pub fn router_address(&self) -> &Bytes {
1107 &self.router_address
1108 }
1109
1110 pub fn permit2_address(&self) -> &Bytes {
1112 &self.permit2_address
1113 }
1114}
1115
1116#[derive(Debug, Serialize, Deserialize)]
1118#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1119pub struct ErrorResponse {
1120 #[cfg_attr(feature = "openapi", schema(example = "bad request: no orders provided"))]
1121 error: String,
1122 #[cfg_attr(feature = "openapi", schema(example = "BAD_REQUEST"))]
1123 code: String,
1124 #[serde(skip_serializing_if = "Option::is_none")]
1125 details: Option<serde_json::Value>,
1126}
1127
1128impl ErrorResponse {
1129 pub fn new(error: String, code: String) -> Self {
1131 Self { error, code, details: None }
1132 }
1133
1134 pub fn with_details(mut self, details: serde_json::Value) -> Self {
1136 self.details = Some(details);
1137 self
1138 }
1139
1140 pub fn error(&self) -> &str {
1142 &self.error
1143 }
1144
1145 pub fn code(&self) -> &str {
1147 &self.code
1148 }
1149
1150 pub fn details(&self) -> Option<&serde_json::Value> {
1152 self.details.as_ref()
1153 }
1154}
1155
1156#[serde_as]
1162#[derive(Debug, Clone, Serialize, Deserialize)]
1163#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
1164pub struct Transaction {
1165 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"))]
1167 to: Bytes,
1168 #[serde_as(as = "DisplayFromStr")]
1170 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "0"))]
1171 value: BigUint,
1172 #[cfg_attr(feature = "openapi", schema(value_type = String, example = "0x1234567890abcdef"))]
1174 #[serde(serialize_with = "serialize_bytes_hex", deserialize_with = "deserialize_bytes_hex")]
1175 data: Vec<u8>,
1176}
1177
1178impl Transaction {
1179 pub fn new(to: Bytes, value: BigUint, data: Vec<u8>) -> Self {
1181 Self { to, value, data }
1182 }
1183
1184 pub fn to(&self) -> &Bytes {
1186 &self.to
1187 }
1188
1189 pub fn value(&self) -> &BigUint {
1191 &self.value
1192 }
1193
1194 pub fn data(&self) -> &[u8] {
1196 &self.data
1197 }
1198}
1199
1200fn serialize_bytes_hex<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
1206where
1207 S: serde::Serializer,
1208{
1209 serializer.serialize_str(&format!("0x{}", hex::encode(bytes)))
1210}
1211
1212fn deserialize_bytes_hex<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
1214where
1215 D: serde::Deserializer<'de>,
1216{
1217 let s = String::deserialize(deserializer)?;
1218 let s = s.strip_prefix("0x").unwrap_or(&s);
1219 hex::decode(s).map_err(serde::de::Error::custom)
1220}
1221
1222fn generate_order_id() -> String {
1228 Uuid::new_v4().to_string()
1229}
1230
1231#[cfg(test)]
1240mod wire_format_tests {
1241 use num_bigint::BigUint;
1242
1243 use super::*;
1244
1245 #[test]
1252 fn bytes_deserializes_without_0x_prefix() {
1253 let b: Bytes = serde_json::from_str(r#""deadbeef""#).unwrap();
1254 assert_eq!(b.as_ref(), [0xDE, 0xAD, 0xBE, 0xEF]);
1255 }
1256
1257 #[test]
1264 fn order_serializes_to_full_json() {
1265 let order = Order::new(
1266 Bytes::from([0xAAu8; 20]),
1267 Bytes::from([0xBBu8; 20]),
1268 BigUint::from(1_000_000_000_000_000_000u64),
1269 OrderSide::Sell,
1270 Bytes::from([0xCCu8; 20]),
1271 )
1272 .with_id("abc");
1273
1274 assert_eq!(
1275 serde_json::to_value(&order).unwrap(),
1276 serde_json::json!({
1277 "id": "abc",
1278 "token_in": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1279 "token_out": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1280 "amount": "1000000000000000000",
1281 "side": "sell",
1282 "sender": "0xcccccccccccccccccccccccccccccccccccccccc"
1283 })
1284 );
1285 }
1286
1287 #[test]
1294 fn order_quote_deserializes_from_json() {
1295 let json = r#"{
1296 "order_id": "order-1",
1297 "status": "success",
1298 "amount_in": "1000000000000000000",
1299 "amount_out": "2000000000",
1300 "gas_estimate": "150000",
1301 "amount_out_net_gas": "1999000000",
1302 "price_impact_bps": 5,
1303 "block": { "number": 21000000, "hash": "0xdeadbeef", "timestamp": 1700000000 },
1304 "route": { "swaps": [{
1305 "component_id": "pool-1",
1306 "protocol": "uniswap_v3",
1307 "token_in": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1308 "token_out": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1309 "amount_in": "1000000000000000000",
1310 "amount_out": "2000000000",
1311 "gas_estimate": "150000",
1312 "split": "0"
1313 }]}
1314 }"#;
1315
1316 let quote: OrderQuote = serde_json::from_str(json).unwrap();
1317
1318 assert_eq!(quote.status(), QuoteStatus::Success);
1319 assert_eq!(*quote.amount_in(), BigUint::from(1_000_000_000_000_000_000u64));
1320 assert_eq!(quote.price_impact_bps(), Some(5));
1321 assert_eq!(quote.block().number(), 21_000_000);
1322
1323 let swap = "e.route().unwrap().swaps()[0];
1324 assert_eq!(swap.token_in().as_ref(), [0xAAu8; 20]);
1325 assert_eq!(swap.token_out().as_ref(), [0xBBu8; 20]);
1326 assert_eq!(swap.split(), 0.0);
1327 }
1328
1329 #[test]
1336 fn encoding_options_serializes_to_full_json() {
1337 assert_eq!(
1338 serde_json::to_value(EncodingOptions::new(0.005)).unwrap(),
1339 serde_json::json!({
1340 "slippage": "0.005",
1341 "transfer_type": "transfer_from"
1342 })
1343 );
1344 }
1345
1346 #[test]
1353 fn instance_info_deserializes_and_ignores_unknown_fields() {
1354 let json = r#"{
1355 "chain_id": 1,
1356 "router_address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1357 "permit2_address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
1358 "future_field": "ignored"
1359 }"#;
1360
1361 let info: InstanceInfo = serde_json::from_str(json).unwrap();
1362 assert_eq!(info.chain_id(), 1);
1363 assert_eq!(info.router_address().as_ref(), [0xAAu8; 20]);
1364 assert_eq!(info.permit2_address().as_ref(), [0xBBu8; 20]);
1365 }
1366}
1367
1368#[cfg(feature = "core")]
1379mod conversions {
1380 use tycho_simulation::tycho_core::Bytes as TychoBytes;
1381
1382 use super::*;
1383
1384 impl From<TychoBytes> for Bytes {
1390 fn from(b: TychoBytes) -> Self {
1391 Self(b.0)
1392 }
1393 }
1394
1395 impl From<Bytes> for TychoBytes {
1396 fn from(b: Bytes) -> Self {
1397 Self(b.0)
1398 }
1399 }
1400
1401 impl Into<fynd_core::QuoteRequest> for QuoteRequest {
1406 fn into(self) -> fynd_core::QuoteRequest {
1407 fynd_core::QuoteRequest::new(
1408 self.orders
1409 .into_iter()
1410 .map(Into::into)
1411 .collect(),
1412 self.options.into(),
1413 )
1414 }
1415 }
1416
1417 impl Into<fynd_core::QuoteOptions> for QuoteOptions {
1418 fn into(self) -> fynd_core::QuoteOptions {
1419 let mut opts = fynd_core::QuoteOptions::default();
1420 if let Some(ms) = self.timeout_ms {
1421 opts = opts.with_timeout_ms(ms);
1422 }
1423 if let Some(n) = self.min_responses {
1424 opts = opts.with_min_responses(n);
1425 }
1426 if let Some(gas) = self.max_gas {
1427 opts = opts.with_max_gas(gas);
1428 }
1429 if let Some(enc) = self.encoding_options {
1430 opts = opts.with_encoding_options(enc.into());
1431 }
1432 opts
1433 }
1434 }
1435
1436 impl Into<fynd_core::EncodingOptions> for EncodingOptions {
1437 fn into(self) -> fynd_core::EncodingOptions {
1438 let mut opts = fynd_core::EncodingOptions::new(self.slippage)
1439 .with_transfer_type(self.transfer_type.into());
1440 if let (Some(permit), Some(sig)) = (self.permit, self.permit2_signature) {
1441 opts = opts
1442 .with_permit(permit.into())
1443 .with_signature(sig.into());
1444 }
1445 if let Some(fee) = self.client_fee_params {
1446 opts = opts.with_client_fee_params(fee.into());
1447 }
1448 opts
1449 }
1450 }
1451
1452 impl Into<fynd_core::ClientFeeParams> for ClientFeeParams {
1453 fn into(self) -> fynd_core::ClientFeeParams {
1454 fynd_core::ClientFeeParams::new(
1455 self.bps,
1456 self.receiver.into(),
1457 self.max_contribution,
1458 self.deadline,
1459 self.signature.into(),
1460 )
1461 }
1462 }
1463
1464 impl Into<fynd_core::UserTransferType> for UserTransferType {
1465 fn into(self) -> fynd_core::UserTransferType {
1466 match self {
1467 UserTransferType::TransferFromPermit2 => {
1468 fynd_core::UserTransferType::TransferFromPermit2
1469 }
1470 UserTransferType::TransferFrom => fynd_core::UserTransferType::TransferFrom,
1471 UserTransferType::UseVaultsFunds => fynd_core::UserTransferType::UseVaultsFunds,
1472 }
1473 }
1474 }
1475
1476 impl Into<fynd_core::PermitSingle> for PermitSingle {
1477 fn into(self) -> fynd_core::PermitSingle {
1478 fynd_core::PermitSingle::new(
1479 self.details.into(),
1480 self.spender.into(),
1481 self.sig_deadline,
1482 )
1483 }
1484 }
1485
1486 impl Into<fynd_core::PermitDetails> for PermitDetails {
1487 fn into(self) -> fynd_core::PermitDetails {
1488 fynd_core::PermitDetails::new(
1489 self.token.into(),
1490 self.amount,
1491 self.expiration,
1492 self.nonce,
1493 )
1494 }
1495 }
1496
1497 impl Into<fynd_core::Order> for Order {
1498 fn into(self) -> fynd_core::Order {
1499 let mut order = fynd_core::Order::new(
1500 self.token_in.into(),
1501 self.token_out.into(),
1502 self.amount,
1503 self.side.into(),
1504 self.sender.into(),
1505 )
1506 .with_id(self.id);
1507 if let Some(r) = self.receiver {
1508 order = order.with_receiver(r.into());
1509 }
1510 order
1511 }
1512 }
1513
1514 impl Into<fynd_core::OrderSide> for OrderSide {
1515 fn into(self) -> fynd_core::OrderSide {
1516 match self {
1517 OrderSide::Sell => fynd_core::OrderSide::Sell,
1518 }
1519 }
1520 }
1521
1522 impl From<fynd_core::Quote> for Quote {
1527 fn from(core: fynd_core::Quote) -> Self {
1528 let solve_time_ms = core.solve_time_ms();
1529 let total_gas_estimate = core.total_gas_estimate().clone();
1530 Self {
1531 orders: core
1532 .into_orders()
1533 .into_iter()
1534 .map(Into::into)
1535 .collect(),
1536 total_gas_estimate,
1537 solve_time_ms,
1538 }
1539 }
1540 }
1541
1542 impl From<fynd_core::OrderQuote> for OrderQuote {
1543 fn from(core: fynd_core::OrderQuote) -> Self {
1544 let order_id = core.order_id().to_string();
1545 let status = core.status().into();
1546 let amount_in = core.amount_in().clone();
1547 let amount_out = core.amount_out().clone();
1548 let gas_estimate = core.gas_estimate().clone();
1549 let price_impact_bps = core.price_impact_bps();
1550 let amount_out_net_gas = core.amount_out_net_gas().clone();
1551 let block = core.block().clone().into();
1552 let gas_price = core.gas_price().cloned();
1553 let transaction = core
1554 .transaction()
1555 .cloned()
1556 .map(Into::into);
1557 let fee_breakdown = core
1558 .fee_breakdown()
1559 .cloned()
1560 .map(Into::into);
1561 let route = core.into_route().map(Into::into);
1562 Self {
1563 order_id,
1564 status,
1565 route,
1566 amount_in,
1567 amount_out,
1568 gas_estimate,
1569 price_impact_bps,
1570 amount_out_net_gas,
1571 block,
1572 gas_price,
1573 transaction,
1574 fee_breakdown,
1575 }
1576 }
1577 }
1578
1579 impl From<fynd_core::QuoteStatus> for QuoteStatus {
1580 fn from(core: fynd_core::QuoteStatus) -> Self {
1581 match core {
1582 fynd_core::QuoteStatus::Success => Self::Success,
1583 fynd_core::QuoteStatus::NoRouteFound => Self::NoRouteFound,
1584 fynd_core::QuoteStatus::InsufficientLiquidity => Self::InsufficientLiquidity,
1585 fynd_core::QuoteStatus::Timeout => Self::Timeout,
1586 fynd_core::QuoteStatus::NotReady => Self::NotReady,
1587 _ => Self::NotReady,
1589 }
1590 }
1591 }
1592
1593 impl From<fynd_core::BlockInfo> for BlockInfo {
1594 fn from(core: fynd_core::BlockInfo) -> Self {
1595 Self {
1596 number: core.number(),
1597 hash: core.hash().to_string(),
1598 timestamp: core.timestamp(),
1599 }
1600 }
1601 }
1602
1603 impl From<fynd_core::Route> for Route {
1604 fn from(core: fynd_core::Route) -> Self {
1605 Self {
1606 swaps: core
1607 .into_swaps()
1608 .into_iter()
1609 .map(Into::into)
1610 .collect(),
1611 }
1612 }
1613 }
1614
1615 impl From<fynd_core::Swap> for Swap {
1616 fn from(core: fynd_core::Swap) -> Self {
1617 Self {
1618 component_id: core.component_id().to_string(),
1619 protocol: core.protocol().to_string(),
1620 token_in: core.token_in().clone().into(),
1621 token_out: core.token_out().clone().into(),
1622 amount_in: core.amount_in().clone(),
1623 amount_out: core.amount_out().clone(),
1624 gas_estimate: core.gas_estimate().clone(),
1625 split: *core.split(),
1626 }
1627 }
1628 }
1629
1630 impl From<fynd_core::Transaction> for Transaction {
1631 fn from(core: fynd_core::Transaction) -> Self {
1632 Self {
1633 to: core.to().clone().into(),
1634 value: core.value().clone(),
1635 data: core.data().to_vec(),
1636 }
1637 }
1638 }
1639
1640 impl From<fynd_core::FeeBreakdown> for FeeBreakdown {
1641 fn from(core: fynd_core::FeeBreakdown) -> Self {
1642 Self {
1643 router_fee: core.router_fee().clone(),
1644 client_fee: core.client_fee().clone(),
1645 max_slippage: core.max_slippage().clone(),
1646 min_amount_received: core.min_amount_received().clone(),
1647 }
1648 }
1649 }
1650
1651 #[cfg(test)]
1652 mod tests {
1653 use num_bigint::BigUint;
1654
1655 use super::*;
1656
1657 fn make_address(byte: u8) -> Address {
1658 Address::from([byte; 20])
1659 }
1660
1661 #[test]
1662 fn test_quote_request_roundtrip() {
1663 let dto = QuoteRequest {
1664 orders: vec![Order {
1665 id: "test-id".to_string(),
1666 token_in: make_address(0x01),
1667 token_out: make_address(0x02),
1668 amount: BigUint::from(1000u64),
1669 side: OrderSide::Sell,
1670 sender: make_address(0xAA),
1671 receiver: None,
1672 }],
1673 options: QuoteOptions {
1674 timeout_ms: Some(5000),
1675 min_responses: None,
1676 max_gas: None,
1677 encoding_options: None,
1678 },
1679 };
1680
1681 let core: fynd_core::QuoteRequest = dto.clone().into();
1682 assert_eq!(core.orders().len(), 1);
1683 assert_eq!(core.orders()[0].id(), "test-id");
1684 assert_eq!(core.options().timeout_ms(), Some(5000));
1685 }
1686
1687 #[test]
1688 fn test_quote_from_core() {
1689 let core: fynd_core::Quote = serde_json::from_str(
1690 r#"{"orders":[],"total_gas_estimate":"100000","solve_time_ms":50}"#,
1691 )
1692 .unwrap();
1693
1694 let dto = Quote::from(core);
1695 assert_eq!(dto.total_gas_estimate, BigUint::from(100_000u64));
1696 assert_eq!(dto.solve_time_ms, 50);
1697 }
1698
1699 #[test]
1700 fn test_order_side_into_core() {
1701 let core: fynd_core::OrderSide = OrderSide::Sell.into();
1702 assert_eq!(core, fynd_core::OrderSide::Sell);
1703 }
1704
1705 #[test]
1706 fn test_client_fee_params_into_core() {
1707 let dto = ClientFeeParams::new(
1708 200,
1709 Bytes::from(make_address(0xBB).as_ref()),
1710 BigUint::from(1_000_000u64),
1711 1_893_456_000u64,
1712 Bytes::from(vec![0xABu8; 65]),
1713 );
1714 let core: fynd_core::ClientFeeParams = dto.into();
1715 assert_eq!(core.bps(), 200);
1716 assert_eq!(*core.max_contribution(), BigUint::from(1_000_000u64));
1717 assert_eq!(core.deadline(), 1_893_456_000u64);
1718 assert_eq!(core.signature().len(), 65);
1719 }
1720
1721 #[test]
1722 fn test_encoding_options_with_client_fee_into_core() {
1723 let fee = ClientFeeParams::new(
1724 100,
1725 Bytes::from(make_address(0xCC).as_ref()),
1726 BigUint::from(500u64),
1727 9_999u64,
1728 Bytes::from(vec![0xDEu8; 65]),
1729 );
1730 let dto = EncodingOptions::new(0.005).with_client_fee_params(fee);
1731 let core: fynd_core::EncodingOptions = dto.into();
1732
1733 assert!(core.client_fee_params().is_some());
1734 let core_fee = core.client_fee_params().unwrap();
1735 assert_eq!(core_fee.bps(), 100);
1736 assert_eq!(*core_fee.max_contribution(), BigUint::from(500u64));
1737 }
1738
1739 #[test]
1740 fn test_client_fee_params_serde_roundtrip() {
1741 let fee = ClientFeeParams::new(
1742 150,
1743 Bytes::from(make_address(0xDD).as_ref()),
1744 BigUint::from(999_999u64),
1745 1_700_000_000u64,
1746 Bytes::from(vec![0xFFu8; 65]),
1747 );
1748 let json = serde_json::to_string(&fee).unwrap();
1749 assert!(json.contains(r#""max_contribution":"999999""#));
1750 assert!(json.contains(r#""deadline":1700000000"#));
1751
1752 let deserialized: ClientFeeParams = serde_json::from_str(&json).unwrap();
1753 assert_eq!(deserialized.bps(), 150);
1754 assert_eq!(*deserialized.max_contribution(), BigUint::from(999_999u64));
1755 }
1756
1757 #[test]
1758 fn test_quote_status_from_core() {
1759 let cases = [
1760 (fynd_core::QuoteStatus::Success, QuoteStatus::Success),
1761 (fynd_core::QuoteStatus::NoRouteFound, QuoteStatus::NoRouteFound),
1762 (fynd_core::QuoteStatus::InsufficientLiquidity, QuoteStatus::InsufficientLiquidity),
1763 (fynd_core::QuoteStatus::Timeout, QuoteStatus::Timeout),
1764 (fynd_core::QuoteStatus::NotReady, QuoteStatus::NotReady),
1765 ];
1766
1767 for (core, expected) in cases {
1768 assert_eq!(QuoteStatus::from(core), expected);
1769 }
1770 }
1771 }
1772}