use {
crate::{
config::{Config, ConfigOverride},
AbsolutePath,
},
anchor_client::Cluster,
anyhow::{anyhow, Result},
clap::Parser,
solana_commitment_config::CommitmentConfig,
solana_pubkey::Pubkey,
solana_rpc_client::rpc_client::RpcClient,
std::{fs::File, io::Write, path::PathBuf},
};
#[derive(Debug, Parser, AbsolutePath)]
pub struct ShowAccountCommand {
pub account_address: Pubkey,
#[clap(long)]
pub lamports: bool,
#[clap(short = 'o', long)]
pub output_file: Option<PathBuf>,
#[clap(long, value_parser = ["json", "json-compact"])]
pub output: Option<String>,
}
pub fn show_account(cfg_override: &ConfigOverride, cmd: ShowAccountCommand) -> Result<()> {
let url = show_account_cluster_url(cfg_override)?;
let rpc_client = RpcClient::new_with_commitment(url, CommitmentConfig::confirmed());
let account = rpc_client
.get_account(&cmd.account_address)
.map_err(|e| anyhow!("Unable to fetch account {}: {}", cmd.account_address, e))?;
if let Some(format) = cmd.output {
use base64::{engine::general_purpose::STANDARD, Engine};
let json_output = serde_json::json!({
"pubkey": cmd.account_address.to_string(),
"account": {
"lamports": account.lamports,
"owner": account.owner.to_string(),
"executable": account.executable,
"rentEpoch": account.rent_epoch,
"data": STANDARD.encode(&account.data),
}
});
let output_str = match format.as_str() {
"json" => serde_json::to_string_pretty(&json_output)?,
"json-compact" => serde_json::to_string(&json_output)?,
_ => unreachable!(),
};
if let Some(output_file) = cmd.output_file {
let mut file = File::create(&output_file)?;
file.write_all(output_str.as_bytes())?;
println!("Wrote account to {}", output_file.display());
} else {
println!("{}", output_str);
}
return Ok(());
}
println!("Public Key: {}", cmd.account_address);
if cmd.lamports {
println!("Balance: {} lamports", account.lamports);
} else {
println!("Balance: {} SOL", account.lamports as f64 / 1_000_000_000.0);
}
println!("Owner: {}", account.owner);
println!("Executable: {}", account.executable);
println!("Rent Epoch: {}", account.rent_epoch);
let data_len = account.data.len();
println!("Length: {} (0x{:x}) bytes", data_len, data_len);
if !account.data.is_empty() {
if let Some(output_file) = cmd.output_file {
let mut file = File::create(&output_file)?;
file.write_all(&account.data)?;
println!("Wrote account data to {}", output_file.display());
}
print_hex_dump(&account.data);
}
Ok(())
}
fn show_account_cluster_url(cfg_override: &ConfigOverride) -> Result<String> {
let workspace_cfg = Config::discover(cfg_override)?;
Ok(show_account_cluster_url_from(
cfg_override.cluster.as_ref(),
workspace_cfg.as_deref(),
))
}
fn show_account_cluster_url_from(
cluster_override: Option<&Cluster>,
workspace_cfg: Option<&Config>,
) -> String {
if let Some(cluster) = cluster_override {
cluster.url().to_string()
} else if let Some(cfg) = workspace_cfg {
cfg.provider.cluster.url().to_string()
} else {
Cluster::Mainnet.url().to_string()
}
}
fn print_hex_dump(data: &[u8]) {
const BYTES_PER_LINE: usize = 16;
for (i, chunk) in data.chunks(BYTES_PER_LINE).enumerate() {
let offset = i * BYTES_PER_LINE;
print!("{:04x}: ", offset);
for (j, byte) in chunk.iter().enumerate() {
if j > 0 && j % 4 == 0 {
print!(" ");
}
print!("{:02x} ", byte);
}
if chunk.len() < BYTES_PER_LINE {
for j in chunk.len()..BYTES_PER_LINE {
if j > 0 && j % 4 == 0 {
print!(" ");
}
print!(" ");
}
}
print!(" ");
for byte in chunk {
let c = *byte as char;
if c.is_ascii_graphic() || c == ' ' {
print!("{}", c);
} else {
print!(".");
}
}
println!();
}
}
#[cfg(test)]
mod tests {
use {super::*, crate::config::ProviderConfig};
#[test]
fn show_account_defaults_to_mainnet_without_workspace_or_override() {
assert_eq!(
show_account_cluster_url_from(None, None),
Cluster::Mainnet.url().to_string()
);
}
#[test]
fn show_account_uses_workspace_cluster_before_default() {
let cfg = Config {
provider: ProviderConfig {
cluster: Cluster::Devnet,
..ProviderConfig::default()
},
..Config::default()
};
assert_eq!(
show_account_cluster_url_from(None, Some(&cfg)),
Cluster::Devnet.url().to_string()
);
}
#[test]
fn show_account_cluster_override_wins() {
let cfg = Config {
provider: ProviderConfig {
cluster: Cluster::Devnet,
..ProviderConfig::default()
},
..Config::default()
};
assert_eq!(
show_account_cluster_url_from(Some(&Cluster::Localnet), Some(&cfg)),
Cluster::Localnet.url().to_string()
);
}
}