1use crate::{
2 account::{AccountFieldVisibility, DisplayKeyedAccount, KeyedAccount},
3 config::ExplorerConfig,
4 display::DisplayFormat,
5 error::{ExplorerError, Result},
6 program::{DisplayUpgradeableProgram, ProgramFieldVisibility},
7 transaction::{
8 DisplayRawTransaction, DisplayTransaction, RawTransactionFieldVisibility,
9 TransactionFieldVisibility,
10 },
11};
12use console::style;
13use pretty_hex::*;
14use solana_client::rpc_config::RpcTransactionConfig;
15use solana_sdk::{
16 account_utils::StateMut, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
17 bpf_loader_upgradeable::UpgradeableLoaderState, commitment_config::CommitmentConfig,
18 native_token, pubkey::Pubkey, signature::Signature,
19};
20use solana_transaction_status::{TransactionConfirmationStatus, UiTransactionEncoding};
21use std::{cmp::Ordering, fmt::Write};
22
23pub fn pretty_lamports_to_sol(lamports: u64) -> String {
24 let sol_str = format!("{:.9}", native_token::lamports_to_sol(lamports));
25 sol_str
26 .trim_end_matches('0')
27 .trim_end_matches('.')
28 .to_string()
29}
30
31pub fn classify_account(fee_payer: bool, writable: bool, signer: bool, program: bool) -> String {
32 let mut account_type_string = String::new();
33 let mut started = false;
34 if fee_payer {
35 account_type_string.push_str("[Fee Payer]");
36 started = true;
37 }
38 if writable {
39 if started {
40 account_type_string.push(' ');
41 }
42 account_type_string.push_str("[Writable]");
43 started = true;
44 }
45 if signer {
46 if started {
47 account_type_string.push(' ');
48 }
49 account_type_string.push_str("[Signer]");
50 started = true;
51 }
52 if program {
53 if started {
54 account_type_string.push(' ');
55 }
56 account_type_string.push_str("[Program]");
57 }
58 account_type_string
59}
60
61pub fn calculate_change(post: u64, pre: u64) -> String {
62 match post.cmp(&pre) {
63 Ordering::Greater => format!(
64 "◎ {} (+{})",
65 pretty_lamports_to_sol(post),
66 pretty_lamports_to_sol(post - pre)
67 ),
68 Ordering::Less => format!(
69 "◎ {} (-{})",
70 pretty_lamports_to_sol(post),
71 pretty_lamports_to_sol(pre - post)
72 ),
73 Ordering::Equal => format!("◎ {}", pretty_lamports_to_sol(post)),
74 }
75}
76
77pub fn change_in_sol(post: u64, pre: u64) -> String {
78 match post.cmp(&pre) {
79 Ordering::Greater => format!("+{}", pretty_lamports_to_sol(post - pre)),
80 Ordering::Less => format!("-{}", pretty_lamports_to_sol(pre - post)),
81 Ordering::Equal => "0".to_string(),
82 }
83}
84
85pub fn status_to_string(status: &TransactionConfirmationStatus) -> String {
86 match status {
87 TransactionConfirmationStatus::Processed => "Processed".to_string(),
88 TransactionConfirmationStatus::Confirmed => "Confirmed".to_string(),
89 TransactionConfirmationStatus::Finalized => "Finalized".to_string(),
90 }
91}
92
93pub async fn print_account(
94 pubkey: &Pubkey,
95 visibility: &AccountFieldVisibility,
96 format: DisplayFormat,
97 config: &ExplorerConfig,
98) -> Result<()> {
99 let account_string = get_account_string(pubkey, visibility, format, config).await?;
100 println!("{account_string}");
101 Ok(())
102}
103
104pub async fn print_program(
105 program_id: &Pubkey,
106 visibility: &ProgramFieldVisibility,
107 format: DisplayFormat,
108 config: &ExplorerConfig,
109) -> Result<()> {
110 let program_string = get_program_string(program_id, visibility, format, config).await?;
111 println!("{program_string}");
112 Ok(())
113}
114
115pub async fn print_raw_transaction(
116 signature: &Signature,
117 visibility: &RawTransactionFieldVisibility,
118 format: DisplayFormat,
119 config: &ExplorerConfig,
120) -> Result<()> {
121 let raw_transaction_string =
122 get_raw_transaction_string(signature, visibility, format, config).await?;
123 println!("{raw_transaction_string}");
124 Ok(())
125}
126
127pub async fn print_transaction(
128 signature: &Signature,
129 visibility: &TransactionFieldVisibility,
130 format: DisplayFormat,
131 config: &ExplorerConfig,
132) -> Result<()> {
133 let transaction_string = get_transaction_string(signature, visibility, format, config).await?;
134 println!("{transaction_string}");
135 Ok(())
136}
137
138pub async fn get_account_string(
139 pubkey: &Pubkey,
140 visibility: &AccountFieldVisibility,
141 format: DisplayFormat,
142 config: &ExplorerConfig,
143) -> Result<String> {
144 let rpc_client = config.rpc_client();
145 let account = rpc_client.get_account(pubkey)?;
146 let keyed_account = KeyedAccount {
147 pubkey: *pubkey,
148 account,
149 };
150 let display_keyed_account = DisplayKeyedAccount::from_keyed_account(&keyed_account, visibility);
151 let mut account_string = format.formatted_string(&display_keyed_account)?;
152
153 if display_keyed_account.account.data.is_some() {
154 let data = &keyed_account.account.data;
155 if let DisplayFormat::Cli = format {
156 if !data.is_empty() {
157 writeln!(&mut account_string)?;
158 writeln!(&mut account_string)?;
159
160 writeln!(
161 &mut account_string,
162 "{} {} bytes",
163 style("Hexdump:").bold(),
164 data.len()
165 )?;
166 const MAX_BYTES_SHOWN: usize = 64;
168 let len = data.len();
169 let (end, finished) = if MAX_BYTES_SHOWN > len {
170 (len, true)
171 } else {
172 (MAX_BYTES_SHOWN, false)
173 };
174 let raw_account_data = &data[..end];
175 let cfg = HexConfig {
176 title: false,
177 width: 16,
178 group: 0,
179 chunk: 2,
180 ..HexConfig::default()
181 };
182 write!(&mut account_string, "{:?}", raw_account_data.hex_conf(cfg))?;
183 if !finished {
184 writeln!(&mut account_string)?;
185 write!(&mut account_string, "... (skipped)")?;
186 }
187 }
188 };
189 }
190
191 Ok(account_string)
192}
193
194pub async fn get_program_string(
195 program_id: &Pubkey,
196 visibility: &ProgramFieldVisibility,
197 format: DisplayFormat,
198 config: &ExplorerConfig,
199) -> Result<String> {
200 let rpc_client = config.rpc_client();
201 let program_account = rpc_client.get_account(program_id)?;
202 let program_keyed_account = KeyedAccount {
203 pubkey: *program_id,
204 account: program_account,
205 };
206
207 if program_keyed_account.account.owner == bpf_loader::id()
208 || program_keyed_account.account.owner == bpf_loader_deprecated::id()
209 {
210 let mut program_string = get_account_string(
212 program_id,
213 &AccountFieldVisibility::new_all_enabled(),
214 format,
215 config,
216 )
217 .await?;
218
219 if let DisplayFormat::Cli = format {
220 program_string.push_str(
221 "\n\nNote: the program is loaded either by the deprecated BPFLoader or BPFLoader2,
222it is an executable account with program.so in its data, hence this output.",
223 );
224 }
225
226 Ok(program_string)
227 } else if program_keyed_account.account.owner == bpf_loader_upgradeable::id() {
228 if let Ok(UpgradeableLoaderState::Program {
230 programdata_address,
231 }) = program_keyed_account.account.state()
232 {
233 if let Ok(programdata_account) = rpc_client.get_account(&programdata_address) {
234 let programdata_keyed_account = KeyedAccount {
235 pubkey: programdata_address,
236 account: programdata_account,
237 };
238 if let Ok(UpgradeableLoaderState::ProgramData {
239 upgrade_authority_address,
240 slot,
241 }) = programdata_keyed_account.account.state()
242 {
243 let program = DisplayUpgradeableProgram::from(
244 &program_keyed_account,
245 &programdata_keyed_account,
246 slot,
247 &upgrade_authority_address,
248 visibility,
249 );
250 let mut program_string = format.formatted_string(&program)?;
251
252 if program.programdata_account.is_some() {
253 if let DisplayFormat::Cli = format {
254 writeln!(&mut program_string)?;
255 writeln!(&mut program_string)?;
256 writeln!(
257 &mut program_string,
258 "{} {} bytes",
259 style("Followed by Raw Program Data (program.so):").bold(),
260 programdata_keyed_account.account.data.len()
261 - UpgradeableLoaderState::size_of_programdata_metadata()
262 )?;
263
264 const MAX_BYTES_SHOWN: usize = 64;
266 let len = programdata_keyed_account.account.data.len();
267 let offset = UpgradeableLoaderState::size_of_programdata_metadata();
268 let (end, finished) = if offset + MAX_BYTES_SHOWN > len {
269 (len, true)
270 } else {
271 (offset + MAX_BYTES_SHOWN, false)
272 };
273 let raw_program_data =
274 &programdata_keyed_account.account.data[offset..end];
275 let cfg = HexConfig {
276 title: false,
277 width: 16,
278 group: 0,
279 chunk: 2,
280 ..HexConfig::default()
281 };
282 write!(&mut program_string, "{:?}", raw_program_data.hex_conf(cfg))?;
283 if !finished {
284 writeln!(&mut program_string)?;
285 write!(&mut program_string, "... (skipped)")?;
286 }
287 }
288 }
289
290 Ok(program_string)
291 } else {
292 Err(ExplorerError::Custom(format!(
293 "Program {program_id} has been closed"
294 )))
295 }
296 } else {
297 Err(ExplorerError::Custom(format!(
298 "Program {program_id} has been closed"
299 )))
300 }
301 } else {
302 Err(ExplorerError::Custom(format!(
303 "{program_id} is not a Program account"
304 )))
305 }
306 } else {
307 Err(ExplorerError::Custom(format!(
308 "{program_id} is not a pubkey of an on-chain BPF program."
309 )))
310 }
311}
312
313pub async fn get_raw_transaction_string(
314 signature: &Signature,
315 visibility: &RawTransactionFieldVisibility,
316 format: DisplayFormat,
317 config: &ExplorerConfig,
318) -> Result<String> {
319 let rpc_client = config.rpc_client();
320 let config = RpcTransactionConfig {
321 encoding: Some(UiTransactionEncoding::Binary),
322 commitment: Some(CommitmentConfig::confirmed()),
323 max_supported_transaction_version: None,
324 };
325
326 let transaction = rpc_client.get_transaction_with_config(signature, config)?;
327
328 let response = rpc_client.get_signature_statuses_with_history(&[*signature])?;
329
330 let transaction_status = response.value[0].as_ref().unwrap();
331
332 let display_transaction =
333 DisplayRawTransaction::from(&transaction, transaction_status, visibility)?;
334
335 let transaction_string = format.formatted_string(&display_transaction)?;
336
337 Ok(transaction_string)
338}
339
340pub async fn get_transaction_string(
341 signature: &Signature,
342 visibility: &TransactionFieldVisibility,
343 format: DisplayFormat,
344 config: &ExplorerConfig,
345) -> Result<String> {
346 let rpc_client = config.rpc_client();
347 let config = RpcTransactionConfig {
348 encoding: Some(UiTransactionEncoding::Binary),
349 commitment: Some(CommitmentConfig::confirmed()),
350 max_supported_transaction_version: Some(0),
351 };
352
353 let transaction = rpc_client.get_transaction_with_config(signature, config)?;
354
355 let response = rpc_client.get_signature_statuses_with_history(&[*signature])?;
356
357 let transaction_status = response.value[0].as_ref().unwrap();
358
359 let display_transaction =
360 DisplayTransaction::from(&transaction, transaction_status, visibility)?;
361
362 let transaction_string = format.formatted_string(&display_transaction)?;
363
364 Ok(transaction_string)
365}