feat: ✨ init 2546aa65
Steve Simkins · 2025-07-03 00:47 4 file(s) · +168 −0
.gitignore (added) +2 −0
1 +
/target
2 +
.env
Cargo.toml (added) +12 −0
1 +
[package]
2 +
name = "wallet-fetch"
3 +
version = "0.0.1"
4 +
edition = "2024"
5 +
6 +
[dependencies]
7 +
clap = { version = "4.4", features = ["derive"] }
8 +
reqwest = { version = "0.11", features = ["blocking", "json"]}
9 +
serde = { version = "1.0", features = ["derive"]}
10 +
serde_json = "1.0"
11 +
tokio = { version = "1.0", features = ["full"]}
12 +
future = "0.3"
agents.md (added) +12 −0
1 +
# Learning Approach
2 +
3 +
I want to use AI as a learning tool only. Please follow these guidelines:
4 +
5 +
1. **DO NOT write code for me** - I want to write all the code myself
6 +
2. **DO provide explanations** - Help me understand Rust concepts and patterns
7 +
3. **DO suggest approaches** - Give me high-level guidance on how to implement features
8 +
4. **DO review my code** - Provide feedback on code I've written
9 +
5. **DO answer questions** - Explain concepts when I ask about them
10 +
6. **DO provide examples** - When relevant, show small examples to illustrate concepts
11 +
12 +
The goal is for me to learn Rust by doing the implementation work myself, with AI serving as a guide and mentor rather than doing the work for me.
src/main.rs (added) +142 −0
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 +
}