If you've traded memecoins on Solana for a few months, your wallet balance is probably a bit lower than your trades say it should be.

Part of the gap is rent. Every token you've ever held opened a token account, and each of those accounts keeps a small SOL deposit. Selling the token doesn't give the deposit back. The empty account just sits there.

This post explains where that SOL is, gives you a read-only script to find it, and shows how to get it back.

Why every token costs you 0.0015 to 0.002 SOL

On Solana, your wallet doesn't hold tokens directly. Each token you own lives in a separate token account, owned by your wallet.

Every account must hold enough SOL to be rent-exempt. Today, for a standard SPL token account (165 bytes), that's 0.00148844 SOL. A Token-2022 account with the usual ImmutableOwner extension is 170 bytes, so about 0.00151 SOL.

The catch: an account keeps the deposit it paid when it was opened. For years, a token account cost 0.00203928 SOL, so older accounts still hold that much. The older your wallet, the more SOL per empty account.

You can check today's figure yourself with the getMinimumBalanceForRentExemption RPC call (pass 165).

When you sell your whole bag, the token balance goes to zero, but the account stays open, with its deposit inside. Buy 100 different memecoins, sell them all, and you have 0.15 to 0.2 SOL sitting in 100 empty accounts, depending on when you opened them. One random memecoin trader's wallet I scanned with the script below: 23 empty accounts, 0.04 SOL.

Find them: a read-only script

This uses only getTokenAccountsByOwner, a read-only RPC call. Nothing connects to your wallet and nothing is signed. It checks both the classic Token program and Token-2022.

// find-empty-accounts.mjs // Usage: node find-empty-accounts.mjs <WALLET_ADDRESS> // Node 18+ (built-in fetch). Use your own RPC URL (a free Helius key works). const RPC = process.env.RPC_URL ?? "https://api.mainnet-beta.solana.com"; const PROGRAMS = { "Token": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "Token-2022": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", }; async function rpc(method, params) { const res = await fetch(RPC, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }), }); const { result, error } = await res.json(); if (error) throw new Error(`${method}: ${error.message}`); return result; } async function main() { const owner = process.argv[2]; if (!owner) throw new Error("Pass a wallet address"); let emptyCount = 0; let lockedLamports = 0; for (const [name, programId] of Object.entries(PROGRAMS)) { const { value } = await rpc("getTokenAccountsByOwner", [ owner, { programId }, { encoding: "jsonParsed" }, ]); for (const { pubkey, account } of value) { const info = account.data.parsed.info; const isEmpty = info.tokenAmount.amount === "0"; const isFrozen = info.state === "frozen"; if (isEmpty) { emptyCount += 1; lockedLamports += account.lamports; console.log( `${name.padEnd(10)} ${pubkey} ${(account.lamports / 1e9).toFixed(6)} SOL` + (isFrozen ? " (frozen: can't be closed)" : "") ); } if (info.delegate) { console.log(` delegate set on ${pubkey}: ${info.delegate}`); } } } console.log( `\n${emptyCount} empty token accounts, ${(lockedLamports / 1e9).toFixed(6)} SOL locked as rent` ); } main().catch((err) => { console.error(err.message); process.exit(1); }); 

The public RPC rate-limits quickly, so set RPC_URL to your own endpoint for big wallets.

Here's the output on a random memecoin trader's wallet:

Script output: 23 empty token accounts, 0.040226 SOL locked as rent

Notice the different amounts. 0.002039 and 0.002074 SOL are older Token and Token-2022 accounts, opened at the old rent. 0.001488 and 0.001514 SOL are recent ones. The odd 0.001560 is a Token-2022 account with an extra extension.

Get it back: closing an account

The SPL Token program has a CloseAccount instruction. It deletes a token account and sends its SOL deposit to a wallet of your choice. A few rules:

  • The token balance must be zero. You can't close an account that still holds tokens, even dust. Burn the dust first if you want to close it.
  • Only the owner (or the account's close authority) can sign it. No tool can close your accounts without your signature.
  • Frozen accounts can't be closed until they're thawed, and only the token's freeze authority can do that.
  • Token-2022 accounts can carry extra state. For example, an account with withheld transfer fees can't be closed until those fees are harvested.

You can close accounts one by one in some wallets, or batch many CloseAccount instructions into a few transactions.

If you'd rather not write that yourself, I built a tool that does exactly this: Reclaim SOL. The scan is free, it only closes accounts with a zero balance, you see and sign the transaction in your own wallet, and the fee is 5% of what you reclaim, as a visible line in the same transaction.

While you're cleaning up: check your delegates

You may have noticed the delegate line in the script. When you "approve" a dApp to spend a token, the SPL Token program writes a delegate and an allowance on that token account. Until it's revoked, the delegate can move up to that amount without asking you again.

Most delegates are harmless leftovers. But drainers rely on exactly this: you sign one innocent-looking approval, and they spend later.

Two ways to clear them:

  • Close the account if it's empty: the delegate goes with it.
  • Send a Revoke instruction if the account still holds tokens.

To see which approvals are still active on any wallet, without connecting it: Revoke Approvals (free).

TL;DR

  • Every token account locks 0.0015 to 0.002 SOL of rent, and selling the token doesn't release it.
  • Closing an empty account gives the SOL back. Only you can sign it.
  • Check your delegates while you're at it: they outlive the dApp you approved.

Full disclosure: I build Opsnode, the tools linked above. The script in this post works on its own, with nothing to install but Node.

How much did your wallet have locked? I'm curious what the record is.