simulator_api/reroute_report/
render.rs1use super::flow::{Direction, Flow, Report, fold};
4use crate::mints::short_mint;
5
6const MAX_DIRECTIONS: usize = 12;
9
10impl Report {
11 pub fn render(&self, subtitle: Option<&str>) -> String {
16 let target = self.target();
17 let mut out = String::new();
18 let name = target
19 .label()
20 .map(str::to_string)
21 .or_else(|| target.program().map(|program| program.to_string()))
22 .unwrap_or_else(|| "venue".to_string());
23 out.push_str(&format!("\n {name}"));
24 if let Some(subtitle) = subtitle {
25 out.push_str(&format!(" {subtitle}"));
26 }
27 out.push_str("\n\n");
28
29 let priced = self.sol_usd().is_some();
30 out.push_str(&row("", "on L1", "re-quoted", "delta"));
31 let total = self.total();
32 out.push_str(&rows_for(&total, "", priced));
33 out.push_str(&share_row(&total, ""));
34
35 let mut ranked: Vec<_> = self
36 .by_direction()
37 .filter(|(_, direction)| direction.present())
38 .collect();
39 ranked.sort_unstable_by_key(|(mints, direction)| {
40 (std::cmp::Reverse(direction.rank()), *mints)
41 });
42 for ((input, output), direction) in ranked.iter().take(MAX_DIRECTIONS) {
43 out.push_str(&format!(
44 "\n {} → {}\n",
45 short_mint(input),
46 short_mint(output)
47 ));
48 out.push_str(&rows_for(direction, " ", priced));
49 out.push_str(&share_row(direction, " "));
50 }
51 if let Some(rest) = ranked.get(MAX_DIRECTIONS..).filter(|rest| !rest.is_empty()) {
52 let tail = fold(rest.iter().map(|(_, direction)| **direction));
53 out.push_str(&format!("\n + {} more directions\n", rest.len()));
54 out.push_str(&rows_for(&tail, " ", priced));
55 out.push_str(&share_row(&tail, " "));
56 }
57
58 out
59 }
60
61 pub fn to_json(&self) -> serde_json::Value {
63 let target = self.target();
64 let total = self.total();
65 let directions: Vec<_> = self
66 .by_direction()
67 .map(|((input, output), direction)| {
68 serde_json::json!({
69 "inputMint": input,
70 "outputMint": output,
71 "l1": flow_json(&direction.l1),
72 "requoted": flow_json(&direction.requoted),
73 "sharePct": direction.share().map(|(l1, _)| l1),
74 "requotedSharePct": direction.share().map(|(_, requoted)| requoted),
75 })
76 })
77 .collect();
78 serde_json::json!({
79 "label": target.label(),
80 "program": target.program().map(|program| program.to_string()),
81 "total": {
82 "l1": flow_json(&total.l1),
83 "requoted": flow_json(&total.requoted),
84 },
85 "share": {
87 "l1Pct": total.share().map(|(l1, _)| l1),
88 "requotedPct": total.share().map(|(_, requoted)| requoted),
89 },
90 "byDirection": directions,
91 })
92 }
93}
94
95fn flow_json(flow: &Flow) -> serde_json::Value {
96 serde_json::json!({
97 "swaps": flow.legs,
98 "usd": flow.usd,
99 "swapsWithoutAmount": flow.without_amount,
100 "swapsWithoutPrice": flow.without_price,
101 })
102}
103
104fn share_row(direction: &Direction, indent: &str) -> String {
105 let Some((l1, requoted)) = direction.share() else {
106 return String::new();
107 };
108 row(
109 &format!("{indent}share"),
110 &format!("{l1:.1}%"),
111 &format!("{requoted:.1}%"),
112 &format!("{:+.1}pp", requoted - l1),
113 )
114}
115
116fn row(label: &str, l1: &str, requoted: &str, delta: &str) -> String {
117 format!(" {label:<10} {l1:>14} {requoted:>14} {delta:>14}\n")
118}
119
120fn rows_for(direction: &Direction, indent: &str, priced: bool) -> String {
123 let mut out = totals("swaps", indent, direction.l1.legs, direction.requoted.legs);
124 if priced && !direction.fully_unpriced() {
125 out.push_str(&usd_row(direction, indent));
126 }
127 if direction.unpriced() {
128 out.push_str(&format!(
129 "{indent} ({} swaps in mints this range could not price)\n",
130 direction.l1.without_price + direction.requoted.without_price
131 ));
132 }
133 out
134}
135
136fn totals(label: &str, indent: &str, l1: u64, requoted: u64) -> String {
137 row(
138 &format!("{indent}{label}"),
139 &l1.to_string(),
140 &requoted.to_string(),
141 &signed(i128::from(requoted) - i128::from(l1)),
142 )
143}
144
145fn usd_row(direction: &Direction, indent: &str) -> String {
146 row(
147 &format!("{indent}volume"),
148 &money(direction.l1.usd),
149 &money(direction.requoted.usd),
150 &format!(
151 "{}{}",
152 if direction.requoted.usd < direction.l1.usd {
153 "-"
154 } else {
155 "+"
156 },
157 money((direction.requoted.usd - direction.l1.usd).abs())
158 ),
159 )
160}
161
162fn money(usd: f64) -> String {
163 match usd {
164 usd if usd >= 1e6 => format!("${:.2}M", usd / 1e6),
165 usd if usd >= 1e3 => format!("${:.1}k", usd / 1e3),
166 usd => format!("${usd:.0}"),
167 }
168}
169
170fn signed(delta: i128) -> String {
171 match delta < 0 {
172 true => delta.to_string(),
173 false => format!("+{delta}"),
174 }
175}