Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 28 additions & 7 deletions src/commands/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::commands::info;
use crate::utils::{config, horizon, print as p};
use crate::utils::{config, horizon, optimizer, print as p};
use anyhow::Result;
use clap::Args;
use colored::*;
Expand All @@ -20,6 +19,9 @@ pub struct DeployArgs {
/// Wallet name to use for deployment
#[arg(long)]
pub wallet: Option<String>,
/// Optimize the WASM before deployment using the built-in optimizer
#[arg(long, default_value = "false")]
pub optimize: bool,
/// Skip confirmation prompt
#[arg(long, default_value = "false")]
pub yes: bool,
Expand Down Expand Up @@ -71,12 +73,30 @@ pub fn handle(args: DeployArgs) -> Result<()> {
);
}

let wasm_bytes = fs::read(&args.wasm)?;
let wasm_size_kb = wasm_bytes.len() as f64 / 1024.0;
let wasm_hash = compute_local_wasm_hash(&wasm_bytes);
let mut wasm_path = args.wasm.clone();
let mut wasm_bytes = fs::read(&wasm_path)?;
let mut wasm_size_kb = wasm_bytes.len() as f64 / 1024.0;

if args.optimize {
let optimized_path = args
.wasm
.with_file_name(format!("{}-optimized.wasm", args.wasm.file_stem().unwrap_or_default().to_string_lossy()));
p::header("WASM Optimization");
p::kv("Input WASM", &args.wasm.display().to_string());
p::kv("Output WASM", &optimized_path.display().to_string());
let result = optimizer::optimize_wasm(&args.wasm, &optimized_path)?;
wasm_path = optimized_path;
wasm_bytes = fs::read(&wasm_path)?;
wasm_size_kb = wasm_bytes.len() as f64 / 1024.0;
println!();
p::success("Optimization pass completed");
p::kv("Input size", &format!("{} bytes", result.input_size_bytes));
p::kv("Output size", &format!("{} bytes", result.output_size_bytes));
p::separator();
}

p::separator();
p::kv("WASM file", &args.wasm.display().to_string());
p::kv("WASM file", &wasm_path.display().to_string());
p::kv("WASM size", &format!("{:.1} KB", wasm_size_kb));
p::kv("Network", &args.network);

Expand All @@ -85,6 +105,7 @@ pub fn handle(args: DeployArgs) -> Result<()> {
"WASM is {:.1} KB - Soroban limit is 128 KB. Optimize with --release.",
wasm_size_kb
));
p::info("If this contract is still too large, use `starforge gas optimize --target <input>.wasm --output <output>.wasm` or external tools such as `wasm-opt -Oz`.");
}

let cfg = config::load()?;
Expand Down Expand Up @@ -193,7 +214,7 @@ pub fn handle(args: DeployArgs) -> Result<()> {
"Ready! Run this to complete the deployment:".bright_white()
);
println!();
let deploy_cmd = build_stellar_deploy_command(&args.wasm, &wallet.public_key, &args.network);
let deploy_cmd = build_stellar_deploy_command(&wasm_path, &wallet.public_key, &args.network);
for line in deploy_cmd.lines() {
println!(" {}", line.cyan());
}
Expand Down
19 changes: 17 additions & 2 deletions src/commands/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ pub enum NetworkCommands {
/// Optional Soroban RPC URL
#[arg(long)]
soroban_rpc_url: Option<String>,
/// Optional network faucet / Friendbot URL
#[arg(long)]
friendbot_url: Option<String>,
},
/// Test connectivity to a network
Test {
Expand Down Expand Up @@ -51,6 +54,9 @@ fn show() -> Result<()> {
if let Some(soroban_url) = &net_cfg.soroban_rpc_url {
p::kv("Soroban RPC", soroban_url);
}
if let Some(friendbot_url) = &net_cfg.friendbot_url {
p::kv("Friendbot", friendbot_url);
}
println!();
}

Expand Down Expand Up @@ -91,7 +97,7 @@ fn switch(target: String) -> Result<()> {
Ok(())
}

fn add_network(name: String, horizon_url: String, soroban_rpc_url: Option<String>) -> Result<()> {
fn add_network(name: String, horizon_url: String, soroban_rpc_url: Option<String>, friendbot_url: Option<String>) -> Result<()> {
let mut cfg = config::load()?;

if !horizon_url.starts_with("http://") && !horizon_url.starts_with("https://") {
Expand All @@ -104,14 +110,23 @@ fn add_network(name: String, horizon_url: String, soroban_rpc_url: Option<String
}
}

config::add_custom_network(&mut cfg, name.clone(), horizon_url.clone(), soroban_rpc_url.clone())?;
if let Some(ref url) = friendbot_url {
if !url.starts_with("http://") && !url.starts_with("https://") {
anyhow::bail!("Friendbot URL must start with http:// or https://");
}
}

config::add_custom_network(&mut cfg, name.clone(), horizon_url.clone(), soroban_rpc_url.clone(), friendbot_url.clone())?;
config::save(&cfg)?;

p::success(&format!("Network '{}' added successfully", name));
p::kv("Horizon", &horizon_url);
if let Some(url) = soroban_rpc_url {
p::kv("Soroban RPC", &url);
}
if let Some(url) = friendbot_url {
p::kv("Friendbot", &url);
}
Ok(())
}

Expand Down
24 changes: 14 additions & 10 deletions src/commands/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub enum WalletCommands {
Create {
/// A friendly name for the wallet (e.g. "alice", "deployer")
name: String,
/// Fund the wallet via Friendbot immediately (testnet only)
/// Fund the wallet via network-specific faucet immediately when available
#[arg(long, default_value = "false")]
fund: bool,
/// Network to associate with this wallet (overrides global config)
Expand All @@ -69,7 +69,7 @@ pub enum WalletCommands {
#[arg(long, default_value = "false")]
reveal: bool,
},
/// Fund a wallet via Friendbot (testnet only)
/// Fund a wallet via a configured network faucet
Fund {
/// Wallet name to fund
name: String,
Expand Down Expand Up @@ -389,16 +389,17 @@ fn create(name: String, fund: bool, network_override: Option<String>, encrypt: b
cfg.wallets.push(wallet);

if fund {
if network == "mainnet" {
let net_cfg = config::get_network_config(&cfg, &network)?;
if net_cfg.friendbot_url.is_none() && network == "mainnet" {
p::warn("Friendbot is not available on Mainnet. Skipping fund step.");
} else {
p::step(3, steps, "Funding via Friendbot…");
match horizon::fund_account(&public_key) {
p::step(3, steps, "Funding via network faucet…");
match horizon::fund_account(&public_key, &network) {
Ok(_) => {
if let Some(w) = cfg.wallets.iter_mut().find(|w| w.name == name) {
w.funded = true;
}
p::success("Funded with 10,000 XLM on testnet");
p::success("Account funded via configured faucet");
}
Err(e) => p::warn(&format!("Funding failed: {}", e)),
}
Expand Down Expand Up @@ -524,7 +525,10 @@ fn fund_wallet(name: String) -> Result<()> {
let mut cfg = config::load()?;

if cfg.network == "mainnet" {
anyhow::bail!("Friendbot is not available on Mainnet.");
let net_cfg = config::get_network_config(&cfg, &cfg.network)?;
if net_cfg.friendbot_url.is_none() {
anyhow::bail!("Friendbot is not available on Mainnet.");
}
}

let public_key = cfg
Expand All @@ -534,8 +538,8 @@ fn fund_wallet(name: String) -> Result<()> {
.map(|w| w.public_key.clone())
.ok_or_else(|| anyhow::anyhow!("Wallet '{}' not found", name))?;

p::info(&format!("Funding '{}' via Friendbot…", name));
horizon::fund_account(&public_key)?;
p::info(&format!("Funding '{}' via configured network faucet…", name));
horizon::fund_account(&public_key, &cfg.network)?;

if let Some(w) = cfg.wallets.iter_mut().find(|w| w.name == name) {
w.funded = true;
Expand Down Expand Up @@ -1094,4 +1098,4 @@ fn multisig_submit(name: String, transaction: PathBuf, network: Option<String>)
));
Ok(())
}

54 changes: 29 additions & 25 deletions src/utils/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,17 @@ pub fn validate_file_path(path: &std::path::Path, expected_ext: Option<&str>) ->
pub fn validate_network(network: &str) -> Result<()> {
match network {
"testnet" | "mainnet" | "docker-testnet" => Ok(()),
_ => anyhow::bail!(
"Unsupported network '{}'. Use 'testnet', 'mainnet', or 'docker-testnet'.",
network
),
_ => {
let cfg = load()?;
if cfg.networks.contains_key(network) {
Ok(())
} else {
anyhow::bail!(
"Unsupported network '{}'. Use 'testnet', 'mainnet', 'docker-testnet', or a configured custom network.",
network
)
}
}
}
}

Expand Down Expand Up @@ -174,6 +181,7 @@ fn default_version() -> String {
pub struct NetworkConfig {
pub horizon_url: String,
pub soroban_rpc_url: Option<String>,
pub friendbot_url: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
Expand All @@ -199,25 +207,22 @@ pub struct WalletRotationRecord {
impl Default for Config {
fn default() -> Self {
let mut networks = HashMap::new();
networks.insert(
"testnet".to_string(),
NetworkConfig {
horizon_url: "https://horizon-testnet.stellar.org".to_string(),
soroban_rpc_url: Some("https://soroban-testnet.stellar.org".to_string()),
},
);
networks.insert(
"mainnet".to_string(),
NetworkConfig {
horizon_url: "https://horizon.stellar.org".to_string(),
soroban_rpc_url: Some("https://mainnet.sorobanrpc.com".to_string()),
},
);
networks.insert("testnet".to_string(), NetworkConfig {
horizon_url: "https://horizon-testnet.stellar.org".to_string(),
soroban_rpc_url: Some("https://soroban-testnet.stellar.org".to_string()),
friendbot_url: Some("https://friendbot.stellar.org".to_string()),
});
networks.insert("mainnet".to_string(), NetworkConfig {
horizon_url: "https://horizon.stellar.org".to_string(),
soroban_rpc_url: Some("https://mainnet.sorobanrpc.com".to_string()),
friendbot_url: None,
});
networks.insert(
"docker-testnet".to_string(),
NetworkConfig {
horizon_url: "http://localhost:8000".to_string(),
soroban_rpc_url: Some("http://localhost:8000/rpc".to_string()),
friendbot_url: None,
},
);

Expand Down Expand Up @@ -470,17 +475,16 @@ pub fn add_custom_network(
name: String,
horizon_url: String,
soroban_rpc_url: Option<String>,
friendbot_url: Option<String>,
) -> Result<()> {
if config.networks.contains_key(&name) {
anyhow::bail!("Network '{}' already exists", name);
}
config.networks.insert(
name,
NetworkConfig {
horizon_url,
soroban_rpc_url,
},
);
config.networks.insert(name, NetworkConfig {
horizon_url,
soroban_rpc_url,
friendbot_url,
});
Ok(())
}

Loading