orca_tx_sender/
compute_budget.rs1use crate::fee_config::{FeeConfig, Percentile, PriorityFeeStrategy};
2use crate::rpc_config::RpcConfig;
3use solana_client::nonblocking::rpc_client::RpcClient;
4use solana_client::rpc_config::RpcSimulateTransactionConfig;
5use solana_compute_budget_interface::ComputeBudgetInstruction;
6use solana_instruction::Instruction;
7use solana_message::AddressLookupTableAccount;
8use solana_message::{v0::Message, VersionedMessage};
9use solana_pubkey::Pubkey;
10use solana_rpc_client_api::response::RpcPrioritizationFee;
11use solana_transaction::versioned::VersionedTransaction;
12
13#[derive(Debug, Default)]
18pub enum ComputeUnitLimitStrategy {
19 #[default]
20 Dynamic,
21 Exact(u32),
22}
23
24#[derive(Debug, Default)]
25pub struct ComputeConfig {
26 pub unit_limit: ComputeUnitLimitStrategy,
27}
28
29pub(crate) fn format_simulation_error(
30 err: impl std::fmt::Display,
31 logs: Option<Vec<String>>,
32) -> String {
33 let mut message = format!("Transaction simulation failed: {}", err);
34
35 if let Some(logs) = logs {
36 if !logs.is_empty() {
37 message.push_str("\nSimulation logs:\n");
38 message.push_str(&logs.join("\n"));
39 }
40 }
41
42 message
43}
44
45pub async fn estimate_compute_units(
47 rpc_client: &RpcClient,
48 instructions: &[Instruction],
49 payer: &Pubkey,
50 alts: Option<Vec<AddressLookupTableAccount>>,
51) -> Result<u32, String> {
52 let alt_accounts = alts.unwrap_or_default();
53 let blockhash = rpc_client
54 .get_latest_blockhash()
55 .await
56 .map_err(|e| format!("Failed to get recent blockhash: {}", e))?;
57
58 let mut simulation_instructions =
60 vec![ComputeBudgetInstruction::set_compute_unit_limit(1_400_000)];
61 simulation_instructions.extend_from_slice(instructions);
62
63 let message = Message::try_compile(payer, &simulation_instructions, &alt_accounts, blockhash)
64 .map_err(|e| format!("Failed to compile message: {}", e))?;
65
66 let transaction = VersionedTransaction {
67 signatures: vec![
68 solana_signature::Signature::default();
69 message.header.num_required_signatures.into()
70 ],
71 message: VersionedMessage::V0(message),
72 };
73
74 let result = rpc_client
75 .simulate_transaction_with_config(
76 &transaction,
77 RpcSimulateTransactionConfig {
78 sig_verify: false,
79 replace_recent_blockhash: true,
80 ..Default::default()
81 },
82 )
83 .await;
84
85 match result {
86 Ok(simulation_result) => {
87 if let Some(err) = simulation_result.value.err {
88 return Err(format_simulation_error(err, simulation_result.value.logs));
89 }
90 match simulation_result.value.units_consumed {
91 Some(units) => Ok(units as u32),
92 None => Err("Transaction simulation didn't return consumed units".to_string()),
93 }
94 }
95 Err(e) => Err(format!("Transaction simulation failed: {}", e)),
96 }
97}
98
99pub async fn get_compute_budget_instruction(
101 client: &RpcClient,
102 compute_units: u32,
103 _payer: &Pubkey,
104 rpc_config: &RpcConfig,
105 fee_config: &FeeConfig,
106 writable_accounts: &[Pubkey],
107) -> Result<Vec<Instruction>, String> {
108 let mut budget_instructions = Vec::new();
109 let compute_units_with_margin =
110 (compute_units as f64 * (fee_config.compute_unit_margin_multiplier)) as u32;
111
112 budget_instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(
113 compute_units_with_margin,
114 ));
115
116 match &fee_config.priority_fee {
117 PriorityFeeStrategy::Dynamic {
118 percentile,
119 max_lamports,
120 } => {
121 let fee =
122 calculate_dynamic_priority_fee(client, rpc_config, writable_accounts, *percentile)
123 .await?;
124 let clamped_fee = std::cmp::min(fee, *max_lamports);
125
126 if clamped_fee > 0 {
127 budget_instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
128 clamped_fee,
129 ));
130 }
131 }
132 PriorityFeeStrategy::Exact(lamports) => {
133 if *lamports > 0 {
134 budget_instructions
135 .push(ComputeBudgetInstruction::set_compute_unit_price(*lamports));
136 }
137 }
138 PriorityFeeStrategy::Disabled => {}
139 }
140
141 Ok(budget_instructions)
142}
143
144pub(crate) async fn calculate_dynamic_priority_fee(
146 client: &RpcClient,
147 rpc_config: &RpcConfig,
148 writable_accounts: &[Pubkey],
149 percentile: Percentile,
150) -> Result<u64, String> {
151 if rpc_config.supports_priority_fee_percentile {
152 get_priority_fee_with_percentile(client, writable_accounts, percentile).await
153 } else {
154 get_priority_fee_legacy(client, writable_accounts, percentile).await
155 }
156}
157
158pub(crate) async fn get_priority_fee_with_percentile(
160 client: &RpcClient,
161 writable_accounts: &[Pubkey],
162 percentile: Percentile,
163) -> Result<u64, String> {
164 let rpc_url = client.url();
167
168 let response = reqwest::Client::new()
169 .post(rpc_url)
170 .json(&serde_json::json!({
171 "jsonrpc": "2.0",
172 "id": 1,
173 "method": "getRecentPrioritizationFees",
174 "params": [{
175 "lockedWritableAccounts": writable_accounts.iter().map(|p| p.to_string()).collect::<Vec<String>>(),
176 "percentile": percentile.as_value() * 100
177 }]
178 }))
179 .send()
180 .await
181 .map_err(|e| format!("RPC Error: {}", e))?;
182
183 #[derive(serde::Deserialize)]
184 struct Response {
185 result: RpcPrioritizationFee,
186 }
187
188 response
189 .json::<Response>()
190 .await
191 .map(|resp| resp.result.prioritization_fee)
192 .map_err(|e| format!("Failed to parse prioritization fee response: {}", e))
193}
194
195pub(crate) async fn get_priority_fee_legacy(
197 client: &RpcClient,
198 writable_accounts: &[Pubkey],
199 percentile: Percentile,
200) -> Result<u64, String> {
201 let recent_fees = client
203 .get_recent_prioritization_fees(writable_accounts)
204 .await
205 .map_err(|e| format!("RPC Error: {}", e))?;
206
207 let mut non_zero_fees: Vec<u64> = recent_fees
209 .iter()
210 .filter(|fee| fee.prioritization_fee > 0)
211 .map(|fee| fee.prioritization_fee)
212 .collect();
213
214 non_zero_fees.sort_unstable();
215
216 if non_zero_fees.is_empty() {
217 return Ok(0);
218 }
219
220 let index = (non_zero_fees.len() as f64 * (percentile.as_value() as f64 / 100.0)) as usize;
222 let index = std::cmp::min(index, non_zero_fees.len() - 1);
223
224 Ok(non_zero_fees[index])
225}
226
227pub fn get_writable_accounts(instructions: &[Instruction]) -> Vec<Pubkey> {
229 let mut writable = std::collections::HashSet::new();
230
231 for ix in instructions {
232 for meta in &ix.accounts {
233 if meta.is_writable {
234 writable.insert(meta.pubkey);
235 }
236 }
237 }
238
239 writable.into_iter().collect()
240}