Skip to main content

Claim SOL API: Reclaim Rent from Empty Solana Token Accounts

Every pump.fun buy opens a token account for you, and every token account holds a small SOL deposit ("rent") that Solana requires to keep it alive. When you sell out of a position, the tokens are gone but the account — and its deposit, roughly 0.002 SOL — stays open unless something explicitly closes it. Trade a few hundred tokens and that adds up to real, sitting SOL.

This API scans a wallet for empty and dust token accounts, closes them, and returns the rent to the owner — minus a 2% commission. It works for both the SPL Token and Token-2022 programs.

Just want your SOL back without writing code? Use the Claim SOL app — check any wallet for free, then claim its rent together with pump.fun creator fees and trading cashback. This page is for bots and scripts.

API Endpoints:

  • GET https://pumpdev.io/api/reclaim/scan — List empty/dust accounts for a wallet (read-only) — 10 requests/min
  • GET https://pumpdev.io/api/reclaim/balance — The wallet's SOL balance, uncached (read-only) — 30 requests/min
  • POST https://pumpdev.io/api/reclaim — Build the close/burn transactions — 20 requests/min
  • POST https://pumpdev.io/api/reclaim/claims — Build creator-fee and cashback claims at the same 2% — 20 requests/min
  • POST https://pumpdev.io/api/reclaim/send — Send your signed transactions — 60 requests/min
  • POST https://pumpdev.io/api/reclaim/confirm — Confirm and record transactions your wallet sent itself — 60 requests/min
  • GET https://pumpdev.io/api/reclaim/stats — Public counter (read-only) — unmetered
  • GET https://pumpdev.io/api/reclaim/activity — Latest landed reclaims (read-only) — unmetered

Before You Start

No API key is required. Every endpoint is open — send the request without an Authorization header.

Scan, then build, then send — in that order. /api/reclaim/scan never builds a transaction. /api/reclaim builds transactions from the accounts you pass in, and re-verifies every one of them against live chain state — a stale or wrong address is simply dropped into skipped, not fatal to the request. /api/reclaim/send only broadcasts transactions that /api/reclaim itself produced (see Send Reclaim Transactions).

A built batch expires. A transaction returned by POST /api/reclaim is only recognized by /api/reclaim/send for about 3 minutes after it was built. Sign and send promptly. If you miss the window, rebuild — call /api/reclaim again for the accounts you still want closed. Retrying an expired transaction against /send fails; there is nothing to retry with the same payload.

Empty accounts cost nothing to close, dust accounts destroy the leftover tokens. empty accounts hold a zero balance and are closed outright. dust accounts hold any non-zero token balance — closing one burns that balance first. Only pass dust accounts you're fine losing the remaining tokens from, and check each one's usdValue first (see Dust Token Info).


Scan a Wallet

Read-only. Classifies every SPL Token and Token-2022 account the wallet owns into what can be closed for free, what needs a burn first, and what has to be left alone.

Endpoint: GET https://pumpdev.io/api/reclaim/scan

Results are cached in-process per wallet for 60 seconds.

Query Parameters

ParameterTypeRequiredDescription
publicKeystringYesWallet address to scan

Response

{
"publicKey": "YourWalletPublicKey",
"feeBps": 200,
"empty": [
{ "account": "Acc1...", "mint": "Mint1...", "program": "token", "lamports": 2039280 }
],
"dust": [
{
"account": "Acc2...", "mint": "Mint2...", "program": "token-2022", "lamports": 2039280,
"amount": "17", "decimals": 6, "uiAmount": "0.000017",
"symbol": "PEPE", "name": "Pepe", "logo": "https://...", "usdPrice": 0.0004, "usdValue": 0.0000068
}
],
"skipped": [
{ "account": "Acc3...", "reason": "frozen" },
{ "account": "Acc4...", "reason": "wsol" }
],
"totals": {
"emptyCount": 1,
"dustCount": 1,
"lamports": 2039280,
"feeLamports": 40785,
"netLamports": 1998495,
"sol": 0.00203928
}
}
FieldDescription
feeBpsCommission in basis points (200 = 2%)
emptyZero-balance token accounts — closable outright
dustEvery non-zero, non-native token account — closable after a burn. Despite the name, this includes balances worth real money: check usdValue before burning
skippedAccounts left alone, with a reason (see below)
totalsCovers empty accounts only — dust is opt-in and not pre-priced, since burning it is a value judgment only you can make

Dust Token Info

Each dust entry carries the token's name and price, so you can tell leftover crumbs from a live position before burning anything. Prices come from Jupiter and are cached for about 5 minutes.

FieldTypeDescription
amountstringBalance in raw base units
decimalsnumberThe mint's decimals
uiAmountstringamount scaled by decimals, exact (e.g. "1.5")
symbol / name / logostring | nullToken metadata, null if unknown
usdPricenumber | nullUSD price of one token, null if there's no price
usdValuenumber | nulluiAmount × usdPrice — what burning this balance destroys, null if there's no price

The token info fields are null when there's no price for a token, or when the price source is briefly unavailable — the scan itself still succeeds. Treat null as "unknown", not "worthless". The Claim SOL app flags anything worth $1 or more and asks for explicit confirmation before burning it.

totals.lamports is the gross rent recoverable from empty accounts; totals.feeLamports is 2% of that; totals.netLamports is what you'd net after the 2% commission, before the ordinary network fee you pay yourself (see Fee Model).

Skipped Reasons (scan)

ReasonMeaning
frozenThe token account is frozen by its mint's freeze authority — cannot be burned or closed
wsolA wrapped-SOL account holding a real balance — burning it would destroy real SOL, so it's left alone. An empty wSOL account is ordinary and appears in empty instead

Check the Wallet Balance

Read-only and never cached. The owner pays the network fee, and Solana takes it before the transaction runs — the SOL a reclaim unlocks can't pay for the reclaim itself. A wallet that can't cover the fee fails simulation, and wallets such as Phantom show that as "This dApp could be malicious". Check the balance first and ask for a top-up instead.

Endpoint: GET https://pumpdev.io/api/reclaim/balance?publicKey=<wallet>

{ "publicKey": "3qc8Hun2xNJxQXDB26C8JBtHaY1sKKrCuK9SxERAHGEk", "lamports": 12345678 }

Each reclaim batch costs about 15,000 lamports (0.000015 SOL: the base fee plus the reclaim priority fee). 400 for a missing/invalid publicKey, 502 if every RPC failed — treat that as "unknown", not as zero.

Build Reclaim Transactions

Takes the accounts you want closed (typically the empty and/or dust arrays from a scan) and returns ready-to-sign transactions. Every account is re-read from chain state before anything is built — the scan result is a hint, not something the server trusts blindly.

Endpoint: POST https://pumpdev.io/api/reclaim

Request Body

ParameterTypeRequiredDescription
publicKeystringYesWallet that owns the accounts
accountsstring[]YesToken account addresses to close/burn (max 500, deduplicated server-side before that cap is checked)

Response

{
"transactions": ["base64-versioned-tx-1", "base64-versioned-tx-2"],
"batches": [
{
"accounts": ["Acc1...", "Acc2..."],
"lamports": 4078560,
"feeLamports": 81571
}
],
"skipped": [{ "account": "Acc3...", "reason": "unclosable" }],
"failed": [{ "accounts": ["Acc4..."], "error": "..." }]
}

Accounts are packed into multiple transactions ("batches") automatically — up to 10 close-equivalent operations per transaction (a burn+close counts as 2, a close alone as 1) — kept well under Solana's 1232-byte limit so wallets such as Phantom have room to add their own safety checks without flagging the transaction, so a large accounts list comes back as several transactions in transactions/batches, one per entry, in the same order.

FieldDescription
transactionsBase64-encoded unsigned VersionedTransactions — sign each as the owner and send
batchesOne entry per transaction: which accounts it closes, gross lamports reclaimed, and feeLamports (2% commission)
skippedAccounts dropped before or during building, with a reason
failedBatches that could not be built at all, with the accounts and the simulation error

Who Pays the Network Fee

You do. The returned transactions name your wallet as the fee payer, and Solana deducts that fee before a transaction runs — so the rent these instructions unlock can never pay for the transaction unlocking it. Budget roughly 0.000015 SOL per transaction of your own; a wallet holding nothing at all cannot start, however much rent it has locked up.

The 2% commission is different: it is an ordinary transfer placed after the closes, so it comes out of the rent you just reclaimed rather than out of what you were holding.

Error Codes

HTTPerrorMeaning
400Invalid or missing publicKeypublicKey isn't a valid Solana address
400accounts must be a non-empty arrayaccounts missing or empty
400accounts must hold at most 500 entriesToo many distinct addresses (after dedup)
400accounts contains an invalid addressOne of the addresses isn't a valid Solana pubkey
400too_many_batchesThe account list packs into more than 32 transactions in one request — split it across multiple calls
502Failed to build reclaim transactionsUpstream RPC failure — retry

Skipped Reasons (build)

ReasonMeaning
foreignThe address isn't a token account owned by publicKey right now — dropped, not built
missingThe address doesn't exist on-chain (already closed, or never existed)
frozen / wsolSame meaning as in scan — re-checked here against fresh chain state
unclosableThe account failed simulation on its own (e.g. a Token-2022 account with a pending transfer fee) and was dropped from its batch after one retry. If it still fails, the remaining accounts show up in failed instead — see below

A batch can also land in failed instead of producing a transaction at all: this happens when a simulation error can't be attributed to a single account, or when the batch still fails after the blamed account was dropped.


Build Fee and Cashback Claims

The same claims as /api/claim-account and /api/claim-cashback, but priced like a reclaim — 2%, no minimum — and registered so /api/reclaim/send broadcasts them. Sign them together with the reclaim batches and a wallet shows one approval for everything. Only SOL amounts are included; a claim that would net less than two network fees is not built.

Endpoint: POST https://pumpdev.io/api/reclaim/claims — body { "publicKey": "<wallet>" }

{
"transactions": ["<base64 v0 transaction>"],
"claims": [{ "kind": "fees", "lamports": 50000000, "feeLamports": 1000000 }],
"failed": []
}

claims[i] describes transactions[i] (kind is "fees" or "cashback"). Nothing to claim is an empty list, not an error. Send the signed transactions to /api/reclaim/send like reclaim batches, within about three minutes of building them.

Send Reclaim Transactions

Broadcasts transactions you signed after calling /api/reclaim, and confirms each one.

Endpoint: POST https://pumpdev.io/api/reclaim/send

Request Body

ParameterTypeRequiredDescription
transactionsstring[]YesBase64-encoded signed VersionedTransactions (max 4 per call), as returned by POST /api/reclaim and then signed

Response

An array, one entry per transaction, in the same order:

[
{ "signature": "5abc..." },
{ "error": "expired", "detail": "..." },
{ "error": "unknown_transaction" },
{ "error": "unconfirmed", "signature": "5def..." }
]

A successful entry has only signature. A failed entry has error and usually detail. An unconfirmed entry has both error and signature — see below.

Error Codes (per-entry, HTTP status stays 200)

errorMeaning
unknown_transactionThis transaction wasn't produced by POST /api/reclaim, or its 3-minute registration window has expired. A signed transaction that round-tripped correctly is still refused if it's simply not one we built — rebuild it instead of resending
expiredThe transaction landed too late — its blockhash is no longer valid. Rebuild rather than retry: the old transaction can never land
failedMalformed transaction, or it landed on-chain with an instruction error (see detail)
unconfirmedThe transaction was broadcast successfully, but confirmation didn't come back before our confirm timeout. It may still land. The entry carries signature — look it up (e.g. on Solscan) before deciding whether to rebuild. Nothing is recorded to the ledger for this entry until you (or we) know it actually confirmed

Error Codes (request-level)

HTTPerrorMeaning
400transactions must be a non-empty arraytransactions missing or empty
400transactions must hold at most 4 entriesToo many in one call — split across requests

A transaction that lands successfully is recorded once, from the transaction's own signed contents (never anything the client claims about it), and is then removed from the server's short-lived registry — resending the exact same signed bytes afterward reports unknown_transaction, not a duplicate success. An unconfirmed entry is not removed from the registry — its outcome is still unknown, so the same signed transaction can be resent within the 3-minute registration window.


Confirm Wallet-Sent Transactions

Browser wallets such as Phantom warn ("This dApp could be malicious") when a site signs transactions and broadcasts them itself, and trust signAndSendTransaction / signAndSendAllTransactions, where the wallet sends. If your wallet sends the transactions from /api/reclaim and /api/reclaim/claims, report the signatures here instead of using /api/reclaim/send: each is awaited, read back from chain and recorded in the stats and activity feed.

Endpoint: POST https://pumpdev.io/api/reclaim/confirm — body { "publicKey": "<wallet>", "signatures": ["<sig>", …] } (1–4 per call)

Returns one entry per signature, in order: { "signature" } (landed and recorded), { "error": "unconfirmed", "signature" }, { "error": "failed", "detail" }, or { "error": "unknown_transaction" } — a transaction counts only if publicKey paid for it and it paid the PumpDev fee wallet.

Reclaim Stats

Public counter for the landing page and dashboards — total SOL reclaimed, accounts closed, and distinct wallets cleaned across all users. Cached 30 seconds.

Endpoint: GET https://pumpdev.io/api/reclaim/stats

{
"solReclaimed": 128.44,
"accountsClosed": 61207,
"wallets": 4310
}

Recent Activity

Recent activity across all users, newest first — the same feed as the "Recent activity" table on the Claim SOL app. Two kinds of row, told apart by status:

  • landed — a reclaim that confirmed on-chain, with its signature and exact amounts.
  • prepared — a creator-fee or cashback claim that our claim API built. The wallet signs and sends it itself, so there is no signature, the amount is an estimate, and whether it was ever sent isn't known.

Cached 30 seconds, refreshed as soon as a new reclaim lands.

Endpoint: GET https://pumpdev.io/api/reclaim/activity

{
"items": [
{
"type": "rent",
"wallet": "WalletPublicKey",
"accounts": 214,
"lamports": 436405920,
"feeLamports": 8728118,
"netLamports": 427677802,
"signature": "5VERv8NM...",
"createdAt": "2026-09-23T14:02:11.000Z",
"status": "landed"
},
{
"type": "creator-fees",
"status": "prepared",
"wallet": "WalletPublicKey",
"accounts": null,
"estimatedSol": 1.2841,
"signature": null,
"createdAt": "2026-09-23T14:01:40.000Z"
}
]
}
FieldDescription
type"rent", "creator-fees" or "cashback"; new kinds may be added, so ignore ones you don't recognize
status"landed" (confirmed on-chain) or "prepared" (built by the API, not tracked after that)
walletWallet the rent went back to
accountsToken accounts closed in that transaction
lamports / feeLamports / netLamportsLanded rows: gross rent, the 2% commission, and what the wallet received
estimatedSolPrepared rows: the claimable amount when the claim was built, in SOL — null when unknown or not in SOL
signatureLanded rows: the transaction signature. null on prepared rows
createdAtWhen it landed (ISO 8601, UTC)

Fee Model

The commission is 2% of the gross rent reclaimed (RECLAIM_FEE_BPS, 200 bps by default), taken as an on-chain SOL transfer to the pumpdev fee wallet inside the same transaction that closes your accounts — there's no separate charge or invoice.

The transaction carries one trailing transfer: the 2% commission to the fee wallet. It sits after the closes, so it is paid out of the rent just reclaimed. You net lamports - feeLamports from batches[i], minus the ordinary network fee you pay yourself.


Worked Example: A Bot With 200 Wallets

A common shape: a bot holds 200 trading wallets, each of which has bought and sold dozens of tokens over time and is sitting on empty accounts. Runnable version: examples/reclaim.js.

import { VersionedTransaction, Keypair } from '@solana/web3.js';
import bs58 from 'bs58';

const API_URL = 'https://pumpdev.io';

async function reclaimWallet(secretKeyBase58) {
const keypair = Keypair.fromSecretKey(bs58.decode(secretKeyBase58));
const publicKey = keypair.publicKey.toBase58();

// 1. Scan — what's actually reclaimable?
const scanRes = await fetch(`${API_URL}/api/reclaim/scan?publicKey=${publicKey}`);
const scan = await scanRes.json();

if (scan.empty.length === 0) {
console.log(`${publicKey}: nothing to reclaim`);
return;
}
console.log(`${publicKey}: ${scan.empty.length} empty accounts, ~${scan.totals.sol} SOL`);

// 2. Build — self-gas, so the wallet pays its own network fee
const buildRes = await fetch(`${API_URL}/api/reclaim`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publicKey,
accounts: scan.empty.map((a) => a.account),
}),
});
const built = await buildRes.json();
if (built.transactions.length === 0) {
console.log(`${publicKey}: nothing built`, built.skipped, built.failed);
return;
}

// 3. Sign every returned transaction locally
const signed = built.transactions.map((base64) => {
const tx = VersionedTransaction.deserialize(Buffer.from(base64, 'base64'));
tx.sign([keypair]);
return Buffer.from(tx.serialize()).toString('base64');
});

// 4. Send — promptly, the batches are only valid for ~3 minutes. One call
// holds at most 4 transactions, so more than 4 batches need several calls.
const MAX_TX_PER_SEND = 4;
const results = [];
for (let i = 0; i < signed.length; i += MAX_TX_PER_SEND) {
const chunk = signed.slice(i, i + MAX_TX_PER_SEND);
const sendRes = await fetch(`${API_URL}/api/reclaim/send`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ transactions: chunk }),
});
results.push(...(await sendRes.json()));
}

for (const r of results) {
if (r.signature && !r.error) console.log(` landed: ${r.signature}`);
else if (r.error === 'unconfirmed') console.log(` unconfirmed — may still land: ${r.signature}`);
else console.log(` ${r.error}: ${r.detail ?? ''}`);
}
}

// for (const secretKey of your200WalletSecretKeys) {
// await reclaimWallet(secretKey);
// }

A Note on SIMD-0437

Solana's SIMD-0437 lowered the per-byte rent rate, which shrinks how much SOL a newly created token account has to hold. Accounts created before each step of that reduction rolled out keep their original, larger deposit — closing one of those still returns the full ~0.00204 SOL. Token accounts created after each step hold roughly a tenth of that, around 0.0002 SOL.

Practically: the backlog of old, fully-funded empty accounts is a one-time windfall, not a renewable resource. As wallets accumulate accounts under the new, lower rent, the average reclaim per account will keep shrinking. There's no cliff — old accounts already stuck out there don't change retroactively — but the total reclaimable stock across the ecosystem is finite and trending down, not something that resets every time someone trades.


Frequently Asked Questions

How much SOL can I reclaim per token account?

About 0.00204 SOL for a token account created before SIMD-0437 lowered the rent rate, and roughly 0.0002 SOL for one created after it. You receive that amount minus the 2% commission and the ~0.000015 SOL network fee per transaction.

Do I need to share my private key?

No. The API only needs your public key. POST /api/reclaim returns unsigned transactions; you sign them locally and pass the signed bytes to /api/reclaim/send.

Will closing accounts destroy my tokens?

Closing an empty account destroys nothing, because its balance is already zero. Closing a dust account burns whatever balance it still holds, so check each entry's usdValue before passing it in. Frozen accounts and wSOL accounts that hold a balance are always skipped.

Why do I need SOL in the wallet to reclaim SOL?

Solana charges the network fee before a transaction runs, so the rent a transaction unlocks can't pay for that same transaction. Keep about 0.000015 SOL per batch in the wallet, and check it with /api/reclaim/balance first.

Does it work for pump.fun tokens and Token-2022?

Yes. The scan covers every SPL Token and Token-2022 account the wallet owns, including the one every pump.fun buy opens.


Next Steps