1use std::process::{Child, Command, Stdio};
7use std::time::Duration;
8use thiserror::Error;
9use tokio::time::sleep;
10
11#[derive(Error, Debug)]
12pub enum AnvilError {
13 #[error("Failed to spawn anvil process: {0}")]
14 SpawnFailed(String),
15 #[error("Anvil process failed to start within timeout")]
16 StartupTimeout,
17 #[error("Failed to kill anvil process: {0}")]
18 KillFailed(String),
19}
20
21pub struct AnvilInstance {
22 process: Child,
23 pub rpc_url: String,
24 pub chain_id: u64,
25}
26
27impl AnvilInstance {
28 pub async fn spawn(chain_id: u64, port: u16, fork_url: Option<String>) -> Result<Self, AnvilError> {
29 let mut cmd = Command::new("anvil");
30
31 cmd.arg("--port")
32 .arg(port.to_string())
33 .arg("--chain-id")
34 .arg(chain_id.to_string())
35 .arg("--block-time")
36 .arg("1")
37 .arg("--accounts")
38 .arg("10")
39 .arg("--balance")
40 .arg("10000")
41 .stdout(Stdio::null())
42 .stderr(Stdio::null());
43
44 if let Some(fork) = fork_url {
45 cmd.arg("--fork-url").arg(fork);
46 }
47
48 let process = cmd
49 .spawn()
50 .map_err(|e| AnvilError::SpawnFailed(e.to_string()))?;
51
52 let rpc_url = format!("http://127.0.0.1:{}", port);
53
54 let instance = Self {
55 process,
56 rpc_url: rpc_url.clone(),
57 chain_id,
58 };
59
60 instance.wait_for_ready().await?;
61
62 println!("✓ Anvil spawned on {} (chain_id: {})", rpc_url, chain_id);
63
64 Ok(instance)
65 }
66
67 async fn wait_for_ready(&self) -> Result<(), AnvilError> {
68 let client = reqwest::Client::new();
69 let max_attempts = 30;
70
71 for _ in 0..max_attempts {
72 let payload = serde_json::json!({
73 "jsonrpc": "2.0",
74 "method": "eth_chainId",
75 "params": [],
76 "id": 1
77 });
78
79 if let Ok(response) = client
80 .post(&self.rpc_url)
81 .json(&payload)
82 .send()
83 .await
84 {
85 if response.status().is_success() {
86 return Ok(());
87 }
88 }
89
90 sleep(Duration::from_millis(100)).await;
91 }
92
93 Err(AnvilError::StartupTimeout)
94 }
95
96 pub fn kill(&mut self) -> Result<(), AnvilError> {
97 self.process
98 .kill()
99 .map_err(|e| AnvilError::KillFailed(e.to_string()))?;
100
101 self.process
102 .wait()
103 .map_err(|e| AnvilError::KillFailed(e.to_string()))?;
104
105 println!("✓ Anvil process terminated");
106 Ok(())
107 }
108}
109
110impl Drop for AnvilInstance {
111 fn drop(&mut self) {
112 let _ = self.process.kill();
113 let _ = self.process.wait();
114 }
115}