use crate::graph::PoolEdge;
use scematica_core::types::DexKind;
use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArbPath {
pub id: String,
pub mint_path: Vec<Pubkey>,
pub pool_path: Vec<PoolEdge>,
pub input_amount: u128,
pub output_amount: u128,
pub hop_amounts: Vec<u64>,
pub profit: i128,
pub profit_pct: f64,
#[serde(default)]
pub fetched_at_ms: u64,
}
impl ArbPath {
pub fn new(
mint_path: Vec<Pubkey>,
pool_path: Vec<PoolEdge>,
input_amount: u128,
output_amount: u128,
hop_amounts: Vec<u64>,
) -> Self {
let profit = output_amount as i128 - input_amount as i128;
let profit_pct = if input_amount > 0 {
profit as f64 / input_amount as f64 * 100.0
} else {
0.0
};
let mint_keys: Vec<String> = mint_path.iter().map(|m| m.to_string()).collect();
let pool_keys: Vec<String> = pool_path.iter().map(|p| p.pool_address.to_string()).collect();
let id = format!("{}{}", mint_keys.join(""), pool_keys.join(""));
let fetched_at_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
Self {
id,
mint_path,
pool_path,
input_amount,
output_amount,
hop_amounts,
profit,
profit_pct,
fetched_at_ms,
}
}
pub fn is_profitable(&self) -> bool {
self.profit > 0
}
pub fn hops(&self) -> usize {
self.pool_path.len()
}
pub fn dexes(&self) -> Vec<DexKind> {
self.pool_path.iter().map(|p| p.dex).collect()
}
}
impl std::fmt::Display for ArbPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mints: Vec<String> = self.mint_path.iter()
.map(|m| m.to_string()[..8].to_string())
.collect();
write!(
f,
"{} | profit: {} ({:.3}%) | input: {}",
mints.join(" → "),
self.profit,
self.profit_pct,
self.input_amount,
)
}
}