use std::net::SocketAddr;
use clap::{Parser, Subcommand};
use log::error;
use soroban_fork::{server::Server, test_accounts, ForkConfig};
const DEFAULT_TEST_ACCOUNTS: usize = 10;
#[derive(Parser, Debug)]
#[command(
version,
about = "Lazy-loading mainnet/testnet fork for Soroban tests. Inspired by Foundry's Anvil."
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
Serve {
#[arg(
long,
default_value = "https://soroban-rpc.mainnet.stellar.gateway.fm",
env = "SOROBAN_FORK_RPC"
)]
rpc: String,
#[arg(long, default_value = "127.0.0.1:8000", env = "SOROBAN_FORK_LISTEN")]
listen: SocketAddr,
#[arg(long, env = "SOROBAN_FORK_CACHE")]
cache: Option<std::path::PathBuf>,
#[arg(long)]
tracing: bool,
#[arg(long, default_value_t = DEFAULT_TEST_ACCOUNTS)]
accounts: usize,
},
}
#[tokio::main]
async fn main() {
env_logger::Builder::from_env(
env_logger::Env::default().default_filter_or("soroban_fork=info"),
)
.init();
let cli = Cli::parse();
if let Err(e) = run(cli).await {
error!("{e}");
std::process::exit(1);
}
}
async fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
match cli.command {
Command::Serve {
rpc,
listen,
cache,
tracing,
accounts,
} => {
print_account_banner(accounts, listen);
let mut config = ForkConfig::new(rpc)
.tracing(tracing)
.test_account_count(accounts);
if let Some(path) = cache {
config = config.cache_file(path);
}
Server::builder(config).listen(listen).serve().await?;
Ok(())
}
}
}
fn print_account_banner(count: usize, listen: SocketAddr) {
println!("soroban-fork v{}", env!("CARGO_PKG_VERSION"));
println!("Listening on http://{listen}");
if count == 0 {
println!("(no pre-funded test accounts; pass --accounts N to enable)");
return;
}
println!();
println!("Available test accounts:");
let accounts = test_accounts::generate(count);
for (i, account) in accounts.iter().enumerate() {
let xlm = account.balance_stroops as f64 / 10_000_000.0;
println!(
"({i}) {} ({xlm:.7} XLM) -> {}",
account.account_strkey(),
account.secret_key_strkey()
);
}
println!();
}