Skip to main content

foundry_cli/opts/
rpc_common.rs

1//! Common RPC options shared between `RpcOpts` and `EvmArgs`.
2
3use clap::Parser;
4use eyre::Result;
5use foundry_config::{
6    Config,
7    figment::{
8        self, Metadata, Profile,
9        value::{Dict, Map},
10    },
11};
12use serde::Serialize;
13use std::borrow::Cow;
14
15/// Common RPC-related options shared across CLI commands.
16///
17/// This struct holds fields that both [`super::RpcOpts`] (cast) and
18/// [`super::EvmArgs`] (forge/script) need, eliminating duplication and
19/// making the two structs composable.
20///
21/// Note: `ETH_RPC_URL` is intentionally **not** bound here as a clap env
22/// fallback; otherwise it would be inherited by `EvmArgs` and silently
23/// fork all `forge test` runs. Cast resolves `ETH_RPC_URL` explicitly
24/// at the call site (see [`super::RpcOpts::url`]).
25#[derive(Clone, Debug, Default, Serialize, Parser)]
26pub struct RpcCommonOpts {
27    /// The RPC endpoint.
28    #[arg(short, long, visible_alias = "fork-url", value_name = "URL")]
29    #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")]
30    pub rpc_url: Option<String>,
31
32    /// Allow insecure RPC connections (accept invalid HTTPS certificates).
33    ///
34    /// When the provider's inner runtime transport variant is HTTP, this configures the reqwest
35    /// client to accept invalid certificates.
36    #[arg(short = 'k', long = "insecure", default_value = "false")]
37    #[serde(skip)]
38    pub accept_invalid_certs: bool,
39
40    /// Timeout for the RPC request in seconds.
41    ///
42    /// The specified timeout will be used to override the default timeout for RPC requests.
43    ///
44    /// Default value: 45
45    #[arg(long, env = "ETH_RPC_TIMEOUT")]
46    #[serde(rename = "eth_rpc_timeout", skip_serializing_if = "Option::is_none")]
47    pub rpc_timeout: Option<u64>,
48
49    /// Disable automatic proxy detection.
50    ///
51    /// Use this in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) where
52    /// system proxy detection causes crashes. When enabled, HTTP_PROXY/HTTPS_PROXY environment
53    /// variables and system proxy settings will be ignored.
54    #[arg(long = "no-proxy", alias = "disable-proxy", default_value = "false")]
55    #[serde(skip)]
56    pub no_proxy: bool,
57
58    /// Sets the number of assumed available compute units per second for this provider.
59    ///
60    /// default value: 330
61    ///
62    /// See also <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
63    #[arg(long, alias = "cups", value_name = "CUPS")]
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub compute_units_per_second: Option<u64>,
66
67    /// Disables rate limiting for this node's provider.
68    ///
69    /// See also <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
70    #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rate-limit")]
71    #[serde(skip)]
72    pub no_rpc_rate_limit: bool,
73}
74
75impl figment::Provider for RpcCommonOpts {
76    fn metadata(&self) -> Metadata {
77        Metadata::named("RpcCommonOpts")
78    }
79
80    fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
81        Ok(Map::from([(Config::selected_profile(), self.dict())]))
82    }
83}
84
85impl RpcCommonOpts {
86    /// Returns the RPC endpoint URL, resolving from CLI args or config.
87    pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result<Option<Cow<'a, str>>> {
88        let url = match (self.rpc_url.as_deref(), config) {
89            (Some(url), _) => Some(Cow::Borrowed(url)),
90            (None, Some(config)) => config.get_rpc_url().transpose()?,
91            (None, None) => None,
92        };
93        Ok(url)
94    }
95
96    /// Builds a figment-compatible dictionary from these options.
97    pub fn dict(&self) -> Dict {
98        let mut dict = Dict::new();
99        if let Ok(Some(url)) = self.url(None) {
100            dict.insert("eth_rpc_url".into(), url.into_owned().into());
101        }
102        if let Some(rpc_timeout) = self.rpc_timeout {
103            dict.insert("eth_rpc_timeout".into(), rpc_timeout.into());
104        }
105        if self.accept_invalid_certs {
106            dict.insert("eth_rpc_accept_invalid_certs".into(), true.into());
107        }
108        if self.no_proxy {
109            dict.insert("eth_rpc_no_proxy".into(), true.into());
110        }
111        if let Some(cups) = self.compute_units_per_second {
112            dict.insert("compute_units_per_second".into(), cups.into());
113        }
114        if self.no_rpc_rate_limit {
115            dict.insert("no_rpc_rate_limit".into(), true.into());
116        }
117        dict
118    }
119}