use super::flow::{Direction, Flow, Report, fold};
use crate::mints::short_mint;
const MAX_DIRECTIONS: usize = 12;
impl Report {
pub fn render(&self, subtitle: Option<&str>) -> String {
let target = self.target();
let mut out = String::new();
let name = target
.label()
.map(str::to_string)
.or_else(|| target.program().map(|program| program.to_string()))
.unwrap_or_else(|| "venue".to_string());
out.push_str(&format!("\n {name}"));
if let Some(subtitle) = subtitle {
out.push_str(&format!(" {subtitle}"));
}
out.push_str("\n\n");
let priced = self.sol_usd().is_some();
out.push_str(&row("", "on L1", "re-quoted", "delta"));
let total = self.total();
out.push_str(&rows_for(&total, "", priced));
out.push_str(&share_row(&total, ""));
let mut ranked: Vec<_> = self
.by_direction()
.filter(|(_, direction)| direction.present())
.collect();
ranked.sort_unstable_by_key(|(mints, direction)| {
(std::cmp::Reverse(direction.rank()), *mints)
});
for ((input, output), direction) in ranked.iter().take(MAX_DIRECTIONS) {
out.push_str(&format!(
"\n {} → {}\n",
short_mint(input),
short_mint(output)
));
out.push_str(&rows_for(direction, " ", priced));
out.push_str(&share_row(direction, " "));
}
if let Some(rest) = ranked.get(MAX_DIRECTIONS..).filter(|rest| !rest.is_empty()) {
let tail = fold(rest.iter().map(|(_, direction)| **direction));
out.push_str(&format!("\n + {} more directions\n", rest.len()));
out.push_str(&rows_for(&tail, " ", priced));
out.push_str(&share_row(&tail, " "));
}
out
}
pub fn to_json(&self) -> serde_json::Value {
let target = self.target();
let total = self.total();
let directions: Vec<_> = self
.by_direction()
.map(|((input, output), direction)| {
serde_json::json!({
"inputMint": input,
"outputMint": output,
"l1": flow_json(&direction.l1),
"requoted": flow_json(&direction.requoted),
"sharePct": direction.share().map(|(l1, _)| l1),
"requotedSharePct": direction.share().map(|(_, requoted)| requoted),
})
})
.collect();
serde_json::json!({
"label": target.label(),
"program": target.program().map(|program| program.to_string()),
"total": {
"l1": flow_json(&total.l1),
"requoted": flow_json(&total.requoted),
},
"share": {
"l1Pct": total.share().map(|(l1, _)| l1),
"requotedPct": total.share().map(|(_, requoted)| requoted),
},
"byDirection": directions,
})
}
}
fn flow_json(flow: &Flow) -> serde_json::Value {
serde_json::json!({
"swaps": flow.legs,
"usd": flow.usd,
"swapsWithoutAmount": flow.without_amount,
"swapsWithoutPrice": flow.without_price,
})
}
fn share_row(direction: &Direction, indent: &str) -> String {
let Some((l1, requoted)) = direction.share() else {
return String::new();
};
row(
&format!("{indent}share"),
&format!("{l1:.1}%"),
&format!("{requoted:.1}%"),
&format!("{:+.1}pp", requoted - l1),
)
}
fn row(label: &str, l1: &str, requoted: &str, delta: &str) -> String {
format!(" {label:<10} {l1:>14} {requoted:>14} {delta:>14}\n")
}
fn rows_for(direction: &Direction, indent: &str, priced: bool) -> String {
let mut out = totals("swaps", indent, direction.l1.legs, direction.requoted.legs);
if priced && !direction.fully_unpriced() {
out.push_str(&usd_row(direction, indent));
}
if direction.unpriced() {
out.push_str(&format!(
"{indent} ({} swaps in mints this range could not price)\n",
direction.l1.without_price + direction.requoted.without_price
));
}
out
}
fn totals(label: &str, indent: &str, l1: u64, requoted: u64) -> String {
row(
&format!("{indent}{label}"),
&l1.to_string(),
&requoted.to_string(),
&signed(i128::from(requoted) - i128::from(l1)),
)
}
fn usd_row(direction: &Direction, indent: &str) -> String {
row(
&format!("{indent}volume"),
&money(direction.l1.usd),
&money(direction.requoted.usd),
&format!(
"{}{}",
if direction.requoted.usd < direction.l1.usd {
"-"
} else {
"+"
},
money((direction.requoted.usd - direction.l1.usd).abs())
),
)
}
fn money(usd: f64) -> String {
match usd {
usd if usd >= 1e6 => format!("${:.2}M", usd / 1e6),
usd if usd >= 1e3 => format!("${:.1}k", usd / 1e3),
usd => format!("${usd:.0}"),
}
}
fn signed(delta: i128) -> String {
match delta < 0 {
true => delta.to_string(),
false => format!("+{delta}"),
}
}