mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-13 23:16:55 +00:00
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
This commit is contained in:
10
README.md
10
README.md
@@ -4,7 +4,6 @@
|
||||
|
||||

|
||||
|
||||
|
||||

|
||||

|
||||

|
||||
@@ -23,7 +22,6 @@ An open-source toolkit for connecting AI agents to Solana protocols. Now, any ag
|
||||
|
||||
Anyone - whether an SF-based AI researcher or a crypto-native builder - can bring their AI agents trained with any model and seamlessly integrate with Solana.
|
||||
|
||||
|
||||
[](https://replit.com/@sendaifun/Solana-Agent-Kit)
|
||||
> Replit template created by [Arpit Singh](https://github.com/The-x-35)
|
||||
|
||||
@@ -301,6 +299,13 @@ const signature = await agent.closePerpTradeLong({
|
||||
});
|
||||
```
|
||||
|
||||
### Close Empty Token Accounts
|
||||
|
||||
``` typescript
|
||||
|
||||
const { signature } = await agent.closeEmptyTokenAccounts();
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### LangGraph Multi-Agent System
|
||||
@@ -341,7 +346,6 @@ Refer to [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines on how to co
|
||||
<img src="https://contrib.rocks/image?repo=sendaifun/solana-agent-kit" />
|
||||
</a>
|
||||
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#sendaifun/solana-agent-kit&Date)
|
||||
|
||||
1384
pnpm-lock.yaml
generated
1384
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
71
src/actions/closeEmptyTokenAccounts.ts
Normal file
71
src/actions/closeEmptyTokenAccounts.ts
Normal file
@@ -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;
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
create_TipLink,
|
||||
listNFTForSale,
|
||||
cancelListing,
|
||||
closeEmptyTokenAccounts,
|
||||
fetchTokenReportSummary,
|
||||
fetchTokenDetailedReport,
|
||||
fetchPythPrice,
|
||||
@@ -547,6 +548,13 @@ export class SolanaAgentKit {
|
||||
return cancelListing(this, nftMint);
|
||||
}
|
||||
|
||||
async closeEmptyTokenAccounts(): Promise<{
|
||||
signature: string;
|
||||
size: number;
|
||||
}> {
|
||||
return closeEmptyTokenAccounts(this);
|
||||
}
|
||||
|
||||
async fetchTokenReportSummary(mint: string): Promise<TokenCheck> {
|
||||
return fetchTokenReportSummary(mint);
|
||||
}
|
||||
|
||||
@@ -2294,7 +2294,7 @@ export class Solana3LandCreateSingle extends Tool {
|
||||
...(isMainnet && { isMainnet }),
|
||||
};
|
||||
|
||||
let collectionAccount = inputFormat.collectionAccount;
|
||||
const collectionAccount = inputFormat.collectionAccount;
|
||||
|
||||
const itemName = inputFormat?.itemName;
|
||||
const sellerFee = inputFormat?.sellerFee;
|
||||
@@ -2407,6 +2407,34 @@ export class Solana3LandCreateCollection extends Tool {
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaCloseEmptyTokenAccounts extends Tool {
|
||||
name = "close_empty_token_accounts";
|
||||
description = `Close all empty spl-token accounts and reclaim the rent`;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(): Promise<string> {
|
||||
try {
|
||||
const { signature, size } =
|
||||
await this.solanaKit.closeEmptyTokenAccounts();
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: `${size} accounts closed successfully. ${size === 48 ? "48 accounts can be closed in a single transaction try again to close more accounts" : ""}`,
|
||||
signature,
|
||||
});
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
return [
|
||||
new SolanaBalanceTool(solanaKit),
|
||||
@@ -2457,6 +2485,7 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
new SolanaTipLinkTool(solanaKit),
|
||||
new SolanaListNFTForSaleTool(solanaKit),
|
||||
new SolanaCancelNFTListingTool(solanaKit),
|
||||
new SolanaCloseEmptyTokenAccounts(solanaKit),
|
||||
new SolanaFetchTokenReportSummaryTool(solanaKit),
|
||||
new SolanaFetchTokenDetailedReportTool(solanaKit),
|
||||
new Solana3LandCreateSingle(solanaKit),
|
||||
|
||||
103
src/tools/close_empty_token_accounts.ts
Normal file
103
src/tools/close_empty_token_accounts.ts
Normal file
@@ -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;
|
||||
}
|
||||
@@ -41,6 +41,9 @@ export * from "./send_compressed_airdrop";
|
||||
export * from "./stake_with_jup";
|
||||
export * from "./stake_with_solayer";
|
||||
export * from "./tensor_trade";
|
||||
|
||||
export * from "./close_empty_token_accounts";
|
||||
|
||||
export * from "./trade";
|
||||
export * from "./transfer";
|
||||
export * from "./flash_open_trade";
|
||||
|
||||
Reference in New Issue
Block a user