-
Notifications
You must be signed in to change notification settings - Fork 437
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add close empty spl-token accounts transaction (#118)
# Pull Request Description ## Changes Made This PR adds the following changes: - all the close instruction for empty token accounts - this instruction closes the token account and reclaim's the rent ## Implementation Details - createCloseAccountInstruction from @solana/spl-token library to close the spl-token account ## Transaction executed by agent <img width="1467" alt="Screenshot 2025-01-04 at 11 22 20 PM" src="https://github.com/user-attachments/assets/1a48bb54-b76d-49f9-b425-b76b84e924e8" /> Example transaction: [transaction](https://explorer.solana.com/tx/3KmPyiZvJQk8CfBVVaz8nf3c2crb6iqjQVDqNxknnusyb1FTFpXqD8zVSCBAd1X3rUcD8WiG1bdSjFbeHsmcYGXY) ## Prompt Used close my empty token accounts ## Checklist - [x] I have tested these changes locally - [x] I have updated the documentation - [x] I have added a transaction link - [x] I have added the prompt used to test it
- Loading branch information
Showing
7 changed files
with
994 additions
and
616 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { Action } from "../types/action"; | ||
import { SolanaAgentKit } from "../agent"; | ||
import { z } from "zod"; | ||
import { closeEmptyTokenAccounts } from "../tools"; | ||
|
||
const closeEmptyTokenAccountsAction: Action = { | ||
name: "CLOSE_EMPTY_TOKEN_ACCOUNTS", | ||
similes: [ | ||
"close token accounts", | ||
"remove empty accounts", | ||
"clean up token accounts", | ||
"close SPL token accounts", | ||
"clean wallet", | ||
], | ||
description: `Close empty SPL Token accounts associated with your wallet to reclaim rent. | ||
This action will close both regular SPL Token accounts and Token-2022 accounts that have zero balance. `, | ||
examples: [ | ||
[ | ||
{ | ||
input: {}, | ||
output: { | ||
status: "success", | ||
signature: | ||
"3KmPyiZvJQk8CfBVVaz8nf3c2crb6iqjQVDqNxknnusyb1FTFpXqD8zVSCBAd1X3rUcD8WiG1bdSjFbeHsmcYGXY", | ||
accountsClosed: 10, | ||
}, | ||
explanation: "Closed 10 empty token accounts successfully.", | ||
}, | ||
], | ||
[ | ||
{ | ||
input: {}, | ||
output: { | ||
status: "success", | ||
signature: "", | ||
accountsClosed: 0, | ||
}, | ||
explanation: "No empty token accounts were found to close.", | ||
}, | ||
], | ||
], | ||
schema: z.object({}), | ||
handler: async (agent: SolanaAgentKit) => { | ||
try { | ||
const result = await closeEmptyTokenAccounts(agent); | ||
|
||
if (result.size === 0) { | ||
return { | ||
status: "success", | ||
signature: "", | ||
accountsClosed: 0, | ||
message: "No empty token accounts found to close", | ||
}; | ||
} | ||
|
||
return { | ||
status: "success", | ||
signature: result.signature, | ||
accountsClosed: result.size, | ||
message: `Successfully closed ${result.size} empty token accounts`, | ||
}; | ||
} catch (error: any) { | ||
return { | ||
status: "error", | ||
message: `Failed to close empty token accounts: ${error.message}`, | ||
}; | ||
} | ||
}, | ||
}; | ||
|
||
export default closeEmptyTokenAccountsAction; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import { | ||
PublicKey, | ||
Transaction, | ||
TransactionInstruction, | ||
} from "@solana/web3.js"; | ||
import { SolanaAgentKit } from "../agent"; | ||
import { | ||
AccountLayout, | ||
createCloseAccountInstruction, | ||
TOKEN_2022_PROGRAM_ID, | ||
TOKEN_PROGRAM_ID, | ||
} from "@solana/spl-token"; | ||
|
||
/** | ||
* Close Empty SPL Token accounts of the agent | ||
* @param agent SolanaAgentKit instance | ||
* @returns transaction signature and total number of accounts closed | ||
*/ | ||
export async function closeEmptyTokenAccounts( | ||
agent: SolanaAgentKit, | ||
): Promise<{ signature: string; size: number }> { | ||
try { | ||
const spl_token = await create_close_instruction(agent, TOKEN_PROGRAM_ID); | ||
const token_2022 = await create_close_instruction( | ||
agent, | ||
TOKEN_2022_PROGRAM_ID, | ||
); | ||
const transaction = new Transaction(); | ||
|
||
const MAX_INSTRUCTIONS = 40; // 40 instructions can be processed in a single transaction without failing | ||
|
||
spl_token | ||
.slice(0, Math.min(MAX_INSTRUCTIONS, spl_token.length)) | ||
.forEach((instruction) => transaction.add(instruction)); | ||
|
||
token_2022 | ||
.slice(0, Math.max(0, MAX_INSTRUCTIONS - spl_token.length)) | ||
.forEach((instruction) => transaction.add(instruction)); | ||
|
||
const size = spl_token.length + token_2022.length; | ||
|
||
if (size === 0) { | ||
return { | ||
signature: "", | ||
size: 0, | ||
}; | ||
} | ||
|
||
const signature = await agent.connection.sendTransaction(transaction, [ | ||
agent.wallet, | ||
]); | ||
|
||
return { signature, size }; | ||
} catch (error) { | ||
throw new Error(`Error closing empty token accounts: ${error}`); | ||
} | ||
} | ||
|
||
/** | ||
* creates the close instuctions of a spl token account | ||
* @param agnet SolanaAgentKit instance | ||
* @param token_program Token Program Id | ||
* @returns close instuction array | ||
*/ | ||
|
||
async function create_close_instruction( | ||
agent: SolanaAgentKit, | ||
token_program: PublicKey, | ||
): Promise<TransactionInstruction[]> { | ||
const instructions = []; | ||
|
||
const ata_accounts = await agent.connection.getTokenAccountsByOwner( | ||
agent.wallet_address, | ||
{ programId: token_program }, | ||
"confirmed", | ||
); | ||
|
||
const tokens = ata_accounts.value; | ||
|
||
const accountExceptions = [ | ||
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC | ||
]; | ||
|
||
for (let i = 0; i < tokens.length; i++) { | ||
const token_data = AccountLayout.decode(tokens[i].account.data); | ||
if ( | ||
token_data.amount === BigInt(0) && | ||
!accountExceptions.includes(token_data.mint.toString()) | ||
) { | ||
const closeInstruction = createCloseAccountInstruction( | ||
ata_accounts.value[i].pubkey, | ||
agent.wallet_address, | ||
agent.wallet_address, | ||
[], | ||
token_program, | ||
); | ||
|
||
instructions.push(closeInstruction); | ||
} | ||
} | ||
|
||
return instructions; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters