1use crate::fee_config::{FeeConfig, JitoFeeStrategy, JitoPercentile};
2use serde::Deserialize;
3use solana_program::instruction::Instruction;
4use solana_program::pubkey::Pubkey;
5use solana_system_interface::instruction::transfer;
6use std::str::FromStr;
7
8const JITO_TIP_ADDRESSES: [&str; 8] = [
10 "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5",
11 "HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe",
12 "Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY",
13 "ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49",
14 "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
15 "ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt",
16 "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
17 "3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT",
18];
19
20#[derive(Debug, Deserialize)]
22pub struct JitoTipData {
23 pub time: String,
24 pub landed_tips_25th_percentile: f64,
25 pub landed_tips_50th_percentile: f64,
26 pub landed_tips_75th_percentile: f64,
27 pub landed_tips_95th_percentile: f64,
28 pub landed_tips_99th_percentile: f64,
29 pub ema_landed_tips_50th_percentile: f64,
30}
31
32pub fn create_tip_instruction(lamports: u64, payer: &Pubkey) -> Instruction {
34 let random_index = Pubkey::new_unique().to_bytes()[0] as usize % JITO_TIP_ADDRESSES.len();
36 let jito_address_str = JITO_TIP_ADDRESSES[random_index];
37 let jito_pubkey = Pubkey::from_str(jito_address_str).expect("Invalid pubkey string");
38 transfer(payer, &jito_pubkey, lamports)
39}
40
41pub async fn add_jito_tip_instruction(
43 fee_config: &FeeConfig,
44 payer: &Pubkey,
45) -> Result<Option<Instruction>, String> {
46 match &fee_config.jito {
47 JitoFeeStrategy::Dynamic {
48 percentile,
49 max_lamports,
50 } => {
51 let tip = calculate_dynamic_jito_tip(fee_config, *percentile).await?;
52 let clamped_tip = std::cmp::min(tip, *max_lamports);
53
54 if clamped_tip > 0 {
55 let tip_instruction = create_tip_instruction(clamped_tip, payer);
56 return Ok(Some(tip_instruction));
57 }
58 }
59 JitoFeeStrategy::Exact(lamports) => {
60 if *lamports > 0 {
61 let tip_instruction = create_tip_instruction(*lamports, payer);
62 return Ok(Some(tip_instruction));
63 }
64 }
65 JitoFeeStrategy::Disabled => {}
66 }
67
68 Ok(None)
69}
70
71pub(crate) async fn calculate_dynamic_jito_tip(
73 fee_config: &FeeConfig,
74 percentile: JitoPercentile,
75) -> Result<u64, String> {
76 let reqwest_client = reqwest::Client::new();
78 let url = format!(
79 "{}/api/v1/bundles/tip_floor",
80 fee_config.jito_block_engine_url
81 );
82
83 let response = reqwest_client
84 .get(&url)
85 .send()
86 .await
87 .map_err(|e| format!("Jito Error: {}", e))?;
88
89 if !response.status().is_success() {
90 return Err(format!(
91 "Fee Calculation Failed: Failed to get Jito tips: HTTP {}",
92 response.status()
93 ));
94 }
95
96 let tip_data: Vec<JitoTipData> = response
98 .json()
99 .await
100 .map_err(|e| format!("Jito Error: {}", e))?;
101
102 if let Some(data) = tip_data.first() {
104 let value = match percentile {
106 JitoPercentile::P25 => data.landed_tips_25th_percentile,
107 JitoPercentile::P50 => data.landed_tips_50th_percentile,
108 JitoPercentile::P50Ema => data.ema_landed_tips_50th_percentile,
109 JitoPercentile::P75 => data.landed_tips_75th_percentile,
110 JitoPercentile::P95 => data.landed_tips_95th_percentile,
111 JitoPercentile::P99 => data.landed_tips_99th_percentile,
112 };
113
114 let lamports = (value * 1_000_000_000.0).floor() as u64;
116 return Ok(lamports);
117 }
118
119 Ok(0)
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn test_create_tip_instruction() {
129 let payer = Pubkey::new_unique();
130 let instruction = create_tip_instruction(1000, &payer);
131
132 assert_eq!(
133 instruction.program_id,
134 solana_system_interface::program::id()
135 );
136 assert_eq!(instruction.accounts[0].pubkey, payer);
137 assert!(JITO_TIP_ADDRESSES.contains(&instruction.accounts[1].pubkey.to_string().as_str()));
138 }
139}