use std::fmt;
use alloy_primitives::{Address, FixedBytes};
use colored::{ColoredString, Colorize};
use indoc::indoc;
use itertools::Itertools;
use prettytable::{Cell, Row, Table};
use reth_primitives::B256;
use crate::{
mev::{ArbDetails, AtomicArbType, Bundle, BundleData, CexDex, OptimisticTrade},
utils::ToFloatNearest,
};
pub fn display_sandwich(bundle: &Bundle, f: &mut fmt::Formatter) -> fmt::Result {
let ascii_header = indoc! {r#"
_____ _ _ _
/ ___| | | (_) | |
\ `--. __ _ _ __ __| |_ ___ ___| |__
`--. \/ _` | '_ \ / _` \ \ /\ / / |/ __| '_ \
/\__/ / (_| | | | | (_| |\ V V /| | (__| | | |
\____/ \__,_|_| |_|\__,_| \_/\_/ |_|\___|_| |_|
"#};
for line in ascii_header.lines() {
writeln!(f, "{}", line.bright_red())?;
}
let sandwich_data = match &bundle.data {
BundleData::Sandwich(data) => data,
_ => panic!("Wrong bundle type"),
};
writeln!(f, "{}: \n", "Transaction Details".bold().underline().bright_yellow())?;
writeln!(f, " - EOA: {}", bundle.header.eoa)?;
match bundle.header.mev_contract {
Some(contract) => {
writeln!(f, " - Mev Contract: {}", contract)?;
}
None => {
writeln!(f, " - Mev Contract: None")?;
}
}
writeln!(f, "\n{}:", "Attacks".bright_yellow().underline())?;
for (i, ((tx_hash, swaps), gas_details)) in sandwich_data
.frontrun_tx_hash
.iter()
.zip(sandwich_data.frontrun_swaps.iter())
.zip(sandwich_data.frontrun_gas_details.iter())
.enumerate()
{
writeln!(
f,
"\n {}: {}",
format!("Frontrun {}", i + 1)
.bright_blue()
.bold()
.underline(),
format_etherscan_url(tx_hash)
)?;
writeln!(f, " - {}:", "Swaps".bright_blue())?;
for (j, swap) in swaps.iter().enumerate() {
writeln!(f, " {}: {}", format!(" - {}", j + 1).green(), swap)?;
}
writeln!(f, " - {}:", "Gas details".bright_blue())?;
gas_details.pretty_print_with_spaces(f, 12)?;
writeln!(f, "\n {}:", "Victims".bright_red().bold().underline())?;
if let Some(victim_tx_hashes) = sandwich_data.victim_swaps_tx_hashes.get(i) {
for (k, tx_hash) in victim_tx_hashes.iter().enumerate() {
let victim_swaps = sandwich_data.victim_swaps.get(k); let victim_gas_details = sandwich_data.victim_swaps_gas_details.get(k); writeln!(
f,
"\n {}: {}",
format!("Victim {}", k + 1).bright_red().bold(),
format_etherscan_url(tx_hash)
)?;
writeln!(f, " - {}:", "Swaps".bright_blue())?;
if let Some(swaps) = victim_swaps {
for (l, swap) in swaps.iter().enumerate() {
writeln!(
f,
" {}: {}",
format!(" - {}", l + 1).green(),
swap
)?;
}
}
writeln!(f, " - {}:", "Gas details".bright_blue())?;
if let Some(gas_details) = victim_gas_details {
gas_details.pretty_print_with_spaces(f, 16)?;
}
}
}
}
writeln!(f, "\n{}:\n", "Backrun Transaction".bright_yellow().underline())?;
writeln!(
f,
" - {}: {}",
"Transaction".bright_blue(),
format_etherscan_url(&sandwich_data.backrun_tx_hash)
)?;
writeln!(f, " - {}:", "Swaps".bright_blue())?;
for (i, swap) in sandwich_data.backrun_swaps.iter().enumerate() {
writeln!(f, " {}: {}", format!(" - {}", i + 1).green(), swap)?;
}
writeln!(f, " - {}:", "Gas Details".bright_blue())?;
sandwich_data
.backrun_gas_details
.pretty_print_with_spaces(f, 8)?;
writeln!(f, "\n{}\n", "Profitability".bright_yellow().underline())?;
writeln!(
f,
" - {}: {}",
"Bundle Profit (USD)".bright_white(),
format_profit(bundle.header.profit_usd)
.to_string()
.bright_white()
)?;
writeln!(
f,
" - {}: {}",
"Bribe (USD)".bright_white(),
format_bribe(bundle.header.bribe_usd)
.to_string()
.bright_red()
)?;
bundle
.header
.balance_deltas
.iter()
.for_each(|tx_delta| writeln!(f, "{}", tx_delta).expect("Failed to write balance deltas"));
Ok(())
}
pub fn display_jit_liquidity_sandwich(bundle: &Bundle, f: &mut fmt::Formatter) -> fmt::Result {
let ascii_header = indoc! {r#"
___ _ _ _____ _ _ _
|_ (_) | / ___| | | (_) | |
| |_| |_ ______\ `--. __ _ _ __ __| |_ ___ ___| |__
| | | __|______|`--. \/ _` | '_ \ / _` \ \ /\ / / |/ __| '_ \
/\__/ / | |_ /\__/ / (_| | | | | (_| |\ V V /| | (__| | | |
\____/|_|\__| \____/ \__,_|_| |_|\__,_| \_/\_/ |_|\___|_| |_|
"#};
for line in ascii_header.lines() {
writeln!(f, "{}", line.bright_red())?;
}
let jit_sandwich_data = match &bundle.data {
BundleData::JitSandwich(data) => data,
_ => panic!("Wrong bundle type"),
};
writeln!(f, "{}: \n", "Transaction Details".bold().underline().bright_yellow())?;
writeln!(f, " - EOA: {}", bundle.header.eoa)?;
match bundle.header.mev_contract {
Some(contract) => {
writeln!(f, " - Mev Contract: {}", contract)?;
}
None => {
writeln!(f, " - Mev Contract: None")?;
}
}
writeln!(f, "\n{}:", "Attacks".bright_yellow().underline())?;
for (i, (((tx_hash, swaps), mints), gas_details)) in jit_sandwich_data
.frontrun_tx_hash
.iter()
.zip(jit_sandwich_data.frontrun_swaps.iter())
.zip(jit_sandwich_data.frontrun_mints.iter())
.zip(jit_sandwich_data.frontrun_gas_details.iter())
.enumerate()
{
writeln!(
f,
"\n {}: {}",
format!("Frontrun {}", i + 1)
.bright_blue()
.bold()
.underline(),
format_etherscan_url(tx_hash)
)?;
writeln!(f, " - {}:", "Swaps".bright_blue())?;
for (j, swap) in swaps.iter().enumerate() {
writeln!(f, " {}: {}", format!(" - {}", j + 1).green(), swap)?;
}
if let Some(ref mints) = mints {
if !mints.is_empty() {
writeln!(f, " - {}:", "Mints".bright_blue())?;
for (j, mint) in mints.iter().enumerate() {
writeln!(f, " {}: {}", format!(" - {}", j + 1).green(), mint)?;
}
}
}
writeln!(f, " - {}:", "Gas details".bright_blue())?;
gas_details.pretty_print_with_spaces(f, 12)?;
writeln!(f, "\n {}:", "Victims".bright_red().bold().underline())?;
if let Some(victim_tx_hashes) = jit_sandwich_data.victim_swaps_tx_hashes.get(i) {
for (k, tx_hash) in victim_tx_hashes.iter().enumerate() {
let victim_swaps = jit_sandwich_data.victim_swaps.get(k);
let victim_gas_details = jit_sandwich_data.victim_swaps_gas_details.get(k);
writeln!(
f,
"\n {}: {}",
format!("Victim {}", k + 1).bright_red().bold(),
format_etherscan_url(tx_hash)
)?;
writeln!(f, " - {}:", "Swaps".bright_blue())?;
if let Some(swaps) = victim_swaps {
for (l, swap) in swaps.iter().enumerate() {
writeln!(
f,
" {}: {}",
format!(" - {}", l + 1).green(),
swap
)?;
}
}
writeln!(f, " - {}:", "Gas details".bright_blue())?;
if let Some(gas_details) = victim_gas_details {
gas_details.pretty_print_with_spaces(f, 16)?;
}
}
}
}
writeln!(f, "\n{}\n", "Backrun Transaction".bright_yellow().underline())?;
writeln!(
f,
" - {}: {}",
"Backrun Transaction".bright_blue(),
format_etherscan_url(&jit_sandwich_data.backrun_tx_hash)
)?;
writeln!(f, " - {}:", "Actions".bright_blue())?;
for (i, swap) in jit_sandwich_data.backrun_swaps.iter().enumerate() {
writeln!(f, " {}: {}", format!(" - {}", i + 1).green(), swap)?;
}
for (i, burn) in jit_sandwich_data.backrun_burns.iter().enumerate() {
writeln!(f, " {}: {}", format!(" - {}", i + 1).green(), burn)?;
}
writeln!(f, " - {}:", "Gas Details".bright_blue())?;
jit_sandwich_data
.backrun_gas_details
.pretty_print_with_spaces(f, 8)?;
writeln!(f, "\n{}\n", "Profitability".bright_yellow().underline())?;
writeln!(
f,
" - {}: {}",
"Bundle Profit (USD)".bright_white(),
format_profit(bundle.header.profit_usd)
.to_string()
.bright_white()
)?;
writeln!(
f,
" - {}: {}",
"Bribe (USD)".bright_white(),
format_bribe(bundle.header.bribe_usd)
.to_string()
.bright_red()
)?;
bundle
.header
.balance_deltas
.iter()
.for_each(|tx_delta| writeln!(f, "{}", tx_delta).expect("Failed to write balance deltas"));
Ok(())
}
const STABLE_COIN_HEADER: &str = indoc! {r#"
_____ _ _ _ _ ___ _
/ ___| | | | | | (_) / _ \ | |
\ `--.| |_ __ _| |__ | | ___ ___ ___ _ _ __ / /_\ \_ __| |__
`--. \ __/ _` | '_ \| |/ _ \/ __/ _ \| | '_ \ | _ | '__| '_ \
/\__/ / || (_| | |_) | | __/ (_| (_) | | | | | | | | | | | |_) |
\____/ \__\__,_|_.__/|_|\___|\___\___/|_|_| |_| \_| |_/_| |_.__/
"#};
const LONG_TAIL_HEADER: &str = indoc! {r#"
_ _______ _ _ _
| | |__ __| (_) | /\ | |
| | ___ _ __ __ _ | | __ _ _| | / \ _ __| |__
| | / _ \| '_ \ / _` | | |/ _` | | | / /\ \ | '__| '_ \
| |___| (_) | | | | (_| | | | (_| | | | / ____ \| | | |_) |
|______\___/|_| |_|\__, | |_|\__,_|_|_| /_/ \_\_| |_.__/
__/ |
|___/
"#};
const CROSS_PAIR: &str = indoc! {r#"
_____ ______ _ ___ _
/ __ \ | ___ \ (_) / _ \ | |
| / \/_ __ ___ ___ ___ | |_/ /_ _ _ _ __ / /_\ \_ __| |__
| | | '__/ _ \/ __/ __| | __/ _` | | '__| | _ | '__| '_ \
| \__/\ | | (_) \__ \__ \ | | | (_| | | | | | | | | | |_) |
\____/_| \___/|___/___/ \_| \__,_|_|_| \_| |_/_| |_.__/
"#};
const TRIANGULAR_ARB: &str = indoc! {r#"
_____ _ _ ___ _
|_ _| (_) | | / _ \ | |
| |_ __ _ __ _ _ __ __ _ _ _| | __ _ _ __ / /_\ \_ __| |__
| | '__| |/ _` | '_ \ / _` | | | | |/ _` | '__| | _ | '__| '_ \
| | | | | (_| | | | | (_| | |_| | | (_| | | | | | | | | |_) |
\_/_| |_|\__,_|_| |_|\__, |\__,_|_|\__,_|_| \_| |_/_| |_.__/
__/ |
|___/
"#};
pub fn display_atomic_backrun(bundle: &Bundle, f: &mut fmt::Formatter) -> fmt::Result {
let atomic_backrun_data = match &bundle.data {
BundleData::AtomicArb(data) => data,
_ => panic!("Wrong bundle type"),
};
match atomic_backrun_data.arb_type {
AtomicArbType::Triangle => {
for line in TRIANGULAR_ARB.lines() {
writeln!(f, "{}", line.bright_yellow())?;
}
}
AtomicArbType::CrossPair(_) => {
for line in CROSS_PAIR.lines() {
writeln!(f, "{}", line.bright_yellow())?;
}
}
AtomicArbType::StablecoinArb => {
for line in STABLE_COIN_HEADER.lines() {
writeln!(f, "{}", line.bright_blue())?;
}
}
AtomicArbType::LongTail => {
for line in LONG_TAIL_HEADER.lines() {
writeln!(f, "{}", line.bright_green())?;
}
}
}
writeln!(f, "{}: \n", "Transaction Details".bold().underline().bright_yellow())?;
writeln!(f, " - Tx Index: {}", bundle.header.tx_index.to_string().bold())?;
writeln!(f, " - EOA: {}", bundle.header.eoa)?;
match bundle.header.mev_contract {
Some(contract) => {
writeln!(f, " - Mev Contract: {}", contract)?;
}
None => {
writeln!(f, " - Mev Contract: None")?;
}
}
let tx_url = format!("https://etherscan.io/tx/{:?}", bundle.header.tx_hash).underline();
writeln!(f, " - Etherscan: {}", tx_url)?;
if atomic_backrun_data.trigger_tx != B256::ZERO {
let tx_url =
format!("https://etherscan.io/tx/{:?}", atomic_backrun_data.trigger_tx).underline();
writeln!(f, " - Trigger Tx: {}", tx_url)?;
}
writeln!(
f,
"\n{}\n",
atomic_backrun_data
.arb_type
.to_string()
.bright_yellow()
.underline()
)?;
writeln!(f, " - {}", "Swaps:".bright_blue())?;
for (i, swap) in atomic_backrun_data.swaps.iter().enumerate() {
writeln!(f, " {}: {}", format!(" - {}", i + 1).green(), swap)?;
}
writeln!(f, " - {}:", "Gas Details".bright_blue())?;
atomic_backrun_data
.gas_details
.pretty_print_with_spaces(f, 8)?;
writeln!(f, "\n{}\n", "Profitability".bright_yellow().underline())?;
writeln!(
f,
" - {}: {}",
"Bundle Profit (USD)".bright_white(),
format_profit(bundle.header.profit_usd)
.to_string()
.bright_white()
)?;
writeln!(
f,
" - {}: {}",
"Bribe (USD)".bright_white(),
format_bribe(bundle.header.bribe_usd)
.to_string()
.bright_red()
)?;
bundle
.header
.balance_deltas
.iter()
.for_each(|tx_delta| writeln!(f, "{}", tx_delta).expect("Failed to write balance deltas"));
Ok(())
}
pub fn display_liquidation(bundle: &Bundle, f: &mut fmt::Formatter) -> fmt::Result {
let ascii_header = indoc! {r#"
_ _ _ _ _ _
| | (_) (_) | | | | (_)
| | _ __ _ _ _ _ __| | __ _| |_ _ ___ _ __
| | | |/ _` | | | | |/ _` |/ _` | __| |/ _ \| '_ \
| |___| | (_| | |_| | | (_| | (_| | |_| | (_) | | | |
\_____/_|\__, |\__,_|_|\__,_|\__,_|\__|_|\___/|_| |_|
| |
|_|
"#};
for line in ascii_header.lines() {
writeln!(f, "{}", line.bright_red())?;
}
let liquidation_data = match &bundle.data {
BundleData::Liquidation(data) => data,
_ => panic!("Wrong bundle type"),
};
writeln!(f, "\n{}: \n", "Transaction Details".bold().underline().bright_yellow())?;
writeln!(f, " - EOA: {}", bundle.header.eoa)?;
match bundle.header.mev_contract {
Some(contract) => {
writeln!(f, " - Mev Contract: {}", contract)?;
}
None => {
writeln!(f, " - Mev Contract: None")?;
}
}
writeln!(f, "{}\n", "Liquidation".bright_yellow().underline())?;
writeln!(
f,
" - {}: {}",
"Transaction".bright_blue(),
format_etherscan_url(&liquidation_data.liquidation_tx_hash)
)?;
writeln!(
f,
" - {}: {}",
"Trigger".bright_blue(),
format_etherscan_url(&liquidation_data.trigger)
)?;
writeln!(f, "\n{}\n", "Liquidation Swaps".bright_yellow().underline())?;
for (i, swap) in liquidation_data.liquidation_swaps.iter().enumerate() {
writeln!(f, " {}: {}", format!(" - {}", i + 1).green(), swap)?;
}
writeln!(f, "\n{}\n", "Liquidations".bright_yellow().underline())?;
for (i, liquidation) in liquidation_data.liquidations.iter().enumerate() {
writeln!(f, " - {}:", format!("Liquidation {}", i + 1).bright_blue())?;
liquidation.pretty_print(f, 8)?;
}
writeln!(f, "\n - {}:", "Gas Details".bright_blue())?;
liquidation_data
.gas_details
.pretty_print_with_spaces(f, 8)?;
writeln!(f, "\n{}\n", "Profitability".bright_yellow().underline())?;
writeln!(
f,
" - {}: {}",
"Bundle Profit (USD)".bright_white(),
format_profit(bundle.header.profit_usd)
.to_string()
.bright_white()
)?;
writeln!(
f,
" - {}: {}\n",
"Bribe (USD)".bright_white(),
format_bribe(bundle.header.bribe_usd)
.to_string()
.bright_red()
)?;
bundle
.header
.balance_deltas
.iter()
.for_each(|tx_delta| writeln!(f, "{}", tx_delta).expect("Failed to write balance deltas"));
Ok(())
}
pub fn display_jit_liquidity(bundle: &Bundle, f: &mut fmt::Formatter) -> fmt::Result {
let ascii_header = indoc! {r#"
___ _ _ _ _ _ _ _ _
|_ (_) | | | (_) (_) | (_) |
| |_| |_ ______| | _ __ _ _ _ _ __| |_| |_ _ _
| | | __|______| | | |/ _` | | | | |/ _` | | __| | | |
/\__/ / | |_ | |___| | (_| | |_| | | (_| | | |_| |_| |
\____/|_|\__| \_____/_|\__, |\__,_|_|\__,_|_|\__|\__, |
| | __/ |
|_| |___/
"#};
for line in ascii_header.lines() {
writeln!(f, "{}", line.bright_red())?;
}
let jit_data = match &bundle.data {
BundleData::Jit(data) => data,
_ => panic!("Wrong bundle type"),
};
writeln!(f, "{}: \n", "Transaction Details".bold().underline().bright_yellow())?;
writeln!(f, " - EOA: {}", bundle.header.eoa)?;
match bundle.header.mev_contract {
Some(contract) => {
writeln!(f, " - Mev Contract: {}", contract)?;
}
None => {
writeln!(f, " - Mev Contract: None")?;
}
}
writeln!(f, "\n{}\n", "Frontrun Mints".bright_yellow().underline())?;
writeln!(
f,
" - {}: {}",
"Mint Transaction".bright_blue(),
format_etherscan_url(&jit_data.frontrun_mint_tx_hash)
)?;
writeln!(f, " - {}", "Mints:".bright_blue())?;
for (i, mint) in jit_data.frontrun_mints.iter().enumerate() {
writeln!(f, " {}: {}", format!(" - {}", i + 1).green(), mint)?;
}
writeln!(f, " - {}:", "Gas Details".bright_blue())?;
jit_data
.frontrun_mint_gas_details
.pretty_print_with_spaces(f, 8)?;
writeln!(f, "\n{}\n", "Victim Transactions".bright_yellow().underline())?;
for (i, tx_hash) in jit_data.victim_swaps_tx_hashes.iter().enumerate() {
let swaps = &jit_data.victim_swaps[i];
let gas_details = &jit_data.victim_swaps_gas_details[i];
writeln!(
f,
" - {}: {}",
format!("Victim Transaction {}", i + 1).bright_magenta(),
format_etherscan_url(tx_hash)
)?;
writeln!(f, " - {}:", "Swaps".bright_blue())?;
for (j, swap) in swaps.iter().enumerate() {
writeln!(f, " {}: {}", format!(" - {}", j + 1).green(), swap)?;
}
writeln!(f, " - {}:", "Gas Details".bright_blue())?;
gas_details.pretty_print_with_spaces(f, 8)?;
}
writeln!(f, "\n{}\n", "Backrun Burns".bright_yellow().underline())?;
writeln!(
f,
" - {}: {}",
"Burn Transaction".to_string().bright_magenta(),
format_etherscan_url(&jit_data.backrun_burn_tx_hash)
)?;
writeln!(f, " - {}", "Burns:".bright_blue())?;
for (i, burn) in jit_data.backrun_burns.iter().enumerate() {
writeln!(f, " {}: {}", format!(" - {}", i + 1).green(), burn)?;
}
writeln!(f, " - {}:", "Gas Details".bright_blue())?;
jit_data
.backrun_burn_gas_details
.pretty_print_with_spaces(f, 8)?;
writeln!(f, "\n{}\n", "Profitability".bright_yellow().underline())?;
writeln!(
f,
" - {}: {}",
"Bundle Profit (USD)".bright_white(),
format_profit(bundle.header.profit_usd)
.to_string()
.bright_white()
)?;
writeln!(
f,
" - {}: {}",
"Bribe (USD)".bright_white(),
format_bribe(bundle.header.bribe_usd)
.to_string()
.bright_red()
)?;
bundle
.header
.balance_deltas
.iter()
.for_each(|tx_delta| writeln!(f, "{}", tx_delta).expect("Failed to write balance deltas"));
Ok(())
}
pub fn display_cex_dex(bundle: &Bundle, f: &mut fmt::Formatter) -> fmt::Result {
let ascii_header = indoc! {r#"
_____ _ _ ___ _
/ ___| | | | / _ \ | |
\ `--.| |_ __ _| |_ ______/ /_\ \_ __| |__
`--. \ __/ _` | __|______| _ | '__| '_ \
/\__/ / || (_| | |_ | | | | | | |_) |
\____/ \__\__,_|\__| \_| |_/_| |_.__/
"#};
for line in ascii_header.lines() {
writeln!(f, "{}", line.purple())?;
}
let cex_dex_data = match &bundle.data {
BundleData::CexDex(data) => data,
_ => panic!("Wrong bundle type"),
};
writeln!(f, "\n{}: \n", "Transaction Details".bold().underline().bright_yellow())?;
writeln!(f, " - Tx Index: {}", bundle.header.tx_index.to_string().bold())?;
writeln!(f, " - EOA: {}", bundle.header.eoa)?;
match bundle.header.mev_contract {
Some(contract) => {
writeln!(f, " - Mev Contract: {}", contract)?;
}
None => {
writeln!(f, " - Mev Contract: None")?;
}
}
writeln!(f, " - Etherscan: {}", format_etherscan_url(&bundle.header.tx_hash))?;
writeln!(f, "\n{}", "MEV:\n".bold().underline().bright_yellow())?;
writeln!(f, " - Max Profit Route (USD): {}", format_profit(bundle.header.profit_usd))?;
writeln!(
f,
" - Max Profit Methodology: {}",
cex_dex_data.header_pnl_methodology.to_string().red()
)?;
writeln!(f, " - Bribe (USD): {}", (format_bribe(bundle.header.bribe_usd)).to_string().red())?;
writeln!(f, "Block Timestamp:\n {}", cex_dex_data.block_timestamp)?;
writeln!(f, "\n{}", "Cex-Dex Details:\n".bold().bright_yellow().underline())?;
writeln!(f, " - {}:", "PnL".bright_blue())?;
writeln!(f, " - {}: Global VMAP PnL", "PnL".bright_blue())?;
writeln!(
f,
" - Maker: {:.6}, Taker: {:.6}",
cex_dex_data.global_vmap_pnl_maker.clone().to_float(),
cex_dex_data.global_vmap_pnl_taker.clone().to_float()
)?;
writeln!(f, " - {}: Optimistic PnL", "PnL".bright_yellow())?;
writeln!(
f,
" - Maker: {:.6}, Taker: {:.6}",
cex_dex_data.optimistic_route_pnl_maker.clone().to_float(),
cex_dex_data.optimistic_route_pnl_taker.clone().to_float()
)?;
display_optimistic_trades(f, cex_dex_data)?;
writeln!(f, " - {}: Optimal Route PnL", "PnL".bright_blue())?;
writeln!(
f,
" - Maker: {:.6}, Taker: {:.6}",
cex_dex_data.optimal_route_pnl_maker.clone().to_float(),
cex_dex_data.optimal_route_pnl_taker.clone().to_float()
)?;
writeln!(f, " - {}", "Per Exchange PnL:".bold().underline().purple())?;
for (exchange, pnl) in &cex_dex_data.per_exchange_pnl {
writeln!(f, " - {}:", exchange.to_string().bold().underline().green())?;
writeln!(
f,
" - Maker: {:.6} Taker: {:.6}",
pnl.0.clone().to_float(),
pnl.1.clone().to_float()
)?;
}
writeln!(f, "\n----------------------------------------")?;
writeln!(f, "{}", "Arb Details".bold().red().underline())?;
for (i, swap) in cex_dex_data.swaps.iter().enumerate() {
writeln!(f, "\n{}: - {}", format!("Swap {}", i + 1).bold().blue().underline(), swap)?;
writeln!(f, " - {}:", "Max Profit Route".purple().bold().underline())?;
if i < cex_dex_data.optimal_route_details.len() {
display_arb_details(f, &cex_dex_data.optimal_route_details[i])?;
} else {
writeln!(f, " - Error: No optimal route detail available for swap {}", i + 1)?;
}
writeln!(f, " - {}:", "Global VMAP".purple().bold().underline())?;
if i < cex_dex_data.global_vmap_details.len() {
display_arb_details(f, &cex_dex_data.global_vmap_details[i])?;
} else {
writeln!(f, " - Error: No global VMAP detail available for swap {}", i + 1)?;
}
writeln!(f, " {}:", "Per Exchange Arb Details".purple().bold().underline())?;
if i < cex_dex_data.per_exchange_details.len() {
for details in cex_dex_data.per_exchange_details[i].iter() {
display_arb_details(f, details)?;
}
} else {
writeln!(f, " - Error: No per exchange arb details available for swap {}", i + 1)?;
}
}
writeln!(f, "\n{}: \n", "Gas Details".underline().bright_yellow())?;
cex_dex_data.gas_details.pretty_print_with_spaces(f, 8)?;
bundle.header.balance_deltas.iter().for_each(|tx_delta| {
writeln!(f, "\n\n{}", tx_delta).expect("Failed to write balance deltas")
});
Ok(())
}
fn display_arb_details(f: &mut fmt::Formatter<'_>, details: &ArbDetails) -> fmt::Result {
writeln!(f, " Pairs: {:?}", details.pairs)?;
writeln!(f, " Trade Window: {} - {}", details.trade_start_time, details.trade_end_time)?;
writeln!(f, " CEX Exchange: {:?}", details.cex_exchange)?;
writeln!(
f,
" Price (Maker/Taker): {:.8} / {:.8}",
details.price_maker.clone().to_float(),
details.price_taker.clone().to_float()
)?;
writeln!(f, " DEX Exchange: {:?}", details.dex_exchange)?;
writeln!(f, " DEX Price: {:.8}", details.dex_price.clone().to_float())?;
writeln!(f, " DEX Amount: {:.8}", details.dex_amount.clone().to_float())?;
writeln!(
f,
" PnL (Maker/Taker): {:.8} / {:.8}",
details.pnl_maker.clone().to_float(),
details.pnl_taker.clone().to_float()
)?;
Ok(())
}
pub fn display_optimistic_trades(
f: &mut std::fmt::Formatter<'_>,
cex_dex_data: &CexDex,
) -> std::fmt::Result {
writeln!(f, " - {}: Optimistic Route PnL", "PnL".bright_blue())?;
writeln!(
f,
" - Maker: {:.8}, Taker: {:.8}",
cex_dex_data.optimistic_route_pnl_maker.clone().to_float(),
cex_dex_data.optimistic_route_pnl_taker.clone().to_float(),
)?;
if !cex_dex_data.optimistic_trade_details.is_empty() {
writeln!(f, "\n - {}: Optimistic Trade Details", "Trades".bright_green())?;
let mut table = Table::new();
table.add_row(Row::new(vec![
Cell::new("Exchange").style_spec("Fb"),
Cell::new("Pair").style_spec("Fb"),
Cell::new("Time from block (ms)").style_spec("Fb"),
Cell::new("Price").style_spec("Fb"),
Cell::new("Volume").style_spec("Fb"),
]));
let all_trades: Vec<&OptimisticTrade> = cex_dex_data
.optimistic_trade_details
.iter()
.flatten()
.sorted_by_key(|trade| trade.timestamp)
.collect();
let merged_trades = merge_trades(all_trades, 7); for trade in merged_trades {
let relative_time =
(trade.timestamp as i64 - cex_dex_data.block_timestamp as i64) / 1000;
table.add_row(Row::new(vec![
Cell::new(&format!("{:?}", trade.exchange)),
Cell::new(&format!("{:?}", trade.pair)),
Cell::new(&format!("{}", relative_time)),
Cell::new(&format!("{:.8}", trade.price.clone().to_float())),
Cell::new(&format!("{:.8}", trade.volume.clone().to_float())),
]));
}
write!(f, "{}", table)?;
}
Ok(())
}
fn merge_trades(trades: Vec<&OptimisticTrade>, max_entries: usize) -> Vec<OptimisticTrade> {
if trades.len() <= max_entries {
return trades.into_iter().cloned().collect();
}
let mut merged = Vec::new();
let chunk_size = (trades.len() as f32 / max_entries as f32).ceil() as usize;
for chunk in trades.chunks(chunk_size) {
let mut merged_trade = chunk[0].clone();
for trade in chunk.iter().skip(1) {
merged_trade.volume += trade.volume.clone();
merged_trade.price = (merged_trade.price.clone() * merged_trade.volume.clone()
+ trade.price.clone() * trade.volume.clone())
/ (merged_trade.volume.clone() + trade.volume.clone());
}
merged_trade.exchange = format!("{:?}+", merged_trade.exchange).into();
merged.push(merged_trade);
}
merged
}
pub fn display_cex_dex_quotes(bundle: &Bundle, f: &mut fmt::Formatter) -> fmt::Result {
let ascii_header = indoc! {r#"
██████╗███████╗██╗ ██╗ ██████╗ ███████╗██╗ ██╗
██╔════╝██╔════╝╚██╗██╔╝ ██╔══██╗██╔════╝╚██╗██╔╝
██║ █████╗ ╚███╔╝ ██║ ██║█████╗ ╚███╔╝
██║ ██╔══╝ ██╔██╗ ██║ ██║██╔══╝ ██╔██╗
╚██████╗███████╗██╔╝ ██╗ ██████╔╝███████╗██╔╝ ██╗
╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝
"#};
writeln!(f, "{}", ascii_header.purple())?;
let cex_dex_data = match &bundle.data {
BundleData::CexDexQuote(data) => data,
_ => return Err(fmt::Error),
};
writeln!(f, "\n{}", "Transaction Details".bold().underline().bright_yellow())?;
writeln!(f, " - Tx Hash: {}", format_etherscan_url(&bundle.header.tx_hash))?;
writeln!(f, " - Block Number: {}", bundle.header.block_number)?;
writeln!(f, " - Block Timestamp: {}", cex_dex_data.block_timestamp)?;
writeln!(f, " - Bribe USD: {}", bundle.header.bribe_usd)?;
writeln!(f, "\n{}", "Quote Details".bold().underline().bright_yellow())?;
writeln!(f, " - Exchange: {}", cex_dex_data.exchange.to_string().green())?;
writeln!(f, " - PnL (USD): {}", format!("{:.6}", cex_dex_data.pnl).cyan())?;
writeln!(f, "\n{}", "Swaps".bold().underline().bright_yellow())?;
for (i, swap) in cex_dex_data.swaps.iter().enumerate() {
writeln!(f, " Swap {}: {}", i + 1, swap)?;
if i < cex_dex_data.instant_mid_price.len() {
writeln!(f, " - Mid Price: {:.6}", cex_dex_data.instant_mid_price[i])?;
}
if i < cex_dex_data.t2_mid_price.len() {
writeln!(f, " - Mid Price (T2): {:.6}", cex_dex_data.t2_mid_price[i])?;
}
if i < cex_dex_data.t12_mid_price.len() {
writeln!(f, " - Mid Price (T12): {:.6}", cex_dex_data.t12_mid_price[i])?;
}
if i < cex_dex_data.t60_mid_price.len() {
writeln!(f, " - Mid Price (T60): {:.6}", cex_dex_data.t60_mid_price[i])?;
}
if i < cex_dex_data.t300_mid_price.len() {
writeln!(f, " - Mid Price (T300): {:.6}", cex_dex_data.t300_mid_price[i])?;
}
}
writeln!(f, "\n{}: \n", "Gas Details".underline().bright_yellow())?;
cex_dex_data.gas_details.pretty_print_with_spaces(f, 8)?;
Ok(())
}
pub fn display_searcher_tx(bundle: &Bundle, f: &mut fmt::Formatter) -> fmt::Result {
let ascii_header = indoc! {r#"
_____ _ _____
/ ___| | | |_ _|
\ `--. ___ __ _ _ __ ___| |__ ___ _ __ | |_ __
`--. \/ _ \/ _` | '__/ __| '_ \ / _ \ '__| | \ \/ /
/\__/ / __/ (_| | | | (__| | | | __/ | | |> <
\____/ \___|\__,_|_| \___|_| |_|\___|_| \_/_/\_\
"#};
let searcher_tx_data = match &bundle.data {
BundleData::Unknown(data) => data,
_ => panic!("Wrong bundle type"),
};
for line in ascii_header.lines() {
writeln!(f, "{}", line)?;
}
writeln!(f, "\n{}: \n", "Transaction Details".bold().underline().bright_yellow())?;
writeln!(f, " - Tx Index: {}", bundle.header.tx_index.to_string().bold())?;
writeln!(f, " - EOA: {}", bundle.header.eoa)?;
match bundle.header.mev_contract {
Some(contract) => {
writeln!(f, " - Mev Contract: {}", formate_etherscan_address_url(&contract))?;
}
None => {
writeln!(f, " - Mev Contract: None")?;
}
}
writeln!(f, " - Etherscan: {}", format_etherscan_url(&bundle.header.tx_hash))?;
writeln!(f, " - {}:", "PnL".bright_blue())?;
writeln!(f, " - Transaction Profit (USD): {}", format_profit(bundle.header.profit_usd))?;
writeln!(f, " - Bribe (USD): {}", (format_bribe(bundle.header.bribe_usd)).to_string().red())?;
bundle
.header
.balance_deltas
.iter()
.for_each(|tx_delta| writeln!(f, "{}", tx_delta).expect("Failed to write balance deltas"));
writeln!(f, "\n{}: \n", "Gas Details".underline().bright_yellow())?;
searcher_tx_data
.gas_details
.pretty_print_with_spaces(f, 8)?;
Ok(())
}
fn format_profit(value: f64) -> ColoredString {
if value < 0.0 {
format!("-${:.2}", value.abs()).red()
} else if value > 0.0 {
format!("${:.2}", value).green()
} else {
format!("${:.2}", value).white()
}
}
fn format_bribe(value: f64) -> ColoredString {
format!("${:.2}", value).red()
}
pub fn format_etherscan_url(tx_hash: &FixedBytes<32>) -> String {
format!("https://etherscan.io/tx/{:?}", tx_hash)
.underline()
.to_string()
}
pub fn formate_etherscan_address_url(tx_hash: &Address) -> String {
format!("https://etherscan.io/address/{:?}", tx_hash)
.underline()
.to_string()
}