use std::io::Write;
use base64::Engine;
use prettytable::{format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR, row, Table};
use solana_client::nonblocking::rpc_client::RpcClient as Client;
use solana_sdk::{account::Account, pubkey::Pubkey};
use spl_token_2022::extension::ExtensionType;
use crate::utils::{display_balance, get_network};
use self::{
system::SystemAccount,
token::{Token22Account, TokenProgramAccount, TokenkegAccount},
};
pub mod system;
pub mod token;
pub async fn handler(rpc_url: String, account: crate::Account) {
let client = Client::new(get_network(&rpc_url));
let fetched_account: Account = client.get_account(&account.pubkey).await.unwrap();
let parsed_account = parse_account(&fetched_account, &account.pubkey, &client).await;
println!();
parsed_account.display(&account.pubkey);
println!();
}
async fn parse_account<'a>(
account: &'a Account,
key: &'a Pubkey,
client: &Client,
) -> ParsedAccount<'a> {
SystemAccount::parse(account, key, client)
.await
.or(TokenProgramAccount::parse(account, client).await)
.or_else(|| Some(ParsedAccount::Other(account)))
.unwrap()
}
pub enum ParsedAccount<'a> {
System(SystemAccount<'a>),
TokenProgram(TokenProgramAccount),
Other(&'a Account),
}
impl<'a> ParsedAccount<'a> {
pub fn display(self, key: &Pubkey) {
match self {
ParsedAccount::System(system) => system.display(),
ParsedAccount::TokenProgram(token) => token.display(key),
ParsedAccount::Other(other) => other_display(other, key),
}
}
}
fn other_display(other: &Account, key: &Pubkey) {
let Account {
lamports,
data,
owner,
executable,
rent_epoch: _,
} = other;
use terminal_size::{terminal_size, Width};
let size = terminal_size();
let width = size.map(|(Width(w), _height)| w as usize).unwrap_or(32);
let padded_width = width.saturating_sub(4);
const ACCOUNT_DATA_STR: &str = "Account Data";
let bs64_approx_max_len = 3 * data.len() / 2;
let mut data_string = String::with_capacity(2 * width + 2 + bs64_approx_max_len);
let pad_len = width / 2 - ACCOUNT_DATA_STR.len() / 2;
data_string.push_str(&" ".repeat(pad_len));
data_string.push_str(ACCOUNT_DATA_STR);
data_string.push('\n');
data_string.push_str(&"-".repeat(width));
base64::engine::general_purpose::STANDARD.encode_string(data, &mut data_string);
data_string.push('\n');
let mut account_table = Table::new();
account_table.set_titles(row![c->"Account", key]);
account_table.add_row(row![c->"Owner", owner]);
account_table.add_row(row![c->"SOL Balance", display_balance(*lamports, 9)]);
account_table.add_row(row![c->"Executable", executable]);
let mut tables = Table::new();
tables.add_row(row![c->account_table]);
tables.add_row(row![" ".repeat(padded_width)]);
tables.set_format(*FORMAT_NO_BORDER_LINE_SEPARATOR);
tables.printstd();
let mut stdout = std::io::stdout();
stdout.write(data_string.as_bytes()).unwrap();
stdout.flush().unwrap();
}
impl<'a> ParsedAccount<'a> {
#[inline(always)]
pub fn tokenkeg_token(
token_account: spl_token::state::Account,
mint_account: spl_token::state::Mint,
symbol: Option<String>,
) -> ParsedAccount<'a> {
ParsedAccount::TokenProgram(TokenProgramAccount::Tokenkeg(
TokenkegAccount::TokenAccount {
token_account,
mint_account,
symbol,
},
))
}
#[inline(always)]
pub fn tokenkeg_mint(mint: spl_token::state::Mint) -> ParsedAccount<'a> {
ParsedAccount::TokenProgram(TokenProgramAccount::Tokenkeg(TokenkegAccount::MintAccount(
mint,
)))
}
#[inline(always)]
pub fn token22_token(
token_account: spl_token_2022::state::Account,
mint_account: spl_token_2022::state::Mint,
symbol: Option<String>,
) -> ParsedAccount<'a> {
ParsedAccount::TokenProgram(TokenProgramAccount::Token22(Token22Account::TokenAccount {
token_account,
mint_account,
symbol,
}))
}
#[inline(always)]
pub fn token22_mint(
mint_account: spl_token_2022::state::Mint,
extensions: Vec<ExtensionType>,
) -> ParsedAccount<'a> {
ParsedAccount::TokenProgram(TokenProgramAccount::Token22(Token22Account::MintAccount {
mint_account,
extensions,
}))
}
}