|
1 |
+ |
use clap::{Arg, Command}; |
|
2 |
+ |
use reqwest::blocking::Client; |
|
3 |
+ |
use std::collections::HashMap; |
|
4 |
+ |
use std::error::Error; |
|
5 |
+ |
use std::env; |
|
6 |
+ |
use serde::{Deserialize,Serialize}; |
|
7 |
+ |
use futures::future::join_all; |
|
8 |
+ |
//use serde_json::json; |
|
9 |
+ |
|
|
10 |
+ |
#[derive(Serialize)] |
|
11 |
+ |
struct JsonRpcRequest { |
|
12 |
+ |
jsonrpc: String, |
|
13 |
+ |
method: String, |
|
14 |
+ |
params: Vec<String>, |
|
15 |
+ |
id: u32, |
|
16 |
+ |
} |
|
17 |
+ |
|
|
18 |
+ |
#[derive(Deserialize)] |
|
19 |
+ |
struct JsonRpcResponse { |
|
20 |
+ |
// id: u32, |
|
21 |
+ |
// jsoonrpc: String, |
|
22 |
+ |
result: String, |
|
23 |
+ |
//#[serde(default)] |
|
24 |
+ |
// error: Option<JsonRpcError>, |
|
25 |
+ |
} |
|
26 |
+ |
|
|
27 |
+ |
// #[derive(Debug, Deserialize)] |
|
28 |
+ |
// struct JsonRpcError { |
|
29 |
+ |
// code: i32, |
|
30 |
+ |
// message: String, |
|
31 |
+ |
// } |
|
32 |
+ |
|
|
33 |
+ |
struct Network { |
|
34 |
+ |
chain_id: u64, |
|
35 |
+ |
name: String, |
|
36 |
+ |
rpc_url: String, |
|
37 |
+ |
} |
|
38 |
+ |
|
|
39 |
+ |
fn collect_rpc_urls() -> HashMap<u64, Network> { |
|
40 |
+ |
let mut networks = HashMap::new(); |
|
41 |
+ |
|
|
42 |
+ |
for (key, value) in env::vars() { |
|
43 |
+ |
if key.starts_with("RPC_URL_"){ |
|
44 |
+ |
if let Some(chain_id_str) = key.strip_prefix("RPC_URL_"){ |
|
45 |
+ |
if let Ok(chain_id) = chain_id_str.parse::<u64>(){ |
|
46 |
+ |
let name = match chain_id { |
|
47 |
+ |
1 => "Ethereum Mainnet", |
|
48 |
+ |
8453 => "Base", |
|
49 |
+ |
_ => "Uknown Network" |
|
50 |
+ |
}; |
|
51 |
+ |
|
|
52 |
+ |
networks.insert(chain_id, Network{ |
|
53 |
+ |
chain_id, |
|
54 |
+ |
name: name.to_string(), |
|
55 |
+ |
rpc_url: value, |
|
56 |
+ |
}) |
|
57 |
+ |
} |
|
58 |
+ |
} |
|
59 |
+ |
} |
|
60 |
+ |
} |
|
61 |
+ |
} |
|
62 |
+ |
|
|
63 |
+ |
fn main() -> Result<(), Box<dyn Error>> { |
|
64 |
+ |
let matches = Command::new("wallet-fetch") |
|
65 |
+ |
.version("0.0.1") |
|
66 |
+ |
.author("Steve Simkins") |
|
67 |
+ |
.about("Neofetch but for your wallet") |
|
68 |
+ |
.arg( |
|
69 |
+ |
Arg::new("address") |
|
70 |
+ |
.help("Address to fetch info for ") |
|
71 |
+ |
.index(1) |
|
72 |
+ |
.required(false) |
|
73 |
+ |
) |
|
74 |
+ |
.get_matches(); |
|
75 |
+ |
|
|
76 |
+ |
let address = match matches.get_one::<String>("address"){ |
|
77 |
+ |
Some(addr) if !addr.is_empty() => addr.to_string(), |
|
78 |
+ |
_ => { |
|
79 |
+ |
match env::var("WALLET_FETCH_ADDRESS") { |
|
80 |
+ |
Ok(addr) if !addr.is_empty() => addr, |
|
81 |
+ |
_ => { |
|
82 |
+ |
eprintln!("Error: No Ethereum address provided. Either pass it as an argument or set the WALLET_FETCH_ADDRESS environment variable"); |
|
83 |
+ |
return Err("No Ethereum address provided".into()); |
|
84 |
+ |
} |
|
85 |
+ |
} |
|
86 |
+ |
} |
|
87 |
+ |
}; |
|
88 |
+ |
|
|
89 |
+ |
let rpc_url = match env::var("RPC_URL"){ |
|
90 |
+ |
Ok(url) => url, |
|
91 |
+ |
Err(_) => { |
|
92 |
+ |
eprintln!("RPC URL not defined"); |
|
93 |
+ |
return Err("RPC_URL environment variable not set".into()); |
|
94 |
+ |
} |
|
95 |
+ |
}; |
|
96 |
+ |
|
|
97 |
+ |
let client = Client::new(); |
|
98 |
+ |
|
|
99 |
+ |
let request_data = JsonRpcRequest { |
|
100 |
+ |
jsonrpc: "2.0".to_string(), |
|
101 |
+ |
method:"eth_getBalance".to_string(), |
|
102 |
+ |
params: vec![address.to_string(), "latest".to_string()], |
|
103 |
+ |
id: 1, |
|
104 |
+ |
}; |
|
105 |
+ |
|
|
106 |
+ |
|
|
107 |
+ |
let response = match client.post(&rpc_url) |
|
108 |
+ |
.json(&request_data) |
|
109 |
+ |
.send() { |
|
110 |
+ |
Ok(resp) => resp, |
|
111 |
+ |
Err(e) => { |
|
112 |
+ |
eprintln!("Error sending request: {}", e); |
|
113 |
+ |
return Err(e.into()); |
|
114 |
+ |
} |
|
115 |
+ |
}; |
|
116 |
+ |
|
|
117 |
+ |
if !response.status().is_success() { |
|
118 |
+ |
eprintln!("Error: HTTP status {}", response.status()); |
|
119 |
+ |
eprintln!("Error: {}", response.text()?); |
|
120 |
+ |
return Err(format!("Http error").into()); |
|
121 |
+ |
} |
|
122 |
+ |
|
|
123 |
+ |
let response_text = response.text()?; |
|
124 |
+ |
|
|
125 |
+ |
let response_body: JsonRpcResponse = match |
|
126 |
+ |
serde_json::from_str(&response_text){ |
|
127 |
+ |
Ok(body) => body, |
|
128 |
+ |
Err(e) => { |
|
129 |
+ |
eprintln!("Error parsing response: {}", e); |
|
130 |
+ |
return Err(e.into()); |
|
131 |
+ |
} |
|
132 |
+ |
}; |
|
133 |
+ |
|
|
134 |
+ |
if let Some(hex_str) = response_body.result.strip_prefix("0x"){ |
|
135 |
+ |
if let Ok(balance) = u128::from_str_radix(hex_str, 16){ |
|
136 |
+ |
let eth_balance = balance as f64 / 1_000_000_000_000_000_000.0; |
|
137 |
+ |
println!("Balance in ETH: {:.4}", eth_balance); |
|
138 |
+ |
} |
|
139 |
+ |
} |
|
140 |
+ |
|
|
141 |
+ |
Ok(()) |
|
142 |
+ |
} |