solagent_plugin_solana/
close_empty_token_accounts.rs1use serde::{Deserialize, Serialize};
16use solagent_core::{
17 solana_client::rpc_request::TokenAccountsFilter,
18 solana_sdk::{instruction::Instruction, pubkey::Pubkey, transaction::Transaction},
19 SolanaAgentKit,
20};
21use spl_token::instruction::close_account;
22
23pub const USDC: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
24
25#[derive(serde::Deserialize)]
26pub struct Parsed {
27 pub info: SplToken,
28}
29
30#[derive(serde::Deserialize)]
31pub struct SplToken {
32 pub mint: String,
33 #[serde(rename(deserialize = "tokenAmount"))]
34 pub token_amount: Amount,
35}
36
37#[allow(dead_code)]
38#[derive(serde::Deserialize)]
39pub struct Amount {
40 pub amount: String,
41 #[serde(rename(deserialize = "uiAmountString"))]
42 ui_amount_string: String,
43 #[serde(rename(deserialize = "uiAmount"))]
44 pub ui_amount: f64,
45 pub decimals: u8,
46}
47
48#[derive(Serialize, Deserialize, Debug, Default)]
49pub struct CloseEmptyTokenAccountsData {
50 pub signature: String,
51 pub closed_size: usize,
52}
53
54impl CloseEmptyTokenAccountsData {
55 pub fn new(signature: String, closed_size: usize) -> Self {
56 CloseEmptyTokenAccountsData {
57 signature,
58 closed_size,
59 }
60 }
61}
62
63pub async fn close_empty_token_accounts(
73 agent: &SolanaAgentKit,
74) -> Result<CloseEmptyTokenAccountsData, Box<dyn std::error::Error>> {
75 let max_instructions = 40_u32;
76 let mut transaction: Vec<Instruction> = vec![];
77 let mut closed_size = 0;
78 let token_programs = vec![spl_token::ID, spl_token_2022::ID];
79
80 for token_program in token_programs {
81 let accounts = agent
82 .connection
83 .get_token_accounts_by_owner(
84 &agent.wallet.pubkey,
85 TokenAccountsFilter::ProgramId(token_program.to_owned()),
86 )
87 .expect("get_token_accounts_by_owner");
88
89 closed_size += accounts.len();
90
91 for account in accounts {
92 if transaction.len() >= max_instructions as usize {
93 break;
94 }
95
96 if let solana_account_decoder::UiAccountData::Json(d) = &account.account.data {
97 if let Ok(parsed) = serde_json::from_value::<Parsed>(d.parsed.clone()) {
98 if parsed
99 .info
100 .token_amount
101 .amount
102 .parse::<u32>()
103 .unwrap_or_default()
104 == 0_u32
105 && parsed.info.mint != USDC
106 {
107 let account_pubkey = Pubkey::from_str_const(&account.pubkey);
108 if let Ok(instruct) = close_account(
109 &token_program,
110 &account_pubkey,
111 &agent.wallet.pubkey,
112 &agent.wallet.pubkey,
113 &[&agent.wallet.pubkey],
114 ) {
115 transaction.push(instruct);
116 }
117 }
118 }
119 }
120 }
121 }
122
123 if transaction.is_empty() {
124 return Ok(CloseEmptyTokenAccountsData::default());
125 }
126
127 let recent_blockhash = agent.connection.get_latest_blockhash()?;
129 let transaction = Transaction::new_signed_with_payer(
130 &transaction,
131 Some(&agent.wallet.pubkey),
132 &[&agent.wallet.keypair],
133 recent_blockhash,
134 );
135
136 let signature = agent
137 .connection
138 .send_and_confirm_transaction(&transaction)?;
139 let data = CloseEmptyTokenAccountsData::new(signature.to_string(), closed_size);
140 Ok(data)
141}