use std::{cmp::Ordering, str::FromStr};
use futures::StreamExt;
use prettytable::{format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR, row, Table};
use solana_account_decoder::UiAccountData;
use solana_client::{
nonblocking::rpc_client::RpcClient as Client, rpc_request::TokenAccountsFilter,
rpc_response::RpcKeyedAccount,
};
use solana_sdk::{account::Account, pubkey::Pubkey, system_program};
use spl_token_2022::extension::BaseStateWithExtensions;
use spl_type_length_value::variable_len_pack::VariableLenPack;
use crate::utils::display_balance;
use super::{token::TokenAccountBalance, ParsedAccount};
pub struct SystemAccount<'a> {
pub account: &'a Account,
pub key: &'a Pubkey,
pub token_accounts: Vec<TokenAccountBalance>,
}
impl<'a> SystemAccount<'a> {
pub async fn parse(
account: &'a Account,
key: &'a Pubkey,
client: &Client,
) -> Option<ParsedAccount<'a>> {
if account.owner != system_program::ID {
return None;
}
let tokenkeg_accounts_futures = client
.get_token_accounts_by_owner(key, TokenAccountsFilter::ProgramId(spl_token::ID))
.await
.unwrap()
.into_iter()
.map(parse_keyed_account_to_token)
.map(|account| async move { get_symbol_for_token_account(&account, &client).await });
let mut token_accounts: Vec<TokenAccountBalance> =
futures::stream::iter(tokenkeg_accounts_futures)
.buffer_unordered(10)
.collect()
.await;
let token22_accounts_futures = client
.get_token_accounts_by_owner(key, TokenAccountsFilter::ProgramId(spl_token_2022::ID))
.await
.unwrap()
.into_iter()
.map(parse_keyed_account_to_token)
.map(|account| async move { get_symbol_for_token_account(&account, &client).await });
token_accounts.extend(
futures::stream::iter(token22_accounts_futures)
.buffer_unordered(10)
.collect::<Vec<_>>()
.await,
);
token_accounts.sort_by(|a, b| match (&a.symbol, &b.symbol) {
(Some(_), None) => Ordering::Less,
(None, Some(_)) => Ordering::Greater,
(Some(a_sym), Some(b_sym)) => a_sym.cmp(b_sym),
(None, None) => Ordering::Equal,
});
Some(ParsedAccount::System(SystemAccount {
account,
key,
token_accounts,
}))
}
pub fn display(self) {
let sol_balance: String = display_balance(self.account.lamports, 9);
let mut account_table = Table::new();
account_table.add_row(row![c->format!("Account {}", self.key)]);
account_table.add_row(row!["SOL balance", sol_balance]);
let mut token_account_table = Table::new();
token_account_table
.add_row(row![c->"Token Account", c->"Token", c->"Balance", c->"Standard"]);
for balance in self.token_accounts {
let meta_or_mint = balance.mint;
token_account_table.add_row(row![
balance.key,
if let Some(symbol) = balance.symbol {
symbol
} else {
meta_or_mint
},
balance.balance,
balance.program
]);
}
let mut table_of_tables = Table::new();
table_of_tables.add_row(row![c->account_table]);
table_of_tables.add_row(row![c->token_account_table]);
table_of_tables.set_format(*FORMAT_NO_BORDER_LINE_SEPARATOR);
table_of_tables.printstd();
}
}
fn parse_keyed_account_to_token(keyed_account: RpcKeyedAccount) -> TokenAccountBalance {
match keyed_account.account.data {
UiAccountData::Json(json) => {
TokenAccountBalance::parse_validated_json(json, keyed_account.pubkey)
}
_ => unimplemented!("unused right now"),
}
}
async fn get_symbol_for_token_account(
account: &TokenAccountBalance,
client: &Client,
) -> TokenAccountBalance {
let meta_or_mint: String = account.mint.to_string();
let mint_acc_key: Pubkey = match Pubkey::from_str(&meta_or_mint) {
Ok(key) => key,
Err(_) => return account.clone(), };
let mpl_metadata_key = mpl_token_metadata::accounts::Metadata::find_pda(&mint_acc_key).0;
let mut symbol = client
.get_account_data(&mpl_metadata_key)
.await
.ok()
.and_then(|data| {
let metadata = mpl_token_metadata::accounts::Metadata::from_bytes(&data);
metadata.map(|meta| meta.symbol).ok()
});
if symbol.is_none() {
use spl_token_metadata_interface::state::TokenMetadata;
if let Ok(mint_account) = client.get_account(&mint_acc_key).await {
let mint = spl_token_2022::extension::StateWithExtensions::<
spl_token_2022::state::Mint,
>::unpack(&mint_account.data)
.unwrap();
if let Ok(token_metadata) = mint
.get_extension_bytes::<TokenMetadata>()
.and_then(<TokenMetadata as VariableLenPack>::unpack_from_slice)
{
symbol.replace(token_metadata.symbol);
}
}
}
TokenAccountBalance {
symbol,
..account.clone()
}
}