diff --git a/CHANGELOG.md b/CHANGELOG.md index 018d0670e4..6e5b2fd53b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Debug logging for `sncast` commands that can be enabled by setting `CAST_LOG` env variable. - `sncast declare` command now outputs a ready-to-use deployment command after successful declaration. +- Support for `--network devnet` flag that attempts to auto-detect running `starknet-devnet` instance and connect to it. ## [0.50.0] - 2025-09-29 diff --git a/crates/sncast/src/helpers/block_explorer.rs b/crates/sncast/src/helpers/block_explorer.rs index 566fb1bf99..bd4ed8b39d 100644 --- a/crates/sncast/src/helpers/block_explorer.rs +++ b/crates/sncast/src/helpers/block_explorer.rs @@ -24,6 +24,7 @@ impl Service { (Service::ViewBlock, Network::Mainnet) => Ok(Box::new(ViewBlock)), (Service::OkLink, Network::Mainnet) => Ok(Box::new(OkLink)), (_, Network::Sepolia) => Err(ExplorerError::SepoliaNotSupported), + (_, Network::Devnet) => Err(ExplorerError::DevnetNotSupported), } } } @@ -36,8 +37,8 @@ pub trait LinkProvider { const fn network_subdomain(network: Network) -> &'static str { match network { - Network::Mainnet => "", Network::Sepolia => "sepolia.", + Network::Mainnet | Network::Devnet => "", } } diff --git a/crates/sncast/src/helpers/devnet_detection.rs b/crates/sncast/src/helpers/devnet_detection.rs new file mode 100644 index 0000000000..2802f95dde --- /dev/null +++ b/crates/sncast/src/helpers/devnet_detection.rs @@ -0,0 +1,312 @@ +use std::process::Command; + +#[derive(Debug, Clone)] +struct DevnetInfo { + host: String, + port: u16, +} + +#[derive(Debug)] +enum FindDevnetError { + None, + Multiple, + CommandFailed, +} + +pub fn detect_devnet_url() -> Result { + detect_devnet_from_processes() +} + +#[must_use] +pub fn is_devnet_running() -> bool { + detect_devnet_from_processes().is_ok() +} + +fn detect_devnet_from_processes() -> Result { + match find_devnet_process_info() { + Ok(info) => Ok(format!("http://{}:{}", info.host, info.port)), + Err(FindDevnetError::Multiple) => { + Err("Multiple starknet-devnet instances found. Please use --url to specify which one to use.".to_string()) + } + Err(FindDevnetError::None | FindDevnetError::CommandFailed) => { + // Fallback to default starknet-devnet URL if reachable + if is_port_reachable("127.0.0.1", 5050) { + Ok("http://127.0.0.1:5050".to_string()) + } else { + Err( + "Could not detect running starknet-devnet instance. Please use --url instead." + .to_string(), + ) + } + } + } +} + +fn find_devnet_process_info() -> Result { + let output = Command::new("ps") + .args(["aux"]) + .output() + .map_err(|_| FindDevnetError::CommandFailed)?; + let ps_output = String::from_utf8_lossy(&output.stdout); + + let devnet_processes: Vec = ps_output + .lines() + .filter(|line| line.contains("starknet-devnet")) + .map(|line| { + if line.contains("docker") || line.contains("podman") { + extract_devnet_info_from_docker_line(line) + } else { + extract_devnet_info_from_cmdline(line) + } + }) + .collect(); + + match devnet_processes.as_slice() { + [] => Err(FindDevnetError::None), + [single] => Ok(single.clone()), + _ => Err(FindDevnetError::Multiple), + } +} + +fn extract_string_from_flag(cmdline: &str, flag: &str) -> Option { + if let Some(pos) = cmdline.find(flag) { + let after_pattern = &cmdline[pos + flag.len()..]; + let value_str = after_pattern + .split_whitespace() + .next() + .unwrap_or("") + .trim_start_matches('=') + .trim_start_matches(':'); + + if !value_str.is_empty() { + return Some(value_str.to_string()); + } + } + None +} + +fn extract_port_from_flag(cmdline: &str, flag: &str) -> Option { + if let Some(port_str) = extract_string_from_flag(cmdline, flag) + && let Ok(p) = port_str.parse::() + && p > 1024 + && p < 65535 + { + return Some(p); + } + + None +} + +fn extract_docker_mapping(cmdline: &str) -> Option<(String, u16)> { + let port_flags = ["-p", "--publish"]; + + for flag in &port_flags { + if let Some(port_mapping) = extract_string_from_flag(cmdline, flag) { + let parts: Vec<&str> = port_mapping.split(':').collect(); + if parts.len() == 3 + && let Ok(host_port) = parts[1].parse::() + { + return Some((parts[0].to_string(), host_port)); + } else if parts.len() == 2 + && let Ok(host_port) = parts[0].parse::() + { + return Some(("127.0.0.1".to_string(), host_port)); + } + } + } + + None +} + +fn extract_devnet_info_from_docker_line(cmdline: &str) -> DevnetInfo { + let mut port = None; + let mut host = None; + + if let Some((docker_host, docker_port)) = extract_docker_mapping(cmdline) { + host = Some(docker_host); + port = Some(docker_port); + } + + if port.is_none() { + port = extract_port_from_flag(cmdline, "--port"); + } + + let final_host = host.unwrap_or_else(|| "127.0.0.1".to_string()); + let final_port = port.unwrap_or(5050); + + DevnetInfo { + host: final_host, + port: final_port, + } +} + +fn extract_devnet_info_from_cmdline(cmdline: &str) -> DevnetInfo { + let mut port = extract_port_from_flag(cmdline, "--port"); + let mut host = extract_string_from_flag(cmdline, "--host"); + + if port.is_none() + && let Ok(port_env) = std::env::var("PORT") + && let Ok(p) = port_env.parse::() + && p > 1024 + && p < 65535 + { + port = Some(p); + } + + if host.is_none() + && let Ok(host_env) = std::env::var("HOST") + && !host_env.is_empty() + { + host = Some(host_env); + } + + let final_port = port.unwrap_or(5050); + let final_host = host.unwrap_or_else(|| "127.0.0.1".to_string()); + + DevnetInfo { + host: final_host, + port: final_port, + } +} + +fn is_port_reachable(host: &str, port: u16) -> bool { + let url = format!("http://{host}:{port}/is_alive"); + + std::process::Command::new("curl") + .args(["-s", "-f", "--max-time", "1", &url]) + .output() + .map(|output| output.status.success()) + .unwrap_or(false) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::process::{Command, Stdio}; + use std::thread; + use std::time::{Duration, Instant}; + + // These tests are marked to run serially to avoid interference from environment variables + #[test] + fn test_devnet_parsing() { + test_extract_devnet_info_from_cmdline(); + + test_extract_devnet_info_from_docker_line(); + + test_extract_devnet_info_with_both_envs(); + + test_cmdline_args_override_env(); + + test_detect_devnet_url(); + } + + fn test_extract_devnet_info_from_cmdline() { + let cmdline1 = "starknet-devnet --port 6000 --host 127.0.0.1"; + let info1 = extract_devnet_info_from_cmdline(cmdline1); + assert_eq!(info1.port, 6000); + assert_eq!(info1.host, "127.0.0.1"); + + let cmdline2 = "/usr/bin/starknet-devnet --port=5000"; + let info2 = extract_devnet_info_from_cmdline(cmdline2); + assert_eq!(info2.port, 5000); + assert_eq!(info2.host, "127.0.0.1"); + + let cmdline3 = "starknet-devnet --host 127.0.0.1"; + let info3 = extract_devnet_info_from_cmdline(cmdline3); + assert_eq!(info3.port, 5050); + assert_eq!(info3.host, "127.0.0.1"); + } + + fn test_extract_devnet_info_from_docker_line() { + let cmdline1 = "docker run -p 127.0.0.1:5055:5050 shardlabs/starknet-devnet-rs"; + let info1 = extract_devnet_info_from_docker_line(cmdline1); + assert_eq!(info1.port, 5055); + assert_eq!(info1.host, "127.0.0.1"); + + let cmdline2 = "docker run --publish 8080:5050 shardlabs/starknet-devnet-rs"; + let info2 = extract_devnet_info_from_docker_line(cmdline2); + assert_eq!(info2.port, 8080); + assert_eq!(info2.host, "127.0.0.1"); + + let cmdline3 = "docker run --network host shardlabs/starknet-devnet-rs --port 5055"; + let info3 = extract_devnet_info_from_docker_line(cmdline3); + assert_eq!(info3.port, 5055); + assert_eq!(info3.host, "127.0.0.1"); + } + + fn test_extract_devnet_info_with_both_envs() { + // SAFETY: Variables are only modified within this test and cleaned up afterwards + unsafe { + std::env::set_var("PORT", "9999"); + std::env::set_var("HOST", "9.9.9.9"); + } + + let cmdline = "starknet-devnet"; + let info = extract_devnet_info_from_cmdline(cmdline); + assert_eq!(info.port, 9999); + assert_eq!(info.host, "9.9.9.9"); + + // SAFETY: Clean up environment variables to prevent interference + unsafe { + std::env::remove_var("PORT"); + std::env::remove_var("HOST"); + } + } + + fn test_cmdline_args_override_env() { + // SAFETY: Variables are only modified within this test and cleaned up afterwards + unsafe { + std::env::set_var("PORT", "3000"); + std::env::set_var("HOST", "7.7.7.7"); + } + + let cmdline = "starknet-devnet --port 9999 --host 192.168.1.1"; + let info = extract_devnet_info_from_cmdline(cmdline); + assert_eq!(info.port, 9999); + assert_eq!(info.host, "192.168.1.1"); + + // SAFETY: Clean up environment variables to prevent interference + unsafe { + std::env::remove_var("PORT"); + std::env::remove_var("HOST"); + } + } + + fn test_detect_devnet_url() { + let child = spawn_devnet("5090"); + + let result = detect_devnet_url().expect("Failed to detect devnet URL"); + assert_eq!(result, "http://127.0.0.1:5090"); + + cleanup_process(child); + } + + fn spawn_devnet(port: &str) -> std::process::Child { + let mut child = Command::new("starknet-devnet") + .args(["--port", port]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("Failed to spawn starknet-devnet process"); + + let port_num: u16 = port.parse().expect("Invalid port number"); + let start_time = Instant::now(); + let timeout = Duration::from_secs(10); + + while start_time.elapsed() < timeout { + if is_port_reachable("127.0.0.1", port_num) { + return child; + } + thread::sleep(Duration::from_millis(500)); + } + + let _ = child.kill(); + let _ = child.wait(); + panic!("Devnet did not start in time on port {port}"); + } + + fn cleanup_process(mut child: std::process::Child) { + child.kill().expect("Failed to kill devnet process"); + child.wait().expect("Failed to wait for devnet process"); + } +} diff --git a/crates/sncast/src/helpers/mod.rs b/crates/sncast/src/helpers/mod.rs index c3982de862..168d07a7fd 100644 --- a/crates/sncast/src/helpers/mod.rs +++ b/crates/sncast/src/helpers/mod.rs @@ -5,6 +5,7 @@ pub mod command; pub mod config; pub mod configuration; pub mod constants; +pub mod devnet_detection; pub mod devnet_provider; pub mod fee; pub mod interactive; diff --git a/crates/sncast/src/helpers/rpc.rs b/crates/sncast/src/helpers/rpc.rs index a91c7271a4..887a3ffb1b 100644 --- a/crates/sncast/src/helpers/rpc.rs +++ b/crates/sncast/src/helpers/rpc.rs @@ -1,12 +1,12 @@ use crate::helpers::configuration::CastConfig; +use crate::helpers::devnet_detection; use crate::{Network, get_provider}; -use anyhow::{Context, Result, bail}; +use anyhow::{Result, bail}; use clap::Args; use foundry_ui::UI; use shared::consts::RPC_URL_VERSION; use shared::verify_and_warn_if_incompatible_rpc_version; use starknet::providers::{JsonRpcClient, jsonrpc::HttpTransport}; -use url::Url; #[derive(Args, Clone, Debug, Default)] #[group(required = false, multiple = false)] @@ -15,7 +15,8 @@ pub struct RpcArgs { #[arg(short, long)] pub url: Option, - /// Use predefined network with a public provider. Note that this option may result in rate limits or other unexpected behavior + /// Use predefined network with a public provider. Note that this option may result in rate limits or other unexpected behavior. + /// For devnet, attempts to auto-detect running starknet-devnet instance. If auto-detection fails, use --url instead. #[arg(long)] pub network: Option, } @@ -32,9 +33,7 @@ impl RpcArgs { ) } - let url = self - .get_url(&config.url) - .context("Either `--network` or `--url` must be provided")?; + let url = self.get_url(&config.url)?; assert!(!url.is_empty(), "url cannot be empty"); let provider = get_provider(&url)?; @@ -44,29 +43,17 @@ impl RpcArgs { Ok(provider) } - #[must_use] - pub fn get_url(&self, config_url: &String) -> Option { - if let Some(network) = self.network { - let free_provider = FreeProvider::semi_random(); - Some(network.url(&free_provider)) - } else { - self.url.clone().or_else(|| { - if config_url.is_empty() { - None - } else { - Some(config_url.to_string()) - } - }) + pub fn get_url(&self, config_url: &str) -> Result { + match (&self.network, &self.url, config_url.is_empty()) { + (Some(network), None, _) => { + let free_provider = FreeProvider::semi_random(); + network.url(&free_provider) + } + (None, Some(url), _) => Ok(url.clone()), + (None, None, false) => Ok(config_url.to_string()), + _ => bail!("Either `--network` or `--url` must be provided"), } } - - #[must_use] - pub fn is_localhost(&self, config_url: &String) -> bool { - self.get_url(config_url) - .and_then(|url_str| Url::parse(&url_str).ok()) - .and_then(|url| url.host_str().map(str::to_string)) - .is_some_and(|host| host == "localhost" || host == "127.0.0.1" || host == "::1") - } } pub enum FreeProvider { @@ -81,11 +68,11 @@ impl FreeProvider { } impl Network { - #[must_use] - pub fn url(self, provider: &FreeProvider) -> String { + pub fn url(self, provider: &FreeProvider) -> Result { match self { - Network::Mainnet => Self::free_mainnet_rpc(provider), - Network::Sepolia => Self::free_sepolia_rpc(provider), + Network::Mainnet => Ok(Self::free_mainnet_rpc(provider)), + Network::Sepolia => Ok(Self::free_sepolia_rpc(provider)), + Network::Devnet => Self::devnet_rpc(provider), } } @@ -96,6 +83,10 @@ impl Network { fn free_sepolia_rpc(_provider: &FreeProvider) -> String { format!("https://starknet-sepolia.public.blastapi.io/rpc/{RPC_URL_VERSION}") } + + fn devnet_rpc(_provider: &FreeProvider) -> Result { + devnet_detection::detect_devnet_url().map_err(|e| anyhow::anyhow!(e)) + } } #[must_use] diff --git a/crates/sncast/src/lib.rs b/crates/sncast/src/lib.rs index 606624bbeb..330eca7ed4 100644 --- a/crates/sncast/src/lib.rs +++ b/crates/sncast/src/lib.rs @@ -89,10 +89,14 @@ pub const MAINNET: Felt = pub const SEPOLIA: Felt = Felt::from_hex_unchecked(const_hex::const_encode::<10, true>(b"SN_SEPOLIA").as_str()); +pub const DEVNET: Felt = + Felt::from_hex_unchecked(const_hex::const_encode::<6, true>(b"SN_DEV").as_str()); + #[derive(ValueEnum, Clone, Copy, Debug, PartialEq)] pub enum Network { Mainnet, Sepolia, + Devnet, } impl Display for Network { @@ -100,6 +104,7 @@ impl Display for Network { match self { Network::Mainnet => write!(f, "mainnet"), Network::Sepolia => write!(f, "sepolia"), + Network::Devnet => write!(f, "devnet"), } } } @@ -112,6 +117,8 @@ impl TryFrom for Network { Ok(Network::Mainnet) } else if value == SEPOLIA { Ok(Network::Sepolia) + } else if value == DEVNET { + Ok(Network::Devnet) } else { bail!("Given network is neither Mainnet nor Sepolia") } diff --git a/crates/sncast/src/main.rs b/crates/sncast/src/main.rs index 1f80014308..b27c360e51 100644 --- a/crates/sncast/src/main.rs +++ b/crates/sncast/src/main.rs @@ -310,7 +310,7 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> }); let block_explorer_link = - block_explorer_link_if_allowed(&result, provider.chain_id().await?, &rpc, &config); + block_explorer_link_if_allowed(&result, provider.chain_id().await?, &config); let deploy_command_message = if let Ok(response) = &result { // TODO(#3785) @@ -321,7 +321,7 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> serde_json::from_str(&contract_artifacts.sierra) .context("Failed to parse sierra artifact")?; let network_flag = generate_network_flag( - rpc.get_url(&config.url).as_deref(), + rpc.get_url(&config.url).ok().as_deref(), rpc.network.as_ref(), ); Some(DeployCommandMessage::new( @@ -346,7 +346,6 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> Commands::DeclareFrom(declare_from) => { let provider = declare_from.rpc.get_provider(&config, ui).await?; - let rpc_args = declare_from.rpc.clone(); let source_provider = declare_from.source_rpc.get_provider(ui).await?; let account = get_account( @@ -377,12 +376,8 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> } }); - let block_explorer_link = block_explorer_link_if_allowed( - &result, - provider.chain_id().await?, - &rpc_args, - &config, - ); + let block_explorer_link = + block_explorer_link_if_allowed(&result, provider.chain_id().await?, &config); process_command_result("declare-from", result, ui, block_explorer_link); Ok(()) @@ -424,7 +419,7 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> .map_err(handle_starknet_command_error); let block_explorer_link = - block_explorer_link_if_allowed(&result, provider.chain_id().await?, &rpc, &config); + block_explorer_link_if_allowed(&result, provider.chain_id().await?, &config); process_command_result("deploy", result, ui, block_explorer_link); Ok(()) @@ -507,7 +502,7 @@ async fn run_async_command(cli: Cli, config: CastConfig, ui: &UI) -> Result<()> .map_err(handle_starknet_command_error); let block_explorer_link = - block_explorer_link_if_allowed(&result, provider.chain_id().await?, &rpc, &config); + block_explorer_link_if_allowed(&result, provider.chain_id().await?, &config); process_command_result("invoke", result, ui, block_explorer_link); diff --git a/crates/sncast/src/response/explorer_link.rs b/crates/sncast/src/response/explorer_link.rs index 35da81d18a..a9aa0367d0 100644 --- a/crates/sncast/src/response/explorer_link.rs +++ b/crates/sncast/src/response/explorer_link.rs @@ -1,4 +1,5 @@ -use crate::helpers::{block_explorer::LinkProvider, configuration::CastConfig, rpc::RpcArgs}; +use crate::Network; +use crate::helpers::{block_explorer::LinkProvider, configuration::CastConfig, devnet_detection}; use foundry_ui::Message; use serde::Serialize; use serde_json::{Value, json}; @@ -48,29 +49,30 @@ pub enum ExplorerError { SepoliaNotSupported, #[error("Custom network is not recognized by block explorer service")] UnrecognizedNetwork, + #[error("Block explorer service is not available for Devnet Network")] + DevnetNotSupported, } pub fn block_explorer_link_if_allowed( result: &anyhow::Result, chain_id: Felt, - rpc: &RpcArgs, config: &CastConfig, ) -> Option where T: OutputLink + Clone, { - if (!config.show_explorer_links || rpc.is_localhost(&config.url)) - && !is_explorer_link_overridden() - { - return None; - } - let Ok(response) = result else { return None; }; let network = chain_id.try_into().ok()?; + let is_devnet = matches!(network, Network::Devnet) || devnet_detection::is_devnet_running(); + + if (!config.show_explorer_links || is_devnet) && !is_explorer_link_overridden() { + return None; + } + config .block_explorer .unwrap_or_default() diff --git a/crates/sncast/src/starknet_commands/account/mod.rs b/crates/sncast/src/starknet_commands/account/mod.rs index 5e3db05867..7c53de2f6c 100644 --- a/crates/sncast/src/starknet_commands/account/mod.rs +++ b/crates/sncast/src/starknet_commands/account/mod.rs @@ -257,12 +257,8 @@ pub async fn account( ) .await; - let block_explorer_link = block_explorer_link_if_allowed( - &result, - provider.chain_id().await?, - &create.rpc, - &config, - ); + let block_explorer_link = + block_explorer_link_if_allowed(&result, provider.chain_id().await?, &config); process_command_result("account create", result, ui, block_explorer_link); Ok(()) @@ -305,12 +301,8 @@ pub async fn account( )); } - let block_explorer_link = block_explorer_link_if_allowed( - &result, - provider.chain_id().await?, - &deploy.rpc, - &config, - ); + let block_explorer_link = + block_explorer_link_if_allowed(&result, provider.chain_id().await?, &config); process_command_result("account deploy", result, ui, block_explorer_link); Ok(()) } diff --git a/crates/sncast/src/starknet_commands/declare_from.rs b/crates/sncast/src/starknet_commands/declare_from.rs index b2b68a8204..ec4d5c7563 100644 --- a/crates/sncast/src/starknet_commands/declare_from.rs +++ b/crates/sncast/src/starknet_commands/declare_from.rs @@ -74,7 +74,7 @@ impl SourceRpcArgs { fn get_url(&self) -> Option { if let Some(network) = self.source_network { let free_provider = FreeProvider::semi_random(); - Some(network.url(&free_provider)) + Some(network.url(&free_provider).ok()?) } else { self.source_url .as_ref() diff --git a/crates/sncast/src/starknet_commands/multicall/mod.rs b/crates/sncast/src/starknet_commands/multicall/mod.rs index 5bad0bc618..549154e8aa 100644 --- a/crates/sncast/src/starknet_commands/multicall/mod.rs +++ b/crates/sncast/src/starknet_commands/multicall/mod.rs @@ -57,12 +57,8 @@ pub async fn multicall( starknet_commands::multicall::run::run(run.clone(), &account, wait_config, ui) .await; - let block_explorer_link = block_explorer_link_if_allowed( - &result, - provider.chain_id().await?, - &run.rpc, - &config, - ); + let block_explorer_link = + block_explorer_link_if_allowed(&result, provider.chain_id().await?, &config); process_command_result("multicall run", result, ui, block_explorer_link); Ok(()) } diff --git a/crates/sncast/src/starknet_commands/verify/mod.rs b/crates/sncast/src/starknet_commands/verify/mod.rs index 2d18d43aec..c087c6db23 100644 --- a/crates/sncast/src/starknet_commands/verify/mod.rs +++ b/crates/sncast/src/starknet_commands/verify/mod.rs @@ -142,7 +142,7 @@ pub async fn verify( if config.url.is_empty() { let network = network.ok_or_else(|| anyhow!("Either --network or --url must be provided"))?; - let free_rpc_provider = network.url(&FreeProvider::semi_random()); + let free_rpc_provider = network.url(&FreeProvider::semi_random())?; Url::parse(&free_rpc_provider)? } else { Url::parse(&config.url)? diff --git a/crates/sncast/src/starknet_commands/verify/voyager.rs b/crates/sncast/src/starknet_commands/verify/voyager.rs index 6229cbd458..79bf1e802f 100644 --- a/crates/sncast/src/starknet_commands/verify/voyager.rs +++ b/crates/sncast/src/starknet_commands/verify/voyager.rs @@ -392,6 +392,7 @@ impl<'a> VerificationInterface<'a> for Voyager<'a> { Err(_) => match self.network { Network::Mainnet => "https://api.voyager.online/beta".to_string(), Network::Sepolia => "https://sepolia-api.voyager.online/beta".to_string(), + Network::Devnet => String::new(), }, } } diff --git a/crates/sncast/src/starknet_commands/verify/walnut.rs b/crates/sncast/src/starknet_commands/verify/walnut.rs index 790f766fa9..946d77feab 100644 --- a/crates/sncast/src/starknet_commands/verify/walnut.rs +++ b/crates/sncast/src/starknet_commands/verify/walnut.rs @@ -114,6 +114,7 @@ impl VerificationInterface<'_> for WalnutVerificationInterface { let path = match self.network { Network::Mainnet => "/v1/sn_main/verify", Network::Sepolia => "/v1/sn_sepolia/verify", + Network::Devnet => "", }; format!("{api_base_url}{path}") } diff --git a/docs/src/appendix/sncast/account/create.md b/docs/src/appendix/sncast/account/create.md index e6cb0d631a..52b6cd87b4 100644 --- a/docs/src/appendix/sncast/account/create.md +++ b/docs/src/appendix/sncast/account/create.md @@ -23,7 +23,7 @@ Optional. Use predefined network with a public provider -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. ## `--type, -t ` Optional. Required if `--class-hash` is passed. diff --git a/docs/src/appendix/sncast/account/delete.md b/docs/src/appendix/sncast/account/delete.md index 3dfadee9ae..7f419405ec 100644 --- a/docs/src/appendix/sncast/account/delete.md +++ b/docs/src/appendix/sncast/account/delete.md @@ -18,7 +18,7 @@ Optional. Use predefined network with a public provider -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. ## `--network-name` Optional. diff --git a/docs/src/appendix/sncast/account/deploy.md b/docs/src/appendix/sncast/account/deploy.md index 7d51c45419..01095cbdaa 100644 --- a/docs/src/appendix/sncast/account/deploy.md +++ b/docs/src/appendix/sncast/account/deploy.md @@ -18,7 +18,7 @@ Optional. Use predefined network with a public provider -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. ## `--max-fee, -m ` Optional. diff --git a/docs/src/appendix/sncast/account/import.md b/docs/src/appendix/sncast/account/import.md index 474c52d89a..e3d21d39a8 100644 --- a/docs/src/appendix/sncast/account/import.md +++ b/docs/src/appendix/sncast/account/import.md @@ -37,7 +37,7 @@ Optional. Use predefined network with public provider -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. ## `--class-hash, -c ` Optional. diff --git a/docs/src/appendix/sncast/call.md b/docs/src/appendix/sncast/call.md index d7ad3daa89..5acae1a729 100644 --- a/docs/src/appendix/sncast/call.md +++ b/docs/src/appendix/sncast/call.md @@ -23,7 +23,7 @@ Optional. Use predefined network with public provider -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. ## `--calldata, -c ` Optional. diff --git a/docs/src/appendix/sncast/declare.md b/docs/src/appendix/sncast/declare.md index a1d6e51193..cb94a52c6f 100644 --- a/docs/src/appendix/sncast/declare.md +++ b/docs/src/appendix/sncast/declare.md @@ -22,7 +22,7 @@ Optional. Use predefined network with public provider -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. ## `--max-fee, -m ` Optional. diff --git a/docs/src/appendix/sncast/declare_from.md b/docs/src/appendix/sncast/declare_from.md index fc8a380d3c..57cf1847c1 100644 --- a/docs/src/appendix/sncast/declare_from.md +++ b/docs/src/appendix/sncast/declare_from.md @@ -22,7 +22,7 @@ Optional. Use predefined network with public provider -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. ## `--source-url, -u ` Optional. @@ -34,7 +34,7 @@ Optional. Use predefined network with public provider where the contract is already declared. -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. ## `--max-fee, -m ` Optional. diff --git a/docs/src/appendix/sncast/deploy.md b/docs/src/appendix/sncast/deploy.md index 8eacc86f0d..63077d3f78 100644 --- a/docs/src/appendix/sncast/deploy.md +++ b/docs/src/appendix/sncast/deploy.md @@ -22,7 +22,7 @@ Optional. Use predefined network with public provider -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. ## `--constructor-calldata, -c ` Optional. diff --git a/docs/src/appendix/sncast/invoke.md b/docs/src/appendix/sncast/invoke.md index 395862e55b..616f9a1d29 100644 --- a/docs/src/appendix/sncast/invoke.md +++ b/docs/src/appendix/sncast/invoke.md @@ -43,7 +43,7 @@ Optional. Use predefined network with public provider -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. ## `--max-fee, -m ` Optional. diff --git a/docs/src/appendix/sncast/multicall/run.md b/docs/src/appendix/sncast/multicall/run.md index c81ca9b3e3..102513d1bd 100644 --- a/docs/src/appendix/sncast/multicall/run.md +++ b/docs/src/appendix/sncast/multicall/run.md @@ -23,7 +23,7 @@ Optional. Use predefined network with public provider -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. ## `--max-fee, -m ` Optional. diff --git a/docs/src/appendix/sncast/script/run.md b/docs/src/appendix/sncast/script/run.md index 3c9aa0ed80..2e2941117a 100644 --- a/docs/src/appendix/sncast/script/run.md +++ b/docs/src/appendix/sncast/script/run.md @@ -22,7 +22,7 @@ Optional. Use predefined network with public provider -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. ## `--package ` Optional. diff --git a/docs/src/appendix/sncast/show_config.md b/docs/src/appendix/sncast/show_config.md index 5a95a404d5..a29f52f7bb 100644 --- a/docs/src/appendix/sncast/show_config.md +++ b/docs/src/appendix/sncast/show_config.md @@ -13,4 +13,4 @@ Optional. Use predefined network with public provider -Possible values: `mainnet`, `sepolia`. \ No newline at end of file +Possible values: `mainnet`, `sepolia`, `devnet`. \ No newline at end of file diff --git a/docs/src/appendix/sncast/tx-status.md b/docs/src/appendix/sncast/tx-status.md index 8ebf1a3cfe..8692273df5 100644 --- a/docs/src/appendix/sncast/tx-status.md +++ b/docs/src/appendix/sncast/tx-status.md @@ -20,4 +20,4 @@ Optional. Use predefined network with public provider -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. diff --git a/docs/src/appendix/sncast/utils/serialize.md b/docs/src/appendix/sncast/utils/serialize.md index 3f58c4f5b4..976ea86106 100644 --- a/docs/src/appendix/sncast/utils/serialize.md +++ b/docs/src/appendix/sncast/utils/serialize.md @@ -44,4 +44,4 @@ Optional. Use predefined network with public provider -Possible values: `mainnet`, `sepolia`. +Possible values: `mainnet`, `sepolia`, `devnet`. diff --git a/docs/src/starknet/sncast-overview.md b/docs/src/starknet/sncast-overview.md index ead7935205..52cb7aec45 100644 --- a/docs/src/starknet/sncast-overview.md +++ b/docs/src/starknet/sncast-overview.md @@ -56,8 +56,16 @@ Response: [0x0, 0x0, 0x43686172697a617264, 0x9, 0x0, 0x0, 0x41a78e741e5af2fec34b ### Network and RPC Providers -When providing `--network` flag, `sncast` will randomly select on of the free RPC providers. -When using free provider you may experience rate limits and other unexpected behavior. +The `--network` flag supports the following networks: + +- **mainnet** - Connects to Starknet mainnet using a free RPC provider +- **sepolia** - Connects to Starknet Sepolia testnet using a free RPC provider +- **devnet** - Attempts to auto-detect running starknet-devnet instances + +When using **mainnet** or **sepolia**, `sncast` will randomly select one of the free RPC providers. +When using free providers you may experience rate limits and other unexpected behavior. + +For **devnet**, `sncast` will try to detect running `starknet-devnet` instance and connect to it, but it may fail. If detection fails, then use the `--url` flag instead. If using `sncast` extensively, we recommend getting access to a dedicated RPC node and providing its URL to sncast with `--url` flag.