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#[derive(Clone, Debug, Default, Serialize, Parser)]
21pub struct RpcCommonOpts {
22    /// The RPC endpoint.
23    #[arg(short, long, visible_alias = "fork-url", env = "ETH_RPC_URL")]
24    #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")]
25    pub rpc_url: Option<String>,
26
27    /// Allow insecure RPC connections (accept invalid HTTPS certificates).
28    ///
29    /// When the provider's inner runtime transport variant is HTTP, this configures the reqwest
30    /// client to accept invalid certificates.
31    #[arg(short = 'k', long = "insecure", default_value = "false")]
32    #[serde(skip)]
33    pub accept_invalid_certs: bool,
34
35    /// Timeout for the RPC request in seconds.
36    ///
37    /// The specified timeout will be used to override the default timeout for RPC requests.
38    ///
39    /// Default value: 45
40    #[arg(long, env = "ETH_RPC_TIMEOUT")]
41    #[serde(rename = "eth_rpc_timeout", skip_serializing_if = "Option::is_none")]
42    pub rpc_timeout: Option<u64>,
43
44    /// Disable automatic proxy detection.
45    ///
46    /// Use this in sandboxed environments (e.g., Cursor IDE sandbox, macOS App Sandbox) where
47    /// system proxy detection causes crashes. When enabled, HTTP_PROXY/HTTPS_PROXY environment
48    /// variables and system proxy settings will be ignored.
49    #[arg(long = "no-proxy", alias = "disable-proxy", default_value = "false")]
50    #[serde(skip)]
51    pub no_proxy: bool,
52
53    /// Sets the number of assumed available compute units per second for this provider.
54    ///
55    /// default value: 330
56    ///
57    /// See also <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
58    #[arg(long, alias = "cups", value_name = "CUPS")]
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub compute_units_per_second: Option<u64>,
61
62    /// Disables rate limiting for this node's provider.
63    ///
64    /// See also <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
65    #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rate-limit")]
66    #[serde(skip)]
67    pub no_rpc_rate_limit: bool,
68}
69
70impl figment::Provider for RpcCommonOpts {
71    fn metadata(&self) -> Metadata {
72        Metadata::named("RpcCommonOpts")
73    }
74
75    fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
76        Ok(Map::from([(Config::selected_profile(), self.dict())]))
77    }
78}
79
80impl RpcCommonOpts {
81    /// Returns the RPC endpoint URL, resolving from CLI args or config.
82    pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result<Option<Cow<'a, str>>> {
83        let url = match (self.rpc_url.as_deref(), config) {
84            (Some(url), _) => Some(Cow::Borrowed(url)),
85            (None, Some(config)) => config.get_rpc_url().transpose()?,
86            (None, None) => None,
87        };
88        Ok(url)
89    }
90
91    /// Builds a figment-compatible dictionary from these options.
92    pub fn dict(&self) -> Dict {
93        let mut dict = Dict::new();
94        if let Ok(Some(url)) = self.url(None) {
95            dict.insert("eth_rpc_url".into(), url.into_owned().into());
96        }
97        if let Some(rpc_timeout) = self.rpc_timeout {
98            dict.insert("eth_rpc_timeout".into(), rpc_timeout.into());
99        }
100        if self.accept_invalid_certs {
101            dict.insert("eth_rpc_accept_invalid_certs".into(), true.into());
102        }
103        if self.no_proxy {
104            dict.insert("eth_rpc_no_proxy".into(), true.into());
105        }
106        if let Some(cups) = self.compute_units_per_second {
107            dict.insert("compute_units_per_second".into(), cups.into());
108        }
109        if self.no_rpc_rate_limit {
110            dict.insert("no_rpc_rate_limit".into(), true.into());
111        }
112        dict
113    }
114}