use hypertor::{ConfigBuilder, IsolationLevel, Result, TorClient};
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt::init();
println!("🧅 hypertor - Multiple Identities Example");
println!("==========================================");
println!();
println!("Each identity uses a separate Tor circuit,");
println!("appearing to come from a different IP address.");
println!();
let config = ConfigBuilder::new()
.timeout(Duration::from_secs(60))
.isolation(IsolationLevel::PerRequest)
.build()?;
let client = TorClient::with_config(config).await?;
println!("✅ Connected to Tor network");
println!();
println!("🔄 Creating 3 isolated identities...");
println!();
let identities = vec![client.isolated(), client.isolated(), client.isolated()];
println!("📡 Checking IP addresses for each identity:");
println!("--------------------------------------------");
let mut ips: Vec<Option<String>> = Vec::new();
for (i, identity) in identities.iter().enumerate() {
match client
.get("https://check.torproject.org/api/ip")?
.isolation(identity.token())
.send()
.await
{
Ok(response) => {
let text = response.text()?;
if let Ok(data) = serde_json::from_str::<serde_json::Value>(&text) {
let ip = data.get("IP").and_then(|v| v.as_str()).unwrap_or("unknown");
println!(" Identity {}: {}", i + 1, ip);
ips.push(Some(ip.to_string()));
} else {
println!(" Identity {}: Failed to parse response", i + 1);
ips.push(None);
}
}
Err(e) => {
println!(" Identity {}: Error - {}", i + 1, e);
ips.push(None);
}
}
}
println!();
let unique_ips: std::collections::HashSet<&String> = ips
.iter()
.filter_map(|ip: &Option<String>| ip.as_ref())
.collect();
let valid_count = ips
.iter()
.filter(|ip: &&Option<String>| ip.is_some())
.count();
if unique_ips.len() == valid_count && valid_count > 0 {
println!("✅ All identities have different IPs!");
println!(" Circuit isolation is working correctly.");
} else if unique_ips.len() > 1 {
println!("⚠️ Some identities share IPs (normal for same exit node)");
} else if valid_count > 0 {
println!("⚠️ All identities have the same IP");
println!(" This can happen if Tor reuses the same exit node.");
}
println!();
println!("📡 Testing identity persistence:");
println!("--------------------------------");
println!(" Making 3 requests with same identity token...");
let persistent_identity = client.isolated();
for i in 1..=3 {
match client
.get("https://check.torproject.org/api/ip")?
.isolation(persistent_identity.token())
.send()
.await
{
Ok(response) => {
let text = response.text()?;
if let Ok(data) = serde_json::from_str::<serde_json::Value>(&text) {
let ip = data.get("IP").and_then(|v| v.as_str()).unwrap_or("?");
println!(" Request {}: IP = {}", i, ip);
}
}
Err(e) => println!(" Request {}: Error - {}", i, e),
}
}
println!();
println!(" 💡 Same identity token = same circuit = same IP");
println!();
println!("✅ Multiple identities demo completed!");
println!();
println!("Key takeaways:");
println!(" • isolated() creates a unique circuit token");
println!(" • Different tokens → different circuits → different IPs");
println!(" • Same token → same circuit → same IP (until rotation)");
println!(" • Useful for accessing multiple accounts anonymously");
Ok(())
}