High-Frequency Small Transactions on Solana

What breaks when your app sends thousands of low-value transactions: fee economics, per-account contention, and the batching decision.

BoltTx Team··9 min read
solanathroughputtransaction-landingbatchingfeesbackend

Trading applications send a few valuable transactions. A different class of application sends thousands of cheap ones — in-game actions, points updates, per-user state changes, micro-settlements.

The failure modes are not the same. When a single transaction is worth very little, ★the fee stops being a rounding error and becomes the product's unit economics.★

Not Every Action Needs a Transaction

The most valuable optimisation happens before any Solana code: decide what actually belongs on chain.

On chain Off chain
Ownership transfer Position updates
Value settlement Intermediate state
Anything disputable Anything recomputable
★Final outcomes★ ★Steps toward them★

★A system putting every intermediate step on chain is paying for durability it does not need.★ Run the interaction off chain, settle the outcome on chain.

The test is simple: if you could recompute this from the final state, it does not need to be a transaction.

Batching Is the Main Lever

Once you know what must land, the question is how many transactions it takes. A transaction carries a base fee per signature, so ★a hundred separate transfers cost roughly a hundred times what one batched transaction costs★, and every one of them is an independent chance to fail.

// One transaction, many recipients.
const tx = new Transaction();
for (const { to, lamports } of updates.slice(0, 20)) {
  tx.add(SystemProgram.transfer({
    fromPubkey: treasury.publicKey, toPubkey: to, lamports,
  }));
}

Two ceilings decide how many fit:

1232 bytes. Each account reference costs 32 bytes. That is the binding constraint for most batches, and it is why AddressLookupTableProgram matters here — your recipient set is often stable and reusable across batches.

Compute units. Each instruction consumes compute, and the per-transaction budget is finite. Simulate the real batch shape rather than extrapolating from one instruction.

const sim = await connection.simulateTransaction(tx, {
  replaceRecentBlockhash: true, sigVerify: false,
});
console.log("units:", sim.value.unitsConsumed);
console.log("bytes:", tx.serialize().length, "/ 1232");

★Find your batch size empirically, then leave headroom.★ A batch sized to exactly fit will break the first time an instruction is slightly larger than usual.

If your batching is right and throughput is still short, a free BoltTx key is one line to test the submission path.

Write Contention Is the Real Throughput Limit

This is the constraint most teams meet without recognising it.

Solana executes transactions in parallel — unless they write to the same account. Transactions writing the same account are serialised, so a design funnelling every action through one shared account has a hard ceiling regardless of how many transactions you send.

★one shared counter account★
   ↓ every write serialises here
throughput ≈ one write per slot

★per-user accounts★
   ↓ independent writes
throughput scales with parallelism

★If throughput is flat no matter how much you send, look for a shared writable account before looking at your RPC.★

The fixes are structural:

Shard state. Split one hot account into many, keyed by user or bucket.

Move counters off chain. Aggregate off chain, settle periodically.

Per-user accounts. More rent, but writes proceed in parallel.

Aggregate before writing. One write representing a hundred actions beats a hundred writes.

Fee Economics at Volume

At this volume, fee policy is a business decision.

Base fee per signature. One batched transaction with one signature costs far less than many single ones. Batching is a fee strategy, not only a latency one.

Priority fees are optional here. Trading bots compete for position. ★Routine background writes usually do not need to win a race — they need to land eventually.★ A modest fee that lands within several slots is often correct where a trading bot would overpay.

Rent is a real line item. Every account you create locks rent-exempt lamports. Creating accounts per user per session is a growing liability, and closing accounts when done recovers it.

★Decide who pays, and decide it early.★ If users pay, they need SOL and a wallet interaction, which is friction. If you pay, your cost scales with usage and needs to be in your margin model from the start rather than discovered later.

Handle Failure in Bulk

At a few transactions per minute you can inspect failures individually. At thousands, you need policy.

const outcome = await resolve(sig, lastValidBlockHeight);

switch (outcome.status) {
  case "success":
    await markComplete(batchIds); break;
  case "expired":
    await requeue(batchIds); break;            // ★never ran — safe★
  case "reverted":
    // ★Landed and failed. The whole batch failed together.★
    await splitAndInvestigate(batchIds, outcome.err); break;
}

★The revert case is where batching costs you.★ One bad instruction reverts the entire batch, because the transaction is atomic. Twenty updates fail because one recipient account did not exist.

So validate before batching, not after failing. Check account existence and balances while assembling, and keep known-risky items out of large batches — the atomicity that makes batching efficient is the same property that makes one bad item expensive.

Congestion Behaves Differently Here

For a trading bot, congestion means losing a race. For a high-volume background system, congestion means a queue that grows faster than it drains — and ★a backlog that outlasts the congestion★.

Plan for it explicitly:

Prioritise within your own queue. Value-bearing settlements before cosmetic updates.

Let low-value work wait. Not everything needs to land this minute, and treating everything as urgent means paying urgent fees for all of it.

Cap queue depth. Rejecting new work with a clear signal beats accumulating a backlog that takes hours to clear.

Alert on drain rate, not depth. A deep queue that is draining is fine; a shallow queue that is not draining is the problem.

What Landing Looks Like

Real transactions through our delivery nodes: median confirmation 336ms — under one slot.

★At volume, the tail is what determines queue behaviour.★ A system where most transactions land quickly but a meaningful share take much longer accumulates a backlog during exactly the periods when your users are most active.

Where BoltTx Fits

We handle submission. Not your state design, not your batching, not your fee model.

Submissions route through our own delivery nodes in four regions with stake-weighted routing and no public mempool exposure. For high-volume systems the relevant property is predictability — a consistent distribution is what lets you size queues and set timeouts that hold under load.

You sign locally. We never hold funds, never sign, and never modify transaction contents. The tip travels inside the transaction, paid on chain from your own wallet, and reverts with the transaction if it fails, because that is how Solana handles atomic transactions. You pay only on transactions that reach the chain.

Get a free API key. No monthly fee:

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

FAQ

How many transactions per second can one application send on Solana? It is usually bounded by write contention rather than by the network. Transactions writing the same account serialise, so a shared hot account caps throughput regardless of how many you submit.

How do I batch many small Solana transactions? Put multiple instructions in one transaction, bounded by the 1232-byte size limit and the compute budget. Measure both by simulating the real batch shape, then leave headroom.

Why is my throughput flat no matter how many transactions I send? Almost certainly a shared writable account. Solana parallelises execution except where transactions write the same account, so one hot account serialises everything behind it.

Should game actions go on chain? Only those that need to be durable or disputable. If a step can be recomputed from the final state, settling the outcome on chain and running the rest off chain is cheaper and faster.

How much does a Solana transaction cost at volume? A base fee per signature plus any priority fee. At thousands of transactions the base fee dominates, which is why batching many instructions into one signature is the main lever.

Do high-volume applications need priority fees? Usually less than trading bots do. Routine background writes need to land eventually rather than win a race, so a modest fee is often correct where a trading bot would overpay.

What happens if one instruction in a batch fails? The entire transaction reverts, since Solana transactions are atomic. That is why validation belongs at assembly time and why known-risky items should be kept out of large batches.

How do I shard state to avoid write contention? Split one hot account into many keyed by user or bucket so writes proceed in parallel. The cost is more rent and more accounts to manage, against a throughput ceiling you cannot otherwise raise.

Is rent a significant cost for high-volume applications? It becomes one when you create accounts per user or per session. Rent-exempt lamports are recoverable on close, so a cleanup path matters as much as the creation path.

How large should a transaction batch be? As large as fits within 1232 bytes and your compute budget, minus headroom. Find it empirically for your instruction mix rather than assuming a number, since account references dominate size.

Should users or the application pay transaction fees? Either, but decide early. User-paid means they need SOL and a wallet interaction; application-paid means your cost scales with usage and belongs in your margin model from the start.

How do I handle a backlog during congestion? Prioritise within your own queue by value, let low-value work wait, cap queue depth, and alert on drain rate rather than depth. A deep queue that is draining is not a problem.

Do address lookup tables help high-volume applications? Often significantly, since account references at 32 bytes each are usually what limits batch size. They fit well here because recipient sets tend to be stable and reused.

How do I measure whether batching is working? Track instructions landed per transaction and fee cost per action, not transaction count. Sending fewer transactions while completing the same work is the outcome you want.

Can I send batches in parallel? Yes, with bounded concurrency and provided the batches do not write the same accounts. Batches contending on shared accounts serialise regardless of how many you send at once.

What should I monitor for a high-volume Solana system? Queue drain rate, landed-versus-attempted per batch, revert rate with the failing instruction identified, slot distance as a distribution, and fee cost per completed action.

Back to all posts