orca_tx_sender/
rpc_config.rs

1use solana_client::nonblocking::rpc_client::RpcClient;
2use solana_program::hash::Hash;
3use std::time::Duration;
4
5const MAINNET_HASH: &str = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d";
6const DEVNET_HASH: &str = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
7const ECLIPSE_HASH: &str = "EAQLJCV2mh23BsK2P9oYpV5CHVLDNHTxYss3URrNmg3s";
8const ECLIPSE_TESTNET_HASH: &str = "CX4huckiV9QNAkKNVKi5Tj8nxzBive5kQimd94viMKsU";
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum ChainId {
12    Mainnet,
13    Devnet,
14    Eclipse,
15    EclipseTestnet,
16    Unknown(Hash),
17}
18
19impl ChainId {
20    pub fn is_mainnet(&self) -> bool {
21        matches!(self, Self::Mainnet)
22    }
23}
24
25impl From<Hash> for ChainId {
26    fn from(hash: Hash) -> Self {
27        // Convert hash to string once for comparison
28        let hash_str = hash.to_string();
29        // Compare with string constants directly
30        if hash_str == MAINNET_HASH {
31            return Self::Mainnet;
32        } else if hash_str == DEVNET_HASH {
33            return Self::Devnet;
34        } else if hash_str == ECLIPSE_HASH {
35            return Self::Eclipse;
36        } else if hash_str == ECLIPSE_TESTNET_HASH {
37            return Self::EclipseTestnet;
38        }
39        Self::Unknown(hash)
40    }
41}
42
43/// RPC configuration for connecting to Solana nodes.
44///
45/// Start with [`RpcConfig::default`] and use the `with_*` methods to override
46/// individual settings, or use [`RpcConfig::new`] to detect the chain from an
47/// RPC endpoint.
48#[non_exhaustive]
49#[derive(Debug, Default, Clone, PartialEq)]
50pub struct RpcConfig {
51    pub url: String,
52    pub supports_priority_fee_percentile: bool,
53    pub chain_id: Option<ChainId>,
54}
55
56impl RpcConfig {
57    /// Set the RPC URL without contacting the endpoint or detecting its chain.
58    /// Prefer [`RpcConfig::new`] when the chain id is not already known.
59    pub fn with_url(mut self, url: impl Into<String>) -> Self {
60        self.url = url.into();
61        self
62    }
63
64    pub fn with_priority_fee_percentile_support(
65        mut self,
66        supports_priority_fee_percentile: bool,
67    ) -> Self {
68        self.supports_priority_fee_percentile = supports_priority_fee_percentile;
69        self
70    }
71
72    pub fn with_chain_id(mut self, chain_id: ChainId) -> Self {
73        self.chain_id = Some(chain_id);
74        self
75    }
76
77    pub async fn new(url: impl Into<String>) -> Result<Self, String> {
78        let url = url.into();
79        let client = RpcClient::new(url.clone());
80        let genesis_hash = client
81            .get_genesis_hash()
82            .await
83            .map_err(|e| format!("Chain Detection Error: Failed to get genesis hash: {e}"))?;
84
85        Ok(Self {
86            url,
87            supports_priority_fee_percentile: false,
88            chain_id: Some(ChainId::from(genesis_hash)),
89        })
90    }
91
92    pub fn client(&self) -> RpcClient {
93        RpcClient::new_with_timeout(self.url.clone(), Duration::from_millis(90_000))
94    }
95
96    /// Check if the RPC is connected to Solana mainnet
97    pub fn is_mainnet(&self) -> bool {
98        self.chain_id
99            .as_ref()
100            .is_some_and(|chain_id| chain_id.is_mainnet())
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn builder_sets_rpc_configuration() {
110        let config = RpcConfig::default()
111            .with_url("https://example.com")
112            .with_priority_fee_percentile_support(true)
113            .with_chain_id(ChainId::Mainnet);
114
115        assert_eq!(config.url, "https://example.com");
116        assert!(config.supports_priority_fee_percentile);
117        assert_eq!(config.chain_id, Some(ChainId::Mainnet));
118    }
119}