I have a bunch of 0 balance SPL token accounts. How do I close these accounts to redeem the rent lamports?
5 Answers
The other answers are Rust implementations. Here's a Typescript one for browsers/Node applications using @solana/web3.js
and @solana/spl-token
(v0.2.0):
NOTE: Solana enforces transaction size limits, so you'll need to batch the account closures. dApps nowadays usually do 10 at a time.
// Split an array into chunks of length `chunkSize`
const chunks = <T>(array: T[], chunkSize = 10): T[][] => {
let res: T[][] = [];
for (let currentChunk = 0; currentChunk < array.length; currentChunk += chunkSize) {
res.push(array.slice(currentChunk, currentChunk + chunkSize));
}
return res;
};
// Get all token accounts of `wallet`
const tokenAccounts = await connection.getParsedTokenAccountsByOwner(wallet.publicKey, { programId: TOKEN_PROGRAM_ID });
// You can only close accounts that have a 0 token balance. Be sure to filter those out!
const filteredAccounts = tokenAccounts.value.filter(account => account.account.data.parsed.info.tokenAmount.uiAmount === 0);
const transactions: Transaction[] = [];
const recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
chunks(filteredAccounts).forEach((chunk) => {
// New empty transaction
const txn = new Transaction();
txn.feePayer = wallet.publicKey;
txn.recentBlockhash = recentBlockhash;
for (const account of chunk) {
// Add a `closeAccount` instruction for every token account in the chunk
txn.add(createCloseAccountInstruction(account.pubkey, wallet.publicKey, wallet.publicKey));
}
tranasctions.push(txn);
});
Afterward you can either iterate through transactions
and send each one, but I like to use the following so that users only have to click Approve once:
const signedTransactions = await wallet.signAllTransactions(transactions);
const serializedTransactions = signedTransactions.map(t => t.serialize());
serializedTransactions.forEach(async t => await connection.sendRawTransaction(t));
Note that you should be using a custom RPC if going the above route to avoid rate-limits.
The arguments of createCloseAccountInstruction
:
createCloseAccountInstruction(TOKEN_ACCOUNT_ADDRESS, SOL_DESTINATION_ADDRESS, OWNER_OF_TOKEN_ACCOUNT)
so usually you'll just put the wallet's public key in both of those last two arguments to return the SOL to a user in a dApp scenario.
-
2One note, if you have a lot of empty accounts, you can get out of transaction size limits, so you may want to split the loop for N batches / txs. If i recall correctly, you cannot close more than 27 accounts within 1 TX, above is out of packet size limits.– rxdxCommented Jul 13, 2022 at 11:19
-
1Thank you! This is perfect for closing multiple token accounts. It would be great if you add an extra note on the transaction limit as rxdx suggested.– Son DangCommented Jul 13, 2022 at 14:31
-
-
We made a tool for this over at Step Finance app.step.finance/en/account-tools– QuellenCommented Jan 21 at 21:19
I get the feeling this isn't what you mean by programmatic but I'll leave this here anyway in case someone else comes here looking for this.
If you're asking how to do this inside an on-chain Anchor program, then you'd usually add a #[account(mut, ..., close = <target_account>)]
constraint.
But this won't work for a token account since your program doesn't own that account state. The token program does.
Here is how I've done it, but again, this might not be the best way.
Make sure your account has no balance in it.
Make sure you're not vulnerable to re-init attack vectors in the PDA (if it is a program owned token account). Change or increment the PDA path if it is owned by the program.
Make a cross program invoke to close the account.
// We can't close the account though because anchor doesn't know how to sign the CPI for that. // Manually close the token account. let cpi_accounts = anchor_spl::token::CloseAccount { account: ctx.accounts.vault.to_account_info().clone(), destination: ctx.accounts.authority.to_account_info().clone(), authority: ctx.accounts.vault.to_account_info().clone(), }; let cpi_program = ctx.accounts.token_program.to_account_info().clone(); let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer); token::close_account(cpi_ctx)?;
If the accounts are "ancillary" token accounts, meaning not associated token accounts, you can use the spl-token CLI's gc
command, which cleans up extra accounts.
You can simply run:
$ spl-token gc --owner <YOUR_OWNER_KEYPAIR>
More information in the SPL Token docs: https://spl.solana.com/token#garbage-collecting-ancillary-token-accounts
SPL has a closeAccount instruction. Here's the anchor_spl docs. I don't think there's any way to mass-close accounts, you will just have to close them one by one (or several per transaction).
-
1Stack exchanges tend to copy paste the actual code as well as linking just because links can go dead pretty easy.– Henry ECommented Jul 13, 2022 at 7:26
Closing accounts can be done using the CLI, the spl js library, or using a third-party service. To close a few accounts it is better to use the CLI, although it requires some technical knowledge. The library only makes sense if you are building a product or if you need to do this very often. The easiest way is by using a third-party service, it encapsulates all the logic for you and you just have to sign the transaction. I just found recently the https://soldustvacuum.app
it worked flawlessly for me.
-
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From Review– McBainCommented Jun 18 at 13:31
spl-token accounts
gives you a list of token accounts If the account is empty, you can close it withspl-token close <address>
. Closing empty token accounts is safe and gives you back the rent. If they are not empty, look up the account address withspl-token info <token address>
and then issue a burn command to the token account this is displayed to empty it out. Then close the account. If you script it this way, then be careful you don't burn tokens you actually want to keep.