Solana 租金豁免:你花不掉的那部分 SOL

为什么余额看着很充足却报 InsufficientFundsForRent、每类账户锁住多少,以及租金怎么悄悄吃掉交易机器人的可用资金。

BoltTx Team··14 min read
solana租金豁免账户模型余额不足交易上链spl-token

一笔交易报 InsufficientFundsForRent,而钱包里明明有 SOL。余额看着没问题,转账金额看着也没问题,错误照样触发。

原因是:每个账户的余额里,有一部分是花不掉的。 那是一笔押金,而 Solana 在执行时会强制检查。

租金豁免到底是什么

Solana 上每个账户都占用存储,而存储对网络是有成本的。Solana 不按周期收费,而是要求账户持有一笔与其大小成正比的一次性余额。持有到这个数,账户就是租金豁免的 —— 可以无限期存在。

★这笔余额不是被消耗掉,是被锁住。★ 关闭账户时会还给你 —— 这就是为什么"清理不用的账户"是一个真能拿回 SOL 的操作。

import { NONCE_ACCOUNT_LENGTH } from "@solana/web3.js";

// 永远不要写死 —— 它取决于网络参数。
const rent = await connection.getMinimumBalanceForRentExemption(
  NONCE_ACCOUNT_LENGTH,
);

这个错误为什么让人困惑

InsufficientFundsForRent 不是说"你没有 SOL"。它说的是 ★这笔交易执行后,某个账户会被留在豁免线以下。★

三种"看起来资金很充足"却触发它的方式:

清空钱包。 你把全部余额转出去,导致账户自己掉到最低要求以下。

const balance = await connection.getBalance(wallet);

// 失败:账户会掉到豁免线以下。
SystemProgram.transfer({ lamports: balance, ... });

// 可行:留下豁免额加手续费。
const minimum = await connection.getMinimumBalanceForRentExemption(0);
const fee = 5_000;
SystemProgram.transfer({ lamports: balance - minimum - fee, ... });

创建账户时没给够。 账户被创建出来,但立刻低于阈值,运行时直接拒绝。

链式指令里顺带创建了账户。 一笔会创建关联代币账户的 swap,需要在其它所有开销之上再加上那个新账户的豁免额。★这个成本出现在一笔你并不认为在"创建东西"的交易里。★

每类账户锁住多少

近似值,因为豁免额随字节大小和网络参数变化:

账户 大小 相对成本
基础系统账户 0 字节 ★最小★
代币账户(ATA) 165 字节 ★不大,但会累积★
nonce 账户 80 字节
mint 账户 82 字节
程序数据账户 不定 ★随大小增长★

★永远查询,不要写死。★ 这些是网络参数,从某篇博客抄来的常量,是一个等着参数变更来引爆的 bug。

如果你的交易资金没问题、却仍然不上链,免费领个 BoltTx key 改一行就能测测路由那一侧。

这件事在交易机器人身上怎么咬人

单笔金额都很小。真正花钱的是那个模式。

★钱包每持有一种不同的代币,就需要一个自己的代币账户,而每个都要租金豁免。★ 一个做 meme 币的机器人会一个代币积一个账户,而那些余额会一直锁着,直到账户被关闭。

一个月里交易了 200 种不同的代币
  → 最多 200 个代币账户
  → ★200 × 豁免额,全部锁死★
  → 其中大多数只剩下已清仓代币的粉尘

这些钱一分没丢 —— 但它是躺在你已经不用的账户里的可用资金。对一个跑在固定余额上的机器人来说,这是本可以拿去开仓的钱。

怎么拿回来

空的代币账户可以关闭,豁免额会还给你:

import { createCloseAccountInstruction } from "@solana/spl-token";

// 只对零余额账户有效。先把粉尘烧掉或转走。
const ix = createCloseAccountInstruction(
  tokenAccount,
  destination,   // 回收的 lamport 打到哪
  owner,
);

★一个清理例程值得写一次。★ 扫出零余额的代币账户、把关闭指令批量打包、把锁住的 SOL 收回来。交易品种多的机器人会持续积累这些账户。

两个注意事项:

只关零余额账户。 关闭一个还有代币的账户会烧掉它们。要在同一笔交易里检查余额,别依赖一个可能已经过时的读取结果。

可以批量,但要顾及交易大小。 每条关闭指令都增加字节数。交易一旦超过大小上限就直接失败,所以保守分批,别打包到极限。

租金和交易失败的关系

和上链的关联是直接的,而且这是个大多数人靠踩坑才学会的失败模式。

★一笔要创建账户的交易,需要在执行时有足够的豁免额,不是签名时。★ 如果你的余额在"构造"和"落块"之间掉下去了 —— 因为你另一笔交易先落块了 —— 账户创建就会失败。

对一个用同一个钱包并发发送的机器人来说,这是一个真实的竞态:

交易 A:swap,创建 ATA,需要豁免额
交易 B:swap,创建 ATA,需要豁免额
  → ★两笔都签在一个只够一笔的余额上★
  → 后落块的那笔失败

解法是预留余量,而不是按你当前的精确余额去算。★把钱包里一部分资金视为不可用,额度按你的并发数来定。★

发送之前先检查

async function canAffordAccountCreation(
  connection: Connection,
  payer: PublicKey,
  accountSize: number,
  concurrentTxs: number,
): Promise<boolean> {
  const balance = await connection.getBalance(payer);
  const rent = await connection.getMinimumBalanceForRentExemption(accountSize);
  const payerMinimum = await connection.getMinimumBalanceForRentExemption(0);
  const feeBuffer = 5_000 * concurrentTxs;

  // 给每一笔在途交易都留余量,不只是当前这一笔。
  return balance >= payerMinimum + rent * concurrentTxs + feeBuffer;
}

上链应该是什么水平

我们投递节点上的真实交易:中位确认 336 毫秒 —— 不到一个 slot。

★租金相关的失败发生在执行阶段,意味着交易已经进了区块、已经消耗了基础费。★ 这让它比"从未上链"的交易更贵 —— 所以它值得预防,而不是重试。

BoltTx 在这里的位置

我们做提交。租金是你这笔交易自身的属性,在到达我们之前就已经决定了。

提交走我们自建的四区域投递节点,带 SWQoS 路由,不暴露公共 mempool —— 交易在落块之前,传输途中观察不到。你在本地签名,我们不托管资金、不代签、不修改交易内容。

tip 带在交易里、从你自己的钱包链上支付,交易失败时跟着一起 revert —— 这是 Solana 原子交易的机制决定的。只有到达链上的交易才计费。

免费领 API key,没有月费:

const connection = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");

常见问题

Solana 的租金豁免是什么? 一笔与账户大小成正比的最低余额,让账户可以无限期存在。它是被锁住而不是被花掉,关闭账户时会还给你。

我明明有 SOL,为什么报 InsufficientFundsForRent? 因为这笔交易会让某个账户掉到豁免线以下。通常是你在清空全部余额,或者创建账户时没充到最低要求。

一个代币账户锁多少 SOL? 代币账户是 165 字节,豁免额随大小变化。getMinimumBalanceForRentExemption(165),别写死 —— 它取决于网络参数。

租金豁免的 SOL 能拿回来吗? 能,关闭账户就行。代币账户用 createCloseAccountInstruction 回收锁住的 lamport,但只对零余额账户有效 —— 关一个还有代币的账户会烧掉它们。

怎么把 SOL 余额全部转走? 转不走全部。要留下零字节账户的豁免额加上手续费,否则转账会以 InsufficientFundsForRent 失败。

我的 swap 为什么报租金错误? 这笔 swap 很可能为一个你之前没持有过的 mint 创建了关联代币账户。那次创建需要在 swap 金额和手续费之上,再加一份豁免额 —— 而且是在同一笔交易里。

需要定期交租金吗? 不需要。Solana 已经用豁免模型取代了周期性收租。账户持有对应大小的最低余额,就能一直存在,不再另外扣费。

一个交易机器人会积累多少代币账户? 它持有过多少种不同的 mint,就有多少个。做 meme 币的机器人可能积累几百个,每个都锁着一份豁免额直到被关闭,而大多数最后只剩已清仓代币的粉尘。

该关掉空的代币账户吗? 如果你交易品种多,该。每关一个就回收一份锁住的豁免额。一个定期扫描零余额账户、批量关闭的清理例程,能收回相当可观的资金。

能关闭还有代币的代币账户吗? 那条指令会烧掉它们。先把剩余部分转走或烧掉,并且在同一笔交易里检查余额,别依赖一个可能已过时的读取。

一笔交易能装多少条关闭指令? 足够做有意义的批量,但每条都增加字节数,而交易超过大小上限会直接失败。保守分批,别打包到极限。

为什么并发时租金会导致失败? 因为豁免额是在执行时检查的,不是签名时。两笔并发交易各自要创建一个账户,可能都签在一个只够一笔的余额上 —— 后落块的那笔失败。

机器人钱包该留多少余量? 够覆盖"所有在途交易可能创建的账户"的豁免额,加上它们全部的手续费。按当前精确余额去算,恰恰是那个并发竞态的成因。

租金豁免额会随时间变化吗? 它的费率是网络参数,所以写死的值可能过时。永远按你需要的大小去查 getMinimumBalanceForRentExemption

每个账户的租金豁免额都一样吗? 不一样,它随字节大小变化。零字节的系统账户最少;165 字节的代币账户多一些;大的程序数据账户则多得多。

租金和手续费有什么区别? 手续费是被消耗掉、拿不回来的。租金豁免是被锁住、关闭账户时可回收的。一个是成本,另一个是押金。

延伸阅读

返回博客列表