orca_tx_sender/
config.rs

1// Solana program imports
2pub use solana_commitment_config::CommitmentConfig;
3pub use solana_commitment_config::CommitmentLevel;
4pub use solana_compute_budget_interface::ComputeBudgetInstruction;
5pub use solana_hash::Hash;
6pub use solana_instruction::Instruction;
7pub use solana_keypair::Signer;
8pub use solana_message::AddressLookupTableAccount;
9pub use solana_message::{v0::Message, Message as LegacyMessage, VersionedMessage};
10pub use solana_pubkey::Pubkey;
11pub use solana_signature::Signature;
12pub use solana_transaction::versioned::VersionedTransaction;
13pub use solana_transaction::Transaction;
14// Solana client imports
15pub use solana_client::rpc_config::RpcSendTransactionConfig;
16// Standard library imports
17pub use std::time::{Duration, Instant};
18pub use tokio::time::sleep;
19
20use solana_client::nonblocking::rpc_client::RpcClient;
21use std::sync::{Arc, OnceLock, RwLock};
22
23use crate::fee_config::{FeeConfig, JitoFeeStrategy, PriorityFeeStrategy};
24use crate::rpc_config::RpcConfig;
25
26/// Global configuration state
27/// The `GlobalConfig` contains:
28/// - `Option<RpcConfig>` - Must be explicitly set with `set_rpc()` before sending transactions
29/// - `Option<Arc<RpcClient>>` - Created when RPC is set, for reuse across transactions
30/// - `FeeConfig` - Configured with defaults but can be customized
31static GLOBAL_CONFIG: OnceLock<RwLock<GlobalConfig>> = OnceLock::new();
32
33// Initialize the global config
34pub(crate) fn get_global_config() -> &'static RwLock<GlobalConfig> {
35    GLOBAL_CONFIG.get_or_init(|| {
36        RwLock::new(GlobalConfig {
37            rpc_config: None,
38            rpc_client: None,
39            fee_config: FeeConfig::default(),
40        })
41    })
42}
43
44/// Global configuration for transaction sending
45///
46/// This struct holds the application-wide settings for transaction building and sending.
47/// Access and modify it through the global functions like `set_rpc()`, `set_priority_fee_strategy()`, etc.
48///
49/// Note that `rpc_config` and `rpc_client` are `Option<T>` because they must be explicitly set before
50/// sending transactions. Attempting to send a transaction without setting RPC first will result in
51/// an error.
52#[derive(Clone)]
53pub struct GlobalConfig {
54    /// RPC configuration (None until explicitly set)
55    pub rpc_config: Option<RpcConfig>,
56    /// Shared RPC client (created when RPC config is set)
57    pub rpc_client: Option<Arc<RpcClient>>,
58    pub fee_config: FeeConfig,
59}
60
61/// Set the RPC configuration globally
62pub async fn set_rpc(url: &str) -> Result<(), String> {
63    let rpc_config = RpcConfig::new(url).await?;
64    let rpc_client = Arc::new(rpc_config.client());
65
66    let mut config = get_global_config()
67        .write()
68        .map_err(|e| format!("Lock error: {}", e))?;
69    config.rpc_config = Some(rpc_config);
70    config.rpc_client = Some(rpc_client);
71    Ok(())
72}
73
74/// Set the priority fee strategy globally
75pub fn set_priority_fee_strategy(strategy: PriorityFeeStrategy) -> Result<(), String> {
76    let mut config = get_global_config()
77        .write()
78        .map_err(|e| format!("Lock error: {}", e))?;
79    config.fee_config.priority_fee = strategy;
80    Ok(())
81}
82
83/// Set the Jito tip strategy globally
84pub fn set_jito_fee_strategy(strategy: JitoFeeStrategy) -> Result<(), String> {
85    let mut config = get_global_config()
86        .write()
87        .map_err(|e| format!("Lock error: {}", e))?;
88    config.fee_config.jito = strategy;
89    Ok(())
90}
91
92/// Set the compute unit margin multiplier globally
93pub fn set_compute_unit_margin_multiplier(multiplier: f64) -> Result<(), String> {
94    let mut config = get_global_config()
95        .write()
96        .map_err(|e| format!("Lock error: {}", e))?;
97    config.fee_config.compute_unit_margin_multiplier = multiplier;
98    Ok(())
99}
100
101/// Set the Jito block engine URL globally
102pub fn set_jito_block_engine_url(url: String) -> Result<(), String> {
103    let mut config = get_global_config()
104        .write()
105        .map_err(|e| format!("Lock error: {}", e))?;
106    config.fee_config.jito_block_engine_url = url;
107    Ok(())
108}
109
110/// Helper function to get RPC client from global config
111pub fn get_rpc_client() -> Result<Arc<RpcClient>, String> {
112    let config = get_global_config()
113        .read()
114        .map_err(|e| format!("Lock error: {}", e))?;
115    // Return the shared RPC client if available
116    config
117        .rpc_client
118        .clone()
119        .ok_or_else(|| "RPC not configured. Call set_rpc() first.".to_string())
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_global_config() {
128        // Test setting and getting global config values
129        set_compute_unit_margin_multiplier(1.2).unwrap();
130
131        let config = get_global_config().read().unwrap();
132        assert_eq!(config.fee_config.compute_unit_margin_multiplier, 1.2);
133
134        // Verify RPC is None by default
135        assert!(config.rpc_config.is_none());
136        assert!(config.rpc_client.is_none());
137    }
138}