DEV Community

Cover image for A Rent Recovery Tool for Solana Accounts
Ola Adesoye
Ola Adesoye

Posted on

A Rent Recovery Tool for Solana Accounts

A Quick Solana Primer

If you're not deep in the Solana ecosystem, here's a 30-second crash course.

Solana is a blockchain — like Ethereum, but faster and cheaper. To do anything on Solana (send tokens, interact with apps, mint NFTs), you need to pay transaction fees. These fees are paid in SOL, Solana's native currency.

Here's where it gets interesting: every piece of data stored on Solana lives in something called an "account." Your wallet? An account. Your token balances? Each one is a separate account. And every account needs to hold a minimum amount of SOL to exist — this is called rent.

Think of it like a security deposit. The blockchain holds onto this SOL to prevent spam (otherwise people could create infinite accounts and bloat the network). The good news? When you close an account, you get that deposit back.

For a standard token account, this deposit is about 0.002 SOL (~$0.30-0.50 USD). Not much for one account. But what if you have thousands?


Kora for Gasless Transactions on Solana

Now let's talk about Kora.

One of the friction points in crypto is that you always need the native token to do anything. Want to send USDC on Solana? You need SOL for the fee. Just got some tokens airdropped but have zero SOL? Too bad, you can't move them.

Kora solves this. It's infrastructure enables gasless transactions. Users can pay transaction fees in tokens they already have (like USDC or BONK) instead of SOL.

Here's how it works at a high level:

  1. A user wants to make a transaction but doesn't have SOL for fees
  2. They submit their transaction to a Kora node
  3. The Kora node sponsors the SOL fee on their behalf
  4. In return, the user pays the node in a token (like USDC)
  5. The transaction goes through, everyone's happy

Kora nodes are essentially "paymasters" who front the SOL and get paid back in tokens.


The Problem: Phantom Token Accounts

Here's where my project comes in.

Every time a Kora node receives a token payment from a user, Solana creates a token account to hold those tokens. This happens automatically. The node accumulates USDC from one user, BONK from another, maybe some random memecoin from a third.

Eventually, these tokens get swept to a treasury or converted to SOL. The token accounts become empty.

But here's the catch: empty doesn't mean gone.

Those accounts still exist on the blockchain. And they're still holding that rent deposit hostage — 0.002 SOL each, locked up and unusable.

A busy Kora node might process thousands of transactions across hundreds of different tokens. That's potentially hundreds of empty token accounts. At 0.002 SOL each, we're talking about real money just... sitting there. Doing nothing.

I built a tool to get it back.


The Tool

Kora Rent-Reclaim is a command-line tool that:

  1. Discovers all token accounts owned by your wallet
  2. Filters to find the ones that are empty and safe to close
  3. Closes them and sends the rent back to an address you specify
  4. Reports exactly what happened for your records

The concept is simple, but the execution required handling a lot of edge cases.


The "Oh, That's How Solana Works" Moments

There's always something to learn. Building this taught me a lot about Solana's internals. Here are the interesting bits:

Rent-Exempt Minimum

Solana used to actually charge ongoing rent — your account balance would slowly decrease over time. They've since moved to a simpler model: hold a minimum balance, and your account lives forever. For token accounts, that magic number is exactly 0.00203928 SOL.

When you close an account, this entire amount returns to you. It's not a fee — it's more like a refundable deposit.

Token Accounts Have Rules

You can't just delete any account. Token accounts (technically called Associated Token Accounts or ATAs) follow specific rules:

  • Only the owner can close them
  • They must have zero token balance
  • They must be in an initialized state (not frozen)

Try to close an account with tokens still in it? The blockchain rejects the transaction. This is actually great since it means the tool can't accidentally destroy your assets.

The getProgramAccounts Challenge

To find all token accounts for a wallet, you query the Token Program with getProgramAccounts. This returns every matching account in one response.

For a wallet with 10,000 accounts? That's a heavy call. There's no pagination, no streaming. You get everything at once or nothing. This informed some of my design decisions around memory management.


Building It: The Interesting Parts

The Pipeline

The tool follows a straightforward pipeline:

Discover → Filter → Reclaim → Report
Enter fullscreen mode Exit fullscreen mode

Discover: Query the RPC endpoint to get all token accounts owned by the signer wallet.

Filter: Categorize accounts into closeable (zero balance, initialized, not whitelisted) versus non-closeable (has balance, frozen, protected).

Reclaim: Bundle close instructions into transactions, send them, track successes and failures.

Report: Display results to the user by writing to a CSV audit log.

Handling the Real World

Production infrastructure is messy. Things fail. I built the tool to handle:

RPC Rate Limiting: Endpoints return 429 errors when you hit them too hard. The tool uses exponential backoff — wait 100ms, then 200ms, then 400ms between retries.

// Retry with exponential backoff
for (let attempt = 1; attempt <= 3; attempt++) {
  try {
    return await fn();
  } catch (error) {
    if (!isRetryable(error)) throw error;
    await sleep(100 * Math.pow(2, attempt - 1));
  }
}
Enter fullscreen mode Exit fullscreen mode

User Interruption: Someone hits Ctrl+C mid-operation. The tool catches SIGINT, finishes the current transaction batch (no partial state), logs what completed, and exits cleanly.

Low Balance Warning: If your wallet doesn't have enough SOL to cover transaction fees, the tool warns you upfront rather than failing mysteriously later.

Error Messages That Actually Help

Every error follows a What / Why / Do pattern:

[ERROR] Cannot read keypair file: ./missing.json
        File not found or not readable.
        → Check the path and ensure the file exists.
Enter fullscreen mode Exit fullscreen mode

No cryptic stack traces and no generic error message like "something went wrong." Tell users what broke, why it matters, and exactly what to do about it.


Design Decisions

Dry-Run by Default

The first time you run this tool, nothing happens. You see what would happen. You have to explicitly pass --execute to actually close accounts.

Why? Because closing accounts is irreversible. Once closed, the account is gone. Better to make the dangerous action opt-in rather than opt-out.

Batching: 10 Accounts Per Transaction

Solana transactions have compute limits. If you pack too many instructions into one transaction, it fails. Through testing, I landed on 10 close instructions per transaction — safe across different RPC providers and network conditions.

Memory Efficiency for Large Wallets

For wallets with 10,000+ accounts, keeping everything in memory gets problematic. The tool writes history log entries in batches rather than accumulating them all until the end. This is a small change but it has significant impact on memory usage.

Verbose Mode for Debugging

When things go wrong, you need visibility. The --verbose flag shows RPC call timing, slot numbers, and batch progress — all going to stderr so it doesn't interfere with JSON output if you're piping results somewhere.


What I Learned

1. Solana's rent model is clever. It solves blockchain state bloat by making storage have a real cost, but you get your money back when you're done. It's a deposit, not a tax.

2. CLI user experience matters more than you'd think. Nobody reads documentation. The tool needs to guide users with first-run tips, clear defaults, and helpful error messages.

3. Defensive coding isn't paranoia — it's necessity. RPCs fail. Users interrupt operations. Account states change between when you check and when you act. Handle all of it, or your tool becomes unreliable.

4. TypeScript for CLI tools is underrated. Type safety caught countless bugs during development. The confidence to refactor without fear is worth the initial setup overhead.

5. The boring parts matter most. Retry logic, graceful shutdown, audit logging — none of this is exciting to build, but it's what separates a script from a tool you can actually rely on.


Try It Yourself

If you have a Solana wallet with old token accounts (and most active wallets do):

# Clone and build
git clone https://github.com/Zolldyk/Auto-rent-reclaim-bot.git
cd Auto-rent-reclaim-bot
npm install && npm run build

# Preview what you could reclaim (safe - no changes made)
npx kora-reclaim \
  --signer-keypair ~/your-wallet.json \
  --treasury YOUR_SOL_ADDRESS \
  --rpc-url https://api.mainnet-beta.solana.com

# Actually reclaim (when you're ready)
npx kora-reclaim \
  --signer-keypair ~/your-wallet.json \
  --treasury YOUR_SOL_ADDRESS \
  --rpc-url https://api.mainnet-beta.solana.com \
  --execute
Enter fullscreen mode Exit fullscreen mode

You might be surprised how much SOL is sitting in empty accounts and waiting to be reclaimed.


Wrapping Up

This project started as a specific solution for Kora node operators but works for anyone with unused token accounts on Solana. The core insight is simple: those empty accounts aren't free — they're holding your SOL.

The implementation taught me a lot about Solana's architecture, building reliable CLI tools, and the importance of handling edge cases gracefully.

Sometimes the best tools are the ones that solve one specific problem really well.


Top comments (0)