1use std::fmt;
23
24use serde::{Deserialize, Serialize};
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct Totals {
32 pub tokens: String,
34 pub orders: String,
36 pub traders: String,
38 pub settlements: String,
40 pub volume_usd: String,
42 pub volume_eth: String,
44 pub fees_usd: String,
46 pub fees_eth: String,
48}
49
50impl fmt::Display for Totals {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 write!(f, "totals(orders={}, traders={})", self.orders, self.traders)
53 }
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(rename_all = "camelCase")]
59pub struct DailyVolume {
60 pub timestamp: String,
62 pub volume_usd: String,
64}
65
66impl fmt::Display for DailyVolume {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 write!(f, "daily-vol(ts={}, ${})", self.timestamp, self.volume_usd)
69 }
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct HourlyVolume {
76 pub timestamp: String,
78 pub volume_usd: String,
80}
81
82impl fmt::Display for HourlyVolume {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 write!(f, "hourly-vol(ts={}, ${})", self.timestamp, self.volume_usd)
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub struct DailyTotal {
94 pub timestamp: String,
96 pub orders: String,
98 pub traders: String,
100 pub tokens: String,
102 pub settlements: String,
104 pub volume_eth: String,
106 pub volume_usd: String,
108 pub fees_eth: String,
110 pub fees_usd: String,
112}
113
114impl fmt::Display for DailyTotal {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 write!(f, "daily-total(ts={}, orders={})", self.timestamp, self.orders)
117 }
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct HourlyTotal {
124 pub timestamp: String,
126 pub orders: String,
128 pub traders: String,
130 pub tokens: String,
132 pub settlements: String,
134 pub volume_eth: String,
136 pub volume_usd: String,
138 pub fees_eth: String,
140 pub fees_usd: String,
142}
143
144impl fmt::Display for HourlyTotal {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 write!(f, "hourly-total(ts={}, orders={})", self.timestamp, self.orders)
147 }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
154#[serde(rename_all = "camelCase")]
155pub struct Token {
156 pub id: String,
158 pub address: String,
160 pub first_trade_timestamp: String,
162 pub name: String,
164 pub symbol: String,
166 pub decimals: String,
168 pub total_volume: String,
170 pub price_eth: String,
172 pub price_usd: String,
174 pub number_of_trades: String,
176}
177
178impl Token {
179 #[must_use]
185 pub fn symbol_ref(&self) -> &str {
186 &self.symbol
187 }
188
189 #[must_use]
195 pub fn address_ref(&self) -> &str {
196 &self.address
197 }
198}
199
200impl fmt::Display for Token {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 write!(f, "{} ({})", self.symbol, self.address)
203 }
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208#[serde(rename_all = "camelCase")]
209pub struct TokenDailyTotal {
210 pub id: String,
212 pub token: Token,
214 pub timestamp: String,
216 pub total_volume: String,
218 pub total_volume_usd: String,
220 pub total_trades: String,
222 pub open_price: String,
224 pub close_price: String,
226 pub higher_price: String,
228 pub lower_price: String,
230 pub average_price: String,
232}
233
234impl fmt::Display for TokenDailyTotal {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 write!(f, "token-daily({}, ts={})", self.token, self.timestamp)
237 }
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
242#[serde(rename_all = "camelCase")]
243pub struct TokenHourlyTotal {
244 pub id: String,
246 pub token: Token,
248 pub timestamp: String,
250 pub total_volume: String,
252 pub total_volume_usd: String,
254 pub total_trades: String,
256 pub open_price: String,
258 pub close_price: String,
260 pub higher_price: String,
262 pub lower_price: String,
264 pub average_price: String,
266}
267
268impl fmt::Display for TokenHourlyTotal {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 write!(f, "token-hourly({}, ts={})", self.token, self.timestamp)
271 }
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
276#[serde(rename_all = "camelCase")]
277pub struct TokenTradingEvent {
278 pub id: String,
280 pub token: Token,
282 pub price_usd: String,
284 pub timestamp: String,
286}
287
288impl fmt::Display for TokenTradingEvent {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 write!(f, "trade-event({}, ts={}, price=${})", self.token, self.timestamp, self.price_usd)
291 }
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
298#[serde(rename_all = "camelCase")]
299pub struct User {
300 pub id: String,
302 pub address: String,
304 pub first_trade_timestamp: String,
306 pub number_of_trades: String,
308 pub solved_amount_eth: String,
310 pub solved_amount_usd: String,
312}
313
314impl fmt::Display for User {
315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316 f.write_str(&self.address)
317 }
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
324#[serde(rename_all = "camelCase")]
325pub struct Settlement {
326 pub id: String,
328 pub tx_hash: String,
330 pub first_trade_timestamp: String,
332 pub solver: String,
334 #[serde(skip_serializing_if = "Option::is_none")]
336 pub tx_cost: Option<String>,
337 #[serde(skip_serializing_if = "Option::is_none")]
339 pub tx_fee_in_eth: Option<String>,
340}
341
342impl Settlement {
343 #[must_use]
349 pub const fn has_gas_cost(&self) -> bool {
350 self.tx_cost.is_some()
351 }
352
353 #[must_use]
359 pub const fn has_tx_fee(&self) -> bool {
360 self.tx_fee_in_eth.is_some()
361 }
362}
363
364impl fmt::Display for Settlement {
365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366 write!(f, "settlement({})", self.tx_hash)
367 }
368}
369
370#[derive(Debug, Clone, Serialize, Deserialize)]
374#[serde(rename_all = "camelCase")]
375pub struct Trade {
376 pub id: String,
378 pub timestamp: String,
380 pub gas_price: String,
382 pub fee_amount: String,
384 pub tx_hash: String,
386 pub settlement: String,
388 pub buy_amount: String,
390 pub sell_amount: String,
392 pub sell_amount_before_fees: String,
394 pub buy_token: Token,
396 pub sell_token: Token,
398 pub owner: User,
400 pub order: String,
402}
403
404impl Trade {
405 #[must_use]
411 pub const fn has_tx_hash(&self) -> bool {
412 !self.tx_hash.is_empty()
413 }
414}
415
416impl fmt::Display for Trade {
417 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418 write!(f, "trade({} {} → {})", self.tx_hash, self.sell_token, self.buy_token)
419 }
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize)]
426#[serde(rename_all = "camelCase")]
427pub struct Order {
428 pub id: String,
430 pub owner: User,
432 pub sell_token: Token,
434 pub buy_token: Token,
436 #[serde(skip_serializing_if = "Option::is_none")]
438 pub receiver: Option<String>,
439 pub sell_amount: String,
441 pub buy_amount: String,
443 pub valid_to: String,
445 pub app_data: String,
447 pub fee_amount: String,
449 pub kind: String,
451 pub partially_fillable: bool,
453 pub status: String,
455 pub executed_sell_amount: String,
457 pub executed_sell_amount_before_fees: String,
459 pub executed_buy_amount: String,
461 pub executed_fee_amount: String,
463 #[serde(skip_serializing_if = "Option::is_none")]
465 pub invalidate_timestamp: Option<String>,
466 pub timestamp: String,
468 pub tx_hash: String,
470 pub is_signer_safe: bool,
472 pub signing_scheme: String,
474 pub uid: String,
476 #[serde(skip_serializing_if = "Option::is_none")]
478 pub surplus: Option<String>,
479}
480
481impl Order {
482 #[must_use]
488 pub fn is_sell(&self) -> bool {
489 self.kind == "sell"
490 }
491
492 #[must_use]
498 pub fn is_buy(&self) -> bool {
499 self.kind == "buy"
500 }
501
502 #[must_use]
508 pub fn is_open(&self) -> bool {
509 self.status == "open"
510 }
511
512 #[must_use]
518 pub fn is_filled(&self) -> bool {
519 self.status == "filled"
520 }
521
522 #[must_use]
528 pub fn is_cancelled(&self) -> bool {
529 self.status == "cancelled"
530 }
531
532 #[must_use]
538 pub fn is_expired(&self) -> bool {
539 self.status == "expired"
540 }
541
542 #[must_use]
548 pub fn is_terminal(&self) -> bool {
549 self.is_filled() || self.is_cancelled() || self.is_expired()
550 }
551
552 #[must_use]
558 pub const fn has_receiver(&self) -> bool {
559 self.receiver.is_some()
560 }
561
562 #[must_use]
568 pub const fn has_invalidate_timestamp(&self) -> bool {
569 self.invalidate_timestamp.is_some()
570 }
571
572 #[must_use]
578 pub const fn has_surplus(&self) -> bool {
579 self.surplus.is_some()
580 }
581
582 #[must_use]
588 pub const fn is_partially_fillable(&self) -> bool {
589 self.partially_fillable
590 }
591
592 #[must_use]
598 pub const fn is_signer_safe(&self) -> bool {
599 self.is_signer_safe
600 }
601}
602
603impl fmt::Display for Order {
604 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
605 let short_uid = if self.uid.len() > 10 { &self.uid[..10] } else { &self.uid };
606 write!(f, "order({short_uid}… {} {})", self.kind, self.status)
607 }
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize)]
614#[serde(rename_all = "camelCase")]
615pub struct Pair {
616 pub id: String,
618 pub token0: Token,
620 pub token1: Token,
622 pub volume_token0: String,
624 pub volume_token1: String,
626 pub number_of_trades: String,
628}
629
630impl fmt::Display for Pair {
631 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
632 write!(f, "pair({}/{})", self.token0, self.token1)
633 }
634}
635
636#[derive(Debug, Clone, Serialize, Deserialize)]
638#[serde(rename_all = "camelCase")]
639pub struct PairDaily {
640 pub id: String,
642 pub token0: Token,
644 pub token1: Token,
646 pub timestamp: String,
648 pub volume_token0: String,
650 pub volume_token1: String,
652 pub number_of_trades: String,
654}
655
656impl fmt::Display for PairDaily {
657 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
658 write!(f, "pair-daily({}/{}, ts={})", self.token0, self.token1, self.timestamp)
659 }
660}
661
662#[derive(Debug, Clone, Serialize, Deserialize)]
664#[serde(rename_all = "camelCase")]
665pub struct PairHourly {
666 pub id: String,
668 pub token0: Token,
670 pub token1: Token,
672 pub timestamp: String,
674 pub volume_token0: String,
676 pub volume_token1: String,
678 pub number_of_trades: String,
680}
681
682impl fmt::Display for PairHourly {
683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684 write!(f, "pair-hourly({}/{}, ts={})", self.token0, self.token1, self.timestamp)
685 }
686}
687
688#[derive(Debug, Clone, Serialize, Deserialize)]
694#[serde(rename_all = "camelCase")]
695pub struct Bundle {
696 pub id: String,
698 pub eth_price_usd: String,
700}
701
702impl Bundle {
703 #[must_use]
709 pub fn eth_price_usd_ref(&self) -> &str {
710 &self.eth_price_usd
711 }
712}
713
714impl fmt::Display for Bundle {
715 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
716 write!(f, "eth-price=${}", self.eth_price_usd)
717 }
718}
719
720#[derive(Debug, Clone, Serialize, Deserialize)]
728#[serde(rename_all = "camelCase")]
729pub struct Total {
730 pub id: String,
732 pub orders: String,
734 pub settlements: String,
736 pub tokens: String,
738 pub traders: String,
740 pub number_of_trades: String,
742 #[serde(skip_serializing_if = "Option::is_none")]
744 pub volume_eth: Option<String>,
745 #[serde(skip_serializing_if = "Option::is_none")]
747 pub volume_usd: Option<String>,
748 #[serde(skip_serializing_if = "Option::is_none")]
750 pub fees_eth: Option<String>,
751 #[serde(skip_serializing_if = "Option::is_none")]
753 pub fees_usd: Option<String>,
754}
755
756impl fmt::Display for Total {
757 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
758 write!(f, "total(orders={}, traders={})", self.orders, self.traders)
759 }
760}
761
762#[derive(Debug, Clone, Serialize, Deserialize)]
768#[serde(rename_all = "camelCase")]
769pub struct UniswapToken {
770 pub id: String,
772 pub address: String,
774 pub name: String,
776 pub symbol: String,
778 pub decimals: i32,
780 #[serde(skip_serializing_if = "Option::is_none")]
782 pub price_eth: Option<String>,
783 #[serde(skip_serializing_if = "Option::is_none")]
785 pub price_usd: Option<String>,
786}
787
788impl fmt::Display for UniswapToken {
789 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
790 write!(f, "{} ({})", self.symbol, self.address)
791 }
792}
793
794#[derive(Debug, Clone, Serialize, Deserialize)]
800#[serde(rename_all = "camelCase")]
801pub struct UniswapPool {
802 pub id: String,
804 pub liquidity: String,
806 #[serde(skip_serializing_if = "Option::is_none")]
808 pub tick: Option<String>,
809 pub token0: UniswapToken,
811 pub token0_price: String,
813 pub token1: UniswapToken,
815 pub token1_price: String,
817 pub total_value_locked_token0: String,
819 pub total_value_locked_token1: String,
821}
822
823impl fmt::Display for UniswapPool {
824 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
825 write!(f, "pool({}, {}/{})", self.id, self.token0, self.token1)
826 }
827}
828
829#[derive(Debug, Clone, Serialize, Deserialize)]
835#[serde(rename_all = "camelCase")]
836pub struct SubgraphBlock {
837 #[serde(skip_serializing_if = "Option::is_none")]
839 pub hash: Option<String>,
840 pub number: i64,
842 #[serde(skip_serializing_if = "Option::is_none")]
844 pub parent_hash: Option<String>,
845 #[serde(skip_serializing_if = "Option::is_none")]
847 pub timestamp: Option<i64>,
848}
849
850impl fmt::Display for SubgraphBlock {
851 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
852 write!(f, "block(#{})", self.number)
853 }
854}
855
856#[derive(Debug, Clone, Serialize, Deserialize)]
860#[serde(rename_all = "camelCase")]
861pub struct SubgraphMeta {
862 pub block: SubgraphBlock,
864 pub deployment: String,
866 pub has_indexing_errors: bool,
868}
869
870impl fmt::Display for SubgraphMeta {
871 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
872 write!(f, "meta(deploy={}, block={})", self.deployment, self.block)
873 }
874}
875
876#[cfg(test)]
877mod tests {
878 use super::*;
879
880 fn sample_token() -> Token {
883 Token {
884 id: "0xabc".into(),
885 address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".into(),
886 first_trade_timestamp: "1700000000".into(),
887 name: "USD Coin".into(),
888 symbol: "USDC".into(),
889 decimals: "6".into(),
890 total_volume: "1000000".into(),
891 price_eth: "0.0003".into(),
892 price_usd: "1.0".into(),
893 number_of_trades: "42".into(),
894 }
895 }
896
897 fn sample_user() -> User {
898 User {
899 id: "0xuser".into(),
900 address: "0xUserAddress".into(),
901 first_trade_timestamp: "1700000000".into(),
902 number_of_trades: "10".into(),
903 solved_amount_eth: "5.0".into(),
904 solved_amount_usd: "10000".into(),
905 }
906 }
907
908 #[test]
911 fn totals_display() {
912 let t = Totals {
913 tokens: "100".into(),
914 orders: "500".into(),
915 traders: "50".into(),
916 settlements: "20".into(),
917 volume_usd: "1000000".into(),
918 volume_eth: "500".into(),
919 fees_usd: "1000".into(),
920 fees_eth: "0.5".into(),
921 };
922 assert_eq!(t.to_string(), "totals(orders=500, traders=50)");
923 }
924
925 #[test]
926 fn daily_volume_display() {
927 let d = DailyVolume { timestamp: "1700000000".into(), volume_usd: "500000".into() };
928 assert_eq!(d.to_string(), "daily-vol(ts=1700000000, $500000)");
929 }
930
931 #[test]
932 fn hourly_volume_display() {
933 let h = HourlyVolume { timestamp: "1700000000".into(), volume_usd: "10000".into() };
934 assert_eq!(h.to_string(), "hourly-vol(ts=1700000000, $10000)");
935 }
936
937 #[test]
938 fn daily_total_display() {
939 let d = DailyTotal {
940 timestamp: "1700000000".into(),
941 orders: "100".into(),
942 traders: "10".into(),
943 tokens: "5".into(),
944 settlements: "3".into(),
945 volume_eth: "50".into(),
946 volume_usd: "100000".into(),
947 fees_eth: "0.1".into(),
948 fees_usd: "200".into(),
949 };
950 assert_eq!(d.to_string(), "daily-total(ts=1700000000, orders=100)");
951 }
952
953 #[test]
954 fn hourly_total_display() {
955 let h = HourlyTotal {
956 timestamp: "1700000000".into(),
957 orders: "10".into(),
958 traders: "5".into(),
959 tokens: "3".into(),
960 settlements: "1".into(),
961 volume_eth: "10".into(),
962 volume_usd: "20000".into(),
963 fees_eth: "0.01".into(),
964 fees_usd: "20".into(),
965 };
966 assert_eq!(h.to_string(), "hourly-total(ts=1700000000, orders=10)");
967 }
968
969 #[test]
970 fn token_display() {
971 let t = sample_token();
972 assert_eq!(t.to_string(), "USDC (0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48)");
973 }
974
975 #[test]
976 fn token_accessors() {
977 let t = sample_token();
978 assert_eq!(t.symbol_ref(), "USDC");
979 assert_eq!(t.address_ref(), "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
980 }
981
982 #[test]
983 fn user_display() {
984 let u = sample_user();
985 assert_eq!(u.to_string(), "0xUserAddress");
986 }
987
988 #[test]
989 fn settlement_display_and_methods() {
990 let s = Settlement {
991 id: "0xtx".into(),
992 tx_hash: "0xdeadbeef".into(),
993 first_trade_timestamp: "1700000000".into(),
994 solver: "0xsolver".into(),
995 tx_cost: Some("1000".into()),
996 tx_fee_in_eth: None,
997 };
998 assert_eq!(s.to_string(), "settlement(0xdeadbeef)");
999 assert!(s.has_gas_cost());
1000 assert!(!s.has_tx_fee());
1001
1002 let s2 = Settlement {
1003 id: "0xtx".into(),
1004 tx_hash: "0xdeadbeef".into(),
1005 first_trade_timestamp: "1700000000".into(),
1006 solver: "0xsolver".into(),
1007 tx_cost: None,
1008 tx_fee_in_eth: Some("0.01".into()),
1009 };
1010 assert!(!s2.has_gas_cost());
1011 assert!(s2.has_tx_fee());
1012 }
1013
1014 #[test]
1015 fn trade_display_and_has_tx_hash() {
1016 let t = Trade {
1017 id: "0x-0".into(),
1018 timestamp: "1700000000".into(),
1019 gas_price: "20".into(),
1020 fee_amount: "100".into(),
1021 tx_hash: "0xabc".into(),
1022 settlement: "0xsettle".into(),
1023 buy_amount: "500".into(),
1024 sell_amount: "1000".into(),
1025 sell_amount_before_fees: "1100".into(),
1026 buy_token: sample_token(),
1027 sell_token: sample_token(),
1028 owner: sample_user(),
1029 order: "0xorder".into(),
1030 };
1031 assert!(t.has_tx_hash());
1032 assert!(t.to_string().contains("0xabc"));
1033 }
1034
1035 #[test]
1036 fn order_methods() {
1037 let o = Order {
1038 id: "0xorderuid1234567890".into(),
1039 owner: sample_user(),
1040 sell_token: sample_token(),
1041 buy_token: sample_token(),
1042 receiver: Some("0xreceiver".into()),
1043 sell_amount: "1000".into(),
1044 buy_amount: "500".into(),
1045 valid_to: "1700000000".into(),
1046 app_data: "0xappdata".into(),
1047 fee_amount: "10".into(),
1048 kind: "sell".into(),
1049 partially_fillable: true,
1050 status: "open".into(),
1051 executed_sell_amount: "0".into(),
1052 executed_sell_amount_before_fees: "0".into(),
1053 executed_buy_amount: "0".into(),
1054 executed_fee_amount: "0".into(),
1055 invalidate_timestamp: None,
1056 timestamp: "1700000000".into(),
1057 tx_hash: "0xtx".into(),
1058 is_signer_safe: false,
1059 signing_scheme: "eip712".into(),
1060 uid: "0xorderuid1234567890abcdef".into(),
1061 surplus: Some("50".into()),
1062 };
1063 assert!(o.is_sell());
1064 assert!(!o.is_buy());
1065 assert!(o.is_open());
1066 assert!(!o.is_filled());
1067 assert!(!o.is_cancelled());
1068 assert!(!o.is_expired());
1069 assert!(!o.is_terminal());
1070 assert!(o.has_receiver());
1071 assert!(!o.has_invalidate_timestamp());
1072 assert!(o.has_surplus());
1073 assert!(o.is_partially_fillable());
1074 assert!(!o.is_signer_safe());
1075 assert!(o.to_string().contains("sell"));
1076 assert!(o.to_string().contains("open"));
1077 }
1078
1079 #[test]
1080 fn order_terminal_states() {
1081 let make = |status: &str| Order {
1082 id: "x".into(),
1083 owner: sample_user(),
1084 sell_token: sample_token(),
1085 buy_token: sample_token(),
1086 receiver: None,
1087 sell_amount: "0".into(),
1088 buy_amount: "0".into(),
1089 valid_to: "0".into(),
1090 app_data: "0x".into(),
1091 fee_amount: "0".into(),
1092 kind: "buy".into(),
1093 partially_fillable: false,
1094 status: status.into(),
1095 executed_sell_amount: "0".into(),
1096 executed_sell_amount_before_fees: "0".into(),
1097 executed_buy_amount: "0".into(),
1098 executed_fee_amount: "0".into(),
1099 invalidate_timestamp: Some("100".into()),
1100 timestamp: "0".into(),
1101 tx_hash: String::new(),
1102 is_signer_safe: true,
1103 signing_scheme: "eip1271".into(),
1104 uid: "short".into(),
1105 surplus: None,
1106 };
1107 assert!(make("filled").is_filled());
1108 assert!(make("filled").is_terminal());
1109 assert!(make("cancelled").is_cancelled());
1110 assert!(make("cancelled").is_terminal());
1111 assert!(make("expired").is_expired());
1112 assert!(make("expired").is_terminal());
1113
1114 let buy_order = make("open");
1115 assert!(buy_order.is_buy());
1116 assert!(buy_order.has_invalidate_timestamp());
1117 assert!(!buy_order.has_surplus());
1118 assert!(!buy_order.is_partially_fillable());
1119 assert!(buy_order.is_signer_safe());
1120 }
1121
1122 #[test]
1123 fn bundle_display_and_accessor() {
1124 let b = Bundle { id: "1".into(), eth_price_usd: "3500.00".into() };
1125 assert_eq!(b.to_string(), "eth-price=$3500.00");
1126 assert_eq!(b.eth_price_usd_ref(), "3500.00");
1127 }
1128
1129 #[test]
1130 fn total_display() {
1131 let t = Total {
1132 id: "1".into(),
1133 orders: "100".into(),
1134 settlements: "10".into(),
1135 tokens: "20".into(),
1136 traders: "30".into(),
1137 number_of_trades: "200".into(),
1138 volume_eth: None,
1139 volume_usd: None,
1140 fees_eth: None,
1141 fees_usd: None,
1142 };
1143 assert_eq!(t.to_string(), "total(orders=100, traders=30)");
1144 }
1145
1146 #[test]
1147 fn subgraph_block_display() {
1148 let b = SubgraphBlock {
1149 hash: Some("0xabc".into()),
1150 number: 12345,
1151 parent_hash: None,
1152 timestamp: Some(1700000000),
1153 };
1154 assert_eq!(b.to_string(), "block(#12345)");
1155 }
1156
1157 #[test]
1158 fn subgraph_meta_display() {
1159 let m = SubgraphMeta {
1160 block: SubgraphBlock { hash: None, number: 999, parent_hash: None, timestamp: None },
1161 deployment: "deploy-123".into(),
1162 has_indexing_errors: false,
1163 };
1164 assert_eq!(m.to_string(), "meta(deploy=deploy-123, block=block(#999))");
1165 }
1166
1167 #[test]
1168 fn uniswap_token_display() {
1169 let t = UniswapToken {
1170 id: "0xabc".into(),
1171 address: "0xABC".into(),
1172 name: "Token".into(),
1173 symbol: "TKN".into(),
1174 decimals: 18,
1175 price_eth: None,
1176 price_usd: None,
1177 };
1178 assert_eq!(t.to_string(), "TKN (0xABC)");
1179 }
1180
1181 #[test]
1182 fn uniswap_pool_display() {
1183 let t0 = UniswapToken {
1184 id: "0xa".into(),
1185 address: "0xA".into(),
1186 name: "A".into(),
1187 symbol: "A".into(),
1188 decimals: 18,
1189 price_eth: None,
1190 price_usd: None,
1191 };
1192 let t1 = UniswapToken {
1193 id: "0xb".into(),
1194 address: "0xB".into(),
1195 name: "B".into(),
1196 symbol: "B".into(),
1197 decimals: 18,
1198 price_eth: None,
1199 price_usd: None,
1200 };
1201 let p = UniswapPool {
1202 id: "0xpool".into(),
1203 liquidity: "1000".into(),
1204 tick: Some("100".into()),
1205 token0: t0,
1206 token0_price: "1.0".into(),
1207 token1: t1,
1208 token1_price: "1.0".into(),
1209 total_value_locked_token0: "500".into(),
1210 total_value_locked_token1: "500".into(),
1211 };
1212 assert_eq!(p.to_string(), "pool(0xpool, A (0xA)/B (0xB))");
1213 }
1214
1215 #[test]
1216 fn pair_display() {
1217 let p = Pair {
1218 id: "0xa-0xb".into(),
1219 token0: sample_token(),
1220 token1: sample_token(),
1221 volume_token0: "100".into(),
1222 volume_token1: "200".into(),
1223 number_of_trades: "5".into(),
1224 };
1225 assert!(p.to_string().starts_with("pair("));
1226 }
1227
1228 #[test]
1229 fn pair_daily_display() {
1230 let pd = PairDaily {
1231 id: "0xa-0xb-123".into(),
1232 token0: sample_token(),
1233 token1: sample_token(),
1234 timestamp: "1700000000".into(),
1235 volume_token0: "100".into(),
1236 volume_token1: "200".into(),
1237 number_of_trades: "5".into(),
1238 };
1239 assert!(pd.to_string().contains("pair-daily("));
1240 }
1241
1242 #[test]
1243 fn pair_hourly_display() {
1244 let ph = PairHourly {
1245 id: "0xa-0xb-123".into(),
1246 token0: sample_token(),
1247 token1: sample_token(),
1248 timestamp: "1700000000".into(),
1249 volume_token0: "100".into(),
1250 volume_token1: "200".into(),
1251 number_of_trades: "5".into(),
1252 };
1253 assert!(ph.to_string().contains("pair-hourly("));
1254 }
1255
1256 #[test]
1257 fn token_daily_total_display() {
1258 let tdt = TokenDailyTotal {
1259 id: "0x-123".into(),
1260 token: sample_token(),
1261 timestamp: "1700000000".into(),
1262 total_volume: "1000".into(),
1263 total_volume_usd: "1000".into(),
1264 total_trades: "10".into(),
1265 open_price: "1.0".into(),
1266 close_price: "1.01".into(),
1267 higher_price: "1.02".into(),
1268 lower_price: "0.99".into(),
1269 average_price: "1.005".into(),
1270 };
1271 assert!(tdt.to_string().contains("token-daily("));
1272 }
1273
1274 #[test]
1275 fn token_hourly_total_display() {
1276 let tht = TokenHourlyTotal {
1277 id: "0x-123".into(),
1278 token: sample_token(),
1279 timestamp: "1700000000".into(),
1280 total_volume: "500".into(),
1281 total_volume_usd: "500".into(),
1282 total_trades: "5".into(),
1283 open_price: "1.0".into(),
1284 close_price: "1.01".into(),
1285 higher_price: "1.02".into(),
1286 lower_price: "0.99".into(),
1287 average_price: "1.005".into(),
1288 };
1289 assert!(tht.to_string().contains("token-hourly("));
1290 }
1291
1292 #[test]
1293 fn token_trading_event_display() {
1294 let e = TokenTradingEvent {
1295 id: "evt1".into(),
1296 token: sample_token(),
1297 price_usd: "1.01".into(),
1298 timestamp: "1700000000".into(),
1299 };
1300 assert!(e.to_string().contains("trade-event("));
1301 assert!(e.to_string().contains("price=$1.01"));
1302 }
1303
1304 #[test]
1307 fn totals_serde_roundtrip() {
1308 let t = Totals {
1309 tokens: "100".into(),
1310 orders: "500".into(),
1311 traders: "50".into(),
1312 settlements: "20".into(),
1313 volume_usd: "1000000".into(),
1314 volume_eth: "500".into(),
1315 fees_usd: "1000".into(),
1316 fees_eth: "0.5".into(),
1317 };
1318 let json = serde_json::to_string(&t).unwrap();
1319 let t2: Totals = serde_json::from_str(&json).unwrap();
1320 assert_eq!(t2.orders, "500");
1321 }
1322
1323 #[test]
1324 fn settlement_serde_skips_none() {
1325 let s = Settlement {
1326 id: "x".into(),
1327 tx_hash: "0x".into(),
1328 first_trade_timestamp: "0".into(),
1329 solver: "0x".into(),
1330 tx_cost: None,
1331 tx_fee_in_eth: None,
1332 };
1333 let json = serde_json::to_string(&s).unwrap();
1334 assert!(!json.contains("txCost"));
1335 assert!(!json.contains("txFeeInEth"));
1336 }
1337
1338 #[test]
1339 fn total_serde_roundtrip_with_optional_fields() {
1340 let t = Total {
1341 id: "1".into(),
1342 orders: "10".into(),
1343 settlements: "5".into(),
1344 tokens: "3".into(),
1345 traders: "2".into(),
1346 number_of_trades: "20".into(),
1347 volume_eth: Some("100".into()),
1348 volume_usd: Some("200000".into()),
1349 fees_eth: None,
1350 fees_usd: None,
1351 };
1352 let json = serde_json::to_string(&t).unwrap();
1353 assert!(json.contains("volumeEth"));
1354 assert!(!json.contains("feesEth"));
1355 let t2: Total = serde_json::from_str(&json).unwrap();
1356 assert_eq!(t2.volume_eth, Some("100".into()));
1357 assert_eq!(t2.fees_eth, None);
1358 }
1359
1360 #[test]
1361 fn bundle_serde_roundtrip() {
1362 let b = Bundle { id: "1".into(), eth_price_usd: "3500".into() };
1363 let json = serde_json::to_string(&b).unwrap();
1364 assert!(json.contains("ethPriceUsd"));
1365 let b2: Bundle = serde_json::from_str(&json).unwrap();
1366 assert_eq!(b2.eth_price_usd, "3500");
1367 }
1368}