1use std::fmt;
24
25use serde::{Deserialize, Serialize};
26
27pub use cow_types::CowHook;
31
32pub const LATEST_APP_DATA_VERSION: &str = "1.14.0";
42
43pub const LATEST_QUOTE_METADATA_VERSION: &str = "1.1.0";
45
46pub const LATEST_REFERRER_METADATA_VERSION: &str = "1.0.0";
48
49pub const LATEST_ORDER_CLASS_METADATA_VERSION: &str = "0.3.0";
51
52pub const LATEST_UTM_METADATA_VERSION: &str = "0.3.0";
54
55pub const LATEST_HOOKS_METADATA_VERSION: &str = "0.2.0";
57
58pub const LATEST_SIGNER_METADATA_VERSION: &str = "0.1.0";
60
61pub const LATEST_WIDGET_METADATA_VERSION: &str = "0.1.0";
63
64pub const LATEST_PARTNER_FEE_METADATA_VERSION: &str = "1.0.0";
66
67pub const LATEST_REPLACED_ORDER_METADATA_VERSION: &str = "0.1.0";
69
70pub const LATEST_WRAPPERS_METADATA_VERSION: &str = "0.2.0";
72
73pub const LATEST_USER_CONSENTS_METADATA_VERSION: &str = "0.1.0";
75
76#[derive(Debug, Clone, Default, Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct AppDataDoc {
107 pub version: String,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub app_code: Option<String>,
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub environment: Option<String>,
115 pub metadata: Metadata,
117}
118
119impl AppDataDoc {
120 #[must_use]
148 pub fn new(app_code: impl Into<String>) -> Self {
149 Self {
150 version: LATEST_APP_DATA_VERSION.to_owned(),
151 app_code: Some(app_code.into()),
152 environment: None,
153 metadata: Metadata::default(),
154 }
155 }
156
157 #[must_use]
171 pub fn with_environment(mut self, env: impl Into<String>) -> Self {
172 self.environment = Some(env.into());
173 self
174 }
175
176 #[must_use]
189 pub fn with_referrer(mut self, referrer: Referrer) -> Self {
190 self.metadata.referrer = Some(referrer);
191 self
192 }
193
194 #[must_use]
207 pub fn with_utm(mut self, utm: Utm) -> Self {
208 self.metadata.utm = Some(utm);
209 self
210 }
211
212 #[must_use]
226 pub fn with_hooks(mut self, hooks: OrderInteractionHooks) -> Self {
227 self.metadata.hooks = Some(hooks);
228 self
229 }
230
231 #[must_use]
255 pub fn with_partner_fee(mut self, fee: PartnerFee) -> Self {
256 self.metadata.partner_fee = Some(fee);
257 self
258 }
259
260 #[must_use]
274 pub fn with_replaced_order(mut self, uid: impl Into<String>) -> Self {
275 self.metadata.replaced_order = Some(ReplacedOrder { uid: uid.into() });
276 self
277 }
278
279 #[must_use]
293 pub fn with_signer(mut self, signer: impl Into<String>) -> Self {
294 self.metadata.signer = Some(signer.into());
295 self
296 }
297
298 #[must_use]
311 pub const fn with_order_class(mut self, kind: OrderClassKind) -> Self {
312 self.metadata.order_class = Some(OrderClass { order_class: kind });
313 self
314 }
315
316 #[must_use]
330 pub fn with_bridging(mut self, bridging: Bridging) -> Self {
331 self.metadata.bridging = Some(bridging);
332 self
333 }
334
335 #[must_use]
349 pub fn with_flashloan(mut self, flashloan: Flashloan) -> Self {
350 self.metadata.flashloan = Some(flashloan);
351 self
352 }
353
354 #[must_use]
367 pub fn with_wrappers(mut self, wrappers: Vec<WrapperEntry>) -> Self {
368 self.metadata.wrappers = Some(wrappers);
369 self
370 }
371
372 #[must_use]
382 pub fn with_user_consents(mut self, consents: Vec<UserConsent>) -> Self {
383 self.metadata.user_consents = Some(consents);
384 self
385 }
386}
387
388impl fmt::Display for AppDataDoc {
389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390 let code = self.app_code.as_deref().map_or("none", |s| s);
391 write!(f, "app-data(v{}, code={})", self.version, code)
392 }
393}
394#[derive(Debug, Clone, Default, Serialize, Deserialize)]
418#[serde(rename_all = "camelCase")]
419pub struct Metadata {
420 #[serde(skip_serializing_if = "Option::is_none")]
422 pub referrer: Option<Referrer>,
423 #[serde(skip_serializing_if = "Option::is_none")]
425 pub utm: Option<Utm>,
426 #[serde(skip_serializing_if = "Option::is_none")]
428 pub quote: Option<Quote>,
429 #[serde(skip_serializing_if = "Option::is_none")]
431 pub order_class: Option<OrderClass>,
432 #[serde(skip_serializing_if = "Option::is_none")]
434 pub hooks: Option<OrderInteractionHooks>,
435 #[serde(skip_serializing_if = "Option::is_none")]
437 pub widget: Option<Widget>,
438 #[serde(skip_serializing_if = "Option::is_none")]
440 pub partner_fee: Option<PartnerFee>,
441 #[serde(skip_serializing_if = "Option::is_none")]
443 pub replaced_order: Option<ReplacedOrder>,
444 #[serde(skip_serializing_if = "Option::is_none")]
446 pub signer: Option<String>,
447 #[serde(skip_serializing_if = "Option::is_none")]
449 pub bridging: Option<Bridging>,
450 #[serde(skip_serializing_if = "Option::is_none")]
452 pub flashloan: Option<Flashloan>,
453 #[serde(skip_serializing_if = "Option::is_none")]
455 pub wrappers: Option<Vec<WrapperEntry>>,
456 #[serde(skip_serializing_if = "Option::is_none")]
458 pub user_consents: Option<Vec<UserConsent>>,
459}
460
461impl Metadata {
462 #[must_use]
472 pub fn with_referrer(mut self, referrer: Referrer) -> Self {
473 self.referrer = Some(referrer);
474 self
475 }
476
477 #[must_use]
487 pub fn with_utm(mut self, utm: Utm) -> Self {
488 self.utm = Some(utm);
489 self
490 }
491
492 #[must_use]
503 pub const fn with_quote(mut self, quote: Quote) -> Self {
504 self.quote = Some(quote);
505 self
506 }
507
508 #[must_use]
518 pub const fn with_order_class(mut self, order_class: OrderClass) -> Self {
519 self.order_class = Some(order_class);
520 self
521 }
522
523 #[must_use]
533 pub fn with_hooks(mut self, hooks: OrderInteractionHooks) -> Self {
534 self.hooks = Some(hooks);
535 self
536 }
537
538 #[must_use]
548 pub fn with_widget(mut self, widget: Widget) -> Self {
549 self.widget = Some(widget);
550 self
551 }
552
553 #[must_use]
563 pub fn with_partner_fee(mut self, fee: PartnerFee) -> Self {
564 self.partner_fee = Some(fee);
565 self
566 }
567
568 #[must_use]
578 pub fn with_replaced_order(mut self, order: ReplacedOrder) -> Self {
579 self.replaced_order = Some(order);
580 self
581 }
582
583 #[must_use]
593 pub fn with_signer(mut self, signer: impl Into<String>) -> Self {
594 self.signer = Some(signer.into());
595 self
596 }
597
598 #[must_use]
608 pub fn with_bridging(mut self, bridging: Bridging) -> Self {
609 self.bridging = Some(bridging);
610 self
611 }
612
613 #[must_use]
623 pub fn with_flashloan(mut self, flashloan: Flashloan) -> Self {
624 self.flashloan = Some(flashloan);
625 self
626 }
627
628 #[must_use]
638 pub fn with_wrappers(mut self, wrappers: Vec<WrapperEntry>) -> Self {
639 self.wrappers = Some(wrappers);
640 self
641 }
642
643 #[must_use]
653 pub fn with_user_consents(mut self, consents: Vec<UserConsent>) -> Self {
654 self.user_consents = Some(consents);
655 self
656 }
657
658 #[must_use]
660 pub const fn has_referrer(&self) -> bool {
661 self.referrer.is_some()
662 }
663
664 #[must_use]
666 pub const fn has_utm(&self) -> bool {
667 self.utm.is_some()
668 }
669
670 #[must_use]
672 pub const fn has_quote(&self) -> bool {
673 self.quote.is_some()
674 }
675
676 #[must_use]
678 pub const fn has_order_class(&self) -> bool {
679 self.order_class.is_some()
680 }
681
682 #[must_use]
684 pub const fn has_hooks(&self) -> bool {
685 self.hooks.is_some()
686 }
687
688 #[must_use]
690 pub const fn has_widget(&self) -> bool {
691 self.widget.is_some()
692 }
693
694 #[must_use]
696 pub const fn has_partner_fee(&self) -> bool {
697 self.partner_fee.is_some()
698 }
699
700 #[must_use]
702 pub const fn has_replaced_order(&self) -> bool {
703 self.replaced_order.is_some()
704 }
705
706 #[must_use]
708 pub const fn has_signer(&self) -> bool {
709 self.signer.is_some()
710 }
711
712 #[must_use]
714 pub const fn has_bridging(&self) -> bool {
715 self.bridging.is_some()
716 }
717
718 #[must_use]
720 pub const fn has_flashloan(&self) -> bool {
721 self.flashloan.is_some()
722 }
723
724 #[must_use]
726 pub fn has_wrappers(&self) -> bool {
727 self.wrappers.as_ref().is_some_and(|v| !v.is_empty())
728 }
729
730 #[must_use]
732 pub fn has_user_consents(&self) -> bool {
733 self.user_consents.as_ref().is_some_and(|v| !v.is_empty())
734 }
735}
736
737impl fmt::Display for Metadata {
738 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
739 f.write_str("metadata")
740 }
741}
742
743#[derive(Debug, Clone, Serialize, Deserialize)]
768#[serde(untagged)]
769pub enum Referrer {
770 Address {
772 address: String,
774 },
775 Code {
777 code: String,
780 },
781}
782
783impl Referrer {
784 #[must_use]
801 pub fn address(address: impl Into<String>) -> Self {
802 Self::Address { address: address.into() }
803 }
804
805 #[must_use]
821 pub fn code(code: impl Into<String>) -> Self {
822 Self::Code { code: code.into() }
823 }
824
825 #[must_use]
831 #[deprecated(since = "1.1.0", note = "use Referrer::address or Referrer::code explicitly")]
832 pub fn new(address: impl Into<String>) -> Self {
833 Self::address(address)
834 }
835
836 #[must_use]
838 pub const fn as_address(&self) -> Option<&str> {
839 match self {
840 Self::Address { address } => Some(address.as_str()),
841 Self::Code { .. } => None,
842 }
843 }
844
845 #[must_use]
847 pub const fn as_code(&self) -> Option<&str> {
848 match self {
849 Self::Code { code } => Some(code.as_str()),
850 Self::Address { .. } => None,
851 }
852 }
853}
854
855impl fmt::Display for Referrer {
856 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
857 match self {
858 Self::Address { address } => write!(f, "referrer(address={address})"),
859 Self::Code { code } => write!(f, "referrer(code={code})"),
860 }
861 }
862}
863
864#[derive(Debug, Clone, Default, Serialize, Deserialize)]
866#[serde(rename_all = "camelCase")]
867pub struct Utm {
868 #[serde(skip_serializing_if = "Option::is_none")]
870 pub utm_source: Option<String>,
871 #[serde(skip_serializing_if = "Option::is_none")]
873 pub utm_medium: Option<String>,
874 #[serde(skip_serializing_if = "Option::is_none")]
876 pub utm_campaign: Option<String>,
877 #[serde(skip_serializing_if = "Option::is_none")]
879 pub utm_content: Option<String>,
880 #[serde(skip_serializing_if = "Option::is_none")]
882 pub utm_term: Option<String>,
883}
884
885impl Utm {
886 #[must_use]
906 pub fn new() -> Self {
907 Self::default()
908 }
909
910 #[must_use]
920 pub fn with_source(mut self, source: impl Into<String>) -> Self {
921 self.utm_source = Some(source.into());
922 self
923 }
924
925 #[must_use]
935 pub fn with_medium(mut self, medium: impl Into<String>) -> Self {
936 self.utm_medium = Some(medium.into());
937 self
938 }
939
940 #[must_use]
950 pub fn with_campaign(mut self, campaign: impl Into<String>) -> Self {
951 self.utm_campaign = Some(campaign.into());
952 self
953 }
954
955 #[must_use]
965 pub fn with_content(mut self, content: impl Into<String>) -> Self {
966 self.utm_content = Some(content.into());
967 self
968 }
969
970 #[must_use]
980 pub fn with_term(mut self, term: impl Into<String>) -> Self {
981 self.utm_term = Some(term.into());
982 self
983 }
984
985 #[must_use]
987 pub const fn has_source(&self) -> bool {
988 self.utm_source.is_some()
989 }
990
991 #[must_use]
993 pub const fn has_medium(&self) -> bool {
994 self.utm_medium.is_some()
995 }
996
997 #[must_use]
999 pub const fn has_campaign(&self) -> bool {
1000 self.utm_campaign.is_some()
1001 }
1002
1003 #[must_use]
1005 pub const fn has_content(&self) -> bool {
1006 self.utm_content.is_some()
1007 }
1008
1009 #[must_use]
1011 pub const fn has_term(&self) -> bool {
1012 self.utm_term.is_some()
1013 }
1014}
1015
1016impl fmt::Display for Utm {
1017 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1018 let src = self.utm_source.as_deref().map_or("none", |s| s);
1019 write!(f, "utm(source={src})")
1020 }
1021}
1022
1023#[derive(Debug, Clone, Serialize, Deserialize)]
1039#[serde(rename_all = "camelCase")]
1040pub struct Quote {
1041 pub slippage_bips: u32,
1043 #[serde(skip_serializing_if = "Option::is_none")]
1045 pub smart_slippage: Option<bool>,
1046}
1047
1048impl Quote {
1049 #[must_use]
1071 pub const fn new(slippage_bips: u32) -> Self {
1072 Self { slippage_bips, smart_slippage: None }
1073 }
1074
1075 #[must_use]
1084 pub const fn with_smart_slippage(mut self) -> Self {
1085 self.smart_slippage = Some(true);
1086 self
1087 }
1088}
1089
1090impl fmt::Display for Quote {
1091 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1092 write!(f, "quote({}bips)", self.slippage_bips)
1093 }
1094}
1095
1096#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1113#[serde(rename_all = "camelCase")]
1114pub enum OrderClassKind {
1115 Market,
1117 Limit,
1119 Liquidity,
1121 Twap,
1123}
1124
1125impl OrderClassKind {
1126 #[must_use]
1128 pub const fn as_str(self) -> &'static str {
1129 match self {
1130 Self::Market => "market",
1131 Self::Limit => "limit",
1132 Self::Liquidity => "liquidity",
1133 Self::Twap => "twap",
1134 }
1135 }
1136
1137 #[must_use]
1139 pub const fn is_market(self) -> bool {
1140 matches!(self, Self::Market)
1141 }
1142
1143 #[must_use]
1145 pub const fn is_limit(self) -> bool {
1146 matches!(self, Self::Limit)
1147 }
1148
1149 #[must_use]
1151 pub const fn is_liquidity(self) -> bool {
1152 matches!(self, Self::Liquidity)
1153 }
1154
1155 #[must_use]
1157 pub const fn is_twap(self) -> bool {
1158 matches!(self, Self::Twap)
1159 }
1160}
1161
1162impl fmt::Display for OrderClassKind {
1163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1164 f.write_str(self.as_str())
1165 }
1166}
1167
1168impl TryFrom<&str> for OrderClassKind {
1169 type Error = cow_errors::CowError;
1170
1171 fn try_from(s: &str) -> Result<Self, Self::Error> {
1173 match s {
1174 "market" => Ok(Self::Market),
1175 "limit" => Ok(Self::Limit),
1176 "liquidity" => Ok(Self::Liquidity),
1177 "twap" => Ok(Self::Twap),
1178 other => Err(cow_errors::CowError::Parse {
1179 field: "OrderClassKind",
1180 reason: format!("unknown value: {other}"),
1181 }),
1182 }
1183 }
1184}
1185
1186#[derive(Debug, Clone, Serialize, Deserialize)]
1188#[serde(rename_all = "camelCase")]
1189pub struct OrderClass {
1190 pub order_class: OrderClassKind,
1192}
1193
1194impl OrderClass {
1195 #[must_use]
1209 pub const fn new(order_class: OrderClassKind) -> Self {
1210 Self { order_class }
1211 }
1212}
1213
1214impl fmt::Display for OrderClass {
1215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1216 fmt::Display::fmt(&self.order_class, f)
1217 }
1218}
1219
1220#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1238#[serde(rename_all = "camelCase")]
1239pub struct OrderInteractionHooks {
1240 #[serde(skip_serializing_if = "Option::is_none")]
1242 pub version: Option<String>,
1243 #[serde(skip_serializing_if = "Option::is_none")]
1245 pub pre: Option<Vec<CowHook>>,
1246 #[serde(skip_serializing_if = "Option::is_none")]
1248 pub post: Option<Vec<CowHook>>,
1249}
1250
1251impl OrderInteractionHooks {
1252 #[must_use]
1278 pub fn new(pre: Vec<CowHook>, post: Vec<CowHook>) -> Self {
1279 Self {
1280 version: None,
1281 pre: if pre.is_empty() { None } else { Some(pre) },
1282 post: if post.is_empty() { None } else { Some(post) },
1283 }
1284 }
1285
1286 #[must_use]
1296 pub fn with_version(mut self, version: impl Into<String>) -> Self {
1297 self.version = Some(version.into());
1298 self
1299 }
1300
1301 #[must_use]
1303 pub fn has_pre(&self) -> bool {
1304 self.pre.as_ref().is_some_and(|v| !v.is_empty())
1305 }
1306
1307 #[must_use]
1309 pub fn has_post(&self) -> bool {
1310 self.post.as_ref().is_some_and(|v| !v.is_empty())
1311 }
1312}
1313
1314#[derive(Debug, Clone, Serialize, Deserialize)]
1319#[serde(rename_all = "camelCase")]
1320pub struct Widget {
1321 pub app_code: String,
1323 #[serde(skip_serializing_if = "Option::is_none")]
1325 pub environment: Option<String>,
1326}
1327
1328impl Widget {
1329 #[must_use]
1352 pub fn new(app_code: impl Into<String>) -> Self {
1353 Self { app_code: app_code.into(), environment: None }
1354 }
1355
1356 #[must_use]
1366 pub fn with_environment(mut self, env: impl Into<String>) -> Self {
1367 self.environment = Some(env.into());
1368 self
1369 }
1370
1371 #[must_use]
1373 pub const fn has_environment(&self) -> bool {
1374 self.environment.is_some()
1375 }
1376}
1377
1378impl fmt::Display for Widget {
1379 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1380 write!(f, "widget({})", self.app_code)
1381 }
1382}
1383
1384#[derive(Debug, Clone, Serialize, Deserialize)]
1405#[serde(rename_all = "camelCase")]
1406pub struct PartnerFeeEntry {
1407 #[serde(skip_serializing_if = "Option::is_none")]
1409 pub volume_bps: Option<u32>,
1410 #[serde(skip_serializing_if = "Option::is_none")]
1412 pub surplus_bps: Option<u32>,
1413 #[serde(skip_serializing_if = "Option::is_none")]
1415 pub price_improvement_bps: Option<u32>,
1416 #[serde(skip_serializing_if = "Option::is_none")]
1418 pub max_volume_bps: Option<u32>,
1419 pub recipient: String,
1421}
1422
1423impl PartnerFeeEntry {
1424 #[must_use]
1450 pub fn volume(volume_bps: u32, recipient: impl Into<String>) -> Self {
1451 Self {
1452 volume_bps: Some(volume_bps),
1453 surplus_bps: None,
1454 price_improvement_bps: None,
1455 max_volume_bps: None,
1456 recipient: recipient.into(),
1457 }
1458 }
1459
1460 #[must_use]
1486 pub fn surplus(surplus_bps: u32, max_volume_bps: u32, recipient: impl Into<String>) -> Self {
1487 Self {
1488 volume_bps: None,
1489 surplus_bps: Some(surplus_bps),
1490 price_improvement_bps: None,
1491 max_volume_bps: Some(max_volume_bps),
1492 recipient: recipient.into(),
1493 }
1494 }
1495
1496 #[must_use]
1524 pub fn price_improvement(
1525 price_improvement_bps: u32,
1526 max_volume_bps: u32,
1527 recipient: impl Into<String>,
1528 ) -> Self {
1529 Self {
1530 volume_bps: None,
1531 surplus_bps: None,
1532 price_improvement_bps: Some(price_improvement_bps),
1533 max_volume_bps: Some(max_volume_bps),
1534 recipient: recipient.into(),
1535 }
1536 }
1537
1538 #[must_use]
1544 pub const fn volume_bps(&self) -> Option<u32> {
1545 self.volume_bps
1546 }
1547
1548 #[must_use]
1554 pub const fn surplus_bps(&self) -> Option<u32> {
1555 self.surplus_bps
1556 }
1557
1558 #[must_use]
1564 pub const fn price_improvement_bps(&self) -> Option<u32> {
1565 self.price_improvement_bps
1566 }
1567
1568 #[must_use]
1574 pub const fn max_volume_bps(&self) -> Option<u32> {
1575 self.max_volume_bps
1576 }
1577}
1578
1579impl fmt::Display for PartnerFeeEntry {
1580 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1581 if let Some(bps) = self.volume_bps {
1582 write!(f, "volume-fee({}bps, {})", bps, self.recipient)
1583 } else if let Some(bps) = self.surplus_bps {
1584 write!(f, "surplus-fee({}bps, {})", bps, self.recipient)
1585 } else if let Some(bps) = self.price_improvement_bps {
1586 write!(f, "price-improvement-fee({}bps, {})", bps, self.recipient)
1587 } else {
1588 write!(f, "fee({})", self.recipient)
1589 }
1590 }
1591}
1592#[derive(Debug, Clone, Serialize, Deserialize)]
1599#[serde(untagged)]
1600pub enum PartnerFee {
1601 Single(PartnerFeeEntry),
1603 Multiple(Vec<PartnerFeeEntry>),
1605}
1606
1607impl PartnerFee {
1608 #[must_use]
1632 pub const fn single(entry: PartnerFeeEntry) -> Self {
1633 Self::Single(entry)
1634 }
1635
1636 pub fn entries(&self) -> impl Iterator<Item = &PartnerFeeEntry> {
1645 match self {
1646 Self::Single(e) => std::slice::from_ref(e).iter(),
1647 Self::Multiple(v) => v.iter(),
1648 }
1649 }
1650
1651 #[must_use]
1653 pub const fn is_single(&self) -> bool {
1654 matches!(self, Self::Single(_))
1655 }
1656
1657 #[must_use]
1659 pub const fn is_multiple(&self) -> bool {
1660 matches!(self, Self::Multiple(_))
1661 }
1662
1663 #[must_use]
1679 pub const fn count(&self) -> usize {
1680 match self {
1681 Self::Single(_) => 1,
1682 Self::Multiple(v) => v.len(),
1683 }
1684 }
1685}
1686
1687impl fmt::Display for PartnerFee {
1688 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1689 match self {
1690 Self::Single(e) => fmt::Display::fmt(e, f),
1691 Self::Multiple(v) => write!(f, "fees({})", v.len()),
1692 }
1693 }
1694}
1695#[must_use]
1722pub fn get_partner_fee_bps(fee: Option<&PartnerFee>) -> Option<u32> {
1723 fee?.entries().find_map(PartnerFeeEntry::volume_bps)
1724}
1725
1726#[derive(Debug, Clone, Serialize, Deserialize)]
1728#[serde(rename_all = "camelCase")]
1729pub struct ReplacedOrder {
1730 pub uid: String,
1732}
1733
1734impl ReplacedOrder {
1735 #[must_use]
1756 pub fn new(uid: impl Into<String>) -> Self {
1757 Self { uid: uid.into() }
1758 }
1759}
1760
1761impl fmt::Display for ReplacedOrder {
1762 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1763 write!(f, "replaced({})", self.uid)
1764 }
1765}
1766
1767#[derive(Debug, Clone, Serialize, Deserialize)]
1783#[serde(rename_all = "camelCase")]
1784pub struct Bridging {
1785 pub provider: String,
1787 pub destination_chain_id: String,
1789 pub destination_token_address: String,
1791 #[serde(skip_serializing_if = "Option::is_none")]
1793 pub quote_id: Option<String>,
1794 #[serde(skip_serializing_if = "Option::is_none")]
1796 pub quote_signature: Option<String>,
1797 #[serde(skip_serializing_if = "Option::is_none")]
1799 pub attestation_signature: Option<String>,
1800 #[serde(skip_serializing_if = "Option::is_none")]
1802 pub quote_body: Option<String>,
1803}
1804
1805impl Bridging {
1806 #[must_use]
1833 pub fn new(
1834 provider: impl Into<String>,
1835 destination_chain_id: impl Into<String>,
1836 destination_token_address: impl Into<String>,
1837 ) -> Self {
1838 Self {
1839 provider: provider.into(),
1840 destination_chain_id: destination_chain_id.into(),
1841 destination_token_address: destination_token_address.into(),
1842 quote_id: None,
1843 quote_signature: None,
1844 attestation_signature: None,
1845 quote_body: None,
1846 }
1847 }
1848
1849 #[must_use]
1859 pub fn with_quote_id(mut self, id: impl Into<String>) -> Self {
1860 self.quote_id = Some(id.into());
1861 self
1862 }
1863
1864 #[must_use]
1874 pub fn with_quote_signature(mut self, sig: impl Into<String>) -> Self {
1875 self.quote_signature = Some(sig.into());
1876 self
1877 }
1878
1879 #[must_use]
1889 pub fn with_attestation_signature(mut self, sig: impl Into<String>) -> Self {
1890 self.attestation_signature = Some(sig.into());
1891 self
1892 }
1893
1894 #[must_use]
1904 pub fn with_quote_body(mut self, body: impl Into<String>) -> Self {
1905 self.quote_body = Some(body.into());
1906 self
1907 }
1908
1909 #[must_use]
1911 pub const fn has_quote_id(&self) -> bool {
1912 self.quote_id.is_some()
1913 }
1914
1915 #[must_use]
1917 pub const fn has_quote_signature(&self) -> bool {
1918 self.quote_signature.is_some()
1919 }
1920
1921 #[must_use]
1923 pub const fn has_attestation_signature(&self) -> bool {
1924 self.attestation_signature.is_some()
1925 }
1926
1927 #[must_use]
1929 pub const fn has_quote_body(&self) -> bool {
1930 self.quote_body.is_some()
1931 }
1932}
1933
1934impl fmt::Display for Bridging {
1935 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1936 write!(f, "bridge({}, chain={})", self.provider, self.destination_chain_id)
1937 }
1938}
1939
1940#[derive(Debug, Clone, Serialize, Deserialize)]
1961#[serde(rename_all = "camelCase")]
1962pub struct Flashloan {
1963 pub loan_amount: String,
1965 pub liquidity_provider_address: String,
1967 pub protocol_adapter_address: String,
1969 pub receiver_address: String,
1971 pub token_address: String,
1973}
1974
1975impl Flashloan {
1976 #[must_use]
2006 pub fn new(
2007 loan_amount: impl Into<String>,
2008 liquidity_provider_address: impl Into<String>,
2009 token_address: impl Into<String>,
2010 ) -> Self {
2011 Self {
2012 loan_amount: loan_amount.into(),
2013 liquidity_provider_address: liquidity_provider_address.into(),
2014 protocol_adapter_address: String::new(),
2015 receiver_address: String::new(),
2016 token_address: token_address.into(),
2017 }
2018 }
2019
2020 #[must_use]
2033 pub fn with_protocol_adapter(mut self, address: impl Into<String>) -> Self {
2034 self.protocol_adapter_address = address.into();
2035 self
2036 }
2037
2038 #[must_use]
2048 pub fn with_receiver(mut self, address: impl Into<String>) -> Self {
2049 self.receiver_address = address.into();
2050 self
2051 }
2052}
2053
2054impl fmt::Display for Flashloan {
2055 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2056 write!(f, "flashloan({}, amount={})", self.token_address, self.loan_amount)
2057 }
2058}
2059
2060#[derive(Debug, Clone, Serialize, Deserialize)]
2062#[serde(rename_all = "camelCase")]
2063pub struct WrapperEntry {
2064 pub wrapper_address: String,
2066 #[serde(skip_serializing_if = "Option::is_none")]
2068 pub wrapper_data: Option<String>,
2069 #[serde(skip_serializing_if = "Option::is_none")]
2071 pub is_omittable: Option<bool>,
2072}
2073
2074impl WrapperEntry {
2075 #[must_use]
2097 pub fn new(wrapper_address: impl Into<String>) -> Self {
2098 Self { wrapper_address: wrapper_address.into(), wrapper_data: None, is_omittable: None }
2099 }
2100
2101 #[must_use]
2111 pub fn with_wrapper_data(mut self, data: impl Into<String>) -> Self {
2112 self.wrapper_data = Some(data.into());
2113 self
2114 }
2115
2116 #[must_use]
2130 pub const fn with_is_omittable(mut self, omittable: bool) -> Self {
2131 self.is_omittable = Some(omittable);
2132 self
2133 }
2134
2135 #[must_use]
2137 pub const fn has_wrapper_data(&self) -> bool {
2138 self.wrapper_data.is_some()
2139 }
2140
2141 #[must_use]
2143 pub const fn has_is_omittable(&self) -> bool {
2144 self.is_omittable.is_some()
2145 }
2146
2147 #[must_use]
2149 pub const fn is_omittable(&self) -> bool {
2150 matches!(self.is_omittable, Some(true))
2151 }
2152}
2153
2154impl fmt::Display for WrapperEntry {
2155 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2156 write!(f, "wrapper({})", self.wrapper_address)
2157 }
2158}
2159
2160#[derive(Debug, Clone, Serialize, Deserialize)]
2162#[serde(rename_all = "camelCase")]
2163pub struct UserConsent {
2164 pub terms: String,
2166 pub accepted_date: String,
2168}
2169
2170impl UserConsent {
2171 #[must_use]
2191 pub fn new(terms: impl Into<String>, accepted_date: impl Into<String>) -> Self {
2192 Self { terms: terms.into(), accepted_date: accepted_date.into() }
2193 }
2194}
2195
2196impl fmt::Display for UserConsent {
2197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2198 write!(f, "consent({}, {})", self.terms, self.accepted_date)
2199 }
2200}
2201
2202#[cfg(test)]
2203mod tests {
2204 use super::*;
2205
2206 #[test]
2209 fn constants_are_expected_values() {
2210 assert_eq!(LATEST_APP_DATA_VERSION, "1.14.0");
2211 assert_eq!(LATEST_QUOTE_METADATA_VERSION, "1.1.0");
2212 assert_eq!(LATEST_REFERRER_METADATA_VERSION, "1.0.0");
2213 assert_eq!(LATEST_ORDER_CLASS_METADATA_VERSION, "0.3.0");
2214 assert_eq!(LATEST_UTM_METADATA_VERSION, "0.3.0");
2215 assert_eq!(LATEST_HOOKS_METADATA_VERSION, "0.2.0");
2216 assert_eq!(LATEST_SIGNER_METADATA_VERSION, "0.1.0");
2217 assert_eq!(LATEST_WIDGET_METADATA_VERSION, "0.1.0");
2218 assert_eq!(LATEST_PARTNER_FEE_METADATA_VERSION, "1.0.0");
2219 assert_eq!(LATEST_REPLACED_ORDER_METADATA_VERSION, "0.1.0");
2220 assert_eq!(LATEST_WRAPPERS_METADATA_VERSION, "0.2.0");
2221 assert_eq!(LATEST_USER_CONSENTS_METADATA_VERSION, "0.1.0");
2222 }
2223
2224 #[test]
2227 fn app_data_doc_new() {
2228 let doc = AppDataDoc::new("TestApp");
2229 assert_eq!(doc.version, LATEST_APP_DATA_VERSION);
2230 assert_eq!(doc.app_code.as_deref(), Some("TestApp"));
2231 assert!(doc.environment.is_none());
2232 assert!(!doc.metadata.has_referrer());
2233 }
2234
2235 #[test]
2236 fn app_data_doc_default() {
2237 let doc = AppDataDoc::default();
2238 assert!(doc.version.is_empty());
2239 assert!(doc.app_code.is_none());
2240 assert!(doc.environment.is_none());
2241 }
2242
2243 #[test]
2244 fn app_data_doc_with_environment() {
2245 let doc = AppDataDoc::new("App").with_environment("staging");
2246 assert_eq!(doc.environment.as_deref(), Some("staging"));
2247 }
2248
2249 #[test]
2250 fn app_data_doc_with_referrer() {
2251 let doc = AppDataDoc::new("App").with_referrer(Referrer::code("COWRS"));
2252 assert!(doc.metadata.has_referrer());
2253 assert_eq!(doc.metadata.referrer.unwrap().as_code(), Some("COWRS"));
2254 }
2255
2256 #[test]
2257 fn app_data_doc_with_utm() {
2258 let utm = Utm::new().with_source("twitter");
2259 let doc = AppDataDoc::new("App").with_utm(utm);
2260 assert!(doc.metadata.has_utm());
2261 }
2262
2263 #[test]
2264 fn app_data_doc_with_hooks() {
2265 let hook = CowHook::new("0xTarget", "0xData", "50000");
2266 let hooks = OrderInteractionHooks::new(vec![hook], vec![]);
2267 let doc = AppDataDoc::new("App").with_hooks(hooks);
2268 assert!(doc.metadata.has_hooks());
2269 }
2270
2271 #[test]
2272 fn app_data_doc_with_partner_fee() {
2273 let fee = PartnerFee::single(PartnerFeeEntry::volume(50, "0xAddr"));
2274 let doc = AppDataDoc::new("App").with_partner_fee(fee);
2275 assert!(doc.metadata.has_partner_fee());
2276 }
2277
2278 #[test]
2279 fn app_data_doc_with_replaced_order() {
2280 let uid = format!("0x{}", "ab".repeat(56));
2281 let doc = AppDataDoc::new("App").with_replaced_order(&uid);
2282 assert!(doc.metadata.has_replaced_order());
2283 assert_eq!(doc.metadata.replaced_order.unwrap().uid, uid);
2284 }
2285
2286 #[test]
2287 fn app_data_doc_with_signer() {
2288 let doc = AppDataDoc::new("App").with_signer("0xSignerAddr");
2289 assert!(doc.metadata.has_signer());
2290 assert_eq!(doc.metadata.signer.as_deref(), Some("0xSignerAddr"));
2291 }
2292
2293 #[test]
2294 fn app_data_doc_with_order_class() {
2295 let doc = AppDataDoc::new("App").with_order_class(OrderClassKind::Limit);
2296 assert!(doc.metadata.has_order_class());
2297 assert_eq!(doc.metadata.order_class.unwrap().order_class, OrderClassKind::Limit);
2298 }
2299
2300 #[test]
2301 fn app_data_doc_with_bridging() {
2302 let b = Bridging::new("across", "42161", "0xToken");
2303 let doc = AppDataDoc::new("App").with_bridging(b);
2304 assert!(doc.metadata.has_bridging());
2305 }
2306
2307 #[test]
2308 fn app_data_doc_with_flashloan() {
2309 let fl = Flashloan::new("1000", "0xPool", "0xToken");
2310 let doc = AppDataDoc::new("App").with_flashloan(fl);
2311 assert!(doc.metadata.has_flashloan());
2312 }
2313
2314 #[test]
2315 fn app_data_doc_with_wrappers() {
2316 let w = WrapperEntry::new("0xWrapper");
2317 let doc = AppDataDoc::new("App").with_wrappers(vec![w]);
2318 assert!(doc.metadata.has_wrappers());
2319 }
2320
2321 #[test]
2322 fn app_data_doc_with_user_consents() {
2323 let c = UserConsent::new("https://cow.fi/tos", "2025-04-07");
2324 let doc = AppDataDoc::new("App").with_user_consents(vec![c]);
2325 assert!(doc.metadata.has_user_consents());
2326 }
2327
2328 #[test]
2329 fn app_data_doc_display() {
2330 let doc = AppDataDoc::new("MyApp");
2331 assert_eq!(doc.to_string(), "app-data(v1.14.0, code=MyApp)");
2332
2333 let doc_no_code = AppDataDoc::default();
2334 assert_eq!(doc_no_code.to_string(), "app-data(v, code=none)");
2335 }
2336
2337 #[test]
2340 fn metadata_default_all_none() {
2341 let m = Metadata::default();
2342 assert!(!m.has_referrer());
2343 assert!(!m.has_utm());
2344 assert!(!m.has_quote());
2345 assert!(!m.has_order_class());
2346 assert!(!m.has_hooks());
2347 assert!(!m.has_widget());
2348 assert!(!m.has_partner_fee());
2349 assert!(!m.has_replaced_order());
2350 assert!(!m.has_signer());
2351 assert!(!m.has_bridging());
2352 assert!(!m.has_flashloan());
2353 assert!(!m.has_wrappers());
2354 assert!(!m.has_user_consents());
2355 }
2356
2357 #[test]
2358 fn metadata_with_referrer() {
2359 let m = Metadata::default().with_referrer(Referrer::address("0xAddr"));
2360 assert!(m.has_referrer());
2361 }
2362
2363 #[test]
2364 fn metadata_with_utm() {
2365 let m = Metadata::default().with_utm(Utm::new());
2366 assert!(m.has_utm());
2367 }
2368
2369 #[test]
2370 fn metadata_with_quote() {
2371 let m = Metadata::default().with_quote(Quote::new(50));
2372 assert!(m.has_quote());
2373 }
2374
2375 #[test]
2376 fn metadata_with_order_class() {
2377 let oc = OrderClass::new(OrderClassKind::Market);
2378 let m = Metadata::default().with_order_class(oc);
2379 assert!(m.has_order_class());
2380 }
2381
2382 #[test]
2383 fn metadata_with_hooks() {
2384 let hooks = OrderInteractionHooks::new(vec![], vec![]);
2385 let m = Metadata::default().with_hooks(hooks);
2386 assert!(m.has_hooks());
2387 }
2388
2389 #[test]
2390 fn metadata_with_widget() {
2391 let w = Widget::new("Host");
2392 let m = Metadata::default().with_widget(w);
2393 assert!(m.has_widget());
2394 }
2395
2396 #[test]
2397 fn metadata_with_partner_fee() {
2398 let fee = PartnerFee::single(PartnerFeeEntry::volume(10, "0x1"));
2399 let m = Metadata::default().with_partner_fee(fee);
2400 assert!(m.has_partner_fee());
2401 }
2402
2403 #[test]
2404 fn metadata_with_replaced_order() {
2405 let ro = ReplacedOrder::new("0xUID");
2406 let m = Metadata::default().with_replaced_order(ro);
2407 assert!(m.has_replaced_order());
2408 }
2409
2410 #[test]
2411 fn metadata_with_signer() {
2412 let m = Metadata::default().with_signer("0xSigner");
2413 assert!(m.has_signer());
2414 }
2415
2416 #[test]
2417 fn metadata_with_bridging() {
2418 let b = Bridging::new("across", "1", "0xToken");
2419 let m = Metadata::default().with_bridging(b);
2420 assert!(m.has_bridging());
2421 }
2422
2423 #[test]
2424 fn metadata_with_flashloan() {
2425 let fl = Flashloan::new("100", "0xPool", "0xToken");
2426 let m = Metadata::default().with_flashloan(fl);
2427 assert!(m.has_flashloan());
2428 }
2429
2430 #[test]
2431 fn metadata_with_wrappers() {
2432 let m = Metadata::default().with_wrappers(vec![WrapperEntry::new("0xW")]);
2433 assert!(m.has_wrappers());
2434 }
2435
2436 #[test]
2437 fn metadata_with_wrappers_empty_is_false() {
2438 let m = Metadata::default().with_wrappers(vec![]);
2439 assert!(!m.has_wrappers());
2441 }
2442
2443 #[test]
2444 fn metadata_with_user_consents() {
2445 let c = UserConsent::new("tos", "2025-01-01");
2446 let m = Metadata::default().with_user_consents(vec![c]);
2447 assert!(m.has_user_consents());
2448 }
2449
2450 #[test]
2451 fn metadata_with_user_consents_empty_is_false() {
2452 let m = Metadata::default().with_user_consents(vec![]);
2453 assert!(!m.has_user_consents());
2454 }
2455
2456 #[test]
2457 fn metadata_display() {
2458 assert_eq!(Metadata::default().to_string(), "metadata");
2459 }
2460
2461 #[test]
2464 fn referrer_address_variant() {
2465 let r = Referrer::address("0xb6BAd41ae76A11D10f7b0E664C5007b908bC77C9");
2466 assert_eq!(r.as_address(), Some("0xb6BAd41ae76A11D10f7b0E664C5007b908bC77C9"));
2467 assert_eq!(r.as_code(), None);
2468 }
2469
2470 #[test]
2471 fn referrer_code_variant() {
2472 let r = Referrer::code("COWRS-PARTNER");
2473 assert_eq!(r.as_code(), Some("COWRS-PARTNER"));
2474 assert_eq!(r.as_address(), None);
2475 }
2476
2477 #[test]
2478 #[allow(deprecated, reason = "testing deprecated Referrer::new alias")]
2479 fn referrer_new_deprecated_alias() {
2480 let r = Referrer::new("0xAddr");
2481 assert_eq!(r.as_address(), Some("0xAddr"));
2482 }
2483
2484 #[test]
2485 fn referrer_display_address() {
2486 let r = Referrer::address("0xABC");
2487 assert_eq!(r.to_string(), "referrer(address=0xABC)");
2488 }
2489
2490 #[test]
2491 fn referrer_display_code() {
2492 let r = Referrer::code("COWRS");
2493 assert_eq!(r.to_string(), "referrer(code=COWRS)");
2494 }
2495
2496 #[test]
2497 fn referrer_serde_address_roundtrip() {
2498 let r = Referrer::address("0xb6BAd41ae76A11D10f7b0E664C5007b908bC77C9");
2499 let json = serde_json::to_string(&r).unwrap();
2500 assert!(json.contains("\"address\""));
2501 let r2: Referrer = serde_json::from_str(&json).unwrap();
2502 assert_eq!(r2.as_address(), Some("0xb6BAd41ae76A11D10f7b0E664C5007b908bC77C9"));
2503 }
2504
2505 #[test]
2506 fn referrer_serde_code_roundtrip() {
2507 let r = Referrer::code("COWRS");
2508 let json = serde_json::to_string(&r).unwrap();
2509 assert!(json.contains("\"code\""));
2510 let r2: Referrer = serde_json::from_str(&json).unwrap();
2511 assert_eq!(r2.as_code(), Some("COWRS"));
2512 }
2513
2514 #[test]
2517 fn utm_new_all_none() {
2518 let utm = Utm::new();
2519 assert!(!utm.has_source());
2520 assert!(!utm.has_medium());
2521 assert!(!utm.has_campaign());
2522 assert!(!utm.has_content());
2523 assert!(!utm.has_term());
2524 }
2525
2526 #[test]
2527 fn utm_with_all_fields() {
2528 let utm = Utm::new()
2529 .with_source("google")
2530 .with_medium("cpc")
2531 .with_campaign("launch")
2532 .with_content("banner")
2533 .with_term("cow protocol");
2534 assert!(utm.has_source());
2535 assert!(utm.has_medium());
2536 assert!(utm.has_campaign());
2537 assert!(utm.has_content());
2538 assert!(utm.has_term());
2539 assert_eq!(utm.utm_source.as_deref(), Some("google"));
2540 assert_eq!(utm.utm_medium.as_deref(), Some("cpc"));
2541 assert_eq!(utm.utm_campaign.as_deref(), Some("launch"));
2542 assert_eq!(utm.utm_content.as_deref(), Some("banner"));
2543 assert_eq!(utm.utm_term.as_deref(), Some("cow protocol"));
2544 }
2545
2546 #[test]
2547 fn utm_display() {
2548 let utm = Utm::new().with_source("twitter");
2549 assert_eq!(utm.to_string(), "utm(source=twitter)");
2550
2551 let utm_none = Utm::new();
2552 assert_eq!(utm_none.to_string(), "utm(source=none)");
2553 }
2554
2555 #[test]
2556 fn utm_serde_roundtrip() {
2557 let utm = Utm::new().with_source("google").with_campaign("test");
2558 let json = serde_json::to_string(&utm).unwrap();
2559 let utm2: Utm = serde_json::from_str(&json).unwrap();
2560 assert_eq!(utm2.utm_source.as_deref(), Some("google"));
2561 assert_eq!(utm2.utm_campaign.as_deref(), Some("test"));
2562 assert!(utm2.utm_medium.is_none());
2563 }
2564
2565 #[test]
2568 fn quote_new() {
2569 let q = Quote::new(50);
2570 assert_eq!(q.slippage_bips, 50);
2571 assert_eq!(q.smart_slippage, None);
2572 }
2573
2574 #[test]
2575 fn quote_with_smart_slippage() {
2576 let q = Quote::new(100).with_smart_slippage();
2577 assert_eq!(q.slippage_bips, 100);
2578 assert_eq!(q.smart_slippage, Some(true));
2579 }
2580
2581 #[test]
2582 fn quote_display() {
2583 assert_eq!(Quote::new(50).to_string(), "quote(50bips)");
2584 }
2585
2586 #[test]
2587 fn quote_serde_roundtrip() {
2588 let q = Quote::new(75).with_smart_slippage();
2589 let json = serde_json::to_string(&q).unwrap();
2590 let q2: Quote = serde_json::from_str(&json).unwrap();
2591 assert_eq!(q2.slippage_bips, 75);
2592 assert_eq!(q2.smart_slippage, Some(true));
2593 }
2594
2595 #[test]
2598 fn order_class_kind_as_str() {
2599 assert_eq!(OrderClassKind::Market.as_str(), "market");
2600 assert_eq!(OrderClassKind::Limit.as_str(), "limit");
2601 assert_eq!(OrderClassKind::Liquidity.as_str(), "liquidity");
2602 assert_eq!(OrderClassKind::Twap.as_str(), "twap");
2603 }
2604
2605 #[test]
2606 fn order_class_kind_is_predicates() {
2607 assert!(OrderClassKind::Market.is_market());
2608 assert!(!OrderClassKind::Market.is_limit());
2609 assert!(!OrderClassKind::Market.is_liquidity());
2610 assert!(!OrderClassKind::Market.is_twap());
2611
2612 assert!(OrderClassKind::Limit.is_limit());
2613 assert!(OrderClassKind::Liquidity.is_liquidity());
2614 assert!(OrderClassKind::Twap.is_twap());
2615 }
2616
2617 #[test]
2618 fn order_class_kind_display() {
2619 assert_eq!(OrderClassKind::Twap.to_string(), "twap");
2620 }
2621
2622 #[test]
2623 fn order_class_kind_try_from_valid() {
2624 assert_eq!(OrderClassKind::try_from("market").unwrap(), OrderClassKind::Market);
2625 assert_eq!(OrderClassKind::try_from("limit").unwrap(), OrderClassKind::Limit);
2626 assert_eq!(OrderClassKind::try_from("liquidity").unwrap(), OrderClassKind::Liquidity);
2627 assert_eq!(OrderClassKind::try_from("twap").unwrap(), OrderClassKind::Twap);
2628 }
2629
2630 #[test]
2631 fn order_class_kind_try_from_invalid() {
2632 assert!(OrderClassKind::try_from("unknown").is_err());
2633 }
2634
2635 #[test]
2636 fn order_class_kind_serde_roundtrip() {
2637 let kind = OrderClassKind::Limit;
2638 let json = serde_json::to_string(&kind).unwrap();
2639 assert_eq!(json, "\"limit\"");
2640 let kind2: OrderClassKind = serde_json::from_str(&json).unwrap();
2641 assert_eq!(kind2, OrderClassKind::Limit);
2642 }
2643
2644 #[test]
2647 fn order_class_new() {
2648 let oc = OrderClass::new(OrderClassKind::Twap);
2649 assert_eq!(oc.order_class, OrderClassKind::Twap);
2650 }
2651
2652 #[test]
2653 fn order_class_display() {
2654 let oc = OrderClass::new(OrderClassKind::Market);
2655 assert_eq!(oc.to_string(), "market");
2656 }
2657
2658 #[test]
2661 fn hooks_new_empty_vecs_become_none() {
2662 let hooks = OrderInteractionHooks::new(vec![], vec![]);
2663 assert!(!hooks.has_pre());
2664 assert!(!hooks.has_post());
2665 assert!(hooks.pre.is_none());
2666 assert!(hooks.post.is_none());
2667 }
2668
2669 #[test]
2670 fn hooks_new_with_entries() {
2671 let pre = CowHook::new("0xTarget", "0xData", "50000");
2672 let post = CowHook::new("0xTarget2", "0xData2", "60000");
2673 let hooks = OrderInteractionHooks::new(vec![pre], vec![post]);
2674 assert!(hooks.has_pre());
2675 assert!(hooks.has_post());
2676 }
2677
2678 #[test]
2679 fn hooks_with_version() {
2680 let hooks = OrderInteractionHooks::new(vec![], vec![]).with_version("0.2.0");
2681 assert_eq!(hooks.version.as_deref(), Some("0.2.0"));
2682 }
2683
2684 #[test]
2687 fn cow_hook_new() {
2688 let hook = CowHook::new("0xTarget", "0xCallData", "100000");
2689 assert_eq!(hook.target, "0xTarget");
2690 assert_eq!(hook.call_data, "0xCallData");
2691 assert_eq!(hook.gas_limit, "100000");
2692 assert!(!hook.has_dapp_id());
2693 }
2694
2695 #[test]
2696 fn cow_hook_with_dapp_id() {
2697 let hook = CowHook::new("0xTarget", "0xData", "50000").with_dapp_id("my-dapp");
2698 assert!(hook.has_dapp_id());
2699 assert_eq!(hook.dapp_id.as_deref(), Some("my-dapp"));
2700 }
2701
2702 #[test]
2703 fn cow_hook_display() {
2704 let hook = CowHook::new("0xTarget", "0xData", "50000");
2705 assert_eq!(hook.to_string(), "hook(target=0xTarget, gas=50000)");
2706 }
2707
2708 #[test]
2711 fn widget_new() {
2712 let w = Widget::new("WidgetHost");
2713 assert_eq!(w.app_code, "WidgetHost");
2714 assert!(!w.has_environment());
2715 }
2716
2717 #[test]
2718 fn widget_with_environment() {
2719 let w = Widget::new("Host").with_environment("production");
2720 assert!(w.has_environment());
2721 assert_eq!(w.environment.as_deref(), Some("production"));
2722 }
2723
2724 #[test]
2725 fn widget_display() {
2726 assert_eq!(Widget::new("Host").to_string(), "widget(Host)");
2727 }
2728
2729 #[test]
2732 fn partner_fee_entry_volume() {
2733 let fee = PartnerFeeEntry::volume(50, "0xRecipient");
2734 assert_eq!(fee.volume_bps(), Some(50));
2735 assert_eq!(fee.surplus_bps(), None);
2736 assert_eq!(fee.price_improvement_bps(), None);
2737 assert_eq!(fee.max_volume_bps(), None);
2738 assert_eq!(fee.recipient, "0xRecipient");
2739 }
2740
2741 #[test]
2742 fn partner_fee_entry_surplus() {
2743 let fee = PartnerFeeEntry::surplus(30, 100, "0xRecipient");
2744 assert_eq!(fee.volume_bps(), None);
2745 assert_eq!(fee.surplus_bps(), Some(30));
2746 assert_eq!(fee.price_improvement_bps(), None);
2747 assert_eq!(fee.max_volume_bps(), Some(100));
2748 }
2749
2750 #[test]
2751 fn partner_fee_entry_price_improvement() {
2752 let fee = PartnerFeeEntry::price_improvement(20, 80, "0xRecipient");
2753 assert_eq!(fee.volume_bps(), None);
2754 assert_eq!(fee.surplus_bps(), None);
2755 assert_eq!(fee.price_improvement_bps(), Some(20));
2756 assert_eq!(fee.max_volume_bps(), Some(80));
2757 }
2758
2759 #[test]
2760 fn partner_fee_entry_display_volume() {
2761 let fee = PartnerFeeEntry::volume(50, "0xAddr");
2762 assert_eq!(fee.to_string(), "volume-fee(50bps, 0xAddr)");
2763 }
2764
2765 #[test]
2766 fn partner_fee_entry_display_surplus() {
2767 let fee = PartnerFeeEntry::surplus(30, 100, "0xAddr");
2768 assert_eq!(fee.to_string(), "surplus-fee(30bps, 0xAddr)");
2769 }
2770
2771 #[test]
2772 fn partner_fee_entry_display_price_improvement() {
2773 let fee = PartnerFeeEntry::price_improvement(20, 80, "0xAddr");
2774 assert_eq!(fee.to_string(), "price-improvement-fee(20bps, 0xAddr)");
2775 }
2776
2777 #[test]
2780 fn partner_fee_single() {
2781 let fee = PartnerFee::single(PartnerFeeEntry::volume(50, "0xAddr"));
2782 assert!(fee.is_single());
2783 assert!(!fee.is_multiple());
2784 assert_eq!(fee.count(), 1);
2785 }
2786
2787 #[test]
2788 fn partner_fee_multiple() {
2789 let fee = PartnerFee::Multiple(vec![
2790 PartnerFeeEntry::volume(50, "0x1"),
2791 PartnerFeeEntry::surplus(30, 100, "0x2"),
2792 ]);
2793 assert!(!fee.is_single());
2794 assert!(fee.is_multiple());
2795 assert_eq!(fee.count(), 2);
2796 }
2797
2798 #[test]
2799 fn partner_fee_entries_iterator() {
2800 let fee = PartnerFee::single(PartnerFeeEntry::volume(50, "0xAddr"));
2801 let entries: Vec<_> = fee.entries().collect();
2802 assert_eq!(entries.len(), 1);
2803 assert_eq!(entries[0].volume_bps(), Some(50));
2804
2805 let multi = PartnerFee::Multiple(vec![
2806 PartnerFeeEntry::volume(10, "0x1"),
2807 PartnerFeeEntry::surplus(20, 50, "0x2"),
2808 ]);
2809 let entries: Vec<_> = multi.entries().collect();
2810 assert_eq!(entries.len(), 2);
2811 }
2812
2813 #[test]
2814 fn partner_fee_display_single() {
2815 let fee = PartnerFee::single(PartnerFeeEntry::volume(50, "0xAddr"));
2816 assert_eq!(fee.to_string(), "volume-fee(50bps, 0xAddr)");
2817 }
2818
2819 #[test]
2820 fn partner_fee_display_multiple() {
2821 let fee = PartnerFee::Multiple(vec![
2822 PartnerFeeEntry::volume(10, "0x1"),
2823 PartnerFeeEntry::surplus(20, 50, "0x2"),
2824 ]);
2825 assert_eq!(fee.to_string(), "fees(2)");
2826 }
2827
2828 #[test]
2831 fn get_partner_fee_bps_some() {
2832 let fee = PartnerFee::single(PartnerFeeEntry::volume(50, "0xAddr"));
2833 assert_eq!(get_partner_fee_bps(Some(&fee)), Some(50));
2834 }
2835
2836 #[test]
2837 fn get_partner_fee_bps_none() {
2838 assert_eq!(get_partner_fee_bps(None), None);
2839 }
2840
2841 #[test]
2842 fn get_partner_fee_bps_no_volume() {
2843 let fee = PartnerFee::single(PartnerFeeEntry::surplus(30, 100, "0xAddr"));
2844 assert_eq!(get_partner_fee_bps(Some(&fee)), None);
2845 }
2846
2847 #[test]
2848 fn get_partner_fee_bps_multiple_finds_first_volume() {
2849 let fee = PartnerFee::Multiple(vec![
2850 PartnerFeeEntry::surplus(30, 100, "0x1"),
2851 PartnerFeeEntry::volume(50, "0x2"),
2852 ]);
2853 assert_eq!(get_partner_fee_bps(Some(&fee)), Some(50));
2854 }
2855
2856 #[test]
2859 fn replaced_order_new() {
2860 let uid = format!("0x{}", "ab".repeat(56));
2861 let ro = ReplacedOrder::new(&uid);
2862 assert_eq!(ro.uid, uid);
2863 assert_eq!(ro.uid.len(), 114);
2864 }
2865
2866 #[test]
2867 fn replaced_order_display() {
2868 let ro = ReplacedOrder::new("0xUID");
2869 assert_eq!(ro.to_string(), "replaced(0xUID)");
2870 }
2871
2872 #[test]
2875 fn bridging_new() {
2876 let b = Bridging::new("across", "42161", "0xToken");
2877 assert_eq!(b.provider, "across");
2878 assert_eq!(b.destination_chain_id, "42161");
2879 assert_eq!(b.destination_token_address, "0xToken");
2880 assert!(!b.has_quote_id());
2881 assert!(!b.has_quote_signature());
2882 assert!(!b.has_attestation_signature());
2883 assert!(!b.has_quote_body());
2884 }
2885
2886 #[test]
2887 fn bridging_with_all_optional_fields() {
2888 let b = Bridging::new("bungee", "10", "0xToken")
2889 .with_quote_id("q-123")
2890 .with_quote_signature("0xSig")
2891 .with_attestation_signature("0xAttest")
2892 .with_quote_body("body-data");
2893 assert!(b.has_quote_id());
2894 assert!(b.has_quote_signature());
2895 assert!(b.has_attestation_signature());
2896 assert!(b.has_quote_body());
2897 assert_eq!(b.quote_id.as_deref(), Some("q-123"));
2898 assert_eq!(b.quote_signature.as_deref(), Some("0xSig"));
2899 assert_eq!(b.attestation_signature.as_deref(), Some("0xAttest"));
2900 assert_eq!(b.quote_body.as_deref(), Some("body-data"));
2901 }
2902
2903 #[test]
2904 fn bridging_display() {
2905 let b = Bridging::new("across", "42161", "0xToken");
2906 assert_eq!(b.to_string(), "bridge(across, chain=42161)");
2907 }
2908
2909 #[test]
2912 fn flashloan_new() {
2913 let fl = Flashloan::new("1000000", "0xPool", "0xToken");
2914 assert_eq!(fl.loan_amount, "1000000");
2915 assert_eq!(fl.liquidity_provider_address, "0xPool");
2916 assert_eq!(fl.token_address, "0xToken");
2917 assert!(fl.protocol_adapter_address.is_empty());
2918 assert!(fl.receiver_address.is_empty());
2919 }
2920
2921 #[test]
2922 fn flashloan_with_builders() {
2923 let fl = Flashloan::new("1000", "0xPool", "0xToken")
2924 .with_protocol_adapter("0xAdapter")
2925 .with_receiver("0xReceiver");
2926 assert_eq!(fl.protocol_adapter_address, "0xAdapter");
2927 assert_eq!(fl.receiver_address, "0xReceiver");
2928 }
2929
2930 #[test]
2931 fn flashloan_display() {
2932 let fl = Flashloan::new("1000", "0xPool", "0xToken");
2933 assert_eq!(fl.to_string(), "flashloan(0xToken, amount=1000)");
2934 }
2935
2936 #[test]
2939 fn wrapper_entry_new() {
2940 let w = WrapperEntry::new("0xWrapper");
2941 assert_eq!(w.wrapper_address, "0xWrapper");
2942 assert!(!w.has_wrapper_data());
2943 assert!(!w.has_is_omittable());
2944 assert!(!w.is_omittable());
2945 }
2946
2947 #[test]
2948 fn wrapper_entry_with_wrapper_data() {
2949 let w = WrapperEntry::new("0xW").with_wrapper_data("0xABI");
2950 assert!(w.has_wrapper_data());
2951 assert_eq!(w.wrapper_data.as_deref(), Some("0xABI"));
2952 }
2953
2954 #[test]
2955 fn wrapper_entry_with_is_omittable_true() {
2956 let w = WrapperEntry::new("0xW").with_is_omittable(true);
2957 assert!(w.has_is_omittable());
2958 assert!(w.is_omittable());
2959 }
2960
2961 #[test]
2962 fn wrapper_entry_with_is_omittable_false() {
2963 let w = WrapperEntry::new("0xW").with_is_omittable(false);
2964 assert!(w.has_is_omittable());
2965 assert!(!w.is_omittable());
2966 }
2967
2968 #[test]
2969 fn wrapper_entry_display() {
2970 assert_eq!(WrapperEntry::new("0xW").to_string(), "wrapper(0xW)");
2971 }
2972
2973 #[test]
2976 fn user_consent_new() {
2977 let c = UserConsent::new("https://cow.fi/tos", "2025-04-07");
2978 assert_eq!(c.terms, "https://cow.fi/tos");
2979 assert_eq!(c.accepted_date, "2025-04-07");
2980 }
2981
2982 #[test]
2983 fn user_consent_display() {
2984 let c = UserConsent::new("tos", "2025-01-01");
2985 assert_eq!(c.to_string(), "consent(tos, 2025-01-01)");
2986 }
2987
2988 #[test]
2991 fn app_data_doc_serde_roundtrip() {
2992 let doc = AppDataDoc::new("TestApp")
2993 .with_environment("production")
2994 .with_referrer(Referrer::code("COWRS"))
2995 .with_order_class(OrderClassKind::Limit);
2996 let json = serde_json::to_string(&doc).unwrap();
2997 let doc2: AppDataDoc = serde_json::from_str(&json).unwrap();
2998 assert_eq!(doc2.version, LATEST_APP_DATA_VERSION);
2999 assert_eq!(doc2.app_code.as_deref(), Some("TestApp"));
3000 assert_eq!(doc2.environment.as_deref(), Some("production"));
3001 assert!(doc2.metadata.has_referrer());
3002 assert!(doc2.metadata.has_order_class());
3003 }
3004
3005 #[test]
3006 fn app_data_doc_serde_camel_case() {
3007 let doc = AppDataDoc::new("App").with_environment("prod");
3008 let json = serde_json::to_string(&doc).unwrap();
3009 assert!(json.contains("\"appCode\""));
3010 assert!(!json.contains("\"app_code\""));
3011 }
3012
3013 #[test]
3014 fn app_data_doc_serde_skip_none_fields() {
3015 let doc = AppDataDoc::new("App");
3016 let json = serde_json::to_string(&doc).unwrap();
3017 assert!(!json.contains("\"environment\""));
3018 assert!(!json.contains("\"referrer\""));
3019 }
3020
3021 #[test]
3022 fn bridging_serde_roundtrip() {
3023 let b = Bridging::new("across", "42161", "0xToken").with_quote_id("q-1");
3024 let json = serde_json::to_string(&b).unwrap();
3025 let b2: Bridging = serde_json::from_str(&json).unwrap();
3026 assert_eq!(b2.provider, "across");
3027 assert_eq!(b2.quote_id.as_deref(), Some("q-1"));
3028 }
3029
3030 #[test]
3031 fn flashloan_serde_roundtrip() {
3032 let fl = Flashloan::new("999", "0xPool", "0xToken")
3033 .with_protocol_adapter("0xA")
3034 .with_receiver("0xR");
3035 let json = serde_json::to_string(&fl).unwrap();
3036 let fl2: Flashloan = serde_json::from_str(&json).unwrap();
3037 assert_eq!(fl2.loan_amount, "999");
3038 assert_eq!(fl2.protocol_adapter_address, "0xA");
3039 assert_eq!(fl2.receiver_address, "0xR");
3040 }
3041
3042 #[test]
3043 fn partner_fee_serde_single_roundtrip() {
3044 let fee = PartnerFee::single(PartnerFeeEntry::volume(50, "0xAddr"));
3045 let json = serde_json::to_string(&fee).unwrap();
3046 let fee2: PartnerFee = serde_json::from_str(&json).unwrap();
3047 assert!(fee2.is_single());
3048 assert_eq!(fee2.entries().next().unwrap().volume_bps(), Some(50));
3049 }
3050
3051 #[test]
3052 fn partner_fee_serde_multiple_roundtrip() {
3053 let fee = PartnerFee::Multiple(vec![
3054 PartnerFeeEntry::volume(10, "0x1"),
3055 PartnerFeeEntry::surplus(20, 50, "0x2"),
3056 ]);
3057 let json = serde_json::to_string(&fee).unwrap();
3058 let fee2: PartnerFee = serde_json::from_str(&json).unwrap();
3059 assert!(fee2.is_multiple());
3060 assert_eq!(fee2.count(), 2);
3061 }
3062
3063 #[test]
3064 fn cow_hook_serde_roundtrip() {
3065 let hook = CowHook::new("0xTarget", "0xData", "100000").with_dapp_id("my-dapp");
3066 let json = serde_json::to_string(&hook).unwrap();
3067 let hook2: CowHook = serde_json::from_str(&json).unwrap();
3068 assert_eq!(hook2.target, "0xTarget");
3069 assert_eq!(hook2.call_data, "0xData");
3070 assert_eq!(hook2.gas_limit, "100000");
3071 assert_eq!(hook2.dapp_id.as_deref(), Some("my-dapp"));
3072 }
3073
3074 #[test]
3075 fn wrapper_entry_serde_roundtrip() {
3076 let w = WrapperEntry::new("0xW").with_wrapper_data("0xABI").with_is_omittable(true);
3077 let json = serde_json::to_string(&w).unwrap();
3078 let w2: WrapperEntry = serde_json::from_str(&json).unwrap();
3079 assert_eq!(w2.wrapper_address, "0xW");
3080 assert_eq!(w2.wrapper_data.as_deref(), Some("0xABI"));
3081 assert!(w2.is_omittable());
3082 }
3083
3084 #[test]
3085 fn user_consent_serde_roundtrip() {
3086 let c = UserConsent::new("tos-url", "2025-04-07");
3087 let json = serde_json::to_string(&c).unwrap();
3088 let c2: UserConsent = serde_json::from_str(&json).unwrap();
3089 assert_eq!(c2.terms, "tos-url");
3090 assert_eq!(c2.accepted_date, "2025-04-07");
3091 }
3092
3093 #[test]
3094 fn order_interaction_hooks_serde_roundtrip() {
3095 let pre = CowHook::new("0xA", "0xB", "1000");
3096 let hooks = OrderInteractionHooks::new(vec![pre], vec![]).with_version("0.2.0");
3097 let json = serde_json::to_string(&hooks).unwrap();
3098 let hooks2: OrderInteractionHooks = serde_json::from_str(&json).unwrap();
3099 assert!(hooks2.has_pre());
3100 assert!(!hooks2.has_post());
3101 assert_eq!(hooks2.version.as_deref(), Some("0.2.0"));
3102 }
3103
3104 #[test]
3107 fn app_data_doc_full_builder_chain() {
3108 let doc = AppDataDoc::new("FullApp")
3109 .with_environment("production")
3110 .with_referrer(Referrer::code("PARTNER"))
3111 .with_utm(Utm::new().with_source("test"))
3112 .with_hooks(OrderInteractionHooks::new(
3113 vec![CowHook::new("0xT", "0xD", "1000")],
3114 vec![],
3115 ))
3116 .with_partner_fee(PartnerFee::single(PartnerFeeEntry::volume(50, "0xFee")))
3117 .with_replaced_order("0xUID")
3118 .with_signer("0xSigner")
3119 .with_order_class(OrderClassKind::Market)
3120 .with_bridging(Bridging::new("across", "42161", "0xToken"))
3121 .with_flashloan(Flashloan::new("1000", "0xPool", "0xToken"))
3122 .with_wrappers(vec![WrapperEntry::new("0xW")])
3123 .with_user_consents(vec![UserConsent::new("tos", "2025-01-01")]);
3124
3125 assert_eq!(doc.app_code.as_deref(), Some("FullApp"));
3126 assert_eq!(doc.environment.as_deref(), Some("production"));
3127
3128 let m = &doc.metadata;
3129 assert!(m.has_referrer());
3130 assert!(m.has_utm());
3131 assert!(m.has_hooks());
3132 assert!(m.has_partner_fee());
3133 assert!(m.has_replaced_order());
3134 assert!(m.has_signer());
3135 assert!(m.has_order_class());
3136 assert!(m.has_bridging());
3137 assert!(m.has_flashloan());
3138 assert!(m.has_wrappers());
3139 assert!(m.has_user_consents());
3140
3141 let json = serde_json::to_string(&doc).unwrap();
3143 let doc2: AppDataDoc = serde_json::from_str(&json).unwrap();
3144 assert_eq!(doc2.version, LATEST_APP_DATA_VERSION);
3145 assert!(doc2.metadata.has_referrer());
3146 assert!(doc2.metadata.has_flashloan());
3147 assert!(doc2.metadata.has_user_consents());
3148 }
3149}