solana_tools_lite_cli/flows/presenter/
analysis_presenter.rs1use crate::flows::presenter::{emit_line, Presentable};
4use crate::shell::error::CliError;
5use solana_tools_lite::constants::compute_budget;
6use solana_tools_lite::models::analysis::{AnalysisWarning, TokenProgramKind, TxAnalysis};
7use solana_tools_lite::utils::format_sol;
8
9pub struct AnalysisPresenter<'a> {
11 pub analysis: Option<&'a TxAnalysis>,
12 pub summary_payload: Option<&'a str>,
13}
14
15impl Presentable for AnalysisPresenter<'_> {
16 fn present(
17 &self,
18 json: bool,
19 _show_secret: bool,
20 to_stderr: bool,
21 ) -> Result<(), CliError> {
22 if let Some(analysis) = self.analysis {
23 emit_summary(analysis);
24 }
25
26 if json {
27 if let Some(payload) = self.summary_payload {
28 emit_line(payload, to_stderr);
29 }
30 }
31 Ok(())
32 }
33}
34
35fn emit_summary(analysis: &TxAnalysis) {
36 for (i, t) in analysis.transfers.iter().enumerate() {
37 eprintln!("==================================================");
38 eprintln!("Instruction #{}: System Program (Transfer)", i + 1);
39 eprintln!(
40 " From: {}{}",
41 t.from,
42 if t.from_is_signer { " (signer)" } else { "" }
43 );
44 eprintln!(" To: {}", t.to);
45 eprintln!(" Amount: {} ({} lamports)", format_sol(t.lamports as u128), t.lamports);
46 }
48
49 eprintln!("--------------------------------------------------");
50 eprintln!("TRANSACTION SUMMARY");
51 eprintln!("Non-SOL Assets: {}", if analysis.has_non_sol_assets { "Yes (SPL/Token-2022 detected)" } else { "No" });
52 eprintln!(
53 "Network Fee: {} ({} lamports)",
54 format_sol(analysis.base_fee_lamports),
55 analysis.base_fee_lamports
56 );
57
58 if analysis.is_fee_payer {
59 eprintln!(" !!! YOU ARE THE FEE PAYER !!!");
60 }
61
62 if let Some((pf, est)) = analysis.priority_fee_lamports {
63 if est {
64 eprintln!(
65 "Priority Fee: {} ({} lamports, estimated with default {} CU)",
66 format_sol(pf),
67 pf,
68 compute_budget::DEFAULT_COMPUTE_UNIT_LIMIT
69 );
70 } else {
71 eprintln!(
72 "Priority Fee: {} ({} lamports)",
73 format_sol(pf),
74 pf
75 );
76 }
77 } else {
78 eprintln!("Priority Fee: {} (0 lamports)", format_sol(0));
79 }
80
81 if let Some(price) = analysis.compute_unit_price_micro {
82 let limit = analysis
83 .compute_unit_limit
84 .unwrap_or(compute_budget::DEFAULT_COMPUTE_UNIT_LIMIT);
85 eprintln!(
86 "Compute Budget: price={} micro-lamports, limit={}",
87 price, limit
88 );
89 }
90 let total_cost = analysis.total_fee_lamports + analysis.total_sol_send_by_signer;
91
92 if analysis.total_sol_send_by_signer > 0 {
93 eprintln!(
94 "YOU SEND: {} ({} lamports)",
95 format_sol(analysis.total_sol_send_by_signer as u128),
96 analysis.total_sol_send_by_signer
97 );
98 }
99
100 eprintln!("MAX TOTAL COST: {}", format_sol(total_cost));
101
102 let (label, desc) = analysis.privacy_level.display_info(
103 analysis.confidential_ops_count,
104 analysis.storage_ops_count
105 );
106 eprintln!("PRIVACY LEVEL: {} ({})", label, desc);
107 eprintln!("--------------------------------------------------");
108
109 if analysis.confidential_ops_count > 0 || analysis.storage_ops_count > 0 {
110 eprintln!("EXTENSION PROTOCOLS SUMMARY:");
111 if analysis.confidential_ops_count > 0 {
112 eprintln!(" - Private (Confidential) Operations: {}", analysis.confidential_ops_count);
113 }
114 if analysis.storage_ops_count > 0 {
115 eprintln!(" - Storage/Bridge (Public->ZK) Operations: {}", analysis.storage_ops_count);
116 }
117 eprintln!("--------------------------------------------------");
118 }
119
120 if !analysis.extension_actions.is_empty() {
122 eprintln!("EXTENSION PROTOCOLS DETECTED:");
123 for action in &analysis.extension_actions {
124 eprintln!(" - {}: {}", action.protocol_name(), action.description());
125 }
126 eprintln!("--------------------------------------------------");
127 }
128
129 if !analysis.extension_notices.is_empty() {
131 for notice in &analysis.extension_notices {
132 eprintln!("{}", notice);
133 eprintln!("--------------------------------------------------");
134 }
135 }
136
137 if !analysis.warnings.is_empty() {
138 eprintln!("WARNINGS:");
139 for w in &analysis.warnings {
140 eprintln!("- {}", warning_to_message(w));
141 }
142 eprintln!("--------------------------------------------------");
143 }
144}
145
146fn warning_to_message(warning: &AnalysisWarning) -> String {
147 match warning {
148 AnalysisWarning::LookupTableNotProvided => {
149 "Address table lookups present but lookup table was not provided; some accounts may be unresolved".to_string()
150 }
151 AnalysisWarning::LookupTableMissing(key) => {
152 format!("Lookup table {} missing or incomplete; some accounts may be unresolved", key)
153 }
154 AnalysisWarning::TokenTransferDetected(kind) => {
155 let label = match kind {
156 TokenProgramKind::SplToken => "Token Program",
157 TokenProgramKind::Token2022 => "Token-2022 Program",
158 TokenProgramKind::AssociatedToken => "Associated Token Program",
159 };
160 format!(
161 "{} interaction detected. Detailed token transfer amounts are not displayed in offline mode.",
162 label
163 )
164 }
165 AnalysisWarning::UnknownProgram { program_id } => {
166 format!("Unknown program encountered: {}", program_id)
167 }
168 AnalysisWarning::SignerNotRequired => {
169 "!!! SECURITY WARNING !!! Your signature is NOT REQUIRED for this transaction. This might be a phishing attempt if you were asked to sign it.".to_string()
170 }
171 AnalysisWarning::CpiLimit => {
172 "Analysis limited to top-level instructions. CPI (Cross-Program Invocations) not analyzed.".to_string()
173 }
174 AnalysisWarning::ConfidentialTransferDetected => {
175 "Confidential Transfer (Token-2022) detected. Transaction privacy level set to Hybrid/Confidential.".to_string()
176 }
177 AnalysisWarning::MalformedInstruction => {
178 "One or more protocol instructions are malformed (too short or corrupted data)".to_string()
179 }
180 }
181}