Skip to main content

roboticus_cli/cli/
wallet.rs

1use super::*;
2
3pub async fn cmd_wallet(url: &str, json: bool) -> Result<(), Box<dyn std::error::Error>> {
4    let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
5    let (OK, ACTION, WARN, DETAIL, ERR) = icons();
6    let c = RoboticusClient::new(url)?;
7    let balance = c.get("/api/wallet/balance").await.map_err(|e| {
8        RoboticusClient::check_connectivity_hint(&*e);
9        e
10    })?;
11    let address = c.get("/api/wallet/address").await?;
12    if json {
13        let combined = serde_json::json!({ "balance": balance, "address": address });
14        println!("{}", serde_json::to_string_pretty(&combined)?);
15        return Ok(());
16    }
17    heading("Wallet");
18    let bal = balance["balance"].as_str().unwrap_or("0.00");
19    let currency = balance["currency"].as_str().unwrap_or("USDC");
20    let addr = address["address"].as_str().unwrap_or("not connected");
21    let treasury = &balance["treasury"];
22    let swap = &treasury["revenue_swap"];
23    let tax = &balance["self_funding"]["tax"];
24    let accounting = &balance["revenue_accounting"];
25    let swap_queue = &balance["revenue_swap_queue"];
26    let tax_queue = &balance["revenue_tax_queue"];
27    let strategy_summary = balance["revenue_strategy_summary"]
28        .as_array()
29        .cloned()
30        .unwrap_or_default();
31    let feedback_summary = balance["revenue_feedback_summary"]
32        .as_array()
33        .cloned()
34        .unwrap_or_default();
35    let seed_readiness = &balance["seed_exercise_readiness"];
36    let seed_progress = &balance["seed_exercise_progress"];
37    let seed_plan = balance["seed_exercise_plan"]
38        .as_object()
39        .cloned()
40        .unwrap_or_default();
41    kv_accent("Balance", &format!("{bal} {currency}"));
42    kv_mono("Address", addr);
43    if swap.is_object() {
44        let swap_status = if swap["enabled"].as_bool().unwrap_or(false) {
45            "enabled"
46        } else {
47            "disabled"
48        };
49        let target = swap["target_symbol"].as_str().unwrap_or("PUSD");
50        let chain = swap["default_chain"].as_str().unwrap_or("ETH");
51        kv(
52            "Revenue Swap",
53            &format!("{swap_status} -> {target} on {chain}"),
54        );
55        if let Some(chains) = swap["chains"].as_array() {
56            let configured: Vec<String> = chains
57                .iter()
58                .filter_map(|entry| entry["chain"].as_str())
59                .map(str::to_string)
60                .collect();
61            if !configured.is_empty() {
62                kv("Swap Chains", &configured.join(", "));
63            }
64        }
65    }
66    if tax.is_object() {
67        let tax_status = if tax["enabled"].as_bool().unwrap_or(false) {
68            "enabled"
69        } else {
70            "disabled"
71        };
72        let tax_rate = tax["rate"].as_f64().unwrap_or(0.0) * 100.0;
73        kv("Profit Tax", &format!("{tax_status} @ {tax_rate:.2}%"));
74        if let Some(dest) = tax["destination_wallet"].as_str().filter(|s| !s.is_empty()) {
75            kv("Tax Wallet", dest);
76        }
77    }
78    if accounting.is_object() {
79        kv(
80            "Revenue Gross",
81            &format!(
82                "{:.2} USDC",
83                accounting["gross_revenue_usdc"].as_f64().unwrap_or(0.0)
84            ),
85        );
86        kv(
87            "Revenue Net",
88            &format!(
89                "{:.2} USDC",
90                accounting["net_profit_usdc"].as_f64().unwrap_or(0.0)
91            ),
92        );
93        kv(
94            "Retained",
95            &format!(
96                "{:.2} USDC",
97                accounting["retained_earnings_usdc"].as_f64().unwrap_or(0.0)
98            ),
99        );
100    }
101    if swap_queue.is_object() {
102        kv(
103            "Swap Queue",
104            &format!(
105                "total={} pending={} in_progress={} failed={} stale={}",
106                swap_queue["total"].as_i64().unwrap_or(0),
107                swap_queue["pending"].as_i64().unwrap_or(0),
108                swap_queue["in_progress"].as_i64().unwrap_or(0),
109                swap_queue["failed"].as_i64().unwrap_or(0),
110                swap_queue["stale_in_progress"].as_i64().unwrap_or(0),
111            ),
112        );
113    }
114    if tax_queue.is_object() {
115        kv(
116            "Tax Queue",
117            &format!(
118                "total={} pending={} in_progress={} failed={} completed={}",
119                tax_queue["total"].as_i64().unwrap_or(0),
120                tax_queue["pending"].as_i64().unwrap_or(0),
121                tax_queue["in_progress"].as_i64().unwrap_or(0),
122                tax_queue["failed"].as_i64().unwrap_or(0),
123                tax_queue["completed"].as_i64().unwrap_or(0),
124            ),
125        );
126    }
127    if !strategy_summary.is_empty() {
128        let top = &strategy_summary[0];
129        kv(
130            "Top Revenue Strategy",
131            &format!(
132                "{} (net {:.2} USDC)",
133                top["strategy"].as_str().unwrap_or("unknown"),
134                top["net_profit_usdc"].as_f64().unwrap_or(0.0)
135            ),
136        );
137    }
138    if !feedback_summary.is_empty() {
139        let top = &feedback_summary[0];
140        kv(
141            "Top Feedback Strategy",
142            &format!(
143                "{} ({:.2}/5 over {} signals)",
144                top["strategy"].as_str().unwrap_or("unknown"),
145                top["avg_grade"].as_f64().unwrap_or(0.0),
146                top["feedback_count"].as_i64().unwrap_or(0)
147            ),
148        );
149    }
150    if seed_readiness.is_object() {
151        kv(
152            "$50 Seed Readiness",
153            if seed_readiness["meets_seed_target"]
154                .as_bool()
155                .unwrap_or(false)
156            {
157                "ready"
158            } else {
159                "not ready"
160            },
161        );
162        kv(
163            "Stable Balance",
164            &format!(
165                "{:.2} / {:.2} USDC target",
166                seed_readiness["stable_balance_usdc"]
167                    .as_f64()
168                    .unwrap_or(0.0),
169                seed_readiness["seed_target_usdc"].as_f64().unwrap_or(50.0)
170            ),
171        );
172    }
173    if seed_progress.is_object() {
174        kv(
175            "Seed Next Action",
176            seed_progress["next_action"]
177                .as_str()
178                .unwrap_or("no action available"),
179        );
180    }
181    if let Some(phases) = seed_plan.get("phases").and_then(|v| v.as_array()) {
182        kv("Seed Exercise Phases", &phases.len().to_string());
183        if let Some(first) = phases.first() {
184            kv(
185                "Seed First Phase",
186                first["label"].as_str().unwrap_or("no phase available"),
187            );
188        }
189    }
190    if let Some(note) = balance["note"].as_str() {
191        eprintln!();
192        eprintln!("    {DIM}\u{2139}  {note}{RESET}");
193    }
194    eprintln!();
195    Ok(())
196}
197
198pub async fn cmd_wallet_address(url: &str, json: bool) -> Result<(), Box<dyn std::error::Error>> {
199    let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
200    let (OK, ACTION, WARN, DETAIL, ERR) = icons();
201    let c = RoboticusClient::new(url)?;
202    let address = c.get("/api/wallet/address").await.map_err(|e| {
203        RoboticusClient::check_connectivity_hint(&*e);
204        e
205    })?;
206    if json {
207        println!("{}", serde_json::to_string_pretty(&address)?);
208        return Ok(());
209    }
210    let addr = address["address"].as_str().unwrap_or("not connected");
211    eprintln!();
212    eprintln!("    {MONO}{addr}{RESET}");
213    eprintln!();
214    Ok(())
215}
216
217pub async fn cmd_wallet_balance(url: &str, json: bool) -> Result<(), Box<dyn std::error::Error>> {
218    let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
219    let (OK, ACTION, WARN, DETAIL, ERR) = icons();
220    let c = RoboticusClient::new(url)?;
221    let balance = c.get("/api/wallet/balance").await.map_err(|e| {
222        RoboticusClient::check_connectivity_hint(&*e);
223        e
224    })?;
225    if json {
226        println!("{}", serde_json::to_string_pretty(&balance)?);
227        return Ok(());
228    }
229    let bal = balance["balance"].as_str().unwrap_or("0.00");
230    let currency = balance["currency"].as_str().unwrap_or("USDC");
231    let swap = &balance["treasury"]["revenue_swap"];
232    let tax = &balance["self_funding"]["tax"];
233    let accounting = &balance["revenue_accounting"];
234    let swap_queue = &balance["revenue_swap_queue"];
235    let tax_queue = &balance["revenue_tax_queue"];
236    let strategy_summary = balance["revenue_strategy_summary"]
237        .as_array()
238        .cloned()
239        .unwrap_or_default();
240    let feedback_summary = balance["revenue_feedback_summary"]
241        .as_array()
242        .cloned()
243        .unwrap_or_default();
244    let seed_readiness = &balance["seed_exercise_readiness"];
245    let seed_progress = &balance["seed_exercise_progress"];
246    let seed_plan = balance["seed_exercise_plan"]
247        .as_object()
248        .cloned()
249        .unwrap_or_default();
250    eprintln!();
251    kv_accent("Balance", &format!("{bal} {currency}"));
252    if swap.is_object() {
253        let swap_status = if swap["enabled"].as_bool().unwrap_or(false) {
254            "enabled"
255        } else {
256            "disabled"
257        };
258        let target = swap["target_symbol"].as_str().unwrap_or("PUSD");
259        let chain = swap["default_chain"].as_str().unwrap_or("ETH");
260        kv(
261            "Revenue Swap",
262            &format!("{swap_status} -> {target} on {chain}"),
263        );
264    }
265    if tax.is_object() {
266        let tax_status = if tax["enabled"].as_bool().unwrap_or(false) {
267            "enabled"
268        } else {
269            "disabled"
270        };
271        let tax_rate = tax["rate"].as_f64().unwrap_or(0.0) * 100.0;
272        kv("Profit Tax", &format!("{tax_status} @ {tax_rate:.2}%"));
273    }
274    if accounting.is_object() {
275        kv(
276            "Revenue Net",
277            &format!(
278                "{:.2} USDC",
279                accounting["net_profit_usdc"].as_f64().unwrap_or(0.0)
280            ),
281        );
282    }
283    if swap_queue.is_object() {
284        kv(
285            "Swap Queue",
286            &format!(
287                "pending={} in_progress={} failed={}",
288                swap_queue["pending"].as_i64().unwrap_or(0),
289                swap_queue["in_progress"].as_i64().unwrap_or(0),
290                swap_queue["failed"].as_i64().unwrap_or(0),
291            ),
292        );
293    }
294    if tax_queue.is_object() {
295        kv(
296            "Tax Queue",
297            &format!(
298                "pending={} in_progress={} failed={} completed={}",
299                tax_queue["pending"].as_i64().unwrap_or(0),
300                tax_queue["in_progress"].as_i64().unwrap_or(0),
301                tax_queue["failed"].as_i64().unwrap_or(0),
302                tax_queue["completed"].as_i64().unwrap_or(0),
303            ),
304        );
305    }
306    if !strategy_summary.is_empty() {
307        let top = &strategy_summary[0];
308        kv(
309            "Top Strategy",
310            &format!(
311                "{} ({:.2} USDC net)",
312                top["strategy"].as_str().unwrap_or("unknown"),
313                top["net_profit_usdc"].as_f64().unwrap_or(0.0)
314            ),
315        );
316    }
317    if !feedback_summary.is_empty() {
318        let top = &feedback_summary[0];
319        kv(
320            "Top Feedback",
321            &format!(
322                "{} ({:.2}/5 over {} signals)",
323                top["strategy"].as_str().unwrap_or("unknown"),
324                top["avg_grade"].as_f64().unwrap_or(0.0),
325                top["feedback_count"].as_i64().unwrap_or(0)
326            ),
327        );
328    }
329    if seed_readiness.is_object() {
330        kv(
331            "Seed Readiness",
332            if seed_readiness["meets_seed_target"]
333                .as_bool()
334                .unwrap_or(false)
335            {
336                "ready"
337            } else {
338                "not ready"
339            },
340        );
341    }
342    if seed_progress.is_object() {
343        kv(
344            "Seed Next Action",
345            seed_progress["next_action"]
346                .as_str()
347                .unwrap_or("no action available"),
348        );
349    }
350    if let Some(phases) = seed_plan.get("phases").and_then(|v| v.as_array()) {
351        kv("Seed Exercise Phases", &phases.len().to_string());
352    }
353    eprintln!();
354    Ok(())
355}