orca_tx_sender/
jito.rs

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
8// Jito tip receiver addresses
9const JITO_TIP_ADDRESSES: [&str; 8] = [
10    "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5",
11    "HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe",
12    "Cw8CFyM9FkoMi7K7Crf6HNQqf4uEMzpKw6QNghXLvLkY",
13    "ADaUMid9yfUytqMBgopwjb2DTLSokTSzL1zt6iGPaS49",
14    "DfXygSm4jCyNCybVYYK6DwvWqjKee8pbDmJGcLWNDXjh",
15    "ADuUkR4vqLUMWXxW9gh6D6L8pMSawimctcNZ5pGwDcEt",
16    "DttWaMuVvTiduZRnguLF7jNxTgiMBZ1hyAumKUiL2KRL",
17    "3AVi9Tg9Uo68tJfuvoKvqKNWKkC5wPdSSdeBnizKZ6jT",
18];
19
20/// Represents a single entry in the Jito tip data response
21#[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
32/// Create a Jito tip instruction
33pub fn create_tip_instruction(lamports: u64, payer: &Pubkey) -> Instruction {
34    // Pick a random Jito tip address from the list using the native random function
35    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
41/// Calculate and return Jito tip instruction if enabled
42pub 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
71/// Calculate dynamic Jito tip based on recent tips
72pub(crate) async fn calculate_dynamic_jito_tip(
73    fee_config: &FeeConfig,
74    percentile: JitoPercentile,
75) -> Result<u64, String> {
76    // Make a request to the Jito block engine API to get recent tips
77    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    // Parse the response as a structured type
97    let tip_data: Vec<JitoTipData> = response
98        .json()
99        .await
100        .map_err(|e| format!("Jito Error: {}", e))?;
101
102    // Get the first entry if available
103    if let Some(data) = tip_data.first() {
104        // Get the appropriate percentile value based on the requested percentile
105        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        // Convert from SOL to lamports (multiply by 10^9)
115        let lamports = (value * 1_000_000_000.0).floor() as u64;
116        return Ok(lamports);
117    }
118
119    // Default to 0 if we couldn't get a valid tip
120    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}