use base_simulacrum::{AnvilInstance, Eip5792Engine, SendCallsParams};
use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "base-simulacrum")]
#[command(about = "Local EIP-5792 batch transaction tester for Base", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Run {
#[arg(short, long)]
calls: PathBuf,
#[arg(short, long, default_value = "false")]
sponsor: bool,
#[arg(long, default_value = "8453")]
chain_id: u64,
#[arg(short, long, default_value = "8545")]
port: u16,
#[arg(short, long)]
fork: Option<String>,
},
Status {
#[arg(short, long)]
batch_id: String,
#[arg(short, long, default_value = "http://127.0.0.1:8545")]
rpc: String,
},
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
match cli.command {
Commands::Run {
calls,
sponsor,
chain_id,
port,
fork,
} => {
run_batch(calls, sponsor, chain_id, port, fork).await?;
}
Commands::Status { batch_id, rpc } => {
check_status(batch_id, rpc).await?;
}
}
Ok(())
}
async fn run_batch(
calls_path: PathBuf,
sponsor: bool,
chain_id: u64,
port: u16,
fork: Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
println!("🚀 Base Simulacrum: Local EIP-5792 Batch Tester");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
let params: SendCallsParams = {
let content = tokio::fs::read_to_string(&calls_path).await?;
serde_json::from_str(&content)?
};
println!("📄 Loaded batch configuration:");
println!(" Version: {}", params.version);
println!(" Chain ID: {}", params.chain_id);
println!(" From: {}", params.from);
println!(" Calls: {}", params.calls.len());
println!();
let mut anvil = AnvilInstance::spawn(chain_id, port, fork).await?;
println!();
let engine = Eip5792Engine::new(anvil.rpc_url.clone(), sponsor);
println!(" Executing batch transaction...");
println!();
let batch_id = match engine.wallet_send_calls(params).await {
Ok(id) => {
println!();
println!("✓ Batch execution completed successfully");
println!(" Batch ID: {}", id);
id
}
Err(e) => {
eprintln!();
eprintln!("✗ Batch execution failed: {}", e);
anvil.kill()?;
return Err(e.into());
}
};
println!();
println!(" Fetching execution status...");
let status = engine.wallet_get_calls_status(&batch_id).await?;
println!(" Status: {:?}", status.status);
println!(" Receipts: {}", status.receipts.len());
for (idx, receipt) in status.receipts.iter().enumerate() {
println!();
println!(" Receipt {}:", idx + 1);
println!(" Status: {}", receipt.status);
println!(" Block: {}", receipt.block_number);
println!(" Gas Used: {}", receipt.gas_used);
println!(" Tx Hash: {}", receipt.transaction_hash);
}
println!();
anvil.kill()?;
println!();
println!("✓ Test completed successfully");
Ok(())
}
async fn check_status(batch_id: String, rpc: String) -> Result<(), Box<dyn std::error::Error>> {
println!(" Checking batch status...");
println!(" Batch ID: {}", batch_id);
println!(" RPC: {}", rpc);
println!();
let engine = Eip5792Engine::new(rpc, false);
let status = engine.wallet_get_calls_status(&batch_id).await?;
println!("Status: {:?}", status.status);
println!("Receipts: {}", status.receipts.len());
for (idx, receipt) in status.receipts.iter().enumerate() {
println!();
println!("Receipt {}:", idx + 1);
println!(" Status: {}", receipt.status);
println!(" Block: {}", receipt.block_number);
println!(" Gas Used: {}", receipt.gas_used);
println!(" Tx Hash: {}", receipt.transaction_hash);
}
Ok(())
}