Transaction simulation failed: Transaction results in an account (1) with insufficient funds for rent
This error confuses people because the sender usually has plenty of SOL. The problem isn't whether you can afford the transaction. It's the balance some account would be left with after the transaction runs.
What rent means on Solana today
Every account stores data on validators, and Solana charges for that storage through rent. In practice, new accounts must be rent-exempt: they must hold at least a minimum lamport balance that depends on how many bytes of data they store. An account with zero data (a plain wallet) still has a minimum, because every account has fixed overhead.
The rule the runtime enforces after each transaction is, in practice:
An account must end the transaction either with zero lamports (it's closed) or with at least the rent-exempt minimum for its size. Anything in between fails.
The number in parentheses is the index of the offending account in the transaction's account list, not an amount. Index 0 is usually the fee payer.
Don't hard-code the minimum. Ask the cluster for it:
const minForWallet = await connection.getMinimumBalanceForRentExemption(0);
const minFor165Bytes = await connection.getMinimumBalanceForRentExemption(165); // SPL token account size
Find which account it is
Map the index back to an address before guessing:
// legacy Transaction
const keys = tx.compileMessage().accountKeys;
console.log(keys[1].toBase58());
// VersionedTransaction (static keys only; lookup-table keys come after these)
console.log(vtx.message.staticAccountKeys[1].toBase58());
Then match it to one of the causes below.
Cause 1: sending a small amount to a new address
You transfer 0.0001 SOL to an address that has never held SOL. After the transfer, that account would exist with a balance below the rent-exempt minimum for a zero-data account, so the transaction fails. The recipient is the flagged account.
Fix: the first transfer to a brand-new address must be at least the minimum from getMinimumBalanceForRentExemption(0). If your app sends small payouts, check getBalance(recipient) first and handle the empty-account case explicitly.
Cause 2: draining a wallet, but not completely
You send "everything minus a bit for fees" and leave a few thousand lamports behind. The sender now has a balance above zero and below the minimum. The flagged account is the sender (often index 0, the fee payer).
Fix: either leave at least the rent-exempt minimum, or send exactly balance minus fee so the account ends at zero:
import { SystemProgram, Transaction, PublicKey, Keypair, Connection } from "@solana/web3.js";
async function buildSweep(connection: Connection, from: Keypair, to: PublicKey) {
const balance = await connection.getBalance(from.publicKey, "confirmed");
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
const tx = new Transaction({ feePayer: from.publicKey, blockhash, lastValidBlockHeight });
tx.add(SystemProgram.transfer({ fromPubkey: from.publicKey, toPubkey: to, lamports: balance }));
const fee = (await connection.getFeeForMessage(tx.compileMessage(), "confirmed")).value;
if (fee === null) throw new Error("Could not estimate fee");
// rebuild with the exact amount so the sender ends at 0 lamports
const sweep = new Transaction({ feePayer: from.publicKey, blockhash, lastValidBlockHeight });
sweep.add(SystemProgram.transfer({ fromPubkey: from.publicKey, toPubkey: to, lamports: balance - fee }));
return sweep;
}
If you add a priority fee instruction, include it before estimating so the fee is correct. The same recipient rule from Cause 1 still applies.
Cause 3: your program grew an account without topping it up
If an on-chain program increases an account's data size, the rent-exempt minimum goes up with it. If nobody transfers the difference in, the account is now under-funded and the transaction fails with this error, flagging that data account.
In Anchor, let the realloc constraint handle the lamports:
#[derive(Accounts)]
#[instruction(new_len: usize)]
pub struct Resize<'info> {
#[account(
mut,
realloc = 8 + new_len,
realloc::payer = payer,
realloc::zero = false,
)]
pub data: Account<'info, MyData>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
Anchor computes the new rent-exempt minimum, transfers the shortfall from payer (or refunds the excess to it when shrinking), and resizes the account. In a native program, do the same by hand: compute Rent::get()?.minimum_balance(new_len), transfer the difference from a payer via the System Program, then resize.
Cause 4: partially draining a program-owned account
A program moves lamports out of a PDA, for example paying out fees it collected, and leaves it below the minimum for its size. Either keep the minimum in the account, or close it properly. In Anchor:
#[account(mut, close = receiver)]
pub vault: Account<'info, Vault>,
#[account(mut)]
pub receiver: SystemAccount<'info>,
close moves all lamports to receiver and marks the account closed, so it ends at zero and passes the check.
Don't confuse it with "insufficient lamports"
A similar-looking failure has a different cause:
Transfer: insufficient lamports 1000000, need 2039280
Program 11111111111111111111111111111111 failed: custom program error: 0x1
That comes from the System Program itself: the payer doesn't have enough SOL to fund a transfer or an account creation (creating a token account, init in Anchor). The fix is to fund the payer. "Insufficient funds for rent", by contrast, means the transaction would have succeeded but left some account in an invalid in-between balance.
Checklist
- Read the index in the error and map it to an address.
- Recipient flagged: the first deposit to a new account must be at least the rent-exempt minimum.
- Sender or fee payer flagged: leave at least the minimum, or sweep to exactly zero.
- Program data account flagged: fund reallocations (
realloc::payerin Anchor) and close accounts withclose =instead of partial withdrawals. - Always get the minimum from
getMinimumBalanceForRentExemption(orRent::get()on-chain), never from a hard-coded number.
Get the weekly commit
New blockchain deep dives every week.